simple_format 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +54 -0
- data/Rakefile +1 -0
- data/config/index.json +4356 -0
- data/lib/simple_format.rb +40 -0
- data/lib/simple_format/auto_link.rb +94 -0
- data/lib/simple_format/emoji.rb +112 -0
- data/lib/simple_format/version.rb +3 -0
- data/simple_format.gemspec +25 -0
- metadata +111 -0
@@ -0,0 +1,40 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require 'simple_format/version'
|
3
|
+
require 'simple_format/emoji'
|
4
|
+
require 'simple_format/auto_link'
|
5
|
+
require 'sanitize' unless defined?(::Sanitize)
|
6
|
+
|
7
|
+
module SimpleFormat
|
8
|
+
extend self
|
9
|
+
|
10
|
+
def h(string, size=64)
|
11
|
+
return string unless string
|
12
|
+
string = emoji.replace_emoji_with_images(string, size)
|
13
|
+
string = auto_link(string)
|
14
|
+
string = clean(string)
|
15
|
+
end
|
16
|
+
|
17
|
+
def replace_emoji_with_images(string, size=64)
|
18
|
+
emoji.replace_emoji_with_images(string, size)
|
19
|
+
end
|
20
|
+
|
21
|
+
def clean(html, options = {})
|
22
|
+
options = { elements: sanitized_allowed_tags, attributes: { all: sanitized_allowed_attributes } } if options.empty?
|
23
|
+
Sanitize.clean(html, options)
|
24
|
+
end
|
25
|
+
|
26
|
+
def emoji
|
27
|
+
@emoji ||= Emoji.new
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def sanitized_allowed_tags
|
33
|
+
['a', 'p', 'br', 'img', 'abbr', 'mark', 'm', 'fieldset', 'legend', 'label', 'summary', 'details', 'address', 'map', 'area', 'td', 'tr', 'th', 'thead', 'tbody', 'tfoot', 'caption', 'table', 'h4', 'h5', 'h6', 'hr', 'span', 'em', 'dl', 'dt', 'dd', 'b', 'del', 'ins', 'small', 'pre', 'blockquote', 'strong', 'audio', 'video', 'source', 'ul', 'ol', 'li', 'i', 'embed', 'sub', 'sup']
|
34
|
+
end
|
35
|
+
|
36
|
+
def sanitized_allowed_attributes
|
37
|
+
['shape', 'coords', 'target', 'href', 'muted', 'volume', 'name', 'class', 'title', 'border', 'poster', 'loop', 'autoplay', 'allowfullscreen', 'fullscreen', 'align' , 'quality', 'allowscriptaccess', 'wmode', 'flashvars', 'webkit-playsinline', 'x-webkit-airplay', 'data-original', 'src', 'controls', 'preload', 'type', 'width', 'height', 'size', 'alt']
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
module SimpleFormat
|
4
|
+
extend self
|
5
|
+
|
6
|
+
def auto_link(text, *args, &block) #link = :all, html = {}, &block)
|
7
|
+
return text unless text
|
8
|
+
|
9
|
+
options = {}
|
10
|
+
unless args.empty?
|
11
|
+
options[:link] = args[0] || :all
|
12
|
+
end
|
13
|
+
options.merge!(:link => :all)
|
14
|
+
# 转换回行
|
15
|
+
text = nl2br(text).to_str
|
16
|
+
# 根据类型生成链接
|
17
|
+
case options[:link].to_sym
|
18
|
+
when :all then auto_link_email_addresses(auto_link_urls(text, &block), &block)
|
19
|
+
when :email_addresses then auto_link_email_addresses(text, &block)
|
20
|
+
when :urls then auto_link_urls(text, &block)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def nl2br(string)
|
27
|
+
if string
|
28
|
+
string.gsub("\n\r","<br />").gsub("\r", "").gsub("\n", "<br />")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
AUTO_LINK_RE = %r{
|
33
|
+
(?: ((?:ed2k|ftp|http|https|irc|mailto|news|gopher|nntp|telnet|webcal|xmpp|callto|feed|svn|urn|aim|rsync|tag|ssh|sftp|rtsp|afs|file):)// | www\. )
|
34
|
+
[^\s<]+
|
35
|
+
}x
|
36
|
+
|
37
|
+
# regexps for determining context, used high-volume
|
38
|
+
AUTO_LINK_CRE = [/<[^>]+$/, /^[^>]*>/, /<a\b.*?>/i, /<\/a>/i]
|
39
|
+
|
40
|
+
AUTO_EMAIL_RE = /[\w.!#\$%+-]+@[\w-]+(?:\.[\w-]+)+/
|
41
|
+
|
42
|
+
BRACKETS = { ']' => '[', ')' => '(', '}' => '{' }
|
43
|
+
|
44
|
+
WORD_PATTERN = RUBY_VERSION < '1.9' ? '\w' : '\p{Word}'
|
45
|
+
|
46
|
+
# Turns all urls into clickable links. If a block is given, each url
|
47
|
+
# is yielded and the result is used as the link text.
|
48
|
+
def auto_link_urls(text)
|
49
|
+
|
50
|
+
text.gsub(AUTO_LINK_RE) do
|
51
|
+
scheme, href = $1, $&
|
52
|
+
punctuation = []
|
53
|
+
|
54
|
+
if auto_linked?($`, $')
|
55
|
+
# do not change string; URL is already linked
|
56
|
+
href
|
57
|
+
else
|
58
|
+
# don't include trailing punctuation character as part of the URL
|
59
|
+
while href.sub!(/[^#{WORD_PATTERN}\/-]$/, '')
|
60
|
+
punctuation.push $&
|
61
|
+
if opening = BRACKETS[punctuation.last] and href.scan(opening).size > href.scan(punctuation.last).size
|
62
|
+
href << punctuation.pop
|
63
|
+
break
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
link_text = block_given?? yield(href) : href
|
68
|
+
href = 'http://' + href unless scheme
|
69
|
+
|
70
|
+
"<a href='#{href}' target='_blank'>#{link_text}</a>" + punctuation.reverse.join('')
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Turns all email addresses into clickable links. If a block is given,
|
76
|
+
# each email is yielded and the result is used as the link text.
|
77
|
+
def auto_link_email_addresses(text)
|
78
|
+
text.gsub(AUTO_EMAIL_RE) do
|
79
|
+
text = $&
|
80
|
+
if auto_linked?($`, $')
|
81
|
+
text
|
82
|
+
else
|
83
|
+
display_text = (block_given?) ? yield(text) : text
|
84
|
+
# mail_to text, display_text
|
85
|
+
"<a href='mailto:#{text}'>#{display_text}</a>"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Detects already linked context or position in the middle of a tag
|
91
|
+
def auto_linked?(left, right)
|
92
|
+
(left =~ AUTO_LINK_CRE[0] and right =~ AUTO_LINK_CRE[1]) or (left.rindex(AUTO_LINK_CRE[2]) and $' !~ AUTO_LINK_CRE[3])
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require 'multi_json' unless defined?(::MultiJson)
|
3
|
+
|
4
|
+
module SimpleFormat
|
5
|
+
class Emoji
|
6
|
+
def initialize(mapping=nil)
|
7
|
+
mapping ||= begin
|
8
|
+
emoji_json = File.read(File.absolute_path(File.dirname(__FILE__) + '/../../config/index.json'))
|
9
|
+
MultiJson.load(emoji_json, symbolize_keys: true)
|
10
|
+
end
|
11
|
+
|
12
|
+
@emoji_by_name = {}
|
13
|
+
@emoji_by_unicode = {}
|
14
|
+
|
15
|
+
mapping.each do |emoji_hash|
|
16
|
+
name = emoji_hash[:name]
|
17
|
+
@emoji_by_name[name] = emoji_hash if name
|
18
|
+
|
19
|
+
unicode = emoji_hash[:unicode]
|
20
|
+
@emoji_by_unicode[unicode] = emoji_hash if unicode
|
21
|
+
end
|
22
|
+
|
23
|
+
@emoji_name_regex = /:([a-z0-9\+\-_]+):/
|
24
|
+
@emoji_unicode_regex = /#{@emoji_by_unicode.keys.join('|')}/
|
25
|
+
end
|
26
|
+
# 主机地址
|
27
|
+
def asset_host
|
28
|
+
@asset_host || '//l.ruby-china.org'
|
29
|
+
end
|
30
|
+
# 设置主机地址
|
31
|
+
def asset_host=(host)
|
32
|
+
@asset_host = host
|
33
|
+
end
|
34
|
+
# 资源路径
|
35
|
+
def asset_path
|
36
|
+
@asset_path || '/assets/emojis/'
|
37
|
+
end
|
38
|
+
# 设置路径
|
39
|
+
def asset_path=(path)
|
40
|
+
@asset_path = path
|
41
|
+
end
|
42
|
+
# 通过(名称、字符)替换表情
|
43
|
+
def replace_emoji_with_images(string, size='64')
|
44
|
+
return string unless string
|
45
|
+
|
46
|
+
html ||= string.dup
|
47
|
+
html = replace_name_with_images(html, size)
|
48
|
+
html = replace_unicode_with_images(html.to_str, size)
|
49
|
+
puts html
|
50
|
+
puts '_'*88 + 'replace_unicode_with_images'
|
51
|
+
return html
|
52
|
+
end
|
53
|
+
# 通过(名称)替换表情
|
54
|
+
def replace_name_with_images(string, size='64')
|
55
|
+
unless string && string.match(names_regex)
|
56
|
+
return string
|
57
|
+
end
|
58
|
+
|
59
|
+
string.to_str.gsub(names_regex) do |match|
|
60
|
+
if names.include?($1)
|
61
|
+
%Q{<img class="emoji" src="#{ image_url_for_name($1) }" width="#{size}" height="#{size}" />}
|
62
|
+
else
|
63
|
+
match
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
# 通过(字符)替换表情
|
68
|
+
def replace_unicode_with_images(string, size='64')
|
69
|
+
unless string && string.match(unicodes_regex)
|
70
|
+
return string
|
71
|
+
end
|
72
|
+
|
73
|
+
html ||= string.dup
|
74
|
+
html.gsub!(unicodes_regex) do |unicode|
|
75
|
+
%Q{<img class="emoji" src="#{ image_url_for_unicode(unicode) }" width="#{size}" height="#{size}" />}
|
76
|
+
end
|
77
|
+
end
|
78
|
+
# 通过(名称)合成图片地址
|
79
|
+
def image_url_for_name(name)
|
80
|
+
"#{asset_host}#{ File.join(asset_path, name) }.png"
|
81
|
+
end
|
82
|
+
# 通过(字符)合成图片地址
|
83
|
+
def image_url_for_unicode(unicode)
|
84
|
+
emoji = find_by_unicode(unicode)
|
85
|
+
image_url_for_name(emoji[:name]) unless emoji.nil?
|
86
|
+
end
|
87
|
+
# 通过(字符)找表情
|
88
|
+
def find_by_unicode(moji)
|
89
|
+
@emoji_by_unicode[moji]
|
90
|
+
end
|
91
|
+
# 通过(名称)表情
|
92
|
+
def find_by_name(name)
|
93
|
+
@emoji_by_name[name]
|
94
|
+
end
|
95
|
+
# 名称列表
|
96
|
+
def names
|
97
|
+
@emoji_by_name.keys
|
98
|
+
end
|
99
|
+
# 字符列表
|
100
|
+
def unicodes
|
101
|
+
@emoji_by_unicode.keys
|
102
|
+
end
|
103
|
+
# 名称匹配表达式
|
104
|
+
def names_regex
|
105
|
+
@emoji_name_regex
|
106
|
+
end
|
107
|
+
# 字符匹配表达式
|
108
|
+
def unicodes_regex
|
109
|
+
@emoji_unicode_regex
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'simple_format/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "simple_format"
|
8
|
+
spec.version = SimpleFormat::VERSION
|
9
|
+
spec.authors = ["Howl王"]
|
10
|
+
spec.email = ["howl.wong@gmail.com"]
|
11
|
+
spec.summary = %q{Returns text transformed into HTML using simple formatting rules.}
|
12
|
+
spec.homepage = "https://github.com/mimosa/simple_format/"
|
13
|
+
spec.license = "MIT"
|
14
|
+
|
15
|
+
spec.files = `git ls-files`.split($/)
|
16
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
|
+
spec.require_paths = ["lib"]
|
19
|
+
|
20
|
+
spec.add_dependency "multi_json"
|
21
|
+
spec.add_dependency "sanitize"
|
22
|
+
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.5"
|
24
|
+
spec.add_development_dependency "rake"
|
25
|
+
end
|
metadata
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: simple_format
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Howl王
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-02-28 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: multi_json
|
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: sanitize
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.5'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.5'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description:
|
70
|
+
email:
|
71
|
+
- howl.wong@gmail.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- ".gitignore"
|
77
|
+
- Gemfile
|
78
|
+
- LICENSE.txt
|
79
|
+
- README.md
|
80
|
+
- Rakefile
|
81
|
+
- config/index.json
|
82
|
+
- lib/simple_format.rb
|
83
|
+
- lib/simple_format/auto_link.rb
|
84
|
+
- lib/simple_format/emoji.rb
|
85
|
+
- lib/simple_format/version.rb
|
86
|
+
- simple_format.gemspec
|
87
|
+
homepage: https://github.com/mimosa/simple_format/
|
88
|
+
licenses:
|
89
|
+
- MIT
|
90
|
+
metadata: {}
|
91
|
+
post_install_message:
|
92
|
+
rdoc_options: []
|
93
|
+
require_paths:
|
94
|
+
- lib
|
95
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
96
|
+
requirements:
|
97
|
+
- - ">="
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: '0'
|
100
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - ">="
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '0'
|
105
|
+
requirements: []
|
106
|
+
rubyforge_project:
|
107
|
+
rubygems_version: 2.2.1
|
108
|
+
signing_key:
|
109
|
+
specification_version: 4
|
110
|
+
summary: Returns text transformed into HTML using simple formatting rules.
|
111
|
+
test_files: []
|