dyngle 0.4.1__tar.gz → 0.4.3__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.4.3/PACKAGE.md +141 -0
- {dyngle-0.4.1 → dyngle-0.4.3}/PKG-INFO +68 -18
- {dyngle-0.4.1 → dyngle-0.4.3}/dyngle/command/run_command.py +4 -1
- {dyngle-0.4.1 → dyngle-0.4.3}/dyngle/expression.py +38 -4
- dyngle-0.4.3/dyngle/template.py +51 -0
- {dyngle-0.4.1 → dyngle-0.4.3}/pyproject.toml +1 -1
- dyngle-0.4.1/PACKAGE.md +0 -91
- dyngle-0.4.1/dyngle/template.py +0 -31
- {dyngle-0.4.1 → dyngle-0.4.3}/dyngle/__init__.py +0 -0
- {dyngle-0.4.1 → dyngle-0.4.3}/dyngle/__main__.py +0 -0
- {dyngle-0.4.1 → dyngle-0.4.3}/dyngle/command/__init__.py +0 -0
- {dyngle-0.4.1 → dyngle-0.4.3}/dyngle/error.py +0 -0
- {dyngle-0.4.1 → dyngle-0.4.3}/dyngle/safe_path.py +0 -0
dyngle-0.4.3/PACKAGE.md
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# Dyngle
|
|
2
|
+
|
|
3
|
+
## Run lightweight local workflows
|
|
4
|
+
|
|
5
|
+
Dyngle is a simple workflow runner that executes sequences of commands defined in configuration files. It's like a lightweight combination of Make and a task runner, designed for automating common development and operational tasks.
|
|
6
|
+
|
|
7
|
+
## Basic usage
|
|
8
|
+
|
|
9
|
+
Create a configuration file (e.g., `.dyngle.yml`) with your workflows:
|
|
10
|
+
|
|
11
|
+
```yaml
|
|
12
|
+
dyngle:
|
|
13
|
+
operations:
|
|
14
|
+
build:
|
|
15
|
+
- python -m pip install -e .
|
|
16
|
+
- python -m pytest
|
|
17
|
+
deploy:
|
|
18
|
+
- docker build -t myapp .
|
|
19
|
+
- docker push myapp
|
|
20
|
+
clean:
|
|
21
|
+
- rm -rf __pycache__
|
|
22
|
+
- rm -rf .pytest_cache
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Run an operation:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
dyngle run build
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Configuration
|
|
32
|
+
|
|
33
|
+
Dyngle reads configuration from YAML files. You can specify the config file location using:
|
|
34
|
+
|
|
35
|
+
- `--config` command line option
|
|
36
|
+
- `DYNGLE_CONFIG` environment variable
|
|
37
|
+
- `.dyngle.yml` in current directory
|
|
38
|
+
- `~/.dyngle.yml` in home directory
|
|
39
|
+
|
|
40
|
+
The configuration has 2 parts: `operations:` and `expressions`.
|
|
41
|
+
|
|
42
|
+
## Data
|
|
43
|
+
|
|
44
|
+
Dyngle maintains a block of data throughout operations, which is parsed from YAML in stdin.
|
|
45
|
+
|
|
46
|
+
## Operations
|
|
47
|
+
|
|
48
|
+
Operations contain steps as a YAML array. The lifecycle of an operation is:
|
|
49
|
+
|
|
50
|
+
1. Load input data if it exists from YAML on stdin (if no tty)
|
|
51
|
+
2. Perform template rendering on a step, using data and expressions (see below)
|
|
52
|
+
3. Execute the step in a subprocess
|
|
53
|
+
4. Continue with the next step
|
|
54
|
+
|
|
55
|
+
Note that operations in the config are _not_ full shell lines. They are passed directly to the system.
|
|
56
|
+
|
|
57
|
+
## Templates
|
|
58
|
+
|
|
59
|
+
Prior to running commands, the line containing that command is processed as a template. Entries from the data set can be substituted into the command line using Jinja-like expressions in double-curly brackets (`{{` and `}}`).
|
|
60
|
+
|
|
61
|
+
For example, if stdin contains the following data:
|
|
62
|
+
|
|
63
|
+
```yaml
|
|
64
|
+
name: Francis
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
And the command looks like:
|
|
68
|
+
|
|
69
|
+
``` yaml
|
|
70
|
+
- echo "Hello {{name}}!"
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Then the command will output "Hello Francis!".
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
## Expressions
|
|
77
|
+
|
|
78
|
+
Configs can also contain expressions, written in Python, that can also be referenced in operation steps.
|
|
79
|
+
|
|
80
|
+
```yaml
|
|
81
|
+
dyngle:
|
|
82
|
+
expressions:
|
|
83
|
+
say-hello: >-
|
|
84
|
+
'Hello ' + name + '!'
|
|
85
|
+
operations:
|
|
86
|
+
say-hello: echo {{say-hello}}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Expressions can use a controlled subset of the Python standard library, including:
|
|
90
|
+
|
|
91
|
+
- Built-in data types such as `str()`
|
|
92
|
+
- Essential built-in functions such as `len()`
|
|
93
|
+
- The core modules from the `datetime` package (but some methods such as `strftime()` will fail)
|
|
94
|
+
- A specialized function called `formatted()` to perform string formatting operations on a `datetime` object
|
|
95
|
+
- A restricted version of `Path()` that only operates within the current working directory
|
|
96
|
+
- Various other useful utilities, mostly read-only, such as the `math` module
|
|
97
|
+
- A special function called `resolve` which resolves data expressions using the same logic as in templates
|
|
98
|
+
|
|
99
|
+
Data keys containing hyphens are converted to valid Python names by replacing hyphens with underscores.
|
|
100
|
+
|
|
101
|
+
Expressions can reference data directly as local names in Python (using the underscore replacements)...
|
|
102
|
+
|
|
103
|
+
```yaml
|
|
104
|
+
dyngle:
|
|
105
|
+
expressions:
|
|
106
|
+
say-hello: >-
|
|
107
|
+
'Hello ' + full_name + '!'
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
... or using the `resolve()` function, which also allows expressions to essentially call other expressions, using the same underlying data set.
|
|
111
|
+
|
|
112
|
+
```yaml
|
|
113
|
+
dyngle:
|
|
114
|
+
expressions:
|
|
115
|
+
hello: >-
|
|
116
|
+
'Hello ' + resolve('formal-name') + '!'
|
|
117
|
+
formal-name: >-
|
|
118
|
+
'Ms. ' + full_name
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Note it's also _possible_ to call other expressions by name as functions, if they only return hard-coded values (i.e. constants).
|
|
122
|
+
|
|
123
|
+
```yaml
|
|
124
|
+
dyngle:
|
|
125
|
+
expressions:
|
|
126
|
+
author-name: Francis Potter
|
|
127
|
+
author-hello: >-
|
|
128
|
+
'Hello ' + author_name()
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Security
|
|
132
|
+
|
|
133
|
+
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.
|
|
134
|
+
|
|
135
|
+
## Quick installation (MacOS)
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
brew install python@3.11
|
|
139
|
+
python3.11 -m pip install pipx
|
|
140
|
+
pipx install dyngle
|
|
141
|
+
```
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: dyngle
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.3
|
|
4
4
|
Summary: Run lightweight local workflows
|
|
5
5
|
License: MIT
|
|
6
6
|
Author: Steampunk Wizard
|
|
@@ -52,30 +52,45 @@ Dyngle reads configuration from YAML files. You can specify the config file loca
|
|
|
52
52
|
- `.dyngle.yml` in current directory
|
|
53
53
|
- `~/.dyngle.yml` in home directory
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
The configuration has 2 parts: `operations:` and `expressions`.
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
## Data
|
|
58
58
|
|
|
59
|
-
|
|
59
|
+
Dyngle maintains a block of data throughout operations, which is parsed from YAML in stdin.
|
|
60
|
+
|
|
61
|
+
## Operations
|
|
62
|
+
|
|
63
|
+
Operations contain steps as a YAML array. The lifecycle of an operation is:
|
|
64
|
+
|
|
65
|
+
1. Load input data if it exists from YAML on stdin (if no tty)
|
|
66
|
+
2. Perform template rendering on a step, using data and expressions (see below)
|
|
67
|
+
3. Execute the step in a subprocess
|
|
68
|
+
4. Continue with the next step
|
|
69
|
+
|
|
70
|
+
Note that operations in the config are _not_ full shell lines. They are passed directly to the system.
|
|
71
|
+
|
|
72
|
+
## Templates
|
|
73
|
+
|
|
74
|
+
Prior to running commands, the line containing that command is processed as a template. Entries from the data set can be substituted into the command line using Jinja-like expressions in double-curly brackets (`{{` and `}}`).
|
|
75
|
+
|
|
76
|
+
For example, if stdin contains the following data:
|
|
60
77
|
|
|
61
78
|
```yaml
|
|
62
|
-
|
|
63
|
-
operations:
|
|
64
|
-
test:
|
|
65
|
-
- python -m unittest discover
|
|
66
|
-
- python -m coverage report
|
|
67
|
-
docs:
|
|
68
|
-
- sphinx-build docs docs/_build
|
|
69
|
-
- open docs/_build/index.html
|
|
70
|
-
setup:
|
|
71
|
-
- python -m venv venv
|
|
72
|
-
- source venv/bin/activate
|
|
73
|
-
- pip install -r requirements.txt
|
|
79
|
+
name: Francis
|
|
74
80
|
```
|
|
75
81
|
|
|
82
|
+
And the command looks like:
|
|
83
|
+
|
|
84
|
+
``` yaml
|
|
85
|
+
- echo "Hello {{name}}!"
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Then the command will output "Hello Francis!".
|
|
89
|
+
|
|
90
|
+
|
|
76
91
|
## Expressions
|
|
77
92
|
|
|
78
|
-
Configs can also contain expressions.
|
|
93
|
+
Configs can also contain expressions, written in Python, that can also be referenced in operation steps.
|
|
79
94
|
|
|
80
95
|
```yaml
|
|
81
96
|
dyngle:
|
|
@@ -85,6 +100,7 @@ dyngle:
|
|
|
85
100
|
operations:
|
|
86
101
|
say-hello: echo {{say-hello}}
|
|
87
102
|
```
|
|
103
|
+
|
|
88
104
|
Expressions can use a controlled subset of the Python standard library, including:
|
|
89
105
|
|
|
90
106
|
- Built-in data types such as `str()`
|
|
@@ -92,10 +108,44 @@ Expressions can use a controlled subset of the Python standard library, includin
|
|
|
92
108
|
- The core modules from the `datetime` package (but some methods such as `strftime()` will fail)
|
|
93
109
|
- A specialized function called `formatted()` to perform string formatting operations on a `datetime` object
|
|
94
110
|
- A restricted version of `Path()` that only operates within the current working directory
|
|
111
|
+
- Various other useful utilities, mostly read-only, such as the `math` module
|
|
112
|
+
- A special function called `resolve` which resolves data expressions using the same logic as in templates
|
|
113
|
+
|
|
114
|
+
Data keys containing hyphens are converted to valid Python names by replacing hyphens with underscores.
|
|
115
|
+
|
|
116
|
+
Expressions can reference data directly as local names in Python (using the underscore replacements)...
|
|
117
|
+
|
|
118
|
+
```yaml
|
|
119
|
+
dyngle:
|
|
120
|
+
expressions:
|
|
121
|
+
say-hello: >-
|
|
122
|
+
'Hello ' + full_name + '!'
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
... or using the `resolve()` function, which also allows expressions to essentially call other expressions, using the same underlying data set.
|
|
126
|
+
|
|
127
|
+
```yaml
|
|
128
|
+
dyngle:
|
|
129
|
+
expressions:
|
|
130
|
+
hello: >-
|
|
131
|
+
'Hello ' + resolve('formal-name') + '!'
|
|
132
|
+
formal-name: >-
|
|
133
|
+
'Ms. ' + full_name
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Note it's also _possible_ to call other expressions by name as functions, if they only return hard-coded values (i.e. constants).
|
|
137
|
+
|
|
138
|
+
```yaml
|
|
139
|
+
dyngle:
|
|
140
|
+
expressions:
|
|
141
|
+
author-name: Francis Potter
|
|
142
|
+
author-hello: >-
|
|
143
|
+
'Hello ' + author_name()
|
|
144
|
+
```
|
|
95
145
|
|
|
96
146
|
## Security
|
|
97
147
|
|
|
98
|
-
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.
|
|
148
|
+
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.
|
|
99
149
|
|
|
100
150
|
## Quick installation (MacOS)
|
|
101
151
|
|
|
@@ -18,6 +18,8 @@ class RunCommand(DyngleCommand):
|
|
|
18
18
|
def add_args(cls, parser: WizParser):
|
|
19
19
|
super().add_args(parser)
|
|
20
20
|
parser.add_argument('operation', help='Operation name to run')
|
|
21
|
+
parser.add_argument(
|
|
22
|
+
'args', nargs='*', help='Optional operation arguments')
|
|
21
23
|
|
|
22
24
|
def handle_vals(self):
|
|
23
25
|
super().handle_vals()
|
|
@@ -37,7 +39,8 @@ class RunCommand(DyngleCommand):
|
|
|
37
39
|
self._validate_operation_exists(operations)
|
|
38
40
|
steps = operations[self.operation]
|
|
39
41
|
data_string = self.app.stream.text
|
|
40
|
-
data = safe_load(data_string)
|
|
42
|
+
data = safe_load(data_string) or {}
|
|
43
|
+
data['args'] = self.args
|
|
41
44
|
for step_template in steps:
|
|
42
45
|
step = Template(step_template).render(data, expressions)
|
|
43
46
|
parts = shlex.split(step)
|
|
@@ -9,6 +9,8 @@ import json
|
|
|
9
9
|
import re
|
|
10
10
|
import yaml
|
|
11
11
|
|
|
12
|
+
from dyngle.template import Template
|
|
13
|
+
|
|
12
14
|
|
|
13
15
|
def formatted_datetime(dt: datetime, format_string=None) -> str:
|
|
14
16
|
"""Safe datetime formatting using string operations"""
|
|
@@ -72,9 +74,38 @@ GLOBALS = {
|
|
|
72
74
|
}
|
|
73
75
|
|
|
74
76
|
|
|
75
|
-
def _evaluate(expression: str,
|
|
77
|
+
def _evaluate(expression: str, locals: dict) -> str:
|
|
78
|
+
"""Evaluate a Python expression with safe globals and user data context.
|
|
79
|
+
|
|
80
|
+
Safely evaluates a Python expression string using a restricted set of
|
|
81
|
+
global functions and modules, combined with user-provided data. The
|
|
82
|
+
expression is evaluated in a sandboxed environment that includes basic
|
|
83
|
+
Python built-ins, mathematical operations, date/time handling, and data
|
|
84
|
+
manipulation utilities.
|
|
85
|
+
|
|
86
|
+
Parameters
|
|
87
|
+
----------
|
|
88
|
+
expression : str
|
|
89
|
+
A valid Python expression string to be evaluated.
|
|
90
|
+
data : dict
|
|
91
|
+
Dictionary containing variables and values to be made available during
|
|
92
|
+
expression evaluation. Note that hyphens in keys will be replaced by
|
|
93
|
+
underscores to create valid Python names.
|
|
94
|
+
|
|
95
|
+
Returns
|
|
96
|
+
-------
|
|
97
|
+
str
|
|
98
|
+
String representation of the evaluated expression result. If the result
|
|
99
|
+
is a tuple, returns the string representation of the last element.
|
|
100
|
+
|
|
101
|
+
Raises
|
|
102
|
+
------
|
|
103
|
+
DyngleError
|
|
104
|
+
If the expression contains invalid variable names that are not found in
|
|
105
|
+
the provided data dictionary or global context.
|
|
106
|
+
"""
|
|
76
107
|
try:
|
|
77
|
-
result = eval(expression, GLOBALS,
|
|
108
|
+
result = eval(expression, GLOBALS, locals)
|
|
78
109
|
except KeyError:
|
|
79
110
|
raise DyngleError(f"The following expression contains " +
|
|
80
111
|
f"at least one invalid name: {expression}")
|
|
@@ -83,6 +114,9 @@ def _evaluate(expression: str, data: dict) -> str:
|
|
|
83
114
|
|
|
84
115
|
|
|
85
116
|
def expression(text: str) -> Callable[[dict], str]:
|
|
86
|
-
def evaluate(data: dict) -> str:
|
|
87
|
-
|
|
117
|
+
def evaluate(data: dict = None) -> str:
|
|
118
|
+
items = data.items() if data else ()
|
|
119
|
+
locals = {k.replace('-', '_'): v for k, v in items}
|
|
120
|
+
def resolve(s): return Template.resolve(s, data)
|
|
121
|
+
return _evaluate(text, locals | {'resolve': resolve})
|
|
88
122
|
return evaluate
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from functools import partial
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
PATTERN = re.compile(r'\{\{\s*([^}]+)\s*\}\}')
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class Template:
|
|
11
|
+
|
|
12
|
+
template: str
|
|
13
|
+
|
|
14
|
+
def render(self, data: dict = None, expressions: dict = None) -> str:
|
|
15
|
+
"""Render the template with the provided data and expressions.
|
|
16
|
+
|
|
17
|
+
Parameters
|
|
18
|
+
----------
|
|
19
|
+
data : dict
|
|
20
|
+
String data to insert
|
|
21
|
+
expressions : dict
|
|
22
|
+
Functions to call with data
|
|
23
|
+
|
|
24
|
+
Returns
|
|
25
|
+
-------
|
|
26
|
+
str
|
|
27
|
+
Template rendered with expression resolution and values inserted.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
data = data if data else {}
|
|
31
|
+
expressions = expressions if expressions else {}
|
|
32
|
+
resolver = partial(self._resolve, live_data=data | expressions)
|
|
33
|
+
return PATTERN.sub(resolver, self.template)
|
|
34
|
+
|
|
35
|
+
def _resolve(self, match, *, live_data: dict):
|
|
36
|
+
"""Resolve a single name/path from the template. The argument is a
|
|
37
|
+
merge of the raw data and the expressions, either of which are valid
|
|
38
|
+
substitutions."""
|
|
39
|
+
key = match.group(1).strip()
|
|
40
|
+
return self.resolve(key, live_data)
|
|
41
|
+
|
|
42
|
+
@staticmethod
|
|
43
|
+
def resolve(key: str, live_data: dict):
|
|
44
|
+
parts = key.split('.')
|
|
45
|
+
current = live_data
|
|
46
|
+
for part in parts:
|
|
47
|
+
current = current[part]
|
|
48
|
+
if callable(current):
|
|
49
|
+
return current(live_data)
|
|
50
|
+
else:
|
|
51
|
+
return current
|
dyngle-0.4.1/PACKAGE.md
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
# Dyngle
|
|
2
|
-
|
|
3
|
-
## Run lightweight local workflows
|
|
4
|
-
|
|
5
|
-
Dyngle is a simple workflow runner that executes sequences of commands defined in configuration files. It's like a lightweight combination of Make and a task runner, designed for automating common development and operational tasks.
|
|
6
|
-
|
|
7
|
-
## Basic usage
|
|
8
|
-
|
|
9
|
-
Create a configuration file (e.g., `.dyngle.yml`) with your workflows:
|
|
10
|
-
|
|
11
|
-
```yaml
|
|
12
|
-
dyngle:
|
|
13
|
-
operations:
|
|
14
|
-
build:
|
|
15
|
-
- python -m pip install -e .
|
|
16
|
-
- python -m pytest
|
|
17
|
-
deploy:
|
|
18
|
-
- docker build -t myapp .
|
|
19
|
-
- docker push myapp
|
|
20
|
-
clean:
|
|
21
|
-
- rm -rf __pycache__
|
|
22
|
-
- rm -rf .pytest_cache
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
Run an operation:
|
|
26
|
-
|
|
27
|
-
```bash
|
|
28
|
-
dyngle run build
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
## Configuration
|
|
32
|
-
|
|
33
|
-
Dyngle reads configuration from YAML files. You can specify the config file location using:
|
|
34
|
-
|
|
35
|
-
- `--config` command line option
|
|
36
|
-
- `DYNGLE_CONFIG` environment variable
|
|
37
|
-
- `.dyngle.yml` in current directory
|
|
38
|
-
- `~/.dyngle.yml` in home directory
|
|
39
|
-
|
|
40
|
-
## Workflow structure
|
|
41
|
-
|
|
42
|
-
Each operation is defined as a list of tasks under `dyngle.operations`. Tasks are executed sequentially using Python's subprocess module for security.
|
|
43
|
-
|
|
44
|
-
Example with multiple operations:
|
|
45
|
-
|
|
46
|
-
```yaml
|
|
47
|
-
dyngle:
|
|
48
|
-
operations:
|
|
49
|
-
test:
|
|
50
|
-
- python -m unittest discover
|
|
51
|
-
- python -m coverage report
|
|
52
|
-
docs:
|
|
53
|
-
- sphinx-build docs docs/_build
|
|
54
|
-
- open docs/_build/index.html
|
|
55
|
-
setup:
|
|
56
|
-
- python -m venv venv
|
|
57
|
-
- source venv/bin/activate
|
|
58
|
-
- pip install -r requirements.txt
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
## Expressions
|
|
62
|
-
|
|
63
|
-
Configs can also contain expressions.
|
|
64
|
-
|
|
65
|
-
```yaml
|
|
66
|
-
dyngle:
|
|
67
|
-
expressions:
|
|
68
|
-
say-hello: >-
|
|
69
|
-
'Hello ' + name + '!'
|
|
70
|
-
operations:
|
|
71
|
-
say-hello: echo {{say-hello}}
|
|
72
|
-
```
|
|
73
|
-
Expressions can use a controlled subset of the Python standard library, including:
|
|
74
|
-
|
|
75
|
-
- Built-in data types such as `str()`
|
|
76
|
-
- Essential built-in functions such as `len()`
|
|
77
|
-
- The core modules from the `datetime` package (but some methods such as `strftime()` will fail)
|
|
78
|
-
- A specialized function called `formatted()` to perform string formatting operations on a `datetime` object
|
|
79
|
-
- A restricted version of `Path()` that only operates within the current working directory
|
|
80
|
-
|
|
81
|
-
## Security
|
|
82
|
-
|
|
83
|
-
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.
|
|
84
|
-
|
|
85
|
-
## Quick installation (MacOS)
|
|
86
|
-
|
|
87
|
-
```bash
|
|
88
|
-
brew install python@3.11
|
|
89
|
-
python3.11 -m pip install pipx
|
|
90
|
-
pipx install dyngle
|
|
91
|
-
```
|
dyngle-0.4.1/dyngle/template.py
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
2
|
-
from functools import partial
|
|
3
|
-
import re
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
PATTERN = re.compile(r'\{\{\s*([^}]+)\s*\}\}')
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
@dataclass
|
|
10
|
-
class Template:
|
|
11
|
-
|
|
12
|
-
template: str
|
|
13
|
-
|
|
14
|
-
def render(self, data: dict = None, expressions: dict = None) -> str:
|
|
15
|
-
"""Render the template with the provided data."""
|
|
16
|
-
data = data if data else {}
|
|
17
|
-
expressions = expressions if expressions else {}
|
|
18
|
-
resolver = partial(self._resolve, data=data, expressions=expressions)
|
|
19
|
-
return PATTERN.sub(resolver, self.template)
|
|
20
|
-
|
|
21
|
-
def _resolve(self, match, *, data: dict, expressions: dict):
|
|
22
|
-
"""Resolve a single name/path from the template."""
|
|
23
|
-
key = match.group(1).strip()
|
|
24
|
-
if key in expressions:
|
|
25
|
-
return expressions[key](data)
|
|
26
|
-
else:
|
|
27
|
-
parts = key.split('.')
|
|
28
|
-
current = data
|
|
29
|
-
for part in parts:
|
|
30
|
-
current = current[part]
|
|
31
|
-
return current
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|