metanorma-standoc 1.3.24 → 1.3.29

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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/macos.yml +10 -1
  3. data/.github/workflows/ubuntu.yml +13 -3
  4. data/.github/workflows/windows.yml +8 -1
  5. data/lib/asciidoctor/standoc/base.rb +29 -11
  6. data/lib/asciidoctor/standoc/biblio.rng +75 -28
  7. data/lib/asciidoctor/standoc/blocks.rb +12 -5
  8. data/lib/asciidoctor/standoc/cleanup.rb +17 -8
  9. data/lib/asciidoctor/standoc/cleanup_block.rb +3 -0
  10. data/lib/asciidoctor/standoc/cleanup_boilerplate.rb +1 -3
  11. data/lib/asciidoctor/standoc/cleanup_inline.rb +3 -0
  12. data/lib/asciidoctor/standoc/cleanup_ref.rb +15 -6
  13. data/lib/asciidoctor/standoc/cleanup_section.rb +36 -8
  14. data/lib/asciidoctor/standoc/front.rb +5 -3
  15. data/lib/asciidoctor/standoc/inline.rb +43 -18
  16. data/lib/asciidoctor/standoc/isodoc.rng +31 -1
  17. data/lib/asciidoctor/standoc/macros.rb +2 -1
  18. data/lib/asciidoctor/standoc/macros_yaml2text.rb +142 -0
  19. data/lib/asciidoctor/standoc/ref.rb +6 -4
  20. data/lib/asciidoctor/standoc/section.rb +41 -9
  21. data/lib/asciidoctor/standoc/utils.rb +2 -11
  22. data/lib/asciidoctor/standoc/validate.rb +8 -2
  23. data/lib/asciidoctor/standoc/validate_section.rb +1 -3
  24. data/lib/metanorma/standoc/latexml_requirement.rb +14 -12
  25. data/lib/metanorma/standoc/version.rb +1 -1
  26. data/metanorma-standoc.gemspec +2 -2
  27. data/spec/asciidoctor-standoc/base_spec.rb +8 -0
  28. data/spec/asciidoctor-standoc/blocks_spec.rb +74 -2
  29. data/spec/asciidoctor-standoc/cleanup_spec.rb +75 -28
  30. data/spec/asciidoctor-standoc/inline_spec.rb +4 -3
  31. data/spec/asciidoctor-standoc/macros_spec.rb +9 -8
  32. data/spec/asciidoctor-standoc/macros_yaml2text_spec.rb +564 -0
  33. data/spec/asciidoctor-standoc/refs_dl_spec.rb +91 -3
  34. data/spec/asciidoctor-standoc/refs_spec.rb +272 -166
  35. data/spec/asciidoctor-standoc/section_spec.rb +197 -6
  36. data/spec/asciidoctor-standoc/validate_spec.rb +67 -2
  37. data/spec/assets/codes.yml +695 -0
  38. data/spec/assets/xref_error.adoc +7 -0
  39. data/spec/examples/codes_table.html +3174 -0
  40. data/spec/metanorma/processor_spec.rb +2 -2
  41. data/spec/spec_helper.rb +1 -0
  42. data/spec/vcr_cassettes/dated_iso_ref_joint_iso_iec.yml +77 -271
  43. data/spec/vcr_cassettes/isobib_get_123.yml +36 -82
  44. data/spec/vcr_cassettes/isobib_get_123_2001.yml +17 -40
  45. data/spec/vcr_cassettes/isobib_get_124.yml +19 -101
  46. data/spec/vcr_cassettes/rfcbib_get_rfc8341.yml +8 -8
  47. data/spec/vcr_cassettes/separates_iev_citations_by_top_level_clause.yml +34 -34
  48. metadata +11 -6
@@ -131,10 +131,13 @@ module Asciidoctor
131
131
  def note_cleanup(xmldoc)
132
132
  q = "//note[following-sibling::*[not(local-name() = 'note')]]"
133
133
  xmldoc.xpath(q).each do |n|
134
+ next if n["keep-separate"] == "true"
134
135
  next unless n.ancestors("table").empty?
135
136
  prev = n.previous_element || next
136
137
  n.parent = prev if ELEMS_ALLOW_NOTES.include? prev.name
137
138
  end
139
+ xmldoc.xpath("//note[@keep-separate]").each { |n| n.delete("keep-separate") }
140
+ xmldoc.xpath("//termnote[@keep-separate]").each { |n| n.delete("keep-separate") }
138
141
  end
139
142
 
