releasehx 0.1.1 → 0.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.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/README.adoc +366 -331
  3. data/build/docs/_config.yml +18 -1
  4. data/build/docs/_release_index.adoc +10 -0
  5. data/build/docs/config-reference.adoc +203 -16
  6. data/build/docs/config-reference.json +60 -10
  7. data/build/docs/index.adoc +316 -59
  8. data/build/docs/landing.adoc +11 -4
  9. data/build/docs/manpage.adoc +2 -2
  10. data/build/docs/release-procedure.adoc +365 -0
  11. data/build/docs/release-procedure.html +87 -0
  12. data/build/docs/releasehx.1 +17 -5
  13. data/build/docs/releases.adoc +28 -0
  14. data/build/docs/sample-config.adoc +2 -0
  15. data/build/docs/sample-config.yml +16 -9
  16. data/lib/releasehx/cli.rb +21 -9
  17. data/lib/releasehx/configuration.rb +0 -1
  18. data/lib/releasehx/generated.rb +1 -1
  19. data/lib/releasehx/mcp/assets/agent-config-guide.md +1 -1
  20. data/lib/releasehx/mcp/assets/config-def.yml +126 -8
  21. data/lib/releasehx/mcp/assets/config-reference.adoc +203 -16
  22. data/lib/releasehx/mcp/assets/config-reference.json +60 -10
  23. data/lib/releasehx/mcp/assets/sample-config.yml +16 -9
  24. data/lib/releasehx/mcp/server.rb +0 -1
  25. data/lib/releasehx/ops/enrich_ops.rb +161 -55
  26. data/lib/releasehx/ops/template_ops.rb +1 -1
  27. data/lib/releasehx/rhyml/adapter.rb +13 -9
  28. data/lib/releasehx/rhyml/mappings/github.yaml +3 -1
  29. data/lib/releasehx/rhyml/templates/bootstrap-overrides.css +15 -0
  30. data/lib/releasehx/rhyml/templates/changelog.adoc.liquid +2 -0
  31. data/lib/releasehx/rhyml/templates/changelog.html.liquid +6 -4
  32. data/lib/releasehx/rhyml/templates/changelog.md.liquid +1 -0
  33. data/lib/releasehx/rhyml/templates/embedded.css.liquid +263 -0
  34. data/lib/releasehx/rhyml/templates/entry.adoc.liquid +4 -7
  35. data/lib/releasehx/rhyml/templates/entry.html.liquid +21 -20
  36. data/lib/releasehx/rhyml/templates/entry.md.liquid +14 -21
  37. data/lib/releasehx/rhyml/templates/head-parser.liquid +6 -2
  38. data/lib/releasehx/rhyml/templates/header.liquid +13 -4
  39. data/lib/releasehx/rhyml/templates/history.html.liquid +152 -33
  40. data/lib/releasehx/rhyml/templates/metadata-entry.adoc.liquid +83 -49
  41. data/lib/releasehx/rhyml/templates/metadata-entry.html.liquid +60 -1
  42. data/lib/releasehx/rhyml/templates/metadata-entry.md.liquid +65 -113
  43. data/lib/releasehx/rhyml/templates/metadata-note.adoc.liquid +83 -49
  44. data/lib/releasehx/rhyml/templates/metadata-note.html.liquid +59 -22
  45. data/lib/releasehx/rhyml/templates/metadata-note.md.liquid +68 -23
  46. data/lib/releasehx/rhyml/templates/note.adoc.liquid +2 -40
  47. data/lib/releasehx/rhyml/templates/note.html.liquid +25 -19
  48. data/lib/releasehx/rhyml/templates/note.md.liquid +43 -29
  49. data/lib/releasehx/rhyml/templates/parts-listing.liquid +6 -6
  50. data/lib/releasehx/rhyml/templates/release-notes.adoc.liquid +2 -0
  51. data/lib/releasehx/rhyml/templates/release-notes.html.liquid +6 -4
  52. data/lib/releasehx/rhyml/templates/release-notes.md.liquid +1 -0
  53. data/lib/releasehx/rhyml/templates/release.adoc.liquid +2 -0
  54. data/lib/releasehx/rhyml/templates/release.md.liquid +8 -7
  55. data/lib/releasehx/rhyml/templates/rhyml-change.yaml.liquid +36 -35
  56. data/lib/releasehx/rhyml/templates/wrapper.html.liquid +103 -0
  57. data/lib/releasehx/sgyml/helpers.rb +0 -2
  58. data/lib/releasehx/transforms/adf_to_markdown.rb +1 -1
  59. data/lib/releasehx/version.rb +0 -2
  60. data/lib/releasehx.rb +2 -2
  61. data/specs/data/config-def.yml +126 -8
  62. metadata +50 -26
  63. data/build/docs/Gemfile.lock +0 -95
  64. data/build/docs/schemagraphy_readme.html +0 -0
  65. data/build/docs/sourcerer_readme.html +0 -46
  66. data/lib/schemagraphy/attribute_resolver.rb +0 -48
  67. data/lib/schemagraphy/cfgyml/definition.rb +0 -90
  68. data/lib/schemagraphy/cfgyml/doc_builder.rb +0 -52
  69. data/lib/schemagraphy/cfgyml/path_reference.rb +0 -24
  70. data/lib/schemagraphy/data_query/json_pointer.rb +0 -42
  71. data/lib/schemagraphy/loader.rb +0 -59
  72. data/lib/schemagraphy/regexp_utils.rb +0 -215
  73. data/lib/schemagraphy/safe_expression.rb +0 -189
  74. data/lib/schemagraphy/schema_utils.rb +0 -124
  75. data/lib/schemagraphy/tag_utils.rb +0 -32
  76. data/lib/schemagraphy/templating.rb +0 -104
  77. data/lib/schemagraphy.rb +0 -17
  78. data/lib/sourcerer/builder.rb +0 -120
  79. data/lib/sourcerer/jekyll/bootstrapper.rb +0 -78
  80. data/lib/sourcerer/jekyll/liquid/file_system.rb +0 -74
  81. data/lib/sourcerer/jekyll/liquid/filters.rb +0 -215
  82. data/lib/sourcerer/jekyll/liquid/tags.rb +0 -44
  83. data/lib/sourcerer/jekyll/monkeypatches.rb +0 -73
  84. data/lib/sourcerer/jekyll.rb +0 -26
  85. data/lib/sourcerer/plaintext_converter.rb +0 -75
  86. data/lib/sourcerer/templating.rb +0 -190
  87. data/lib/sourcerer.rb +0 -322
