Command Steps
Command steps are the default type of operation step in Dyngle. They execute system commands directly, without using a shell.
Not a Shell
Important: Operation steps use shell-like syntax but are not executed in a shell. Commands are parsed and executed directly by Python's subprocess.run().
This means shell-specific features won't work:
Doesn't work (shell syntax):
- echo "Hello" | grep Hello # Use data flow operators instead
- export VAR=value # Use expressions or values instead
- ls > files.txt # Use data flow operators instead
- cd /some/path && ls # Each step is independent
Works (Dyngle syntax):
- echo "Hello world"
- npm install
- python script.py --arg value
- curl -s "https://api.example.com/data"
Use Dyngle's features for data flow, variable substitution, and composability instead of shell features.
Template Syntax
Use double-curly-bracket syntax ({{ and }}) to inject values from the operation context into commands:
dyngle:
operations:
hello:
- echo "Hello {{name}}!"
Templates are rendered before the command executes, replacing {{variable}} with the value from the operation context.
Using Templates
The initial context used for command steps may come from YAML over stdin to the Dyngle run command.
echo "name: Francis" | dyngle run hello
Output:
Hello Francis!
Nested Properties
The double-brackets may contain a context path, using dot-separated strings and ints to navigate nested dicts and lists.
dyngle:
operations:
weather-report:
steps:
- curl -s "https://api.example.com/weather" => weather
- 'echo "Temperature: {{weather.temperature}}"'
- 'echo "Location: {{weather.location.city}}, {{weather.location.country}}"'
dyngle:
constants:
python-command: /bin/python3
expressions:
unittest-command: format('{{python-command}}) -m unittest').split()
operations:
test:
- '{{unittest-command.0}} {{unittest-command.1}} {{unittest-command.2}} test/*'
YAML type inference
YAML type inference impacts how Dyngle configurations are parsed. For example, if a string contains a colon, the YAML parser might interpret the content to the left of the colon as an object key.
- echo 'Temperature: {{temperature}}' => data # YAML parsing error!
One solution is to enclose the entire string entry in single or double quotes.
- "echo 'Temperature: {{temperature}}' => data"
Another is to use a YAML multiline string.
- >-
echo 'Temperature: {{temperature}}' => data
Remember that the YAML parser sends the entire string to Dyngle to parse, so the command and data flow operators must all occupy the same string entry in YAML.
Data Flow Operators
Dyngle provides two operators to pass data between steps.
The Send Operator (->)
The send operator passes a value from the operation context as stdin to a command:
dyngle:
operations:
process-data:
steps:
- curl -s "https://api.example.com/data" => raw-data
- raw-data -> jq '.items' => filtered
- filtered -> python process.py
The value is passed to the command's standard input.
The Receive Operator (=>)
The receive operator captures stdout from a command and assigns it to a named variable in the operation context:
dyngle:
operations:
fetch-data:
steps:
- curl -s "https://api.example.com/users" => users
- echo "Fetched: {{users}}"
The captured value becomes available for subsequent steps.
Combining Operators
You can use both operators in a single step:
<input-variable> -> <command> => <output-variable>
Example:
dyngle:
operations:
weather:
steps:
- curl -s "https://api.example.com/weather" => weather-data
- weather-data -> jq -j '.temperature' => temp
- echo "Temperature: {{temp}} degrees"
Operator Order
When using both operators, they must appear in this order:
- Send operator (
->) first - Command in the middle
- Receive operator (
=>) last
Operator Spacing
Operators must be isolated with whitespace:
Correct:
- command => output
- input -> command
- input -> command => output
Incorrect:
- command=>output # Missing spaces
- input->command # Missing spaces
Siilarity to Sub-operations
Note the similarity between these operators in command steps and the send: and receive: attributes in sub-operation steps:
Command step:
- input-data -> command => output-data
Sub-operation step:
- sub: operation-name
send: input-data
receive: output-data
Both follow the same pattern of sending input and receiving output. See Sub-operations for details.
Practical Examples
API Data Processing
dyngle:
operations:
get-user-emails:
returns: emails
steps:
- curl -s "https://api.example.com/users" => users
- users -> jq -r '.[].email' => emails
Multi-step Pipeline
dyngle:
operations:
analyze-logs:
returns: summary
steps:
- curl -s "https://logs.example.com/today" => logs
- logs -> grep "ERROR" => errors
- errors -> wc -l => error-count
- echo "Found {{error-count}} errors" => summary
Data Transformation
dyngle:
operations:
transform-json:
returns: result
steps:
- cat input.json => raw
- raw -> jq '.data | map({id, name})' => transformed
- transformed -> python format.py => result
Important Notes
Variables Created with =>
Values populated with the receive operator (=>) have the highest precedence in the operation context. They override values, expressions, and inputs with the same name.
See Operation context for complete precedence rules.
Working with Structured Data
When data flow captures structured data (JSON, YAML), use a context path to access nested properties in templates:
dyngle:
operations:
weather:
steps:
- curl -s "https://api.example.com/weather" => weather-data
- 'echo "Temperature: {{weather-data.temperature}}"'
- 'echo "City: {{weather-data.location.city}}"'
dyngle:
operations:
process-items:
expressions:
items: from_json(get('items-json'))
steps:
- curl -s "https://api.example.com/items" => items-json
- echo 'The first item is called {{items.0.name}}'
Using Expressions with Data Flow
You can reference captured data in expressions:
dyngle:
operations:
process:
expressions:
message: "format('Processed {{count}} items')"
steps:
- curl -s "https://api.example.com/items" => items
- items -> jq 'length' => count
- echo "{{message}}"
Prompt Steps
Prompt steps allow operations to pause and wait for user input, even when the operation receives data via stdin.
Basic Syntax
Use the prompt: key to display a message and wait for user input:
dyngle:
operations:
wait-for-user:
steps:
- prompt: "Press enter to continue"
- echo "Continuing..."
Capturing Input
Use the receive: attribute to capture the user's input into a variable:
dyngle:
operations:
get-name:
steps:
- prompt: "Enter your name: "
receive: user-name
- echo "Hello {{user-name}}!"
The captured input becomes available in the operation context for use in subsequent steps.
Template Support
Prompt messages support template substitution:
dyngle:
constants:
app-name: "MyApp"
operations:
welcome:
steps:
- prompt: "Welcome to {{app-name}}! Press enter to start"
receive: confirm
- echo "Starting {{app-name}}..."
How It Works
Prompt steps use WizLib's UI handler to access the terminal (TTY) directly. This means prompts work even when stdin is redirected for passing data to the operation:
echo "config: value" | dyngle run my-operation
The operation can receive YAML data via stdin AND prompt the user for input during execution.
Use Cases
Interactive confirmation:
dyngle:
operations:
deploy:
steps:
- echo "About to deploy to production"
- prompt: "Type 'yes' to confirm: "
receive: confirmation
- echo "Deploying..." # Only if user confirmed
Collecting user input:
dyngle:
operations:
setup:
steps:
- prompt: "Enter project name: "
receive: project-name
- prompt: "Enter author name: "
receive: author
- echo "Creating {{project-name}} by {{author}}"
Pausing for review:
dyngle:
operations:
analyze:
steps:
- curl -s "https://api.example.com/data" => data
- echo "{{data}}"
- prompt: "Review the data above. Press enter to continue"
- data -> python process.py
Important Notes
TTY Required: Prompt steps require a TTY (interactive terminal). They will fail in non-interactive environments like cron jobs or CI/CD pipelines unless those environments provide TTY access.
stdin vs TTY: Operations can receive structured data via stdin (for YAML input) and still prompt users interactively. These are independent mechanisms:
- stdin: For passing data to operations
- TTY: For interactive user prompts
receive: Behavior:
When using receive:, the captured input:
- Can be an empty string if the user just presses enter
- Becomes available immediately in the operation context
- Has the same precedence as other variables created during execution