diff makeitso/makeitso.py @ 34:46c2d0a7335a

continued refactor to have template classes
author Jeff Hammel <jhammel@mozilla.com>
date Sat, 01 Jan 2011 21:21:53 -0800
parents 190f310f2f5e
children 7e47ff4b0cd3
line wrap: on
line diff
--- a/makeitso/makeitso.py	Wed Dec 22 18:51:08 2010 -0800
+++ b/makeitso/makeitso.py	Sat Jan 01 21:21:53 2011 -0800
@@ -69,27 +69,37 @@
 shebang_re = '#!.*makeitso.*'
 shebang_re = re.compile(shebang_re)
 
-def base_uri(uri):
+### URIs
+
+def parent_uri(uri):
+    """parent resource of a URI"""
+    
     if '://' in uri:
-        return 'uri'.rsplit('/', 1)[0] + '/'
+        return uri.rsplit('/', 1)[0] + '/'
     else:
-        here = os.path.dirname(os.path.abspath('me'))
+        here = os.path.dirname(os.path.abspath(uri))
         here = here.rstrip(os.path.sep) + os.path.sep
         return here
 
+def basename(uri):
+    """return the basename of a URI"""
+    if '://' in uri:
+        return uri.rsplit('/', 1)[1]
+    else:
+        return os.path.basename(uri)
+
 def include(uri):
     f, headers = urllib.urlretrieve(uri)
     return file(f).read()
 
 ### things that deal with variables
 
-# XXX duplicated in URITemplate namespace....don't need two
-defaults = {'include': include}
-
 class MissingVariablesException(Exception):
     """exception for (non-interactive) missing variables"""
-    def __init__(self, message, missing):
+    def __init__(self, missing):
         self.missing = missing
