# Copyright(c) 2016 Association of Universities for Research in Astronomy, Inc. # This script uses the OS Python (both the LSB and MacOS define Python in # /usr/bin as standard), avoiding dependence on the installation that is still # in progress when it gets run. It is not made executable because conda insists # on changing any Python interpreter path to $PREFIX/bin/python, which does not # always exist; it can instead be invoked via ac_config_iraf_pkg. # # Supporting ancient OS Python versions (v2.4 in RHE 5) means avoiding features # such as argparse, new-style string formatting, context managers etc. import sys import os, os.path import shutil import re # AstroConda naming conventions: extern_pkg = 'extern.pkg' extpkg_bak = 'extern.pkg.ac_bak' # avoid possible collision with user .bak extern_dir = 'iraf_extern' template = 'ur_extern.pkg' path_sub = 'UR_VDIR' # a regexp # Get command-line user arguments: usage = """ Usage: ac_update_extern_pkg [--remove] path name positional arguments: path directory containing extern.pkg and iraf_extern/ name name of IRAF package (and its directory) """ argv = sys.argv if '--remove' in argv: args_remove = True argv.remove('--remove') else: args_remove = False if len(argv) != 3 or argv[1].startswith('-') or argv[2].startswith('-'): sys.exit(usage) args_path, args_name = argv[1:] # Convert to canonical path and ensure it exists and is writeable, with an # iraf_extern/package/ur_extern.pkg (ie. files already installed): path = os.path.abspath(args_path) # env dir with extern.pkg extpkg_path = os.path.join(path, extern_pkg) # extern.pkg path extbak_path = os.path.join(path, extpkg_bak) # path to backup copy pkg_path = os.path.join(path, extern_dir, args_name) # path to this IRAF pkg template_path = os.path.join(pkg_path, template) # this ur_extern.pkg if not os.access(path, os.W_OK): raise ValueError("cannot write to %s" % (path)) if not os.access(template_path, os.R_OK): raise ValueError("cannot access %s" % (template_path)) # Read extern.pkg (if there is one): if os.path.isfile(extpkg_path): extpkg_file = open(extpkg_path, 'r') buffer = extpkg_file.read() extpkg_file.close() else: buffer = '' extbak_path = None # Read the package's template ur_extern.pkg, removing any outer blank lines: template_file = open(template_path, 'r') tempbuf = template_file.read().strip() template_file.close() # Here we convert the template entry to a regex for finding and removing old, # previously-generated entries for the same package. This mainly consists of # escaping special characters to match literally and allowing for a new path, # but we also allow for ignoring some trivial whitespace differences. Any other # user-modified entries are deliberately preserved, but still get overridden # when adding the new package entry at the end. We don't use re.escape() here # because it's easier & more understandable to do those subs in concert with # the others. The template ur_extern.pkg files must not contain backslashes. replacements = ( ('special_nospace', (r'([][.^${}\|*?])', r'\\\1')), ('special_sp_end', (r'[ \t]*([=+()])[ \t]*$', r'[ \t]*\\\1')), ('special_space', (r'[ \t]*([=+()])[ \t]*', r'[ \t]*\\\1[ \t]*')), ('lead_space', (r'^[ \t]+', r'')), ('end_space', (r'[ \t]+$', r'')), ('extra_space', (r'[ \t]+', r'[ \t]+')), ('line_space', (r'\s*\n\s*', r'\s*\n\s*')), # see below ('path_sub', (path_sub, r'.*')), ) repdict = dict(replacements) # keep both because no OrderedDict in python < 2.7 # Note that ^ and $ only match in conjunction with their adjoining character, # which, unlike the \n above, can get consumed by a prior pattern. Also, # certain other combinations of \s and other space seem to cause "catastrophic # backtracking" when there's no match, which can take 11 minutes! # Concatenate individual expressions for replacement, so they can be done # simultaneously on the original buffer (otherwise they trip each other up): cat_re = '|'.join(['(?P<%s>%s)' % (key, regex) \ for key, (regex, sub) in replacements]) # Determine the replacement for a given match to the concatenated regex: def match_fn(match): sub_re, sub = repdict[match.lastgroup] # look up replacement tuple return re.sub(sub_re, sub, match.group()) # do & return the substitution # Perform the substitutions to turn the template string into a regex itself: # template_re = re.escape(tempbuf) # if using built-in escape for special chars template_re = re.compile(cat_re, flags=re.M).sub(match_fn, tempbuf) # Update the original template entry with our new path: tempbuf = re.sub(path_sub, os.path.join(pkg_path, ''), tempbuf) # trailing / # print(template_re) # debug # Define expected "keep" line(s) at the end of extern.pkg: keep_re = '(?:^[ \t]*(?:;\s*)*keep\s*)+\Z' keep_str = ';\n\nkeep\n' # Temporarily remove any "keep" from the end of the buffer, before appending to # it (and replacing with ";\n\nkeep\n", to avoid a CL parser bug): match = re.search(keep_re, buffer, flags=re.M) if match: buffer = buffer[:match.start()] # Make sure what goes before the "keep" ends with a new line (unless empty): if buffer.strip(): sep = '\n' else: sep = '' buffer = re.compile('\n?\Z', flags=re.M).sub(sep, buffer) # Find the last entry in extern.pkg (if any) that matches the template: match = None for match in re.finditer(template_re, buffer, flags=re.M): pass # Check for any other definition(s) of this package after the last one based # on the template, requiring that our new definition be moved to the end of the # file, to override the other defs. without actually losing the user's edits: if match: # The name of the path variable in our extern.pkg template entries must be # the name of the IRAF package, otherwise we would have to parse the actual # variable name from the template using these commented regexes and search # for that, instead of the package name itself, to find redefinitions: # pkgdef_re = '^[ \t]*task[ \t]+{0}[.]pkg[ \t]*=[ \t]*(.*)$'.format(name) # pathvar_re = '["\']?(.*)[$].*[.]cl' # find path variable name in pkg def. # Match any non-commented instances of the package name, in the remainder # of the buffer, that don't look like a substring of a longer name: pkgname_re = '^[^\n#]*(?