# HG changeset patch # User Jeff Hammel # Date 1293945713 28800 # Node ID 46c2d0a7335af5edff9e855b3b1de59475d77249 # Parent 190f310f2f5e97e76f88b76d416d87c7feb46802 continued refactor to have template classes diff -r 190f310f2f5e -r 46c2d0a7335a examples/directory-example/foo.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/examples/directory-example/foo.txt Sat Jan 01 21:21:53 2011 -0800 @@ -0,0 +1,1 @@ +{{foo}} is a wonderful {{bar}} diff -r 190f310f2f5e -r 46c2d0a7335a makeitso/makeitso.py --- 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()