gollum-lib 5.0.a.4-java → 5.0.5-java

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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -1
  3. data/HISTORY.md +4 -0
  4. data/README.md +12 -7
  5. data/Rakefile +8 -7
  6. data/gemspec.rb +17 -10
  7. data/gollum-lib.gemspec +8 -6
  8. data/gollum-lib_java.gemspec +2 -2
  9. data/lib/gollum-lib.rb +9 -9
  10. data/lib/gollum-lib/blob_entry.rb +2 -8
  11. data/lib/gollum-lib/committer.rb +23 -61
  12. data/lib/gollum-lib/file.rb +105 -82
  13. data/lib/gollum-lib/file_view.rb +8 -4
  14. data/lib/gollum-lib/filter.rb +12 -0
  15. data/lib/gollum-lib/filter/code.rb +9 -13
  16. data/lib/gollum-lib/filter/critic_markup.rb +97 -0
  17. data/lib/gollum-lib/filter/emoji.rb +11 -8
  18. data/lib/gollum-lib/filter/macro.rb +5 -2
  19. data/lib/gollum-lib/filter/plantuml.rb +1 -1
  20. data/lib/gollum-lib/filter/remote_code.rb +3 -2
  21. data/lib/gollum-lib/filter/render.rb +25 -2
  22. data/lib/gollum-lib/filter/sanitize.rb +1 -8
  23. data/lib/gollum-lib/filter/tags.rb +57 -46
  24. data/lib/gollum-lib/filter/toc.rb +17 -21
  25. data/lib/gollum-lib/filter/yaml.rb +1 -1
  26. data/lib/gollum-lib/git_access.rb +0 -25
  27. data/lib/gollum-lib/helpers.rb +13 -3
  28. data/lib/gollum-lib/macro.rb +4 -0
  29. data/lib/gollum-lib/macro/audio.rb +9 -0
  30. data/lib/gollum-lib/macro/global_toc.rb +2 -1
  31. data/lib/gollum-lib/macro/navigation.rb +8 -6
  32. data/lib/gollum-lib/macro/note.rb +19 -0
  33. data/lib/gollum-lib/macro/octicon.rb +12 -0
  34. data/lib/gollum-lib/macro/series.rb +2 -2
  35. data/lib/gollum-lib/macro/warn.rb +11 -0
  36. data/lib/gollum-lib/markup.rb +17 -32
  37. data/lib/gollum-lib/markups.rb +13 -8
  38. data/lib/gollum-lib/page.rb +80 -166
  39. data/lib/gollum-lib/pagination.rb +7 -6
  40. data/lib/gollum-lib/redirects.rb +42 -0
  41. data/lib/gollum-lib/sanitization.rb +32 -357
  42. data/lib/gollum-lib/version.rb +1 -1
  43. data/lib/gollum-lib/wiki.rb +216 -404
  44. metadata +65 -27
  45. data/ROADMAP +0 -6
@@ -14,17 +14,11 @@ class Gollum::Filter::Emoji < Gollum::Filter
14
14
  (?!\]{^2})
15
15
  }ix
16
16
 
17
- PROCESS_PATTERN = %r{
18
- =EEMMOOJJII=
19
- (?<name>[\w-]+)
20
- =IIJJOOMMEE=
21
- }ix
22
-
23
17
  def extract(data)
24
18
  data.gsub! EXTRACT_PATTERN do
25
19
  case
26
20
  when $~[:escape] then $&[1..-1]
27
- when emoji_exists?($~[:name]) then "=EEMMOOJJII=#{$~[:name]}=IIJJOOMMEE="
21
+ when emoji_exists?($~[:name]) then "#{open_pattern}#{$~[:name]}#{close_pattern}"
28
22
  else $&
29
23
  end
30
24
  end
@@ -32,12 +26,21 @@ class Gollum::Filter::Emoji < Gollum::Filter
32
26
  end
33
27
 
34
28
  def process(data)
35
- data.gsub! PROCESS_PATTERN, %q(<img src="/emoji/\k<name>" alt="\k<name>" class="emoji">)
29
+ src = ::File.join(@markup.wiki.base_path, '/gollum/emoji/\\k<name>')
30
+ data.gsub! process_pattern, %Q(<img src="#{src}" alt="\\k<name>" class="emoji">)
36
31
  data
