├── Gemfile ├── .rubocop.yml ├── .gitignore ├── .travis.yml ├── Rakefile ├── test ├── support │ └── new_pipeline.rb ├── test_helper.rb └── test_jekyll_html_pipeline.rb ├── LICENSE.txt ├── jekyll-html-pipeline.gemspec ├── README.md └── lib └── jekyll-html-pipeline.rb /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | 5 | # Specify your gem's dependencies in jekyll-html-pipeline.gemspec 6 | gemspec 7 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | inherit_gem: 2 | rubocop-standard: 3 | - config/default.yml 4 | 5 | Style/StringLiterals: 6 | Enabled: true 7 | EnforcedStyle: single_quotes 8 | 9 | RequireParentheses: 10 | Enabled: true 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | Gemfile.lock 7 | InstalledFiles 8 | _yardoc 9 | coverage 10 | doc/ 11 | lib/bundler/man 12 | pkg 13 | rdoc 14 | spec/reports 15 | test/tmp 16 | test/version_tmp 17 | tmp 18 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 2.3.6 4 | - 2.4.3 5 | - 2.5.0 6 | 7 | git: 8 | depth: 10 9 | 10 | sudo: false 11 | cache: bundler 12 | 13 | matrix: 14 | include: 15 | - script: bundle exec rake rubocop 16 | rvm: 2.5.0 17 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bundler/gem_tasks' 4 | 5 | task default: [:test] 6 | 7 | require 'rake/testtask' 8 | Rake::TestTask.new(:test) do |test| 9 | test.libs << 'lib' << 'test' 10 | test.pattern = 'test/**/test_*.rb' 11 | test.verbose = true 12 | end 13 | 14 | require 'rubocop/rake_task' 15 | 16 | RuboCop::RakeTask.new(:rubocop) 17 | -------------------------------------------------------------------------------- /test/support/new_pipeline.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'html/pipeline' 4 | 5 | class HelpMarkdownFilter < HTML::Pipeline::MarkdownFilter 6 | def call 7 | html = super 8 | 9 | format_callout!(html) 10 | end 11 | 12 | def format_callout!(html) 13 | html.gsub!(%r{(?:

)?{{#(tip|warning|error)}}(?:

)?}, '
') 14 | html.gsub!(%r{(?:

)?{{/(tip|warning|error)}}(?:

)?}, '
') 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Garen Torikian 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rubygems' 4 | 5 | require 'jekyll' 6 | require 'liquid' 7 | 8 | require 'minitest/autorun' 9 | 10 | require 'jekyll-html-pipeline' 11 | 12 | # Send STDERR into the void to suppress program output messages 13 | # STDERR.reopen(test(?e, '/dev/null') ? '/dev/null' : 'NUL:') 14 | 15 | module Converter 16 | class HTMLPipelineTestCase < MiniTest::Test 17 | end 18 | end 19 | 20 | # module 21 | # class Test::Unit::TestCase 22 | # def dest_dir(*subdirs) 23 | # test_dir('dest', *subdirs) 24 | # end 25 | 26 | # def source_dir(*subdirs) 27 | # test_dir('source', *subdirs) 28 | # end 29 | 30 | # def clear_dest 31 | # FileUtils.rm_rf(dest_dir) 32 | # end 33 | 34 | # def test_dir(*subdirs) 35 | # File.join(File.dirname(__FILE__), *subdirs) 36 | # end 37 | 38 | # def directory_with_contents(path) 39 | # FileUtils.rm_rf(path) 40 | # FileUtils.mkdir(path) 41 | # File.open("#{path}/index.html", "w"){ |f| f.write("I was previously generated.") } 42 | # end 43 | 44 | # def capture_stdout 45 | # $old_stdout = $stdout 46 | # $stdout = StringIO.new 47 | # yield 48 | # $stdout.rewind 49 | # return $stdout.string 50 | # ensure 51 | # $stdout = $old_stdout 52 | # end 53 | # end 54 | -------------------------------------------------------------------------------- /jekyll-html-pipeline.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Gem::Specification.new do |spec| 4 | spec.name = 'jekyll-html-pipeline' 5 | spec.version = '1.1.1' 6 | spec.authors = ['Garen Torikian'] 7 | spec.email = ['gjtorikian@gmail.com'] 8 | spec.summary = "Use GitHub's HTML::Pipeline, in Jekyll!" 9 | spec.description = "This is a custom Markdown processor for Jekyll 2.0 and above. It allows you to use GitHub's HTML::Pipeline in your Jekyll projects. " 10 | spec.homepage = 'https://github.com/gjtorikian/jekyll-html-pipeline' 11 | spec.license = 'MIT' 12 | 13 | spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR) 14 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 15 | spec.test_files = spec.files.grep(%r{^(test)/}) 16 | spec.require_paths = ['lib'] 17 | 18 | spec.add_runtime_dependency 'jekyll', '~> 3.0' 19 | spec.add_dependency 'html-pipeline', '~> 2.8' 20 | 21 | spec.add_development_dependency 'commonmarker', '~> 0.17' 22 | spec.add_development_dependency 'gemoji', '~> 2.0' 23 | spec.add_development_dependency 'minitest', '~> 5.0' 24 | spec.add_development_dependency 'rake' 25 | spec.add_development_dependency 'rubocop' 26 | spec.add_development_dependency 'rubocop-standard' 27 | spec.add_development_dependency 'sanitize', '~> 2.0.6' 28 | end 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/gjtorikian/jekyll-html-pipeline.svg?branch=master)](https://travis-ci.org/gjtorikian/jekyll-html-pipeline) 2 | 3 | # jekyll-html-pipeline 4 | 5 | An [HTML::Pipeline](https://github.com/jch/html-pipeline), for Jekyll. 6 | 7 | ## Installation 8 | 9 | In your *_config.yml* file, add this gem: 10 | 11 | ``` yaml 12 | gems: 13 | - jekyll-html-pipeline 14 | ``` 15 | 16 | ## Configuration 17 | 18 | You'll need to be running a Jekyll version after 2.0.0, which is when custom 19 | Markdown filters were introduced. In your *_config.yml* file, indicate that you 20 | want to use `html_pipeline`: 21 | 22 | ``` yaml 23 | markdown: HTMLPipeline 24 | ``` 25 | 26 | Next, create an `html_pipeline` key, and indicate which filters you want to include: 27 | 28 | ``` yaml 29 | markdown: HTMLPipeline 30 | html_pipeline: 31 | filters: 32 | - "markdownfilter" 33 | - "sanitizationfilter" 34 | - "emojifilter" 35 | - "mentionfilter" 36 | ``` 37 | 38 | Finally, some filters require a context object. You can define these next: 39 | 40 | ``` yaml 41 | markdown: HTMLPipeline 42 | html_pipeline: 43 | filters: 44 | - "markdownfilter" 45 | - "sanitizationfilter" 46 | - "emojifilter" 47 | - "mentionfilter" 48 | context: 49 | asset_root: "http://foo.com/icons" 50 | base_url: "https://github.com/" 51 | ``` 52 | 53 | Keep in mind that [filter dependencies are not bundled](https://github.com/jch/html-pipeline#dependencies), 54 | so you'll need to add these in yourself. 55 | 56 | ## Custom filters 57 | 58 | Custom filters can be designed [the same as in HTML::Pipeline](https://github.com/jch/html-pipeline#extending). 59 | 60 | Check out [the test filter](./test/support/new_pipeline.rb) for an example. Because computers are stupid, remember that case-sensitivity matters when adding the custom filter to `filters`. 61 | -------------------------------------------------------------------------------- /test/test_jekyll_html_pipeline.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'test_helper' 4 | 5 | class HTMLPipelineTest < Converter::HTMLPipelineTestCase 6 | def setup 7 | @config = { 8 | 'html_pipeline' => { 9 | 'filters' => %w[markdownfilter sanitizationfilter emojifilter mentionfilter], 10 | 'context' => { 11 | 'asset_root' => 'http://foo.com/icons', 12 | 'base_url' => 'https://github.com/', 13 | 'commonmarker_extensions' => %w[table strikethrough tagfilter autolink] 14 | } 15 | }, 16 | 'markdown' => 'HTMLPipeline' 17 | } 18 | @markdown = Jekyll::Converters::Markdown.new @config 19 | end 20 | 21 | def test_passes_regular_options 22 | assert_equal '

