From 6ee52a54d3c56deeda5114b1786bb2bbe60fd55b Mon Sep 17 00:00:00 2001 From: Brian Dominick Date: Fri, 2 Jan 2026 15:25:58 -0500 Subject: [PATCH 01/31] Bump version to 0.2.0-dev --- README.adoc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.adoc b/README.adoc index 150af28..4da015c 100644 --- a/README.adoc +++ b/README.adoc @@ -13,11 +13,11 @@ :releasehx_demo_repo: {docopslab_git_www}/releasehx-demo :this_prod_repo: {releasehx_prod_repo} :this_prod_vrsn_major: 0 -:this_prod_vrsn_minor: 1 +:this_prod_vrsn_minor: 2 :this_prod_vrsn_major-minor: {this_prod_vrsn_major}.{this_prod_vrsn_minor} -:this_prod_vrsn_patch: 1 +:this_prod_vrsn_patch: 0 :this_prod_vrsn: {this_prod_vrsn_major-minor}.{this_prod_vrsn_patch} -:next_prod_vrsn: 0.2.0 +:next_prod_vrsn: 0.3.0 :tagline: Generate formatted release histories from Jira, GitHub, GitLab, YAML, or JSON sources. :description: pass:q[CLI utility and Ruby API for generating structured release notes and changelog documents from various issue-tracking platforms or YAML definitions into plaintext drafts (*AsciiDoc*, *Markdown*, *YAML*) and rich-text output (*HTML* and *PDF*).] :gem_config_definition_path: ./specs/data/config-def.yml From da81987814641db343c6701542c2ee9a6f50651f Mon Sep 17 00:00:00 2001 From: Brian Dominick Date: Fri, 2 Jan 2026 22:19:03 -0500 Subject: [PATCH 02/31] chore: Style cleanup in MCP docs --- README.adoc | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/README.adoc b/README.adoc index 4da015c..79f3a60 100644 --- a/README.adoc +++ b/README.adoc @@ -265,6 +265,7 @@ It does not provide assistance for command-line actions or other configurations For your LLM client (such as Copilot, Claude Code, Codex, Cursor, etc) to interact with this service, it must be configured using a general MCP syntax. This data is usually added to a `mcp.json` file or another object. +// 1. This block is totally fine Generic MCP config (global gem install):: [source,json] ---- @@ -277,12 +278,12 @@ Generic MCP config (global gem install):: } ---- +// 2. This block looks totally fine Generic MCP config (Docker):: + --- Use the Docker image for maximum compatibility across environments. -This is the **recommended approach** for most users. - +This is the *recommended approach* for most users. ++ [source,json] ---- { @@ -294,13 +295,11 @@ This is the **recommended approach** for most users. } } ---- --- +// 3. The first line on this block is white instead of blue like a DL term designator should be VS Code MCP configuration (Docker):: -+ --- Create or update `~/.config/Code/User/mcp.json` (Linux/Mac) or `%APPDATA%\Code\User\mcp.json` (Windows). - ++ [source,json] ---- { @@ -312,13 +311,11 @@ Create or update `~/.config/Code/User/mcp.json` (Linux/Mac) or `%APPDATA%\Code\U } } ---- --- +// 4.This and all the following blocks are improperly highlighted VS Code MCP configuration (global gem install):: -+ --- If you have ReleaseHx installed globally (`gem install releasehx`), you can use this simpler configuration: - ++ [source,json] ---- { @@ -329,7 +326,6 @@ If you have ReleaseHx installed globally (`gem install releasehx`), you can use } } ---- --- Local repo MCP config (Bundler + cwd):: [source,json] @@ -410,9 +406,11 @@ Global gem:: If your MCP server isn't working in VS Code or Copilot: -. **Verify Docker image exists**: Run `docker images | grep releasehx` to confirm the image is available. +. *Verify Docker image exists.* +Run `docker images | grep releasehx` to confirm the image is available. -. **Test MCP server manually**: Run the following to verify the server responds: +. *Test MCP server manually.* +Run the following to verify the server responds: + [.prompt] ---- @@ -421,11 +419,14 @@ echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion": + You should see a JSON response with `"serverInfo":{"name":"releasehx-mcp"}` -. **Check VS Code MCP config**: Ensure `~/.config/Code/User/mcp.json` exists and uses the correct command format (see examples above). +. *Check VS Code MCP config.* +Ensure `~/.config/Code/User/mcp.json` exists and uses the correct command format (see examples above). -. **Restart VS Code**: After changing `mcp.json`, restart VS Code completely for changes to take effect. +. *Restart VS Code.* +After changing `mcp.json`, restart VS Code completely for changes to take effect. -. **Check VS Code logs**: Open Output panel (Ctrl+Shift+U) and look for errors related to MCP or the releasehx server. +*Check VS Code logs.* +Open Output panel (kbd:[Ctrl+Shift+U]) and look for errors related to MCP or the releasehx server. [TIP] ==== From 4932aa31f9c647a92b487f34903a6ecc88cd501a Mon Sep 17 00:00:00 2001 From: Brian Dominick Date: Sun, 1 Mar 2026 01:33:32 -0500 Subject: [PATCH 03/31] feat: Comprehensive template overhaul with styling system Summary 1. Added configurable HTML wrapping and styling support with Bootstrap and theming. 2. Refined HTML/Liquid templates for richer metadata and markdown/asciidoc note rendering. 3. Introduced conversion engine selection and updated dependencies/filters. 4. Updated configuration, CLI behavior, and documentation; added template tests. Details 1. HTML wrapping and styling: - New wrapper (layout) template with framework selection (bootstrap4/5 or bare). - Styling modes: framework, embedded, external, and minimal, plus theme variants and dark-mode handling. - Embedded CSS template with CSS variables, responsive design, and print styles. 2. Template refinements: - HTML entry/note templates updated for semantic HTML5 with Bootstrap 5 components. - Markdown/AsciiDoc note rendering standardized via `markdownify`/`asciidocify`. - Metadata templates reworked to show types, parts, tags, links, and note indicators consistently. 3. Conversion engines and filters: - Engine selection for HTML/PDF with intelligent defaults (html5s for AsciiDoc, kramdown for Markdown). - New `asciidoctor-html5s` dependency added. - Jekyll and jekyll-asciidoc filters registered for Liquid rendering. 4. Config/CLI/docs/tests: - BREAKING: Renamed `modes.wrapped` to `modes.html_wrap`. - BREAKING: `conversions.engine` replaced by `conversions.engines.html` / `conversions.engines.pdf`. - BREAKING: Swapped default `notes.spot` and `changelog.spot` (Changelog now appears first). - Config schema expanded with `history.html_framework` and `history.styling.*` options. - New template system tests (templates_spec.rb) and ~190 lines of README documentation. - Release procedure guide added (docs/agent/release-procedure.md). 5. Bug fixes: - Adapter now reads canonical `conversions.note` property (was `note_source`). - Adapter spec updated to match config-def.yml schema. Resolves Issue #38. --- .agent/team/frontend-dev-styles.adoc | 408 ++++++++++++++++++ .config/rubocop.yml | 2 +- Gemfile.lock | 7 +- README.adoc | 237 +++++++++- docs/agent/mcp-server/agent-config-guide.md | 2 +- docs/agent/release-procedure.md | 254 +++++++++++ lib/releasehx/cli.rb | 4 +- lib/releasehx/ops/enrich_ops.rb | 216 +++++++--- lib/releasehx/rhyml/adapter.rb | 2 +- .../rhyml/templates/bootstrap-overrides.css | 15 + .../rhyml/templates/changelog.adoc.liquid | 2 + .../rhyml/templates/changelog.html.liquid | 10 +- .../rhyml/templates/changelog.md.liquid | 1 + .../rhyml/templates/embedded.css.liquid | 263 +++++++++++ .../rhyml/templates/entry.adoc.liquid | 1 + .../rhyml/templates/entry.html.liquid | 41 +- lib/releasehx/rhyml/templates/entry.md.liquid | 13 +- .../rhyml/templates/head-parser.liquid | 8 +- lib/releasehx/rhyml/templates/header.liquid | 17 +- .../rhyml/templates/history.html.liquid | 185 ++++++-- .../templates/metadata-entry.adoc.liquid | 83 ++-- .../templates/metadata-entry.html.liquid | 61 ++- .../rhyml/templates/metadata-entry.md.liquid | 178 +++----- .../rhyml/templates/metadata-note.adoc.liquid | 83 ++-- .../rhyml/templates/metadata-note.html.liquid | 81 +++- .../rhyml/templates/metadata-note.md.liquid | 91 +++- .../rhyml/templates/note.html.liquid | 44 +- lib/releasehx/rhyml/templates/note.md.liquid | 32 +- .../rhyml/templates/release-notes.adoc.liquid | 2 + .../rhyml/templates/release-notes.html.liquid | 10 +- .../rhyml/templates/release-notes.md.liquid | 1 + .../rhyml/templates/release.adoc.liquid | 2 + .../rhyml/templates/release.md.liquid | 15 +- .../rhyml/templates/wrapper.html.liquid | 103 +++++ lib/sourcerer/jekyll.rb | 4 +- lib/sourcerer/jekyll/liquid/filters.rb | 22 + releasehx.gemspec | 1 + specs/data/config-def.yml | 128 +++++- specs/tests/rspec/build_integration_spec.rb | 4 +- specs/tests/rspec/dependencies_spec.rb | 5 +- specs/tests/rspec/rhyml/adapter_spec.rb | 4 +- specs/tests/rspec/templates_spec.rb | 130 ++++++ 42 files changed, 2369 insertions(+), 403 deletions(-) create mode 100644 .agent/team/frontend-dev-styles.adoc create mode 100644 docs/agent/release-procedure.md create mode 100644 lib/releasehx/rhyml/templates/bootstrap-overrides.css create mode 100644 lib/releasehx/rhyml/templates/embedded.css.liquid create mode 100644 lib/releasehx/rhyml/templates/wrapper.html.liquid create mode 100644 specs/tests/rspec/templates_spec.rb diff --git a/.agent/team/frontend-dev-styles.adoc b/.agent/team/frontend-dev-styles.adoc new file mode 100644 index 0000000..d1ffb76 --- /dev/null +++ b/.agent/team/frontend-dev-styles.adoc @@ -0,0 +1,408 @@ += ReleaseHx Frontend Development Styles & Conventions +:toc: macro +:toclevels: 2 + +Project-specific guidance for frontend work on ReleaseHx 0.2.0 template overhaul (Issue #38). + +toc::[] + +== Context & Starting Point + +See `.agent/team/template-overhaul-plan.adoc` for full implementation strategy and phased approach. + +All work is tracked in `.agent/team/template-overhaul-plan.imyml.yml` with specific task details. + +== Quick Start for Frontend Developers + +**For Phase 1 (HTML Templates):** + +. Read `.agent/team/template-overhaul-plan.adoc` sections on "Tier 1: Semantic HTML Foundation" and "Phase 1: HTML Templates Core" +. Review current templates in `lib/releasehx/rhyml/templates/` to understand patterns +. Check `artifacts/` in releasehx-demo repo to see current output +. Look at specific task in IMYML file for detailed requirements +. Review "HTML Component Patterns" section below for ReleaseHx conventions +. Start implementing with Bootstrap 5 and semantic HTML + +## Technologies & Frameworks + +=== Templating + +* **Liquid**: Jekyll variant with custom filters +* Available filters in ReleaseHx: `md_to_asciidoc`, `render`, `indent`, `pasterize`, `demarkupify`, `inspect_yaml` +* Template location: `lib/releasehx/rhyml/templates/` + +=== CSS Framework + +* **Bootstrap 5**: Loaded via CDN in current templates +* Bootstrap CDN: `https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css` +* Font Awesome 4.7: `https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css` +* No JavaScript required (pure HTML/CSS solution) + +=== Output Targets + +* **HTML**: Standalone (with embedded CSS), SSG partial (with external CSS), or minimal +* **AsciiDoc**: Using role attributes `[.classname]` for styling hooks +* **Markdown**: Graceful text-based fallback with HTML class syntax where supported +* **PDF**: Generated from HTML via Asciidoctor or other converters + +== HTML Component Patterns + +=== Release History Structure + +[source,html] +---- +
+

