Michael Merickel
2018-10-26 035f6cf8238211d097c991677fde6b5bc046a57b
commit | author | age
eb0432 1 # (c) 2005 Ian Bicking and contributors; written for Paste
CM 2 # (http://pythonpaste.org) Licensed under the MIT license:
3 # http://www.opensource.org/licenses/mit-license.php
4
b7f994 5 import argparse
a30a39 6 import os
54624f 7 import os.path
a30a39 8 import pkg_resources
CM 9 import re
10 import sys
f09cc1 11 from pyramid.compat import input_
e7a731 12
a30a39 13 _bad_chars_re = re.compile('[^a-zA-Z0-9_]')
CM 14
3fb84e 15
d29151 16 def main(argv=sys.argv, quiet=False):
CM 17     command = PCreateCommand(argv, quiet)
589a39 18     try:
IS 19         return command.run()
91db73 20     except KeyboardInterrupt:  # pragma: no cover
589a39 21         return 1
IS 22
8548e2 23
126afb 24 class PCreateCommand(object):
91db73 25     verbosity = 1  # required
df57ec 26     parser = argparse.ArgumentParser(
SP 27         description="""\
80b7e8 28 Render Pyramid scaffolding to an output directory.
SP 29
44ae73 30 Note: As of Pyramid 1.8, this command is deprecated. Use
SP 31 pyramid-cookiecutter-starter instead:
32 https://github.com/Pylons/pyramid-cookiecutter-starter
df57ec 33 """,
c9b2fa 34         formatter_class=argparse.RawDescriptionHelpFormatter,
df57ec 35     )
0c29cf 36     parser.add_argument(
MM 37         '-s',
38         '--scaffold',
39         dest='scaffold_name',
40         action='append',
41         help=(
42             "Add a scaffold to the create process "
43             "(multiple -s args accepted)"
44         ),
45     )
46     parser.add_argument(
47         '-t',
48         '--template',
49         dest='scaffold_name',
50         action='append',
51         help=(
52             'A backwards compatibility alias for '
53             '-s/--scaffold.  Add a scaffold to the '
54             'create process (multiple -t args accepted)'
55         ),
56     )
57     parser.add_argument(
58         '-l',
59         '--list',
60         dest='list',
61         action='store_true',
62         help="List all available scaffold names",
63     )
64     parser.add_argument(
65         '--list-templates',
66         dest='list',
67         action='store_true',
68         help=(
69             "A backwards compatibility alias for -l/--list. "
70             "List all available scaffold names."
71         ),
72     )
73     parser.add_argument(
74         '--package-name',
75         dest='package_name',
76         action='store',
77         help='Package name to use. The name provided is '
78         'assumed to be a valid Python package name, and '
79         'will not be validated. By default the package '
80         'name is derived from the value of '
81         'output_directory.',
82     )
83     parser.add_argument(
84         '--simulate',
85         dest='simulate',
86         action='store_true',
87         help='Simulate but do no work',
88     )
89     parser.add_argument(
90         '--overwrite',
91         dest='overwrite',
92         action='store_true',
93         help='Always overwrite',
94     )
95     parser.add_argument(
96         '--interactive',
97         dest='interactive',
98         action='store_true',
99         help='When a file would be overwritten, interrogate '
100         '(this is the default, but you may specify it to '
101         'override --overwrite)',
102     )
103     parser.add_argument(
104         '--ignore-conflicting-name',
105         dest='force_bad_name',
106         action='store_true',
107         default=False,
108         help='Do create a project even if the chosen name '
109         'is the name of an already existing / importable '
110         'package.',
111     )
112     parser.add_argument(
113         'output_directory',
114         nargs='?',
115         default=None,
116         help='The directory where the project will be ' 'created.',
117     )
a30a39 118
0eff66 119     pyramid_dist = pkg_resources.get_distribution("pyramid")
MR 120
d29151 121     def __init__(self, argv, quiet=False):
CM 122         self.quiet = quiet
c47bfc 123         self.args = self.parser.parse_args(argv[1:])
b7f994 124         if not self.args.interactive and not self.args.overwrite:
SP 125             self.args.interactive = True
126afb 126         self.scaffolds = self.all_scaffolds()
a30a39 127
CM 128     def run(self):
b7f994 129         if self.args.list:
a30a39 130             return self.show_scaffolds()
be51e5 131         if not self.args.scaffold_name and not self.args.output_directory:
91db73 132             if not self.quiet:  # pragma: no cover
0786c7 133                 self.parser.print_help()
BJR 134                 self.out('')
135                 self.show_scaffolds()
136             return 2
589a39 137
IS 138         if not self.validate_input():
d58614 139             return 2
f5ae21 140         self._warn_pcreate_deprecated()
589a39 141
126afb 142         return self.render_scaffolds()
a30a39 143
589a39 144     @property
IS 145     def output_path(self):
b7f994 146         return os.path.abspath(os.path.normpath(self.args.output_directory))
589a39 147
IS 148     @property
149     def project_vars(self):
150         output_dir = self.output_path
073e52 151         project_name = os.path.basename(os.path.split(output_dir)[1])
b7f994 152         if self.args.package_name is None:
24c635 153             pkg_name = _bad_chars_re.sub(
0c29cf 154                 '', project_name.lower().replace('-', '_')
MM 155             )
24c635 156             safe_name = pkg_resources.safe_name(project_name)
G 157         else:
b7f994 158             pkg_name = self.args.package_name
24c635 159             safe_name = pkg_name
126afb 160         egg_name = pkg_resources.to_filename(safe_name)
0eff66 161
MR 162         # get pyramid package version
163         pyramid_version = self.pyramid_dist.version
e18b5f 164
91db73 165         # map pyramid package version of the documentation branch ##
64b65d 166         # if version ends with 'dev' then docs version is 'master'
G 167         if self.pyramid_dist.version[-3:] == 'dev':
168             pyramid_docs_branch = 'master'
0eff66 169         else:
64b65d 170             # if not version is not 'dev' find the version.major_version string
G 171             # and combine it with '-branch'
172             version_match = re.match(r'(\d+\.\d+)', self.pyramid_dist.version)
173             if version_match is not None:
174                 pyramid_docs_branch = "%s-branch" % version_match.group()
175             # if can not parse the version then default to 'latest'
176             else:
177                 pyramid_docs_branch = 'latest'
0eff66 178
589a39 179         return {
126afb 180             'project': project_name,
a30a39 181             'package': pkg_name,
CM 182             'egg': egg_name,
0eff66 183             'pyramid_version': pyramid_version,
MR 184             'pyramid_docs_branch': pyramid_docs_branch,
589a39 185         }
IS 186
187     def render_scaffolds(self):
188         props = self.project_vars
189         output_dir = self.output_path
b7f994 190         for scaffold_name in self.args.scaffold_name:
a30a39 191             for scaffold in self.scaffolds:
CM 192                 if scaffold.name == scaffold_name:
589a39 193                     scaffold.run(self, output_dir, props)
d58614 194         return 0
a30a39 195
CM 196     def show_scaffolds(self):
126afb 197         scaffolds = sorted(self.scaffolds, key=lambda x: x.name)
CM 198         if scaffolds:
199             max_name = max([len(t.name) for t in scaffolds])
200             self.out('Available scaffolds:')
201             for scaffold in scaffolds:
0c29cf 202                 self.out(
MM 203                     '  %s:%s  %s'
204                     % (
205                         scaffold.name,
206                         ' ' * (max_name - len(scaffold.name)),
207                         scaffold.summary,
208                     )
209                 )
126afb 210         else:
CM 211             self.out('No scaffolds available')
d58614 212         return 0
a30a39 213
126afb 214     def all_scaffolds(self):
CM 215         scaffolds = []
216         eps = list(pkg_resources.iter_entry_points('pyramid.scaffold'))
217         for entry in eps:
218             try:
219                 scaffold_class = entry.load()
220                 scaffold = scaffold_class(entry.name)
221                 scaffolds.append(scaffold)
91db73 222             except Exception as e:  # pragma: no cover
0c29cf 223                 self.out(
MM 224                     'Warning: could not load entry point %s (%s: %s)'
225                     % (entry.name, e.__class__.__name__, e)
226                 )
126afb 227         return scaffolds
a30a39 228
91db73 229     def out(self, msg):  # pragma: no cover
d29151 230         if not self.quiet:
126afb 231             print(msg)
CM 232
589a39 233     def validate_input(self):
b7f994 234         if not self.args.scaffold_name:
0c29cf 235             self.out(
MM 236                 'You must provide at least one scaffold name: '
237                 '-s <scaffold name>'
238             )
589a39 239             self.out('')
IS 240             self.show_scaffolds()
241             return False
74bed0 242         if not self.args.output_directory:
589a39 243             self.out('You must provide a project name')
IS 244             return False
245         available = [x.name for x in self.scaffolds]
b7f994 246         diff = set(self.args.scaffold_name).difference(available)
589a39 247         if diff:
f09cc1 248             self.out('Unavailable scaffolds: %s' % ", ".join(sorted(diff)))
589a39 249             return False
IS 250
251         pkg_name = self.project_vars['package']
252
b7f994 253         if pkg_name == 'site' and not self.args.force_bad_name:
0c29cf 254             self.out(
MM 255                 'The package name "site" has a special meaning in '
256                 'Python. Are you sure you want to use it as your '
257                 'project\'s name?'
258             )
259             return self.confirm_bad_name(
260                 'Really use "{0}"?: '.format(pkg_name)
261             )
589a39 262
IS 263         # check if pkg_name can be imported (i.e. already exists in current
264         # $PYTHON_PATH, if so - let the user confirm
265         pkg_exists = True
266         try:
91db73 267             # use absolute imports
SP 268             __import__(pkg_name, globals(), locals(), [], 0)
25737f 269         except ImportError:
589a39 270             pkg_exists = False
IS 271         if not pkg_exists:
272             return True
273
b7f994 274         if self.args.force_bad_name:
589a39 275             return True
0c29cf 276         self.out(
MM 277             'A package named "{0}" already exists, are you sure you want '
278             'to use it as your project\'s name?'.format(pkg_name)
279         )
7dc81f 280         return self.confirm_bad_name('Really use "{0}"?: '.format(pkg_name))
589a39 281
91db73 282     def confirm_bad_name(self, prompt):  # pragma: no cover
f09cc1 283         answer = input_('{0} [y|N]: '.format(prompt))
589a39 284         return answer.strip().lower() == 'y'
IS 285
80b7e8 286     def _warn_pcreate_deprecated(self):
0c29cf 287         self.out(
MM 288             '''\
80b7e8 289 Note: As of Pyramid 1.8, this command is deprecated. Use a specific
SP 290 cookiecutter instead:
291 https://github.com/pylons/?query=cookiecutter
0c29cf 292 '''
MM 293         )
294
80b7e8 295
91db73 296 if __name__ == '__main__':  # pragma: no cover
40d54e 297     sys.exit(main() or 0)