Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 22 additions & 18 deletions docs/src/content/docs/reference/include-directives.md
Original file line number Diff line number Diff line change
@@ -1,40 +1,44 @@
---
title: Include Directives
description: Learn how to modularize and reuse workflow components across multiple workflows using include directives for better organization and maintainability.
description: Learn how to modularize and reuse workflow components across multiple workflows using import directives for better organization and maintainability.
sidebar:
order: 4
---

Include directives allow you to modularize and reuse workflow components across multiple workflows.
Import directives allow you to modularize and reuse workflow components across multiple workflows.

## Basic Include Syntax
## Basic Import Syntax

```aw wrap
@include relative/path/to/file.md
@import relative/path/to/file.md
```

Includes files relative to the current markdown file's location.
Imports files relative to the current markdown file's location.

## Optional Include Syntax
:::note
`@import` and `@include` are aliases - you can use either keyword interchangeably.
:::

## Optional Import Syntax

```aw wrap
@include? relative/path/to/file.md
@import? relative/path/to/file.md
```

Includes files optionally - if the file doesn't exist, no error occurs and a friendly informational comment is added to the workflow. The optional file will be watched for changes in `gh aw compile --watch` mode, so creating the file later will automatically include it.
Imports files optionally - if the file doesn't exist, no error occurs and a friendly informational comment is added to the workflow. The optional file will be watched for changes in `gh aw compile --watch` mode, so creating the file later will automatically import it.

## Section-Specific Includes
## Section-Specific Imports

```aw wrap
@include filename.md#Section
@import filename.md#Section
```

Includes only a specific section from a markdown file using the section header.
Imports only a specific section from a markdown file using the section header.

## Frontmatter Merging

- **Only `tools:` frontmatter** is allowed in included files, other entries give a warning.
- **Tool merging**: `allowed:` tools are merged across all included files
- **Only `tools:` frontmatter** is allowed in imported files, other entries give a warning.
- **Tool merging**: `allowed:` tools are merged across all imported files

### Example Tool Merging
```aw wrap
Expand All @@ -45,7 +49,7 @@ tools:
allowed: [get_issue]
---

@include shared/extra-tools.md # Adds more GitHub tools
@import shared/extra-tools.md # Adds more GitHub tools
```

```aw wrap
Expand All @@ -60,11 +64,11 @@ tools:

**Result**: Final workflow has `github.allowed: [get_issue, add_issue_comment, update_issue]` and Claude Edit tool.

## Include Path Resolution
## Import Path Resolution

- **Relative paths**: Resolved relative to the including file
- **Nested includes**: Included files can include other files
- **Circular protection**: System prevents infinite include loops
- **Relative paths**: Resolved relative to the importing file
- **Nested imports**: Imported files can import other files
- **Circular protection**: System prevents infinite import loops

## Related Documentation

Expand Down
4 changes: 2 additions & 2 deletions docs/src/content/docs/reference/safe-jobs.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,10 +185,10 @@ safe-outputs:
run: echo "Deploying..."
---

@include shared/common-jobs.md
@import shared/common-jobs.md
```

