prompt_manager 1.0.2 → 1.2.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +138 -27
- data/README.md +127 -271
- data/lib/pm/configuration.rb +34 -4
- data/lib/pm/core_directives.rb +21 -20
- data/lib/pm/directive.rb +26 -34
- data/lib/pm/directives.rb +55 -16
- data/lib/pm/errors.rb +71 -0
- data/lib/pm/metadata.rb +110 -9
- data/lib/pm/parsed.rb +51 -46
- data/lib/pm/render_scope.rb +74 -0
- data/lib/pm/renderer.rb +59 -0
- data/lib/pm/shell.rb +95 -40
- data/lib/pm/version.rb +1 -1
- data/lib/pm.rb +116 -57
- data/lib/prompt_manager.rb +6 -0
- metadata +13 -113
- data/.envrc +0 -3
- data/.irbrc +0 -14
- data/COMMITS.md +0 -196
- data/Rakefile +0 -12
- data/docs/api/configuration.md +0 -59
- data/docs/api/constants.md +0 -60
- data/docs/api/index.md +0 -14
- data/docs/api/metadata.md +0 -99
- data/docs/api/parsed.md +0 -98
- data/docs/api/pm-module.md +0 -169
- data/docs/api/render-context.md +0 -51
- data/docs/architecture/design-decisions.md +0 -70
- data/docs/architecture/index.md +0 -6
- data/docs/architecture/processing-pipeline.md +0 -112
- data/docs/assets/css/custom.css +0 -1
- data/docs/assets/images/prompt_manager.gif +0 -0
- data/docs/assets/images/prompt_manager.mp4 +0 -0
- data/docs/examples/ai-agent-prompts.md +0 -173
- data/docs/examples/code-review-prompt.md +0 -107
- data/docs/examples/index.md +0 -7
- data/docs/examples/multi-file-composition.md +0 -123
- data/docs/getting-started/configuration.md +0 -106
- data/docs/getting-started/index.md +0 -7
- data/docs/getting-started/installation.md +0 -34
- data/docs/getting-started/quick-start.md +0 -94
- data/docs/guides/comment-stripping.md +0 -64
- data/docs/guides/custom-directives.md +0 -180
- data/docs/guides/erb-rendering.md +0 -102
- data/docs/guides/includes.md +0 -184
- data/docs/guides/index.md +0 -11
- data/docs/guides/parameters.md +0 -96
- data/docs/guides/parsing.md +0 -151
- data/docs/guides/shell-expansion.md +0 -108
- data/docs/index.md +0 -70
- data/mkdocs.yml +0 -181
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
# Multi-File Composition
|
|
2
|
-
|
|
3
|
-
Compose prompts from shared components using the `include` directive.
|
|
4
|
-
|
|
5
|
-
## Directory Structure
|
|
6
|
-
|
|
7
|
-
```
|
|
8
|
-
prompts/
|
|
9
|
-
review.md
|
|
10
|
-
common/
|
|
11
|
-
system_context.md
|
|
12
|
-
output_format.md
|
|
13
|
-
ruby_standards.md
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
## Shared Components
|
|
17
|
-
|
|
18
|
-
`prompts/common/system_context.md`:
|
|
19
|
-
|
|
20
|
-
```markdown
|
|
21
|
-
---
|
|
22
|
-
title: System Context
|
|
23
|
-
---
|
|
24
|
-
You are a senior software engineer with expertise in <%= language %>.
|
|
25
|
-
Current date: $(date +%Y-%m-%d)
|
|
26
|
-
User: $USER
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
`prompts/common/output_format.md`:
|
|
30
|
-
|
|
31
|
-
```markdown
|
|
32
|
-
---
|
|
33
|
-
title: Output Format
|
|
34
|
-
---
|
|
35
|
-
Format your response as:
|
|
36
|
-
1. Summary (2-3 sentences)
|
|
37
|
-
2. Issues found (bulleted list)
|
|
38
|
-
3. Suggestions (numbered list)
|
|
39
|
-
4. Revised code (if applicable)
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
`prompts/common/ruby_standards.md`:
|
|
43
|
-
|
|
44
|
-
```markdown
|
|
45
|
-
---
|
|
46
|
-
title: Ruby Standards
|
|
47
|
-
---
|
|
48
|
-
Follow these Ruby conventions:
|
|
49
|
-
- Use snake_case for methods and variables
|
|
50
|
-
- Prefer single-line blocks with braces for short expressions
|
|
51
|
-
- Use guard clauses over nested conditionals
|
|
52
|
-
- Avoid monkey-patching standard library classes
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
## The Composed Prompt
|
|
56
|
-
|
|
57
|
-
`prompts/review.md`:
|
|
58
|
-
|
|
59
|
-
```markdown
|
|
60
|
-
---
|
|
61
|
-
title: Full Code Review
|
|
62
|
-
parameters:
|
|
63
|
-
language: ruby
|
|
64
|
-
code: null
|
|
65
|
-
---
|
|
66
|
-
<%= include 'common/system_context.md' %>
|
|
67
|
-
|
|
68
|
-
<%= include 'common/ruby_standards.md' %>
|
|
69
|
-
|
|
70
|
-
Review the following code:
|
|
71
|
-
|
|
72
|
-
<%= code %>
|
|
73
|
-
|
|
74
|
-
<%= include 'common/output_format.md' %>
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
## Usage
|
|
78
|
-
|
|
79
|
-
```ruby
|
|
80
|
-
PM.configure { |c| c.prompts_dir = './prompts' }
|
|
81
|
-
|
|
82
|
-
parsed = PM.parse('review.md')
|
|
83
|
-
prompt = parsed.to_s('code' => File.read('app.rb'))
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
The rendered prompt combines all four files into a single coherent prompt.
|
|
87
|
-
|
|
88
|
-
## Inspecting the Include Tree
|
|
89
|
-
|
|
90
|
-
```ruby
|
|
91
|
-
parsed.metadata.includes
|
|
92
|
-
#=> [
|
|
93
|
-
# { path: ".../common/system_context.md", depth: 1, metadata: {...}, includes: [] },
|
|
94
|
-
# { path: ".../common/ruby_standards.md", depth: 1, metadata: {...}, includes: [] },
|
|
95
|
-
# { path: ".../common/output_format.md", depth: 1, metadata: {...}, includes: [] }
|
|
96
|
-
# ]
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
## Nested Includes
|
|
100
|
-
|
|
101
|
-
Components can include other components. For example, `system_context.md` could include a `team_info.md`:
|
|
102
|
-
|
|
103
|
-
```markdown
|
|
104
|
-
---
|
|
105
|
-
title: System Context
|
|
106
|
-
---
|
|
107
|
-
You are a senior software engineer.
|
|
108
|
-
<%= include 'common/team_info.md' %>
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
The include tree reflects the nesting:
|
|
112
|
-
|
|
113
|
-
```ruby
|
|
114
|
-
parsed.metadata.includes[0][:includes]
|
|
115
|
-
#=> [{ path: ".../common/team_info.md", depth: 2, metadata: {...}, includes: [] }]
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
## Benefits
|
|
119
|
-
|
|
120
|
-
- **Reuse** -- Shared components (system context, output format) written once
|
|
121
|
-
- **Consistency** -- Changes to a component propagate to all prompts that include it
|
|
122
|
-
- **Modularity** -- Swap components per use case (different standards for different languages)
|
|
123
|
-
- **Traceability** -- The includes tree shows exactly what went into each prompt
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
# Configuration
|
|
2
|
-
|
|
3
|
-
PM has three global settings controlled through `PM.configure`.
|
|
4
|
-
|
|
5
|
-
## Setting Defaults
|
|
6
|
-
|
|
7
|
-
```ruby
|
|
8
|
-
PM.configure do |config|
|
|
9
|
-
config.prompts_dir = '~/.prompts' # default: ''
|
|
10
|
-
config.shell = true # default: true
|
|
11
|
-
config.erb = true # default: true
|
|
12
|
-
end
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
## prompts_dir
|
|
16
|
-
|
|
17
|
-
Prepended to relative file paths passed to `PM.parse`. Absolute paths bypass it. Symbols and single words are converted to `.md` basenames first.
|
|
18
|
-
|
|
19
|
-
```ruby
|
|
20
|
-
PM.configure { |c| c.prompts_dir = '/usr/share/prompts' }
|
|
21
|
-
|
|
22
|
-
PM.parse('code_review.md')
|
|
23
|
-
#=> reads /usr/share/prompts/code_review.md
|
|
24
|
-
|
|
25
|
-
PM.parse(:code_review)
|
|
26
|
-
#=> reads /usr/share/prompts/code_review.md
|
|
27
|
-
|
|
28
|
-
PM.parse('code_review')
|
|
29
|
-
#=> reads /usr/share/prompts/code_review.md
|
|
30
|
-
|
|
31
|
-
PM.parse('/absolute/path/review.md')
|
|
32
|
-
#=> reads /absolute/path/review.md (prompts_dir ignored)
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
Subdirectories work naturally:
|
|
36
|
-
|
|
37
|
-
```ruby
|
|
38
|
-
PM.configure { |c| c.prompts_dir = '~/.prompts' }
|
|
39
|
-
|
|
40
|
-
PM.parse('agents/summarize.md')
|
|
41
|
-
#=> reads ~/.prompts/agents/summarize.md
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
## shell
|
|
45
|
-
|
|
46
|
-
Controls whether shell expansion (`$VAR`, `$(command)`) runs during parsing. Defaults to `true`.
|
|
47
|
-
|
|
48
|
-
```ruby
|
|
49
|
-
PM.configure { |c| c.shell = false }
|
|
50
|
-
|
|
51
|
-
# All files now skip shell expansion by default
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
Per-file YAML metadata overrides the global setting:
|
|
55
|
-
|
|
56
|
-
```markdown
|
|
57
|
-
---
|
|
58
|
-
title: Override Example
|
|
59
|
-
shell: true
|
|
60
|
-
---
|
|
61
|
-
This file gets shell expansion even when the global default is false.
|
|
62
|
-
User: $USER
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
## erb
|
|
66
|
-
|
|
67
|
-
Controls whether ERB rendering runs during `to_s`. Defaults to `true`.
|
|
68
|
-
|
|
69
|
-
```ruby
|
|
70
|
-
PM.configure { |c| c.erb = false }
|
|
71
|
-
|
|
72
|
-
# All files now skip ERB rendering by default
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
Per-file metadata overrides this too:
|
|
76
|
-
|
|
77
|
-
```markdown
|
|
78
|
-
---
|
|
79
|
-
title: Override Example
|
|
80
|
-
erb: true
|
|
81
|
-
---
|
|
82
|
-
This file renders ERB even when the global default is false.
|
|
83
|
-
Result: <%= 2 + 2 %>
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
## Resetting
|
|
87
|
-
|
|
88
|
-
Restore all settings to their defaults:
|
|
89
|
-
|
|
90
|
-
```ruby
|
|
91
|
-
PM.config.reset!
|
|
92
|
-
|
|
93
|
-
PM.config.prompts_dir #=> ''
|
|
94
|
-
PM.config.shell #=> true
|
|
95
|
-
PM.config.erb #=> true
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
## Accessing Current Settings
|
|
99
|
-
|
|
100
|
-
```ruby
|
|
101
|
-
PM.config.prompts_dir
|
|
102
|
-
PM.config.shell
|
|
103
|
-
PM.config.erb
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
`PM.config` returns the singleton `PM::Configuration` instance. `PM.configure` yields the same instance.
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
# Getting Started
|
|
2
|
-
|
|
3
|
-
This section covers everything you need to start using PM in your Ruby projects.
|
|
4
|
-
|
|
5
|
-
- [Installation](installation.md) -- Add PM to your project
|
|
6
|
-
- [Quick Start](quick-start.md) -- Parse your first prompt in a few lines
|
|
7
|
-
- [Configuration](configuration.md) -- Set global defaults for prompt directories, shell expansion, and ERB rendering
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
# Installation
|
|
2
|
-
|
|
3
|
-
## Gemfile
|
|
4
|
-
|
|
5
|
-
Add PM to your Gemfile:
|
|
6
|
-
|
|
7
|
-
```ruby
|
|
8
|
-
gem 'prompt_manager'
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
Then run:
|
|
12
|
-
|
|
13
|
-
```bash
|
|
14
|
-
bundle install
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
## Manual Install
|
|
18
|
-
|
|
19
|
-
```bash
|
|
20
|
-
gem install prompt_manager
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
## Require
|
|
24
|
-
|
|
25
|
-
```ruby
|
|
26
|
-
require 'pm'
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
The `PM` module is the primary interface. All public methods are accessed through it.
|
|
30
|
-
|
|
31
|
-
## Requirements
|
|
32
|
-
|
|
33
|
-
- Ruby >= 3.2.0
|
|
34
|
-
- No runtime dependencies beyond the standard library (plus `ostruct` on Ruby 4.0+)
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
# Quick Start
|
|
2
|
-
|
|
3
|
-
## Parse a String
|
|
4
|
-
|
|
5
|
-
```ruby
|
|
6
|
-
require 'pm'
|
|
7
|
-
|
|
8
|
-
parsed = PM.parse("---\ntitle: Hello\n---\nContent here")
|
|
9
|
-
|
|
10
|
-
parsed.metadata.title #=> "Hello"
|
|
11
|
-
parsed.content #=> "\nContent here\n"
|
|
12
|
-
```
|
|
13
|
-
|
|
14
|
-
## Parse a File
|
|
15
|
-
|
|
16
|
-
Given `greeting.md`:
|
|
17
|
-
|
|
18
|
-
```markdown
|
|
19
|
-
---
|
|
20
|
-
title: Greeting
|
|
21
|
-
parameters:
|
|
22
|
-
name: null
|
|
23
|
-
---
|
|
24
|
-
Hello, <%= name %>! Welcome.
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
```ruby
|
|
28
|
-
# Any of these forms work:
|
|
29
|
-
parsed = PM.parse('greeting.md') # explicit .md extension
|
|
30
|
-
parsed = PM.parse(:greeting) # Symbol — .md appended
|
|
31
|
-
parsed = PM.parse('greeting') # single word — .md appended
|
|
32
|
-
|
|
33
|
-
parsed.metadata.title #=> "Greeting"
|
|
34
|
-
parsed.metadata.parameters #=> {"name" => nil}
|
|
35
|
-
|
|
36
|
-
# Render with parameters
|
|
37
|
-
puts parsed.to_s('name' => 'Alice')
|
|
38
|
-
#=> "Hello, Alice! Welcome."
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
When parsing a file, PM also adds `directory`, `name`, `created_at`, and `modified_at` to the metadata.
|
|
42
|
-
|
|
43
|
-
## Parse a String Directly
|
|
44
|
-
|
|
45
|
-
`PM.parse` treats single words as prompt IDs and looks them up as files. To always parse a string as content, use `PM.parse_string`:
|
|
46
|
-
|
|
47
|
-
```ruby
|
|
48
|
-
PM.parse('hello') #=> looks for hello.md in prompts_dir
|
|
49
|
-
PM.parse_string('hello') #=> parses "hello" as string content
|
|
50
|
-
|
|
51
|
-
parsed = PM.parse_string("---\ntitle: Inline\n---\nSome content")
|
|
52
|
-
parsed.metadata.title #=> "Inline"
|
|
53
|
-
parsed.content #=> "Some content\n"
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
## Parameters
|
|
57
|
-
|
|
58
|
-
Parameters declared in the YAML front-matter define template variables:
|
|
59
|
-
|
|
60
|
-
- **`null` value** -- parameter is required; `to_s` raises `ArgumentError` if missing
|
|
61
|
-
- **Any other value** -- used as the default; can be overridden in `to_s`
|
|
62
|
-
|
|
63
|
-
```ruby
|
|
64
|
-
parsed = PM.parse("---\nparameters:\n lang: ruby\n code: null\n---\n<%= lang %>: <%= code %>")
|
|
65
|
-
|
|
66
|
-
# Use defaults where available, supply required params
|
|
67
|
-
parsed.to_s('code' => 'puts "hi"') #=> "ruby: puts \"hi\""
|
|
68
|
-
|
|
69
|
-
# Override defaults
|
|
70
|
-
parsed.to_s('code' => 'print("hi")', 'lang' => 'python')
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
## Shell Expansion
|
|
74
|
-
|
|
75
|
-
Environment variables and commands are expanded at parse time:
|
|
76
|
-
|
|
77
|
-
```markdown
|
|
78
|
-
---
|
|
79
|
-
title: Info
|
|
80
|
-
---
|
|
81
|
-
User: $USER
|
|
82
|
-
Date: $(date +%Y-%m-%d)
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
```ruby
|
|
86
|
-
parsed = PM.parse('info.md')
|
|
87
|
-
parsed.content #=> "User: dewayne\nDate: 2025-01-15\n"
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
## Next Steps
|
|
91
|
-
|
|
92
|
-
- [Configuration](configuration.md) -- Set a prompts directory and global defaults
|
|
93
|
-
- [Parsing Guide](../guides/parsing.md) -- File vs string parsing in depth
|
|
94
|
-
- [Parameters Guide](../guides/parameters.md) -- Required and default parameters
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
# Comment Stripping
|
|
2
|
-
|
|
3
|
-
HTML comments are stripped before any other processing stage.
|
|
4
|
-
|
|
5
|
-
## Automatic Stripping
|
|
6
|
-
|
|
7
|
-
Comments are removed during `PM.parse`:
|
|
8
|
-
|
|
9
|
-
```markdown
|
|
10
|
-
<!-- This comment will be removed -->
|
|
11
|
-
---
|
|
12
|
-
title: My Prompt
|
|
13
|
-
---
|
|
14
|
-
Content here. <!-- This too -->
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
After parsing:
|
|
18
|
-
|
|
19
|
-
```ruby
|
|
20
|
-
parsed = PM.parse(source)
|
|
21
|
-
parsed.content #=> "\nContent here. \n"
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
## Multiline Comments
|
|
25
|
-
|
|
26
|
-
Multiline comments are fully removed:
|
|
27
|
-
|
|
28
|
-
```markdown
|
|
29
|
-
<!--
|
|
30
|
-
This entire block
|
|
31
|
-
is removed
|
|
32
|
-
-->
|
|
33
|
-
---
|
|
34
|
-
title: Example
|
|
35
|
-
---
|
|
36
|
-
Visible content.
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
## Comments Before Metadata
|
|
40
|
-
|
|
41
|
-
Comments before the YAML front-matter are stripped first, so the metadata parser sees clean input:
|
|
42
|
-
|
|
43
|
-
```markdown
|
|
44
|
-
<!-- Editor note: draft version -->
|
|
45
|
-
---
|
|
46
|
-
title: My Prompt
|
|
47
|
-
---
|
|
48
|
-
Content
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
## Direct Access
|
|
52
|
-
|
|
53
|
-
The comment stripping method is available as a standalone utility:
|
|
54
|
-
|
|
55
|
-
```ruby
|
|
56
|
-
PM.strip_comments("Hello <!-- removed --> World")
|
|
57
|
-
#=> "Hello World"
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
## Use Cases
|
|
61
|
-
|
|
62
|
-
- **Editor notes** -- Leave comments for prompt authors that don't appear in the rendered output
|
|
63
|
-
- **Disabled sections** -- Temporarily comment out parts of a prompt
|
|
64
|
-
- **Documentation** -- Annotate prompt files without affecting the AI input
|
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
# Custom Directives
|
|
2
|
-
|
|
3
|
-
Register custom methods that become available inside ERB templates.
|
|
4
|
-
|
|
5
|
-
## Registering a Directive
|
|
6
|
-
|
|
7
|
-
```ruby
|
|
8
|
-
PM.register(:read) { |_ctx, path| File.read(path) }
|
|
9
|
-
PM.register(:env) { |_ctx, key| ENV.fetch(key, '') }
|
|
10
|
-
PM.register(:run) { |_ctx, cmd| `#{cmd}`.chomp }
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
Use them in any prompt file:
|
|
14
|
-
|
|
15
|
-
```markdown
|
|
16
|
-
---
|
|
17
|
-
title: Deploy Prompt
|
|
18
|
-
---
|
|
19
|
-
Hostname: <%= read '/etc/hostname' %>
|
|
20
|
-
Environment: <%= env 'DEPLOY_ENV' %>
|
|
21
|
-
Recent commits: <%= run 'git log --oneline -5' %>
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
## Aliases
|
|
25
|
-
|
|
26
|
-
Register multiple names for the same directive by passing additional names:
|
|
27
|
-
|
|
28
|
-
```ruby
|
|
29
|
-
PM.register(:webpage, :website, :web) { |_ctx, url| fetch_page(url) }
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
All names point to the same block. Use any of them in ERB:
|
|
33
|
-
|
|
34
|
-
```markdown
|
|
35
|
-
<%= webpage 'https://example.com' %>
|
|
36
|
-
<%= web 'https://example.com' %>
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
Duplicate detection still applies — if any name is already registered, an error is raised.
|
|
40
|
-
|
|
41
|
-
## The RenderContext
|
|
42
|
-
|
|
43
|
-
The first argument to every directive block is a `PM::RenderContext` with access to the current render state:
|
|
44
|
-
|
|
45
|
-
| Field | Type | Description |
|
|
46
|
-
|-------|------|-------------|
|
|
47
|
-
| `directory` | String | Directory of the file being rendered |
|
|
48
|
-
| `params` | Hash | Merged parameter values |
|
|
49
|
-
| `metadata` | PM::Metadata | Current file's metadata |
|
|
50
|
-
| `depth` | Integer | Include nesting depth (0 for top-level) |
|
|
51
|
-
| `included` | Set | File paths already in the include chain |
|
|
52
|
-
|
|
53
|
-
```ruby
|
|
54
|
-
PM.register(:current_file) { |ctx| ctx.metadata.name || 'unknown' }
|
|
55
|
-
PM.register(:nesting) { |ctx| ctx.depth.to_s }
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
The context is always the first argument. Additional arguments come from the ERB call:
|
|
59
|
-
|
|
60
|
-
```ruby
|
|
61
|
-
PM.register(:greet) { |_ctx, name| "Hello, #{name}!" }
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
```markdown
|
|
65
|
-
<%= greet 'Alice' %>
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
## Duplicate Registration
|
|
69
|
-
|
|
70
|
-
Registering a name that already exists raises an error:
|
|
71
|
-
|
|
72
|
-
```ruby
|
|
73
|
-
PM.register(:include) { |_ctx, path| path }
|
|
74
|
-
#=> RuntimeError: Directive already registered: include
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
This protects built-in directives from being overwritten.
|
|
78
|
-
|
|
79
|
-
## Resetting Directives
|
|
80
|
-
|
|
81
|
-
Remove all custom directives and restore only the built-ins:
|
|
82
|
-
|
|
83
|
-
```ruby
|
|
84
|
-
PM.reset_directives!
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
After reset, only the built-in directives are registered: `include`, `insert`, and `read`.
|
|
88
|
-
|
|
89
|
-
## Directives in Included Files
|
|
90
|
-
|
|
91
|
-
Custom directives are available in included files too. They share the same directive registry:
|
|
92
|
-
|
|
93
|
-
```markdown
|
|
94
|
-
<!-- parent.md -->
|
|
95
|
-
<%= include 'child.md' %>
|
|
96
|
-
|
|
97
|
-
<!-- child.md -->
|
|
98
|
-
<%= read '/etc/hostname' %>
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
## Name Format
|
|
102
|
-
|
|
103
|
-
Both symbols and strings are accepted:
|
|
104
|
-
|
|
105
|
-
```ruby
|
|
106
|
-
PM.register(:my_helper) { |_ctx| "works" }
|
|
107
|
-
PM.register('other_helper') { |_ctx| "also works" }
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
## Class-Based Directives
|
|
111
|
-
|
|
112
|
-
For organized groups of directives, subclass `PM::Directive`:
|
|
113
|
-
|
|
114
|
-
```ruby
|
|
115
|
-
class MyDirectives < PM::Directive
|
|
116
|
-
desc "Fetch environment variable"
|
|
117
|
-
def env(ctx, key)
|
|
118
|
-
ENV.fetch(key, '')
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
desc "Run a shell command"
|
|
122
|
-
def run(ctx, cmd)
|
|
123
|
-
`#{cmd}`.chomp
|
|
124
|
-
end
|
|
125
|
-
alias_method :exec, :run
|
|
126
|
-
|
|
127
|
-
# No desc — not registered as a directive
|
|
128
|
-
def helper
|
|
129
|
-
"internal"
|
|
130
|
-
end
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
PM::Directive.register_all
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
`desc` marks the next method as a directive. Methods without `desc` are ordinary helpers and won't be registered. `alias_method` aliases are detected automatically via `UnboundMethod#original_name`.
|
|
137
|
-
|
|
138
|
-
### Category name
|
|
139
|
-
|
|
140
|
-
The class name determines a category heading (useful for help output):
|
|
141
|
-
|
|
142
|
-
```ruby
|
|
143
|
-
MyDirectives.category_name #=> "My"
|
|
144
|
-
PM::CoreDirectives.category_name #=> "Core"
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
The last segment of the class name is used, with `Directives` stripped and camelCase split.
|
|
148
|
-
|
|
149
|
-
### Dispatch customization
|
|
150
|
-
|
|
151
|
-
Override `build_dispatch_block` to customize how methods are called when dispatched through `PM.directives`:
|
|
152
|
-
|
|
153
|
-
```ruby
|
|
154
|
-
class MyDirectives < PM::Directive
|
|
155
|
-
class << self
|
|
156
|
-
# Default: proc { |ctx, *args| inst.send(method_name, ctx, *args) }
|
|
157
|
-
def build_dispatch_block(inst, method_name)
|
|
158
|
-
proc { |_ctx, *args| inst.send(method_name, args.flatten) }
|
|
159
|
-
end
|
|
160
|
-
end
|
|
161
|
-
end
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
### Subclass tracking
|
|
165
|
-
|
|
166
|
-
All `PM::Directive` subclasses (direct and indirect) are tracked centrally:
|
|
167
|
-
|
|
168
|
-
```ruby
|
|
169
|
-
PM::Directive.directive_subclasses
|
|
170
|
-
#=> [PM::CoreDirectives, MyDirectives, ...]
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
`register_all` iterates this list, creates a singleton instance per subclass, and registers each described method with `PM.register`.
|
|
174
|
-
|
|
175
|
-
## Listing Directives
|
|
176
|
-
|
|
177
|
-
```ruby
|
|
178
|
-
PM.directives
|
|
179
|
-
#=> { include: #<Proc>, insert: #<Proc>, read: #<Proc>, ... }
|
|
180
|
-
```
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
# ERB Rendering
|
|
2
|
-
|
|
3
|
-
ERB templates are evaluated on demand when `to_s` is called.
|
|
4
|
-
|
|
5
|
-
## Basics
|
|
6
|
-
|
|
7
|
-
Any `<%= expression %>` in the content is evaluated as Ruby:
|
|
8
|
-
|
|
9
|
-
```markdown
|
|
10
|
-
---
|
|
11
|
-
title: Math
|
|
12
|
-
---
|
|
13
|
-
Result: <%= 2 + 2 %>
|
|
14
|
-
Today: <%= Time.now.strftime('%Y-%m-%d') %>
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
```ruby
|
|
18
|
-
parsed = PM.parse('math.md')
|
|
19
|
-
parsed.to_s #=> "Result: 4\nToday: 2025-01-15\n"
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
## Template Parameters
|
|
23
|
-
|
|
24
|
-
Parameters declared in the YAML front-matter are available as local methods inside ERB:
|
|
25
|
-
|
|
26
|
-
```markdown
|
|
27
|
-
---
|
|
28
|
-
title: Greeting
|
|
29
|
-
parameters:
|
|
30
|
-
name: World
|
|
31
|
-
---
|
|
32
|
-
Hello, <%= name %>!
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
```ruby
|
|
36
|
-
parsed = PM.parse('greeting.md')
|
|
37
|
-
parsed.to_s #=> "Hello, World!"
|
|
38
|
-
parsed.to_s('name' => 'Alice') #=> "Hello, Alice!"
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
See [Parameters](parameters.md) for details on required vs default values.
|
|
42
|
-
|
|
43
|
-
## Registered Directives
|
|
44
|
-
|
|
45
|
-
Custom directives registered with `PM.register` are also available in ERB:
|
|
46
|
-
|
|
47
|
-
```ruby
|
|
48
|
-
PM.register(:upcase) { |_ctx, str| str.upcase }
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
```markdown
|
|
52
|
-
---
|
|
53
|
-
title: Example
|
|
54
|
-
---
|
|
55
|
-
<%= upcase 'hello' %>
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
See [Custom Directives](custom-directives.md) for the full guide.
|
|
59
|
-
|
|
60
|
-
## Built-in Directives
|
|
61
|
-
|
|
62
|
-
PM includes one built-in directive:
|
|
63
|
-
|
|
64
|
-
- **`include`** -- Include and render another prompt file. See [Including Files](includes.md).
|
|
65
|
-
|
|
66
|
-
## Disabling ERB
|
|
67
|
-
|
|
68
|
-
Set `erb: false` in the file's YAML metadata:
|
|
69
|
-
|
|
70
|
-
```markdown
|
|
71
|
-
---
|
|
72
|
-
title: Raw Template
|
|
73
|
-
erb: false
|
|
74
|
-
---
|
|
75
|
-
This <%= expression %> is preserved as-is.
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
When ERB is disabled:
|
|
79
|
-
|
|
80
|
-
- `to_s` returns the content without template evaluation
|
|
81
|
-
- Parameters are ignored
|
|
82
|
-
- The `include` directive does not execute
|
|
83
|
-
- `metadata.includes` is an empty array after `to_s`
|
|
84
|
-
|
|
85
|
-
### Global Default
|
|
86
|
-
|
|
87
|
-
```ruby
|
|
88
|
-
PM.configure { |c| c.erb = false }
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
Per-file metadata always overrides the global setting. A file with `erb: true` gets ERB rendering even when the global default is `false`.
|
|
92
|
-
|
|
93
|
-
## Pipeline Position
|
|
94
|
-
|
|
95
|
-
ERB rendering is the last stage of the pipeline, executed on demand:
|
|
96
|
-
|
|
97
|
-
1. Strip HTML comments
|
|
98
|
-
2. Extract YAML metadata
|
|
99
|
-
3. Shell expansion (at parse time)
|
|
100
|
-
4. **ERB rendering** (at `to_s` time)
|
|
101
|
-
|
|
102
|
-
This means shell variables are already expanded before ERB sees the content. If content contains both `$VAR` and `<%= param %>`, the shell variable is resolved first, then ERB fills in the parameters.
|