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