140
143
  def requirement_cleanup(x)
@@ -18,7 +18,6 @@ module Asciidoctor
18
18
  source.each do |s|
19
19
  @anchors[s["bibitemid"]] or
20
20
  @log.add("Crossreferences", nil, "term source #{s['bibitemid']} not referenced")
21
- #warn "term source #{s['bibitemid']} not referenced"
22
21
  end
23
22
  if source.empty? && term.nil?
24
23
  div.next = @no_terms_boilerplate
@@ -49,8 +48,7 @@ module Asciidoctor
49
48
  TERM_CLAUSE = "//sections/terms | "\
50
49
  "//sections/clause[descendant::terms]".freeze
51
50
 
52
- NORM_REF = "//bibliography/references[title = 'Normative References' or "\
53
- "title = 'Normative references']".freeze
51
+ NORM_REF = "//bibliography/references[@normative = 'true']".freeze
54
52
 
55
53
  def boilerplate_isodoc(xmldoc)
56
54
  x = xmldoc.dup
@@ -90,6 +90,9 @@ module Asciidoctor
90
90
  end
91
91
 
92
92
  def origin_cleanup(xmldoc)
93
+ xmldoc.xpath("//origin/concept[termref]").each do |x|
94
+ x.replace(x.children)
95
+ end
93
96
  xmldoc.xpath("//origin").each do |x|
94
97
  x["citeas"] = @anchors&.dig(x["bibitemid"], :xref) ||
95
98
  @log.add("Crossreferences", x,
@@ -5,7 +5,7 @@ module Asciidoctor
5
5
  module Standoc
6
6
  module Cleanup
7
7
  def biblio_reorder(xmldoc)
8
- xmldoc.xpath("//references[title = 'Bibliography']").each do |r|
8
+ xmldoc.xpath("//references[@normative = 'false']").each do |r|
9
9
  biblio_reorder1(r)
10
10
  end
11
11
  end
@@ -49,8 +49,8 @@ module Asciidoctor
49
49
  # consecutively, but that standards codes are preserved as is:
50
50
  # only numeric references are renumbered
51
51
  def biblio_renumber(xmldoc)
52
- r = xmldoc.at("//references[title = 'Bibliography'] | "\
53
- "//clause[title = 'Bibliography'][.//bibitem]") or return
52
+ r = xmldoc.at("//references[@normative = 'false'] | "\
53
+ "//clause[.//references[@normative = 'false']]") or return
54
54
  r.xpath(".//bibitem[not(ancestor::bibitem)]").each_with_index do |b, i|
55
55
  next unless docid = b.at("./docidentifier[@type = 'metanorma']")
56
56
  next unless /^\[\d+\]$/.match(docid.text)
@@ -67,18 +67,23 @@ module Asciidoctor
67
67
  end
68
68
 
69
69
  def normref_cleanup(xmldoc)
70
- r = xmldoc.at(NORM_REF) || return
71
- #return if r.at("./bibitem[1]/preceding-sibling::*[1][local-name()='title']")
72
- preface = r.xpath("./title/following-sibling::*") &
70
+ r = xmldoc.at(self.class::NORM_REF) || return
71
+ preface = r.xpath("./title/following-sibling::*") & # intersection
73
72
  r.xpath("./bibitem[1]/preceding-sibling::*")
74
73
  preface.each { |n| n.remove }
75
74
  end
76
75
 
77
76
  def biblio_cleanup(xmldoc)
78
77
  biblio_reorder(xmldoc)
78
+ biblio_nested(xmldoc)
79
79
  biblio_renumber(xmldoc)
80
+ end
81
+
82
+ def biblio_nested(xmldoc)
80
83
  xmldoc.xpath("//references[references]").each do |t|
81
84
  t.name = "clause"
85
+ t.xpath("./references").each { |r| r["normative"] = t["normative"] }
86
+ t.delete("normative")
82
87
  end
83
88
  end
84
89
 
@@ -187,6 +192,10 @@ module Asciidoctor
187
192
  end
188
193
  bib
189
194
  end
195
+
196
+ def fetch_termbase(termbase, id)
197
+ ""
198
+ end
190
199
  end
191
200
  end
192
201
  end
@@ -11,7 +11,7 @@ module Asciidoctor
11
11
  module Cleanup
12
12
  def make_preface(x, s)
13
13
  if x.at("//foreword | //introduction | //acknowledgements | "\
14
- "//clause[@preface]")
14
+ "//*[@preface]")
15
15
  preface = s.add_previous_sibling("<preface/>").first
