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.

@@ -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.5.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
- operations = self._get_configuration_details('operations')
48
- if not operations:
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 expressions(self):
66
+ def global_expressions(self):
54
67
  expr_texts = self._get_configuration_details('expressions')
55
- if expr_texts:
56
- return {k: expression(t) for k, t in expr_texts.items()}
57
- else:
58
- return {}
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
- steps = operations[self.operation]
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
@@ -0,0 +1,9 @@
1
+ from dataclasses import dataclass
2
+
3
+
4
+ @dataclass
5
+ class Operation:
6
+
7
+ local_expressions: dict
8
+
9
+ steps: list
@@ -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)
@@ -8,7 +8,7 @@ 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.5.0"
11
+ version = "0.6.0"
12
12
 
13
13
  [tool.poetry.dependencies]
14
14
  python = "~3.11"
File without changes
File without changes
File without changes
File without changes