| [3] | 1 | # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) | 
|---|
 | 2 | # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php | 
|---|
 | 3 | import re | 
|---|
 | 4 | import sys | 
|---|
 | 5 | import os | 
|---|
 | 6 | import pkg_resources | 
|---|
 | 7 | from command import Command, BadCommand | 
|---|
 | 8 | import copydir | 
|---|
 | 9 | import pluginlib | 
|---|
 | 10 | import fnmatch | 
|---|
 | 11 | try: | 
|---|
 | 12 |     set | 
|---|
 | 13 | except NameError: | 
|---|
 | 14 |     from sets import Set as set | 
|---|
 | 15 |  | 
|---|
 | 16 | class CreateDistroCommand(Command): | 
|---|
 | 17 |  | 
|---|
 | 18 |     usage = 'PACKAGE_NAME [VAR=VALUE VAR2=VALUE2 ...]' | 
|---|
 | 19 |     summary = "Create the file layout for a Python distribution" | 
|---|
 | 20 |     short_description = summary | 
|---|
 | 21 |  | 
|---|
 | 22 |     description = """\ | 
|---|
 | 23 |     Create a new project.  Projects are typically Python packages, | 
|---|
 | 24 |     ready for distribution.  Projects are created from templates, and | 
|---|
 | 25 |     represent different kinds of projects -- associated with a | 
|---|
 | 26 |     particular framework for instance. | 
|---|
 | 27 |     """ | 
|---|
 | 28 |  | 
|---|
 | 29 |     parser = Command.standard_parser( | 
|---|
 | 30 |         simulate=True, no_interactive=True, quiet=True, overwrite=True) | 
|---|
 | 31 |     parser.add_option('-t', '--template', | 
|---|
 | 32 |                       dest='templates', | 
|---|
 | 33 |                       metavar='TEMPLATE', | 
|---|
 | 34 |                       action='append', | 
|---|
 | 35 |                       help="Add a template to the create process") | 
|---|
 | 36 |     parser.add_option('-o', '--output-dir', | 
|---|
 | 37 |                       dest='output_dir', | 
|---|
 | 38 |                       metavar='DIR', | 
|---|
 | 39 |                       default='.', | 
|---|
 | 40 |                       help="Write put the directory into DIR (default current directory)") | 
|---|
 | 41 |     parser.add_option('--svn-repository', | 
|---|
 | 42 |                       dest='svn_repository', | 
|---|
 | 43 |                       metavar='REPOS', | 
|---|
 | 44 |                       help="Create package at given repository location (this will create the standard trunk/ tags/ branches/ hierarchy)") | 
|---|
 | 45 |     parser.add_option('--list-templates', | 
|---|
 | 46 |                       dest='list_templates', | 
|---|
 | 47 |                       action='store_true', | 
|---|
 | 48 |                       help="List all templates available") | 
|---|
 | 49 |     parser.add_option('--list-variables', | 
|---|
 | 50 |                       dest="list_variables", | 
|---|
 | 51 |                       action="store_true", | 
|---|
 | 52 |                       help="List all variables expected by the given template (does not create a package)") | 
|---|
 | 53 |     parser.add_option('--inspect-files', | 
|---|
 | 54 |                       dest='inspect_files', | 
|---|
 | 55 |                       action='store_true', | 
|---|
 | 56 |                       help="Show where the files in the given (already created) directory came from (useful when using multiple templates)") | 
|---|
 | 57 |     parser.add_option('--config', | 
|---|
 | 58 |                       action='store', | 
|---|
 | 59 |                       dest='config', | 
|---|
 | 60 |                       help="Template variables file") | 
|---|
 | 61 |  | 
|---|
 | 62 |     _bad_chars_re = re.compile('[^a-zA-Z0-9_]') | 
|---|
 | 63 |  | 
|---|
 | 64 |     default_verbosity = 1 | 
|---|
 | 65 |     default_interactive = 1 | 
|---|
 | 66 |  | 