16
16
  f = x.at("//foreword") and preface.add_child f.remove
17
17
  f = x.at("//introduction") and preface.add_child f.remove
@@ -22,7 +22,7 @@ module Asciidoctor
22
22
  end
23
23
 
24
24
  def move_clauses_into_preface(x, preface)
25
- x.xpath("//clause[@preface]").each do |c|
25
+ x.xpath("//*[@preface]").each do |c|
26
26
  c.delete("preface")
27
27
  preface.add_child c.remove
28
28
  end
@@ -66,9 +66,22 @@ module Asciidoctor
66
66
  def sections_order_cleanup(x)
67
67
  s = x.at("//sections")
68
68
  make_preface(x, s)
69
+ make_annexes(x)
69
70
  make_bibliography(x, s)
70
71
  x.xpath("//sections/annex").reverse_each { |r| s.next = r.remove }
71
72
  end
73
+
74
+ def make_annexes(x)
75
+ x.xpath("//*[@annex]").each do |y|
76
+ y.delete("annex")
77
+ next if y.name == "annex" || !y.ancestors("annex").empty?
78
+ y.wrap("<annex/>")
79
+ y.parent["id"] = "_#{UUIDTools::UUID.random_create}"
80
+ y.parent["obligation"] = y["obligation"]
81
+ y.parent["language"] = y["language"]
82
+ y.parent["script"] = y["script"]
83
+ end
84
+ end
72
85
 
73
86
  def maxlevel(x)
74
87
  max = 5
@@ -117,7 +130,7 @@ module Asciidoctor
117
130
  end
118
131
 
119
132
  def obligations_cleanup_inherit(x)
120
- x.xpath("//annex | //clause").each do |r|
133
+ x.xpath("//annex | //clause[not(ancestor::boilerplate)]").each do |r|
121
134
  r["obligation"] = "normative" unless r["obligation"]
122
135
  end
123
136
  x.xpath(Utils::SUBCLAUSE_XPATH).each do |r|
@@ -192,7 +205,16 @@ module Asciidoctor
192
205
  end
193
206
  end
194
207
 
208
+ def termdef_from_termbase(xmldoc)
209
+ xmldoc.xpath("//term").each do |x|
210
+ if c = x.at("./origin/termref") and !x.at("./definition")
211
+ x.at("./origin").previous = fetch_termbase(c["base"], c.text)
212
+ end
213
+ end
214
+ end
215
+
195
216
  def termdef_cleanup(xmldoc)
217
+ termdef_from_termbase(xmldoc)
196
218
  termdef_unnest_cleanup(xmldoc)
197
219
  termdef_stem_cleanup(xmldoc)
198
220
  termdomain_cleanup(xmldoc)
@@ -209,15 +231,21 @@ module Asciidoctor
209
231
  # Numbers sort *after* letters; we use thorn to force that sort order.
210
232
  def symbol_key(x)
211
233
  key = x.dup
212
- key.xpath("//*[local-name() = 'math']").each do |m|
213
- m.replace(MathML2AsciiMath.m2a(m.to_xml))
234
+ key.traverse do |n|
235
+ next unless n.name == "math"
236
+ n.replace(grkletters(MathML2AsciiMath.m2a(n.to_xml)))
214
237
  end
