cdnget 0.1.0 → 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 +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
|
|