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:

  1. Send operator (->) first
  2. Command in the middle
  3. 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

Next Steps