@@ -129,7 +129,8 @@
129
129
  "note": {
130
130
  "path": "conversions.note",
131
131
  "desc": "The source of the release notes content.\nMust be `issue_body`, `custom_field`, or `commit_message`.\n\nDefaults to `issue_body` for GitHub and GitLab, but to `custom_field` for Jira.\n",
132
- "type": "String"
132
+ "type": "String",
133
+ "default": "issue_body"
133
134
  },
134
135
  "note_custom_field": {
135
136
  "path": "conversions.note_custom_field",
@@ -140,7 +141,7 @@
140
141
  "path": "conversions.note_pattern",
141
142
  "desc": "The Regular Expressions pattern to match in the body of an issue or commit message, after which all content is considered the release `note` matter.\n\nDefaults to a Markdown or AsciiDoc header or HTML comment with the case-insensitive string `release note` in it.\n\nUses Capture group `note` in the Regular Expression to establish the entire note content.\n\nSee the `conversions.head_pattern` property for details on extracting a heading (`head` in RHYML) from the `note` content.\n",
142
143
  "type": "RegExp",
143
- "default": "/^((#|=)+ (Draft )?Release Note.*)|(<!-- (draft )?release note -->)\\n(?<note>\\w(.|\\n)+)/gmi"
144
+ "default": "/^(((#|=)+ (Draft )?Release Note.*?)|(<!-- (draft )?release note -->))\\n+(?<note>(.| )+)/gmi"
144
145
  },
145
146
  "head_pattern": {
146
147
  "path": "conversions.head_pattern",
@@ -154,10 +155,23 @@
154
155
  "type": "String",
155
156
  "default": "markdown"
156
157
  },
157
- "engine": {
158
- "path": "conversions.engine",
159
- "desc": "The markup converter to use for the issues.\nDefaults to `asciidoctor` for AsciiDoc and `redcarpet` for Markdown.\nOptions include `asciidoctor`, `redcarpet`, `commonmarker`, `kramdown`, or `pandoc`.\n",
160
- "type": "String"
158
+ "engines": {
159
+ "path": "conversions.engines",
160
+ "desc": "Specifies the conversion engines to use when enriching to various output formats.\n\nThese settings determine which tool processes the conversion from draft formats to rich output.\nIntelligent defaults are applied based on source format when engines are not explicitly configured.\n",
161
+ "properties": {
162
+ "html": {
163
+ "path": "conversions.engines.html",
164
+ "desc": "Engine for converting to HTML format.\n\nValid engines:\n\n[horizontal]\n`asciidoctor-html5`:: Standard Asciidoctor HTML5 backend (nested div structure)\n`asciidoctor-html5s`:: Semantic HTML5 backend (cleaner section-based markup)\n`kramdown`:: Kramdown Markdown converter (for Markdown sources)\n`pandoc`:: Universal document converter (if available)\n\nWhen not specified, intelligent defaults apply:\n\n- AsciiDoc → HTML: `asciidoctor-html5s` (semantic HTML5)\n- Markdown → HTML: `kramdown`\n- RHYML → HTML: Liquid templates (no engine needed)\n",
165
+ "docs": "The html5s backend produces cleaner, more semantic HTML with `<section>` elements\ninstead of nested `<div class=\"sect1\"><div class=\"sectionbody\">` structures.\n\nThis is particularly useful when you want to apply custom CSS or when generating\nHTML that will be further processed or embedded in other systems.\n\nFor backwards compatibility or when you need the traditional Asciidoctor structure,\nuse `asciidoctor-html5`.\n",
166
+ "type": "String"
167
+ },
168
+ "pdf": {
169
+ "path": "conversions.engines.pdf",
170
+ "desc": "Engine for converting to PDF format.\n\nValid engines:\n\n[horizontal]\n`asciidoctor-pdf`:: Asciidoctor PDF converter (default for AsciiDoc)\n`asciidoctor-web-pdf`:: Web-based PDF converter using headless Chrome\n`pandoc`:: Universal document converter (if available)\n\nWhen not specified, intelligent defaults apply:\n\n- AsciiDoc → PDF: `asciidoctor-pdf`\n- Markdown → PDF: `pandoc` (if available)\n",
171
+ "docs": "The default `asciidoctor-pdf` engine provides excellent typography and layout\nfor technical documentation with support for themes, fonts, and advanced formatting.\n",
172
+ "type": "String"
173
+ }
174
+ }
161
175
  }
162
176
  }
