metanorma-plugin-datastruct 0.3.9 → 0.3.11

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8b1a1aff3342ca19d9bad692c321719cc381a1d74d98b2827c23bb1da7f6d2de
4
- data.tar.gz: 3c5f60aba996ac9e1f8924439e60d453c847da10189025917334fbcb200d1d85
3
+ metadata.gz: 35c1de600ac66e34295b75d0bfd4fee0328ea75901be90148928d347844e474b
4
+ data.tar.gz: 4752f9aa448c03e63af8a95f244013ddf5fe84b65b36b29b4a5e72621e1ef44f
5
5
  SHA512:
6
- metadata.gz: 4e69b0df873ef7d4bb9db7fa7d998c8eb5ee943d14d153e472368087ee55afbcd62db101f3791f530f5cc7f7619eff5514ef92a66a8ea8b2ea5a7ef58e94a66e
7
- data.tar.gz: f3746f6e9d3e917dd2ac0e861eb837d8fc6d665699737e41244c427b54112863dd29e5134b23ea3f3b2d7b228f93840ab29935c5da08c471638e7a75e1268ef3
6
+ metadata.gz: 0302bce3b5cc508e540ce7b897710ddec93de2592939f90fec3578a65f59aa47a326c7e73ac5af3ceea489cade3b48c8deea02950eab2920d3f9995acddf67ec
7
+ data.tar.gz: f34daa0895c9f5d404e18ef8a094f05c9b14348c2fccb815a5f9eb952587ac3e69b70f13381f0d3427c6c8da38e3bff9c5e912fb87a453b74337467a4b49b621
@@ -2,6 +2,9 @@
2
2
  # See https://github.com/metanorma/cimas
3
3
  name: rake
4
4
 
5
+ permissions:
6
+ contents: read
7
+
5
8
  on:
6
9
  push:
7
10
  branches: [ master, main ]
@@ -2,6 +2,11 @@
2
2
  # See https://github.com/metanorma/cimas
3
3
  name: release
4
4
 
5
+ permissions:
6
+ contents: write
7
+ packages: write
8
+ id-token: write
9
+
5
10
  on:
6
11
  workflow_dispatch:
7
12
  inputs:
@@ -22,4 +27,3 @@ jobs:
22
27
  secrets:
23
28
  rubygems-api-key: ${{ secrets.METANORMA_CI_RUBYGEMS_API_KEY }}
24
29
  pat_token: ${{ secrets.METANORMA_CI_PAT_TOKEN }}
25
-
data/CLAUDE.md ADDED
@@ -0,0 +1,60 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Project Overview
6
+
7
+ `metanorma-plugin-datastruct` is a Ruby gem that provides Asciidoctor preprocessors for the Metanorma document processing pipeline. It allows Metanorma AsciiDoc documents to load data from YAML/JSON files and interpolate that data into the document using Liquid templates.
8
+
9
+ ## Build & Test Commands
10
+
11
+ ```bash
12
+ bundle install # install dependencies
13
+ bundle exec rake # run the full test suite (default rake task = rspec)
14
+ bundle exec rspec # run all specs
15
+ bundle exec rspec spec/metanorma/plugin/datastruct/macros_yaml2text_spec.rb # run a single spec file
16
+ bundle exec rspec spec/metanorma/plugin/datastruct/macros_yaml2text_spec.rb:75 # run a single test by line number
17
+ ```
18
+
19
+ ## Architecture
20
+
21
+ ### Core preprocessors
22
+
23
+ Both are Asciidoctor `Preprocessor` extensions that intercept `[yaml2text,...]` and `[json2text,...]` block macros:
24
+
25
+ - **`Yaml2TextPreprocessor`** (`lib/metanorma/plugin/datastruct/yaml2_text_preprocessor.rb`) — parses YAML data files via `YAML.safe_load`
26
+ - **`Json2TextPreprocessor`** (`lib/metanorma/plugin/datastruct/json2_text_preprocessor.rb`) — parses JSON data files via `JSON.parse`
27
+
28
+ Both inherit from **`BaseStructuredTextPreprocessor`** (`base_structured_text_preprocessor.rb`), which handles:
29
+ - Scanning lines for `[yaml2text|json2text,<file>,<context>]` block headers
30
+ - Collecting block content and handling nested blocks (via `with_yaml_nested_context` / `with_json_nested_context`)
31
+ - Transforming AsciiDoc-style `{variable}` interpolation into Liquid `{{variable}}` syntax
32
+ - Rendering the combined template through the Liquid engine
33
+
34
+ ### Liquid extensions
35
+
36
+ Custom Liquid tags and filters registered at load time in `base_structured_text_preprocessor.rb`:
37
+
38
+ - **`KeyIterator`** (`lib/liquid/custom_blocks/key_iterator.rb`) — `{keyiterator}` tag, iterates over Hash keys or arrays with an index variable
39
+ - **`WithYamlNestedContext`** / **`WithJsonNestedContext`** — `{with_yaml_nested_context}` / `{with_json_nested_context}` tags, load an additional data file into the Liquid context mid-template (enables nested file references)
40
+ - **`CustomFilters.values`** (`lib/liquid/custom_filters/values.rb`) — `values` filter, exposes `Hash#values` in Liquid templates
41
+
42
+ ### Template syntax transformation
43
+
44
+ `BaseStructuredTextPreprocessor.transform_line_liquid` converts custom AsciiDoc-like syntax to Liquid before rendering:
45
+ - `{context.*,item,EOF}` → `{% keyiterator context, item %}` ... `{EOF}` → `{% endkeyiterator %}`
46
+ - Single-brace `{var}` → double-brace `{{var}}`
47
+ - `{var.#}` → `index` (loop index)
48
+ - `{var + N}` / `{var - N}` → Liquid `plus`/`minus` filters
49
+ - `{var.values[X]}` → uses the custom `values` filter
50
+
51
+ ### Test setup
52
+
53
+ Tests use `metanorma-standoc` to process full AsciiDoc documents through the Metanorma pipeline, then compare the resulting XML output. The spec helper registers datastruct's preprocessors **before** standoc's extension group to avoid lutaml intercepting the same block names. Shared examples in `spec/support/` run the same test matrix for both YAML and JSON.
54
+
55
+ ## Key Dependencies
56
+
57
+ - `asciidoctor` (~> 2.0.0) — document processing framework
58
+ - `liquid` (>= 4) — template engine
59
+ - `relaton-cli`, `isodoc` — Metanorma ecosystem dependencies
60
+ - Dev dependencies: `metanorma`, `metanorma-standoc` for integration testing
data/Gemfile CHANGED
@@ -1,13 +1,20 @@
1
- Encoding.default_external = Encoding::UTF_8
2
- Encoding.default_internal = Encoding::UTF_8
3
-
4
1
  source "https://rubygems.org"
