Module: ReleaseHx::EnrichOps

Defined in:
lib/releasehx/ops/enrich_ops.rb

Overview

Provides rich-text output generation from Release objects and draft source files.

The EnrichOps module handles the conversion of Release data and various draft formats

(YAML, Markdown, AsciiDoc) into rich output formats (HTML, PDF) using appropriate
rendering engines and template processing.

Class Method Summary collapse

Class Method Details

.convert_asciidoc(file_path_or_content, format:, outpath:, config: nil, force: nil) ⇒ String

Converts AsciiDoc content or files to rich-text formats using configured engines.

Accepts either file paths or raw content strings as input. Selects appropriate converter engine based on config.conversions.engines.html/pdf settings with intelligent defaults:

  • AsciiDoc → HTML: asciidoctor-html5s (default)

  • AsciiDoc → PDF: asciidoctor-pdf (default)

HTML output can be wrapped with Bootstrap CSS when config.modes.html_wrap is enabled.

Parameters:

  • file_path_or_content (String)

    File path or raw AsciiDoc content.

  • format (Symbol)

    The target output format (:html or :pdf).

  • outpath (String)

    The target output file path.

  • config (ReleaseHx::Configuration) (defaults to: nil)

    Configuration for engines and wrapping options.

  • force (Boolean) (defaults to: nil)

    Whether to overwrite existing files.

Returns:

  • (String)

    The path to the generated output file.



110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/releasehx/ops/enrich_ops.rb', line 110

def self.convert_asciidoc file_path_or_content, format:, outpath:, config: nil, force: nil
  # Determine if input is file path or raw content
  is_file = file_path_or_content.is_a?(String) && File.exist?(file_path_or_content)
  content = is_file ? File.read(file_path_or_content) : file_path_or_content

  case format.to_sym
  when :html
    engine = resolve_engine(format: :html, source_format: :asciidoc, config: config)
    html_fragment = convert_with_engine(content, engine: engine, format: :html)

    # Wrap HTML with Bootstrap styling if enabled
    enriched = if config && config.dig('modes', 'html_wrap') != false
                 ReleaseHx.logger.debug('Applying wrapper to AsciiDoc-derived HTML')
                 wrap_html(html_fragment, config)
               else
                 html_fragment
               end
  when :pdf
    engine = resolve_engine(format: :pdf, source_format: :asciidoc, config: config)
    convert_with_engine(content, engine: engine, format: :pdf, outpath: outpath)
    return outpath
  else
    raise "Unsupported format for AsciiDoc: #{format}"
  end

  # Prevent accidental file overwrites for HTML output
  if File.exist?(outpath) && !force
    ReleaseHx.logger.warn("File exists: #{outpath}. Use --force to overwrite.")
    return outpath
  end
  WriteOps.safe_write(outpath, enriched)
  outpath
end

.convert_with_engine(content, engine:, format:, outpath: nil) ⇒ String

Converts content using the specified engine.

rubocop:disable Lint/UnusedMethodArgument

Parameters:

  • content (String)

    Source content to convert

  • engine (String)

    Engine name (e.g., ‘asciidoctor-html5s’, ‘asciidoctor-pdf’)

  • format (Symbol)

    Output format (:html or :pdf)

  • outpath (String, nil) (defaults to: nil)

    Output file path (required for PDF)

Returns:

  • (String)

    Converted content (for HTML) or output path (for PDF)



280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
# File 'lib/releasehx/ops/enrich_ops.rb', line 280

def self.convert_with_engine content, engine:, format:, outpath: nil
  case engine
  when 'asciidoctor-html5', 'asciidoctor-html5s'
    require 'asciidoctor'
    backend = engine.split('-').last # 'html5' or 'html5s'

    if backend == 'html5s'
      begin
        require 'asciidoctor-html5s'
        ReleaseHx.logger.debug('Using asciidoctor-html5s backend for semantic HTML5')
      rescue LoadError
        ReleaseHx.logger.warn('asciidoctor-html5s not available, falling back to html5')
        backend = 'html5'
      end
    end

    Asciidoctor.convert(content, safe: :safe, backend: backend)

  when 'asciidoctor-pdf'
    require 'asciidoctor'
    require 'asciidoctor-pdf'
    ReleaseHx.logger.debug('Using asciidoctor-pdf for PDF generation')
    Asciidoctor.convert(content, to_file: outpath, safe: :safe, backend: 'pdf')
    outpath

  when 'asciidoctor-web-pdf'
    require 'asciidoctor'
    require 'asciidoctor-web-pdf'
    ReleaseHx.logger.debug('Using asciidoctor-web-pdf for PDF generation')
    Asciidoctor.convert(content, to_file: outpath, safe: :safe, backend: 'web-pdf')
    outpath

  when 'kramdown'
    require 'kramdown'
    ReleaseHx.logger.debug('Using Kramdown for HTML conversion')
    Kramdown::Document.new(content).to_html

  when 'pandoc'
    # Future implementation - would shell out to pandoc
    raise 'Pandoc engine not yet implemented'

  else
    raise "Unsupported engine: #{engine}"
  end
