comparison 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
comparison
equal deleted inserted replaced
33:190f310f2f5e 34:46c2d0a7335a
67 67
68 # regular expressions for finding the shebang 68 # regular expressions for finding the shebang
69 shebang_re = '#!.*makeitso.*' 69 shebang_re = '#!.*makeitso.*'
70 shebang_re = re.compile(shebang_re) 70 shebang_re = re.compile(shebang_re)
71 71
72 def base_uri(uri): 72 ### URIs
73
74 def parent_uri(uri):
75 """parent resource of a URI"""
76
73 if '://' in uri: 77 if '://' in uri:
74 return 'uri'.rsplit('/', 1)[0] + '/' 78 return uri.rsplit('/', 1)[0] + '/'
75 else: 79 else:
76 here = os.path.dirname(os.path.abspath('me')) 80 here = os.path.dirname(os.path.abspath(uri))
77 here = here.rstrip(os.path.sep) + os.path.sep 81 here = here.rstrip(os.path.sep) + os.path.sep
78 return here 82 return here
83
84 def basename(uri):
85 """return the basename of a URI"""
86 if '://' in uri:
87 return uri.rsplit('/', 1)[1]
88 else:
89 return os.path.basename(uri)
79 90
80 def include(uri): 91 def include(uri):
81 f, headers = urllib.urlretrieve(uri) 92 f, headers = urllib.urlretrieve(uri)
82 return file(f).read() 93 return file(f).read()
83 94
84 ### things that deal with variables 95 ### things that deal with variables
85 96
86 # XXX duplicated in URITemplate namespace....don't need two
87 defaults = {'include': include}
88
89 class MissingVariablesException(Exception): 97 class MissingVariablesException(Exception):
90 """exception for (non-interactive) missing variables""" 98 """exception for (non-interactive) missing variables"""
91 def __init__(self, message, missing): 99 def __init__(self, missing):
92 self.missing = missing 100 self.missing = missing
101 message = 'Missing variables: %s' % ', '.join(missing)
102 Exception.__init__(self, message)
93 103
94 def get_missing(name_error): 104 def get_missing(name_error):
95 """ 105 """
96 This is a horrible hack because python doesn't do the proper thing 106 This is a horrible hack because python doesn't do the proper thing
97 via eval and return the name of the variable; instead, it just gives 107 via eval and return the name of the variable; instead, it just gives
107 117
108 ### template classes 118 ### template classes
109 119
110 class ContentTemplate(tempita.Template): 120 class ContentTemplate(tempita.Template):
111 """MakeItSo's extension of tempita's Template class""" 121 """MakeItSo's extension of tempita's Template class"""
122
112 defaults = {'include': include} 123 defaults = {'include': include}
113 def __init__(self, content, interactive=True): 124
114 tempita.Template.__init__(self, content) 125 def __init__(self, content, name=None, interactive=True, **variables):
126
127 # default variables
128 self.defaults = self.__class__.defaults.copy()
129 self.defaults.update(variables)
130
115 # TODO: automagically tell if the program is interactive or not 131 # TODO: automagically tell if the program is interactive or not
116 self.interactive = True 132 self.interactive = interactive
117 raise NotImplementedError 133
118 134 tempita.Template.__init__(self, content, name=name)
119 class URITemplate(tempita.Template): 135
120 136 def missing(self, **variables):
121 def __init__(self, interactive=True): 137 """return additional variables needed"""
122 raise NotImplementedError 138 vars = variables.copy()
123 139 missing = set([])
124 class DirectoryTemplate(tempita.Template): 140 while True:
125 def __init__(self): 141 try:
126 raise NotImplementedError 142 tempita.Template.substitute(self, **vars)
127 143 return missing
128 def missing_variables(template, variables): 144 except NameError, e:
129 """return additional variables needed""" 145 missed = get_missing(e)
130 vars = variables.copy() 146 missing.add(missed)
131 missing = set([]) 147 vars[missed] = ''
132 while True: 148 return missing
133 try: 149
134 template.substitute(**vars) 150 def variables(self):
135 return missing 151 """return the variables needed for a template"""
136 except NameError, e: 152 return self.missing()
137 missed = get_missing(e) 153
138 missing.add(missed) 154 def substitute(self, **variables):
139 vars[missed] = '' 155 """interactive (for now) substitution"""
140 return missing 156 vars = self.defaults.copy()
141 157 vars.update(variables)
142 def template_variables(template): 158 missing = self.missing(vars)
143 """return the variables needed for a template""" 159 if missing:
144 return missing_variables(template, {}) 160 if self.interactive:
145 161 vars.update(self.read_variables(missing))
146 def read_variables(variables): 162 else:
147 retval = {} 163 raise MissingVariablesException(missing)
148 for i in variables: 164 self._substitute(**vars)
149 print 'Enter %s: ' % i, 165
150 retval[i] = raw_input() 166 def _substitute(self, **variables):
151 return retval 167 return tempita.Template.substitute(self, **variables)
152 168
153 ### functions for substitution 169 def read_variables(self, variables):
154 170 """read variables from stdin"""
155 def substitute(content, variables=None): 171 # TODO: variables should (optionally) be richer objects
156 """interactive (for now) substitution""" 172 retval = {}
157 173 for i in variables:
158 # remove makeitso shebang if it has one 174 print 'Enter %s: ' % i,
159 if shebang_re.match(content): 175 retval[i] = raw_input()
160 content = os.linesep.join(content.splitlines()[1:]) 176 return retval
161 177
162 variables = variables or defaults.copy() 178
163 template = tempita.Template(content) 179 class URITemplate(ContentTemplate):
164 missing = missing_variables(template, variables) 180 """template for a file or URL"""
165 if missing: 181
166 # TODO: add a switch for interactive or not 182 def __init__(self, uri, output=None, interactive=True, **variables):
167 variables.update(read_variables(missing)) 183 self.output = output or sys.stdout
168 return template.substitute(**variables) 184
169 185 content = include(uri)
170 def substitute_directory(directory, output=None, variables=None): 186
171 # TODO: interpolate directory names 187 # remove makeitso shebang if it has one
172 188 if shebang_re.match(content):
173 ### 189 content = os.linesep.join(content.splitlines()[1:])
190
191 variables['here'] = parent_uri(uri)
192 ContentTemplate.__init__(self, content, name=uri,
193 interactive=interactive,
194 **variables)
195
196 def substitute(self, **variables):
197 output = ContentTemplate.substitute(self, **variables)
198 f = self.output
199 if isinstance(f, basestring):
200 if os.path.isdir(f):
201 f = os.path.join(f, basename(self.name))
202 f = file(f, 'w')
203 print >> f, output
204
205
206 class DirectoryTemplate(ContentTemplate):
207 """template for a directory structure"""
208
209 def __init__(self, directory, output=None, interactive=True, **variables):
210 """
211 - output : output directory; if None will render in place
212 """
213 assert os.path.isdir(directory)
214 self.name = directory
215 self.interactive = interactive
216 self.output = output
217 if output is not None:
218 if os.path.exists(output):
219 assert os.path.isdir(output), "%s: Must be a directory" % self.name
220 self.defaults = ContentTemplate.defaults.copy()
221 self.defaults.update(variables)
222
223
224 def missing(self, **variables):
225 variables = variables.copy()
226 missing = set([])
227 for dirpath, dirnames, filenames in os.walk(self.name):
228
229 # find variables from directory names
230 for d in dirnames:
231 missed = ContentTemplate(d).missing(**variables)
232 missing.update(missed)
233 variables.update(dict([(i, '') for i in missed]))
234
235 # find variables from files
236 for f in filenames:
237 path = os.path.join(dirpath, f)
238 template = URITemplate(path)
239 missed = template.missing(**variables)
240 missing.update(missed)
241 variables.update(dict([(i, '') for i in missed]))
242
243 return missing
244
245 def _substitute(self, **variables):
246
247 # make output directory if necessary
248 output = self.output
249 if output and not os.path.exists(output):
250 os.makedirs(output)
251
252 for dirname, dirnames, filenames in os.walk(self.name):
253
254 # interpolate directory names
255 for d in dirnames:
256 path = os.path.join(dirname, interpolated)
257 interpolated = ContentTemplate(path).substitute(**variables)
258 if os.path.exists(interpolated):
259 # ensure its a directory
260 pass
261 else:
262 os.makedirs(interpolated)
263
264
265 class PolyTemplate(ContentTemplate):
266 """
267 template for several files/directories
268 """
269
270 def __init__(self, templates, output=None, interactive=True, **variables):
271
272 assert templates, "No templates given!"
273
274 self.templates = []
275 self.output = output
276 for template in templates:
277 if os.path.isdir(template):
278 self.templates.append(DirectoryTemplate(template, output=output, **variables))
279 else:
280 self.templates.append(URITemplate(template, output=output, **variables))
281
282 def missing(self, **variables):
283 vars = variables.copy()
284 missing = set([])
285 for template in self.templates:
286 missed = template.missing(**vars)
287 missing.update(missed)
288 vars.update(dict([(i, '') for i in missed]))
289 return missing
290
291 def _substitute(self, **variables):
292
293 # determine where the hell to put these things
294 if self.output is None:
295 dirs = [i for i in templates if os.path.isdir(i)]
296 if not ((len(dirs) == 0) or len(dirs) == len(templates)):
297 raise AssertionError("Must specify output when mixing directories and URIs")
298
299 # TODO: check for missing
300 if len(self.templates) > 1 and not os.path.exists(self.output):
301 os.makedirs(self.output)
302 for template in self.templates:
303 template.substitute(**variables)
304
305 ### command line stuff
174 306
175 def invocation(url, **variables): 307 def invocation(url, **variables):
176 """returns a string appropriate for TTW invocation""" 308 """returns a string appropriate for TTW invocation"""
177 variables_string = ' '.join(['%s=%s' % (i,j) for i,j in variables.items()]) 309 variables_string = ' '.join(['%s=%s' % (i,j) for i,j in variables.items()])
178 return 'python <(curl %s) %s %s' % (location, url, variables_string) 310 return 'python <(curl %s) %s %s' % (location, url, variables_string)
190 help='starting delimeter') 322 help='starting delimeter')
191 parser.add_option('-]', '--end-braces', dest='end_braces', 323 parser.add_option('-]', '--end-braces', dest='end_braces',
192 help='starting delimeter') 324 help='starting delimeter')
193 325
194 # options about where to put things 326 # options about where to put things
195 parser.add_option('--in-place', dest='in_place',
196 action='store_true', default=False,
197 help='interpret files in place') # TODO: unused
198 parser.add_option('-o', '--output', dest='output', 327 parser.add_option('-o', '--output', dest='output',
199 help='where to put the output (filename or directory)') 328 help='where to put the output (filename or directory)')
200 329
201 # 330 # options for getting information
202 parser.add_option('--commandline', dest='commandline', 331 parser.add_option('--commandline', dest='commandline',
203 action='store_true', default=False, 332 action='store_true', default=False,
204 help="print the commandline to invoke this script TTW") 333 help="print the commandline to invoke this script TTW")
205 parser.add_option('--variables', dest='variables', 334 parser.add_option('--variables', dest='variables',
206 action='store_true', default=False, 335 action='store_true', default=False,
207 help='print the variables in a template') 336 help='print the variables in a template')
337
208 options, args = parser.parse_args(args) 338 options, args = parser.parse_args(args)
209 339
210 # print the variables for the templates 340 # print the variables for the templates
211 if options.variables: 341 if options.variables:
212 342
214 if not args: 344 if not args:
215 parser.print_usage() 345 parser.print_usage()
216 parser.exit() 346 parser.exit()
217 347
218 # find all variables 348 # find all variables
219 variables = set() 349 template = PolyTemplate(templates=args)
220 for arg in args: 350 variables = template.variables()
221 content = file(arg).read()
222 template = tempita.Template(content)
223 variables.update(template_variables(template))
224 351
225 # print them 352 # print them
226 for variable in variables: 353 for variable in sorted(variables):
227 print variable 354 print variable
228 return 355 return
229 356
230 # template variables 357 # template variables
231 variables = defaults.copy()
232 _vars = [] 358 _vars = []
233 _args = [] 359 _args = []
234 for arg in args: 360 for arg in args:
235 if '=' in arg: 361 if '=' in arg:
236 key, value = arg.split('=') 362 key, value = arg.split('=')
248 print invocation('[URI]', **variables) 374 print invocation('[URI]', **variables)
249 return 375 return
250 376
251 # get the content 377 # get the content
252 if args: 378 if args:
253 for arg in args: 379 template = PolyTemplate(templates=args,
254 var_copy = variables.copy() 380 output=options.output,
255 var_copy['here'] = base_uri(arg) 381 variables=variables)
256 content = include(arg)
257 print substitute(content, variables=var_copy)
258 else: 382 else:
383 template = ContentTemplate(sys.stdin.read(), variables=variables)
259 content = sys.stdin.read() 384 content = sys.stdin.read()
260 print substitute(content, variables=variables) 385
261 386
262 # cleanup 387 # cleanup
263 cleanup() 388 cleanup()
264 389
265 if __name__ == '__main__': 390 if __name__ == '__main__':