215
- ret = Nokogiri::XML(MathML2AsciiMath.m2a(key.to_xml))
216
- HTMLEntities.new.decode(ret.text).strip.
238
+ ret = Nokogiri::XML(key.to_xml)
239
+ HTMLEntities.new.decode(ret.text).
240
+ gsub(/[\[\]\{\}<>\(\)]/, "").strip.
217
241
  gsub(/[[:punct]]|[_^]/, ":\\0").gsub(/`/, "").
218
242
  gsub(/[0-9]+/, "þ\\0")
219
243
  end
220
244
 
245
+ def grkletters(x)
246
+ x.gsub(/\b(alpha|beta|gamma|delta|epsilon|zeta|eta|theta|iota|kappa|lambda|mu|nu|xi|omicron|pi|rho|sigma|tau|upsilon|phi|chi|psi|omega)\b/i, "&\\1;")
247
+ end
248
+
221
249
  def extract_symbols_list(dl)
222
250
  dl_out = []
223
251
  dl.xpath("./dt | ./dd").each do |dtd|
@@ -233,7 +261,7 @@ module Asciidoctor
233
261
  def symbols_cleanup(docxml)
234
262
  docxml.xpath("//definitions/dl").each do |dl|
235
263
  dl_out = extract_symbols_list(dl)
236
- dl_out.sort! { |a, b| a[:key] <=> b[:key] }
264
+ dl_out.sort! { |a, b| a[:key] <=> b[:key] || a[:dt] <=> b[:dt] }
237
265
  dl.children = dl_out.map { |d| d[:dt].to_s + d[:dd].to_s }.join("\n")
238
266
  end
239
267
  docxml
@@ -78,7 +78,9 @@ module Asciidoctor
78
78
 
79
79
  def datetypes
80
80
  %w{ published accessed created implemented obsoleted
81
- confirmed updated issued circulated unchanged received }
81
+ confirmed updated issued circulated unchanged received
82
+ vote-started vote-ended
83
+ }
82
84
  end
83
85
 
84
86
  def metadata_date(node, xml)
@@ -190,8 +192,8 @@ module Asciidoctor
190
192
  ["en"].each do |lang|
191
193
  at = { language: lang, format: "text/plain" }
192
194
  xml.title **attr_code(at) do |t|
193
- t << Utils::asciidoc_sub(node.attr("title") || node.attr("title-en") ||
194
- node.title)
195
+ t << (Utils::asciidoc_sub(node.attr("title") || node.attr("title-en")) ||
196
+ node.title)
195
197
  end
196
198
  end
197
199
  end
@@ -3,6 +3,7 @@ require "htmlentities"
3
3
  require "unicode2latex"
4
4
  require "mime/types"
5
5
  require "base64"
6
+ require 'English'
6
7
 
7
8
  module Asciidoctor
8
9
  module Standoc
@@ -106,23 +107,47 @@ module Asciidoctor
106
107
  gsub(/&quot;/, '"').gsub(/&#xa;/, "\n")
107
108
  end
108
109
 
110
+ def latex_run1(lxm_input, cmd)
111
+ IO.popen(cmd, "r+", external_encoding: "UTF-8") do |io|
112
+ io.write(lxm_input)
113
+ io.close_write
114
+ io.read
115
+ end
116
+ end
117
+
118
+ def latex_run(lxm_input)
119
+ results = nil
120
+ Metanorma::Standoc::Requirements[:latexml].cmd.each_with_index do |cmd, i|
121
+ warn "Retrying with #{cmd}" if i > 0
122
+ results = latex_run1(lxm_input, cmd)
123
+ if $CHILD_STATUS.to_i.zero?
124
+ warn "Success!" if i > 0
125
+ break
126
+ end
127
+ end
128
+ $CHILD_STATUS.to_i.zero? ? results : nil
129
+ end
130
+
131
+ def latex_parse(text)
132
+ lxm_input = Unicode2LaTeX.unicode2latex(HTMLEntities.new.decode(text))
133
+ results = latex_run(lxm_input)
134
+ results.nil? and
135
+ @log.add('Math', nil,
136
+ "latexmlmath failed to process equation:\n#{lxm_input}")
137
+ results
138
+ end
139
+
109
140
  def stem_parse(text, xml, style)
110
141
  if /&lt;([^:>&]+:)?math(\s+[^>&]+)?&gt; |
111
142
  <([^:>&]+:)?math(\s+[^>&]+)?>/x.match text
112
143
  math = xml_encode(text)
113
144
  xml.stem math, **{ type: "MathML" }
114
145
  elsif style == :latexmath
115
- latex_cmd = Metanorma::Standoc::Requirements[:latexml].cmd
116
- latexmlmath_input =
117
- Unicode2LaTeX::unicode2latex(HTMLEntities.new.decode(text)).
118
- gsub(/'/, '\\').gsub(/\n/, " ")
119
- latex = IO.popen(latex_cmd, "r+", external_encoding: "UTF-8") do |io|
120
- io.write(latexmlmath_input)
121
- io.close_write
122
- io.read
123
- end
146
+ latex = latex_parse(text) or return xml.stem **{ type: "MathML" }
124
147
  xml.stem **{ type: "MathML" } do |s|
125
- s << latex.sub(/<\?[^>]+>/, "")
148
+ math = Nokogiri::XML.fragment(latex.sub(/<\?[^>]+>/, "")).elements[0]
149
+ math.delete("alttext")
150
+ s.parent.children = math
126
151
  end
127
152
  else
128
153
  xml.stem text, **{ type: "AsciiMath" }
@@ -172,14 +197,14 @@ module Asciidoctor
172
197
  types = /^data:/.match(uri) ? datauri2mime(uri) : MIME::Types.type_for(uri)
173
198
  type = types.first.to_s
174
199
  uri = uri.sub(%r{^data:image/\*;}, "data:#{type};")
175
- attr_code(src: @datauriimage ? datauri(uri) : uri,
176
- id: Utils::anchor_or_uuid,
177
- mimetype: type,
178
- height: node.attr("height") || "auto",
179
- width: node.attr("width") || "auto" ,
180
- filename: node.attr("filename"),
181
- title: node.attr("titleattr"),
182
- alt: node.alt == node.attr("default-alt") ? nil : node.alt)
200
+ attr_code(src: uri, #@datauriimage ? datauri(uri) : uri,
201
+ id: Utils::anchor_or_uuid,
202
+ mimetype: type,
203
+ height: node.attr("height") || "auto",
204
+ width: node.attr("width") || "auto" ,
205
+ filename: node.attr("filename"),
206
+ title: node.attr("titleattr"),
207
+ alt: node.alt == node.attr("default-alt") ? nil : node.alt)
183
208
  end
184
209
 
185
210
  def inline_image(node)
@@ -129,6 +129,9 @@
129
129
  </choice>
130
130
  </attribute>
131
131
  </optional>
132
+ <attribute name="normative">
133
+ <data type="boolean"/>
134
+ </attribute>
132
135
  <optional>
133
136
  <ref name="section-title"/>
134
137
  </optional>
@@ -305,6 +308,21 @@
305
308
  </define>
306
309
  </include>
307
310
  <!-- end overrides -->
311
+ <define name="TextElement" combine="choice">
312
+ <ref name="concept"/>
313
+ </define>
314
+ <define name="concept">
315
+ <element name="concept">
316
+ <optional>
317
+ <attribute name="term"/>
318
+ </optional>
319
+ <choice>
320
+ <ref name="eref"/>
321
+ <ref name="xref"/>
322
+ <ref name="termref"/>
323
+ </choice>
324
+ </element>
325
+ </define>
308
326
  <define name="BasicBlock" combine="choice">
309
327
  <choice>
310
328
  <ref name="requirement"/>
@@ -911,7 +929,10 @@
911
929
  </define>
912
930
  <define name="origin">
913
931
  <element name="origin">
914
- <ref name="erefType"/>
932
+ <choice>
933
+ <ref name="erefType"/>
934
+ <ref name="termref"/>
935
+ </choice>
915
936
  </element>
916
937
  </define>
917
938
  <define name="modification">
@@ -919,6 +940,15 @@
919
940
  <ref name="paragraph"/>
920
941
  </element>
921
942
  </define>
943
+ <define name="termref">
944
+ <element name="termref">
945
+ <attribute name="base"/>
946
+ <attribute name="target"/>
947
+ <optional>
948
+ <text/>
949
+ </optional>
950
+ </element>
951
+ </define>
922
952
  <define name="structuredidentifier">
923
953
  <element name="structuredidentifier">
924
954
  <optional>
@@ -2,6 +2,7 @@ require "asciidoctor/extensions"
2
2
  require "fileutils"
3
3
  require "uuidtools"
4
4
  require_relative "./macros_plantuml.rb"
5
+ require_relative "./macros_yaml2text.rb"
5
6
 
6
7
  module Asciidoctor
7
8
  module Standoc
@@ -58,7 +59,7 @@ module Asciidoctor
58
59
  named :concept
59
60
  name_positional_attributes "id", "word", "term"
60
61
  #match %r{concept:(?<target>[^\[]*)\[(?<content>|.*?[^\\])\]$}
61
- match /\{\{(?<content>|.*?[^\\])\}\}$/
62
+ match /\{\{(?<content>|.*?[^\\])\}\}/
62
63
  using_format :short
63
64
 
64
65
  # deal with locality attrs and their disruption of positional attrs
@@ -0,0 +1,142 @@
1
+ require 'ostruct'
2
+
3
+ module Asciidoctor
4
+ module Standoc
5
+ class YamlBlockStruct < OpenStruct
6
+ def to_a
7
+ @table.to_h.keys
8
+ end
9
+
10
+ def values
11
+ @table.to_h.values
12
+ end
13
+
14
+ def each
15
+ return to_a.each unless block_given?
16
+
17
+ to_a.each do |key|
18
+ yield(key)
19
+ end
20
+ end
21
+ end
22
+
23
+ class YamlContextRenderer
24
+ attr_reader :context_object, :context_name
25
+
26
+ def initialize(context_object:, context_name:)
27
+ @context_object = context_object
28
+ @context_name = context_name
29
+ end
30
+
31
+ def respond_to_missing?(name)
32
+ respond_to?(name)
33
+ end
34
+
35
+ def method_missing(name, *_args)
36
+ return context_object if name.to_s == context_name
37
+
38
+ super
39
+ end
40
+
41
+ def render(template)
42
+ ERB.new(template).result(binding)
43
+ end
44
+ end
45
+
46
+ class Yaml2TextPreprocessor < Asciidoctor::Extensions::Preprocessor
47
+ BLOCK_START_REGEXP = /\{(.+?)\.\*,(.+),(.+)\}/.freeze
48
+ BLOCK_END_REGEXP = /\A\{[A-Z]+\}\z/.freeze
49
+ # search document for block `yaml2text`
50
+ # after that take template from block and read file into this template
51
+ # example:
52
+ # [yaml2text,foobar.yaml]
53
+ # ----
54
+ # === {item.name}
55
+ # {item.desc}
56
+ #
57
+ # {item.symbol}:: {item.symbol_def}
58
+ # ----
59
+ #
60
+ # with content of `foobar.yaml` file equal to:
61
+ # - name: spaghetti
62
+ # desc: wheat noodles of 9mm diameter
63
+ # symbol: SPAG
64
+ # symbol_def: the situation is message like spaghetti at a kid's meal
65
+ #
66
+ # will produce:
67
+ # === spaghetti
68
+ # wheat noodles of 9mm diameter
69
+ #
70
+ # SPAG:: the situation is message like spaghetti at a kid's meal
71
+ def process(document, reader)
72
+ input_lines = reader.readlines.to_enum
73
+ Reader.new(processed_lines(document, input_lines))
74
+ end
75
+
76
+ private
77
+
78
+ def processed_lines(document, input_lines)
79
+ result = []
80
+ loop do
81
+ line = input_lines.next
82
+ if yaml_block_match = line.match(/^\[yaml2text,(.+?),(.+?)\]/)
83
+ mark = input_lines.next
84
+ current_yaml_block = []
85
+ while (yaml_block_line = input_lines.next) != mark
86
+ current_yaml_block.push(yaml_block_line)
87
+ end
88
+ content = nested_open_struct_from_yaml(yaml_block_match[1], document)
89
+ result.push(*
90
+ parse_blocks_recursively(lines: current_yaml_block,
91
+ attributes: content,
92
+ context_name: yaml_block_match[2]))
93
+ else
94
+ result.push(line)
95
+ end
96
+ end
97
+ result
98
+ end
99
+
100
+ def nested_open_struct_from_yaml(file_path, document)
101
+ docfile_directory = File.dirname(document.attributes['docfile'] || '.')
102
+ yaml_file_path = document.path_resolver.system_path(file_path, docfile_directory)
103
+ content = YAML.safe_load(File.read(yaml_file_path))
104
+ # Load content as json, then parse with JSON as nested open_struct
105
+ JSON.parse(content.to_json, object_class: YamlBlockStruct)
106
+ end
107
+
108
+ def parse_blocks_recursively(lines:,
109
+ attributes:,
110
+ context_name:,
111
+ parent_context: nil)
112
+ lines = lines.to_enum
113
+ result = []
114
+ loop do
115
+ line = lines.next
116
+ if line.match(BLOCK_START_REGEXP)
117
+ line.gsub!(BLOCK_START_REGEXP, '<% \1.each.with_index do |\2,index| %>')
118
+ end
119
+
120
+ if line.match(BLOCK_END_REGEXP)
121
+ line.gsub!(BLOCK_END_REGEXP, '<% end %>')
122
+ end
123
+ line = line.gsub(/{(.+?[^}]*)}/, '<%= \1 %>').gsub(/[a-z\.]+\#/, 'index')
124
+ result.push(line)
125
+ end
126
+ result = parse_context_block(context_lines: result,
127
+ context_items: attributes,
128
+ context_name: context_name,
129
+ parent_context: parent_context)
130
+ result
131
+ end
132
+
133
+ def parse_context_block(context_lines:,
134
+ context_items:,
135
+ context_name:,
136
+ parent_context: nil)
137
+ renderer = YamlContextRenderer.new(context_object: context_items, context_name: context_name)
138
+ renderer.render(context_lines.join('\n')).split('\n')
139
+ end
140
+ end
141
+ end
142
+ end