|---|
 | 67 |     def command(self): | 
|---|
 | 68 |         if self.options.list_templates: | 
|---|
 | 69 |             return self.list_templates() | 
|---|
 | 70 |         asked_tmpls = self.options.templates or ['basic_package'] | 
|---|
 | 71 |         templates = [] | 
|---|
 | 72 |         for tmpl_name in asked_tmpls: | 
|---|
 | 73 |             self.extend_templates(templates, tmpl_name) | 
|---|
 | 74 |         if self.options.list_variables: | 
|---|
 | 75 |             return self.list_variables(templates) | 
|---|
 | 76 |         if self.verbose: | 
|---|
 | 77 |             print 'Selected and implied templates:' | 
|---|
 | 78 |             max_tmpl_name = max([len(tmpl_name) for tmpl_name, tmpl in templates]) | 
|---|
 | 79 |             for tmpl_name, tmpl in templates: | 
|---|
 | 80 |                 print '  %s%s  %s' % ( | 
|---|
 | 81 |                     tmpl_name, ' '*(max_tmpl_name-len(tmpl_name)), | 
|---|
 | 82 |                     tmpl.summary) | 
|---|
 | 83 |             print | 
|---|
 | 84 |         if not self.args: | 
|---|
 | 85 |             if self.interactive: | 
|---|
 | 86 |                 dist_name = self.challenge('Enter project name') | 
|---|
 | 87 |             else: | 
|---|
 | 88 |                 raise BadCommand('You must provide a PACKAGE_NAME') | 
|---|
 | 89 |         else: | 
|---|
 | 90 |             dist_name = self.args[0].lstrip(os.path.sep) | 
|---|
 | 91 |  | 
|---|
 | 92 |         templates = [tmpl for name, tmpl in templates] | 
|---|
 | 93 |         output_dir = os.path.join(self.options.output_dir, dist_name) | 
|---|
 | 94 |          | 
|---|
 | 95 |         pkg_name = self._bad_chars_re.sub('', dist_name.lower()) | 
|---|
 | 96 |         vars = {'project': dist_name, | 
|---|
 | 97 |                 'package': pkg_name, | 
|---|
 | 98 |                 'egg': pluginlib.egg_name(dist_name), | 
|---|
 | 99 |                 } | 
|---|
 | 100 |         vars.update(self.parse_vars(self.args[1:])) | 
|---|
 | 101 |         if self.options.config and os.path.exists(self.options.config): | 
|---|
 | 102 |             for key, value in self.read_vars(self.options.config).items(): | 
|---|
 | 103 |                 vars.setdefault(key, value) | 
|---|
 | 104 |          | 
|---|
 | 105 |         if self.verbose: # @@: > 1? | 
|---|
 | 106 |             self.display_vars(vars) | 
|---|
 | 107 |  | 
|---|
 | 108 |         if self.options.inspect_files: | 
|---|
 | 109 |             self.inspect_files( | 
|---|
 | 110 |                 output_dir, templates, vars) | 
|---|
 | 111 |             return | 
|---|
 | 112 |         if not os.path.exists(output_dir): | 
|---|
 | 113 |             # We want to avoid asking questions in copydir if the path | 
|---|
 | 114 |             # doesn't exist yet | 
|---|
 | 115 |             copydir.all_answer = 'y' | 
|---|
 | 116 |          | 
|---|
 | 117 |         if self.options.svn_repository: | 
|---|
 | 118 |             self.setup_svn_repository(output_dir, dist_name) | 
|---|
 | 119 |  | 
|---|
 | 120 |         # First we want to make sure all the templates get a chance to | 
|---|
 | 121 |         # set their variables, all at once, with the most specialized | 
|---|
 | 122 |         # template going first (the last template is the most | 
|---|
 | 123 |         # specialized)... | 
|---|
 | 124 |         for template in templates[::-1]: | 
|---|
 | 125 |             vars = template.check_vars(vars, self) | 