end

.enrich_from_file(file_path, format:, config:, outpath: nil, force: false) ⇒ String

Generates rich-text output from source draft files of various formats.

Automatically detects the input file type and applies the appropriate conversion

strategy, supporting YAML (via RHYML) and AsciiDoc source formats.

Parameters:

  • file_path (String)

    The path to the source draft file.

  • format (Symbol)

    The target output format (:html or :pdf).

  • config (ReleaseHx::Configuration)

    The application configuration.

  • outpath (String, nil) (defaults to: nil)

    The explicit output file path; inferred if nil.

  • force (Boolean) (defaults to: false)

    Whether to overwrite existing output files.

Returns:

  • (String)

    The path to the generated output file.



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/releasehx/ops/enrich_ops.rb', line 69

def self.enrich_from_file file_path, format:, config:, outpath: nil, force: false
  raise "File not found: #{file_path}" unless File.exist?(file_path)

  file_ext = File.extname(file_path).downcase
  outpath ||= file_path.sub(/\.[^.]+$/, ".#{format}")

  # Prevent accidental file overwrites
  if File.exist?(outpath) && !force
    ReleaseHx.logger.warn("File exists: #{outpath}. Use --force to overwrite.")
    return outpath
  end

  # Route to appropriate conversion method based on source format
  case file_ext
  when '.yml', '.yaml'
    # RHYML/YAML files: load as Release object and use Liquid templates
    release = load_rhyml_from_yaml(file_path, config: config)
    enrich_from_rhyml(release: release, config: config, format: format, outpath: outpath, force: true)
  when '.adoc'
    # AsciiDoc files: use native converter
    convert_asciidoc(file_path, format: format, config: config, outpath: outpath, force: true)
  else
    raise "Unsupported source file format: #{file_ext}"
  end
end

.enrich_from_rhyml(release:, config:, format:, outpath: nil, force: false) ⇒ String

Generates rich-text output directly from a Release object.

Processes Release data through Liquid templates to produce HTML or PDF output. HTML generation uses direct template rendering, while PDF generation creates

intermediate AsciiDoc content before conversion.

Parameters:

  • release (ReleaseHx::RHYML::Release)

    The Release object to enrich.

  • config (ReleaseHx::Configuration)

    The application configuration.

  • format (Symbol)

    The output format (:html or :pdf).

  • outpath (String, nil) (defaults to: nil)

    The explicit output file path; auto-resolved if nil.

  • force (Boolean) (defaults to: false)

    Whether to overwrite existing output files.

Returns:

  • (String)

    The path to the generated output file.



22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/releasehx/ops/enrich_ops.rb', line 22

def self.enrich_from_rhyml release:, config:, format:, outpath: nil, force: false
  # Use proper config-based output path resolution if not provided
  outpath ||= resolve_enrich_path(release.code, format, config)

  if File.exist?(outpath) && !force
    ReleaseHx.logger.warn("File exists: #{outpath}. Use --force to overwrite.")
    return outpath
  end

  ReleaseHx.logger.debug("Enriching release #{release.code} to #{outpath} (format: #{format.to_s.upcase})")

  case format
  when :html
    # Direct Liquid template rendering to HTML
    html_content = DraftOps.process_template_content(release: release, config: config, format: :html)

    # Wrap HTML with Bootstrap styling if enabled
    enriched = if config && config.dig('modes', 'html_wrap') != false
                 ReleaseHx.logger.debug('Applying HTML wrapper')
                 wrap_html(html_content, config)
               else
                 html_content
               end

    WriteOps.safe_write(outpath, enriched)
  when :pdf
    # Two-stage process: RHYML → AsciiDoc → PDF
    asciidoc_content = DraftOps.process_template_content(release: release, config: config, format: :adoc)
    convert_asciidoc(asciidoc_content, format: :pdf, outpath: outpath)
  else
    raise ArgumentError, "Unsupported enrich format: #{format}"
  end

  outpath
end

.load_rhyml_from_yaml(file_path, config:) ⇒ ReleaseHx::RHYML::Release

Loads RHYML data from a YAML file and creates a Release object.

Processes YAML files containing either a single Release or a collection of Releases, extracting the first Release for processing.

rubocop:disable Lint/UnusedMethodArgument

Parameters:

  • file_path (String)

    Path to the YAML file containing RHYML data.

  • config (ReleaseHx::Configuration)

    Reserved for future use.

Returns:



153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/releasehx/ops/enrich_ops.rb', line 153

