ppqs 1.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-1.0/LICENSE +18 -0
- ppqs-1.0/PKG-INFO +140 -0
- ppqs-1.0/README.md +124 -0
- ppqs-1.0/pyproject.toml +66 -0
- ppqs-1.0/setup.cfg +4 -0
- ppqs-1.0/src/ppqs.egg-info/PKG-INFO +140 -0
- ppqs-1.0/src/ppqs.egg-info/SOURCES.txt +13 -0
- ppqs-1.0/src/ppqs.egg-info/dependency_links.txt +1 -0
- ppqs-1.0/src/ppqs.egg-info/entry_points.txt +2 -0
- ppqs-1.0/src/ppqs.egg-info/requires.txt +3 -0
- ppqs-1.0/src/ppqs.egg-info/top_level.txt +1 -0
- ppqs-1.0/src/py_proj_quick_scripts/__init__.py +281 -0
- ppqs-1.0/test/test_cli.py +73 -0
- ppqs-1.0/test/test_find_pyproject.py +30 -0
- ppqs-1.0/test/test_parse_scripts.py +248 -0
ppqs-1.0/LICENSE
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
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-1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ppqs
|
|
3
|
+
Version: 1.0
|
|
4
|
+
Summary: Python project quick scripts
|
|
5
|
+
Author-email: Karl Wette <karl.wette@anu.edu.au>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/kwwette/ppqs
|
|
8
|
+
Project-URL: Issues, https://github.com/kwwette/ppqs/issues
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.9
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: tomli>=1.1.0; python_version < "3.11"
|
|
15
|
+
Dynamic: license-file
|
|
16
|
+
|
|
17
|
+
# Python project quick scripts
|
|
18
|
+
|
|
19
|
+
**Define simple development scripts in `pyproject.toml`**
|
|
20
|
+
|
|
21
|
+
Developing a Python project usually involves many tasks: setting up, installing
|
|
22
|
+
prerequisites, building, testing, cleaning up build products, etc. It can be
|
|
23
|
+
difficult to remember which program to run to perform a given task for a given
|
|
24
|
+
project, particularly when many different tools are being used.
|
|
25
|
+
|
|
26
|
+
**ppqs** defines simple (*"quick"*) scripts in `pyproject.toml`, and provides a
|
|
27
|
+
command-line utility -- `ppqs` -- to run them. In this way **ppqs** hides the
|
|
28
|
+
details of whatever dependency/virtual environment manager, build front-end,
|
|
29
|
+
test harness, etc. the project uses behind a simple interface.
|
|
30
|
+
|
|
31
|
+
Here is an example of **ppqs** being used for its own project:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
$ ppqs --help
|
|
35
|
+
Quick scripts for Python project: ppqs
|
|
36
|
+
|
|
37
|
+
Usage: ppqs {init,lint,build,test,clean}
|
|
38
|
+
|
|
39
|
+
Scripts:
|
|
40
|
+
init Initialise project
|
|
41
|
+
lint Perform linting checks
|
|
42
|
+
build Build project
|
|
43
|
+
test Run tests
|
|
44
|
+
clean Clean up build files
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Scripts are always run from the project root directory, i.e. the directory
|
|
48
|
+
containing `pyproject.toml`. `ppqs` traverses back through parent directories
|
|
49
|
+
until it finds the root directory. It is therefore safe to run `ppqs` from any
|
|
50
|
+
sub-directory within the project directory tree.
|
|
51
|
+
|
|
52
|
+
## Script definition in `pyproject.toml`
|
|
53
|
+
|
|
54
|
+
**ppqs** scripts are simple lists of commands which are run in sequence. If a
|
|
55
|
+
command errors, the remainder of the script is aborted.
|
|
56
|
+
|
|
57
|
+
Scripts are defined in `pyproject.toml` under the `[tool.ppqs.scripts]`
|
|
58
|
+
section. Script names may contain only lowercase letters (`[a-z]`) and dashes
|
|
59
|
+
(`-`). Scripts may be single or multi-line strings: commands are separated by
|
|
60
|
+
newlines, and command arguments are separated by spaces.
|
|
61
|
+
|
|
62
|
+
```toml
|
|
63
|
+
[tool.ppqs.scripts]
|
|
64
|
+
init = "command"
|
|
65
|
+
lint = """
|
|
66
|
+
command
|
|
67
|
+
"""
|
|
68
|
+
build = """
|
|
69
|
+
command1 -v
|
|
70
|
+
command2 -q
|
|
71
|
+
"""
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Scripts may also be defined as lists of lists, i.e. a list of commands, each of
|
|
75
|
+
which is a list of arguments:
|
|
76
|
+
|
|
77
|
+
```toml
|
|
78
|
+
[tool.ppqs.scripts]
|
|
79
|
+
test = [
|
|
80
|
+
["command1"],
|
|
81
|
+
["command2", "-vv"],
|
|
82
|
+
]
|
|
83
|
+
clean = [
|
|
84
|
+
["command"],
|
|
85
|
+
]
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
If a script contains an ellipsis (`...`) it is replaced with any additional
|
|
89
|
+
arguments passed to `ppqs`. For example, the following script:
|
|
90
|
+
|
|
91
|
+
```toml
|
|
92
|
+
[tool.ppqs.scripts]
|
|
93
|
+
print-something = "echo ..."
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
may be run as
|
|
97
|
+
```bash
|
|
98
|
+
$ ppqs print-something Hi
|
|
99
|
+
Hi
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Scripts may also be defined in their own section, which permit a few options:
|
|
103
|
+
|
|
104
|
+
```toml
|
|
105
|
+
[tool.ppqs.scripts.init]
|
|
106
|
+
description = "Initialise project"
|
|
107
|
+
print_header = true
|
|
108
|
+
script = """
|
|
109
|
+
command
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
[tool.ppqs.scripts.build]
|
|
113
|
+
# default description = "Run build script"
|
|
114
|
+
# default print_header = false
|
|
115
|
+
script = [
|
|
116
|
+
["command1", "-v"],
|
|
117
|
+
["command2", "-q"],
|
|
118
|
+
]
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
where:
|
|
124
|
+
|
|
125
|
+
* `description` [optional]: description of the script which appears in `ppqs
|
|
126
|
+
--help`. Default is `Run {name} script` where `name` is the script name.
|
|
127
|
+
|
|
128
|
+
* `print_header` [optional]: If true, print a header before running each command
|
|
129
|
+
in the script. The header consists of the command to be run, centred on the
|
|
130
|
+
console and padded with `*`s. Default is false.
|
|
131
|
+
|
|
132
|
+
Commands are *not* parsed to the shell, so e.g. wildcards are not expanded. If
|
|
133
|
+
the features of a shell script are needed (wildcards, conditional statements,
|
|
134
|
+
etc.), one can write a helper script, e.g. `scripts/lots-to-do.py`, which may
|
|
135
|
+
then be called by `ppqs` as:
|
|
136
|
+
|
|
137
|
+
```toml
|
|
138
|
+
[tool.ppqs.scripts]
|
|
139
|
+
lots-to-do = [["python", "scripts/lots-to-do.py"]]
|
|
140
|
+
```
|
ppqs-1.0/README.md
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Python project quick scripts
|
|
2
|
+
|
|
3
|
+
**Define simple development scripts in `pyproject.toml`**
|
|
4
|
+
|
|
5
|
+
Developing a Python project usually involves many tasks: setting up, installing
|
|
6
|
+
prerequisites, building, testing, cleaning up build products, etc. It can be
|
|
7
|
+
difficult to remember which program to run to perform a given task for a given
|
|
8
|
+
project, particularly when many different tools are being used.
|
|
9
|
+
|
|
10
|
+
**ppqs** defines simple (*"quick"*) scripts in `pyproject.toml`, and provides a
|
|
11
|
+
command-line utility -- `ppqs` -- to run them. In this way **ppqs** hides the
|
|
12
|
+
details of whatever dependency/virtual environment manager, build front-end,
|
|
13
|
+
test harness, etc. the project uses behind a simple interface.
|
|
14
|
+
|
|
15
|
+
Here is an example of **ppqs** being used for its own project:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
$ ppqs --help
|
|
19
|
+
Quick scripts for Python project: ppqs
|
|
20
|
+
|
|
21
|
+
Usage: ppqs {init,lint,build,test,clean}
|
|
22
|
+
|
|
23
|
+
Scripts:
|
|
24
|
+
init Initialise project
|
|
25
|
+
lint Perform linting checks
|
|
26
|
+
build Build project
|
|
27
|
+
test Run tests
|
|
28
|
+
clean Clean up build files
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Scripts are always run from the project root directory, i.e. the directory
|
|
32
|
+
containing `pyproject.toml`. `ppqs` traverses back through parent directories
|
|
33
|
+
until it finds the root directory. It is therefore safe to run `ppqs` from any
|
|
34
|
+
sub-directory within the project directory tree.
|
|
35
|
+
|
|
36
|
+
## Script definition in `pyproject.toml`
|
|
37
|
+
|
|
38
|
+
**ppqs** scripts are simple lists of commands which are run in sequence. If a
|
|
39
|
+
command errors, the remainder of the script is aborted.
|
|
40
|
+
|
|
41
|
+
Scripts are defined in `pyproject.toml` under the `[tool.ppqs.scripts]`
|
|
42
|
+
section. Script names may contain only lowercase letters (`[a-z]`) and dashes
|
|
43
|
+
(`-`). Scripts may be single or multi-line strings: commands are separated by
|
|
44
|
+
newlines, and command arguments are separated by spaces.
|
|
45
|
+
|
|
46
|
+
```toml
|
|
47
|
+
[tool.ppqs.scripts]
|
|
48
|
+
init = "command"
|
|
49
|
+
lint = """
|
|
50
|
+
command
|
|
51
|
+
"""
|
|
52
|
+
build = """
|
|
53
|
+
command1 -v
|
|
54
|
+
command2 -q
|
|
55
|
+
"""
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Scripts may also be defined as lists of lists, i.e. a list of commands, each of
|
|
59
|
+
which is a list of arguments:
|
|
60
|
+
|
|
61
|
+
```toml
|
|
62
|
+
[tool.ppqs.scripts]
|
|
63
|
+
test = [
|
|
64
|
+
["command1"],
|
|
65
|
+
["command2", "-vv"],
|
|
66
|
+
]
|
|
67
|
+
clean = [
|
|
68
|
+
["command"],
|
|
69
|
+
]
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
If a script contains an ellipsis (`...`) it is replaced with any additional
|
|
73
|
+
arguments passed to `ppqs`. For example, the following script:
|
|
74
|
+
|
|
75
|
+
```toml
|
|
76
|
+
[tool.ppqs.scripts]
|
|
77
|
+
print-something = "echo ..."
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
may be run as
|
|
81
|
+
```bash
|
|
82
|
+
$ ppqs print-something Hi
|
|
83
|
+
Hi
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Scripts may also be defined in their own section, which permit a few options:
|
|
87
|
+
|
|
88
|
+
```toml
|
|
89
|
+
[tool.ppqs.scripts.init]
|
|
90
|
+
description = "Initialise project"
|
|
91
|
+
print_header = true
|
|
92
|
+
script = """
|
|
93
|
+
command
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
[tool.ppqs.scripts.build]
|
|
97
|
+
# default description = "Run build script"
|
|
98
|
+
# default print_header = false
|
|
99
|
+
script = [
|
|
100
|
+
["command1", "-v"],
|
|
101
|
+
["command2", "-q"],
|
|
102
|
+
]
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
where:
|
|
108
|
+
|
|
109
|
+
* `description` [optional]: description of the script which appears in `ppqs
|
|
110
|
+
--help`. Default is `Run {name} script` where `name` is the script name.
|
|
111
|
+
|
|
112
|
+
* `print_header` [optional]: If true, print a header before running each command
|
|
113
|
+
in the script. The header consists of the command to be run, centred on the
|
|
114
|
+
console and padded with `*`s. Default is false.
|
|
115
|
+
|
|
116
|
+
Commands are *not* parsed to the shell, so e.g. wildcards are not expanded. If
|
|
117
|
+
the features of a shell script are needed (wildcards, conditional statements,
|
|
118
|
+
etc.), one can write a helper script, e.g. `scripts/lots-to-do.py`, which may
|
|
119
|
+
then be called by `ppqs` as:
|
|
120
|
+
|
|
121
|
+
```toml
|
|
122
|
+
[tool.ppqs.scripts]
|
|
123
|
+
lots-to-do = [["python", "scripts/lots-to-do.py"]]
|
|
124
|
+
```
|
ppqs-1.0/pyproject.toml
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "ppqs"
|
|
7
|
+
version = "1.0"
|
|
8
|
+
description = "Python project quick scripts"
|
|
9
|
+
authors = [
|
|
10
|
+
{ name = "Karl Wette", email = "karl.wette@anu.edu.au" },
|
|
11
|
+
]
|
|
12
|
+
requires-python = ">=3.9"
|
|
13
|
+
dependencies = [
|
|
14
|
+
"tomli>=1.1.0; python_version<'3.11'"
|
|
15
|
+
]
|
|
16
|
+
readme = "README.md"
|
|
17
|
+
license = "MIT"
|
|
18
|
+
license-files = ["LICENSE"]
|
|
19
|
+
classifiers = [
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Operating System :: OS Independent",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
[project.urls]
|
|
25
|
+
Homepage = "https://github.com/kwwette/ppqs"
|
|
26
|
+
Issues = "https://github.com/kwwette/ppqs/issues"
|
|
27
|
+
|
|
28
|
+
[project.scripts]
|
|
29
|
+
ppqs = "py_proj_quick_scripts:cli"
|
|
30
|
+
|
|
31
|
+
[tool.pytest.ini_options]
|
|
32
|
+
minversion = "6.0"
|
|
33
|
+
addopts = "--cov=py_proj_quick_scripts --cov-report=term-missing --cov-fail-under=100"
|
|
34
|
+
testpaths = ["test"]
|
|
35
|
+
|
|
36
|
+
[tool.ppqs.scripts.init]
|
|
37
|
+
description = "Initialise project"
|
|
38
|
+
print_header = true
|
|
39
|
+
script = """
|
|
40
|
+
python -m pip install -r requirements-dev.txt
|
|
41
|
+
pre-commit install
|
|
42
|
+
python -m pip install -e .
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
[tool.ppqs.scripts.lint]
|
|
46
|
+
description = "Perform linting checks"
|
|
47
|
+
script = """
|
|
48
|
+
pre-commit run -a
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
[tool.ppqs.scripts.build]
|
|
52
|
+
description = "Build project"
|
|
53
|
+
script = """
|
|
54
|
+
git clean -Xdf
|
|
55
|
+
python -m build
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
[tool.ppqs.scripts.test]
|
|
59
|
+
description = "Run tests"
|
|
60
|
+
script = """
|
|
61
|
+
pytest ...
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
[tool.ppqs.scripts.clean]
|
|
65
|
+
description = "Clean up build files"
|
|
66
|
+
script = "git clean -Xdf"
|
ppqs-1.0/setup.cfg
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ppqs
|
|
3
|
+
Version: 1.0
|
|
4
|
+
Summary: Python project quick scripts
|
|
5
|
+
Author-email: Karl Wette <karl.wette@anu.edu.au>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/kwwette/ppqs
|
|
8
|
+
Project-URL: Issues, https://github.com/kwwette/ppqs/issues
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.9
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: tomli>=1.1.0; python_version < "3.11"
|
|
15
|
+
Dynamic: license-file
|
|
16
|
+
|
|
17
|
+
# Python project quick scripts
|
|
18
|
+
|
|
19
|
+
**Define simple development scripts in `pyproject.toml`**
|
|
20
|
+
|
|
21
|
+
Developing a Python project usually involves many tasks: setting up, installing
|
|
22
|
+
prerequisites, building, testing, cleaning up build products, etc. It can be
|
|
23
|
+
difficult to remember which program to run to perform a given task for a given
|
|
24
|
+
project, particularly when many different tools are being used.
|
|
25
|
+
|
|
26
|
+
**ppqs** defines simple (*"quick"*) scripts in `pyproject.toml`, and provides a
|
|
27
|
+
command-line utility -- `ppqs` -- to run them. In this way **ppqs** hides the
|
|
28
|
+
details of whatever dependency/virtual environment manager, build front-end,
|
|
29
|
+
test harness, etc. the project uses behind a simple interface.
|
|
30
|
+
|
|
31
|
+
Here is an example of **ppqs** being used for its own project:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
$ ppqs --help
|
|
35
|
+
Quick scripts for Python project: ppqs
|
|
36
|
+
|
|
37
|
+
Usage: ppqs {init,lint,build,test,clean}
|
|
38
|
+
|
|
39
|
+
Scripts:
|
|
40
|
+
init Initialise project
|
|
41
|
+
lint Perform linting checks
|
|
42
|
+
build Build project
|
|
43
|
+
test Run tests
|
|
44
|
+
clean Clean up build files
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Scripts are always run from the project root directory, i.e. the directory
|
|
48
|
+
containing `pyproject.toml`. `ppqs` traverses back through parent directories
|
|
49
|
+
until it finds the root directory. It is therefore safe to run `ppqs` from any
|
|
50
|
+
sub-directory within the project directory tree.
|
|
51
|
+
|
|
52
|
+
## Script definition in `pyproject.toml`
|
|
53
|
+
|
|
54
|
+
**ppqs** scripts are simple lists of commands which are run in sequence. If a
|
|
55
|
+
command errors, the remainder of the script is aborted.
|
|
56
|
+
|
|
57
|
+
Scripts are defined in `pyproject.toml` under the `[tool.ppqs.scripts]`
|
|
58
|
+
section. Script names may contain only lowercase letters (`[a-z]`) and dashes
|
|
59
|
+
(`-`). Scripts may be single or multi-line strings: commands are separated by
|
|
60
|
+
newlines, and command arguments are separated by spaces.
|
|
61
|
+
|
|
62
|
+
```toml
|
|
63
|
+
[tool.ppqs.scripts]
|
|
64
|
+
init = "command"
|
|
65
|
+
lint = """
|
|
66
|
+
command
|
|
67
|
+
"""
|
|
68
|
+
build = """
|
|
69
|
+
command1 -v
|
|
70
|
+
command2 -q
|
|
71
|
+
"""
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Scripts may also be defined as lists of lists, i.e. a list of commands, each of
|
|
75
|
+
which is a list of arguments:
|
|
76
|
+
|
|
77
|
+
```toml
|
|
78
|
+
[tool.ppqs.scripts]
|
|
79
|
+
test = [
|
|
80
|
+
["command1"],
|
|
81
|
+
["command2", "-vv"],
|
|
82
|
+
]
|
|
83
|
+
clean = [
|
|
84
|
+
["command"],
|
|
85
|
+
]
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
If a script contains an ellipsis (`...`) it is replaced with any additional
|
|
89
|
+
arguments passed to `ppqs`. For example, the following script:
|
|
90
|
+
|
|
91
|
+
```toml
|
|
92
|
+
[tool.ppqs.scripts]
|
|
93
|
+
print-something = "echo ..."
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
may be run as
|
|
97
|
+
```bash
|
|
98
|
+
$ ppqs print-something Hi
|
|
99
|
+
Hi
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Scripts may also be defined in their own section, which permit a few options:
|
|
103
|
+
|
|
104
|
+
```toml
|
|
105
|
+
[tool.ppqs.scripts.init]
|
|
106
|
+
description = "Initialise project"
|
|
107
|
+
print_header = true
|
|
108
|
+
script = """
|
|
109
|
+
command
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
[tool.ppqs.scripts.build]
|
|
113
|
+
# default description = "Run build script"
|
|
114
|
+
# default print_header = false
|
|
115
|
+
script = [
|
|
116
|
+
["command1", "-v"],
|
|
117
|
+
["command2", "-q"],
|
|
118
|
+
]
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
where:
|
|
124
|
+
|
|
125
|
+
* `description` [optional]: description of the script which appears in `ppqs
|
|
126
|
+
--help`. Default is `Run {name} script` where `name` is the script name.
|
|
127
|
+
|
|
128
|
+
* `print_header` [optional]: If true, print a header before running each command
|
|
129
|
+
in the script. The header consists of the command to be run, centred on the
|
|
130
|
+
console and padded with `*`s. Default is false.
|
|
131
|
+
|
|
132
|
+
Commands are *not* parsed to the shell, so e.g. wildcards are not expanded. If
|
|
133
|
+
the features of a shell script are needed (wildcards, conditional statements,
|
|
134
|
+
etc.), one can write a helper script, e.g. `scripts/lots-to-do.py`, which may
|
|
135
|
+
then be called by `ppqs` as:
|
|
136
|
+
|
|
137
|
+
```toml
|
|
138
|
+
[tool.ppqs.scripts]
|
|
139
|
+
lots-to-do = [["python", "scripts/lots-to-do.py"]]
|
|
140
|
+
```
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/ppqs.egg-info/PKG-INFO
|
|
5
|
+
src/ppqs.egg-info/SOURCES.txt
|
|
6
|
+
src/ppqs.egg-info/dependency_links.txt
|
|
7
|
+
src/ppqs.egg-info/entry_points.txt
|
|
8
|
+
src/ppqs.egg-info/requires.txt
|
|
9
|
+
src/ppqs.egg-info/top_level.txt
|
|
10
|
+
src/py_proj_quick_scripts/__init__.py
|
|
11
|
+
test/test_cli.py
|
|
12
|
+
test/test_find_pyproject.py
|
|
13
|
+
test/test_parse_scripts.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
py_proj_quick_scripts
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
import shutil
|
|
4
|
+
import subprocess
|
|
5
|
+
import sys
|
|
6
|
+
import textwrap
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
if sys.version_info >= (3, 11): # pragma: no cover
|
|
10
|
+
import tomllib
|
|
11
|
+
else: # pragma: no cover
|
|
12
|
+
import tomli as tomllib
|
|
13
|
+
|
|
14
|
+
__author__ = "Karl Wette"
|
|
15
|
+
__version__ = "1.0"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class MissingPyProjectError(Exception):
|
|
19
|
+
"""
|
|
20
|
+
Raise if 'pyproject.toml' could not be found.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class InvalidScriptError(Exception):
|
|
27
|
+
"""
|
|
28
|
+
Raise for invalid 'pyproject.toml' script errors.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(self, pyproject_path, msg):
|
|
32
|
+
self.pyproject_path = pyproject_path
|
|
33
|
+
self.msg = msg
|
|
34
|
+
super().__init__(self.msg)
|
|
35
|
+
|
|
36
|
+
def __str__(self):
|
|
37
|
+
return f"{self.pyproject_path}: {self.msg}"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def find_pyproject():
|
|
41
|
+
"""
|
|
42
|
+
Look for pyproject.toml in current/parent directories.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
# Start in the current directory
|
|
46
|
+
current_dir = starting_dir = Path.cwd()
|
|
47
|
+
|
|
48
|
+
# Traverse down directory parents until just before root
|
|
49
|
+
while len(current_dir.parts) > 1:
|
|
50
|
+
|
|
51
|
+
# Stop when user no longer has write access to parent directory
|
|
52
|
+
# - excludes any pyproject.toml in home directory, /tmp, etc.
|
|
53
|
+
if not os.access(current_dir.parent, os.W_OK):
|
|
54
|
+
break
|
|
55
|
+
|
|
56
|
+
# If pyproject.toml is in this directory, return its path
|
|
57
|
+
pyproject_path = current_dir / "pyproject.toml"
|
|
58
|
+
if os.access(pyproject_path, os.F_OK | os.R_OK):
|
|
59
|
+
return pyproject_path
|
|
60
|
+
|
|
61
|
+
# Look in parent directory
|
|
62
|
+
current_dir = current_dir.parent
|
|
63
|
+
|
|
64
|
+
msg = f"'pyproject.toml' could not be found in '{starting_dir}' or its parent directories"
|
|
65
|
+
raise MissingPyProjectError(msg)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def parse_scripts(pyproject_path):
|
|
69
|
+
"""
|
|
70
|
+
Parse scripts from pyproject.toml.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
# Read pyproject.toml
|
|
74
|
+
pyproject_toml = tomllib.load(pyproject_path.open("rb"))
|
|
75
|
+
project_name = pyproject_toml["project"]["name"]
|
|
76
|
+
try:
|
|
77
|
+
scripts_toml = pyproject_toml["tool"]["ppqs"]["scripts"]
|
|
78
|
+
except KeyError:
|
|
79
|
+
scripts_toml = {}
|
|
80
|
+
if not scripts_toml:
|
|
81
|
+
msg = "does not contain a non-empty '[tool.ppqs.scripts]' section"
|
|
82
|
+
raise InvalidScriptError(pyproject_path, msg)
|
|
83
|
+
|
|
84
|
+
# Parse scripts
|
|
85
|
+
scripts = {}
|
|
86
|
+
for script_name, script_toml in scripts_toml.items():
|
|
87
|
+
|
|
88
|
+
# Check name
|
|
89
|
+
if script_name.startswith("-"):
|
|
90
|
+
msg = "script name '{script_name}' may not start with '-'"
|
|
91
|
+
raise InvalidScriptError(pyproject_path, msg)
|
|
92
|
+
invalid_chars = re.sub(r"[a-z-]", "", script_name)
|
|
93
|
+
if len(invalid_chars) > 0:
|
|
94
|
+
msg = f"script name '{script_name}' may not contain characters '{invalid_chars}'"
|
|
95
|
+
raise InvalidScriptError(pyproject_path, msg)
|
|
96
|
+
|
|
97
|
+
# Create default description
|
|
98
|
+
script_description = f"Run {script_name} script"
|
|
99
|
+
script_print_header = False
|
|
100
|
+
script_commands_toml = script_toml
|
|
101
|
+
|
|
102
|
+
if isinstance(script_toml, dict):
|
|
103
|
+
|
|
104
|
+
# Check for invalid keys
|
|
105
|
+
invalid_keys = [
|
|
106
|
+
k
|
|
107
|
+
for k in script_toml.keys()
|
|
108
|
+
if k not in ("description", "print_header", "script")
|
|
109
|
+
]
|
|
110
|
+
if len(invalid_keys) > 0:
|
|
111
|
+
invalid_keys_str = "', '".join(invalid_keys)
|
|
112
|
+
msg = (
|
|
113
|
+
f"script '{script_name}' may not contain keys '{invalid_keys_str}'"
|
|
114
|
+
)
|
|
115
|
+
raise InvalidScriptError(pyproject_path, msg)
|
|
116
|
+
|
|
117
|
+
script_description = str(script_toml.get("description", script_description))
|
|
118
|
+
script_print_header = bool(
|
|
119
|
+
script_toml.get("print_header", script_print_header)
|
|
120
|
+
)
|
|
121
|
+
script_commands_toml = script_toml["script"]
|
|
122
|
+
|
|
123
|
+
msg = f"script '{script_name}' may be either a string or a list of lists"
|
|
124
|
+
if isinstance(script_commands_toml, str):
|
|
125
|
+
|
|
126
|
+
# Parse a script as a string
|
|
127
|
+
script_commands = [
|
|
128
|
+
line.split()
|
|
129
|
+
for line in script_commands_toml.splitlines()
|
|
130
|
+
if len(line.strip()) > 0
|
|
131
|
+
]
|
|
132
|
+
|
|
133
|
+
elif isinstance(script_commands_toml, list):
|
|
134
|
+
|
|
135
|
+
# Parse a script as a list of lists
|
|
136
|
+
script_commands = []
|
|
137
|
+
for line in script_commands_toml:
|
|
138
|
+
if not isinstance(line, list):
|
|
139
|
+
raise InvalidScriptError(pyproject_path, msg)
|
|
140
|
+
script_line = []
|
|
141
|
+
for arg in line:
|
|
142
|
+
if isinstance(arg, list):
|
|
143
|
+
|
|
144
|
+
# List arguments are treated as paths
|
|
145
|
+
arg_path = Path(*arg)
|
|
146
|
+
if arg_path.is_absolute():
|
|
147
|
+
msg2 = f"path argument '{arg_path}' must be a relative path"
|
|
148
|
+
raise InvalidScriptError(pyproject_path, msg2)
|
|
149
|
+
|
|
150
|
+
script_line.append(str(arg_path))
|
|
151
|
+
|
|
152
|
+
else:
|
|
153
|
+
script_line.append(arg)
|
|
154
|
+
|
|
155
|
+
script_commands.append(script_line)
|
|
156
|
+
|
|
157
|
+
else:
|
|
158
|
+
raise InvalidScriptError(pyproject_path, msg)
|
|
159
|
+
|
|
160
|
+
scripts[script_name] = {
|
|
161
|
+
"description": script_description,
|
|
162
|
+
"print_header": script_print_header,
|
|
163
|
+
"commands": script_commands,
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return project_name, scripts
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def print_help(project_name, scripts):
|
|
170
|
+
"""
|
|
171
|
+
Print help.
|
|
172
|
+
"""
|
|
173
|
+
script_names = ",".join(scripts.keys())
|
|
174
|
+
max_script_name_len = max(len(script_name) for script_name in scripts)
|
|
175
|
+
list_of_scripts = "\n".join(
|
|
176
|
+
" "
|
|
177
|
+
+ script_name.ljust(max_script_name_len, " ")
|
|
178
|
+
+ " "
|
|
179
|
+
+ script["description"][0].upper()
|
|
180
|
+
+ script["description"][1:]
|
|
181
|
+
for script_name, script in scripts.items()
|
|
182
|
+
)
|
|
183
|
+
print(
|
|
184
|
+
textwrap.dedent(
|
|
185
|
+
f"""\
|
|
186
|
+
Quick scripts for Python project: {project_name}
|
|
187
|
+
|
|
188
|
+
Usage: ppqs {{{script_names}}}
|
|
189
|
+
|
|
190
|
+
Scripts:
|
|
191
|
+
"""
|
|
192
|
+
)
|
|
193
|
+
+ list_of_scripts
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def run_script(script, argv, cwd):
|
|
198
|
+
"""
|
|
199
|
+
Run a script.
|
|
200
|
+
"""
|
|
201
|
+
col_width = shutil.get_terminal_size().columns
|
|
202
|
+
for command in script["commands"]:
|
|
203
|
+
cmd = []
|
|
204
|
+
|
|
205
|
+
# Replace "..." in command with arguments
|
|
206
|
+
for arg in command:
|
|
207
|
+
if arg == "...":
|
|
208
|
+
cmd.extend(argv)
|
|
209
|
+
else:
|
|
210
|
+
cmd.append(arg)
|
|
211
|
+
|
|
212
|
+
# Print header
|
|
213
|
+
if script["print_header"]:
|
|
214
|
+
cmd_str = " ".join(cmd)
|
|
215
|
+
print(f" {cmd_str} ".center(col_width, "*"))
|
|
216
|
+
|
|
217
|
+
# Run command
|
|
218
|
+
retn = subprocess.run(cmd, stdin=subprocess.DEVNULL, shell=False, cwd=cwd)
|
|
219
|
+
if retn.returncode != 0:
|
|
220
|
+
raise SystemExit(retn.returncode)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def regular_cli(argv):
|
|
224
|
+
"""
|
|
225
|
+
Parse regular command line.
|
|
226
|
+
"""
|
|
227
|
+
|
|
228
|
+
# Parse scripts
|
|
229
|
+
pyproject_path = find_pyproject()
|
|
230
|
+
project_name, scripts = parse_scripts(pyproject_path)
|
|
231
|
+
|
|
232
|
+
# Parse command line
|
|
233
|
+
if argv[0] in ("-h", "-help", "--help"):
|
|
234
|
+
print_help(project_name, scripts)
|
|
235
|
+
raise SystemExit(1)
|
|
236
|
+
elif argv[0] not in scripts:
|
|
237
|
+
msg = f"unknown script '{argv[0]}'"
|
|
238
|
+
raise InvalidScriptError(pyproject_path, msg)
|
|
239
|
+
else:
|
|
240
|
+
cwd = pyproject_path.parent
|
|
241
|
+
run_script(scripts[argv[0]], argv[1:], cwd)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def cli(*argv):
|
|
245
|
+
"""
|
|
246
|
+
Parse command line.
|
|
247
|
+
"""
|
|
248
|
+
|
|
249
|
+
argv = [str(a) for a in (argv or sys.argv[1:] or ["--help"])]
|
|
250
|
+
|
|
251
|
+
if argv[0] == "--bash-completion": # pragma: no cover
|
|
252
|
+
|
|
253
|
+
# Perform Bash completion
|
|
254
|
+
# - suppress any error output
|
|
255
|
+
try:
|
|
256
|
+
pyproject_path = find_pyproject()
|
|
257
|
+
_, scripts = parse_scripts(pyproject_path)
|
|
258
|
+
line = os.environ["COMP_LINE"]
|
|
259
|
+
words = line.split()
|
|
260
|
+
if line.endswith(" ") and len(words) == 1:
|
|
261
|
+
|
|
262
|
+
# Complete full script name, e.g.:
|
|
263
|
+
# $ ppqs <TAB><TAB>
|
|
264
|
+
for script_name in scripts:
|
|
265
|
+
print(script_name)
|
|
266
|
+
|
|
267
|
+
if not line.endswith(" ") and len(words) == 2:
|
|
268
|
+
|
|
269
|
+
# Complete partial script name, e.g.:
|
|
270
|
+
# $ ppqs nam<TAB>
|
|
271
|
+
for script_name in scripts:
|
|
272
|
+
if script_name.startswith(words[-1]):
|
|
273
|
+
print(script_name)
|
|
274
|
+
|
|
275
|
+
except Exception:
|
|
276
|
+
pass
|
|
277
|
+
|
|
278
|
+
else:
|
|
279
|
+
|
|
280
|
+
# Parse regular command line
|
|
281
|
+
regular_cli(argv)
|
|
@@ -0,0 +1,73 @@
|
|
|
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 == ""
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from py_proj_quick_scripts import (
|
|
4
|
+
MissingPyProjectError,
|
|
5
|
+
find_pyproject,
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
from . import in_dir, write_file
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_find_pyproject(tmp_path):
|
|
12
|
+
"""
|
|
13
|
+
Test find_pyproject().
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
with in_dir(tmp_path):
|
|
17
|
+
with pytest.raises(MissingPyProjectError):
|
|
18
|
+
find_pyproject()
|
|
19
|
+
|
|
20
|
+
pyproject_path = tmp_path / "pyproject.toml"
|
|
21
|
+
write_file(pyproject_path, "[tool.ppqs.scripts]")
|
|
22
|
+
|
|
23
|
+
with in_dir(pyproject_path.parent):
|
|
24
|
+
assert find_pyproject() == pyproject_path
|
|
25
|
+
|
|
26
|
+
with in_dir(pyproject_path.parent / "subdir"):
|
|
27
|
+
assert find_pyproject() == pyproject_path
|
|
28
|
+
|
|
29
|
+
with in_dir(pyproject_path.parent / "subdir" / "subdir"):
|
|
30
|
+
assert find_pyproject() == pyproject_path
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
from contextlib import nullcontext
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
from py_proj_quick_scripts import (
|
|
7
|
+
InvalidScriptError,
|
|
8
|
+
parse_scripts,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
from . import in_dir, write_file
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@pytest.mark.parametrize(
|
|
15
|
+
"script_toml,expectation",
|
|
16
|
+
[
|
|
17
|
+
pytest.param(
|
|
18
|
+
"""
|
|
19
|
+
[some.section]
|
|
20
|
+
""",
|
|
21
|
+
pytest.raises(
|
|
22
|
+
InvalidScriptError, match=r"non-empty '\[tool.ppqs.scripts\]' section"
|
|
23
|
+
),
|
|
24
|
+
id="missing section",
|
|
25
|
+
),
|
|
26
|
+
pytest.param(
|
|
27
|
+
"""
|
|
28
|
+
[tool.ppqs.scripts]
|
|
29
|
+
""",
|
|
30
|
+
pytest.raises(
|
|
31
|
+
InvalidScriptError, match=r"non-empty '\[tool.ppqs.scripts\]' section"
|
|
32
|
+
),
|
|
33
|
+
id="empty section",
|
|
34
|
+
),
|
|
35
|
+
pytest.param(
|
|
36
|
+
"""
|
|
37
|
+
[tool.ppqs.scripts]
|
|
38
|
+
init = 123
|
|
39
|
+
""",
|
|
40
|
+
pytest.raises(
|
|
41
|
+
InvalidScriptError, match="either a string or a list of lists"
|
|
42
|
+
),
|
|
43
|
+
id="wrong type",
|
|
44
|
+
),
|
|
45
|
+
pytest.param(
|
|
46
|
+
"""
|
|
47
|
+
[tool.ppqs.scripts]
|
|
48
|
+
init = "pip install -r requirements.txt"
|
|
49
|
+
""",
|
|
50
|
+
nullcontext(
|
|
51
|
+
{
|
|
52
|
+
"init": {
|
|
53
|
+
"description": "Run init script",
|
|
54
|
+
"print_header": False,
|
|
55
|
+
"commands": [["pip", "install", "-r", "requirements.txt"]],
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
),
|
|
59
|
+
id="string script",
|
|
60
|
+
),
|
|
61
|
+
pytest.param(
|
|
62
|
+
"""
|
|
63
|
+
[tool.ppqs.scripts]
|
|
64
|
+
init = '''
|
|
65
|
+
pip install -r requirements.txt
|
|
66
|
+
pre-commit install
|
|
67
|
+
'''
|
|
68
|
+
""",
|
|
69
|
+
nullcontext(
|
|
70
|
+
{
|
|
71
|
+
"init": {
|
|
72
|
+
"description": "Run init script",
|
|
73
|
+
"print_header": False,
|
|
74
|
+
"commands": [
|
|
75
|
+
["pip", "install", "-r", "requirements.txt"],
|
|
76
|
+
["pre-commit", "install"],
|
|
77
|
+
],
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
),
|
|
81
|
+
id="multi-line string script",
|
|
82
|
+
),
|
|
83
|
+
pytest.param(
|
|
84
|
+
"""
|
|
85
|
+
[tool.ppqs.scripts]
|
|
86
|
+
-invalid-name = "something"
|
|
87
|
+
""",
|
|
88
|
+
pytest.raises(InvalidScriptError, match="may not start with '-'"),
|
|
89
|
+
id="script name starts with dash",
|
|
90
|
+
),
|
|
91
|
+
pytest.param(
|
|
92
|
+
"""
|
|
93
|
+
[tool.ppqs.scripts]
|
|
94
|
+
name_contains-2-invalid-characters = "something"
|
|
95
|
+
""",
|
|
96
|
+
pytest.raises(InvalidScriptError, match="may not contain characters '_2'"),
|
|
97
|
+
id="script name invalid characters",
|
|
98
|
+
),
|
|
99
|
+
pytest.param(
|
|
100
|
+
"""
|
|
101
|
+
[tool.ppqs.scripts]
|
|
102
|
+
some-script = { description = "something", another_key = "invalid"}
|
|
103
|
+
""",
|
|
104
|
+
pytest.raises(
|
|
105
|
+
InvalidScriptError, match="may not contain keys 'another_key'"
|
|
106
|
+
),
|
|
107
|
+
id="script invalid keys",
|
|
108
|
+
),
|
|
109
|
+
pytest.param(
|
|
110
|
+
"""
|
|
111
|
+
[tool.ppqs.scripts.init]
|
|
112
|
+
description = "Initialise project"
|
|
113
|
+
script = "pip install -r requirements.txt"
|
|
114
|
+
""",
|
|
115
|
+
nullcontext(
|
|
116
|
+
{
|
|
117
|
+
"init": {
|
|
118
|
+
"description": "Initialise project",
|
|
119
|
+
"print_header": False,
|
|
120
|
+
"commands": [["pip", "install", "-r", "requirements.txt"]],
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
),
|
|
124
|
+
id="string script with description",
|
|
125
|
+
),
|
|
126
|
+
pytest.param(
|
|
127
|
+
"""
|
|
128
|
+
[tool.ppqs.scripts.init]
|
|
129
|
+
description = "Initialise project"
|
|
130
|
+
script = [
|
|
131
|
+
"some_commands",
|
|
132
|
+
]
|
|
133
|
+
""",
|
|
134
|
+
pytest.raises(
|
|
135
|
+
InvalidScriptError, match="either a string or a list of lists"
|
|
136
|
+
),
|
|
137
|
+
id="script is not list of lists",
|
|
138
|
+
),
|
|
139
|
+
pytest.param(
|
|
140
|
+
"""
|
|
141
|
+
[tool.ppqs.scripts]
|
|
142
|
+
init = [
|
|
143
|
+
["pip", "install", "-r", "requirements.txt"],
|
|
144
|
+
]
|
|
145
|
+
""",
|
|
146
|
+
nullcontext(
|
|
147
|
+
{
|
|
148
|
+
"init": {
|
|
149
|
+
"description": "Run init script",
|
|
150
|
+
"print_header": False,
|
|
151
|
+
"commands": [["pip", "install", "-r", "requirements.txt"]],
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
),
|
|
155
|
+
id="list script",
|
|
156
|
+
),
|
|
157
|
+
pytest.param(
|
|
158
|
+
"""
|
|
159
|
+
[tool.ppqs.scripts.init]
|
|
160
|
+
description = "Initialise project"
|
|
161
|
+
print_header = true
|
|
162
|
+
script = [
|
|
163
|
+
["pip", "install", "-r", "requirements.txt"],
|
|
164
|
+
]
|
|
165
|
+
""",
|
|
166
|
+
nullcontext(
|
|
167
|
+
{
|
|
168
|
+
"init": {
|
|
169
|
+
"description": "Initialise project",
|
|
170
|
+
"print_header": True,
|
|
171
|
+
"commands": [["pip", "install", "-r", "requirements.txt"]],
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
),
|
|
175
|
+
id="list script with description",
|
|
176
|
+
),
|
|
177
|
+
pytest.param(
|
|
178
|
+
"""
|
|
179
|
+
[tool.ppqs.scripts.init]
|
|
180
|
+
description = "Initialise project"
|
|
181
|
+
script = [
|
|
182
|
+
["pip", "install", "-r", "requirements.txt"],
|
|
183
|
+
["pre-commit", "install"],
|
|
184
|
+
]
|
|
185
|
+
""",
|
|
186
|
+
nullcontext(
|
|
187
|
+
{
|
|
188
|
+
"init": {
|
|
189
|
+
"description": "Initialise project",
|
|
190
|
+
"print_header": False,
|
|
191
|
+
"commands": [
|
|
192
|
+
["pip", "install", "-r", "requirements.txt"],
|
|
193
|
+
["pre-commit", "install"],
|
|
194
|
+
],
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
),
|
|
198
|
+
id="multi-line list script with description",
|
|
199
|
+
),
|
|
200
|
+
pytest.param(
|
|
201
|
+
"""
|
|
202
|
+
[tool.ppqs.scripts]
|
|
203
|
+
paths = [
|
|
204
|
+
["echo", ["/tmp"]],
|
|
205
|
+
]
|
|
206
|
+
""",
|
|
207
|
+
pytest.raises(InvalidScriptError, match="must be a relative path"),
|
|
208
|
+
id="list script with absolute path",
|
|
209
|
+
),
|
|
210
|
+
pytest.param(
|
|
211
|
+
"""
|
|
212
|
+
[tool.ppqs.scripts]
|
|
213
|
+
paths = [
|
|
214
|
+
["echo", ["a", "b"]],
|
|
215
|
+
]
|
|
216
|
+
""",
|
|
217
|
+
nullcontext(
|
|
218
|
+
{
|
|
219
|
+
"paths": {
|
|
220
|
+
"description": "Run paths script",
|
|
221
|
+
"print_header": False,
|
|
222
|
+
"commands": [["echo", str(Path("a", "b"))]],
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
),
|
|
226
|
+
id="list script with relative path",
|
|
227
|
+
),
|
|
228
|
+
],
|
|
229
|
+
)
|
|
230
|
+
def test_parse_scripts(tmp_path, script_toml, expectation):
|
|
231
|
+
"""
|
|
232
|
+
Test parse_scripts().
|
|
233
|
+
"""
|
|
234
|
+
|
|
235
|
+
pyproject_path = tmp_path / "pyproject.toml"
|
|
236
|
+
pyproject_toml = f"""
|
|
237
|
+
[project]
|
|
238
|
+
name = "test"
|
|
239
|
+
{script_toml}
|
|
240
|
+
"""
|
|
241
|
+
|
|
242
|
+
write_file(pyproject_path, pyproject_toml)
|
|
243
|
+
|
|
244
|
+
with in_dir(tmp_path):
|
|
245
|
+
with expectation as expected_result:
|
|
246
|
+
project_name, scripts = parse_scripts(pyproject_path)
|
|
247
|
+
assert project_name == "test"
|
|
248
|
+
assert scripts == expected_result
|