releasehx 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/README.adoc +363 -330
  3. data/build/docs/_config.yml +1 -0
  4. data/build/docs/_release_index.adoc +3 -2
  5. data/build/docs/config-reference.adoc +197 -10
  6. data/build/docs/config-reference.json +56 -7
  7. data/build/docs/index.adoc +315 -59
  8. data/build/docs/landing.adoc +1 -1
  9. data/build/docs/manpage.adoc +2 -2
  10. data/build/docs/release-procedure.adoc +365 -0
  11. data/build/docs/release-procedure.html +87 -0
  12. data/build/docs/releasehx.1 +17 -5
  13. data/build/docs/sample-config.yml +14 -7
  14. data/lib/releasehx/cli.rb +5 -2
  15. data/lib/releasehx/configuration.rb +0 -1
  16. data/lib/releasehx/generated.rb +1 -1
  17. data/lib/releasehx/mcp/assets/agent-config-guide.md +1 -1
  18. data/lib/releasehx/mcp/assets/config-def.yml +122 -6
  19. data/lib/releasehx/mcp/assets/config-reference.adoc +197 -10
  20. data/lib/releasehx/mcp/assets/config-reference.json +56 -7
  21. data/lib/releasehx/mcp/assets/sample-config.yml +14 -7
  22. data/lib/releasehx/mcp/server.rb +0 -1
  23. data/lib/releasehx/ops/enrich_ops.rb +161 -55
  24. data/lib/releasehx/ops/template_ops.rb +1 -1
  25. data/lib/releasehx/rhyml/adapter.rb +0 -3
  26. data/lib/releasehx/rhyml/templates/bootstrap-overrides.css +15 -0
  27. data/lib/releasehx/rhyml/templates/changelog.adoc.liquid +2 -0
  28. data/lib/releasehx/rhyml/templates/changelog.html.liquid +6 -4
  29. data/lib/releasehx/rhyml/templates/changelog.md.liquid +1 -0
  30. data/lib/releasehx/rhyml/templates/embedded.css.liquid +263 -0
  31. data/lib/releasehx/rhyml/templates/entry.adoc.liquid +1 -0
  32. data/lib/releasehx/rhyml/templates/entry.html.liquid +21 -20
  33. data/lib/releasehx/rhyml/templates/entry.md.liquid +15 -21
  34. data/lib/releasehx/rhyml/templates/head-parser.liquid +6 -2
  35. data/lib/releasehx/rhyml/templates/header.liquid +13 -4
  36. data/lib/releasehx/rhyml/templates/history.html.liquid +152 -33
  37. data/lib/releasehx/rhyml/templates/metadata-entry.adoc.liquid +83 -38
  38. data/lib/releasehx/rhyml/templates/metadata-entry.html.liquid +60 -1
  39. data/lib/releasehx/rhyml/templates/metadata-entry.md.liquid +65 -113
  40. data/lib/releasehx/rhyml/templates/metadata-note.adoc.liquid +83 -38
  41. data/lib/releasehx/rhyml/templates/metadata-note.html.liquid +59 -22
  42. data/lib/releasehx/rhyml/templates/metadata-note.md.liquid +68 -23
  43. data/lib/releasehx/rhyml/templates/note.html.liquid +25 -19
  44. data/lib/releasehx/rhyml/templates/note.md.liquid +44 -26
  45. data/lib/releasehx/rhyml/templates/release-notes.adoc.liquid +2 -0
  46. data/lib/releasehx/rhyml/templates/release-notes.html.liquid +6 -4
  47. data/lib/releasehx/rhyml/templates/release-notes.md.liquid +1 -0
  48. data/lib/releasehx/rhyml/templates/release.adoc.liquid +2 -0
  49. data/lib/releasehx/rhyml/templates/release.md.liquid +8 -7
  50. data/lib/releasehx/rhyml/templates/rhyml-change.yaml.liquid +36 -36
  51. data/lib/releasehx/rhyml/templates/wrapper.html.liquid +103 -0
  52. data/lib/releasehx/sgyml/helpers.rb +0 -2
  53. data/lib/releasehx/transforms/adf_to_markdown.rb +1 -1
  54. data/lib/releasehx/version.rb +0 -2
  55. data/lib/releasehx.rb +2 -2
  56. data/specs/data/config-def.yml +122 -6
  57. metadata +48 -25
  58. data/build/docs/schemagraphy_readme.html +0 -0
  59. data/build/docs/sourcerer_readme.html +0 -46
  60. data/lib/schemagraphy/attribute_resolver.rb +0 -48
  61. data/lib/schemagraphy/cfgyml/definition.rb +0 -90
  62. data/lib/schemagraphy/cfgyml/doc_builder.rb +0 -52
  63. data/lib/schemagraphy/cfgyml/path_reference.rb +0 -24
  64. data/lib/schemagraphy/data_query/json_pointer.rb +0 -42
  65. data/lib/schemagraphy/loader.rb +0 -59
  66. data/lib/schemagraphy/regexp_utils.rb +0 -235
  67. data/lib/schemagraphy/safe_expression.rb +0 -189
  68. data/lib/schemagraphy/schema_utils.rb +0 -124
  69. data/lib/schemagraphy/tag_utils.rb +0 -32
  70. data/lib/schemagraphy/templating.rb +0 -104
  71. data/lib/schemagraphy.rb +0 -17
  72. data/lib/sourcerer/builder.rb +0 -120
  73. data/lib/sourcerer/jekyll/bootstrapper.rb +0 -78
  74. data/lib/sourcerer/jekyll/liquid/file_system.rb +0 -74
  75. data/lib/sourcerer/jekyll/liquid/filters.rb +0 -215
  76. data/lib/sourcerer/jekyll/liquid/tags.rb +0 -44
  77. data/lib/sourcerer/jekyll/monkeypatches.rb +0 -73
  78. data/lib/sourcerer/jekyll.rb +0 -26
  79. data/lib/sourcerer/plaintext_converter.rb +0 -75
  80. data/lib/sourcerer/templating.rb +0 -190
  81. data/lib/sourcerer.rb +0 -322