def self.load_rhyml_from_yaml file_path, config:
  # NOTE: config parameter is currently unused but kept for API consistency
  # Load RHYML data using SchemaGraphy for tag processing
  rhyml_data = SchemaGraphy::Loader.load_yaml_with_tags(file_path)

  # Extract Release data: first item from 'releases' array or single Release hash
  release_data = rhyml_data['releases'] ? rhyml_data['releases'].first : rhyml_data

  # Construct Release object with keyword arguments
  ReleaseHx::RHYML::Release.new(
    code: release_data['code'],
    date: release_data['date'],
    hash: release_data['hash'],
    memo: release_data['memo'],
    changes: release_data['changes'] || [])
end

.parse_framework_spec(spec) ⇒ Array<String, String>

Parses framework specification string into name and version.

Parameters:

  • spec (String)

    Framework specification like ‘bootstrap5’ or ‘bootstrap:5.3.0’

Returns:

  • (Array<String, String>)

    Tuple of [framework_name, framework_version]



226
227
228
229
230
231
232
233
234
235
236
237
238
# File 'lib/releasehx/ops/enrich_ops.rb', line 226

def self.parse_framework_spec spec
  case spec.to_s
  when /^(.+):(.+)$/
    # Format: "bootstrap:5.3.0"
    [::Regexp.last_match(1), ::Regexp.last_match(2)]
  when 'bootstrap5'
    ['bootstrap', '5.3.0']
  when 'bootstrap4'
    ['bootstrap', '4.6.2']
  else
    ['bare', nil]
  end
end

.resolve_engine(format:, source_format:, config: nil) ⇒ String

Resolves the appropriate conversion engine based on format and source.

Applies intelligent defaults based on source format:

  • AsciiDoc → HTML: asciidoctor-html5s

  • AsciiDoc → PDF: asciidoctor-pdf

  • Markdown → HTML: kramdown

  • Markdown → PDF: pandoc

Parameters:

  • format (Symbol)

    Output format (:html or :pdf)

  • source_format (Symbol)

    Source format (:asciidoc or :markdown)

  • config (Hash) (defaults to: nil)

    Configuration hash

Returns:

  • (String)

    Engine name



252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
# File 'lib/releasehx/ops/enrich_ops.rb', line 252

def self.resolve_engine format:, source_format:, config: nil
  # Check for explicit engine configuration
  explicit_engine = config&.dig('conversions', 'engines', format.to_s)
  return explicit_engine if explicit_engine

  # Apply intelligent defaults based on source format
  case [source_format, format]
  when %i[asciidoc html]
    'asciidoctor-html5s'
  when %i[asciidoc pdf]
    'asciidoctor-pdf'
  when %i[markdown html]
    'kramdown'
  when %i[markdown pdf]
    'pandoc'
  else
    raise "Unsupported conversion: #{source_format}#{format}"
  end
end

.resolve_enrich_path(version, format, config) ⇒ String

Resolves the output file path using configuration-based templated filenames.

Constructs the full output path by combining configured directories and processing template variables in the filename pattern.

Parameters:

  • version (String)

    The release version code for template substitution.

  • format (Symbol)

    The output format for file extension determination.

  • config (ReleaseHx::Configuration)

    Configuration containing path templates.

Returns:

  • (String)

    The resolved absolute output file path.



180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# File 'lib/releasehx/ops/enrich_ops.rb', line 180

def self.resolve_enrich_path version, format, config
  # Extract path configuration from config
  output_dir = config.dig('paths', 'output_dir')
  enrich_dir = config.dig('paths', 'enrich_dir')
  filename_template = config.dig('paths', 'enrich_filename')

  # Build template context for filename processing
  context = {
    'version' => version,
    'format_ext' => format.to_s
  }

  # Process templated filename and construct full path
  filename = SchemaGraphy::Templating.render_field_if_template(filename_template, context)
  File.join(output_dir, enrich_dir, filename.strip)
end

.wrap_html(html_fragment, config) ⇒ String

Wraps an HTML fragment with Bootstrap CSS and semantic HTML structure.

Takes a raw HTML fragment and wraps it with proper DOCTYPE, head section with Bootstrap CDN link, and body tag. Uses the wrapper.html.liquid template for markup generation.

Parameters:

  • html_fragment (String)

    The raw HTML content to wrap.

  • config (ReleaseHx::Configuration)

    Configuration object for title and settings.

Returns:

  • (String)

    Complete HTML document with Bootstrap styling.



205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
# File 'lib/releasehx/ops/enrich_ops.rb', line 205

def self.wrap_html html_fragment, config
  # Extract and parse framework setting
  framework_spec = config.dig('history', 'html_framework') || 'bare'
  framework_name, framework_version = parse_framework_spec(framework_spec)

  template_path = WriteOps.resolve_template_path('wrapper.html.liquid', config)

  template_vars = {
    'config' => config,
    'content' => html_fragment,
    'framework' => framework_name,
    'framework_version' => framework_version
  }

  WriteOps.process_template(template_path, template_vars, config)
end