qiita-markdown 0.0.1

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.

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: