cdnget 0.3.1 → 1.0.0
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 -5
- data/CHANGES.md +15 -0
- data/README.md +6 -4
- data/Rakefile +1 -1
- data/bin/cdnget +354 -90
- data/cdnget.gemspec +3 -3
- data/lib/cdnget.rb +354 -90
- data/test/cdnget_test.rb +634 -108
- metadata +9 -8
data/lib/cdnget.rb
CHANGED
@@ -2,30 +2,97 @@
|
|
2
2
|
# -*- coding: utf-8 -*-
|
3
3
|
|
4
4
|
##
|
5
|
-
## Download files from CDN (CDNJS,
|
5
|
+
## Download files from CDN (CDNJS, jsDelivr, UNPKG, Google).
|
6
6
|
##
|
7
7
|
## - CDNJS (https://cdnjs.com/)
|
8
|
-
## - Google (https://developers.google.com/speed/libraries/)
|
9
8
|
## - jsDelivr (https://www.jsdelivr.com/)
|
9
|
+
## - UNPKG (https://unpkg.com/)
|
10
|
+
## - Google (https://developers.google.com/speed/libraries/)
|
10
11
|
##
|
11
12
|
## Example:
|
12
13
|
## $ cdnget # list public CDN
|
13
14
|
## $ cdnget [-q] cdnjs # list libraries
|
14
15
|
## $ cdnget [-q] cdnjs jquery # list versions
|
15
|
-
## $ cdnget [-q] cdnjs jquery
|
16
|
-
## $ cdnget [-q] cdnjs jquery
|
16
|
+
## $ cdnget [-q] cdnjs jquery latest # detect latest version
|
17
|
+
## $ cdnget [-q] cdnjs jquery 3.6.0 # list files
|
18
|
+
## $ cdnget [-q] cdnjs jquery 3.6.0 /tmp # download files
|
17
19
|
##
|
18
20
|
|
19
21
|
require 'open-uri'
|
20
22
|
require 'uri'
|
23
|
+
require 'net/http'
|
24
|
+
require 'openssl'
|
21
25
|
require 'json'
|
22
26
|
require 'fileutils'
|
27
|
+
require 'pp'
|
23
28
|
|
24
29
|
|
25
30
|
module CDNGet
|
26
31
|
|
27
32
|
|
28
|
-
RELEASE = '$Release: 0.
|
33
|
+
RELEASE = '$Release: 1.0.0 $'.split()[1]
|
34
|
+
|
35
|
+
|
36
|
+
class HttpConnection
|
37
|
+
|
38
|
+
def initialize(uri, headers=nil)
|
39
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
40
|
+
if uri.scheme == 'https'
|
41
|
+
http.use_ssl = true
|
42
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
43
|
+
end
|
44
|
+
http.start()
|
45
|
+
@http = http
|
46
|
+
@headers = headers
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.open(uri, headers=nil)
|
50
|
+
http = self.new(uri, headers)
|
51
|
+
return http unless block_given?()
|
52
|
+
begin
|
53
|
+
return yield http
|
54
|
+
ensure
|
55
|
+
http.close()
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def get(uri)
|
60
|
+
resp = @http.send_request('GET', uri.path, nil, @headers)
|
61
|
+
case resp
|
62
|
+
when Net::HTTPSuccess
|
63
|
+
return resp.body
|
64
|
+
#when HTTPInformation, Net::HTTPRedirection, HTTPClientError, HTTPServerError
|
65
|
+
else
|
66
|
+
raise HttpError.new(resp.code.to_i, resp.message)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def post(uri, payload)
|
71
|
+
path = uri.path
|
72
|
+
path += "?"+uri.query if uri.query && !uri.query.empty?
|
73
|
+
resp = @http.send_request('POST', path, payload, @headers)
|
74
|
+
case resp
|
75
|
+
when Net::HTTPSuccess ; return resp.body
|
76
|
+
else ; raise HttpError.new(resp.code.to_i, resp.message)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def close()
|
81
|
+
@http.finish()
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
class HttpError < StandardError
|
88
|
+
def initialize(code, msgtext)
|
89
|
+
super("#{code} #{msgtext}")
|
90
|
+
@code = code
|
91
|
+
@msgtext = msgtext
|
92
|
+
end
|
93
|
+
attr_reader :code, :msgtext
|
94
|
+
end
|
95
|
+
|
29
96
|
|
30
97
|
CLASSES = []
|
31
98
|
|
@@ -36,10 +103,22 @@ module CDNGet
|
|
36
103
|
CLASSES << klass
|
37
104
|
end
|
38
105
|
|
106
|
+
def initialize(debug_mode: false)
|
107
|
+
@debug_mode = debug_mode
|
108
|
+
end
|
109
|
+
attr_reader :debug_mode
|
110
|
+
|
39
111
|
def list()
|
40
112
|
raise NotImplementedError.new("#{self.class.name}#list(): not implemented yet.")
|
41
113
|
end
|
42
114
|
|
115
|
+
def search(pattern)
|
116
|
+
return list().select {|d| File.fnmatch(pattern, d[:name], File::FNM_CASEFOLD) }
|
117
|
+
#rexp_str = pattern.split('*', -1).collect {|x| Regexp.escape(x) }.join('.*')
|
118
|
+
#rexp = Regexp.compile("\\A#{rexp_str}\\z", Regexp::IGNORECASE)
|
119
|
+
#return list().select {|d| d[:name] =~ rexp }
|
120
|
+
end
|
121
|
+
|
43
122
|
def find(library)
|
44
123
|
raise NotImplementedError.new("#{self.class.name}#find(): not implemented yet.")
|
45
124
|
end
|
@@ -54,14 +133,28 @@ module CDNGet
|
|
54
133
|
raise CommandError.new("#{basedir}: not exist.")
|
55
134
|
File.directory?(basedir) or
|
56
135
|
raise CommandError.new("#{basedir}: not a directory.")
|
57
|
-
target_dir = File.join(basedir, library, version)
|
58
136
|
d = get(library, version)
|
137
|
+
target_dir = d[:destdir] ? File.join(basedir, d[:destdir]) \
|
138
|
+
: File.join(basedir, library, version)
|
139
|
+
http = nil
|
59
140
|
d[:files].each do |file|
|
60
141
|
filepath = File.join(target_dir, file)
|
142
|
+
if filepath.end_with?('/')
|
143
|
+
if File.exist?(filepath)
|
144
|
+
puts "#{filepath} ... Done (Already exists)" unless quiet
|
145
|
+
else
|
146
|
+
print "#{filepath} ..." unless quiet
|
147
|
+
FileUtils.mkdir_p(filepath)
|
148
|
+
puts " Done (Created)" unless quiet
|
149
|
+
end
|
150
|
+
next
|
151
|
+
end
|
61
152
|
dirpath = File.dirname(filepath)
|
62
153
|
print "#{filepath} ..." unless quiet
|
63
154
|
url = File.join(d[:baseurl], file) # not use URI.join!
|
64
|
-
|
155
|
+
uri = URI.parse(url)
|
156
|
+
http ||= HttpConnection.new(uri)
|
157
|
+
content = http.get(uri)
|
65
158
|
content = content.force_encoding('ascii-8bit')
|
66
159
|
print " Done (#{format_integer(content.bytesize)} byte)" unless quiet
|
67
160
|
FileUtils.mkdir_p(dirpath) unless File.exist?(dirpath)
|
@@ -73,17 +166,32 @@ module CDNGet
|
|
73
166
|
end
|
74
167
|
puts() unless quiet
|
75
168
|
end
|
169
|
+
http.close() if http
|
76
170
|
nil
|
77
171
|
end
|
78
172
|
|
79
173
|
protected
|
80
174
|
|
175
|
+
def http_get(url)
|
176
|
+
## * `open()` on Ruby 3.X can't open http url
|
177
|
+
## * `URI.open()` on Ruby <= 2.4 raises NoMethodError (private method `open' called)
|
178
|
+
## * `URI.__send__(:open)` is a hack to work on both Ruby 2.X and 3.X
|
179
|
+
return URI.__send__(:open, url, 'rb') {|f| f.read() }
|
180
|
+
end
|
181
|
+
|
81
182
|
def fetch(url, library=nil)
|
82
183
|
begin
|
83
|
-
html =
|
184
|
+
html = http_get(url)
|
84
185
|
return html
|
85
|
-
rescue OpenURI::HTTPError =>
|
86
|
-
|
186
|
+
rescue OpenURI::HTTPError => exc
|
187
|
+
if ! (exc.message == "404 Not Found" && library)
|
188
|
+
raise CommandError.new("GET #{url} : #{exc.message}")
|
189
|
+
elsif ! library.end_with?('js')
|
190
|
+
raise CommandError.new("#{library}: Library not found.")
|
191
|
+
else
|
192
|
+
maybe = library.end_with?('.js') ? library.sub('.js', 'js') : library.sub(/js$/, '.js')
|
193
|
+
raise CommandError.new("#{library}: Library not found (maybe '#{maybe}'?).")
|
194
|
+
end
|
87
195
|
end
|
88
196
|
end
|
89
197
|
|
@@ -102,27 +210,21 @@ module CDNGet
|
|
102
210
|
return value.to_s.reverse.scan(/..?.?/).collect {|s| s.reverse }.reverse.join(',')
|
103
211
|
end
|
104
212
|
|
105
|
-
|
106
|
-
|
213
|
+
def _debug_print(x)
|
214
|
+
if @debug_mode
|
215
|
+
$stderr.puts "\e[0;35m*** #{PP.pp(x,'')}\e[0m"
|
216
|
+
end
|
217
|
+
end
|
107
218
|
|
108
|
-
class HttpError < StandardError
|
109
219
|
end
|
110
220
|
|
111
221
|
|
112
|
-
class CDNJS < Base
|
222
|
+
class CDNJS < Base
|
113
223
|
CODE = "cdnjs"
|
114
224
|
SITE_URL = 'https://cdnjs.com/'
|
115
225
|
|
116
226
|
def fetch(url, library=nil)
|
117
|
-
|
118
|
-
json_str = URI.open(url, 'rb') {|f| f.read() }
|
119
|
-
rescue OpenURI::HTTPError => exc
|
120
|
-
if exc.message == "404 Not Found"
|
121
|
-
json_str = "{}"
|
122
|
-
else
|
123
|
-
raise HttpError.new("GET #{url} : #{ex.message}")
|
124
|
-
end
|
125
|
-
end
|
227
|
+
json_str = super
|
126
228
|
if json_str == "{}" && library
|
127
229
|
if library.end_with?('js')
|
128
230
|
maybe = library.end_with?('.js') \
|
@@ -137,10 +239,10 @@ module CDNGet
|
|
137
239
|
end
|
138
240
|
protected :fetch
|
139
241
|
|
140
|
-
def list
|
141
|
-
libs = []
|
242
|
+
def list()
|
142
243
|
jstr = fetch("https://api.cdnjs.com/libraries?fields=name,description")
|
143
244
|
jdata = JSON.parse(jstr)
|
245
|
+
_debug_print(jdata)
|
144
246
|
libs = jdata['results'].collect {|d| {name: d['name'], desc: d['description']} }
|
145
247
|
return libs.sort_by {|d| d[:name] }.uniq
|
146
248
|
end
|
@@ -149,12 +251,16 @@ module CDNGet
|
|
149
251
|
validate(library, nil)
|
150
252
|
jstr = fetch("https://api.cdnjs.com/libraries/#{library}", library)
|
151
253
|
jdata = JSON.parse(jstr)
|
254
|
+
_debug_print(jdata)
|
152
255
|
versions = jdata['assets'].collect {|d| d['version'] }\
|
153
256
|
.sort_by {|v| v.split(/[-.]/).map(&:to_i) }
|
154
257
|
return {
|
155
258
|
name: library,
|
156
259
|
desc: jdata['description'],
|
157
260
|
tags: (jdata['keywords'] || []).join(", "),
|
261
|
+
site: jdata['homepage'],
|
262
|
+
info: File.join(SITE_URL, "/libraries/#{library}"),
|
263
|
+
license: jdata['license'],
|
158
264
|
versions: versions.reverse(),
|
159
265
|
}
|
160
266
|
end
|
@@ -163,17 +269,21 @@ module CDNGet
|
|
163
269
|
validate(library, version)
|
164
270
|
jstr = fetch("https://api.cdnjs.com/libraries/#{library}", library)
|
165
271
|
jdata = JSON.parse(jstr)
|
272
|
+
_debug_print(jdata)
|
166
273
|
d = jdata['assets'].find {|d| d['version'] == version } or
|
167
274
|
raise CommandError.new("#{library}/#{version}: Library or version not found.")
|
168
275
|
baseurl = "https://cdnjs.cloudflare.com/ajax/libs/#{library}/#{version}/"
|
169
276
|
return {
|
170
277
|
name: library,
|
278
|
+
version: version,
|
171
279
|
desc: jdata['description'],
|
172
280
|
tags: (jdata['keywords'] || []).join(", "),
|
173
|
-
|
281
|
+
site: jdata['homepage'],
|
282
|
+
info: File.join(SITE_URL, "/libraries/#{library}/#{version}"),
|
174
283
|
urls: d['files'].collect {|s| baseurl + s },
|
175
284
|
files: d['files'],
|
176
285
|
baseurl: baseurl,
|
286
|
+
license: jdata['license'],
|
177
287
|
}
|
178
288
|
end
|
179
289
|
|
@@ -183,59 +293,200 @@ module CDNGet
|
|
183
293
|
class JSDelivr < Base
|
184
294
|
CODE = "jsdelivr"
|
185
295
|
SITE_URL = "https://www.jsdelivr.com/"
|
186
|
-
API_URL = "https://api.jsdelivr.com/v1/jsdelivr/libraries"
|
296
|
+
#API_URL = "https://api.jsdelivr.com/v1/jsdelivr/libraries"
|
297
|
+
API_URL = "https://data.jsdelivr.com/v1"
|
298
|
+
HEADERS = {
|
299
|
+
"x-algo""lia-app""lication-id"=>"OFCNC""OG2CU",
|
300
|
+
"x-algo""lia-api""-key"=>"f54e21fa3a2""a0160595bb05""8179bfb1e",
|
301
|
+
}
|
187
302
|
|
188
|
-
def list
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
303
|
+
def list()
|
304
|
+
return nil # nil means that this CDN can't list libraries without pattern
|
305
|
+
end
|
306
|
+
|
307
|
+
def search(pattern)
|
308
|
+
form_data = {
|
309
|
+
query: pattern,
|
310
|
+
page: "0",
|
311
|
+
hitsPerPage: "1000",
|
312
|
+
attributesToHighlight: '[]',
|
313
|
+
attributesToRetrieve: '["name","description","version"]'
|
314
|
+
}
|
315
|
+
payload = JSON.dump({"params"=>URI.encode_www_form(form_data)})
|
316
|
+
url = "https://ofcncog2cu-3.algolianet.com/1/indexes/npm-search/query"
|
317
|
+
uri = URI.parse(url)
|
318
|
+
json = HttpConnection.open(uri, HEADERS) {|http| http.post(uri, payload) }
|
319
|
+
jdata = JSON.load(json)
|
320
|
+
_debug_print(jdata)
|
321
|
+
return jdata["hits"].select {|d|
|
322
|
+
File.fnmatch(pattern, d["name"], File::FNM_CASEFOLD)
|
323
|
+
}.collect {|d|
|
324
|
+
{name: d["name"], desc: d["description"], version: d["version"]}
|
193
325
|
}
|
194
326
|
end
|
195
327
|
|
196
328
|
def find(library)
|
197
329
|
validate(library, nil)
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
330
|
+
url = "https://ofcncog2cu-dsn.algolia.net/1/indexes/npm-search/#{library}"
|
331
|
+
uri = URI.parse(url)
|
332
|
+
begin
|
333
|
+
json = HttpConnection.open(uri, HEADERS) {|http| http.get(uri) }
|
334
|
+
rescue HttpError
|
335
|
+
raise CommandError, "#{library}: Library not found."
|
336
|
+
end
|
337
|
+
dict1 = JSON.load(json)
|
338
|
+
_debug_print(dict1)
|
339
|
+
#
|
340
|
+
json = fetch("#{API_URL}/package/npm/#{library}")
|
341
|
+
dict2 = JSON.load(json)
|
342
|
+
_debug_print(dict2)
|
343
|
+
#
|
344
|
+
d = dict1
|
202
345
|
return {
|
203
|
-
name:
|
204
|
-
desc:
|
205
|
-
|
206
|
-
versions:
|
346
|
+
name: d['name'],
|
347
|
+
desc: d['description'],
|
348
|
+
#versions: d['versions'].collect {|k,v| k },
|
349
|
+
versions: dict2['versions'],
|
350
|
+
tags: (d['keywords'] || []).join(", "),
|
351
|
+
site: d['homepage'],
|
352
|
+
info: File.join(SITE_URL, "/package/npm/#{library}"),
|
353
|
+
license: d['license'],
|
207
354
|
}
|
208
355
|
end
|
209
356
|
|
210
357
|
def get(library, version)
|
211
358
|
validate(library, version)
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
359
|
+
url = File.join(API_URL, "/package/npm/#{library}@#{version}/flat")
|
360
|
+
begin
|
361
|
+
json = fetch(url, library)
|
362
|
+
rescue CommandError
|
363
|
+
raise CommandError.new("#{library}@#{version}: Library or version not found.")
|
364
|
+
end
|
365
|
+
jdata = JSON.load(json)
|
366
|
+
files = jdata["files"].collect {|d| d["name"] }
|
367
|
+
baseurl = "https://cdn.jsdelivr.net/npm/#{library}@#{version}"
|
368
|
+
_debug_print(jdata)
|
369
|
+
#
|
370
|
+
dict = find(library)
|
371
|
+
dict.delete(:versions)
|
372
|
+
dict.update({
|
373
|
+
version: version,
|
374
|
+
info: File.join(SITE_URL, "/package/npm/#{library}?version=#{version}"),
|
375
|
+
npmpkg: "https://registry.npmjs.org/#{library}/-/#{library}-#{version}.tgz",
|
376
|
+
urls: files.collect {|x| baseurl + x },
|
377
|
+
files: files,
|
378
|
+
baseurl: baseurl,
|
379
|
+
default: jdata["default"],
|
380
|
+
destdir: "#{library}@#{version}",
|
381
|
+
})
|
382
|
+
return dict
|
383
|
+
end
|
384
|
+
|
385
|
+
end
|
386
|
+
|
387
|
+
|
388
|
+
class Unpkg < Base
|
389
|
+
CODE = "unpkg"
|
390
|
+
SITE_URL = "https://unpkg.com/"
|
391
|
+
#API_URL = "https://www.npmjs.com"
|
392
|
+
API_URL = "https://api.npms.io/v2"
|
393
|
+
|
394
|
+
protected
|
395
|
+
|
396
|
+
def http_get(url)
|
397
|
+
return URI.__send__(:open, url, 'rb', {"x-spiferack"=>"1"}) {|f| f.read() }
|
398
|
+
end
|
399
|
+
|
400
|
+
public
|
401
|
+
|
402
|
+
def list()
|
403
|
+
return nil # nil means that this CDN can't list libraries without pattern
|
404
|
+
end
|
405
|
+
|
406
|
+
def search(pattern)
|
407
|
+
#json = fetch("#{API_URL}/search?q=#{pattern}")
|
408
|
+
json = fetch("#{API_URL}/search?q=#{pattern}&size=250")
|
409
|
+
jdata = JSON.load(json)
|
410
|
+
_debug_print(jdata)
|
411
|
+
#arr = jdata["objects"] # www.npmjs.com
|
412
|
+
arr = jdata["results"] # api.npms.io
|
413
|
+
return arr.select {|dict|
|
414
|
+
File.fnmatch(pattern, dict["package"]["name"], File::FNM_CASEFOLD)
|
415
|
+
}.collect {|dict|
|
416
|
+
d = dict["package"]
|
417
|
+
{name: d["name"], desc: d["description"], version: d["version"]}
|
418
|
+
}
|
419
|
+
end
|
420
|
+
|
421
|
+
def find(library)
|
422
|
+
validate(library, nil)
|
423
|
+
json = fetch("#{API_URL}/package/#{library}", library)
|
424
|
+
jdata = JSON.load(json)
|
425
|
+
_debug_print(jdata)
|
426
|
+
dict = jdata["collected"]["metadata"]
|
427
|
+
versions = [dict["version"]]
|
428
|
+
#
|
429
|
+
url = File.join(SITE_URL, "/browse/#{library}/")
|
430
|
+
html = fetch(url, library)
|
431
|
+
_debug_print(html)
|
432
|
+
if html =~ /<script>window.__DATA__\s*=\s*(.*?)<\/script>/m
|
433
|
+
jdata2 = JSON.load($1)
|
434
|
+
versions = jdata2["availableVersions"].reverse()
|
435
|
+
end
|
436
|
+
#
|
219
437
|
return {
|
438
|
+
name: dict["name"],
|
439
|
+
desc: dict["description"],
|
440
|
+
tags: (dict["keywords"] || []).join(", "),
|
441
|
+
site: dict["links"] ? dict["links"]["homepage"] : dict["links"]["npm"],
|
442
|
+
info: File.join(SITE_URL, "/browse/#{library}/"),
|
443
|
+
versions: versions,
|
444
|
+
license: dict["license"],
|
445
|
+
}
|
446
|
+
end
|
447
|
+
|
448
|
+
def get(library, version)
|
449
|
+
validate(library, version)
|
450
|
+
dict = find(library)
|
451
|
+
dict.delete(:versions)
|
452
|
+
#
|
453
|
+
url = "https://data.jsdelivr.com/v1/package/npm/#{library}@#{version}/flat"
|
454
|
+
begin
|
455
|
+
json = fetch(url, library)
|
456
|
+
rescue CommandError
|
457
|
+
raise CommandError.new("#{library}@#{version}: Library or version not found.")
|
458
|
+
end
|
459
|
+
jdata = JSON.load(json)
|
460
|
+
files = jdata["files"].collect {|d| d["name"] }
|
461
|
+
baseurl = File.join(SITE_URL, "/#{library}@#{version}")
|
462
|
+
_debug_print(jdata)
|
463
|
+
#
|
464
|
+
dict.update({
|
220
465
|
name: library,
|
221
466
|
version: version,
|
222
|
-
|
467
|
+
info: File.join(SITE_URL, "/browse/#{library}@#{version}/"),
|
468
|
+
npmpkg: "https://registry.npmjs.org/#{library}/-/#{library}-#{version}.tgz",
|
469
|
+
urls: files.collect {|x| baseurl+x },
|
223
470
|
files: files,
|
224
471
|
baseurl: baseurl,
|
225
|
-
|
472
|
+
default: jdata["default"],
|
473
|
+
destdir: "#{library}@#{version}",
|
474
|
+
})
|
475
|
+
return dict
|
226
476
|
end
|
227
477
|
|
228
478
|
end
|
229
479
|
|
230
480
|
|
231
|
-
class GoogleCDN < Base
|
481
|
+
class GoogleCDN < Base
|
232
482
|
CODE = "google"
|
233
483
|
SITE_URL = 'https://developers.google.com/speed/libraries/'
|
234
484
|
|
235
|
-
def list
|
236
|
-
libs = []
|
485
|
+
def list()
|
237
486
|
html = fetch("https://developers.google.com/speed/libraries/")
|
487
|
+
_debug_print(html)
|
238
488
|
rexp = %r`"https://ajax\.googleapis\.com/ajax/libs/([^/]+)/([^/]+)/([^"]+)"`
|
489
|
+
libs = []
|
239
490
|
html.scan(rexp) do |lib, ver, file|
|
240
491
|
libs << {name: lib, desc: "latest version: #{ver}" }
|
241
492
|
end
|
@@ -245,6 +496,7 @@ module CDNGet
|
|
245
496
|
def find(library)
|
246
497
|
validate(library, nil)
|
247
498
|
html = fetch("https://developers.google.com/speed/libraries/")
|
499
|
+
_debug_print(html)
|
248
500
|
rexp = %r`"https://ajax\.googleapis\.com/ajax/libs/#{library}/`
|
249
501
|
site_url = nil
|
250
502
|
versions = []
|
@@ -255,9 +507,7 @@ module CDNGet
|
|
255
507
|
found = true
|
256
508
|
if text =~ /<dt>.*?snippet:<\/dt>\s*<dd>(.*?)<\/dd>/m
|
257
509
|
s = $1
|
258
|
-
s.scan(/\b(?:src|href)="([^"]*?)"/)
|
259
|
-
urls << href
|
260
|
-
end
|
510
|
+
s.scan(/\b(?:src|href)="([^"]*?)"/) {|href,| urls << href }
|
261
511
|
end
|
262
512
|
if text =~ /<dt>site:<\/dt>\s*<dd>(.*?)<\/dd>/m
|
263
513
|
s = $1
|
@@ -278,6 +528,7 @@ module CDNGet
|
|
278
528
|
return {
|
279
529
|
name: library,
|
280
530
|
site: site_url,
|
531
|
+
info: "#{SITE_URL}\##{library}",
|
281
532
|
urls: urls,
|
282
533
|
versions: versions,
|
283
534
|
}
|
@@ -298,6 +549,7 @@ module CDNGet
|
|
298
549
|
return {
|
299
550
|
name: d[:name],
|
300
551
|
site: d[:site],
|
552
|
+
info: "#{SITE_URL}\##{library}",
|
301
553
|
urls: urls,
|
302
554
|
files: files,
|
303
555
|
baseurl: baseurl,
|
@@ -330,24 +582,28 @@ module CDNGet
|
|
330
582
|
@script = script || File.basename($0)
|
331
583
|
end
|
332
584
|
|
333
|
-
def help_message
|
585
|
+
def help_message()
|
334
586
|
script = @script
|
335
587
|
return <<END
|
336
|
-
#{script} -- download files from public CDN
|
588
|
+
#{script} -- download files from public CDN (cdnjs/jsdelivr/unpkg/google)
|
337
589
|
|
338
|
-
Usage: #{script} [options] [CDN
|
590
|
+
Usage: #{script} [<options>] [<CDN> [<library> [<version> [<directory>]]]]
|
339
591
|
|
340
592
|
Options:
|
341
593
|
-h, --help : help
|
342
594
|
-v, --version : version
|
343
595
|
-q, --quiet : minimal output
|
596
|
+
--debug : (debug mode)
|
344
597
|
|
345
598
|
Example:
|
346
|
-
$ #{script} # list public CDN
|
599
|
+
$ #{script} # list public CDN names
|
347
600
|
$ #{script} [-q] cdnjs # list libraries
|
601
|
+
$ #{script} [-q] cdnjs 'jquery*' # search libraries
|
348
602
|
$ #{script} [-q] cdnjs jquery # list versions
|
349
|
-
$ #{script} [-q] cdnjs jquery
|
350
|
-
$ #{script} [-q] cdnjs jquery
|
603
|
+
$ #{script} [-q] cdnjs jquery latest # show latest version
|
604
|
+
$ #{script} [-q] cdnjs jquery 3.6.0 # list files
|
605
|
+
$ #{script} [-q] cdnjs jquery 3.6.0 /tmp # download files into directory
|
606
|
+
|
351
607
|
END
|
352
608
|
end
|
353
609
|
|
@@ -362,10 +618,11 @@ END
|
|
362
618
|
end
|
363
619
|
|
364
620
|
def run(*args)
|
365
|
-
cmdopts = parse_cmdopts(args, "hvq", ["help", "version", "quiet"])
|
621
|
+
cmdopts = parse_cmdopts(args, "hvq", ["help", "version", "quiet", "debug"])
|
366
622
|
return help_message() if cmdopts['h'] || cmdopts['help']
|
367
623
|
return RELEASE + "\n" if cmdopts['v'] || cmdopts['version']
|
368
624
|
@quiet = cmdopts['quiet'] || cmdopts['q']
|
625
|
+
@debug_mode = cmdopts['debug']
|
369
626
|
#
|
370
627
|
validate(args[1], args[2])
|
371
628
|
#
|
@@ -377,11 +634,9 @@ END
|
|
377
634
|
return do_list_libraries(cdn_code)
|
378
635
|
when 2
|
379
636
|
cdn_code, library = args
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
return do_find_library(cdn_code, library)
|
384
|
-
end
|
637
|
+
return library.include?('*') \
|
638
|
+
? do_search_libraries(cdn_code, library) \
|
639
|
+
: do_find_library(cdn_code, library)
|
385
640
|
when 3
|
386
641
|
cdn_code, library, version = args
|
387
642
|
return do_get_library(cdn_code, library, version)
|
@@ -435,35 +690,29 @@ END
|
|
435
690
|
def find_cdn(cdn_code)
|
436
691
|
klass = CLASSES.find {|c| c::CODE == cdn_code } or
|
437
692
|
raise CommandError.new("#{cdn_code}: no such CDN.")
|
438
|
-
return klass.new
|
693
|
+
return klass.new(debug_mode: @debug_mode)
|
439
694
|
end
|
440
695
|
|
441
696
|
def render_list(list)
|
442
|
-
if @quiet
|
443
|
-
|
444
|
-
else
|
445
|
-
return list.collect {|d| "%-20s # %s\n" % [d[:name], d[:desc]] }.join()
|
446
|
-
end
|
697
|
+
return list.collect {|d| "#{d[:name]}\n" }.join() if @quiet
|
698
|
+
return list.collect {|d| "%-20s # %s\n" % [d[:name], d[:desc]] }.join()
|
447
699
|
end
|
448
700
|
|
449
|
-
def do_list_cdns
|
450
|
-
if @quiet
|
451
|
-
|
452
|
-
else
|
453
|
-
return CLASSES.map {|c| "%-10s # %s\n" % [c::CODE, c::SITE_URL] }.join()
|
454
|
-
end
|
701
|
+
def do_list_cdns()
|
702
|
+
return CLASSES.map {|c| "#{c::CODE}\n" }.join() if @quiet
|
703
|
+
return CLASSES.map {|c| "%-10s # %s\n" % [c::CODE, c::SITE_URL] }.join()
|
455
704
|
end
|
456
705
|
|
457
706
|
def do_list_libraries(cdn_code)
|
458
707
|
cdn = find_cdn(cdn_code)
|
459
|
-
|
708
|
+
list = cdn.list() or
|
709
|
+
raise CommandError.new("#{cdn_code}: cannot list libraries; please specify pattern such as 'jquery*'.")
|
710
|
+
return render_list(list)
|
460
711
|
end
|
461
712
|
|
462
713
|
def do_search_libraries(cdn_code, pattern)
|
463
714
|
cdn = find_cdn(cdn_code)
|
464
|
-
|
465
|
-
rexp = Regexp.compile("\\A#{rexp_str}\\z", Regexp::IGNORECASE)
|
466
|
-
return render_list(cdn.list.select {|a| a[:name] =~ rexp })
|
715
|
+
return render_list(cdn.search(pattern))
|
467
716
|
end
|
468
717
|
|
469
718
|
def do_find_library(cdn_code, library)
|
@@ -475,10 +724,12 @@ END
|
|
475
724
|
s << "#{ver}\n"
|
476
725
|
end if d[:versions]
|
477
726
|
else
|
478
|
-
s << "name:
|
479
|
-
s << "desc:
|
480
|
-
s << "tags:
|
481
|
-
s << "site:
|
727
|
+
s << "name: #{d[:name]}\n"
|
728
|
+
s << "desc: #{d[:desc]}\n" if d[:desc]
|
729
|
+
s << "tags: #{d[:tags]}\n" if d[:tags]
|
730
|
+
s << "site: #{d[:site]}\n" if d[:site]
|
731
|
+
s << "info: #{d[:info]}\n" if d[:info]
|
732
|
+
s << "license: #{d[:license]}\n" if d[:license]
|
482
733
|
s << "snippet: |\n" << d[:snippet].gsub(/^/, ' ') if d[:snippet]
|
483
734
|
s << "versions:\n"
|
484
735
|
d[:versions].each do |ver|
|
@@ -490,6 +741,7 @@ END
|
|
490
741
|
|
491
742
|
def do_get_library(cdn_code, library, version)
|
492
743
|
cdn = find_cdn(cdn_code)
|
744
|
+
version = _latest_version(cdn, library) if version == 'latest'
|
493
745
|
d = cdn.get(library, version)
|
494
746
|
s = ""
|
495
747
|
if @quiet
|
@@ -499,9 +751,13 @@ END
|
|
499
751
|
else
|
500
752
|
s << "name: #{d[:name]}\n"
|
501
753
|
s << "version: #{d[:version]}\n"
|
502
|
-
s << "desc: #{d[:desc]}\n"
|
503
|
-
s << "tags: #{d[:tags]}\n"
|
504
|
-
s << "site: #{d[:site]}\n"
|
754
|
+
s << "desc: #{d[:desc]}\n" if d[:desc]
|
755
|
+
s << "tags: #{d[:tags]}\n" if d[:tags]
|
756
|
+
s << "site: #{d[:site]}\n" if d[:site]
|
757
|
+
s << "info: #{d[:info]}\n" if d[:info]
|
758
|
+
s << "npmpkg: #{d[:npmpkg]}\n" if d[:npmpkg]
|
759
|
+
s << "default: #{d[:default]}\n" if d[:default]
|
760
|
+
s << "license: #{d[:license]}\n" if d[:license]
|
505
761
|
s << "snippet: |\n" << d[:snippet].gsub(/^/, ' ') if d[:snippet]
|
506
762
|
s << "urls:\n" if d[:urls]
|
507
763
|
d[:urls].each do |url|
|
@@ -513,10 +769,18 @@ END
|
|
513
769
|
|
514
770
|
def do_download_library(cdn_code, library, version, basedir)
|
515
771
|
cdn = find_cdn(cdn_code)
|
772
|
+
version = _latest_version(cdn, library) if version == 'latest'
|
516
773
|
cdn.download(library, version, basedir, quiet: @quiet)
|
517
774
|
return nil
|
518
775
|
end
|
519
776
|
|
777
|
+
private
|
778
|
+
|
779
|
+
def _latest_version(cdn, library)
|
780
|
+
d = cdn.find(library)
|
781
|
+
return d[:versions].first
|
782
|
+
end
|
783
|
+
|
520
784
|
end
|
521
785
|
|
522
786
|
|