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.
@@ -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.2
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: LICENSE
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
- The following only applies to scripts defined as lists of lists: if a command
104
- argument is itself a list, it is concatenated into a path appropriate for the
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
- do-something = [["do-something-to", ["subdir", "afile"]]]
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
- would run the command `do-something-to subdir/afile` on a Unix-based operating
113
- system.
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
- (e.g. wildcards, conditional statements) are not available. The recommended
151
- solution is to write a helper script, e.g. `scripts/lots-to-do.py`, which may
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
- The following only applies to scripts defined as lists of lists: if a command
88
- argument is itself a list, it is concatenated into a path appropriate for the
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
- do-something = [["do-something-to", ["subdir", "afile"]]]
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
- would run the command `do-something-to subdir/afile` on a Unix-based operating
97
- system.
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
- (e.g. wildcards, conditional statements) are not available. The recommended
135
- solution is to write a helper script, e.g. `scripts/lots-to-do.py`, which may
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.2"
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 = ["LICENSE"]
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
- git clean -Xdf
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.2
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: LICENSE
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
- The following only applies to scripts defined as lists of lists: if a command
104
- argument is itself a list, it is concatenated into a path appropriate for the
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
- do-something = [["do-something-to", ["subdir", "afile"]]]
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
- would run the command `do-something-to subdir/afile` on a Unix-based operating
113
- system.
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
- (e.g. wildcards, conditional statements) are not available. The recommended
151
- solution is to write a helper script, e.g. `scripts/lots-to-do.py`, which may
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,6 +1,6 @@
1
- LICENSE
2
1
  README.md
3
2
  pyproject.toml
3
+ LICENSES/MIT.txt
4
4
  src/ppqs.egg-info/PKG-INFO
5
5
  src/ppqs.egg-info/SOURCES.txt
6
6
  src/ppqs.egg-info/dependency_links.txt
@@ -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(str(arg_path))
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(script_name, script, argv, cwd):
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
- print(f" ppqs {script_name}: {cmd_str} ".center(col_width, "*"), flush=True)
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 regular_cli(argv):
284
+ def bash_completion(): # pragma: no cover
234
285
  """
235
- Parse regular command line.
286
+ Perform Bash command-line completion.
236
287
  """
237
288
 
238
289
  # Parse scripts
239
290
  pyproject_path = find_pyproject()
240
- project_name, scripts = parse_scripts(pyproject_path)
241
-
242
- # Parse command line
243
- if argv[0] in ("-h", "-help", "--help"):
244
- print_help(project_name, scripts)
245
- raise SystemExit(1)
246
- elif argv[0] not in scripts:
247
- msg = f"unknown script '{argv[0]}'"
248
- raise InvalidScriptError(pyproject_path, msg)
249
- else:
250
- cwd = pyproject_path.parent
251
- run_script(argv[0], scripts[argv[0]], argv[1:], cwd)
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
- pyproject_path = find_pyproject()
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 regular command line
291
- regular_cli(argv)
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
  import pytest
2
6
 
3
7
  from py_proj_quick_scripts import (
@@ -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", str(Path("a", "b"))]],
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.
@@ -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