qiita-markdown 0.0.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of qiita-markdown might be problematic. Click here for more details.

checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 3ace54fd9f6e5346ddf6a794191f6500be756e39
4
+ data.tar.gz: d48b1fb03edf8b81e180dee32871b832ca7875d9
5
+ SHA512:
6
+ metadata.gz: fba9a372384bda242a1761d42b5ce0b81c2a75199f5f3c3c1877a474882ca500d21e78b066e823894996f0ed347c18651349e96d9a76806d4f3a1f34a304e849
7
+ data.tar.gz: d3d4a67698fa5b495d975b8564f9e095c721608a3aaf8bcb55bbac9881f261ecbd5685822c28f21eaa2374ae2c14b08fd47e82d5a62dedfbb081fec3e1fc49ed
data/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --require spec_helper
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ before_install:
2
+ - sudo apt-get update -qq
3
+ - sudo apt-get install libicu-dev
4
+ language: ruby
5
+ rvm:
6
+ - 2.0.0
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in qiita-markdown.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Ryo Nakamura
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,42 @@
1
+ # Qiita::Markdown [![Build Status](https://travis-ci.org/increments/qiita-markdown.svg)](https://travis-ci.org/increments/qiita-markdown)
2
+ Qiita-specified markdown renderer.
3
+
4
+ ## Usage
5
+ Qiita::Markdown::Processor provides markdown rendering logic.
6
+
7
+ ```ruby
8
+ processor = Qiita::Markdown::Processor.new
9
+ processor.call(markdown)
10
+ # => {
11
+ # codes: [
12
+ # {
13
+ # code: "1 + 1\n",
14
+ # language: "ruby",
15
+ # filename: "example.rb",
16
+ # },
17
+ # ],
18
+ # mentioned_usernames: [
19
+ # "alice",
20
+ # "bob",
21
+ # ],
22
+ # output: "<h1>Example</h1>\n...",
23
+ # }
24
+ ```
25
+
26
+ ### Filters
27
+ Qiita::Markdown is built on [jch/html-pipeline](https://github.com/jch/html-pipeline).
28
+ Add your favorite html-pipeline-compatible filters.
29
+
30
+ ```ruby
31
+ processor = Qiita::Markdown::Processor.new
32
+ processor.filters << HTML::Pipeline::ImageMaxWidthFilter
33
+ processor.call(text)
34
+ ```
35
+
36
+ ### Context
37
+ Processor takes optional context as a Hash which is shared by all filters.
38
+
39
+ ```ruby
40
+ processor = Qiita::Markdown::Processor.new(asset_root: "http://example.com/assets")
41
+ processor.call(text, asset_root: "http://cdn.example.com")
42
+ ```
data/Rakefile ADDED
@@ -0,0 +1,5 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+ task default: :spec
@@ -0,0 +1 @@
1
+ require "qiita/markdown"
@@ -0,0 +1,15 @@
1
+ require "active_support/core_ext/object/blank"
2
+ require "html/pipeline"
3
+ require "linguist"
4
+ require "nokogiri"
5
+ require "redcarpet"
6
+ require "sanitize"
7
+
8
+ require "qiita/markdown/filters/code"
9
+ require "qiita/markdown/filters/mention"
10
+ require "qiita/markdown/filters/redcarpet"
11
+ require "qiita/markdown/filters/sanitize"
12
+ require "qiita/markdown/filters/syntax_highlight"
13
+ require "qiita/markdown/filters/toc"
14
+ require "qiita/markdown/processor"
15
+ require "qiita/markdown/version"
@@ -0,0 +1,98 @@
1
+ module Qiita
2
+ module Markdown
3
+ module Filters
4
+ DEFAULT_LANGUAGE_ALIASES = {
5
+ "el" => "common-lisp",
6
+ "zsh" => "bash",
7
+ }
8
+
9
+ # 1. Detects language written in <pre> element.
10
+ # 2. Adds lang attribute (but this attribute is consumed by syntax highliter).
11
+ # 3. Adds detected code data into `result[:codes]`.
12
+ #
13
+ # You can pass language aliases table via context[:language_aliases].
14
+ class Code < HTML::Pipeline::Filter
15
+ def call
16
+ result[:codes] ||= []
17
+ doc.search("pre").each do |pre|
18
+ if code = pre.at("code")
19
+ label = Label.new(code["class"])
20
+ filename = label.filename
21
+ language = label.language
22
+ language = language_aliases[language] || language
23
+ pre["filename"] = filename if filename
24
+ pre["lang"] = language if language
25
+ result[:codes] << {
26
+ code: pre.text,
27
+ filename: filename,
28
+ language: language,
29
+ }
30
+ end
31
+ end
32
+ doc
33
+ end
34
+
35
+ private
36
+
37
+ def language_aliases
38
+ context[:language_aliases] || DEFAULT_LANGUAGE_ALIASES
39
+ end
40
+
41
+ # Detects language from code block label.
42
+ class Label
43
+ # @param text [String, nil]
44
+ def initialize(text)
45
+ @text = text
46
+ end
47
+
48
+ # @return [String, nil]
49
+ def filename
50
+ case
51
+ when empty?
52
+ nil
53
+ when has_only_filename?
54
+ sections[0]
55
+ else
56
+ sections[1]
57
+ end
58
+ end
59
+
60
+ # @example
61
+ # Label.new(nil).language #=> nil
62
+ # Label.new("ruby").language #=> "ruby"
63
+ # Label.new("ruby:foo.rb").language #=> "ruby"
64
+ # Label.new("foo.rb").language #=> "ruby"
65
+ # @return [String, nil]
66
+ def language
67
+ case
68
+ when empty?
69
+ nil
70
+ when !has_only_filename?
71
+ sections[0]
72
+ when linguist_language
73
+ linguist_language.default_alias_name
74
+ end
75
+ end
76
+
77
+ private
78
+
79
+ def empty?
80
+ @text.nil?
81
+ end
82
+
83
+ def has_only_filename?
84
+ sections[1].nil? && sections[0].include?(".")
85
+ end
86
+
87
+ def linguist_language
88
+ @linguist_language ||= Linguist::Language.find_by_filename(filename).first
89
+ end
90
+
91
+ def sections
92
+ @sections ||= (@text || "").split(":")
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,48 @@
1
+ module Qiita
2
+ module Markdown
3
+ module Filters
4
+ # 1. Adds :mentioned_usernames into result Hash as Array of String.
5
+ # 2. Replaces @mention with link.
6
+ #
7
+ # You can pass :allowed_usernames context to limit mentioned usernames.
8
+ class Mention < HTML::Pipeline::MentionFilter
9
+ # Overrides HTML::Pipeline::MentionFilter's constant.
10
+ # Allows "_" instead of "-" in username pattern.
11
+ MentionPattern = /
12
+ (?:^|\W)
13
+ @((?>[\w][\w-]{1,30}\w(?:@github)?))
14
+ (?!\/)
15
+ (?=
16
+ \.+[ \t\W]|
17
+ \.+$|
18
+ [^0-9a-zA-Z_.]|
19
+ $
20
+ )
21
+ /ix
22
+
23
+ # @note Override to use customized MentionPattern and allowed_usernames logic.
24
+ def mention_link_filter(text, _, _)
25
+ text.gsub(MentionPattern) do |match|
26
+ name = $1
27
+ if allowed_usernames && !allowed_usernames.include?(name)
28
+ match
29
+ else
30
+ result[:mentioned_usernames] |= [name]
31
+ url = File.join(base_url, name)
32
+ match.sub(
33
+ "@#{name}",
34
+ %[<a href="#{url}" class="user-mention" target="_blank" title="#{name}">@#{name}</a>]
35
+ )
36
+ end
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ def allowed_usernames
43
+ context[:allowed_usernames]
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,31 @@
1
+ require "redcarpet"
2
+
3
+ module Qiita
4
+ module Markdown
5
+ module Filters
6
+ class Redcarpet < HTML::Pipeline::TextFilter
7
+ class << self
8
+ # Memoize.
9
+ # @return [Redcarpet::Markdown]
10
+ def renderer
11
+ @renderer = ::Redcarpet::Markdown.new(
12
+ ::Redcarpet::Render::HTML.new(
13
+ hard_wrap: true,
14
+ ),
15
+ autolink: true,
16
+ fenced_code_blocks: true,
17
+ no_intra_emphasis: true,
18
+ strikethrough: true,
19
+ tables: true,
20
+ )
21
+ end
22
+ end
23
+
24
+ # @return [Nokogiri::HTML::DocumentFragment]
25
+ def call
26
+ Nokogiri::HTML.fragment(self.class.renderer.render(@text))
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,184 @@
1
+ module Qiita
2
+ module Markdown
3
+ module Filters
4
+ # Sanitizes undesirable elements by whitelist-based rule.
5
+ # You can pass optional :rule and :script context.
6
+ class Sanitize < HTML::Pipeline::Filter
7
+ # Wraps a node env to transform invalid node.
8
+ class TransformableNode
9
+ def self.call(*args)
10
+ new(*args).transform
11
+ end
12
+
13
+ def initialize(env)
14
+ @env = env
15
+ end
16
+
17
+ def transform
18
+ if has_invalid_list_node? || has_invalid_table_node?
19
+ node.replace(node.children)
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def has_invalid_list_node?
26
+ name == "li" && !node.ancestors.any? do |ancestor|
27
+ %w[ol ul].include?(ancestor.name)
28
+ end
29
+ end
30
+
31
+ def has_invalid_table_node?
32
+ %w[thead tbody tfoot tr td th].include?(name) && !node.ancestors.any? do |ancestor|
33
+ ancestor.name == "table"
34
+ end
35
+ end
36
+
37
+ def name
38
+ @env[:node_name]
39
+ end
40
+
41
+ def node
42
+ @env[:node]
43
+ end
44
+ end
45
+
46
+ RULE = {
47
+ attributes: {
48
+ "a" => [
49
+ "href",
50
+ ],
51
+ "img" => [
52
+ "src",
53
+ ],
54
+ "div" => [
55
+ "itemscope",
56
+ "itemtype",
57
+ ],
58
+ all: [
59
+ "abbr",
60
+ "align",
61
+ "alt",
62
+ "border",
63
+ "cellpadding",
64
+ "cellspacing",
65
+ "cite",
66
+ "class",
67
+ "color",
68
+ "cols",
69
+ "colspan",
70
+ "datetime",
71
+ "height",
72
+ "hreflang",
73
+ "id",
74
+ "itemprop",
75
+ "lang",
76
+ "name",
77
+ "tabindex",
78
+ "target",
79
+ "title",
80
+ "width",
81
+ ],
82
+ },
83
+ elements: [
84
+ "a",
85
+ "b",
86
+ "blockquote",
87
+ "br",
88
+ "code",
89
+ "dd",
90
+ "del",
91
+ "div",
92
+ "dl",
93
+ "dt",
94
+ "em",
95
+ "h1",
96
+ "h2",
97
+ "h3",
98
+ "h4",
99
+ "h5",
100
+ "h6",
101
+ "h7",
102
+ "h8",
103
+ "hr",
104
+ "i",
105
+ "img",
106
+ "ins",
107
+ "kbd",
108
+ "li",
109
+ "ol",
110
+ "p",
111
+ "pre",
112
+ "q",
113
+ "rp",
114
+ "rt",
115
+ "ruby",
116
+ "s",
117
+ "samp",
118
+ "strike",
119
+ "strong",
120
+ "sub",
121
+ "sup",
122
+ "table",
123
+ "tbody",
124
+ "td",
125
+ "tfoot",
126
+ "th",
127
+ "thead",
128
+ "tr",
129
+ "tt",
130
+ "ul",
131
+ "var",
132
+ ],
133
+ protocols: {
134
+ "a" => {
135
+ "href" => [
136
+ :relative,
137
+ "http",
138
+ "https",
139
+ ],
140
+ },
141
+ "img" => {
142
+ "src" => [
143
+ :relative,
144
+ "http",
145
+ "https",
146
+ ],
147
+ },
148
+ },
149
+ remove_contents: [
150
+ "script",
151
+ ],
152
+ transformers: TransformableNode,
153
+ }
154
+
155
+ SCRIPTABLE_RULE = RULE.dup.tap do |rule|
156
+ rule[:elements] = RULE[:elements] + ["script"]
157
+ rule[:remove_contents] = []
158
+ end
159
+
160
+ def call
161
+ ::Sanitize.clean_node!(doc, rule)
162
+ doc
163
+ end
164
+
165
+ private
166
+
167
+ def rule
168
+ case
169
+ when context[:rule]
170
+ context[:rule]
171
+ when has_script_context?
172
+ SCRIPTABLE_RULE
173
+ else
174
+ RULE
175
+ end
176
+ end
177
+
178
+ def has_script_context?
179
+ context[:script] == true
180
+ end
181
+ end
182
+ end
183
+ end
184
+ end
@@ -0,0 +1,98 @@
1
+ module Qiita
2
+ module Markdown
3
+ module Filters
4
+ class SyntaxHighlight < HTML::Pipeline::Filter
5
+ DEFAULT_LANGUAGE = "text"
6
+
7
+ def call
8
+ doc.search("pre").each do |node|
9
+ Highlighter.call(
10
+ default_language: default_language,
11
+ node: node,
12
+ )
13
+ end
14
+ doc
15
+ end
16
+
17
+ private
18
+
19
+ def default_language
20
+ context[:default_language] || DEFAULT_LANGUAGE
21
+ end
22
+
23
+ class Highlighter
24
+ def self.call(*args)
25
+ new(*args).call
26
+ end
27
+
28
+ def initialize(default_language: nil, node: nil)
29
+ @default_language = default_language
30
+ @node = node
31
+ end
32
+
33
+ def call
34
+ outer = Nokogiri::HTML.fragment(%Q[<div class="code-frame" data-lang="#{language}">])
35
+ frame = outer.at('div')
36
+ frame.add_child(filename_node) if filename
37
+ frame.add_child(highlighted_node)
38
+ @node.replace(outer)
39
+ end
40
+
41
+ private
42
+
43
+ def code
44
+ @node.inner_text
45
+ end
46
+
47
+ def filename
48
+ @node["filename"]
49
+ end
50
+
51
+ def filename_node
52
+ %Q[<div class="code-lang"><span class="bold">#{filename}</span></div>]
53
+ end
54
+
55
+ def has_inline_php?
56
+ specific_language == "php" && code !~ /^<\?php/
57
+ end
58
+
59
+ def highlight(language)
60
+ Pygments.highlight(code, lexer: language, options: pygments_options)
61
+ end
62
+
63
+ def highlighted_node
64
+ if specific_language
65
+ begin
66
+ highlight(specific_language).presence or raise
67
+ rescue
68
+ highlight(@default_language)
69
+ end
70
+ else
71
+ highlight(@default_language)
72
+ end
73
+ end
74
+
75
+ def language
76
+ specific_language || @default_language
77
+ end
78
+
79
+ def language_node
80
+ Nokogiri::HTML.fragment(%Q[<div class="code-frame" data-lang="#{language}"></div>])
81
+ end
82
+
83
+ def pygments_options
84
+ @pygments_options ||= begin
85
+ options = { encoding: "utf-8" }
86
+ options[:startinline] = true if has_inline_php?
87
+ options
88
+ end
89
+ end
90
+
91
+ def specific_language
92
+ @node["lang"]
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,68 @@
1
+ module Qiita
2
+ module Markdown
3
+ module Filters
4
+ class Toc < HTML::Pipeline::Filter
5
+ def call
6
+ counter = Hash.new(0)
7
+ doc.css("h1, h2, h3, h4, h5, h6").each do |node|
8
+ heading = Heading.new(node, counter)
9
+ heading.add_anchor_element if heading.has_first_child?
10
+ heading.increment
11
+ end
12
+ doc
13
+ end
14
+
15
+ class Heading
16
+ def initialize(node, counter)
17
+ @node = node
18
+ @counter = counter
19
+ end
20
+
21
+ def add_anchor_element
22
+ first_child.add_previous_sibling(anchor_element)
23
+ end
24
+
25
+ def anchor_element
26
+ %[<span id="#{suffixed_id}" class="fragment"></span><a href="##{suffixed_id}"><i class="fa fa-link"></i></a>]
27
+ end
28
+
29
+ def content
30
+ @content ||= node.children.first
31
+ end
32
+
33
+ def count
34
+ @counter[id]
35
+ end
36
+
37
+ def first_child
38
+ @first_child ||= @node.children.first
39
+ end
40
+
41
+ def has_count?
42
+ count > 0
43
+ end
44
+
45
+ def has_first_child?
46
+ !!first_child
47
+ end
48
+
49
+ def id
50
+ @node.text.downcase.gsub(/[^\p{Word}\- ]/u, '').gsub(' ', '-')
51
+ end
52
+
53
+ def increment
54
+ @counter[id] += 1
55
+ end
56
+
57
+ def suffix
58
+ has_count? ? "-#{count}" : ""
59
+ end
60
+
61
+ def suffixed_id
62
+ "#{id}#{suffix}"
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,45 @@
1
+ module Qiita
2
+ module Markdown
3
+ class Processor
4
+ DEFAULT_CONTEXT = {
5
+ asset_root: "/images",
6
+ }
7
+
8
+ DEFAULT_FILTERS = [
9
+ Filters::Redcarpet,
10
+ Filters::Sanitize,
11
+ Filters::Code,
12
+ Filters::Toc,
13
+ HTML::Pipeline::EmojiFilter,
14
+ Filters::SyntaxHighlight,
15
+ Filters::Mention,
16
+ ]
17
+
18
+ # @param [Hash] context Optional context for HTML::Pipeline.
19
+ def initialize(context = {})
20
+ @context = DEFAULT_CONTEXT.merge(context)
21
+ end
22
+
23
+ # Converts Markdown text into HTML string with extracted metadata.
24
+ #
25
+ # @param [String] input Markdown text.
26
+ # @param [Hash] context Optional context merged into default context.
27
+ # @return [Hash] Process result.
28
+ # @example
29
+ # Qiita::Markdown::Processor.new.call(markdown) #=> {
30
+ # codes: [...],
31
+ # mentioned_usernames: [...],
32
+ # output: "...",
33
+ # }
34
+ def call(input, context = {})
35
+ HTML::Pipeline.new(filters, @context).call(input, context)
36
+ end
37
+
38
+ # @note Modify filters if you want.
39
+ # @return [Array<HTML::Pipeline::Filter>]
40
+ def filters
41
+ @filters ||= DEFAULT_FILTERS
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,5 @@
1
+ module Qiita
2
+ module Markdown
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
@@ -0,0 +1,29 @@
1
+ lib = File.expand_path("../lib", __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require "qiita/markdown/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "qiita-markdown"
7
+ spec.version = Qiita::Markdown::VERSION
8
+ spec.authors = ["Ryo Nakamura"]
9
+ spec.email = ["r7kamura@gmail.com"]
10
+ spec.summary = "Qiita-specified markdown renderer."
11
+ spec.homepage = "https://github.com/increments/qiita-markdown"
12
+ spec.license = "MIT"
13
+
14
+ spec.files = `git ls-files -z`.split("\x0")
15
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
16
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
17
+ spec.require_paths = ["lib"]
18
+
19
+ spec.add_dependency "activesupport"
20
+ spec.add_dependency "gemoji", ">= 2.0.0"
21
+ spec.add_dependency "github-linguist"
22
+ spec.add_dependency "html-pipeline"
23
+ spec.add_dependency "redcarpet"
24
+ spec.add_dependency "sanitize"
25
+ spec.add_development_dependency "bundler", "~> 1.7"
26
+ spec.add_development_dependency "pry"
27
+ spec.add_development_dependency "rake", "~> 10.0"
28
+ spec.add_development_dependency "rspec", "3.1.0"
29
+ end
@@ -0,0 +1,317 @@
1
+ require "active_support/core_ext/string/strip"
2
+
3
+ describe Qiita::Markdown::Processor do
4
+ describe "#call" do
5
+ subject do
6
+ result[:output].to_s
7
+ end
8
+
9
+ let(:context) do
10
+ {}
11
+ end
12
+
13
+ let(:markdown) do
14
+ raise NotImplementedError
15
+ end
16
+
17
+ let(:result) do
18
+ described_class.new(context).call(markdown)
19
+ end
20
+
21
+ context "with valid condition" do
22
+ let(:markdown) do
23
+ <<-EOS.strip_heredoc
24
+ example
25
+ EOS
26
+ end
27
+
28
+ it "returns a Hash with HTML output and other metadata" do
29
+ expect(result[:codes]).to be_an Array
30
+ expect(result[:mentioned_usernames]).to be_an Array
31
+ expect(result[:output]).to be_a Nokogiri::HTML::DocumentFragment
32
+ end
33
+ end
34
+
35
+ context "with HTML-characters" do
36
+ let(:markdown) do
37
+ "<>&"
38
+ end
39
+
40
+ it "sanitizes them" do
41
+ should eq <<-EOS.strip_heredoc
42
+ <p>&lt;&gt;&amp;</p>
43
+ EOS
44
+ end
45
+ end
46
+
47
+ context "with headings" do
48
+ let(:markdown) do
49
+ <<-EOS.strip_heredoc
50
+ # a
51
+ ## a
52
+ ### a
53
+ ### a
54
+ EOS
55
+ end
56
+
57
+ it "adds ID for ToC" do
58
+ should eq <<-EOS.strip_heredoc
59
+ <h1>
60
+ <span id="a" class="fragment"></span><a href="#a"><i class="fa fa-link"></i></a>a</h1>
61
+
62
+ <h2>
63
+ <span id="a-1" class="fragment"></span><a href="#a-1"><i class="fa fa-link"></i></a>a</h2>
64
+
65
+ <h3>
66
+ <span id="a-2" class="fragment"></span><a href="#a-2"><i class="fa fa-link"></i></a>a</h3>
67
+
68
+ <h3>
69
+ <span id="a-3" class="fragment"></span><a href="#a-3"><i class="fa fa-link"></i></a>a</h3>
70
+ EOS
71
+ end
72
+ end
73
+
74
+ context "with code" do
75
+ let(:markdown) do
76
+ <<-EOS.strip_heredoc
77
+ ```foo.rb
78
+ puts 'hello world'
79
+ ```
80
+ EOS
81
+ end
82
+
83
+ it "returns detected codes" do
84
+ expect(result[:codes]).to eq [
85
+ {
86
+ code: "puts 'hello world'\n",
87
+ filename: "foo.rb",
88
+ language: "ruby",
89
+ },
90
+ ]
91
+ end
92
+ end
93
+
94
+ context 'with code & filename' do
95
+ let(:markdown) do
96
+ <<-EOS.strip_heredoc
97
+ ```example.rb
98
+ 1
99
+ ```
100
+ EOS
101
+ end
102
+
103
+ it 'returns code-frame, code-lang, and highlighted pre element' do
104
+ should eq <<-EOS.strip_heredoc
105
+ <div class="code-frame" data-lang="ruby">
106
+ <div class="code-lang"><span class="bold">example.rb</span></div>
107
+ <div class="highlight"><pre><span class="mi">1</span>
108
+ </pre></div>
109
+ </div>
110
+ EOS
111
+ end
112
+
113
+ end
114
+
115
+ context 'with code & no filename' do
116
+ let(:markdown) do
117
+ <<-EOS.strip_heredoc
118
+ ```ruby
119
+ 1
120
+ ```
121
+ EOS
122
+ end
123
+
124
+ it 'returns code-frame and highlighted pre element' do
125
+ should eq <<-EOS.strip_heredoc
126
+ <div class="code-frame" data-lang="ruby"><div class="highlight"><pre><span class="mi">1</span>
127
+ </pre></div></div>
128
+ EOS
129
+ end
130
+ end
131
+
132
+ context "with undefined but aliased language" do
133
+ let(:markdown) do
134
+ <<-EOS.strip_heredoc
135
+ ```zsh
136
+ true
137
+ ```
138
+ EOS
139
+ end
140
+
141
+ it "returns aliased language name" do
142
+ expect(result[:codes]).to eq [
143
+ {
144
+ code: "true\n",
145
+ filename: nil,
146
+ language: "bash",
147
+ },
148
+ ]
149
+ end
150
+ end
151
+
152
+ context "with script element" do
153
+ let(:markdown) do
154
+ <<-EOS.strip_heredoc
155
+ <script>alert(1)</script>
156
+ EOS
157
+ end
158
+
159
+ it "removes script element" do
160
+ should eq "\n"
161
+ end
162
+ end
163
+
164
+ context "with script context" do
165
+ before do
166
+ context[:script] = true
167
+ end
168
+
169
+ let(:markdown) do
170
+ <<-EOS.strip_heredoc
171
+ <script>alert(1)</script>
172
+ EOS
173
+ end
174
+
175
+ it "allows script element" do
176
+ should eq markdown
177
+ end
178
+ end
179
+
180
+ context "with mention" do
181
+ let(:markdown) do
182
+ "@alice"
183
+ end
184
+
185
+ it "replaces mention with link" do
186
+ should include(<<-EOS.strip_heredoc.rstrip)
187
+ <a href="/alice" class="user-mention" target="_blank" title="alice">@alice</a>
188
+ EOS
189
+ end
190
+ end
191
+
192
+ context "with mentions in complex patterns" do
193
+ let(:markdown) do
194
+ <<-EOS.strip_heredoc
195
+ @alice
196
+
197
+ ```
198
+ @bob
199
+ ```
200
+
201
+ @charlie/@dave
202
+ @ell_en
203
+ @fran-k
204
+ @Isaac
205
+ @justin
206
+ @justin
207
+ @mallory@github
208
+ @#{?o * 33}
209
+ @oo
210
+ EOS
211
+ end
212
+
213
+ it "extracts mentions correctly" do
214
+ expect(result[:mentioned_usernames]).to eq %W[
215
+ alice
216
+ dave
217
+ ell_en
218
+ fran-k
219
+ Isaac
220
+ justin
221
+ mallory@github
222
+ ]
223
+ end
224
+ end
225
+
226
+ context "with allowed_usernames context" do
227
+ before do
228
+ context[:allowed_usernames] = ["alice"]
229
+ end
230
+
231
+ let(:markdown) do
232
+ <<-EOS.strip_heredoc
233
+ @alice
234
+ @bob
235
+ EOS
236
+ end
237
+
238
+ it "limits mentions to allowed usernames" do
239
+ expect(result[:mentioned_usernames]).to eq ["alice"]
240
+ end
241
+ end
242
+
243
+ context "with normal link" do
244
+ let(:markdown) do
245
+ "[](/example)"
246
+ end
247
+
248
+ it "creates link for that" do
249
+ should eq <<-EOS.strip_heredoc
250
+ <p><a href="/example"></a></p>
251
+ EOS
252
+ end
253
+ end
254
+
255
+ context "with anchor link" do
256
+ let(:markdown) do
257
+ "[](#example)"
258
+ end
259
+
260
+ it "creates link for that" do
261
+ should eq <<-EOS.strip_heredoc
262
+ <p><a href="#example"></a></p>
263
+ EOS
264
+ end
265
+ end
266
+
267
+ context "with javascript: link" do
268
+ let(:markdown) do
269
+ "[](javascript:alert(1))"
270
+ end
271
+
272
+ it "removes that link by creating empty a element" do
273
+ should eq <<-EOS.strip_heredoc
274
+ <p><a></a></p>
275
+ EOS
276
+ end
277
+ end
278
+
279
+ context "with mailto: link" do
280
+ let(:markdown) do
281
+ "[](mailto:info@example.com)"
282
+ end
283
+
284
+ it "removes that link by creating empty a element" do
285
+ should eq <<-EOS.strip_heredoc
286
+ <p><a></a></p>
287
+ EOS
288
+ end
289
+ end
290
+
291
+ context "with emoji" do
292
+ let(:markdown) do
293
+ ":+1:"
294
+ end
295
+
296
+ it "replaces it with img element" do
297
+ should eq <<-EOS.strip_heredoc
298
+ <p><img class="emoji" title=":+1:" alt=":+1:" src="/images/emoji/unicode/1f44d.png" height="20" width="20" align="absmiddle"></p>
299
+ EOS
300
+ end
301
+ end
302
+
303
+ context "with emoji in pre or code element" do
304
+ let(:markdown) do
305
+ <<-EOS.strip_heredoc
306
+ ```
307
+ :+1:
308
+ ```
309
+ EOS
310
+ end
311
+
312
+ it "does not replace it" do
313
+ should_not include('img')
314
+ end
315
+ end
316
+ end
317
+ end
@@ -0,0 +1,15 @@
1
+ require "qiita-markdown"
2
+
3
+ RSpec.configure do |config|
4
+ config.expect_with :rspec do |expectations|
5
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
6
+ end
7
+
8
+ config.mock_with :rspec do |mocks|
9
+ mocks.verify_partial_doubles = true
10
+ end
11
+
12
+ config.default_formatter = 'doc'
13
+ config.filter_run :focus
14
+ config.run_all_when_everything_filtered = true
15
+ end
metadata ADDED
@@ -0,0 +1,207 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: qiita-markdown
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Ryo Nakamura
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-10-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: gemoji
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 2.0.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 2.0.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: github-linguist
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: html-pipeline
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: redcarpet
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: sanitize
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: bundler
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '1.7'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '1.7'
111
+ - !ruby/object:Gem::Dependency
112
+ name: pry
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: rake
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '10.0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '10.0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: rspec
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - '='
144
+ - !ruby/object:Gem::Version
145
+ version: 3.1.0
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - '='
151
+ - !ruby/object:Gem::Version
152
+ version: 3.1.0
153
+ description:
154
+ email:
155
+ - r7kamura@gmail.com
156
+ executables: []
157
+ extensions: []
158
+ extra_rdoc_files: []
159
+ files:
160
+ - ".gitignore"
161
+ - ".rspec"
162
+ - ".travis.yml"
163
+ - Gemfile
164
+ - LICENSE.txt
165
+ - README.md
166
+ - Rakefile
167
+ - lib/qiita-markdown.rb
168
+ - lib/qiita/markdown.rb
169
+ - lib/qiita/markdown/filters/code.rb
170
+ - lib/qiita/markdown/filters/mention.rb
171
+ - lib/qiita/markdown/filters/redcarpet.rb
172
+ - lib/qiita/markdown/filters/sanitize.rb
173
+ - lib/qiita/markdown/filters/syntax_highlight.rb
174
+ - lib/qiita/markdown/filters/toc.rb
175
+ - lib/qiita/markdown/processor.rb
176
+ - lib/qiita/markdown/version.rb
177
+ - qiita-markdown.gemspec
178
+ - spec/qiita/markdown/processor_spec.rb
179
+ - spec/spec_helper.rb
180
+ homepage: https://github.com/increments/qiita-markdown
181
+ licenses:
182
+ - MIT
183
+ metadata: {}
184
+ post_install_message:
185
+ rdoc_options: []
186
+ require_paths:
187
+ - lib
188
+ required_ruby_version: !ruby/object:Gem::Requirement
189
+ requirements:
190
+ - - ">="
191
+ - !ruby/object:Gem::Version
192
+ version: '0'
193
+ required_rubygems_version: !ruby/object:Gem::Requirement
194
+ requirements:
195
+ - - ">="
196
+ - !ruby/object:Gem::Version
197
+ version: '0'
198
+ requirements: []
199
+ rubyforge_project:
200
+ rubygems_version: 2.2.2
201
+ signing_key:
202
+ specification_version: 4
203
+ summary: Qiita-specified markdown renderer.
204
+ test_files:
205
+ - spec/qiita/markdown/processor_spec.rb
206
+ - spec/spec_helper.rb
207
+ has_rdoc: