recp 0.1.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.
- recp/__init__.py +0 -0
- recp/cli/__init__.py +0 -0
- recp/cli/main.py +141 -0
- recp/py.typed +0 -0
- recp/utils/__init__.py +0 -0
- recp/utils/apply.py +275 -0
- recp/utils/collections.py +43 -0
- recp/utils/config.py +114 -0
- recp/utils/exceptions.py +19 -0
- recp/utils/io.py +75 -0
- recp/utils/recipe.py +369 -0
- recp-0.1.1.dist-info/METADATA +485 -0
- recp-0.1.1.dist-info/RECORD +15 -0
- recp-0.1.1.dist-info/WHEEL +4 -0
- recp-0.1.1.dist-info/entry_points.txt +3 -0
recp/__init__.py
ADDED
|
File without changes
|
recp/cli/__init__.py
ADDED
|
File without changes
|
recp/cli/main.py
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import argparse
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from importlib.metadata import version
|
|
6
|
+
from ..utils.recipe import Recipe
|
|
7
|
+
from ..utils.config import PackageConfig
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_parser() -> argparse.ArgumentParser:
|
|
11
|
+
parser = argparse.ArgumentParser(
|
|
12
|
+
description="run command line tools recp",
|
|
13
|
+
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
14
|
+
allow_abbrev=False
|
|
15
|
+
)
|
|
16
|
+
subparser = parser.add_subparsers(dest="action")
|
|
17
|
+
|
|
18
|
+
# Run parser
|
|
19
|
+
run_parser = subparser.add_parser(
|
|
20
|
+
"run",
|
|
21
|
+
description="run recipes in .yaml format",
|
|
22
|
+
help="run recipes in .yaml format",
|
|
23
|
+
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
24
|
+
allow_abbrev=False
|
|
25
|
+
)
|
|
26
|
+
run_parser.add_argument(
|
|
27
|
+
"recipe",
|
|
28
|
+
type=str,
|
|
29
|
+
help="recipe .yaml file"
|
|
30
|
+
)
|
|
31
|
+
run_parser.add_argument(
|
|
32
|
+
"-t", "--tag",
|
|
33
|
+
nargs="*",
|
|
34
|
+
type=str,
|
|
35
|
+
help="include only steps matching a specific tag"
|
|
36
|
+
)
|
|
37
|
+
run_parser.add_argument(
|
|
38
|
+
"-d", "--dry-run",
|
|
39
|
+
action="store_true",
|
|
40
|
+
help="show the sequence of commands to be run without running them"
|
|
41
|
+
)
|
|
42
|
+
run_parser.add_argument(
|
|
43
|
+
"--ignore-errors",
|
|
44
|
+
action="store_true",
|
|
45
|
+
help="ignore and skip steps that resulted in errors"
|
|
46
|
+
)
|
|
47
|
+
run_parser.add_argument(
|
|
48
|
+
"--unsafe",
|
|
49
|
+
action="store_true",
|
|
50
|
+
help="enable unsafe !expr constructor in recipe files"
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
# Config parser
|
|
54
|
+
config_parser = subparser.add_parser(
|
|
55
|
+
"config", # Show available recipes too
|
|
56
|
+
description="configure recp",
|
|
57
|
+
help="configure recp",
|
|
58
|
+
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
59
|
+
allow_abbrev=False
|
|
60
|
+
)
|
|
61
|
+
config_parser_option_group = config_parser.add_mutually_exclusive_group()
|
|
62
|
+
config_parser_option_group.add_argument(
|
|
63
|
+
"--set",
|
|
64
|
+
type=str,
|
|
65
|
+
nargs=2,
|
|
66
|
+
help="set parameter",
|
|
67
|
+
)
|
|
68
|
+
config_parser_option_group.add_argument(
|
|
69
|
+
"--add",
|
|
70
|
+
type=str,
|
|
71
|
+
help="add a recipe",
|
|
72
|
+
)
|
|
73
|
+
return parser
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def main() -> None:
|
|
77
|
+
# Version print
|
|
78
|
+
if (
|
|
79
|
+
len(sys.argv) == 1
|
|
80
|
+
or (len(sys.argv) == 2 and sys.argv[1] in ("-v", "--version"))
|
|
81
|
+
):
|
|
82
|
+
print(
|
|
83
|
+
f"recp version {version('recp')} developed by Esteban Gómez 2025-"
|
|
84
|
+
f"{datetime.now().year} (Speech Interaction Technology, Aalto "
|
|
85
|
+
"University)"
|
|
86
|
+
)
|
|
87
|
+
sys.exit(0)
|
|
88
|
+
|
|
89
|
+
# Parse args
|
|
90
|
+
parser = get_parser()
|
|
91
|
+
args = parser.parse_args()
|
|
92
|
+
|
|
93
|
+
match args.action:
|
|
94
|
+
case "config":
|
|
95
|
+
config = PackageConfig(app_name="recp", app_author="Esteban Gómez")
|
|
96
|
+
|
|
97
|
+
if args.set:
|
|
98
|
+
config.set_param(param=args.set[0], value=args.set[1])
|
|
99
|
+
|
|
100
|
+
elif args.add:
|
|
101
|
+
config.add_recipe(args.add)
|
|
102
|
+
|
|
103
|
+
else:
|
|
104
|
+
config.print_params()
|
|
105
|
+
|
|
106
|
+
case "run":
|
|
107
|
+
# Check if recipe is a preset
|
|
108
|
+
if not args.recipe.endswith(".yaml"):
|
|
109
|
+
config = PackageConfig(
|
|
110
|
+
app_name="recp",
|
|
111
|
+
app_author="Esteban Gómez"
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
if os.path.isfile(
|
|
115
|
+
os.path.join(config.recipes_dir, args.recipe + ".yaml")
|
|
116
|
+
):
|
|
117
|
+
args.recipe = os.path.join(
|
|
118
|
+
config.recipes_dir,
|
|
119
|
+
args.recipe + ".yaml"
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
else:
|
|
123
|
+
raise FileNotFoundError(
|
|
124
|
+
f"Recipe {args.recipe!r} not found in recipes folder "
|
|
125
|
+
f"{config.recipes_dir!r}"
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
# Create and run recipe
|
|
129
|
+
recipe = Recipe(file=args.recipe, allow_expr=args.unsafe)
|
|
130
|
+
recipe.run(
|
|
131
|
+
tag=args.tag,
|
|
132
|
+
ignore_errors=args.ignore_errors,
|
|
133
|
+
dry_run=args.dry_run
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
case _:
|
|
137
|
+
raise AssertionError
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
if __name__ == "__main__":
|
|
141
|
+
main()
|
recp/py.typed
ADDED
|
File without changes
|
recp/utils/__init__.py
ADDED
|
File without changes
|
recp/utils/apply.py
ADDED
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import random
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing import List
|
|
5
|
+
from .io import get_dir_files
|
|
6
|
+
from .exceptions import LenghtError
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def apply_date(
|
|
10
|
+
cmd_list: List[str],
|
|
11
|
+
token: str,
|
|
12
|
+
format: str = "%Y-%m-%d"
|
|
13
|
+
) -> List[str]:
|
|
14
|
+
"""Replaces a given token by a date.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
cmd_list (List[str]): Input commands.
|
|
18
|
+
token (str): The token within the command strings to be replaced.
|
|
19
|
+
format (str): Date format to use.
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
List[str]: List of modified commands.
|
|
23
|
+
"""
|
|
24
|
+
for cmd_idx, cmd in enumerate(cmd_list):
|
|
25
|
+
cmd_list[cmd_idx] = cmd.replace(token, datetime.now().strftime(format))
|
|
26
|
+
|
|
27
|
+
return cmd_list
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def apply_dir_files(
|
|
31
|
+
cmd_list: List[str],
|
|
32
|
+
token: str,
|
|
33
|
+
dir: str,
|
|
34
|
+
ext: str | List[str] = "*",
|
|
35
|
+
recursive: bool = True
|
|
36
|
+
) -> List[str]:
|
|
37
|
+
"""Replaces a given token by the path to each file in a foleder.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
cmd_list (List[str]): Input commands.
|
|
41
|
+
token (str): The token within the command strings to be replaced.
|
|
42
|
+
dir (str): Path to the folder to be searched.
|
|
43
|
+
ext (str | List[str]): The file extension(s) used to filter files. Only
|
|
44
|
+
files matching these extension(s) will be considered.
|
|
45
|
+
recursive (bool): If `True`, the search is done recursively.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
List[str]: List of modified commands.
|
|
49
|
+
"""
|
|
50
|
+
for cmd in cmd_list:
|
|
51
|
+
files = get_dir_files(dir=dir, ext=ext, recursive=recursive)
|
|
52
|
+
cmd_expanded_list = []
|
|
53
|
+
|
|
54
|
+
for file in files:
|
|
55
|
+
cmd_expanded_list.append(cmd.replace(token, file))
|
|
56
|
+
|
|
57
|
+
return cmd_expanded_list
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def apply_index(
|
|
61
|
+
cmd_list: List[str],
|
|
62
|
+
token: str,
|
|
63
|
+
offset: int = 0,
|
|
64
|
+
zfill: int = 0
|
|
65
|
+
) -> List[str]:
|
|
66
|
+
"""Replaces a given token by the command index value.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
cmd_list (List[str]): Input commands.
|
|
70
|
+
token (str): Token to be replaced.
|
|
71
|
+
offset (int): Offset applied to all values.
|
|
72
|
+
zfill (int | None): Minimum width of the number, padded with leading
|
|
73
|
+
zeros if needed.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
List[str]: List of modified commands.
|
|
77
|
+
"""
|
|
78
|
+
for cmd_idx, cmd in enumerate(cmd_list):
|
|
79
|
+
cmd_list[cmd_idx] = cmd.replace(
|
|
80
|
+
token,
|
|
81
|
+
str(cmd_idx + offset).zfill(zfill)
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
return cmd_list
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def apply_parent_dir(
|
|
88
|
+
cmd_list: List[str],
|
|
89
|
+
token: str,
|
|
90
|
+
path: str
|
|
91
|
+
) -> List[str]:
|
|
92
|
+
"""Replaces a given token by the parent path of a given path.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
cmd_list (List[str]): Input commands.
|
|
96
|
+
token (str): The token within the command strings to be replaced.
|
|
97
|
+
path (str): The file system path from which the parent directory will
|
|
98
|
+
be extracted.
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
List[str]: List of modified commands.
|
|
102
|
+
"""
|
|
103
|
+
for cmd_idx, cmd in enumerate(cmd_list):
|
|
104
|
+
cmd_list[cmd_idx] = cmd.replace(
|
|
105
|
+
token,
|
|
106
|
+
os.path.dirname(os.path.normpath(path))
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
return cmd_list
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def apply_randchoice(
|
|
113
|
+
cmd_list: List[str],
|
|
114
|
+
token: str,
|
|
115
|
+
choices: List[str],
|
|
116
|
+
seed: int | None = None
|
|
117
|
+
) -> List[str]:
|
|
118
|
+
"""Replace a token by a random choice from a list of choices.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
cmd_list (List[str]): Input commands.
|
|
122
|
+
token (str): Token to be replaced.
|
|
123
|
+
choices (List[str]): List of choices.
|
|
124
|
+
seed (int | None): Random seed.
|
|
125
|
+
"""
|
|
126
|
+
generator = random.Random(seed)
|
|
127
|
+
|
|
128
|
+
for cmd_idx, cmd in enumerate(cmd_list):
|
|
129
|
+
value = generator.choice(choices)
|
|
130
|
+
cmd_list[cmd_idx] = cmd.replace(token, str(value))
|
|
131
|
+
|
|
132
|
+
return cmd_list
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def apply_randint(
|
|
136
|
+
cmd_list: List[str],
|
|
137
|
+
token: str,
|
|
138
|
+
min: int,
|
|
139
|
+
max: int,
|
|
140
|
+
seed: int | None = None
|
|
141
|
+
) -> List[str]:
|
|
142
|
+
"""Replace a token by a random integer number within a range, including
|
|
143
|
+
both `min` and `max` within this range.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
cmd_list (List[str]): Input commands.
|
|
147
|
+
token (str): Token to be replaced.
|
|
148
|
+
min (int): Minimum `int` value to be generated.
|
|
149
|
+
max (int): Maximum `int` value to be generated.
|
|
150
|
+
seed (int | None): Random seed.
|
|
151
|
+
"""
|
|
152
|
+
generator = random.Random(seed)
|
|
153
|
+
|
|
154
|
+
for cmd_idx, cmd in enumerate(cmd_list):
|
|
155
|
+
value = generator.randint(min, max)
|
|
156
|
+
cmd_list[cmd_idx] = cmd.replace(token, str(value))
|
|
157
|
+
|
|
158
|
+
return cmd_list
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def apply_randfloat(
|
|
162
|
+
cmd_list: List[str],
|
|
163
|
+
token: str,
|
|
164
|
+
min: float,
|
|
165
|
+
max: float,
|
|
166
|
+
seed: int | None = None
|
|
167
|
+
) -> List[str]:
|
|
168
|
+
"""Replace a token by a random floating-point number within the
|
|
169
|
+
`[min, max)` range.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
cmd_list (List[str]): Input commands.
|
|
173
|
+
token (str): Token to be replaced.
|
|
174
|
+
min (float): Minimum `float` value to be generated (inclusive).
|
|
175
|
+
max (float): Maximum `float` value to be generated (exclusive).
|
|
176
|
+
seed (int | None): Random seed.
|
|
177
|
+
"""
|
|
178
|
+
generator = random.Random(seed)
|
|
179
|
+
|
|
180
|
+
for cmd_idx, cmd in enumerate(cmd_list):
|
|
181
|
+
value = generator.uniform(min, max)
|
|
182
|
+
cmd_list[cmd_idx] = cmd.replace(token, str(value))
|
|
183
|
+
|
|
184
|
+
return cmd_list
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def apply_replace(cmd_list: List[str], **kwargs) -> List[str]:
|
|
188
|
+
"""Replace placeholders by specified values.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
cmd_list (List[str]): Input commands.
|
|
192
|
+
**kwargs: Each subsequent argument corresponds to the value to be
|
|
193
|
+
replaced, and the value is the updated value it will take.
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
List[str]: List of modified commands.
|
|
197
|
+
"""
|
|
198
|
+
for cmd_idx, cmd in enumerate(cmd_list):
|
|
199
|
+
for k, v in kwargs.items():
|
|
200
|
+
cmd = cmd.replace(k, v)
|
|
201
|
+
|
|
202
|
+
cmd_list[cmd_idx] = cmd
|
|
203
|
+
|
|
204
|
+
return cmd_list
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def apply_repeat(cmd_list: List[str], n: int) -> List[str]:
|
|
208
|
+
"""Repeat a command or command list `n` times.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
cmd_list (List[str]): Input commands.
|
|
212
|
+
n (int): Number of repetitions.
|
|
213
|
+
|
|
214
|
+
Returns:
|
|
215
|
+
List[str]: List of modified commands.
|
|
216
|
+
"""
|
|
217
|
+
return cmd_list * n
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def apply_match(
|
|
221
|
+
cmd_list: List[str],
|
|
222
|
+
var: str,
|
|
223
|
+
token: str,
|
|
224
|
+
choices: List[str],
|
|
225
|
+
values: List[str],
|
|
226
|
+
) -> List[str]:
|
|
227
|
+
"""Matches a variable value against multiple choices and replaces a token
|
|
228
|
+
in the command based on the matching value.
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
cmd_list (List[str]): Input commands.
|
|
232
|
+
var (str): Variable to match.
|
|
233
|
+
token (str): Token to replace.
|
|
234
|
+
choices (List[str]): Possible values for `var`.
|
|
235
|
+
values (List[str]): Values used to replace `token` based on the
|
|
236
|
+
matching value from `choices`.
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
List[str]: List of modified commands.
|
|
240
|
+
"""
|
|
241
|
+
# Assertions
|
|
242
|
+
if len(choices) != len(values):
|
|
243
|
+
raise LenghtError(
|
|
244
|
+
"case and value lists must have the same number of elements, but "
|
|
245
|
+
f"case has {len(choices)} elements and value has {len(values)} "
|
|
246
|
+
"elements"
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
# Turn into sets to filter out repeated values
|
|
250
|
+
choice = list(dict.fromkeys(choices)) # Preserves order
|
|
251
|
+
value = list(dict.fromkeys(values))
|
|
252
|
+
|
|
253
|
+
for cmd_idx, cmd in enumerate(cmd_list):
|
|
254
|
+
value_idx = choice.index(var)
|
|
255
|
+
cmd_list[cmd_idx] = cmd.replace(token, value[value_idx])
|
|
256
|
+
|
|
257
|
+
return cmd_list
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def get_apply_registy() -> dict:
|
|
261
|
+
"""Returns the registry of all functions that can be used withing the `run`
|
|
262
|
+
key of a recipe `.yaml` file.
|
|
263
|
+
"""
|
|
264
|
+
return {
|
|
265
|
+
"date": apply_date,
|
|
266
|
+
"dir_files": apply_dir_files,
|
|
267
|
+
"index": apply_index,
|
|
268
|
+
"match": apply_match,
|
|
269
|
+
"parent_dir": apply_parent_dir,
|
|
270
|
+
"randchoice": apply_randchoice,
|
|
271
|
+
"randint": apply_randint,
|
|
272
|
+
"randfloat": apply_randfloat,
|
|
273
|
+
"replace": apply_replace,
|
|
274
|
+
"repeat": apply_repeat
|
|
275
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from contextlib import contextmanager
|
|
3
|
+
from typing import (
|
|
4
|
+
Any,
|
|
5
|
+
List
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def make_list(x: Any) -> List[Any]:
|
|
10
|
+
"""If `x` is a single element, turns it into a `list` of one element.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
x (Any): Element(s) to be returned as a `list`.
|
|
14
|
+
|
|
15
|
+
Returns:
|
|
16
|
+
(list): `x` as a `list`.
|
|
17
|
+
"""
|
|
18
|
+
return [x] if not isinstance(x, list) and x is not None else x
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@contextmanager
|
|
22
|
+
def temp_env(merge: dict = {}):
|
|
23
|
+
"""Temporarily modify the environment variables.
|
|
24
|
+
|
|
25
|
+
This context manager allows temporary modification of the
|
|
26
|
+
environment variables for the duration of the context. Once the
|
|
27
|
+
context is exited, the environment variables are restored to their
|
|
28
|
+
original state.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
merge (dict): A dictionary of environment variables to be
|
|
32
|
+
temporarily added or updated. The keys are the variable
|
|
33
|
+
names and the values are their corresponding values.
|
|
34
|
+
"""
|
|
35
|
+
old_env = os.environ.copy()
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
os.environ.update({k: str(v) for k, v in merge.items()})
|
|
39
|
+
yield
|
|
40
|
+
|
|
41
|
+
finally:
|
|
42
|
+
os.environ.clear()
|
|
43
|
+
os.environ.update(old_env)
|
recp/utils/config.py
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import json
|
|
3
|
+
import shutil
|
|
4
|
+
from typing import Any
|
|
5
|
+
from platformdirs import PlatformDirs
|
|
6
|
+
from .exceptions import (
|
|
7
|
+
FileExtensionError,
|
|
8
|
+
FolderNotFoundError
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class PackageConfig:
|
|
13
|
+
"""Package configuration parameters.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
app_name (str): Application name.
|
|
17
|
+
app_author (str): Application author name.
|
|
18
|
+
"""
|
|
19
|
+
def __init__(
|
|
20
|
+
self,
|
|
21
|
+
app_name: str,
|
|
22
|
+
app_author: str
|
|
23
|
+
) -> None:
|
|
24
|
+
super().__init__()
|
|
25
|
+
|
|
26
|
+
# Params
|
|
27
|
+
self._platform = PlatformDirs(appname=app_name, appauthor=app_author)
|
|
28
|
+
self.maybe_reset_default_config()
|
|
29
|
+
self.config_file = os.path.join(
|
|
30
|
+
self._platform.user_data_dir,
|
|
31
|
+
"settings.json"
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
with open(self.config_file, "r") as f:
|
|
35
|
+
self.config = json.load(f)
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def recipes_dir(self) -> str:
|
|
39
|
+
"""Returns the folder where user recipes are stored.
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
(str): Folder where user recipes are stored.
|
|
43
|
+
"""
|
|
44
|
+
return self.config["recipes.dir"]
|
|
45
|
+
|
|
46
|
+
def maybe_reset_default_config(self) -> None:
|
|
47
|
+
"""Restores the default package configuration if it does not exist."""
|
|
48
|
+
BASE_DIR = self._platform.user_data_dir
|
|
49
|
+
RECIPES_DIR = os.path.join(BASE_DIR, "recipes")
|
|
50
|
+
BASE_SETTINGS_FILE = os.path.join(BASE_DIR, "settings.json")
|
|
51
|
+
BASE_SETTINGS = {"recipes.dir": RECIPES_DIR}
|
|
52
|
+
|
|
53
|
+
os.makedirs(BASE_DIR, exist_ok=True)
|
|
54
|
+
os.makedirs(RECIPES_DIR, exist_ok=True)
|
|
55
|
+
|
|
56
|
+
if not os.path.isfile(BASE_SETTINGS_FILE):
|
|
57
|
+
with open(BASE_SETTINGS_FILE, "w") as f:
|
|
58
|
+
json.dump(obj=BASE_SETTINGS, fp=f, indent=2)
|
|
59
|
+
|
|
60
|
+
def save_config(self) -> None:
|
|
61
|
+
"""Saves the `settings.json` configuration file with the update
|
|
62
|
+
configuration parameters.
|
|
63
|
+
"""
|
|
64
|
+
with open(self.config_file, "w", encoding="utf-8") as f:
|
|
65
|
+
json.dump(self.config, f)
|
|
66
|
+
|
|
67
|
+
def set_param(self, param: str, value: Any) -> None:
|
|
68
|
+
"""Sets a parameter value in the user configuration file.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
param (str): Parameter name.
|
|
72
|
+
value (Any): Parameter value.
|
|
73
|
+
"""
|
|
74
|
+
if param not in self.config:
|
|
75
|
+
raise ValueError(
|
|
76
|
+
f"Invalid parameter {param!r}. Run 'recp config' to list "
|
|
77
|
+
"existing parameters"
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
match param:
|
|
81
|
+
case "recipes.dir":
|
|
82
|
+
if not os.path.isdir(value):
|
|
83
|
+
raise FolderNotFoundError(f"Invalid folder {value!r}")
|
|
84
|
+
|
|
85
|
+
self.config[param] = value
|
|
86
|
+
|
|
87
|
+
case _:
|
|
88
|
+
raise AssertionError
|
|
89
|
+
|
|
90
|
+
self.save_config()
|
|
91
|
+
|
|
92
|
+
def add_recipe(self, file: str) -> None:
|
|
93
|
+
"""Adds a new recipe to the user recipes folder.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
file (str): `.yaml` recipe file to add.
|
|
97
|
+
"""
|
|
98
|
+
if not os.path.isfile(file):
|
|
99
|
+
raise FileNotFoundError(f"File {file!r} not found")
|
|
100
|
+
|
|
101
|
+
if not file.endswith(".yaml"):
|
|
102
|
+
raise FileExtensionError(
|
|
103
|
+
"Only .yaml files can be added as recipes"
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
shutil.copy(file, self.recipes_dir)
|
|
107
|
+
print(f"Recipe {file!r} successfully added to recipes folder")
|
|
108
|
+
|
|
109
|
+
def print_params(self) -> None:
|
|
110
|
+
"""Prints user parameters to the terminal. """
|
|
111
|
+
print(f"file: {self.config_file!r}")
|
|
112
|
+
|
|
113
|
+
for k, v in self.config.items():
|
|
114
|
+
print(f"{k}: {v!r}")
|
recp/utils/exceptions.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
|
|
2
|
+
class RequiredValueNotFoundError(Exception):
|
|
3
|
+
pass
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class MinimumVersionRequirementError(Exception):
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class FolderNotFoundError(Exception):
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class FileExtensionError(Exception):
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class LenghtError(Exception):
|
|
19
|
+
pass
|
recp/utils/io.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from glob import glob
|
|
3
|
+
from typing import (
|
|
4
|
+
Callable,
|
|
5
|
+
List
|
|
6
|
+
)
|
|
7
|
+
from .collections import make_list
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_dir_files(
|
|
11
|
+
dir: str | List[str],
|
|
12
|
+
ext: str | List[str] = "*",
|
|
13
|
+
recursive: bool = True,
|
|
14
|
+
key: Callable | None = None
|
|
15
|
+
) -> List[str]:
|
|
16
|
+
"""Returns a `list` with all the files inside folder with extension `ext`.
|
|
17
|
+
It supports a recursive search and searching in more than one root folder
|
|
18
|
+
at a time if `recursive=True` and `dir` is a `list` of `str`,
|
|
19
|
+
respectively.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
dir (str | List[str]): Folder(s) to be searched.
|
|
23
|
+
ext (str | Tuple[str]): File extensions to be considered. Accepts `.*`
|
|
24
|
+
as a wild card.
|
|
25
|
+
recursive (bool): If `True`, the search inside each folder will be
|
|
26
|
+
recursive.
|
|
27
|
+
key (Callable | None): Key function to sort the results. If it is not
|
|
28
|
+
provided, files will be sorted alphabetically.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
`list` of `str` with the path to each retrieved file.
|
|
32
|
+
|
|
33
|
+
Raises:
|
|
34
|
+
FileNotFoundError: If one of the folder(s) cannot be found.
|
|
35
|
+
"""
|
|
36
|
+
dir = make_list(dir)
|
|
37
|
+
ext = make_list(ext)
|
|
38
|
+
|
|
39
|
+
# Expand user and vars
|
|
40
|
+
for idx, dir_ in enumerate(dir):
|
|
41
|
+
dir[idx] = dir_
|
|
42
|
+
|
|
43
|
+
# Check dirs exist before fetching content
|
|
44
|
+
for dir_ in dir:
|
|
45
|
+
if not os.path.isdir(dir_):
|
|
46
|
+
raise FileNotFoundError(f"Folder not found: '{dir_}'")
|
|
47
|
+
|
|
48
|
+
all_files = []
|
|
49
|
+
|
|
50
|
+
# Search dirs
|
|
51
|
+
for dir_ in dir:
|
|
52
|
+
for ext_ in ext:
|
|
53
|
+
if recursive:
|
|
54
|
+
all_files.extend(
|
|
55
|
+
list(
|
|
56
|
+
glob(
|
|
57
|
+
os.path.join(dir_, "**", f"*{ext_}"),
|
|
58
|
+
recursive=True
|
|
59
|
+
)
|
|
60
|
+
)
|
|
61
|
+
)
|
|
62
|
+
else:
|
|
63
|
+
all_files.extend(list(glob(os.path.join(dir_, f"*{ext_}"))))
|
|
64
|
+
|
|
65
|
+
# Filter out folders with file-like names (e.g. ending in .wav extension)
|
|
66
|
+
flagged_files = []
|
|
67
|
+
|
|
68
|
+
for file in all_files:
|
|
69
|
+
if not os.path.isfile(file):
|
|
70
|
+
flagged_files.append(file)
|
|
71
|
+
|
|
72
|
+
for flagged_file in flagged_files:
|
|
73
|
+
all_files.remove(flagged_file)
|
|
74
|
+
|
|
75
|
+
return sorted(all_files, key=key)
|