BASIS  r3148
basisproject.py
Go to the documentation of this file.
00001 #! /sbia/sbiasfw/external/python/epd/7.3.1/bin/python
00002 import sys; import os.path; __dir__ = os.path.dirname(os.path.realpath(__file__)); sys.path.insert(0, os.path.realpath(os.path.join(__dir__, '../lib/python'))); sys.path.insert(0, os.path.realpath(os.path.join(__dir__, '../lib'))) # <-- added by BASIS
00003 ##############################################################################
00004 # @file  basisproject.py
00005 # @brief Project tool used to create and/or modify a BASIS project.
00006 #
00007 # Copyright (c) 2011, 2012 University of Pennsylvania. All rights reserved.<br />
00008 # See https://www.cbica.upenn.edu/sbia/software/license.html or COPYING file.
00009 #
00010 # Contact: SBIA Group <sbia-software at uphs.upenn.edu>
00011 #
00012 # @ingroup Tools
00013 ##############################################################################
00014 
00015 # ============================================================================
00016 # modules
00017 # ============================================================================
00018 
00019 import os                  # file manipulation
00020 import sys                 # system functions
00021 import re                  # regular expressions
00022 import datetime            # now(), to get current year for copyright
00023 import shutil              # copyfile()
00024 
00025 from basis import basis    # basic utilities
00026 from basis import argparse # command-line parsing
00027 from basis import diff3    # three-way diff algorithm
00028 
00029 # ============================================================================
00030 # constants
00031 # ============================================================================
00032 
00033 # executable information
00034 _EXEC_DIR  = basis.exedir()
00035 _EXEC_NAME = basis.exename()
00036 
00037 # constants used as values for the template options
00038 DEL = -1 # remove feature/file if present
00039 ADD =  1 # add feature/file if missing
00040 
00041 # root directory of project template
00042 _template = os.path.abspath(os.path.join(_EXEC_DIR, '../share/template'))
00043 
00044 # ============================================================================
00045 # auxiliary functions
00046 # ============================================================================
00047 
00048 # ----------------------------------------------------------------------------
00049 def get_template_options(op=None):
00050     return {'config-settings'     : op, # add/remove general project settings file
00051             'config-components'   : op, # add/remove components configuration file
00052             'config-package'      : op, # add/remove package configuration file
00053             'config-find'         : op, # add/remove find package configuration file
00054             'config-find_version' : op, # add/remove find package configuration version file
00055             'config-script'       : op, # add/remove script configuration file
00056             'config-test'         : op, # add/remove testing configuration file
00057             'config-use'          : op, # add/remove package configuration use file
00058             'data'                : op, # add/remove auxiliary data files
00059             'doc'                 : op, # add/remove documentation
00060             'doc-rst'             : op, # add/remove template reST files
00061             'example'             : op, # add/remove example
00062             'include'             : op, # add/remove public includes tree
00063             'src'                 : op, # add/remove directory for source files
00064             'modules'             : op, # add/remove support for modularization
00065             'test'                : op, # add/remove testing configuration file
00066             'test-internal'       : op} # add/remove internal testing configuration file
00067  
00068 # ----------------------------------------------------------------------------
00069 def get_standard_template_options(opts, overwrite=False):
00070     """Select standard project template."""
00071     if overwrite or opts.get('config-settings')     == None: opts['config-settings']     = DEL
00072     if overwrite or opts.get('config-components')   == None: opts['config-components']   = DEL
00073     if overwrite or opts.get('config-package')      == None: opts['config-package']      = DEL
00074     if overwrite or opts.get('config-find')         == None: opts['config-find']         = DEL
00075     if overwrite or opts.get('config-find_version') == None: opts['config-find_version'] = DEL
00076     if overwrite or opts.get('config-script')       == None: opts['config-script']       = DEL
00077     if overwrite or opts.get('config-test')         == None: opts['config-test']         = DEL
00078     if overwrite or opts.get('config-use')          == None: opts['config-use']          = DEL
00079     if overwrite or opts.get('data')                == None: opts['data']                = DEL
00080     if overwrite or opts.get('doc')                 == None: opts['doc']                 = ADD
00081     if overwrite or opts.get('doc-rst')             == None: opts['doc-rst']             = ADD
00082     if overwrite or opts.get('example')             == None: opts['example']             = ADD
00083     if overwrite or opts.get('include')             == None: opts['include']             = DEL
00084     if overwrite or opts.get('test')                == None: opts['test']                = ADD
00085     if overwrite or opts.get('test-internal')       == None: opts['test-internal']       = DEL
00086     if (overwrite
00087             or (opts.get('src') == None and opts.get('modules') == None)
00088             or (opts.get('src') == None and opts.get('modules') == DEL)
00089             or (opts.get('src') == DEL  and opts.get('modules') == None)):
00090         if overwrite or opts.get('src') != DEL:
00091             opts['src']     = ADD
00092             opts['modules'] = DEL
00093         else:
00094             opts['src']     = DEL
00095             opts['modules'] = ADD
00096     return opts
00097 
00098 # ----------------------------------------------------------------------------
00099 def _update_dflt_opt(root, opts, opt, paths):
00100     if not type(paths) is list:
00101         paths = [paths]
00102     for path in paths:
00103         if opt not in opts:
00104             opts[opt] = None
00105         if opts[opt] == None and os.path.exists(os.path.join(root, path)):
00106             opts[opt] = ADD
00107     return opts
00108 
00109 # ----------------------------------------------------------------------------
00110 def get_default_template_options(root, opts, create):
00111     """Select default options."""
00112     if create:
00113         opts = get_standard_template_options(opts, overwrite=False)
00114     else:
00115         if os.path.isdir(os.path.join(root, 'config')):
00116             opts = _update_dflt_opt(root, opts, 'config-components',   'config/Components.cmake')
00117             opts = _update_dflt_opt(root, opts, 'config-find',         'config/Config.cmake.in')
00118             opts = _update_dflt_opt(root, opts, 'config-find_version', 'config/ConfigVersion.cmake.in')
00119             opts = _update_dflt_opt(root, opts, 'config-test',         'config/CTestCustom.cmake.in')
00120             opts = _update_dflt_opt(root, opts, 'config-package',      'config/Package.cmake')
00121             opts = _update_dflt_opt(root, opts, 'config-script',       'config/ScriptConfig.cmake.in')
00122             opts = _update_dflt_opt(root, opts, 'config-settings',     'config/Settings.cmake')
00123             opts = _update_dflt_opt(root, opts, 'config-use',          'config/ConfigUse.cmake.in')
00124         opts = _update_dflt_opt(root, opts, 'data',          'data')
00125         opts = _update_dflt_opt(root, opts, 'doc',           'doc')
00126         opts = _update_dflt_opt(root, opts, 'doc-rst',       'doc/index.rst')
00127         opts = _update_dflt_opt(root, opts, 'example',       'example')
00128         opts = _update_dflt_opt(root, opts, 'include',       'include')
00129         opts = _update_dflt_opt(root, opts, 'src',           'src')
00130         opts = _update_dflt_opt(root, opts, 'modules',       'modules')
00131         opts = _update_dflt_opt(root, opts, 'test',          'test')
00132         opts = _update_dflt_opt(root, opts, 'test-internal', 'test/internal')
00133     return opts
00134 
00135 # ----------------------------------------------------------------------------
00136 def get_project_name(cmake_file):
00137     """Extract project name from BasisProject.cmake file of existing project."""
00138     fp = open(cmake_file, 'rt')
00139     txt = fp.read()
00140     fp.close()
00141     m = re.search(r'\s*(basis_project|basis_slicer_module)\s*\((\s*#[^\n]*\n)*\s*(NAME|SUBPROJECT)\s+"?(?P<name>\w+)"?', txt)
00142     if m: return m.group('name')
00143     return None
00144 
00145 # ----------------------------------------------------------------------------
00146 def get_template_version(cmake_file):
00147     """Extract template version from root CMakeLists.cmake file of existing project."""
00148     fp = open(cmake_file, 'rt')
00149     txt = fp.read()
00150     fp.close()
00151     m = re.search(r'\s*set\s*\(\s*TEMPLATE_VERSION\s+"?(?P<version>[0-9.]+)"?', txt)
00152     if m: return m.group('version')
00153     return None
00154 
00155 # ============================================================================
00156 # add / remove project file
00157 # ============================================================================
00158 
00159 # ----------------------------------------------------------------------------
00160 def get_template(template, path):
00161     m = re.match(r'(.*)-(\d+)\.(\d+)$', template)
00162     if not m is None:
00163         prefix   = m.group(1)
00164         major    = int(m.group(2))
00165         minor    = int(m.group(3))
00166         root     = os.path.dirname (prefix)
00167         base     = os.path.basename(prefix)
00168         # determine available template versions and stores minor version
00169         # numbers of each major version required to initialize minor number
00170         # when proceeding with the previous major version
00171         minors = {}
00172         for f in os.listdir(root):
00173             if os.path.isdir(os.path.join(root, f)):
00174                 m = re.match('%s-(\d+)\.(\d+)$' % base, f)
00175                 if not m is None:
00176                     k = int(m.group(1))
00177                     v = int(m.group(2))
00178                     if not k in minors: minors[k] = []
00179                     minors[k].append(v)
00180         # decrease template version until template file/directory exists
00181         while major > 0:
00182             while minor >= 0:
00183                 template_path = os.path.join('%s-%d.%d' % (prefix, major, minor), path)
00184                 if os.path.exists(template_path):
00185                     return template_path
00186                 minor -= 1
00187             # set minor version number to maximum minor number for next major version
00188             while major > 0 and minor < 0:
00189                 major -= 1
00190                 if major in minors:
00191                     for next_minor in minors[major]:
00192                         if next_minor > minor: minor = next_minor
00193             if minor < 0: break
00194     # otherwise, just return input
00195     return os.path.join(template, path)
00196 
00197 # ----------------------------------------------------------------------------
00198 def add_dependencies(txt, deps, required=True, test=False):
00199     """Add dependencies to basis_project() command in BasisProject.cmake file."""
00200     if test:
00201         if required:
00202             placeholder = '#<test-dependency>'
00203         else:
00204             placeholder = '#<optional-test-dependency>'
00205     else:
00206         if required:
00207             placeholder = '#<dependency>'
00208         else:
00209             placeholder = '#<optional-dependency>'
00210     for dep in deps:
00211         txt = txt.replace(placeholder, dep + '\n    ' + placeholder, 1)
00212     return txt
00213 
00214 # ----------------------------------------------------------------------------
00215 def alter(filename, args, backup=True, update=False):
00216     """Alter project file, i.e., substitute placeholders.
00217 
00218     @param [in] filename Path of text file to alter.
00219     @param [in] args     Command-line arguments.
00220     @param [in] backup   Whether to backup file before overwriting it.
00221     @param [in] update   Whether template file is generated for the update
00222                          of an existing project file. In this case, certain
00223                          modifications should not be applied to avoid conflicts.
00224 
00225     @retval -1 on error
00226     @retval  0 if file was not modified
00227     @retval  1 if file has been modified
00228 
00229     """
00230     # read file content
00231     fp = open(filename, 'rt')
00232     txt = fp.read()
00233     fp.close()
00234     out = txt
00235     # <template-version>
00236     if not update:
00237         out = out.replace('<template-version>', '1.7')
00238     # <project> or <project_l>
00239     if hasattr(args, 'name') and args.name:
00240         out = out.replace('#<project>',   args.name)
00241         out = out.replace('<project>',    args.name)
00242         out = out.replace('#<project_l>', args.name.lower())
00243         out = out.replace('<project_l>',  args.name.lower())
00244     # <description>
00245     if hasattr(args, 'description') and args.description:
00246         out = out.replace('#<description>', '"' + args.description + '"')
00247         out = out.replace('<description>',  args.description)
00248     # <author>
00249     if hasattr(args, 'author') and args.author:
00250         start = out.find('<author>')
00251         if start != -1:
00252             linestart = out.rfind('\n', 0, start)
00253             if linestart == -1: linestart  = 0
00254             else:               linestart += 1
00255             authors = [a.strip() for a in args.author.split(',')]
00256             prefix  = out[linestart:start].replace("AUTHORS", "       ").replace("AUTHOR", "      ")
00257             if len(prefix) > 0 and prefix[-1] == '#':
00258                 for author in authors:
00259                     out = out.replace('#<author>', ''.join(['"', author, '"\n', prefix, '<author>']), 1)
00260             else:
00261                 authors = ('\n' + prefix).join(authors)
00262                 out = out.replace('<author>', authors)
00263     # <year>
00264     year = None
00265     if hasattr(args, 'year') and args.year:
00266         year = args.year
00267     elif not update:
00268         year = datetime.datetime.now().year
00269     if year:
00270         out = out.replace('<year>', str(year))
00271     # dependencies
00272     if filename.endswith('BasisProject.cmake'):
00273         if hasattr(args, 'use') and args.use:
00274             out = add_dependencies(out, args.use, required=True, test=False)
00275         if hasattr(args, 'useopt') and args.useopt:
00276             out = add_dependencies(out, args.useopt, required=False, test=False)
00277         if hasattr(args, 'usetest') and args.usetest:
00278             out = add_dependencies(out, args.usetest, required=True, test=True)
00279         if hasattr(args, 'useopttest') and args.useopttest:
00280             out = add_dependencies(out, args.useopttest, required=False, test=True)
00281     # return if file content is unchanged
00282     if out == txt: return 0
00283     # otherwise, backup file first
00284     if backup:
00285         try:
00286             shutil.copy(filename, filename + '~')
00287         except Exception, e:
00288             sys.stderr.write("E %s - failed to backup file before altering it" % filename)
00289             if args.verbose > 0: sys.stderr.write(': ' + str(e))
00290             sys.stderr.write('\n')
00291             return -1
00292     # then overwrite it
00293     fp = open(filename, 'wt')
00294     fp.write(out)
00295     fp.close()
00296     return 1
00297 
00298 # ----------------------------------------------------------------------------
00299 def update_template_version(args):
00300     """Update template version in root CMakeLists.txt file of project."""
00301     ok = True
00302     filename = os.path.join(args.root, 'CMakeLists.txt')
00303     fp = open(filename, 'rt')
00304     if fp:
00305         txt = fp.read()
00306         fp.close()
00307         if re.search('set\s+\(TEMPLATE_VERSION 1.7\)', txt):
00308             return 0
00309         else:
00310             out = re.sub('set\s+\(TEMPLATE_VERSION "?[0-9]+(\.[0-9]+)?"?\)',
00311                          'set (TEMPLATE_VERSION 1.7)', txt)
00312     else:
00313         ok = False
00314     if ok and out != txt:
00315         fp = open(filename, 'wt')
00316         if fp:
00317             fp.write(out)
00318             fp.close()
00319         else:
00320             ok = False
00321     else:
00322         ok = False
00323     if ok:
00324         sys.stderr.write("G %s\n" % filename)
00325         return 1
00326     else:
00327         sys.stderr.write("E %s - failed to update template version\n" % filename)
00328         return -1
00329 
00330 # ----------------------------------------------------------------------------
00331 def add(path, args, isdir=False):
00332     """Add or modify project directory or file.
00333 
00334     @param [in] path  The path of the directory or file relative to the
00335                       template or project root, respectively.
00336     @param [in] args  Namespace with command-line arguments.
00337     @param [in] isdir Whether @p path is a directory that may not have
00338                       a corresponding template.
00339 
00340     @retval -1 on error
00341     @retval  0 if existing file is up to date
00342     @retval  1 if project file has been added/updated
00343     @retval  2 if project file has been updated, but with conflicts
00344 
00345     """
00346     retval = 0
00347 
00348     template_path          = get_template(args.template, path)
00349     original_template_path = get_template(args.original_template, path)
00350     project_path           = os.path.join(args.root, path)
00351 
00352     # check existence of template
00353     if not isdir and not os.path.exists(template_path):
00354         sys.stderr.write("E %s - template missing\n" % project_path)
00355         return -1
00356 
00357     # handle case that path in project exists already
00358     if os.path.exists(project_path):
00359         if isdir or os.path.isdir(template_path):
00360             if not os.path.isdir(project_path):
00361                 # template is directory, but there is a file in the project
00362                 sys.stderr.write("E %s - not a directory\n" % project_path)
00363                 return -1
00364             else:
00365                 # directory already exists, nothing to do
00366                 return 0
00367         elif os.path.isfile(template_path):
00368             if not os.path.isfile(project_path):
00369                 # template is file, but there is a directory in the project
00370                 sys.stderr.write("E %s - not a file\n" % project_path)
00371                 return -1
00372             # if update of existing files is disabled, only alter file
00373             # to add further dependencies or specify an author...
00374             if not args.update:
00375                 rv = alter(project_path, args, backup=args.backup)
00376                 if rv == 1: print "M %s" % project_path
00377                 return rv
00378 
00379     # create (intermediate) directory
00380     if isdir or os.path.isdir(template_path):
00381         path_dir = path
00382     else:
00383         path_dir = os.path.dirname(path)
00384     project_dir = os.path.join(args.root, path_dir)
00385     if not os.path.isdir(project_dir):
00386         try:
00387             os.makedirs(project_dir)
00388             print "A %s" % project_dir
00389         except Exception, e:
00390             sys.stderr.write("E %s - failed to make directory" % project_dir)
00391             if args.verbose > 0: sys.stderr.write(': ' + str(e))
00392             sys.stderr.write('\n')
00393             return -1
00394 
00395     # add/update file
00396     if os.path.isfile(template_path):
00397         # project file does not exist yet
00398         if not os.path.isfile(project_path):
00399             # copy template
00400             try:
00401                 shutil.copyfile(template_path, project_path)
00402             except Exception, e:
00403                 sys.stderr.write("E %s - failed to add file" % project_path)
00404                 if args.verbose > 0: sys.stderr.write(': ' + str(e))
00405                 sys.stderr.write('\n')
00406                 return -1
00407             # alter project file, e.g., substitute for project name
00408             try:
00409                 if alter(project_path, args, backup=False) == -1:
00410                     retval = -1
00411             except Exception, e:
00412                 retval = -1
00413             if retval != -1:
00414                 print "A %s" % project_path
00415                 retval = 1
00416             else:
00417                 sys.stderr.write("E %s - failed to alter file, may require manual edits" % project_path)
00418                 if args.verbose > 0: sys.stderr.write(': ' + str(e))
00419                 sys.stderr.write('\n')
00420         # project file exists already
00421         else:
00422             # update project file if copy of previous template exists
00423             if os.path.isfile(original_template_path):
00424                 # alter new template
00425                 try:
00426                     shutil.copyfile(template_path, project_path + '.template')
00427                     if alter(project_path + '.template', args, backup=False, update=True) == -1:
00428                         raise Exception('failed to alter template file')
00429                 except Exception, e:
00430                     sys.stderr.write("E %s - failed to create temporary template" % project_path)
00431                     if args.verbose > 0: sys.stderr.write(': ' + str(e))
00432                     sys.stderr.write('\n')
00433                     return -1
00434                 # merge new template with project file using three-way diff
00435                 fp = open(project_path, 'rt')
00436                 current = fp.readlines()
00437                 fp.close()
00438                 fp = open(project_path + '.template', 'rt')
00439                 template = fp.readlines()
00440                 fp.close()
00441                 fp = open(original_template_path, 'rt')
00442                 initial = fp.readlines()
00443                 fp.close()
00444                 merge = diff3.merge(current, initial, template)
00445                 # check if anything has changed at all
00446                 if merge['body'] != current:
00447                     # in case of conflicts, backup file using .mine suffix
00448                     if merge['conflict']:
00449                         try:
00450                             shutil.copy(project_path, project_path + '.mine')
00451                         except Exception, e:
00452                             sys.stderr.write("E %s - failed to backup file" % project_path)
00453                             if args.verbose > 0: sys.stderr.write(': ' + str(e))
00454                             sys.stderr.write('\n')
00455                             return -1
00456                     # otherwise, backup current project file using ~ suffix
00457                     elif args.backup:
00458                         try:
00459                             shutil.copyfile(project_path, project_path + '~')
00460                         except Exception, e:
00461                             sys.stderr.write("E %s - failed to backup file" % project_path)
00462                             if args.verbose > 0: sys.stderr.write(': ' + str(e))
00463                             sys.stderr.write('\n')
00464                             return -1
00465                     # replace project file by merged file
00466                     try:
00467                         fp = open(project_path, 'wt')
00468                         fp.writelines(merge['body'])
00469                         fp.close()
00470                     except Exception, e:
00471                         sys.stderr.write("E %s - failed to update file" % project_path)
00472                         if args.verbose > 0: sys.stderr.write(': ' + str(e))
00473                         sys.stderr.write('\n')
00474                         return -1
00475                     if retval != -1:
00476                         if merge['conflict']: retval = 2
00477                         else:                 retval = 1
00478                 # remove altered template again if update was successful
00479                 if not merge['conflict']:
00480                     try:
00481                         os.remove(project_path + '.template')
00482                     except Exception, e:
00483                         sys.stderr.write("E %s - failed to remove temporary file" % project_path)
00484                         if args.verbose > 0: sys.stderr.write(': ' + str(e))
00485                         sys.stderr.write('\n')
00486                         retval = -1
00487             # cannot update files without copy of original template
00488             # note that this may also be caused by a project file of
00489             # same name as a newly added template file
00490             else:
00491                 try:
00492                     # copy current template for reference next to the corresponding project file
00493                     shutil.copyfile(template_path, project_path + '.template')
00494                 except:
00495                     pass
00496                 sys.stderr.write("S %s - cannot add or update file: either project file of same name exists already or missing original template\n" % project_path)
00497                 retval = -1
00498             # print status message if file was updated
00499             if retval > 0:
00500                 if merge['conflict']: print "C %s" % project_path
00501                 else:                 print "G %s" % project_path
00502     # done
00503     return retval
00504 
00505 # ----------------------------------------------------------------------------
00506 def remove_directory(path):
00507     isempty = True
00508     for filename in os.listdir(path):
00509         if filename != '.svn' and filename != '.git' and filename != '.hg':
00510             isempty = False
00511             break
00512     if isempty:
00513         shutil.rmtree(path)
00514         return True
00515     return False
00516 
00517 # ----------------------------------------------------------------------------
00518 def delete(path, args, isdir=False):
00519     """Delete file or empty directory.
00520 
00521     @param [in] path  Path relative to template or project root, respectively.
00522     @param [in] args  Namespace with command-line arguments.
00523     @param [in] isdir Whether @p path is a directory path that may not have
00524                       a corresponding template.
00525 
00526     @retval -1 on error
00527     @retval  0 if file/directory did not exist
00528     @retval  1 if file/directory was removed
00529 
00530     """
00531     retval = 0
00532 
00533     original_template_path = get_template(args.original_template, path)
00534     project_path = os.path.join(args.root, path)
00535 
00536     # delete existing directory
00537     if os.path.isdir(project_path):
00538         if args.force:
00539             try:
00540                 shutil.rmtree(project_path)
00541             except Exception, e:
00542                 sys.stderr.write("E %s - failed to remove directory" % project_path)
00543                 if verbose > 0: sys.stderr.write(': ' + str(e))
00544                 sys.stderr.write('\n')
00545                 retval = -1
00546         else:
00547             try:
00548                 os.rmdir(project_path)
00549             except Exception, e:
00550                 sys.stderr.write("E %s - failed to remove directory;"
00551                                  " use --force to force deletion of non-empty directory" % project_path)
00552                 if args.verbose > 0: sys.stderr.write(': ' + str(e))
00553                 sys.stderr.write('\n')
00554                 retval = -1
00555         if retval != -1:
00556             print "D %s" % project_path
00557             retval = 1
00558     # delete existing file
00559     elif os.path.isfile(project_path):
00560         # check if project file differs from template
00561         if not args.force:
00562             if not os.path.isfile(original_template_path):
00563                 sys.stderr.write("E %s - original template missing, use --force to force deletion\n" % project_path)
00564                 return -1
00565             # alter new template
00566             try:
00567                 shutil.copyfile(original_template_path, project_path + '.template')
00568                 if alter(project_path + '.template', args, backup=False) == -1:
00569                     raise Exception('failed to alter template file')
00570             except Exception, e:
00571                 sys.stderr.write("E %s - failed to create temporary template,"
00572                                  " use --force to force deletion")
00573                 if args.verbose > 0: sys.stderr.write(': ' + str(e))
00574                 sys.stderr.write('\n')
00575                 return -1
00576             # compare files
00577             fp = open(project_path, 'rt')
00578             current = fp.read()
00579             fp.close()
00580             fp = open(project_path + '.template', 'rt')
00581             template = fp.read()
00582             fp.close()
00583             # remove altered template file
00584             try:
00585                 os.remove(project_path + '.template')
00586             except Exception, e:
00587                 sys.stderr.write("E %s - failed to remove temporary file" % project_path)
00588                 if args.verbose > 0: sys.stderr.write(': ' + str(e))
00589                 sys.stderr.write('\n')
00590                 retval = -1
00591             # check for differences
00592             if current != template:
00593                 if retval != -1:
00594                     sys.stderr.write("S %s - file was modified, use --force to force deletion\n" % project_path)
00595                 return -1
00596         # delete project file
00597         try:
00598             os.remove(project_path)
00599         except Exception, e:
00600             sys.stderr.write("E %s - failed to remove file" % project_path)
00601             if args.verbose > 0: sys.stderr.write(': ' + str(e))
00602             sys.stderr.write('\n')
00603             return -1
00604         # print status message
00605         if retval != -1:
00606             print "D %s" % project_path
00607             retval = 1
00608         # remove now empty directories
00609         subdir = os.path.dirname(project_path)
00610         while subdir != args.root:
00611             try:
00612                 if remove_directory(subdir):
00613                     print "D %s" % subdir
00614             except Exception, e:
00615                 sys.stderr.write("W %s - failed to remove directory" % subdir)
00616                 if args.verbose > 0: sys.stderr.write(': ' + str(e))
00617                 sys.stderr.write('\n')
00618                 retval = -1
00619                 break
00620             subdir = os.path.dirname(subdir)
00621     # done
00622     return retval
00623 
00624 # ----------------------------------------------------------------------------
00625 def addordel(op, path, args, isdir=False):
00626     """Add/update or delete file or directory depending on operation specified.
00627 
00628     @param [in] op    Operations. Either one of ADD, None, or DEL.
00629     @param [in] path  File path relative to template or project root, respectively.
00630     @param [in] args  Namespace with command-line arguments.
00631     @param [in] isdir Whether @p path is a directory path that may not have
00632                       a corresponding template.
00633 
00634     @retval -1 on error
00635     @retval  0 if nothing was done
00636     @retval  1 if operation was successful
00637 
00638     """
00639     # add file/directory to project
00640     if op == ADD:
00641         return add(path, args, isdir=isdir)
00642     # delete file/directory from project
00643     elif op == DEL:
00644         return delete(path, args, isdir=isdir)
00645     # nothing to do otherwise
00646     return 0
00647 
00648 # ----------------------------------------------------------------------------
00649 class AccumulatorForNumbersOfChangesAndErrors(object):
00650     """Helper class used to accumulate number of changes, conflicts, and
00651        errors when calling either one of the add(), delete(), or addordel()
00652        functions."""
00653     # ------------------------------------------------------------------------
00654     changes   = 0
00655     conflicts = 0
00656     errors    = 0
00657     # ------------------------------------------------------------------------
00658     def call(self, func, *args, **kwargs):
00659         """Call the specified function with the given arguments."""
00660         rt = func(*args, **kwargs)
00661         if rt == -1: self.errors += 1
00662         else:
00663             if rt  > 0: self.changes   += 1
00664             if rt == 2: self.conflicts += 1
00665         sys.stdout.flush()
00666 
00667 # ============================================================================
00668 # main
00669 # ============================================================================
00670 
00671 # ----------------------------------------------------------------------------
00672 if __name__ == '__main__':
00673     ok = True
00674 
00675     # ------------------------------------------------------------------------
00676     # program help
00677     parser = argparse.ArgumentParser(prog='basisproject', description="""
00678   This command-line tool, also referred to as project tool, can be used to
00679   create a new project from the BASIS project template version 1.7 or to modify a
00680   previously created BASIS project.
00681 
00682   Depending on the grade of customization or optional inclusion of template
00683   components, different subsets of the fully featured project template can be
00684   selected. Additional template files and directories can be added to an existing
00685   project at any time. Further, if the --no* options are given explicitly,
00686   project files which were previously copied from the template are deleted.
00687   Files are, however, only deleted if they were not modified by the project
00688   developer since their creation and hence do not contain project related changes.
00689   Similarly are directories deleted by this tool only if empty. The deletion of
00690   modified files can be forced by supplying the --force option.
00691 
00692   Besides the name of the new project and a brief description, names of external
00693   packages required or optionally used by this project can be specified. For each
00694   such package, an entry in the list of dependencies given as argument to either
00695   one of the DEPENDS* options of the basis_project() command is added.
00696 
00697   An additional feature of this tool is, that it can upgrade an existing project
00698   to a newer project template version, given that the existing directory structure
00699   and file names were preserved. User changes to previously added template files
00700   are preserved and merged with the changes of the template using a so-called
00701   three-way diff similar to the Subversion tool svn. If the automatic file merge
00702   is not successful, a copy of the original project file (*.mine) as well as the
00703   new template file (*.template) are written to the project directory next to the
00704   project file which has been overwritten with the merged content which includes
00705   markers to indicate where the conflicts occurred. The project file has to be
00706   edited manually in this case to resolve any conflicts. Once the conflicts have
00707   been resolved, the *.mine and *.template files must be removed before this
00708   tool can be used for another update of the project files. This can be done
00709   either manually or by running this program with the --cleanup option.""",
00710         formatter_class=argparse.ArgumentDefaultsHelpFormatter)
00711 
00712     # ----------------------------------------------------------------------------
00713     # define command-line arguments
00714 
00715     parser.set_defaults(opts=get_template_options())
00716 
00717     # --version
00718     parser.add_argument('--version', action='version',
00719             help="Show version information and exit.",
00720             version="""%(prog)s (BASIS) r3148
00721 Copyright (c) 2011, 2012 University of Pennsylvania. All rights reserved.
00722 See https://www.cbica.upenn.edu/sbia/software/license.html or COPYING file.""")
00723 
00724     # --verbose
00725     parser.add_argument('-v', '--verbose', action='count', default=0,
00726             help="Increase verbosity of output messages.")
00727 
00728     # --name/--description/--author
00729     parser.add_argument('--name', metavar='<name>',
00730             help="Name of new project.")
00731 
00732     parser.add_argument('--description', metavar='<text>',
00733             help="Brief project description.")
00734 
00735     parser.add_argument('--author', metavar='<author>',
00736             help="Name of original author of the software.")
00737 
00738     # --root/--template
00739     parser.add_argument('--root', metavar='<dir>', type=os.path.abspath,
00740             help="""Project root directory. Defaults to a subdirectory named after the
00741 project created in the current working directory. In order to update
00742 an existing project, specify the root directory of this project
00743 using this option.""")
00744 
00745     parser.add_argument('--template', metavar='<dir>',
00746             type=os.path.abspath, default=_template + '-1.7',
00747             help="""Root directory of project template. Defaults to the template
00748 corresponding to this BASIS installation.""")
00749 
00750     parser.add_argument('--original', metavar='<dir>',
00751             type=os.path.abspath, dest='original_template',
00752             help="""Root directory of project template which the already existing project
00753 was created from or last updated to. By default, the version of the original template
00754 is extracted from the root CMakeLists.txt file of the project. Given this version,
00755 this tool determines the path of the corresponding project template which is part of
00756 the BASIS installation. If this previous template is not available, an automatic
00757 update of project files to a newer template version is not feasible.""")
00758 
00759     # --use et al.
00760     parser.add_argument('--use', metavar='<pkg>', action='append', default=[],
00761             help="""Name of external package used by this project.
00762 Note that the package name is case sensitive.""")
00763 
00764     parser.add_argument('--useopt', metavar='<pkg>', action='append', default=[],
00765             help="""Name of external package optionally used by this project.
00766 Note that the package name is case sensitive.""")
00767 
00768 
00769     parser.add_argument('--usetest', metavar='<pkg>', action='append', default=[],
00770             help="""Name of external package required by tests of this project.
00771 Note that the package name is case sensitive.""")
00772 
00773     parser.add_argument('--useopttest', metavar='<pkg>', action='append', default=[],
00774             help="""Name of external package optionally used by tests of this project.
00775 Note that the package name is case sensitive.""")
00776 
00777     # --minimal
00778     class MinimalTemplateAction(argparse.Action):
00779         def __call__(self, parser, namespace, value, option_string):
00780             setattr(namespace, 'opts', get_template_options(DEL))
00781 
00782     parser.add_argument('--minimal', nargs=0, action=MinimalTemplateAction,
00783             help="""Choose minimal project template. Corresponds to not
00784 selecting any of the additional template files.""")
00785 
00786     # --standard
00787     class StandardTemplateAction(argparse.Action):
00788         def __call__(self, parser, namespace, value, option_string):
00789             setattr(namespace, 'opts', get_standard_template_options({}))
00790 
00791     parser.add_argument('--standard', nargs=0, action=StandardTemplateAction,
00792             help="""Choose standard project template. This is the default
00793 project template if no template was selected explicitly.
00794 Corresponds to:
00795     --doc
00796     --doc-rst
00797     --example
00798     --noconfig
00799     --nodata
00800     --test""")
00801 
00802     # --full
00803     class FullTemplateAction(argparse.Action):
00804         def __call__(self, parser, namespace, value, option_string):
00805             setattr(namespace, 'opts', get_template_options(ADD))
00806 
00807     parser.add_argument('--full', nargs=0, action=FullTemplateAction,
00808             help="""Choose full-featured project template.""")
00809 
00810     # --toplevel
00811     class TopLevelTemplateAction(argparse.Action):
00812         def __call__(self, parser, namespace, value, option_string):
00813             opts = get_standard_template_options({})
00814             opts['modules'] = ADD
00815             opts['src']     = DEL
00816             opts['test']    = DEL
00817             setattr(namespace, 'opts', opts)
00818 
00819     parser.add_argument('--toplevel', nargs=0, action=TopLevelTemplateAction,
00820             help="""Choose top-level project template.""")
00821 
00822     # --module
00823     class ModuleTemplateAction(argparse.Action):
00824         def __call__(self, parser, namespace, value, option_string):
00825             opts = get_template_options(DEL)
00826             opts['include'] = ADD
00827             opts['src']     = ADD
00828             opts['test']    = ADD
00829             setattr(namespace, 'opts', opts)
00830 
00831     parser.add_argument('--module', nargs=0, action=ModuleTemplateAction,
00832             help="""Choose project module template.""")
00833 
00834     # --[no]config
00835     class ConfigAction(argparse.Action):
00836         def __call__(self, parser, namespace, values, option_string):
00837             if option_string == '--config': opts = self._get_options(ADD)
00838             else:                           opts = self._get_options(DEL)
00839             setattr(namespace, 'opts', opts)
00840 
00841         def _get_options(self, op):
00842             return {'config-settings':     op,
00843                     'config-components':   op,
00844                     'config-package':      op,
00845                     'config-find':         op,
00846                     'config-find_version': op,
00847                     'config-script':       op,
00848                     'config-test':         op,
00849                     'config-use':          op}
00850 
00851     parser.add_argument('--config', '--noconfig',
00852             nargs=0, action=ConfigAction,
00853             help="""Include/exclude all custom configuration files.""")
00854 
00855     # granular template options
00856     class OptAction(argparse.Action):
00857         def __call__(self, parser, namespace, values, option_string):
00858             if not hasattr(namespace, 'opts'):
00859                 setattr(namespace, 'opts', {})
00860             if option_string.startswith('--no'):
00861                 op  = DEL
00862                 opt = option_string[4:]
00863             else:
00864                 op  = ADD
00865                 opt = option_string[2:]
00866             namespace.opts[opt] = op
00867 
00868     parser.add_argument('--config-components', '--noconfig-components',
00869             nargs=0, action=OptAction,
00870             help="Include/exclude custom Components.cmake file.")
00871 
00872     parser.add_argument('--config-find', '--noconfig-find',
00873             nargs=0, action=OptAction,
00874             help="Include/exclude custom Config.cmake.in file.")
00875 
00876     parser.add_argument('--config-find-version', '--noconfig-find-version',
00877             nargs=0, action=OptAction,
00878             help="Include/exclude custom ConfigVersion.cmake.in file.")
00879 
00880     parser.add_argument('--config-package', '--noconfig-package',
00881             nargs=0, action=OptAction,
00882             help="Include/exclude custom Package.cmake file.")
00883 
00884     parser.add_argument('--config-script', '--noconfig-script',
00885             nargs=0, action=OptAction,
00886             help="Include/exclude custom ScriptConfig.cmake.in file.")
00887 
00888     parser.add_argument('--config-settings', '--noconfig-settings',
00889             nargs=0, action=OptAction,
00890             help="Include/exclude custom Settings.cmake file.")
00891 
00892     parser.add_argument('--config-test', '--noconfig-test',
00893             nargs=0, action=OptAction,
00894             help="Include/exclude custom CTestCustom.cmake.in file.")
00895 
00896     parser.add_argument('--config-use', '--noconfig-use',
00897             nargs=0, action=OptAction,
00898             help="Include/exclude custom ConfigUse.cmake.in file.")
00899 
00900     parser.add_argument('--data', '--nodata',
00901             nargs=0, action=OptAction,
00902             help="Add/remove directory for auxiliary data files.")
00903 
00904     parser.add_argument('--doc', '--nodoc',
00905             nargs=0, action=OptAction,
00906             help="Add/remove directory for documentation files.")
00907 
00908     parser.add_argument('--doc-rst', '--nodoc-rst',
00909             nargs=0, action=OptAction,
00910             help="Add/remove reStructuredText (.rst) files for software manual/web site.")
00911 
00912     parser.add_argument('--example', '--noexample',
00913             nargs=0, action=OptAction,
00914             help="Add/remove directory for example files.")
00915 
00916     parser.add_argument('--include', '--noinclude',
00917             nargs=0, action=OptAction,
00918             help="Add/remove directory for public header files.")
00919 
00920     parser.add_argument('--modules', '--nomodules',
00921             nargs=0, action=OptAction,
00922             help="Add/remove support for modularization.")
00923 
00924     parser.add_argument('--src', '--nosrc',
00925             nargs=0, action=OptAction,
00926             help="Add/remove directory for project source files.")
00927 
00928     parser.add_argument('--test', '--notest',
00929             nargs=0, action=OptAction,
00930             help="Add/remove support for testing.")
00931 
00932     parser.add_argument('--test-internal', '--notest-internal',
00933             nargs=0, action=OptAction,
00934             help="Add/remove support for internal testing.")
00935 
00936     # --update
00937     parser.add_argument('-u', '--update', action='store_true', default=False,
00938             help="""Enable update of existing project files. If this option is given,
00939 changes of the project file are merged with those of the
00940 corresponding template file if the template has been modified,
00941 for example, as part of a new release of BASIS.""")
00942 
00943     # --cleanup
00944     parser.add_argument('-c', '--cleanup', action='store_true', default=False,
00945             help="""Remove files resulting from merge conflicts of a previous update
00946 of existing project files and backups. The conflicts should have
00947 been manually resolved before using this option.""")
00948 
00949     # --force
00950     parser.add_argument('-f', '--force', action='store_true', default=False,
00951             help="""Enable removal of non-empty directories and modified project files.
00952 By default, only empty directories and project files which have not
00953 been edited since their creation are removed.""")
00954 
00955     # --nobackup
00956     parser.add_argument('--nobackup', action='store_false', default=True, dest='backup',
00957             help="""Disable backup of existing project files. By default, whenever an
00958 existing project file is modified, a backup of this file is made and saved under the
00959 same filename, but with the ~ character as suffix. If this option is given, such backup
00960 files are not made. In case of a merge conflict, however, existing project files are
00961 always backed-up, using .mine as suffix for the file name of the backup in this case.""")
00962 
00963     # ----------------------------------------------------------------------------
00964     # parse command-line arguments
00965     if len(sys.argv) == 1:
00966         parser.print_help()
00967         sys.exit(1)
00968     else:
00969         args = parser.parse_args()
00970 
00971     # ------------------------------------------------------------------------
00972     # create new project or update existing one?
00973     # request to create new project
00974     if args.name:
00975         create = True
00976         # create subdirectory in current working directory by default
00977         if not args.root:
00978             args.root = os.path.join(os.getcwd(), args.name)
00979         # ensure that root directory is empty
00980         if os.path.exists(args.root) and len(os.listdir(args.root)) != 0:
00981             sys.stderr.write("Directory " + args.root + " exists already and is not empty.\n")
00982             sys.stderr.write("Please choose another project name or a different root directory using the --root option.\n")
00983             sys.stderr.write('\n')
00984             sys.stderr.write("If you want to modify an existing project, please specify the root directory\n")
00985             sys.stderr.write("of the existing project using the --root option and remove the --name option.\n")
00986             sys.exit(1)
00987         # require a project description
00988         if not args.description:
00989             sys.stderr.write("No project description given!\n")
00990             sys.exit(1)
00991         # set path of original template to current template
00992         args.original_template = args.template
00993         # verify that project name is valid
00994         if not re.match(r'^[a-zA-Z0-9]+$', args.name):
00995             sys.stderr.write("Invalid project name: " + args.name + "\n")
00996             sys.stderr.write("Project name may only consist of alphanumeric characters!\n")
00997             sys.stderr.write("If you are attempting to modify an existent project, check whether the\n")
00998             sys.stderr.write("project name is correctly extracted from the root CMakeLists.txt file.\n")
00999             sys.exit(1)
01000     # request to modify existing project
01001     else:
01002         create = False
01003         # use current working directory if root not specified
01004         if not args.root:
01005             args.root = os.getcwd()
01006         # remove trailing slashes from root
01007         if args.root[-1] == '/' or args.root[-1] == '\\':
01008             args.root = args.root[0:-1]
01009         # check existence of root directory
01010         if not os.path.isdir(args.root):
01011             sys.stderr.write("Project directory " + args.root + " does not exist!\n")
01012             sys.stderr.write("If you want to create a new project, please specify a project name.\n")
01013             sys.exit(1)
01014         # is project a BASIS project?
01015         if not os.path.isfile(os.path.join(args.root, 'BasisProject.cmake')):
01016             sys.stderr.write("Could not find a BasisProject.cmake file in " + args.root +
01017                     "! Are you sure this is a BASIS project?\n")
01018             sys.exit(1)
01019         # notify user that --description option is invalid
01020         if args.description:
01021             sys.stderr.write("Cannot modify description of existing project. Please edit file BasisProject.cmake.\n")
01022             sys.stderr.write("Do not use option --description when attempting to modify an existing project.\n")
01023             sys.exit(1)
01024         # get project name
01025         if not args.name:
01026             args.name = get_project_name(os.path.join(args.root, 'BasisProject.cmake'))
01027             if not args.name:
01028                 sys.stderr.write("Failed to determine project name!\n")
01029                 sys.exit(1)
01030         # get original template directory
01031         if not args.original_template:
01032             original_template_version = get_template_version(os.path.join(args.root, 'CMakeLists.txt'))
01033             if not original_template_version:
01034                 sys.stderr.write("Failed to determine original template version!\n")
01035                 sys.exit(1)
01036             args.original_template = _template + '-' + original_template_version
01037 
01038     # print template and root path
01039     if args.verbose > 1:
01040         print "Project:  " + args.root
01041         print "Template: " + args.template
01042         print "Original: " + args.original_template
01043         print
01044 
01045     # ------------------------------------------------------------------------
01046     # adjust template options
01047     if not hasattr(args, 'opts'):
01048         setattr(args, 'opts', {})
01049     opts = get_default_template_options(args.root, args.opts, create)
01050 
01051     # ------------------------------------------------------------------------
01052     # do not update exiting project if previous conflicts are not resolved
01053     if not create:
01054         for root, dirs, files in os.walk(args.root):
01055             for filename in files:
01056                 if (filename.endswith('.mine')
01057                         or filename.endswith('.template')
01058                         or filename.endswith('~')):
01059                     if args.cleanup:
01060                         file_path = os.path.join(root, filename)
01061                         try:
01062                             os.remove(file_path)
01063                             print "D %s" % file_path
01064                         except Exception, e:
01065                             sys.stderr.write("E %s - failed to remove file" % file_path)
01066                             if args.verbose > 0: sys.stderr.write(': ' + str(e))
01067                             sys.stderr.write('\n')
01068                             ok = False
01069                     elif args.update and not filename.endswith('~'):
01070                         sys.stderr.write(
01071 """Their occurred conflicts when updating the project files before.
01072 
01073 Please resolve these conflicts first by editing the conflicted files, then remove
01074 the *.mine and *.template files or run this program with the --cleanup option.
01075 Once these conflicts have been resolved, try updating again.
01076 
01077 Note that if you want to revert the previous udpate entirely, you should use
01078 the revert functionality of the revision control system that you are hopefully
01079 using to manage your project files. This should also revert the copies of the
01080 template files in the .basis/ subdirectory.
01081 """)
01082                         sys.exit(1)
01083         if args.cleanup and not ok:
01084             sys.stderr.write("\nFailed to cleanup backup files and temporary files\n")
01085             sys.exit(1)
01086 
01087     # ------------------------------------------------------------------------
01088     # create/modify project
01089     acc = AccumulatorForNumbersOfChangesAndErrors()
01090 
01091     try:
01092         # ---------------------------------------------------------------------
01093         # add/remove project files/directories
01094 
01095         # required project files
01096         acc.call(add, 'AUTHORS.txt',        args)
01097         acc.call(add, 'README.txt',         args)
01098         acc.call(add, 'INSTALL.txt',        args)
01099         acc.call(add, 'COPYING.txt',        args)
01100         acc.call(add, 'ChangeLog.txt',      args)
01101         acc.call(add, 'BasisProject.cmake', args)
01102         acc.call(add, 'CMakeLists.txt',     args)
01103         # additional configuration files
01104         acc.call(addordel, opts.get('config-settings',     None), 'config/Settings.cmake',         args)
01105         acc.call(addordel, opts.get('config-components',   None), 'config/Components.cmake',       args)
01106         acc.call(addordel, opts.get('config-package',      None), 'config/Package.cmake',          args)
01107         acc.call(addordel, opts.get('config-find',         None), 'config/Config.cmake.in',        args)
01108         acc.call(addordel, opts.get('config-find',         None), 'config/ConfigSettings.cmake',   args)
01109         acc.call(addordel, opts.get('config-find_version', None), 'config/ConfigVersion.cmake.in', args)
01110         acc.call(addordel, opts.get('config-script',       None), 'config/ScriptConfig.cmake.in',  args)
01111         acc.call(addordel, opts.get('config-test',         None), 'config/CTestCustom.cmake.in',   args)
01112         acc.call(addordel, opts.get('config-use',          None), 'config/ConfigUse.cmake.in',     args)
01113         # software configuration data
01114         acc.call(addordel, opts.get('data', None), 'data/CMakeLists.txt', args)
01115         # documentation
01116         acc.call(addordel, opts.get('doc',     None), 'doc/CMakeLists.txt',   args)
01117         acc.call(addordel, opts.get('doc-rst', None), 'doc/index.rst',        args)
01118         acc.call(addordel, opts.get('doc-rst', None), 'doc/changelog.rst',    args)
01119         acc.call(addordel, opts.get('doc-rst', None), 'doc/download.rst',     args)
01120         acc.call(addordel, opts.get('doc-rst', None), 'doc/installation.rst', args)
01121         acc.call(addordel, opts.get('doc-rst', None), 'doc/manual.rst',       args)
01122         acc.call(addordel, opts.get('doc-rst', None), 'doc/publications.rst', args)
01123         acc.call(addordel, opts.get('doc-rst', None), 'doc/people.rst',       args)
01124         # modularization
01125         acc.call(addordel, opts.get('modules', None), 'modules', args, isdir=True)
01126         # source files
01127         acc.call(addordel, opts.get('include', None), 'include', args, isdir=True)
01128         acc.call(addordel, opts.get('src',     None), 'src/CMakeLists.txt', args)
01129         # testing tree
01130         acc.call(addordel, opts.get('test',          None), 'CTestConfig.cmake',            args)
01131         acc.call(addordel, opts.get('test',          None), 'test/CMakeLists.txt',          args)
01132         acc.call(addordel, opts.get('test-internal', None), 'test/internal/CMakeLists.txt', args)
01133         # example
01134         acc.call(addordel, opts.get('example', None), 'example/CMakeLists.txt', args)
01135         # update template version
01136         if args.update: acc.call(update_template_version, args)
01137 
01138     except Exception, e:
01139         sys.stderr.write("Failed to ")
01140         if create: sys.stderr.write("create")
01141         else:      sys.stderr.write("modify")
01142         sys.stderr.write(" project: " + str(e) + '\n')
01143         ok = False
01144 
01145     # ------------------------------------------------------------------------
01146     # done
01147     if ok:
01148         if create:
01149             sys.stdout.write("\nCreated project\n")
01150         else:
01151             if acc.changes > 0:
01152                 sys.stdout.write('\n' + str(acc.changes) + " file")
01153                 if acc.changes > 1: sys.stdout.write('s')
01154                 sys.stdout.write(" added (A), removed (D), or modified (G, C)\n")
01155             elif acc.errors == 0:
01156                 if args.update:
01157                     sys.stdout.write("Project is up to date\n")
01158                 else:
01159                     sys.stdout.write("No project files added, removed, or modified\n")
01160         if acc.errors > 0:
01161             if acc.changes == 0: sys.stderr.write('\n')
01162             sys.stderr.write(str(acc.errors) + " error")
01163             if acc.errors > 1: sys.stderr.write('s')
01164             sys.stderr.write(" encountered\n")
01165         if acc.conflicts > 0:
01166             sys.stderr.write(str(acc.conflicts) + " conflicting change")
01167             if acc.conflicts > 1: sys.stderr.write('s')
01168             sys.stderr.write(" (C) encountered\n")
01169             sys.stderr.write(
01170 """
01171 Resolve conflicts by editing the updated files manually, using the corresonding *.mine
01172 and *.template files as reference. Once a conflict is resolved, remove these files.
01173 """)