sinew 3.0.1 → 4.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/test.yml +4 -8
- data/.gitignore +3 -5
- data/.rubocop.yml +13 -48
- data/Gemfile +9 -9
- data/Gemfile.lock +132 -0
- data/LICENSE +1 -1
- data/README.md +113 -48
- data/Rakefile +3 -51
- data/bin/sinew +13 -41
- data/justfile +59 -0
- data/lib/sinew/args.rb +53 -0
- data/lib/sinew/base.rb +252 -0
- data/lib/sinew/csv.rb +89 -0
- data/lib/sinew/main.rb +46 -72
- data/lib/sinew/{connection → middleware}/log_formatter.rb +3 -2
- data/lib/sinew/nokogiri_ext.rb +13 -22
- data/lib/sinew/response.rb +41 -52
- data/lib/sinew/version.rb +1 -1
- data/lib/sinew.rb +23 -9
- data/sample.rb +13 -0
- data/sample.sinew +4 -4
- data/sinew.gemspec +24 -20
- metadata +56 -31
- data/.vscode/extensions.json +0 -3
- data/.vscode/settings.json +0 -5
- data/lib/sinew/connection/rate_limit.rb +0 -29
- data/lib/sinew/connection.rb +0 -52
- data/lib/sinew/core_ext.rb +0 -59
- data/lib/sinew/dsl.rb +0 -115
- data/lib/sinew/output.rb +0 -133
- data/lib/sinew/request.rb +0 -86
- data/lib/sinew/runtime_options.rb +0 -28
data/lib/sinew/response.rb
CHANGED
@@ -1,72 +1,61 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
|
4
|
-
|
5
|
-
# An HTTP response.
|
6
|
-
#
|
1
|
+
require "delegate"
|
2
|
+
require "hashie/mash"
|
3
|
+
require "json"
|
4
|
+
require "nokogiri"
|
7
5
|
|
8
6
|
module Sinew
|
9
|
-
|
10
|
-
|
7
|
+
# A wrapper around Faraday::Response, with some parsing helpers.
|
8
|
+
class Response < SimpleDelegator
|
9
|
+
# Like body, but tries to cleanup whitespace around HTML for easier parsing.
|
10
|
+
def html
|
11
|
+
@html ||= body.dup.tap do
|
12
|
+
# fix invalid utf8
|
13
|
+
if _1.encoding == Encoding::UTF_8
|
14
|
+
_1.encode!("UTF-8", invalid: :replace, undef: :replace, replace: "?")
|
15
|
+
end
|
11
16
|
|
12
|
-
|
13
|
-
|
14
|
-
|
17
|
+
# squish
|
18
|
+
_1.strip!
|
19
|
+
_1.gsub!(/\s+/, " ")
|
15
20
|
|
16
|
-
|
17
|
-
|
18
|
-
_1.request = request
|
19
|
-
_1.uri = fday_response.env.url
|
20
|
-
_1.code = fday_response.status
|
21
|
-
_1.headers = fday_response.headers.to_h
|
22
|
-
_1.body = process_body(fday_response)
|
21
|
+
# kill whitespace around tags
|
22
|
+
_1.gsub!(/ ?<([^>]+)> ?/, '<\\1>')
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
26
|
-
#
|
27
|
-
def
|
28
|
-
|
29
|
-
|
30
|
-
# inflate if necessary
|
31
|
-
bits = body[0, 10].force_encoding('BINARY')
|
32
|
-
if bits =~ /\A\x1f\x8b/n
|
33
|
-
body = Zlib::GzipReader.new(StringIO.new(body)).read
|
34
|
-
end
|
35
|
-
|
36
|
-
# force to utf-8 if we think this could be text
|
37
|
-
if body.encoding != Encoding::UTF_8
|
38
|
-
if content_type = response.headers['content-type']
|
39
|
-
if content_type =~ /\b(html|javascript|json|text|xml)\b/
|
40
|
-
body = body.encode('UTF-8', invalid: :replace, undef: :replace, replace: '?')
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
26
|
+
# Return body as JSON
|
27
|
+
def json
|
28
|
+
@json ||= JSON.parse(body, symbolize_names: true)
|
29
|
+
end
|
44
30
|
|
45
|
-
|
31
|
+
# Return JSON body as Hashie::Mash
|
32
|
+
def mash
|
33
|
+
@mash ||= Hashie::Mash.new(json)
|
46
34
|
end
|
47
35
|
|
48
|
-
#
|
49
|
-
|
50
|
-
|
36
|
+
# Return body HTML as Nokogiri document
|
37
|
+
def noko
|
38
|
+
@noko ||= Nokogiri::HTML(html)
|
39
|
+
end
|
51
40
|
|
52
|
-
|
53
|
-
|
41
|
+
# Return body XML as Nokogiri document
|
42
|
+
def xml
|
43
|
+
@xml ||= Nokogiri::XML(html)
|
54
44
|
end
|
55
45
|
|
56
|
-
|
57
|
-
|
46
|
+
# Return the final URI for the request, after redirects
|
47
|
+
def url
|
48
|
+
env.url
|
58
49
|
end
|
59
50
|
|
60
|
-
|
61
|
-
|
51
|
+
# Return the cache diskpath for this response
|
52
|
+
def diskpath
|
53
|
+
env[:httpdisk_diskpath]
|
62
54
|
end
|
63
55
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
code: code,
|
68
|
-
headers: headers,
|
69
|
-
}
|
56
|
+
# Remove cached response from disk, if any
|
57
|
+
def uncache
|
58
|
+
File.unlink(diskpath) if File.exist?(diskpath)
|
70
59
|
end
|
71
60
|
end
|
72
61
|
end
|
data/lib/sinew/version.rb
CHANGED
data/lib/sinew.rb
CHANGED
@@ -1,9 +1,23 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
1
|
+
# sinew
|
2
|
+
require "sinew/args"
|
3
|
+
require "sinew/base"
|
4
|
+
require "sinew/csv"
|
5
|
+
require "sinew/main"
|
6
|
+
require "sinew/nokogiri_ext"
|
7
|
+
require "sinew/response"
|
8
|
+
require "sinew/version"
|
9
|
+
|
10
|
+
# custom faraday middleware
|
11
|
+
require "sinew/middleware/log_formatter"
|
12
|
+
|
13
|
+
module Sinew
|
14
|
+
# flow control for --limit
|
15
|
+
class LimitError < StandardError; end
|
16
|
+
|
17
|
+
# shortcut for Sinew::Base.new
|
18
|
+
class << self
|
19
|
+
def new(**args)
|
20
|
+
Sinew::Base.new(**args)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/sample.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require_relative "lib/sinew"
|
2
|
+
|
3
|
+
sinew = Sinew.new(output: "sample.csv", verbose: true)
|
4
|
+
|
5
|
+
response = sinew.get "http://httpbingo.org"
|
6
|
+
response.noko.css("ul li a").each do |a|
|
7
|
+
row = {}
|
8
|
+
row[:url] = a[:href]
|
9
|
+
row[:title] = a.text
|
10
|
+
sinew.csv_emit(row)
|
11
|
+
end
|
12
|
+
|
13
|
+
sinew.get "http://httpbingo.org/redirect/2"
|
data/sample.sinew
CHANGED
@@ -1,9 +1,9 @@
|
|
1
|
-
get 'http://httpbingo.org'
|
2
|
-
noko.css('ul li a').each do |a|
|
1
|
+
response = sinew.get 'http://httpbingo.org'
|
2
|
+
response.noko.css('ul li a').each do |a|
|
3
3
|
row = {}
|
4
4
|
row[:url] = a[:href]
|
5
5
|
row[:title] = a.text
|
6
|
-
csv_emit(row)
|
6
|
+
sinew.csv_emit(row)
|
7
7
|
end
|
8
8
|
|
9
|
-
get 'http://httpbingo.org/redirect/2'
|
9
|
+
sinew.get 'http://httpbingo.org/redirect/2'
|
data/sinew.gemspec
CHANGED
@@ -1,32 +1,36 @@
|
|
1
1
|
$LOAD_PATH.unshift("#{__dir__}/lib")
|
2
2
|
|
3
|
-
require
|
3
|
+
require "sinew/version"
|
4
4
|
|
5
5
|
Gem::Specification.new do |s|
|
6
|
-
s.name
|
7
|
-
s.version
|
8
|
-
s.
|
9
|
-
s.
|
10
|
-
|
11
|
-
s.
|
12
|
-
s.
|
13
|
-
s.
|
14
|
-
s.
|
6
|
+
s.name = "sinew"
|
7
|
+
s.version = Sinew::VERSION
|
8
|
+
s.authors = ["Adam Doppelt", "Nathan Kriege"]
|
9
|
+
s.email = ["amd@gurge.com"]
|
10
|
+
|
11
|
+
s.summary = "Sinew - structured web crawling using recipes."
|
12
|
+
s.description = "Crawl web sites easily using ruby recipes, with caching and nokogiri."
|
13
|
+
s.homepage = "http://github.com/gurgeous/sinew"
|
14
|
+
s.license = "MIT"
|
15
|
+
s.required_ruby_version = ">= 3.1"
|
15
16
|
|
16
17
|
# what's in the gem?
|
17
18
|
s.files = Dir.chdir(File.expand_path(__dir__)) do
|
18
19
|
`git ls-files -z`.split("\x0").reject { _1.match(%r{^test/}) }
|
19
20
|
end
|
20
|
-
s.bindir =
|
21
|
+
s.bindir = "bin"
|
21
22
|
s.executables = s.files.grep(%r{^#{s.bindir}/}) { File.basename(_1) }
|
22
|
-
s.require_paths = [
|
23
|
+
s.require_paths = ["lib"]
|
23
24
|
|
24
|
-
|
25
|
-
s.
|
26
|
-
s.
|
27
|
-
s.
|
28
|
-
s.
|
29
|
-
s.
|
30
|
-
s.
|
31
|
-
s.
|
25
|
+
# gem dependencies
|
26
|
+
s.add_dependency "amazing_print", "~> 1.5"
|
27
|
+
s.add_dependency "faraday", "~> 2.7"
|
28
|
+
s.add_dependency "faraday-encoding", "~> 0.0"
|
29
|
+
s.add_dependency "faraday-rate_limiter", "~> 0.0"
|
30
|
+
s.add_dependency "faraday-retry", "~> 2.0"
|
31
|
+
s.add_dependency "hashie", "~> 5.0"
|
32
|
+
s.add_dependency "httpdisk", "~> 1.0"
|
33
|
+
s.add_dependency "nokogiri", "~> 1.15"
|
34
|
+
s.add_dependency "slop", "~> 4.10"
|
35
|
+
s.add_dependency "sterile", "~> 1.0"
|
32
36
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sinew
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 4.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Adam Doppelt
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2023-08-19 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: amazing_print
|
@@ -17,98 +17,126 @@ dependencies:
|
|
17
17
|
requirements:
|
18
18
|
- - "~>"
|
19
19
|
- !ruby/object:Gem::Version
|
20
|
-
version: '1.
|
20
|
+
version: '1.5'
|
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
|
-
version: '1.
|
27
|
+
version: '1.5'
|
28
28
|
- !ruby/object:Gem::Dependency
|
29
29
|
name: faraday
|
30
30
|
requirement: !ruby/object:Gem::Requirement
|
31
31
|
requirements:
|
32
32
|
- - "~>"
|
33
33
|
- !ruby/object:Gem::Version
|
34
|
-
version: '
|
34
|
+
version: '2.7'
|
35
35
|
type: :runtime
|
36
36
|
prerelease: false
|
37
37
|
version_requirements: !ruby/object:Gem::Requirement
|
38
38
|
requirements:
|
39
39
|
- - "~>"
|
40
40
|
- !ruby/object:Gem::Version
|
41
|
-
version: '
|
41
|
+
version: '2.7'
|
42
42
|
- !ruby/object:Gem::Dependency
|
43
43
|
name: faraday-encoding
|
44
44
|
requirement: !ruby/object:Gem::Requirement
|
45
45
|
requirements:
|
46
46
|
- - "~>"
|
47
47
|
- !ruby/object:Gem::Version
|
48
|
-
version: '0'
|
48
|
+
version: '0.0'
|
49
49
|
type: :runtime
|
50
50
|
prerelease: false
|
51
51
|
version_requirements: !ruby/object:Gem::Requirement
|
52
52
|
requirements:
|
53
53
|
- - "~>"
|
54
54
|
- !ruby/object:Gem::Version
|
55
|
-
version: '0'
|
55
|
+
version: '0.0'
|
56
56
|
- !ruby/object:Gem::Dependency
|
57
|
-
name:
|
57
|
+
name: faraday-rate_limiter
|
58
58
|
requirement: !ruby/object:Gem::Requirement
|
59
59
|
requirements:
|
60
60
|
- - "~>"
|
61
61
|
- !ruby/object:Gem::Version
|
62
|
-
version: '0'
|
62
|
+
version: '0.0'
|
63
63
|
type: :runtime
|
64
64
|
prerelease: false
|
65
65
|
version_requirements: !ruby/object:Gem::Requirement
|
66
66
|
requirements:
|
67
67
|
- - "~>"
|
68
68
|
- !ruby/object:Gem::Version
|
69
|
-
version: '0'
|
69
|
+
version: '0.0'
|
70
70
|
- !ruby/object:Gem::Dependency
|
71
|
-
name:
|
71
|
+
name: faraday-retry
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - "~>"
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '2.0'
|
77
|
+
type: :runtime
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - "~>"
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '2.0'
|
84
|
+
- !ruby/object:Gem::Dependency
|
85
|
+
name: hashie
|
86
|
+
requirement: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - "~>"
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '5.0'
|
91
|
+
type: :runtime
|
92
|
+
prerelease: false
|
93
|
+
version_requirements: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - "~>"
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '5.0'
|
98
|
+
- !ruby/object:Gem::Dependency
|
99
|
+
name: httpdisk
|
72
100
|
requirement: !ruby/object:Gem::Requirement
|
73
101
|
requirements:
|
74
102
|
- - "~>"
|
75
103
|
- !ruby/object:Gem::Version
|
76
|
-
version: '1.
|
104
|
+
version: '1.0'
|
77
105
|
type: :runtime
|
78
106
|
prerelease: false
|
79
107
|
version_requirements: !ruby/object:Gem::Requirement
|
80
108
|
requirements:
|
81
109
|
- - "~>"
|
82
110
|
- !ruby/object:Gem::Version
|
83
|
-
version: '1.
|
111
|
+
version: '1.0'
|
84
112
|
- !ruby/object:Gem::Dependency
|
85
|
-
name:
|
113
|
+
name: nokogiri
|
86
114
|
requirement: !ruby/object:Gem::Requirement
|
87
115
|
requirements:
|
88
116
|
- - "~>"
|
89
117
|
- !ruby/object:Gem::Version
|
90
|
-
version: '
|
118
|
+
version: '1.15'
|
91
119
|
type: :runtime
|
92
120
|
prerelease: false
|
93
121
|
version_requirements: !ruby/object:Gem::Requirement
|
94
122
|
requirements:
|
95
123
|
- - "~>"
|
96
124
|
- !ruby/object:Gem::Version
|
97
|
-
version: '
|
125
|
+
version: '1.15'
|
98
126
|
- !ruby/object:Gem::Dependency
|
99
127
|
name: slop
|
100
128
|
requirement: !ruby/object:Gem::Requirement
|
101
129
|
requirements:
|
102
130
|
- - "~>"
|
103
131
|
- !ruby/object:Gem::Version
|
104
|
-
version: '4.
|
132
|
+
version: '4.10'
|
105
133
|
type: :runtime
|
106
134
|
prerelease: false
|
107
135
|
version_requirements: !ruby/object:Gem::Requirement
|
108
136
|
requirements:
|
109
137
|
- - "~>"
|
110
138
|
- !ruby/object:Gem::Version
|
111
|
-
version: '4.
|
139
|
+
version: '4.10'
|
112
140
|
- !ruby/object:Gem::Dependency
|
113
141
|
name: sterile
|
114
142
|
requirement: !ruby/object:Gem::Requirement
|
@@ -134,26 +162,23 @@ files:
|
|
134
162
|
- ".github/workflows/test.yml"
|
135
163
|
- ".gitignore"
|
136
164
|
- ".rubocop.yml"
|
137
|
-
- ".vscode/extensions.json"
|
138
|
-
- ".vscode/settings.json"
|
139
165
|
- Gemfile
|
166
|
+
- Gemfile.lock
|
140
167
|
- LICENSE
|
141
168
|
- README.md
|
142
169
|
- Rakefile
|
143
170
|
- bin/sinew
|
171
|
+
- justfile
|
144
172
|
- lib/sinew.rb
|
145
|
-
- lib/sinew/
|
146
|
-
- lib/sinew/
|
147
|
-
- lib/sinew/
|
148
|
-
- lib/sinew/core_ext.rb
|
149
|
-
- lib/sinew/dsl.rb
|
173
|
+
- lib/sinew/args.rb
|
174
|
+
- lib/sinew/base.rb
|
175
|
+
- lib/sinew/csv.rb
|
150
176
|
- lib/sinew/main.rb
|
177
|
+
- lib/sinew/middleware/log_formatter.rb
|
151
178
|
- lib/sinew/nokogiri_ext.rb
|
152
|
-
- lib/sinew/output.rb
|
153
|
-
- lib/sinew/request.rb
|
154
179
|
- lib/sinew/response.rb
|
155
|
-
- lib/sinew/runtime_options.rb
|
156
180
|
- lib/sinew/version.rb
|
181
|
+
- sample.rb
|
157
182
|
- sample.sinew
|
158
183
|
- sinew.gemspec
|
159
184
|
homepage: http://github.com/gurgeous/sinew
|
@@ -168,14 +193,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
168
193
|
requirements:
|
169
194
|
- - ">="
|
170
195
|
- !ruby/object:Gem::Version
|
171
|
-
version: '
|
196
|
+
version: '3.1'
|
172
197
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
173
198
|
requirements:
|
174
199
|
- - ">="
|
175
200
|
- !ruby/object:Gem::Version
|
176
201
|
version: '0'
|
177
202
|
requirements: []
|
178
|
-
rubygems_version: 3.
|
203
|
+
rubygems_version: 3.3.7
|
179
204
|
signing_key:
|
180
205
|
specification_version: 4
|
181
206
|
summary: Sinew - structured web crawling using recipes.
|
data/.vscode/extensions.json
DELETED
data/.vscode/settings.json
DELETED
@@ -1,29 +0,0 @@
|
|
1
|
-
module Sinew
|
2
|
-
module Connection
|
3
|
-
class RateLimit < Faraday::Middleware
|
4
|
-
attr_reader :rate_limit
|
5
|
-
|
6
|
-
def initialize(app, options = {})
|
7
|
-
super(app)
|
8
|
-
|
9
|
-
@last_request_tm = @current_request_tm = nil
|
10
|
-
@rate_limit = options.fetch(:rate_limit, 1)
|
11
|
-
end
|
12
|
-
|
13
|
-
def on_request(_env)
|
14
|
-
if @last_request_tm
|
15
|
-
sleep = (@last_request_tm + rate_limit) - Time.now
|
16
|
-
sleep(sleep) if sleep > 0
|
17
|
-
end
|
18
|
-
|
19
|
-
@current_request_tm = Time.now
|
20
|
-
end
|
21
|
-
|
22
|
-
def on_complete(env)
|
23
|
-
# Only rate limit on uncached requests
|
24
|
-
@last_request_tm = @current_request_tm unless env[:httpdisk]
|
25
|
-
@current_request_tm = nil
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
data/lib/sinew/connection.rb
DELETED
@@ -1,52 +0,0 @@
|
|
1
|
-
require 'faraday'
|
2
|
-
require 'faraday-encoding'
|
3
|
-
require 'faraday/logging/formatter'
|
4
|
-
require 'httpdisk'
|
5
|
-
require 'sinew/connection/log_formatter'
|
6
|
-
require 'sinew/connection/rate_limit'
|
7
|
-
|
8
|
-
module Sinew
|
9
|
-
module Connection
|
10
|
-
def self.create(options:, runtime_options:)
|
11
|
-
connection_options = {}
|
12
|
-
connection_options[:ssl] = { verify: false } if runtime_options.insecure
|
13
|
-
|
14
|
-
Faraday.new(nil, connection_options) do
|
15
|
-
_1.use RateLimit, rate_limit: runtime_options.rate_limit
|
16
|
-
|
17
|
-
# auto-encode form bodies
|
18
|
-
_1.request :url_encoded
|
19
|
-
|
20
|
-
# Before httpdisk so each redirect segment is cached
|
21
|
-
# Keep track of redirect status for logger
|
22
|
-
_1.response :follow_redirects, callback: ->(_old_env, new_env) { new_env[:redirect] = true }
|
23
|
-
|
24
|
-
# set Ruby string encoding based on Content-Type (should be above httpdisk)
|
25
|
-
_1.response :encoding
|
26
|
-
|
27
|
-
# disk caching
|
28
|
-
httpdisk_options = {
|
29
|
-
dir: options[:cache],
|
30
|
-
force: options[:force],
|
31
|
-
force_errors: options[:force_errors],
|
32
|
-
}.merge(runtime_options.httpdisk_options)
|
33
|
-
|
34
|
-
_1.use :httpdisk, httpdisk_options
|
35
|
-
|
36
|
-
# After httpdisk so that only non-cached requests are logged.
|
37
|
-
# Before retry so that we don't log each retry attempt.
|
38
|
-
_1.response :logger, nil, formatter: LogFormatter if !options[:quiet]
|
39
|
-
|
40
|
-
# After httpdisk so transient failures are not cached
|
41
|
-
retry_options = {
|
42
|
-
interval: runtime_options.rate_limit,
|
43
|
-
max: runtime_options.retries,
|
44
|
-
methods: %w[delete get head options patch post put trace],
|
45
|
-
retry_statuses: (500..600).to_a,
|
46
|
-
retry_if: ->(_env, _err) { true },
|
47
|
-
}
|
48
|
-
_1.request :retry, retry_options
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
data/lib/sinew/core_ext.rb
DELETED
@@ -1,59 +0,0 @@
|
|
1
|
-
#
|
2
|
-
# A few core extensions brought over from ActiveSupport. These are handy for
|
3
|
-
# parsing.
|
4
|
-
#
|
5
|
-
|
6
|
-
class String
|
7
|
-
def squish
|
8
|
-
dup.squish!
|
9
|
-
end
|
10
|
-
|
11
|
-
def squish!
|
12
|
-
strip!
|
13
|
-
gsub!(/\s+/, ' ')
|
14
|
-
self
|
15
|
-
end
|
16
|
-
|
17
|
-
def first(limit = 1)
|
18
|
-
if limit == 0
|
19
|
-
''
|
20
|
-
elsif limit >= size
|
21
|
-
dup
|
22
|
-
else
|
23
|
-
self[0..limit - 1]
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
def last(limit = 1)
|
28
|
-
if limit == 0
|
29
|
-
''
|
30
|
-
elsif limit >= size
|
31
|
-
dup
|
32
|
-
else
|
33
|
-
self[-limit..]
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
alias starts_with? start_with?
|
38
|
-
alias ends_with? end_with?
|
39
|
-
end
|
40
|
-
|
41
|
-
#
|
42
|
-
# blank?/present?
|
43
|
-
#
|
44
|
-
|
45
|
-
class Object
|
46
|
-
def blank?
|
47
|
-
respond_to?(:empty?) ? !!empty? : !self
|
48
|
-
end
|
49
|
-
|
50
|
-
def present?
|
51
|
-
!blank?
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
class String
|
56
|
-
def blank?
|
57
|
-
!!(self =~ /\A\s*\z/)
|
58
|
-
end
|
59
|
-
end
|