37
32
  end
38
33
 
39
34
  private
40
35
 
36
+ def process_pattern
37
+ %r{
38
+ #{open_pattern}
39
+ (?<name>[\w-]+)
40
+ #{close_pattern}
41
+ }ix
42
+ end
43
+
41
44
  def emoji_exists?(name)
42
45
  @index ||= Gemojione::Index.new
43
46
  !!@index.find_by_name(name)
@@ -1,4 +1,5 @@
1
1
  # ~*~ encoding: utf-8 ~*~
2
+ require 'octicons'
2
3
 
3
4
  # Replace specified tokens with dynamically generated content.
4
5
  class Gollum::Filter::Macro < Gollum::Filter
@@ -12,7 +13,7 @@ class Gollum::Filter::Macro < Gollum::Filter
12
13
 
13
14
  data.gsub(/('?)\<\<\s*([A-Z][A-Za-z0-9]*)\s*\(#{arg_list}\)\s*\>\>/) do
14
15
  next CGI.escape_html($&[1..-1]) unless Regexp.last_match[1].empty?
15
- id = Digest::SHA1.hexdigest(Regexp.last_match[2] + Regexp.last_match[3])
16
+ id = "#{open_pattern}#{Digest::SHA1.hexdigest(Regexp.last_match[2] + Regexp.last_match[3])}#{close_pattern}"
16
17
  macro = Regexp.last_match[2]
17
18
  argstr = Regexp.last_match[3]
18
19
  args = []
@@ -47,7 +48,9 @@ class Gollum::Filter::Macro < Gollum::Filter
47
48
  begin
48
49
  Gollum::Macro.instance(macro, @markup.wiki, @markup.page).render(*args)
49
50
  rescue StandardError => e
50
- "!!!Macro Error: #{e.message}!!!"
51
+ icon = Octicons::Octicon.new('zap', {width: 24, height: 24})
52
+ icon.options[:class] << ' mr-2'
53
+ "<div class='flash flash-error'>#{icon.to_svg}Macro Error for #{macro}: #{e.message}</div>"
51
54
  end
52
55
  end
53
56
  end
@@ -70,7 +70,7 @@ class Gollum::Filter::PlantUML < Gollum::Filter
70
70
  # placeholders.
71
71
  def extract(data)
72
72
  data.gsub(/(@startuml\r?\n.+?\r?\n@enduml\r?$)/m) do
73
- id = Digest::SHA1.hexdigest($1)
73
+ id = "#{open_pattern}#{Digest::SHA1.hexdigest($1)}#{close_pattern}"
74
74
  @map[id] = { :code => $1 }
75
75
  id
76
76
  end
@@ -20,7 +20,7 @@ class Gollum::Filter::RemoteCode < Gollum::Filter
20
20
 
21
21
  # Detect local file
22
22
  if protocol.nil?
23
- if (file = @markup.find_file(uri, @markup.wiki.ref))
23
+ if (file = @markup.wiki.file(uri, @markup.wiki.ref))
24
24
  contents = file.raw_data
25
25
  else
26
26
  # How do we communicate a render error?
@@ -45,7 +45,8 @@ class Gollum::Filter::RemoteCode < Gollum::Filter
45
45
  return "Too many redirects or retries" if cut >= 10
46
46
  http = Net::HTTP.new uri.host, uri.port
47
47
  http.use_ssl = true
48
- resp = http.get uri.path, {
48
+ path = uri.path.empty? ? '/' : uri.path
49
+ resp = http.get path, {
49
50
  'Accept' => 'text/plain',
50
51
  'Cache-Control' => 'no-cache',
51
52
  'Connection' => 'keep-alive',
@@ -3,7 +3,11 @@
3
3
  class Gollum::Filter::Render < Gollum::Filter
4
4
  def extract(data)
5
5
  begin
6
- data = GitHub::Markup.render(@markup.name, data)
6
+ working_dir = Pathname.new(@markup.wiki.path).join(@markup.dir)
7
+ working_dir = working_dir.exist? ? working_dir.to_s : '.'
8
+ Dir.chdir(working_dir) do
9
+ data = GitHub::Markup.render_s(@markup.format, data)
10
+ end
7
11
  if data.nil?
8
12
  raise "There was an error converting #{@markup.name} to HTML."
9
13
  end
@@ -15,6 +19,25 @@ class Gollum::Filter::Render < Gollum::Filter
15
19
  end
16
20
 
17
21
  def process(data)
22
+ data = add_editable_header_class(data)
18
23
  data
19
24
  end
20
- end
25
+
26
+ private
27
+
28
+ def add_editable_header_class(data)
29
+ doc = Nokogiri::HTML::DocumentFragment.parse(data)
30
+ doc.css('h1,h2,h3,h4,h5,h6').each_with_index do |header, i|
31
+ next if header.content.empty?
32
+ next if header.inner_html.match(PLACEHOLDER_PATTERN)
33
+ klass = header['class']
34
+ if klass
35
+ header['class'] = klass << ' editable'
36
+ else
37
+ header['class'] = 'editable'
38
+ end
39
+ end
40
+ doc.to_xml(@markup.class.to_xml_opts)
41
+ end
42
+
43
+ end
@@ -6,13 +6,6 @@ class Gollum::Filter::Sanitize < Gollum::Filter
6
6
  end
7
7
 
8
8
  def process(data)
9
- if @markup.sanitize
10
- doc = Nokogiri::HTML::DocumentFragment.parse(data)
11
- doc = @markup.sanitize.clean_node!(doc)
12
-
13
- doc.to_xml(@markup.to_xml_opts).gsub(/<p><\/p>/, '')
14
- else
15
- data
16
- end
9
+ sanitize(data)
17
10
  end
18
11
  end
@@ -34,7 +34,7 @@ class Gollum::Filter::Tags < Gollum::Filter
34
34
  doc.traverse do |node|
35
35
  if node.text? then
36
36
  content = node.content
37
- content.gsub!(/TAG[a-f0-9]+TAG/) do |id|
37
+ content.gsub!(%r{#{open_pattern}[a-f0-9]+#{close_pattern}}) do |id|
38
38
  if (tag = @map[id]) then
39
39
  if is_preformatted?(node) then
40
40
  "[[#{tag}]]"
@@ -56,7 +56,7 @@ class Gollum::Filter::Tags < Gollum::Filter
56
56
  INCLUDE_TAG = 'include:'
57
57
 
58
58
  def register_tag(tag)
59
- id = "TAG#{Digest::SHA1.hexdigest(tag)}TAG"
59
+ id = "#{open_pattern}#{Digest::SHA1.hexdigest(tag)}#{close_pattern}"
60
60
  @map[id] = tag
61
61
  id
62
62
  end
@@ -77,7 +77,6 @@ class Gollum::Filter::Tags < Gollum::Filter
77
77
  return generate_link('', nil, nil, :page_absent) if link_part.nil?
78
78
  img_args = extra ? [extra, link_part] : [link_part]
79
79
  mime = MIME::Types.type_for(::File.extname(img_args.first.to_s)).first
80
-
81
80
  result = if tag =~ /^_TOC_/
82
81
  %{[[#{tag}]]}
83
82
  elsif link_part =~ /^_$/
@@ -122,9 +121,9 @@ class Gollum::Filter::Tags < Gollum::Filter
122
121
  len = INCLUDE_TAG.length
123
122
  return html_error('Cannot process include directive: no page name given') if tag.length <= len
124
123
  page_name = tag[len..-1]
125
- resolved_page_name = ::File.expand_path(page_name, "#{::File::SEPARATOR}#{@markup.dir}")
124
+ resolved_page_name = ::File.join(@markup.dir, page_name)
126
125
  if @markup.include_levels > 0
127
- page = find_page_from_path(resolved_page_name)
126
+ page = find_page_or_file_from_path(resolved_page_name)
128
127
  if page
129
128
  page.formatted_data(@markup.encoding, @markup.include_levels-1)
130
129
  else
@@ -146,8 +145,8 @@ class Gollum::Filter::Tags < Gollum::Filter
146
145
  opts = parse_image_tag_options(options)
147
146
  if path =~ /^https?:\/\/.+$/i
148
147
  generate_image(path, opts)
149
- elsif file = @markup.find_file(path)
150
- generate_image(generate_href_for_path(file.path), opts)
148
+ elsif file = find_page_or_file_from_path(path, :file)
149
+ generate_image(generate_href_for_path(file.url_path), opts)
151
150
  else
152
151
  generate_image('', opts)
153
152
  end
@@ -173,14 +172,8 @@ class Gollum::Filter::Tags < Gollum::Filter
173
172
  # Return the String HTML if the tag is a valid external link tag or
174
173
  # nil if it is not.
175
174
  def process_external_link_tag(url, pretty_name = nil)
176
- accepted_protocols = @markup.wiki.sanitization.protocols['a']['href'].dup
177
- if accepted_protocols.include?(:relative)
178
- accepted_protocols.select!{|protocol| protocol != :relative}
179
- regexp = %r{^((#{accepted_protocols.join("|")}):)?(//)}
180
- else
181
- regexp = %r{^((#{accepted_protocols.join("|")}):)}
182
- end
183
- if url =~ regexp
175
+ @accepted_protocols_regex ||= %r{^((#{::Gollum::Sanitization.accepted_protocols.join('|')}):)?(//)}
176
+ if url =~ @accepted_protocols_regex
184
177
  generate_link(url, pretty_name, nil, :external)
185
178
  else
186
179
  nil
@@ -195,8 +188,9 @@ class Gollum::Filter::Tags < Gollum::Filter
195
188
  # Returns the String HTML if the tag is a valid file link tag or nil
196
189
  # if it is not.
197
190
  def process_file_link_tag(link_part, pretty_name)
198
- if file = @markup.find_file(link_part)
199
- generate_link(file.path, pretty_name, nil, :file)
191
+ return nil if ::Gollum::Page.valid_extension?(link_part)
192
+ if file = find_page_or_file_from_path(link_part, :file)
193
+ generate_link(file.url_path, pretty_name, nil, :file)
200
194
  else
201
195
  nil
202
196
  end
@@ -212,18 +206,32 @@ class Gollum::Filter::Tags < Gollum::Filter
212
206
  def process_page_link_tag(link_part, pretty_name = nil)
213
207
  presence = :page_absent
214
208
  link = link_part
215
- page = find_page_from_path(link)
209
+ page = find_page_or_file_from_path(link)
216
210
 
217
211
  # If no match yet, try finding page with anchor removed
218
- if (page.nil? && pos = link.rindex('#'))
219
- extra = link[pos..-1]
220
- link = link[0...pos]
221
- page = find_page_from_path(link)
212
+ if page.nil?
213
+ if pos = link.rindex('#')
214
+ extra = link[pos..-1]
215
+ link = link[0...pos]
216
+ else
217
+ extra = nil
218
+ end
219
+
220
+ if link.empty? && extra # Internal anchor link, don't search for the page but return immediately
221
+ return generate_link(nil, pretty_name, extra, :internal_anchor)
222
+ end
223
+
224
+ page = find_page_or_file_from_path(link)
225
+ end
226
+ presence = :page_present if page
227
+
228
+ if pretty_name
229
+ name = pretty_name
230
+ else
231
+ name = page ? path_to_link_text(link) : link
222
232
  end
223
- presence = :page_present if page
224
233
 
225
- name = pretty_name ? pretty_name : link
226
- link = page ? page.escaped_url_path : CGI.escape(link)
234
+ link = page ? page.escaped_url_path : ERB::Util.url_encode(link).force_encoding('utf-8')
227
235
  generate_link(link, name, extra, presence)
228
236
  end
229
237
 
@@ -232,15 +240,15 @@ class Gollum::Filter::Tags < Gollum::Filter
232
240
  # path - The String path to search for.
233
241
  #
234
242
  # Returns a Gollum::Page instance if a page is found, or nil otherwise
235
- def find_page_from_path(path)
236
- slash = path.rindex('/')
237
-
238
- unless slash.nil?
239
- name = path[slash+1..-1]
240
- path = path[0..slash]
241
- @markup.wiki.paged(name, path)
243
+ def find_page_or_file_from_path(path, kind = :page)
244
+ if Pathname.new(path).relative?
245
+ result = @markup.wiki.send(kind, ::File.join(@markup.dir, path))
246
+ if result.nil? && @markup.wiki.global_tag_lookup # 4.x link compatibility option. Slow!
247
+ result = @markup.wiki.send(kind, path, nil, true)
248
+ end
249
+ result
242
250
  else
243
- @markup.wiki.page(path)
251
+ @markup.wiki.send(kind, path)
244
252
  end
245
253
  end
246
254
 
@@ -264,7 +272,8 @@ class Gollum::Filter::Tags < Gollum::Filter
264
272
  #
265
273
  # Returns a String href.
266
274
  def generate_href_for_path(path, extra = nil)
267
- "#{trim_leading_slash(::File.join(@markup.wiki.base_path, path))}#{extra}"
275
+ return extra if !path && extra # Internal anchor link
276
+ "#{trim_leading_slashes(::File.join(@markup.wiki.base_path, path))}#{extra}"
268
277
  end
269
278
 
270
279
  # Construct a CSS class and attribute string for different kinds of links: internal Pages (absent or present) and Files, and External links.
@@ -278,6 +287,8 @@ class Gollum::Filter::Tags < Gollum::Filter
278
287
  'class="internal absent"'
279
288
  when :page_present
280
289
  'class="internal present"'
290
+ when :internal_anchor
291
+ 'class="internal anchorlink"'
281
292
  when :file
282
293
  nil
283
294
  when :external
@@ -299,10 +310,10 @@ class Gollum::Filter::Tags < Gollum::Filter
299
310
  attr_string = attrs.map {|key, value| "#{key}=\"#{value}\""}.join(' ')
300
311
 
301
312
  if containered
302
- %{<span class="#{classes.join(' ')}">} +
303
- %{<span>} +
313
+ %{<span class="d-flex #{classes[:container].join(' ')}">} +
314
+ %{<span class="#{classes[:nested].join(' ')}">} +
304
315
  %{<img src="#{path}" #{attr_string}/>} +
305
- (attrs[:alt] ? %{<span>#{attrs[:alt]}</span>} : '') +
316
+ (options[:frame] && attrs[:alt] ? %{<span class="clearfix">#{attrs[:alt]}</span>} : '') +
306
317
  %{</span>} +
307
318
  %{</span>}
308
319
  else
@@ -314,31 +325,31 @@ class Gollum::Filter::Tags < Gollum::Filter
314
325
  #
315
326
  # options - The Hash of parsed image options.
316
327
  #
317
- # Returns an Array of CSS classes, a Hash of CSS attributes, and a Boolean indicating whether or not the image is containered.
328
+ # Returns a Hash containing CSS class Arrays, a Hash of CSS attributes, and a Boolean indicating whether or not the image is containered.
318
329
  def generate_image_attributes(options)
319
330
  containered = false
320
- classes = [] # applied to whatever the outermost container is
331
+ classes = {container: [], nested: []} # applied to the container(s)
321
332
  attrs = {} # applied to the image
322
333
 
323
334
  align = options[:align]
324
335
  if options[:float]
325
336
  containered = true
326
- align ||= 'left'
327
- if %w{left right}.include?(align)
328
- classes << "float-#{align}"
329
- end
337
+ align = 'left' unless align == 'right'
338
+ classes[:container] << "float-#{align} pb-4"
330
339
  elsif %w{top texttop middle absmiddle bottom absbottom baseline}.include?(align)
331
340
  attrs[:align] = align
332
341
  elsif align
333
342
  if %w{left center right}.include?(align)
334
343
  containered = true
335
- classes << "align-#{align}"
344
+ text_align = "text-#{align}"
345
+ align = 'end' if align == 'right'
346
+ classes[:container] << "flex-justify-#{align} #{text_align}"
336
347
  end
337
348
  end
338
349
 
339
- if options[:frame]
350
+ if options[:frame]
340
351
  containered = true
341
- classes << 'frame'
352
+ classes[:nested] << 'border p-4'
342
353
  end
343
354
 
344
355
  attrs[:alt] = options[:alt] if options[:alt]
@@ -21,15 +21,14 @@ class Gollum::Filter::TOC < Gollum::Filter
21
21
  next if (i == 0 && header.name =~ /[Hh]1/) && @markup.wiki && @markup.wiki.h1_title
22
22
 
23
23
  anchor_name = generate_anchor_name(header)
24
-
25
24
  add_anchor_to_header header, anchor_name
26
25
  add_entry_to_toc header, anchor_name
27
26
  end
28
27
  if not @toc_doc.nil?
29
- toc_str = @toc_doc.to_xml(@markup.to_xml_opts)
28
+ toc_str = @toc_doc.to_xml(@markup.class.to_xml_opts)
30
29
  end
31
30
 
32
- data = @doc.to_xml(@markup.to_xml_opts)
31
+ data = @doc.to_xml(@markup.class.to_xml_opts)
33
32
  end
34
33
 
35
34
  @markup.toc = toc_str
@@ -51,7 +50,7 @@ class Gollum::Filter::TOC < Gollum::Filter
51
50
  e.remove
52
51
  end
53
52
  end
54
- toc_clone.to_xml(@markup.to_xml_opts)
53
+ toc_clone.to_xml(@markup.class.to_xml_opts)
55
54
  end
56
55
  end
57
56
 
@@ -60,9 +59,9 @@ class Gollum::Filter::TOC < Gollum::Filter
60
59
 
61
60
  private
62
61
 
63
- # Generates the anchor name from the given header element
62
+ # Generates the anchor name from the given header element
64
63
  # removing all non alphanumeric characters, replacing them
65
- # with single dashes.
64
+ # with single dashes.
66
65
  #
67
66
  # Generates heading ancestry prefixing the headings
68
67
  # ancestor names to the generated name.
@@ -70,31 +69,28 @@ class Gollum::Filter::TOC < Gollum::Filter
70
69
  # Prefixes duplicate anchors with an index
71
70
  def generate_anchor_name(header)
72
71
  name = header.content
73
- level = header.name.gsub(/[hH]/, '').to_i
72
+ level = header.name[1..-1].to_i
74
73
 
75
74
  # normalize the header name
76
- name.gsub!(/[^\d\w\u00C0-\u1FFF\u2C00-\uD7FF]/, "-")
77
- name.gsub!(/-+/, "-")
78
- name.gsub!(/^-/, "")
79
- name.gsub!(/-$/, "")
75
+ name.gsub!(/[^\d\w\u00C0-\u1FFF\u2C00-\uD7FF]/, '-')
76
+ name.gsub!(/-+/, '-')
77
+ name.gsub!(/^-/, '')
78
+ name.gsub!(/-$/, '')
80
79
  name.downcase!
81
80
 
82
- @current_ancestors[level - 1] = name
83
- @current_ancestors = @current_ancestors.take(level)
84
- anchor_name = @current_ancestors.compact.join("_")
85
-
86
81
  # Ensure duplicate anchors have a unique prefix or the toc will break
87
- index = increment_anchor_index(anchor_name)
88
- anchor_name = "#{index}-#{anchor_name}" unless index.zero? # if the index is zero this name is unique
89
-
90
- anchor_name
82
+ index = increment_anchor_index(name)
83
+ index.zero? ? name : "#{name}-#{index}"
91
84
  end
92
85
 
93
86
  # Creates an anchor element with the given name and adds it before
94
87
  # the given header element.
95
88
  def add_anchor_to_header(header, name)
96
- anchor_element = %Q(<a class="anchor" id="#{name}" href="##{name}"><i class="fa fa-link"></i></a>)
97
- header.children.before anchor_element # Add anchor element before the header
89
+ a = Nokogiri::XML::Node.new('a', @doc)
90
+ a['class'] = 'anchor'
91
+ a['id'] = name
92
+ a['href'] = "##{name}"
93
+ header.children.before(a) # Add anchor element before the header
98
94
  end
99
95
 
100
96
  # Adds an entry to the TOC for the given header. The generated entry
@@ -10,7 +10,7 @@ class Gollum::Filter::YAML < Gollum::Filter
10
10
  data.gsub!(YAML_FRONT_MATTER_REGEXP) do
11
11
  @markup.metadata ||= {}
12
12
  begin
13
- frontmatter = ::YAML.safe_load(@markup.sanitize.clean(Regexp.last_match[1]))
13
+ frontmatter = ::YAML.safe_load(sanitize(Regexp.last_match[1]))
14
14
  @markup.metadata.merge!(frontmatter) if frontmatter.respond_to?(:keys) && frontmatter.respond_to?(:values)
15
15
  rescue ::Psych::SyntaxError, ::Psych::DisallowedClass, ::Psych::BadAlias => error
16
16
  @markup.metadata['errors'] ||= []