5
- git_source(:github) { |repo| "https://github.com/#{repo}" }
6
2
 
3
+ # Specify your gem's dependencies in metanorma-plugin-datastruct.gemspec
7
4
  gemspec
8
5
 
9
- begin
10
- eval_gemfile("Gemfile.devel")
11
- rescue StandardError
12
- nil
13
- end
6
+ eval_gemfile("Gemfile.devel") if File.exist?("Gemfile.devel")
7
+
8
+ gem "canon"
9
+ gem "metanorma", github: "metanorma/metanorma", branch: "main"
10
+ gem "metanorma-standoc", github: "metanorma/metanorma-standoc", branch: "main"
11
+ gem "rake", "~> 13"
12
+ gem "rspec"
13
+ gem "rubocop"
14
+ gem "rubocop-performance"
15
+ gem "rubocop-rake"
16
+ gem "rubocop-rspec"
17
+ gem "simplecov"
18
+ gem "timecop"
19
+ gem "vcr"
20
+ gem "webmock"
data/Gemfile.devel ADDED
@@ -0,0 +1,2 @@
1
+ gem "metanorma-plugin-lutaml", git: "https://github.com/metanorma/metanorma-plugin-lutaml", branch: "main"
2
+ gem "ogc-gml", "~> 1.1.0"
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "liquid"
4
+
5
+ module Liquid
6
+ module CustomBlocks
7
+ class KeyIterator < Block
8
+ def initialize(tag_name, markup, tokens)
9
+ super
10
+ @context_name, @var_name = markup.split(",").map(&:strip)
11
+ end
12
+
13
+ def render(context)
14
+ collection = context[@context_name]
15
+ items = enumerable_items(collection)
16
+ result = +""
17
+ items.each.with_index do |item, index|
18
+ context["index"] = index
19
+ context[@var_name] = item
20
+ result << super
21
+ end
22
+ result
23
+ end
24
+
25
+ private
26
+
27
+ def enumerable_items(collection)
28
+ case collection
29
+ when Hash then collection.keys
30
+ else collection
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "liquid"
4
+
5
+ module Liquid
6
+ module CustomBlocks
7
+ class NestedContextBlock < Block
8
+ def initialize(tag_name, markup, tokens)
9
+ super
10
+ @context_file_variable, @context_name = markup.split(",").map(&:strip)
11
+ end
12
+
13
+ def render(context)
14
+ context_file = context[@context_file_variable].to_s.strip
15
+ context[@context_name] = load_content(context_file)
16
+ super
17
+ end
18
+
19
+ private
20
+
21
+ def load_content(_file_path)
22
+ raise NotImplementedError
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require_relative "nested_context_block"
5
+
6
+ module Liquid
7
+ module CustomBlocks
8
+ class WithJsonNestedContext < NestedContextBlock
9
+ private
10
+
11
+ def load_content(file_path)
12
+ JSON.parse(File.read(file_path, encoding: "utf-8"))
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+ require_relative "nested_context_block"
5
+
6
+ module Liquid
7
+ module CustomBlocks
8
+ class WithYamlNestedContext < NestedContextBlock
9
+ private
10
+
11
+ def load_content(file_path)
12
+ YAML.safe_load(
13
+ File.read(file_path, encoding: "utf-8"),
14
+ permitted_classes: [Date, Time],
15
+ aliases: true,
16
+ )
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Liquid
4
+ module CustomFilters
5
+ def values(list)
6
+ return list unless list.respond_to?(:values)
7
+ list.values
8
+ end
9
+ end
10
+ end
@@ -1,56 +1,60 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "set"
3
4
  require "liquid"
4
5
  require "asciidoctor"
5
6
  require "asciidoctor/reader"
6
- require_relative "liquid/custom_blocks/key_iterator"
7
- require_relative "liquid/custom_filters/values"
8
- require_relative "liquid/custom_filters/replace_regex"
9
- require_relative "liquid/custom_filters/loadfile"
7
+ require "liquid/custom_blocks/key_iterator"
8
+ require "liquid/custom_blocks/with_yaml_nested_context"
9
+ require "liquid/custom_blocks/with_json_nested_context"
10
+ require "liquid/custom_filters/values"
11
+ require_relative "path_resolver"
10
12
  require_relative "source_extractor"
11
13
 
12
- module Asciidoctor
13
- class PreprocessorNoIfdefsReader < PreprocessorReader
14
- def preprocess_conditional_directive(_keyword, _target, _delimiter, _text)
15
- false # decline to resolve idefs
16
- end
17
- end
18
- end
19
-
20
14
  module Metanorma
21
15
  module Plugin
22
16
  module Datastruct
23
- # Base class for processing structured data blocks(yaml, json)
24
17
  class BaseStructuredTextPreprocessor <
25
18
  Asciidoctor::Extensions::Preprocessor
19
+ include PathResolver
20
+
26
21
  BLOCK_START_REGEXP = /\{(.+?)\.\*,(.+),(.+)\}/.freeze
27
22
  BLOCK_END_REGEXP = /\A\{[A-Z]+\}\z/.freeze
