view makeitso/makeitso.py @ 28:dc18d6db4956

depend on trunk tempita; stubbing for next stage
author Jeff Hammel <jhammel@mozilla.com>
date Wed, 22 Dec 2010 13:30:09 -0800
parents ac44c36da885
children 1549be7f0fcb
line wrap: on
line source

#!/usr/bin/env python
"""
filesystem template interpreter
"""

import os
import re
import shutil
import subprocess
import sys
import tempfile
import urllib
# TODO: may have to use urllib2.urlopen to get reasonable timeouts

from optparse import OptionParser

# URL of this file
location = 'http://k0s.org/mozilla/hg/MakeItSo/raw-file/tip/makeitso/makeitso.py'

# URL of tempita
tempita_location = 'http://bitbucket.org/ianb/tempita/raw-file/tip/tempita/'

def cleanup():
    # remove temporary net module directory
    if 'tempdir' in globals():
        shutil.remove(tempdir)

try:
    import tempita
except ImportError:

    # Get tempita from the net
    # TODO: abstract this to get arbitrary modules from the net
    def getFiles(url, subdir, files):
        """
        fetch files from the internet
        - url : base url
        - subdirectory: to put things in
        - files : list of files to retrieve
        returns the location of a temporary directory
        """
        globals()['tempdir'] = tempfile.mkdtemp()
        os.mkdir(subdir)
        url = url.rstrip('/')
        for filename in files:
            f, headers = urllib.urlretrive('%s/%s' % (url, filename))
            content = file(f).read()
            outfile = os.path.join(globals()['tempdir'], subdir, filename)
            o = file(outfile, 'w')
            print >> o, content
        return globals()['tempdir']

    tempita_files = ['__init__.py', '_looper.py', 'compat3.py']

    try:
        t = getFiles(tempita_location, 'tempita', tempita_files)
        sys.path.append(t)
        import tempita
    except:
        cleanup()
        raise NotImplementedError('This should say something like youre not connected to the net')



# regular expressions for finding the shebang
shebang_re = '#!.*makeitso.*'
shebang_re = re.compile(shebang_re)

class MissingVariablesException(Exception):
    def __init__(self, message, missing):
        self.missing = missing

def call(command, *args, **kw):
    code = subprocess.call(command, *args, **kw)
    if code:
        if isinstance(command, basestring):
            cmdstr = command
        else:
            cmdstr = ' '.join(command)
        raise SystemExit("Command `%s` exited with code %d" % (cmdstr, code))

def base_uri(uri):
    if '://' in uri:
        return 'uri'.rsplit('/', 1)[0] + '/'
    else:
        here = os.path.dirname(os.path.abspath('me'))
        here = here.rstrip(os.path.sep) + os.path.sep
        return here

def include(uri):
    f, headers = urllib.urlretrieve(uri)
    return file(f).read()

### functions for variables

defaults = {'include': include}

def get_missing(name_error):
    """
    This is a horrible hack because python doesn't do the proper thing
    via eval and return the name of the variable;  instead, it just gives
    you a message:
    >>> try:
    ...   eval('2*foo')
    ... except Exception, e:
    ...   pass
    """
    message = name_error.args[0]
    varname = message.split("'")[1]
    return varname

def missing_variables(template, variables):
    """return additional variables needed"""
    vars = variables.copy()
    missing = set([])
    while True:
        try:
            template.substitute(**vars)
            return missing
        except NameError, e:
            missed = get_missing(e)
            missing.add(missed)
            vars[missed] = ''
    return missing

def template_variables(template):
    """return the variables needed for a template"""
    return missing_variables(template, {})

def read_variables(variables):
    retval = {}
    for i in variables:
        print 'Enter %s: ' % i,
        retval[i] = raw_input()
    return retval

### functions for substitution
        
def substitute(content, variables=None):
    """interactive (for now) substitution"""

    # remove makeitso shebang if it has one
    if shebang_re.match(content):
        content = os.linesep.join(content.splitlines()[1:])

    variables = variables or defaults.copy()
    template = tempita.Template(content)
    missing = missing_variables(template, variables)
    if missing:
        # TODO: add a switch for interactive or not
        variables.update(read_variables(missing))
    return template.substitute(**variables)

def substitute_directory(directory, output=None, variables=None):
    # TODO: interpolate directory names

###

def invocation(url, **variables):
    """returns a string appropriate for TTW invocation"""
    variables_string = ' '.join(['%s=%s' % (i,j) for i,j in variables.items()])
    return 'python <(curl %s) %s %s' % (location, url, variables_string)

def main(args=sys.argv[1:]):

    # create option parser
    usage = '%prog [options] template <template> <...>'
    parser = OptionParser(usage, description=__doc__)

    # delimeters
    # XXX needs tempita trunk
    parser.add_option('-[', '--start-braces', dest='start_braces',
                      help='starting delimeter')
    parser.add_option('-]', '--end-braces', dest='end_braces',
                      help='starting delimeter')

    # options about where to put things
    parser.add_option('--in-place', dest='in_place',
                      action='store_true', default=False,
                      help='interpret files in place') # TODO: unused
    parser.add_option('-o', '--output', dest='output',
                      help='where to put the output (filename or directory)')

    # 
    parser.add_option('--commandline', dest='commandline',
                      action='store_true', default=False,
                      help="print the commandline to invoke this script TTW")
    parser.add_option('--variables', dest='variables',
                      action='store_true', default=False,
                      help='print the variables in a template')
    options, args = parser.parse_args(args)

    # print the variables for the templates
    if options.variables:

        # makes no sense without a template
        if not args:
            parser.print_usage()
            parser.exit()

        # find all variables
        variables = set()
        for arg in args:
            content = file(arg).read()
            template = tempita.Template(content)
            variables.update(template_variables(template))

        # print them
        for variable in variables:
            print variable
        return

    # template variables
    variables = defaults.copy()
    _vars = []
    _args = []
    for arg in args:
        if '=' in arg:
            key, value = arg.split('=')
            variables[key] = value
        else:
            _args.append(arg)
    args = _args

    # print TTW commandline for invocation
    if options.commandline:
        if args:
            for arg in args:
                print invocation(arg, **variables)
        else:
            print invocation('[URI]', **variables)
        return

    # get the content
    if args:
        for arg in args:
            var_copy = variables.copy()
            var_copy['here'] = base_uri(arg)
            content = include(arg)
            print substitute(content, variables=var_copy)
    else:
        content = sys.stdin.read()
        print substitute(content, variables=variables)

    # cleanup
    cleanup()
        
if __name__ == '__main__':
    main()