metanorma-plugin-datastruct 0.3.10 → 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: ddd2e0f19eb24c1c23dd1445ecdf17734b4b70263d1098e9446454c6e5bbddc6
4
- data.tar.gz: 346f50de2a3a8a36a1dbc7ab89141226ec5ae5133fc0664bb106c7a01fd7356e
3
+ metadata.gz: 35c1de600ac66e34295b75d0bfd4fee0328ea75901be90148928d347844e474b
4
+ data.tar.gz: 4752f9aa448c03e63af8a95f244013ddf5fe84b65b36b29b4a5e72621e1ef44f
5
5
  SHA512:
6
- metadata.gz: 67bab763402f9e0d651effa3fa6e76228331459dd67e4b2faec2b6bb411490aaf7a5751cbddcd4302a9d8962e9412e2de6c0431ff5e9f0222687c3dc88a9c140
7
- data.tar.gz: 6a87d9f07929323519f1cc4ea9402df25918eb5ec464115be321abe695b4f579f10fac1c72822abccb8bd293656b8660acb47386bcbd76685b120966b652aa45
6
+ metadata.gz: 0302bce3b5cc508e540ce7b897710ddec93de2592939f90fec3578a65f59aa47a326c7e73ac5af3ceea489cade3b48c8deea02950eab2920d3f9995acddf67ec
7
+ data.tar.gz: f34daa0895c9f5d404e18ef8a094f05c9b14348c2fccb815a5f9eb952587ac3e69b70f13381f0d3427c6c8da38e3bff9c5e912fb87a453b74337467a4b49b621
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
@@ -1,6 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Liquid
2
4
  module CustomFilters
3
5
  def values(list)
6
+ return list unless list.respond_to?(:values)
4
7
  list.values
5
8
  end
6
9
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "set"
3
4
  require "liquid"
4
5
  require "asciidoctor"
5
6
  require "asciidoctor/reader"
@@ -19,6 +20,11 @@ module Metanorma
19
20
 
20
21
  BLOCK_START_REGEXP = /\{(.+?)\.\*,(.+),(.+)\}/.freeze
21
22
  BLOCK_END_REGEXP = /\A\{[A-Z]+\}\z/.freeze
23
+ NESTED_CONTEXT_SUFFIX = {
24
+ "yaml2text" => "yaml",
25
+ "json2text" => "json",
26
+ "data2text" => "yaml",
27
+ }.freeze
22
28
 
23
29
  def initialize(config = {})
24
30
  super
@@ -72,7 +78,7 @@ module Metanorma
72
78
 
73
79
  def collect_internal_block_lines(document, input_lines, end_mark)
74
80
  current_block = []
75
- nested_marks = []
81
+ nested_marks = Set.new
76
82
  while (block_line = input_lines.next) != end_mark
77
83
  if nested_match = block_line
78
84
  .match(/^\[#{config[:block_name]},(.+?),(.+?)\]/)
@@ -80,11 +86,11 @@ module Metanorma
80
86
  .push(*nested_context_tag(document,
81
87
  nested_match[1],
82
88
  nested_match[2]).split("\n"))
83
- next nested_marks.push(input_lines.next)
89
+ next nested_marks.add(input_lines.next)
84
90
  end
85
91
 
86
92
  if nested_marks.include?(block_line)
87
- current_block.push("{% endwith_#{data_file_type}_nested_context %}")
93
+ current_block.push("{% endwith_#{nested_context_suffix}_nested_context %}")
88
94
  next nested_marks.delete(block_line)
89
95
  end
90
96
  current_block.push(block_line)
@@ -92,8 +98,8 @@ module Metanorma
92
98
  current_block
93
99
  end
94
100
 
95
- def data_file_type
96
- config[:block_name].split("2").first
101
+ def nested_context_suffix
102
+ NESTED_CONTEXT_SUFFIX[config[:block_name]]
97
103
  end
98
104
 
99
105
  def nested_context_tag(document, file_path, context_name)
@@ -102,7 +108,7 @@ module Metanorma
102
108
  {% capture nested_file_path %}
103
109
  #{absolute_file_path}
104
110
  {% endcapture %}
105
- {% with_#{data_file_type}_nested_context nested_file_path, #{context_name} %}
111
+ {% with_#{nested_context_suffix}_nested_context nested_file_path, #{context_name} %}
106
112
  TEMPLATE
107
113
  end
108
114
 
@@ -122,13 +128,8 @@ module Metanorma
122
128
  end
123
129
 
124
130
  def transform_line_liquid(line)
125
- if line.match?(BLOCK_START_REGEXP)
126
- line.gsub!(BLOCK_START_REGEXP, '{% keyiterator \1, \2 %}')
127
- end
128
-
129
- if line.strip.match?(BLOCK_END_REGEXP)
130
- line.gsub!(BLOCK_END_REGEXP, "{% endkeyiterator %}")
131
- end
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)
132
133
  line
133
134
  .gsub(/(?<!{){(?!%)([^{}]+)(?<!%)}(?!})/, '{{\1}}')
134
135
  .gsub(/[a-z.]+\#/, "index")
@@ -33,28 +33,26 @@ module Metanorma
33
33
  JSON.parse(File.read(resolved_file_path, encoding: "UTF-8"))
34
34
  end
35
35
 
36
- def content_from_file(document, file_path)
36
+ def load_file_content(document, file_path)
37
37
  resolved_file_path = relative_file_path(document, file_path)
38
- load_content_from_file(resolved_file_path)
38
+ load_content_by_extension(resolved_file_path)
39
39
  end
40
40
 
41
- def load_content_from_file(resolved_file_path)
41
+ private
42
+
43
+ def load_content_by_extension(resolved_file_path)
42
44
  unless File.exist?(resolved_file_path)
43
45
  ::Metanorma::Util
44
46
  .log("Failed to load content from file: #{resolved_file_path}",
45
47
  :error)
46
48
  end
47
49
 
48
- if json_file?(resolved_file_path)
50
+ if resolved_file_path.end_with?(".json")
49
51
  json_content_from_file(resolved_file_path)
50
52
  else
51
53
  yaml_content_from_file(resolved_file_path)
52
54
  end
53
55
  end
54
-
55
- def json_file?(file_path)
56
- file_path.end_with?(".json")
57
- end
58
56
  end
59
57
  end
60
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
@@ -1,7 +1,7 @@
1
1
  module Metanorma
2
2
  module Plugin
3
3
  module Datastruct
4
- VERSION = "0.3.10".freeze
4
+ VERSION = "0.3.11".freeze
5
5
  end
6
6
  end
7
7
  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.10
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: 2026-05-11 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
@@ -79,6 +79,7 @@ files:
79
79
  - ".hound.yml"
80
80
  - ".rspec"
81
81
  - ".rubocop.yml"
82
+ - CLAUDE.md
82
83
  - CODE_OF_CONDUCT.md
83
84
  - Gemfile
84
85
  - Gemfile.devel
@@ -102,6 +103,8 @@ files:
102
103
  - lib/metanorma/plugin/datastruct/version.rb
103
104
  - lib/metanorma/plugin/datastruct/yaml2_text_preprocessor.rb
104
105
  - metanorma-plugin-datastruct.gemspec
106
+ - metanorma.asciidoc.log.txt
107
+ - test.asciidoc.log.txt
105
108
  homepage: https://github.com/metanorma/metanorma-plugin-datastruct
106
109
  licenses:
107
110
  - BSD-2-Clause