releasehx 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.adoc +2915 -0
- data/bin/releasehx +7 -0
- data/bin/rhx +7 -0
- data/bin/rhx-mcp +7 -0
- data/bin/sourcerer +32 -0
- data/build/docs/CNAME +1 -0
- data/build/docs/Gemfile.lock +95 -0
- data/build/docs/_config.yml +36 -0
- data/build/docs/config-reference.adoc +4104 -0
- data/build/docs/config-reference.json +1546 -0
- data/build/docs/index.adoc +2915 -0
- data/build/docs/landing.adoc +21 -0
- data/build/docs/manpage.adoc +68 -0
- data/build/docs/releasehx.1 +281 -0
- data/build/docs/releasehx_readme.html +367 -0
- data/build/docs/sample-config.adoc +9 -0
- data/build/docs/sample-config.yml +251 -0
- data/build/docs/schemagraphy_readme.html +0 -0
- data/build/docs/sourcerer_readme.html +46 -0
- data/build/snippets/helpscreen.txt +29 -0
- data/lib/docopslab/mcp/asset_packager.rb +30 -0
- data/lib/docopslab/mcp/manifest.rb +67 -0
- data/lib/docopslab/mcp/resource_pack.rb +46 -0
- data/lib/docopslab/mcp/server.rb +92 -0
- data/lib/docopslab/mcp.rb +6 -0
- data/lib/releasehx/cli.rb +937 -0
- data/lib/releasehx/configuration.rb +215 -0
- data/lib/releasehx/generated.rb +17 -0
- data/lib/releasehx/helpers.rb +58 -0
- data/lib/releasehx/mcp/asset_packager.rb +21 -0
- data/lib/releasehx/mcp/assets/agent-config-guide.md +178 -0
- data/lib/releasehx/mcp/assets/config-def.yml +1426 -0
- data/lib/releasehx/mcp/assets/config-reference.adoc +4104 -0
- data/lib/releasehx/mcp/assets/config-reference.json +1546 -0
- data/lib/releasehx/mcp/assets/sample-config.yml +251 -0
- data/lib/releasehx/mcp/manifest.rb +18 -0
- data/lib/releasehx/mcp/resource_pack.rb +26 -0
- data/lib/releasehx/mcp/server.rb +57 -0
- data/lib/releasehx/mcp.rb +7 -0
- data/lib/releasehx/ops/check_ops.rb +136 -0
- data/lib/releasehx/ops/draft_ops.rb +173 -0
- data/lib/releasehx/ops/enrich_ops.rb +221 -0
- data/lib/releasehx/ops/template_ops.rb +61 -0
- data/lib/releasehx/ops/write_ops.rb +124 -0
- data/lib/releasehx/rest/clients/github.yml +46 -0
- data/lib/releasehx/rest/clients/gitlab.yml +31 -0
- data/lib/releasehx/rest/clients/jira.yml +31 -0
- data/lib/releasehx/rest/yaml_client.rb +418 -0
- data/lib/releasehx/rhyml/adapter.rb +740 -0
- data/lib/releasehx/rhyml/change.rb +167 -0
- data/lib/releasehx/rhyml/liquid.rb +13 -0
- data/lib/releasehx/rhyml/loaders.rb +37 -0
- data/lib/releasehx/rhyml/mappings/github.yaml +60 -0
- data/lib/releasehx/rhyml/mappings/gitlab.yaml +73 -0
- data/lib/releasehx/rhyml/mappings/jira.yaml +29 -0
- data/lib/releasehx/rhyml/mappings/verb_past_tenses.yml +98 -0
- data/lib/releasehx/rhyml/release.rb +144 -0
- data/lib/releasehx/rhyml.rb +15 -0
- data/lib/releasehx/sgyml/helpers.rb +45 -0
- data/lib/releasehx/transforms/adf_to_markdown.rb +307 -0
- data/lib/releasehx/version.rb +7 -0
- data/lib/releasehx.rb +69 -0
- data/lib/schemagraphy/attribute_resolver.rb +48 -0
- data/lib/schemagraphy/cfgyml/definition.rb +90 -0
- data/lib/schemagraphy/cfgyml/doc_builder.rb +52 -0
- data/lib/schemagraphy/cfgyml/path_reference.rb +24 -0
- data/lib/schemagraphy/data_query/json_pointer.rb +42 -0
- data/lib/schemagraphy/loader.rb +59 -0
- data/lib/schemagraphy/regexp_utils.rb +215 -0
- data/lib/schemagraphy/safe_expression.rb +189 -0
- data/lib/schemagraphy/schema_utils.rb +124 -0
- data/lib/schemagraphy/tag_utils.rb +32 -0
- data/lib/schemagraphy/templating.rb +104 -0
- data/lib/schemagraphy.rb +17 -0
- data/lib/sourcerer/builder.rb +120 -0
- data/lib/sourcerer/jekyll/bootstrapper.rb +78 -0
- data/lib/sourcerer/jekyll/liquid/file_system.rb +74 -0
- data/lib/sourcerer/jekyll/liquid/filters.rb +215 -0
- data/lib/sourcerer/jekyll/liquid/tags.rb +44 -0
- data/lib/sourcerer/jekyll/monkeypatches.rb +73 -0
- data/lib/sourcerer/jekyll.rb +26 -0
- data/lib/sourcerer/plaintext_converter.rb +75 -0
- data/lib/sourcerer/templating.rb +190 -0
- data/lib/sourcerer.rb +322 -0
- data/specs/data/api-client-schema.yaml +160 -0
- data/specs/data/config-def.yml +1426 -0
- data/specs/data/mcp-manifest.yml +50 -0
- data/specs/data/rhyml-mapping-schema.yaml +410 -0
- data/specs/data/rhyml-schema.yaml +152 -0
- metadata +376 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ReleaseHx
|
|
4
|
+
# The DraftOps module provides methods for creating Release objects and
|
|
5
|
+
# generating draft files from source data like API payloads.
|
|
6
|
+
module DraftOps
|
|
7
|
+
# Converts a raw JSON payload from an issue management system into a
|
|
8
|
+
# ReleaseHx::RHYML::Release object.
|
|
9
|
+
#
|
|
10
|
+
# @param payload [Hash, Array] The raw payload from the API.
|
|
11
|
+
# @param config [ReleaseHx::Configuration] The application configuration.
|
|
12
|
+
# @param mapping [Hash] The mapping definition for converting payload fields.
|
|
13
|
+
# @param release_code [String] The version code for the release.
|
|
14
|
+
# @param release_date [Date, String, nil] The date for the release.
|
|
15
|
+
# @param scan [Boolean] Indicates if this is a scan-only operation.
|
|
16
|
+
# @return [ReleaseHx::RHYML::Release] The generated Release object.
|
|
17
|
+
def self.from_payload payload:, config:, mapping:, release_code:, release_date: nil, scan: false
|
|
18
|
+
adapter = ReleaseHx::RHYML::Adapter.new(mapping: mapping, config: config)
|
|
19
|
+
|
|
20
|
+
adapter.to_release(
|
|
21
|
+
payload,
|
|
22
|
+
release_code: release_code,
|
|
23
|
+
release_date: release_date || Date.today,
|
|
24
|
+
scan: scan)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Shared method for preparing RHYML object template context
|
|
28
|
+
def self.prepare_template_context release:, config:
|
|
29
|
+
raise ArgumentError, 'release is nil' if release.nil?
|
|
30
|
+
|
|
31
|
+
# Establish available datasets
|
|
32
|
+
all_changes = release.changes.select { |ch| ch.respond_to?(:to_h) }
|
|
33
|
+
changes_mapped = all_changes.map(&:to_h)
|
|
34
|
+
|
|
35
|
+
sorted = build_sorted_changes(changes_mapped, config)
|
|
36
|
+
|
|
37
|
+
context_scope = {
|
|
38
|
+
'release' => release.to_h,
|
|
39
|
+
'changes' => changes_mapped,
|
|
40
|
+
'sorted' => sorted,
|
|
41
|
+
'config' => config
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
SchemaGraphy::Templating.render_all_templated_fields!(config, context_scope)
|
|
45
|
+
|
|
46
|
+
{
|
|
47
|
+
variables: {
|
|
48
|
+
'release' => release.to_h,
|
|
49
|
+
'changes' => changes_mapped,
|
|
50
|
+
'config' => config,
|
|
51
|
+
'sorted' => sorted
|
|
52
|
+
},
|
|
53
|
+
context_scope: context_scope
|
|
54
|
+
}
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Generates a string of content for a draft file (e.g., Markdown, YAML)
|
|
58
|
+
# from a Release object using the configured Liquid templates.
|
|
59
|
+
#
|
|
60
|
+
# @param release [ReleaseHx::RHYML::Release] The release object.
|
|
61
|
+
# @param config [ReleaseHx::Configuration] The application configuration.
|
|
62
|
+
# @param format [Symbol] The output format (:yaml, :md, :adoc, :html).
|
|
63
|
+
# @return [String] The rendered content.
|
|
64
|
+
def self.process_template_content release:, config:, format:
|
|
65
|
+
context = prepare_template_context(release: release, config: config)
|
|
66
|
+
|
|
67
|
+
tplt = case format.to_sym
|
|
68
|
+
when :yaml, :yml then 'rhyml.yaml.liquid'
|
|
69
|
+
when :md then 'release.md.liquid'
|
|
70
|
+
when :adoc then 'release.adoc.liquid'
|
|
71
|
+
when :html then 'release.html.liquid'
|
|
72
|
+
else raise ArgumentError, "Unsupported format: #{format}"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
tplt_path = WriteOps.resolve_template_path(tplt, config)
|
|
76
|
+
ReleaseHx.logger.debug "Using template: #{tplt_path}"
|
|
77
|
+
|
|
78
|
+
WriteOps.process_template(tplt_path, { 'vars' => context[:variables] }, config)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Builds sorted changes structure (moved from EnrichOps)
|
|
82
|
+
def self.build_sorted_changes changes_mapped, config
|
|
83
|
+
sorted = {
|
|
84
|
+
'by' => {
|
|
85
|
+
'tag' => {},
|
|
86
|
+
'type' => {},
|
|
87
|
+
'part' => {}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
changes_mapped.each do |ch|
|
|
92
|
+
Array(ch['tags']).each do |tag|
|
|
93
|
+
sorted['by']['tag'][tag] ||= []
|
|
94
|
+
sorted['by']['tag'][tag] << ch
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
type = ch['type']
|
|
98
|
+
if type
|
|
99
|
+
sorted['by']['type'][type] ||= []
|
|
100
|
+
sorted['by']['type'][type] << ch
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# treat 'parts' differently from type or tags as it is an Array in all cases
|
|
104
|
+
parts = ch['parts']
|
|
105
|
+
next unless parts
|
|
106
|
+
|
|
107
|
+
Array(parts).each do |part|
|
|
108
|
+
sorted['by']['part'][part] ||= []
|
|
109
|
+
sorted['by']['part'][part] << ch
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Ensures all config-defined parts/types/tags are initialized
|
|
114
|
+
Array(config['types']&.keys).each do |type|
|
|
115
|
+
sorted['by']['type'][type] ||= []
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
Array(config['parts']&.keys).each do |part|
|
|
119
|
+
sorted['by']['part'][part] ||= []
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
Array(config['tags']&.keys).each do |tag|
|
|
123
|
+
next if %w[_include _exclude].include?(tag)
|
|
124
|
+
|
|
125
|
+
sorted['by']['tag'][tag] ||= []
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
sorted
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# rubocop:disable Lint/UnusedMethodArgument
|
|
132
|
+
def self.append_changes yaml_file_path:, version_code:, config:, mapping:, source_type:, payload: nil, force: false
|
|
133
|
+
# NOTE: force parameter is currently unused but kept for API consistency
|
|
134
|
+
# Load existing YAML Release
|
|
135
|
+
existing_release = ReleaseHx::RHYML::ReleaseLoader.load(yaml_file_path)
|
|
136
|
+
existing_tick_ids = existing_release.changes.map(&:tick).compact
|
|
137
|
+
|
|
138
|
+
ReleaseHx.logger.debug "Found #{existing_tick_ids.size} existing changes with tick IDs: #{existing_tick_ids}"
|
|
139
|
+
|
|
140
|
+
# Use provided payload or default to API fetch
|
|
141
|
+
payload ||= fetch_payload_for_version(version_code, source_type, config)
|
|
142
|
+
|
|
143
|
+
new_release = from_payload(
|
|
144
|
+
payload: payload,
|
|
145
|
+
config: config,
|
|
146
|
+
mapping: mapping,
|
|
147
|
+
release_code: version_code,
|
|
148
|
+
release_date: existing_release.date)
|
|
149
|
+
|
|
150
|
+
# Find new changes that weren't in the existing YAML
|
|
151
|
+
new_changes = new_release.changes.select do |change|
|
|
152
|
+
change.tick && !existing_tick_ids.include?(change.tick)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
return 0 if new_changes.empty?
|
|
156
|
+
|
|
157
|
+
ReleaseHx.logger.info "Found #{new_changes.size} new changes to append"
|
|
158
|
+
|
|
159
|
+
# Generate append content and write it
|
|
160
|
+
WriteOps.append_changes_to_yaml(yaml_file_path, new_changes, config)
|
|
161
|
+
|
|
162
|
+
new_changes.size
|
|
163
|
+
end
|
|
164
|
+
# rubocop:enable Lint/UnusedMethodArgument
|
|
165
|
+
|
|
166
|
+
# Legacy method kept for backward compatibility
|
|
167
|
+
def self.draft_output release:, config:, format:, outpath:
|
|
168
|
+
content = process_template_content(release: release, config: config, format: format)
|
|
169
|
+
WriteOps.safe_write(outpath, content) if outpath
|
|
170
|
+
content
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ReleaseHx
|
|
4
|
+
# Provides rich-text output generation from Release objects and draft source files.
|
|
5
|
+
#
|
|
6
|
+
# The EnrichOps module handles the conversion of Release data and various draft formats
|
|
7
|
+
# (YAML, Markdown, AsciiDoc) into rich output formats (HTML, PDF) using appropriate
|
|
8
|
+
# rendering engines and template processing.
|
|
9
|
+
module EnrichOps
|
|
10
|
+
# Generates rich-text output directly from a Release object.
|
|
11
|
+
#
|
|
12
|
+
# Processes Release data through Liquid templates to produce HTML or PDF output.
|
|
13
|
+
# HTML generation uses direct template rendering, while PDF generation creates
|
|
14
|
+
# intermediate AsciiDoc content before conversion.
|
|
15
|
+
#
|
|
16
|
+
# @param release [ReleaseHx::RHYML::Release] The Release object to enrich.
|
|
17
|
+
# @param config [ReleaseHx::Configuration] The application configuration.
|
|
18
|
+
# @param format [Symbol] The output format (:html or :pdf).
|
|
19
|
+
# @param outpath [String, nil] The explicit output file path; auto-resolved if nil.
|
|
20
|
+
# @param force [Boolean] Whether to overwrite existing output files.
|
|
21
|
+
# @return [String] The path to the generated output file.
|
|
22
|
+
def self.enrich_from_rhyml release:, config:, format:, outpath: nil, force: false
|
|
23
|
+
# Use proper config-based output path resolution if not provided
|
|
24
|
+
outpath ||= resolve_enrich_path(release.code, format, config)
|
|
25
|
+
|
|
26
|
+
if File.exist?(outpath) && !force
|
|
27
|
+
ReleaseHx.logger.warn("File exists: #{outpath}. Use --force to overwrite.")
|
|
28
|
+
return outpath
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
ReleaseHx.logger.debug("Enriching release #{release.code} to #{outpath} (format: #{format.to_s.upcase})")
|
|
32
|
+
|
|
33
|
+
case format
|
|
34
|
+
when :html
|
|
35
|
+
# Direct Liquid template rendering to HTML
|
|
36
|
+
html_content = DraftOps.process_template_content(release: release, config: config, format: :html)
|
|
37
|
+
WriteOps.safe_write(outpath, html_content)
|
|
38
|
+
when :pdf
|
|
39
|
+
# Two-stage process: RHYML → AsciiDoc → PDF
|
|
40
|
+
asciidoc_content = DraftOps.process_template_content(release: release, config: config, format: :adoc)
|
|
41
|
+
convert_asciidoc(asciidoc_content, format: :pdf, outpath: outpath)
|
|
42
|
+
else
|
|
43
|
+
raise ArgumentError, "Unsupported enrich format: #{format}"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
outpath
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Generates rich-text output from source draft files of various formats.
|
|
50
|
+
#
|
|
51
|
+
# Automatically detects the input file type and applies the appropriate conversion
|
|
52
|
+
# strategy, supporting YAML (via RHYML), Markdown, and AsciiDoc source formats.
|
|
53
|
+
#
|
|
54
|
+
# @param file_path [String] The path to the source draft file.
|
|
55
|
+
# @param format [Symbol] The target output format (:html or :pdf).
|
|
56
|
+
# @param config [ReleaseHx::Configuration] The application configuration.
|
|
57
|
+
# @param outpath [String, nil] The explicit output file path; inferred if nil.
|
|
58
|
+
# @param force [Boolean] Whether to overwrite existing output files.
|
|
59
|
+
# @return [String] The path to the generated output file.
|
|
60
|
+
def self.enrich_from_file file_path, format:, config:, outpath: nil, force: false
|
|
61
|
+
raise "File not found: #{file_path}" unless File.exist?(file_path)
|
|
62
|
+
|
|
63
|
+
file_ext = File.extname(file_path).downcase
|
|
64
|
+
outpath ||= file_path.sub(/\.[^.]+$/, ".#{format}")
|
|
65
|
+
|
|
66
|
+
# Prevent accidental file overwrites
|
|
67
|
+
if File.exist?(outpath) && !force
|
|
68
|
+
ReleaseHx.logger.warn("File exists: #{outpath}. Use --force to overwrite.")
|
|
69
|
+
return outpath
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Route to appropriate conversion method based on source format
|
|
73
|
+
case file_ext
|
|
74
|
+
when '.yml', '.yaml'
|
|
75
|
+
# RHYML/YAML files: load as Release object and use Liquid templates
|
|
76
|
+
release = load_rhyml_from_yaml(file_path, config: config)
|
|
77
|
+
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
|
+
when '.adoc'
|
|
82
|
+
# AsciiDoc files: use native converter
|
|
83
|
+
convert_asciidoc(file_path, format: format, config: config, outpath: outpath, force: true)
|
|
84
|
+
else
|
|
85
|
+
raise "Unsupported source file format: #{file_ext}"
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Converts Markdown files to rich-text formats using native converters.
|
|
90
|
+
#
|
|
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.
|
|
128
|
+
#
|
|
129
|
+
# Accepts either file paths or raw content strings as input. Uses Asciidoctor
|
|
130
|
+
# for HTML generation and Asciidoctor-PDF for direct PDF output.
|
|
131
|
+
#
|
|
132
|
+
# @param file_path_or_content [String] File path or raw AsciiDoc content.
|
|
133
|
+
# @param format [Symbol] The target output format (:html or :pdf).
|
|
134
|
+
# @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.
|
|
137
|
+
# @return [String] The path to the generated output file.
|
|
138
|
+
# rubocop:disable Lint/UnusedMethodArgument
|
|
139
|
+
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
|
+
# Determine if input is file path or raw content
|
|
142
|
+
is_file = file_path_or_content.is_a?(String) && File.exist?(file_path_or_content)
|
|
143
|
+
content = is_file ? File.read(file_path_or_content) : file_path_or_content
|
|
144
|
+
|
|
145
|
+
case format.to_sym
|
|
146
|
+
when :html
|
|
147
|
+
require 'asciidoctor'
|
|
148
|
+
enriched = Asciidoctor.convert(content, safe: :safe, backend: 'html5')
|
|
149
|
+
when :pdf
|
|
150
|
+
require 'asciidoctor'
|
|
151
|
+
require 'asciidoctor-pdf'
|
|
152
|
+
Asciidoctor.convert(content, to_file: outpath, safe: :safe, backend: 'pdf')
|
|
153
|
+
return outpath
|
|
154
|
+
else
|
|
155
|
+
raise "Unsupported format for AsciiDoc: #{format}"
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Prevent accidental file overwrites for HTML output
|
|
159
|
+
if File.exist?(outpath) && !force
|
|
160
|
+
ReleaseHx.logger.warn("File exists: #{outpath}. Use --force to overwrite.")
|
|
161
|
+
return outpath
|
|
162
|
+
end
|
|
163
|
+
WriteOps.safe_write(outpath, enriched)
|
|
164
|
+
outpath
|
|
165
|
+
end
|
|
166
|
+
# rubocop:enable Lint/UnusedMethodArgument
|
|
167
|
+
|
|
168
|
+
# Loads RHYML data from a YAML file and creates a Release object.
|
|
169
|
+
#
|
|
170
|
+
# Processes YAML files containing either a single Release or a collection
|
|
171
|
+
# of Releases, extracting the first Release for processing.
|
|
172
|
+
#
|
|
173
|
+
# @param file_path [String] Path to the YAML file containing RHYML data.
|
|
174
|
+
# @param config [ReleaseHx::Configuration] Reserved for future use.
|
|
175
|
+
# @return [ReleaseHx::RHYML::Release] The constructed Release object.
|
|
176
|
+
# rubocop:disable Lint/UnusedMethodArgument
|
|
177
|
+
def self.load_rhyml_from_yaml file_path, config:
|
|
178
|
+
# NOTE: config parameter is currently unused but kept for API consistency
|
|
179
|
+
# Load RHYML data using SchemaGraphy for tag processing
|
|
180
|
+
rhyml_data = SchemaGraphy::Loader.load_yaml_with_tags(file_path)
|
|
181
|
+
|
|
182
|
+
# Extract Release data: first item from 'releases' array or single Release hash
|
|
183
|
+
release_data = rhyml_data['releases'] ? rhyml_data['releases'].first : rhyml_data
|
|
184
|
+
|
|
185
|
+
# Construct Release object with keyword arguments
|
|
186
|
+
ReleaseHx::RHYML::Release.new(
|
|
187
|
+
code: release_data['code'],
|
|
188
|
+
date: release_data['date'],
|
|
189
|
+
hash: release_data['hash'],
|
|
190
|
+
memo: release_data['memo'],
|
|
191
|
+
changes: release_data['changes'] || [])
|
|
192
|
+
end
|
|
193
|
+
# rubocop:enable Lint/UnusedMethodArgument
|
|
194
|
+
|
|
195
|
+
# Resolves the output file path using configuration-based templated filenames.
|
|
196
|
+
#
|
|
197
|
+
# Constructs the full output path by combining configured directories and
|
|
198
|
+
# processing template variables in the filename pattern.
|
|
199
|
+
#
|
|
200
|
+
# @param version [String] The release version code for template substitution.
|
|
201
|
+
# @param format [Symbol] The output format for file extension determination.
|
|
202
|
+
# @param config [ReleaseHx::Configuration] Configuration containing path templates.
|
|
203
|
+
# @return [String] The resolved absolute output file path.
|
|
204
|
+
def self.resolve_enrich_path version, format, config
|
|
205
|
+
# Extract path configuration from config
|
|
206
|
+
output_dir = config.dig('paths', 'output_dir')
|
|
207
|
+
enrich_dir = config.dig('paths', 'enrich_dir')
|
|
208
|
+
filename_template = config.dig('paths', 'enrich_filename')
|
|
209
|
+
|
|
210
|
+
# Build template context for filename processing
|
|
211
|
+
context = {
|
|
212
|
+
'version' => version,
|
|
213
|
+
'format_ext' => format.to_s
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
# Process templated filename and construct full path
|
|
217
|
+
filename = SchemaGraphy::Templating.render_field_if_template(filename_template, context)
|
|
218
|
+
File.join(output_dir, enrich_dir, filename.strip)
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'pathname'
|
|
4
|
+
|
|
5
|
+
module ReleaseHx
|
|
6
|
+
# Provides Liquid template rendering services with extended Jekyll integration and configurable template paths.
|
|
7
|
+
#
|
|
8
|
+
# The TemplateOps module handles the complex setup required for Liquid template processing, including
|
|
9
|
+
# Jekyll plugin loading, template path resolution, and Sourcerer Jekyll runtime initialization for
|
|
10
|
+
# advanced template features and includes.
|
|
11
|
+
module TemplateOps
|
|
12
|
+
# Renders a Liquid template string with provided variables and configuration context.
|
|
13
|
+
#
|
|
14
|
+
# Sets up a complete, enhanced Liquid rendering environment with plugin support,
|
|
15
|
+
# configurable template include paths, and proper site context for advanced template features.
|
|
16
|
+
# Supports both user-defined and gem-bundled template directories with fallback resolution.
|
|
17
|
+
#
|
|
18
|
+
# @param input [String] The raw Liquid template string to process.
|
|
19
|
+
# @param vars [Hash] Variable context for template variable substitution.
|
|
20
|
+
# @param config [ReleaseHx::Configuration] Configuration object containing template path settings.
|
|
21
|
+
# @return [String] The fully rendered template output.
|
|
22
|
+
# @raise [StandardError] If template rendering fails due to syntax errors or missing variables.
|
|
23
|
+
def self.render_liquid_string input, vars, config
|
|
24
|
+
plugin_dirs = [File.expand_path('../../../jekyll_plugins', __dir__)]
|
|
25
|
+
user_templates_dir = config.dig('paths', 'templates_dir')
|
|
26
|
+
gem_templates_dir = File.expand_path('../rhyml/templates', __dir__)
|
|
27
|
+
|
|
28
|
+
# Build template include path hierarchy: user templates take precedence over gem defaults
|
|
29
|
+
includes = []
|
|
30
|
+
if user_templates_dir
|
|
31
|
+
unless Pathname.new(user_templates_dir).absolute?
|
|
32
|
+
user_templates_dir = File.expand_path(user_templates_dir, Dir.pwd)
|
|
33
|
+
end
|
|
34
|
+
includes << user_templates_dir if File.directory?(user_templates_dir)
|
|
35
|
+
end
|
|
36
|
+
includes << gem_templates_dir
|
|
37
|
+
|
|
38
|
+
Sourcerer::Jekyll.initialize_liquid_runtime
|
|
39
|
+
|
|
40
|
+
# Bootstrap an ephemeral Jekyll site context for template processing with full feature support
|
|
41
|
+
site = Sourcerer::Jekyll::Bootstrapper.fake_site(
|
|
42
|
+
includes_load_paths: includes,
|
|
43
|
+
plugin_dirs: plugin_dirs)
|
|
44
|
+
|
|
45
|
+
# Parse template and render with comprehensive register context
|
|
46
|
+
tpl = ::Liquid::Template.parse(input)
|
|
47
|
+
rendered = tpl.render(
|
|
48
|
+
vars,
|
|
49
|
+
registers: {
|
|
50
|
+
site: site,
|
|
51
|
+
file_system: Sourcerer::Jekyll::Liquid::FileSystem.new(includes.first),
|
|
52
|
+
includes_load_paths: includes,
|
|
53
|
+
releasehx_debug: true
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
raise "Template rendering failed:\n#{rendered}" if rendered.include?('Liquid error')
|
|
57
|
+
|
|
58
|
+
rendered
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
require 'tilt'
|
|
5
|
+
require_relative 'template_ops'
|
|
6
|
+
|
|
7
|
+
module ReleaseHx
|
|
8
|
+
# Provides file writing operations and template processing utilities for output generation.
|
|
9
|
+
#
|
|
10
|
+
# The WriteOps module handles safe file creation with directory management, template resolution
|
|
11
|
+
# with user/gem fallback paths, and content post-processing for consistent output formatting.
|
|
12
|
+
module WriteOps
|
|
13
|
+
# Safely writes content to a file, creating parent directories as needed.
|
|
14
|
+
#
|
|
15
|
+
# Ensures parent directory structure exists before writing and provides
|
|
16
|
+
# logging feedback for successful file creation operations.
|
|
17
|
+
#
|
|
18
|
+
# @param path [String] The target file path for writing.
|
|
19
|
+
# @param content [String] The content to write to the file.
|
|
20
|
+
# @return [Symbol] Returns :written to indicate successful completion.
|
|
21
|
+
def self.safe_write path, content
|
|
22
|
+
dirname = File.dirname(path)
|
|
23
|
+
FileUtils.mkdir_p(dirname)
|
|
24
|
+
File.write(path, content)
|
|
25
|
+
ReleaseHx.logger.info "Wrote file: #{path}"
|
|
26
|
+
:written
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Establishes the absolute path to the gem's bundled template directory.
|
|
30
|
+
#
|
|
31
|
+
# @return [String] The path to the default gem template directory.
|
|
32
|
+
def self.gem_template_root
|
|
33
|
+
File.expand_path('../rhyml/templates', __dir__)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Resolves template file path using hierarchical search with user directory precedence.
|
|
37
|
+
#
|
|
38
|
+
# Searches for templates first in user-configured directories, then falls back to
|
|
39
|
+
# gem-bundled templates, providing clear error messages when templates are not found.
|
|
40
|
+
#
|
|
41
|
+
# @param name [String] The template filename to locate.
|
|
42
|
+
# @param config [ReleaseHx::Configuration] Configuration object containing template path settings.
|
|
43
|
+
# @return [String] The absolute path to the located template file.
|
|
44
|
+
# @raise [StandardError] If the template cannot be found in any search path.
|
|
45
|
+
def self.resolve_template_path name, config
|
|
46
|
+
user_dir = config.dig('paths', 'templates_dir')
|
|
47
|
+
fallback_dir = gem_template_root
|
|
48
|
+
|
|
49
|
+
search_paths = []
|
|
50
|
+
search_paths << File.expand_path(name, user_dir) if user_dir
|
|
51
|
+
search_paths << File.join(fallback_dir, name)
|
|
52
|
+
|
|
53
|
+
found = search_paths.find { |p| File.exist?(p) }
|
|
54
|
+
|
|
55
|
+
return found if found
|
|
56
|
+
|
|
57
|
+
raise "Template not found: #{name} (searched #{search_paths.join(' , ')})"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Processes a Liquid template file with variable substitution and content post-processing.
|
|
61
|
+
#
|
|
62
|
+
# Loads and renders templates using the TemplateOps system, then applies configurable
|
|
63
|
+
# post-processing rules such as excess line removal for consistent output formatting.
|
|
64
|
+
#
|
|
65
|
+
# @param template_path [String] The absolute path to the template file.
|
|
66
|
+
# @param vars [Hash] Variable context for template rendering.
|
|
67
|
+
# @param config [ReleaseHx::Configuration] Configuration object with processing settings.
|
|
68
|
+
# @return [String] The fully processed template output.
|
|
69
|
+
def self.process_template template_path, vars, config
|
|
70
|
+
template_content = File.read(template_path)
|
|
71
|
+
includes_load_paths = []
|
|
72
|
+
user_templates_dir = config.dig('paths', 'templates_dir')
|
|
73
|
+
gem_templates_dir = File.expand_path('../../rhyml/templates', __dir__)
|
|
74
|
+
|
|
75
|
+
if user_templates_dir
|
|
76
|
+
unless Pathname.new(user_templates_dir).absolute?
|
|
77
|
+
user_templates_dir = File.expand_path(user_templates_dir, Dir.pwd)
|
|
78
|
+
end
|
|
79
|
+
includes_load_paths << user_templates_dir
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
includes_load_paths << gem_templates_dir
|
|
83
|
+
|
|
84
|
+
rendered = TemplateOps.render_liquid_string(template_content, vars, config)
|
|
85
|
+
|
|
86
|
+
# Apply configurable post-processing for line spacing control
|
|
87
|
+
blank_lines = config.dig('modes', 'remove_excess_lines')
|
|
88
|
+
if blank_lines.zero?
|
|
89
|
+
rendered = rendered.gsub(/\n{2,}/, "\n")
|
|
90
|
+
elsif blank_lines
|
|
91
|
+
rendered = rendered.gsub(/\n{#{blank_lines + 1},}/, "\n" * (blank_lines + 1))
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
rendered
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Appends new changes to an existing YAML file using template-based formatting.
|
|
98
|
+
#
|
|
99
|
+
# Generates properly formatted YAML content for new changes using a template,
|
|
100
|
+
# then appends it to the specified file while maintaining file structure.
|
|
101
|
+
#
|
|
102
|
+
# @param yaml_file_path [String] The path to the target YAML file for appending.
|
|
103
|
+
# @param new_changes [Array<ReleaseHx::RHYML::Change>] Array of Change objects to append.
|
|
104
|
+
# @param config [ReleaseHx::Configuration] Configuration object for template processing.
|
|
105
|
+
# @return [void]
|
|
106
|
+
def self.append_changes_to_yaml yaml_file_path, new_changes, config
|
|
107
|
+
# Generate properly formatted YAML content using template
|
|
108
|
+
context = {
|
|
109
|
+
'changes' => new_changes.map(&:to_h),
|
|
110
|
+
'config' => config
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
template_path = resolve_template_path('rhyml-change-append.yaml.liquid', config)
|
|
114
|
+
append_content = process_template(template_path, { 'vars' => context }, config)
|
|
115
|
+
|
|
116
|
+
# Append to existing file maintaining structure
|
|
117
|
+
File.open(yaml_file_path, 'a') do |file|
|
|
118
|
+
file.write(append_content)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
ReleaseHx.logger.debug "Appended #{new_changes.size} changes to #{yaml_file_path}"
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# GitHub REST API Client Configuration
|
|
2
|
+
# For GitHub v3 API - Issues endpoint
|
|
3
|
+
name: github
|
|
4
|
+
desc: |
|
|
5
|
+
GitHub REST API client for fetching issues and pull requests.
|
|
6
|
+
Supports GitHub v3 API with token-based authentication.
|
|
7
|
+
Includes dynamic milestone resolution.
|
|
8
|
+
|
|
9
|
+
auth:
|
|
10
|
+
mode: header
|
|
11
|
+
header: Authorization
|
|
12
|
+
format: "Bearer {{ env[auth.key_env] | default: env.GITHUB_TOKEN }}"
|
|
13
|
+
|
|
14
|
+
# GitHub API endpoint template
|
|
15
|
+
# Variables available: project, version, env (flattened from api config)
|
|
16
|
+
href: "{{ env.GITHUB_API_HOST | default: 'https://api.github.com' }}/repos/{{ project | default: env.GITHUB_REPO }}/issues"
|
|
17
|
+
query_type: key_value
|
|
18
|
+
|
|
19
|
+
# Dynamic resolution for milestone titles -> numbers
|
|
20
|
+
resolutions:
|
|
21
|
+
milestone:
|
|
22
|
+
endpoint: "/repos/{{ project | default: env.GITHUB_REPO }}/milestones"
|
|
23
|
+
query_param: "milestone" # which param in main query needs resolution
|
|
24
|
+
lookup_field: "title" # field to match against (milestone title)
|
|
25
|
+
return_field: "number" # field to use as resolved value
|
|
26
|
+
match_value: "{{ version }}" # what to match (template)
|
|
27
|
+
|
|
28
|
+
# Structured query parameters - much cleaner than query_string!
|
|
29
|
+
# The {{ milestone }} template will be resolved to the milestone number
|
|
30
|
+
query_params:
|
|
31
|
+
milestone: "{{ milestone }}" # will be resolved to milestone number
|
|
32
|
+
state: "closed"
|
|
33
|
+
sort: "updated"
|
|
34
|
+
direction: "desc"
|
|
35
|
+
per_page: 100
|
|
36
|
+
|
|
37
|
+
# Pagination settings using GitHub's Link header
|
|
38
|
+
pagination:
|
|
39
|
+
param: page
|
|
40
|
+
page_size_param: per_page
|
|
41
|
+
page_size: 100
|
|
42
|
+
max_pages: 10
|
|
43
|
+
|
|
44
|
+
# Response format expectation
|
|
45
|
+
response_format: json
|
|
46
|
+
root_issues_path: "."
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# GitLab REST API Client Configuration
|
|
2
|
+
# For GitLab v4 API - Issues endpoint
|
|
3
|
+
name: gitlab
|
|
4
|
+
desc: |
|
|
5
|
+
GitLab REST API client for fetching issues and merge requests.
|
|
6
|
+
Supports GitLab v4 API with token-based authentication.
|
|
7
|
+
|
|
8
|
+
auth:
|
|
9
|
+
mode: header
|
|
10
|
+
header: PRIVATE-TOKEN
|
|
11
|
+
format: "{{ env.GITLAB_TOKEN }}"
|
|
12
|
+
|
|
13
|
+
# GitLab API endpoint template
|
|
14
|
+
# Variables available: origin.project, version, env
|
|
15
|
+
href: "{{ env.GITLAB_API_HOST | default: 'https://gitlab.com' }}/api/v4/projects/{{ origin.project | default: env.GITLAB_PROJECT_ID }}/issues"
|
|
16
|
+
query_type: key_value
|
|
17
|
+
|
|
18
|
+
# Query parameters for filtering issues
|
|
19
|
+
query_string: |
|
|
20
|
+
milestone={{ version }}&state=closed&per_page=100
|
|
21
|
+
|
|
22
|
+
# Pagination settings using GitLab's offset pagination
|
|
23
|
+
pagination:
|
|
24
|
+
param: page
|
|
25
|
+
page_size_param: per_page
|
|
26
|
+
page_size: 100
|
|
27
|
+
max_pages: 10
|
|
28
|
+
|
|
29
|
+
# Response format expectation
|
|
30
|
+
response_format: json
|
|
31
|
+
root_issues_path: "."
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Jira REST API Client Configuration
|
|
2
|
+
# Jira Cloud API with ADF (Atlassian Document Format) support
|
|
3
|
+
name: jira
|
|
4
|
+
desc: |
|
|
5
|
+
Jira Cloud REST API client for fetching issues and creating release notes.
|
|
6
|
+
Supports ADF format in description and custom fields.
|
|
7
|
+
|
|
8
|
+
auth:
|
|
9
|
+
mode: basic
|
|
10
|
+
header: Authorization
|
|
11
|
+
format: "Basic {{ env.JIRA_USERNAME | append: ':' | append: env.JIRA_API_TOKEN | base64 }}"
|
|
12
|
+
|
|
13
|
+
# Jira REST API endpoint template
|
|
14
|
+
# Variables available: origin.project, version, env
|
|
15
|
+
href: "{{ env.JIRA_HOST | default: 'https://your-domain.atlassian.net' }}/rest/api/3/search/jql"
|
|
16
|
+
|
|
17
|
+
# Query parameters for filtering issues
|
|
18
|
+
query_params:
|
|
19
|
+
jql: 'project="{{ origin.project | default: env.JIRA_PROJECT }}" AND fixVersion="{{ version }}" ORDER BY updated DESC'
|
|
20
|
+
fields: "key,summary,description,issuetype,components,labels,fixVersions,assignee"
|
|
21
|
+
|
|
22
|
+
# Pagination settings for large result sets
|
|
23
|
+
pagination:
|
|
24
|
+
param: startAt
|
|
25
|
+
page_size_param: maxResults
|
|
26
|
+
page_size: 50
|
|
27
|
+
max_pages: 20
|
|
28
|
+
|
|
29
|
+
# Response format expectation
|
|
30
|
+
response_format: json
|
|
31
|
+
root_issues_path: issues
|