├── Gemfile ├── lib ├── pot_markdown.rb ├── pot_markdown │ ├── version.rb │ ├── filters │ │ ├── sanitize_transformers │ │ │ ├── id_transformer.rb │ │ │ ├── list_transformer.rb │ │ │ └── table_transformer.rb │ │ ├── markdown_filter.rb │ │ ├── mention_filter.rb │ │ ├── sanitize_script_filter.rb │ │ ├── checkbox_filter.rb │ │ ├── sanitize_iframe_filter.rb │ │ ├── toc_filter.rb │ │ └── sanitize_html_filter.rb │ └── processor.rb └── kramdown │ └── parser │ ├── pot_markdown.rb │ └── pot_markdown │ ├── code_block.rb │ └── table.rb ├── bin ├── console └── setup ├── .gitignore ├── .travis.yml ├── Rakefile ├── test ├── test_helper.rb ├── files │ ├── sample_noscript_toc.html │ ├── sample_script_toc.html │ ├── sample.md │ ├── sample_noscript_body.html │ └── sample_script_body.html └── pot_markdown │ ├── benchmark_test.rb │ └── processor_test.rb ├── .rubocop_todo.yml ├── .rubocop.yml ├── pot_markdown.gemspec └── README.md /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | -------------------------------------------------------------------------------- /lib/pot_markdown.rb: -------------------------------------------------------------------------------- 1 | require 'pot_markdown/version' 2 | require 'pot_markdown/processor' 3 | -------------------------------------------------------------------------------- /lib/pot_markdown/version.rb: -------------------------------------------------------------------------------- 1 | module PotMarkdown 2 | VERSION = '0.1.6'.freeze 3 | end 4 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'bundler/setup' 4 | require 'pot_markdown' 5 | require 'pry' 6 | Pry.start 7 | 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | 8 | # Do any other automated setup that you need to do here 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | cache: bundler 3 | rvm: 4 | - 2.2.5 5 | - 2.3.1 6 | - ruby-head 7 | before_install: 8 | - gem install bundler 9 | script: 10 | - bundle exec rake 11 | - bundle exec rubocop 12 | 13 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler/gem_tasks' 2 | require 'rake/testtask' 3 | 4 | Rake::TestTask.new do |t| 5 | t.libs << 'test' 6 | t.test_files = FileList['test/**/*_test.rb'] 7 | t.verbose = true 8 | end 9 | 10 | task default: :test 11 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | require 'bundler/setup' 2 | require 'test/unit' 3 | require 'pot_markdown' 4 | require 'active_support' 5 | require 'active_support/core_ext' 6 | require 'diffy' 7 | 8 | def read_file(filename) 9 | File.read(File.expand_path("../files/#{filename}", __FILE__)) 10 | end 11 | -------------------------------------------------------------------------------- /.rubocop_todo.yml: -------------------------------------------------------------------------------- 1 | # This configuration was generated by 2 | # `rubocop --auto-gen-config` 3 | # on 2016-10-16 15:15:49 +0900 using RuboCop version 0.44.1. 4 | # The point is for the user to remove these configuration records 5 | # one by one as the offenses are removed from the code base. 6 | # Note that changes in the inspected code, or installation of new 7 | # versions of RuboCop, may require this file to be generated again. 8 | 9 | # Offense count: 4 10 | Metrics/AbcSize: 11 | Max: 33 12 | -------------------------------------------------------------------------------- /lib/kramdown/parser/pot_markdown.rb: -------------------------------------------------------------------------------- 1 | require 'kramdown/parser' 2 | require 'rouge' 3 | 4 | module Kramdown 5 | module Parser 6 | class PotMarkdown < Kramdown::Parser::GFM 7 | def initialize(source, options) 8 | super 9 | 10 | # replace table 11 | @block_parsers.insert(@block_parsers.index(:table), :table_pot) 12 | @block_parsers.delete(:table) 13 | end 14 | 15 | require 'kramdown/parser/pot_markdown/code_block' 16 | require 'kramdown/parser/pot_markdown/table' 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | Exclude: 3 | - bin/**/* 4 | - tmp/**/* 5 | - vendor/**/* 6 | - test/files/**/* 7 | - lib/kramdown/parser/pot_markdown/table.rb 8 | 9 | AsciiComments: 10 | Enabled: false 11 | 12 | ClassLength: 13 | Max: 200 14 | 15 | Documentation: 16 | Enabled: false 17 | 18 | LineLength: 19 | Max: 120 20 | 21 | MethodLength: 22 | Max: 30 23 | 24 | BlockLength: 25 | Exclude: 26 | - test/**/* 27 | 28 | PerceivedComplexity: 29 | Exclude: 30 | - lib/kramdown/**/* 31 | 32 | inherit_from: .rubocop_todo.yml 33 | -------------------------------------------------------------------------------- /test/files/sample_noscript_toc.html: -------------------------------------------------------------------------------- 1 |
85 | 86 | -------------------------------------------------------------------------------- /lib/pot_markdown/filters/sanitize_html_filter.rb: -------------------------------------------------------------------------------- 1 | require 'nokogiri' 2 | require 'sanitize' 3 | 4 | require 'pot_markdown/filters/sanitize_transformers/id_transformer' 5 | require 'pot_markdown/filters/sanitize_transformers/list_transformer' 6 | require 'pot_markdown/filters/sanitize_transformers/table_transformer' 7 | 8 | module PotMarkdown 9 | module Filters 10 | class SanitizeHTMLFilter < HTML::Pipeline::Filter 11 | def call 12 | Sanitize.clean_node!(doc, rule) 13 | end 14 | 15 | RULE = { 16 | elements: %w( 17 | a 18 | b 19 | blockquote 20 | br 21 | code 22 | dd 23 | del 24 | details 25 | div 26 | dl 27 | dt 28 | em 29 | h1 30 | h2 31 | h3 32 | h4 33 | h5 34 | h6 35 | hr 36 | i 37 | img 38 | input 39 | ins 40 | kbd 41 | li 42 | ol 43 | p 44 | pre 45 | q 46 | rp 47 | rt 48 | ruby 49 | s 50 | samp 51 | span 52 | strike 53 | strong 54 | sub 55 | summary 56 | sup 57 | table 58 | tbody 59 | td 60 | tfoot 61 | th 62 | thead 63 | tr 64 | tt 65 | ul 66 | var 67 | ), 68 | attributes: { 69 | all: %w( 70 | abbr 71 | align 72 | alt 73 | border 74 | cellpadding 75 | cellspacing 76 | cite 77 | class 78 | color 79 | cols 80 | colspan 81 | datetime 82 | height 83 | hreflang 84 | id 85 | itemprop 86 | lang 87 | name 88 | rowspan 89 | style 90 | tabindex 91 | target 92 | title 93 | width 94 | ) + [:data], 95 | 'a' => %w( 96 | href 97 | ), 98 | 'div' => %w( 99 | itemscope 100 | itemtype 101 | ), 102 | 'iframe' => %w( 103 | allowfullscreen 104 | frameborder 105 | src 106 | scrolling 107 | ), 108 | 'img' => %w( 109 | src 110 | ), 111 | 'input' => %w( 112 | checked 113 | disabled 114 | type 115 | ), 116 | 'script' => %w( 117 | src 118 | ) 119 | }, 120 | css: { 121 | properties: %w( 122 | border 123 | color 124 | height 125 | text-align 126 | width 127 | ) 128 | }, 129 | protocols: { 130 | 'a' => { 131 | 'href' => ['http', 'https', :relative] 132 | }, 133 | 'img' => { 134 | 'src' => ['http', 'https', :relative] 135 | } 136 | }, 137 | transformers: [ 138 | SanitizeTransformers::IdTransformer, 139 | SanitizeTransformers::ListTransformer, 140 | SanitizeTransformers::TableTransformer 141 | ] 142 | }.freeze 143 | 144 | RULE_EXT = RULE.dup.tap do |rule| 145 | rule[:elements] += %w(script iframe) 146 | end 147 | 148 | private 149 | 150 | def rule 151 | if context[:sanitize_rule] 152 | context[:sanitize_rule] 153 | elsif context[:sanitize_use_external] 154 | RULE_EXT 155 | else 156 | RULE 157 | end 158 | end 159 | end 160 | end 161 | end 162 | -------------------------------------------------------------------------------- /test/pot_markdown/processor_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper.rb' 2 | 3 | class ProcessorTest < Test::Unit::TestCase 4 | def render(text, context = {}) 5 | PotMarkdown::Processor.new.call(text, context)[:output].to_s.strip 6 | end 7 | 8 | def render_toc(text, context = {}) 9 | PotMarkdown::Processor.new.call(text, context)[:toc].to_s.strip 10 | end 11 | 12 | def render_body_diff(result, markdown, context = {}) 13 | Diffy::Diff.new(render(markdown, context), result.strip).to_s 14 | end 15 | 16 | def render_toc_diff(result, markdown, context = {}) 17 | Diffy::Diff.new(render_toc(markdown, context), result.strip).to_s 18 | end 19 | 20 | sub_test_case 'integration' do 21 | sub_test_case 'script enable' do 22 | test 'body' do 23 | diff = render_body_diff( 24 | read_file('sample_script_body.html'), read_file('sample.md'), sanitize_use_external: true 25 | ) 26 | assert_true(diff.empty? ? true : (puts diff)) 27 | end 28 | 29 | test 'toc' do 30 | diff = render_toc_diff( 31 | read_file('sample_script_toc.html'), read_file('sample.md'), sanitize_use_external: true 32 | ) 33 | assert_true(diff.empty? ? true : (puts diff)) 34 | end 35 | end 36 | 37 | sub_test_case 'script disable' do 38 | test 'body' do 39 | diff = render_body_diff( 40 | read_file('sample_noscript_body.html'), read_file('sample.md'), sanitize_use_external: false 41 | ) 42 | assert_true(diff.empty? ? true : (puts diff)) 43 | end 44 | 45 | test 'toc' do 46 | diff = render_toc_diff( 47 | read_file('sample_noscript_toc.html'), read_file('sample.md'), sanitize_use_external: false 48 | ) 49 | assert_true(diff.empty? ? true : (puts diff)) 50 | end 51 | end 52 | end 53 | 54 | test 'headers' do 55 | assert do 56 | text = <<-EOL 57 | # h1 58 | ## h2 59 | ### h3 60 | #### h4 61 | ##### h5 62 | ###### h6 63 | EOL 64 | result = <<-EOL 65 |できた! #RPGツクールMV #ツクールMV
— Ru/むっくRu (@ru_shalm) 2015年11月5日
戦闘中セリフ表示さん for MV - 鳥小屋.txthttps://t.co/tWdpq5CABF pic.twitter.com/KMaGaE5Sgd
strikethrough text
Text bold italic 打ち消し線
![]()

