markdown_composer 0.7.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 +7 -0
- data/CHANGELOG.md +23 -0
- data/LICENSE.txt +21 -0
- data/README.md +278 -0
- data/ROADMAP.md +80 -0
- data/docs/_md_composer_architecture.md +50 -0
- data/docs/_md_composer_cheatsheet.md +72 -0
- data/docs/_md_composer_concepts.md +64 -0
- data/docs/_md_composer_dev_guide.md +55 -0
- data/docs/_md_composer_getting_started.md +114 -0
- data/docs/_md_composer_readme.md +93 -0
- data/docs/_md_composer_user_guide.md +65 -0
- data/docs/ai/md_composer_ai_audit.md +35 -0
- data/docs/ai/md_composer_ai_canonical_docs.md +44 -0
- data/docs/ai/md_composer_ai_source_map.md +39 -0
- data/docs/compose/md_composer_compose_actions.md +338 -0
- data/docs/compose/md_composer_compose_anatomy.md +156 -0
- data/docs/compose/md_composer_compose_buffer.md +81 -0
- data/docs/compose/md_composer_compose_examples.md +31 -0
- data/docs/compose/md_composer_compose_include.md +136 -0
- data/docs/compose/md_composer_compose_select.md +198 -0
- data/docs/compose/md_composer_compose_sources.md +161 -0
- data/docs/compose/md_composer_compose_targets.md +194 -0
- data/docs/examples/md_composer_example_basic_compose.md +57 -0
- data/docs/examples/md_composer_example_buffer_target_actions.md +83 -0
- data/docs/examples/md_composer_example_fixtures.md +62 -0
- data/docs/examples/md_composer_example_html_output.md +50 -0
- data/docs/examples/md_composer_example_modify.md +77 -0
- data/docs/examples/md_composer_example_multi_row_compose.md +67 -0
- data/docs/examples/md_composer_example_ruby_plans.md +62 -0
- data/docs/examples/md_composer_example_structured_data.md +68 -0
- data/docs/examples/md_composer_example_transforms.md +68 -0
- data/docs/examples/md_composer_example_yaml_json_rows.md +56 -0
- data/docs/examples/md_composer_examples_readme.md +45 -0
- data/docs/examples/md_composer_runnable_examples.md +374 -0
- data/docs/examples/md_composer_source_ruby_dsl.md +88 -0
- data/docs/reference/md_composer_nested.md +170 -0
- data/docs/reference/md_composer_reference_api.md +71 -0
- data/docs/reference/md_composer_reference_capabilities.md +63 -0
- data/docs/reference/md_composer_reference_diagnostics.md +54 -0
- data/docs/reference/md_composer_reference_plan_schema.md +75 -0
- data/docs/reference/md_composer_reference_registries.md +63 -0
- data/docs/reference/md_composer_take.md +221 -0
- data/docs/reference/md_composer_unit_tokens.md +228 -0
- data/docs/reference/md_composer_where.md +227 -0
- data/docs/transform/md_composer_transform_anatomy.md +112 -0
- data/docs/transform/md_composer_transform_examples.md +30 -0
- data/docs/transform/md_composer_transform_modes.md +83 -0
- data/docs/transform/md_composer_transform_options.md +142 -0
- data/docs/transform/md_composer_transform_scope.md +97 -0
- data/docs/transform/md_composer_transform_transforms.md +99 -0
- data/examples/README.md +20 -0
- data/examples/advanced_composer.rb +207 -0
- data/examples/basic_compose.rb +24 -0
- data/examples/complex_composer.rb +235 -0
- data/examples/example_support.rb +18 -0
- data/examples/fixtures/current.md +179 -0
- data/examples/fixtures/faq.md +58 -0
- data/examples/fixtures/guide.md +62 -0
- data/examples/fixtures/site_intro.md +29 -0
- data/examples/fixtures/source.html +22 -0
- data/examples/html_input.rb +26 -0
- data/examples/output/advanced_composer.md +76 -0
- data/examples/output/basic_compose.md +25 -0
- data/examples/output/complex_composer.md +85 -0
- data/examples/output/html_input.md +4 -0
- data/examples/output/source_list_dsl.md +126 -0
- data/examples/output/standard_composer.md +46 -0
- data/examples/output/standard_sources_buffer.md +31 -0
- data/examples/output/yaml_plan.md +43 -0
- data/examples/plans/basic.yml +20 -0
- data/examples/source_list_dsl.rb +41 -0
- data/examples/standard_composer.rb +42 -0
- data/examples/standard_sources_buffer.rb +62 -0
- data/examples/yaml_plan.rb +17 -0
- data/lib/markdown_composer/capabilities.rb +223 -0
- data/lib/markdown_composer/composition_buffer.rb +378 -0
- data/lib/markdown_composer/data_path.rb +313 -0
- data/lib/markdown_composer/diagnostics.rb +63 -0
- data/lib/markdown_composer/document_index/html_parser.rb +84 -0
- data/lib/markdown_composer/document_index/markdown_parser.rb +338 -0
- data/lib/markdown_composer/document_index.rb +94 -0
- data/lib/markdown_composer/executor.rb +284 -0
- data/lib/markdown_composer/markdown_renderer.rb +105 -0
- data/lib/markdown_composer/plan.rb +436 -0
- data/lib/markdown_composer/plan_builder.rb +111 -0
- data/lib/markdown_composer/registries/action_entries.rb +26 -0
- data/lib/markdown_composer/registries/condition_entries.rb +58 -0
- data/lib/markdown_composer/registries/registry.rb +69 -0
- data/lib/markdown_composer/registries/source_entries.rb +18 -0
- data/lib/markdown_composer/registries/support_values.rb +23 -0
- data/lib/markdown_composer/registries/take_entries.rb +31 -0
- data/lib/markdown_composer/registries/take_registry.rb +18 -0
- data/lib/markdown_composer/registries/target_entries.rb +40 -0
- data/lib/markdown_composer/registries/unit_token_entries.rb +62 -0
- data/lib/markdown_composer/registries/where_registry.rb +84 -0
- data/lib/markdown_composer/registries.rb +46 -0
- data/lib/markdown_composer/result.rb +34 -0
- data/lib/markdown_composer/selection_resolver.rb +181 -0
- data/lib/markdown_composer/source.rb +57 -0
- data/lib/markdown_composer/source_list_builder.rb +47 -0
- data/lib/markdown_composer/take.rb +129 -0
- data/lib/markdown_composer/transform_options.rb +66 -0
- data/lib/markdown_composer/transform_runner/content_placement.rb +63 -0
- data/lib/markdown_composer/transform_runner/field_interpolator.rb +213 -0
- data/lib/markdown_composer/transform_runner/heading_numbering.rb +106 -0
- data/lib/markdown_composer/transform_runner/scope_resolver.rb +87 -0
- data/lib/markdown_composer/transform_runner.rb +264 -0
- data/lib/markdown_composer/transforms/default_entries.rb +31 -0
- data/lib/markdown_composer/transforms/registry.rb +11 -0
- data/lib/markdown_composer/validator.rb +378 -0
- data/lib/markdown_composer/value_object.rb +15 -0
- data/lib/markdown_composer/version.rb +5 -0
- data/lib/markdown_composer/where.rb +313 -0
- data/lib/markdown_composer.rb +114 -0
- metadata +260 -0
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "markdown_composer"
|
|
4
|
+
require_relative "example_support"
|
|
5
|
+
|
|
6
|
+
root = File.expand_path(__dir__)
|
|
7
|
+
|
|
8
|
+
# A comprehensive Ruby DSL example:
|
|
9
|
+
# - runtime sources: current, explicit, inherited
|
|
10
|
+
# - row sources: inline, previous, buffer
|
|
11
|
+
# - actions: set, append, prepend, insert_before, insert_after, insert_between,
|
|
12
|
+
# replace, copy, move, modify, remove_buffer_target, transform_buffer_target
|
|
13
|
+
# - transforms: scoped text replacement, insertion, dedupe, link rewrite, levels
|
|
14
|
+
plan = MarkdownComposer.plan do
|
|
15
|
+
output :markdown
|
|
16
|
+
|
|
17
|
+
from :current
|
|
18
|
+
select 'h2_section where title:equals("Feature Matrix")'
|
|
19
|
+
include "heading_title"
|
|
20
|
+
include "paragraph[first:1]"
|
|
21
|
+
include "link[first:1]"
|
|
22
|
+
include "table[first:1]"
|
|
23
|
+
include "heading_3_section[position:1,2] { heading_title; paragraph[first:1]; code_block; table }"
|
|
24
|
+
set
|
|
25
|
+
|
|
26
|
+
from({
|
|
27
|
+
type: "inline",
|
|
28
|
+
markdown: "### Advanced Preface\n\nThis section is prepended first, then moved into a better reading position."
|
|
29
|
+
})
|
|
30
|
+
select "h3_section"
|
|
31
|
+
include "all"
|
|
32
|
+
prepend
|
|
33
|
+
|
|
34
|
+
from({ type: "inherited", key: "site_intro" })
|
|
35
|
+
select 'h2_section where title:equals("Welcome")'
|
|
36
|
+
include "heading_title"
|
|
37
|
+
include "paragraph[first:1]"
|
|
38
|
+
append
|
|
39
|
+
|
|
40
|
+
transform_buffer_target(
|
|
41
|
+
'h2 where title:equals("Welcome")',
|
|
42
|
+
transforms: [
|
|
43
|
+
{
|
|
44
|
+
"transform" => "heading_levels",
|
|
45
|
+
"mode" => "demote",
|
|
46
|
+
"options" => {
|
|
47
|
+
"by" => 1
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
]
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
from({ type: "explicit", key: "guide" })
|
|
54
|
+
select 'h2_section where title:equals("Validate")'
|
|
55
|
+
include "all"
|
|
56
|
+
append
|
|
57
|
+
|
|
58
|
+
from :previous
|
|
59
|
+
select 'h2_section where title:equals("Publish")'
|
|
60
|
+
include "all"
|
|
61
|
+
append
|
|
62
|
+
|
|
63
|
+
transform_buffer_target(
|
|
64
|
+
'h2 where title:equals("Validate")',
|
|
65
|
+
transforms: [
|
|
66
|
+
{
|
|
67
|
+
"transform" => "heading_levels",
|
|
68
|
+
"mode" => "demote",
|
|
69
|
+
"options" => {
|
|
70
|
+
"by" => 1
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"scope" => "heading_3",
|
|
75
|
+
"transform" => "replace_text",
|
|
76
|
+
"mode" => "word",
|
|
77
|
+
"options" => {
|
|
78
|
+
"from" => "Validate",
|
|
79
|
+
"to" => "Validation Snapshot"
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
]
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
transform_buffer_target(
|
|
86
|
+
'h2 where title:equals("Publish")',
|
|
87
|
+
transforms: [
|
|
88
|
+
{
|
|
89
|
+
"transform" => "heading_levels",
|
|
90
|
+
"mode" => "demote",
|
|
91
|
+
"options" => {
|
|
92
|
+
"by" => 1
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"scope" => "heading_3",
|
|
97
|
+
"transform" => "replace_text",
|
|
98
|
+
"mode" => "word",
|
|
99
|
+
"options" => {
|
|
100
|
+
"from" => "Publish",
|
|
101
|
+
"to" => "Release Checklist"
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
]
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
from({
|
|
108
|
+
type: "inline",
|
|
109
|
+
markdown: "### Release Note\n\nThis inserted note shows small inline Markdown inside a larger plan."
|
|
110
|
+
})
|
|
111
|
+
select "h3_section"
|
|
112
|
+
include "all"
|
|
113
|
+
insert_before 'h3_section where title:equals("Automation")'
|
|
114
|
+
|
|
115
|
+
from :current
|
|
116
|
+
select 'h3_section where title:equals("Reporting")'
|
|
117
|
+
include "heading_title"
|
|
118
|
+
include "paragraph[first:1]"
|
|
119
|
+
replace 'h3_section where title:equals("Automation")'
|
|
120
|
+
|
|
121
|
+
from({
|
|
122
|
+
type: "inline",
|
|
123
|
+
markdown: "### Support Note\n\nThis note is inserted between two existing composed sections."
|
|
124
|
+
})
|
|
125
|
+
select "h3_section"
|
|
126
|
+
include "all"
|
|
127
|
+
insert_between 'Between h3_section where title:equals("Release Note") and h3_section where title:equals("Reporting")'
|
|
128
|
+
|
|
129
|
+
from :current
|
|
130
|
+
select 'h3_section where title:equals("Monitoring")'
|
|
131
|
+
include "heading_title"
|
|
132
|
+
include "paragraph[first:1]"
|
|
133
|
+
insert_after 'h3_section where title:equals("Reporting")'
|
|
134
|
+
|
|
135
|
+
from :buffer
|
|
136
|
+
select 'h3_section where title:equals("Release Note")'
|
|
137
|
+
include "all"
|
|
138
|
+
copy "end"
|
|
139
|
+
|
|
140
|
+
from :buffer
|
|
141
|
+
select 'h3_section where title:equals("Dashboard")'
|
|
142
|
+
include "all"
|
|
143
|
+
modify "in_place", transforms: [
|
|
144
|
+
{
|
|
145
|
+
"scope" => "heading_3, paragraph",
|
|
146
|
+
"transform" => "replace_text",
|
|
147
|
+
"mode" => "literal",
|
|
148
|
+
"options" => {
|
|
149
|
+
"from" => "Dashboard",
|
|
150
|
+
"to" => "Dashboard Snapshot"
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
"scope" => "paragraph[first:1]",
|
|
155
|
+
"transform" => "insert_after",
|
|
156
|
+
"mode" => "insert",
|
|
157
|
+
"options" => {
|
|
158
|
+
"content" => "This paragraph was inserted by the modify action before final output transforms run.",
|
|
159
|
+
"as" => "paragraph"
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
]
|
|
163
|
+
|
|
164
|
+
from :buffer
|
|
165
|
+
select 'h3_section where title:equals("Advanced Preface")'
|
|
166
|
+
include "all"
|
|
167
|
+
move 'after h2_section where title:equals("Feature Matrix")'
|
|
168
|
+
|
|
169
|
+
from({
|
|
170
|
+
type: "inline",
|
|
171
|
+
markdown: "### Cleanup Marker\n\nThis temporary marker exists only to demonstrate remove_buffer_target."
|
|
172
|
+
})
|
|
173
|
+
select "h3_section"
|
|
174
|
+
include "all"
|
|
175
|
+
append
|
|
176
|
+
|
|
177
|
+
from :buffer
|
|
178
|
+
select 'h3_section where title:equals("Cleanup Marker")'
|
|
179
|
+
remove_buffer_target
|
|
180
|
+
|
|
181
|
+
transform "output", "dedupe", "normalised_text"
|
|
182
|
+
|
|
183
|
+
transform "heading_2", "replace_text", "literal", {
|
|
184
|
+
"from" => "Feature Matrix",
|
|
185
|
+
"to" => "Advanced Composer Walkthrough"
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
transform "link[first:1]", "links", "rewrite_url", {
|
|
189
|
+
"from" => "/features/dashboard",
|
|
190
|
+
"to" => "https://example.test/features/dashboard"
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
transform "heading", "heading_levels", "promote", {
|
|
194
|
+
"by" => 1
|
|
195
|
+
}
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
result = MarkdownComposer.compose(
|
|
199
|
+
sources: MarkdownComposer.source_list do
|
|
200
|
+
current File.read(File.join(root, "fixtures/current.md"))
|
|
201
|
+
explicit :guide, File.read(File.join(root, "fixtures/guide.md"))
|
|
202
|
+
inherited :site_intro, File.read(File.join(root, "fixtures/site_intro.md"))
|
|
203
|
+
end,
|
|
204
|
+
plan: plan
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
ExampleSupport.write_output(root, "advanced_composer", result)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "markdown_composer"
|
|
4
|
+
require_relative "example_support"
|
|
5
|
+
|
|
6
|
+
root = File.expand_path(__dir__)
|
|
7
|
+
current_markdown = File.read(File.join(root, "fixtures/current.md"))
|
|
8
|
+
|
|
9
|
+
# Select the first H2 section from the current source and emit it unchanged.
|
|
10
|
+
plan = MarkdownComposer.plan do
|
|
11
|
+
from :current
|
|
12
|
+
select "h2_section[first:1]"
|
|
13
|
+
include "all"
|
|
14
|
+
set
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
result = MarkdownComposer.compose(
|
|
18
|
+
sources: [
|
|
19
|
+
{ key: "current", type: "current", markdown: current_markdown }
|
|
20
|
+
],
|
|
21
|
+
plan: plan
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
ExampleSupport.write_output(root, "basic_compose", result)
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "markdown_composer"
|
|
4
|
+
require_relative "example_support"
|
|
5
|
+
|
|
6
|
+
root = File.expand_path(__dir__)
|
|
7
|
+
|
|
8
|
+
# The complex example is intentionally broad but still readable.
|
|
9
|
+
# Sources live in the compose call at the bottom, so the plan stays focused.
|
|
10
|
+
plan = MarkdownComposer.plan do
|
|
11
|
+
output :markdown
|
|
12
|
+
|
|
13
|
+
# Start with a focused current-source feature section.
|
|
14
|
+
from :current
|
|
15
|
+
select 'h2_section where title:equals("Feature Matrix")'
|
|
16
|
+
include "heading_title"
|
|
17
|
+
include "paragraph[first:1]"
|
|
18
|
+
include "link[first:1]"
|
|
19
|
+
include "table[first:1]"
|
|
20
|
+
include "heading_3_section[position:1,2] { heading_title; paragraph[first:1]; code_block; table }"
|
|
21
|
+
set
|
|
22
|
+
|
|
23
|
+
# Prepend an inline section, then move it later to demonstrate both actions.
|
|
24
|
+
from({
|
|
25
|
+
type: "inline",
|
|
26
|
+
markdown: "### Complex Preface\n\nThis section is prepended first, then moved into the final reading order."
|
|
27
|
+
})
|
|
28
|
+
select "h3_section"
|
|
29
|
+
include "all"
|
|
30
|
+
prepend
|
|
31
|
+
|
|
32
|
+
# Append inherited site copy and demote it while it is still in the buffer.
|
|
33
|
+
from({ type: "inherited", key: "site_intro" })
|
|
34
|
+
select 'h2_section where title:equals("Welcome")'
|
|
35
|
+
include "heading_title"
|
|
36
|
+
include "paragraph[first:1]"
|
|
37
|
+
append
|
|
38
|
+
|
|
39
|
+
transform_buffer_target(
|
|
40
|
+
'h2 where title:equals("Welcome")',
|
|
41
|
+
transforms: [
|
|
42
|
+
{
|
|
43
|
+
"transform" => "heading_levels",
|
|
44
|
+
"mode" => "demote",
|
|
45
|
+
"options" => { "by" => 1 }
|
|
46
|
+
}
|
|
47
|
+
]
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
# Append explicit guide content, then use previous to keep working in guide.
|
|
51
|
+
from({ type: "explicit", key: "guide" })
|
|
52
|
+
select 'h2_section where title:equals("Validate")'
|
|
53
|
+
include "all"
|
|
54
|
+
append
|
|
55
|
+
|
|
56
|
+
from :previous
|
|
57
|
+
select 'h2_section where title:equals("Publish")'
|
|
58
|
+
include "all"
|
|
59
|
+
append
|
|
60
|
+
|
|
61
|
+
transform_buffer_target(
|
|
62
|
+
'h2 where title:equals("Validate")',
|
|
63
|
+
transforms: [
|
|
64
|
+
{
|
|
65
|
+
"transform" => "heading_levels",
|
|
66
|
+
"mode" => "demote",
|
|
67
|
+
"options" => { "by" => 1 }
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"scope" => "heading_3",
|
|
71
|
+
"transform" => "replace_text",
|
|
72
|
+
"mode" => "word",
|
|
73
|
+
"options" => { "from" => "Validate", "to" => "Validation Snapshot" }
|
|
74
|
+
}
|
|
75
|
+
]
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
transform_buffer_target(
|
|
79
|
+
'h2 where title:equals("Publish")',
|
|
80
|
+
transforms: [
|
|
81
|
+
{
|
|
82
|
+
"transform" => "heading_levels",
|
|
83
|
+
"mode" => "demote",
|
|
84
|
+
"options" => { "by" => 1 }
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"scope" => "heading_3",
|
|
88
|
+
"transform" => "replace_text",
|
|
89
|
+
"mode" => "word",
|
|
90
|
+
"options" => { "from" => "Publish", "to" => "Release Checklist" }
|
|
91
|
+
}
|
|
92
|
+
]
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
# Add an explicit FAQ section and keep only the useful summary pieces.
|
|
96
|
+
from({ type: "explicit", key: "faq" })
|
|
97
|
+
select 'h2_section where title:equals("What source types exist?")'
|
|
98
|
+
include "heading_title"
|
|
99
|
+
include "paragraph[first:1]"
|
|
100
|
+
append
|
|
101
|
+
|
|
102
|
+
transform_buffer_target(
|
|
103
|
+
'h2 where title:equals("What source types exist?")',
|
|
104
|
+
transforms: [
|
|
105
|
+
{
|
|
106
|
+
"transform" => "heading_levels",
|
|
107
|
+
"mode" => "demote",
|
|
108
|
+
"options" => { "by" => 1 }
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"scope" => "heading_3",
|
|
112
|
+
"transform" => "replace_text",
|
|
113
|
+
"mode" => "literal",
|
|
114
|
+
"options" => {
|
|
115
|
+
"from" => "What source types exist?",
|
|
116
|
+
"to" => "Source Type Notes"
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
]
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
# Use inline source content with insert_before and insert_between.
|
|
123
|
+
from({
|
|
124
|
+
type: "inline",
|
|
125
|
+
markdown: "### Release Note\n\nThis inserted note shows small inline Markdown inside a larger plan."
|
|
126
|
+
})
|
|
127
|
+
select "h3_section"
|
|
128
|
+
include "all"
|
|
129
|
+
insert_before 'h3_section where title:equals("Automation")'
|
|
130
|
+
|
|
131
|
+
from({
|
|
132
|
+
type: "inline",
|
|
133
|
+
markdown: "### Support Note\n\nThis note is inserted between two existing composed sections."
|
|
134
|
+
})
|
|
135
|
+
select "h3_section"
|
|
136
|
+
include "all"
|
|
137
|
+
insert_between 'Between h3_section where title:equals("Release Note") and h3_section where title:equals("Automation")'
|
|
138
|
+
|
|
139
|
+
# Replace Automation with Reporting, then insert Monitoring after Reporting.
|
|
140
|
+
from :current
|
|
141
|
+
select 'h3_section where title:equals("Reporting")'
|
|
142
|
+
include "heading_title"
|
|
143
|
+
include "paragraph[first:1]"
|
|
144
|
+
replace 'h3_section where title:equals("Automation")'
|
|
145
|
+
|
|
146
|
+
from :current
|
|
147
|
+
select 'h3_section where title:equals("Monitoring")'
|
|
148
|
+
include "heading_title"
|
|
149
|
+
include "paragraph[first:1]"
|
|
150
|
+
insert_after 'h3_section where title:equals("Reporting")'
|
|
151
|
+
|
|
152
|
+
# Copy a section, then let final dedupe remove the duplicate content.
|
|
153
|
+
from :buffer
|
|
154
|
+
select 'h3_section where title:equals("Release Note")'
|
|
155
|
+
include "all"
|
|
156
|
+
copy "end"
|
|
157
|
+
|
|
158
|
+
# Modify an existing buffer section in place with two nested transforms.
|
|
159
|
+
from :buffer
|
|
160
|
+
select 'h3_section where title:equals("Dashboard")'
|
|
161
|
+
include "all"
|
|
162
|
+
modify "in_place", transforms: [
|
|
163
|
+
{
|
|
164
|
+
"scope" => "heading_3, paragraph",
|
|
165
|
+
"transform" => "replace_text",
|
|
166
|
+
"mode" => "literal",
|
|
167
|
+
"options" => {
|
|
168
|
+
"from" => "Dashboard",
|
|
169
|
+
"to" => "Dashboard Snapshot"
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
"scope" => "paragraph[first:1]",
|
|
174
|
+
"transform" => "insert_after",
|
|
175
|
+
"mode" => "insert",
|
|
176
|
+
"options" => {
|
|
177
|
+
"content" => "This paragraph was inserted by the modify action before final output transforms run.",
|
|
178
|
+
"as" => "paragraph"
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
]
|
|
182
|
+
|
|
183
|
+
# Move the preface into the document body.
|
|
184
|
+
from :buffer
|
|
185
|
+
select 'h3_section where title:equals("Complex Preface")'
|
|
186
|
+
include "all"
|
|
187
|
+
move 'after h2_section where title:equals("Feature Matrix")'
|
|
188
|
+
|
|
189
|
+
# Add and remove temporary content to demonstrate remove_buffer_target.
|
|
190
|
+
from({
|
|
191
|
+
type: "inline",
|
|
192
|
+
markdown: "### Cleanup Marker\n\nThis temporary marker exists only to demonstrate remove_buffer_target."
|
|
193
|
+
})
|
|
194
|
+
select "h3_section"
|
|
195
|
+
include "all"
|
|
196
|
+
append
|
|
197
|
+
|
|
198
|
+
from :buffer
|
|
199
|
+
select 'h3_section where title:equals("Cleanup Marker")'
|
|
200
|
+
remove_buffer_target
|
|
201
|
+
|
|
202
|
+
# Final output transforms shape the composed Markdown.
|
|
203
|
+
transform "output", "dedupe", "normalised_text"
|
|
204
|
+
|
|
205
|
+
transform "heading_2", "replace_text", "literal", {
|
|
206
|
+
"from" => "Feature Matrix",
|
|
207
|
+
"to" => "Complex Composer Walkthrough"
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
transform "link[first:1]", "links", "rewrite_url", {
|
|
211
|
+
"from" => "/features/dashboard",
|
|
212
|
+
"to" => "https://example.test/features/dashboard"
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
transform "heading", "heading_levels", "promote", {
|
|
216
|
+
"by" => 1
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
transform "paragraph[first:1]", "insert_after", "insert", {
|
|
220
|
+
"content" => "\n## Summary\n\nThis summary was inserted by a final transform after composition actions completed.",
|
|
221
|
+
"as" => "paragraph"
|
|
222
|
+
}
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
result = MarkdownComposer.compose(
|
|
226
|
+
sources: MarkdownComposer.source_list do
|
|
227
|
+
current File.read(File.join(root, "fixtures/current.md"))
|
|
228
|
+
explicit :guide, File.read(File.join(root, "fixtures/guide.md"))
|
|
229
|
+
explicit :faq, File.read(File.join(root, "fixtures/faq.md"))
|
|
230
|
+
inherited :site_intro, File.read(File.join(root, "fixtures/site_intro.md"))
|
|
231
|
+
end,
|
|
232
|
+
plan: plan
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
ExampleSupport.write_output(root, "complex_composer", result)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ExampleSupport
|
|
4
|
+
module_function
|
|
5
|
+
|
|
6
|
+
# Keep example scripts small: fail loudly on Composer errors and write
|
|
7
|
+
# the generated Markdown beside the runnable examples.
|
|
8
|
+
def write_output(root, name, result)
|
|
9
|
+
abort result.errors.map(&:message).join("\n") unless result.success?
|
|
10
|
+
|
|
11
|
+
output_dir = File.join(root, "output")
|
|
12
|
+
Dir.mkdir(output_dir) unless Dir.exist?(output_dir)
|
|
13
|
+
path = File.join(output_dir, "#{name}.md")
|
|
14
|
+
File.write(path, result.markdown)
|
|
15
|
+
puts "Wrote #{path}"
|
|
16
|
+
puts result.markdown
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# Product Knowledge Base
|
|
2
|
+
|
|
3
|
+
Welcome to the product knowledge base. This file is the main `current` source for runnable examples.
|
|
4
|
+
|
|
5
|
+
It intentionally contains several Markdown structures so you can experiment with selectors such as `h2_section`, `h3_section`, `paragraph[first:2]`, `table`, `code_block`, `mermaid`, and `math_block`.
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
This overview section is the normal starting point for Markdown Composer examples.
|
|
10
|
+
|
|
11
|
+
It includes [a product link](/features/dashboard), inline code `composer.preview`, and a second paragraph so include rules can choose content.
|
|
12
|
+
|
|
13
|
+
### What Composer Should See
|
|
14
|
+
|
|
15
|
+
Composer should index nested sections under Overview and keep selected content in source order.
|
|
16
|
+
|
|
17
|
+
- The first bullet links to [getting started](/docs/getting-started).
|
|
18
|
+
- The second bullet mentions `source_list`.
|
|
19
|
+
- The third bullet is plain text for list-item selection.
|
|
20
|
+
|
|
21
|
+
#### Selector Notes
|
|
22
|
+
|
|
23
|
+
Use `h2_section[first:1]` to select the whole Overview section, including this H4.
|
|
24
|
+
|
|
25
|
+
Use `paragraph[first:1]` inside an include rule to keep only the first paragraph.
|
|
26
|
+
|
|
27
|
+
### What Composer Should Ignore
|
|
28
|
+
|
|
29
|
+
This H3 gives you a second nested section to filter by title or position.
|
|
30
|
+
|
|
31
|
+
> Blockquotes are useful for testing paragraph and block-level extraction.
|
|
32
|
+
|
|
33
|
+
## Feature Matrix
|
|
34
|
+
|
|
35
|
+
The table below is intended for table selection and table-row experiments.
|
|
36
|
+
|
|
37
|
+
| Feature | Status | Owner | Link |
|
|
38
|
+
| --- | --- | --- | --- |
|
|
39
|
+
| Dashboard | ready | Product | [Open](/features/dashboard) |
|
|
40
|
+
| Automation | beta | Platform | [Open](/features/automation) |
|
|
41
|
+
| Reporting | draft | Data | [Open](/features/reporting) |
|
|
42
|
+
|
|
43
|
+
### Dashboard
|
|
44
|
+
|
|
45
|
+
Dashboard cards should keep this heading and paragraph when selected.
|
|
46
|
+
|
|
47
|
+
| Card | Visible |
|
|
48
|
+
| --- | --- |
|
|
49
|
+
| Summary | yes |
|
|
50
|
+
| Activity | yes |
|
|
51
|
+
|
|
52
|
+
```ruby
|
|
53
|
+
class DashboardCard
|
|
54
|
+
def visible?
|
|
55
|
+
status == "ready"
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
#### Dashboard Deep Dive
|
|
61
|
+
|
|
62
|
+
This H4 is nested under Dashboard and can be used to test deeper section boundaries.
|
|
63
|
+
|
|
64
|
+
### Automation
|
|
65
|
+
|
|
66
|
+
Automation has two paragraphs so examples can select first or last content.
|
|
67
|
+
|
|
68
|
+
The second paragraph mentions API triggers, webhooks, retries, and scheduled jobs.
|
|
69
|
+
|
|
70
|
+
| Trigger | Retry |
|
|
71
|
+
| --- | --- |
|
|
72
|
+
| webhook | yes |
|
|
73
|
+
| schedule | yes |
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
bundle exec ruby automation_rule.rb
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
```json
|
|
80
|
+
{
|
|
81
|
+
"feature": "automation",
|
|
82
|
+
"status": "beta",
|
|
83
|
+
"retries": 3
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Reporting
|
|
88
|
+
|
|
89
|
+
Reporting is marked draft so examples can filter it out by title or selected position.
|
|
90
|
+
|
|
91
|
+
## Operations
|
|
92
|
+
|
|
93
|
+
Operations content gives the examples runbook-style Markdown.
|
|
94
|
+
|
|
95
|
+
### Deployment
|
|
96
|
+
|
|
97
|
+
Deployment notes keep release steps short, repeatable, and easy to audit.
|
|
98
|
+
|
|
99
|
+
1. Build the package.
|
|
100
|
+
2. Run the test suite.
|
|
101
|
+
3. Publish the generated gem.
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
bundle exec rake test
|
|
105
|
+
gem build markdown_composer.gemspec
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
#### Rollback
|
|
109
|
+
|
|
110
|
+
Rollback should happen only after the published package or integration has been verified.
|
|
111
|
+
|
|
112
|
+
### Monitoring
|
|
113
|
+
|
|
114
|
+
Monitor compose success rate, validation errors, and unexpected empty selections.
|
|
115
|
+
|
|
116
|
+
## Diagrams And Math
|
|
117
|
+
|
|
118
|
+
This section includes Mermaid and math examples.
|
|
119
|
+
|
|
120
|
+
### Composition Flow
|
|
121
|
+
|
|
122
|
+
The composition flow diagram shows the order used by the examples.
|
|
123
|
+
|
|
124
|
+
```mermaid
|
|
125
|
+
flowchart LR
|
|
126
|
+
Source --> Select
|
|
127
|
+
Select --> Include
|
|
128
|
+
Include --> Action
|
|
129
|
+
Action --> Buffer
|
|
130
|
+
Buffer --> Transform
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Score Formula
|
|
134
|
+
|
|
135
|
+
Inline math can appear in prose as `$score = selected / total$`.
|
|
136
|
+
|
|
137
|
+
Display math can appear as its own block:
|
|
138
|
+
|
|
139
|
+
```math
|
|
140
|
+
precision = true_positive / (true_positive + false_positive)
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
#### Formula Notes
|
|
144
|
+
|
|
145
|
+
Math blocks and inline math are useful for testing token preservation.
|
|
146
|
+
|
|
147
|
+
## Draft Notes
|
|
148
|
+
|
|
149
|
+
This section is deliberately named Draft so examples can remove or filter it.
|
|
150
|
+
|
|
151
|
+
### Cleanup Target
|
|
152
|
+
|
|
153
|
+
Use this nested section to test `where title:contains("Cleanup")` or `h3_section[last:1]`.
|
|
154
|
+
|
|
155
|
+
<!-- composer: removable draft comment -->
|
|
156
|
+
|
|
157
|
+
## Appendix
|
|
158
|
+
|
|
159
|
+
The appendix gives the main fixture a fifth H2 section.
|
|
160
|
+
|
|
161
|
+
### Source Types
|
|
162
|
+
|
|
163
|
+
Source type rows describe where content comes from during composition.
|
|
164
|
+
|
|
165
|
+
| Source | Meaning |
|
|
166
|
+
| --- | --- |
|
|
167
|
+
| current | Main runtime source |
|
|
168
|
+
| explicit | Keyed runtime source |
|
|
169
|
+
| inherited | Host-resolved runtime source |
|
|
170
|
+
| inline | Plan-row source content |
|
|
171
|
+
|
|
172
|
+
### Copy-Paste Checklist
|
|
173
|
+
|
|
174
|
+
Use this checklist when adapting the examples to a local script.
|
|
175
|
+
|
|
176
|
+
- Read files with `File.read`.
|
|
177
|
+
- Build sources with `MarkdownComposer.source_list`.
|
|
178
|
+
- Build plans with `MarkdownComposer.plan` or `MarkdownComposer.parse_yaml`.
|
|
179
|
+
- Run `MarkdownComposer.compose`.
|