d93c5f
#!/usr/bin/python
d93c5f
#
d93c5f
# createmodule.py - Takes the name of a environment init script and 
d93c5f
# produces a modulefile that duplicates the changes made by the init script
d93c5f
#
d93c5f
# Copyright (C) 2012 by Orion E. Poplawski <orion@cora.nwra.com>
d93c5f
#
d93c5f
# This program is free software: you can redistribute it and/or modify
d93c5f
# it under the terms of the GNU General Public License as published by
d93c5f
# the Free Software Foundation, either version 2 of the License, or
d93c5f
# (at your option) any later version.
d93c5f
d93c5f
# This program is distributed in the hope that it will be useful,
d93c5f
# but WITHOUT ANY WARRANTY; without even the implied warranty of
d93c5f
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
d93c5f
# GNU General Public License for more details.
d93c5f
d93c5f
# You should have received a copy of the GNU General Public License
d93c5f
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
d93c5f
from __future__ import print_function
d93c5f
d93c5f
from optparse import OptionParser
d93c5f
import os,sys,re
d93c5f
from subprocess import *
d93c5f
d93c5f
# Handle options
d93c5f
usage = "Usage: %prog [-p prefix] <initscript> [args]"
d93c5f
parser = OptionParser()
d93c5f
parser.set_usage(usage)
d93c5f
parser.add_option('-p', '--prefix', action='store', type='string', dest='prefix', help='Specify path prefix')
d93c5f
parser.add_option('--noprefix', action='store_true', dest='noprefix', default=False, help='Do not generate a prefix')
d93c5f
(options, args) = parser.parse_args()
d93c5f
d93c5f
# Need a script name
d93c5f
if not args:
d93c5f
    parser.print_usage()
d93c5f
    exit(1)
d93c5f
d93c5f
# Return environment after a command
d93c5f
def getenv(cmd = ':'):
d93c5f
    env = {}
d93c5f
    p = Popen(cmd + ";env", shell=True, stdout=PIPE, stderr=PIPE)
d93c5f
    (stdout, stderr) = p.communicate()
d93c5f
    if p.returncode != 0:
d93c5f
        print("EROR: Could not execute initscript:")
d93c5f
        print("%s returned exit code %d" % (cmd, p.returncode))
d93c5f
        print(stderr)
d93c5f
        exit(1)
d93c5f
    if stderr != '':
d93c5f
        print("WARNING: initscript sent the following to stderr:")
d93c5f
        print(stderr)
d93c5f
    # Parse the output key=value pairs
d93c5f
    skip = False
d93c5f
    for line in stdout.splitlines():
d93c5f
        if skip:
d93c5f
            if line == '}':
d93c5f
                skip = False
d93c5f
            continue
d93c5f
        try:
d93c5f
            (var,value) = line.split('=',1)
d93c5f
        except ValueError:
d93c5f
            print("ERROR: Could not parse output line:")
d93c5f
            print(line)
d93c5f
            exit(1)
d93c5f
        # Exported functions - not handled
d93c5f
        if value.find('() {') == 0:
d93c5f
            skip = True
d93c5f
        else:
d93c5f
            env[var] = value
d93c5f
    return env
d93c5f
d93c5f
#Record initial environment
d93c5f
env1=getenv()
d93c5f
d93c5f
#Record environment after sourcing the initscript
d93c5f
env2=getenv(". " + " ".join(args))
d93c5f
d93c5f
# Initialize our variables for storing modifications
d93c5f
chdir = None
d93c5f
appendpath = {}
d93c5f
prependpath = {}
d93c5f
unhandled = {}
d93c5f
setenv = {}
d93c5f
unsetenv = []
d93c5f
pathnames = []
d93c5f
d93c5f
# Function to nomalize all paths in a list of paths and remove duplicate items
d93c5f
def normpaths(paths):
d93c5f
    newpaths = []
d93c5f
    for path in paths:
d93c5f
        normpath = os.path.normpath(path)
d93c5f
        if normpath not in newpaths and normpath != '.':
d93c5f
             newpaths.append(os.path.normpath(path))
d93c5f
    return newpaths
d93c5f
d93c5f
# Start with existing keys and look for changes
d93c5f
for key in env1.keys():
d93c5f
    # Test for delete
d93c5f
    if key not in env2:
d93c5f
        unsetenv.append(key)
d93c5f
        continue
d93c5f
    # No change
d93c5f
    if env1[key] == env2[key]:
d93c5f
        del env2[key]
d93c5f
        continue
d93c5f
    #Working directory change
d93c5f
    if key == 'PWD':
d93c5f
	chdir=os.path.normpath(env2[key])
d93c5f
        pathnames.append(chdir)
d93c5f
        del env2[key]
d93c5f
        continue
d93c5f
    # Determine modifcations to beginning and end of the string
d93c5f
    try:
d93c5f
        (prepend,append) = env2[key].split(env1[key])
d93c5f
    except ValueError:
d93c5f
         continue
d93c5f
    if prepend:
d93c5f
        presep = prepend[-1:]
d93c5f
        prependpaths = prepend.strip(presep).split(presep)
d93c5f
        # LICENSE variables often include paths outside install directory
d93c5f
        if 'LICENSE' not in key:
d93c5f
            pathnames += prependpaths
d93c5f
        if presep not in prependpath:
d93c5f
            prependpath[presep] = {}