Release History

+ +
+---- + +=== Release Section + +[source,html] +---- +
+

Version 1.0.0

+ +
+---- + +=== Changelog Section + +[source,html] +---- +
+

Changelog

+
+ +
+
+---- + +=== Changelog Entry + +[source,html] +---- +
+
+
+ Change summary text + +
+ +
+
+---- + +=== Release Notes Section + +[source,html] +---- +
+

Release Notes

+ +
+---- + +=== Release Note Card + +[source,html] +---- +
+
+
Note title
+
+ +
+ +
+
+---- + +=== Metadata Display + +[source,html] +---- + +---- + +== CSS Styling Conventions + +=== Class Naming + +Use semantic, BEM-inspired naming: + +[source,css] +---- +.release-history { } /* Main component */ +.release-section { } /* Block element */ +.release-section__header { } /* Child element */ + +.release-note { } /* Component */ +.release-note--breaking { } /* Modifier variant */ + +.is-active { } /* State class */ +.has-note { } /* State class */ +---- + +=== Styling Modes + +**Minimal** (default, backwards compatible): +* Emit semantic classes only +* Minimal inline styles +* Rely on Bootstrap utilities +* External CSS for any custom styling + +**Embedded** (optional): +* Include ` + + + + {{ config.history.head | default: "Release History" }} + + + - + +
{%- endif -%} {%- if config.modes.html_frontmatter -%} {%- assign frontmatter = config.history.html_frontmatter | render: vars -%} {%- if frontmatter and frontmatter != "" -%} - + {%- endif -%} {%- endif -%} -
-

{{ config.history.head | render: vars | default: "Release History" }}

+
+
+

{{ config.history.head | render: vars | default: "Release History" }}

+

Complete history of releases, changes, and improvements

+
- {%- for release in var.releases %} - {%- assign config = var.config %} - {%- assign release = var.release %} - {%- assign changes = var.changes %} - {%- assign sorted = var.sorted -%} - {%- include release.html.liquid - release=release - config=config - level=2 %} - {%- endfor %} -
+
+{%- for release in var.releases %} +{%- assign config = var.config %} +{%- assign release = var.release %} +{%- assign changes = var.changes %} +{%- assign sorted = var.sorted -%} +{%- include release.html.liquid + release=release + config=config + level=2 %} +{%- endfor %} +
+ -{%- if config.modes.wrapped != false -%} +
+ +{%- if config.modes.html_wrap != false -%} diff --git a/lib/releasehx/rhyml/templates/metadata-entry.adoc.liquid b/lib/releasehx/rhyml/templates/metadata-entry.adoc.liquid index a04b8cb..f8137d6 100644 --- a/lib/releasehx/rhyml/templates/metadata-entry.adoc.liquid +++ b/lib/releasehx/rhyml/templates/metadata-entry.adoc.liquid @@ -2,55 +2,84 @@ {%- assign show_icons = entry_metadata_icons %} {%- assign metadata_kinds = 'type,part' | split: ',' %} {%- assign format = "asciidoc" %} +{%- assign metadata_items = "" | split: "," %} {%- embed tags-listing.liquid %} {%- embed parts-listing.liquid %} -{%- capture change_metadata %} + +{%- comment %}Build inline metadata array for compact formatting{%- endcomment %} + +{%- comment %}Add parts/components{%- endcomment %} {%- if parts_listing != "" and entry_show_parts_label %} -{{- parts_listing | trim }} +{%- assign metadata_items = metadata_items | push: parts_listing %} {%- elsif change.parts and change.parts.size > 0 and entry_show_parts_label == false %} {%- for part in change.parts %} {%- unless part == "changelog" or part == "release_note_needed" %} -{{ config.parts[part]['text'] | default: part }} +{%- assign part_text = config.parts[part]['text'] | default: part %} +{%- assign part_badge = "[.badge.part-badge.part-" | append: part | append: "]#" | append: part_text | append: "#" %} +{%- assign metadata_items = metadata_items | push: part_badge %} {%- endunless %} {%- endfor %} {%- endif %} + +{%- comment %}Add type{%- endcomment %} {%- if change.type %} {%- assign type_config = config.types[change.type] %} -{%- if entry_show_type_label %} -[.meta-label.type-label]*{{ labeling_type_label }}:* -{%- endif %} -{%- if type_config.icon and show_icons and show_icons == "before" %} -icon:{{ type_config.icon | default: 'tag' }}[role=meta-icon] -{%- endif %} -{{ type_config.text | default: change.type }} -{%- if type_config.icon and show_icons and show_icons == "after" %} -icon:{{ type_config.icon | default: 'tag' }}[role=meta-icon] -{%- endif %} +{%- capture type_item %} +{%- if entry_show_type_label %}[.meta-label.type-label]*{{ labeling_type_label }}:* {% endif -%} +{%- if type_config.icon and show_icons and show_icons == "before" %}icon:{{ type_config.icon | default: 'tag' }}[role=meta-icon,role=type-icon] {% endif -%} +[.badge.type-badge.type-{{ change.type }}]#{{ type_config.text | default: change.type }}# +{%- if type_config.icon and show_icons and show_icons == "after" %} icon:{{ type_config.icon | default: 'tag' }}[role=meta-icon,role=type-icon]{% endif -%} +{%- endcapture %} +{%- assign metadata_items = metadata_items | push: type_item %} {%- endif %} + +{%- comment %}Add tags{%- endcomment %} {%- if tags_listing != "" and entry_show_tags_label %} -{{- tags_listing | trim }} +{%- assign metadata_items = metadata_items | push: tags_listing %} {%- elsif change.tags and change.tags.size > 0 and entry_show_tags_label == false %} {%- for tag in change.tags %} {%- unless tag == "changelog" or tag == "release_note_needed" %} -{{ config.tags[tag]['text'] | default: tag }} +{%- assign tag_text = config.tags[tag]['text'] | default: tag %} +{%- assign tag_badge = "[.badge.tag-badge.tag-" | append: tag | append: "]#" | append: tag_text | append: "#" %} +{%- assign metadata_items = metadata_items | push: tag_badge %} {%- endunless %} {%- endfor %} {%- endif %} + +{%- comment %}Add lead/assignee{%- endcomment %} {%- if entry_show_lead and change.lead %} -{%- if entry_show_lead_label %} -[.meta-label.lead-label]*{{ labeling_lead_label }}:* -{%- endif %} -{%- if entry_user_link_template %} -link:{{ entry_user_link_template | render: change }}[icon:user[]{{ change.lead }}] -{%- else %} -icon:user[]{{ change.lead }} -{%- endif %} +{%- capture lead_item %} +{%- if entry_show_lead_label %}[.meta-label.lead-label]*{{ labeling_lead_label }}:* {% endif -%} +{%- if entry_user_link_template -%} +link:{{ entry_user_link_template | render: change }}[icon:user[role=user-icon]{{ change.lead }},role=user-link] +{%- else -%} +icon:user[role=user-icon]{{ change.lead }} +{%- endif -%} +{%- endcapture %} +{%- assign metadata_items = metadata_items | push: lead_item %} {%- endif %} + +{%- comment %}Add ticket/issue link{%- endcomment %} {%- if config.links.web.href and config.links.web.href != "" %} -link:{{ config.links.web.href | render: change }}[icon:{{ config.links.web.icon }}[]{{ config.links.web.text | render: change }}] -{%- endif -%} +{%- capture ticket_link %}link:{{ config.links.web.href | render: change }}[icon:{{ config.links.web.icon }}[role=ticket-icon]{{ config.links.web.text | render: change }},role=ticket-link]{% endcapture %} +{%- assign metadata_items = metadata_items | push: ticket_link %} +{%- endif %} + +{%- comment %}Add git commit link{%- endcomment %} {%- if entry_show_git_links and config.links.git.href and config.links.git.href != "" %} -link:{{ config.links.git.href | render: change }}[icon:{{ config.links.git.icon | default: 'code-fork' }}[]{{ change.hash | slice: 0, 7 }}] +{%- capture git_link %}link:{{ config.links.git.href | render: change }}[icon:{{ config.links.git.icon | default: 'code-fork' }}[role=git-icon]{{ change.hash | slice: 0, 7 }},role=git-link]{% endcapture %} +{%- assign metadata_items = metadata_items | push: git_link %} +{%- endif %} + +{%- comment %}Add docs link{%- endcomment %} +{%- if config.links.docs and config.links.docs.href and config.links.docs.href != "" %} +{%- capture docs_link %}link:{{ config.links.docs.href | render: change }}[icon:{{ config.links.docs.icon | default: 'book' }}[role=docs-icon]{{ config.links.docs.text | render: change | default: 'Docs' }},role=docs-link]{% endcapture %} +{%- assign metadata_items = metadata_items | push: docs_link %} {%- endif %} -{%- endcapture %} + +{%- comment %}Output as inline metadata with semantic classes{%- endcomment %} +{%- assign change_metadata = metadata_items | join: " " | strip %} +{%- if change_metadata != "" %} +{%- assign change_metadata = "[.change-metadata]" | append: newline | append: change_metadata %} +{%- endif %} {%- assign catch_metadata_error = change_metadata %} diff --git a/lib/releasehx/rhyml/templates/metadata-entry.html.liquid b/lib/releasehx/rhyml/templates/metadata-entry.html.liquid index 6afbb60..fca714a 100644 --- a/lib/releasehx/rhyml/templates/metadata-entry.html.liquid +++ b/lib/releasehx/rhyml/templates/metadata-entry.html.liquid @@ -1,2 +1,61 @@ -{%- embed metadata-note.html.liquid -%} +{%- assign show_labels = content_config.items.metadata_labels %} +{%- assign show_icons = content_config.items.metadata_icons %} +{%- assign metadata_kinds = 'type,part' | split: ',' %} +{%- embed tags-listing.liquid %} + +{%- capture change_metadata %} +{%- if change.type %} +{%- assign type_config = config.types[change.type] %} +{%- if type_config.icon and show_icons and show_icons == "before" %} + +{%- endif %} +{%- if show_labels %} +{%- assign type_text = type_config.text | default: change.type %} +{{ type_text }} +{%- endif %} +{%- if type_config.icon and show_icons and show_icons == "after" %} + +{%- endif %} +{%- endif %} + +{%- if change.part and change.part != "changelog" and change.part != "release_note_needed" %} +{%- assign part_config = config.parts[change.part] %} +{%- if part_config %} +{%- if part_config.icon and show_icons and show_icons == "before" %} + +{%- endif %} +{%- if show_labels %} +{%- assign part_text = part_config.text | default: change.part %} +{{ part_text }} +{%- endif %} +{%- if part_config.icon and show_icons and show_icons == "after" %} + +{%- endif %} +{%- endif %} +{%- endif %} + +{%- if tags_listing != "" %} +{{ tags_listing | trim }} +{%- endif %} + +{%- if config.links.web and config.links.web.href %} + + {{ config.links.web.text | render: change | default: change.tick }} + +{%- endif %} + +{%- if config.links.git and config.links.git.href %} +{%- assign git_text = config.links.git.text | render: change | default: change.hash %} + + {{ git_text | slice: 0, 7 }} + +{%- endif %} + +{%- if change.note %} + + NOTE + +{%- endif %} +{%- endcapture %} +{%- assign catch_metadata_error = change_metadata %} diff --git a/lib/releasehx/rhyml/templates/metadata-entry.md.liquid b/lib/releasehx/rhyml/templates/metadata-entry.md.liquid index babcbbc..6204163 100644 --- a/lib/releasehx/rhyml/templates/metadata-entry.md.liquid +++ b/lib/releasehx/rhyml/templates/metadata-entry.md.liquid @@ -1,130 +1,82 @@ -{%- assign metadata_parts = "" | split: "," -%} +{%- comment -%} Build inline metadata array with minimal HTML badges {%- endcomment -%} +{%- assign metadata_items = "" | split: "," -%} -{%- comment -%}Collect ticket link{%- endcomment -%} -{%- if entry_show_issue_links and change.tick and config.links.web.href and config.links.web.href != "" -%} -{%- assign ticket_link_text = config.links.web.text | render: change -%} -{%- if config.links.web.href and config.links.web.href != "" -%} -{%- assign ticket_link = "[" | append: ticket_link_text | append: "](" | append: config.links.web.href | render: change | append: ")" -%} +{%- comment -%} Type badge {%- endcomment -%} +{%- if metadata_kinds contains "type" and change.type -%} +{%- assign type_config = config.types[change.type] -%} +{%- if type_config -%} +{%- assign type_text = type_config.text | default: change.type -%} +{%- assign type_slug = change.type | sluggerize: slug_type -%} +{%- capture type_badge -%}{{ type_text }}{%- endcapture -%} +{%- assign metadata_items = metadata_items | push: type_badge -%} {%- endif -%} -{%- assign ticket_part = ticket_link | split: "," -%} -{%- assign metadata_parts = metadata_parts | concat: ticket_part -%} {%- endif -%} -{%- if metadata_kinds contains "part" %} -{%- comment -%}Collect parts/components{%- endcomment -%} -{%- assign effective_parts = "" | split: "," -%} -{%- if change.parts and change.parts.size > 0 -%} -{%- assign effective_parts = change.parts -%} -{%- elsif change.part -%} -{%- assign effective_parts = change.part | split: "," -%} -{%- endif -%} -{%- if effective_parts.size > 0 -%} -{%- assign parts_formatted = "" | split: "," -%} -{%- assign parts_count = 0 -%} -{%- for part in effective_parts -%} -{%- assign skip_part = false -%} -{%- if part == "changelog" or part == "release_note_needed" -%} -{%- assign skip_part = true -%} -{%- elsif part == group1_slug or part == group2_slug -%} -{%- assign skip_part = true -%} -{%- elsif config.parts[part] == nil -%} -{%- assign skip_part = true -%} -{%- endif -%} -{%- unless skip_part -%} -{%- assign part_config = config.parts[part] -%} -{%- assign part_text = part_config.text | default: part -%} -{%- assign bold_part = "**" | append: part_text | append: "**" -%} -{%- assign bold_part_array = bold_part | split: "," -%} -{%- assign parts_formatted = parts_formatted | concat: bold_part_array -%} -{%- assign parts_count = parts_count | plus: 1 -%} -{%- endunless -%} -{%- endfor -%} -{%- if parts_count > 0 -%} -{%- assign parts_joined = parts_formatted | join: labeling_join_string -%} -{%- if entry_show_parts_label -%} -{%- if labeling_singularize_labels and parts_count == 1 -%} -{%- assign parts_text = labeling_part_label | append: ": " | append: parts_joined -%} -{%- else -%} -{%- assign parts_text = labeling_parts_label | append: ": " | append: parts_joined -%} +{%- comment -%} Parts/components {%- endcomment -%} +{%- if metadata_kinds contains "part" -%} +{%- assign effective_parts = "" | split: "," -%} +{%- if change.parts and change.parts.size > 0 -%} +{%- assign effective_parts = change.parts -%} +{%- elsif change.part -%} +{%- assign effective_parts = change.part | split: "," -%} +{%- endif -%} +{%- if effective_parts.size > 0 -%} +{%- for part in effective_parts -%} +{%- assign skip_part = false -%} +{%- if part == "changelog" or part == "release_note_needed" -%} +{%- assign skip_part = true -%} +{%- elsif part == group1_slug or part == group2_slug -%} +{%- assign skip_part = true -%} +{%- elsif config.parts[part] == nil -%} +{%- assign skip_part = true -%} {%- endif -%} -{%- else -%} -{%- assign parts_text = parts_joined -%} -{%- endif -%} -{%- assign parts_part = parts_text | split: "," -%} -{%- assign metadata_parts = metadata_parts | concat: parts_part -%} +{%- unless skip_part -%} +{%- assign part_config = config.parts[part] -%} +{%- assign part_text = part_config.text | default: part -%} +{%- assign part_slug = part | sluggerize: slug_type -%} +{%- capture part_badge -%}{{ part_text }}{%- endcapture -%} +{%- assign metadata_items = metadata_items | push: part_badge -%} +{%- endunless -%} +{%- endfor -%} {%- endif -%} {%- endif -%} -{%- endif -%} -{%- if metadata_kinds contains "type" %} -{%- comment -%}Collect type (only if not already being grouped by type){%- endcomment -%} -{%- assign skip_type = false -%} -{%- if change.type == group1_slug or change.type == group2_slug -%} -{%- assign skip_type = true -%} +{%- comment -%} Tags {%- endcomment -%} +{%- if metadata_kinds contains "tag" and change.tags and change.tags.size > 0 -%} +{%- for tag in change.tags -%} +{%- assign tag_slug = tag | sluggerize: slug_type -%} +{%- capture tag_badge -%}{{ tag }}{%- endcapture -%} +{%- assign metadata_items = metadata_items | push: tag_badge -%} +{%- endfor -%} {%- endif -%} -{%- if change.type and skip_type == false -%} -{%- assign type_config = config.types[change.type] -%} -{%- if entry_show_type_label -%} -{%- assign type_text = labeling_type_label | append: ": " | append: type_config.text | default: change.type | capitalize -%} + +{%- comment -%} Lead/user {%- endcomment -%} +{%- if metadata_kinds contains "lead" and change.lead -%} +{%- if entry_show_lead_links and config.links.user.href -%} +{%- capture lead_link -%}[@{{ change.lead }}]({{ config.links.user.href | replace: '{{user}}', change.lead }}){%- endcapture -%} +{%- assign metadata_items = metadata_items | push: lead_link -%} {%- else -%} -{%- assign type_text = type_config.text | default: change.type | capitalize -%} +{%- capture lead_text -%}@{{ change.lead }}{%- endcapture -%} +{%- assign metadata_items = metadata_items | push: lead_text -%} {%- endif -%} -{%- assign type_part = type_text | split: "," -%} -{%- assign metadata_parts = metadata_parts | concat: type_part -%} -{%- endif -%} {%- endif -%} -{%- if metadata_kinds contains "tag" %} -{%- comment -%}Collect tags{%- endcomment -%} -{%- if change.tags and change.tags.size > 0 -%} -{%- assign tags_formatted = "" | split: "," -%} -{%- assign tags_count = 0 -%} -{%- for tag in change.tags -%} -{%- assign skip_tag = false -%} -{%- if tag == "changelog" or tag == "release_note_needed" -%} -{%- assign skip_tag = true -%} -{%- elsif tag == group1_slug or tag == group2_slug -%} -{%- assign skip_tag = true -%} -{%- elsif config.tags[tag] == nil -%} -{%- assign skip_tag = true -%} -{%- endif -%} -{%- unless skip_tag -%} -{%- assign tag_text_array = tag | split: "," -%} -{%- assign tags_formatted = tags_formatted | concat: tag_text_array -%} -{%- assign tags_count = tags_count | plus: 1 -%} -{%- endunless -%} -{%- endfor -%} -{%- if tags_count > 0 -%} -{%- if entry_show_tags_label -%} -{%- if labeling_singularize_labels and tags_count == 1 -%} -{%- assign tags_text = labeling_tag_label | append: ": " | append: tags_formatted | join: labeling_join_string -%} -{%- else -%} -{%- assign tags_text = labeling_tags_label | append: ": " | append: tags_formatted | join: labeling_join_string -%} -{%- endif -%} -{%- else -%} -{%- assign tags_text = tags_formatted | join: labeling_join_string -%} -{%- endif -%} -{%- assign tags_part = tags_text | split: "," -%} -{%- assign metadata_parts = metadata_parts | concat: tags_part -%} -{%- endif -%} -{%- endif -%} +{%- comment -%} Issue/ticket link {%- endcomment -%} +{%- if entry_show_issue_links and change.tick and config.links.web.href and config.links.web.href != "" -%} +{%- assign ticket_link_text = config.links.web.text | render: change -%} +{%- capture ticket_link -%}[{{ ticket_link_text }}]({{ config.links.web.href | render: change }}){%- endcapture -%} +{%- assign metadata_items = metadata_items | push: ticket_link -%} {%- endif -%} -{%- comment -%}Collect lead contributor{%- endcomment -%} -{%- if entry_show_lead and change.lead -%} -{%- if entry_show_lead_label -%} -{%- assign lead_prefix = labeling_lead_label | append: ": " -%} -{%- else -%} -{%- assign lead_prefix = "" -%} -{%- endif -%} -{%- if entry_user_link_template -%} -{%- assign lead_text = lead_prefix | append: "[" | append: change.lead | append: "](" | append: entry_user_link_template | render: change | append: ")" -%} -{%- else -%} -{%- assign lead_text = lead_prefix | append: change.lead -%} -{%- endif -%} -{%- assign lead_part = lead_text | split: "," -%} -{%- assign metadata_parts = metadata_parts | concat: lead_part -%} +{%- comment -%} Git commit link {%- endcomment -%} +{%- if metadata_kinds contains "git" and change.ghsh and config.links.git.href -%} +{%- capture git_link -%}[`{{ change.ghsh | slice: 0, 7 }}`]({{ config.links.git.href | render: change }}){%- endcapture -%} +{%- assign metadata_items = metadata_items | push: git_link -%} {%- endif -%} -{%- comment -%}Output the final metadata string{%- endcomment -%} -{%- assign change_metadata = metadata_parts | join: " | " -%} +{%- comment -%} Join metadata inline {%- endcomment -%} +{%- if metadata_items.size > 0 -%} +{%- assign change_metadata = metadata_items | join: " " -%} +{%- else -%} +{%- assign change_metadata = "" -%} +{%- endif -%} diff --git a/lib/releasehx/rhyml/templates/metadata-note.adoc.liquid b/lib/releasehx/rhyml/templates/metadata-note.adoc.liquid index 0f0477c..efa2317 100644 --- a/lib/releasehx/rhyml/templates/metadata-note.adoc.liquid +++ b/lib/releasehx/rhyml/templates/metadata-note.adoc.liquid @@ -2,55 +2,84 @@ {%- assign show_icons = note_metadata_icons %} {%- assign metadata_kinds = 'type,part' | split: ',' %} {%- assign format = "asciidoc" %} +{%- assign metadata_items = "" | split: "," %} {%- embed tags-listing.liquid %} {%- embed parts-listing.liquid %} -{%- capture change_metadata %} + +{%- comment %}Build inline metadata array for compact formatting{%- endcomment %} + +{%- comment %}Add parts/components{%- endcomment %} {%- if parts_listing != "" and note_show_parts_label %} -{{- parts_listing | trim }} +{%- assign metadata_items = metadata_items | push: parts_listing %} {%- elsif change.parts and change.parts.size > 0 and note_show_parts_label == false %} {%- for part in change.parts %} {%- unless part == "changelog" or part == "release_note_needed" %} -{{ config.parts[part]['text'] | default: part }} +{%- assign part_text = config.parts[part]['text'] | default: part %} +{%- assign part_badge = "[.badge.part-badge.part-" | append: part | append: "]#" | append: part_text | append: "#" %} +{%- assign metadata_items = metadata_items | push: part_badge %} {%- endunless %} {%- endfor %} {%- endif %} + +{%- comment %}Add type{%- endcomment %} {%- if change.type %} {%- assign type_config = config.types[change.type] %} -{%- if note_show_type_label %} -[.meta-label.type-label]*{{ labeling_type_label }}:* -{%- endif %} -{%- if type_config.icon and show_icons and show_icons == "before" %} -icon:{{ type_config.icon | default: 'tag' }}[role=meta-icon] -{%- endif %} -{{ type_config.text | default: change.type }} -{%- if type_config.icon and show_icons and show_icons == "after" %} -icon:{{ type_config.icon | default: 'tag' }}[role=meta-icon] -{%- endif %} +{%- capture type_item %} +{%- if note_show_type_label %}[.meta-label.type-label]*{{ labeling_type_label }}:* {% endif -%} +{%- if type_config.icon and show_icons and show_icons == "before" %}icon:{{ type_config.icon | default: 'tag' }}[role=meta-icon,role=type-icon] {% endif -%} +[.badge.type-badge.type-{{ change.type }}]#{{ type_config.text | default: change.type }}# +{%- if type_config.icon and show_icons and show_icons == "after" %} icon:{{ type_config.icon | default: 'tag' }}[role=meta-icon,role=type-icon]{% endif -%} +{%- endcapture %} +{%- assign metadata_items = metadata_items | push: type_item %} {%- endif %} + +{%- comment %}Add tags{%- endcomment %} {%- if tags_listing != "" and note_show_tags_label %} -{{- tags_listing | trim }} +{%- assign metadata_items = metadata_items | push: tags_listing %} {%- elsif change.tags and change.tags.size > 0 and note_show_tags_label == false %} {%- for tag in change.tags %} {%- unless tag == "changelog" or tag == "release_note_needed" %} -{{ config.tags[tag]['text'] | default: tag }} +{%- assign tag_text = config.tags[tag]['text'] | default: tag %} +{%- assign tag_badge = "[.badge.tag-badge.tag-" | append: tag | append: "]#" | append: tag_text | append: "#" %} +{%- assign metadata_items = metadata_items | push: tag_badge %} {%- endunless %} {%- endfor %} {%- endif %} + +{%- comment %}Add lead/assignee{%- endcomment %} {%- if note_show_lead and change.lead %} -{%- if note_show_lead_label %} -[.meta-label.lead-label]*{{ labeling_lead_label }}:* -{%- endif %} -{%- if note_user_link_template %} -link:{{ note_user_link_template | render: change }}[icon:user[]{{ change.lead }}] -{%- else %} -icon:user[]{{ change.lead }} -{%- endif %} +{%- capture lead_item %} +{%- if note_show_lead_label %}[.meta-label.lead-label]*{{ labeling_lead_label }}:* {% endif -%} +{%- if note_user_link_template -%} +link:{{ note_user_link_template | render: change }}[icon:user[role=user-icon]{{ change.lead }},role=user-link] +{%- else -%} +icon:user[role=user-icon]{{ change.lead }} +{%- endif -%} +{%- endcapture %} +{%- assign metadata_items = metadata_items | push: lead_item %} {%- endif %} + +{%- comment %}Add ticket/issue link{%- endcomment %} {%- if config.links.web.href and config.links.web.href != "" %} -link:{{ config.links.web.href | render: change }}[icon:{{ config.links.web.icon }}[]{{ config.links.web.text | render: change }}] -{%- endif -%} +{%- capture ticket_link %}link:{{ config.links.web.href | render: change }}[icon:{{ config.links.web.icon }}[role=ticket-icon]{{ config.links.web.text | render: change }},role=ticket-link]{% endcapture %} +{%- assign metadata_items = metadata_items | push: ticket_link %} +{%- endif %} + +{%- comment %}Add git commit link{%- endcomment %} {%- if note_show_git_links and config.links.git.href and config.links.git.href != "" %} -link:{{ config.links.git.href | render: change }}[icon:{{ config.links.git.icon | default: 'code-fork' }}[]{{ change.hash | slice: 0, 7 }}] +{%- capture git_link %}link:{{ config.links.git.href | render: change }}[icon:{{ config.links.git.icon | default: 'code-fork' }}[role=git-icon]{{ change.hash | slice: 0, 7 }},role=git-link]{% endcapture %} +{%- assign metadata_items = metadata_items | push: git_link %} +{%- endif %} + +{%- comment %}Add docs link{%- endcomment %} +{%- if config.links.docs and config.links.docs.href and config.links.docs.href != "" %} +{%- capture docs_link %}link:{{ config.links.docs.href | render: change }}[icon:{{ config.links.docs.icon | default: 'book' }}[role=docs-icon]{{ config.links.docs.text | render: change | default: 'Docs' }},role=docs-link]{% endcapture %} +{%- assign metadata_items = metadata_items | push: docs_link %} {%- endif %} -{%- endcapture %} + +{%- comment %}Output as inline metadata with semantic classes{%- endcomment %} +{%- assign change_metadata = metadata_items | join: " " | strip %} +{%- if change_metadata != "" %} +{%- assign change_metadata = "[.note-metadata]" | append: newline | append: change_metadata %} +{%- endif %} {%- assign catch_metadata_error = change_metadata %} diff --git a/lib/releasehx/rhyml/templates/metadata-note.html.liquid b/lib/releasehx/rhyml/templates/metadata-note.html.liquid index 7a644b7..d3ac0c0 100644 --- a/lib/releasehx/rhyml/templates/metadata-note.html.liquid +++ b/lib/releasehx/rhyml/templates/metadata-note.html.liquid @@ -1,36 +1,73 @@ {%- assign show_labels = content_config.items.metadata_labels %} {%- assign show_icons = content_config.items.metadata_icons %} +{%- assign metadata_kinds = 'type,part' | split: ',' %} {%- embed tags-listing.liquid %} {%- capture change_metadata %} -{%- for kind in metadata_kinds %} -{%- assign change_meta_kind = change[kind] %} -{%- assign kind_plural = kind | append: "s" %} -{%- if kind == "tag" %} -{%- continue %} -{%- endif %} -{%- if config[kind_plural][change_meta_kind].icon and show_icons and show_icons == "before" %} - +{%- if change.type %} +{%- assign type_config = config.types[change.type] %} +{%- if type_config.icon and show_icons and show_icons == "before" %} + +{%- endif %} +{%- if show_labels %} +{%- assign type_text = type_config.text | default: change.type %} +{{ type_text }} +{%- endif %} +{%- if type_config.icon and show_icons and show_icons == "after" %} + +{%- endif %} +{%- endif %} + +{%- if change.part and change.part != "changelog" and change.part != "release_note_needed" %} +{%- assign part_config = config.parts[change.part] %} +{%- if part_config %} +{%- if part_config.icon and show_icons and show_icons == "before" %} + {%- endif %} {%- if show_labels %} -{{ config[kind_plural][change_meta_kind]['text'] | default: change_meta_kind }} +{%- assign part_text = part_config.text | default: change.part %} +{{ part_text }} {%- endif %} -{%- if config[kind_plural][change_meta_kind].icon and show_icons and show_icons == "after" %} - +{%- if part_config.icon and show_icons and show_icons == "after" %} + {%- endif %} -{%- endfor %} -{%- if tags_listing != "" %} -{{ tags_listing | trim }} {%- endif %} -{%- if config.links.web and config.links.web.href %} - - {{ config.links.web.text | render: change | default: change.tick }} +{%- endif %} + +{%- if tags_listing != "" %} +{{ tags_listing | trim }} +{%- endif %} + +{%- if change.lead and config.links.user and config.links.user.href %} +{%- assign user_link = config.links.user.href | render: change %} + + {{ change.lead }} -{%- endif -%} -{%- if config.links.git and config.links.git.href %} - - {% assign git_text = config.links.git.text | render: change | default: change.hash %}{{ git_text | slice: 0, 7 }} +{%- elsif change.lead %} + + {{ change.lead }} + +{%- endif %} + +{%- if config.links.web and config.links.web.href %} + + {{ config.links.web.text | render: change | default: change.tick }} -{%- endif %} +{%- endif %} + +{%- if config.links.git and config.links.git.href %} +{%- assign git_text = config.links.git.text | render: change | default: change.hash %} + + {{ git_text | slice: 0, 7 }} + +{%- endif %} + +{%- if config.links.docs and config.links.docs.href %} + + {{ config.links.docs.text | render: change | default: 'Docs' }} + +{%- endif %} {%- endcapture %} {%- assign catch_metadata_error = change_metadata %} + + diff --git a/lib/releasehx/rhyml/templates/metadata-note.md.liquid b/lib/releasehx/rhyml/templates/metadata-note.md.liquid index 5341394..9dbfaf6 100644 --- a/lib/releasehx/rhyml/templates/metadata-note.md.liquid +++ b/lib/releasehx/rhyml/templates/metadata-note.md.liquid @@ -1,30 +1,75 @@ -{%- capture change_metadata -%} -{%- assign metadata_parts = "" | split: "," -%} +{%- comment -%} Build inline metadata with badges for release notes {%- endcomment -%} +{%- assign metadata_kinds = 'type,part' | split: ',' -%} +{%- assign metadata_items = "" | split: "," -%} + +{%- comment -%} Type badge {%- endcomment -%} +{%- if metadata_kinds contains "type" and change.type -%} +{%- assign type_config = config.types[change.type] -%} +{%- if type_config -%} +{%- assign type_text = type_config.text | default: change.type -%} +{%- assign type_slug = change.type | sluggerize: slug_type -%} +{%- capture type_badge -%}{{ type_text }}{%- endcapture -%} +{%- assign metadata_items = metadata_items | push: type_badge -%} +{%- endif -%} +{%- endif -%} + +{%- comment -%} Parts {%- endcomment -%} +{%- if metadata_kinds contains "part" -%} +{%- assign effective_parts = "" | split: "," -%} +{%- if change.parts and change.parts.size > 0 -%} +{%- assign effective_parts = change.parts -%} +{%- elsif change.part -%} +{%- assign effective_parts = change.part | split: "," -%} +{%- endif -%} +{%- if effective_parts.size > 0 -%} +{%- for part in effective_parts -%} +{%- assign part_config = config.parts[part] -%} +{%- if part_config -%} +{%- assign part_text = part_config.text | default: part -%} +{%- assign part_slug = part | sluggerize: slug_type -%} +{%- capture part_badge -%}{{ part_text }}{%- endcapture -%} +{%- assign metadata_items = metadata_items | push: part_badge -%} +{%- endif -%} +{%- endfor -%} +{%- endif -%} +{%- endif -%} + +{%- comment -%} Tags {%- endcomment -%} +{%- if metadata_kinds contains "tag" and change.tags and change.tags.size > 0 -%} +{%- for tag in change.tags -%} +{%- assign tag_slug = tag | sluggerize: slug_type -%} +{%- capture tag_badge -%}{{ tag }}{%- endcapture -%} +{%- assign metadata_items = metadata_items | push: tag_badge -%} +{%- endfor -%} +{%- endif -%} + +{%- comment -%} Lead/user {%- endcomment -%} {%- if note_show_lead and change.lead -%} - {%- capture lead_text -%} - {%- if note_show_lead_links and config.links.user.href -%} - [{{ change.lead }}]({{ config.links.user.href | replace: '{{user}}', change.lead }}) - {%- else -%} - {{ change.lead }} - {%- endif -%} - {%- endcapture -%} - {%- assign metadata_parts = metadata_parts | push: lead_text -%} +{%- if note_show_lead_links and config.links.user.href -%} +{%- capture lead_link -%}[@{{ change.lead }}]({{ config.links.user.href | replace: '{{user}}', change.lead }}){%- endcapture -%} +{%- assign metadata_items = metadata_items | push: lead_link -%} +{%- else -%} +{%- capture lead_text -%}@{{ change.lead }}{%- endcapture -%} +{%- assign metadata_items = metadata_items | push: lead_text -%} +{%- endif -%} {%- endif -%} + +{%- comment -%} Issue link {%- endcomment -%} {%- if note_show_issue_links and change.tick and config.links.web.href -%} - {%- capture issue_link -%}[{{ config.links.web.text | render: change }}]({{ config.links.web.href | render: change }}){%- endcapture -%} - {%- assign metadata_parts = metadata_parts | push: issue_link -%} +{%- capture issue_link -%}[{{ config.links.web.text | render: change }}]({{ config.links.web.href | render: change }}){%- endcapture -%} +{%- assign metadata_items = metadata_items | push: issue_link -%} {%- elsif change.tick -%} - {%- assign metadata_parts = metadata_parts | push: change.tick -%} -{%- endif -%} -{%- embed parts-listing.liquid -%} -{%- if parts_listing and parts_listing != "" -%} - {%- assign metadata_parts = metadata_parts | push: parts_listing -%} +{%- assign metadata_items = metadata_items | push: change.tick -%} {%- endif -%} -{%- embed tags-listing.liquid -%} -{%- if tags_listing and tags_listing != "" -%} - {%- assign metadata_parts = metadata_parts | push: tags_listing -%} + +{%- comment -%} Git commit {%- endcomment -%} +{%- if metadata_kinds contains "git" and change.ghsh and config.links.git.href -%} +{%- capture git_link -%}[`{{ change.ghsh | slice: 0, 7 }}`]({{ config.links.git.href | render: change }}){%- endcapture -%} +{%- assign metadata_items = metadata_items | push: git_link -%} {%- endif -%} -{%- if metadata_parts.size > 0 -%} -({{ metadata_parts | join: ", " }}) + +{%- if metadata_items.size > 0 -%} +{%- assign change_metadata = metadata_items | join: " " -%} +{%- else -%} +{%- assign change_metadata = "" -%} {%- endif -%} -{%- endcapture -%} diff --git a/lib/releasehx/rhyml/templates/note.html.liquid b/lib/releasehx/rhyml/templates/note.html.liquid index 6ce7b55..cff004a 100644 --- a/lib/releasehx/rhyml/templates/note.html.liquid +++ b/lib/releasehx/rhyml/templates/note.html.liquid @@ -1,25 +1,31 @@ +{%- assign raw_note = change.note | default: "NO RELEASE NOTE PROVIDED" %} +{%- assign change_note = raw_note | replace: "## Release Note", "" | trim %} {%- if config.conversions.markup == "markdown" %} -{%- assign change_note = change.note | default: "NO RELEASE NOTE PROVIDED" | md_to_html | trim %} -{%- else %} -{%- assign change_note = change.note | default: "NO RELEASE NOTE PROVIDED" | trim %} +{%- assign change_note = change_note | markdownify | trim %} +{%- elsif config.conversions.markup == "asciidoc" %} +{%- assign change_note = change_note | asciidocify | trim %} {%- endif %} {%- include metadata-note.html.liquid %} -
-
- {%- if change.head %} -
{{ change.head }}
- {%- endif %} - -
- {{ change_note }} -
- - {%- if change_metadata != "" %} - - {%- endif %} +
+
+ {%- if change.head %} +
{{ change.head }}
+ {%- else %} +
Release Note
+ {%- endif %} +
+
+
+ {{ change_note }} +
+ {%- if change_metadata != "" %} + -
+ {%- endif %} +
+ diff --git a/lib/releasehx/rhyml/templates/note.md.liquid b/lib/releasehx/rhyml/templates/note.md.liquid index 58b1c08..150b16e 100644 --- a/lib/releasehx/rhyml/templates/note.md.liquid +++ b/lib/releasehx/rhyml/templates/note.md.liquid @@ -1,37 +1,55 @@ + {% if config.conversions.markup == "markdown" -%} {% assign change_note = change.note | default: "NO RELEASE NOTE PROVIDED" | trim -%} {% else -%} {% assign change_note = change.note | default: "NO RELEASE NOTE PROVIDED" | adoc_to_md | trim -%} {% endif -%} +{%- comment -%} Remove "## Release Note" heading if it appears in the note content {%- endcomment -%} +{%- assign change_note = change_note | replace: "## Release Note", "" | trim -%} {% embed metadata-note.md.liquid -%} + {%- if config.notes.items.frame == "table" %} + | Change | Details | |--------|---------| | {{ change_metadata }} | {% if change.head %}**{{ change.head }}**
{% endif %}{{ change_note }} | +{%- elsif config.notes.items.frame == "table-cols-1" %} + +| Note | +|------| +| {{ change_metadata }}
{% if change.head %}**{{ change.head }}**
{% endif %}{{ change_note }} | +{%- elsif config.notes.items.frame == "table-cols-2" %} + +| Note | Metadata | +|------|----------| +| {% if change.head %}**{{ change.head }}**
{% endif %}{{ change_note }} | {{ change_metadata }} | {%- elsif config.notes.items.frame == "desc-list" %} +{% if change.head -%} **{{ change.head }}** +{% endif -%} {{ change_note }} -*{{ change_metadata }}* +{{ change_metadata }} {% elsif config.notes.items.frame == "admonition" %} {% if change.type == "removal" or change.type == "deprecation" or change.type == "breaking" %} -{% assign admonition_type = "Warning" %} +{% assign admonition_type = "⚠️ Warning" %} {% elsif change.type == "security" %} -{% assign admonition_type = "Important" %} +{% assign admonition_type = "🔒 Security" %} {% elsif change.type == "experimental" %} -{% assign admonition_type = "Tip" %} +{% assign admonition_type = "💡 Tip" %} {% else %} -{% assign admonition_type = "Note" %} +{% assign admonition_type = "📝 Note" %} {% endif %} -> **{{ admonition_type }}**: {% if change.head %}**{{ change.head }}** - {% endif %}{{ change_note }} +> **{{ admonition_type }}**: {% if change.head %}**{{ change.head }}** — {% endif %}{{ change_note }} > {{ change_metadata }} {% else -%} {% if change.head -%} ### {{ change.head }} {% endif -%} {{ change_note }} -{{- change_metadata }} + +{{ change_metadata }} {% endif %} diff --git a/lib/releasehx/rhyml/templates/release-notes.adoc.liquid b/lib/releasehx/rhyml/templates/release-notes.adoc.liquid index 6669311..788538c 100644 --- a/lib/releasehx/rhyml/templates/release-notes.adoc.liquid +++ b/lib/releasehx/rhyml/templates/release-notes.adoc.liquid @@ -11,4 +11,6 @@ {%- assign item_count = 0 %} {%- embed section-text.liquid %} +[.notes-section] + {%- embed groupings.liquid %} \ No newline at end of file diff --git a/lib/releasehx/rhyml/templates/release-notes.html.liquid b/lib/releasehx/rhyml/templates/release-notes.html.liquid index b301125..a5c0dea 100644 --- a/lib/releasehx/rhyml/templates/release-notes.html.liquid +++ b/lib/releasehx/rhyml/templates/release-notes.html.liquid @@ -4,11 +4,13 @@ {%- assign dflt_next_header_level = level | plus: 1 -%} {%- assign item_count = 0 %} -
- {%- include header.liquid format=format level=level text=content_config.head %} +
+ {%- include header.liquid format=format level=level text=content_config.head %} - {%- embed section-text.liquid %} + {%- embed section-text.liquid %} +
{%- embed groupings.liquid %} -
+
+ diff --git a/lib/releasehx/rhyml/templates/release-notes.md.liquid b/lib/releasehx/rhyml/templates/release-notes.md.liquid index 151e57d..1f7dae7 100644 --- a/lib/releasehx/rhyml/templates/release-notes.md.liquid +++ b/lib/releasehx/rhyml/templates/release-notes.md.liquid @@ -1,3 +1,4 @@ + {%- assign content_config = config.notes %} {%- assign section_type = "notes" %} {%- embed config-cascade.liquid %} diff --git a/lib/releasehx/rhyml/templates/release.adoc.liquid b/lib/releasehx/rhyml/templates/release.adoc.liquid index ce80e5b..8c297fb 100644 --- a/lib/releasehx/rhyml/templates/release.adoc.liquid +++ b/lib/releasehx/rhyml/templates/release.adoc.liquid @@ -6,6 +6,8 @@ :icons: font {% embed head-parser.liquid %} +[.release-section] + {%- if release.hash and release.hash != "" and config.links.git and config.links.git.trim != "" %} link:{{ config.links.git | render: release }}[icon:{{ config.links.git_icon | default: 'code-fork' }}[]{{ release.hash | slice: 0, 7 }}] {%- endif %} diff --git a/lib/releasehx/rhyml/templates/release.md.liquid b/lib/releasehx/rhyml/templates/release.md.liquid index db1ca30..c0e90a2 100644 --- a/lib/releasehx/rhyml/templates/release.md.liquid +++ b/lib/releasehx/rhyml/templates/release.md.liquid @@ -1,12 +1,13 @@ -{% assign format = "markdown" -%} -{% assign config = vars.config -%} -{% assign release = vars.release -%} -{% assign changes = vars.changes -%} -{% assign sorted = vars.sorted -%} +{%- assign format = "markdown" -%} +{%- assign config = vars.config -%} +{%- assign release = vars.release -%} +{%- assign changes = vars.changes -%} +{%- assign sorted = vars.sorted -%} {%- embed head-parser.liquid %} -{% if release.memo -%} + +{%- if release.memo -%} {{ release.memo }} -{% endif -%} +{%- endif -%} {% if release.hash and release.hash != "" and config.links.git and config.links.git.trim != "" -%} **Git:** [{{ release.hash | slice: 0, 7 }}]({{ config.links.git | render: release }}) diff --git a/lib/releasehx/rhyml/templates/wrapper.html.liquid b/lib/releasehx/rhyml/templates/wrapper.html.liquid new file mode 100644 index 0000000..10be250 --- /dev/null +++ b/lib/releasehx/rhyml/templates/wrapper.html.liquid @@ -0,0 +1,103 @@ + + + + + + + {% assign title = config.history.head | default: 'Release History' | escape %} + {{ title }} + + {%- assign styling_mode = config.history.styling.mode | default: 'framework' %} + {%- assign css_url = config.history.styling.css_url %} + {%- assign embed_css = config.history.styling.embed_css | default: false %} + {%- assign theme = config.history.styling.theme | default: 'default' %} + + {%- if styling_mode == 'external' and css_url %} + + {%- elsif styling_mode == 'framework' or styling_mode == 'embedded' %} + {%- if framework == 'bootstrap' %} + {%- case framework_version %} + {%- when '5.3.0' %} + + {%- when '4.6.2' %} + + {%- else %} + + {%- endcase %} + {%- endif %} + + + + {%- comment %}Load Bootstrap overrides (can be customized in _templates/bootstrap-overrides.css){%- endcomment %} + + {%- endif %} + + {%- if styling_mode == 'embedded' or embed_css %} + + {%- elsif styling_mode == 'minimal' %} + + {%- endif %} + + +
+
+ {{ content }} +
+
+ {%- if framework == 'bootstrap' %} + {%- case framework_version %} + {%- when '5.3.0' %} + + + {%- when '4.6.2' %} + + {%- else %} + + {%- endcase %} + {%- endif %} + + diff --git a/lib/sourcerer/jekyll.rb b/lib/sourcerer/jekyll.rb index f193487..2296b89 100644 --- a/lib/sourcerer/jekyll.rb +++ b/lib/sourcerer/jekyll.rb @@ -19,8 +19,10 @@ def self.initialize_liquid_runtime Monkeypatches.patch_jekyll # Ensure Sourcerer filters are registered ::Liquid::Template.register_filter(::Sourcerer::Jekyll::Liquid::Filters) + # Ensure Jekyll filters are registered + ::Liquid::Template.register_filter(::Jekyll::Filters) # Ensure jekyll-asciidoc filters are registered - # ::Liquid::Template.register_filter(Jekyll::AsciiDoc::Filters) + ::Liquid::Template.register_filter(::Jekyll::AsciiDoc::Filters) end end end diff --git a/lib/sourcerer/jekyll/liquid/filters.rb b/lib/sourcerer/jekyll/liquid/filters.rb index 5c2919c..103642c 100644 --- a/lib/sourcerer/jekyll/liquid/filters.rb +++ b/lib/sourcerer/jekyll/liquid/filters.rb @@ -125,6 +125,28 @@ def ruby_class input input.class.name end + # Returns a string with the first letter of each word capitalized + # @param input [String] The string to capitalize. + # @param hyphen [Boolean] Whether to also capitalize after hyphens + # @return [String] The capitalized string. + # @note Does not force lowercase letters in any word + # Example: + # {{ "hello world-example" | title_caps: true }} + # => "Hello World-Example" + # {{ "hello world-example" | title_caps }} + # => "Hello World-example" + # {{ "API documentation" | title_caps }} + # => "API Documentation" + def title_caps input, hyphen=false + return input unless input.is_a? String + + if hyphen + input.gsub(/(^|[\s-])([[:alpha:]])/) { "#{::Regexp.last_match(1)}#{::Regexp.last_match(2).upcase}" } + else + input.gsub(/(^|\s)([[:alpha:]])/) { "#{::Regexp.last_match(1)}#{::Regexp.last_match(2).upcase}" } + end + end + # Removes markup from a string. # @param input [String] The string to demarkupify. # @return [String] The demarkupified string. diff --git a/releasehx.gemspec b/releasehx.gemspec index 266e34f..372534f 100644 --- a/releasehx.gemspec +++ b/releasehx.gemspec @@ -55,6 +55,7 @@ Gem::Specification.new do |spec| spec.add_dependency 'tilt', '~> 2.3' spec.add_dependency 'yaml', '~> 0.4' + spec.add_dependency 'asciidoctor-html5s', '~> 0.5' spec.add_dependency 'asciidoctor-pdf', '~> 2.3' spec.add_dependency 'commonmarker', '~> 0.23' spec.add_dependency 'jekyll', '~> 4.4' diff --git a/specs/data/config-def.yml b/specs/data/config-def.yml index c3def6a..d0c6e91 100644 --- a/specs/data/config-def.yml +++ b/specs/data/config-def.yml @@ -201,12 +201,59 @@ properties: The origin markup format for notes. May be `markdown` or `asciidoc`. dflt: markdown - engine: - type: String + engines: desc: | - The markup converter to use for the issues. - Defaults to `asciidoctor` for AsciiDoc and `redcarpet` for Markdown. - Options include `asciidoctor`, `redcarpet`, `commonmarker`, `kramdown`, or `pandoc`. + Specifies the conversion engines to use when enriching to various output formats. + + These settings determine which tool processes the conversion from draft formats to rich output. + Intelligent defaults are applied based on source format when engines are not explicitly configured. + properties: + html: + type: String + desc: | + Engine for converting to HTML format. + + Valid engines: + + [horizontal] + `asciidoctor-html5`:: Standard Asciidoctor HTML5 backend (nested div structure) + `asciidoctor-html5s`:: Semantic HTML5 backend (cleaner section-based markup) + `kramdown`:: Kramdown Markdown converter (for Markdown sources) + `pandoc`:: Universal document converter (if available) + + When not specified, intelligent defaults apply: + + - AsciiDoc → HTML: `asciidoctor-html5s` (semantic HTML5) + - Markdown → HTML: `kramdown` + - RHYML → HTML: Liquid templates (no engine needed) + docs: | + The html5s backend produces cleaner, more semantic HTML with `
` elements + instead of nested `
` structures. + + This is particularly useful when you want to apply custom CSS or when generating + HTML that will be further processed or embedded in other systems. + + For backwards compatibility or when you need the traditional Asciidoctor structure, + use `asciidoctor-html5`. + pdf: + type: String + desc: | + Engine for converting to PDF format. + + Valid engines: + + [horizontal] + `asciidoctor-pdf`:: Asciidoctor PDF converter (default for AsciiDoc) + `asciidoctor-web-pdf`:: Web-based PDF converter using headless Chrome + `pandoc`:: Universal document converter (if available) + + When not specified, intelligent defaults apply: + + - AsciiDoc → PDF: `asciidoctor-pdf` + - Markdown → PDF: `pandoc` (if available) + docs: | + The default `asciidoctor-pdf` engine provides excellent typography and layout + for technical documentation with support for themes, fonts, and advanced formatting. extensions: desc: Default file extensions. @@ -835,7 +882,7 @@ properties: desc: | Default settings for `rhx` command executions. properties: - wrapped: + html_wrap: type: Boolean desc: | Include (or exclude) head, header, and footer elements when enriching to HTML. @@ -860,6 +907,7 @@ properties: Include frontmatter in AsciiDoc drafts. Uses the `templates.asciidoc_frontmatter` template. + dflt: false fetch: type: String desc: | @@ -1041,6 +1089,74 @@ properties: It may include `{{ title }}`, `{{ version }}`, `{{ date }}`, as well as any `vars`-scoped variables as you pass in. dflt: *markdown_frontmatter_tplt + html_framework: + type: String + desc: | + The HTML framework to use when enriching to HTML. + + Valid entries: + + [horizontal] + `bare`:: minimal HTML structure + `bootstrap4`:: Bootstrap 4 framework + `bootstrap5`:: Bootstrap 5 framework + dflt: bare + styling: + desc: | + Configuration options for HTML styling and CSS framework integration. + properties: + mode: + type: String + desc: | + The HTML styling approach to use when generating wrapped HTML output. + + Valid options: + + * `minimal` -- Basic semantic HTML with minimal inline styles + * `embedded` -- Include comprehensive CSS in `