cdnget 0.3.1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|