webget 0.3.1 → 0.3.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 +4 -4
- data/CHANGELOG.md +1 -3
- data/Manifest.txt +3 -0
- data/README.md +3 -3
- data/Rakefile +3 -4
- data/lib/webget/version.rb +1 -2
- data/lib/webget/webcache.rb +39 -167
- data/lib/webget/webcache_disk-rewrite.rb +86 -0
- data/lib/webget/webcache_disk.rb +244 -0
- data/lib/webget/webcache_headers.rb +79 -0
- data/lib/webget/webget.rb +20 -19
- data/lib/webget.rb +6 -0
- metadata +12 -23
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bb98b8d5740ff961389adbfcda8b1a461c1aa0f8fe6f9cc748ac8411c8559610
|
|
4
|
+
data.tar.gz: ccb159f52cc457f6a83e2fceabdab1d197018a58347e765b32726651a5985f01
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1d469ba45efeaf35d95d241e0521e2b447779ac88e8d5788e5e9599a759c9d7ad0e25c9ecff0a263d2755c9d1cf757196f0511a7172d17788652ab37c1a25f45
|
|
7
|
+
data.tar.gz: 87122ab93ceabb553b289f3f2ebc9343ee8d063b38ddd7f142e0534543acd9bb152aa8903b94b442b5d9249a05ffaaefcdd34b2ad41701540d0970d428551d0d
|
data/CHANGELOG.md
CHANGED
data/Manifest.txt
CHANGED
data/README.md
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
webget gem - a web (go get) crawler incl. web cache
|
|
4
4
|
|
|
5
|
-
* home :: [github.com/
|
|
6
|
-
* bugs :: [github.com/
|
|
5
|
+
* home :: [github.com/rubycocos/webclient](https://github.com/rubycocos/webclient)
|
|
6
|
+
* bugs :: [github.com/rubycocos/webclient/issues](https://github.com/rubycocos/webclient/issues)
|
|
7
7
|
* gem :: [rubygems.org/gems/webget](https://rubygems.org/gems/webget)
|
|
8
8
|
* rdoc :: [rubydoc.info/gems/webget](http://rubydoc.info/gems/webget)
|
|
9
9
|
|
|
@@ -16,4 +16,4 @@ TBD
|
|
|
16
16
|
## License
|
|
17
17
|
|
|
18
18
|
The `webget` scripts are dedicated to the public domain.
|
|
19
|
-
Use
|
|
19
|
+
Use as you please with no restrictions whatsoever.
|
data/Rakefile
CHANGED
|
@@ -8,7 +8,7 @@ Hoe.spec 'webget' do
|
|
|
8
8
|
self.summary = 'webget gem - a web (go get) crawler incl. web cache'
|
|
9
9
|
self.description = summary
|
|
10
10
|
|
|
11
|
-
self.urls = { home: 'https://github.com/
|
|
11
|
+
self.urls = { home: 'https://github.com/rubycocos/webclient' }
|
|
12
12
|
|
|
13
13
|
self.author = 'Gerald Bauer'
|
|
14
14
|
self.email = 'gerald.bauer@gmail.com'
|
|
@@ -18,15 +18,14 @@ Hoe.spec 'webget' do
|
|
|
18
18
|
self.history_file = 'CHANGELOG.md'
|
|
19
19
|
|
|
20
20
|
self.extra_deps = [
|
|
21
|
-
['webclient', '>= 0.
|
|
22
|
-
['csvreader', '>= 1.2.4'],
|
|
21
|
+
['webclient', '>= 0.3.1'],
|
|
23
22
|
['cocos'],
|
|
24
23
|
]
|
|
25
24
|
|
|
26
25
|
self.licenses = ['Public Domain']
|
|
27
26
|
|
|
28
27
|
self.spec_extras = {
|
|
29
|
-
required_ruby_version: '>=
|
|
28
|
+
required_ruby_version: '>= 3.1.0'
|
|
30
29
|
}
|
|
31
30
|
|
|
32
31
|
end
|
data/lib/webget/version.rb
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
class Webget
|
|
3
3
|
MAJOR = 0 ## todo: namespace inside version or something - why? why not??
|
|
4
4
|
MINOR = 3
|
|
5
|
-
PATCH =
|
|
5
|
+
PATCH = 3
|
|
6
6
|
VERSION = [MAJOR,MINOR,PATCH].join('.')
|
|
7
7
|
|
|
8
8
|
def self.version
|
|
@@ -18,4 +18,3 @@ class Webget
|
|
|
18
18
|
File.expand_path( File.dirname(File.dirname(File.dirname(__FILE__))) )
|
|
19
19
|
end
|
|
20
20
|
end # module Webget
|
|
21
|
-
|
data/lib/webget/webcache.rb
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module Webcache
|
|
4
4
|
|
|
5
|
+
|
|
5
6
|
#####
|
|
6
7
|
# copied from props gem, see Env.home
|
|
7
8
|
# - https://github.com/rubycoco/props/blob/master/props/lib/props/env.rb
|
|
@@ -26,7 +27,6 @@ module Webcache
|
|
|
26
27
|
## note: use File.expand_path to "unify" path e.g
|
|
27
28
|
## C:\Users\roman becomes
|
|
28
29
|
## C:/Users/roman
|
|
29
|
-
|
|
30
30
|
File.expand_path( path )
|
|
31
31
|
end
|
|
32
32
|
|
|
@@ -34,7 +34,14 @@ module Webcache
|
|
|
34
34
|
class Configuration
|
|
35
35
|
## root directory - todo/check: find/use a better name - why? why not?
|
|
36
36
|
def root() @root || "#{Webcache.home}/.cache"; end
|
|
37
|
-
def root=(
|
|
37
|
+
def root=(path)
|
|
38
|
+
## note: use File.expand_path to "unify" and auto-expand path
|
|
39
|
+
## to make sure always absolute
|
|
40
|
+
## e.g
|
|
41
|
+
## C:\Users\roman becomes
|
|
42
|
+
## C:/Users/roman
|
|
43
|
+
@root = File.expand_path(path)
|
|
44
|
+
end
|
|
38
45
|
end # class Configuration
|
|
39
46
|
|
|
40
47
|
|
|
@@ -49,186 +56,51 @@ module Webcache
|
|
|
49
56
|
## add "high level" root convenience helpers
|
|
50
57
|
## use delegate helper - why? why not?
|
|
51
58
|
def self.root() config.root; end
|
|
52
|
-
def self.root=(
|
|
59
|
+
def self.root=(path) config.root = path; end
|
|
53
60
|
|
|
54
61
|
|
|
55
|
-
### "interface" for "generic" cache storage (might be sqlite database or filesystem)
|
|
56
|
-
def self.cache() @cache ||= DiskCache.new; end
|
|
57
|
-
|
|
58
|
-
def self.record( url, response,
|
|
59
|
-
path: nil,
|
|
60
|
-
encoding: 'UTF-8',
|
|
61
|
-
format: 'html' )
|
|
62
|
-
cache.record( url, response,
|
|
63
|
-
path: path,
|
|
64
|
-
encoding: encoding,
|
|
65
|
-
format: format );
|
|
66
|
-
end
|
|
67
|
-
def self.cached?( url ) cache.cached?( url ); end
|
|
68
|
-
class << self
|
|
69
|
-
alias_method :exist?, :cached?
|
|
70
|
-
end
|
|
71
|
-
def self.url_to_id( url ) cache.url_to_id( url ); end ## todo/check: rename to just id or something - why? why not?
|
|
72
|
-
def self.read( url ) cache.read( url ); end
|
|
73
|
-
def self.read_json( url ) cache.read_json( url ); end
|
|
74
|
-
def self.read_csv( url ) cache.read_csv( url ); end
|
|
75
62
|
|
|
76
63
|
|
|
77
64
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
body_path = "#{Webcache.root}/#{url_to_path( url )}"
|
|
81
|
-
File.exist?( body_path )
|
|
82
|
-
end
|
|
83
|
-
alias_method :exist?, :cached?
|
|
65
|
+
### "interface" for "generic" cache storage (might be sqlite database or filesystem)
|
|
66
|
+
def self.cache() @cache ||= DiskCache.new; end
|
|
84
67
|
|
|
68
|
+
def self.record( url, response, format: ) ## html|txt|csv|json|etc.
|
|
69
|
+
## note - (text) encoding_user MUST get passed along in response obj/wrapper
|
|
70
|
+
## response._encoding_user = encoding! !!
|
|
71
|
+
cache.record( url, response, format: format );
|
|
72
|
+
end
|
|
85
73
|
|
|
86
|
-
|
|
87
|
-
body_path = "#{Webcache.root}/#{url_to_path( url )}"
|
|
88
|
-
File.open( body_path, 'r:utf-8' ) {|f| f.read }
|
|
89
|
-
end
|
|
74
|
+
def self.cached?( url ) cache.cached?( url ); end
|
|
90
75
|
|
|
91
|
-
def
|
|
92
|
-
body_path = "#{Webcache.root}/#{url_to_path( url )}"
|
|
93
|
-
txt = File.open( body_path, 'r:utf-8' ) {|f| f.read }
|
|
94
|
-
data = JSON.parse( txt )
|
|
95
|
-
data
|
|
96
|
-
end
|
|
76
|
+
## def self.url_to_id( url ) cache.url_to_id( url ); end ## todo/check: rename to just id or something - why? why not?
|
|
97
77
|
|
|
98
|
-
def read_csv( url )
|
|
99
|
-
body_path = "#{Webcache.root}/#{url_to_path( url )}"
|
|
100
|
-
txt = File.open( body_path, 'r:utf-8' ) {|f| f.read }
|
|
101
|
-
data = CsvHash.parse( txt )
|
|
102
|
-
data
|
|
103
|
-
end
|
|
104
78
|
|
|
79
|
+
def self.read( url ) cache.read( url ); end
|
|
80
|
+
def self.read_json( url ) cache.read_json( url ); end
|
|
81
|
+
def self.read_csv( url ) cache.read_csv( url ); end
|
|
105
82
|
|
|
106
|
-
## add more save / put / etc. aliases - why? why not?
|
|
107
|
-
## rename to record_html - why? why not?
|
|
108
|
-
def record( url, response,
|
|
109
|
-
path: nil,
|
|
110
|
-
encoding: 'UTF-8',
|
|
111
|
-
format: 'html' )
|
|
112
|
-
|
|
113
|
-
body_path = "#{Webcache.root}/#{url_to_path( url, path: path )}"
|
|
114
|
-
meta_path = "#{body_path}.meta.txt"
|
|
115
|
-
|
|
116
|
-
## make sure path exits
|
|
117
|
-
FileUtils.mkdir_p( File.dirname( body_path ) )
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
puts "[cache] saving #{body_path}..."
|
|
121
|
-
|
|
122
|
-
## todo/check: verify content-type - why? why not?
|
|
123
|
-
## note - for now respone.text always assume (converted) to utf8!!!!!!!!!
|
|
124
|
-
##
|
|
125
|
-
## fix: newlines - always use "unix" style" - why? why not?
|
|
126
|
-
## fix: use :newline => :universal option? translates to univeral "\n"
|
|
127
|
-
if format == 'json'
|
|
128
|
-
File.open( body_path, 'w:utf-8' ) {|f| f.write( JSON.pretty_generate( response.json )) }
|
|
129
|
-
elsif format == 'csv'
|
|
130
|
-
## fix: newlines - always use "unix" style" - why? why not?
|
|
131
|
-
## fix: use :newline => :universal option? translates to univeral "\n"
|
|
132
|
-
text = response.text( encoding: encoding ).gsub( "\r\n", "\n" )
|
|
133
|
-
File.open( body_path, 'w:utf-8' ) {|f| f.write( text ) }
|
|
134
|
-
else ## html or txt
|
|
135
|
-
text = response.text( encoding: encoding ).gsub( "\r\n", "\n" )
|
|
136
|
-
File.open( body_path, 'w:utf-8' ) {|f| f.write( text ) }
|
|
137
|
-
end
|
|
138
83
|
|
|
84
|
+
#### new - read (cached) meta data
|
|
85
|
+
## todo/check - find a better/different name - why? why not?
|
|
86
|
+
## e.g. read_headers or simply meta or headers or such
|
|
87
|
+
def self.read_meta( url ) cache.read_meta( url ); end
|
|
139
88
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
89
|
+
## add convenience expire (shortcut) helpers
|
|
90
|
+
def self.expired?( url, expires_in: Time.now.utc-60*60*12 )
|
|
91
|
+
if cached?( url )
|
|
92
|
+
meta = read_meta( url )
|
|
93
|
+
meta.expired?( expires_in )
|
|
94
|
+
else
|
|
95
|
+
true # note - not in cache; expired by default
|
|
96
|
+
end
|
|
148
97
|
end
|
|
98
|
+
def self.expired_in_12h?( url ) expired?( url, expires_in: Time.now.utc-60*60*12 ); end
|
|
99
|
+
def self.expired_in_24h?( url ) expired?( url, expires_in: Time.now.utc-60*60*24 ); end
|
|
149
100
|
|
|
150
101
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
def url_to_id( str ) "disk://#{url_to_path( str )}"; end
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
### helpers
|
|
158
|
-
def url_to_path( str, path: nil )
|
|
159
|
-
## map url to file path
|
|
160
|
-
uri = URI.parse( str )
|
|
161
|
-
|
|
162
|
-
## note: ignore scheme (e.g. http/https)
|
|
163
|
-
## and post (e.g. 80, 8080, etc.) for now
|
|
164
|
-
## always downcase for now (internet domain is case insensitive)
|
|
165
|
-
host_dir = uri.host.downcase
|
|
166
|
-
|
|
167
|
-
req_path = if path ## use "custom" (file)path for cache storage if passed in
|
|
168
|
-
path
|
|
169
|
-
else
|
|
170
|
-
## "/this/is/everything?query=params"
|
|
171
|
-
## cut-off leading slash and
|
|
172
|
-
## convert query ? =
|
|
173
|
-
uri.request_uri[1..-1]
|
|
174
|
-
end
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
### special "prettify" rule for weltfussball
|
|
179
|
-
## /eng-league-one-2019-2020/ => /eng-league-one-2019-2020.html
|
|
180
|
-
if host_dir.index( 'weltfussball.de' ) ||
|
|
181
|
-
host_dir.index( 'worldfootball.net' )
|
|
182
|
-
if req_path.end_with?( '/' )
|
|
183
|
-
req_path = "#{req_path[0..-2]}.html"
|
|
184
|
-
else
|
|
185
|
-
puts "ERROR: expected request_uri for >#{host_dir}< ending with '/'; got: >#{req_path}<"
|
|
186
|
-
exit 1
|
|
187
|
-
end
|
|
188
|
-
elsif host_dir.index( 'tipp3.at' )
|
|
189
|
-
req_path = req_path.sub( '.jsp', '' ) # shorten - cut off .jsp extension
|
|
190
|
-
|
|
191
|
-
## change ? to -I-
|
|
192
|
-
## change = to ~
|
|
193
|
-
## Example:
|
|
194
|
-
## sportwetten/classicresults.jsp?oddsetProgramID=888
|
|
195
|
-
## =>
|
|
196
|
-
## sportwetten/classicresults-I-oddsetProgramID~888
|
|
197
|
-
req_path = req_path.gsub( '?', '-I-' )
|
|
198
|
-
.gsub( '=', '~')
|
|
199
|
-
|
|
200
|
-
req_path = "#{req_path}.html"
|
|
201
|
-
elsif host_dir.index( 'fbref.com' )
|
|
202
|
-
req_path = req_path.sub( 'en/', '' ) # shorten - cut off en/
|
|
203
|
-
req_path = "#{req_path}.html" # auto-add html extension
|
|
204
|
-
elsif host_dir.index( 'football-data.co.uk' )
|
|
205
|
-
req_path = req_path.sub( 'mmz4281/', '' ) # shorten - cut off mmz4281/
|
|
206
|
-
req_path = req_path.sub( 'new/', '' ) # shorten - cut off new/
|
|
207
|
-
elsif host_dir.index( 'football-data.org' )
|
|
208
|
-
## req_path = req_path.sub( 'v2/', '' ) # shorten - cut off v2/
|
|
209
|
-
|
|
210
|
-
## flattern - make a file path - for auto-save
|
|
211
|
-
## change ? to -I-
|
|
212
|
-
## change / to ~~
|
|
213
|
-
## change = to ~
|
|
214
|
-
req_path = req_path.gsub( '?', '-I-' )
|
|
215
|
-
.gsub( '/', '~~' )
|
|
216
|
-
.gsub( '=', '~')
|
|
217
|
-
|
|
218
|
-
req_path = "#{req_path}.json"
|
|
219
|
-
elsif host_dir.index( 'api.cryptokitties.co' )
|
|
220
|
-
## for now always auto-add .json extensions e.g.
|
|
221
|
-
## kitties/1 => kitties/1.json
|
|
222
|
-
## cattributes => cattributes.json
|
|
223
|
-
req_path = "#{req_path}.json"
|
|
224
|
-
else
|
|
225
|
-
## no special rule
|
|
226
|
-
end
|
|
227
|
-
|
|
228
|
-
page_path = "#{host_dir}/#{req_path}"
|
|
229
|
-
page_path
|
|
102
|
+
class << self
|
|
103
|
+
alias_method :exist?, :cached?
|
|
104
|
+
alias_method :expired_in_1d?, :expired_in_24h?
|
|
230
105
|
end
|
|
231
|
-
end # class DiskCache
|
|
232
|
-
|
|
233
|
-
|
|
234
106
|
end # module Webcache
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
|
|
2
|
+
module Webcache
|
|
3
|
+
class DiskCache
|
|
4
|
+
|
|
5
|
+
####
|
|
6
|
+
## todo/fix - make rewrite_path configurable "pipeline"
|
|
7
|
+
## lets you auto-add more rewriters
|
|
8
|
+
## and keep this code "generic"
|
|
9
|
+
## move rewriters "downstream" into "userland" ??
|
|
10
|
+
|
|
11
|
+
def rewrite_path( host, req_path )
|
|
12
|
+
|
|
13
|
+
### special "prettify" rule for weltfussball
|
|
14
|
+
## /eng-league-one-2019-2020/ => /eng-league-one-2019-2020.html
|
|
15
|
+
|
|
16
|
+
### todo/fix - move rules downstream to user - why? why not?
|
|
17
|
+
|
|
18
|
+
if host.include?( 'uefa.com' ) ||
|
|
19
|
+
host.include?( 'kicker.de' ) ||
|
|
20
|
+
host.include?( 'kicker.at' )
|
|
21
|
+
if req_path.end_with?( '/' )
|
|
22
|
+
req_path = "#{req_path[0..-2]}.html"
|
|
23
|
+
else
|
|
24
|
+
puts "ERROR: expected request_uri for >#{host}< ending with '/'; got: >#{req_path}<"
|
|
25
|
+
exit 1
|
|
26
|
+
end
|
|
27
|
+
elsif host.include?( 'weltfussball.de' ) ||
|
|
28
|
+
host.include?( 'worldfootball.net' )
|
|
29
|
+
if req_path.end_with?( '/' )
|
|
30
|
+
req_path = "#{req_path[0..-2]}.html"
|
|
31
|
+
else
|
|
32
|
+
puts "ERROR: expected request_uri for >#{host}< ending with '/'; got: >#{req_path}<"
|
|
33
|
+
exit 1
|
|
34
|
+
end
|
|
35
|
+
elsif host.include?( 'tipp3.at' )
|
|
36
|
+
req_path = req_path.sub( '.jsp', '' ) # shorten - cut off .jsp extension
|
|
37
|
+
|
|
38
|
+
## change ? to -I-
|
|
39
|
+
## change = to ~
|
|
40
|
+
## Example:
|
|
41
|
+
## sportwetten/classicresults.jsp?oddsetProgramID=888
|
|
42
|
+
## =>
|
|
43
|
+
## sportwetten/classicresults-I-oddsetProgramID~888
|
|
44
|
+
req_path = req_path.gsub( '?', '-I-' )
|
|
45
|
+
.gsub( '=', '~')
|
|
46
|
+
|
|
47
|
+
req_path = "#{req_path}.html"
|
|
48
|
+
elsif host.include?( 'fbref.com' )
|
|
49
|
+
req_path = req_path.sub( 'en/', '' ) # shorten - cut off en/
|
|
50
|
+
req_path = "#{req_path}.html" # auto-add html extension
|
|
51
|
+
elsif host.include?( 'football-data.co.uk' )
|
|
52
|
+
req_path = req_path.sub( 'mmz4281/', '' ) # shorten - cut off mmz4281/
|
|
53
|
+
req_path = req_path.sub( 'new/', '' ) # shorten - cut off new/
|
|
54
|
+
elsif host.include?( 'football-data.org' )
|
|
55
|
+
## req_path = req_path.sub( 'v2/', '' ) # shorten - cut off v2/
|
|
56
|
+
|
|
57
|
+
## flattern - make a file path - for auto-save
|
|
58
|
+
## change ? to -I-
|
|
59
|
+
## change / to ~~
|
|
60
|
+
## change = to ~
|
|
61
|
+
req_path = req_path.gsub( '?', '-I-' )
|
|
62
|
+
.gsub( '/', '~~' )
|
|
63
|
+
.gsub( '=', '~')
|
|
64
|
+
|
|
65
|
+
req_path = "#{req_path}.json"
|
|
66
|
+
elsif host.include?( 'api-sports.io' )
|
|
67
|
+
req_path = req_path.gsub( '?', '-I-' )
|
|
68
|
+
.gsub( '&', '~~' ) ### check if & present?
|
|
69
|
+
.gsub( '=', '~')
|
|
70
|
+
|
|
71
|
+
req_path = "#{req_path}.json"
|
|
72
|
+
elsif host.include?( 'api.cryptokitties.co' )
|
|
73
|
+
## for now always auto-add .json extensions e.g.
|
|
74
|
+
## kitties/1 => kitties/1.json
|
|
75
|
+
## cattributes => cattributes.json
|
|
76
|
+
req_path = "#{req_path}.json"
|
|
77
|
+
else
|
|
78
|
+
## no special rule
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
req_path
|
|
82
|
+
end # method rewrite_path
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
end # class DiskCache
|
|
86
|
+
end # module Webcache
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
|
|
2
|
+
module Webcache
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class DiskCache ### todo/check - change to Disk - why? why not?
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _body_path( url ) "#{Webcache.root}/#{url_to_path( url )}"; end
|
|
9
|
+
def _meta_path( url ) "#{Webcache.root}/#{url_to_path( url )}.meta.txt"; end
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def cached?( url )
|
|
13
|
+
body_path = _body_path( url )
|
|
14
|
+
exist = File.exist?( body_path )
|
|
15
|
+
|
|
16
|
+
=begin
|
|
17
|
+
## not really working - check back later
|
|
18
|
+
### The catch on Windows
|
|
19
|
+
## On Windows, File.realpath does NOT normalize casing to the on-disk canonical case.
|
|
20
|
+
##
|
|
21
|
+
## note - on windows - file.exist? is case-insensitive
|
|
22
|
+
## use the strict: true flag if you want to enforce case-sensitive exists checks on windows!!!
|
|
23
|
+
## On Windows, File.realpath returns the "true" path
|
|
24
|
+
## as stored on the disk with the correct casing.
|
|
25
|
+
## If the path you provide doesn't match the casing of the real path,
|
|
26
|
+
## you know the match was case-insensitive.
|
|
27
|
+
if exist && strict
|
|
28
|
+
exist = File.realpath(body_path) == File.expand_path(body_path)
|
|
29
|
+
end
|
|
30
|
+
=end
|
|
31
|
+
|
|
32
|
+
exist
|
|
33
|
+
end
|
|
34
|
+
alias_method :exist?, :cached?
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
### fix-fix-fix
|
|
39
|
+
## change to read_txt/read_text/read_html
|
|
40
|
+
## plus add
|
|
41
|
+
## read_blob/read_bin(ary) !!!
|
|
42
|
+
def read( url )
|
|
43
|
+
_read_utf8(_body_path( url ))
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def read_json( url )
|
|
49
|
+
txt = _read_utf8(_body_path( url ))
|
|
50
|
+
data = JSON.parse( txt )
|
|
51
|
+
data
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def read_csv( url )
|
|
55
|
+
txt = _read_utf8(_body_path( url ))
|
|
56
|
+
data = CsvHash.parse( txt )
|
|
57
|
+
data
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def read_meta( url )
|
|
62
|
+
txt = _read_utf8(_meta_path( url ))
|
|
63
|
+
data = Headers.parse( txt )
|
|
64
|
+
data
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
## add more save / put / etc. aliases - why? why not?
|
|
71
|
+
## rename to record_html - why? why not?
|
|
72
|
+
|
|
73
|
+
def record( url, response, format: )
|
|
74
|
+
|
|
75
|
+
###
|
|
76
|
+
## note - encoding_user MUST be passed along with response (wrapper) obj
|
|
77
|
+
## e.g. response._encoding_user = encoding ??
|
|
78
|
+
## see Webget.page|text|dataset|etc.
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
## todo/check - use rel_path or local_path or such??
|
|
82
|
+
save_path = url_to_path( url )
|
|
83
|
+
|
|
84
|
+
body_path = _body_path( url )
|
|
85
|
+
meta_path = _meta_path( url ) ## is _body_path + ".meta.txt"
|
|
86
|
+
|
|
87
|
+
## make sure path exits
|
|
88
|
+
FileUtils.mkdir_p( File.dirname( body_path ) )
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
puts "[cache] saving #{body_path}..."
|
|
92
|
+
|
|
93
|
+
## todo/check: verify content-type - why? why not?
|
|
94
|
+
## note - for now respone.text always assume (converted) to utf8!!!!!!!!!
|
|
95
|
+
|
|
96
|
+
if format == 'json'
|
|
97
|
+
_write_utf8( body_path, JSON.pretty_generate( response.json ))
|
|
98
|
+
x_encoding = nil ## for now do not track; always assume UTF-8
|
|
99
|
+
x_encoding_source = nil
|
|
100
|
+
x_encoding_valid = nil
|
|
101
|
+
x_ascii_only = nil
|
|
102
|
+
x_8bit = nil
|
|
103
|
+
x_utf8_replace = nil
|
|
104
|
+
else ## html, txt or csv
|
|
105
|
+
_write_utf8( body_path, response.text )
|
|
106
|
+
|
|
107
|
+
x_encoding = response._text_encoding
|
|
108
|
+
x_encoding_source = response._text_encoding_source
|
|
109
|
+
x_encoding_valid = response._text_encoding_valid # true|false or nil (undef)
|
|
110
|
+
x_ascii_only = response._text_ascii_only
|
|
111
|
+
x_8bit = response._text_8bit
|
|
112
|
+
x_utf8_replace = response._text_utf8_replace
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
### fix-fix-fix -- add support for binary/image formats
|
|
117
|
+
## e.g. bin|gif|jpg|etc - why? why not?
|
|
118
|
+
|
|
119
|
+
####
|
|
120
|
+
## get file size in bytes
|
|
121
|
+
## or use File.stat( body_path ).size (using File::Stat) ??
|
|
122
|
+
x_size = File.size( body_path )
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
## todo/check:
|
|
127
|
+
## do headers also need to converted (like text) if encoding is NOT utf-8 ???
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
#### add our own custom headers first!!
|
|
131
|
+
## change x-save to x-filename or ??
|
|
132
|
+
## change to x-7bit-only or x-ascii7bit or x-ascii7bit-only or ??
|
|
133
|
+
## change x-size to x-bytesize or ??
|
|
134
|
+
## change x-8bit to ???
|
|
135
|
+
|
|
136
|
+
### start w/ comment line
|
|
137
|
+
### uncomment - http status - why? why not?
|
|
138
|
+
buf = String.new
|
|
139
|
+
buf << "# fetched on #{Time.now.utc}\n"
|
|
140
|
+
buf << "# HTTP/#{response.version} #{response.status.code} #{response.status.message}\n"
|
|
141
|
+
buf << "\n"
|
|
142
|
+
|
|
143
|
+
buf << "x-url: #{url}\n"
|
|
144
|
+
buf << "x-encoding: #{x_encoding}\n" if x_encoding
|
|
145
|
+
buf << "x-encoding-source: #{x_encoding_source}\n" if x_encoding_source
|
|
146
|
+
buf << "x-encoding-valid: #{x_encoding_valid}\n" if x_encoding_valid
|
|
147
|
+
buf << "x-ascii-only: #{x_ascii_only}\n" if x_ascii_only
|
|
148
|
+
buf << "x-8bit: #{x_8bit}\n" if x_8bit
|
|
149
|
+
buf << "x-utf8-replace: #{x_utf8_replace}\n" if x_utf8_replace
|
|
150
|
+
buf << "x-save: #{save_path}\n"
|
|
151
|
+
buf << "x-size: #{x_size}\n"
|
|
152
|
+
buf << "x-format: #{format}\n" ## e.g. json|html|csv|etc.
|
|
153
|
+
buf << "\n"
|
|
154
|
+
|
|
155
|
+
# iterate all response headers
|
|
156
|
+
response.headers.each do |key, value|
|
|
157
|
+
buf << "#{key}: #{value}\n"
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
_write_utf8( meta_path, buf )
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
### note: use file path as id for DiskCache (is different for DbCache/SqlCache?)
|
|
166
|
+
## use file:// instead of disk:// - why? why not?
|
|
167
|
+
## def url_to_id( str ) "disk://#{url_to_path( str )}"; end
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
### helpers
|
|
171
|
+
|
|
172
|
+
def url_to_path( str )
|
|
173
|
+
## map url to file path
|
|
174
|
+
uri = URI( str ) ## URI() same as URI.parse()
|
|
175
|
+
|
|
176
|
+
## note: ignore scheme (e.g. http/https)
|
|
177
|
+
## and post (e.g. 80, 8080, etc.) for now
|
|
178
|
+
## always downcase for now (internet domain is case insensitive)
|
|
179
|
+
host_dir = uri.host.downcase
|
|
180
|
+
|
|
181
|
+
## "/this/is/everything?query=params"
|
|
182
|
+
## cut-off leading slash and
|
|
183
|
+
## convert query ? =
|
|
184
|
+
## check if [1..] is sames as [1..-1]
|
|
185
|
+
req_path = rewrite_path( host_dir, uri.request_uri[1..] )
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
page_path = "#{host_dir}/#{req_path}"
|
|
189
|
+
page_path
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _read_utf8( path )
|
|
195
|
+
## note - by default ruby on windows (automagically)
|
|
196
|
+
## translates \r\n newlines to \n (universal/unix-style)
|
|
197
|
+
## when read files!!!!
|
|
198
|
+
## note - only handles \r\n (not "legacy" mac classic-style \r)
|
|
199
|
+
|
|
200
|
+
File.open( path, 'r:utf-8' ) {|f| f.read }
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def _write_utf8( path, text )
|
|
204
|
+
## write out utf8 (always use "universal" newlines on any platform)
|
|
205
|
+
## todo / fix - add universial or such to open too ?
|
|
206
|
+
##
|
|
207
|
+
## fix: newlines - always use "unix" style" - why? why not?
|
|
208
|
+
## fix: use :newline => :universal option? translates to univeral "\n"
|
|
209
|
+
##
|
|
210
|
+
##
|
|
211
|
+
## The universal_newline: true flag forces Ruby to look through the string and
|
|
212
|
+
## convert both Windows-style (\r\n) and old Mac-style (\r) newlines
|
|
213
|
+
## into the standard Unix newline (\n)
|
|
214
|
+
##
|
|
215
|
+
## File.open("output.txt", "w", universal_newline: true) do |file|
|
|
216
|
+
## file.write(content)
|
|
217
|
+
## end
|
|
218
|
+
##
|
|
219
|
+
## mixed_string = "Line one\r\nLine two\rLine three\n"
|
|
220
|
+
##
|
|
221
|
+
## Converts all \r\n and \r into \n
|
|
222
|
+
## clean_string = mixed_string.encode(universal_newline: true)
|
|
223
|
+
##
|
|
224
|
+
## Why Use encode Instead of gsub?
|
|
225
|
+
## While many developers use regular expressions like .gsub(/\r\n?/, "\n"),
|
|
226
|
+
## using .encode is highly preferred because:
|
|
227
|
+
## Edge-case Safety: It is an internal, optimized C-level implementation
|
|
228
|
+
## that handles mixed and broken newline edges perfectly.
|
|
229
|
+
## Encoding Preservation: It seamlessly preserves the existing character encoding
|
|
230
|
+
## (e.g., UTF-8) of your string.
|
|
231
|
+
##
|
|
232
|
+
## was - text = text.gsub( "\r\n", "\n" )
|
|
233
|
+
##
|
|
234
|
+
## note - by default ruby on windows (automagically) translates newlines to \r\n (crlf)!!!
|
|
235
|
+
## thus, always use/ add universal_newline flag!!!
|
|
236
|
+
|
|
237
|
+
File.open( path, 'w:utf-8', universal_newline: true ) do |f|
|
|
238
|
+
f.write( text )
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
end # class DiskCache
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
end ## module Webcache
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
|
|
2
|
+
module Webcache
|
|
3
|
+
|
|
4
|
+
##############################
|
|
5
|
+
# nested class for convenience access to (meta) headers
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Headers ## todo/check - rename to Meta or such - why? why not?
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
## def self.read( path ) parse(read_text( path )); end
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def self.parse( txt )
|
|
16
|
+
data = {}
|
|
17
|
+
txt.each_line do |line|
|
|
18
|
+
line = line.strip
|
|
19
|
+
next if line.empty? || line.start_with?( '#' )
|
|
20
|
+
|
|
21
|
+
key, value = line.split( ':', 2 ) ## split on first colon
|
|
22
|
+
|
|
23
|
+
## todo/fix: deal with possible duplicate header keys!!
|
|
24
|
+
## if duplicate do NOT replease, add with leading ", " comma-separated!!!
|
|
25
|
+
##
|
|
26
|
+
## check if multi-line headers are possible!!!
|
|
27
|
+
## and than may turn value into array of values - why? why not?
|
|
28
|
+
## or auto-add
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
## note - always downcase keys for now
|
|
32
|
+
## and strip value from leading and trailing spaces
|
|
33
|
+
data[ key.strip.downcase ] = value.strip
|
|
34
|
+
end
|
|
35
|
+
new( data )
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def initialize( data )
|
|
41
|
+
@data = data
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def to_h() @data; end
|
|
45
|
+
def [](key) @data[key]; end
|
|
46
|
+
|
|
47
|
+
def each( &blk )
|
|
48
|
+
@data.each do |key, value|
|
|
49
|
+
blk.call( key, value )
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def date
|
|
56
|
+
## return date header
|
|
57
|
+
## parses the time as RFC 1123 date of HTTP-date defined by RFC 2616:
|
|
58
|
+
## day-of-week, DD month-name CCYY hh:mm:ss GMT
|
|
59
|
+
## !!! Note that the result is always UTC (GMT). !!!
|
|
60
|
+
## e.g. Sun, 19 May 2024 15:15:34 GMT
|
|
61
|
+
## Mon, 10 Jun 2024 15:58:16 GMT
|
|
62
|
+
@date ||= Time.httpdate( @data['date'] )
|
|
63
|
+
@date
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
## default to 12h (60secs*60min*12h)
|
|
67
|
+
def expired?( expires_in_date=Time.now.utc-60*60*12 )
|
|
68
|
+
## pp expires_in_date
|
|
69
|
+
expires_in_date > date
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
## add convenience helpers - why? why not?
|
|
73
|
+
def expired_in_12h?() expired?( Time.now.utc-60*60*12 ); end
|
|
74
|
+
def expired_in_24h?() expired?( Time.now.utc-60*60*24 ); end
|
|
75
|
+
alias_method :expired_in_1d?, :expired_in_24h?
|
|
76
|
+
end # class Headers
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
end ## module Webcache
|
data/lib/webget/webget.rb
CHANGED
|
@@ -22,16 +22,15 @@ class Webget # a web (go get) crawler
|
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
|
|
25
|
-
## note - assumes json format
|
|
25
|
+
## note - assumes json format
|
|
26
26
|
## encoding always utf-8 by definition! - double check?)
|
|
27
|
-
def self.call( url, headers: {} )
|
|
27
|
+
def self.call( url, headers: {} )
|
|
28
28
|
response = _get( url, headers: headers )
|
|
29
29
|
|
|
30
30
|
if response.status.ok? ## must be HTTP 200
|
|
31
31
|
puts "#{response.status.code} #{response.status.message}"
|
|
32
32
|
## note: use format json for pretty printing and parse check!!!!
|
|
33
|
-
Webcache.record( url, response,
|
|
34
|
-
format: 'json' )
|
|
33
|
+
Webcache.record( url, response, format: 'json' )
|
|
35
34
|
else
|
|
36
35
|
## todo/check - log error
|
|
37
36
|
puts "!! HTTP ERROR - #{response.status.code} #{response.status.message}:"
|
|
@@ -40,17 +39,22 @@ class Webget # a web (go get) crawler
|
|
|
40
39
|
|
|
41
40
|
## to be done / continued
|
|
42
41
|
response
|
|
43
|
-
end # method self.
|
|
42
|
+
end # method self._get_and_record
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
44
46
|
|
|
45
47
|
## todo/check: rename encoding to html/http-like charset - why? why not?
|
|
46
48
|
## check encoding UTF-8 or utf-8 - makes a difference?
|
|
47
|
-
|
|
49
|
+
## note - default text encoding "upstream" in webclient for text is UTF-8!!
|
|
50
|
+
|
|
51
|
+
def self.page( url, encoding: nil, headers: {} ) ## assumes html format
|
|
48
52
|
response = _get( url, headers: headers )
|
|
53
|
+
response._encoding_user = encoding
|
|
49
54
|
|
|
50
55
|
if response.status.ok? ## must be HTTP 200
|
|
51
56
|
puts "#{response.status.code} #{response.status.message}"
|
|
52
|
-
Webcache.record( url, response,
|
|
53
|
-
encoding: encoding ) ## assumes format: html (default)
|
|
57
|
+
Webcache.record( url, response, format: 'html' )
|
|
54
58
|
else
|
|
55
59
|
## todo/check - log error
|
|
56
60
|
puts "!! HTTP ERROR - #{response.status.code} #{response.status.message}:"
|
|
@@ -63,15 +67,13 @@ class Webget # a web (go get) crawler
|
|
|
63
67
|
|
|
64
68
|
|
|
65
69
|
## assumes txt format
|
|
66
|
-
def self.text( url,
|
|
70
|
+
def self.text( url, encoding: nil, headers: {} )
|
|
67
71
|
response = _get( url, headers: headers )
|
|
72
|
+
response._encoding_user = encoding
|
|
68
73
|
|
|
69
74
|
if response.status.ok? ## must be HTTP 200
|
|
70
75
|
puts "#{response.status.code} #{response.status.message}"
|
|
71
|
-
|
|
72
|
-
Webcache.record( url, response,
|
|
73
|
-
path: path, ## optional "custom" (file)path for saving in cache
|
|
74
|
-
format: 'txt' )
|
|
76
|
+
Webcache.record( url, response, format: 'txt' )
|
|
75
77
|
else
|
|
76
78
|
## todo/check - log error
|
|
77
79
|
puts "!! HTTP ERROR - #{response.status.code} #{response.status.message}:"
|
|
@@ -86,14 +88,13 @@ class Webget # a web (go get) crawler
|
|
|
86
88
|
|
|
87
89
|
## todo/check: rename to csv or file or records or - why? why not?
|
|
88
90
|
## todo/check: rename encoding to html/http-like charset - why? why not?
|
|
89
|
-
def self.dataset( url, encoding:
|
|
91
|
+
def self.dataset( url, encoding: nil, headers: {} ) ## assumes csv format
|
|
90
92
|
response = _get( url, headers: headers )
|
|
93
|
+
response._encoding_user = encoding
|
|
91
94
|
|
|
92
95
|
if response.status.ok? ## must be HTTP 200
|
|
93
96
|
puts "#{response.status.code} #{response.status.message}"
|
|
94
|
-
Webcache.record( url, response,
|
|
95
|
-
encoding: encoding,
|
|
96
|
-
format: 'csv' ) ## pass along csv format - why? why not?
|
|
97
|
+
Webcache.record( url, response, format: 'csv' ) ## pass along csv format - why? why not?
|
|
97
98
|
else
|
|
98
99
|
## todo/check - log error
|
|
99
100
|
puts "!! HTTP ERROR - #{response.status.code} #{response.status.message}:"
|
|
@@ -106,8 +107,9 @@ class Webget # a web (go get) crawler
|
|
|
106
107
|
|
|
107
108
|
|
|
108
109
|
|
|
110
|
+
|
|
109
111
|
####
|
|
110
|
-
## private helpers
|
|
112
|
+
## private helpers
|
|
111
113
|
## make private - why? why not?
|
|
112
114
|
def self._get( url, headers: {} )
|
|
113
115
|
@@requests ||= 0 ## track number of requests
|
|
@@ -121,4 +123,3 @@ class Webget # a web (go get) crawler
|
|
|
121
123
|
Webclient.get( url, headers: headers ) ## returns respone
|
|
122
124
|
end
|
|
123
125
|
end # class Webget
|
|
124
|
-
|
data/lib/webget.rb
CHANGED
|
@@ -10,7 +10,13 @@ require 'cocos' # - note - cococs incl. webclient & cvsreader !!!!
|
|
|
10
10
|
|
|
11
11
|
## our own code
|
|
12
12
|
require_relative 'webget/version' # let version go first
|
|
13
|
+
|
|
14
|
+
require_relative 'webget/webcache_headers'
|
|
15
|
+
require_relative 'webget/webcache_disk'
|
|
16
|
+
require_relative 'webget/webcache_disk-rewrite'
|
|
13
17
|
require_relative 'webget/webcache'
|
|
18
|
+
|
|
19
|
+
|
|
14
20
|
require_relative 'webget/webget'
|
|
15
21
|
|
|
16
22
|
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: webget
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Gerald Bauer
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-09-07 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: webclient
|
|
@@ -16,28 +16,14 @@ dependencies:
|
|
|
16
16
|
requirements:
|
|
17
17
|
- - ">="
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: 0.
|
|
19
|
+
version: 0.3.1
|
|
20
20
|
type: :runtime
|
|
21
21
|
prerelease: false
|
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
23
|
requirements:
|
|
24
24
|
- - ">="
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
|
-
version: 0.
|
|
27
|
-
- !ruby/object:Gem::Dependency
|
|
28
|
-
name: csvreader
|
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
|
30
|
-
requirements:
|
|
31
|
-
- - ">="
|
|
32
|
-
- !ruby/object:Gem::Version
|
|
33
|
-
version: 1.2.4
|
|
34
|
-
type: :runtime
|
|
35
|
-
prerelease: false
|
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
-
requirements:
|
|
38
|
-
- - ">="
|
|
39
|
-
- !ruby/object:Gem::Version
|
|
40
|
-
version: 1.2.4
|
|
26
|
+
version: 0.3.1
|
|
41
27
|
- !ruby/object:Gem::Dependency
|
|
42
28
|
name: cocos
|
|
43
29
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -78,14 +64,14 @@ dependencies:
|
|
|
78
64
|
requirements:
|
|
79
65
|
- - "~>"
|
|
80
66
|
- !ruby/object:Gem::Version
|
|
81
|
-
version: '4.
|
|
67
|
+
version: '4.2'
|
|
82
68
|
type: :development
|
|
83
69
|
prerelease: false
|
|
84
70
|
version_requirements: !ruby/object:Gem::Requirement
|
|
85
71
|
requirements:
|
|
86
72
|
- - "~>"
|
|
87
73
|
- !ruby/object:Gem::Version
|
|
88
|
-
version: '4.
|
|
74
|
+
version: '4.2'
|
|
89
75
|
description: webget gem - a web (go get) crawler incl. web cache
|
|
90
76
|
email: gerald.bauer@gmail.com
|
|
91
77
|
executables: []
|
|
@@ -102,8 +88,11 @@ files:
|
|
|
102
88
|
- lib/webget.rb
|
|
103
89
|
- lib/webget/version.rb
|
|
104
90
|
- lib/webget/webcache.rb
|
|
91
|
+
- lib/webget/webcache_disk-rewrite.rb
|
|
92
|
+
- lib/webget/webcache_disk.rb
|
|
93
|
+
- lib/webget/webcache_headers.rb
|
|
105
94
|
- lib/webget/webget.rb
|
|
106
|
-
homepage: https://github.com/
|
|
95
|
+
homepage: https://github.com/rubycocos/webclient
|
|
107
96
|
licenses:
|
|
108
97
|
- Public Domain
|
|
109
98
|
metadata: {}
|
|
@@ -117,14 +106,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
117
106
|
requirements:
|
|
118
107
|
- - ">="
|
|
119
108
|
- !ruby/object:Gem::Version
|
|
120
|
-
version:
|
|
109
|
+
version: 3.1.0
|
|
121
110
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
122
111
|
requirements:
|
|
123
112
|
- - ">="
|
|
124
113
|
- !ruby/object:Gem::Version
|
|
125
114
|
version: '0'
|
|
126
115
|
requirements: []
|
|
127
|
-
rubygems_version: 3.
|
|
116
|
+
rubygems_version: 3.5.22
|
|
128
117
|
signing_key:
|
|
129
118
|
specification_version: 4
|
|
130
119
|
summary: webget gem - a web (go get) crawler incl. web cache
|