+        message = 'Missing variables: %s' % ', '.join(missing)
+        Exception.__init__(self, message)
 
 def get_missing(name_error):
     """
@@ -109,68 +119,190 @@
 
 class ContentTemplate(tempita.Template):
     """MakeItSo's extension of tempita's Template class"""
+    
     defaults = {'include': include}
-    def __init__(self, content, interactive=True):
-        tempita.Template.__init__(self, content)
+
+    def __init__(self, content, name=None, interactive=True, **variables):
+
+        # default variables
+        self.defaults = self.__class__.defaults.copy()
+        self.defaults.update(variables)
+
         # TODO: automagically tell if the program is interactive or not
-        self.interactive = True
-        raise NotImplementedError
+        self.interactive = interactive
+        
+        tempita.Template.__init__(self, content, name=name)
 
-class URITemplate(tempita.Template):
-
-    def __init__(self, interactive=True):
-        raise NotImplementedError
+    def missing(self, **variables):
+        """return additional variables needed"""
+        vars = variables.copy()
+        missing = set([])
+        while True:
+            try:
+                tempita.Template.substitute(self, **vars)
+                return missing
+            except NameError, e:
+                missed = get_missing(e)
+                missing.add(missed)
+                vars[missed] = ''
+        return missing
+    
+    def variables(self):
+        """return the variables needed for a template"""
+        return self.missing()
 
-class DirectoryTemplate(tempita.Template):
-    def __init__(self):
-        raise NotImplementedError
+    def substitute(self, **variables):
+        """interactive (for now) substitution"""
+        vars = self.defaults.copy()
+        vars.update(variables)
+        missing = self.missing(vars)
+        if missing:
+            if self.interactive:
+                vars.update(self.read_variables(missing))
+            else:
+                raise MissingVariablesException(missing)
+        self._substitute(**vars)
+
+    def _substitute(self, **variables):
+        return tempita.Template.substitute(self, **variables)
+
+    def read_variables(self, variables):
+        """read variables from stdin"""
+        # TODO: variables should (optionally) be richer objects
+        retval = {}
+        for i in variables:
+            print 'Enter %s: ' % i,
+            retval[i] = raw_input()
+        return retval
+
 
-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
+class URITemplate(ContentTemplate):
+    """template for a file or URL"""
+
+    def __init__(self, uri, output=None, interactive=True, **variables):
+        self.output = output or sys.stdout
+        
+        content = include(uri)
+        
+        # remove makeitso shebang if it has one
+        if shebang_re.match(content):
+            content = os.linesep.join(content.splitlines()[1:])
+
+        variables['here'] = parent_uri(uri)        
+        ContentTemplate.__init__(self, content, name=uri,
+                                 interactive=interactive,
+                                 **variables)
+
+    def substitute(self, **variables):
+        output = ContentTemplate.substitute(self, **variables)
+        f = self.output
+        if isinstance(f, basestring):
+            if os.path.isdir(f):
+                f = os.path.join(f, basename(self.name))
+            f = file(f, 'w')
+        print >> f, output
+            
 
-def template_variables(template):
-    """return the variables needed for a template"""
-    return missing_variables(template, {})
+class DirectoryTemplate(ContentTemplate):
+    """template for a directory structure"""
+    
+    def __init__(self, directory, output=None, interactive=True, **variables):
+        """
+        - output : output directory; if None will render in place
+        """
+        assert os.path.isdir(directory)
+        self.name = directory
+        self.interactive = interactive
+        self.output = output
+        if output is not None:
+            if os.path.exists(output):
+                assert os.path.isdir(output), "%s: Must be a directory" % self.name
+        self.defaults = ContentTemplate.defaults.copy()
+        self.defaults.update(variables)
+
 
-def read_variables(variables):
-    retval = {}
-    for i in variables:
-        print 'Enter %s: ' % i,
-        retval[i] = raw_input()
-    return retval
+    def missing(self, **variables):
+        variables = variables.copy()
+        missing = set([])
+        for dirpath, dirnames, filenames in os.walk(self.name):
+
+            # find variables from directory names
+            for d in dirnames:
+                missed = ContentTemplate(d).missing(**variables)
+                missing.update(missed)
+                variables.update(dict([(i, '') for i in missed]))
 
-### functions for substitution
-        
-def substitute(content, variables=None):
-    """interactive (for now) substitution"""
+            # find variables from files
+            for f in filenames:
+                path = os.path.join(dirpath, f)
+                template = URITemplate(path)
+                missed = template.missing(**variables)
+                missing.update(missed)
+                variables.update(dict([(i, '') for i in missed]))
+
+        return missing
+
+    def _substitute(self, **variables):
 
-    # remove makeitso shebang if it has one
-    if shebang_re.match(content):
-        content = os.linesep.join(content.splitlines()[1:])
+        # make output directory if necessary
+        output = self.output
+        if output and not os.path.exists(output):
+            os.makedirs(output)
+            
+        for dirname, dirnames, filenames in os.walk(self.name):
+            
+            # interpolate directory names
+            for d in dirnames:
+                path = os.path.join(dirname, interpolated)
+                interpolated = ContentTemplate(path).substitute(**variables)
+                if os.path.exists(interpolated):
+                    # ensure its a directory
+                    pass
+                else:
+                    os.makedirs(interpolated)
+                
+
+class PolyTemplate(ContentTemplate):
+    """
+    template for several files/directories
+    """
+
+    def __init__(self, templates, output=None, interactive=True, **variables):
 
-    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)
+        assert templates, "No templates given!"
+            
+        self.templates = []
+        self.output = output
+        for template in templates:
+            if os.path.isdir(template):
+                self.templates.append(DirectoryTemplate(template, output=output, **variables))
+            else:
+                self.templates.append(URITemplate(template, output=output, **variables))
 
-def substitute_directory(directory, output=None, variables=None):
-    # TODO: interpolate directory names
+    def missing(self, **variables):
+        vars = variables.copy()
+        missing = set([])
+        for template in self.templates:
+            missed = template.missing(**vars)
+            missing.update(missed)
+            vars.update(dict([(i, '') for i in missed]))
+        return missing
+
+    def _substitute(self, **variables):
 
-###
+        # determine where the hell to put these things
+        if self.output is None:
+            dirs = [i for i in templates if os.path.isdir(i)]
+            if not ((len(dirs) == 0) or len(dirs) == len(templates)):
+                raise AssertionError("Must specify output when mixing directories and URIs")
+        
+        # TODO: check for missing
+        if len(self.templates) > 1 and not os.path.exists(self.output):
+            os.makedirs(self.output)
+        for template in self.templates:
+            template.substitute(**variables)
+        
+### command line stuff
 
 def invocation(url, **variables):
     """returns a string appropriate for TTW invocation"""
@@ -192,19 +324,17 @@
                           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)')
 
-    # 
+    # options for getting information
     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
@@ -216,19 +346,15 @@
             parser.exit()
 
         # find all variables
-        variables = set()
-        for arg in args:
-            content = file(arg).read()
-            template = tempita.Template(content)
-            variables.update(template_variables(template))
+        template = PolyTemplate(templates=args)
+        variables = template.variables()
 
         # print them
-        for variable in variables:
+        for variable in sorted(variables):
             print variable
         return
 
     # template variables
-    variables = defaults.copy()
     _vars = []
     _args = []
     for arg in args:
@@ -250,14 +376,13 @@
 
     # 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)
+        template = PolyTemplate(templates=args,
+                                output=options.output,
+                                variables=variables)
     else:
+        template = ContentTemplate(sys.stdin.read(), variables=variables)
         content = sys.stdin.read()
-        print substitute(content, variables=variables)
+                            
 
     # cleanup
     cleanup()