d93c5f
        newpath = presep.join(normpaths(prependpaths))
d93c5f
        if newpath:
d93c5f
            prependpath[presep][key] = newpath
d93c5f
        else:
d93c5f
            unhandled[key] = env2[key]
d93c5f
    if append:
d93c5f
        appsep = append[0:1]
d93c5f
        appendpaths = append.strip(appsep).split(appsep)
d93c5f
        # LICENSE variables often include paths outside install directory
d93c5f
        if 'LICENSE' not in key:
d93c5f
            pathnames += appendpaths
d93c5f
        if appsep not in appendpath:
d93c5f
            appendpath[appsep] = {}
d93c5f
        newpath = appsep.join(normpaths(appendpaths))
d93c5f
        if newpath:
d93c5f
            appendpath[appsep][key] = newpath
d93c5f
        else:
d93c5f
            unhandled[key] = env2[key]
d93c5f
    del env2[key]
d93c5f
      
d93c5f
# We're left with new keys in env2
d93c5f
for key in env2.keys():
d93c5f
    # Use prepend-path for new paths
d93c5f
    if (re.search('(DIRS|FILES|PATH)$',key)) or (':' in env2[key]):
d93c5f
        prependpaths = env2[key].strip(':').split(':')
d93c5f
        # MANPATH can have system defaults added it it wasn't previously set
d93c5f
        # LICENSE variables often include paths outside install directory
d93c5f
        if key != 'MANPATH' and 'LICENSE' not in key:
d93c5f
            pathnames += prependpaths
d93c5f
        if ':' not in prependpath:
d93c5f
            prependpath[':'] = {}
d93c5f
        prependpath[':'][key] = ':'.join(normpaths(prependpaths))
d93c5f
        continue
d93c5f
    # Set new variables
d93c5f
    setenv[key] = os.path.normpath(env2[key])
d93c5f
    if 'LICENSE' not in key:
d93c5f
        pathnames.append(setenv[key])
d93c5f
d93c5f
# Report unhandled keys
d93c5f
for key in unhandled.keys():
d93c5f
    print("Unhandled change of", key, file=sys.stderr)
d93c5f
    print("Before <%s>" % env1[key], file=sys.stderr)
d93c5f
    print("After <%s>" % unhandled[key], file=sys.stderr)
d93c5f
    for sepkey in appendpath.keys():
d93c5f
        appendpath[sepkey].pop(key, None)
d93c5f
    for sepkey in prependpath.keys():
d93c5f
        prependpath[sepkey].pop(key, None)
d93c5f
d93c5f
# Determine a prefix
d93c5f
prefix=None
d93c5f
if options.prefix:
d93c5f
    prefix = options.prefix
d93c5f
elif not options.noprefix:
d93c5f
    prefix = os.path.commonprefix(pathnames).rstrip('/')
d93c5f
    if prefix == '':
d93c5f
          prefix = None
d93c5f
d93c5f
# Print out the modulefile
d93c5f
print("#%Module 1.0")
d93c5f
d93c5f
# Prefix
d93c5f
if prefix is not None:
d93c5f
    print("\nset prefix " + prefix + "\n")
d93c5f
d93c5f
# Chdir
d93c5f
if chdir is not None:
d93c5f
    print("chdir\t" + chdir)
d93c5f
d93c5f
# Function to format output line with tabs and substituting prefix
d93c5f
def formatline(item, key, value=None):
d93c5f
    print(item, end=' ')
d93c5f
    print("\t"*(2-(len(item)+1)/8), end=' ')
d93c5f
    print(key, end=' ')
d93c5f
    if value is not None:
d93c5f
        print("\t"*(3-(len(key)+1)/8), end=' ')
d93c5f
        if prefix is not None:
d93c5f
            print(value.replace(prefix,'$prefix'))
d93c5f
        else:
d93c5f
            print(value)
d93c5f
d93c5f
# Paths first, grouped by variable name
d93c5f
for sepkey in prependpath.keys():
d93c5f
    pathkeys = prependpath[sepkey].keys()
d93c5f
    pathkeys.sort()
d93c5f
    for key in pathkeys:
d93c5f
        if sepkey == ":":
d93c5f
            formatline("prepend-path",key,prependpath[sepkey][key])
d93c5f
        else:
d93c5f
            formatline("prepend-path --delim %s" % sepkey,key,prependpath[sepkey][key])
d93c5f
d93c5f
for sepkey in appendpath.keys():
d93c5f
    pathkeys = appendpath[sepkey].keys()
d93c5f
    pathkeys.sort()
d93c5f
    for key in pathkeys:
d93c5f
        if sepkey == ":":
d93c5f
            formatline("append-path",key,appendpath[sepkey][key])
d93c5f
        else:
d93c5f
            formatline("append-path --delim %s" % sepkey,key,appendpath[sepkey][key])
d93c5f
d93c5f
# Setenv
d93c5f
setenvkeys = list(setenv.keys())
d93c5f
setenvkeys.sort()
d93c5f
if setenvkeys:
d93c5f
    print()
d93c5f
for key in setenvkeys:
d93c5f
    formatline("setenv",key,setenv[key])
d93c5f
d93c5f
# Unsetenv
d93c5f
unsetenv.sort()
d93c5f
if unsetenv:
d93c5f
    print()
d93c5f
for key in unsetenv:
d93c5f
    formatline("unsetenv",key)