**Included file (shared/common-jobs.md):**
**Imported file (shared/common-jobs.md):**
```aw wrap
---
safe-outputs:
Expand Down
4 changes: 2 additions & 2 deletions pkg/cli/remove_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -367,12 +367,12 @@ func cleanupAllIncludes(verbose bool) error {
return err
}

// findIncludesInContent finds all @include references in content
// findIncludesInContent finds all @include and @import references in content
func findIncludesInContent(content, baseDir string, verbose bool) ([]string, error) {
_ = baseDir // unused parameter for now, keeping for potential future use
_ = verbose // unused parameter for now, keeping for potential future use
var includes []string
includePattern := regexp.MustCompile(`^@include(\?)?\s+(.+)$`)
includePattern := regexp.MustCompile(`^@(?:include|import)(\?)?\s+(.+)$`)

scanner := bufio.NewScanner(strings.NewReader(content))
for scanner.Scan() {
Expand Down
20 changes: 10 additions & 10 deletions pkg/parser/frontmatter.go
Original file line number Diff line number Diff line change
Expand Up @@ -284,17 +284,17 @@ func ExtractMarkdown(filePath string) (string, error) {
return ExtractMarkdownContent(string(content))
}

// ProcessIncludes processes @include directives in markdown content
// ProcessIncludes processes @include and @import directives in markdown content
// This matches the bash process_includes function behavior
func ProcessIncludes(content, baseDir string, extractTools bool) (string, error) {
scanner := bufio.NewScanner(strings.NewReader(content))
var result bytes.Buffer
includePattern := regexp.MustCompile(`^@include(\?)?\s+(.+)$`)
includePattern := regexp.MustCompile(`^@(?:include|import)(\?)?\s+(.+)$`)

for scanner.Scan() {
line := scanner.Text()

// Check if this line is an @include directive
// Check if this line is an @include or @import directive
if matches := includePattern.FindStringSubmatch(line); matches != nil {
isOptional := matches[1] == "?"
includePath := strings.TrimSpace(matches[2])
Expand Down Expand Up @@ -505,7 +505,7 @@ func extractEngineFromContent(content string) (string, error) {
return strings.TrimSpace(string(engineJSON)), nil
}

// ExpandIncludes recursively expands @include directives until no more remain
// ExpandIncludes recursively expands @include and @import directives until no more remain
// This matches the bash expand_includes function behavior
func ExpandIncludes(content, baseDir string, extractTools bool) (string, error) {
const maxDepth = 10
Expand All @@ -518,9 +518,9 @@ func ExpandIncludes(content, baseDir string, extractTools bool) (string, error)
return "", err
}

// For tools mode, check if we still have @include directives
// For tools mode, check if we still have @include or @import directives
if extractTools {
if !strings.Contains(processedContent, "@include") {
if !strings.Contains(processedContent, "@include") && !strings.Contains(processedContent, "@import") {
// No more includes to process for tools mode
currentContent = processedContent
break
Expand All @@ -544,7 +544,7 @@ func ExpandIncludes(content, baseDir string, extractTools bool) (string, error)
return currentContent, nil
}

// ExpandIncludesForEngines recursively expands @include directives to extract engine configurations
// ExpandIncludesForEngines recursively expands @include and @import directives to extract engine configurations
func ExpandIncludesForEngines(content, baseDir string) ([]string, error) {
const maxDepth = 10
var engines []string
Expand Down Expand Up @@ -572,17 +572,17 @@ func ExpandIncludesForEngines(content, baseDir string) ([]string, error) {
return engines, nil
}

// ProcessIncludesForEngines processes @include directives to extract engine configurations
// ProcessIncludesForEngines processes @include and @import directives to extract engine configurations
func ProcessIncludesForEngines(content, baseDir string) ([]string, string, error) {
scanner := bufio.NewScanner(strings.NewReader(content))
var result bytes.Buffer
var engines []string
includePattern := regexp.MustCompile(`^@include(\?)?\s+(.+)$`)
includePattern := regexp.MustCompile(`^@(?:include|import)(\?)?\s+(.+)$`)

for scanner.Scan() {
line := scanner.Text()

// Check if this line is an @include directive
// Check if this line is an @include or @import directive
if matches := includePattern.FindStringSubmatch(line); matches != nil {
isOptional := matches[1] == "?"
includePath := strings.TrimSpace(matches[2])
Expand Down
42 changes: 42 additions & 0 deletions pkg/parser/frontmatter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,34 @@ Some content here.
extractTools: false,
expected: "# Content with Extra Newlines\nSome content here.\n# After include\n",
},
{
name: "simple import (alias for include)",
content: "@import test.md\n# After import",
baseDir: tempDir,
extractTools: false,
expected: "# Test Content\nThis is a test file content.\n# After import\n",
},
{
name: "extract tools with import",
content: "@import test.md",
baseDir: tempDir,
extractTools: true,
expected: `{"bash":{"allowed":["ls","cat"]}}` + "\n",
},
{
name: "import file not found",
content: "@import nonexistent.md",
baseDir: tempDir,
extractTools: false,
wantErr: true,
},
{
name: "optional import missing file",
content: "@import? missing.md\n",
baseDir: tempDir,
extractTools: false,
expected: "",
},
}

// Create test file with invalid frontmatter for testing validation
Expand Down Expand Up @@ -1059,6 +1087,20 @@ This is test content.
extractTools: true,
wantContains: `"bash"`,
},
{
name: "expand markdown content with import",
content: "# Start\n@import test.md\n# End",
baseDir: tempDir,
extractTools: false,
wantContains: "# Test Content\nThis is test content.",
},
{
name: "expand tools with import",
content: "@import test.md",
baseDir: tempDir,
extractTools: true,
wantContains: `"bash"`,
},
}

for _, tt := range tests {
Expand Down
Loading