28
- LOAD_FILE_REGEXP = /{% assign (.*) = (.*) \| load_file %}/.freeze
23
+ NESTED_CONTEXT_SUFFIX = {
24
+ "yaml2text" => "yaml",
25
+ "json2text" => "json",
26
+ "data2text" => "yaml",
27
+ }.freeze
28
+
29
+ def initialize(config = {})
30
+ super
31
+ register_liquid_extensions
32
+ end
29
33
 
30
34
  def process(document, reader)
31
- r = ::Asciidoctor::PreprocessorNoIfdefsReader
32
- .new document, reader.lines
33
- input_lines = r.readlines
34
- Metanorma::Plugin::Datastruct::SourceExtractor.extract(
35
- document,
36
- input_lines,
37
- )
38
- Asciidoctor::PreprocessorNoIfdefsReader
39
- .new(document, processed_lines(document, input_lines.to_enum))
35
+ input_lines = reader.readlines.to_enum
36
+ Asciidoctor::Reader.new(processed_lines(document, input_lines))
40
37
  end
41
38
 
42
39
  protected
43
40
 
44
41
  def content_from_file(_document, _file_path)
45
- raise ArgumentError, "Implement `content_from_file` in your class"
46
- end
47
-
48
- def content_from_anchor(_document, _file_path)
49
- raise ArgumentError, "Implement `content_from_anchor` in your class"
42
+ raise NotImplementedError,
43
+ "Implement `content_from_file` in your subclass"
50
44
  end
51
45
 
52
46
  private
53
47
 
48
+ def register_liquid_extensions
49
+ env = Liquid::Environment.default
50
+ env.register_tag("keyiterator", Liquid::CustomBlocks::KeyIterator)
51
+ env.register_tag("with_yaml_nested_context",
52
+ Liquid::CustomBlocks::WithYamlNestedContext)
53
+ env.register_tag("with_json_nested_context",
54
+ Liquid::CustomBlocks::WithJsonNestedContext)
55
+ env.register_filter(Liquid::CustomFilters)
56
+ end
57
+
54
58
  def processed_lines(document, input_lines)
55
59
  result = []
56
60
  loop do
@@ -59,19 +63,9 @@ module Metanorma
59
63
  result
60
64
  end
61
65
 
62
- def relative_file_path(document, file_path)
63
- docfile_directory = File.dirname(
64
- document.attributes["docfile"] || ".",
65
- )
66
- document
67
- .path_resolver
68
- .system_path(file_path, docfile_directory)
69
- end
70
-
71
66
  def process_text_blocks(document, input_lines)
72
67
  line = input_lines.next