@@ -1,46 +0,0 @@
1
- <div class="paragraph">
2
- <p>This gem introduces a module called Sourcerer, by which AsciiDoc files can be parsed and their contents harvested for use in the application build.
3
- The module also handles Liquid template processing with enhanced attribute resolution capabilities.</p>
4
- </div>
5
- <div class="admonitionblock note">
6
- <table>
7
- <tr>
8
- <td class="icon">
9
- <div class="title">Note</div>
10
- </td>
11
- <td class="content">
12
- Sourcerer is intended to be spun off as its own gem once it successfully proves the concept in this gem.
13
- It will probably be called <em>AsciiSourcerer</em> and may replace an older and unmaintained utility of mine called LiquiDoc.
14
- </td>
15
- </tr>
16
- </table>
17
- </div>
18
- <div class="paragraph">
19
- <p>It is invoked in the Rakefile, establishing global namespaces:</p>
20
- </div>
21
- <div class="ulist">
22
- <ul>
23
- <li>
24
- <p><code>ReleaseHx::ATTRIBUTES[:globals]</code> (derived from this <code>README.adoc</code> file)</p>
25
- </li>
26
- <li>
27
- <p><code>ReleaseHx.read_built_snippet(:&lt;name&gt;)</code> (such as <code>:helpscreen</code>)</p>
28
- </li>
29
- </ul>
30
- </div>
31
- <div class="paragraph">
32
- <p>The Sourcerer module also generates files like <code>build/docs/manpage.adoc</code>, which generates the formatted terminal manual, using content from <code>build/docs/config-reference.adoc</code> and this README (<code>tags="cli_options"</code>, for instance).</p>
33
- </div>
34
- <div class="paragraph">
35
- <p>It also generates an AsciiDoc-formatted configuration reference, a machine-readable JSON reference, and a sample config using the <code>specs/data/config-def.yml</code> file.
36
- The Sourcerer system now supports <strong>attribute resolution</strong> from AsciiDoc source files, enabling templates to access README attributes during rendering, ensuring configuration documentation reflects actual default values.</p>
37
- </div>
38
- <div class="paragraph">
39
- <p>Sourcerer also performs basic testing of select commands in the ASciiDoc file that have been assigned a <code>testable</code> role.</p>
40
- </div>
41
- <div class="paragraph">
42
- <p>This is mostly just showing off what Sourcerer can do, and hopefully setting into habit some best practices for my more complicated apps.</p>
43
- </div>
44
- <div class="paragraph">
45
- <p>Sourcerer is also where integration with Jekyll&#8217;s extensions of Liquid occurs, bringing ReleaseHx&#8217;s templating powers closely in line with how Jekyll&#8217;s work, as described in <a href="#custom-liquid">[custom-liquid]</a>.</p>
46
- </div>
@@ -1,48 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SchemaGraphy
4
- # The AttributeResolver module provides methods for resolving AsciiDoc attribute references
5
- # within a schema hash. It is used to substitute placeholders like `\{attribute_name}`
6
- # with actual values.
7
- module AttributeResolver
8
- # Recursively walk a schema Hash and resolve `\{attribute_name}` references
9
- # in 'dflt' values.
10
- #
11
- # @param schema [Hash] The schema or definition hash to process.
12
- # @param attrs [Hash] The key-value pairs from AsciiDoc attributes to use for resolution.
13
- # @return [Hash] The schema with resolved attributes.
14
- def self.resolve_attributes! schema, attrs
15
- case schema
16
- when Hash
17
- schema.transform_values! do |value|
18
- if value.is_a?(Hash)
19
- if value.key?('dflt') && value['dflt'].is_a?(String)
20
- value['dflt'] = resolve_attribute_reference(value['dflt'], attrs)
21
- end
22
- resolve_attributes!(value, attrs)
23
- else
24
- value
25
- end
26
- end
27
- end
28
- schema
29
- end
30
-
31
- # Replace `\{attribute_name}` patterns with corresponding values from the attrs hash.
32
- #
33
- # @param value [String] The string to process.
34
- # @param attrs [Hash] The attributes to use for resolution.
35
- # @return [String] The processed string with attribute references replaced.
36
- def self.resolve_attribute_reference value, attrs
37
- # Handle \{attribute_name} references
38
- if value.match?(/\{[^}]+\}/)
39
- value.gsub(/\{([^}]+)\}/) do |match|
40
- attr_name = ::Regexp.last_match(1)
41
- attrs[attr_name] || match # Keep original if no matching attribute
42
- end
43
- else
44
- value
45
- end
46
- end
47
- end
48
- end
@@ -1,90 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative '../loader'
4
- require_relative '../schema_utils'
5
-
6
- module SchemaGraphy
7
- # A module for handling CFGYML, a schema-driven configuration system.
8
- module CFGYML
9
- # Represents a configuration definition loaded from a schema file.
10
- # It provides methods for accessing defaults and rendering documentation.
11
- class Definition
12
- # @return [Hash] The loaded schema hash.
13
- attr_reader :schema
14
-
15
- # @return [Hash] The attributes used for resolving placeholders in the schema.
16
- attr_reader :attributes
17
-
18
- # @param schema_path [String] The path to the schema YAML file.
19
- # @param attrs [Hash] A hash of attributes for placeholder resolution.
20
- def initialize schema_path, attrs = {}
21
- @schema = Loader.load_yaml_with_attributes(schema_path, attrs)
22
- @attributes = attrs
23
- end
24
-
25
- # Extract default values from the loaded schema.
26
- # @return [Hash] A hash of default values.
27
- def defaults
28
- SchemaUtils.crawl_defaults(@schema)
29
- end
30
-
31
- # Get the search paths for templates.
32
- # @return [Array<String>] An array of template paths.
33
- def template_paths
34
- @template_paths ||= [
35
- File.join(File.dirname(__FILE__), '..', 'templates', 'cfgyml'),
36
- *additional_template_paths
37
- ]
38
- end
39
-
40
- # Render a configuration reference or sample in the specified format.
41
- #
42
- # @param format [Symbol] The output format (`:adoc` or `:yaml`).
43
- # @return [String] The rendered output.
44
- # @raise [ArgumentError] if the format is unsupported.
45
- def render_reference format = :adoc
46
- template = case format
47
- when :adoc
48
- 'config-reference.adoc.liquid'
49
- when :yaml
50
- 'sample-config.yaml.liquid'
51
- else
52
- raise ArgumentError, "Unsupported format: #{format}"
53
- end
54
-
55
- render_template(template)
56
- end
57
-
58
- private
59
-
60
- # Render a template using the Liquid engine.
61
- def render_template template_name
62
- template_path = find_template(template_name)
63
- raise "Template not found: #{template_name}" unless template_path
64
-
65
- require 'liquid'
66
- template_content = File.read(template_path)
67
- template = Liquid::Template.parse(template_content)
68
-
69
- template.render(
70
- 'config_def' => @schema,
71
- 'attrs' => @attributes)
72
- end
73
-
74
- # Find a template file in the configured template paths.
75
- def find_template name
76
- template_paths.each do |path|
77
- file = File.join(path, name)
78
- return file if File.exist?(file)
79
- end
80
- nil
81
- end
82
-
83
- # Provides an extension point for subclasses to add more template paths.
84
- def additional_template_paths
85
- # Can be overridden by subclasses
86
- []
87
- end
88
- end
89
- end
90
- end
@@ -1,52 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'json'
4
-
5
- module SchemaGraphy
6
- module CFGYML
7
- # Builds documentation-friendly CFGYML references for machine consumption.
8
- module DocBuilder
9
- module_function
10
-
11
- def call schema, options = {}
12
- pretty = options.fetch(:pretty, true)
13
- data = reference_hash(schema)
14
- pretty ? JSON.pretty_generate(data) : JSON.generate(data)
15
- end
16
-
17
- def reference_hash schema
18
- {
19
- 'format' => 'releasehx-config-reference',
20
- 'version' => 1,
21
- 'properties' => build_properties(schema['properties'], [])
22
- }
23
- end
24
-
25
- def build_properties properties, path
26
- return {} unless properties.is_a?(Hash)
27
-
28
- properties.each_with_object({}) do |(key, definition), acc|
29
- next unless definition.is_a?(Hash)
30
-
31
- current_path = path + [key]
32
- entry = build_entry(current_path, definition)
33
- children = build_properties(definition['properties'], current_path)
34
- entry['properties'] = children unless children.empty?
35
- acc[key] = entry
36
- end
37
- end
38
-
39
- def build_entry path, definition
40
- entry = {
41
- 'path' => path.join('.'),
42
- 'desc' => definition['desc'],
43
- 'docs' => definition['docs'],
44
- 'type' => definition['type'],
45
- 'templating' => definition['templating'],
46
- 'default' => definition.key?('dflt') ? definition['dflt'] : nil
47
- }
48
- entry.compact
49
- end
50
- end
51
- end
52
- end
@@ -1,24 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'json'
4
-
5
- module SchemaGraphy
6
- module CFGYML
7
- # Loads and queries a JSON config reference using JSON Pointer.
8
- class PathReference
9
- def initialize data
10
- @data = data
11
- end
12
-
13
- def self.load path
14
- new(JSON.parse(File.read(path)))
15
- end
16
-
17
- def get pointer
18
- SchemaGraphy::DataQuery::JSONPointer.resolve(@data, pointer)
19
- end
20
- end
21
-
22
- Reference = PathReference
23
- end
24
- end
@@ -1,42 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SchemaGraphy
4
- module DataQuery
5
- # Resolves JSON Pointer queries against a Hash or Array.
6
- module JSONPointer
7
- module_function
8
-
9
- def resolve data, pointer
10
- return data if pointer.nil? || pointer == ''
11
- raise ArgumentError, "Invalid JSON Pointer: #{pointer}" unless pointer.start_with?('/')
12
-
13
- tokens = pointer.split('/')[1..]
14
- tokens.reduce(data) do |current, token|
15
- key = unescape(token)
16
- resolve_token(current, key, pointer)
17
- end
18
- end
19
-
20
- def resolve_token current, key, pointer
21
- case current
22
- when Array
23
- index = Integer(key, 10)
24
- current.fetch(index)
25
- when Hash
26
- return current.fetch(key) if current.key?(key)
27
- return current.fetch(key.to_sym) if current.key?(key.to_sym)
28
-
29
- raise KeyError, "JSON Pointer not found: #{pointer}"
30
- else
31
- raise KeyError, "JSON Pointer not found: #{pointer}"
32
- end
33
- rescue ArgumentError, IndexError, KeyError
34
- raise KeyError, "JSON Pointer not found: #{pointer}"
35
- end
36
-
37
- def unescape token
38
- token.gsub('~1', '/').gsub('~0', '~')
39
- end
40
- end
41
- end
42
- end
@@ -1,59 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'yaml'
4
- require 'psych'
5
- require_relative 'attribute_resolver'
6
-
7
- module SchemaGraphy
8
- # The Loader class provides methods for loading YAML files while preserving
9
- # custom tags and resolving attribute references.
10
- class Loader
11
- # Load a YAML file and resolve AsciiDoc attribute references like `\{attribute_name}`.
12
- #
13
- # @param path [String] The path to the YAML file.
14
- # @param attrs [Hash] The AsciiDoc attributes to use for resolution.
15
- # @return [Hash] The loaded YAML data with attributes resolved.
16
- def self.load_yaml_with_attributes path, attrs = {}
17
- raw_data = load_yaml_with_tags(path)
18
- AttributeResolver.resolve_attributes!(raw_data, attrs)
19
- raw_data
20
- end
21
-
22
- # Load a YAML file, preserving any custom tags (e.g., `!foo`).
23
- # Custom tags are attached to the data structure.
24
- #
25
- # @param path [String] The path to the YAML file.
26
- # @return [Hash] The loaded YAML data with custom tags attached.
27
- def self.load_yaml_with_tags path
28
- return {} if File.empty?(path)
29
-
30
- data = Psych.load_file(path, aliases: true, permitted_classes: [Date, Time])
31
- ast = Psych.parse_file(path)
32
- attach_tags(ast.root, data)
33
- data
34
- end
35
-
36
- # Recursively attach YAML tags to the loaded data structure for template processing.
37
- #
38
- # @param node [Psych::Nodes::Node] The current AST node.
39
- # @param data [Object] The data corresponding to the current node.
40
- # @api private
41
- def self.attach_tags node, data
42
- return unless node.is_a?(Psych::Nodes::Mapping)
43
-
44
- node.children.each_slice(2) do |key_node, val_node|
45
- key = key_node.value
46
-
47
- if val_node.respond_to?(:tag) && val_node.tag && data[key].is_a?(String)
48
- normalized_tag = val_node.tag.sub(/^!+/, '').sub(/^.*:/, '')
49
- data[key] = {
50
- 'value' => data[key],
51
- '__tag__' => normalized_tag
52
- }
53
- elsif data[key].is_a?(Hash)
54
- attach_tags(val_node, data[key])
55
- end
56
- end
57
- end
58
- end
59
- end
@@ -1,235 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'to_regexp'
4
-
5
- module SchemaGraphy
6
- # A utility module for robustly parsing and using regular expressions.
7
- # It handles various formats, including literals and plain strings,
8
- # and provides helpers for extracting captured content.
9
- module RegexpUtils
10
- module_function
11
-
12
- # Parse a regex pattern string using the `to_regexp` gem for robust parsing.
13
- # Handles `/pattern/flags`, `%r{pattern}flags`, and plain text formats.
14
- #
15
- # @example
16
- # parse_pattern("/^hello.*$/im")
17
- # # => { pattern: "^hello.*$", flags: "im", regexp: /^hello.*$/im, options: 6 }
18
- #
19
- # @example
20
- # parse_pattern("hello world")
21
- # # => { pattern: "hello world", flags: "", regexp: /hello world/, options: 0 }
22
- #
23
- # @example
24
- # parse_pattern("hello world", "i")
25
- # # => { pattern: "hello world", flags: "i", regexp: /hello world/i, options: 1 }
26
- #
27
- # @param input [String] The input string, e.g., "/pattern/flags" or "plain pattern".
28
- # @param default_flags [String] Default flags to apply if none are specified (default: "").
29
- # @return [Hash, nil] A hash with `:pattern`, `:flags`, `:regexp`, and `:options`, or `nil`.
30
- def parse_pattern input, default_flags = ''
31
- return nil if input.nil? || input.to_s.strip.empty?
32
-
33
- input_str = input.to_s.strip
34
-
35
- # Remove surrounding quotes that might come from YAML parsing
36
- clean_input = input_str.gsub(/^["']|["']$/, '')
37
-
38
- # Manual parsing for /pattern/flags format (common in YAML configs)
39
- if clean_input =~ %r{^/(.+)/([a-z]*)$}
40
- pattern_str = Regexp.last_match(1)
41
- flags_str = Regexp.last_match(2)
42
- options = flags_to_options(flags_str)
43
-
44
- begin
45
- regexp_obj = Regexp.new(pattern_str, options)
46
-
47
- return {
48
- pattern: pattern_str,
49
- flags: flags_str,
50
- regexp: regexp_obj,
51
- options: options
52
- }
53
- rescue RegexpError => e
54
- raise RegexpError, "Invalid regex pattern '#{input}': #{e.message}"
55
- end
56
- end
57
-
58
- # Heuristic to detect if it's a Regexp literal
59
- is_literal = clean_input.start_with?('%r{')
60
-
61
- if is_literal
62
- # Try to parse as regex literal using to_regexp
63
- begin
64
- regexp_obj = clean_input.to_regexp(detect: true)
65
-
66
- # Extract pattern and flags from the compiled regexp
67
- pattern_str = regexp_obj.source
68
- flags_str = extract_flags_from_regexp(regexp_obj)
69
-
70
- {
71
- pattern: pattern_str,
72
- flags: flags_str,
73
- regexp: regexp_obj,
74
- options: regexp_obj.options
75
- }
76
- rescue RegexpError => e
77
- # Malformed literal is an error
78
- raise RegexpError, "Invalid regex literal '#{input}': #{e.message}"
79
- end
80
- else
81
- # Treat as plain pattern string with default flags
82
- flags_str = default_flags.to_s
83
- options = flags_to_options(flags_str)
84
-
85
- begin
86
- regexp_obj = Regexp.new(clean_input, options)
87
-
88
- {
89
- pattern: clean_input,
90
- flags: flags_str,
91
- regexp: regexp_obj,
92
- options: options
93
- }
94
- rescue RegexpError => e
95
- raise RegexpError, "Invalid regex pattern '#{input}': #{e.message}"
96
- end
97
- end
98
- end
99
-
100
- # @note Not yet implemented.
101
- # Future enhancement to parse structured pattern definitions from a Hash.
102
- # @param pattern_hash [Hash] A hash with 'pattern' and 'flags' keys.
103
- # @raise [NotImplementedError] Always raises this error.
104
- def parse_structured_pattern pattern_hash
105
- # TODO: Implement structured pattern parsing
106
- # pattern_hash should have 'pattern' and 'flags' keys
107
- # flags can be string or array
108
- raise NotImplementedError, 'Structured pattern parsing not yet implemented'
109
- end
110
-
111
- # @note Not yet implemented.
112
- # Future enhancement to parse custom YAML tags for regular expressions.
113
- # @param tagged_input [String] The input string with a YAML tag.
114
- # @param tag_type [Symbol] The type of tag, e.g., `:literal` or `:pattern`.
115
- # @raise [NotImplementedError] Always raises this error.
116
- def parse_tagged_pattern tagged_input, tag_type
117
- # TODO: Implement custom YAML tag parsing
118
- # tag_type would be :literal or :pattern
119
- raise NotImplementedError, 'Tagged pattern parsing not yet implemented'
120
- end
121
-
122
- # Convert a flags string (ex: "im") to a Regexp options integer.
123
- #
124
- # @param flags [String] String containing regex flags.
125
- # @return [Integer] Regexp options integer.
126
- def flags_to_options flags
127
- options = 0
128
- flags = flags.to_s
129
-
130
- options |= Regexp::IGNORECASE if flags.include?('i')
131
- options |= Regexp::MULTILINE if flags.include?('m')
132
- options |= Regexp::EXTENDED if flags.include?('x')
133
-
134
- # NOTE: 'g' (global) and 'o' (once) are not standard Ruby flags
135
- # encoding flags ('n', 'e', 's', 'u') are handled by to_regexp
136
-
137
- options
138
- end
139
-
140
- # Extract a flags string from a compiled Regexp object.
141
- #
142
- # @param regexp [Regexp] A compiled regexp object.
143
- # @return [String] String representation of the flags (e.g., "im").
144
- def extract_flags_from_regexp regexp
145
- flags = ''
146
- flags += 'i' if regexp.options.anybits?(Regexp::IGNORECASE)
147
- flags += 'm' if regexp.options.anybits?(Regexp::MULTILINE)
148
- flags += 'x' if regexp.options.anybits?(Regexp::EXTENDED)
149
- flags
150
- end
151
-
152
- # Create a Regexp object from a pattern string and explicit flags.
153
- #
154
- # @param pattern [String] The regex pattern (without delimiters).
155
- # @param flags [String] The flags string (ex: "im").
156
- # @return [Regexp] The compiled Regexp object.
157
- def create_regexp pattern, flags = ''
158
- options = flags_to_options(flags)
159
- Regexp.new(pattern, options)
160
- end
161
-
162
- # Extract content using named or positional capture groups.
163
- #
164
- # @param text [String] The text to match against.
165
- # @param pattern_info [Hash] The hash result from `parse_pattern`.
166
- # @param capture_name [String] The name of the capture group to extract (optional).
167
- # @return [String, nil] The extracted text, or `nil` if no match is found.
168
- def extract_capture text, pattern_info, capture_name = nil
169
- return nil unless text && pattern_info
170
-
171
- regexp = pattern_info[:regexp]
172
- match = text.match(regexp)
173
-
174
- return nil unless match
175
-
176
- if capture_name && match.names.include?(capture_name.to_s)
177
- # Extract named capture group
178
- match[capture_name.to_s]
179
- elsif match.captures.any?
180
- # Extract first capture group
181
- match[1]
182
- else
183
- # Return the entire match
184
- match[0]
185
- end
186
- end
187
-
188
- # Extract all named capture groups as a hash or positional captures as an array.
189
- #
190
- # @param text [String] The text to match against.
191
- # @param pattern_info [Hash] The hash result from `parse_pattern`.
192
- # @return [Hash, Array, nil] A hash of named captures, an array of positional captures, or `nil`.
193
- def extract_all_captures text, pattern_info
194
- return nil unless text && pattern_info
195
-
196
- regexp = pattern_info[:regexp]
197
- match = text.match(regexp)
198
-
199
- return nil unless match
200
-
201
- if match.names.any?
202
- # Return hash of named captures
203
- match.names.each_with_object({}) do |name, captures|
204
- captures[name] = match[name]
205
- end
206
- else
207
- # Return array of positional captures
208
- match.captures
209
- end
210
- end
211
-
212
- # A convenience method that combines parsing and a single extraction.
213
- #
214
- # @param text [String] The text to match against.
215
- # @param pattern_input [String] The pattern string (with or without /flags/).
216
- # @param capture_name [String] Name of the capture group to extract (optional).
217
- # @param default_flags [String] Default flags if the pattern has no flags.
218
- # @return [String, nil] The extracted text, or `nil` if no match is found.
219
- def parse_and_extract text, pattern_input, capture_name = nil, default_flags = ''
220
- pattern_info = parse_pattern(pattern_input, default_flags)
221
- extract_capture(text, pattern_info, capture_name)
222
- end
223
-
224
- # A convenience method that combines parsing and extraction of all captures.
225
- #
226
- # @param text [String] The text to match against.
227
- # @param pattern_input [String] The pattern string (with or without /flags/).
228
- # @param default_flags [String] Default flags if the pattern has no flags.
229
- # @return [Hash, Array, nil] All captured content, or `nil` if no match is found.
230
- def parse_and_extract_all text, pattern_input, default_flags = ''
231
- pattern_info = parse_pattern(pattern_input, default_flags)
232
- extract_all_captures(text, pattern_info)
233
- end
234
- end
235
- end