shellscriptor 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- shellscriptor/__init__.py +23 -0
- shellscriptor/__main__.py +142 -0
- shellscriptor/_argparse.py +213 -0
- shellscriptor/_code.py +76 -0
- shellscriptor/_log.py +125 -0
- shellscriptor/_output.py +72 -0
- shellscriptor/_script.py +268 -0
- shellscriptor/_types.py +271 -0
- shellscriptor/_validate.py +413 -0
- shellscriptor/py.typed +0 -0
- shellscriptor-0.1.0.dist-info/METADATA +761 -0
- shellscriptor-0.1.0.dist-info/RECORD +15 -0
- shellscriptor-0.1.0.dist-info/WHEEL +5 -0
- shellscriptor-0.1.0.dist-info/entry_points.txt +2 -0
- shellscriptor-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,761 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: shellscriptor
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Programmatic shell script generator with argparse, logging, validation, and structured output
|
|
5
|
+
Author: RepoDynamics
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/RepoDynamics/ShellScriptor
|
|
8
|
+
Project-URL: Repository, https://github.com/RepoDynamics/ShellScriptor
|
|
9
|
+
Keywords: shell,bash,script,codegen,argparse
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Topic :: Software Development :: Code Generators
|
|
14
|
+
Classifier: Topic :: System :: Shells
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
Requires-Dist: pydantic >=2.0
|
|
18
|
+
Provides-Extra: dev
|
|
19
|
+
Requires-Dist: pytest >=8.0 ; extra == 'dev'
|
|
20
|
+
Provides-Extra: yaml
|
|
21
|
+
Requires-Dist: pyyaml >=6.0 ; extra == 'yaml'
|
|
22
|
+
|
|
23
|
+
# ShellScriptor
|
|
24
|
+
|
|
25
|
+
Programmatic shell script generator with argument parsing, logging, validation,
|
|
26
|
+
and structured output — driven by Python dicts or YAML/JSON definition files.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Contents
|
|
31
|
+
|
|
32
|
+
- [ShellScriptor](#shellscriptor)
|
|
33
|
+
- [Contents](#contents)
|
|
34
|
+
- [Overview](#overview)
|
|
35
|
+
- [Installation](#installation)
|
|
36
|
+
- [Quick start](#quick-start)
|
|
37
|
+
- [Python](#python)
|
|
38
|
+
- [CLI](#cli)
|
|
39
|
+
- [Definition format](#definition-format)
|
|
40
|
+
- [Script definition (`ScriptDef`)](#script-definition-scriptdef)
|
|
41
|
+
- [Function definition (`FunctionDef`)](#function-definition-functiondef)
|
|
42
|
+
- [Parameter definition (`ParameterDef`)](#parameter-definition-parameterdef)
|
|
43
|
+
- [Validation rules (`ValidationDef`)](#validation-rules-validationdef)
|
|
44
|
+
- [Return value definition (`ReturnDef`)](#return-value-definition-returndef)
|
|
45
|
+
- [Script types](#script-types)
|
|
46
|
+
- [`shell_src` — sourced library](#shell_src--sourced-library)
|
|
47
|
+
- [`shell_exec` — executable script](#shell_exec--executable-script)
|
|
48
|
+
- [Parameter types](#parameter-types)
|
|
49
|
+
- [Argument parsing](#argument-parsing)
|
|
50
|
+
- [Required vs optional parameters](#required-vs-optional-parameters)
|
|
51
|
+
- [Short flag aliases](#short-flag-aliases)
|
|
52
|
+
- [Help / usage output](#help--usage-output)
|
|
53
|
+
- [Environment variable fallback (`shell_exec`)](#environment-variable-fallback-shell_exec)
|
|
54
|
+
- [Validation](#validation)
|
|
55
|
+
- [Enum (allowed values)](#enum-allowed-values)
|
|
56
|
+
- [Path existence](#path-existence)
|
|
57
|
+
- [Integer range](#integer-range)
|
|
58
|
+
- [Regex pattern](#regex-pattern)
|
|
59
|
+
- [Custom shell lines](#custom-shell-lines)
|
|
60
|
+
- [Return values / structured output](#return-values--structured-output)
|
|
61
|
+
- [Embedded functions](#embedded-functions)
|
|
62
|
+
- [Global function pool](#global-function-pool)
|
|
63
|
+
- [Logging](#logging)
|
|
64
|
+
- [Body sections](#body-sections)
|
|
65
|
+
- [CLI usage](#cli-usage)
|
|
66
|
+
- [Definition file formats](#definition-file-formats)
|
|
67
|
+
- [Python API](#python-api)
|
|
68
|
+
- [`create_script(name, data, script_type, global_functions=None) -> str`](#create_scriptname-data-script_type-global_functionsnone---str)
|
|
69
|
+
- [`create_function(name, data, script_type) -> list[str]`](#create_functionname-data-script_type---liststr)
|
|
70
|
+
- [Low-level generators](#low-level-generators)
|
|
71
|
+
- [Examples](#examples)
|
|
72
|
+
- [Required string parameter with short flag](#required-string-parameter-with-short-flag)
|
|
73
|
+
- [All four parameter types](#all-four-parameter-types)
|
|
74
|
+
- [Enum + path validation](#enum--path-validation)
|
|
75
|
+
- [Integer range](#integer-range-1)
|
|
76
|
+
- [Regex pattern](#regex-pattern-1)
|
|
77
|
+
- [Return values](#return-values)
|
|
78
|
+
- [Genuinely optional parameter (no default, no required check)](#genuinely-optional-parameter-no-default-no-required-check)
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Overview
|
|
83
|
+
|
|
84
|
+
`shellscriptor` generates complete, production-quality shell scripts from a
|
|
85
|
+
structured description. You provide a dict (or a YAML/JSON file) that declares
|
|
86
|
+
parameters, validation rules, embedded functions, and body code; the library
|
|
87
|
+
assembles the boilerplate so you don't have to:
|
|
88
|
+
|
|
89
|
+
| Feature | `shell_src` | `shell_exec` |
|
|
90
|
+
|---|---|---|
|
|
91
|
+
| `while/case` argparse loop | ✓ | ✓ |
|
|
92
|
+
| `--help` / `__usage__` | ✓ (when descriptions present) | ✓ |
|
|
93
|
+
| Validation checks | ✓ | ✓ |
|
|
94
|
+
| Entry / exit log markers | — | ✓ |
|
|
95
|
+
| Argument-read logs | — | ✓ |
|
|
96
|
+
| `tee` log capture to `$LOGFILE` | — | ✓ |
|
|
97
|
+
| `trap __cleanup__ EXIT` | — | ✓ |
|
|
98
|
+
| `set -x` when `$DEBUG=true` | — | ✓ |
|
|
99
|
+
| Env-var fallback (called with no args) | — | ✓ |
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Installation
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
pip install shellscriptor
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
YAML definition files require the optional `pyyaml` dependency:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
pip install "shellscriptor[yaml]"
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## Quick start
|
|
118
|
+
|
|
119
|
+
### Python
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
from shellscriptor import create_script
|
|
123
|
+
|
|
124
|
+
script = create_script(
|
|
125
|
+
name="greet",
|
|
126
|
+
script_type="shell_src",
|
|
127
|
+
data={
|
|
128
|
+
"interpreter": "usr/bin/env bash",
|
|
129
|
+
"parameter": {
|
|
130
|
+
"name": {"type": "string", "description": "Name to greet"},
|
|
131
|
+
},
|
|
132
|
+
"body": 'echo "Hello, $NAME!"',
|
|
133
|
+
},
|
|
134
|
+
)
|
|
135
|
+
print(script)
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### CLI
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
# Write to stdout
|
|
142
|
+
shellscriptor greet.yaml
|
|
143
|
+
|
|
144
|
+
# Write to a file
|
|
145
|
+
shellscriptor greet.yaml --output dist/greet.sh
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
`greet.yaml`:
|
|
149
|
+
|
|
150
|
+
```yaml
|
|
151
|
+
name: greet
|
|
152
|
+
type: shell_src
|
|
153
|
+
script:
|
|
154
|
+
interpreter: usr/bin/env bash
|
|
155
|
+
parameter:
|
|
156
|
+
name:
|
|
157
|
+
type: string
|
|
158
|
+
description: Name to greet
|
|
159
|
+
body: 'echo "Hello, $NAME!"'
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Definition format
|
|
165
|
+
|
|
166
|
+
### Script definition (`ScriptDef`)
|
|
167
|
+
|
|
168
|
+
Top-level keys accepted by `create_script()` and by the CLI wrapper format:
|
|
169
|
+
|
|
170
|
+
| Key | Type | Description |
|
|
171
|
+
|---|---|---|
|
|
172
|
+
| `interpreter` | `str \| null` | Shebang path without the leading `#!`. `"usr/bin/env bash"` → `#!/usr/bin/env bash`. Omit for no shebang. |
|
|
173
|
+
| `flags` | `str \| null` | Arguments passed to `set` after the shebang, e.g. `"-euo pipefail"`. |
|
|
174
|
+
| `import` | `list[str]` | Names of functions to pull in from the `global_functions` pool passed to `create_script()`. |
|
|
175
|
+
| `function` | `dict[str, FunctionDef]` | Locally defined functions embedded in the script. |
|
|
176
|
+
| `parameter` | `dict[str, ParameterDef]` | Script-level parameters accepted on the command line (or from env vars in `shell_exec` mode). |
|
|
177
|
+
| `body` | body form | Main script body executed after argument parsing and validation. |
|
|
178
|
+
| `return` | `list[ReturnDef]` | Values written to stdout on exit (`shell_exec` only). |
|
|
179
|
+
|
|
180
|
+
### Function definition (`FunctionDef`)
|
|
181
|
+
|
|
182
|
+
Accepted inside `function` and by `create_function()`:
|
|
183
|
+
|
|
184
|
+
| Key | Type | Description |
|
|
185
|
+
|---|---|---|
|
|
186
|
+
| `parameter` | `dict[str, ParameterDef]` | Parameters accepted by the function. |
|
|
187
|
+
| `body` | body form | Function body. |
|
|
188
|
+
| `return` | `list[ReturnDef]` | Values written to stdout when the function returns. |
|
|
189
|
+
|
|
190
|
+
### Parameter definition (`ParameterDef`)
|
|
191
|
+
|
|
192
|
+
| Key | Type | Default | Description |
|
|
193
|
+
|---|---|---|---|
|
|
194
|
+
| `type` | `"string" \| "boolean" \| "integer" \| "array"` | **required** | Shell type. |
|
|
195
|
+
| `default` | `str \| list[str] \| null` | `null` | Default value. `null` with no explicit `required` field implies the parameter is required. |
|
|
196
|
+
| `required` | `bool \| null` | `null` | Explicit required flag. `null` → inferred from `default`. |
|
|
197
|
+
| `description` | `str` | `""` | Human-readable description for `--help` output. Triggers `--help`/`__usage__` generation when non-empty on any parameter. |
|
|
198
|
+
| `short` | `str` | `""` | Single-character short flag alias, e.g. `"o"` creates `-o` alongside `--<name>`. |
|
|
199
|
+
| `array_delimiter` | `str` | `" "` | Delimiter used when reading an array from a single environment variable (env-var fallback, `shell_exec` only). |
|
|
200
|
+
| `validation` | `ValidationDef \| null` | `null` | Optional validation rules. |
|
|
201
|
+
|
|
202
|
+
**Required vs optional — rules:**
|
|
203
|
+
|
|
204
|
+
| `required` | `default` | Behaviour |
|
|
205
|
+
|---|---|---|
|
|
206
|
+
| `null` (omitted) | `null` | Required — exits if missing. |
|
|
207
|
+
| `null` (omitted) | `"value"` | Optional — assigns default if missing. |
|
|
208
|
+
| `true` | any | Required — exits if missing; default ignored. |
|
|
209
|
+
| `false` | `"value"` | Optional — assigns default if missing. |
|
|
210
|
+
| `false` | `null` | Genuinely optional — no check emitted; variable may remain empty. |
|
|
211
|
+
|
|
212
|
+
### Validation rules (`ValidationDef`)
|
|
213
|
+
|
|
214
|
+
Nested inside `ParameterDef.validation`:
|
|
215
|
+
|
|
216
|
+
| Key | Type | Description |
|
|
217
|
+
|---|---|---|
|
|
218
|
+
| `enum` | `list[str]` | Exhaustive list of accepted values. A value not in the list causes `exit 1`. |
|
|
219
|
+
| `path_existence` | `PathExistenceDef` | Assert the value is a path satisfying an existence condition. |
|
|
220
|
+
| `integer_range` | `IntegerRangeDef` | Assert the value is an integer within an inclusive numeric range. |
|
|
221
|
+
| `regex` | `str` | ERE pattern the full value must match. Non-matching values cause `exit 1`. |
|
|
222
|
+
| `custom` | `list[str]` | Verbatim shell lines appended after all other checks. |
|
|
223
|
+
|
|
224
|
+
**`PathExistenceDef`**:
|
|
225
|
+
|
|
226
|
+
| Key | Type | Description |
|
|
227
|
+
|---|---|---|
|
|
228
|
+
| `must_exist` | `bool` | `true` → path must exist; `false` → path must not exist. |
|
|
229
|
+
| `type` | `"dir" \| "exec" \| "file" \| "symlink" \| null` | Kind of filesystem entry to test. `null` accepts any entry. |
|
|
230
|
+
|
|
231
|
+
**`IntegerRangeDef`**:
|
|
232
|
+
|
|
233
|
+
| Key | Type | Description |
|
|
234
|
+
|---|---|---|
|
|
235
|
+
| `min` | `int \| null` | Inclusive lower bound. `null` for no lower bound. |
|
|
236
|
+
| `max` | `int \| null` | Inclusive upper bound. `null` for no upper bound. |
|
|
237
|
+
|
|
238
|
+
### Return value definition (`ReturnDef`)
|
|
239
|
+
|
|
240
|
+
| Key | Type | Description |
|
|
241
|
+
|---|---|---|
|
|
242
|
+
| `name` | `str` | Human-readable label used in log messages. |
|
|
243
|
+
| `variable` | `str` | Shell variable name whose value is written to stdout. |
|
|
244
|
+
| `type` | `"string" \| "boolean" \| "integer" \| "array"` | Type of the return value; drives encoding. |
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## Script types
|
|
249
|
+
|
|
250
|
+
### `shell_src` — sourced library
|
|
251
|
+
|
|
252
|
+
Generates a lean script intended to be sourced (`. script.sh`).
|
|
253
|
+
|
|
254
|
+
- Functions and top-level `while/case` argument parsing are emitted.
|
|
255
|
+
- No logging boilerplate, no `trap`, no `tee`.
|
|
256
|
+
- Good for utility libraries and helper functions.
|
|
257
|
+
|
|
258
|
+
### `shell_exec` — executable script
|
|
259
|
+
|
|
260
|
+
Generates a self-contained, directly executable script with:
|
|
261
|
+
|
|
262
|
+
- **Entry/exit log markers** for the script and every function.
|
|
263
|
+
- **Tee log capture**: all output is mirrored to a temp file; when `$LOGFILE`
|
|
264
|
+
is set, the capture is flushed to it on exit via the `__cleanup__` trap.
|
|
265
|
+
- **`$DEBUG` flag**: `set -x` is enabled when `DEBUG=true` is passed.
|
|
266
|
+
- **Env-var fallback**: when the script is called with no arguments, each
|
|
267
|
+
parameter is read from the corresponding environment variable instead.
|
|
268
|
+
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
## Parameter types
|
|
272
|
+
|
|
273
|
+
| Type | Shell representation | Argparse | Env-var fallback |
|
|
274
|
+
|---|---|---|---|
|
|
275
|
+
| `string` | `VAR=""` | `--name) shift; VAR="$1"; shift;;` | `${VAR+defined}` one-liner |
|
|
276
|
+
| `integer` | `VAR=""` | Same as `string` (validation handles numeric check) | Same as `string` |
|
|
277
|
+
| `boolean` | `VAR=""` | `--flag) shift; VAR=true;;` (no value consumed) | `${VAR+defined}` one-liner |
|
|
278
|
+
| `array` | `VAR=()` | Reads all following non-`--` words into `VAR` | `IFS="<delim>" read -ra VAR` |
|
|
279
|
+
|
|
280
|
+
Variable names are derived from parameter names: hyphens become underscores, and
|
|
281
|
+
for script-level parameters (global scope) names are uppercased.
|
|
282
|
+
Function-local parameters stay lowercase.
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
## Argument parsing
|
|
287
|
+
|
|
288
|
+
Every script or function with parameters gets a `while [[ $# -gt 0 ]]; do … done`
|
|
289
|
+
loop. Unknown flags emit `⛔ Unknown option: '<flag>'` to stderr and exit 1.
|
|
290
|
+
Positional (non-flag) arguments emit `⛔ Unexpected argument: '<arg>'` and exit 1.
|
|
291
|
+
|
|
292
|
+
### Required vs optional parameters
|
|
293
|
+
|
|
294
|
+
See the [parameter definition table](#parameter-definition-parameterdef) for the
|
|
295
|
+
full matrix. The generated check for a required parameter:
|
|
296
|
+
|
|
297
|
+
```bash
|
|
298
|
+
[ -z "${OUTPUT_DIR-}" ] && { echo "⛔ Missing required argument 'OUTPUT_DIR'." >&2; exit 1; }
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
For an optional parameter with a default value:
|
|
302
|
+
|
|
303
|
+
```bash
|
|
304
|
+
[ -z "${COUNT-}" ] && { echo "ℹ️ Argument 'COUNT' set to default value '1'." >&2; COUNT="1"; }
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### Short flag aliases
|
|
308
|
+
|
|
309
|
+
Set `short: "o"` to generate a `-o` arm alongside `--output-dir`:
|
|
310
|
+
|
|
311
|
+
```bash
|
|
312
|
+
--output-dir|-o) shift; OUTPUT_DIR="$1"; ...;;
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
### Help / usage output
|
|
316
|
+
|
|
317
|
+
When **any** parameter in a parameter set carries a non-empty `description`,
|
|
318
|
+
shellscriptor:
|
|
319
|
+
|
|
320
|
+
1. Generates a `__usage__()` function that prints a summary table to stderr
|
|
321
|
+
and exits with code 0.
|
|
322
|
+
2. Adds a `--help|-h)` arm to the argparse loop that calls `__usage__`.
|
|
323
|
+
|
|
324
|
+
For script-level parameters `__usage__` is defined at the top of the script
|
|
325
|
+
(before all other functions). For function parameters it is defined at the
|
|
326
|
+
start of the function body, before the argparse loop.
|
|
327
|
+
|
|
328
|
+
Example output:
|
|
329
|
+
|
|
330
|
+
```
|
|
331
|
+
Usage:
|
|
332
|
+
-o, --output-dir (string): Directory to write results into
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
### Environment variable fallback (`shell_exec`)
|
|
336
|
+
|
|
337
|
+
When a `shell_exec` script is invoked with no arguments it reads parameters
|
|
338
|
+
from environment variables. Variable names mirror the script-level variable
|
|
339
|
+
name (uppercase, hyphens → underscores). The env-var block only imports a
|
|
340
|
+
variable when it is already set in the environment (`${VAR+defined}` test), so
|
|
341
|
+
unset variables are not silently turned into empty strings.
|
|
342
|
+
|
|
343
|
+
For array parameters the env var is split on `array_delimiter` using
|
|
344
|
+
`IFS` + `read -ra`.
|
|
345
|
+
|
|
346
|
+
---
|
|
347
|
+
|
|
348
|
+
## Validation
|
|
349
|
+
|
|
350
|
+
Validation checks are applied after argument parsing, in this order:
|
|
351
|
+
|
|
352
|
+
1. Missing-arg / default assignment
|
|
353
|
+
2. Enum membership
|
|
354
|
+
3. Path existence
|
|
355
|
+
4. Integer range
|
|
356
|
+
5. Regex pattern
|
|
357
|
+
6. Custom lines
|
|
358
|
+
|
|
359
|
+
For `array` parameters, checks 2–5 are run per element inside a
|
|
360
|
+
`for elem in "${VAR[@]}"; do … done` loop.
|
|
361
|
+
|
|
362
|
+
### Enum (allowed values)
|
|
363
|
+
|
|
364
|
+
```yaml
|
|
365
|
+
validation:
|
|
366
|
+
enum: [read, write, delete]
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
Generated shell:
|
|
370
|
+
|
|
371
|
+
```bash
|
|
372
|
+
case "${MODE}" in
|
|
373
|
+
"read"|"write"|"delete");;
|
|
374
|
+
*) echo "⛔ Invalid value for argument '--MODE': '${MODE}'" >&2; exit 1;;
|
|
375
|
+
esac
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
### Path existence
|
|
379
|
+
|
|
380
|
+
```yaml
|
|
381
|
+
validation:
|
|
382
|
+
path_existence:
|
|
383
|
+
must_exist: true
|
|
384
|
+
type: file
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
Generated shell (`must_exist: true`, `type: file`):
|
|
388
|
+
|
|
389
|
+
```bash
|
|
390
|
+
[ ! -f "${INPUT_FILE}" ] && { echo "⛔ File argument to parameter 'INPUT_FILE' not found: '${INPUT_FILE}'" >&2; exit 1; }
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
Available `type` values: `"file"` (`-f`), `"dir"` (`-d`), `"symlink"` (`-L`),
|
|
394
|
+
`"exec"` (`-x`), `null` (uses `-e`, accepts any entry).
|
|
395
|
+
|
|
396
|
+
`must_exist: false` inverts the check — the path must **not** exist.
|
|
397
|
+
|
|
398
|
+
### Integer range
|
|
399
|
+
|
|
400
|
+
```yaml
|
|
401
|
+
validation:
|
|
402
|
+
integer_range:
|
|
403
|
+
min: 1
|
|
404
|
+
max: 100
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
Generates checks for both bounds independently (omit either for an open-ended
|
|
408
|
+
range). `min` and `max` must satisfy `min <= max` when both are given.
|
|
409
|
+
|
|
410
|
+
### Regex pattern
|
|
411
|
+
|
|
412
|
+
```yaml
|
|
413
|
+
validation:
|
|
414
|
+
regex: "^[a-z][a-z0-9_-]{2,31}$"
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
Uses `[[ "$VAR" =~ <pattern> ]]`:
|
|
418
|
+
|
|
419
|
+
```bash
|
|
420
|
+
[[ ! "${SLUG}" =~ ^[a-z][a-z0-9_-]{2,31}$ ]] && { echo "⛔ ..." >&2; exit 1; }
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
### Custom shell lines
|
|
424
|
+
|
|
425
|
+
```yaml
|
|
426
|
+
validation:
|
|
427
|
+
custom:
|
|
428
|
+
- '[ -w "${OUTPUT_DIR}" ] || { echo "⛔ Directory is not writable." >&2; exit 1; }'
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
Custom lines are appended verbatim after all other validation checks.
|
|
432
|
+
|
|
433
|
+
---
|
|
434
|
+
|
|
435
|
+
## Return values / structured output
|
|
436
|
+
|
|
437
|
+
`return` (or `return_` in Python) accepts a list of `ReturnDef` entries.
|
|
438
|
+
shellscriptor encodes return values so that callers can unpack them
|
|
439
|
+
unambiguously:
|
|
440
|
+
|
|
441
|
+
| Situation | Encoding | Caller idiom |
|
|
442
|
+
|---|---|---|
|
|
443
|
+
| Single scalar | `echo "$VAR"` | `val=$(fn)` |
|
|
444
|
+
| Single array | `printf '%s\0' "${VAR[@]}"` | `mapfile -d '' -t arr < <(fn)` |
|
|
445
|
+
| Multiple values (scalars and/or arrays) | Each value followed by `\0`; array elements delimited by `\x1F` internally | `IFS= read -r -d $'\0'` per value |
|
|
446
|
+
|
|
447
|
+
In `shell_exec` mode each return value also emits a write-log line.
|
|
448
|
+
|
|
449
|
+
---
|
|
450
|
+
|
|
451
|
+
## Embedded functions
|
|
452
|
+
|
|
453
|
+
Functions defined under `function` are emitted before the script body.
|
|
454
|
+
Each function is generated by `create_function()` and follows the same rules
|
|
455
|
+
as the top-level script: parameters get an argparse loop, validation checks
|
|
456
|
+
are emitted, and in `shell_exec` mode entry/exit log markers are added.
|
|
457
|
+
|
|
458
|
+
```yaml
|
|
459
|
+
function:
|
|
460
|
+
greet:
|
|
461
|
+
parameter:
|
|
462
|
+
person:
|
|
463
|
+
type: string
|
|
464
|
+
required: true
|
|
465
|
+
description: Name to greet
|
|
466
|
+
body: 'echo "Hello, ${person}!"'
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
Generated:
|
|
470
|
+
|
|
471
|
+
```bash
|
|
472
|
+
greet() {
|
|
473
|
+
echo "↪️ Function entry: greet" >&2
|
|
474
|
+
__usage__() {
|
|
475
|
+
echo "Usage:" >&2
|
|
476
|
+
echo " --person (string): Name to greet" >&2
|
|
477
|
+
exit 0
|
|
478
|
+
}
|
|
479
|
+
local person=""
|
|
480
|
+
while [[ $# -gt 0 ]]; do
|
|
481
|
+
case $1 in
|
|
482
|
+
--person) shift; person="$1"; ...; shift;;
|
|
483
|
+
--help|-h) __usage__;;
|
|
484
|
+
...
|
|
485
|
+
esac
|
|
486
|
+
done
|
|
487
|
+
[ -z "${person-}" ] && { echo "⛔ Missing required argument 'person'." >&2; exit 1; }
|
|
488
|
+
echo "Hello, ${person}!"
|
|
489
|
+
echo "↩️ Function exit: greet" >&2
|
|
490
|
+
}
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
---
|
|
494
|
+
|
|
495
|
+
## Global function pool
|
|
496
|
+
|
|
497
|
+
`create_script()` accepts an optional `global_functions` argument — a dict of
|
|
498
|
+
`FunctionDef`-compatible dicts keyed by function name. Only functions listed
|
|
499
|
+
in `import` (or `import_`) are pulled in:
|
|
500
|
+
|
|
501
|
+
```python
|
|
502
|
+
GLOBALS = {
|
|
503
|
+
"check_root": {"body": 'if [ "$(id -u)" -ne 0 ]; then echo "must be root" >&2; exit 1; fi'},
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
script = create_script(
|
|
507
|
+
name="setup",
|
|
508
|
+
script_type="shell_exec",
|
|
509
|
+
data={"import": ["check_root"], "body": "check_root"},
|
|
510
|
+
global_functions=GLOBALS,
|
|
511
|
+
)
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
---
|
|
515
|
+
|
|
516
|
+
## Logging
|
|
517
|
+
|
|
518
|
+
The `log()` function and related helpers generate consistent `echo … >&2`
|
|
519
|
+
statements.
|
|
520
|
+
|
|
521
|
+
| Function | Output |
|
|
522
|
+
|---|---|
|
|
523
|
+
| `log(msg)` | `echo "<msg>" >&2` |
|
|
524
|
+
| `log(msg, "info")` | `echo "ℹ️ <msg>" >&2` |
|
|
525
|
+
| `log(msg, "warn")` | `echo "⚠️ <msg>" >&2` |
|
|
526
|
+
| `log(msg, "error")` | `echo "❌ <msg>" >&2; return 1` |
|
|
527
|
+
| `log(msg, "critical")` | `echo "⛔ <msg>" >&2; exit 1` |
|
|
528
|
+
| `log_endpoint("fn", "function", "entry")` | `echo "↪️ Function entry: fn" >&2` |
|
|
529
|
+
| `log_arg_read("param", "VAR")` | `echo "📩 Read argument 'param': '${VAR}'" >&2` |
|
|
530
|
+
| `log_arg_write("result", "RESULT")` | `echo "📤 Write output 'result': '${RESULT}'" >&2` |
|
|
531
|
+
|
|
532
|
+
---
|
|
533
|
+
|
|
534
|
+
## Body sections
|
|
535
|
+
|
|
536
|
+
The `body` field is flexible. All three forms are equivalent:
|
|
537
|
+
|
|
538
|
+
```python
|
|
539
|
+
# Plain string
|
|
540
|
+
body = 'echo hello\necho world'
|
|
541
|
+
|
|
542
|
+
# List of strings
|
|
543
|
+
body = ["echo hello", "echo world"]
|
|
544
|
+
|
|
545
|
+
# List of section dicts (YAML-friendly; only "content" is used)
|
|
546
|
+
body = [
|
|
547
|
+
{"summary": "Greet", "content": "echo hello"},
|
|
548
|
+
{"content": "echo world"},
|
|
549
|
+
]
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
Comments (`#`) and blank lines are stripped by default by `sanitize_code()`.
|
|
553
|
+
|
|
554
|
+
---
|
|
555
|
+
|
|
556
|
+
## CLI usage
|
|
557
|
+
|
|
558
|
+
```
|
|
559
|
+
shellscriptor DEFINITION_FILE [--output PATH] [--type {shell_src,shell_exec}] [--name NAME]
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
| Option | Description |
|
|
563
|
+
|---|---|
|
|
564
|
+
| `DEFINITION_FILE` | Path to a YAML or JSON definition file. |
|
|
565
|
+
| `--output`, `-o` | Write the generated script to this path (default: stdout). |
|
|
566
|
+
| `--type`, `-t` | Override the script type (`shell_src` or `shell_exec`). Required when the file does not contain a top-level `type` key. |
|
|
567
|
+
| `--name`, `-n` | Script name used in log messages. Defaults to the file stem. |
|
|
568
|
+
|
|
569
|
+
### Definition file formats
|
|
570
|
+
|
|
571
|
+
**Wrapper format** (recommended for YAML):
|
|
572
|
+
|
|
573
|
+
```yaml
|
|
574
|
+
name: deploy
|
|
575
|
+
type: shell_exec
|
|
576
|
+
script:
|
|
577
|
+
interpreter: usr/bin/env bash
|
|
578
|
+
flags: "-euo pipefail"
|
|
579
|
+
parameter:
|
|
580
|
+
env:
|
|
581
|
+
type: string
|
|
582
|
+
required: true
|
|
583
|
+
description: Target environment
|
|
584
|
+
body: echo "Deploying to $ENV"
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
**Bare format** (must pass `--type` and optionally `--name` on the CLI):
|
|
588
|
+
|
|
589
|
+
```json
|
|
590
|
+
{
|
|
591
|
+
"interpreter": "usr/bin/env bash",
|
|
592
|
+
"parameter": {
|
|
593
|
+
"env": {"type": "string", "required": true}
|
|
594
|
+
},
|
|
595
|
+
"body": "echo \"Deploying to $ENV\""
|
|
596
|
+
}
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
---
|
|
600
|
+
|
|
601
|
+
## Python API
|
|
602
|
+
|
|
603
|
+
### `create_script(name, data, script_type, global_functions=None) -> str`
|
|
604
|
+
|
|
605
|
+
Generate a complete shell script string.
|
|
606
|
+
|
|
607
|
+
```python
|
|
608
|
+
from shellscriptor import create_script
|
|
609
|
+
|
|
610
|
+
code = create_script(
|
|
611
|
+
name="deploy",
|
|
612
|
+
data={...}, # dict or ScriptDef
|
|
613
|
+
script_type="shell_exec",
|
|
614
|
+
global_functions={}, # optional pool
|
|
615
|
+
)
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
### `create_function(name, data, script_type) -> list[str]`
|
|
619
|
+
|
|
620
|
+
Generate lines for a single shell function definition.
|
|
621
|
+
|
|
622
|
+
```python
|
|
623
|
+
from shellscriptor import create_function
|
|
624
|
+
|
|
625
|
+
lines = create_function("greet", {"body": ['echo "hi"']}, script_type="shell_src")
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
### Low-level generators
|
|
629
|
+
|
|
630
|
+
These are exported from `shellscriptor` and can be combined freely to build
|
|
631
|
+
custom script snippets:
|
|
632
|
+
|
|
633
|
+
| Function | Returns |
|
|
634
|
+
|---|---|
|
|
635
|
+
| `create_validation_block(parameters, *, local, script_type)` | Validation lines for all parameters |
|
|
636
|
+
| `validate_variable(var_name, var_type, default, required, validations, script_type)` | Validation lines for one variable |
|
|
637
|
+
| `validate_missing_arg(var_name, var_type, default, script_type)` | One-liner: required-or-default check |
|
|
638
|
+
| `validate_enum(var_name, enum)` | `case` block for allowed values |
|
|
639
|
+
| `validate_path_existence(var_name, must_exist, path_type)` | `[ -f/-d/… ]` check |
|
|
640
|
+
| `validate_integer_range(var_name, min_val, max_val)` | Numeric bound checks |
|
|
641
|
+
| `validate_regex(var_name, pattern)` | `[[ =~ ]]` check |
|
|
642
|
+
| `create_output(returns, script_type)` | Output/return encoding lines |
|
|
643
|
+
| `log(msg, level, code, indent_level)` | `echo … >&2` command string |
|
|
644
|
+
| `log_arg_read(param_name, var_name)` | Read-log string |
|
|
645
|
+
| `log_arg_write(param_name, var_name)` | Write-log string |
|
|
646
|
+
| `log_endpoint(name, typ, stage)` | Entry/exit-log string |
|
|
647
|
+
| `indent(code, level)` | Indent a string or list of lines |
|
|
648
|
+
| `sanitize_code(code, ...)` | Strip comments, blank lines, trailing whitespace |
|
|
649
|
+
|
|
650
|
+
---
|
|
651
|
+
|
|
652
|
+
## Examples
|
|
653
|
+
|
|
654
|
+
### Required string parameter with short flag
|
|
655
|
+
|
|
656
|
+
```python
|
|
657
|
+
create_script(
|
|
658
|
+
name="build",
|
|
659
|
+
script_type="shell_exec",
|
|
660
|
+
data={
|
|
661
|
+
"interpreter": "usr/bin/env bash",
|
|
662
|
+
"flags": "-euo pipefail",
|
|
663
|
+
"parameter": {
|
|
664
|
+
"output-dir": {
|
|
665
|
+
"type": "string",
|
|
666
|
+
"required": True,
|
|
667
|
+
"description": "Directory to write build artefacts into",
|
|
668
|
+
"short": "o",
|
|
669
|
+
},
|
|
670
|
+
},
|
|
671
|
+
"body": 'mkdir -p "$OUTPUT_DIR"',
|
|
672
|
+
},
|
|
673
|
+
)
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
### All four parameter types
|
|
677
|
+
|
|
678
|
+
```yaml
|
|
679
|
+
parameter:
|
|
680
|
+
name:
|
|
681
|
+
type: string
|
|
682
|
+
required: true
|
|
683
|
+
description: Name to greet
|
|
684
|
+
count:
|
|
685
|
+
type: integer
|
|
686
|
+
default: "1"
|
|
687
|
+
description: How many times to greet
|
|
688
|
+
verbose:
|
|
689
|
+
type: boolean
|
|
690
|
+
description: Enable verbose output
|
|
691
|
+
tags:
|
|
692
|
+
type: array
|
|
693
|
+
description: Additional tags
|
|
694
|
+
array_delimiter: ","
|
|
695
|
+
```
|
|
696
|
+
|
|
697
|
+
### Enum + path validation
|
|
698
|
+
|
|
699
|
+
```yaml
|
|
700
|
+
parameter:
|
|
701
|
+
action:
|
|
702
|
+
type: string
|
|
703
|
+
required: true
|
|
704
|
+
validation:
|
|
705
|
+
enum: [copy, move, delete]
|
|
706
|
+
source:
|
|
707
|
+
type: string
|
|
708
|
+
required: true
|
|
709
|
+
validation:
|
|
710
|
+
path_existence:
|
|
711
|
+
must_exist: true
|
|
712
|
+
type: file
|
|
713
|
+
```
|
|
714
|
+
|
|
715
|
+
### Integer range
|
|
716
|
+
|
|
717
|
+
```yaml
|
|
718
|
+
parameter:
|
|
719
|
+
workers:
|
|
720
|
+
type: integer
|
|
721
|
+
default: "4"
|
|
722
|
+
validation:
|
|
723
|
+
integer_range:
|
|
724
|
+
min: 1
|
|
725
|
+
max: 32
|
|
726
|
+
```
|
|
727
|
+
|
|
728
|
+
### Regex pattern
|
|
729
|
+
|
|
730
|
+
```yaml
|
|
731
|
+
parameter:
|
|
732
|
+
slug:
|
|
733
|
+
type: string
|
|
734
|
+
required: true
|
|
735
|
+
validation:
|
|
736
|
+
regex: "^[a-z][a-z0-9_-]{2,31}$"
|
|
737
|
+
```
|
|
738
|
+
|
|
739
|
+
### Return values
|
|
740
|
+
|
|
741
|
+
```python
|
|
742
|
+
data = {
|
|
743
|
+
"body": 'result="hello"',
|
|
744
|
+
"return": [{"name": "result", "variable": "result", "type": "string"}],
|
|
745
|
+
}
|
|
746
|
+
```
|
|
747
|
+
|
|
748
|
+
Caller:
|
|
749
|
+
|
|
750
|
+
```bash
|
|
751
|
+
output=$(my_function --arg value)
|
|
752
|
+
```
|
|
753
|
+
|
|
754
|
+
### Genuinely optional parameter (no default, no required check)
|
|
755
|
+
|
|
756
|
+
```yaml
|
|
757
|
+
parameter:
|
|
758
|
+
comment:
|
|
759
|
+
type: string
|
|
760
|
+
required: false # variable may remain empty; no exit-1 check emitted
|
|
761
|
+
```
|