|---|
 | 126 |  | 
|---|
 | 127 |         # Gather all the templates egg_plugins into one var | 
|---|
 | 128 |         egg_plugins = set() | 
|---|
 | 129 |         for template in templates: | 
|---|
 | 130 |             egg_plugins.update(template.egg_plugins) | 
|---|
 | 131 |         egg_plugins = list(egg_plugins) | 
|---|
 | 132 |         egg_plugins.sort() | 
|---|
 | 133 |         vars['egg_plugins'] = egg_plugins | 
|---|
 | 134 |              | 
|---|
 | 135 |         for template in templates: | 
|---|
 | 136 |             self.create_template( | 
|---|
 | 137 |                 template, output_dir, vars) | 
|---|
 | 138 |  | 
|---|
 | 139 |         found_setup_py = False | 
|---|
 | 140 |         paster_plugins_mtime = None | 
|---|
 | 141 |         if os.path.exists(os.path.join(output_dir, 'setup.py')): | 
|---|
 | 142 |             # Grab paster_plugins.txt's mtime; used to determine if the | 
|---|
 | 143 |             # egg_info command wrote to it | 
|---|
 | 144 |             try: | 
|---|
 | 145 |                 egg_info_dir = pluginlib.egg_info_dir(output_dir, dist_name) | 
|---|
 | 146 |             except IOError: | 
|---|
 | 147 |                 egg_info_dir = None | 
|---|
 | 148 |             if egg_info_dir is not None: | 
|---|
 | 149 |                 plugins_path = os.path.join(egg_info_dir, 'paster_plugins.txt') | 
|---|
 | 150 |                 if os.path.exists(plugins_path): | 
|---|
 | 151 |                     paster_plugins_mtime = os.path.getmtime(plugins_path) | 
|---|
 | 152 |  | 
|---|
 | 153 |             self.run_command(sys.executable, 'setup.py', 'egg_info', | 
|---|
 | 154 |                              cwd=output_dir, | 
|---|
 | 155 |                              # This shouldn't be necessary, but a bug in setuptools 0.6c3 is causing a (not entirely fatal) problem that I don't want to fix right now: | 
|---|
 | 156 |                              expect_returncode=True) | 
|---|
 | 157 |             found_setup_py = True | 
|---|
 | 158 |         elif self.verbose > 1: | 
|---|
 | 159 |             print 'No setup.py (cannot run egg_info)' | 
|---|
 | 160 |  | 
|---|
 | 161 |         package_dir = vars.get('package_dir', None) | 
|---|
 | 162 |         if package_dir: | 
|---|
 | 163 |             output_dir = os.path.join(output_dir, package_dir) | 
|---|
 | 164 |  | 
|---|
 | 165 |         # With no setup.py this doesn't make sense: | 
|---|
 | 166 |         if found_setup_py: | 
|---|
 | 167 |             # Only write paster_plugins.txt if it wasn't written by | 
|---|
 | 168 |             # egg_info (the correct way). leaving us to do it is | 
|---|
 | 169 |             # deprecated and you'll get warned | 
|---|
 | 170 |             egg_info_dir = pluginlib.egg_info_dir(output_dir, dist_name) | 
|---|
 | 171 |             plugins_path = os.path.join(egg_info_dir, 'paster_plugins.txt') | 
|---|
 | 172 |             if len(egg_plugins) and (not os.path.exists(plugins_path) or \ | 
|---|
 | 173 |                     os.path.getmtime(plugins_path) == paster_plugins_mtime): | 
|---|
 | 174 |                 if self.verbose: | 
|---|
 | 175 |                     print >> sys.stderr, \ | 
|---|
 | 176 |                         ('Manually creating paster_plugins.txt (deprecated! ' | 
|---|
 | 177 |                          'pass a paster_plugins keyword to setup() instead)') | 
|---|
 | 178 |                 for plugin in egg_plugins: | 
|---|
 | 179 |                     if self.verbose: | 
