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