-
Notifications
You must be signed in to change notification settings - Fork 0
/
create_new_package.py
executable file
·204 lines (146 loc) · 6.6 KB
/
create_new_package.py
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
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Using a model package and the name of your future package, will create a minimal package structure to be used
This model contain a gitlab-ci configuration, a gitignore, a setup.py, some utils functions not available by default,
etc...
"""
import argparse
import shutil
import os
import functools
import sys
# The exist_ok option for os.makedirs appeared in Python 3.2
minimum_version = (3, 2)
reference_folder = "model_package"
package_tag = "{my_package}" # String to be searched and replace by the future package name
valid_characters = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m",
"n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "_",
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
def create_new_package(ref_folder, package_name, package_tag):
"""
Copy directory recursively to create a new package from the model package
:param str ref_folder: Name of the reference folder to copy data from
:param str package_name: Name of the future package
:param str package_tag: Generic and identifiable string to be search in the model directory/file.
"{my_package}" is expected by default
"""
# Directory were the generated package is stored
dest_folder = "generated"
# remove destination folder if exists.
if os.path.isdir(dest_folder):
shutil.rmtree(dest_folder)
# Create partial function that only take src and dst for arguments
custom_copy = functools.partial(copy_with_replace, str1=package_tag, str2=package_name)
for root, dirs, files in os.walk(ref_folder):
# Create empty dirs
for d in dirs:
dpath = os.path.join(root, d)
dpath = dpath.replace(package_tag, package_name)
dpath = dpath.replace(ref_folder, dest_folder)
os.makedirs(dpath)
# Copy files
for f in files:
fpath = os.path.join(root, f)
dst_fpath = fpath.replace(ref_folder, dest_folder)
dst_fpath = dst_fpath.replace(package_tag, package_name)
custom_copy(fpath, dst_fpath)
def copy_with_replace(src, dst, str1, str2, merge=False):
"""
Copy file but replace str1 by str2 every time it's found.
This function is designed to be used on shutil.copytree in place of the default copy function
:param src: Source file
:param dst: destination file
:param str1: string to be searched for
:param str2: replacement string
:param bool merge: If True, will append first file to the existing one
:return:
"""
write_mode = 'w'
if merge:
write_mode = 'a'
with open(src, 'r') as obj:
s = obj.read()
s = s.replace(str1, str2)
new_dst = dst.replace(str1, str2)
with open(new_dst, write_mode) as obj:
obj.write(s)
def update_dependencies(src, dst):
"""
Get the new dependencies in the plugin pyproject.toml and append them in the right section of the
already existing pyproject.toml file
In each plugin, the pyproject.toml must be a list of dependencies as if it was embedded in the rest of the
dependencies, with an extra line at the end:
```
"tkinter",
"pyinstaller",
```
:param src: Source file
:param dst: destination file
"""
# Get the current version of the dst file
with open(dst, 'r') as obj:
current_lines = obj.readlines()
# Search for the line index of the first ']' after the dependency section start
dep_start = "dependencies = [\n"
dep_end = "]\n"
dep_idx_start = current_lines.index(dep_start)
dep_idx_end = current_lines.index(dep_end, dep_idx_start)
with open(src, 'r') as obj:
src_lines = obj.readlines()
# Insert new dependencies at the right position
# Reverse list so we don't need to change the index in the loop
for line in reversed(src_lines):
current_lines.insert(dep_idx_end, line)
# Overwrite the dst file with the updated collated string
with open(dst, 'w') as obj:
obj.write("".join(current_lines))
def add_plugin(plugin_folder, package_name):
"""
Add necessary files to add an .ini file reader into the future package
A plugin folder must have the same structure as the original package
:param str plugin_folder: Name of the folder where the plugin files are in
:param str package_name: Name of the generated package to modify
:return:
"""
dest_folder = "generated"
# Create partial function that only take src and dst for arguments
custom_copy = functools.partial(copy_with_replace, str1=package_tag, str2=package_name, merge=True)
for root, dirs, files in os.walk(plugin_folder):
# Create empty dirs
for d in dirs:
dpath = os.path.join(root, d)
dpath = dpath.replace(package_tag, package_name)
dpath = dpath.replace(plugin_folder, dest_folder)
os.makedirs(dpath, exist_ok=True)
# Copy files
for f in files:
fpath = os.path.join(root, f)
dst_fpath = fpath.replace(plugin_folder, dest_folder)
dst_fpath = dst_fpath.replace(package_tag, package_name)
if "pyproject.toml" in fpath:
update_dependencies(fpath, dst_fpath)
else:
custom_copy(fpath, dst_fpath)
parser = argparse.ArgumentParser()
parser.add_argument("-n", "--name", help="Name of the future package (lowercase)", type=str, required=True)
parser.add_argument("-i", "--ini", help="Add the ini_file plugin for configuration file into the package",
action='store_true')
parser.add_argument("-g", "--gui", help="Add the GUI plugin into the package",
action='store_true')
args = parser.parse_args()
assert sys.version_info >= minimum_version, "You must use Python >= {}.{}".format(*minimum_version)
# Force lower case
package_name = args.name.lower()
# Check if all characters are valid
if not all(c in valid_characters for c in package_name):
raise ValueError("Valid characters for package name are: {}".format("".join(valid_characters)))
# Check if first character is valid
if not package_name[0].isalpha():
raise ValueError("First character of package name ('{}') needs to be a letter".format(package_name))
create_new_package(ref_folder=reference_folder, package_name=package_name, package_tag=package_tag)
if args.gui:
add_plugin(plugin_folder="gui_plugin", package_name=package_name)
args.ini = True # Force the ini plugin if GUI (because it uses it)
if args.ini:
add_plugin(plugin_folder="ini_file_plugin", package_name=package_name)