Fundamental building blocks
Spry transforms Markdown documents into executable workflows. This guide explains the fundamental building blocks that make this possible.
Table of Contents
Section titled “Table of Contents”- Cells
- Tasks
- Processing Instructions
- Attributes
- Frontmatter
- Dependency Graphs
- Partials
- Interpolation
- Notebooks and Playbooks
What is a Cell?
Section titled “What is a Cell?”A cell is any fenced code block in Markdown. Cells are the atomic units of content and execution in Spry.
Basic Example:
```bash task-nameecho "This is a cell"```Cell Types
Section titled “Cell Types”Spry recognizes four types of cells:
| Type | Purpose | Example |
|---|---|---|
| Code Cell | Any fenced block with a language | ```python |
| Task Cell | Executable cell with identity | ```bash build |
| Partial Cell | Reusable code fragment | ```sql PARTIAL header |
| Content Cell | Non-executable content | ```json config |
Cell Anatomy
Section titled “Cell Anatomy”Every cell has multiple parts that define its behavior:
```bash task-name --flag value { "attr": "value" }#!/usr/bin/env bashecho "cell content"```Component Breakdown:
┌─ Language identifier (required)│ ┌─ Task identity (optional, first bare token)│ │ ┌─ Processing Instructions (optional, flags)│ │ │ ┌─ Attributes (optional, JSON5)│ │ │ │bash task-name --flag value { "attr": "value" }| Component | Description | Required |
|---|---|---|
| Language | Execution mode (bash, sql, python, etc.) | Yes |
| Identity | Unique task name | For tasks |
| Flags | Command-line style options | Optional |
| Attributes | Structured JSON5 metadata | Optional |
| Content | The actual code or content | Yes |
Line Numbers and Source Tracking
Section titled “Line Numbers and Source Tracking”Cells track their position in the source file:
interface CodeCell { startLine: number // Where cell begins (including fence) endLine: number // Where cell ends (including closing fence) source: string // The actual content}This enables:
- Accurate error reporting
- Source code linking
- Debug information
What is a Task?
Section titled “What is a Task?”A task is an executable cell with an identity. Tasks are the execution units in Spry workflows.
Simple Task:
```bash greet --descr "Say hello"echo "Hello, World!"```Task Identity Rules:
- First bare token after language becomes the identity
- Must be unique within a document
- Used for dependency references
- Follows:
[language] [identity] [flags...]
Task Nature
Section titled “Task Nature”Tasks have two natures that determine how they’re handled:
| Nature | Description | Example |
|---|---|---|
| TASK | Executable code that runs | Shell scripts, Python |
| CONTENT | Generates content but doesn’t execute | SQL queries, HTML |
Task Lifecycle
Section titled “Task Lifecycle”Define Task → Parse Metadata → Build Dependencies → Execute → Capture OutputExample Task Lifecycle:
```bash build --capture output.txt --dep compilenpm run build > output.txt```- Define: Task named “build” with dependencies
- Parse: Extract flags (capture, dep)
- Dependencies: Wait for “compile” task
- Execute: Run npm build command
- Capture: Save output to output.txt
Task Directives
Section titled “Task Directives”Task directives are extracted metadata describing how to execute a cell:
interface TaskDirective { nature: "TASK" | "CONTENT" // Execution vs generation identity: string // Unique name language: LanguageSpec // How to execute deps?: string[] // Dependencies}Example:
```bash deploy --dep build --dep testkubectl apply -f deployment.yml```Produces directive:
{ nature: "TASK", identity: "deploy", language: { name: "bash", ... }, deps: ["build", "test"]}Processing Instructions
Section titled “Processing Instructions”What are Processing Instructions?
Section titled “What are Processing Instructions?”Processing Instructions (PI) are POSIX-style command-line flags embedded in code fence metadata. They control task behavior without modifying the code content.
Syntax Overview
Section titled “Syntax Overview”language [identity] [flags...] [attributes]Example:
```bash task-name --long-flag value -s --bool-flag { "json": "attrs" }content```Flag Formats
Section titled “Flag Formats”Spry supports multiple flag styles:
Boolean Flags
Section titled “Boolean Flags”```bash task --silent --verbose# Flags: { silent: true, verbose: true }```Short form:
```bash task -s -v# Flags: { s: true, v: true }```Key-Value Flags
Section titled “Key-Value Flags”Long form with space:
```bash task --descr "Task description"# Flags: { descr: "Task description" }```Long form with equals:
```bash task --env=production# Flags: { env: "production" }```Short form:
```bash task -C output.txt# Flags: { C: "output.txt" }```Repeated Flags
Section titled “Repeated Flags”Multiple values for same flag:
```bash task --dep task1 --dep task2 --dep task3# Flags: { dep: ["task1", "task2", "task3"] }```Flexible Text
Section titled “Flexible Text”Some flags accept comma-separated or array values:
```bash task --dep "task1, task2, task3"# Or```bash task --dep task1 --dep task2# Both produce: { dep: ["task1", "task2", "task3"] }```Common Flag Reference
Section titled “Common Flag Reference”Dependency Management
Section titled “Dependency Management”| Flag | Short | Type | Description |
|---|---|---|---|
--dep | -D | string[] | Declare dependency on another task |
--injected-dep | string | Inject as dependency using regex pattern |
Example:
```bash build --dep compile --dep lintnpm run build```Output Capture
Section titled “Output Capture”| Flag | Short | Type | Description |
|---|---|---|---|
--capture | string | Capture output to file | |
-C | string | Capture output to memory variable | |
--gitignore | boolean | Add captured file to .gitignore |
Example:
```bash test --capture test-results.txt --gitignorenpm test```Execution Control
Section titled “Execution Control”| Flag | Short | Type | Description |
|---|---|---|---|
--interpolate | -I | boolean | Enable template interpolation |
--silent | boolean | Suppress output | |
--descr | string | Task description |
Example:
```bash deploy -I --silent --descr "Deploy to production"echo "Deploying ${APP_VERSION}"```Graph Organization
Section titled “Graph Organization”| Flag | Short | Type | Description |
|---|---|---|---|
--graph | -G | string | Assign task to named graph |
Example:
```bash cleanup --graph maintenancerm -rf temp/```Flag Parsing Rules
Section titled “Flag Parsing Rules”- Order matters: Positional tokens come first, flags after
- Last wins: Repeated non-array flags use last value
- Arrays accumulate:
--depflags combine into array - Quotes preserved: Quoted strings keep spaces
- Numbers coerced: Numeric strings become numbers (if enabled)
Attributes
Section titled “Attributes”What are Attributes?
Section titled “What are Attributes?”Attributes are JSON5 objects at the end of fence metadata. They provide structured, typed metadata that’s more complex than simple flags.
Syntax
Section titled “Syntax”```language identity --flags { "key": "value", nested: { obj: true } }content```JSON5 Features
Section titled “JSON5 Features”JSON5 is more flexible than JSON:
```sql page.sql { route: { caption: "Home Page", description: "Main landing page" }, cache: true, ttl: 3600, // Comments allowed 'single-quotes': 'work', trailingComma: 'ok',}SELECT * FROM pages;```When to Use Attributes vs Flags
Section titled “When to Use Attributes vs Flags”| Scenario | Use | Example |
|---|---|---|
| Simple boolean | Flag | --silent |
| Simple string | Flag | --descr "text" |
| Nested objects | Attributes | { route: { path: "/" } } |
| Arrays of objects | Attributes | { items: [{}, {}] } |
| Type-safe config | Attributes | { port: 8080, debug: true } |
Accessing Attributes
Section titled “Accessing Attributes”In code that processes cells:
interface CodeCell<Attrs = unknown> { attrs?: Attrs}
// With typed attributesinterface RouteAttrs { route: { caption: string description?: string }}
const cell: CodeCell<RouteAttrs> = /* ... */if (cell.attrs?.route) { console.log(cell.attrs.route.caption)}Frontmatter
Section titled “Frontmatter”What is Frontmatter?
Section titled “What is Frontmatter?”Frontmatter is YAML metadata at the very top of a Markdown file. It provides document-level configuration.
Syntax RepresentTion
Section titled “Syntax RepresentTion”---title: My Spryfileversion: 1.0sqlpage-conf: database_url: sqlite://data.db port: 9227custom: - value1 - value2---
# Document starts here
Content...Common Frontmatter Fields
Section titled “Common Frontmatter Fields”SQLPage Configuration
Section titled “SQLPage Configuration”---sqlpage-conf: database_url: postgresql://localhost/mydb port: 9227 web_root: ./public---Document Metadata
Section titled “Document Metadata”---title: Deployment Runbookauthor: DevOps Teamversion: 2.1.0tags: - deployment - production---Custom Configuration
Section titled “Custom Configuration”---app: name: MyApp environment: staging regions: - us-east-1 - eu-west-1---Accessing Frontmatter
Section titled “Accessing Frontmatter”In interpolated cells:
```bash deploy -Iecho "Deploying ${fm.app.name} to ${fm.app.environment}"```In TypeScript code:
interface MyFrontmatter { app: { name: string environment: string }}
const notebook: Notebook<string, MyFrontmatter> = /* ... */console.log(notebook.fm.app.name)Frontmatter Validation
Section titled “Frontmatter Validation”Use Zod schemas for type-safe frontmatter:
const fmSchema = z.object({ title: z.string(), version: z.string(), "sqlpage-conf": z.object({ database_url: z.string().url(), port: z.number().min(1000).max(65535) }).optional()})
type Frontmatter = z.infer<typeof fmSchema>Dependency Graphs
Section titled “Dependency Graphs”What is a Dependency Graph?
Section titled “What is a Dependency Graph?”Tasks form a Directed Acyclic Graph (DAG) based on their dependencies. The graph determines execution order.
Basic Dependencies
Section titled “Basic Dependencies”```bash compilegcc main.c -o main```
```bash test --dep compile./main --test```
```bash deploy --dep testscp main server:/app/```Graph Structure:
compile → test → deployMultiple Dependencies
Section titled “Multiple Dependencies”```bash linteslint src/```
```bash typechecktsc --noEmit```
```bash build --dep lint --dep typechecknpm run build```Graph Structure:
lint ───────┐ ├──→ buildtypecheck ──┘Execution Order
Section titled “Execution Order”Spry uses topological sort to determine execution order:
- Find tasks with no dependencies (entry points)
- Execute them (potentially in parallel)
- Remove from graph
- Repeat until all tasks complete
Example:
```bash aecho "A"```
```bash b --dep aecho "B"```
```bash c --dep aecho "C"```
```bash d --dep b --dep cecho "D"```Execution order:
Step 1: a (no deps)Step 2: b, c (only depend on a, can run in parallel)Step 3: d (depends on b and c)Cycle Detection
Section titled “Cycle Detection”Spry prevents circular dependencies:
```bash task-a --dep task-becho "A"```
```bash task-b --dep task-aecho "B"```Error:
Error: Circular dependency detected: task-a → task-b → task-aNamed Graphs
Section titled “Named Graphs”Isolate tasks into separate graphs:
```bash buildnpm run build```
```bash test --dep buildnpm test```
```bash clean --graph maintenancerm -rf dist/```
```bash reset --graph maintenance --dep cleangit clean -fdx```Two separate graphs:
- Main graph: build → test
- Maintenance graph: clean → reset
Run specific graph:
spry run file.md # Runs main graphspry run file.md --graph maintenance # Runs maintenance graphInjected Dependencies
Section titled “Injected Dependencies”Tasks can inject themselves as dependencies of other tasks using regex patterns:
```bash setup --injected-dep ".*" --silentexport PATH=$PATH:/custom/bin```
```bash task-aecho "Task A"```
```bash task-becho "Task B"```Effective graph:
setup → task-asetup → task-bPattern Matching:
".*"- matches all tasks"build.*"- matches tasks starting with “build”"test-.*"- matches tasks starting with “test-”
Partials
Section titled “Partials”What are Partials?
Section titled “What are Partials?”Partials are reusable code fragments that can be included in other cells, similar to functions or templates.
Defining a Partial
Section titled “Defining a Partial”```sql PARTIAL header.sqlSELECT 'shell' AS component, 'My App' AS title;```Key elements:
PARTIALkeyword (uppercase, acts as identity)- Name for reference (
header.sql) - Optional
--injectpattern for auto-inclusion
Manual Inclusion
Section titled “Manual Inclusion”Use in cells with interpolation enabled:
```sql page.sql -I${await partial("header.sql")}SELECT 'list' AS component;SELECT * FROM users;```Rendered output:
SELECT 'shell' AS component, 'My App' AS title;SELECT 'list' AS component;SELECT * FROM users;Automatic Injection
Section titled “Automatic Injection”Partials with --inject pattern are automatically prepended:
```sql PARTIAL layout.sql --inject **/*.sqlSELECT 'shell' AS component, 'App' AS title;```
```sql index.sqlSELECT 'Welcome' AS message;```
```sql about.sqlSELECT 'About' AS message;```Both cells automatically become:
SELECT 'shell' AS component, 'App' AS title;SELECT 'Welcome' AS message;Injection Patterns
Section titled “Injection Patterns”Glob patterns control where partials inject:
| Pattern | Matches |
|---|---|
**/*.sql | All SQL cells |
pages/*.sql | SQL cells in “pages” context |
api-*.sql | SQL cells starting with “api-” |
*.ts | All TypeScript cells |
Partial Execution Order
Section titled “Partial Execution Order”Partials don’t execute as tasks; they’re content injected during preprocessing:
1. Load all partials2. Build injection map3. For each cell: - Find matching partials - Prepend partial content - Process cell normallyNested Partials
Section titled “Nested Partials”Partials can include other partials:
```sql PARTIAL base.sql-- Base styles```
```sql PARTIAL layout.sql -I --inject **/*.sql${await partial("base.sql")}SELECT 'shell' AS component;```Interpolation
Section titled “Interpolation”What is Interpolation?
Section titled “What is Interpolation?”Interpolation allows embedding dynamic JavaScript expressions in cell content using template literal syntax.
Enabling Interpolation
Section titled “Enabling Interpolation”Use --interpolate or -I:
```bash deploy -Iecho "Deploying version ${env.APP_VERSION}"```Template Syntax
Section titled “Template Syntax”Uses JavaScript template literal syntax:
```bash greet -Iecho "Hello, ${env.USER || 'World'}!"echo "Date: ${new Date().toISOString()}"```Available Context
Section titled “Available Context”Environment Variables
Section titled “Environment Variables”```bash -Iecho "Home: ${env.HOME}"echo "Path: ${env.PATH}"```Frontmatter Access
Section titled “Frontmatter Access”---app: name: MyApp version: 1.0---
```bash -Iecho "App: ${fm.app.name} v${fm.app.version}"```Cell Metadata
Section titled “Cell Metadata”```bash task-name -I --descr "My task"echo "Running task: ${cell.identity}"echo "Description: ${cell.taskDirective?.description}"```Captured Output
Section titled “Captured Output”```bash get-version -C versionnode -p "require('./package.json').version"```
```bash deploy -I --dep get-versiondocker build -t myapp:${captured.version} .```Partial Inclusion
Section titled “Partial Inclusion”```sql page.sql -I${await partial("header.sql")}SELECT * FROM data;```Utility Functions
Section titled “Utility Functions”Safe JSON stringify:
```bash -Iecho '${safeJsonStringify({ key: "value" })}'```Safe vs Unsafe Interpolation
Section titled “Safe vs Unsafe Interpolation”Unsafe (Default with -I)
Section titled “Unsafe (Default with -I)”Full JavaScript eval with access to all context:
```bash -Iecho "Today is ${new Date().toLocaleDateString()}"echo "Random: ${Math.random()}"```Pros:
- Maximum flexibility
- Full JavaScript power
- Complex logic possible
Cons:
- Security risk with untrusted input
- Can execute arbitrary code
- Harder to debug
Safe (Schema-Validated)
Section titled “Safe (Schema-Validated)”Restricted to validated variable substitution:
// Only allow specific variablesconst safeContext = { env: { USER: "alice" }, fm: { version: "1.0" }}Pros:
- Secure with untrusted input
- Predictable behavior
- Easy to audit
Cons:
- Limited expressiveness
- No complex logic
Interpolation Timing
Section titled “Interpolation Timing”Interpolation happens at execution time:
Parse → Build DAG → Execute Task → Interpolate → Run CommandThis ensures:
- Dependencies complete first
- Captured output available
- Environment variables current
Error Handling
Section titled “Error Handling”Interpolation errors are caught and reported:
```bash -Iecho "Value: ${nonexistent.property}"```Error:
Interpolation error in task 'unnamed':Cannot read property 'property' of undefinedNotebooks and Playbooks
Section titled “Notebooks and Playbooks”Document Hierarchy
Section titled “Document Hierarchy”Spry organizes Markdown into structured levels:
Raw Markdown ↓ ParseMDAST (Abstract Syntax Tree) ↓ ExtractNotebook (cells + frontmatter) ↓ SectionPlaybook (sections + context) ↓ AnalyzeTaskDirectives (executable)Notebook
Section titled “Notebook”A Notebook represents a parsed Markdown document.
Structure:
interface Notebook<Provenance, Frontmatter, CellAttrs> { provenance: Provenance // File path or identifier fm: Frontmatter // Parsed frontmatter cells: NotebookCodeCell[] // All code cells issues: Issue[] // Parsing warnings/errors}What it contains:
- All code cells from document
- Document frontmatter
- Parsing issues (warnings, errors)
- Source provenance (file path)
Example:
---title: My Notebook---
```bash task1echo "one"```
```bash task2echo "two"```Becomes:
{ provenance: "file.md", fm: { title: "My Notebook" }, cells: [ { language: "bash", source: 'echo "one"', ... }, { language: "bash", source: 'echo "two"', ... } ], issues: []}Playbook
Section titled “Playbook”A Playbook extends Notebook with section structure.
Structure:
interface Playbook<Provenance, Frontmatter, CellAttrs> { notebook: Notebook<...> // Underlying notebook sections: Section[] // Document sections cells: PlaybookCodeCell[] // Cells with section context}Section Boundaries:
Sections are created by:
- Level 1 headings (
#) - Horizontal rules (
---) - Document start/end
Example:
# Section One
```bash task1echo "one"```
---
# Section Two
```bash task2echo "two"```Becomes:
{ notebook: { /* ... */ }, sections: [ { heading: "Section One", level: 1, startLine: 1 }, { heading: "Section Two", level: 1, startLine: 7 } ], cells: [ { /* task1 */, section: sections[0] }, { /* task2 */, section: sections[1] } ]}PlaybookCodeCell
Section titled “PlaybookCodeCell”Cells in a Playbook have additional context:
interface PlaybookCodeCell<Provenance> extends NotebookCodeCell<Provenance> { section?: Section // Which section contains this cell sectionIdx?: number // Section index}Benefits:
- Know which section contains each cell
- Group cells by section
- Apply section-level rules
- Generate section-based routes (SQLPage)
Section-Based Organization
Section titled “Section-Based Organization”Sections enable powerful patterns:
SQLPage Routes
Section titled “SQLPage Routes”Each section becomes a web page:
# Home
```sql index.sql { route: { caption: "Home" } }SELECT 'Welcome' AS message;```
---
# About
```sql about.sql { route: { caption: "About" } }SELECT 'About Us' AS message;```Logical Grouping
Section titled “Logical Grouping”# Setup Tasks
```bash install-depsnpm install```
```bash setup-dbcreatedb myapp```
---
# Build Tasks
```bash compile --dep install-depstsc```
```bash bundle --dep compilewebpack```Issues
Section titled “Issues”Both Notebooks and Playbooks track issues:
interface Issue { severity: "error" | "warning" | "info" message: string node?: Node // AST node location?: { line: number column: number }}Example issues:
- Missing dependencies
- Invalid syntax in PI
- Schema validation failures
- Duplicate task names
Summary
Section titled “Summary”Core Concepts Quick Reference
Section titled “Core Concepts Quick Reference”| Concept | Purpose | Key Feature |
|---|---|---|
| Cell | Basic unit | Fenced code block |
| Task | Executable unit | Identity + execution |
| Processing Instructions | Task configuration | POSIX-style flags |
| Attributes | Structured metadata | JSON5 objects |
| Frontmatter | Document config | YAML header |
| Dependency Graph | Execution order | DAG with topological sort |
| Partial | Reusable code | Include/inject patterns |
| Interpolation | Dynamic content | Template literals |
| Notebook | Document model | Cells + frontmatter |
| Playbook | Structured document | Sections + context |
Learning Path
Section titled “Learning Path”- Start simple: Basic cells and tasks
- Add dependencies: Build task graphs
- Use partials: Reuse common code
- Enable interpolation: Dynamic content
- Structure with sections: Organize complex workflows
- Add attributes: Type-safe configuration
Next Steps
Section titled “Next Steps”- Architecture Deep Dive - Internal design
- Task Reference - Complete flag documentation
- SQLPage Integration - Web applications
- Examples - Real-world workflows