cdnget 0.2.0 → 1.0.1
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 +34 -0
- data/README.md +6 -4
- data/Rakefile +1 -1
- data/bin/cdnget +390 -120
- data/cdnget.gemspec +5 -5
- data/lib/cdnget.rb +390 -120
- data/test/cdnget_test.rb +665 -113
- metadata +8 -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: 0.
|
33
|
+
RELEASE = '$Release: 1.0.1 $'.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,36 @@ 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
|
140
|
+
skipfile = d[:skipfile] # ex: /\.DS_Store\z/
|
58
141
|
d[:files].each do |file|
|
59
142
|
filepath = File.join(target_dir, file)
|
143
|
+
#
|
144
|
+
if skipfile && file =~ skipfile
|
145
|
+
puts "#{filepath} ... Skipped" # for example, skip '.DS_Store' files
|
146
|
+
next
|
147
|
+
end
|
148
|
+
#
|
149
|
+
if filepath.end_with?('/')
|
150
|
+
if File.exist?(filepath)
|
151
|
+
puts "#{filepath} ... Done (Already exists)" unless quiet
|
152
|
+
else
|
153
|
+
print "#{filepath} ..." unless quiet
|
154
|
+
FileUtils.mkdir_p(filepath)
|
155
|
+
puts " Done (Created)" unless quiet
|
156
|
+
end
|
157
|
+
next
|
158
|
+
end
|
159
|
+
#
|
60
160
|
dirpath = File.dirname(filepath)
|
61
161
|
print "#{filepath} ..." unless quiet
|
62
162
|
url = File.join(d[:baseurl], file) # not use URI.join!
|
63
|
-
|
163
|
+
uri = URI.parse(url)
|
164
|
+
http ||= HttpConnection.new(uri)
|
165
|
+
content = http.get(uri)
|
64
166
|
content = content.force_encoding('ascii-8bit')
|
65
167
|
print " Done (#{format_integer(content.bytesize)} byte)" unless quiet
|
66
168
|
FileUtils.mkdir_p(dirpath) unless File.exist?(dirpath)
|
@@ -72,17 +174,32 @@ module CDNGet
|
|
72
174
|
end
|
73
175
|
puts() unless quiet
|
74
176
|
end
|
177
|
+
http.close() if http
|
75
178
|
nil
|
76
179
|
end
|
77
180
|
|
78
181
|
protected
|
79
182
|
|
183
|
+
def http_get(url)
|
184
|
+
## * `open()` on Ruby 3.X can't open http url
|
185
|
+
## * `URI.open()` on Ruby <= 2.4 raises NoMethodError (private method `open' called)
|
186
|
+
## * `URI.__send__(:open)` is a hack to work on both Ruby 2.X and 3.X
|
187
|
+
return URI.__send__(:open, url, 'rb') {|f| f.read() }
|
188
|
+
end
|
189
|
+
|
80
190
|
def fetch(url, library=nil)
|
81
191
|
begin
|
82
|
-
html =
|
192
|
+
html = http_get(url)
|
83
193
|
return html
|
84
|
-
rescue OpenURI::HTTPError =>
|
85
|
-
|
194
|
+
rescue OpenURI::HTTPError => exc
|
195
|
+
if ! (exc.message == "404 Not Found" && library)
|
196
|
+
raise CommandError.new("GET #{url} : #{exc.message}")
|
197
|
+
elsif ! library.end_with?('js')
|
198
|
+
raise CommandError.new("#{library}: Library not found.")
|
199
|
+
else
|
200
|
+
maybe = library.end_with?('.js') ? library.sub('.js', 'js') : library.sub(/js$/, '.js')
|
201
|
+
raise CommandError.new("#{library}: Library not found (maybe '#{maybe}'?).")
|
202
|
+
end
|
86
203
|
end
|
87
204
|
end
|
88
205
|
|
@@ -101,83 +218,80 @@ module CDNGet
|
|
101
218
|
return value.to_s.reverse.scan(/..?.?/).collect {|s| s.reverse }.reverse.join(',')
|
102
219
|
end
|
103
220
|
|
104
|
-
|
105
|
-
|
221
|
+
def _debug_print(x)
|
222
|
+
if @debug_mode
|
223
|
+
$stderr.puts "\e[0;35m*** #{PP.pp(x,'')}\e[0m"
|
224
|
+
end
|
225
|
+
end
|
106
226
|
|
107
|
-
class HttpError < StandardError
|
108
227
|
end
|
109
228
|
|
110
229
|
|
111
|
-
class CDNJS < Base
|
230
|
+
class CDNJS < Base
|
112
231
|
CODE = "cdnjs"
|
113
232
|
SITE_URL = 'https://cdnjs.com/'
|
114
233
|
|
115
234
|
def fetch(url, library=nil)
|
116
|
-
|
117
|
-
|
118
|
-
if library
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
235
|
+
json_str = super
|
236
|
+
if json_str == "{}" && library
|
237
|
+
if library.end_with?('js')
|
238
|
+
maybe = library.end_with?('.js') \
|
239
|
+
? library.sub('.js', 'js') \
|
240
|
+
: library.sub(/js$/, '.js')
|
241
|
+
raise CommandError.new("#{library}: Library not found (maybe '#{maybe}'?).")
|
242
|
+
else
|
243
|
+
raise CommandError.new("#{library}: Library not found.")
|
123
244
|
end
|
124
|
-
return html
|
125
|
-
rescue OpenURI::HTTPError => ex
|
126
|
-
raise HttpError.new("GET #{url} : #{ex.message}")
|
127
245
|
end
|
246
|
+
return json_str
|
128
247
|
end
|
129
248
|
protected :fetch
|
130
249
|
|
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
|
250
|
+
def list()
|
251
|
+
jstr = fetch("https://api.cdnjs.com/libraries?fields=name,description")
|
252
|
+
jdata = JSON.parse(jstr)
|
253
|
+
_debug_print(jdata)
|
254
|
+
libs = jdata['results'].collect {|d| {name: d['name'], desc: d['description']} }
|
140
255
|
return libs.sort_by {|d| d[:name] }.uniq
|
141
256
|
end
|
142
257
|
|
143
258
|
def find(library)
|
144
259
|
validate(library, nil)
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
versions = []
|
149
|
-
|
150
|
-
versions << ver
|
151
|
-
end
|
152
|
-
desc = tags = nil
|
153
|
-
if html =~ /<\/p>\s*<p>(.*?)<\/p>\s*<em>(.*?)<\/em>/
|
154
|
-
desc = $1
|
155
|
-
tags = $2
|
156
|
-
end
|
260
|
+
jstr = fetch("https://api.cdnjs.com/libraries/#{library}", library)
|
261
|
+
jdata = JSON.parse(jstr)
|
262
|
+
_debug_print(jdata)
|
263
|
+
versions = jdata['assets'].collect {|d| d['version'] }\
|
264
|
+
.sort_by {|v| v.split(/[-.]/).map(&:to_i) }
|
157
265
|
return {
|
158
266
|
name: library,
|
159
|
-
desc:
|
160
|
-
tags:
|
161
|
-
|
267
|
+
desc: jdata['description'],
|
268
|
+
tags: (jdata['keywords'] || []).join(", "),
|
269
|
+
site: jdata['homepage'],
|
270
|
+
info: File.join(SITE_URL, "/libraries/#{library}"),
|
271
|
+
license: jdata['license'],
|
272
|
+
versions: versions.reverse(),
|
162
273
|
}
|
163
274
|
end
|
164
275
|
|
165
276
|
def get(library, version)
|
166
277
|
validate(library, version)
|
167
|
-
|
278
|
+
jstr = fetch("https://api.cdnjs.com/libraries/#{library}", library)
|
279
|
+
jdata = JSON.parse(jstr)
|
280
|
+
_debug_print(jdata)
|
281
|
+
d = jdata['assets'].find {|d| d['version'] == version } or
|
282
|
+
raise CommandError.new("#{library}/#{version}: Library or version not found.")
|
168
283
|
baseurl = "https://cdnjs.cloudflare.com/ajax/libs/#{library}/#{version}/"
|
169
|
-
basepat = Regexp.escape("#{library}/#{version}")
|
170
|
-
files = []
|
171
|
-
html.scan(%r`>#{basepat}/([^<]+)<\/p>`) do |file,|
|
172
|
-
files << file.gsub(///, '/')
|
173
|
-
end
|
174
|
-
urls = files.collect {|s| baseurl + s }
|
175
284
|
return {
|
176
285
|
name: library,
|
177
286
|
version: version,
|
178
|
-
|
179
|
-
|
287
|
+
desc: jdata['description'],
|
288
|
+
tags: (jdata['keywords'] || []).join(", "),
|
289
|
+
site: jdata['homepage'],
|
290
|
+
info: File.join(SITE_URL, "/libraries/#{library}/#{version}"),
|
291
|
+
urls: d['files'].collect {|s| baseurl + s },
|
292
|
+
files: d['files'],
|
180
293
|
baseurl: baseurl,
|
294
|
+
license: jdata['license'],
|
181
295
|
}
|
182
296
|
end
|
183
297
|
|
@@ -187,59 +301,201 @@ module CDNGet
|
|
187
301
|
class JSDelivr < Base
|
188
302
|
CODE = "jsdelivr"
|
189
303
|
SITE_URL = "https://www.jsdelivr.com/"
|
190
|
-
API_URL = "https://api.jsdelivr.com/v1/jsdelivr/libraries"
|
304
|
+
#API_URL = "https://api.jsdelivr.com/v1/jsdelivr/libraries"
|
305
|
+
API_URL = "https://data.jsdelivr.com/v1"
|
306
|
+
HEADERS = {
|
307
|
+
"x-algo""lia-app""lication-id"=>"OFCNC""OG2CU",
|
308
|
+
"x-algo""lia-api""-key"=>"f54e21fa3a2""a0160595bb05""8179bfb1e",
|
309
|
+
}
|
191
310
|
|
192
|
-
def list
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
311
|
+
def list()
|
312
|
+
return nil # nil means that this CDN can't list libraries without pattern
|
313
|
+
end
|
314
|
+
|
315
|
+
def search(pattern)
|
316
|
+
form_data = {
|
317
|
+
query: pattern,
|
318
|
+
page: "0",
|
319
|
+
hitsPerPage: "1000",
|
320
|
+
attributesToHighlight: '[]',
|
321
|
+
attributesToRetrieve: '["name","description","version"]'
|
322
|
+
}
|
323
|
+
payload = JSON.dump({"params"=>URI.encode_www_form(form_data)})
|
324
|
+
url = "https://ofcncog2cu-3.algolianet.com/1/indexes/npm-search/query"
|
325
|
+
uri = URI.parse(url)
|
326
|
+
json = HttpConnection.open(uri, HEADERS) {|http| http.post(uri, payload) }
|
327
|
+
jdata = JSON.load(json)
|
328
|
+
_debug_print(jdata)
|
329
|
+
return jdata["hits"].select {|d|
|
330
|
+
File.fnmatch(pattern, d["name"], File::FNM_CASEFOLD)
|
331
|
+
}.collect {|d|
|
332
|
+
{name: d["name"], desc: d["description"], version: d["version"]}
|
197
333
|
}
|
198
334
|
end
|
199
335
|
|
200
336
|
def find(library)
|
201
337
|
validate(library, nil)
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
338
|
+
url = "https://ofcncog2cu-dsn.algolia.net/1/indexes/npm-search/#{library}"
|
339
|
+
uri = URI.parse(url)
|
340
|
+
begin
|
341
|
+
json = HttpConnection.open(uri, HEADERS) {|http| http.get(uri) }
|
342
|
+
rescue HttpError
|
343
|
+
raise CommandError, "#{library}: Library not found."
|
344
|
+
end
|
345
|
+
dict1 = JSON.load(json)
|
346
|
+
_debug_print(dict1)
|
347
|
+
#
|
348
|
+
json = fetch("#{API_URL}/package/npm/#{library}")
|
349
|
+
dict2 = JSON.load(json)
|
350
|
+
_debug_print(dict2)
|
351
|
+
#
|
352
|
+
d = dict1
|
206
353
|
return {
|
207
|
-
name:
|
208
|
-
desc:
|
209
|
-
|
210
|
-
versions:
|
354
|
+
name: d['name'],
|
355
|
+
desc: d['description'],
|
356
|
+
#versions: d['versions'].collect {|k,v| k },
|
357
|
+
versions: dict2['versions'],
|
358
|
+
tags: (d['keywords'] || []).join(", "),
|
359
|
+
site: d['homepage'],
|
360
|
+
info: File.join(SITE_URL, "/package/npm/#{library}"),
|
361
|
+
license: d['license'],
|
211
362
|
}
|
212
363
|
end
|
213
364
|
|
214
365
|
def get(library, version)
|
215
366
|
validate(library, version)
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
367
|
+
url = File.join(API_URL, "/package/npm/#{library}@#{version}/flat")
|
368
|
+
begin
|
369
|
+
json = fetch(url, library)
|
370
|
+
rescue CommandError
|
371
|
+
raise CommandError.new("#{library}@#{version}: Library or version not found.")
|
372
|
+
end
|
373
|
+
jdata = JSON.load(json)
|
374
|
+
files = jdata["files"].collect {|d| d["name"] }
|
375
|
+
baseurl = "https://cdn.jsdelivr.net/npm/#{library}@#{version}"
|
376
|
+
_debug_print(jdata)
|
377
|
+
#
|
378
|
+
dict = find(library)
|
379
|
+
dict.delete(:versions)
|
380
|
+
dict.update({
|
381
|
+
version: version,
|
382
|
+
info: File.join(SITE_URL, "/package/npm/#{library}?version=#{version}"),
|
383
|
+
npmpkg: "https://registry.npmjs.org/#{library}/-/#{library}-#{version}.tgz",
|
384
|
+
urls: files.collect {|x| baseurl + x },
|
385
|
+
files: files,
|
386
|
+
baseurl: baseurl,
|
387
|
+
default: jdata["default"],
|
388
|
+
destdir: "#{library}@#{version}",
|
389
|
+
})
|
390
|
+
return dict
|
391
|
+
end
|
392
|
+
|
393
|
+
end
|
394
|
+
|
395
|
+
|
396
|
+
class Unpkg < Base
|
397
|
+
CODE = "unpkg"
|
398
|
+
SITE_URL = "https://unpkg.com/"
|
399
|
+
#API_URL = "https://www.npmjs.com"
|
400
|
+
API_URL = "https://api.npms.io/v2"
|
401
|
+
|
402
|
+
protected
|
403
|
+
|
404
|
+
def http_get(url)
|
405
|
+
return URI.__send__(:open, url, 'rb', {"x-spiferack"=>"1"}) {|f| f.read() }
|
406
|
+
end
|
407
|
+
|
408
|
+
public
|
409
|
+
|
410
|
+
def list()
|
411
|
+
return nil # nil means that this CDN can't list libraries without pattern
|
412
|
+
end
|
413
|
+
|
414
|
+
def search(pattern)
|
415
|
+
#json = fetch("#{API_URL}/search?q=#{pattern}")
|
416
|
+
json = fetch("#{API_URL}/search?q=#{pattern}&size=250")
|
417
|
+
jdata = JSON.load(json)
|
418
|
+
_debug_print(jdata)
|
419
|
+
#arr = jdata["objects"] # www.npmjs.com
|
420
|
+
arr = jdata["results"] # api.npms.io
|
421
|
+
return arr.select {|dict|
|
422
|
+
File.fnmatch(pattern, dict["package"]["name"], File::FNM_CASEFOLD)
|
423
|
+
}.collect {|dict|
|
424
|
+
d = dict["package"]
|
425
|
+
{name: d["name"], desc: d["description"], version: d["version"]}
|
426
|
+
}
|
427
|
+
end
|
428
|
+
|
429
|
+
def find(library)
|
430
|
+
validate(library, nil)
|
431
|
+
json = fetch("#{API_URL}/package/#{library}", library)
|
432
|
+
jdata = JSON.load(json)
|
433
|
+
_debug_print(jdata)
|
434
|
+
dict = jdata["collected"]["metadata"]
|
435
|
+
versions = [dict["version"]]
|
436
|
+
#
|
437
|
+
url = File.join(SITE_URL, "/browse/#{library}/")
|
438
|
+
html = fetch(url, library)
|
439
|
+
_debug_print(html)
|
440
|
+
if html =~ /<script>window.__DATA__\s*=\s*(.*?)<\/script>/m
|
441
|
+
jdata2 = JSON.load($1)
|
442
|
+
versions = jdata2["availableVersions"].reverse()
|
443
|
+
end
|
444
|
+
#
|
223
445
|
return {
|
446
|
+
name: dict["name"],
|
447
|
+
desc: dict["description"],
|
448
|
+
tags: (dict["keywords"] || []).join(", "),
|
449
|
+
site: dict["links"] ? dict["links"]["homepage"] : dict["links"]["npm"],
|
450
|
+
info: File.join(SITE_URL, "/browse/#{library}/"),
|
451
|
+
versions: versions,
|
452
|
+
license: dict["license"],
|
453
|
+
}
|
454
|
+
end
|
455
|
+
|
456
|
+
def get(library, version)
|
457
|
+
validate(library, version)
|
458
|
+
dict = find(library)
|
459
|
+
dict.delete(:versions)
|
460
|
+
#
|
461
|
+
url = "https://data.jsdelivr.com/v1/package/npm/#{library}@#{version}/flat"
|
462
|
+
begin
|
463
|
+
json = fetch(url, library)
|
464
|
+
rescue CommandError
|
465
|
+
raise CommandError.new("#{library}@#{version}: Library or version not found.")
|
466
|
+
end
|
467
|
+
jdata = JSON.load(json)
|
468
|
+
files = jdata["files"].collect {|d| d["name"] }
|
469
|
+
baseurl = File.join(SITE_URL, "/#{library}@#{version}")
|
470
|
+
_debug_print(jdata)
|
471
|
+
#
|
472
|
+
dict.update({
|
224
473
|
name: library,
|
225
474
|
version: version,
|
226
|
-
|
475
|
+
info: File.join(SITE_URL, "/browse/#{library}@#{version}/"),
|
476
|
+
npmpkg: "https://registry.npmjs.org/#{library}/-/#{library}-#{version}.tgz",
|
477
|
+
urls: files.collect {|x| baseurl+x },
|
227
478
|
files: files,
|
228
479
|
baseurl: baseurl,
|
229
|
-
|
480
|
+
default: jdata["default"],
|
481
|
+
destdir: "#{library}@#{version}",
|
482
|
+
skipfile: /\.DS_Store\z/, # downloading '.DS_Store' from UNPKG results in 403
|
483
|
+
})
|
484
|
+
return dict
|
230
485
|
end
|
231
486
|
|
232
487
|
end
|
233
488
|
|
234
489
|
|
235
|
-
class GoogleCDN < Base
|
490
|
+
class GoogleCDN < Base
|
236
491
|
CODE = "google"
|
237
492
|
SITE_URL = 'https://developers.google.com/speed/libraries/'
|
238
493
|
|
239
|
-
def list
|
240
|
-
libs = []
|
494
|
+
def list()
|
241
495
|
html = fetch("https://developers.google.com/speed/libraries/")
|
496
|
+
_debug_print(html)
|
242
497
|
rexp = %r`"https://ajax\.googleapis\.com/ajax/libs/([^/]+)/([^/]+)/([^"]+)"`
|
498
|
+
libs = []
|
243
499
|
html.scan(rexp) do |lib, ver, file|
|
244
500
|
libs << {name: lib, desc: "latest version: #{ver}" }
|
245
501
|
end
|
@@ -249,6 +505,7 @@ module CDNGet
|
|
249
505
|
def find(library)
|
250
506
|
validate(library, nil)
|
251
507
|
html = fetch("https://developers.google.com/speed/libraries/")
|
508
|
+
_debug_print(html)
|
252
509
|
rexp = %r`"https://ajax\.googleapis\.com/ajax/libs/#{library}/`
|
253
510
|
site_url = nil
|
254
511
|
versions = []
|
@@ -259,9 +516,7 @@ module CDNGet
|
|
259
516
|
found = true
|
260
517
|
if text =~ /<dt>.*?snippet:<\/dt>\s*<dd>(.*?)<\/dd>/m
|
261
518
|
s = $1
|
262
|
-
s.scan(/\b(?:src|href)="([^"]*?)"/)
|
263
|
-
urls << href
|
264
|
-
end
|
519
|
+
s.scan(/\b(?:src|href)="([^"]*?)"/) {|href,| urls << href }
|
265
520
|
end
|
266
521
|
if text =~ /<dt>site:<\/dt>\s*<dd>(.*?)<\/dd>/m
|
267
522
|
s = $1
|
@@ -282,6 +537,7 @@ module CDNGet
|
|
282
537
|
return {
|
283
538
|
name: library,
|
284
539
|
site: site_url,
|
540
|
+
info: "#{SITE_URL}\##{library}",
|
285
541
|
urls: urls,
|
286
542
|
versions: versions,
|
287
543
|
}
|
@@ -302,6 +558,7 @@ module CDNGet
|
|
302
558
|
return {
|
303
559
|
name: d[:name],
|
304
560
|
site: d[:site],
|
561
|
+
info: "#{SITE_URL}\##{library}",
|
305
562
|
urls: urls,
|
306
563
|
files: files,
|
307
564
|
baseurl: baseurl,
|
@@ -334,24 +591,28 @@ module CDNGet
|
|
334
591
|
@script = script || File.basename($0)
|
335
592
|
end
|
336
593
|
|
337
|
-
def help_message
|
594
|
+
def help_message()
|
338
595
|
script = @script
|
339
596
|
return <<END
|
340
|
-
#{script} -- download files from public CDN
|
597
|
+
#{script} -- download files from public CDN (cdnjs/jsdelivr/unpkg/google)
|
341
598
|
|
342
|
-
Usage: #{script} [options] [CDN
|
599
|
+
Usage: #{script} [<options>] [<CDN> [<library> [<version> [<directory>]]]]
|
343
600
|
|
344
601
|
Options:
|
345
602
|
-h, --help : help
|
346
603
|
-v, --version : version
|
347
604
|
-q, --quiet : minimal output
|
605
|
+
--debug : (debug mode)
|
348
606
|
|
349
607
|
Example:
|
350
|
-
$ #{script} # list public CDN
|
608
|
+
$ #{script} # list public CDN names
|
351
609
|
$ #{script} [-q] cdnjs # list libraries
|
610
|
+
$ #{script} [-q] cdnjs 'jquery*' # search libraries
|
352
611
|
$ #{script} [-q] cdnjs jquery # list versions
|
353
|
-
$ #{script} [-q] cdnjs jquery
|
354
|
-
$ #{script} [-q] cdnjs jquery
|
612
|
+
$ #{script} [-q] cdnjs jquery latest # show latest version
|
613
|
+
$ #{script} [-q] cdnjs jquery 3.6.0 # list files
|
614
|
+
$ #{script} [-q] cdnjs jquery 3.6.0 /tmp # download files into directory
|
615
|
+
|
355
616
|
END
|
356
617
|
end
|
357
618
|
|
@@ -366,10 +627,11 @@ END
|
|
366
627
|
end
|
367
628
|
|
368
629
|
def run(*args)
|
369
|
-
cmdopts = parse_cmdopts(args, "hvq", ["help", "version", "quiet"])
|
630
|
+
cmdopts = parse_cmdopts(args, "hvq", ["help", "version", "quiet", "debug"])
|
370
631
|
return help_message() if cmdopts['h'] || cmdopts['help']
|
371
632
|
return RELEASE + "\n" if cmdopts['v'] || cmdopts['version']
|
372
633
|
@quiet = cmdopts['quiet'] || cmdopts['q']
|
634
|
+
@debug_mode = cmdopts['debug']
|
373
635
|
#
|
374
636
|
validate(args[1], args[2])
|
375
637
|
#
|
@@ -381,11 +643,9 @@ END
|
|
381
643
|
return do_list_libraries(cdn_code)
|
382
644
|
when 2
|
383
645
|
cdn_code, library = args
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
return do_find_library(cdn_code, library)
|
388
|
-
end
|
646
|
+
return library.include?('*') \
|
647
|
+
? do_search_libraries(cdn_code, library) \
|
648
|
+
: do_find_library(cdn_code, library)
|
389
649
|
when 3
|
390
650
|
cdn_code, library, version = args
|
391
651
|
return do_get_library(cdn_code, library, version)
|
@@ -439,35 +699,29 @@ END
|
|
439
699
|
def find_cdn(cdn_code)
|
440
700
|
klass = CLASSES.find {|c| c::CODE == cdn_code } or
|
441
701
|
raise CommandError.new("#{cdn_code}: no such CDN.")
|
442
|
-
return klass.new
|
702
|
+
return klass.new(debug_mode: @debug_mode)
|
443
703
|
end
|
444
704
|
|
445
705
|
def render_list(list)
|
446
|
-
if @quiet
|
447
|
-
|
448
|
-
else
|
449
|
-
return list.collect {|d| "%-20s # %s\n" % [d[:name], d[:desc]] }.join()
|
450
|
-
end
|
706
|
+
return list.collect {|d| "#{d[:name]}\n" }.join() if @quiet
|
707
|
+
return list.collect {|d| "%-20s # %s\n" % [d[:name], d[:desc]] }.join()
|
451
708
|
end
|
452
709
|
|
453
|
-
def do_list_cdns
|
454
|
-
if @quiet
|
455
|
-
|
456
|
-
else
|
457
|
-
return CLASSES.map {|c| "%-10s # %s\n" % [c::CODE, c::SITE_URL] }.join()
|
458
|
-
end
|
710
|
+
def do_list_cdns()
|
711
|
+
return CLASSES.map {|c| "#{c::CODE}\n" }.join() if @quiet
|
712
|
+
return CLASSES.map {|c| "%-10s # %s\n" % [c::CODE, c::SITE_URL] }.join()
|
459
713
|
end
|
460
714
|
|
461
715
|
def do_list_libraries(cdn_code)
|
462
716
|
cdn = find_cdn(cdn_code)
|
463
|
-
|
717
|
+
list = cdn.list() or
|
718
|
+
raise CommandError.new("#{cdn_code}: cannot list libraries; please specify pattern such as 'jquery*'.")
|
719
|
+
return render_list(list)
|
464
720
|
end
|
465
721
|
|
466
722
|
def do_search_libraries(cdn_code, pattern)
|
467
723
|
cdn = find_cdn(cdn_code)
|
468
|
-
|
469
|
-
rexp = Regexp.compile("\\A#{rexp_str}\\z", Regexp::IGNORECASE)
|
470
|
-
return render_list(cdn.list.select {|a| a[:name] =~ rexp })
|
724
|
+
return render_list(cdn.search(pattern))
|
471
725
|
end
|
472
726
|
|
473
727
|
def do_find_library(cdn_code, library)
|
@@ -479,10 +733,12 @@ END
|
|
479
733
|
s << "#{ver}\n"
|
480
734
|
end if d[:versions]
|
481
735
|
else
|
482
|
-
s << "name:
|
483
|
-
s << "desc:
|
484
|
-
s << "tags:
|
485
|
-
s << "site:
|
736
|
+
s << "name: #{d[:name]}\n"
|
737
|
+
s << "desc: #{d[:desc]}\n" if d[:desc]
|
738
|
+
s << "tags: #{d[:tags]}\n" if d[:tags]
|
739
|
+
s << "site: #{d[:site]}\n" if d[:site]
|
740
|
+
s << "info: #{d[:info]}\n" if d[:info]
|
741
|
+
s << "license: #{d[:license]}\n" if d[:license]
|
486
742
|
s << "snippet: |\n" << d[:snippet].gsub(/^/, ' ') if d[:snippet]
|
487
743
|
s << "versions:\n"
|
488
744
|
d[:versions].each do |ver|
|
@@ -494,6 +750,7 @@ END
|
|
494
750
|
|
495
751
|
def do_get_library(cdn_code, library, version)
|
496
752
|
cdn = find_cdn(cdn_code)
|
753
|
+
version = _latest_version(cdn, library) if version == 'latest'
|
497
754
|
d = cdn.get(library, version)
|
498
755
|
s = ""
|
499
756
|
if @quiet
|
@@ -503,8 +760,13 @@ END
|
|
503
760
|
else
|
504
761
|
s << "name: #{d[:name]}\n"
|
505
762
|
s << "version: #{d[:version]}\n"
|
506
|
-
s << "
|
507
|
-
s << "
|
763
|
+
s << "desc: #{d[:desc]}\n" if d[:desc]
|
764
|
+
s << "tags: #{d[:tags]}\n" if d[:tags]
|
765
|
+
s << "site: #{d[:site]}\n" if d[:site]
|
766
|
+
s << "info: #{d[:info]}\n" if d[:info]
|
767
|
+
s << "npmpkg: #{d[:npmpkg]}\n" if d[:npmpkg]
|
768
|
+
s << "default: #{d[:default]}\n" if d[:default]
|
769
|
+
s << "license: #{d[:license]}\n" if d[:license]
|
508
770
|
s << "snippet: |\n" << d[:snippet].gsub(/^/, ' ') if d[:snippet]
|
509
771
|
s << "urls:\n" if d[:urls]
|
510
772
|
d[:urls].each do |url|
|
@@ -516,10 +778,18 @@ END
|
|
516
778
|
|
517
779
|
def do_download_library(cdn_code, library, version, basedir)
|
518
780
|
cdn = find_cdn(cdn_code)
|
781
|
+
version = _latest_version(cdn, library) if version == 'latest'
|
519
782
|
cdn.download(library, version, basedir, quiet: @quiet)
|
520
783
|
return nil
|
521
784
|
end
|
522
785
|
|
786
|
+
private
|
787
|
+
|
788
|
+
def _latest_version(cdn, library)
|
789
|
+
d = cdn.find(library)
|
790
|
+
return d[:versions].first
|
791
|
+
end
|
792
|
+
|
523
793
|
end
|
524
794
|
|
525
795
|
|