163
177
  },
@@ -995,8 +1009,8 @@
995
1009
  "path": "modes",
996
1010
  "desc": "Default settings for `rhx` command executions.\n",
997
1011
  "properties": {
998
- "wrapped": {
999
- "path": "modes.wrapped",
1012
+ "html_wrap": {
1013
+ "path": "modes.html_wrap",
1000
1014
  "desc": "Include (or exclude) head, header, and footer elements when enriching to HTML.\n",
1001
1015
  "type": "Boolean",
1002
1016
  "default": false
@@ -1016,7 +1030,8 @@
1016
1030
  "asciidoc_frontmatter": {
1017
1031
  "path": "modes.asciidoc_frontmatter",
1018
1032
  "desc": "Include frontmatter in AsciiDoc drafts.\n\nUses the `templates.asciidoc_frontmatter` template.\n",
1019
- "type": "Boolean"
1033
+ "type": "Boolean",
1034
+ "default": false
1020
1035
  },
1021
1036
  "fetch": {
1022
1037
  "path": "modes.fetch",
@@ -1125,6 +1140,41 @@
1125
1140
  "type": "Liquid",
1126
1141
  "default": "---\ntitle: Release History for {{ release.code }}\nversion: {{ release.code }}\ndate: {{ release.date }}\n---\n"
1127
1142
  },
1143
+ "html_framework": {
1144
+ "path": "history.html_framework",
1145
+ "desc": "The HTML framework to use when enriching to HTML.\n\nValid entries:\n\n[horizontal]\n`bare`:: minimal HTML structure\n`bootstrap4`:: Bootstrap 4 framework\n`bootstrap5`:: Bootstrap 5 framework\n",
1146
+ "type": "String",
1147
+ "default": "bare"
1148
+ },
1149
+ "styling": {
1150
+ "path": "history.styling",
1151
+ "desc": "Configuration options for HTML styling and CSS framework integration.\n",
1152
+ "properties": {
1153
+ "mode": {
1154
+ "path": "history.styling.mode",
1155
+ "desc": "The HTML styling approach to use when generating wrapped HTML output.\n\nValid options:\n\n* `minimal` -- Basic semantic HTML with minimal inline styles\n* `embedded` -- Include comprehensive CSS in `<style>` block \n* `external` -- Reference external stylesheet via `<link>` tag\n* `framework` -- Use configured CSS framework only (Bootstrap, etc.)\n\nWhen `mode: framework`, styling relies entirely on the configured CSS framework.\nWhen `mode: embedded`, CSS is included inline for standalone HTML files.\nWhen `mode: external`, a custom CSS file must be provided via `css_url`.\n",
1156
+ "type": "String",
1157
+ "default": "framework"
1158
+ },
1159
+ "theme": {
1160
+ "path": "history.styling.theme",
1161
+ "desc": "The visual theme variant to apply to HTML output.\n\nBuilt-in themes:\n\n* `default` -- Standard professional appearance\n* `compact` -- Reduced spacing and condensed layout\n* `detailed` -- Enhanced metadata display with expanded information\n\nTheme selection affects spacing, typography, and metadata prominence.\n",
1162
+ "type": "String",
1163
+ "default": "default"
1164
+ },
1165
+ "embed_css": {
1166
+ "path": "history.styling.embed_css",
1167
+ "desc": "Include comprehensive CSS directly in the HTML `<style>` block.\n\nWhen enabled, generates standalone HTML files that display correctly\nwithout external dependencies. CSS includes framework customizations,\nresponsive design rules, and print optimizations.\n\nAutomatically enabled when `mode: embedded`.\n",
1168
+ "type": "Boolean",
1169
+ "default": false
1170
+ },
1171
+ "css_url": {
1172
+ "path": "history.styling.css_url",
1173
+ "desc": "URL or path to external CSS stylesheet for custom styling.\n\nWhen `mode: external`, this stylesheet is linked via `<link rel=\"stylesheet\">`.\nCan be a relative path, absolute URL, or CDN link.\n\nExample: `assets/custom-releasehx.css` or `https://example.com/styles.css`\n\nWhen provided, framework CSS and embedded CSS are disabled.\n",
1174
+ "type": "String"
1175
+ }
1176
+ }
1177
+ },
1128
1178
  "items": {
1129
1179
  "path": "history.items",
1130
1180
  "desc": "Settings pertaining to displayed items across Changelog and Release Notes sections.\n\nMost of these settings can be defined separately for each section under <<conf_ppty_changelog_items>> and <<conf_ppty_notes_items>>.\nIf an identically named setting exists, it will override the primary designator defined in this `config.history.items` block.\n",
@@ -1228,7 +1278,7 @@
1228
1278
  "path": "history.labeling.part_label",
1229
1279
  "desc": "The label to use for the _singular_ part/component affected by the change, when only one part is permitted.\n\nThis value will apply either when <<conf_ppty_rhyml_max_parts>> is set to `1` or when the change has only one part _and_ <<conf_ppty_history_labeling_singularize_labels>> is `true`.\n",
1230
1280
  "type": "String",
1231
- "default": "Part"
1281
+ "default": "Component"
1232
1282
  },
1233
1283
  "parts_icon": {
1234
1284
  "path": "history.labeling.parts_icon",
@@ -22,10 +22,12 @@ origin: # The API or file source for the issues.
22
22
  conversions: # Details about content origination, as well as markup sources and conversion.
23
23
  summ: issue # The source of the summary (Changelog) content.
24
24
  # head: # The source of release-note headlines, when it is not the same as the summary.
25
- # note: # The source of the release notes content.
25
+ note: issue_body # The source of the release notes content.
26
26
  # note_custom_field: # The name of the custom field to use for the release notes content.Liquid error: wrong number of arguments (given 3, expected 1..2)Liquid error: wrong number of arguments (given 3, expected 1..2)
27
27
  markup: markdown # The origin markup format for notes.
28
- # engine: # The markup converter to use for the issues.
28
+ engines: # Specifies the conversion engines to use when enriching to various output formats.
29
+ # html: # Engine for converting to HTML format.
30
+ # pdf: # Engine for converting to PDF format.
29
31
  extensions: # Default file extensions.
30
32
  markdown: md # File extension for Markdown drafts.
31
33
  asciidoc: adoc # File extension for AsciiDoc drafts.
@@ -72,7 +74,7 @@ tags: # Handling for tags, labels, or toggles associated with source Issues.
72
74
  - breaking
73
75
  - experimental
74
76
  - changelog
75
- _exclude: # The list of tags that cause a change/issue to be excluded from the release history.
77
+ _exclude: [] # The list of tags that cause a change/issue to be excluded from the release history.
76
78
  highlight: # The tag, label, or toggle that indicates an issue is to be highlighted or prioritized in sorts.
77
79
  head: Highlights # How this tag will display as a grouping title.
78
80
  text: highlight # How this tag will display as a label.
@@ -163,10 +165,10 @@ paths: # The configuration for the paths to include in the release history listi
163
165
  mappings_dir: _mappings # The path to the directory containing user-defined API mappings.
164
166
  api_clients_dir: _apis # The path to the directory containing user-defined API client definitions.
165
167
  modes: # Default settings for rhx command executions.
166
- wrapped: false # Include (or exclude) head, header, and footer elements when enriching to HTML.
168
+ html_wrap: false # Include (or exclude) head, header, and footer elements when enriching to HTML.
167
169
  html_frontmatter: true # Include frontmatter in the rendered HTML.
168
170
  markdown_frontmatter: false # Include frontmatter in Markdown drafts.
169
- # asciidoc_frontmatter: # Include frontmatter in AsciiDoc drafts.
171
+ asciidoc_frontmatter: false # Include frontmatter in AsciiDoc drafts.
170
172
  fetch: notes-only # What to fetch when gathering issues from API.
171
173
  remove_excess_lines: 1 # Reduces N+ consecutive blank lines to N lines.
172
174
  rhyml: # Settings related to RHYML data objects and documents.
@@ -178,6 +180,12 @@ rhyml: # Settings related to RHYML data objects and documents.
178
180
  pasterize_head: false # Whether to convert verbs in the head property to past tense when drafting.
179
181
  history: # Configurations for the overall document, when applicable.Liquid error: wrong number of arguments (given 3, expected 1..2)
180
182
  htag: h1 # The heading level (H1, H2, etc) for the release history header.Liquid error: wrong number of arguments (given 3, expected 1..2)Liquid error: wrong number of arguments (given 3, expected 1..2)Liquid error: wrong number of arguments (given 3, expected 1..2)
183
+ html_framework: bare # The HTML framework to use when enriching to HTML.
184
+ styling: # Configuration options for HTML styling and CSS framework integration.
185
+ mode: framework # The HTML styling approach to use when generating wrapped HTML output.
186
+ theme: default # The visual theme variant to apply to HTML output.
187
+ embed_css: false # Include comprehensive CSS directly in the HTML <style> block.
188
+ # css_url: # URL or path to external CSS stylesheet for custom styling.
181
189
  items: # Settings pertaining to displayed items across Changelog and Release Notes sections.
182
190
  allow_redundant: false # Whether to allow duplicate entries in a given section, for instance across groups for part:group sorts where a change affects...
183
191
  show_issue_links: false # Whether to include web links in item metadata.
@@ -195,7 +203,7 @@ history: # Configurations for the overall document, when applicable.Liquid error
195
203
  type_label: type # The label to use for the type of change.
196
204
  # type_icon: # The icon to use for the type of change.
197
205
  parts_label: Components # The label to use for the part/component affected by the change.
198
- part_label: Part # The label to use for the singular part/component affected by the change, when only one part is permitted.
206
+ part_label: Component # The label to use for the singular part/component affected by the change, when only one part is permitted.
199
207
  parts_icon: puzzle-piece # The icon to use for the part/component affected by the change.
200
208
  tags_label: # The tags associated with the change, if any.
201
209
  - Tags
@@ -210,8 +218,7 @@ changelog: # The configuration for the changelog output.
210
218
  head: Changelog # The header for the changelog output.Liquid error: wrong number of arguments (given 3, expected 1..2)
211
219
  htag: h2 # The heading level (H1, H2, etc) for the changelog section header.
212
220
  spot: 2 # Where in the document to place the changelog (1 = top, 2 = bottom).
213
- sort: # The sort order for the changelog output.
214
- - part:grouping1
221
+ sort: [part:grouping1] # The sort order for the changelog output.
215
222
  items: # Settings that affect the frame/shape and arrangement of individual changelog entries.
216
223
  frame: unordered # The layout for the changelog entry display.
217
224
  allow_redundant: false # Whether to allow duplicate entries in a given section, for instance across groups for part:group sorts where a change affects...
@@ -248,4 +255,4 @@ notes: # The configuration for the Release Notes listing section.
248
255
  show_parts_label: false # Whether to show the parts label in the item metadata output.
249
256
  show_tags_label: false # Whether to show the tags label in the item metadata output.
250
257
  show_lead_label: false # Whether to show the lead label in the item metadata output.
251
- show_auths_label: false # Whether to show the authors label in the item metadata output.
258
+ show_auths_label: false # Whether to show the authors label in the item metadata output.
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'json'
4
- require_relative '../../schemagraphy'
5
4
  require_relative '../../docopslab/mcp/server'
6
5
  require_relative 'manifest'
7
6
  require_relative 'resource_pack'
@@ -34,7 +34,16 @@ module ReleaseHx
34
34
  when :html
35
35
  # Direct Liquid template rendering to HTML
36
36
  html_content = DraftOps.process_template_content(release: release, config: config, format: :html)
37
- WriteOps.safe_write(outpath, html_content)
37
+
38
+ # Wrap HTML with Bootstrap styling if enabled
39
+ enriched = if config && config.dig('modes', 'html_wrap') != false
40
+ ReleaseHx.logger.debug('Applying HTML wrapper')
41
+ wrap_html(html_content, config)
42
+ else
43
+ html_content
44
+ end
45
+
46
+ WriteOps.safe_write(outpath, enriched)
38
47
  when :pdf
39
48
  # Two-stage process: RHYML → AsciiDoc → PDF
40
49
  asciidoc_content = DraftOps.process_template_content(release: release, config: config, format: :adoc)
@@ -49,7 +58,7 @@ module ReleaseHx
49
58
  # Generates rich-text output from source draft files of various formats.
50
59
  #
51
60
  # Automatically detects the input file type and applies the appropriate conversion
52
- # strategy, supporting YAML (via RHYML), Markdown, and AsciiDoc source formats.
61
+ # strategy, supporting YAML (via RHYML) and AsciiDoc source formats.
53
62
  #
54
63
  # @param file_path [String] The path to the source draft file.
55
64
  # @param format [Symbol] The target output format (:html or :pdf).
@@ -75,9 +84,6 @@ module ReleaseHx
75
84
  # RHYML/YAML files: load as Release object and use Liquid templates
76
85
  release = load_rhyml_from_yaml(file_path, config: config)
77
86
  enrich_from_rhyml(release: release, config: config, format: format, outpath: outpath, force: true)
78
- when '.md'
79
- # Markdown files: use native converter
80
- convert_markdown(file_path, format: format, config: config, outpath: outpath, force: true)
81
87
  when '.adoc'
82
88
  # AsciiDoc files: use native converter
83
89
  convert_asciidoc(file_path, format: format, config: config, outpath: outpath, force: true)
@@ -86,70 +92,41 @@ module ReleaseHx
86
92
  end
87
93
  end
88
94
 
89
- # Converts Markdown files to rich-text formats using native converters.
95
+ # Converts AsciiDoc content or files to rich-text formats using configured engines.
90
96
  #
91
- # Uses Kramdown for HTML generation and Tilt with Pandoc for PDF conversion.
92
- # Note: Some parameters are reserved for API consistency across conversion methods.
93
- #
94
- # @param file_path [String] The path to the source Markdown file.
95
- # @param format [Symbol] The target output format (:html or :pdf).
96
- # @param config [ReleaseHx::Configuration] Reserved for future use.
97
- # @param outpath [String] The target output file path.
98
- # @param force [Boolean] Reserved for future use.
99
- # @return [String] The path to the generated output file.
100
- # rubocop:disable Lint/UnusedMethodArgument
101
- def self.convert_markdown file_path, format:, config:, outpath:, force:
102
- # NOTE: config and force parameters are currently unused but kept for API consistency
103
- content = File.read(file_path)
104
-
105
- case format.to_sym
106
- when :html
107
- require 'kramdown'
108
- enriched = Kramdown::Document.new(content).to_html
109
- WriteOps.safe_write(outpath, enriched)
110
- when :pdf
111
- # Use Tilt with Pandoc for direct Markdown to PDF conversion
112
- require 'tilt'
113
- require 'tilt/pandoc'
114
-
115
- template = Tilt::PandocTemplate.new(file_path)
116
- enriched = template.render(nil, to: 'pdf')
117
- WriteOps.safe_write(outpath, enriched)
118
- ReleaseHx.logger.info("PDF generated via Tilt/Pandoc: #{outpath}")
119
- else
120
- raise "Unsupported format for Markdown: #{format}"
121
- end
122
-
123
- outpath
124
- end
125
- # rubocop:enable Lint/UnusedMethodArgument
126
-
127
- # Converts AsciiDoc content or files to rich-text formats using Asciidoctor.
97
+ # Accepts either file paths or raw content strings as input. Selects appropriate
98
+ # converter engine based on config.conversions.engines.html/pdf settings with intelligent defaults:
99
+ # - AsciiDoc → HTML: asciidoctor-html5s (default)
100
+ # - AsciiDoc PDF: asciidoctor-pdf (default)
128
101
  #
129
- # Accepts either file paths or raw content strings as input. Uses Asciidoctor
130
- # for HTML generation and Asciidoctor-PDF for direct PDF output.
102
+ # HTML output can be wrapped with Bootstrap CSS when config.modes.html_wrap is enabled.
131
103
  #
132
104
  # @param file_path_or_content [String] File path or raw AsciiDoc content.
133
105
  # @param format [Symbol] The target output format (:html or :pdf).
134
106
  # @param outpath [String] The target output file path.
135
- # @param config [ReleaseHx::Configuration] Reserved for future use.
136
- # @param force [Boolean] Reserved for future use.
107
+ # @param config [ReleaseHx::Configuration] Configuration for engines and wrapping options.
108
+ # @param force [Boolean] Whether to overwrite existing files.
137
109
  # @return [String] The path to the generated output file.
138
- # rubocop:disable Lint/UnusedMethodArgument
139
110
  def self.convert_asciidoc file_path_or_content, format:, outpath:, config: nil, force: nil
140
- # NOTE: config and force parameters are currently unused but kept for API consistency
141
111
  # Determine if input is file path or raw content
142
112
  is_file = file_path_or_content.is_a?(String) && File.exist?(file_path_or_content)
143
113
  content = is_file ? File.read(file_path_or_content) : file_path_or_content
144
114
 
145
115
  case format.to_sym
146
116
  when :html
147
- require 'asciidoctor'
148
- enriched = Asciidoctor.convert(content, safe: :safe, backend: 'html5')
117
+ engine = resolve_engine(format: :html, source_format: :asciidoc, config: config)
118
+ html_fragment = convert_with_engine(content, engine: engine, format: :html)
119
+
120
+ # Wrap HTML with Bootstrap styling if enabled
121
+ enriched = if config && config.dig('modes', 'html_wrap') != false
122
+ ReleaseHx.logger.debug('Applying wrapper to AsciiDoc-derived HTML')
123
+ wrap_html(html_fragment, config)
124
+ else
125
+ html_fragment
126
+ end
149
127
  when :pdf
150
- require 'asciidoctor'
151
- require 'asciidoctor-pdf'
152
- Asciidoctor.convert(content, to_file: outpath, safe: :safe, backend: 'pdf')
128
+ engine = resolve_engine(format: :pdf, source_format: :asciidoc, config: config)
129
+ convert_with_engine(content, engine: engine, format: :pdf, outpath: outpath)
153
130
  return outpath
154
131
  else
155
132
  raise "Unsupported format for AsciiDoc: #{format}"
@@ -163,7 +140,6 @@ module ReleaseHx
163
140
  WriteOps.safe_write(outpath, enriched)
164
141
  outpath
165
142
  end
166
- # rubocop:enable Lint/UnusedMethodArgument
167
143
 
168
144
  # Loads RHYML data from a YAML file and creates a Release object.
169
145
  #
@@ -217,5 +193,135 @@ module ReleaseHx
217
193
  filename = SchemaGraphy::Templating.render_field_if_template(filename_template, context)
218
194
  File.join(output_dir, enrich_dir, filename.strip)
219
195
  end
196
+
197
+ # Wraps an HTML fragment with Bootstrap CSS and semantic HTML structure.
198
+ #
199
+ # Takes a raw HTML fragment and wraps it with proper DOCTYPE, head section with Bootstrap CDN link, and body tag.
200
+ # Uses the wrapper.html.liquid template for markup generation.
201
+ #
202
+ # @param html_fragment [String] The raw HTML content to wrap.
203
+ # @param config [ReleaseHx::Configuration] Configuration object for title and settings.
204
+ # @return [String] Complete HTML document with Bootstrap styling.
205
+ def self.wrap_html html_fragment, config
206
+ # Extract and parse framework setting
207
+ framework_spec = config.dig('history', 'html_framework') || 'bare'
208
+ framework_name, framework_version = parse_framework_spec(framework_spec)
209
+
210
+ template_path = WriteOps.resolve_template_path('wrapper.html.liquid', config)
211
+
212
+ template_vars = {
213
+ 'config' => config,
214
+ 'content' => html_fragment,
215
+ 'framework' => framework_name,
216
+ 'framework_version' => framework_version
217
+ }
218
+
219
+ WriteOps.process_template(template_path, template_vars, config)
220
+ end
221
+
222
+ # Parses framework specification string into name and version.
223
+ #
224
+ # @param spec [String] Framework specification like 'bootstrap5' or 'bootstrap:5.3.0'
225
+ # @return [Array<String, String>] Tuple of [framework_name, framework_version]
226
+ def self.parse_framework_spec spec
227
+ case spec.to_s
228
+ when /^(.+):(.+)$/
229
+ # Format: "bootstrap:5.3.0"
230
+ [::Regexp.last_match(1), ::Regexp.last_match(2)]
231
+ when 'bootstrap5'
232
+ ['bootstrap', '5.3.0']
233
+ when 'bootstrap4'
234
+ ['bootstrap', '4.6.2']
235
+ else
236
+ ['bare', nil]
237
+ end
238
+ end
239
+
240
+ # Resolves the appropriate conversion engine based on format and source.
241
+ #
242
+ # Applies intelligent defaults based on source format:
243
+ # - AsciiDoc → HTML: asciidoctor-html5s
244
+ # - AsciiDoc → PDF: asciidoctor-pdf
245
+ # - Markdown → HTML: kramdown
246
+ # - Markdown → PDF: pandoc
247
+ #
248
+ # @param format [Symbol] Output format (:html or :pdf)
249
+ # @param source_format [Symbol] Source format (:asciidoc or :markdown)
250
+ # @param config [Hash] Configuration hash
251
+ # @return [String] Engine name
252
+ def self.resolve_engine format:, source_format:, config: nil
253
+ # Check for explicit engine configuration
254
+ explicit_engine = config&.dig('conversions', 'engines', format.to_s)
255
+ return explicit_engine if explicit_engine
256
+
257
+ # Apply intelligent defaults based on source format
258
+ case [source_format, format]
259
+ when %i[asciidoc html]
260
+ 'asciidoctor-html5s'
261
+ when %i[asciidoc pdf]
262
+ 'asciidoctor-pdf'
263
+ when %i[markdown html]
264
+ 'kramdown'
265
+ when %i[markdown pdf]
266
+ 'pandoc'
267
+ else
268
+ raise "Unsupported conversion: #{source_format} → #{format}"
269
+ end
270
+ end
271
+
272
+ # Converts content using the specified engine.
273
+ #
274
+ # @param content [String] Source content to convert
275
+ # @param engine [String] Engine name (e.g., 'asciidoctor-html5s', 'asciidoctor-pdf')
276
+ # @param format [Symbol] Output format (:html or :pdf)
277
+ # @param outpath [String, nil] Output file path (required for PDF)
278
+ # @return [String] Converted content (for HTML) or output path (for PDF)
279
+ # rubocop:disable Lint/UnusedMethodArgument
280
+ def self.convert_with_engine content, engine:, format:, outpath: nil
281
+ case engine
282
+ when 'asciidoctor-html5', 'asciidoctor-html5s'
283
+ require 'asciidoctor'
284
+ backend = engine.split('-').last # 'html5' or 'html5s'
285
+
286
+ if backend == 'html5s'
287
+ begin
288
+ require 'asciidoctor-html5s'
289
+ ReleaseHx.logger.debug('Using asciidoctor-html5s backend for semantic HTML5')
290
+ rescue LoadError
291
+ ReleaseHx.logger.warn('asciidoctor-html5s not available, falling back to html5')
292
+ backend = 'html5'
293
+ end
294
+ end
295
+
296
+ Asciidoctor.convert(content, safe: :safe, backend: backend)
297
+
298
+ when 'asciidoctor-pdf'
299
+ require 'asciidoctor'
300
+ require 'asciidoctor-pdf'
301
+ ReleaseHx.logger.debug('Using asciidoctor-pdf for PDF generation')
302
+ Asciidoctor.convert(content, to_file: outpath, safe: :safe, backend: 'pdf')
303
+ outpath
304
+
305
+ when 'asciidoctor-web-pdf'
306
+ require 'asciidoctor'
307
+ require 'asciidoctor-web-pdf'
308
+ ReleaseHx.logger.debug('Using asciidoctor-web-pdf for PDF generation')
309
+ Asciidoctor.convert(content, to_file: outpath, safe: :safe, backend: 'web-pdf')
310
+ outpath
311
+
312
+ when 'kramdown'
313
+ require 'kramdown'
314
+ ReleaseHx.logger.debug('Using Kramdown for HTML conversion')
315
+ Kramdown::Document.new(content).to_html
316
+
317
+ when 'pandoc'
318
+ # Future implementation - would shell out to pandoc
319
+ raise 'Pandoc engine not yet implemented'
320
+
321
+ else
322
+ raise "Unsupported engine: #{engine}"
323
+ end
324
+ end
325
+ # rubocop:enable Lint/UnusedMethodArgument
220
326
  end
221
327
  end
@@ -53,7 +53,7 @@ module ReleaseHx
53
53
  releasehx_debug: true
54
54
  })
55
55
 
56
- raise "Template rendering failed:\n#{rendered}" if rendered.include?('Liquid error')
56
+ raise "Template rendering failed:\n#{rendered}" if rendered.match?(/Liquid error[:(]/)
57
57
 
58
58
  rendered
59
59
  end
@@ -6,9 +6,6 @@ require 'liquid'
6
6
  require 'erb'
7
7
  require 'yaml'
8
8
  require 'json'
9
- require_relative '../../schemagraphy'
10
- require_relative '../../schemagraphy/safe_expression'
11
- require_relative '../../sourcerer/jekyll'
12
9
 
13
10
  module ReleaseHx
14
11
  module RHYML
@@ -349,7 +346,7 @@ module ReleaseHx
349
346
  note_pattern = sources['note_pattern'] || templates['note_pattern']
350
347
  head_pattern = sources['head_pattern'] || templates['head_pattern']
351
348
  head_source = sources['head_source']
352
- note_source = sources['note_source']
349
+ note_source = sources['note']
353
350
 
354
351
  extract_note!(data, note_source, note_pattern)
355
352
  extract_head!(data, head_source, head_pattern)
@@ -406,10 +403,10 @@ module ReleaseHx
406
403
  end
407
404
 
408
405
  # STEP 2: Apply regex pattern extraction if configured
409
- return unless note_source =~ /issue_body/i && original_content.is_a?(String) && note_pattern.is_a?(String)
406
+ return unless note_source == 'issue_body' && original_content.is_a?(String) && note_pattern
410
407
 
411
408
  ReleaseHx.logger.debug "Extracting note using pattern: #{note_pattern}"
412
- ReleaseHx.logger.debug "Original content: #{original_content}"
409
+ ReleaseHx.logger.debug "Original content: #{original_content[0..100]}..."
413
410
 
414
411
  begin
415
412
  # Apply sensible default flag 'm' (multiline/dotall in Ruby) when no flags provided
@@ -421,11 +418,18 @@ module ReleaseHx
421
418
  pattern_info,
422
419
  'note')
423
420
 
424
- # Only update if we got a match
425
- data['note'] = extracted_note.strip if extracted_note
421
+ if extracted_note
422
+ # Pattern matched - use extracted content
423
+ data['note'] = extracted_note.strip
424
+ ReleaseHx.logger.debug "Extracted note (#{extracted_note.length} chars)"
425
+ else
426
+ # Pattern didn't match - clear the note so empty_notes policy applies
427
+ ReleaseHx.logger.warn "Note pattern did not match for issue #{data['tick']} - no Release Note section found"
428
+ data['note'] = nil
429
+ end
426
430
  rescue RegexpError => e
427
431
  ReleaseHx.logger.warn "Invalid note_pattern '#{note_pattern}': #{e.message}"
428
- data['note'] = original_content # Restore original on error
432
+ # Preserve original content on pattern error - don't lose data due to bad config
429
433
  end
430
434
  end
431
435
 
@@ -25,7 +25,9 @@ note:
25
25
 
26
26
  # Extract type from the native GitHub Issues type field (modern approach)
27
27
  type:
28
- path: "issue_type.name"
28
+ path: "type.name"
29
+ ruby: |
30
+ path&.downcase
29
31
 
30
32
  # Derive `parts` from labels using direct key matching or slug override
31
33
  parts:
@@ -0,0 +1,15 @@
1
+ /* Bootstrap Overrides for ReleaseHx */
2
+ /* Users can override this file by placing bootstrap-overrides.css in their _templates directory */
3
+
4
+ /* Dark Mode Fixes */
5
+ @media (prefers-color-scheme: dark) {
6
+ /* Fix .bg-light in card headers - use semantic card background instead */
7
+ .card-header.bg-light {
8
+ background-color: var(--bs-card-cap-bg) !important;
9
+ border-bottom: var(--bs-card-border-width) solid var(--bs-card-border-color);
10
+ }
11
+ }
12
+
13
+ .container-lg {
14
+ max-width: 900px;
15
+ }
@@ -11,4 +11,6 @@
11
11
  {%- assign item_count = 0 %}
12
12
  {%- embed section-text.liquid %}
13
13
 
14
+ [.changelog-section]
15
+
14
16
  {%- embed groupings.liquid %}
@@ -4,11 +4,13 @@
4
4
  {%- assign dflt_next_header_level = level | plus: 1 -%}
5
5
  {%- assign item_count = 0 %}
6
6
 
7
- <div class="changelog-section">
8
- {%- include header.liquid format=format level=level text=content_config.head %}
7
+ <section class="changelog-section py-4 px-3 mb-4 rounded">
8
+ {%- include header.liquid format=format level=level text=content_config.head %}
9
9
 
10
- {%- embed section-text.liquid %}
10
+ {%- embed section-text.liquid %}
11
11
 
12
+ <div class="changelog-content mt-4">
12
13
  {%- embed groupings.liquid %}
13
- </div>
14
+ </div>
15
+ </section>
14
16
 
@@ -1,3 +1,4 @@
1
+ <!-- .changelog-section -->
1
2
  {%- assign content_config = config.changelog %}
2
3
  {%- assign section_type = "changelog" %}
3
4
  {%- embed config-cascade.liquid %}