Some Header

', @markdown.convert('# Some Header #').strip 23 | end 24 | 25 | def test_pass_rendering_emoji 26 | assert_equal '

:trollface:

', @markdown.convert(':trollface:').strip 27 | end 28 | 29 | def test_pass_rendering_mentions 30 | assert_equal '

Hey, @mojombo!

', @markdown.convert('**Hey, @mojombo**!').strip 31 | end 32 | 33 | def test_fail_when_a_library_dependency_is_not_met 34 | override = @config.dup 35 | override['html_pipeline']['filters'] << 'AutolinkFilter' 36 | markdown = Jekyll::Converters::Markdown.new override 37 | assert_raises(LoadError) { markdown.convert('http://www.github.com') } 38 | end 39 | 40 | def test_fail_when_a_context_dependency_is_not_met 41 | override = @config.dup 42 | override['html_pipeline'].delete 'context' 43 | markdown = Jekyll::Converters::Markdown.new override 44 | assert_raises(ArgumentError) { markdown.convert(':trollface:') } 45 | end 46 | 47 | def test_work_for_custom_filters 48 | require 'support/new_pipeline' 49 | override = @config.dup 50 | override['html_pipeline']['filters'] = ['HelpMarkdownFilter'] 51 | markdown = Jekyll::Converters::Markdown.new override 52 | text = "\n {{#tip}}\n **Tip**: Wow! \n {{/tip}}" 53 | assert_equal "

