pylibtemplate 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pylibtemplate/__init__.py +918 -0
- pylibtemplate/version.py +34 -0
- pylibtemplate-0.0.1.dist-info/METADATA +392 -0
- pylibtemplate-0.0.1.dist-info/RECORD +8 -0
- pylibtemplate-0.0.1.dist-info/WHEEL +5 -0
- pylibtemplate-0.0.1.dist-info/entry_points.txt +2 -0
- pylibtemplate-0.0.1.dist-info/licenses/LICENSE +674 -0
- pylibtemplate-0.0.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,918 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
# Copyright 2025 Matthew Fitzpatrick.
|
3
|
+
#
|
4
|
+
# This program is free software: you can redistribute it and/or modify it under
|
5
|
+
# the terms of the GNU General Public License as published by the Free Software
|
6
|
+
# Foundation, version 3.
|
7
|
+
#
|
8
|
+
# This program is distributed in the hope that it will be useful, but WITHOUT
|
9
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
10
|
+
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
11
|
+
#
|
12
|
+
# You should have received a copy of the GNU General Public License along with
|
13
|
+
# this program. If not, see <https://www.gnu.org/licenses/gpl-3.0.html>.
|
14
|
+
r""":mod:`pylibtemplate` (short for 'Python Library Template') is a Python
|
15
|
+
library that generates ``git`` repository templates for building Python
|
16
|
+
libraries that are suitable for publication on PyPI.
|
17
|
+
|
18
|
+
"""
|
19
|
+
|
20
|
+
|
21
|
+
|
22
|
+
#####################################
|
23
|
+
## Load libraries/packages/modules ##
|
24
|
+
#####################################
|
25
|
+
|
26
|
+
# For converting relative paths to absolute paths, and for making directories.
|
27
|
+
import pathlib
|
28
|
+
|
29
|
+
# For timing the execution of different segments of code.
|
30
|
+
import time
|
31
|
+
|
32
|
+
# For getting the current year.
|
33
|
+
import datetime
|
34
|
+
|
35
|
+
# For pattern matching.
|
36
|
+
import re
|
37
|
+
|
38
|
+
# For removing directories.
|
39
|
+
import shutil
|
40
|
+
|
41
|
+
# For removing files, renaming directories, and getting directory trees.
|
42
|
+
import os
|
43
|
+
|
44
|
+
# For wrapping text.
|
45
|
+
import textwrap
|
46
|
+
|
47
|
+
# For parsing command line arguments.
|
48
|
+
import argparse
|
49
|
+
|
50
|
+
|
51
|
+
|
52
|
+
# For validating and converting objects.
|
53
|
+
import czekitout.check
|
54
|
+
import czekitout.convert
|
55
|
+
|
56
|
+
# For cloning ``git`` repositories.
|
57
|
+
import git
|
58
|
+
|
59
|
+
|
60
|
+
|
61
|
+
# Get version of current package.
|
62
|
+
from pylibtemplate.version import __version__
|
63
|
+
|
64
|
+
|
65
|
+
|
66
|
+
##################################
|
67
|
+
## Define classes and functions ##
|
68
|
+
##################################
|
69
|
+
|
70
|
+
# List of public objects in package.
|
71
|
+
__all__ = ["generate_local_git_repo_template"]
|
72
|
+
|
73
|
+
|
74
|
+
|
75
|
+
_pylibtemplate_lib_name_for_imports = "pylibtemplate"
|
76
|
+
_pylibtemplate_abbreviated_lib_name_for_docs = "PyLibTemplate"
|
77
|
+
_pylibtemplate_non_abbreviated_lib_name_for_docs = "Python Library Template"
|
78
|
+
_pylibtemplate_author = "Matthew Fitzpatrick"
|
79
|
+
_pylibtemplate_email = "matthew.rc.fitzpatrick@gmail.com"
|
80
|
+
_pylibtemplate_gist_id = "7baba2a56d07b59cc49b8323f44416e5"
|
81
|
+
_pylibtemplate_copyright_year = "2025"
|
82
|
+
|
83
|
+
|
84
|
+
|
85
|
+
def _check_and_convert_lib_name_for_imports(params):
|
86
|
+
obj_name = "lib_name_for_imports"
|
87
|
+
kwargs = {"obj": params[obj_name], "obj_name": obj_name}
|
88
|
+
lib_name_for_imports = czekitout.convert.to_str_from_str_like(**kwargs)
|
89
|
+
|
90
|
+
current_func_name = "_check_and_convert_lib_name_for_imports"
|
91
|
+
|
92
|
+
if not lib_name_for_imports.isidentifier():
|
93
|
+
err_msg = globals()[current_func_name+"_err_msg_1"]
|
94
|
+
raise ValueError(err_msg)
|
95
|
+
|
96
|
+
return lib_name_for_imports
|
97
|
+
|
98
|
+
|
99
|
+
|
100
|
+
def _check_and_convert_abbreviated_lib_name_for_docs(params):
|
101
|
+
obj_name = \
|
102
|
+
"abbreviated_lib_name_for_docs"
|
103
|
+
kwargs = \
|
104
|
+
{"obj": params[obj_name], "obj_name": obj_name}
|
105
|
+
abbreviated_lib_name_for_docs = \
|
106
|
+
czekitout.convert.to_str_from_str_like(**kwargs)
|
107
|
+
|
108
|
+
current_func_name = "_check_and_convert_abbreviated_lib_name_for_docs"
|
109
|
+
|
110
|
+
if not abbreviated_lib_name_for_docs.isidentifier():
|
111
|
+
err_msg = globals()[current_func_name+"_err_msg_1"]
|
112
|
+
raise ValueError(err_msg)
|
113
|
+
|
114
|
+
return abbreviated_lib_name_for_docs
|
115
|
+
|
116
|
+
|
117
|
+
|
118
|
+
def _check_and_convert_non_abbreviated_lib_name_for_docs(params):
|
119
|
+
obj_name = \
|
120
|
+
"non_abbreviated_lib_name_for_docs"
|
121
|
+
kwargs = \
|
122
|
+
{"obj": params[obj_name], "obj_name": obj_name}
|
123
|
+
non_abbreviated_lib_name_for_docs = \
|
124
|
+
czekitout.convert.to_str_from_str_like(**kwargs)
|
125
|
+
|
126
|
+
return non_abbreviated_lib_name_for_docs
|
127
|
+
|
128
|
+
|
129
|
+
|
130
|
+
def _check_and_convert_author(params):
|
131
|
+
obj_name = "author"
|
132
|
+
kwargs = {"obj": params[obj_name], "obj_name": obj_name}
|
133
|
+
author = czekitout.convert.to_str_from_str_like(**kwargs)
|
134
|
+
|
135
|
+
return author
|
136
|
+
|
137
|
+
|
138
|
+
|
139
|
+
def _check_and_convert_email(params):
|
140
|
+
obj_name = "email"
|
141
|
+
kwargs = {"obj": params[obj_name], "obj_name": obj_name}
|
142
|
+
email = czekitout.convert.to_str_from_str_like(**kwargs)
|
143
|
+
|
144
|
+
return email
|
145
|
+
|
146
|
+
|
147
|
+
|
148
|
+
def _check_and_convert_gist_id(params):
|
149
|
+
obj_name = "gist_id"
|
150
|
+
kwargs = {"obj": params[obj_name], "obj_name": obj_name}
|
151
|
+
gist_id = czekitout.convert.to_str_from_str_like(**kwargs)
|
152
|
+
|
153
|
+
return gist_id
|
154
|
+
|
155
|
+
|
156
|
+
|
157
|
+
def _check_and_convert_path_to_directory_to_contain_new_repo(params):
|
158
|
+
obj_name = \
|
159
|
+
"path_to_directory_to_contain_new_repo"
|
160
|
+
kwargs = \
|
161
|
+
{"obj": params[obj_name], "obj_name": obj_name}
|
162
|
+
path_to_directory_to_contain_new_repo = \
|
163
|
+
czekitout.convert.to_str_from_str_like(**kwargs)
|
164
|
+
path_to_directory_to_contain_new_repo = \
|
165
|
+
str(pathlib.Path(path_to_directory_to_contain_new_repo).absolute())
|
166
|
+
|
167
|
+
return path_to_directory_to_contain_new_repo
|
168
|
+
|
169
|
+
|
170
|
+
|
171
|
+
_default_lib_name_for_imports = \
|
172
|
+
_pylibtemplate_lib_name_for_imports
|
173
|
+
_default_abbreviated_lib_name_for_docs = \
|
174
|
+
_pylibtemplate_abbreviated_lib_name_for_docs
|
175
|
+
_default_non_abbreviated_lib_name_for_docs = \
|
176
|
+
_pylibtemplate_non_abbreviated_lib_name_for_docs
|
177
|
+
_default_author = \
|
178
|
+
_pylibtemplate_author
|
179
|
+
_default_email = \
|
180
|
+
_pylibtemplate_email
|
181
|
+
_default_gist_id = \
|
182
|
+
_pylibtemplate_gist_id
|
183
|
+
_default_path_to_directory_to_contain_new_repo = \
|
184
|
+
""
|
185
|
+
|
186
|
+
|
187
|
+
|
188
|
+
def generate_local_git_repo_template(
|
189
|
+
lib_name_for_imports=\
|
190
|
+
_default_lib_name_for_imports,
|
191
|
+
abbreviated_lib_name_for_docs=\
|
192
|
+
_default_abbreviated_lib_name_for_docs,
|
193
|
+
non_abbreviated_lib_name_for_docs=\
|
194
|
+
_default_non_abbreviated_lib_name_for_docs,
|
195
|
+
author=\
|
196
|
+
_default_author,
|
197
|
+
email=\
|
198
|
+
_default_email,
|
199
|
+
gist_id=\
|
200
|
+
_default_gist_id,
|
201
|
+
path_to_directory_to_contain_new_repo=\
|
202
|
+
_default_path_to_directory_to_contain_new_repo):
|
203
|
+
r"""Generate a local ``git`` repository template.
|
204
|
+
|
205
|
+
The primary purpose of generating a local ``git`` repository template is to
|
206
|
+
modify it subsequently to develop a new Python library.
|
207
|
+
|
208
|
+
This Python function will perform several actions: First, it will clone the
|
209
|
+
``git`` commit of the ``pylibtemplate`` GitHub repository corresponding to
|
210
|
+
the version of ``pylibtemplate`` being used currently, in the directory at
|
211
|
+
the path ``path_to_directory_to_contain_new_repo``, i.e. the ``git clone``
|
212
|
+
command is executed while the working directory is set temporarily to the
|
213
|
+
path ``path_to_directory_to_contain_new_repo``; Next, it will rename the
|
214
|
+
cloned repository to ``lib_name_for_imports`` such that the path to the
|
215
|
+
cloned repository becomes
|
216
|
+
``path_to_directory_to_contain_new_repo+"/"+lib_name_for_imports``; Next,
|
217
|
+
all instances of the string of characters "pylibtemplate" are replaced with
|
218
|
+
``lib_name_for_imports``, be it in file contents, directory basenames, or
|
219
|
+
file basenames; Next, all instances of the string of characters
|
220
|
+
"PyLibTemplate" are replaced with ``abbreviated_lib_name_for_docs``; Next,
|
221
|
+
all instances of the string of characters "Python Library Template" are
|
222
|
+
replaced with ``non_abbreviated_lib_name_for_docs``; Next, all email address
|
223
|
+
placeholders (i.e. instances of the string of characters
|
224
|
+
"matthew.rc.fitzpatrick@gmail.com") are replaced with ``email``; Next, all
|
225
|
+
instances of the gist ID of ``pylibtemplate`` are replaced with ``gist_id``;
|
226
|
+
Next, all author placeholders (i.e. instances of the string of characters
|
227
|
+
"Matthew Fitzpatrick") are replaced with ``author``; Next, all copyright
|
228
|
+
statements are updated according to the current year; And lastly, the
|
229
|
+
following file is removed::
|
230
|
+
|
231
|
+
* ``<local_repo_root>/docs/how_to_create_a_python_library_using_pylibtemplate.rst``
|
232
|
+
|
233
|
+
where ``<local_repo_root>`` is the root of the local ``git`` repository, as
|
234
|
+
well as the following directory::
|
235
|
+
|
236
|
+
* ``<local_repo_root>/.git``
|
237
|
+
|
238
|
+
On a unrelated note, for the purposes of demonstrating the citing of
|
239
|
+
literature, we cite Ref. [RefLabel1]_ and [RefLabel2]_.
|
240
|
+
|
241
|
+
Parameters
|
242
|
+
----------
|
243
|
+
lib_name_for_imports : `str`, optional
|
244
|
+
The name of the new Python library, as it would appear in Python import
|
245
|
+
commands. This parameter needs to be a valid Python identifier.
|
246
|
+
abbreviated_lib_name_for_docs : `str`, optional
|
247
|
+
An abbreviated format of the name of the new Python library that appears
|
248
|
+
in documentation pages. This parameter needs to be a valid Python
|
249
|
+
identifier. It can be set to the same value as ``lib_name_for_imports``.
|
250
|
+
non_abbreviated_lib_name_for_docs : `str`, optional
|
251
|
+
A non-abbreviated format of the name of the new Python library that
|
252
|
+
appears in documentation pages.
|
253
|
+
author : `str`, optional
|
254
|
+
The name of the author of the new Python library.
|
255
|
+
email : `str`, optional
|
256
|
+
The email address of the author.
|
257
|
+
gist_id : `str`, optional
|
258
|
+
The ID of the GitHub gist to be used to record the code coverage from
|
259
|
+
your unit tests. See the
|
260
|
+
:ref:`how_to_create_a_python_library_using_pylibtemplate_sec` page for
|
261
|
+
instructions on how to create a GitHub gist for your new Python library.
|
262
|
+
path_to_directory_to_contain_new_repo : `str`, optional
|
263
|
+
The path to the directory inside which the ``git`` commit --- of the
|
264
|
+
``pylibtemplate`` GitHub repository corresponding to the version of
|
265
|
+
``pylibtemplate`` being used currently --- is to be cloned, i.e. the
|
266
|
+
``git clone`` command is executed while the working directory is set
|
267
|
+
temporarily to ``path_to_directory_to_contain_new_repo``.
|
268
|
+
|
269
|
+
"""
|
270
|
+
params = locals()
|
271
|
+
|
272
|
+
global_symbol_table = globals()
|
273
|
+
for param_name in params:
|
274
|
+
func_name = "_check_and_convert_" + param_name
|
275
|
+
func_alias = global_symbol_table[func_name]
|
276
|
+
params[param_name] = func_alias(params)
|
277
|
+
|
278
|
+
kwargs = params
|
279
|
+
result = _generate_local_git_repo_template(**kwargs)
|
280
|
+
|
281
|
+
return None
|
282
|
+
|
283
|
+
|
284
|
+
|
285
|
+
def _generate_local_git_repo_template(lib_name_for_imports,
|
286
|
+
abbreviated_lib_name_for_docs,
|
287
|
+
non_abbreviated_lib_name_for_docs,
|
288
|
+
author,
|
289
|
+
email,
|
290
|
+
gist_id,
|
291
|
+
path_to_directory_to_contain_new_repo):
|
292
|
+
kwargs = locals()
|
293
|
+
_print_generate_local_git_repo_template_starting_msg(**kwargs)
|
294
|
+
|
295
|
+
start_time = time.time()
|
296
|
+
|
297
|
+
kwargs = {"path_to_directory_to_contain_new_repo": \
|
298
|
+
path_to_directory_to_contain_new_repo,
|
299
|
+
"lib_name_for_imports": \
|
300
|
+
lib_name_for_imports}
|
301
|
+
_clone_pylibtemplate_repo(**kwargs)
|
302
|
+
_rm_file_subset_of_local_git_repo(**kwargs)
|
303
|
+
_mv_directory_subset_of_local_git_repo(**kwargs)
|
304
|
+
filenames = _get_paths_to_files_in_local_git_repo(**kwargs)
|
305
|
+
|
306
|
+
text_replacement_map = {_pylibtemplate_lib_name_for_imports: \
|
307
|
+
lib_name_for_imports,
|
308
|
+
_pylibtemplate_abbreviated_lib_name_for_docs: \
|
309
|
+
abbreviated_lib_name_for_docs,
|
310
|
+
_pylibtemplate_non_abbreviated_lib_name_for_docs: \
|
311
|
+
non_abbreviated_lib_name_for_docs,
|
312
|
+
_pylibtemplate_author: \
|
313
|
+
author,
|
314
|
+
_pylibtemplate_email: \
|
315
|
+
email,
|
316
|
+
_pylibtemplate_gist_id: \
|
317
|
+
gist_id,
|
318
|
+
_pylibtemplate_copyright_year: \
|
319
|
+
str(datetime.datetime.now().year)}
|
320
|
+
|
321
|
+
kwargs = {"paths_to_files_in_local_git_repo": filenames,
|
322
|
+
"text_replacement_map": text_replacement_map}
|
323
|
+
_replace_and_wrap_text(**kwargs)
|
324
|
+
|
325
|
+
kwargs = {"start_time": \
|
326
|
+
start_time,
|
327
|
+
"path_to_directory_to_contain_new_repo": \
|
328
|
+
path_to_directory_to_contain_new_repo,
|
329
|
+
"lib_name_for_imports": \
|
330
|
+
lib_name_for_imports}
|
331
|
+
_print_generate_local_git_repo_template_end_msg(**kwargs)
|
332
|
+
|
333
|
+
return None
|
334
|
+
|
335
|
+
|
336
|
+
|
337
|
+
def _print_generate_local_git_repo_template_starting_msg(
|
338
|
+
lib_name_for_imports,
|
339
|
+
abbreviated_lib_name_for_docs,
|
340
|
+
non_abbreviated_lib_name_for_docs,
|
341
|
+
author,
|
342
|
+
email,
|
343
|
+
gist_id,
|
344
|
+
path_to_directory_to_contain_new_repo):
|
345
|
+
unformatted_msg = ("Generating a local ``git`` repository with the "
|
346
|
+
"following parameters:\n"
|
347
|
+
"\n"
|
348
|
+
"``lib_name_for_imports`` = ``'{}'``\n"
|
349
|
+
"``abbreviated_lib_name_for_docs`` = ``'{}'``\n"
|
350
|
+
"``non_abbreviated_lib_name_for_docs`` = ``'{}'``\n"
|
351
|
+
"``author`` = ``'{}'``\n"
|
352
|
+
"``email`` = ``'{}'``\n"
|
353
|
+
"``gist_id`` = ``'{}'``\n"
|
354
|
+
"``path_to_directory_to_contain_new_repo`` = ``'{}'``\n"
|
355
|
+
"\n"
|
356
|
+
"..."
|
357
|
+
"\n")
|
358
|
+
msg = unformatted_msg.format(lib_name_for_imports,
|
359
|
+
abbreviated_lib_name_for_docs,
|
360
|
+
non_abbreviated_lib_name_for_docs,
|
361
|
+
author,
|
362
|
+
email,
|
363
|
+
gist_id,
|
364
|
+
path_to_directory_to_contain_new_repo)
|
365
|
+
print(msg)
|
366
|
+
|
367
|
+
return None
|
368
|
+
|
369
|
+
|
370
|
+
|
371
|
+
def _clone_pylibtemplate_repo(path_to_directory_to_contain_new_repo,
|
372
|
+
lib_name_for_imports):
|
373
|
+
current_func_name = "_clone_pylibtemplate_repo"
|
374
|
+
|
375
|
+
try:
|
376
|
+
kwargs = {"output_dirname": path_to_directory_to_contain_new_repo}
|
377
|
+
_make_output_dir(**kwargs)
|
378
|
+
|
379
|
+
github_url = "https://github.com/mrfitzpa/pylibtemplate.git"
|
380
|
+
|
381
|
+
kwargs = {"path_to_directory_to_contain_new_repo": \
|
382
|
+
path_to_directory_to_contain_new_repo,
|
383
|
+
"lib_name_for_imports": \
|
384
|
+
lib_name_for_imports}
|
385
|
+
path_to_new_repo = _generate_path_to_new_repo(**kwargs)
|
386
|
+
|
387
|
+
tag = _get_pylibtemplate_tag()
|
388
|
+
|
389
|
+
cloning_options = (tuple()
|
390
|
+
if (tag is None)
|
391
|
+
else ("--depth=1", "--branch={}".format(tag)))
|
392
|
+
|
393
|
+
kwargs = {"url": github_url,
|
394
|
+
"to_path": path_to_new_repo,
|
395
|
+
"multi_options": cloning_options}
|
396
|
+
git.Repo.clone_from(**kwargs)
|
397
|
+
except:
|
398
|
+
err_msg = globals()[current_func_name+"_err_msg_1"]
|
399
|
+
IOError(err_msg)
|
400
|
+
|
401
|
+
return None
|
402
|
+
|
403
|
+
|
404
|
+
|
405
|
+
def _make_output_dir(output_dirname):
|
406
|
+
current_func_name = "_make_output_dir"
|
407
|
+
|
408
|
+
try:
|
409
|
+
pathlib.Path(output_dirname).mkdir(parents=True, exist_ok=True)
|
410
|
+
except:
|
411
|
+
unformatted_err_msg = globals()[current_func_name+"_err_msg_1"]
|
412
|
+
err_msg = unformatted_err_msg.format(output_dirname)
|
413
|
+
raise IOError(err_msg)
|
414
|
+
|
415
|
+
return None
|
416
|
+
|
417
|
+
|
418
|
+
|
419
|
+
def _generate_path_to_new_repo(path_to_directory_to_contain_new_repo,
|
420
|
+
lib_name_for_imports):
|
421
|
+
path_to_new_repo = "{}/{}".format(path_to_directory_to_contain_new_repo,
|
422
|
+
lib_name_for_imports)
|
423
|
+
|
424
|
+
return path_to_new_repo
|
425
|
+
|
426
|
+
|
427
|
+
|
428
|
+
def _get_pylibtemplate_tag():
|
429
|
+
pattern = r"[0-9]\.[0-9]\.[0-9]"
|
430
|
+
pylibtemplate_version = __version__
|
431
|
+
pylibtemplate_tag = ("v{}".format(pylibtemplate.version.__version__)
|
432
|
+
if re.fullmatch(pattern, pylibtemplate_version)
|
433
|
+
else None)
|
434
|
+
|
435
|
+
return pylibtemplate_tag
|
436
|
+
|
437
|
+
|
438
|
+
|
439
|
+
def _rm_file_subset_of_local_git_repo(path_to_directory_to_contain_new_repo,
|
440
|
+
lib_name_for_imports):
|
441
|
+
kwargs = {"path_to_directory_to_contain_new_repo": \
|
442
|
+
path_to_directory_to_contain_new_repo,
|
443
|
+
"lib_name_for_imports": \
|
444
|
+
lib_name_for_imports}
|
445
|
+
path_to_new_repo = _generate_path_to_new_repo(**kwargs)
|
446
|
+
|
447
|
+
path_to_dir_to_rm = path_to_new_repo + "/.git"
|
448
|
+
shutil.rmtree(path_to_dir_to_rm)
|
449
|
+
|
450
|
+
basename = "how_to_create_a_python_library_using_pylibtemplate.rst"
|
451
|
+
path_to_file_to_rm = path_to_new_repo + "/docs/" + basename
|
452
|
+
os.remove(path_to_file_to_rm)
|
453
|
+
|
454
|
+
return None
|
455
|
+
|
456
|
+
|
457
|
+
|
458
|
+
def _mv_directory_subset_of_local_git_repo(
|
459
|
+
path_to_directory_to_contain_new_repo,
|
460
|
+
lib_name_for_imports):
|
461
|
+
kwargs = locals()
|
462
|
+
path_to_new_repo = _generate_path_to_new_repo(**kwargs)
|
463
|
+
|
464
|
+
os.rename(path_to_new_repo + "/pylibtemplate",
|
465
|
+
path_to_new_repo + "/" + lib_name_for_imports)
|
466
|
+
|
467
|
+
return None
|
468
|
+
|
469
|
+
|
470
|
+
|
471
|
+
def _get_paths_to_files_in_local_git_repo(path_to_directory_to_contain_new_repo,
|
472
|
+
lib_name_for_imports):
|
473
|
+
kwargs = locals()
|
474
|
+
path_to_new_repo = _generate_path_to_new_repo(**kwargs)
|
475
|
+
|
476
|
+
dir_tree = list(os.walk(path_to_new_repo))
|
477
|
+
|
478
|
+
dirname = os.path.abspath(dir_tree[0][0])
|
479
|
+
basename = os.path.basename(dirname)
|
480
|
+
|
481
|
+
filenames = []
|
482
|
+
subdirnames = []
|
483
|
+
|
484
|
+
for x in dir_tree:
|
485
|
+
subdirname = x[0]
|
486
|
+
subdirnames += [subdirname]
|
487
|
+
|
488
|
+
file_basenames = x[2]
|
489
|
+
for file_basename in file_basenames:
|
490
|
+
filename = subdirname + '/' + file_basename
|
491
|
+
filenames += [filename]
|
492
|
+
|
493
|
+
subdirnames.pop(0)
|
494
|
+
|
495
|
+
paths_to_files_in_local_git_repo = filenames
|
496
|
+
|
497
|
+
return paths_to_files_in_local_git_repo
|
498
|
+
|
499
|
+
|
500
|
+
|
501
|
+
def _replace_and_wrap_text(paths_to_files_in_local_git_repo,
|
502
|
+
text_replacement_map):
|
503
|
+
filenames = paths_to_files_in_local_git_repo
|
504
|
+
|
505
|
+
for filename in filenames:
|
506
|
+
basename = os.path.basename(filename)
|
507
|
+
|
508
|
+
kwargs = {"filename": filename,
|
509
|
+
"text_replacement_map": text_replacement_map}
|
510
|
+
new_file_contents = _generate_new_file_contents(**kwargs)
|
511
|
+
|
512
|
+
with open(filename, "w") as file_obj:
|
513
|
+
line_set = new_file_contents
|
514
|
+
file_obj.write("\n".join(line_set))
|
515
|
+
|
516
|
+
return None
|
517
|
+
|
518
|
+
|
519
|
+
|
520
|
+
def _generate_new_file_contents(filename, text_replacement_map):
|
521
|
+
with open(filename, "r") as file_obj:
|
522
|
+
line_set = file_obj.read().splitlines()
|
523
|
+
original_file_contents = line_set
|
524
|
+
|
525
|
+
kwargs = {"line_set_to_modify": original_file_contents,
|
526
|
+
"text_replacement_map": text_replacement_map}
|
527
|
+
modified_line_set = _apply_text_replacements_to_line_set(**kwargs)
|
528
|
+
modified_file_contents = modified_line_set
|
529
|
+
|
530
|
+
kwargs = {"line_set_to_modify": modified_file_contents,
|
531
|
+
"filename": filename}
|
532
|
+
modified_line_set = _apply_text_wrapping_to_line_set(**kwargs)
|
533
|
+
modified_file_contents = modified_line_set
|
534
|
+
|
535
|
+
new_file_contents = modified_file_contents
|
536
|
+
|
537
|
+
return new_file_contents
|
538
|
+
|
539
|
+
|
540
|
+
|
541
|
+
def _apply_text_replacements_to_line_set(line_set_to_modify,
|
542
|
+
text_replacement_map):
|
543
|
+
modified_line_set = line_set_to_modify.copy()
|
544
|
+
for line_idx, line_to_modify in enumerate(line_set_to_modify):
|
545
|
+
modified_line = line_to_modify.rstrip()
|
546
|
+
for old_substring, new_substring in text_replacement_map.items():
|
547
|
+
modified_line = modified_line.replace(old_substring, new_substring)
|
548
|
+
modified_line_set[line_idx] = modified_line
|
549
|
+
if ((re.fullmatch("=+", modified_line)
|
550
|
+
or re.fullmatch("-+", modified_line))
|
551
|
+
and (len(modified_line) > 3)):
|
552
|
+
modified_line_set[line_idx] = (modified_line[0]
|
553
|
+
* len(modified_line_set[line_idx-1]))
|
554
|
+
|
555
|
+
return modified_line_set
|
556
|
+
|
557
|
+
|
558
|
+
|
559
|
+
def _apply_text_wrapping_to_line_set(line_set_to_modify, filename):
|
560
|
+
file_extension = os.path.splitext(filename)[1]
|
561
|
+
|
562
|
+
if file_extension == ".py":
|
563
|
+
func_alias = _apply_text_wrapping_to_line_set_of_py_file
|
564
|
+
elif file_extension in (".md", ".rst"):
|
565
|
+
func_alias = _apply_text_wrapping_to_line_set_of_md_or_rst_file
|
566
|
+
elif file_extension == ".sh":
|
567
|
+
func_alias = _apply_text_wrapping_to_line_set_of_sh_file
|
568
|
+
|
569
|
+
kwargs = {"line_set_to_modify": line_set_to_modify}
|
570
|
+
modified_line_set = (func_alias(**kwargs)
|
571
|
+
if (file_extension in (".py", ".md", ".sh", ".rst"))
|
572
|
+
else line_set_to_modify.copy())
|
573
|
+
|
574
|
+
return modified_line_set
|
575
|
+
|
576
|
+
|
577
|
+
|
578
|
+
def _apply_text_wrapping_to_line_set_of_py_file(line_set_to_modify):
|
579
|
+
modified_line_set = line_set_to_modify.copy()
|
580
|
+
|
581
|
+
pattern_1 = r"#\ ((Copyright)|(For\ setting\ up))\ .*"
|
582
|
+
pattern_2 = r"\s*((lib_name)|(project)|(author))\ =\ \".+"
|
583
|
+
pattern_3 = r"\s*r\"\"\".+\ ``.+``.+"
|
584
|
+
|
585
|
+
end_of_file_has_not_been_reached = True
|
586
|
+
line_idx = 0
|
587
|
+
|
588
|
+
while end_of_file_has_not_been_reached:
|
589
|
+
modified_line = modified_line_set[line_idx].rstrip()
|
590
|
+
|
591
|
+
if re.fullmatch(pattern_1, modified_line):
|
592
|
+
func_alias = _apply_text_wrapping_to_single_line_python_comment
|
593
|
+
elif re.fullmatch(pattern_2, modified_line):
|
594
|
+
func_alias = _apply_text_wrapping_to_single_line_variable_assignment
|
595
|
+
elif re.fullmatch(pattern_3, modified_line):
|
596
|
+
func_alias = _apply_text_wrapping_to_single_line_partial_doc_str
|
597
|
+
else:
|
598
|
+
func_alias = None
|
599
|
+
|
600
|
+
kwargs = {"line_to_modify": modified_line}
|
601
|
+
modified_line_subset = ([modified_line]
|
602
|
+
if (func_alias is None)
|
603
|
+
else func_alias(**kwargs))
|
604
|
+
|
605
|
+
modified_line_set = (modified_line_set[:line_idx]
|
606
|
+
+ modified_line_subset
|
607
|
+
+ modified_line_set[line_idx+1:])
|
608
|
+
|
609
|
+
line_idx += len(modified_line_subset)
|
610
|
+
if line_idx >= len(modified_line_set):
|
611
|
+
end_of_file_has_not_been_reached = False
|
612
|
+
|
613
|
+
return modified_line_set
|
614
|
+
|
615
|
+
|
616
|
+
|
617
|
+
_char_limit_per_line = 80
|
618
|
+
|
619
|
+
|
620
|
+
|
621
|
+
def _apply_text_wrapping_to_single_line_python_comment(line_to_modify):
|
622
|
+
modified_line = line_to_modify.rstrip()
|
623
|
+
|
624
|
+
char_idx = len(modified_line) - len(modified_line.lstrip())
|
625
|
+
indent = " "*char_idx
|
626
|
+
|
627
|
+
modified_line = modified_line.lstrip()[2:]
|
628
|
+
|
629
|
+
kwargs = {"text": modified_line,
|
630
|
+
"width": _char_limit_per_line-char_idx-2,
|
631
|
+
"break_long_words": False}
|
632
|
+
lines_resulting_from_text_wrapping = textwrap.wrap(**kwargs)
|
633
|
+
|
634
|
+
for line_idx, line in enumerate(lines_resulting_from_text_wrapping):
|
635
|
+
lines_resulting_from_text_wrapping[line_idx] = indent + "# " + line
|
636
|
+
|
637
|
+
return lines_resulting_from_text_wrapping
|
638
|
+
|
639
|
+
|
640
|
+
|
641
|
+
def _apply_text_wrapping_to_single_line_variable_assignment(line_to_modify):
|
642
|
+
modified_line = line_to_modify.rstrip()
|
643
|
+
|
644
|
+
if len(modified_line) <= _char_limit_per_line:
|
645
|
+
lines_resulting_from_text_wrapping = [modified_line]
|
646
|
+
else:
|
647
|
+
char_idx_1 = modified_line.find("=")
|
648
|
+
char_idx_2 = _char_limit_per_line - (char_idx_1+4)
|
649
|
+
|
650
|
+
indent = " "*(char_idx_1+3)
|
651
|
+
|
652
|
+
modified_line = modified_line[char_idx_1+2:]
|
653
|
+
|
654
|
+
line_resulting_from_text_wrapping = (line_to_modify[:char_idx_1+2]
|
655
|
+
+ "(\""
|
656
|
+
+ modified_line[1:char_idx_2]
|
657
|
+
+ "\"")
|
658
|
+
lines_resulting_from_text_wrapping = [line_resulting_from_text_wrapping]
|
659
|
+
|
660
|
+
modified_line = modified_line[char_idx_2:]
|
661
|
+
|
662
|
+
while len(indent+modified_line)+2 > _char_limit_per_line:
|
663
|
+
line_resulting_from_text_wrapping = \
|
664
|
+
indent + "\"" + modified_line[:char_idx_2-1] + "\""
|
665
|
+
lines_resulting_from_text_wrapping += \
|
666
|
+
[line_resulting_from_text_wrapping]
|
667
|
+
|
668
|
+
modified_line = modified_line[char_idx_2-1:]
|
669
|
+
|
670
|
+
line_resulting_from_text_wrapping = \
|
671
|
+
indent + "\"" + modified_line + ")"
|
672
|
+
lines_resulting_from_text_wrapping += \
|
673
|
+
[line_resulting_from_text_wrapping]
|
674
|
+
|
675
|
+
return lines_resulting_from_text_wrapping
|
676
|
+
|
677
|
+
|
678
|
+
|
679
|
+
def _apply_text_wrapping_to_single_line_partial_doc_str(line_to_modify):
|
680
|
+
modified_line = line_to_modify.rstrip()
|
681
|
+
|
682
|
+
char_idx = len(modified_line) - len(modified_line.lstrip())
|
683
|
+
indent = " "*char_idx
|
684
|
+
|
685
|
+
modified_line = line_to_modify.lstrip()
|
686
|
+
|
687
|
+
kwargs = {"text": modified_line,
|
688
|
+
"width": _char_limit_per_line - char_idx,
|
689
|
+
"break_long_words": False}
|
690
|
+
lines_resulting_from_text_wrapping = textwrap.wrap(**kwargs)
|
691
|
+
|
692
|
+
for line_idx, line in enumerate(lines_resulting_from_text_wrapping):
|
693
|
+
lines_resulting_from_text_wrapping[line_idx] = indent + line
|
694
|
+
|
695
|
+
return lines_resulting_from_text_wrapping
|
696
|
+
|
697
|
+
|
698
|
+
|
699
|
+
def _apply_text_wrapping_to_line_set_of_md_or_rst_file(line_set_to_modify):
|
700
|
+
modified_line_set = line_set_to_modify.copy()
|
701
|
+
|
702
|
+
pattern_1 = (r"((\[!\[)|(\s+)|(\*\ )|(\-\ \`)|(\.\.\ )|(\{\%)"
|
703
|
+
r"|(\{\{)|(##)|(#\ \-)|(----+)|(====+)).*")
|
704
|
+
pattern_2 = r"((----+)|(====+))"
|
705
|
+
|
706
|
+
end_of_file_has_not_been_reached = True
|
707
|
+
line_idx = 1
|
708
|
+
|
709
|
+
while end_of_file_has_not_been_reached:
|
710
|
+
modified_line_1 = modified_line_set[line_idx].rstrip()
|
711
|
+
|
712
|
+
if (re.fullmatch(pattern_1, modified_line_1)
|
713
|
+
or (modified_line_1 in ("", "#"))):
|
714
|
+
modified_line_subset = [modified_line_1]
|
715
|
+
else:
|
716
|
+
end_of_paragraph_has_not_been_reached = True
|
717
|
+
|
718
|
+
while end_of_paragraph_has_not_been_reached:
|
719
|
+
if line_idx+1 < len(modified_line_set):
|
720
|
+
modified_line_2 = modified_line_set[line_idx+1].rstrip()
|
721
|
+
if (re.fullmatch(pattern_1, modified_line_2)
|
722
|
+
or (modified_line_2 in ("", "#"))):
|
723
|
+
end_of_paragraph_has_not_been_reached = False
|
724
|
+
else:
|
725
|
+
modified_line_1 += " " + modified_line_2.lstrip()
|
726
|
+
modified_line_set.pop(line_idx+1)
|
727
|
+
else:
|
728
|
+
end_of_paragraph_has_not_been_reached = False
|
729
|
+
|
730
|
+
if re.fullmatch(pattern_2, modified_line_2):
|
731
|
+
modified_line_subset = [modified_line_1]
|
732
|
+
else:
|
733
|
+
kwargs = {"text": modified_line_1,
|
734
|
+
"width": _char_limit_per_line,
|
735
|
+
"break_long_words": False}
|
736
|
+
modified_line_subset = textwrap.wrap(**kwargs)
|
737
|
+
|
738
|
+
modified_line_set = (modified_line_set[:line_idx]
|
739
|
+
+ modified_line_subset
|
740
|
+
+ modified_line_set[line_idx+1:])
|
741
|
+
|
742
|
+
line_idx += len(modified_line_subset)
|
743
|
+
if line_idx >= len(modified_line_set):
|
744
|
+
end_of_file_has_not_been_reached = False
|
745
|
+
|
746
|
+
lines_resulting_from_text_wrapping = modified_line_set
|
747
|
+
|
748
|
+
return lines_resulting_from_text_wrapping
|
749
|
+
|
750
|
+
|
751
|
+
|
752
|
+
def _apply_text_wrapping_to_line_set_of_sh_file(line_set_to_modify):
|
753
|
+
modified_line_set = line_set_to_modify.copy()
|
754
|
+
|
755
|
+
pattern = r"#\ .+"
|
756
|
+
|
757
|
+
end_of_file_has_not_been_reached = True
|
758
|
+
line_idx = 2
|
759
|
+
|
760
|
+
while end_of_file_has_not_been_reached:
|
761
|
+
modified_line_1 = modified_line_set[line_idx].rstrip()
|
762
|
+
|
763
|
+
if re.fullmatch(pattern, modified_line_1):
|
764
|
+
prefix = "# "
|
765
|
+
end_of_paragraph_has_not_been_reached = True
|
766
|
+
|
767
|
+
while end_of_paragraph_has_not_been_reached:
|
768
|
+
modified_line_2 = modified_line_set[line_idx+1].rstrip()
|
769
|
+
if re.fullmatch(pattern, modified_line_2):
|
770
|
+
modified_line_1 += " " + modified_line_2.lstrip(prefix)
|
771
|
+
modified_line_set.pop(line_idx+1)
|
772
|
+
else:
|
773
|
+
end_of_paragraph_has_not_been_reached = False
|
774
|
+
|
775
|
+
kwargs = {"text": modified_line_1,
|
776
|
+
"width": _char_limit_per_line,
|
777
|
+
"subsequent_indent": prefix,
|
778
|
+
"break_long_words": False}
|
779
|
+
modified_line_subset = textwrap.wrap(**kwargs)
|
780
|
+
else:
|
781
|
+
modified_line_subset = [modified_line_1]
|
782
|
+
|
783
|
+
modified_line_set = (modified_line_set[:line_idx]
|
784
|
+
+ modified_line_subset
|
785
|
+
+ modified_line_set[line_idx+1:])
|
786
|
+
|
787
|
+
line_idx += len(modified_line_subset)
|
788
|
+
if line_idx >= len(modified_line_set):
|
789
|
+
end_of_file_has_not_been_reached = False
|
790
|
+
|
791
|
+
lines_resulting_from_text_wrapping = modified_line_set
|
792
|
+
|
793
|
+
return lines_resulting_from_text_wrapping
|
794
|
+
|
795
|
+
|
796
|
+
|
797
|
+
def _print_generate_local_git_repo_template_end_msg(
|
798
|
+
start_time,
|
799
|
+
path_to_directory_to_contain_new_repo,
|
800
|
+
lib_name_for_imports):
|
801
|
+
elapsed_time = time.time() - start_time
|
802
|
+
path_to_local_git_repo_template = (path_to_directory_to_contain_new_repo
|
803
|
+
+ "/"
|
804
|
+
+ lib_name_for_imports)
|
805
|
+
|
806
|
+
unformatted_msg = ("Finished generating the local ``git`` repository, "
|
807
|
+
"which is located at the path ``'{}'``. Time taken to "
|
808
|
+
"generate the local ``git`` repository: {} "
|
809
|
+
"s.\n\n\n")
|
810
|
+
msg = unformatted_msg.format(path_to_local_git_repo_template,
|
811
|
+
elapsed_time)
|
812
|
+
print(msg)
|
813
|
+
|
814
|
+
return None
|
815
|
+
|
816
|
+
|
817
|
+
|
818
|
+
def _run_pylibtemplate_as_an_app(cmd_line_args=None):
|
819
|
+
converted_cmd_line_args = _parse_and_convert_cmd_line_args(cmd_line_args)
|
820
|
+
|
821
|
+
kwargs = converted_cmd_line_args
|
822
|
+
generate_local_git_repo_template(**kwargs)
|
823
|
+
|
824
|
+
return None
|
825
|
+
|
826
|
+
|
827
|
+
|
828
|
+
def _parse_and_convert_cmd_line_args(cmd_line_args):
|
829
|
+
arg_names = ("lib_name_for_imports",
|
830
|
+
"abbreviated_lib_name_for_docs",
|
831
|
+
"non_abbreviated_lib_name_for_docs",
|
832
|
+
"author",
|
833
|
+
"email",
|
834
|
+
"gist_id",
|
835
|
+
"path_to_directory_to_contain_new_repo")
|
836
|
+
|
837
|
+
parser = argparse.ArgumentParser()
|
838
|
+
|
839
|
+
global_symbol_table = globals()
|
840
|
+
for arg_name in arg_names:
|
841
|
+
obj_name = "_default_" + arg_name
|
842
|
+
default_arg_val = global_symbol_table[obj_name]
|
843
|
+
|
844
|
+
arg_type = type(default_arg_val)
|
845
|
+
|
846
|
+
parser.add_argument("--"+arg_name,
|
847
|
+
default=default_arg_val,
|
848
|
+
type=arg_type)
|
849
|
+
|
850
|
+
current_func_name = "_parse_and_convert_cmd_line_args"
|
851
|
+
|
852
|
+
try:
|
853
|
+
args = parser.parse_args(args=cmd_line_args)
|
854
|
+
except:
|
855
|
+
err_msg = globals()[current_func_name+"_err_msg_1"]
|
856
|
+
raise SystemExit(err_msg)
|
857
|
+
|
858
|
+
converted_cmd_line_args = dict()
|
859
|
+
for arg_name in arg_names:
|
860
|
+
converted_cmd_line_args[arg_name] = getattr(args, arg_name)
|
861
|
+
|
862
|
+
return converted_cmd_line_args
|
863
|
+
|
864
|
+
|
865
|
+
|
866
|
+
###########################
|
867
|
+
## Define error messages ##
|
868
|
+
###########################
|
869
|
+
|
870
|
+
_check_and_convert_lib_name_for_imports_err_msg_1 = \
|
871
|
+
("The object ``lib_name_for_imports`` must be a string that is a valid "
|
872
|
+
"identifier.")
|
873
|
+
|
874
|
+
_check_and_convert_abbreviated_lib_name_for_docs_err_msg_1 = \
|
875
|
+
_check_and_convert_lib_name_for_imports_err_msg_1
|
876
|
+
|
877
|
+
_make_output_dir_err_msg_1 = \
|
878
|
+
("An error occurred in trying to make the directory ``'{}'``: see "
|
879
|
+
"traceback for details.")
|
880
|
+
|
881
|
+
_clone_pylibtemplate_repo_err_msg_1 = \
|
882
|
+
("An error occurred while trying to clone the ``pylibtemplate`` "
|
883
|
+
"repository: see the traceback for details.")
|
884
|
+
|
885
|
+
_parse_and_convert_cmd_line_args_err_msg_1 = \
|
886
|
+
("The correct form of the command is:\n"
|
887
|
+
"\n"
|
888
|
+
" pylibtemplate "
|
889
|
+
"--lib_name_for_imports="
|
890
|
+
"<lib_name_for_imports> "
|
891
|
+
"--abbreviated_lib_name_for_docs="
|
892
|
+
"<abbreviated_lib_name_for_docs> "
|
893
|
+
"--non_abbreviated_lib_name_for_docs="
|
894
|
+
"<non_abbreviated_lib_name_for_docs> "
|
895
|
+
"--author="
|
896
|
+
"<author> "
|
897
|
+
"--email="
|
898
|
+
"<email> "
|
899
|
+
"--gist_id="
|
900
|
+
"<gist_id> "
|
901
|
+
"--path_to_directory_to_contain_new_repo="
|
902
|
+
"<path_to_directory_to_contain_new_repo>\n"
|
903
|
+
"\n"
|
904
|
+
"where the command line arguments are the same as the parameters of the "
|
905
|
+
"function ``pylibtemplate.generate_local_git_repo_template``. For "
|
906
|
+
"details, see the documentation for the libary ``pylibtemplate`` via the "
|
907
|
+
"link https://mrfitzpa.github.io/pylibtemplate, and navigate the "
|
908
|
+
"reference guide for the version of the library that is installed.")
|
909
|
+
|
910
|
+
|
911
|
+
|
912
|
+
#########################
|
913
|
+
## Main body of script ##
|
914
|
+
#########################
|
915
|
+
|
916
|
+
_ = (_run_pylibtemplate_as_an_app()
|
917
|
+
if (__name__ == "__main__")
|
918
|
+
else None)
|