cdnget 0.3.0 → 1.0.2
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 +35 -0
- data/MIT-LICENSE +1 -1
- data/README.md +32 -11
- data/Rakefile +2 -2
- data/bin/cdnget +381 -97
- data/cdnget.gemspec +5 -5
- data/lib/cdnget.rb +381 -97
- data/test/cdnget_test.rb +666 -115
- 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.2 $'.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,43 +218,39 @@ 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
|
-
|
123
|
-
|
124
|
-
|
125
|
-
else
|
126
|
-
raise CommandError.new("#{library}: Library not found.")
|
127
|
-
end
|
128
|
-
end
|
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.")
|
129
244
|
end
|
130
|
-
return json_str
|
131
|
-
rescue OpenURI::HTTPError => ex
|
132
|
-
raise HttpError.new("GET #{url} : #{ex.message}")
|
133
245
|
end
|
246
|
+
return json_str
|
134
247
|
end
|
135
248
|
protected :fetch
|
136
249
|
|
137
|
-
def list
|
138
|
-
libs = []
|
250
|
+
def list()
|
139
251
|
jstr = fetch("https://api.cdnjs.com/libraries?fields=name,description")
|
140
252
|
jdata = JSON.parse(jstr)
|
253
|
+
_debug_print(jdata)
|
141
254
|
libs = jdata['results'].collect {|d| {name: d['name'], desc: d['description']} }
|
142
255
|
return libs.sort_by {|d| d[:name] }.uniq
|
143
256
|
end
|
@@ -146,11 +259,17 @@ module CDNGet
|
|
146
259
|
validate(library, nil)
|
147
260
|
jstr = fetch("https://api.cdnjs.com/libraries/#{library}", library)
|
148
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) }
|
149
265
|
return {
|
150
266
|
name: library,
|
151
267
|
desc: jdata['description'],
|
152
268
|
tags: (jdata['keywords'] || []).join(", "),
|
153
|
-
|
269
|
+
site: jdata['homepage'],
|
270
|
+
info: File.join(SITE_URL, "/libraries/#{library}"),
|
271
|
+
license: jdata['license'],
|
272
|
+
versions: versions.reverse(),
|
154
273
|
}
|
155
274
|
end
|
156
275
|
|
@@ -158,17 +277,21 @@ module CDNGet
|
|
158
277
|
validate(library, version)
|
159
278
|
jstr = fetch("https://api.cdnjs.com/libraries/#{library}", library)
|
160
279
|
jdata = JSON.parse(jstr)
|
280
|
+
_debug_print(jdata)
|
161
281
|
d = jdata['assets'].find {|d| d['version'] == version } or
|
162
282
|
raise CommandError.new("#{library}/#{version}: Library or version not found.")
|
163
283
|
baseurl = "https://cdnjs.cloudflare.com/ajax/libs/#{library}/#{version}/"
|
164
284
|
return {
|
165
285
|
name: library,
|
286
|
+
version: version,
|
166
287
|
desc: jdata['description'],
|
167
288
|
tags: (jdata['keywords'] || []).join(", "),
|
168
|
-
|
289
|
+
site: jdata['homepage'],
|
290
|
+
info: File.join(SITE_URL, "/libraries/#{library}/#{version}"),
|
169
291
|
urls: d['files'].collect {|s| baseurl + s },
|
170
292
|
files: d['files'],
|
171
293
|
baseurl: baseurl,
|
294
|
+
license: jdata['license'],
|
172
295
|
}
|
173
296
|
end
|
174
297
|
|
@@ -178,59 +301,201 @@ module CDNGet
|
|
178
301
|
class JSDelivr < Base
|
179
302
|
CODE = "jsdelivr"
|
180
303
|
SITE_URL = "https://www.jsdelivr.com/"
|
181
|
-
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
|
+
}
|
310
|
+
|
311
|
+
def list()
|
312
|
+
return nil # nil means that this CDN can't list libraries without pattern
|
313
|
+
end
|
182
314
|
|
183
|
-
def
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
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"]}
|
188
333
|
}
|
189
334
|
end
|
190
335
|
|
191
336
|
def find(library)
|
192
337
|
validate(library, nil)
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
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
|
197
353
|
return {
|
198
|
-
name:
|
199
|
-
desc:
|
200
|
-
|
201
|
-
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'],
|
202
362
|
}
|
203
363
|
end
|
204
364
|
|
205
365
|
def get(library, version)
|
206
366
|
validate(library, version)
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
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
|
+
#
|
214
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({
|
215
473
|
name: library,
|
216
474
|
version: version,
|
217
|
-
|
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 },
|
218
478
|
files: files,
|
219
479
|
baseurl: baseurl,
|
220
|
-
|
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
|
221
485
|
end
|
222
486
|
|
223
487
|
end
|
224
488
|
|
225
489
|
|
226
|
-
class GoogleCDN < Base
|
490
|
+
class GoogleCDN < Base
|
227
491
|
CODE = "google"
|
228
492
|
SITE_URL = 'https://developers.google.com/speed/libraries/'
|
229
493
|
|
230
|
-
def list
|
231
|
-
libs = []
|
494
|
+
def list()
|
232
495
|
html = fetch("https://developers.google.com/speed/libraries/")
|
496
|
+
_debug_print(html)
|
233
497
|
rexp = %r`"https://ajax\.googleapis\.com/ajax/libs/([^/]+)/([^/]+)/([^"]+)"`
|
498
|
+
libs = []
|
234
499
|
html.scan(rexp) do |lib, ver, file|
|
235
500
|
libs << {name: lib, desc: "latest version: #{ver}" }
|
236
501
|
end
|
@@ -240,6 +505,7 @@ module CDNGet
|
|
240
505
|
def find(library)
|
241
506
|
validate(library, nil)
|
242
507
|
html = fetch("https://developers.google.com/speed/libraries/")
|
508
|
+
_debug_print(html)
|
243
509
|
rexp = %r`"https://ajax\.googleapis\.com/ajax/libs/#{library}/`
|
244
510
|
site_url = nil
|
245
511
|
versions = []
|
@@ -250,9 +516,7 @@ module CDNGet
|
|
250
516
|
found = true
|
251
517
|
if text =~ /<dt>.*?snippet:<\/dt>\s*<dd>(.*?)<\/dd>/m
|
252
518
|
s = $1
|
253
|
-
s.scan(/\b(?:src|href)="([^"]*?)"/)
|
254
|
-
urls << href
|
255
|
-
end
|
519
|
+
s.scan(/\b(?:src|href)="([^"]*?)"/) {|href,| urls << href }
|
256
520
|
end
|
257
521
|
if text =~ /<dt>site:<\/dt>\s*<dd>(.*?)<\/dd>/m
|
258
522
|
s = $1
|
@@ -273,6 +537,7 @@ module CDNGet
|
|
273
537
|
return {
|
274
538
|
name: library,
|
275
539
|
site: site_url,
|
540
|
+
info: "#{SITE_URL}\##{library}",
|
276
541
|
urls: urls,
|
277
542
|
versions: versions,
|
278
543
|
}
|
@@ -293,6 +558,7 @@ module CDNGet
|
|
293
558
|
return {
|
294
559
|
name: d[:name],
|
295
560
|
site: d[:site],
|
561
|
+
info: "#{SITE_URL}\##{library}",
|
296
562
|
urls: urls,
|
297
563
|
files: files,
|
298
564
|
baseurl: baseurl,
|
@@ -325,24 +591,34 @@ module CDNGet
|
|
325
591
|
@script = script || File.basename($0)
|
326
592
|
end
|
327
593
|
|
328
|
-
def help_message
|
594
|
+
def help_message()
|
329
595
|
script = @script
|
330
596
|
return <<END
|
331
|
-
#{script} -- download files from public CDN
|
597
|
+
#{script} -- download files from public CDN (cdnjs/jsdelivr/unpkg/google)
|
332
598
|
|
333
|
-
Usage: #{script} [options] [CDN
|
599
|
+
Usage: #{script} [<options>] [<CDN> [<library> [<version> [<directory>]]]]
|
334
600
|
|
335
601
|
Options:
|
336
602
|
-h, --help : help
|
337
603
|
-v, --version : version
|
338
604
|
-q, --quiet : minimal output
|
605
|
+
--debug : (debug mode)
|
339
606
|
|
340
607
|
Example:
|
341
|
-
$ #{script} # list public CDN
|
608
|
+
$ #{script} # list public CDN names
|
342
609
|
$ #{script} [-q] cdnjs # list libraries
|
610
|
+
$ #{script} [-q] cdnjs 'jquery*' # search libraries
|
343
611
|
$ #{script} [-q] cdnjs jquery # list versions
|
612
|
+
$ #{script} [-q] cdnjs jquery latest # show latest version
|
344
613
|
$ #{script} [-q] cdnjs jquery 2.2.0 # list files
|
345
|
-
$
|
614
|
+
$ mkdir -p static/lib # create a directory
|
615
|
+
$ #{script} [-q] cdnjs jquery 2.2.0 static/lib # download files
|
616
|
+
static/lib/jquery/2.2.0/jquery.js ... Done (258,388 byte)
|
617
|
+
static/lib/jquery/2.2.0/jquery.min.js ... Done (85,589 byte)
|
618
|
+
static/lib/jquery/2.2.0/jquery.min.map ... Done (129,544 byte)
|
619
|
+
$ ls static/lib/jquery/2.2.0
|
620
|
+
jquery.js jquery.min.js jquery.min.map
|
621
|
+
|
346
622
|
END
|
347
623
|
end
|
348
624
|
|
@@ -357,10 +633,11 @@ END
|
|
357
633
|
end
|
358
634
|
|
359
635
|
def run(*args)
|
360
|
-
cmdopts = parse_cmdopts(args, "hvq", ["help", "version", "quiet"])
|
636
|
+
cmdopts = parse_cmdopts(args, "hvq", ["help", "version", "quiet", "debug"])
|
361
637
|
return help_message() if cmdopts['h'] || cmdopts['help']
|
362
638
|
return RELEASE + "\n" if cmdopts['v'] || cmdopts['version']
|
363
639
|
@quiet = cmdopts['quiet'] || cmdopts['q']
|
640
|
+
@debug_mode = cmdopts['debug']
|
364
641
|
#
|
365
642
|
validate(args[1], args[2])
|
366
643
|
#
|
@@ -372,11 +649,9 @@ END
|
|
372
649
|
return do_list_libraries(cdn_code)
|
373
650
|
when 2
|
374
651
|
cdn_code, library = args
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
return do_find_library(cdn_code, library)
|
379
|
-
end
|
652
|
+
return library.include?('*') \
|
653
|
+
? do_search_libraries(cdn_code, library) \
|
654
|
+
: do_find_library(cdn_code, library)
|
380
655
|
when 3
|
381
656
|
cdn_code, library, version = args
|
382
657
|
return do_get_library(cdn_code, library, version)
|
@@ -430,35 +705,29 @@ END
|
|
430
705
|
def find_cdn(cdn_code)
|
431
706
|
klass = CLASSES.find {|c| c::CODE == cdn_code } or
|
432
707
|
raise CommandError.new("#{cdn_code}: no such CDN.")
|
433
|
-
return klass.new
|
708
|
+
return klass.new(debug_mode: @debug_mode)
|
434
709
|
end
|
435
710
|
|
436
711
|
def render_list(list)
|
437
|
-
if @quiet
|
438
|
-
|
439
|
-
else
|
440
|
-
return list.collect {|d| "%-20s # %s\n" % [d[:name], d[:desc]] }.join()
|
441
|
-
end
|
712
|
+
return list.collect {|d| "#{d[:name]}\n" }.join() if @quiet
|
713
|
+
return list.collect {|d| "%-20s # %s\n" % [d[:name], d[:desc]] }.join()
|
442
714
|
end
|
443
715
|
|
444
|
-
def do_list_cdns
|
445
|
-
if @quiet
|
446
|
-
|
447
|
-
else
|
448
|
-
return CLASSES.map {|c| "%-10s # %s\n" % [c::CODE, c::SITE_URL] }.join()
|
449
|
-
end
|
716
|
+
def do_list_cdns()
|
717
|
+
return CLASSES.map {|c| "#{c::CODE}\n" }.join() if @quiet
|
718
|
+
return CLASSES.map {|c| "%-10s # %s\n" % [c::CODE, c::SITE_URL] }.join()
|
450
719
|
end
|
451
720
|
|
452
721
|
def do_list_libraries(cdn_code)
|
453
722
|
cdn = find_cdn(cdn_code)
|
454
|
-
|
723
|
+
list = cdn.list() or
|
724
|
+
raise CommandError.new("#{cdn_code}: cannot list libraries; please specify pattern such as 'jquery*'.")
|
725
|
+
return render_list(list)
|
455
726
|
end
|
456
727
|
|
457
728
|
def do_search_libraries(cdn_code, pattern)
|
458
729
|
cdn = find_cdn(cdn_code)
|
459
|
-
|
460
|
-
rexp = Regexp.compile("\\A#{rexp_str}\\z", Regexp::IGNORECASE)
|
461
|
-
return render_list(cdn.list.select {|a| a[:name] =~ rexp })
|
730
|
+
return render_list(cdn.search(pattern))
|
462
731
|
end
|
463
732
|
|
464
733
|
def do_find_library(cdn_code, library)
|
@@ -470,10 +739,12 @@ END
|
|
470
739
|
s << "#{ver}\n"
|
471
740
|
end if d[:versions]
|
472
741
|
else
|
473
|
-
s << "name:
|
474
|
-
s << "desc:
|
475
|
-
s << "tags:
|
476
|
-
s << "site:
|
742
|
+
s << "name: #{d[:name]}\n"
|
743
|
+
s << "desc: #{d[:desc]}\n" if d[:desc]
|
744
|
+
s << "tags: #{d[:tags]}\n" if d[:tags]
|
745
|
+
s << "site: #{d[:site]}\n" if d[:site]
|
746
|
+
s << "info: #{d[:info]}\n" if d[:info]
|
747
|
+
s << "license: #{d[:license]}\n" if d[:license]
|
477
748
|
s << "snippet: |\n" << d[:snippet].gsub(/^/, ' ') if d[:snippet]
|
478
749
|
s << "versions:\n"
|
479
750
|
d[:versions].each do |ver|
|
@@ -485,6 +756,7 @@ END
|
|
485
756
|
|
486
757
|
def do_get_library(cdn_code, library, version)
|
487
758
|
cdn = find_cdn(cdn_code)
|
759
|
+
version = _latest_version(cdn, library) if version == 'latest'
|
488
760
|
d = cdn.get(library, version)
|
489
761
|
s = ""
|
490
762
|
if @quiet
|
@@ -494,9 +766,13 @@ END
|
|
494
766
|
else
|
495
767
|
s << "name: #{d[:name]}\n"
|
496
768
|
s << "version: #{d[:version]}\n"
|
497
|
-
s << "desc: #{d[:desc]}\n"
|
498
|
-
s << "tags: #{d[:tags]}\n"
|
499
|
-
s << "site: #{d[:site]}\n"
|
769
|
+
s << "desc: #{d[:desc]}\n" if d[:desc]
|
770
|
+
s << "tags: #{d[:tags]}\n" if d[:tags]
|
771
|
+
s << "site: #{d[:site]}\n" if d[:site]
|
772
|
+
s << "info: #{d[:info]}\n" if d[:info]
|
773
|
+
s << "npmpkg: #{d[:npmpkg]}\n" if d[:npmpkg]
|
774
|
+
s << "default: #{d[:default]}\n" if d[:default]
|
775
|
+
s << "license: #{d[:license]}\n" if d[:license]
|
500
776
|
s << "snippet: |\n" << d[:snippet].gsub(/^/, ' ') if d[:snippet]
|
501
777
|
s << "urls:\n" if d[:urls]
|
502
778
|
d[:urls].each do |url|
|
@@ -508,10 +784,18 @@ END
|
|
508
784
|
|
509
785
|
def do_download_library(cdn_code, library, version, basedir)
|
510
786
|
cdn = find_cdn(cdn_code)
|
787
|
+
version = _latest_version(cdn, library) if version == 'latest'
|
511
788
|
cdn.download(library, version, basedir, quiet: @quiet)
|
512
789
|
return nil
|
513
790
|
end
|
514
791
|
|
792
|
+
private
|
793
|
+
|
794
|
+
def _latest_version(cdn, library)
|
795
|
+
d = cdn.find(library)
|
796
|
+
return d[:versions].first
|
797
|
+
end
|
798
|
+
|
515
799
|
end
|
516
800
|
|
517
801
|
|