linkhum 0.0.2 → 0.0.3
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.
- checksums.yaml +5 -13
- data/README.md +30 -2
- data/lib/linkhum.rb +87 -37
- data/linkhum.gemspec +2 -1
- metadata +31 -18
checksums.yaml
CHANGED
@@ -1,15 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
5
|
-
data.tar.gz: !binary |-
|
6
|
-
M2MwNTM2MGVmZjk5ZmZiMzMyOGU4YWFkN2Q2NDUyY2M0ODJlZWUzOA==
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: ad631369a4a44fc5562c929ebc4a8fa923e745a5
|
4
|
+
data.tar.gz: 184172a484e5eb074c8a28d269225ada3bd9c27f
|
7
5
|
SHA512:
|
8
|
-
metadata.gz:
|
9
|
-
|
10
|
-
NDNhMTY2NzE4NzNlMDY4ZWQ2NTA4OTE4ZmU0YzIyZGY1ZWY4Y2Y0NjZkNWFj
|
11
|
-
YTdkZGY1N2U3NmMzMTA0NjU2ZTlkYWRlYTgyZTcwNWI0NGU2NzA=
|
12
|
-
data.tar.gz: !binary |-
|
13
|
-
YWMwMDdhMzZjNzcxMDMyMTlhN2E0MmRkMGY1NDBmMzA0N2UwYWRjMjZjNGU5
|
14
|
-
MjljNGNmZTkxOTIxMDJlMjlkN2ZjZmY1MDhkODI0MjhmZTIyZjRmMWE0MzRm
|
15
|
-
N2NkMTM1OWQxYzQ1YTY1ZGFiYTlhODY3OWNhOTIwNzdhNGI5NDQ=
|
6
|
+
metadata.gz: bef81a104f120fcb54de689365516c1d4211e45652c299f024377c406234fc26eaec9c4924e8ff646b54d65c3e96d3a5c82d2a3d06d5e5ec675a46f53492863a
|
7
|
+
data.tar.gz: ad5e85a10b6c8a580fc8a58988fd13f891fce000255da24de010a8756ae8743fb98d85b4a583047615567cb3628ebcc7d8effe6171d1e5f9571ba903f8889c8b
|
data/README.md
CHANGED
@@ -12,7 +12,7 @@ Features:
|
|
12
12
|
* customizable behavior.
|
13
13
|
|
14
14
|
**NB**: the original algo was written by [squadette](https://github.com/squadette)
|
15
|
-
and the test cases provided by users of
|
15
|
+
and the test cases provided by users of [Mokum](https://mokum.place).
|
16
16
|
Just gemifying this (on behalf of original author).
|
17
17
|
|
18
18
|
## Install
|
@@ -167,10 +167,38 @@ SeveralArgs.urlify('@cool_dude')
|
|
167
167
|
# Receives "cool", "dude"
|
168
168
|
```
|
169
169
|
|
170
|
+
### "Parse only" mode
|
171
|
+
|
172
|
+
If your demands for resulting strings construction is far more complicated
|
173
|
+
than default LinkHum behavior, you can use its `#parse` command to split
|
174
|
+
string into tokens, and process them by yourself. All URL-detection
|
175
|
+
goodness and `special`s still will be with you:
|
176
|
+
|
177
|
+
```ruby
|
178
|
+
class MyParser < LinkHum
|
179
|
+
# You don't need rendering blocks for your specials
|
180
|
+
# Second argument is special's name, it is optional
|
181
|
+
special /@(\S+)\b/, :username
|
182
|
+
special /\#(\S+)\b/, :tag
|
183
|
+
end
|
184
|
+
|
185
|
+
MyParser.parse("Here is @dude. He is #cute. Is he on http://facebook.com?")
|
186
|
+
# => [
|
187
|
+
# {type: :text , content: 'Here is '},
|
188
|
+
# {type: :username, content: '@dude', captures: ['dude']},
|
189
|
+
# {type: :text , content: '. He is '},
|
190
|
+
# {type: :tag , content: '#cute', captures: ['cute']},
|
191
|
+
# {type: :text , content: '. Is he on '},
|
192
|
+
# {type: :url , content: 'http://facebook.com'},
|
193
|
+
# {type: :text , content: '?'}
|
194
|
+
# ]
|
195
|
+
```
|
196
|
+
|
170
197
|
## Credits
|
171
198
|
|
172
199
|
* [squadette](https://github.com/squadette) -- author of original code;
|
173
|
-
* users of
|
200
|
+
* users of [Mokum](https://mokum.place) -- testing and advicing (and now
|
201
|
+
you can observe LinkHum work online at Mokum);
|
174
202
|
* [zverok](https://github.com/zverok) -- gemifying, documenting and
|
175
203
|
writing specs.
|
176
204
|
|
data/lib/linkhum.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
require 'addressable/uri'
|
3
3
|
require 'cgi'
|
4
|
+
require 'strscan'
|
4
5
|
|
5
6
|
class LinkHum
|
6
7
|
class << self
|
@@ -10,76 +11,125 @@ class LinkHum
|
|
10
11
|
new(text).urlify(options.merge(link_processor: block))
|
11
12
|
end
|
12
13
|
|
14
|
+
def parse(text)
|
15
|
+
new(text).parse
|
16
|
+
end
|
17
|
+
|
13
18
|
def specials
|
14
|
-
@specials ||=
|
19
|
+
@specials ||= {}
|
15
20
|
end
|
16
21
|
|
17
|
-
|
18
|
-
|
22
|
+
RESERVED_NAMES = [:text, :url, :special]
|
23
|
+
|
24
|
+
def special(pattern, name = nil, &block)
|
25
|
+
name ||= "special_#{specials.count + 1}".to_sym
|
26
|
+
|
27
|
+
RESERVED_NAMES.include?(name) and fail(ArgumentError, "#{name} is reserved")
|
28
|
+
specials.key?(name) and fail(ArgumentError, "#{name} is already defined")
|
29
|
+
|
30
|
+
specials[name] = [pattern, block]
|
19
31
|
end
|
20
32
|
end
|
21
33
|
|
22
34
|
PROTOCOLS = '(?:https?|ftp)'
|
23
|
-
|
35
|
+
URL_PATTERN = %r{(#{PROTOCOLS}://\p{^Space}+)}i
|
24
36
|
|
25
37
|
MAX_DISPLAY_LENGTH = 64
|
26
38
|
|
27
39
|
def initialize(text)
|
28
40
|
@text = text
|
29
|
-
|
41
|
+
end
|
42
|
+
|
43
|
+
def parse
|
44
|
+
(@text.split(URL_PATTERN) + ['']).tap{|components|
|
45
|
+
(components).each_cons(2){|left, right|
|
46
|
+
# ['http://google.com', '/ and stuff'] => ['http://google.com/', ' and stuff']
|
47
|
+
shift_punct(left, right) if url?(left) && !url?(right)
|
48
|
+
}
|
49
|
+
}.reject(&:empty?).
|
50
|
+
map{|str|
|
51
|
+
url?(str) ? {type: :url, content: str} : parse_specials(str)
|
52
|
+
}.flatten
|
30
53
|
end
|
31
54
|
|
32
55
|
def urlify(options = {})
|
33
|
-
|
34
|
-
|
56
|
+
parse.map{|component|
|
57
|
+
case component[:type]
|
58
|
+
when :url
|
59
|
+
process_url(component[:content], options)
|
60
|
+
when :text
|
61
|
+
process_text(component[:content])
|
62
|
+
else
|
63
|
+
process_special(component)
|
64
|
+
end
|
35
65
|
}.join
|
36
66
|
end
|
37
67
|
|
38
68
|
private
|
39
69
|
|
40
|
-
def
|
41
|
-
|
42
|
-
|
43
|
-
|
70
|
+
def url?(str)
|
71
|
+
str =~ URL_PATTERN
|
72
|
+
end
|
73
|
+
|
74
|
+
# NB: nasty inplace strings changing is going on inside, beware!
|
75
|
+
def shift_punct(url, text_after)
|
76
|
+
url_, punct = url.scan(%r{\A(#{PROTOCOLS}://.+?)(\p{Punct}*)\Z}i).flatten
|
77
|
+
return unless url_
|
78
|
+
|
44
79
|
if punct[0] == '/' || (punct[0] == ')' && url.include?('('))
|
45
|
-
|
80
|
+
url_ << punct.slice!(0)
|
46
81
|
end
|
47
82
|
|
48
|
-
|
83
|
+
url.replace(url_)
|
84
|
+
text_after.prepend(punct)
|
49
85
|
end
|
50
86
|
|
51
|
-
def
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
str
|
56
|
-
|
57
|
-
|
87
|
+
def parse_specials(str)
|
88
|
+
res = []
|
89
|
+
str = str.dup
|
90
|
+
while !str.empty?
|
91
|
+
md = (specials_pattern.match(str)) or break
|
92
|
+
md.length.zero? and fail(RuntimeError, "Empty string matched by special at '#{str}'")
|
93
|
+
|
94
|
+
res << {type: :text, content: md.pre_match}
|
95
|
+
res << {type: :special, content: md[0]}
|
96
|
+
str = md.post_match
|
58
97
|
end
|
98
|
+
res << {type: :text, content: str}
|
99
|
+
res.reject{|r| r[:content].empty?}.each{|r|
|
100
|
+
update_special(r) if r[:type] == :special
|
101
|
+
}
|
59
102
|
end
|
60
103
|
|
61
|
-
def
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
"<a href='#{screen_feet(u)}'>#{s}</a>"
|
71
|
-
else
|
72
|
-
s
|
73
|
-
end
|
104
|
+
def specials_pattern
|
105
|
+
@specials_pattern ||= Regexp.union(self.class.specials.values.map(&:first))
|
106
|
+
end
|
107
|
+
|
108
|
+
def update_special(hash)
|
109
|
+
str = hash[:content]
|
110
|
+
name, (pattern, block) = self.class.specials.detect{|n, (p, b)| str[p] == str}
|
111
|
+
if name
|
112
|
+
hash.update(type: name, captures: pattern.match(str).captures)
|
74
113
|
end
|
75
114
|
end
|
76
115
|
|
77
|
-
def
|
78
|
-
|
79
|
-
|
116
|
+
def process_text(str)
|
117
|
+
CGI.escapeHTML(str)
|
118
|
+
end
|
119
|
+
|
120
|
+
def process_special(special)
|
121
|
+
return special[:content] if special[:type] == :special
|
122
|
+
|
123
|
+
_pattern, block = self.class.specials[special[:type]]
|
124
|
+
args = special[:captures] || [special[:match]]
|
125
|
+
if u = block.call(*args)
|
126
|
+
"<a href='#{screen_feet(u)}'>#{special[:content]}</a>"
|
127
|
+
else
|
128
|
+
special[:content]
|
129
|
+
end
|
80
130
|
end
|
81
131
|
|
82
|
-
def
|
132
|
+
def process_url(url, options)
|
83
133
|
uri = Addressable::URI.parse(url) rescue nil
|
84
134
|
return url unless uri
|
85
135
|
|
data/linkhum.gemspec
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = 'linkhum'
|
3
|
-
s.version = '0.0.
|
3
|
+
s.version = '0.0.3'
|
4
4
|
s.authors = ['Alexey Makhotkin', 'Victor Shepelev']
|
5
5
|
s.email = 'zverok.offline@gmail.com'
|
6
6
|
s.homepage = 'https://github.com/zverok/linkhum'
|
@@ -28,5 +28,6 @@ Gem::Specification.new do |s|
|
|
28
28
|
s.add_development_dependency 'rspec-its', '~> 1'
|
29
29
|
s.add_development_dependency 'nokogiri'
|
30
30
|
s.add_development_dependency 'rubocop'
|
31
|
+
s.add_development_dependency 'rubygems-tasks'
|
31
32
|
#s.add_development_dependency 'dokaz'
|
32
33
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: linkhum
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alexey Makhotkin
|
@@ -9,90 +9,104 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2015-
|
12
|
+
date: 2015-12-06 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: addressable
|
16
16
|
requirement: !ruby/object:Gem::Requirement
|
17
17
|
requirements:
|
18
|
-
- -
|
18
|
+
- - ">="
|
19
19
|
- !ruby/object:Gem::Version
|
20
20
|
version: '0'
|
21
21
|
type: :runtime
|
22
22
|
prerelease: false
|
23
23
|
version_requirements: !ruby/object:Gem::Requirement
|
24
24
|
requirements:
|
25
|
-
- -
|
25
|
+
- - ">="
|
26
26
|
- !ruby/object:Gem::Version
|
27
27
|
version: '0'
|
28
28
|
- !ruby/object:Gem::Dependency
|
29
29
|
name: rake
|
30
30
|
requirement: !ruby/object:Gem::Requirement
|
31
31
|
requirements:
|
32
|
-
- -
|
32
|
+
- - ">="
|
33
33
|
- !ruby/object:Gem::Version
|
34
34
|
version: '0'
|
35
35
|
type: :development
|
36
36
|
prerelease: false
|
37
37
|
version_requirements: !ruby/object:Gem::Requirement
|
38
38
|
requirements:
|
39
|
-
- -
|
39
|
+
- - ">="
|
40
40
|
- !ruby/object:Gem::Version
|
41
41
|
version: '0'
|
42
42
|
- !ruby/object:Gem::Dependency
|
43
43
|
name: rspec
|
44
44
|
requirement: !ruby/object:Gem::Requirement
|
45
45
|
requirements:
|
46
|
-
- - ~>
|
46
|
+
- - "~>"
|
47
47
|
- !ruby/object:Gem::Version
|
48
48
|
version: '3'
|
49
49
|
type: :development
|
50
50
|
prerelease: false
|
51
51
|
version_requirements: !ruby/object:Gem::Requirement
|
52
52
|
requirements:
|
53
|
-
- - ~>
|
53
|
+
- - "~>"
|
54
54
|
- !ruby/object:Gem::Version
|
55
55
|
version: '3'
|
56
56
|
- !ruby/object:Gem::Dependency
|
57
57
|
name: rspec-its
|
58
58
|
requirement: !ruby/object:Gem::Requirement
|
59
59
|
requirements:
|
60
|
-
- - ~>
|
60
|
+
- - "~>"
|
61
61
|
- !ruby/object:Gem::Version
|
62
62
|
version: '1'
|
63
63
|
type: :development
|
64
64
|
prerelease: false
|
65
65
|
version_requirements: !ruby/object:Gem::Requirement
|
66
66
|
requirements:
|
67
|
-
- - ~>
|
67
|
+
- - "~>"
|
68
68
|
- !ruby/object:Gem::Version
|
69
69
|
version: '1'
|
70
70
|
- !ruby/object:Gem::Dependency
|
71
71
|
name: nokogiri
|
72
72
|
requirement: !ruby/object:Gem::Requirement
|
73
73
|
requirements:
|
74
|
-
- -
|
74
|
+
- - ">="
|
75
75
|
- !ruby/object:Gem::Version
|
76
76
|
version: '0'
|
77
77
|
type: :development
|
78
78
|
prerelease: false
|
79
79
|
version_requirements: !ruby/object:Gem::Requirement
|
80
80
|
requirements:
|
81
|
-
- -
|
81
|
+
- - ">="
|
82
82
|
- !ruby/object:Gem::Version
|
83
83
|
version: '0'
|
84
84
|
- !ruby/object:Gem::Dependency
|
85
85
|
name: rubocop
|
86
86
|
requirement: !ruby/object:Gem::Requirement
|
87
87
|
requirements:
|
88
|
-
- -
|
88
|
+
- - ">="
|
89
89
|
- !ruby/object:Gem::Version
|
90
90
|
version: '0'
|
91
91
|
type: :development
|
92
92
|
prerelease: false
|
93
93
|
version_requirements: !ruby/object:Gem::Requirement
|
94
94
|
requirements:
|
95
|
-
- -
|
95
|
+
- - ">="
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '0'
|
98
|
+
- !ruby/object:Gem::Dependency
|
99
|
+
name: rubygems-tasks
|
100
|
+
requirement: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - ">="
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '0'
|
105
|
+
type: :development
|
106
|
+
prerelease: false
|
107
|
+
version_requirements: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - ">="
|
96
110
|
- !ruby/object:Gem::Version
|
97
111
|
version: '0'
|
98
112
|
description:
|
@@ -101,7 +115,7 @@ executables: []
|
|
101
115
|
extensions: []
|
102
116
|
extra_rdoc_files: []
|
103
117
|
files:
|
104
|
-
- .dokaz
|
118
|
+
- ".dokaz"
|
105
119
|
- CHANGELOG.md
|
106
120
|
- LICENSE.txt
|
107
121
|
- README.md
|
@@ -117,12 +131,12 @@ require_paths:
|
|
117
131
|
- lib
|
118
132
|
required_ruby_version: !ruby/object:Gem::Requirement
|
119
133
|
requirements:
|
120
|
-
- -
|
134
|
+
- - ">="
|
121
135
|
- !ruby/object:Gem::Version
|
122
136
|
version: '0'
|
123
137
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
124
138
|
requirements:
|
125
|
-
- -
|
139
|
+
- - ">="
|
126
140
|
- !ruby/object:Gem::Version
|
127
141
|
version: '0'
|
128
142
|
requirements: []
|
@@ -132,4 +146,3 @@ signing_key:
|
|
132
146
|
specification_version: 4
|
133
147
|
summary: Humane link urlifier
|
134
148
|
test_files: []
|
135
|
-
has_rdoc:
|