|---|
 | 180 |                         print 'Adding %s to paster_plugins.txt' % plugin | 
|---|
 | 181 |                     if not self.simulate: | 
|---|
 | 182 |                         pluginlib.add_plugin(egg_info_dir, plugin) | 
|---|
 | 183 |          | 
|---|
 | 184 |         if self.options.svn_repository: | 
|---|
 | 185 |             self.add_svn_repository(vars, output_dir) | 
|---|
 | 186 |  | 
|---|
 | 187 |         if self.options.config: | 
|---|
 | 188 |             write_vars = vars.copy() | 
|---|
 | 189 |             del write_vars['project'] | 
|---|
 | 190 |             del write_vars['package'] | 
|---|
 | 191 |             self.write_vars(self.options.config, write_vars) | 
|---|
 | 192 |          | 
|---|
 | 193 |     def create_template(self, template, output_dir, vars): | 
|---|
 | 194 |         if self.verbose: | 
|---|
 | 195 |             print 'Creating template %s' % template.name | 
|---|
 | 196 |         template.run(self, output_dir, vars) | 
|---|
 | 197 |  | 
|---|
 | 198 |     def setup_svn_repository(self, output_dir, dist_name): | 
|---|
 | 199 |         # @@: Use subprocess | 
|---|
 | 200 |         svn_repos = self.options.svn_repository | 
|---|
 | 201 |         svn_repos_path = os.path.join(svn_repos, dist_name).replace('\\','/') | 
|---|
 | 202 |         svn_command = 'svn' | 
|---|
 | 203 |         if sys.platform == 'win32': | 
|---|
 | 204 |             svn_command += '.exe' | 
|---|
 | 205 |         # @@: The previous method of formatting this string using \ doesn't work on Windows | 
|---|
 | 206 |         cmd = '%(svn_command)s mkdir %(svn_repos_path)s' + \ | 
|---|
 | 207 |             ' %(svn_repos_path)s/trunk %(svn_repos_path)s/tags' + \ | 
|---|
 | 208 |             ' %(svn_repos_path)s/branches -m "New project %(dist_name)s"' | 
|---|
 | 209 |         cmd = cmd % { | 
|---|
 | 210 |             'svn_repos_path': svn_repos_path,  | 
|---|
 | 211 |             'dist_name': dist_name, | 
|---|
 | 212 |             'svn_command':svn_command, | 
|---|
 | 213 |         } | 
|---|
 | 214 |         if self.verbose: | 
|---|
 | 215 |             print "Running:" | 
|---|
 | 216 |             print cmd | 
|---|
 | 217 |         if not self.simulate: | 
|---|
 | 218 |             os.system(cmd) | 
|---|
 | 219 |         svn_repos_path_trunk = os.path.join(svn_repos_path,'trunk').replace('\\','/') | 
|---|
 | 220 |         cmd = svn_command+' co "%s" "%s"' % (svn_repos_path_trunk, output_dir) | 
|---|
 | 221 |         if self.verbose: | 
|---|
 | 222 |             print "Running %s" % cmd | 
|---|
 | 223 |         if not self.simulate: | 
|---|
 | 224 |             os.system(cmd) | 
|---|
 | 225 |  | 
|---|
 | 226 |     ignore_egg_info_files = [ | 
|---|
 | 227 |         'top_level.txt', | 
|---|
 | 228 |         'entry_points.txt', | 
|---|
 | 229 |         'requires.txt', | 
|---|
 | 230 |         'PKG-INFO', | 
|---|
 | 231 |         'namespace_packages.txt', | 
|---|
 | 232 |         'SOURCES.txt', | 
|---|
 | 233 |         'dependency_links.txt', | 
|---|
 | 234 |         'not-zip-safe'] | 
|---|
 | 235 |  | 
|---|
 | 236 |     def add_svn_repository(self, vars, output_dir): | 
|---|
 | 237 |         svn_repos = self.options.svn_repository | 