class Rutan
62 | def self.call
63 | puts 'hello'
64 | end
65 | end
66 |
67 | class Rutan
74 | def self.say
75 | puts '٩(๑❛ᴗ❛๑)۶'
76 | end
77 | end
78 |
79 | class Rutan
86 | def self.bom
87 | puts ':ok_woman:'
88 | end
89 | end
90 |
91 | Footnote is vert cool !!!!!1
97 | 98 |113 |115 | 116 | 117 |できた! #RPGツクールMV #ツクールMV
— Ru/むっくRu (@ru_shalm) 2015年11月5日 114 |
戦闘中セリフ表示さん for MV - 鳥小屋.txthttps://t.co/tWdpq5CABF pic.twitter.com/KMaGaE5Sgd
個人の意見です。 ↩
121 |Text bold italic 打ち消し線
![]()

class Rutan
62 | def self.call
63 | puts 'hello'
64 | end
65 | end
66 |
67 | class Rutan
74 | def self.say
75 | puts '٩(๑❛ᴗ❛๑)۶'
76 | end
77 | end
78 |
79 | class Rutan
86 | def self.bom
87 | puts ':ok_woman:'
88 | end
89 | end
90 |
91 | Footnote is vert cool !!!!!1
97 | 98 |113 |115 | 116 | 117 |できた! #RPGツクールMV #ツクールMV
— Ru/むっくRu (@ru_shalm) 2015年11月5日 114 |
戦闘中セリフ表示さん for MV - 鳥小屋.txthttps://t.co/tWdpq5CABF pic.twitter.com/KMaGaE5Sgd
個人の意見です。 ↩
121 | elements into account!
115 | pipe_on_line = false
116 | while (c = root.children.shift)
117 | lines = c.value.split(/\n/)
118 | if c.type == :codespan
119 | if lines.size > 2 || (lines.size == 2 && !pipe_on_line)
120 | break
121 | elsif lines.size == 2 && pipe_on_line
122 | pipe_on_line = false
123 | end
124 | else
125 | break if lines.size > 1 && !pipe_on_line && lines.first !~ /^#{TABLE_PIPE_CHECK}/
126 | pipe_on_line = (lines.size > 1 ? false : pipe_on_line) || (lines.last =~ /^#{TABLE_PIPE_CHECK}/)
127 | end
128 | end
129 | @src.revert_pos(saved_pos) && (return false) unless pipe_on_line
130 |
131 | add_container.call(has_footer ? :tfoot : :tbody, false) unless rows.empty?
132 |
133 | unless table.children.any? { |el| el.type == :tbody }
134 | warning("Found table without body on line #{table.options[:location]} - ignoring it")
135 | @src.revert_pos(saved_pos)
136 | return false
137 | end
138 |
139 | # adjust all table rows to have equal number of columns, same for alignment defs
140 | table.children.each do |kind|
141 | kind.children.each do |row|
142 | (columns - row.children.length).times do
143 | row.children << Element.new(:td)
144 | end
145 | end
146 | end
147 | if table.options[:alignment].length > columns
148 | table.options[:alignment] = table.options[:alignment][0...columns]
149 | else
150 | table.options[:alignment] += [:default] * (columns - table.options[:alignment].length)
151 | end
152 |
153 | @tree.children << table
154 |
155 | true
156 | end
157 |
158 | define_parser(:table_pot, TABLE_START)
159 | end
160 | end
161 | end
162 |
--------------------------------------------------------------------------------