def generate(self, softpkg, *filenames): loader = self.loader(softpkg) # Map the component model into a language-specific version component = self.map(softpkg) generated = [] skipped = [] # Note all tracked files, for cleaning up stale files; if a file list # was given, only consider those files for deletion. stale = set(self.trackedFiles()) if filenames: stale.intersection_update(filenames) for template in self.templates(component): # Mark the current template as seen so it will not be deleted, # regardless of whether it gets generated if template.filename in stale: stale.remove(template.filename) # If a file list was given, skip files not explicitly listed. if filenames and template.filename not in filenames: continue filename = os.path.join(self.outputdir, template.filename) if os.path.exists(filename): # Check if the file has been modified since last generation. if self.fileChanged(template.filename, template.userfile) and not self.overwrite: skipped.append((template.filename, 'overwrite')) continue action = '' else: action = '(added)' # Attempt to ensure that the full required path exists parentdir = os.path.dirname(filename) if parentdir and not os.path.isdir(parentdir): os.makedirs(parentdir) env = CodegenEnvironment(loader=loader, **template.options()) env.filters.update(template.filters()) tmpl = env.get_template(template.template) # Initially, write the output to a temporary file to avoid trashing # the original file if the template is malformed with tempfile.NamedTemporaryFile() as outfile: # Start with the template-specific context, then add the mapped # component and a reference to this generator with known names. context = template.context() context['component'] = component context['generator'] = self context['versions'] = versions # Evaluate the template in streaming mode (rather than all at # once) gen = tmpl.generate(**context) if self.header: # Define a generator function to insert the header at the # top of the file def generate(gen, header): first = True for chunk in gen: if first: # Take "shebang" into account for executable # scripts if chunk.startswith('#!'): line, chunk = chunk.split('\n', 1) yield line + '\n' yield header first = False yield chunk # Wrap the template's generator with the header insertion # generator gen = generate(gen, template.comment(self.header)) # Write the stream to the output file for chunk in gen: outfile.write(chunk) # Add a trailing newline to work around a Jinja bug. outfile.write('\n') # Now that generation has succeeded, flush the temporary file # to ensure the contents are completer, and copy to the target # location outfile.file.flush() shutil.copy(outfile.name, filename) # Set the executable bit, if requested by the template. if template.executable: st = os.stat(filename) os.chmod(filename, st.st_mode|stat.S_IEXEC) generated.append((template.filename, action)) # Update the MD5 digest self.md5sums[template.filename] = utils.fileMD5(filename) # Remove old files that were not (and would not have been) generated on # this pass, and are unchanged. for existing in stale: filename = os.path.join(self.outputdir, existing) if not os.path.exists(filename): continue # Check for changes, and require explicit action to remove. if self.fileChanged(existing) and not self.overwrite: skipped.append((existing, 'delete')) continue # Delete the file, and remove its MD5 sum (if it has one). os.unlink(filename) generated.append((existing, '(deleted)')) if existing in self.md5sums: del self.md5sums[existing] # Save updated MD5 digests # NB: To work with "md5sum -c", there must be two spaces between the # MD5 digest and the filename. md5out = open(self.md5file, 'w') for name, digest in self.md5sums.items(): print >>md5out, "%s %s" % (digest, name) md5out.close() return generated, skipped
def get_template(self, template): loader = self.loader() env = CodegenEnvironment(loader=loader, **template.options()) env.filters.update(template.filters()) return env.get_template(template.template)
def generate(self, softpkg, *filenames): if not os.path.exists(self.outputdir): os.mkdir(self.outputdir) loader = self.loader(softpkg) # Map the component model into a language-specific version component = self.map(softpkg) generated = [] skipped = [] # Note all tracked files, for cleaning up stale files; if a file list # was given, only consider those files for deletion. stale = set(self.trackedFiles()) if filenames: stale.intersection_update(filenames) for template in self.templates(component): # Mark the current template as seen so it will not be deleted, # regardless of whether it gets generated if template.filename in stale: stale.remove(template.filename) # If a file list was given, skip files not explicitly listed. if filenames and template.filename not in filenames: continue filename = os.path.join(self.outputdir, template.filename) if os.path.exists(filename): # Check if the file has been modified since last generation. if self.fileChanged(template.filename) and not self.overwrite: skipped.append((template.filename, 'overwrite')) continue action = '' else: action = '(added)' # Attempt to ensure that the full required path exists for files # that are more deeply nested. if not os.path.isdir(os.path.dirname(filename)): os.makedirs(os.path.dirname(filename)) env = CodegenEnvironment(loader=loader, **template.options()) env.filters.update(template.filters()) tmpl = env.get_template(template.template) outfile = open(filename, 'w') try: # Start with the template-specific context, then add the mapped # component and a reference to this generator with known names. context = template.context() context['component'] = component context['generator'] = self # Evaluate the template in streaming mode (rather than all at # once), dumping to the output file. tmpl.stream(**context).dump(outfile) # Add a trailing newline to work around a Jinja bug. outfile.write('\n') # Set the executable bit, if requested by the template. if template.executable: fd = outfile.fileno() st = os.fstat(fd) os.chmod(filename, st.st_mode|stat.S_IEXEC) finally: outfile.close() generated.append((template.filename, action)) # Update the MD5 digest self.md5sums[template.filename] = utils.fileMD5(filename) # Remove old files that were not (and would not have been) generated on # this pass, and are unchanged. for existing in stale: filename = os.path.join(self.outputdir, existing) if not os.path.exists(filename): continue # Check for changes, and require explicit action to remove. if self.fileChanged(existing) and not self.overwrite: skipped.append((existing, 'delete')) continue # Delete the file, and remove its MD5 sum (if it has one). os.unlink(filename) generated.append((existing, '(deleted)')) if existing in self.md5sums: del self.md5sums[existing] # Save updated MD5 digests md5out = open(self.md5file, 'w') for name, digest in self.md5sums.items(): print >>md5out, "%s %s" % (digest, name) md5out.close() return generated, skipped
def generate(self, softpkg, *filenames): loader = self.loader(softpkg) # Map the component model into a language-specific version component = self.map(softpkg) generated = [] skipped = [] # Note all tracked files, for cleaning up stale files; if a file list # was given, only consider those files for deletion. stale = set(self.trackedFiles()) if filenames: stale.intersection_update(filenames) for template in self.templates(component): # Mark the current template as seen so it will not be deleted, # regardless of whether it gets generated if template.filename in stale: stale.remove(template.filename) # If a file list was given, skip files not explicitly listed. if filenames and template.filename not in filenames: continue filename = os.path.join(self.outputdir, template.filename) if os.path.exists(filename): # Check if the file has been modified since last generation. if self.fileChanged(template.filename, template.userfile) and not self.overwrite: skipped.append((template.filename, 'overwrite')) continue action = '' else: action = '(added)' # Attempt to ensure that the full required path exists parentdir = os.path.dirname(filename) if parentdir and not os.path.isdir(parentdir): os.makedirs(parentdir) env = CodegenEnvironment(loader=loader, **template.options()) env.filters.update(template.filters()) tmpl = env.get_template(template.template) outfile = open(filename, 'w') try: # Start with the template-specific context, then add the mapped # component and a reference to this generator with known names. context = template.context() context['component'] = component context['generator'] = self context['versions'] = versions # Evaluate the template in streaming mode (rather than all at # once), dumping to the output file. tmpl.stream(**context).dump(outfile) # Add a trailing newline to work around a Jinja bug. outfile.write('\n') # Set the executable bit, if requested by the template. if template.executable: fd = outfile.fileno() st = os.fstat(fd) os.chmod(filename, st.st_mode|stat.S_IEXEC) finally: outfile.close() generated.append((template.filename, action)) # Update the MD5 digest self.md5sums[template.filename] = utils.fileMD5(filename) # Remove old files that were not (and would not have been) generated on # this pass, and are unchanged. for existing in stale: filename = os.path.join(self.outputdir, existing) if not os.path.exists(filename): continue # Check for changes, and require explicit action to remove. if self.fileChanged(existing) and not self.overwrite: skipped.append((existing, 'delete')) continue # Delete the file, and remove its MD5 sum (if it has one). os.unlink(filename) generated.append((existing, '(deleted)')) if existing in self.md5sums: del self.md5sums[existing] # Save updated MD5 digests # NB: To work with "md5sum -c", there must be two spaces between the # MD5 digest and the filename. md5out = open(self.md5file, 'w') for name, digest in self.md5sums.items(): print >>md5out, "%s %s" % (digest, name) md5out.close() return generated, skipped