dyngle 0.5.0__tar.gz → 0.6.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.
Potentially problematic release.
This version of dyngle might be problematic. Click here for more details.
- {dyngle-0.5.0 → dyngle-0.6.0}/PACKAGE.md +28 -0
- {dyngle-0.5.0 → dyngle-0.6.0}/PKG-INFO +29 -1
- {dyngle-0.5.0 → dyngle-0.6.0}/dyngle/__init__.py +25 -8
- {dyngle-0.5.0 → dyngle-0.6.0}/dyngle/command/run_command.py +4 -2
- dyngle-0.6.0/dyngle/operation.py +9 -0
- {dyngle-0.5.0 → dyngle-0.6.0}/dyngle/template.py +5 -0
- {dyngle-0.5.0 → dyngle-0.6.0}/pyproject.toml +1 -1
- {dyngle-0.5.0 → dyngle-0.6.0}/dyngle/__main__.py +0 -0
- {dyngle-0.5.0 → dyngle-0.6.0}/dyngle/command/__init__.py +0 -0
- {dyngle-0.5.0 → dyngle-0.6.0}/dyngle/error.py +0 -0
- {dyngle-0.5.0 → dyngle-0.6.0}/dyngle/expression.py +0 -0
- {dyngle-0.5.0 → dyngle-0.6.0}/dyngle/safe_path.py +0 -0
|
@@ -39,6 +39,18 @@ Dyngle reads configuration from YAML files. You can specify the config file loca
|
|
|
39
39
|
|
|
40
40
|
The configuration has 2 parts: `operations:` and `expressions`.
|
|
41
41
|
|
|
42
|
+
Configuration files can import other configuration files, by providing an entry `imports:` with an array of filepaths. The most obvious example is a Dyngle config in a local directory which imports the user-level configuration.
|
|
43
|
+
|
|
44
|
+
```yaml
|
|
45
|
+
dyngle:
|
|
46
|
+
imports:
|
|
47
|
+
- ~/.dyngle.yml
|
|
48
|
+
expressions:
|
|
49
|
+
operations:
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
In the event of item name conflicts, expressions and operations are loaded from imports in the order specified, so imports lower in the array will override those higher up. The expressions and operations defined in the main file override the imports. Imports are not recursive.
|
|
53
|
+
|
|
42
54
|
## Data
|
|
43
55
|
|
|
44
56
|
Dyngle maintains a block of data throughout operations, which is parsed from YAML in stdin.
|
|
@@ -128,6 +140,22 @@ dyngle:
|
|
|
128
140
|
'Hello ' + author_name()
|
|
129
141
|
```
|
|
130
142
|
|
|
143
|
+
## Local Expressions
|
|
144
|
+
|
|
145
|
+
Expressions can also be defined in a way that applies only to one operation - especially useful for command-line arguments.
|
|
146
|
+
|
|
147
|
+
In this case, the operation definition has a different structure. See the example below.
|
|
148
|
+
|
|
149
|
+
```yaml
|
|
150
|
+
dyngle:
|
|
151
|
+
operations:
|
|
152
|
+
name_from_arg:
|
|
153
|
+
expressions:
|
|
154
|
+
local_name: "args[0]"
|
|
155
|
+
steps:
|
|
156
|
+
- echo "Hello {{local_name}}"
|
|
157
|
+
```
|
|
158
|
+
|
|
131
159
|
## Security
|
|
132
160
|
|
|
133
161
|
Commands are executed using Python's `subprocess.run()` with arguments split in a shell-like fashion. The shell is not used, which reduces the likelihood of shell injection attacks. However, note that Dyngle is not robust to malicious configuration. Use with caution.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: dyngle
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Summary: Run lightweight local workflows
|
|
5
5
|
License: MIT
|
|
6
6
|
Author: Steampunk Wizard
|
|
@@ -54,6 +54,18 @@ Dyngle reads configuration from YAML files. You can specify the config file loca
|
|
|
54
54
|
|
|
55
55
|
The configuration has 2 parts: `operations:` and `expressions`.
|
|
56
56
|
|
|
57
|
+
Configuration files can import other configuration files, by providing an entry `imports:` with an array of filepaths. The most obvious example is a Dyngle config in a local directory which imports the user-level configuration.
|
|
58
|
+
|
|
59
|
+
```yaml
|
|
60
|
+
dyngle:
|
|
61
|
+
imports:
|
|
62
|
+
- ~/.dyngle.yml
|
|
63
|
+
expressions:
|
|
64
|
+
operations:
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
In the event of item name conflicts, expressions and operations are loaded from imports in the order specified, so imports lower in the array will override those higher up. The expressions and operations defined in the main file override the imports. Imports are not recursive.
|
|
68
|
+
|
|
57
69
|
## Data
|
|
58
70
|
|
|
59
71
|
Dyngle maintains a block of data throughout operations, which is parsed from YAML in stdin.
|
|
@@ -143,6 +155,22 @@ dyngle:
|
|
|
143
155
|
'Hello ' + author_name()
|
|
144
156
|
```
|
|
145
157
|
|
|
158
|
+
## Local Expressions
|
|
159
|
+
|
|
160
|
+
Expressions can also be defined in a way that applies only to one operation - especially useful for command-line arguments.
|
|
161
|
+
|
|
162
|
+
In this case, the operation definition has a different structure. See the example below.
|
|
163
|
+
|
|
164
|
+
```yaml
|
|
165
|
+
dyngle:
|
|
166
|
+
operations:
|
|
167
|
+
name_from_arg:
|
|
168
|
+
expressions:
|
|
169
|
+
local_name: "args[0]"
|
|
170
|
+
steps:
|
|
171
|
+
- echo "Hello {{local_name}}"
|
|
172
|
+
```
|
|
173
|
+
|
|
146
174
|
## Security
|
|
147
175
|
|
|
148
176
|
Commands are executed using Python's `subprocess.run()` with arguments split in a shell-like fashion. The shell is not used, which reduces the likelihood of shell injection attacks. However, note that Dyngle is not robust to malicious configuration. Use with caution.
|
|
@@ -8,6 +8,7 @@ from wizlib.ui_handler import UIHandler
|
|
|
8
8
|
from dyngle.command import DyngleCommand
|
|
9
9
|
from dyngle.error import DyngleError
|
|
10
10
|
from dyngle.expression import expression
|
|
11
|
+
from dyngle.operation import Operation
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
class DyngleApp(WizApp):
|
|
@@ -44,15 +45,31 @@ class DyngleApp(WizApp):
|
|
|
44
45
|
|
|
45
46
|
@cached_property
|
|
46
47
|
def operations(self):
|
|
47
|
-
|
|
48
|
-
if not
|
|
49
|
-
raise DyngleError("No operations defined")
|
|
48
|
+
operations_configs = self._get_configuration_details('operations')
|
|
49
|
+
if not operations_configs:
|
|
50
|
+
raise DyngleError("No operations defined in configuration")
|
|
51
|
+
operations = {}
|
|
52
|
+
for key, config in operations_configs.items():
|
|
53
|
+
if isinstance(config, list):
|
|
54
|
+
operation = Operation({}, config)
|
|
55
|
+
elif isinstance(config, dict):
|
|
56
|
+
local_expr_texts = config.get('expressions') or {}
|
|
57
|
+
local_expressions = _expressions_from_texts(local_expr_texts)
|
|
58
|
+
steps = config.get('steps') or []
|
|
59
|
+
operation = Operation(local_expressions, steps)
|
|
60
|
+
else:
|
|
61
|
+
raise DyngleError(f"Invalid operation configuration for {key}")
|
|
62
|
+
operations[key] = operation
|
|
50
63
|
return operations
|
|
51
64
|
|
|
52
65
|
@cached_property
|
|
53
|
-
def
|
|
66
|
+
def global_expressions(self):
|
|
54
67
|
expr_texts = self._get_configuration_details('expressions')
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
68
|
+
return _expressions_from_texts(expr_texts)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _expressions_from_texts(expr_texts):
|
|
72
|
+
if expr_texts:
|
|
73
|
+
return {k: expression(t) for k, t in expr_texts.items()}
|
|
74
|
+
else:
|
|
75
|
+
return {}
|
|
@@ -34,10 +34,12 @@ class RunCommand(DyngleCommand):
|
|
|
34
34
|
|
|
35
35
|
@DyngleCommand.wrap
|
|
36
36
|
def execute(self):
|
|
37
|
-
expressions = self.app.expressions
|
|
38
37
|
operations = self.app.operations
|
|
39
38
|
self._validate_operation_exists(operations)
|
|
40
|
-
|
|
39
|
+
operation = operations[self.operation]
|
|
40
|
+
steps = operation.steps
|
|
41
|
+
expressions = self.app.global_expressions | \
|
|
42
|
+
operation.local_expressions
|
|
41
43
|
data_string = self.app.stream.text
|
|
42
44
|
data = safe_load(data_string) or {}
|
|
43
45
|
data['args'] = self.args
|
|
@@ -2,6 +2,8 @@ from dataclasses import dataclass
|
|
|
2
2
|
from functools import partial
|
|
3
3
|
import re
|
|
4
4
|
|
|
5
|
+
from dyngle.error import DyngleError
|
|
6
|
+
|
|
5
7
|
|
|
6
8
|
PATTERN = re.compile(r'\{\{\s*([^}]+)\s*\}\}')
|
|
7
9
|
|
|
@@ -44,6 +46,9 @@ class Template:
|
|
|
44
46
|
parts = key.split('.')
|
|
45
47
|
current = live_data
|
|
46
48
|
for part in parts:
|
|
49
|
+
if part not in current:
|
|
50
|
+
raise DyngleError(
|
|
51
|
+
f"Invalid expression or data reference '{key}")
|
|
47
52
|
current = current[part]
|
|
48
53
|
if callable(current):
|
|
49
54
|
return current(live_data)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|