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 +4 -4
- data/CLAUDE.md +60 -0
- data/lib/liquid/custom_filters/values.rb +3 -0
- data/lib/metanorma/plugin/datastruct/base_structured_text_preprocessor.rb +14 -13
- data/lib/metanorma/plugin/datastruct/content.rb +6 -8
- data/lib/metanorma/plugin/datastruct/data2_text_preprocessor.rb +6 -26
- data/lib/metanorma/plugin/datastruct/json2_text_preprocessor.rb +7 -24
- data/lib/metanorma/plugin/datastruct/version.rb +1 -1
- data/metanorma.asciidoc.log.txt +1 -0
- data/test.asciidoc.log.txt +20 -0
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 35c1de600ac66e34295b75d0bfd4fee0328ea75901be90148928d347844e474b
|
|
4
|
+
data.tar.gz: 4752f9aa448c03e63af8a95f244013ddf5fe84b65b36b29b4a5e72621e1ef44f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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,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.
|
|
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_#{
|
|
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
|
|
96
|
-
config[:block_name]
|
|
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_#{
|
|
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
|
-
|
|
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
|
|
36
|
+
def load_file_content(document, file_path)
|
|
37
37
|
resolved_file_path = relative_file_path(document, file_path)
|
|
38
|
-
|
|
38
|
+
load_content_by_extension(resolved_file_path)
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
-
|
|
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
|
|
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
|
|
@@ -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.
|
|
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
|
+
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
|