cdnget 0.1.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGES.md +42 -0
- data/README.md +12 -4
- data/Rakefile +3 -3
- data/bin/cdnget +382 -120
- data/cdnget.gemspec +6 -6
- data/lib/cdnget.rb +382 -120
- data/test/cdnget_test.rb +667 -101
- metadata +9 -8
data/bin/cdnget
CHANGED
@@ -2,29 +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'
|
22
|
+
require 'uri'
|
23
|
+
require 'net/http'
|
24
|
+
require 'openssl'
|
20
25
|
require 'json'
|
21
26
|
require 'fileutils'
|
27
|
+
require 'pp'
|
22
28
|
|
23
29
|
|
24
30
|
module CDNGet
|
25
31
|
|
26
32
|
|
27
|
-
RELEASE = '$Release:
|
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
|
+
|
28
96
|
|
29
97
|
CLASSES = []
|
30
98
|
|
@@ -35,10 +103,22 @@ module CDNGet
|
|
35
103
|
CLASSES << klass
|
36
104
|
end
|
37
105
|
|
106
|
+
def initialize(debug_mode: false)
|
107
|
+
@debug_mode = debug_mode
|
108
|
+
end
|
109
|
+
attr_reader :debug_mode
|
110
|
+
|
38
111
|
def list()
|
39
112
|
raise NotImplementedError.new("#{self.class.name}#list(): not implemented yet.")
|
40
113
|
end
|
41
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
|
+
|
42
122
|
def find(library)
|
43
123
|
raise NotImplementedError.new("#{self.class.name}#find(): not implemented yet.")
|
44
124
|
end
|
@@ -53,14 +133,28 @@ module CDNGet
|
|
53
133
|
raise CommandError.new("#{basedir}: not exist.")
|
54
134
|
File.directory?(basedir) or
|
55
135
|
raise CommandError.new("#{basedir}: not a directory.")
|
56
|
-
target_dir = File.join(basedir, library, version)
|
57
136
|
d = get(library, version)
|
137
|
+
target_dir = d[:destdir] ? File.join(basedir, d[:destdir]) \
|
138
|
+
: File.join(basedir, library, version)
|
139
|
+
http = nil
|
58
140
|
d[:files].each do |file|
|
59
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
|
60
152
|
dirpath = File.dirname(filepath)
|
61
153
|
print "#{filepath} ..." unless quiet
|
62
154
|
url = File.join(d[:baseurl], file) # not use URI.join!
|
63
|
-
|
155
|
+
uri = URI.parse(url)
|
156
|
+
http ||= HttpConnection.new(uri)
|
157
|
+
content = http.get(uri)
|
64
158
|
content = content.force_encoding('ascii-8bit')
|
65
159
|
print " Done (#{format_integer(content.bytesize)} byte)" unless quiet
|
66
160
|
FileUtils.mkdir_p(dirpath) unless File.exist?(dirpath)
|
@@ -72,17 +166,32 @@ module CDNGet
|
|
72
166
|
end
|
73
167
|
puts() unless quiet
|
74
168
|
end
|
169
|
+
http.close() if http
|
75
170
|
nil
|
76
171
|
end
|
77
172
|
|
78
173
|
protected
|
79
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
|
+
|
80
182
|
def fetch(url, library=nil)
|
81
183
|
begin
|
82
|
-
html =
|
184
|
+
html = http_get(url)
|
83
185
|
return html
|
84
|
-
rescue OpenURI::HTTPError =>
|
85
|
-
|
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
|
86
195
|
end
|
87
196
|
end
|
88
197
|
|
@@ -101,82 +210,80 @@ module CDNGet
|
|
101
210
|
return value.to_s.reverse.scan(/..?.?/).collect {|s| s.reverse }.reverse.join(',')
|
102
211
|
end
|
103
212
|
|
104
|
-
|
105
|
-
|
213
|
+
def _debug_print(x)
|
214
|
+
if @debug_mode
|
215
|
+
$stderr.puts "\e[0;35m*** #{PP.pp(x,'')}\e[0m"
|
216
|
+
end
|
217
|
+
end
|
106
218
|
|
107
|
-
class HttpError < StandardError
|
108
219
|
end
|
109
220
|
|
110
221
|
|
111
|
-
class CDNJS < Base
|
222
|
+
class CDNJS < Base
|
112
223
|
CODE = "cdnjs"
|
113
224
|
SITE_URL = 'https://cdnjs.com/'
|
114
225
|
|
115
226
|
def fetch(url, library=nil)
|
116
|
-
|
117
|
-
|
118
|
-
if library
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
227
|
+
json_str = super
|
228
|
+
if json_str == "{}" && library
|
229
|
+
if library.end_with?('js')
|
230
|
+
maybe = library.end_with?('.js') \
|
231
|
+
? library.sub('.js', 'js') \
|
232
|
+
: library.sub(/js$/, '.js')
|
233
|
+
raise CommandError.new("#{library}: Library not found (maybe '#{maybe}'?).")
|
234
|
+
else
|
235
|
+
raise CommandError.new("#{library}: Library not found.")
|
123
236
|
end
|
124
|
-
return html
|
125
|
-
rescue OpenURI::HTTPError => ex
|
126
|
-
raise HttpError.new("GET #{url} : #{ex.message}")
|
127
237
|
end
|
238
|
+
return json_str
|
128
239
|
end
|
129
240
|
protected :fetch
|
130
241
|
|
131
|
-
def list
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
name = $1 if str =~ /href="\/libraries\/([-.\w]+)">\s*\1\s*<\/a>/
|
137
|
-
desc = $1.strip if str =~ /<div style="display: none;">(.*?)<\/div>/m
|
138
|
-
libs << {name: name, desc: desc} if name
|
139
|
-
end
|
242
|
+
def list()
|
243
|
+
jstr = fetch("https://api.cdnjs.com/libraries?fields=name,description")
|
244
|
+
jdata = JSON.parse(jstr)
|
245
|
+
_debug_print(jdata)
|
246
|
+
libs = jdata['results'].collect {|d| {name: d['name'], desc: d['description']} }
|
140
247
|
return libs.sort_by {|d| d[:name] }.uniq
|
141
248
|
end
|
142
249
|
|
143
250
|
def find(library)
|
144
251
|
validate(library, nil)
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
desc = tags = nil
|
151
|
-
if html =~ /<p>(.*?)<\/p>\s*<em>(.*?)<\/em>/
|
152
|
-
desc = $1
|
153
|
-
tags = $2
|
154
|
-
end
|
252
|
+
jstr = fetch("https://api.cdnjs.com/libraries/#{library}", library)
|
253
|
+
jdata = JSON.parse(jstr)
|
254
|
+
_debug_print(jdata)
|
255
|
+
versions = jdata['assets'].collect {|d| d['version'] }\
|
256
|
+
.sort_by {|v| v.split(/[-.]/).map(&:to_i) }
|
155
257
|
return {
|
156
258
|
name: library,
|
157
|
-
desc:
|
158
|
-
tags:
|
159
|
-
|
259
|
+
desc: jdata['description'],
|
260
|
+
tags: (jdata['keywords'] || []).join(", "),
|
261
|
+
site: jdata['homepage'],
|
262
|
+
info: File.join(SITE_URL, "/libraries/#{library}"),
|
263
|
+
license: jdata['license'],
|
264
|
+
versions: versions.reverse(),
|
160
265
|
}
|
161
266
|
end
|
162
267
|
|
163
268
|
def get(library, version)
|
164
269
|
validate(library, version)
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
urls << url .gsub(///, '/')
|
172
|
-
files << file.gsub(///, '/')
|
173
|
-
end
|
270
|
+
jstr = fetch("https://api.cdnjs.com/libraries/#{library}", library)
|
271
|
+
jdata = JSON.parse(jstr)
|
272
|
+
_debug_print(jdata)
|
273
|
+
d = jdata['assets'].find {|d| d['version'] == version } or
|
274
|
+
raise CommandError.new("#{library}/#{version}: Library or version not found.")
|
275
|
+
baseurl = "https://cdnjs.cloudflare.com/ajax/libs/#{library}/#{version}/"
|
174
276
|
return {
|
175
277
|
name: library,
|
176
278
|
version: version,
|
177
|
-
|
178
|
-
|
279
|
+
desc: jdata['description'],
|
280
|
+
tags: (jdata['keywords'] || []).join(", "),
|
281
|
+
site: jdata['homepage'],
|
282
|
+
info: File.join(SITE_URL, "/libraries/#{library}/#{version}"),
|
283
|
+
urls: d['files'].collect {|s| baseurl + s },
|
284
|
+
files: d['files'],
|
179
285
|
baseurl: baseurl,
|
286
|
+
license: jdata['license'],
|
180
287
|
}
|
181
288
|
end
|
182
289
|
|
@@ -186,59 +293,200 @@ module CDNGet
|
|
186
293
|
class JSDelivr < Base
|
187
294
|
CODE = "jsdelivr"
|
188
295
|
SITE_URL = "https://www.jsdelivr.com/"
|
189
|
-
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
|
+
}
|
190
302
|
|
191
|
-
def list
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
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"]}
|
196
325
|
}
|
197
326
|
end
|
198
327
|
|
199
328
|
def find(library)
|
200
329
|
validate(library, nil)
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
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
|
205
345
|
return {
|
206
|
-
name:
|
207
|
-
desc:
|
208
|
-
|
209
|
-
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'],
|
210
354
|
}
|
211
355
|
end
|
212
356
|
|
213
357
|
def get(library, version)
|
214
358
|
validate(library, version)
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
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
|
+
#
|
222
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({
|
223
465
|
name: library,
|
224
466
|
version: version,
|
225
|
-
|
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 },
|
226
470
|
files: files,
|
227
471
|
baseurl: baseurl,
|
228
|
-
|
472
|
+
default: jdata["default"],
|
473
|
+
destdir: "#{library}@#{version}",
|
474
|
+
})
|
475
|
+
return dict
|
229
476
|
end
|
230
477
|
|
231
478
|
end
|
232
479
|
|
233
480
|
|
234
|
-
class GoogleCDN < Base
|
481
|
+
class GoogleCDN < Base
|
235
482
|
CODE = "google"
|
236
483
|
SITE_URL = 'https://developers.google.com/speed/libraries/'
|
237
484
|
|
238
|
-
def list
|
239
|
-
libs = []
|
485
|
+
def list()
|
240
486
|
html = fetch("https://developers.google.com/speed/libraries/")
|
487
|
+
_debug_print(html)
|
241
488
|
rexp = %r`"https://ajax\.googleapis\.com/ajax/libs/([^/]+)/([^/]+)/([^"]+)"`
|
489
|
+
libs = []
|
242
490
|
html.scan(rexp) do |lib, ver, file|
|
243
491
|
libs << {name: lib, desc: "latest version: #{ver}" }
|
244
492
|
end
|
@@ -248,6 +496,7 @@ module CDNGet
|
|
248
496
|
def find(library)
|
249
497
|
validate(library, nil)
|
250
498
|
html = fetch("https://developers.google.com/speed/libraries/")
|
499
|
+
_debug_print(html)
|
251
500
|
rexp = %r`"https://ajax\.googleapis\.com/ajax/libs/#{library}/`
|
252
501
|
site_url = nil
|
253
502
|
versions = []
|
@@ -258,9 +507,7 @@ module CDNGet
|
|
258
507
|
found = true
|
259
508
|
if text =~ /<dt>.*?snippet:<\/dt>\s*<dd>(.*?)<\/dd>/m
|
260
509
|
s = $1
|
261
|
-
s.scan(/\b(?:src|href)="([^"]*?)"/)
|
262
|
-
urls << href
|
263
|
-
end
|
510
|
+
s.scan(/\b(?:src|href)="([^"]*?)"/) {|href,| urls << href }
|
264
511
|
end
|
265
512
|
if text =~ /<dt>site:<\/dt>\s*<dd>(.*?)<\/dd>/m
|
266
513
|
s = $1
|
@@ -281,6 +528,7 @@ module CDNGet
|
|
281
528
|
return {
|
282
529
|
name: library,
|
283
530
|
site: site_url,
|
531
|
+
info: "#{SITE_URL}\##{library}",
|
284
532
|
urls: urls,
|
285
533
|
versions: versions,
|
286
534
|
}
|
@@ -301,6 +549,7 @@ module CDNGet
|
|
301
549
|
return {
|
302
550
|
name: d[:name],
|
303
551
|
site: d[:site],
|
552
|
+
info: "#{SITE_URL}\##{library}",
|
304
553
|
urls: urls,
|
305
554
|
files: files,
|
306
555
|
baseurl: baseurl,
|
@@ -333,24 +582,28 @@ module CDNGet
|
|
333
582
|
@script = script || File.basename($0)
|
334
583
|
end
|
335
584
|
|
336
|
-
def help_message
|
585
|
+
def help_message()
|
337
586
|
script = @script
|
338
587
|
return <<END
|
339
|
-
#{script} -- download files from public CDN
|
588
|
+
#{script} -- download files from public CDN (cdnjs/jsdelivr/unpkg/google)
|
340
589
|
|
341
|
-
Usage: #{script} [options] [CDN
|
590
|
+
Usage: #{script} [<options>] [<CDN> [<library> [<version> [<directory>]]]]
|
342
591
|
|
343
592
|
Options:
|
344
593
|
-h, --help : help
|
345
594
|
-v, --version : version
|
346
595
|
-q, --quiet : minimal output
|
596
|
+
--debug : (debug mode)
|
347
597
|
|
348
598
|
Example:
|
349
|
-
$ #{script} # list public CDN
|
599
|
+
$ #{script} # list public CDN names
|
350
600
|
$ #{script} [-q] cdnjs # list libraries
|
601
|
+
$ #{script} [-q] cdnjs 'jquery*' # search libraries
|
351
602
|
$ #{script} [-q] cdnjs jquery # list versions
|
352
|
-
$ #{script} [-q] cdnjs jquery
|
353
|
-
$ #{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
|
+
|
354
607
|
END
|
355
608
|
end
|
356
609
|
|
@@ -365,10 +618,11 @@ END
|
|
365
618
|
end
|
366
619
|
|
367
620
|
def run(*args)
|
368
|
-
cmdopts = parse_cmdopts(args, "hvq", ["help", "version", "quiet"])
|
621
|
+
cmdopts = parse_cmdopts(args, "hvq", ["help", "version", "quiet", "debug"])
|
369
622
|
return help_message() if cmdopts['h'] || cmdopts['help']
|
370
623
|
return RELEASE + "\n" if cmdopts['v'] || cmdopts['version']
|
371
624
|
@quiet = cmdopts['quiet'] || cmdopts['q']
|
625
|
+
@debug_mode = cmdopts['debug']
|
372
626
|
#
|
373
627
|
validate(args[1], args[2])
|
374
628
|
#
|
@@ -380,11 +634,9 @@ END
|
|
380
634
|
return do_list_libraries(cdn_code)
|
381
635
|
when 2
|
382
636
|
cdn_code, library = args
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
return do_find_library(cdn_code, library)
|
387
|
-
end
|
637
|
+
return library.include?('*') \
|
638
|
+
? do_search_libraries(cdn_code, library) \
|
639
|
+
: do_find_library(cdn_code, library)
|
388
640
|
when 3
|
389
641
|
cdn_code, library, version = args
|
390
642
|
return do_get_library(cdn_code, library, version)
|
@@ -438,35 +690,29 @@ END
|
|
438
690
|
def find_cdn(cdn_code)
|
439
691
|
klass = CLASSES.find {|c| c::CODE == cdn_code } or
|
440
692
|
raise CommandError.new("#{cdn_code}: no such CDN.")
|
441
|
-
return klass.new
|
693
|
+
return klass.new(debug_mode: @debug_mode)
|
442
694
|
end
|
443
695
|
|
444
696
|
def render_list(list)
|
445
|
-
if @quiet
|
446
|
-
|
447
|
-
else
|
448
|
-
return list.collect {|d| "%-20s # %s\n" % [d[:name], d[:desc]] }.join()
|
449
|
-
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()
|
450
699
|
end
|
451
700
|
|
452
|
-
def do_list_cdns
|
453
|
-
if @quiet
|
454
|
-
|
455
|
-
else
|
456
|
-
return CLASSES.map {|c| "%-10s # %s\n" % [c::CODE, c::SITE_URL] }.join()
|
457
|
-
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()
|
458
704
|
end
|
459
705
|
|
460
706
|
def do_list_libraries(cdn_code)
|
461
707
|
cdn = find_cdn(cdn_code)
|
462
|
-
|
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)
|
463
711
|
end
|
464
712
|
|
465
713
|
def do_search_libraries(cdn_code, pattern)
|
466
714
|
cdn = find_cdn(cdn_code)
|
467
|
-
|
468
|
-
rexp = Regexp.compile("\\A#{rexp_str}\\z", Regexp::IGNORECASE)
|
469
|
-
return render_list(cdn.list.select {|a| a[:name] =~ rexp })
|
715
|
+
return render_list(cdn.search(pattern))
|
470
716
|
end
|
471
717
|
|
472
718
|
def do_find_library(cdn_code, library)
|
@@ -478,10 +724,12 @@ END
|
|
478
724
|
s << "#{ver}\n"
|
479
725
|
end if d[:versions]
|
480
726
|
else
|
481
|
-
s << "name:
|
482
|
-
s << "desc:
|
483
|
-
s << "tags:
|
484
|
-
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]
|
485
733
|
s << "snippet: |\n" << d[:snippet].gsub(/^/, ' ') if d[:snippet]
|
486
734
|
s << "versions:\n"
|
487
735
|
d[:versions].each do |ver|
|
@@ -493,6 +741,7 @@ END
|
|
493
741
|
|
494
742
|
def do_get_library(cdn_code, library, version)
|
495
743
|
cdn = find_cdn(cdn_code)
|
744
|
+
version = _latest_version(cdn, library) if version == 'latest'
|
496
745
|
d = cdn.get(library, version)
|
497
746
|
s = ""
|
498
747
|
if @quiet
|
@@ -502,8 +751,13 @@ END
|
|
502
751
|
else
|
503
752
|
s << "name: #{d[:name]}\n"
|
504
753
|
s << "version: #{d[:version]}\n"
|
505
|
-
s << "
|
506
|
-
s << "
|
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]
|
507
761
|
s << "snippet: |\n" << d[:snippet].gsub(/^/, ' ') if d[:snippet]
|
508
762
|
s << "urls:\n" if d[:urls]
|
509
763
|
d[:urls].each do |url|
|
@@ -515,10 +769,18 @@ END
|
|
515
769
|
|
516
770
|
def do_download_library(cdn_code, library, version, basedir)
|
517
771
|
cdn = find_cdn(cdn_code)
|
772
|
+
version = _latest_version(cdn, library) if version == 'latest'
|
518
773
|
cdn.download(library, version, basedir, quiet: @quiet)
|
519
774
|
return nil
|
520
775
|
end
|
521
776
|
|
777
|
+
private
|
778
|
+
|
779
|
+
def _latest_version(cdn, library)
|
780
|
+
d = cdn.find(library)
|
781
|
+
return d[:versions].first
|
782
|
+
end
|
783
|
+
|
522
784
|
end
|
523
785
|
|
524
786
|
|