73
- block_match = line.match(/^\[#{config[:block_name]},(.+?),(.+?)\]/) ||
74
- line.match(/^\[#{config[:block_name]},(.+?)\]/)
68
+ block_match = line.match(/^\[#{config[:block_name]},(.+?),(.+?)\]/)
75
69
  return [line] if block_match.nil?
76
70
 
77
71
  end_mark = input_lines.next
@@ -82,127 +76,95 @@ module Metanorma
82
76
  block_match)
83
77
  end
84
78
 
85
- def collect_internal_block_lines(document, input_lines, end_mark) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
79
+ def collect_internal_block_lines(document, input_lines, end_mark)
86
80
  current_block = []
81
+ nested_marks = Set.new
87
82
  while (block_line = input_lines.next) != end_mark
88
- if block_line.match?(LOAD_FILE_REGEXP)
89
- load_file_match = block_line.match(LOAD_FILE_REGEXP)
90
-
91
- # Add parent folder as argument to loadfile filter
92
- block_line = "{% assign #{load_file_match[1]} = "\
93
- "#{load_file_match[2]} | loadfile: " \
94
- "\"#{document.attributes['docdir']}\" %}"
83
+ if nested_match = block_line
84
+ .match(/^\[#{config[:block_name]},(.+?),(.+?)\]/)
85
+ current_block
86
+ .push(*nested_context_tag(document,
87
+ nested_match[1],
88
+ nested_match[2]).split("\n"))
89
+ next nested_marks.add(input_lines.next)
95
90
  end
96
91
 
92
+ if nested_marks.include?(block_line)
93
+ current_block.push("{% endwith_#{nested_context_suffix}_nested_context %}")
94
+ next nested_marks.delete(block_line)
95
+ end
97
96
  current_block.push(block_line)
98
97
  end
99
98
  current_block
100
99
  end
101
100
 
102
- def data_file_type
103
- @config[:block_name].split("2").first
101
+ def nested_context_suffix
102
+ NESTED_CONTEXT_SUFFIX[config[:block_name]]
104
103
  end
105
104
 
106
- def parse_template(document, current_block, block_match) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
107
- transformed_liquid_lines = current_block.map do |x|
108
- transform_line_liquid(x)
109
- end
110
-
111
- contexts = if block_match[1].include?("=")
112
- content_from_multiple_contexts(
113
- document, block_match
114
- )
115
- elsif block_match[1].start_with?("#")
116
- {
117
- block_match[2].strip =>
118
- content_from_anchor(document, block_match[1][1..-1]),
119
- }
120
- else
121
- {
122
- block_match[2].strip =>
123
- content_from_file(document, block_match[1]),
124
- }
125
- end
105
+ def nested_context_tag(document, file_path, context_name)
106
+ absolute_file_path = relative_file_path(document, file_path)
107
+ <<~TEMPLATE
108
+ {% capture nested_file_path %}
109
+ #{absolute_file_path}
110
+ {% endcapture %}
111
+ {% with_#{nested_context_suffix}_nested_context nested_file_path, #{context_name} %}
112
+ TEMPLATE
113
+ end
126
114
 
115
+ def parse_template(document, current_block, block_match)
116
+ transformed_liquid_lines = current_block
117
+ .map(&method(:transform_line_liquid))
118
+ context_items = content_from_file(document, block_match[1])
127
119
  parse_context_block(document: document,
128
120
  context_lines: transformed_liquid_lines,
129
- contexts: contexts)
121
+ context_items: context_items,
122
+ context_name: block_match[2].strip)
130
123
  rescue StandardError => e
131
- ::Metanorma::Util.log("Failed to parse #{config[:block_name]} \
132
- block: #{e.message}", :error)
124
+ document.logger
125
+ .warn("Failed to parse #{config[:block_name]} \
126
+ block: #{e.message}")
133
127
  []
134
128
  end
135
129
 
136
- def content_from_multiple_contexts(document, block_match) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
137
- contexts = {}
138
- (1..block_match.size - 1).each do |i|
139
- context_and_paths = block_match[i].strip
140
- context_and_paths.split(",").each do |context_and_path|
141
- context_and_path.strip!
142
- context_name, path = context_and_path.split("=")
143
- context_items = content_from_file(document, path)
144
- contexts[context_name] = context_items
145
- end
146
- end
147
-
148
- contexts
149
- end
150
-
151
- def transform_line_liquid(line) # rubocop:disable Metrics/MethodLength
152
- if line.match?(BLOCK_START_REGEXP)
153
- line.gsub!(BLOCK_START_REGEXP, '{% keyiterator \1, \2 %}')
154
- end
155
-
156
- if line.strip.match?(BLOCK_END_REGEXP)
157
- line.gsub!(BLOCK_END_REGEXP, "{% endkeyiterator %}")
158
- end
130
+ def transform_line_liquid(line)
131
+ line = line.gsub(BLOCK_START_REGEXP, '{% keyiterator \1, \2 %}') if line.match?(BLOCK_START_REGEXP)
132
+ line = line.gsub(BLOCK_END_REGEXP, "{% endkeyiterator %}") if line.strip.match?(BLOCK_END_REGEXP)
159
133
  line
160
134
  .gsub(/(?<!{){(?!%)([^{}]+)(?<!%)}(?!})/, '{{\1}}')
161
- .gsub(/[a-z\.]+\#/, "index")
135
+ .gsub(/[a-z.]+\#/, "index")
162
136
  .gsub(/{{(.+)\s+\+\s+(\d+)\s*?}}/, '{{ \1 | plus: \2 }}')
163
137
  .gsub(/{{(.+)\s+-\s+(\d+)\s*?}}/, '{{ \1 | minus: \2 }}')
164
138
  .gsub(/{{(.+)\.values(.*?)}}/,
165
139
  '{% assign custom_value = \1 | values %}{{custom_value\2}}')
166
140
  end
167
141
 
168
- def parse_context_block(context_lines:, contexts:, document:)
142
+ def parse_context_block(context_lines:,
143
+ context_items:,
144
+ context_name:,
145
+ document:)
169
146
  render_result, errors = render_liquid_string(
170
147
  template_string: context_lines.join("\n"),
171
- contexts: contexts,
148
+ context_items: context_items,
149
+ context_name: context_name,
172
150
  document: document,
173
151
  )
174
152
  notify_render_errors(document, errors)
175
153
  render_result.split("\n")
176
154
  end
177
155
 
178
- def render_liquid_string(template_string:, contexts:, document:) # rubocop:disable Metrics/MethodLength
179
- liquid_template = ::Liquid::Template
180
- .parse(template_string, environment: create_liquid_environment)
181
-
182
- # Allow includes for the template
156
+ def render_liquid_string(template_string:, context_items:,
157
+ context_name:, document:)
158
+ liquid_template = Liquid::Template.parse(template_string)
183
159
  liquid_template.registers[:file_system] =
184
160
  ::Liquid::LocalFileSystem.new(relative_file_path(document, ""))
185
-
186
- rendered_string = liquid_template.render(
187
- contexts,
188
- strict_variables: false,
189
- error_mode: :warn,
190
- )
161
+ rendered_string = liquid_template
162
+ .render(context_name => context_items,
163
+ strict_variables: true,
164
+ error_mode: :warn)
191
165
  [rendered_string, liquid_template.errors]
192
166
  end
193
167
 
194
- def create_liquid_environment
195
- ::Liquid::Environment.new.tap do |liquid_env|
196
- liquid_env.register_tag(
197
- "keyiterator",
198
- ::Metanorma::Plugin::Datastruct::Liquid::CustomBlocks::KeyIterator, # rubocop:disable Layout/LineLength
199
- )
200
- liquid_env.register_filter(
201
- ::Metanorma::Plugin::Datastruct::Liquid::CustomFilters,
202
- )
203
- end
204
- end
205
-
206
168
  def notify_render_errors(document, errors)
207
169
  errors.each do |error_obj|
208
170
  document
@@ -2,19 +2,21 @@
2
2
 
3
3
  require "json"
4
4
  require "yaml"
5
+ require_relative "path_resolver"
5
6
 
6
7
  module Metanorma
7
8
  module Plugin
8
9
  module Datastruct
9
10
  module Content
11
+ include PathResolver
12
+
10
13
  protected
11
14
 
12
- # https://ruby-doc.org/stdlib-2.5.1/libdoc/psych/rdoc/Psych.html#method-c-safe_load
13
- def yaml_content_from_file(resolved_file_path) # rubocop:disable Metrics/MethodLength
15
+ def yaml_content_from_file(resolved_file_path)
14
16
  unless File.exist?(resolved_file_path)
15
17
  ::Metanorma::Util.log(
16
18
  "YAML file referenced in [yaml2text] block not found: " \
17
- "#{resolved_file_path}", :error
19
+ "#{resolved_file_path}", :error,
18
20
  )
19
21
  return
20
22
  end
@@ -27,62 +29,30 @@ module Metanorma
27
29
  )
28
30
  end
29
31
 
30
- def yaml_content_from_anchor(document, anchor)
31
- YAML.safe_load(
32
- document.attributes["source_blocks"][anchor],
33
- permitted_classes: [Date, Time, Symbol],
34
- permitted_symbols: [],
35
- aliases: true,
36
- )
37
- end
38
-
39
32
  def json_content_from_file(resolved_file_path)
40
33
  JSON.parse(File.read(resolved_file_path, encoding: "UTF-8"))
41
34
  end
42
35
 
43
- def json_content_from_anchor(document, anchor)
44
- JSON.parse(document.attributes["source_blocks"][anchor])
45
- end
46
-
47
- def content_from_file(document, file_path)
36
+ def load_file_content(document, file_path)
48
37
  resolved_file_path = relative_file_path(document, file_path)
49
- load_content_from_file(resolved_file_path)
38
+ load_content_by_extension(resolved_file_path)
50
39
  end
51
40
 
52
- def load_content_from_file(resolved_file_path)
41
+ private
42
+
43
+ def load_content_by_extension(resolved_file_path)
53
44
  unless File.exist?(resolved_file_path)
54
45
  ::Metanorma::Util
55
46
  .log("Failed to load content from file: #{resolved_file_path}",
56
47
  :error)
57
48
  end
58
49
 
59
- if json_file?(resolved_file_path)
50
+ if resolved_file_path.end_with?(".json")
60
51
  json_content_from_file(resolved_file_path)
61
52
  else
62
53
  yaml_content_from_file(resolved_file_path)
63
54
  end
64
55
  end
65
-
66
- def content_from_anchor(document, anchor)
67
- source_block = document.attributes["source_blocks"][anchor]
68
- if json_content?(source_block)
69
- json_content_from_anchor(document, anchor)
70
- else
71
- yaml_content_from_anchor(document, anchor)
72
- end
73
- end
74
-
75
- def json_or_yaml_filepath?(file_path)
76
- file_path.end_with?(".json", ".yaml", ".yml")
77
- end
78
-
79
- def json_file?(file_path)
80
- file_path.end_with?(".json")
81
- end
82
-
83
- def json_content?(content)
84
- content.start_with?("{", "[")
85
- end
86
56
  end
87
57
  end
88
58
  end
@@ -8,37 +8,17 @@ module Metanorma
8
8
  module Datastruct
9
9
  class Data2TextPreprocessor < BaseStructuredTextPreprocessor
10
10
  include Content
11
- # search document for block `data2text`
12
- # after that take template from block and read file into this template
13
- # example:
14
- # [data2text,my_yaml=foobar.yaml,my_json=foobar.json]
15
- # ----
16
- # === {foobar.name}
17
- # {foobar.desc}
18
- #
19
- # {my_json.symbol}:: {my_json.symbol_def}
20
- # ----
21
- #
22
- # with content of `foobar.yaml` file equal to:
23
- # - name: spaghetti
24
- # desc: wheat noodles of 9mm diameter
25
- #
26
- # and content of `foobar.json` file equal to:
27
- # {
28
- # "symbol": "SPAG",
29
- # "symbol_def": "the situation is message like spaghetti",
30
- # }
31
- #
32
- # will produce:
33
- # === spaghetti
34
- # wheat noodles of 9mm diameter
35
- #
36
- # SPAG:: the situation is message like spaghetti
37
11
 
38
12
  def initialize(config = {})
39
13
  super
40
14
  @config[:block_name] = "data2text"
41
15
  end
16
+
17
+ protected
18
+
19
+ def content_from_file(document, file_path)
20
+ load_file_content(document, file_path)
21
+ end
42
22
  end
43
23
  end
44
24
  end
@@ -8,35 +8,18 @@ module Metanorma
8
8
  module Datastruct
9
9
  class Json2TextPreprocessor < BaseStructuredTextPreprocessor
10
10
  include Content
11
- # search document for block `json2text`
12
- # after that take template from block and read file into this template
13
- # example:
14
- # [json2text,foobar.json]
15
- # ----
16
- # === {item.name}
17
- # {item.desc}
18
- #
19
- # {item.symbol}:: {item.symbol_def}
20
- # ----
21
- #
22
- # with content of `foobar.json` file equal to:
23
- # {
24
- # "name": "spaghetti",
25
- # "desc": "wheat noodles of 9mm diameter".
26
- # "symbol": "SPAG",
27
- # "symbol_def": "the situation is message like spaghetti",
28
- # }
29
- #
30
- # will produce:
31
- # === spaghetti
32
- # wheat noodles of 9mm diameter
33
- #
34
- # SPAG:: the situation is message like spaghetti
35
11
 
36
12
  def initialize(config = {})
37
13
  super
38
14
  @config[:block_name] = "json2text"
39
15
  end
16
+
17
+ protected
18
+
19
+ def content_from_file(document, file_path)
20
+ resolved = relative_file_path(document, file_path)
21
+ json_content_from_file(resolved)
22
+ end
40
23
  end
41
24
  end
42
25
  end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Metanorma
4
+ module Plugin
5
+ module Datastruct
6
+ module PathResolver
7
+ def relative_file_path(document, file_path)
8
+ docfile_directory = File.dirname(
9
+ document.attributes["docfile"] || ".",
10
+ )
11
+ document
12
+ .path_resolver
13
+ .system_path(file_path, docfile_directory)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -1,20 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "path_resolver"
4
+
1
5
  module Metanorma
2
6
  module Plugin
3
7
  module Datastruct
4
8
  class SourceExtractor
5
- # example:
6
- # - [[abc]]
7
- ANCHOR_REGEX_1 = /^\[\[(?<id>[^\]]*)\]\]\s*$/.freeze
8
-
9
- # examples:
10
- # - [#abc]
11
- # - [source#abc,ruby]
12
- ANCHOR_REGEX_2 = /^\[[^#,]*#(?<id>[^,\]]*)[,\]]/.freeze
9
+ include PathResolver
13
10
 
14
- # examples:
15
- # - [id=abc]
16
- # - [source,id="abc"]
17
- ANCHOR_REGEX_3 = /^\[(?:.+,)?id=['"]?(?<id>[^,\]'"]*)['"]?[,\]]/.freeze
11
+ ANCHOR_PATTERNS = [
12
+ /^\[\[(?<id>[^\]]*)\]\]\s*$/,
13
+ /^\[[^#,]*#(?<id>[^,\]]*)[,\]]/,
14
+ /^\[(?:.+,)?id=['"]?(?<id>[^,\]'"]*)['"]?[,\]]/,
15
+ ].freeze
18
16
 
19
17
  def initialize(document, input_lines)
20
18
  @document = document
@@ -27,17 +25,17 @@ module Metanorma
27
25
  new(document, input_lines).extract
28
26
  end
29
27
 
30
- def extract # rubocop:disable Metrics/AbcSize
28
+ def extract
31
29
  lines = @input_lines.to_enum
32
30
 
33
31
  loop do
34
32
  line = lines.next
35
33
 
36
34
  if /^embed::|^include::/.match?(line.strip)
37
- file_lines = read(filename(@document, line)) or next
35
+ file_lines = read(filename(line)) or next
38
36
  SourceExtractor.extract(@document, file_lines)
39
37
  elsif m = match_anchor(line)
40
- @document.attributes["source_blocks"][m[:id]] = read_section lines
38
+ @document.attributes["source_blocks"][m[:id]] = read_section(lines)
41
39
  end
42
40
  end
43
41
  end
@@ -45,42 +43,29 @@ module Metanorma
45
43
  private
46
44
 
47
45
  def match_anchor(line)
48
- line.match(ANCHOR_REGEX_1) ||
49
- line.match(ANCHOR_REGEX_2) ||
50
- line.match(ANCHOR_REGEX_3)
51
- end
52
-
53
- def readlines_safe(file)
54
- return [] if file.eof?
55
-
56
- file.readlines
46
+ ANCHOR_PATTERNS.each do |pattern|
47
+ match = line.match(pattern)
48
+ return match if match
49
+ end
50
+ nil
57
51
  end
58
52
 
59
53
  def read(inc_path)
60
- inc_path or return nil
61
- ::File.open inc_path, "r" do |fd|
62
- readlines_safe(fd).map(&:chomp)
54
+ return nil unless inc_path
55
+
56
+ File.open(inc_path, "r") do |fd|
57
+ fd.eof? ? [] : fd.readlines.map(&:chomp)
63
58
  end
64
59
  end
65
60
 
66
- def filename(document, line)
61
+ def filename(line)
67
62
  m = /(^include::|^embed::)([^\[]+)\[/.match(line)
68
63
  return nil unless m
69
64
 
70
- file_path = relative_file_path(document, m[2])
71
-
65
+ file_path = relative_file_path(@document, m[2])
72
66
  File.exist?(file_path) ? file_path : nil
73
67
  end
74
68
 
75
- def relative_file_path(document, file_path)
76
- docfile_directory = File.dirname(
77
- document.attributes["docfile"] || ".",
78
- )
79
- document
80
- .path_resolver
81
- .system_path(file_path, docfile_directory)
82
- end
83
-
84
69
  def read_section(lines)
85
70
  m = lines.next.match(/^--+/)
86
71
  return "" unless m
@@ -1,7 +1,7 @@
1
1
  module Metanorma
2
2
  module Plugin
3
3
  module Datastruct
4
- VERSION = "0.3.9".freeze
4
+ VERSION = "0.3.11".freeze
5
5
  end
6
6
  end
7
7
  end
@@ -8,33 +8,18 @@ module Metanorma
8
8
  module Datastruct
9
9
  class Yaml2TextPreprocessor < BaseStructuredTextPreprocessor
10
10
  include Content
11
- # search document for block `yaml2text`
12
- # after that take template from block and read file into this template
13
- # example:
14
- # [yaml2text,foobar.yaml]
15
- # ----
16
- # === {item.name}
17
- # {item.desc}
18
- #
19
- # {item.symbol}:: {item.symbol_def}
20
- # ----
21
- #
22
- # with content of `foobar.yaml` file equal to:
23
- # - name: spaghetti
24
- # desc: wheat noodles of 9mm diameter
25
- # symbol: SPAG
26
- # symbol_def: the situation is message like spaghetti at a kid's
27
- #
28
- # will produce:
29
- # === spaghetti
30
- # wheat noodles of 9mm diameter
31
- #
32
- # SPAG:: the situation is message like spaghetti at a kid's meal
33
11
 
34
12
  def initialize(config = {})
35
13
  super
36
14
  @config[:block_name] = "yaml2text"
37
15
  end
16
+
17
+ protected
18
+
19
+ def content_from_file(document, file_path)
20
+ resolved = relative_file_path(document, file_path)
21
+ yaml_content_from_file(resolved)
22
+ end
38
23
  end
39
24
  end
40
25
  end
@@ -15,8 +15,7 @@ Gem::Specification.new do |spec|
15
15
  spec.license = "BSD-2-Clause"
16
16
 
17
17
  # Specify which files should be added to the gem when it is released.
18
- # The `git ls-files -z` loads the files in the RubyGem that have been added
19
- # into git.
18
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
20
19
  spec.files = Dir.chdir(File.expand_path(__dir__)) do
21
20
  `git ls-files -z`.split("\x0").reject do |f|
22
21
  f.match(%r{^(test|spec|features)/})
@@ -26,24 +25,9 @@ Gem::Specification.new do |spec|
26
25
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
27
26
  spec.require_paths = ["lib"]
28
27
 
29
- spec.required_ruby_version = ">= 2.7.0" # rubocop:disable Gemspec/RequiredRubyVersion
30
-
31
28
  spec.add_dependency "asciidoctor", "~> 2.0.0"
32
29
  spec.add_dependency "isodoc"
30
+ spec.add_dependency "liquid", ">= 4"
33
31
  spec.add_dependency "relaton-cli"
34
-
35
- spec.add_development_dependency "byebug"
36
- spec.add_development_dependency "equivalent-xml"
37
- spec.add_development_dependency "metanorma"
38
- spec.add_development_dependency "metanorma-standoc"
39
- spec.add_development_dependency "rake", "~> 13"
40
- spec.add_development_dependency "rspec", "~> 3.6"
41
- spec.add_development_dependency "rubocop", "~> 1.58"
42
- spec.add_development_dependency "rubocop-performance", "~> 1.19"
43
- spec.add_development_dependency "simplecov", "~> 0.15"
44
- spec.add_development_dependency "timecop", "~> 0.9"
45
- spec.add_development_dependency "vcr", "~> 6.1.0"
46
- spec.add_development_dependency "webmock"
47
- spec.add_development_dependency "xml-c14n"
48
- spec.metadata["rubygems_mfa_required"] = "false"
32
+ spec.metadata["rubygems_mfa_required"] = "true"
49
33
  end
@@ -0,0 +1 @@
1
+ Document title
@@ -0,0 +1,20 @@
1
+ = Document title
2
+ Author
3
+ :docfile: test.adoc
4
+ :nodoc:
5
+ :novalid:
6
+ :no-isobib:
7
+ :imagesdir: spec/assets
8
+
9
+ === Nicaragua
10
+
11
+ Amateur stations:: O[F-J][:digit:][0-9A-Z]{3}[:upper:]{1}
12
+
13
+
14
+
15
+
16
+ === Niger
17
+
18
+ Amateur stations:: O[F-J][:upper:]{5,10}
19
+
20
+ Experimental:: {"regex"=>"O[F-J][:upper:]{5,10}"}
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: metanorma-plugin-datastruct
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.9
4
+ version: 0.3.11
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-05-14 00:00:00.000000000 Z
11
+ date: 2026-05-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: asciidoctor
@@ -39,195 +39,27 @@ dependencies:
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
- name: relaton-cli
42
+ name: liquid
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - ">="
46
46
  - !ruby/object:Gem::Version
47
- version: '0'
47
+ version: '4'
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
- version: '0'
55
- - !ruby/object:Gem::Dependency
56
- name: byebug
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - ">="
60
- - !ruby/object:Gem::Version
61
- version: '0'
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - ">="
67
- - !ruby/object:Gem::Version
68
- version: '0'
69
- - !ruby/object:Gem::Dependency
70
- name: equivalent-xml
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - ">="
74
- - !ruby/object:Gem::Version
75
- version: '0'
76
- type: :development
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - ">="
81
- - !ruby/object:Gem::Version
82
- version: '0'
83
- - !ruby/object:Gem::Dependency
84
- name: metanorma
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - ">="
88
- - !ruby/object:Gem::Version
89
- version: '0'
90
- type: :development
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - ">="
95
- - !ruby/object:Gem::Version
96
- version: '0'
97
- - !ruby/object:Gem::Dependency
98
- name: metanorma-standoc
99
- requirement: !ruby/object:Gem::Requirement
100
- requirements:
101
- - - ">="
102
- - !ruby/object:Gem::Version
103
- version: '0'
104
- type: :development
105
- prerelease: false
106
- version_requirements: !ruby/object:Gem::Requirement
107
- requirements:
108
- - - ">="
109
- - !ruby/object:Gem::Version
110
- version: '0'
111
- - !ruby/object:Gem::Dependency
112
- name: rake
113
- requirement: !ruby/object:Gem::Requirement
114
- requirements:
115
- - - "~>"
116
- - !ruby/object:Gem::Version
117
- version: '13'
118
- type: :development
119
- prerelease: false
120
- version_requirements: !ruby/object:Gem::Requirement
121
- requirements:
122
- - - "~>"
123
- - !ruby/object:Gem::Version
124
- version: '13'
125
- - !ruby/object:Gem::Dependency
126
- name: rspec
127
- requirement: !ruby/object:Gem::Requirement
128
- requirements:
129
- - - "~>"
130
- - !ruby/object:Gem::Version
131
- version: '3.6'
132
- type: :development
133
- prerelease: false
134
- version_requirements: !ruby/object:Gem::Requirement
135
- requirements:
136
- - - "~>"
137
- - !ruby/object:Gem::Version
138
- version: '3.6'
139
- - !ruby/object:Gem::Dependency
140
- name: rubocop
141
- requirement: !ruby/object:Gem::Requirement
142
- requirements:
143
- - - "~>"
144
- - !ruby/object:Gem::Version
145
- version: '1.58'
146
- type: :development
147
- prerelease: false
148
- version_requirements: !ruby/object:Gem::Requirement
149
- requirements:
150
- - - "~>"
151
- - !ruby/object:Gem::Version
152
- version: '1.58'
153
- - !ruby/object:Gem::Dependency
154
- name: rubocop-performance
155
- requirement: !ruby/object:Gem::Requirement
156
- requirements:
157
- - - "~>"
158
- - !ruby/object:Gem::Version
159
- version: '1.19'
160
- type: :development
161
- prerelease: false
162
- version_requirements: !ruby/object:Gem::Requirement
163
- requirements:
164
- - - "~>"
165
- - !ruby/object:Gem::Version
166
- version: '1.19'
167
- - !ruby/object:Gem::Dependency
168
- name: simplecov
169
- requirement: !ruby/object:Gem::Requirement
170
- requirements:
171
- - - "~>"
172
- - !ruby/object:Gem::Version
173
- version: '0.15'
174
- type: :development
175
- prerelease: false
176
- version_requirements: !ruby/object:Gem::Requirement
177
- requirements:
178
- - - "~>"
179
- - !ruby/object:Gem::Version
180
- version: '0.15'
181
- - !ruby/object:Gem::Dependency
182
- name: timecop
183
- requirement: !ruby/object:Gem::Requirement
184
- requirements:
185
- - - "~>"
186
- - !ruby/object:Gem::Version
187
- version: '0.9'
188
- type: :development
189
- prerelease: false
190
- version_requirements: !ruby/object:Gem::Requirement
191
- requirements:
192
- - - "~>"
193
- - !ruby/object:Gem::Version
194
- version: '0.9'
195
- - !ruby/object:Gem::Dependency
196
- name: vcr
197
- requirement: !ruby/object:Gem::Requirement
198
- requirements:
199
- - - "~>"
200
- - !ruby/object:Gem::Version
201
- version: 6.1.0
202
- type: :development
203
- prerelease: false
204
- version_requirements: !ruby/object:Gem::Requirement
205
- requirements:
206
- - - "~>"
207
- - !ruby/object:Gem::Version
208
- version: 6.1.0
209
- - !ruby/object:Gem::Dependency
210
- name: webmock
211
- requirement: !ruby/object:Gem::Requirement
212
- requirements:
213
- - - ">="
214
- - !ruby/object:Gem::Version
215
- version: '0'
216
- type: :development
217
- prerelease: false
218
- version_requirements: !ruby/object:Gem::Requirement
219
- requirements:
220
- - - ">="
221
- - !ruby/object:Gem::Version
222
- version: '0'
54
+ version: '4'
223
55
  - !ruby/object:Gem::Dependency
224
- name: xml-c14n
56
+ name: relaton-cli
225
57
  requirement: !ruby/object:Gem::Requirement
226
58
  requirements:
227
59
  - - ">="
228
60
  - !ruby/object:Gem::Version
229
61
  version: '0'
230
- type: :development
62
+ type: :runtime
231
63
  prerelease: false
232
64
  version_requirements: !ruby/object:Gem::Requirement
233
65
  requirements:
@@ -247,31 +79,37 @@ files:
247
79
  - ".hound.yml"
248
80
  - ".rspec"
249
81
  - ".rubocop.yml"
82
+ - CLAUDE.md
250
83
  - CODE_OF_CONDUCT.md
251
84
  - Gemfile
85
+ - Gemfile.devel
252
86
  - LICENSE
253
87
  - README.adoc
254
88
  - Rakefile
255
89
  - bin/console
256
90
  - bin/setup
91
+ - lib/liquid/custom_blocks/key_iterator.rb
92
+ - lib/liquid/custom_blocks/nested_context_block.rb
93
+ - lib/liquid/custom_blocks/with_json_nested_context.rb
94
+ - lib/liquid/custom_blocks/with_yaml_nested_context.rb
95
+ - lib/liquid/custom_filters/values.rb
257
96
  - lib/metanorma-plugin-datastruct.rb
258
97
  - lib/metanorma/plugin/datastruct/base_structured_text_preprocessor.rb
259
98
  - lib/metanorma/plugin/datastruct/content.rb
260
99
  - lib/metanorma/plugin/datastruct/data2_text_preprocessor.rb
261
100
  - lib/metanorma/plugin/datastruct/json2_text_preprocessor.rb
262
- - lib/metanorma/plugin/datastruct/liquid/custom_blocks/key_iterator.rb
263
- - lib/metanorma/plugin/datastruct/liquid/custom_filters/loadfile.rb
264
- - lib/metanorma/plugin/datastruct/liquid/custom_filters/replace_regex.rb
265
- - lib/metanorma/plugin/datastruct/liquid/custom_filters/values.rb
101
+ - lib/metanorma/plugin/datastruct/path_resolver.rb
266
102
  - lib/metanorma/plugin/datastruct/source_extractor.rb
267
103
  - lib/metanorma/plugin/datastruct/version.rb
268
104
  - lib/metanorma/plugin/datastruct/yaml2_text_preprocessor.rb
269
105
  - metanorma-plugin-datastruct.gemspec
106
+ - metanorma.asciidoc.log.txt
107
+ - test.asciidoc.log.txt
270
108
  homepage: https://github.com/metanorma/metanorma-plugin-datastruct
271
109
  licenses:
272
110
  - BSD-2-Clause
273
111
  metadata:
274
- rubygems_mfa_required: 'false'
112
+ rubygems_mfa_required: 'true'
275
113
  post_install_message:
276
114
  rdoc_options: []
277
115
  require_paths:
@@ -280,7 +118,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
280
118
  requirements:
281
119
  - - ">="
282
120
  - !ruby/object:Gem::Version
283
- version: 2.7.0
121
+ version: '0'
284
122
  required_rubygems_version: !ruby/object:Gem::Requirement
285
123
  requirements:
286
124
  - - ">="
@@ -1,31 +0,0 @@
1
- module Metanorma
2
- module Plugin
3
- module Datastruct
4
- module Liquid
5
- module CustomBlocks
6
- class KeyIterator < ::Liquid::Block
7
- def initialize(tag_name, markup, tokens)
8
- super
9
- @context_name, @var_name = markup.split(",").map(&:strip)
10
- end
11
-
12
- def render(context) # rubocop:disable Metrics/MethodLength
13
- res = ""
14
- iterator = if context[@context_name].is_a?(Hash)
15
- context[@context_name].keys
16
- else
17
- context[@context_name]
18
- end
19
- iterator.each.with_index do |key, index|
20
- context["index"] = index
21
- context[@var_name] = key
22
- res += super
23
- end
24
- res
25
- end
26
- end
27
- end
28
- end
29
- end
30
- end
31
- end
@@ -1,18 +0,0 @@
1
- require_relative "../../content"
2
-
3
- module Metanorma
4
- module Plugin
5
- module Datastruct
6
- module Liquid
7
- module CustomFilters
8
- include ::Metanorma::Plugin::Datastruct::Content
9
-
10
- def loadfile(path, parent_folder = ".")
11
- resolved_file_path = File.expand_path(path, parent_folder)
12
- load_content_from_file(resolved_file_path)
13
- end
14
- end
15
- end
16
- end
17
- end
18
- end
@@ -1,14 +0,0 @@
1
- module Metanorma
2
- module Plugin
3
- module Datastruct
4
- module Liquid
5
- module CustomFilters
6
- def replace_regex(text, regex_search, replace_value)
7
- regex = /#{regex_search}/
8
- text.to_s.gsub(regex, replace_value)
9
- end
10
- end
11
- end
12
- end
13
- end
14
- end
@@ -1,13 +0,0 @@
1
- module Metanorma
2
- module Plugin
3
- module Datastruct
4
- module Liquid
5
- module CustomFilters
6
- def values(list)
7
- list.values
8
- end
9
- end
10
- end
11
- end
12
- end
13
- end