dyngle 0.7.0__tar.gz → 1.0.1__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.
Potentially problematic release.
This version of dyngle might be problematic. Click here for more details.
- {dyngle-0.7.0 → dyngle-1.0.1}/PACKAGE.md +31 -30
- {dyngle-0.7.0 → dyngle-1.0.1}/PKG-INFO +36 -33
- {dyngle-0.7.0 → dyngle-1.0.1}/dyngle/__init__.py +2 -2
- {dyngle-0.7.0 → dyngle-1.0.1}/dyngle/command/run_command.py +2 -2
- dyngle-1.0.1/dyngle/model/__init__.py +0 -0
- {dyngle-0.7.0/dyngle → dyngle-1.0.1/dyngle/model}/expression.py +32 -8
- dyngle-1.0.1/dyngle/model/live_data.py +22 -0
- {dyngle-0.7.0/dyngle → dyngle-1.0.1/dyngle/model}/operation.py +11 -8
- dyngle-1.0.1/dyngle/model/template.py +30 -0
- {dyngle-0.7.0 → dyngle-1.0.1}/pyproject.toml +3 -3
- dyngle-0.7.0/dyngle/template.py +0 -56
- {dyngle-0.7.0 → dyngle-1.0.1}/dyngle/__main__.py +0 -0
- {dyngle-0.7.0 → dyngle-1.0.1}/dyngle/command/__init__.py +0 -0
- {dyngle-0.7.0 → dyngle-1.0.1}/dyngle/error.py +0 -0
- {dyngle-0.7.0/dyngle → dyngle-1.0.1/dyngle/model}/safe_path.py +0 -0
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
# Dyngle
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
- A lightweight workflow engine
|
|
7
|
-
- A replacement for Make in Python projects
|
|
8
|
-
- A replacement for short functions in RC files
|
|
9
|
-
- Freedom from quirky Bash syntax
|
|
3
|
+
An experimantal, lightweight, easily configurable workflow engine for
|
|
4
|
+
automating development, operations, data processing, and content management
|
|
5
|
+
tasks.
|
|
10
6
|
|
|
11
7
|
Technical foundations
|
|
12
8
|
|
|
@@ -41,16 +37,18 @@ dyngle run hello
|
|
|
41
37
|
|
|
42
38
|
## Configuration
|
|
43
39
|
|
|
44
|
-
Dyngle reads configuration from YAML files.
|
|
40
|
+
Dyngle reads configuration from YAML files. Specify the config file location using any of the following (in order of precedence):
|
|
45
41
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
42
|
+
1. A `--config` command line option, OR
|
|
43
|
+
2. A `DYNGLE_CONFIG` environment variable, OR
|
|
44
|
+
3. `.dyngle.yml` in current directory, OR
|
|
45
|
+
4. `~/.dyngle.yml` in home directory
|
|
50
46
|
|
|
51
47
|
## Operations
|
|
52
48
|
|
|
53
|
-
Operations are defined under `dyngle:` in the configuration. In its simplest form, an Operation
|
|
49
|
+
Operations are defined under `dyngle:` in the configuration. In its simplest form, an Operation is a YAML array defining the Steps, as system commands with space-separated arguments. In that sense, a Dyngle operation looks something akin to a phony Make target, a short Bash script, or a CI/CD job.
|
|
50
|
+
|
|
51
|
+
As a serious example, consider the `init` operation from the Dyngle configuration delivered with the project's source code.
|
|
54
52
|
|
|
55
53
|
```yaml
|
|
56
54
|
dyngle:
|
|
@@ -61,11 +59,11 @@ dyngle:
|
|
|
61
59
|
- .venv/bin/pip install --upgrade pip poetry
|
|
62
60
|
```
|
|
63
61
|
|
|
64
|
-
The elements of the YAML array _look_ like lines of Bash, but Dyngle
|
|
62
|
+
The elements of the YAML array _look_ like lines of Bash, but Dyngle processes them directly as system commands, allowing for template substitution and Python expression evaluation (described below). So shell-specific syntax such as `|`, `>`, and `$VARIABLE` won't work.
|
|
65
63
|
|
|
66
64
|
## Data and Templates
|
|
67
65
|
|
|
68
|
-
Dyngle maintains a block of Data throughout an operation, which is a set of named values (Python `dict`, YAML "mapping"). The values are usually strings but can also be other data types that are valid in both YAML and Python.
|
|
66
|
+
Dyngle maintains a block of "Live Data" throughout an operation, which is a set of named values (Python `dict`, YAML "mapping"). The values are usually strings but can also be other data types that are valid in both YAML and Python.
|
|
69
67
|
|
|
70
68
|
The `dyngle run` command feeds the contents of stdin to the Operation as Data, by converting a YAML mapping to named Python values. The values may be substituted into commands or arguments in Steps using double-curly-bracket syntax (`{{` and `}}`) similar to Jinja2.
|
|
71
69
|
|
|
@@ -92,7 +90,7 @@ Hello Francis!
|
|
|
92
90
|
|
|
93
91
|
## Expressions
|
|
94
92
|
|
|
95
|
-
Operations may contain Expressions, written in Python, that can be referenced in
|
|
93
|
+
Operations may contain Expressions, written in Python, that can be referenced in Operation Step Templates using the same syntax as for Data. In the case of a naming conflict, an Expression takes precedence over Data with the same name. Expressions can reference names in the Data directly.
|
|
96
94
|
|
|
97
95
|
Expressions may be defined in either of two ways in the configuration:
|
|
98
96
|
|
|
@@ -142,8 +140,6 @@ YAML keys can contain hyphens, which are fully supported in Dyngle. To reference
|
|
|
142
140
|
- Reference the name using underscores instead of hyphens (they are automatically replaced), OR
|
|
143
141
|
- Use the built-in special-purpose `resolve()` function (which can also be used to reference other expressions)
|
|
144
142
|
|
|
145
|
-
|
|
146
|
-
|
|
147
143
|
```yaml
|
|
148
144
|
dyngle:
|
|
149
145
|
expressions:
|
|
@@ -207,26 +203,31 @@ dyngle:
|
|
|
207
203
|
- echo "Hello {{name}}"
|
|
208
204
|
```
|
|
209
205
|
|
|
210
|
-
##
|
|
206
|
+
## Passing values between Steps in an Operation
|
|
207
|
+
|
|
208
|
+
The Steps parser supports two special operators designed to move data between Steps in an explicit way.
|
|
211
209
|
|
|
212
|
-
The
|
|
210
|
+
- The data assignment operator (`=>`) assigns the contents of stdout from the command to an element in the data
|
|
211
|
+
- The data input operator (`->`) assigns the value of an element in the data (or an evaluated expression) to stdin for the command
|
|
213
212
|
|
|
214
|
-
The
|
|
213
|
+
The operators must appear in order in the step and must be isolated with whitespace, i.e.
|
|
215
214
|
|
|
216
|
-
|
|
215
|
+
```
|
|
216
|
+
<input-variable-name> -> <command and arguments> => <output-variable-name>
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Here we get into more useful functionality, where commands can be strung together in meaningful ways without the need for Bash.
|
|
217
220
|
|
|
218
221
|
```yaml
|
|
219
222
|
dyngle:
|
|
220
223
|
operations:
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
- date => full-date
|
|
226
|
-
- echo "Today is {{just-the-date}}"
|
|
224
|
+
weather:
|
|
225
|
+
- curl -s "https://api.open-meteo.com/v1/forecast?latitude=52.52&longitude=13.41¤t_weather=true" => weather-data
|
|
226
|
+
- weather-data -> jq -j '.current_weather.temperature' => temperature
|
|
227
|
+
- echo "It's {{temperature}} degrees out there!"
|
|
227
228
|
```
|
|
228
229
|
|
|
229
|
-
|
|
230
|
+
If names overlap, data items populated using the data assignment operator take precedence over expressions and data in the original input from the beginning of the Operation.
|
|
230
231
|
|
|
231
232
|
## Lifecycle
|
|
232
233
|
|
|
@@ -235,7 +236,7 @@ The lifecycle of an operation is:
|
|
|
235
236
|
1. Load Data if it exists from YAML on stdin (if no tty)
|
|
236
237
|
2. Find the named Operation in the configuration
|
|
237
238
|
2. Perform template rendering on the first Step, using Data and Expressions
|
|
238
|
-
3. Execute the Step in a subprocess
|
|
239
|
+
3. Execute the Step in a subprocess, passing in an input value and populating an output value in the Data
|
|
239
240
|
4. Continue with the next Step
|
|
240
241
|
|
|
241
242
|
Note that operations in the config are _not_ full shell lines. They are passed directly to the system.
|
|
@@ -1,27 +1,25 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: dyngle
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 1.0.1
|
|
4
4
|
Summary: Run lightweight local workflows
|
|
5
5
|
License: MIT
|
|
6
6
|
Author: Steampunk Wizard
|
|
7
7
|
Author-email: dyngle@steamwiz.io
|
|
8
|
-
Requires-Python: >=3.11,<
|
|
8
|
+
Requires-Python: >=3.11,<4.0
|
|
9
9
|
Classifier: License :: OSI Approved :: MIT License
|
|
10
10
|
Classifier: Programming Language :: Python :: 3
|
|
11
11
|
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
12
14
|
Requires-Dist: requests (>=2.32.3,<3.0.0)
|
|
13
|
-
Requires-Dist: wizlib (>=3.
|
|
15
|
+
Requires-Dist: wizlib (>=3.3.8,<3.4.0)
|
|
14
16
|
Description-Content-Type: text/markdown
|
|
15
17
|
|
|
16
18
|
# Dyngle
|
|
17
19
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
- A lightweight workflow engine
|
|
22
|
-
- A replacement for Make in Python projects
|
|
23
|
-
- A replacement for short functions in RC files
|
|
24
|
-
- Freedom from quirky Bash syntax
|
|
20
|
+
An experimantal, lightweight, easily configurable workflow engine for
|
|
21
|
+
automating development, operations, data processing, and content management
|
|
22
|
+
tasks.
|
|
25
23
|
|
|
26
24
|
Technical foundations
|
|
27
25
|
|
|
@@ -56,16 +54,18 @@ dyngle run hello
|
|
|
56
54
|
|
|
57
55
|
## Configuration
|
|
58
56
|
|
|
59
|
-
Dyngle reads configuration from YAML files.
|
|
57
|
+
Dyngle reads configuration from YAML files. Specify the config file location using any of the following (in order of precedence):
|
|
60
58
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
59
|
+
1. A `--config` command line option, OR
|
|
60
|
+
2. A `DYNGLE_CONFIG` environment variable, OR
|
|
61
|
+
3. `.dyngle.yml` in current directory, OR
|
|
62
|
+
4. `~/.dyngle.yml` in home directory
|
|
65
63
|
|
|
66
64
|
## Operations
|
|
67
65
|
|
|
68
|
-
Operations are defined under `dyngle:` in the configuration. In its simplest form, an Operation
|
|
66
|
+
Operations are defined under `dyngle:` in the configuration. In its simplest form, an Operation is a YAML array defining the Steps, as system commands with space-separated arguments. In that sense, a Dyngle operation looks something akin to a phony Make target, a short Bash script, or a CI/CD job.
|
|
67
|
+
|
|
68
|
+
As a serious example, consider the `init` operation from the Dyngle configuration delivered with the project's source code.
|
|
69
69
|
|
|
70
70
|
```yaml
|
|
71
71
|
dyngle:
|
|
@@ -76,11 +76,11 @@ dyngle:
|
|
|
76
76
|
- .venv/bin/pip install --upgrade pip poetry
|
|
77
77
|
```
|
|
78
78
|
|
|
79
|
-
The elements of the YAML array _look_ like lines of Bash, but Dyngle
|
|
79
|
+
The elements of the YAML array _look_ like lines of Bash, but Dyngle processes them directly as system commands, allowing for template substitution and Python expression evaluation (described below). So shell-specific syntax such as `|`, `>`, and `$VARIABLE` won't work.
|
|
80
80
|
|
|
81
81
|
## Data and Templates
|
|
82
82
|
|
|
83
|
-
Dyngle maintains a block of Data throughout an operation, which is a set of named values (Python `dict`, YAML "mapping"). The values are usually strings but can also be other data types that are valid in both YAML and Python.
|
|
83
|
+
Dyngle maintains a block of "Live Data" throughout an operation, which is a set of named values (Python `dict`, YAML "mapping"). The values are usually strings but can also be other data types that are valid in both YAML and Python.
|
|
84
84
|
|
|
85
85
|
The `dyngle run` command feeds the contents of stdin to the Operation as Data, by converting a YAML mapping to named Python values. The values may be substituted into commands or arguments in Steps using double-curly-bracket syntax (`{{` and `}}`) similar to Jinja2.
|
|
86
86
|
|
|
@@ -107,7 +107,7 @@ Hello Francis!
|
|
|
107
107
|
|
|
108
108
|
## Expressions
|
|
109
109
|
|
|
110
|
-
Operations may contain Expressions, written in Python, that can be referenced in
|
|
110
|
+
Operations may contain Expressions, written in Python, that can be referenced in Operation Step Templates using the same syntax as for Data. In the case of a naming conflict, an Expression takes precedence over Data with the same name. Expressions can reference names in the Data directly.
|
|
111
111
|
|
|
112
112
|
Expressions may be defined in either of two ways in the configuration:
|
|
113
113
|
|
|
@@ -157,8 +157,6 @@ YAML keys can contain hyphens, which are fully supported in Dyngle. To reference
|
|
|
157
157
|
- Reference the name using underscores instead of hyphens (they are automatically replaced), OR
|
|
158
158
|
- Use the built-in special-purpose `resolve()` function (which can also be used to reference other expressions)
|
|
159
159
|
|
|
160
|
-
|
|
161
|
-
|
|
162
160
|
```yaml
|
|
163
161
|
dyngle:
|
|
164
162
|
expressions:
|
|
@@ -222,26 +220,31 @@ dyngle:
|
|
|
222
220
|
- echo "Hello {{name}}"
|
|
223
221
|
```
|
|
224
222
|
|
|
225
|
-
##
|
|
223
|
+
## Passing values between Steps in an Operation
|
|
224
|
+
|
|
225
|
+
The Steps parser supports two special operators designed to move data between Steps in an explicit way.
|
|
226
226
|
|
|
227
|
-
The
|
|
227
|
+
- The data assignment operator (`=>`) assigns the contents of stdout from the command to an element in the data
|
|
228
|
+
- The data input operator (`->`) assigns the value of an element in the data (or an evaluated expression) to stdin for the command
|
|
228
229
|
|
|
229
|
-
The
|
|
230
|
+
The operators must appear in order in the step and must be isolated with whitespace, i.e.
|
|
230
231
|
|
|
231
|
-
|
|
232
|
+
```
|
|
233
|
+
<input-variable-name> -> <command and arguments> => <output-variable-name>
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Here we get into more useful functionality, where commands can be strung together in meaningful ways without the need for Bash.
|
|
232
237
|
|
|
233
238
|
```yaml
|
|
234
239
|
dyngle:
|
|
235
240
|
operations:
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
- date => full-date
|
|
241
|
-
- echo "Today is {{just-the-date}}"
|
|
241
|
+
weather:
|
|
242
|
+
- curl -s "https://api.open-meteo.com/v1/forecast?latitude=52.52&longitude=13.41¤t_weather=true" => weather-data
|
|
243
|
+
- weather-data -> jq -j '.current_weather.temperature' => temperature
|
|
244
|
+
- echo "It's {{temperature}} degrees out there!"
|
|
242
245
|
```
|
|
243
246
|
|
|
244
|
-
|
|
247
|
+
If names overlap, data items populated using the data assignment operator take precedence over expressions and data in the original input from the beginning of the Operation.
|
|
245
248
|
|
|
246
249
|
## Lifecycle
|
|
247
250
|
|
|
@@ -250,7 +253,7 @@ The lifecycle of an operation is:
|
|
|
250
253
|
1. Load Data if it exists from YAML on stdin (if no tty)
|
|
251
254
|
2. Find the named Operation in the configuration
|
|
252
255
|
2. Perform template rendering on the first Step, using Data and Expressions
|
|
253
|
-
3. Execute the Step in a subprocess
|
|
256
|
+
3. Execute the Step in a subprocess, passing in an input value and populating an output value in the Data
|
|
254
257
|
4. Continue with the next Step
|
|
255
258
|
|
|
256
259
|
Note that operations in the config are _not_ full shell lines. They are passed directly to the system.
|
|
@@ -7,8 +7,8 @@ from wizlib.ui_handler import UIHandler
|
|
|
7
7
|
|
|
8
8
|
from dyngle.command import DyngleCommand
|
|
9
9
|
from dyngle.error import DyngleError
|
|
10
|
-
from dyngle.expression import expression
|
|
11
|
-
from dyngle.operation import Operation
|
|
10
|
+
from dyngle.model.expression import expression
|
|
11
|
+
from dyngle.model.operation import Operation
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class DyngleApp(WizApp):
|
|
@@ -4,8 +4,8 @@ from wizlib.parser import WizParser
|
|
|
4
4
|
from yaml import safe_load
|
|
5
5
|
|
|
6
6
|
from dyngle.command import DyngleCommand
|
|
7
|
-
from dyngle.expression import expression
|
|
8
|
-
from dyngle.template import Template
|
|
7
|
+
from dyngle.model.expression import expression
|
|
8
|
+
from dyngle.model.template import Template
|
|
9
9
|
from dyngle.error import DyngleError
|
|
10
10
|
|
|
11
11
|
|
|
File without changes
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
from typing import Callable
|
|
2
2
|
|
|
3
3
|
from dyngle.error import DyngleError
|
|
4
|
-
from dyngle.
|
|
4
|
+
from dyngle.model.live_data import LiveData
|
|
5
|
+
from dyngle.model.safe_path import SafePath
|
|
5
6
|
|
|
6
7
|
from datetime import datetime as datetime, date, timedelta
|
|
7
8
|
import math
|
|
@@ -9,7 +10,7 @@ import json
|
|
|
9
10
|
import re
|
|
10
11
|
import yaml
|
|
11
12
|
|
|
12
|
-
from dyngle.template import Template
|
|
13
|
+
from dyngle.model.template import Template
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
def formatted_datetime(dt: datetime, format_string=None) -> str:
|
|
@@ -113,10 +114,33 @@ def _evaluate(expression: str, locals: dict) -> str:
|
|
|
113
114
|
return str(result)
|
|
114
115
|
|
|
115
116
|
|
|
117
|
+
# The 'expression' function returns the expression object itself, which is
|
|
118
|
+
# really just a function.
|
|
119
|
+
|
|
116
120
|
def expression(text: str) -> Callable[[dict], str]:
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
121
|
+
"""Generate an expression, which is a function based on a string
|
|
122
|
+
expression"""
|
|
123
|
+
|
|
124
|
+
def definition(live_data: LiveData | dict | None = None) -> str:
|
|
125
|
+
"""The expression function itself"""
|
|
126
|
+
|
|
127
|
+
# Allow for blankness and testability
|
|
128
|
+
live_data = LiveData(live_data)
|
|
129
|
+
|
|
130
|
+
# Translate names to underscore-separated instead of hyphen-separated
|
|
131
|
+
# so they work within the Python namespace.
|
|
132
|
+
|
|
133
|
+
items = live_data.items() if live_data else ()
|
|
134
|
+
locals = LiveData({k.replace('-', '_'): v for k, v in items})
|
|
135
|
+
|
|
136
|
+
# Create a resolve function which allows references using the hyphen
|
|
137
|
+
# syntax too
|
|
138
|
+
|
|
139
|
+
def resolve(key):
|
|
140
|
+
return live_data.resolve(key)
|
|
141
|
+
locals = locals | {'resolve': resolve}
|
|
142
|
+
|
|
143
|
+
# Perform the Python eval, expanded above
|
|
144
|
+
return _evaluate(text, locals)
|
|
145
|
+
|
|
146
|
+
return definition
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from collections import UserDict
|
|
2
|
+
|
|
3
|
+
from dyngle.error import DyngleError
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class LiveData(UserDict):
|
|
7
|
+
|
|
8
|
+
def resolve(self, key: str):
|
|
9
|
+
"""Given a key (which might be dot-separated), return
|
|
10
|
+
the value (which might include evaluating expressions)."""
|
|
11
|
+
|
|
12
|
+
parts = key.split('.')
|
|
13
|
+
current = self.data
|
|
14
|
+
for part in parts:
|
|
15
|
+
if part not in current:
|
|
16
|
+
raise DyngleError(
|
|
17
|
+
f"Invalid expression or data reference '{key}'")
|
|
18
|
+
current = current[part]
|
|
19
|
+
if callable(current):
|
|
20
|
+
return current(self)
|
|
21
|
+
else:
|
|
22
|
+
return current
|
|
@@ -5,7 +5,8 @@ import shlex
|
|
|
5
5
|
import subprocess
|
|
6
6
|
|
|
7
7
|
from dyngle.error import DyngleError
|
|
8
|
-
from dyngle.
|
|
8
|
+
from dyngle.model.live_data import LiveData
|
|
9
|
+
from dyngle.model.template import Template
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
@dataclass
|
|
@@ -16,12 +17,12 @@ class Operation:
|
|
|
16
17
|
steps: list
|
|
17
18
|
|
|
18
19
|
def run(self, data: dict, global_expressions: dict):
|
|
19
|
-
# The data dict is mutable
|
|
20
|
-
steps = self.steps
|
|
21
20
|
expressions = global_expressions | self.local_expressions
|
|
22
|
-
|
|
21
|
+
# Data takes precedence if names match
|
|
22
|
+
live_data = LiveData(expressions) | data
|
|
23
|
+
for markup in self.steps:
|
|
23
24
|
step = Step(markup)
|
|
24
|
-
step.run(
|
|
25
|
+
step.run(live_data)
|
|
25
26
|
|
|
26
27
|
|
|
27
28
|
STEP_PATTERN = re.compile(
|
|
@@ -46,10 +47,12 @@ class Step:
|
|
|
46
47
|
self.input, self.command_template, self.output = \
|
|
47
48
|
parse_step(self.markup)
|
|
48
49
|
|
|
49
|
-
def run(self,
|
|
50
|
-
command = [Template(word).render(
|
|
50
|
+
def run(self, live_data: LiveData):
|
|
51
|
+
command = [Template(word).render(live_data).strip()
|
|
51
52
|
for word in self.command_template]
|
|
52
53
|
pipes = {}
|
|
54
|
+
if self.input:
|
|
55
|
+
pipes["input"] = live_data.resolve(self.input)
|
|
53
56
|
if self.output:
|
|
54
57
|
pipes['stdout'] = subprocess.PIPE
|
|
55
58
|
result = subprocess.run(command, text=True, **pipes)
|
|
@@ -57,4 +60,4 @@ class Step:
|
|
|
57
60
|
raise DyngleError(
|
|
58
61
|
f'Step failed with code {result.returncode}: {self.markup}')
|
|
59
62
|
if self.output:
|
|
60
|
-
|
|
63
|
+
live_data[self.output] = result.stdout.rstrip()
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from functools import partial
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
from dyngle.error import DyngleError
|
|
6
|
+
from dyngle.model.live_data import LiveData
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
PATTERN = re.compile(r'\{\{\s*([^}]+)\s*\}\}')
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class Template:
|
|
14
|
+
|
|
15
|
+
template: str
|
|
16
|
+
|
|
17
|
+
def render(self, live_data: LiveData | dict | None = None) -> str:
|
|
18
|
+
"""Render the template with the provided LiveData (raw data and
|
|
19
|
+
expressions)."""
|
|
20
|
+
|
|
21
|
+
live_data = LiveData(live_data)
|
|
22
|
+
resolver = partial(self._resolve, live_data=live_data)
|
|
23
|
+
return PATTERN.sub(resolver, self.template)
|
|
24
|
+
|
|
25
|
+
def _resolve(self, match, *, live_data: LiveData):
|
|
26
|
+
"""Resolve a single name/path from the template. The argument is a
|
|
27
|
+
merge of the raw data and the expressions, either of which are valid
|
|
28
|
+
substitutions."""
|
|
29
|
+
key = match.group(1).strip()
|
|
30
|
+
return live_data.resolve(key)
|
|
@@ -8,11 +8,11 @@ description = "Run lightweight local workflows"
|
|
|
8
8
|
authors = ["Steampunk Wizard <dyngle@steamwiz.io>"]
|
|
9
9
|
license = "MIT"
|
|
10
10
|
readme = "PACKAGE.md"
|
|
11
|
-
version = "0.
|
|
11
|
+
version = "1.0.1"
|
|
12
12
|
|
|
13
13
|
[tool.poetry.dependencies]
|
|
14
|
-
python = "
|
|
15
|
-
wizlib = "
|
|
14
|
+
python = "^3.11"
|
|
15
|
+
wizlib = "~3.3.8"
|
|
16
16
|
requests = "^2.32.3"
|
|
17
17
|
|
|
18
18
|
[tool.poetry.group.dev.dependencies]
|
dyngle-0.7.0/dyngle/template.py
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
2
|
-
from functools import partial
|
|
3
|
-
import re
|
|
4
|
-
|
|
5
|
-
from dyngle.error import DyngleError
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
PATTERN = re.compile(r'\{\{\s*([^}]+)\s*\}\}')
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
@dataclass
|
|
12
|
-
class Template:
|
|
13
|
-
|
|
14
|
-
template: str
|
|
15
|
-
|
|
16
|
-
def render(self, data: dict = None, expressions: dict = None) -> str:
|
|
17
|
-
"""Render the template with the provided data and expressions.
|
|
18
|
-
|
|
19
|
-
Parameters
|
|
20
|
-
----------
|
|
21
|
-
data : dict
|
|
22
|
-
String data to insert
|
|
23
|
-
expressions : dict
|
|
24
|
-
Functions to call with data
|
|
25
|
-
|
|
26
|
-
Returns
|
|
27
|
-
-------
|
|
28
|
-
str
|
|
29
|
-
Template rendered with expression resolution and values inserted.
|
|
30
|
-
"""
|
|
31
|
-
|
|
32
|
-
data = data if data else {}
|
|
33
|
-
expressions = expressions if expressions else {}
|
|
34
|
-
resolver = partial(self._resolve, live_data=data | expressions)
|
|
35
|
-
return PATTERN.sub(resolver, self.template)
|
|
36
|
-
|
|
37
|
-
def _resolve(self, match, *, live_data: dict):
|
|
38
|
-
"""Resolve a single name/path from the template. The argument is a
|
|
39
|
-
merge of the raw data and the expressions, either of which are valid
|
|
40
|
-
substitutions."""
|
|
41
|
-
key = match.group(1).strip()
|
|
42
|
-
return self.resolve(key, live_data)
|
|
43
|
-
|
|
44
|
-
@staticmethod
|
|
45
|
-
def resolve(key: str, live_data: dict):
|
|
46
|
-
parts = key.split('.')
|
|
47
|
-
current = live_data
|
|
48
|
-
for part in parts:
|
|
49
|
-
if part not in current:
|
|
50
|
-
raise DyngleError(
|
|
51
|
-
f"Invalid expression or data reference '{key}'")
|
|
52
|
-
current = current[part]
|
|
53
|
-
if callable(current):
|
|
54
|
-
return current(live_data)
|
|
55
|
-
else:
|
|
56
|
-
return current
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|