ppqs 2.0.2__tar.gz → 2.2.0__tar.gz
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.
- ppqs-2.2.0/LICENSES/MIT.txt +18 -0
- {ppqs-2.0.2/src/ppqs.egg-info → ppqs-2.2.0}/PKG-INFO +54 -12
- {ppqs-2.0.2 → ppqs-2.2.0}/README.md +52 -10
- {ppqs-2.0.2 → ppqs-2.2.0}/pyproject.toml +13 -4
- {ppqs-2.0.2 → ppqs-2.2.0/src/ppqs.egg-info}/PKG-INFO +54 -12
- {ppqs-2.0.2 → ppqs-2.2.0}/src/ppqs.egg-info/SOURCES.txt +1 -1
- {ppqs-2.0.2 → ppqs-2.2.0}/src/py_proj_quick_scripts/__init__.py +97 -42
- ppqs-2.2.0/test/test_cli.py +138 -0
- {ppqs-2.0.2 → ppqs-2.2.0}/test/test_find_pyproject.py +4 -0
- {ppqs-2.0.2 → ppqs-2.2.0}/test/test_parse_scripts.py +29 -1
- ppqs-2.0.2/LICENSE +0 -18
- ppqs-2.0.2/test/test_cli.py +0 -73
- {ppqs-2.0.2 → ppqs-2.2.0}/setup.cfg +0 -0
- {ppqs-2.0.2 → ppqs-2.2.0}/src/ppqs.egg-info/dependency_links.txt +0 -0
- {ppqs-2.0.2 → ppqs-2.2.0}/src/ppqs.egg-info/entry_points.txt +0 -0
- {ppqs-2.0.2 → ppqs-2.2.0}/src/ppqs.egg-info/requires.txt +0 -0
- {ppqs-2.0.2 → ppqs-2.2.0}/src/ppqs.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Karl Wette
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
|
6
|
+
associated documentation files (the "Software"), to deal in the Software without restriction, including
|
|
7
|
+
without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
|
|
9
|
+
following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial
|
|
12
|
+
portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
|
|
15
|
+
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
|
|
16
|
+
EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
17
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
|
18
|
+
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ppqs
|
|
3
|
-
Version: 2.0
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: Python project quick scripts
|
|
5
5
|
Author-email: Karl Wette <karl.wette@anu.edu.au>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -10,7 +10,7 @@ Classifier: Programming Language :: Python :: 3
|
|
|
10
10
|
Classifier: Operating System :: OS Independent
|
|
11
11
|
Requires-Python: >=3.9
|
|
12
12
|
Description-Content-Type: text/markdown
|
|
13
|
-
License-File:
|
|
13
|
+
License-File: LICENSES/MIT.txt
|
|
14
14
|
Requires-Dist: tomli>=1.1.0; python_version < "3.11"
|
|
15
15
|
Dynamic: license-file
|
|
16
16
|
|
|
@@ -100,17 +100,60 @@ $ ppqs print-something Hi
|
|
|
100
100
|
Hi
|
|
101
101
|
```
|
|
102
102
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
current operating system. For example,
|
|
103
|
+
`ppqs` can recursively run its own scripts, however it will only run any script
|
|
104
|
+
once. For example, given the following script:
|
|
106
105
|
|
|
107
106
|
```toml
|
|
108
107
|
[tool.ppqs.scripts]
|
|
109
|
-
|
|
108
|
+
task-init = """
|
|
109
|
+
echo task init
|
|
110
|
+
"""
|
|
111
|
+
task-a = """
|
|
112
|
+
ppqs task-init
|
|
113
|
+
echo task 1
|
|
114
|
+
"""
|
|
115
|
+
task-b = """
|
|
116
|
+
ppqs task-init
|
|
117
|
+
echo task 2
|
|
118
|
+
"""
|
|
119
|
+
all-tasks = """
|
|
120
|
+
ppqs task-a
|
|
121
|
+
ppqs task-b
|
|
122
|
+
"""
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
running `ppqs all-tasks` will only run `ppqs task-init` once:
|
|
126
|
+
```
|
|
127
|
+
$ ppqs all-tasks
|
|
128
|
+
task init
|
|
129
|
+
task 1
|
|
130
|
+
task 2
|
|
110
131
|
```
|
|
111
132
|
|
|
112
|
-
|
|
113
|
-
|
|
133
|
+
The following only applies to scripts defined as lists of lists:
|
|
134
|
+
|
|
135
|
+
* If a command argument is itself a list, it is concatenated into a path
|
|
136
|
+
appropriate for the current operating system. For example,
|
|
137
|
+
|
|
138
|
+
```toml
|
|
139
|
+
[tool.ppqs.scripts]
|
|
140
|
+
do-something = [["do-something-to", ["subdir", "afile"]]]
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
would run the command `do-something-to subdir/afile` on a Unix-based operating
|
|
144
|
+
system.
|
|
145
|
+
|
|
146
|
+
If the last element of the path contains a wildcard (`*`), the path is
|
|
147
|
+
expanded at runtime into a list of files/directories matching the wildcard
|
|
148
|
+
expression. For example, if the directory `subdir/` contained the files
|
|
149
|
+
`file1.txt`, `file2.txt`, and `file3.dat`, then:
|
|
150
|
+
|
|
151
|
+
```toml
|
|
152
|
+
[tool.ppqs.scripts]
|
|
153
|
+
do-something = [["do-something-to", ["subdir", "*.txt"]]]
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
would run the command `do-something-to subdir/file1.txt subdir/file2.txt`
|
|
114
157
|
|
|
115
158
|
Scripts may also be defined in their own section, which permit a few options:
|
|
116
159
|
|
|
@@ -146,10 +189,9 @@ The default value of some options may be overridden in the
|
|
|
146
189
|
print-header = true
|
|
147
190
|
```
|
|
148
191
|
|
|
149
|
-
Note that commands are *not* passed to the shell, so shell features
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
then be called by `ppqs` as:
|
|
192
|
+
Note that commands are *not* passed to the shell, so shell features are not
|
|
193
|
+
available. The recommended solution is to write a helper script,
|
|
194
|
+
e.g. `scripts/lots-to-do.py`, which may then be called by `ppqs` as:
|
|
153
195
|
|
|
154
196
|
```toml
|
|
155
197
|
[tool.ppqs.scripts]
|
|
@@ -84,17 +84,60 @@ $ ppqs print-something Hi
|
|
|
84
84
|
Hi
|
|
85
85
|
```
|
|
86
86
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
current operating system. For example,
|
|
87
|
+
`ppqs` can recursively run its own scripts, however it will only run any script
|
|
88
|
+
once. For example, given the following script:
|
|
90
89
|
|
|
91
90
|
```toml
|
|
92
91
|
[tool.ppqs.scripts]
|
|
93
|
-
|
|
92
|
+
task-init = """
|
|
93
|
+
echo task init
|
|
94
|
+
"""
|
|
95
|
+
task-a = """
|
|
96
|
+
ppqs task-init
|
|
97
|
+
echo task 1
|
|
98
|
+
"""
|
|
99
|
+
task-b = """
|
|
100
|
+
ppqs task-init
|
|
101
|
+
echo task 2
|
|
102
|
+
"""
|
|
103
|
+
all-tasks = """
|
|
104
|
+
ppqs task-a
|
|
105
|
+
ppqs task-b
|
|
106
|
+
"""
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
running `ppqs all-tasks` will only run `ppqs task-init` once:
|
|
110
|
+
```
|
|
111
|
+
$ ppqs all-tasks
|
|
112
|
+
task init
|
|
113
|
+
task 1
|
|
114
|
+
task 2
|
|
94
115
|
```
|
|
95
116
|
|
|
96
|
-
|
|
97
|
-
|
|
117
|
+
The following only applies to scripts defined as lists of lists:
|
|
118
|
+
|
|
119
|
+
* If a command argument is itself a list, it is concatenated into a path
|
|
120
|
+
appropriate for the current operating system. For example,
|
|
121
|
+
|
|
122
|
+
```toml
|
|
123
|
+
[tool.ppqs.scripts]
|
|
124
|
+
do-something = [["do-something-to", ["subdir", "afile"]]]
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
would run the command `do-something-to subdir/afile` on a Unix-based operating
|
|
128
|
+
system.
|
|
129
|
+
|
|
130
|
+
If the last element of the path contains a wildcard (`*`), the path is
|
|
131
|
+
expanded at runtime into a list of files/directories matching the wildcard
|
|
132
|
+
expression. For example, if the directory `subdir/` contained the files
|
|
133
|
+
`file1.txt`, `file2.txt`, and `file3.dat`, then:
|
|
134
|
+
|
|
135
|
+
```toml
|
|
136
|
+
[tool.ppqs.scripts]
|
|
137
|
+
do-something = [["do-something-to", ["subdir", "*.txt"]]]
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
would run the command `do-something-to subdir/file1.txt subdir/file2.txt`
|
|
98
141
|
|
|
99
142
|
Scripts may also be defined in their own section, which permit a few options:
|
|
100
143
|
|
|
@@ -130,10 +173,9 @@ The default value of some options may be overridden in the
|
|
|
130
173
|
print-header = true
|
|
131
174
|
```
|
|
132
175
|
|
|
133
|
-
Note that commands are *not* passed to the shell, so shell features
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
then be called by `ppqs` as:
|
|
176
|
+
Note that commands are *not* passed to the shell, so shell features are not
|
|
177
|
+
available. The recommended solution is to write a helper script,
|
|
178
|
+
e.g. `scripts/lots-to-do.py`, which may then be called by `ppqs` as:
|
|
137
179
|
|
|
138
180
|
```toml
|
|
139
181
|
[tool.ppqs.scripts]
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ppqs"
|
|
7
|
-
version = "2.0
|
|
7
|
+
version = "2.2.0"
|
|
8
8
|
description = "Python project quick scripts"
|
|
9
9
|
authors = [
|
|
10
10
|
{ name = "Karl Wette", email = "karl.wette@anu.edu.au" },
|
|
@@ -15,7 +15,7 @@ dependencies = [
|
|
|
15
15
|
]
|
|
16
16
|
readme = "README.md"
|
|
17
17
|
license = "MIT"
|
|
18
|
-
license-files = ["
|
|
18
|
+
license-files = ["LICENSES/MIT.txt"]
|
|
19
19
|
classifiers = [
|
|
20
20
|
"Programming Language :: Python :: 3",
|
|
21
21
|
"Operating System :: OS Independent",
|
|
@@ -28,6 +28,15 @@ Issues = "https://github.com/kwwette/ppqs/issues"
|
|
|
28
28
|
[project.scripts]
|
|
29
29
|
ppqs = "py_proj_quick_scripts:cli"
|
|
30
30
|
|
|
31
|
+
[tool.ruff.lint]
|
|
32
|
+
select = [
|
|
33
|
+
"E4",
|
|
34
|
+
"E7",
|
|
35
|
+
"E9",
|
|
36
|
+
"EM",
|
|
37
|
+
"F",
|
|
38
|
+
]
|
|
39
|
+
|
|
31
40
|
[tool.pytest.ini_options]
|
|
32
41
|
minversion = "6.0"
|
|
33
42
|
addopts = "--cov=py_proj_quick_scripts --cov-report=term-missing --cov-fail-under=100"
|
|
@@ -53,14 +62,14 @@ pre-commit run -a
|
|
|
53
62
|
[tool.ppqs.scripts.build]
|
|
54
63
|
description = "Build project"
|
|
55
64
|
script = """
|
|
56
|
-
|
|
65
|
+
ppqs clean
|
|
57
66
|
python -m build
|
|
58
67
|
"""
|
|
59
68
|
|
|
60
69
|
[tool.ppqs.scripts.test]
|
|
61
70
|
description = "Run tests"
|
|
62
71
|
script = """
|
|
63
|
-
pytest ...
|
|
72
|
+
python -m pytest ...
|
|
64
73
|
"""
|
|
65
74
|
|
|
66
75
|
[tool.ppqs.scripts.clean]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ppqs
|
|
3
|
-
Version: 2.0
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: Python project quick scripts
|
|
5
5
|
Author-email: Karl Wette <karl.wette@anu.edu.au>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -10,7 +10,7 @@ Classifier: Programming Language :: Python :: 3
|
|
|
10
10
|
Classifier: Operating System :: OS Independent
|
|
11
11
|
Requires-Python: >=3.9
|
|
12
12
|
Description-Content-Type: text/markdown
|
|
13
|
-
License-File:
|
|
13
|
+
License-File: LICENSES/MIT.txt
|
|
14
14
|
Requires-Dist: tomli>=1.1.0; python_version < "3.11"
|
|
15
15
|
Dynamic: license-file
|
|
16
16
|
|
|
@@ -100,17 +100,60 @@ $ ppqs print-something Hi
|
|
|
100
100
|
Hi
|
|
101
101
|
```
|
|
102
102
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
current operating system. For example,
|
|
103
|
+
`ppqs` can recursively run its own scripts, however it will only run any script
|
|
104
|
+
once. For example, given the following script:
|
|
106
105
|
|
|
107
106
|
```toml
|
|
108
107
|
[tool.ppqs.scripts]
|
|
109
|
-
|
|
108
|
+
task-init = """
|
|
109
|
+
echo task init
|
|
110
|
+
"""
|
|
111
|
+
task-a = """
|
|
112
|
+
ppqs task-init
|
|
113
|
+
echo task 1
|
|
114
|
+
"""
|
|
115
|
+
task-b = """
|
|
116
|
+
ppqs task-init
|
|
117
|
+
echo task 2
|
|
118
|
+
"""
|
|
119
|
+
all-tasks = """
|
|
120
|
+
ppqs task-a
|
|
121
|
+
ppqs task-b
|
|
122
|
+
"""
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
running `ppqs all-tasks` will only run `ppqs task-init` once:
|
|
126
|
+
```
|
|
127
|
+
$ ppqs all-tasks
|
|
128
|
+
task init
|
|
129
|
+
task 1
|
|
130
|
+
task 2
|
|
110
131
|
```
|
|
111
132
|
|
|
112
|
-
|
|
113
|
-
|
|
133
|
+
The following only applies to scripts defined as lists of lists:
|
|
134
|
+
|
|
135
|
+
* If a command argument is itself a list, it is concatenated into a path
|
|
136
|
+
appropriate for the current operating system. For example,
|
|
137
|
+
|
|
138
|
+
```toml
|
|
139
|
+
[tool.ppqs.scripts]
|
|
140
|
+
do-something = [["do-something-to", ["subdir", "afile"]]]
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
would run the command `do-something-to subdir/afile` on a Unix-based operating
|
|
144
|
+
system.
|
|
145
|
+
|
|
146
|
+
If the last element of the path contains a wildcard (`*`), the path is
|
|
147
|
+
expanded at runtime into a list of files/directories matching the wildcard
|
|
148
|
+
expression. For example, if the directory `subdir/` contained the files
|
|
149
|
+
`file1.txt`, `file2.txt`, and `file3.dat`, then:
|
|
150
|
+
|
|
151
|
+
```toml
|
|
152
|
+
[tool.ppqs.scripts]
|
|
153
|
+
do-something = [["do-something-to", ["subdir", "*.txt"]]]
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
would run the command `do-something-to subdir/file1.txt subdir/file2.txt`
|
|
114
157
|
|
|
115
158
|
Scripts may also be defined in their own section, which permit a few options:
|
|
116
159
|
|
|
@@ -146,10 +189,9 @@ The default value of some options may be overridden in the
|
|
|
146
189
|
print-header = true
|
|
147
190
|
```
|
|
148
191
|
|
|
149
|
-
Note that commands are *not* passed to the shell, so shell features
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
then be called by `ppqs` as:
|
|
192
|
+
Note that commands are *not* passed to the shell, so shell features are not
|
|
193
|
+
available. The recommended solution is to write a helper script,
|
|
194
|
+
e.g. `scripts/lots-to-do.py`, which may then be called by `ppqs` as:
|
|
153
195
|
|
|
154
196
|
```toml
|
|
155
197
|
[tool.ppqs.scripts]
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2025 Karl Wette
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: MIT
|
|
4
|
+
|
|
5
|
+
import glob
|
|
1
6
|
import os
|
|
2
7
|
import re
|
|
3
8
|
import shutil
|
|
@@ -12,7 +17,6 @@ else: # pragma: no cover
|
|
|
12
17
|
import tomli as tomllib
|
|
13
18
|
|
|
14
19
|
__author__ = "Karl Wette"
|
|
15
|
-
__version__ = "2.0.2"
|
|
16
20
|
|
|
17
21
|
|
|
18
22
|
class MissingPyProjectError(Exception):
|
|
@@ -151,11 +155,14 @@ def parse_scripts(pyproject_path):
|
|
|
151
155
|
|
|
152
156
|
# List arguments are treated as paths
|
|
153
157
|
arg_path = Path(*arg)
|
|
158
|
+
if any("*" in a for a in arg[:-1]):
|
|
159
|
+
msg2 = f"path argument '{arg_path}' may contain wildcards only in the last element"
|
|
160
|
+
raise InvalidScriptError(pyproject_path, msg2)
|
|
154
161
|
if arg_path.is_absolute():
|
|
155
162
|
msg2 = f"path argument '{arg_path}' must be a relative path"
|
|
156
163
|
raise InvalidScriptError(pyproject_path, msg2)
|
|
157
164
|
|
|
158
|
-
script_line.append(
|
|
165
|
+
script_line.append(arg_path)
|
|
159
166
|
|
|
160
167
|
else:
|
|
161
168
|
script_line.append(arg)
|
|
@@ -171,6 +178,16 @@ def parse_scripts(pyproject_path):
|
|
|
171
178
|
"commands": script_commands,
|
|
172
179
|
}
|
|
173
180
|
|
|
181
|
+
# Check that recursively run scripts exist
|
|
182
|
+
for script in scripts.values():
|
|
183
|
+
for command in script["commands"]:
|
|
184
|
+
cmd_exec = command[0]
|
|
185
|
+
if cmd_exec == "ppqs":
|
|
186
|
+
script_name_r = command[1]
|
|
187
|
+
if script_name_r not in scripts:
|
|
188
|
+
msg = f"script {script_name_r}' does not exist"
|
|
189
|
+
raise InvalidScriptError(pyproject_path, msg)
|
|
190
|
+
|
|
174
191
|
return project_name, scripts
|
|
175
192
|
|
|
176
193
|
|
|
@@ -202,16 +219,49 @@ def print_help(project_name, scripts):
|
|
|
202
219
|
)
|
|
203
220
|
|
|
204
221
|
|
|
205
|
-
def run_script(
|
|
222
|
+
def run_script(scripts, script_name, argv, cwd, has_been_run):
|
|
206
223
|
"""
|
|
207
224
|
Run a script.
|
|
208
225
|
"""
|
|
226
|
+
|
|
209
227
|
col_width = shutil.get_terminal_size().columns
|
|
228
|
+
|
|
229
|
+
# Mark script has having been run
|
|
230
|
+
has_been_run.add(script_name)
|
|
231
|
+
|
|
232
|
+
script = scripts[script_name]
|
|
210
233
|
for command in script["commands"]:
|
|
211
234
|
cmd = []
|
|
212
235
|
|
|
236
|
+
cmd_exec = command.pop(0)
|
|
237
|
+
if cmd_exec == "ppqs":
|
|
238
|
+
|
|
239
|
+
# Recursively run script
|
|
240
|
+
# - silently ignore scripts which have already been run
|
|
241
|
+
script_name_r = command.pop(0)
|
|
242
|
+
if script_name_r not in has_been_run:
|
|
243
|
+
run_script(scripts, script_name_r, argv, cwd, has_been_run)
|
|
244
|
+
continue
|
|
245
|
+
|
|
246
|
+
elif cmd_exec == "python":
|
|
247
|
+
|
|
248
|
+
# Use same Python as ppqs to run Python scripts
|
|
249
|
+
cmd.append(sys.executable)
|
|
250
|
+
|
|
251
|
+
else:
|
|
252
|
+
cmd.append(cmd_exec)
|
|
253
|
+
|
|
213
254
|
for arg in command:
|
|
214
|
-
if arg
|
|
255
|
+
if isinstance(arg, Path):
|
|
256
|
+
if "*" in arg.parts[-1]:
|
|
257
|
+
|
|
258
|
+
# Expand wildcards in last path element
|
|
259
|
+
cmd.extend(sorted(glob.glob(str(arg), recursive=False)))
|
|
260
|
+
|
|
261
|
+
else:
|
|
262
|
+
cmd.append(str(arg))
|
|
263
|
+
|
|
264
|
+
elif arg == "...":
|
|
215
265
|
|
|
216
266
|
# Replace "..." in command with arguments
|
|
217
267
|
cmd.extend(argv)
|
|
@@ -222,7 +272,8 @@ def run_script(script_name, script, argv, cwd):
|
|
|
222
272
|
# Print header
|
|
223
273
|
if script["print-header"]:
|
|
224
274
|
cmd_str = " ".join(cmd)
|
|
225
|
-
|
|
275
|
+
header = f"*** ppqs {script_name}: {cmd_str} ***".center(col_width, "*")
|
|
276
|
+
print(header, flush=True)
|
|
226
277
|
|
|
227
278
|
# Run command
|
|
228
279
|
retn = subprocess.run(cmd, stdin=subprocess.DEVNULL, shell=False, cwd=cwd)
|
|
@@ -230,25 +281,35 @@ def run_script(script_name, script, argv, cwd):
|
|
|
230
281
|
raise SystemExit(retn.returncode)
|
|
231
282
|
|
|
232
283
|
|
|
233
|
-
def
|
|
284
|
+
def bash_completion(): # pragma: no cover
|
|
234
285
|
"""
|
|
235
|
-
|
|
286
|
+
Perform Bash command-line completion.
|
|
236
287
|
"""
|
|
237
288
|
|
|
238
289
|
# Parse scripts
|
|
239
290
|
pyproject_path = find_pyproject()
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
#
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
291
|
+
_, scripts = parse_scripts(pyproject_path)
|
|
292
|
+
|
|
293
|
+
# Get current command line
|
|
294
|
+
line = os.environ["COMP_LINE"]
|
|
295
|
+
words = line.split()
|
|
296
|
+
|
|
297
|
+
if line.endswith(" ") and len(words) == 1:
|
|
298
|
+
|
|
299
|
+
# Complete full script name, e.g.:
|
|
300
|
+
# $ ppqs <TAB><TAB>
|
|
301
|
+
for script_name in scripts:
|
|
302
|
+
print(script_name)
|
|
303
|
+
return
|
|
304
|
+
|
|
305
|
+
if not line.endswith(" ") and len(words) == 2:
|
|
306
|
+
|
|
307
|
+
# Complete partial script name, e.g.:
|
|
308
|
+
# $ ppqs nam<TAB>
|
|
309
|
+
for script_name in scripts:
|
|
310
|
+
if script_name.startswith(words[-1]):
|
|
311
|
+
print(script_name)
|
|
312
|
+
return
|
|
252
313
|
|
|
253
314
|
|
|
254
315
|
def cli(*argv):
|
|
@@ -260,32 +321,26 @@ def cli(*argv):
|
|
|
260
321
|
|
|
261
322
|
if argv[0] == "--bash-completion": # pragma: no cover
|
|
262
323
|
|
|
263
|
-
# Perform Bash completion
|
|
264
|
-
# - suppress any error output
|
|
324
|
+
# Perform Bash command-line completion, suppress any errors
|
|
265
325
|
try:
|
|
266
|
-
|
|
267
|
-
_, scripts = parse_scripts(pyproject_path)
|
|
268
|
-
line = os.environ["COMP_LINE"]
|
|
269
|
-
words = line.split()
|
|
270
|
-
if line.endswith(" ") and len(words) == 1:
|
|
271
|
-
|
|
272
|
-
# Complete full script name, e.g.:
|
|
273
|
-
# $ ppqs <TAB><TAB>
|
|
274
|
-
for script_name in scripts:
|
|
275
|
-
print(script_name)
|
|
276
|
-
|
|
277
|
-
if not line.endswith(" ") and len(words) == 2:
|
|
278
|
-
|
|
279
|
-
# Complete partial script name, e.g.:
|
|
280
|
-
# $ ppqs nam<TAB>
|
|
281
|
-
for script_name in scripts:
|
|
282
|
-
if script_name.startswith(words[-1]):
|
|
283
|
-
print(script_name)
|
|
284
|
-
|
|
326
|
+
bash_completion()
|
|
285
327
|
except Exception:
|
|
286
328
|
pass
|
|
287
329
|
|
|
288
330
|
else:
|
|
289
331
|
|
|
290
|
-
# Parse
|
|
291
|
-
|
|
332
|
+
# Parse scripts
|
|
333
|
+
pyproject_path = find_pyproject()
|
|
334
|
+
project_name, scripts = parse_scripts(pyproject_path)
|
|
335
|
+
|
|
336
|
+
# Parse command line
|
|
337
|
+
if argv[0] in ("-h", "-help", "--help"):
|
|
338
|
+
print_help(project_name, scripts)
|
|
339
|
+
raise SystemExit(1)
|
|
340
|
+
elif argv[0] not in scripts:
|
|
341
|
+
msg = f"unknown script '{argv[0]}'"
|
|
342
|
+
raise InvalidScriptError(pyproject_path, msg)
|
|
343
|
+
else:
|
|
344
|
+
cwd = pyproject_path.parent
|
|
345
|
+
has_been_run = set()
|
|
346
|
+
run_script(scripts, argv[0], argv[1:], cwd, has_been_run)
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2025 Karl Wette
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: MIT
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
|
|
9
|
+
from py_proj_quick_scripts import InvalidScriptError, cli
|
|
10
|
+
|
|
11
|
+
from . import in_dir, write_file
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@pytest.fixture
|
|
15
|
+
def pyproject_path(tmp_path):
|
|
16
|
+
"""
|
|
17
|
+
Write pyproject.toml for command-line tests.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
pyproject_path = tmp_path / "pyproject.toml"
|
|
21
|
+
pyproject_toml = """
|
|
22
|
+
[project]
|
|
23
|
+
name = "test"
|
|
24
|
+
[tool.ppqs.scripts.exit]
|
|
25
|
+
description = "Exits"
|
|
26
|
+
print-header = true
|
|
27
|
+
script = "python -c 'import sys; sys.exit(1)'"
|
|
28
|
+
[tool.ppqs.scripts.echo]
|
|
29
|
+
description = "Echoes"
|
|
30
|
+
script = [
|
|
31
|
+
["python", "-c", "import sys; print(*sys.argv[1:])", "..."],
|
|
32
|
+
]
|
|
33
|
+
[tool.ppqs.scripts.echo-path]
|
|
34
|
+
script = [
|
|
35
|
+
["python", "-c", "import sys; print(*sys.argv[1:])", ["afile"], ["subdir", "bfile"]],
|
|
36
|
+
]
|
|
37
|
+
[tool.ppqs.scripts.echo-wildcard]
|
|
38
|
+
script = [
|
|
39
|
+
["python", "-c", "import sys; print(*sys.argv[1:])", ["subdir", "*.txt"]],
|
|
40
|
+
]
|
|
41
|
+
[tool.ppqs.scripts.greeting]
|
|
42
|
+
script = "echo Hello"
|
|
43
|
+
[tool.ppqs.scripts.farewell]
|
|
44
|
+
script = '''
|
|
45
|
+
ppqs greeting
|
|
46
|
+
ppqs greeting
|
|
47
|
+
echo Goodbye
|
|
48
|
+
'''
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
write_file(pyproject_path, pyproject_toml)
|
|
52
|
+
|
|
53
|
+
return pyproject_path
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def test_help(pyproject_path):
|
|
57
|
+
"""
|
|
58
|
+
Test command line help.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
with in_dir(pyproject_path.parent):
|
|
62
|
+
with pytest.raises(SystemExit):
|
|
63
|
+
cli("--help")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def test_notest(pyproject_path):
|
|
67
|
+
"""
|
|
68
|
+
Test a non-existent test.
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
with in_dir(pyproject_path.parent):
|
|
72
|
+
with pytest.raises(InvalidScriptError):
|
|
73
|
+
cli("notest")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def test_exit(pyproject_path):
|
|
77
|
+
"""
|
|
78
|
+
Test `exit` script.
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
with in_dir(pyproject_path.parent):
|
|
82
|
+
with pytest.raises(SystemExit):
|
|
83
|
+
cli("exit")
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def test_echo(pyproject_path, capfd):
|
|
87
|
+
"""
|
|
88
|
+
Test `echo` script.
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
with in_dir(pyproject_path.parent):
|
|
92
|
+
cli("echo", "Hello")
|
|
93
|
+
captured = capfd.readouterr()
|
|
94
|
+
assert captured.out == "Hello\n"
|
|
95
|
+
assert captured.err == ""
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def test_echo_path(pyproject_path, capfd):
|
|
99
|
+
"""
|
|
100
|
+
Test `echo-path` script.
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
with in_dir(pyproject_path.parent):
|
|
104
|
+
cli("echo-path")
|
|
105
|
+
captured = capfd.readouterr()
|
|
106
|
+
assert captured.out == "afile subdir/bfile\n".replace("/", os.sep)
|
|
107
|
+
assert captured.err == ""
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def test_echo_wildcard(pyproject_path, capfd):
|
|
111
|
+
"""
|
|
112
|
+
Test `echo-wildcard` script.
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
with in_dir(pyproject_path.parent) as wd:
|
|
116
|
+
subdir = wd / "subdir"
|
|
117
|
+
subdir.mkdir()
|
|
118
|
+
write_file(subdir / "a.txt", "Some text")
|
|
119
|
+
write_file(subdir / "b.txt", "Some more text")
|
|
120
|
+
cli("echo-wildcard")
|
|
121
|
+
captured = capfd.readouterr()
|
|
122
|
+
assert captured.out == "subdir/a.txt subdir/b.txt\n".replace("/", os.sep)
|
|
123
|
+
assert captured.err == ""
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def test_recursion(pyproject_path, capfd):
|
|
127
|
+
"""
|
|
128
|
+
Test recursive script running.
|
|
129
|
+
"""
|
|
130
|
+
|
|
131
|
+
with in_dir(pyproject_path.parent):
|
|
132
|
+
cli("farewell")
|
|
133
|
+
captured = capfd.readouterr()
|
|
134
|
+
assert captured.out == "Hello\nGoodbye\n"
|
|
135
|
+
assert captured.err == ""
|
|
136
|
+
|
|
137
|
+
with pytest.raises(InvalidScriptError):
|
|
138
|
+
cli("no-script")
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2025 Karl Wette
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: MIT
|
|
4
|
+
|
|
1
5
|
from contextlib import nullcontext
|
|
2
6
|
from pathlib import Path
|
|
3
7
|
|
|
@@ -219,12 +223,36 @@ from . import in_dir, write_file
|
|
|
219
223
|
"paths": {
|
|
220
224
|
"description": "Run paths script",
|
|
221
225
|
"print-header": False,
|
|
222
|
-
"commands": [["echo",
|
|
226
|
+
"commands": [["echo", Path("a", "b")]],
|
|
223
227
|
}
|
|
224
228
|
}
|
|
225
229
|
),
|
|
226
230
|
id="list script with relative path",
|
|
227
231
|
),
|
|
232
|
+
pytest.param(
|
|
233
|
+
"""
|
|
234
|
+
[tool.ppqs.scripts]
|
|
235
|
+
paths = [
|
|
236
|
+
["echo", ["*", "b"]],
|
|
237
|
+
]
|
|
238
|
+
""",
|
|
239
|
+
pytest.raises(
|
|
240
|
+
InvalidScriptError,
|
|
241
|
+
match="may contain wildcards only in the last element",
|
|
242
|
+
),
|
|
243
|
+
id="list script with path and invalid wildcard",
|
|
244
|
+
),
|
|
245
|
+
pytest.param(
|
|
246
|
+
"""
|
|
247
|
+
[tool.ppqs.scripts.no-script]
|
|
248
|
+
script = "ppqs script-does-not-exist"
|
|
249
|
+
""",
|
|
250
|
+
pytest.raises(
|
|
251
|
+
InvalidScriptError,
|
|
252
|
+
match="does not exist",
|
|
253
|
+
),
|
|
254
|
+
id="recursive script does not exist",
|
|
255
|
+
),
|
|
228
256
|
],
|
|
229
257
|
)
|
|
230
258
|
def test_parse_scripts(tmp_path, script_toml, expectation):
|
ppqs-2.0.2/LICENSE
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
Copyright 2025 Karl Wette
|
|
2
|
-
|
|
3
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
4
|
-
this software and associated documentation files (the “Software”), to deal in
|
|
5
|
-
the Software without restriction, including without limitation the rights to
|
|
6
|
-
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
7
|
-
the Software, and to permit persons to whom the Software is furnished to do so,
|
|
8
|
-
subject to the following conditions:
|
|
9
|
-
|
|
10
|
-
The above copyright notice and this permission notice shall be included in all
|
|
11
|
-
copies or substantial portions of the Software.
|
|
12
|
-
|
|
13
|
-
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
15
|
-
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
16
|
-
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
17
|
-
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
18
|
-
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
ppqs-2.0.2/test/test_cli.py
DELETED
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import pytest
|
|
2
|
-
|
|
3
|
-
from py_proj_quick_scripts import InvalidScriptError, cli
|
|
4
|
-
|
|
5
|
-
from . import in_dir, write_file
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
@pytest.fixture
|
|
9
|
-
def pyproject_path(tmp_path):
|
|
10
|
-
"""
|
|
11
|
-
Write pyproject.toml for command-line tests.
|
|
12
|
-
"""
|
|
13
|
-
|
|
14
|
-
pyproject_path = tmp_path / "pyproject.toml"
|
|
15
|
-
pyproject_toml = """
|
|
16
|
-
[project]
|
|
17
|
-
name = "test"
|
|
18
|
-
[tool.ppqs.scripts.exit]
|
|
19
|
-
description = "Exits"
|
|
20
|
-
print-header = true
|
|
21
|
-
script = "python -c 'import sys; sys.exit(1)'"
|
|
22
|
-
[tool.ppqs.scripts.echo]
|
|
23
|
-
description = "Echoes"
|
|
24
|
-
script = [
|
|
25
|
-
["python", "-c", "import sys; print(*sys.argv[1:])", "..."],
|
|
26
|
-
]
|
|
27
|
-
"""
|
|
28
|
-
|
|
29
|
-
write_file(pyproject_path, pyproject_toml)
|
|
30
|
-
|
|
31
|
-
return pyproject_path
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
def test_help(pyproject_path):
|
|
35
|
-
"""
|
|
36
|
-
Test command line help.
|
|
37
|
-
"""
|
|
38
|
-
|
|
39
|
-
with in_dir(pyproject_path.parent):
|
|
40
|
-
with pytest.raises(SystemExit):
|
|
41
|
-
cli("--help")
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
def test_notest(pyproject_path):
|
|
45
|
-
"""
|
|
46
|
-
Test a non-existent test.
|
|
47
|
-
"""
|
|
48
|
-
|
|
49
|
-
with in_dir(pyproject_path.parent):
|
|
50
|
-
with pytest.raises(InvalidScriptError):
|
|
51
|
-
cli("notest")
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
def test_exit(pyproject_path):
|
|
55
|
-
"""
|
|
56
|
-
Test exit() script.
|
|
57
|
-
"""
|
|
58
|
-
|
|
59
|
-
with in_dir(pyproject_path.parent):
|
|
60
|
-
with pytest.raises(SystemExit):
|
|
61
|
-
cli("exit")
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
def test_echo(pyproject_path, capfd):
|
|
65
|
-
"""
|
|
66
|
-
Test echo() script.
|
|
67
|
-
"""
|
|
68
|
-
|
|
69
|
-
with in_dir(pyproject_path.parent):
|
|
70
|
-
cli("echo", "Hello")
|
|
71
|
-
captured = capfd.readouterr()
|
|
72
|
-
assert captured.out == "Hello\n"
|
|
73
|
-
assert captured.err == ""
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|