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 +7 -0
- data/.gitignore +14 -0
- data/.rspec +1 -0
- data/.travis.yml +6 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +42 -0
- data/Rakefile +5 -0
- data/lib/qiita-markdown.rb +1 -0
- data/lib/qiita/markdown.rb +15 -0
- data/lib/qiita/markdown/filters/code.rb +98 -0
- data/lib/qiita/markdown/filters/mention.rb +48 -0
- data/lib/qiita/markdown/filters/redcarpet.rb +31 -0
- data/lib/qiita/markdown/filters/sanitize.rb +184 -0
- data/lib/qiita/markdown/filters/syntax_highlight.rb +98 -0
- data/lib/qiita/markdown/filters/toc.rb +68 -0
- data/lib/qiita/markdown/processor.rb +45 -0
- data/lib/qiita/markdown/version.rb +5 -0
- data/qiita-markdown.gemspec +29 -0
- data/spec/qiita/markdown/processor_spec.rb +317 -0
- data/spec/spec_helper.rb +15 -0
- metadata +207 -0
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
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--require spec_helper
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
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 [](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 @@
|
|
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,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><>&</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
|
data/spec/spec_helper.rb
ADDED
@@ -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:
|