|---|
 | 238 |         egg_info_dir = pluginlib.egg_info_dir(output_dir, vars['project']) | 
|---|
 | 239 |         svn_command = 'svn' | 
|---|
 | 240 |         if sys.platform == 'win32': | 
|---|
 | 241 |             svn_command += '.exe' | 
|---|
 | 242 |         self.run_command(svn_command, 'add', '-N', egg_info_dir) | 
|---|
 | 243 |         paster_plugins_file = os.path.join( | 
|---|
 | 244 |             egg_info_dir, 'paster_plugins.txt') | 
|---|
 | 245 |         if os.path.exists(paster_plugins_file): | 
|---|
 | 246 |             self.run_command(svn_command, 'add', paster_plugins_file) | 
|---|
 | 247 |         self.run_command(svn_command, 'ps', 'svn:ignore', | 
|---|
 | 248 |                          '\n'.join(self.ignore_egg_info_files), | 
|---|
 | 249 |                          egg_info_dir) | 
|---|
 | 250 |         if self.verbose: | 
|---|
 | 251 |             print ("You must next run 'svn commit' to commit the " | 
|---|
 | 252 |                    "files to repository") | 
|---|
 | 253 |  | 
|---|
 | 254 |     def extend_templates(self, templates, tmpl_name): | 
|---|
 | 255 |         if '#' in tmpl_name: | 
|---|
 | 256 |             dist_name, tmpl_name = tmpl_name.split('#', 1) | 
|---|
 | 257 |         else: | 
|---|
 | 258 |             dist_name, tmpl_name = None, tmpl_name | 
|---|
 | 259 |         if dist_name is None: | 
|---|
 | 260 |             for entry in self.all_entry_points(): | 
|---|
 | 261 |                 if entry.name == tmpl_name: | 
|---|
 | 262 |                     tmpl = entry.load()(entry.name) | 
|---|
 | 263 |                     dist_name = entry.dist.project_name | 
|---|
 | 264 |                     break | 
|---|
 | 265 |             else: | 
|---|
 | 266 |                 raise LookupError( | 
|---|
 | 267 |                     'Template by name %r not found' % tmpl_name) | 
|---|
 | 268 |         else: | 
|---|
 | 269 |             dist = pkg_resources.get_distribution(dist_name) | 
|---|
 | 270 |             entry = dist.get_entry_info( | 
|---|
 | 271 |                 'paste.paster_create_template', tmpl_name) | 
|---|
 | 272 |             tmpl = entry.load()(entry.name) | 
|---|
 | 273 |         full_name = '%s#%s' % (dist_name, tmpl_name) | 
|---|
 | 274 |         for item_full_name, item_tmpl in templates: | 
|---|
 | 275 |             if item_full_name == full_name: | 
|---|
 | 276 |                 # Already loaded | 
|---|
 | 277 |                 return | 
|---|
 | 278 |         for req_name in tmpl.required_templates: | 
|---|
 | 279 |             self.extend_templates(templates, req_name) | 
|---|
 | 280 |         templates.append((full_name, tmpl)) | 
|---|
 | 281 |          | 
|---|
 | 282 |     def all_entry_points(self): | 
|---|
 | 283 |         if not hasattr(self, '_entry_points'): | 
|---|
 | 284 |             self._entry_points = list(pkg_resources.iter_entry_points( | 
|---|
 | 285 |             'paste.paster_create_template')) | 
|---|
 | 286 |         return self._entry_points | 
|---|
 | 287 |  | 
|---|
 | 288 |     def display_vars(self, vars): | 
|---|
 | 289 |         vars = vars.items() | 
|---|
 | 290 |         vars.sort() | 
|---|
 | 291 |         print 'Variables:' | 
|---|
 | 292 |         max_var = max([len(n) for n, v in vars]) | 
|---|
 | 293 |         for name, value in vars: | 
|---|
 | 294 |             print '  %s:%s  %s' % ( | 
|---|
 | 295 |                 name, ' '*(max_var-len(name)), value) | 
|---|
 | 296 |          | 
|---|
 | 297 |     def list_templates(self): | 
|---|
 | 298 |         templates = [] | 
|---|
 | 299 |         for entry in self.all_entry_points(): | 
|---|
 | 300 |             try: | 
|---|
 | 301 |                 templates.append(entry.load()(entry.name)) | 
|---|
 | 302 |             except Exception, e: | 
|---|
 | 303 |                 # We will not be stopped! | 
|---|
 | 304 |                 print 'Warning: could not load entry point %s (%s: %s)' % ( | 
|---|
 | 305 |                     entry.name, e.__class__.__name__, e) | 
|---|
 | 306 |         max_name = max([len(t.name) for t in templates]) | 
|---|
 | 307 |         templates.sort(lambda a, b: cmp(a.name, b.name)) | 
|---|
 | 308 |         print 'Available templates:' | 
|---|
 | 309 |         for template in templates: | 
|---|
 | 310 |             # @@: Wrap description | 
|---|
 | 311 |             print '  %s:%s  %s' % ( | 
|---|
 | 312 |                 template.name, | 
|---|
 | 313 |                 ' '*(max_name-len(template.name)), | 
|---|
 | 314 |                 template.summary) | 
|---|
 | 315 |          | 
|---|
 | 316 |     def inspect_files(self, output_dir, templates, vars): | 
|---|
 | 317 |         file_sources = {} | 
|---|
 | 318 |         for template in templates: | 
|---|
 | 319 |             self._find_files(template, vars, file_sources) | 
|---|
 | 320 |         self._show_files(output_dir, file_sources) | 
|---|
 | 321 |         self._show_leftovers(output_dir, file_sources) | 
|---|
 | 322 |  | 
|---|
 | 323 |     def _find_files(self, template, vars, file_sources): | 
|---|
 | 324 |         tmpl_dir = template.template_dir() | 
|---|
 | 325 |         self._find_template_files( | 
|---|
 | 326 |             template, tmpl_dir, vars, file_sources) | 
|---|
 | 327 |  | 
|---|
 | 328 |     def _find_template_files(self, template, tmpl_dir, vars, | 
|---|
 | 329 |                              file_sources, join=''): | 
|---|
 | 330 |         full_dir = os.path.join(tmpl_dir, join) | 
|---|
 | 331 |         for name in os.listdir(full_dir): | 
|---|
 | 332 |             if name.startswith('.'): | 
|---|
 | 333 |                 continue | 
|---|
 | 334 |             if os.path.isdir(os.path.join(full_dir, name)): | 
|---|
 | 335 |                 self._find_template_files( | 
|---|
 | 336 |                     template, tmpl_dir, vars, file_sources, | 
|---|
 | 337 |                     join=os.path.join(join, name)) | 
|---|
 | 338 |                 continue | 
|---|
 | 339 |             partial = os.path.join(join, name) | 
|---|
 | 340 |             for name, value in vars.items(): | 
|---|
 | 341 |                 partial = partial.replace('+%s+' % name, value) | 
|---|
 | 342 |             if partial.endswith('_tmpl'): | 
|---|
 | 343 |                 partial = partial[:-5] | 
|---|
 | 344 |             file_sources.setdefault(partial, []).append(template) | 
|---|
 | 345 |  | 
|---|
 | 346 |     _ignore_filenames = ['.*', '*.pyc', '*.bak*'] | 
|---|
 | 347 |     _ignore_dirs = ['CVS', '_darcs', '.svn'] | 
|---|
 | 348 |  | 
|---|
 | 349 |     def _show_files(self, output_dir, file_sources, join='', indent=0): | 
|---|
 | 350 |         pad = ' '*(2*indent) | 
|---|
 | 351 |         full_dir = os.path.join(output_dir, join) | 
|---|
 | 352 |         names = os.listdir(full_dir) | 
|---|
 | 353 |         dirs = [n for n in names | 
|---|
 | 354 |                 if os.path.isdir(os.path.join(full_dir, n))] | 
|---|
 | 355 |         fns = [n for n in names | 
|---|
 | 356 |                if not os.path.isdir(os.path.join(full_dir, n))] | 
|---|
 | 357 |         dirs.sort() | 
|---|
 | 358 |         names.sort() | 
|---|
 | 359 |         for name in names: | 
|---|
 | 360 |             skip_this = False | 
|---|
 | 361 |             for ext in self._ignore_filenames: | 
|---|
 | 362 |                 if fnmatch.fnmatch(name, ext): | 
|---|
 | 363 |                     if self.verbose > 1: | 
|---|
 | 364 |                         print '%sIgnoring %s' % (pad, name) | 
|---|
 | 365 |                     skip_this = True | 
|---|
 | 366 |                     break | 
|---|
 | 367 |             if skip_this: | 
|---|
 | 368 |                 continue | 
|---|
 | 369 |             partial = os.path.join(join, name) | 
|---|
 | 370 |             if partial not in file_sources: | 
|---|
 | 371 |                 if self.verbose > 1: | 
|---|
 | 372 |                     print '%s%s (not from template)' % (pad, name) | 
|---|
 | 373 |                 continue | 
|---|
 | 374 |             templates = file_sources.pop(partial) | 
|---|
 | 375 |             print '%s%s from:' % (pad, name) | 
|---|
 | 376 |             for template in templates: | 
|---|
 | 377 |                 print '%s  %s' % (pad, template.name) | 
|---|
 | 378 |         for dir in dirs: | 
|---|
 | 379 |             if dir in self._ignore_dirs: | 
|---|
 | 380 |                 continue | 
|---|
 | 381 |             print '%sRecursing into %s/' % (pad, dir) | 
|---|
 | 382 |             self._show_files( | 
|---|
 | 383 |                 output_dir, file_sources, | 
|---|
 | 384 |                 join=os.path.join(join, dir), | 
|---|
 | 385 |                 indent=indent+1) | 
|---|
 | 386 |  | 
|---|
 | 387 |     def _show_leftovers(self, output_dir, file_sources): | 
|---|
 | 388 |         if not file_sources: | 
|---|
 | 389 |             return | 
|---|
 | 390 |         print  | 
|---|
 | 391 |         print 'These files were supposed to be generated by templates' | 
|---|
 | 392 |         print 'but were not found:' | 
|---|
 | 393 |         file_sources = file_sources.items() | 
|---|
 | 394 |         file_sources.sort() | 
|---|
 | 395 |         for partial, templates in file_sources: | 
|---|
 | 396 |             print '  %s from:' % partial | 
|---|
 | 397 |             for template in templates: | 
|---|
 | 398 |                 print '    %s' % template.name | 
|---|
 | 399 |  | 
|---|
 | 400 |     def list_variables(self, templates): | 
|---|
 | 401 |         for tmpl_name, tmpl in templates: | 
|---|
 | 402 |             if not tmpl.read_vars(): | 
|---|
 | 403 |                 if self.verbose > 1: | 
|---|
 | 404 |                     self._show_template_vars( | 
|---|
 | 405 |                         tmpl_name, tmpl, 'No variables found') | 
|---|
 | 406 |                 continue | 
|---|
 | 407 |             self._show_template_vars(tmpl_name, tmpl) | 
|---|
 | 408 |  | 
|---|
 | 409 |     def _show_template_vars(self, tmpl_name, tmpl, message=None): | 
|---|
 | 410 |         title = '%s (from %s)' % (tmpl.name, tmpl_name) | 
|---|
 | 411 |         print title | 
|---|
 | 412 |         print '-'*len(title) | 
|---|
 | 413 |         if message is not None: | 
|---|
 | 414 |             print '  %s' % message | 
|---|
 | 415 |             print | 
|---|
 | 416 |             return | 
|---|
 | 417 |         tmpl.print_vars(indent=2) | 
|---|