summaryrefslogtreecommitdiff
path: root/scripts/ac_update_extern_pkg
diff options
context:
space:
mode:
authorJames E.H. Turner <jturner@gemini.edu>2016-09-01 09:47:13 -0400
committerJames E.H. Turner <jturner@gemini.edu>2016-09-01 09:47:13 -0400
commitf3268c7558b8679aef84659d98fc89eba9d82cc4 (patch)
tree8f917299efecb3958bee02322f4b40934320f912 /scripts/ac_update_extern_pkg
parentd026f53ee29bc053dfb1d77bf7849c61a176c33d (diff)
downloadastroconda-iraf-helpers-f3268c7558b8679aef84659d98fc89eba9d82cc4.tar.gz
Rename scripts consistently to reflect use of AstroConda-specific conventions.
Diffstat (limited to 'scripts/ac_update_extern_pkg')
-rwxr-xr-xscripts/ac_update_extern_pkg161
1 files changed, 161 insertions, 0 deletions
diff --git a/scripts/ac_update_extern_pkg b/scripts/ac_update_extern_pkg
new file mode 100755
index 0000000..4243e19
--- /dev/null
+++ b/scripts/ac_update_extern_pkg
@@ -0,0 +1,161 @@
+#!/usr/bin/python
+#
+# Copyright(c) 2016 Association of Universities for Research in Astronomy, Inc.
+#
+# This script uses the OS Python to avoid Conda relocation/dependency problems
+# (since it is run before package installation is complete). Both the LSB and
+# MacOS define Python in /usr/bin as standard.
+
+import argparse
+import os, os.path
+import shutil
+import re
+from collections import OrderedDict
+
+
+# AstroConda naming conventions:
+extern_pkg = 'extern.pkg'
+backup = '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:
+parser = argparse.ArgumentParser()
+
+parser.add_argument('path', type=str,
+ help='path to {0} and {1}/'.format(extern_pkg, extern_dir))
+
+parser.add_argument('name', type=str,
+ help='name of IRAF package (and its directory)')
+
+args = parser.parse_args()
+
+# Convert to canonical path and ensure it exists, with an extern.pkg file and
+# 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
+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(extpkg_path, os.R_OK | os.W_OK):
+ raise ValueError("cannot access {0}".format(extpkg_path))
+
+if not os.access(template_path, os.R_OK):
+ raise ValueError("cannot access {0}".format(template_path))
+
+# Read extern.pkg:
+with open(extpkg_path, 'r+') as extpkg_file:
+ buffer = extpkg_file.read()
+
+# Read the package's template ur_extern.pkg, removing any outer blank lines:
+with open(template_path, 'r') as template_file:
+ tempbuf = template_file.read().strip()
+
+# 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 = OrderedDict((
+ ('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'.*')),
+))
+
+# 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<{0}>{1})'.format(key, regex) \
+ for key, (regex, sub) in replacements.items()]) # py 3 way
+
+# Determine the replacement for a given match to the concatenated regex:
+def match_fn(match):
+ sub_re, sub = replacements[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.sub(cat_re, match_fn, tempbuf, flags=re.M)
+
+# 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 that what goes before the "keep" ends with a new line:
+buffer = re.sub('\n?\Z', '\n', buffer, flags=re.M)
+
+# 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#]*(?<![A-z0-9_-]){0}(?![A-z0-9_-])'.format(args.name)
+ later_match = re.search(pkgname_re, buffer[match.end():], flags=re.M)
+
+ # Where a later (user) definition is found, we still remove the last
+ # template-based definition but then continue as if there had been no
+ # match, so a new definition will get placed at the end of the file below:
+ if later_match:
+ buffer = '{0}\n\n{1}'.format(buffer[:match.start()].rstrip(),
+ buffer[match.end():].lstrip())
+ match = None
+
+# Replace the applicable entry with the template-based version:
+if match:
+ buffer = buffer[:match.start()] + tempbuf + buffer[match.end():]
+
+# If there wasn't an existing entry, or the last one wasn't based on the
+# template, put the new entry at the end:
+else:
+ buffer += '{0}\n\n'.format(tempbuf)
+
+# Restore the "keep" line at the end (along with a semicolon to avoid an IRAF
+# parser bug when the last entry ends with a curly bracket):
+buffer += keep_str
+
+# Back up the original extern.pkg:
+shutil.copy2(extpkg_path, os.path.join(path, backup))
+
+# Write the modified extern.pkg:
+with open(extpkg_path, 'w') as extpkg_file:
+ extpkg_file.write(buffer)
+