Class: Metanorma::Standoc::EmbedIncludeProcessor

Inherits:
Asciidoctor::Extensions::Preprocessor
  • Object
show all
Defined in:
lib/metanorma/standoc/macros_embed.rb

Instance Method Summary collapse

Instance Method Details

#embed(line, acc, headings) ⇒ Object



151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/metanorma/standoc/macros_embed.rb', line 151

def embed(line, acc, headings)
  fname, inc_path = filename(line, acc)
  lines = filter_sections(read(inc_path), headings)
  n = Asciidoctor::Document
    .new [], { safe: :safe, base_dir: File.dirname(inc_path) }
  r = ::Asciidoctor::PreprocessorNoIfdefsReader.new n, lines
  ret = embed_acc(n, r).merge(strip_header(r.readlines))
    .merge(file: fname, path: inc_path, orig: acc[:orig])
  ret[:hdr] or
    raise "Embedding an incomplete document with no header: #{ret[:path]}"
  embed_recurse(ret, n, r, headings)
end

#embed_acc(doc, reader) ⇒ Object



48
49
50
51
52
# File 'lib/metanorma/standoc/macros_embed.rb', line 48

def embed_acc(doc, reader)
  { lines: [], hdr: [], id: [],
    orig: doc, doc: doc, file: nil, path: nil,
    reader: reader, prev: nil }
end

#embed_recurse(ret, doc, reader, headings) ⇒ Object



164
165
166
167
168
169
170
171
172
# File 'lib/metanorma/standoc/macros_embed.rb', line 164

def embed_recurse(ret, doc, reader, headings)
  ret1 = ret[:lines].each_with_object(embed_acc(doc, reader)) do |line, m|
    process_line(line, m, headings)
  end
  ret.merge(
    { lines: ret1[:lines], id: ret[:id] + ret1[:id],
      hdr: { text: ret[:hdr].join("\n"), child: ret1[:hdr] } },
  )
end

#filename(line, acc) ⇒ Object



128
129
130
131
132
133
134
135
136
137
# File 'lib/metanorma/standoc/macros_embed.rb', line 128

