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) |