summaryrefslogtreecommitdiff
path: root/scripts
diff options
context:
space:
mode:
authorjturner <jturner@gemini.edu>2016-08-10 16:08:47 -0400
committerjturner <jturner@gemini.edu>2016-08-10 16:08:47 -0400
commitcf51a7b9156ec5ddf2f13be3607f6594a1056aae (patch)
tree451c09c2ede3181642b021bb392d21155af3fdc6 /scripts
parentbbed7c763ac41b536fd50412e880644ff8ffec90 (diff)
downloadastroconda-iraf-helpers-cf51a7b9156ec5ddf2f13be3607f6594a1056aae.tar.gz
New script to update an existing extern.pkg from a single IRAF package's ur_extern.pkg template (as part of its installation).
Diffstat (limited to 'scripts')
-rw-r--r--scripts/update_extern_pkg144
1 files changed, 144 insertions, 0 deletions
diff --git a/scripts/update_extern_pkg b/scripts/update_extern_pkg
new file mode 100644
index 0000000..536ca38
--- /dev/null
+++ b/scripts/update_extern_pkg
@@ -0,0 +1,144 @@
+#!/usr/bin/env python
+#
+# Copyright(c) 2016 Association of Universities for Research in Astronomy, Inc.
+
+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((
+ ('spec_nospace', (r'([][.^${}\|*?])', r'\\\1')),
+ ('spec_space', (r'[ \t]*([=+()])[ \t]*', r'[ \t]*\\\1[ \t]*')),
+ ('extra_space', (r'[ \t]+', r'[ \t]+')),
+ ('end_space', (r'(^|$)', r'[ \t]*')),
+ ('path_sub', (path_sub, r'.*')),
+))
+
+# 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]*keep\s*)+\Z)'
+keep_str = '\nkeep\n'
+
+# 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 (and make sure
+# there's a "keep" at the end of the file):
+if match:
+ buffer = buffer[:match.start()] + tempbuf + buffer[match.end():]
+ if not re.search(keep_re, buffer, flags=re.M):
+ buffer += keep_str # add missing "keep"
+
+# If there wasn't an existing entry, or the last one wasn't based on the
+# template, put the new entry at the end (before "keep"):
+else:
+ match = re.search(keep_re, buffer, flags=re.M)
+ if match:
+ pos = match.start()
+ buffer = buffer[:pos] + '{0}\n\n'.format(tempbuf) + buffer[pos:]
+ else:
+ buffer += '{0}\n{1}'.format(tempbuf, keep_str) # add missing "keep"
+
+# 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)
+