def filename(line, acc)
  m = /^embed::([^\[]+)\[/.match(line)
  f = acc[:doc].normalize_system_path m[1], acc[:reader].dir, nil,
                                      target_name: "include file"
  unless File.exist?(f)
    err = "Missing embed file: #{line}"
    raise err
  end
  [m[1], f]
end

#filter_sections(lines, headings) ⇒ Object



185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
# File 'lib/metanorma/standoc/macros_embed.rb', line 185

def filter_sections(lines, headings)
  skip = false
  lines.each_with_index.with_object([]) do |(l, i), m|
    if headings.include?(l.strip)
      skip = true
      m.pop while !m.empty? && /^\S/.match?(m[-1])
    elsif skip && /^== |^embed::|^include::/.match?(l)
      skip = false
      j = i
      j -= 1 while j >= 0 && /^\S/.match?(m[j])
      lines[j..i].each { |n| m << n }
    else skip or m << l
    end
  end
end

#flatten_embeds(emb) ⇒ Object

lines can contain recursive embed structs, which are resolved into a flat listing of included line chunks (top level doc has { file: nil } )



82
83
84
85
86
87
88
89
90
91
92
# File 'lib/metanorma/standoc/macros_embed.rb', line 82

def flatten_embeds(emb)
  acc = []
  ret = emb[:lines].each_with_object([]) do |l, m|
    if l.is_a?(Hash)
      acc, m = update_embeds(acc, m, emb)
      flatten_embeds(l).each { |x| m << x }
    else acc << l end
  end
  acc, ret = update_embeds(acc, ret, emb)
  ret
end

#process(doc, reader) ⇒ Object



37
38
39
40
41
42
43
44
45
46
# File 'lib/metanorma/standoc/macros_embed.rb', line 37

def process(doc, reader)
  reader.eof? and return reader
  r = ::Asciidoctor::PreprocessorNoIfdefsReader.new doc, reader.lines
  lines = r.readlines
  headings = lines.grep(/^== /).map(&:strip)
  ret = lines.each_with_object(embed_acc(doc, r)) do |line, m|
    process_line(line, m, headings)
  end
  return_to_document(doc, ret)
end

#process_embed(acc, embed, prev) ⇒ Object



111
112
113
114
115
116
117
# File 'lib/metanorma/standoc/macros_embed.rb', line 111

def process_embed(acc, embed, prev)
  acc, embed = process_embed_anchor(acc, embed, prev)
  acc[:lines] << embed
  acc[:hdr] << embed[:hdr]
  acc[:id] += embed[:id]
  acc
end

#process_embed_anchor(acc, embed, prev) ⇒ Object



119
120
121
122
123
124
125
126
# File 'lib/metanorma/standoc/macros_embed.rb', line 119

def process_embed_anchor(acc, embed, prev)
  if /^\[\[.+\]\]/.match?(prev) # anchor
    acc[:id] << prev.sub(/^\[\[/, "").sub(/\]\]$/, "")
    i = embed[:lines].index { |x| /^== /.match?(x) } and
      embed[:lines][i] += " #{prev}" # => bookmark
  end
  [acc, embed]
end

#process_line(line, acc, headings) ⇒ Object



100
101
102
103
104
105
106
107
108
109
# File 'lib/metanorma/standoc/macros_embed.rb', line 100

def process_line(line, acc, headings)
  if /^embed::/.match?(line)
    e = embed(line, acc, headings)
    acc = process_embed(acc, e, acc[:prev])
  else
    acc[:lines] << line
  end
  acc[:prev] = line
  acc
end

#read(inc_path) ⇒ Object



145
146
147
148
149
# File 'lib/metanorma/standoc/macros_embed.rb', line 145

def read(inc_path)
  ::File.open inc_path, "r" do |fd|
    readlines_safe(fd).map(&:chomp)
  end
end

#read_flattened_embeds(ret, doc) ⇒ Object

lines can contain recursive embed structs, containing the lines to read and the file they are in; read these into the (new) reader. This is intended to resolve any file crossreferences, with file paths resolved relative to current file directory – but it won’t: github.com/metanorma/metanorma-standoc/issues/802



66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/metanorma/standoc/macros_embed.rb', line 66

def read_flattened_embeds(ret, doc)
  reader = ::Asciidoctor::PreprocessorReader.new doc
  b = Pathname.new doc.base_dir
  ret.reverse.each do |l|
    # if l[:file]
    # new = Pathname.new(l[:path]).relative_path_from(b).to_s
    # reader.push_include l[:lines], new, l[:path]
    reader.unshift_lines l[:lines]
    # else reader.unshift_lines l[:lines]
    # end
  end
  reader
end

#readlines_safe(file) ⇒ Object



139
140
141
142
143
# File 'lib/metanorma/standoc/macros_embed.rb', line 139

def readlines_safe(file)
  if file.eof? then []
  else file.readlines
  end
end

#return_to_document(doc, embed) ⇒ Object

presupposes single embed



55
56
57
58
59
# File 'lib/metanorma/standoc/macros_embed.rb', line 55

def return_to_document(doc, embed)
  doc.attributes["embed_hdr"] = embed[:hdr]
  doc.attributes["embed_id"] = embed[:id]
  read_flattened_embeds(flatten_embeds(embed), doc)
end

#strip_header(lines) ⇒ Object



174
175
176
177
178
179
180
181
182
183
# File 'lib/metanorma/standoc/macros_embed.rb', line 174

def strip_header(lines)
  return { lines: lines, hdr: nil } unless !lines.empty? &&
    lines.first.start_with?("= ")

  skip = true
  lines.each_with_object({ hdr: [], lines: [] }) do |l, m|
    m[skip ? :hdr : :lines] << l
    skip = false if !/\S/.match?(l)
  end
end

#update_embeds(lines, acc, emb) ⇒ Object



94
95
96
97
98
# File 'lib/metanorma/standoc/macros_embed.rb', line 94

def update_embeds(lines, acc, emb)
  lines.empty? or
    acc << { file: emb[:file], path: emb[:path], lines: lines }
  [[], acc]
end