\nTip: Wow!
\n
", markdown.convert(text) 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /lib/jekyll-html-pipeline.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jekyll 4 | module Converters 5 | class Markdown::HTMLPipeline 6 | def initialize(config) 7 | require 'html/pipeline' 8 | @config = config 9 | @errors = [] 10 | @setup = false 11 | end 12 | 13 | def filter_key(key) 14 | key.to_s.downcase.to_sym 15 | end 16 | 17 | def filter?(filter) 18 | filter < HTML::Pipeline::Filter 19 | rescue LoadError, ArgumentError 20 | false 21 | end 22 | 23 | def symbolize_keys(hash) 24 | hash.each_with_object({}) do |(key, value), result| 25 | new_key = case key 26 | when String then key.to_sym 27 | else key 28 | end 29 | new_value = case value 30 | when Hash then symbolize_keys(value) 31 | when Array then value.map(&:to_sym) 32 | else value 33 | end 34 | result[new_key] = new_value 35 | end 36 | end 37 | 38 | def ensure_default_opts 39 | @config['html_pipeline']['filters'] ||= ['markdownfilter'] 40 | @config['html_pipeline']['context'] ||= { 'gfm' => true } 41 | # symbolize strings as keys, which is what HTML::Pipeline wants 42 | @config['html_pipeline']['context'] = symbolize_keys(@config['html_pipeline']['context']) 43 | end 44 | 45 | def setup 46 | return if @setup 47 | 48 | ensure_default_opts 49 | 50 | filters = @config['html_pipeline']['filters'].map do |filter| 51 | if filter?(filter) 52 | filter 53 | else 54 | key = filter_key(filter) 55 | begin 56 | const_filter = HTML::Pipeline.constants.find { |c| c.downcase == key } 57 | # probably a custom filter 58 | if const_filter.nil? 59 | Jekyll::Converters.const_get(filter) 60 | else 61 | HTML::Pipeline.const_get(const_filter) 62 | end 63 | rescue StandardError => e 64 | raise LoadError, e 65 | end 66 | end 67 | end 68 | 69 | @parser = HTML::Pipeline.new(filters, @config['html_pipeline']['context']) 70 | @setup = true 71 | end 72 | 73 | def convert(content) 74 | setup 75 | @parser.to_html(content) 76 | end 77 | end 78 | end 79 | end 80 | --------------------------------------------------------------------------------