diff options
author | Robert Speicher <rspeicher@gmail.com> | 2016-02-21 19:22:02 -0500 |
---|---|---|
committer | Robert Speicher <rspeicher@gmail.com> | 2016-02-21 19:23:41 -0500 |
commit | a8a8e1b5d662eb955a6a88d55f17ecf659c9a7ca (patch) | |
tree | c3bb386d139e7e7ae4fd28e1e05bdc11ae3f8d55 | |
parent | 2cd24b05ba3cce6f3edf42419ab153f13f4c74a0 (diff) | |
download | gitlab-ce-rs-frontmatter.tar.gz |
Add YamlFrontMatterFilter to the PreProcessPipeliners-frontmatter
This filter will detect YAML Front Matter and convert it to an HTML
table for prettier formatting.
-rw-r--r-- | lib/banzai/filter/yaml_front_matter_filter.rb | 70 | ||||
-rw-r--r-- | lib/banzai/pipeline/pre_process_pipeline.rb | 1 | ||||
-rw-r--r-- | spec/lib/banzai/filter/yaml_front_matter_filter_spec.rb | 137 |
3 files changed, 208 insertions, 0 deletions
diff --git a/lib/banzai/filter/yaml_front_matter_filter.rb b/lib/banzai/filter/yaml_front_matter_filter.rb new file mode 100644 index 00000000000..ed1371f17c4 --- /dev/null +++ b/lib/banzai/filter/yaml_front_matter_filter.rb @@ -0,0 +1,70 @@ +require 'html/pipeline/filter' +require 'yaml' + +module Banzai + module Filter + class YamlFrontMatterFilter < HTML::Pipeline::Filter + DELIM = '---'.freeze + + # Hat-tip to Middleman: https://git.io/v2e0z + PATTERN = %r{ + \A(?:[^\r\n]*coding:[^\r\n]*\r?\n)? + (?<start>#{DELIM})[ ]*\r?\n + (?<frontmatter>.*?)[ ]*\r?\n? + ^(?<stop>#{DELIM})[ ]*\r?\n? + \r?\n? + (?<additional_content>.*) + }mx.freeze + + def call + match = PATTERN.match(html) + + return html unless match + + frontmatter = load_yaml(match['frontmatter']) + + return html unless frontmatter + + tableize(frontmatter) << match['additional_content'] + end + + private + + def load_yaml(yaml_string) + YAML.safe_load(yaml_string, [Date, DateTime, Symbol, Time]) + end + + def escape(html) + ERB::Util.html_escape(html) + end + + # Converts a YAML object into an HTML table + # + # Each row is a key-value pair + def tableize(yaml) + table = "<table>\n" + + if yaml.respond_to?(:each_pair) + yaml.each_pair do |key, value| + table << "<tr><th>#{escape(key)}</th><td>" + + case value + when Array + value.inject(table) { |memo, v| memo << tableize(v) } + when Hash + table << tableize(value) + else + table << escape(value) + end + + table << "</td></tr>\n" + end + else + table << "<tr><td>#{escape(yaml)}</td></tr>" + end + + table << "</table>\n" + end + end + end +end diff --git a/lib/banzai/pipeline/pre_process_pipeline.rb b/lib/banzai/pipeline/pre_process_pipeline.rb index c174f0b862d..b3d1e9b661e 100644 --- a/lib/banzai/pipeline/pre_process_pipeline.rb +++ b/lib/banzai/pipeline/pre_process_pipeline.rb @@ -3,6 +3,7 @@ module Banzai class PreProcessPipeline < BasePipeline def self.filters [ + Filter::YamlFrontMatterFilter ] end diff --git a/spec/lib/banzai/filter/yaml_front_matter_filter_spec.rb b/spec/lib/banzai/filter/yaml_front_matter_filter_spec.rb new file mode 100644 index 00000000000..451b392c17a --- /dev/null +++ b/spec/lib/banzai/filter/yaml_front_matter_filter_spec.rb @@ -0,0 +1,137 @@ +require 'rails_helper' + +describe Banzai::Filter::YamlFrontMatterFilter, lib: true do + include FilterSpecHelper + + def parse(html) + Nokogiri::HTML.parse(html) + end + + it 'allows for `encoding:` before the frontmatter' do + content = <<-MD.strip_heredoc + # encoding: UTF-8 + --- + foo: foo + --- + + # Header + + Content + MD + + output = filter(content) + + expect(output).not_to match 'encoding' + end + + it 'converts YAML frontmatter to a table' do + content = <<-MD.strip_heredoc + --- + foo: foo string + bar: :bar_symbol + --- + + # Header + + Content + MD + + output = parse(filter(content)) + + aggregate_failures do + expect(output.xpath('.//tr[1]/th').text).to eq 'foo' + expect(output.xpath('.//tr[1]/td').text).to eq 'foo string' + expect(output.xpath('.//tr[2]/th').text).to eq 'bar' + expect(output.xpath('.//tr[2]/td').text).to eq 'bar_symbol' + end + end + + it 'loads YAML safely' do + pending "Unsure what we can actually put here to verify this." + + content = <<-MD.strip_heredoc + --- + --- + MD + + expect { filter(content) }.to raise_error(Psych::DisallowedClass) + end + + it 'HTML-escapes all YAML keys' do + content = <<-MD.strip_heredoc + --- + <b>foo</b>: 'foo' + --- + MD + + output = filter(content) + + expect(output).to match '<b>foo</b>' + end + + it 'HTML-escapes all YAML values' do + content = <<-MD.strip_heredoc + --- + foo: <th>foo</th> + --- + MD + + output = filter(content) + + expect(output).to match '<th>foo</th>' + end + + it 'supports Arrays' do + content = <<-MD.strip_heredoc + --- + foo: + - bar + - baz + --- + MD + + output = parse(filter(content)) + + aggregate_failures do + expect(output.css('table').length).to eq 3 + expect(output.xpath('.//table/tr[1]/td/table[1]/tr[1]/td[1]').text).to eq 'bar' + end + end + + it 'supports Arrays of Hashes' do + content = <<-MD.strip_heredoc + --- + mvps: + - version: 8.4 + name: Kyungchul Shin + date: January 22nd, 2016 + - version: 8.3 + name: Greg Smethells + date: December 22nd, 2015 + - version: 8.2 + name: Cristian Bica + date: November 22nd, 2015 + --- + MD + + output = parse(filter(content)) + + aggregate_failures do + expect(output.css('table').length).to eq 4 + expect(output.xpath('.//table/tr[1]/td/table[2]/tr[2]/th[1]').text).to eq 'name' + expect(output.xpath('.//table/tr[1]/td/table[2]/tr[2]/td[1]').text).to eq 'Greg Smethells' + end + end + + context 'on content without frontmatter' do + it 'returns the content unmodified' do + content = <<-MD.strip_heredoc + # This is some Markdown + + It has no YAML frontmatter to parse. + MD + + expect(filter(content)).to eq content + end + end +end |