markdown-ruby-china 0.1 → 0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +5 -0
- data/README.markdown +16 -0
- data/lib/markdown-ruby-china.rb +244 -0
- data/markdown-ruby-china.gemspec +19 -0
- metadata +7 -2
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.markdown
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
Markdown render from Ruby China
|
2
|
+
============================
|
3
|
+
|
4
|
+
## 如何使用
|
5
|
+
### 添加到Gemfile
|
6
|
+
```ruby
|
7
|
+
gem 'markdown-ruby-china'
|
8
|
+
```
|
9
|
+
|
10
|
+
### 调用
|
11
|
+
```ruby
|
12
|
+
MarkdownTopicConverter.format(content)
|
13
|
+
```
|
14
|
+
|
15
|
+
## 新功能
|
16
|
+
1. 代码高亮 自动识别程序语言,文件名,别名等多种格式。
|
@@ -0,0 +1,244 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
require 'rails'
|
4
|
+
require 'rails_autolink'
|
5
|
+
require 'redcarpet'
|
6
|
+
require 'singleton'
|
7
|
+
require 'md_emoji'
|
8
|
+
require 'pygments'
|
9
|
+
require "nokogiri"
|
10
|
+
|
11
|
+
Pygments::Lexer.create :name => "XML", :filenames => ["*.xaml"], :aliases => ["xml"], :mimetypes => ["application/xml+evoque"]
|
12
|
+
|
13
|
+
module Redcarpet
|
14
|
+
module Render
|
15
|
+
class HTMLwithSyntaxHighlight < HTML
|
16
|
+
|
17
|
+
def initialize(extensions={})
|
18
|
+
super(extensions.merge(:xhtml => true,
|
19
|
+
:no_styles => true,
|
20
|
+
:filter_html => true,
|
21
|
+
:hard_wrap => true))
|
22
|
+
end
|
23
|
+
|
24
|
+
# 如果换行符没识别,那么code代码块将不会被这里调用
|
25
|
+
def block_code(code, lexer)
|
26
|
+
lexer = begin
|
27
|
+
# 支持程序语言,文件名,别名等多种格式
|
28
|
+
lexer = (Pygments::Lexer.find(lexer) || Pygments::Lexer.find_by_extname(".#{lexer}") || Pygments::Lexer.find_by_alias(lexer.downcase))
|
29
|
+
lexer.aliases[0]
|
30
|
+
rescue
|
31
|
+
lexer.to_s.split('.')[-1] || 'text'
|
32
|
+
end
|
33
|
+
|
34
|
+
opts = {:lexer => lexer, :formatter => 'html', :options => {:encoding => 'utf-8', :linenos => 'table'}}
|
35
|
+
begin
|
36
|
+
Pygments.highlight(code, opts)
|
37
|
+
rescue => e
|
38
|
+
puts :language => lexer, :code => code
|
39
|
+
puts e
|
40
|
+
puts e.backtrace
|
41
|
+
Pygments.highlight(code, opts.merge(:lexer => 'text'))
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def autolink(link, link_type)
|
46
|
+
# return link
|
47
|
+
if link_type.to_s == "email"
|
48
|
+
link
|
49
|
+
else
|
50
|
+
begin
|
51
|
+
# 防止 C 的 autolink 出来的内容有编码错误,万一有就直接跳过转换
|
52
|
+
# 比如这句:
|
53
|
+
# 此版本并非线上的http://yavaeye.com的源码.
|
54
|
+
link.match(/.+?/)
|
55
|
+
rescue
|
56
|
+
return link
|
57
|
+
end
|
58
|
+
# Fix Chinese neer the URL
|
59
|
+
bad_text = link.to_s.match(/[^\w:\/\-\,\$\!\.=\?&#+\|\%]+/im).to_s
|
60
|
+
link = link.to_s.gsub(bad_text, '')
|
61
|
+
"<a href=\"#{link}\" rel=\"nofollow\" target=\"_blank\">#{link}</a>#{bad_text}"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
class HTMLwithTopic < HTMLwithSyntaxHighlight
|
67
|
+
# Topic 里面,所有的 head 改为 h4 显示
|
68
|
+
def header(text, header_level)
|
69
|
+
"<h4>#{text}</h4>"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
class MarkdownConverter
|
76
|
+
include Singleton
|
77
|
+
|
78
|
+
def self.convert(text)
|
79
|
+
self.instance.convert(text)
|
80
|
+
end
|
81
|
+
|
82
|
+
def convert(text)
|
83
|
+
@converter.render(text)
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
def initialize
|
88
|
+
@converter = Redcarpet::Markdown.new(Redcarpet::Render::HTMLwithSyntaxHighlight, {
|
89
|
+
:autolink => true,
|
90
|
+
:fenced_code_blocks => true,
|
91
|
+
:no_intra_emphasis => true
|
92
|
+
})
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
class MarkdownTopicConverter < MarkdownConverter
|
98
|
+
def self.format(raw)
|
99
|
+
self.instance.format(raw)
|
100
|
+
end
|
101
|
+
|
102
|
+
def format(raw)
|
103
|
+
text = raw.clone
|
104
|
+
return '' if text.blank?
|
105
|
+
|
106
|
+
convert_bbcode_img(text)
|
107
|
+
users = nomalize_user_mentions(text)
|
108
|
+
|
109
|
+
# 如果 ``` 在刚刚换行的时候 Redcapter 无法生成正确,需要两个换行
|
110
|
+
text.gsub!("\n```","\n\n```")
|
111
|
+
|
112
|
+
# 调用MarkdownConverter父类来高亮代码
|
113
|
+
result = convert(text)
|
114
|
+
|
115
|
+
doc = Nokogiri::HTML.fragment(result)
|
116
|
+
link_mention_floor(doc)
|
117
|
+
link_mention_user(doc, users)
|
118
|
+
replace_emoji(doc)
|
119
|
+
|
120
|
+
return doc.to_html.strip
|
121
|
+
rescue => e
|
122
|
+
puts "MarkdownTopicConverter.format ERROR: #{e}"
|
123
|
+
return text
|
124
|
+
end
|
125
|
+
|
126
|
+
private
|
127
|
+
# convert bbcode-style image tag [img]url[/img] to markdown syntax ![alt](url)
|
128
|
+
def convert_bbcode_img(text)
|
129
|
+
text.gsub!(/\[img\](.+?)\[\/img\]/i) {"![#{image_alt $1}](#{$1})"}
|
130
|
+
end
|
131
|
+
|
132
|
+
def image_alt(src)
|
133
|
+
File.basename(src, '.*').capitalize
|
134
|
+
end
|
135
|
+
|
136
|
+
# borrow from html-pipeline
|
137
|
+
def has_ancestors?(node, tags)
|
138
|
+
while node = node.parent
|
139
|
+
if tags.include?(node.name.downcase)
|
140
|
+
break true
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# convert '#N楼' to link
|
146
|
+
# Refer to emoji_filter in html-pipeline
|
147
|
+
def link_mention_floor(doc)
|
148
|
+
doc.search('text()').each do |node|
|
149
|
+
content = node.to_html
|
150
|
+
next if !content.include?('#')
|
151
|
+
next if has_ancestors?(node, %w(pre code))
|
152
|
+
|
153
|
+
html = content.gsub(/#(\d+)([楼樓Ff])/) {
|
154
|
+
%(<a href="#reply#{$1}" class="at_floor" data-floor="#{$1}">##{$1}#{$2}</a>)
|
155
|
+
}
|
156
|
+
|
157
|
+
next if html == content
|
158
|
+
node.replace(html)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
NOMALIZE_USER_REGEXP = /(^|[^a-zA-Z0-9_!#\$%&*@@])@([a-zA-Z0-9_]{1,20})/io
|
163
|
+
LINK_USER_REGEXP = /(^|[^a-zA-Z0-9_!#\$%&*@@])@(user[0-9]{1,6})/io
|
164
|
+
|
165
|
+
# rename user name using incremental id
|
166
|
+
def nomalize_user_mentions(text)
|
167
|
+
users = []
|
168
|
+
|
169
|
+
text.gsub!(NOMALIZE_USER_REGEXP) do
|
170
|
+
prefix = $1
|
171
|
+
user = $2
|
172
|
+
users.push(user)
|
173
|
+
"#{prefix}@user#{users.size}"
|
174
|
+
end
|
175
|
+
|
176
|
+
users
|
177
|
+
end
|
178
|
+
|
179
|
+
# convert '@user' to link
|
180
|
+
# match any user even not exist.
|
181
|
+
def link_mention_user(doc, users)
|
182
|
+
doc.search('text()').each do |node|
|
183
|
+
content = node.to_html
|
184
|
+
next if !content.include?('@')
|
185
|
+
in_code = has_ancestors?(node, %w(pre code))
|
186
|
+
html = content.gsub(LINK_USER_REGEXP) {
|
187
|
+
prefix = $1
|
188
|
+
user_placeholder = $2
|
189
|
+
user_id = user_placeholder.sub(/^user/, '').to_i
|
190
|
+
user = users[user_id - 1] || user_placeholder
|
191
|
+
|
192
|
+
if in_code
|
193
|
+
"#{prefix}@#{user}"
|
194
|
+
else
|
195
|
+
%(#{prefix}<a href="/#{user}" class="at_user" title="@#{user}"><i>@</i>#{user}</a>)
|
196
|
+
end
|
197
|
+
}
|
198
|
+
|
199
|
+
node.replace(html)
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
def replace_emoji(doc)
|
204
|
+
doc.search('text()').each do |node|
|
205
|
+
content = node.to_html
|
206
|
+
next if !content.include?(':')
|
207
|
+
next if has_ancestors?(node, %w(pre code))
|
208
|
+
|
209
|
+
html = content.gsub(/:(\S+):/) do |emoji|
|
210
|
+
|
211
|
+
emoji_code = emoji #.gsub("|", "_")
|
212
|
+
emoji = emoji_code.gsub(":", "")
|
213
|
+
|
214
|
+
if MdEmoji::EMOJI.include?(emoji)
|
215
|
+
file_name = "#{emoji.gsub('+', 'plus')}.png"
|
216
|
+
|
217
|
+
%{<img src="#{upload_url}/assets/emojis/#{file_name}" class="emoji" } +
|
218
|
+
%{title="#{emoji_code}" alt="" />}
|
219
|
+
else
|
220
|
+
emoji_code
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
next if html == content
|
225
|
+
node.replace(html)
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
# for testing
|
230
|
+
def upload_url
|
231
|
+
Setting.upload_url
|
232
|
+
end
|
233
|
+
|
234
|
+
def initialize
|
235
|
+
@converter = Redcarpet::Markdown.new(Redcarpet::Render::HTMLwithTopic.new, {
|
236
|
+
:autolink => true,
|
237
|
+
:fenced_code_blocks => true,
|
238
|
+
:strikethrough => true,
|
239
|
+
:space_after_headers => true,
|
240
|
+
:no_intra_emphasis => true
|
241
|
+
})
|
242
|
+
@emoji = MdEmoji::Render.new
|
243
|
+
end
|
244
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'markdown-ruby-china'
|
3
|
+
s.version = '0.2'
|
4
|
+
s.date = '2013-04-17'
|
5
|
+
s.summary = ""
|
6
|
+
s.description = ""
|
7
|
+
s.authors = ["Ruby-China team", "David Chen"]
|
8
|
+
s.email = 'mvjome@gmail.com'
|
9
|
+
s.homepage = 'https://github.com/eoecn/markdown-ruby-china'
|
10
|
+
s.require_paths = ["lib"]
|
11
|
+
|
12
|
+
s.add_dependency "rails_autolink"
|
13
|
+
s.add_dependency 'redcarpet'
|
14
|
+
s.add_dependency 'md_emoji'
|
15
|
+
s.add_dependency 'pygments.rb'
|
16
|
+
s.add_dependency "nokogiri"
|
17
|
+
|
18
|
+
s.files = `git ls-files`.split("\n")
|
19
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: markdown-ruby-china
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '0.
|
4
|
+
version: '0.2'
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -97,7 +97,12 @@ email: mvjome@gmail.com
|
|
97
97
|
executables: []
|
98
98
|
extensions: []
|
99
99
|
extra_rdoc_files: []
|
100
|
-
files:
|
100
|
+
files:
|
101
|
+
- .gitignore
|
102
|
+
- Gemfile
|
103
|
+
- README.markdown
|
104
|
+
- lib/markdown-ruby-china.rb
|
105
|
+
- markdown-ruby-china.gemspec
|
101
106
|
homepage: https://github.com/eoecn/markdown-ruby-china
|
102
107
|
licenses: []
|
103
108
|
post_install_message:
|