0001
0002
0003import re
0004import sys
0005import os
0006import pkg_resources
0007from command import Command, BadCommand
0008import copydir
0009import pluginlib
0010import fnmatch
0011
0012class CreateDistroCommand(Command):
0013
0014 usage = 'PACKAGE_NAME [VAR=VALUE VAR2=VALUE2 ...]'
0015 summary = "Create the file layout for a Python distribution"
0016 short_description = summary
0017
0018 description = """\
0019 Create a new project. Projects are typically Python packages,
0020 ready for distribution. Projects are created from templates, and
0021 represent different kinds of projects -- associated with a
0022 particular framework for instance.
0023 """
0024
0025 parser = Command.standard_parser(
0026 simulate=True, no_interactive=True, quiet=True, overwrite=True)
0027 parser.add_option('-t', '--template',
0028 dest='templates',
0029 metavar='TEMPLATE',
0030 action='append',
0031 help="Add a template to the create process")
0032 parser.add_option('-o', '--output-dir',
0033 dest='output_dir',
0034 metavar='DIR',
0035 default='.',
0036 help="Write put the directory into DIR (default current directory)")
0037 parser.add_option('--svn-repository',
0038 dest='svn_repository',
0039 metavar='REPOS',
0040 help="Create package at given repository location (this will create the standard trunk/ tags/ branches/ hierarchy)")
0041 parser.add_option('--list-templates',
0042 dest='list_templates',
0043 action='store_true',
0044 help="List all templates available")
0045 parser.add_option('--list-variables',
0046 dest="list_variables",
0047 action="store_true",
0048 help="List all variables expected by the given template (does not create a package)")
0049 parser.add_option('--inspect-files',
0050 dest='inspect_files',
0051 action='store_true',
0052 help="Show where the files in the given (already created) directory came from (useful when using multiple templates)")
0053 parser.add_option('--config',
0054 action='store',
0055 dest='config',
0056 help="Template variables file")
0057
0058 _bad_chars_re = re.compile('[^a-zA-Z0-9_]')
0059
0060 default_verbosity = 1
0061 default_interactive = 1
0062
0063 def command(self):
0064 if self.options.list_templates:
0065 return self.list_templates()
0066 asked_tmpls = self.options.templates or ['basic_package']
0067 templates = []
0068 for tmpl_name in asked_tmpls:
0069 self.extend_templates(templates, tmpl_name)
0070 if self.options.list_variables:
0071 return self.list_variables(templates)
0072 if self.verbose:
0073 print 'Selected and implied templates:'
0074 max_tmpl_name = max([len(tmpl_name) for tmpl_name, tmpl in templates])
0075 for tmpl_name, tmpl in templates:
0076 print ' %s%s %s' % (
0077 tmpl_name, ' '*(max_tmpl_name-len(tmpl_name)),
0078 tmpl.summary)
0079 print
0080 if not self.args:
0081 if self.interactive:
0082 dist_name = self.challenge('Enter project name')
0083 else:
0084 raise BadCommand('You must provide a PACKAGE_NAME')
0085 else:
0086 dist_name = self.args[0].lstrip(os.path.sep)
0087
0088 templates = [tmpl for name, tmpl in templates]
0089 output_dir = os.path.join(self.options.output_dir, dist_name)
0090
0091 pkg_name = self._bad_chars_re.sub('', dist_name.lower())
0092 vars = {'project': dist_name,
0093 'package': pkg_name,
0094 'egg': pluginlib.egg_name(dist_name),
0095 }
0096 vars.update(self.parse_vars(self.args[1:]))
0097 if self.options.config and os.path.exists(self.options.config):
0098 for key, value in self.read_vars(self.options.config).items():
0099 vars.setdefault(key, value)
0100
0101 if self.verbose:
0102 self.display_vars(vars)
0103
0104 if self.options.inspect_files:
0105 self.inspect_files(
0106 output_dir, templates, vars)
0107 return
0108 if not os.path.exists(output_dir):
0109
0110
0111 copydir.all_answer = 'y'
0112
0113 if self.options.svn_repository:
0114 self.setup_svn_repository(output_dir, dist_name)
0115
0116
0117
0118
0119
0120 for template in templates[::-1]:
0121 vars = template.check_vars(vars, self)
0122
0123 for template in templates:
0124 self.create_template(
0125 template, output_dir, vars)
0126
0127 found_setup_py = False
0128 if os.path.exists(os.path.join(output_dir, 'setup.py')):
0129 self.run_command(sys.executable, 'setup.py', 'egg_info',
0130 cwd=output_dir,
0131
0132 expect_returncode=True)
0133 found_setup_py = True
0134 elif self.verbose > 1:
0135 print 'No setup.py (cannot run egg_info)'
0136
0137 package_dir = vars.get('package_dir', None)
0138 if package_dir:
0139 output_dir = os.path.join(output_dir, package_dir)
0140
0141
0142 if found_setup_py:
0143 egg_info_dir = pluginlib.egg_info_dir(output_dir, dist_name)
0144 for template in templates:
0145 for spec in template.egg_plugins:
0146 if self.verbose:
0147 print 'Adding %s to paster_plugins.txt' % spec
0148 if not self.simulate:
0149 pluginlib.add_plugin(egg_info_dir, spec)
0150 if not self.simulate:
0151
0152
0153 pluginlib.add_plugin(egg_info_dir, 'PasteScript')
0154
0155 if self.options.svn_repository:
0156 self.add_svn_repository(vars, output_dir)
0157
0158 if self.options.config:
0159 write_vars = vars.copy()
0160 del write_vars['project']
0161 del write_vars['package']
0162 self.write_vars(self.options.config, write_vars)
0163
0164 def create_template(self, template, output_dir, vars):
0165 if self.verbose:
0166 print 'Creating template %s' % template.name
0167 template.run(self, output_dir, vars)
0168
0169 def setup_svn_repository(self, output_dir, dist_name):
0170
0171 svn_repos = self.options.svn_repository
0172 svn_repos_path = os.path.join(svn_repos, dist_name).replace('\\','/')
0173 svn_command = 'svn'
0174 if sys.platform == 'win32':
0175 svn_command += '.exe'
0176
0177 cmd = '%(svn_command)s mkdir %(svn_repos_path)s' + ' %(svn_repos_path)s/trunk %(svn_repos_path)s/tags' + ' %(svn_repos_path)s/branches -m "New project %(dist_name)s"'
0180 cmd = cmd % {
0181 'svn_repos_path': svn_repos_path,
0182 'dist_name': dist_name,
0183 'svn_command':svn_command,
0184 }
0185 if self.verbose:
0186 print "Running:"
0187 print cmd
0188 if not self.simulate:
0189 os.system(cmd)
0190 svn_repos_path_trunk = os.path.join(svn_repos_path,'trunk').replace('\\','/')
0191 cmd = svn_command+' co "%s" "%s"' % (svn_repos_path_trunk, output_dir)
0192 if self.verbose:
0193 print "Running %s" % cmd
0194 if not self.simulate:
0195 os.system(cmd)
0196
0197 ignore_egg_info_files = [
0198 'top_level.txt',
0199 'entry_points.txt',
0200 'requires.txt',
0201 'PKG-INFO',
0202 'namespace_packages.txt',
0203 'SOURCES.txt',
0204 'dependency_links.txt',
0205 'not-zip-safe']
0206
0207 def add_svn_repository(self, vars, output_dir):
0208 svn_repos = self.options.svn_repository
0209 egg_info_dir = pluginlib.egg_info_dir(output_dir, vars['project'])
0210 svn_command = 'svn'
0211 if sys.platform == 'win32':
0212 svn_command += '.exe'
0213 self.run_command(svn_command, 'add', '-N', egg_info_dir)
0214 paster_plugins_file = os.path.join(
0215 egg_info_dir, 'paster_plugins.txt')
0216 if os.path.exists(paster_plugins_file):
0217 self.run_command(svn_command, 'add', paster_plugins_file)
0218 self.run_command(svn_command, 'ps', 'svn:ignore',
0219 '\n'.join(self.ignore_egg_info_files),
0220 egg_info_dir)
0221 if self.verbose:
0222 print ("You must next run 'svn commit' to commit the "
0223 "files to repository")
0224
0225 def extend_templates(self, templates, tmpl_name):
0226 if '#' in tmpl_name:
0227 dist_name, tmpl_name = tmpl_name.split('#', 1)
0228 else:
0229 dist_name, tmpl_name = None, tmpl_name
0230 if dist_name is None:
0231 for entry in self.all_entry_points():
0232 if entry.name == tmpl_name:
0233 tmpl = entry.load()(entry.name)
0234 dist_name = entry.dist.project_name
0235 break
0236 else:
0237 raise LookupError(
0238 'Template by name %r not found' % tmpl_name)
0239 else:
0240 dist = pkg_resources.get_distribution(dist_name)
0241 entry = dist.get_entry_info(
0242 'paste.paster_create_template', tmpl_name)
0243 tmpl = entry.load()(entry.name)
0244 full_name = '%s#%s' % (dist_name, tmpl_name)
0245 for item_full_name, item_tmpl in templates:
0246 if item_full_name == full_name:
0247
0248 return
0249 for req_name in tmpl.required_templates:
0250 self.extend_templates(templates, req_name)
0251 templates.append((full_name, tmpl))
0252
0253 def all_entry_points(self):
0254 if not hasattr(self, '_entry_points'):
0255 self._entry_points = list(pkg_resources.iter_entry_points(
0256 'paste.paster_create_template'))
0257 return self._entry_points
0258
0259 def display_vars(self, vars):
0260 vars = vars.items()
0261 vars.sort()
0262 print 'Variables:'
0263 max_var = max([len(n) for n, v in vars])
0264 for name, value in vars:
0265 print ' %s:%s %s' % (
0266 name, ' '*(max_var-len(name)), value)
0267
0268 def list_templates(self):
0269 templates = []
0270 for entry in self.all_entry_points():
0271 try:
0272 templates.append(entry.load()(entry.name))
0273 except Exception, e:
0274
0275 print 'Warning: could not load entry point %s (%s: %s)' % (
0276 entry.name, e.__class__.__name__, e)
0277 max_name = max([len(t.name) for t in templates])
0278 templates.sort(lambda a, b: cmp(a.name, b.name))
0279 print 'Available templates:'
0280 for template in templates:
0281
0282 print ' %s:%s %s' % (
0283 template.name,
0284 ' '*(max_name-len(template.name)),
0285 template.summary)
0286
0287 def inspect_files(self, output_dir, templates, vars):
0288 file_sources = {}
0289 for template in templates:
0290 self._find_files(template, vars, file_sources)
0291 self._show_files(output_dir, file_sources)
0292 self._show_leftovers(output_dir, file_sources)
0293
0294 def _find_files(self, template, vars, file_sources):
0295 tmpl_dir = template.template_dir()
0296 self._find_template_files(
0297 template, tmpl_dir, vars, file_sources)
0298
0299 def _find_template_files(self, template, tmpl_dir, vars,
0300 file_sources, join=''):
0301 full_dir = os.path.join(tmpl_dir, join)
0302 for name in os.listdir(full_dir):
0303 if name.startswith('.'):
0304 continue
0305 if os.path.isdir(os.path.join(full_dir, name)):
0306 self._find_template_files(
0307 template, tmpl_dir, vars, file_sources,
0308 join=os.path.join(join, name))
0309 continue
0310 partial = os.path.join(join, name)
0311 for name, value in vars.items():
0312 partial = partial.replace('+%s+' % name, value)
0313 if partial.endswith('_tmpl'):
0314 partial = partial[:-5]
0315 file_sources.setdefault(partial, []).append(template)
0316
0317 _ignore_filenames = ['.*', '*.pyc', '*.bak*']
0318 _ignore_dirs = ['CVS', '_darcs', '.svn']
0319
0320 def _show_files(self, output_dir, file_sources, join='', indent=0):
0321 pad = ' '*(2*indent)
0322 full_dir = os.path.join(output_dir, join)
0323 names = os.listdir(full_dir)
0324 dirs = [n for n in names
0325 if os.path.isdir(os.path.join(full_dir, n))]
0326 fns = [n for n in names
0327 if not os.path.isdir(os.path.join(full_dir, n))]
0328 dirs.sort()
0329 names.sort()
0330 for name in names:
0331 skip_this = False
0332 for ext in self._ignore_filenames:
0333 if fnmatch.fnmatch(name, ext):
0334 if self.verbose > 1:
0335 print '%sIgnoring %s' % (pad, name)
0336 skip_this = True
0337 break
0338 if skip_this:
0339 continue
0340 partial = os.path.join(join, name)
0341 if partial not in file_sources:
0342 if self.verbose > 1:
0343 print '%s%s (not from template)' % (pad, name)
0344 continue
0345 templates = file_sources.pop(partial)
0346 print '%s%s from:' % (pad, name)
0347 for template in templates:
0348 print '%s %s' % (pad, template.name)
0349 for dir in dirs:
0350 if dir in self._ignore_dirs:
0351 continue
0352 print '%sRecursing into %s/' % (pad, dir)
0353 self._show_files(
0354 output_dir, file_sources,
0355 join=os.path.join(join, dir),
0356 indent=indent+1)
0357
0358 def _show_leftovers(self, output_dir, file_sources):
0359 if not file_sources:
0360 return
0361 print
0362 print 'These files were supposed to be generated by templates'
0363 print 'but were not found:'
0364 file_sources = file_sources.items()
0365 file_sources.sort()
0366 for partial, templates in file_sources:
0367 print ' %s from:' % partial
0368 for template in templates:
0369 print ' %s' % template.name
0370
0371 def list_variables(self, templates):
0372 for tmpl_name, tmpl in templates:
0373 if not tmpl.read_vars():
0374 if self.verbose > 1:
0375 self._show_template_vars(
0376 tmpl_name, tmpl, 'No variables found')
0377 continue
0378 self._show_template_vars(tmpl_name, tmpl)
0379
0380 def _show_template_vars(self, tmpl_name, tmpl, message=None):
0381 title = '%s (from %s)' % (tmpl.name, tmpl_name)
0382 print title
0383 print '-'*len(title)
0384 if message is not None:
0385 print ' %s' % message
0386 print
0387 return
0388 tmpl.print_vars(indent=2)