jtag 0.1.19 → 0.1.21

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.
data/lib/jtag/string.rb CHANGED
@@ -1,48 +1,244 @@
1
- # encoding: utf-8
2
- class String
3
- # convert "WikiLink" to "Wiki link"
4
- def break_camel
5
- return downcase if match(/\A[A-Z]+\z/)
6
- gsub(/([A-Z]+)([A-Z][a-z])/, '\1 \2').
7
- gsub(/([a-z])([A-Z])/, '\1 \2').
8
- downcase
9
- end
1
+ # frozen_string_literal: true
10
2
 
11
- def strip_markdown
12
- # strip all Markdown and Liquid tags
13
- gsub(/\{%.*?%\}/,'').
14
- gsub(/\[\^.+?\](\: .*?$)?/,'').
15
- gsub(/\s{0,2}\[.*?\]: .*?$/,'').
16
- gsub(/\!\[.*?\][\[\(].*?[\]\)]/,"").
17
- gsub(/\[(.*?)\][\[\(].*?[\]\)]/,"\\1").
18
- gsub(/^\s{1,2}\[(.*?)\]: (\S+)( ".*?")?\s*$/,'').
19
- gsub(/^\#{1,6}\s*/,'').
20
- gsub(/(\*{1,2})(\S.*?\S)\1/,"\\2").
21
- gsub(/(`{3,})(.*?)\1/m,"\\2").
22
- gsub(/^-{3,}\s*$/,"").
23
- gsub(/`(.+)`/,"\\1").
24
- gsub(/\n{2,}/,"\n\n")
25
- end
3
+ module JekyllTag
4
+ ## String helpers
5
+ class ::String
6
+ ##
7
+ ## normalize bool string to symbo
8
+ ##
9
+ ## @return [Symbol] :and :or :not
10
+ ##
11
+ def to_bool
12
+ case self.downcase
13
+ when /^a/i
14
+ :and
15
+ when /^o/i
16
+ :or
17
+ when /^n/i
18
+ :not
19
+ else
20
+ raise ArgumentError, "Invalid boolean string: #{self}"
21
+ end
22
+ end
26
23
 
27
- def strip_tags
28
- return CGI.unescapeHTML(
29
- gsub(/<(script|style|pre|code|figure).*?>.*?<\/\1>/im, '').
30
- gsub(/<!--.*?-->/m, '').
31
- gsub(/<(img|hr|br).*?>/i, " ").
32
- gsub(/<(dd|a|h\d|p|small|b|i|blockquote|li)( [^>]*?)?>(.*?)<\/\1>/i, " \\3 ").
33
- gsub(/<\/?(dt|a|ul|ol)( [^>]+)?>/i, " ").
34
- gsub(/<[^>]+?>/, '').
35
- gsub(/\[\d+\]/, '').
36
- gsub(/&#8217;/,"'").gsub(/&.*?;/,' ').gsub(/;/,' ')
37
- ).lstrip.gsub("\xE2\x80\x98","'").gsub("\xE2\x80\x99","'").gsub("\xCA\xBC","'").gsub("\xE2\x80\x9C",'"').gsub("\xE2\x80\x9D",'"').gsub("\xCB\xAE",'"').squeeze(" ")
38
- end
24
+ ##
25
+ ## Convert a string to a format symbol (:csv, :complete, :json, :plist, :yaml)
26
+ ##
27
+ ## @return [Symbol] format symbol
28
+ ##
29
+ def to_format
30
+ case self.downcase
31
+ when /^j/i
32
+ :json
33
+ when /^p/i
34
+ :plist
35
+ when /^y/i
36
+ :yaml
37
+ when /^cs/i
38
+ :csv
39
+ when /^c/i
40
+ :complete
41
+ else
42
+ raise ArgumentError, "Invalid format string: #{self}"
43
+ end
44
+ end
39
45
 
40
- def strip_urls
41
- gsub(/(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?/i,"")
42
- end
46
+ ## Check if a string is a list of files
47
+ ##
48
+ ## @param null [Boolean] check for null-delimited list
49
+ ##
50
+ ## @example Check if a string is a list of files
51
+ ## "file1\nfile2\nfile3".file_list?
52
+ ## # => true
53
+ ##
54
+ ## @return [Boolean]
55
+ def file_list?(null = false)
56
+ self.strip.split(null ? "\x00" : "\n").each do |line|
57
+ return false unless File.exist?(line)
58
+ end
59
+ true
60
+ end
43
61
 
44
- def strip_all
45
- strip_tags.strip_markdown.strip
46
- end
62
+ ##
63
+ ## Check if string is YAML
64
+ ##
65
+ ## @example Check if a string is YAML
66
+ ## "tags:\n - tag1\n - tag2".yaml?
67
+ ## # => true
68
+ ##
69
+ ## @example Check if a string is not YAML
70
+ ## "test string".yaml?
71
+ ## # => false
72
+ ##
73
+ ## @return [Boolean]
74
+ ##
75
+ def yaml?
76
+ begin
77
+ YAML.load(self)
78
+ rescue
79
+ return false
80
+ end
81
+ true
82
+ end
83
+
84
+ ##
85
+ ## Matches the string against a given keyword based on various options.
86
+ ##
87
+ ## @param keyword [String] the keyword to match against the string.
88
+ ## @param options [Hash] a hash of options to customize the matching behavior.
89
+ ## @option options [Boolean] :case_sensitive (false) whether the match should be case-sensitive.
90
+ ## @option options [Boolean] :starts_with (false) whether the match should check if the string starts with the keyword.
91
+ ## @option options [Boolean] :exact (false) whether the match should be exact.
92
+ ## @option options [Boolean] :fuzzy (true) whether the match should be fuzzy.
93
+ ## @option options [Integer] :distance (2) the maximum distance between characters for a fuzzy match.
94
+ ##
95
+ ## @return [Boolean] true if the string matches the keyword based on the given options, false otherwise.
96
+ ##
97
+ def match_keyword(keyword, options = {})
98
+ options = {
99
+ case_sensitive: false,
100
+ starts_with: false,
101
+ exact: false,
102
+ fuzzy: false,
103
+ contains: false,
104
+ distance: 2,
105
+ }.merge(options)
106
+
107
+ keyword = Regexp.escape(keyword)
108
+
109
+ if options[:exact]
110
+ re = "^#{keyword}$"
111
+ elsif options[:starts_with]
112
+ re = "^#{keyword}"
113
+ elsif options[:fuzzy]
114
+ re = ".*#{keyword.split(//).join(".{,#{options[:distance] - 1}}")}.*"
115
+ else
116
+ re = ".*#{keyword}.*"
117
+ end
118
+
119
+ if options[:case_sensitive]
120
+ self.match?(/#{re}/)
121
+ else
122
+ self.match?(/#{re}/i)
123
+ end
124
+ end
47
125
 
126
+ ##
127
+ ## Check if a string is JSON
128
+ ##
129
+ ## @example Check if a string is JSON
130
+ ## '{"tags": ["tag1", "tag2"]}'.json?
131
+ ## # => true
132
+ ##
133
+ ## @example Check if a string is not JSON
134
+ ## "test string".json?
135
+ ## # => false
136
+ ##
137
+ ## @return [Boolean]
138
+ ##
139
+ ## @note JSON must be a hash or array
140
+ ##
141
+ def json?
142
+ begin
143
+ JSON.parse(self).is_a?(Hash) || JSON.parse(self).is_a?(Array)
144
+ rescue
145
+ return false
146
+ end
147
+ true
148
+ end
149
+
150
+ ##
151
+ ## Convert parenthetical count to hash
152
+ ##
153
+ ## @example Convert a string to a hash
154
+ ## "tag (5)".to_count
155
+ ## # => { "name" => "tag", "count" => 5 }
156
+ ##
157
+ ## @example Convert a string to a hash with no count
158
+ ## "tag".to_count
159
+ ## # => { "name" => "tag", "count" => 0 }
160
+ ##
161
+ ## @return [Hash] hash with name and count
162
+ ##
163
+ def to_count
164
+ tag = dup.strip.sub(/^[[:punct:]] /, "")
165
+ if tag =~ /(.*) \((\d+)\)/
166
+ { "name" => $1, "count" => $2.to_i }
167
+ else
168
+ { "name" => tag, "count" => 0 }
169
+ end
170
+ end
171
+
172
+ ##
173
+ ## Check if a string is a list of tags
174
+ ##
175
+ ## @example Check if a string is a list of tags
176
+ ## "tag1\ntag2".tag_list?
177
+ ## # => true
178
+ ##
179
+ ## @return [Boolean]
180
+ ##
181
+ ## @note One tag per line
182
+ ## Tags are assumed to be alphanumeric and lowercase
183
+ ## (spaces, underscores, and dashes allowed) with no
184
+ ## leading or preceding or trailing punctuation
185
+ ##
186
+ def tag_list?
187
+ self.strip.split("\n").each do |tag|
188
+ return false unless tag.match?(/^(?![[:punct:]] )[a-z0-9 -_]+(?<![[:punct:]]) *$/)
189
+ end
190
+ true
191
+ end
192
+
193
+ def root_words
194
+ words = break_camel.split("[\s-]")
195
+ words.delete_if { |word| word =~ /^[^a-z]+$/i || word.length < 4 }
196
+ words.map { |word| word = Text::PorterStemming.stem(word).downcase }.join(" ")
197
+ end
198
+
199
+ # convert "WikiLink" to "Wiki link"
200
+ def break_camel
201
+ return downcase if match(/\A[A-Z]+\z/)
202
+ gsub(/([A-Z]+)([A-Z][a-z])/, '\1 \2').
203
+ gsub(/([a-z])([A-Z])/, '\1 \2').
204
+ downcase
205
+ end
206
+
207
+ def strip_markdown
208
+ # strip all Markdown and Liquid tags
209
+ gsub(/\{%.*?%\}/, "").
210
+ gsub(/\[\^.+?\](\: .*?$)?/, "").
211
+ gsub(/\s{0,2}\[.*?\]: .*?$/, "").
212
+ gsub(/\!\[.*?\][\[\(].*?[\]\)]/, "").
213
+ gsub(/\[(.*?)\][\[\(].*?[\]\)]/, "\\1").
214
+ gsub(/^\s{1,2}\[(.*?)\]: (\S+)( ".*?")?\s*$/, "").
215
+ gsub(/^\#{1,6}\s*/, "").
216
+ gsub(/(\*{1,2})(\S.*?\S)\1/, "\\2").
217
+ gsub(/(`{3,})(.*?)\1/m, "\\2").
218
+ gsub(/^-{3,}\s*$/, "").
219
+ gsub(/`(.+)`/, "\\1").
220
+ gsub(/\n{2,}/, "\n\n")
221
+ end
222
+
223
+ def strip_tags
224
+ return CGI.unescapeHTML(
225
+ gsub(/<(script|style|pre|code|figure).*?>.*?<\/\1>/im, "").
226
+ gsub(/<!--.*?-->/m, "").
227
+ gsub(/<(img|hr|br).*?>/i, " ").
228
+ gsub(/<(dd|a|h\d|p|small|b|i|blockquote|li)( [^>]*?)?>(.*?)<\/\1>/i, " \\3 ").
229
+ gsub(/<\/?(dt|a|ul|ol)( [^>]+)?>/i, " ").
230
+ gsub(/<[^>]+?>/, "").
231
+ gsub(/\[\d+\]/, "").
232
+ gsub(/&#8217;/, "'").gsub(/&.*?;/, " ").gsub(/;/, " ")
233
+ ).lstrip.gsub("\xE2\x80\x98", "'").gsub("\xE2\x80\x99", "'").gsub("\xCA\xBC", "'").gsub("\xE2\x80\x9C", '"').gsub("\xE2\x80\x9D", '"').gsub("\xCB\xAE", '"').squeeze(" ")
234
+ end
235
+
236
+ def strip_urls
237
+ gsub(/(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?/i, "")
238
+ end
239
+
240
+ def strip_all
241
+ strip_tags.strip_markdown.strip
242
+ end
243
+ end
48
244
  end
@@ -0,0 +1,319 @@
1
+ # encoding: utf-8
2
+ #
3
+ ## Stupid small pure Ruby JSON parser & generator.
4
+ #
5
+ # Copyright © 2013 Mislav Marohnić
6
+ #
7
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of this
8
+ # software and associated documentation files (the “Software”), to deal in the Software
9
+ # without restriction, including without limitation the rights to use, copy, modify,
10
+ # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
11
+ # permit persons to whom the Software is furnished to do so, subject to the following
12
+ # conditions:
13
+ #
14
+ # The above copyright notice and this permission notice shall be included in all copies or
15
+ # substantial portions of the Software.
16
+ #
17
+ # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
18
+ # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
19
+ # PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT
21
+ # OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ # OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ require 'strscan'
25
+ require 'forwardable'
26
+
27
+ # Usage:
28
+ #
29
+ # JSON.parse(json_string) => Array/Hash
30
+ # JSON.generate(object) => json string
31
+ #
32
+ # Run tests by executing this file directly. Pipe standard input to the script to have it
33
+ # parsed as JSON and to display the result in Ruby.
34
+ #
35
+ class JSON
36
+ def self.parse(data) new(data).parse end
37
+
38
+ WSP = /\s+/
39
+ OBJ = /[{\[]/; HEN = /\}/; AEN = /\]/
40
+ COL = /\s*:\s*/; KEY = /\s*,\s*/
41
+ NUM = /-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?/
42
+ BOL = /true|false/; NUL = /null/
43
+
44
+ extend Forwardable
45
+
46
+ attr_reader :scanner
47
+ alias_method :s, :scanner
48
+ def_delegators :scanner, :scan, :matched
49
+ private :s, :scan, :matched
50
+
51
+ def initialize data
52
+ @scanner = StringScanner.new data.to_s
53
+ end
54
+
55
+ def parse
56
+ space
57
+ object
58
+ end
59
+
60
+ private
61
+
62
+ def space() scan WSP end
63
+
64
+ def endkey() scan(KEY) or space end
65
+
66
+ def object
67
+ matched == '{' ? hash : array if scan(OBJ)
68
+ end
69
+
70
+ def value
71
+ object or string or
72
+ scan(NUL) ? nil :
73
+ scan(BOL) ? matched.size == 4:
74
+ scan(NUM) ? eval(matched) :
75
+ error
76
+ end
77
+
78
+ def hash
79
+ obj = {}
80
+ space
81
+ repeat_until(HEN) { k = string; scan(COL); obj[k] = value; endkey }
82
+ obj
83
+ end
84
+
85
+ def array
86
+ ary = []
87
+ space
88
+ repeat_until(AEN) { ary << value; endkey }
89
+ ary
90
+ end
91
+
92
+ SPEC = {'b' => "\b", 'f' => "\f", 'n' => "\n", 'r' => "\r", 't' => "\t"}
93
+ UNI = 'u'; CODE = /[a-fA-F0-9]{4}/
94
+ STR = /"/; STE = '"'
95
+ ESC = '\\'
96
+
97
+ def string
98
+ if scan(STR)
99
+ str, esc = '', false
100
+ while c = s.getch
101
+ if esc
102
+ str << (c == UNI ? (s.scan(CODE) || error).to_i(16).chr : SPEC[c] || c)
103
+ esc = false
104
+ else
105
+ case c
106
+ when ESC then esc = true
107
+ when STE then break
108
+ else str << c
109
+ end
110
+ end
111
+ end
112
+ str
113
+ end
114
+ end
115
+
116
+ def error
117
+ raise "parse error at: #{scan(/.{1,10}/m).inspect}"
118
+ end
119
+
120
+ def repeat_until reg
121
+ until scan(reg)
122
+ pos = s.pos
123
+ yield
124
+ error unless s.pos > pos
125
+ end
126
+ end
127
+
128
+ module Generator
129
+ def generate(obj)
130
+ raise ArgumentError unless obj.is_a? Array or obj.is_a? Hash
131
+ generate_type(obj)
132
+ end
133
+ alias dump generate
134
+
135
+ private
136
+
137
+ def generate_type(obj)
138
+ type = obj.is_a?(Numeric) ? :Numeric : obj.class.name
139
+ begin send(:"generate_#{type}", obj)
140
+ rescue NoMethodError; raise ArgumentError, "can't serialize #{type}"
141
+ end
142
+ end
143
+
144
+ ESC_MAP = Hash.new {|h,k| k }.update \
145
+ "\r" => 'r',
146
+ "\n" => 'n',
147
+ "\f" => 'f',
148
+ "\t" => 't',
149
+ "\b" => 'b'
150
+
151
+ def quote(str) %("#{str}") end
152
+
153
+ def generate_String(str)
154
+ quote str.gsub(/[\r\n\f\t\b"\\]/) { "\\#{ESC_MAP[$&]}"}
155
+ end
156
+
157
+ def generate_simple(obj) obj.inspect end
158
+ alias generate_Numeric generate_simple
159
+ alias generate_TrueClass generate_simple
160
+ alias generate_FalseClass generate_simple
161
+
162
+ def generate_Symbol(sym) generate_String(sym.to_s) end
163
+
164
+ def generate_Time(time)
165
+ quote time.strftime(time.utc? ? "%F %T UTC" : "%F %T %z")
166
+ end
167
+ def generate_Date(date) quote date.to_s end
168
+
169
+ def generate_NilClass(*) 'null' end
170
+
171
+ def generate_Array(ary) '[%s]' % ary.map {|o| generate_type(o) }.join(', ') end
172
+
173
+ def generate_Hash(hash)
174
+ '{%s}' % hash.map { |key, value|
175
+ "#{generate_String(key.to_s)}: #{generate_type(value)}"
176
+ }.join(', ')
177
+ end
178
+ end
179
+
180
+ extend Generator
181
+ end
182
+
183
+ if __FILE__ == $0
184
+ if !$stdin.tty?
185
+ data = JSON.parse $stdin.read
186
+ require 'pp'
187
+ pp data
188
+ else
189
+ require 'test/unit'
190
+ require 'date'
191
+ class ParserTest < Test::Unit::TestCase
192
+ PARSED = JSON.parse DATA.read
193
+ def parsed() PARSED end
194
+ def parse_string(str) JSON.parse(%(["#{str}"]).gsub('\\\\', '\\')).first end
195
+ def test_string
196
+ assert_equal "Pagination library for \"Rails 3\", Sinatra, Merb, DataMapper, and more",
197
+ parsed['head']['repository']['description']
198
+ end
199
+ def test_string_specials
200
+ assert_equal "\r\n\t\f\b", parse_string('\r\n\t\f\b')
201
+ assert_equal "aA", parse_string('\u0061\u0041')
202
+ assert_equal "\e", parse_string('\u001B')
203
+ assert_equal "xyz", parse_string('\x\y\z')
204
+ assert_equal '"\\/', parse_string('\"\\\\\\/')
205
+ assert_equal 'no #{interpolation}', parse_string('no #{interpolation}')
206
+ end
207
+ def test_hash
208
+ assert_equal %w[label ref repository sha user], parsed['head'].keys.sort
209
+ end
210
+ def test_number
211
+ assert_equal 124.3e2, parsed['head']['repository']['size']
212
+ end
213
+ def test_bool
214
+ assert_equal true, parsed['head']['repository']['fork']
215
+ assert_equal false, parsed['head']['repository']['private']
216
+ end
217
+ def test_nil
218
+ assert_nil parsed['head']['user']['company']
219
+ end
220
+ def test_array
221
+ assert_equal ["4438f", {"a" => "b"}], parsed['head']['sha']
222
+ end
223
+ def test_invalid
224
+ assert_raises(RuntimeError) { JSON.parse %({) }
225
+ assert_raises(RuntimeError) { JSON.parse %({ "foo": }) }
226
+ assert_raises(RuntimeError) { JSON.parse %([ "foo": "bar" ]) }
227
+ assert_raises(RuntimeError) { JSON.parse %([ ~"foo" ]) }
228
+ assert_raises(RuntimeError) { JSON.parse %([ "foo ]) }
229
+ assert_raises(RuntimeError) { JSON.parse %([ "foo\\" ]) }
230
+ assert_raises(RuntimeError) { JSON.parse %([ "foo\\uabGd" ]) }
231
+ end
232
+ end
233
+
234
+ class GeneratorTest < Test::Unit::TestCase
235
+ def generate(obj) JSON.generate(obj) end
236
+ def test_array
237
+ assert_equal %([1, 2, 3]), generate([1, 2, 3])
238
+ end
239
+ def test_bool
240
+ assert_equal %([true, false]), generate([true, false])
241
+ end
242
+ def test_null
243
+ assert_equal %([null]), generate([nil])
244
+ end
245
+ def test_string
246
+ assert_equal %(["abc\\n123"]), generate(["abc\n123"])
247
+ end
248
+ def test_string_unicode
249
+ assert_equal %(["ć\\"č\\nž\\tš\\\\đ"]), generate(["ć\"č\nž\tš\\đ"])
250
+ end
251
+ def test_time
252
+ time = Time.utc(2012, 04, 19, 1, 2, 3)
253
+ assert_equal %(["2012-04-19 01:02:03 UTC"]), generate([time])
254
+ end
255
+ def test_date
256
+ time = Date.new(2012, 04, 19)
257
+ assert_equal %(["2012-04-19"]), generate([time])
258
+ end
259
+ def test_symbol
260
+ assert_equal %(["abc"]), generate([:abc])
261
+ end
262
+ def test_hash
263
+ json = generate(:abc => 123, 123 => 'abc')
264
+ assert_match /^\{/, json
265
+ assert_match /\}$/, json
266
+ assert_equal [%("123": "abc"), %("abc": 123)], json[1...-1].split(', ').sort
267
+ end
268
+ def test_nested_structure
269
+ json = generate(:hash => {1=>2}, :array => [1,2])
270
+ assert json.include?(%("hash": {"1": 2}))
271
+ assert json.include?(%("array": [1, 2]))
272
+ end
273
+ def test_invalid_json
274
+ assert_raises(ArgumentError) { generate("abc") }
275
+ end
276
+ def test_invalid_object
277
+ err = assert_raises(ArgumentError) { generate("a" => Object.new) }
278
+ assert_equal "can't serialize Object", err.message
279
+ end
280
+ end
281
+ end
282
+ end
283
+
284
+ __END__
285
+ {
286
+ "head": {
287
+ "ref": "master",
288
+ "repository": {
289
+ "forks": 0,
290
+ "integrate_branch": "rails3",
291
+ "watchers": 1,
292
+ "language": "Ruby",
293
+ "description": "Pagination library for \"Rails 3\", Sinatra, Merb, DataMapper, and more",
294
+ "has_downloads": true,
295
+ "fork": true,
296
+ "created_at": "2011/10/24 03:20:48 -0700",
297
+ "homepage": "http://github.com/mislav/will_paginate/wikis",
298
+ "size": 124.3e2,
299
+ "private": false,
300
+ "has_wiki": true,
301
+ "name": "will_paginate",
302
+ "owner": "dbackeus",
303
+ "url": "https://github.com/dbackeus/will_paginate",
304
+ "has_issues": false,
305
+ "open_issues": 0,
306
+ "pushed_at": "2011/10/25 05:44:05 -0700"
307
+ },
308
+ "label": "dbackeus:master",
309
+ "sha": ["4438f", { "a" : "b" }],
310
+ "user": {
311
+ "name": "David Backeus",
312
+ "company": null,
313
+ "gravatar_id": "ebe96524f5db9e92188f0542dc9d1d1a",
314
+ "location": "Stockholm (Sweden)",
315
+ "type": "User",
316
+ "login": "dbackeus"
317
+ }
318
+ }
319
+ }