prompt_manager 1.0.0 → 1.0.1
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 +13 -1
- data/README.md +70 -20
- data/docs/guides/custom-directives.md +67 -2
- data/docs/guides/includes.md +38 -0
- data/lib/pm/core_directives.rb +51 -0
- data/lib/pm/directive.rb +144 -0
- data/lib/pm/directives.rb +2 -30
- data/lib/pm/version.rb +1 -1
- data/lib/pm.rb +5 -0
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8f6339dd195179a1d9d9f996d1dfc4d0c689a3ed4bb9415fe2d5d68eaaa761ba
|
|
4
|
+
data.tar.gz: 2b443af6d4ea259fd124c6ff9d225129d0e233fbffc0d9e86a42ce2189595f29
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 26ec66ace02ff31ced22d3bd331884c0c74d5ee7356e138897759ed70be2be7d52dca239d3f1ac30c6599c8133446d199db7833de18fda7053e6fc977a1bde3b
|
|
7
|
+
data.tar.gz: a60049ad709699ec112b43c6f281dd50ae6b80d3774c5c9a58e56d6daebcb5ccfc647544461f62f3f6e4a23367421d1401b34988ae22657764b72bf387c03ff8
|
data/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,16 @@
|
|
|
1
|
-
|
|
1
|
+
### [1.0.1] - 2026-02-04
|
|
2
|
+
|
|
3
|
+
#### Added
|
|
4
|
+
- **`PM::Directive` base class** — class-based DSL for defining directive categories. Use `desc` before method definitions to mark them as directives, and `alias_method` for aliases. Subclass tracking, `register_all`, `category_name`, and `build_dispatch_block` are all built-in.
|
|
5
|
+
- **Built-in `insert` directive** (alias: `read`) — insert any file's raw content into a prompt. Unlike `include`, the inserted content is not parsed, shell-expanded, or ERB-rendered.
|
|
6
|
+
- New test file `directive_base_test.rb` with tests for the `PM::Directive` DSL, subclass tracking, category naming, and dispatch block building.
|
|
7
|
+
- New test fixtures for `insert`/`read` directive coverage.
|
|
8
|
+
|
|
9
|
+
#### Changed
|
|
10
|
+
- **`PM::CoreDirectives`** — built-in `include`, `insert`/`read` directives refactored as a `PM::Directive` subclass using the `desc` DSL.
|
|
11
|
+
- `PM.register` directive aliases now support registration via `alias_method` in `PM::Directive` subclasses, with automatic detection via `UnboundMethod#original_name`.
|
|
12
|
+
- Directive registry simplified; `PM.reset_directives!` now delegates to `PM::Directive.register_all`.
|
|
13
|
+
- Updated README and documentation guides for custom directives and includes.
|
|
2
14
|
|
|
3
15
|
## Released
|
|
4
16
|
### [1.0.0] = 2026-02-03
|
data/README.md
CHANGED
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
- <strong>YAML Metadata</strong> - Parse from markdown strings or files<br>
|
|
15
15
|
- <strong>Conditional Shell Expansion</strong> - $ENVAR, ${ENVAR}, $(command) substitution<br>
|
|
16
16
|
- <strong>Conditional ERB Templates</strong> - On-demand rendering with named parameters<br>
|
|
17
|
-
- <strong>
|
|
17
|
+
- <strong>Recursive Include System</strong> - Compose prompts from multiple files<br>
|
|
18
|
+
- <strong>Raw File Insert</strong> - Insert any file's content verbatim<br>
|
|
18
19
|
- <strong>Custom Directives</strong> - Register custom methods for ERB templates<br>
|
|
19
20
|
- <strong>Configurable Pipeline</strong> - Enable/disable stages per prompt or globally<br>
|
|
20
21
|
- <strong>Comment Stripping</strong> - HTML comments removed before processing
|
|
@@ -190,6 +191,37 @@ Included files go through the full processing pipeline (comment stripping, metad
|
|
|
190
191
|
|
|
191
192
|
Nested includes work — A can include B which includes C. Circular includes raise an error.
|
|
192
193
|
|
|
194
|
+
### Inserting raw file content
|
|
195
|
+
|
|
196
|
+
Use `insert` (or its alias `read`) to insert any file's content verbatim. Unlike `include`, the inserted content is not parsed, shell-expanded, or ERB-rendered — it appears as-is:
|
|
197
|
+
|
|
198
|
+
```md
|
|
199
|
+
---
|
|
200
|
+
title: Code Review
|
|
201
|
+
parameters:
|
|
202
|
+
feedback_style: null
|
|
203
|
+
---
|
|
204
|
+
Review this Ruby code:
|
|
205
|
+
|
|
206
|
+
```ruby
|
|
207
|
+
<%= insert 'app/models/user.rb' %>
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Use a <%= feedback_style %> tone.
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Paths are resolved relative to the parent file's directory (same as `include`). Absolute paths work from any context. Missing files raise an error.
|
|
214
|
+
|
|
215
|
+
`insert` vs `include`:
|
|
216
|
+
|
|
217
|
+
| | `insert` | `include` |
|
|
218
|
+
|---|---|---|
|
|
219
|
+
| File types | Any | `.md` only |
|
|
220
|
+
| ERB in content | Preserved as literal text | Rendered |
|
|
221
|
+
| Shell expansion | Not applied | Applied |
|
|
222
|
+
| Recursion | None | Nested includes supported |
|
|
223
|
+
| Metadata tracking | None | `metadata.includes` tree |
|
|
224
|
+
|
|
193
225
|
After calling `to_s`, the parent's metadata has an `includes` key with a tree of what was included:
|
|
194
226
|
|
|
195
227
|
```ruby
|
|
@@ -215,10 +247,11 @@ parsed.metadata.includes
|
|
|
215
247
|
|
|
216
248
|
### Custom directives
|
|
217
249
|
|
|
250
|
+
#### Block-based registration
|
|
251
|
+
|
|
218
252
|
Register custom methods available in ERB templates:
|
|
219
253
|
|
|
220
254
|
```ruby
|
|
221
|
-
PM.register(:read) { |_ctx, path| File.read(path) }
|
|
222
255
|
PM.register(:env) { |_ctx, key| ENV.fetch(key, '') }
|
|
223
256
|
PM.register(:run) { |_ctx, cmd| `#{cmd}`.chomp }
|
|
224
257
|
```
|
|
@@ -229,26 +262,45 @@ Register multiple names for the same directive (aliases):
|
|
|
229
262
|
PM.register(:webpage, :website, :web) { |_ctx, url| fetch_page(url) }
|
|
230
263
|
```
|
|
231
264
|
|
|
232
|
-
|
|
265
|
+
#### Class-based directives
|
|
266
|
+
|
|
267
|
+
For organized groups of directives, subclass `PM::Directive`:
|
|
268
|
+
|
|
269
|
+
```ruby
|
|
270
|
+
class MyDirectives < PM::Directive
|
|
271
|
+
desc "Fetch environment variable"
|
|
272
|
+
def env(ctx, key)
|
|
273
|
+
ENV.fetch(key, '')
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
desc "Run a shell command"
|
|
277
|
+
def run(ctx, cmd)
|
|
278
|
+
`#{cmd}`.chomp
|
|
279
|
+
end
|
|
280
|
+
alias_method :exec, :run
|
|
281
|
+
end
|
|
233
282
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
<%= website 'https://example.com' %>
|
|
237
|
-
<%= web 'https://example.com' %>
|
|
283
|
+
# Register all directive subclasses with PM
|
|
284
|
+
PM::Directive.register_all
|
|
238
285
|
```
|
|
239
286
|
|
|
240
|
-
|
|
287
|
+
`desc` marks the next method as a directive. Methods without `desc` are helpers and won't be registered. `alias_method` aliases are detected automatically.
|
|
241
288
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
289
|
+
Override `build_dispatch_block` on your base class to customize how methods are called:
|
|
290
|
+
|
|
291
|
+
```ruby
|
|
292
|
+
class MyDirectives < PM::Directive
|
|
293
|
+
class << self
|
|
294
|
+
def build_dispatch_block(inst, method_name)
|
|
295
|
+
proc { |_ctx, *args| inst.send(method_name, args) }
|
|
296
|
+
end
|
|
297
|
+
end
|
|
298
|
+
end
|
|
249
299
|
```
|
|
250
300
|
|
|
251
|
-
|
|
301
|
+
#### RenderContext
|
|
302
|
+
|
|
303
|
+
The first argument to every directive block (or class method) is a `PM::RenderContext`:
|
|
252
304
|
|
|
253
305
|
- `ctx.directory` — directory of the file being rendered
|
|
254
306
|
- `ctx.params` — merged parameter values
|
|
@@ -256,10 +308,7 @@ The first argument to every directive block is a `PM::RenderContext` with access
|
|
|
256
308
|
- `ctx.depth` — include nesting depth
|
|
257
309
|
- `ctx.included` — Set of file paths already in the include chain
|
|
258
310
|
|
|
259
|
-
|
|
260
|
-
PM.register(:current_file) { |ctx| ctx.metadata.name || 'unknown' }
|
|
261
|
-
PM.register(:depth) { |ctx| ctx.depth.to_s }
|
|
262
|
-
```
|
|
311
|
+
#### Duplicate detection and reset
|
|
263
312
|
|
|
264
313
|
Registering a name that already exists raises an error:
|
|
265
314
|
|
|
@@ -272,6 +321,7 @@ Reset to built-in directives only:
|
|
|
272
321
|
|
|
273
322
|
```ruby
|
|
274
323
|
PM.reset_directives!
|
|
324
|
+
# Restores: include, insert, read
|
|
275
325
|
```
|
|
276
326
|
|
|
277
327
|
### Disabling processing stages
|
|
@@ -84,7 +84,7 @@ Remove all custom directives and restore only the built-ins:
|
|
|
84
84
|
PM.reset_directives!
|
|
85
85
|
```
|
|
86
86
|
|
|
87
|
-
After reset, only `include`
|
|
87
|
+
After reset, only the built-in directives are registered: `include`, `insert`, and `read`.
|
|
88
88
|
|
|
89
89
|
## Directives in Included Files
|
|
90
90
|
|
|
@@ -107,9 +107,74 @@ PM.register(:my_helper) { |_ctx| "works" }
|
|
|
107
107
|
PM.register('other_helper') { |_ctx| "also works" }
|
|
108
108
|
```
|
|
109
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
|
+
|
|
110
175
|
## Listing Directives
|
|
111
176
|
|
|
112
177
|
```ruby
|
|
113
178
|
PM.directives
|
|
114
|
-
#=> { include: #<Proc>, read: #<Proc>, ... }
|
|
179
|
+
#=> { include: #<Proc>, insert: #<Proc>, read: #<Proc>, ... }
|
|
115
180
|
```
|
data/docs/guides/includes.md
CHANGED
|
@@ -134,6 +134,44 @@ Nested includes form a tree -- each entry's `includes` array contains its own ch
|
|
|
134
134
|
|
|
135
135
|
The `includes` array is `nil` before `to_s` is called and is reset on each call.
|
|
136
136
|
|
|
137
|
+
## `insert` / `read` — Raw File Insertion
|
|
138
|
+
|
|
139
|
+
Use `insert` (or its alias `read`) to insert a file's content verbatim. Unlike `include`, the content is not parsed, shell-expanded, or ERB-rendered:
|
|
140
|
+
|
|
141
|
+
```markdown
|
|
142
|
+
---
|
|
143
|
+
title: Code Review
|
|
144
|
+
---
|
|
145
|
+
Review this code:
|
|
146
|
+
|
|
147
|
+
```ruby
|
|
148
|
+
<%= insert 'src/app.rb' %>
|
|
149
|
+
```
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Differences from `include`
|
|
153
|
+
|
|
154
|
+
| | `insert` | `include` |
|
|
155
|
+
|---|---|---|
|
|
156
|
+
| File types | Any | `.md` only |
|
|
157
|
+
| ERB in content | Preserved as literal text | Rendered |
|
|
158
|
+
| Shell expansion | Not applied | Applied |
|
|
159
|
+
| Recursion | None | Nested includes supported |
|
|
160
|
+
| Metadata tracking | None | `metadata.includes` tree |
|
|
161
|
+
|
|
162
|
+
### Path resolution
|
|
163
|
+
|
|
164
|
+
Same as `include` — paths resolve relative to the parent file's directory. Absolute paths work from any context, including string-parsed prompts.
|
|
165
|
+
|
|
166
|
+
### Missing files
|
|
167
|
+
|
|
168
|
+
Raises an error:
|
|
169
|
+
|
|
170
|
+
```ruby
|
|
171
|
+
PM.parse("---\n---\n<%= insert '/no/such/file' %>").to_s
|
|
172
|
+
#=> RuntimeError: insert: file not found: /no/such/file
|
|
173
|
+
```
|
|
174
|
+
|
|
137
175
|
## Requirements
|
|
138
176
|
|
|
139
177
|
- The `include` directive requires file context. Using it with string-parsed prompts raises an error:
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# lib/pm/core_directives.rb
|
|
4
|
+
#
|
|
5
|
+
# PM's built-in directives: include, insert/read.
|
|
6
|
+
# Defined as a PM::Directive subclass using the desc/alias DSL.
|
|
7
|
+
|
|
8
|
+
module PM
|
|
9
|
+
class CoreDirectives < Directive
|
|
10
|
+
desc "Include and render another prompt file"
|
|
11
|
+
def include(ctx, path)
|
|
12
|
+
unless ctx.directory
|
|
13
|
+
raise 'include requires a file context (use PM.parse with a file path)'
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
full_path = File.expand_path(path, ctx.directory)
|
|
17
|
+
|
|
18
|
+
if ctx.included.include?(full_path)
|
|
19
|
+
raise "Circular include detected: #{full_path}"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
child = PM.parse(full_path)
|
|
23
|
+
result = child.render_with(ctx.params, ctx.included, ctx.depth + 1)
|
|
24
|
+
|
|
25
|
+
ctx.metadata.includes << {
|
|
26
|
+
path: full_path,
|
|
27
|
+
depth: ctx.depth + 1,
|
|
28
|
+
metadata: child.metadata.to_h.reject { |k, _| k == :includes },
|
|
29
|
+
includes: child.metadata.includes
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
result
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
desc "Insert a file's raw content verbatim (no ERB, no shell expansion)"
|
|
36
|
+
def insert(ctx, path)
|
|
37
|
+
full_path = if ctx&.directory
|
|
38
|
+
File.expand_path(path, ctx.directory)
|
|
39
|
+
else
|
|
40
|
+
File.expand_path(path)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
unless File.exist?(full_path)
|
|
44
|
+
raise "insert: file not found: #{full_path}"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
File.read(full_path)
|
|
48
|
+
end
|
|
49
|
+
alias_method :read, :insert
|
|
50
|
+
end
|
|
51
|
+
end
|
data/lib/pm/directive.rb
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# lib/pm/directive.rb
|
|
4
|
+
#
|
|
5
|
+
# Base class for all directive categories.
|
|
6
|
+
#
|
|
7
|
+
# Subclass to define a category of directives. Use `desc` immediately
|
|
8
|
+
# before a method definition to mark it as a directive and provide its
|
|
9
|
+
# help description. Methods without a preceding `desc` are ordinary
|
|
10
|
+
# helpers and will not be registered.
|
|
11
|
+
#
|
|
12
|
+
# Use Ruby's own `alias_method` to create directive aliases;
|
|
13
|
+
# they are detected automatically via UnboundMethod#original_name.
|
|
14
|
+
#
|
|
15
|
+
# Example:
|
|
16
|
+
#
|
|
17
|
+
# class MyDirectives < PM::Directive
|
|
18
|
+
# desc "Greet someone"
|
|
19
|
+
# def greet(ctx, name)
|
|
20
|
+
# "Hello, #{name}!"
|
|
21
|
+
# end
|
|
22
|
+
# alias_method :hi, :greet
|
|
23
|
+
# end
|
|
24
|
+
#
|
|
25
|
+
|
|
26
|
+
module PM
|
|
27
|
+
class Directive
|
|
28
|
+
# Centralized list of all subclasses across the hierarchy.
|
|
29
|
+
# Initialized here on PM::Directive; the inherited hook always
|
|
30
|
+
# appends to this specific array.
|
|
31
|
+
@directive_subclasses = []
|
|
32
|
+
|
|
33
|
+
class << self
|
|
34
|
+
# ---- Subclass tracking ------------------------------------------------
|
|
35
|
+
# Every subclass (direct or indirect) is tracked in PM::Directive's
|
|
36
|
+
# centralized list so register_all can find them all.
|
|
37
|
+
|
|
38
|
+
def inherited(subclass)
|
|
39
|
+
PM::Directive.directive_subclasses << subclass
|
|
40
|
+
super
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Returns the centralized subclass list. Only meaningful when called
|
|
44
|
+
# on PM::Directive itself; subclasses delegate here explicitly.
|
|
45
|
+
def directive_subclasses
|
|
46
|
+
if equal?(PM::Directive)
|
|
47
|
+
@directive_subclasses
|
|
48
|
+
else
|
|
49
|
+
PM::Directive.directive_subclasses
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# ---- Description helper -----------------------------------------------
|
|
54
|
+
# Call `desc "text"` on the line immediately before `def method_name`.
|
|
55
|
+
|
|
56
|
+
def desc(text)
|
|
57
|
+
@_pending_desc = text
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# ---- Automatic metadata capture via method_added ----------------------
|
|
61
|
+
|
|
62
|
+
def method_added(method_name)
|
|
63
|
+
if @_pending_desc
|
|
64
|
+
directive_descriptions[method_name] = @_pending_desc
|
|
65
|
+
@_pending_desc = nil
|
|
66
|
+
else
|
|
67
|
+
# Detect aliases created by alias_method.
|
|
68
|
+
# UnboundMethod#original_name returns the original method name;
|
|
69
|
+
# when it differs from method_name, this method is an alias.
|
|
70
|
+
begin
|
|
71
|
+
um = instance_method(method_name)
|
|
72
|
+
original = um.original_name
|
|
73
|
+
if original != method_name && directive_descriptions.key?(original)
|
|
74
|
+
(directive_aliases[original] ||= []) << method_name
|
|
75
|
+
end
|
|
76
|
+
rescue NameError
|
|
77
|
+
# ignore — method may reference undefined constants at load time
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
super
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Per-subclass metadata stores (instance variables on the class object).
|
|
84
|
+
|
|
85
|
+
def directive_descriptions
|
|
86
|
+
@directive_descriptions ||= {}
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def directive_aliases
|
|
90
|
+
@directive_aliases ||= {}
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# ---- Category name derived from class name ----------------------------
|
|
94
|
+
# PM::CoreDirectives --> "Core"
|
|
95
|
+
# AIA::WebAndFileDirectives --> "Web and File"
|
|
96
|
+
|
|
97
|
+
def category_name
|
|
98
|
+
name.split('::').last
|
|
99
|
+
.sub(/Directives$/, '')
|
|
100
|
+
.gsub(/([a-z])([A-Z])/, '\1 \2')
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# ---- Singleton instance per subclass ----------------------------------
|
|
104
|
+
# Created by register_all, accessible for tests and state management.
|
|
105
|
+
|
|
106
|
+
def instance
|
|
107
|
+
@instance
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# ---- Dispatch block builder -------------------------------------------
|
|
111
|
+
# Override in subclasses to customize how directive methods are called.
|
|
112
|
+
# The default passes (ctx, *args) straight through — suitable for
|
|
113
|
+
# PM::CoreDirectives whose methods use the RenderContext.
|
|
114
|
+
|
|
115
|
+
def build_dispatch_block(inst, method_name)
|
|
116
|
+
proc { |ctx, *args| inst.send(method_name, ctx, *args) }
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# ---- PM registration --------------------------------------------------
|
|
120
|
+
# Creates one instance per subclass and registers every described
|
|
121
|
+
# method (plus its aliases) with PM.
|
|
122
|
+
|
|
123
|
+
def register_all
|
|
124
|
+
PM::Directive.directive_subclasses.each do |klass|
|
|
125
|
+
next if klass.directive_descriptions.empty?
|
|
126
|
+
|
|
127
|
+
klass.instance_variable_set(:@instance, klass.new)
|
|
128
|
+
inst = klass.instance
|
|
129
|
+
|
|
130
|
+
klass.directive_descriptions.each_key do |method_name|
|
|
131
|
+
aliases = klass.directive_aliases[method_name] || []
|
|
132
|
+
names = [method_name, *aliases]
|
|
133
|
+
|
|
134
|
+
# Remove previously registered names (idempotent re-init)
|
|
135
|
+
names.each { |n| PM.directives.delete(n.to_sym) }
|
|
136
|
+
|
|
137
|
+
block = klass.build_dispatch_block(inst, method_name)
|
|
138
|
+
PM.register(*names, &block)
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
data/lib/pm/directives.rb
CHANGED
|
@@ -25,37 +25,9 @@ module PM
|
|
|
25
25
|
@directives
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
-
# Clears all directives and re-registers
|
|
28
|
+
# Clears all directives and re-registers from PM::Directive subclasses.
|
|
29
29
|
def self.reset_directives!
|
|
30
30
|
@directives.clear
|
|
31
|
-
|
|
31
|
+
PM::Directive.register_all
|
|
32
32
|
end
|
|
33
|
-
|
|
34
|
-
# --- Built-in directives ---
|
|
35
|
-
|
|
36
|
-
def self.register_builtins
|
|
37
|
-
register(:include) do |ctx, path|
|
|
38
|
-
unless ctx.directory
|
|
39
|
-
raise 'include requires a file context (use PM.parse with a file path)'
|
|
40
|
-
end
|
|
41
|
-
full_path = File.expand_path(path, ctx.directory)
|
|
42
|
-
if ctx.included.include?(full_path)
|
|
43
|
-
raise "Circular include detected: #{full_path}"
|
|
44
|
-
end
|
|
45
|
-
child = PM.parse(full_path)
|
|
46
|
-
result = child.render_with(ctx.params, ctx.included, ctx.depth + 1)
|
|
47
|
-
|
|
48
|
-
ctx.metadata.includes << {
|
|
49
|
-
path: full_path,
|
|
50
|
-
depth: ctx.depth + 1,
|
|
51
|
-
metadata: child.metadata.to_h.reject { |k, _| k == :includes },
|
|
52
|
-
includes: child.metadata.includes
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
result
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
private_class_method :register_builtins
|
|
59
|
-
|
|
60
|
-
register_builtins
|
|
61
33
|
end
|
data/lib/pm/version.rb
CHANGED
data/lib/pm.rb
CHANGED
|
@@ -118,4 +118,9 @@ require_relative 'pm/version'
|
|
|
118
118
|
require_relative 'pm/metadata'
|
|
119
119
|
require_relative 'pm/parsed'
|
|
120
120
|
require_relative 'pm/directives'
|
|
121
|
+
require_relative 'pm/directive'
|
|
122
|
+
require_relative 'pm/core_directives'
|
|
121
123
|
require_relative 'pm/shell'
|
|
124
|
+
|
|
125
|
+
# Register built-in directives from PM::CoreDirectives
|
|
126
|
+
PM::Directive.register_all
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: prompt_manager
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0.
|
|
4
|
+
version: 1.0.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dewayne VanHoozer
|
|
@@ -128,6 +128,8 @@ files:
|
|
|
128
128
|
- docs/index.md
|
|
129
129
|
- lib/pm.rb
|
|
130
130
|
- lib/pm/configuration.rb
|
|
131
|
+
- lib/pm/core_directives.rb
|
|
132
|
+
- lib/pm/directive.rb
|
|
131
133
|
- lib/pm/directives.rb
|
|
132
134
|
- lib/pm/metadata.rb
|
|
133
135
|
- lib/pm/parsed.rb
|