cdnget 0.3.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/cdnget.rb CHANGED
@@ -2,30 +2,97 @@
2
2
  # -*- coding: utf-8 -*-
3
3
 
4
4
  ##
5
- ## Download files from CDN (CDNJS, Google, jsDelivr).
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 2.2.0 # list files
16
- ## $ cdnget [-q] cdnjs jquery 2.2.0 /tmp # download files
16
+ ## $ cdnget [-q] cdnjs jquery latest # detect latest version
17
+ ## $ cdnget [-q] cdnjs jquery 3.6.0 # list files
18
+ ## $ cdnget [-q] cdnjs jquery 3.6.0 /tmp # download files
17
19
  ##
18
20
 
19
21
  require 'open-uri'
20
22
  require 'uri'
23
+ require 'net/http'
24
+ require 'openssl'
21
25
  require 'json'
22
26
  require 'fileutils'
27
+ require 'pp'
23
28
 
24
29
 
25
30
  module CDNGet
26
31
 
27
32
 
28
- RELEASE = '$Release: 0.3.1 $'.split()[1]
33
+ RELEASE = '$Release: 1.0.0 $'.split()[1]
34
+
35
+
36
+ class HttpConnection
37
+
38
+ def initialize(uri, headers=nil)
39
+ http = Net::HTTP.new(uri.host, uri.port)
40
+ if uri.scheme == 'https'
41
+ http.use_ssl = true
42
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
43
+ end
44
+ http.start()
45
+ @http = http
46
+ @headers = headers
47
+ end
48
+
49
+ def self.open(uri, headers=nil)
50
+ http = self.new(uri, headers)
51
+ return http unless block_given?()
52
+ begin
53
+ return yield http
54
+ ensure
55
+ http.close()
56
+ end
57
+ end
58
+
59
+ def get(uri)
60
+ resp = @http.send_request('GET', uri.path, nil, @headers)
61
+ case resp
62
+ when Net::HTTPSuccess
63
+ return resp.body
64
+ #when HTTPInformation, Net::HTTPRedirection, HTTPClientError, HTTPServerError
65
+ else
66
+ raise HttpError.new(resp.code.to_i, resp.message)
67
+ end
68
+ end
69
+
70
+ def post(uri, payload)
71
+ path = uri.path
72
+ path += "?"+uri.query if uri.query && !uri.query.empty?
73
+ resp = @http.send_request('POST', path, payload, @headers)
74
+ case resp
75
+ when Net::HTTPSuccess ; return resp.body
76
+ else ; raise HttpError.new(resp.code.to_i, resp.message)
77
+ end
78
+ end
79
+
80
+ def close()
81
+ @http.finish()
82
+ end
83
+
84
+ end
85
+
86
+
87
+ class HttpError < StandardError
88
+ def initialize(code, msgtext)
89
+ super("#{code} #{msgtext}")
90
+ @code = code
91
+ @msgtext = msgtext
92
+ end
93
+ attr_reader :code, :msgtext
94
+ end
95
+
29
96
 
30
97
  CLASSES = []
31
98
 
@@ -36,10 +103,22 @@ module CDNGet
36
103
  CLASSES << klass
37
104
  end
38
105
 
106
+ def initialize(debug_mode: false)
107
+ @debug_mode = debug_mode
108
+ end
109
+ attr_reader :debug_mode
110
+
39
111
  def list()
40
112
  raise NotImplementedError.new("#{self.class.name}#list(): not implemented yet.")
41
113
  end
42
114
 
115
+ def search(pattern)
116
+ return list().select {|d| File.fnmatch(pattern, d[:name], File::FNM_CASEFOLD) }
117
+ #rexp_str = pattern.split('*', -1).collect {|x| Regexp.escape(x) }.join('.*')
118
+ #rexp = Regexp.compile("\\A#{rexp_str}\\z", Regexp::IGNORECASE)
119
+ #return list().select {|d| d[:name] =~ rexp }
120
+ end
121
+
43
122
  def find(library)
44
123
  raise NotImplementedError.new("#{self.class.name}#find(): not implemented yet.")
45
124
  end
@@ -54,14 +133,28 @@ module CDNGet
54
133
  raise CommandError.new("#{basedir}: not exist.")
55
134
  File.directory?(basedir) or
56
135
  raise CommandError.new("#{basedir}: not a directory.")
57
- target_dir = File.join(basedir, library, version)
58
136
  d = get(library, version)
137
+ target_dir = d[:destdir] ? File.join(basedir, d[:destdir]) \
138
+ : File.join(basedir, library, version)
139
+ http = nil
59
140
  d[:files].each do |file|
60
141
  filepath = File.join(target_dir, file)
142
+ if filepath.end_with?('/')
143
+ if File.exist?(filepath)
144
+ puts "#{filepath} ... Done (Already exists)" unless quiet
145
+ else
146
+ print "#{filepath} ..." unless quiet
147
+ FileUtils.mkdir_p(filepath)
148
+ puts " Done (Created)" unless quiet
149
+ end
150
+ next
151
+ end
61
152
  dirpath = File.dirname(filepath)
62
153
  print "#{filepath} ..." unless quiet
63
154
  url = File.join(d[:baseurl], file) # not use URI.join!
64
- content = fetch(url)
155
+ uri = URI.parse(url)
156
+ http ||= HttpConnection.new(uri)
157
+ content = http.get(uri)
65
158
  content = content.force_encoding('ascii-8bit')
66
159
  print " Done (#{format_integer(content.bytesize)} byte)" unless quiet
67
160
  FileUtils.mkdir_p(dirpath) unless File.exist?(dirpath)
@@ -73,17 +166,32 @@ module CDNGet
73
166
  end
74
167
  puts() unless quiet
75
168
  end
169
+ http.close() if http
76
170
  nil
77
171
  end
78
172
 
79
173
  protected
80
174
 
175
+ def http_get(url)
176
+ ## * `open()` on Ruby 3.X can't open http url
177
+ ## * `URI.open()` on Ruby <= 2.4 raises NoMethodError (private method `open' called)
178
+ ## * `URI.__send__(:open)` is a hack to work on both Ruby 2.X and 3.X
179
+ return URI.__send__(:open, url, 'rb') {|f| f.read() }
180
+ end
181
+
81
182
  def fetch(url, library=nil)
82
183
  begin
83
- html = URI.open(url, 'rb') {|f| f.read() }
184
+ html = http_get(url)
84
185
  return html
85
- rescue OpenURI::HTTPError => ex
86
- raise CommandError.new("GET #{url} : #{ex.message}")
186
+ rescue OpenURI::HTTPError => exc
187
+ if ! (exc.message == "404 Not Found" && library)
188
+ raise CommandError.new("GET #{url} : #{exc.message}")
189
+ elsif ! library.end_with?('js')
190
+ raise CommandError.new("#{library}: Library not found.")
191
+ else
192
+ maybe = library.end_with?('.js') ? library.sub('.js', 'js') : library.sub(/js$/, '.js')
193
+ raise CommandError.new("#{library}: Library not found (maybe '#{maybe}'?).")
194
+ end
87
195
  end
88
196
  end
89
197
 
@@ -102,27 +210,21 @@ module CDNGet
102
210
  return value.to_s.reverse.scan(/..?.?/).collect {|s| s.reverse }.reverse.join(',')
103
211
  end
104
212
 
105
- end
106
-
213
+ def _debug_print(x)
214
+ if @debug_mode
215
+ $stderr.puts "\e[0;35m*** #{PP.pp(x,'')}\e[0m"
216
+ end
217
+ end
107
218
 
108
- class HttpError < StandardError
109
219
  end
110
220
 
111
221
 
112
- class CDNJS < Base # TODO: use jsdelivr api
222
+ class CDNJS < Base
113
223
  CODE = "cdnjs"
114
224
  SITE_URL = 'https://cdnjs.com/'
115
225
 
116
226
  def fetch(url, library=nil)
117
- begin
118
- json_str = URI.open(url, 'rb') {|f| f.read() }
119
- rescue OpenURI::HTTPError => exc
120
- if exc.message == "404 Not Found"
121
- json_str = "{}"
122
- else
123
- raise HttpError.new("GET #{url} : #{ex.message}")
124
- end
125
- end
227
+ json_str = super
126
228
  if json_str == "{}" && library
127
229
  if library.end_with?('js')
128
230
  maybe = library.end_with?('.js') \
@@ -137,10 +239,10 @@ module CDNGet
137
239
  end
138
240
  protected :fetch
139
241
 
140
- def list
141
- libs = []
242
+ def list()
142
243
  jstr = fetch("https://api.cdnjs.com/libraries?fields=name,description")
143
244
  jdata = JSON.parse(jstr)
245
+ _debug_print(jdata)
144
246
  libs = jdata['results'].collect {|d| {name: d['name'], desc: d['description']} }
145
247
  return libs.sort_by {|d| d[:name] }.uniq
146
248
  end
@@ -149,12 +251,16 @@ module CDNGet
149
251
  validate(library, nil)
150
252
  jstr = fetch("https://api.cdnjs.com/libraries/#{library}", library)
151
253
  jdata = JSON.parse(jstr)
254
+ _debug_print(jdata)
152
255
  versions = jdata['assets'].collect {|d| d['version'] }\
153
256
  .sort_by {|v| v.split(/[-.]/).map(&:to_i) }
154
257
  return {
155
258
  name: library,
156
259
  desc: jdata['description'],
157
260
  tags: (jdata['keywords'] || []).join(", "),
261
+ site: jdata['homepage'],
262
+ info: File.join(SITE_URL, "/libraries/#{library}"),
263
+ license: jdata['license'],
158
264
  versions: versions.reverse(),
159
265
  }
160
266
  end
@@ -163,17 +269,21 @@ module CDNGet
163
269
  validate(library, version)
164
270
  jstr = fetch("https://api.cdnjs.com/libraries/#{library}", library)
165
271
  jdata = JSON.parse(jstr)
272
+ _debug_print(jdata)
166
273
  d = jdata['assets'].find {|d| d['version'] == version } or
167
274
  raise CommandError.new("#{library}/#{version}: Library or version not found.")
168
275
  baseurl = "https://cdnjs.cloudflare.com/ajax/libs/#{library}/#{version}/"
169
276
  return {
170
277
  name: library,
278
+ version: version,
171
279
  desc: jdata['description'],
172
280
  tags: (jdata['keywords'] || []).join(", "),
173
- version: version,
281
+ site: jdata['homepage'],
282
+ info: File.join(SITE_URL, "/libraries/#{library}/#{version}"),
174
283
  urls: d['files'].collect {|s| baseurl + s },
175
284
  files: d['files'],
176
285
  baseurl: baseurl,
286
+ license: jdata['license'],
177
287
  }
178
288
  end
179
289
 
@@ -183,59 +293,200 @@ module CDNGet
183
293
  class JSDelivr < Base
184
294
  CODE = "jsdelivr"
185
295
  SITE_URL = "https://www.jsdelivr.com/"
186
- API_URL = "https://api.jsdelivr.com/v1/jsdelivr/libraries"
296
+ #API_URL = "https://api.jsdelivr.com/v1/jsdelivr/libraries"
297
+ API_URL = "https://data.jsdelivr.com/v1"
298
+ HEADERS = {
299
+ "x-algo""lia-app""lication-id"=>"OFCNC""OG2CU",
300
+ "x-algo""lia-api""-key"=>"f54e21fa3a2""a0160595bb05""8179bfb1e",
301
+ }
187
302
 
188
- def list
189
- json = fetch("#{API_URL}?fields=name,description,homepage")
190
- arr = JSON.load(json)
191
- return arr.collect {|d|
192
- {name: d["name"], desc: d["description"], site: d["homepage"] }
303
+ def list()
304
+ return nil # nil means that this CDN can't list libraries without pattern
305
+ end
306
+
307
+ def search(pattern)
308
+ form_data = {
309
+ query: pattern,
310
+ page: "0",
311
+ hitsPerPage: "1000",
312
+ attributesToHighlight: '[]',
313
+ attributesToRetrieve: '["name","description","version"]'
314
+ }
315
+ payload = JSON.dump({"params"=>URI.encode_www_form(form_data)})
316
+ url = "https://ofcncog2cu-3.algolianet.com/1/indexes/npm-search/query"
317
+ uri = URI.parse(url)
318
+ json = HttpConnection.open(uri, HEADERS) {|http| http.post(uri, payload) }
319
+ jdata = JSON.load(json)
320
+ _debug_print(jdata)
321
+ return jdata["hits"].select {|d|
322
+ File.fnmatch(pattern, d["name"], File::FNM_CASEFOLD)
323
+ }.collect {|d|
324
+ {name: d["name"], desc: d["description"], version: d["version"]}
193
325
  }
194
326
  end
195
327
 
196
328
  def find(library)
197
329
  validate(library, nil)
198
- json = fetch("#{API_URL}?name=#{library}&fields=name,description,homepage,versions")
199
- arr = JSON.load(json)
200
- d = arr.first or
201
- raise CommandError.new("#{library}: Library not found.")
330
+ url = "https://ofcncog2cu-dsn.algolia.net/1/indexes/npm-search/#{library}"
331
+ uri = URI.parse(url)
332
+ begin
333
+ json = HttpConnection.open(uri, HEADERS) {|http| http.get(uri) }
334
+ rescue HttpError
335
+ raise CommandError, "#{library}: Library not found."
336
+ end
337
+ dict1 = JSON.load(json)
338
+ _debug_print(dict1)
339
+ #
340
+ json = fetch("#{API_URL}/package/npm/#{library}")
341
+ dict2 = JSON.load(json)
342
+ _debug_print(dict2)
343
+ #
344
+ d = dict1
202
345
  return {
203
- name: d['name'],
204
- desc: d['description'],
205
- site: d['homepage'],
206
- versions: d['versions'],
346
+ name: d['name'],
347
+ desc: d['description'],
348
+ #versions: d['versions'].collect {|k,v| k },
349
+ versions: dict2['versions'],
350
+ tags: (d['keywords'] || []).join(", "),
351
+ site: d['homepage'],
352
+ info: File.join(SITE_URL, "/package/npm/#{library}"),
353
+ license: d['license'],
207
354
  }
208
355
  end
209
356
 
210
357
  def get(library, version)
211
358
  validate(library, version)
212
- baseurl = "https://cdn.jsdelivr.net/#{library}/#{version}"
213
- url = "#{API_URL}/#{library}/#{version}"
214
- json = fetch("#{API_URL}/#{library}/#{version}")
215
- files = JSON.load(json)
216
- ! files.empty? or
217
- raise CommandError.new("#{library}: Library not found.")
218
- urls = files.collect {|x| "#{baseurl}/#{x}" }
359
+ url = File.join(API_URL, "/package/npm/#{library}@#{version}/flat")
360
+ begin
361
+ json = fetch(url, library)
362
+ rescue CommandError
363
+ raise CommandError.new("#{library}@#{version}: Library or version not found.")
364
+ end
365
+ jdata = JSON.load(json)
366
+ files = jdata["files"].collect {|d| d["name"] }
367
+ baseurl = "https://cdn.jsdelivr.net/npm/#{library}@#{version}"
368
+ _debug_print(jdata)
369
+ #
370
+ dict = find(library)
371
+ dict.delete(:versions)
372
+ dict.update({
373
+ version: version,
374
+ info: File.join(SITE_URL, "/package/npm/#{library}?version=#{version}"),
375
+ npmpkg: "https://registry.npmjs.org/#{library}/-/#{library}-#{version}.tgz",
376
+ urls: files.collect {|x| baseurl + x },
377
+ files: files,
378
+ baseurl: baseurl,
379
+ default: jdata["default"],
380
+ destdir: "#{library}@#{version}",
381
+ })
382
+ return dict
383
+ end
384
+
385
+ end
386
+
387
+
388
+ class Unpkg < Base
389
+ CODE = "unpkg"
390
+ SITE_URL = "https://unpkg.com/"
391
+ #API_URL = "https://www.npmjs.com"
392
+ API_URL = "https://api.npms.io/v2"
393
+
394
+ protected
395
+
396
+ def http_get(url)
397
+ return URI.__send__(:open, url, 'rb', {"x-spiferack"=>"1"}) {|f| f.read() }
398
+ end
399
+
400
+ public
401
+
402
+ def list()
403
+ return nil # nil means that this CDN can't list libraries without pattern
404
+ end
405
+
406
+ def search(pattern)
407
+ #json = fetch("#{API_URL}/search?q=#{pattern}")
408
+ json = fetch("#{API_URL}/search?q=#{pattern}&size=250")
409
+ jdata = JSON.load(json)
410
+ _debug_print(jdata)
411
+ #arr = jdata["objects"] # www.npmjs.com
412
+ arr = jdata["results"] # api.npms.io
413
+ return arr.select {|dict|
414
+ File.fnmatch(pattern, dict["package"]["name"], File::FNM_CASEFOLD)
415
+ }.collect {|dict|
416
+ d = dict["package"]
417
+ {name: d["name"], desc: d["description"], version: d["version"]}
418
+ }
419
+ end
420
+
421
+ def find(library)
422
+ validate(library, nil)
423
+ json = fetch("#{API_URL}/package/#{library}", library)
424
+ jdata = JSON.load(json)
425
+ _debug_print(jdata)
426
+ dict = jdata["collected"]["metadata"]
427
+ versions = [dict["version"]]
428
+ #
429
+ url = File.join(SITE_URL, "/browse/#{library}/")
430
+ html = fetch(url, library)
431
+ _debug_print(html)
432
+ if html =~ /<script>window.__DATA__\s*=\s*(.*?)<\/script>/m
433
+ jdata2 = JSON.load($1)
434
+ versions = jdata2["availableVersions"].reverse()
435
+ end
436
+ #
219
437
  return {
438
+ name: dict["name"],
439
+ desc: dict["description"],
440
+ tags: (dict["keywords"] || []).join(", "),
441
+ site: dict["links"] ? dict["links"]["homepage"] : dict["links"]["npm"],
442
+ info: File.join(SITE_URL, "/browse/#{library}/"),
443
+ versions: versions,
444
+ license: dict["license"],
445
+ }
446
+ end
447
+
448
+ def get(library, version)
449
+ validate(library, version)
450
+ dict = find(library)
451
+ dict.delete(:versions)
452
+ #
453
+ url = "https://data.jsdelivr.com/v1/package/npm/#{library}@#{version}/flat"
454
+ begin
455
+ json = fetch(url, library)
456
+ rescue CommandError
457
+ raise CommandError.new("#{library}@#{version}: Library or version not found.")
458
+ end
459
+ jdata = JSON.load(json)
460
+ files = jdata["files"].collect {|d| d["name"] }
461
+ baseurl = File.join(SITE_URL, "/#{library}@#{version}")
462
+ _debug_print(jdata)
463
+ #
464
+ dict.update({
220
465
  name: library,
221
466
  version: version,
222
- urls: urls,
467
+ info: File.join(SITE_URL, "/browse/#{library}@#{version}/"),
468
+ npmpkg: "https://registry.npmjs.org/#{library}/-/#{library}-#{version}.tgz",
469
+ urls: files.collect {|x| baseurl+x },
223
470
  files: files,
224
471
  baseurl: baseurl,
225
- }
472
+ default: jdata["default"],
473
+ destdir: "#{library}@#{version}",
474
+ })
475
+ return dict
226
476
  end
227
477
 
228
478
  end
229
479
 
230
480
 
231
- class GoogleCDN < Base # TODO: use jsdelivr api
481
+ class GoogleCDN < Base
232
482
  CODE = "google"
233
483
  SITE_URL = 'https://developers.google.com/speed/libraries/'
234
484
 
235
- def list
236
- libs = []
485
+ def list()
237
486
  html = fetch("https://developers.google.com/speed/libraries/")
487
+ _debug_print(html)
238
488
  rexp = %r`"https://ajax\.googleapis\.com/ajax/libs/([^/]+)/([^/]+)/([^"]+)"`
489
+ libs = []
239
490
  html.scan(rexp) do |lib, ver, file|
240
491
  libs << {name: lib, desc: "latest version: #{ver}" }
241
492
  end
@@ -245,6 +496,7 @@ module CDNGet
245
496
  def find(library)
246
497
  validate(library, nil)
247
498
  html = fetch("https://developers.google.com/speed/libraries/")
499
+ _debug_print(html)
248
500
  rexp = %r`"https://ajax\.googleapis\.com/ajax/libs/#{library}/`
249
501
  site_url = nil
250
502
  versions = []
@@ -255,9 +507,7 @@ module CDNGet
255
507
  found = true
256
508
  if text =~ /<dt>.*?snippet:<\/dt>\s*<dd>(.*?)<\/dd>/m
257
509
  s = $1
258
- s.scan(/\b(?:src|href)="([^"]*?)"/) do |href,|
259
- urls << href
260
- end
510
+ s.scan(/\b(?:src|href)="([^"]*?)"/) {|href,| urls << href }
261
511
  end
262
512
  if text =~ /<dt>site:<\/dt>\s*<dd>(.*?)<\/dd>/m
263
513
  s = $1
@@ -278,6 +528,7 @@ module CDNGet
278
528
  return {
279
529
  name: library,
280
530
  site: site_url,
531
+ info: "#{SITE_URL}\##{library}",
281
532
  urls: urls,
282
533
  versions: versions,
283
534
  }
@@ -298,6 +549,7 @@ module CDNGet
298
549
  return {
299
550
  name: d[:name],
300
551
  site: d[:site],
552
+ info: "#{SITE_URL}\##{library}",
301
553
  urls: urls,
302
554
  files: files,
303
555
  baseurl: baseurl,
@@ -330,24 +582,28 @@ module CDNGet
330
582
  @script = script || File.basename($0)
331
583
  end
332
584
 
333
- def help_message
585
+ def help_message()
334
586
  script = @script
335
587
  return <<END
336
- #{script} -- download files from public CDN
588
+ #{script} -- download files from public CDN (cdnjs/jsdelivr/unpkg/google)
337
589
 
338
- Usage: #{script} [options] [CDN] [library] [version] [directory]
590
+ Usage: #{script} [<options>] [<CDN> [<library> [<version> [<directory>]]]]
339
591
 
340
592
  Options:
341
593
  -h, --help : help
342
594
  -v, --version : version
343
595
  -q, --quiet : minimal output
596
+ --debug : (debug mode)
344
597
 
345
598
  Example:
346
- $ #{script} # list public CDN
599
+ $ #{script} # list public CDN names
347
600
  $ #{script} [-q] cdnjs # list libraries
601
+ $ #{script} [-q] cdnjs 'jquery*' # search libraries
348
602
  $ #{script} [-q] cdnjs jquery # list versions
349
- $ #{script} [-q] cdnjs jquery 2.2.0 # list files
350
- $ #{script} [-q] cdnjs jquery 2.2.0 /tmp # download files
603
+ $ #{script} [-q] cdnjs jquery latest # show latest version
604
+ $ #{script} [-q] cdnjs jquery 3.6.0 # list files
605
+ $ #{script} [-q] cdnjs jquery 3.6.0 /tmp # download files into directory
606
+
351
607
  END
352
608
  end
353
609
 
@@ -362,10 +618,11 @@ END
362
618
  end
363
619
 
364
620
  def run(*args)
365
- cmdopts = parse_cmdopts(args, "hvq", ["help", "version", "quiet"])
621
+ cmdopts = parse_cmdopts(args, "hvq", ["help", "version", "quiet", "debug"])
366
622
  return help_message() if cmdopts['h'] || cmdopts['help']
367
623
  return RELEASE + "\n" if cmdopts['v'] || cmdopts['version']
368
624
  @quiet = cmdopts['quiet'] || cmdopts['q']
625
+ @debug_mode = cmdopts['debug']
369
626
  #
370
627
  validate(args[1], args[2])
371
628
  #
@@ -377,11 +634,9 @@ END
377
634
  return do_list_libraries(cdn_code)
378
635
  when 2
379
636
  cdn_code, library = args
380
- if library.include?('*')
381
- return do_search_libraries(cdn_code, library)
382
- else
383
- return do_find_library(cdn_code, library)
384
- end
637
+ return library.include?('*') \
638
+ ? do_search_libraries(cdn_code, library) \
639
+ : do_find_library(cdn_code, library)
385
640
  when 3
386
641
  cdn_code, library, version = args
387
642
  return do_get_library(cdn_code, library, version)
@@ -435,35 +690,29 @@ END
435
690
  def find_cdn(cdn_code)
436
691
  klass = CLASSES.find {|c| c::CODE == cdn_code } or
437
692
  raise CommandError.new("#{cdn_code}: no such CDN.")
438
- return klass.new
693
+ return klass.new(debug_mode: @debug_mode)
439
694
  end
440
695
 
441
696
  def render_list(list)
442
- if @quiet
443
- return list.collect {|d| "#{d[:name]}\n" }.join()
444
- else
445
- return list.collect {|d| "%-20s # %s\n" % [d[:name], d[:desc]] }.join()
446
- end
697
+ return list.collect {|d| "#{d[:name]}\n" }.join() if @quiet
698
+ return list.collect {|d| "%-20s # %s\n" % [d[:name], d[:desc]] }.join()
447
699
  end
448
700
 
449
- def do_list_cdns
450
- if @quiet
451
- return CLASSES.map {|c| "#{c::CODE}\n" }.join()
452
- else
453
- return CLASSES.map {|c| "%-10s # %s\n" % [c::CODE, c::SITE_URL] }.join()
454
- end
701
+ def do_list_cdns()
702
+ return CLASSES.map {|c| "#{c::CODE}\n" }.join() if @quiet
703
+ return CLASSES.map {|c| "%-10s # %s\n" % [c::CODE, c::SITE_URL] }.join()
455
704
  end
456
705
 
457
706
  def do_list_libraries(cdn_code)
458
707
  cdn = find_cdn(cdn_code)
459
- return render_list(cdn.list)
708
+ list = cdn.list() or
709
+ raise CommandError.new("#{cdn_code}: cannot list libraries; please specify pattern such as 'jquery*'.")
710
+ return render_list(list)
460
711
  end
461
712
 
462
713
  def do_search_libraries(cdn_code, pattern)
463
714
  cdn = find_cdn(cdn_code)
464
- rexp_str = pattern.split('*', -1).collect {|x| Regexp.escape(x) }.join('.*')
465
- rexp = Regexp.compile("\\A#{rexp_str}\\z", Regexp::IGNORECASE)
466
- return render_list(cdn.list.select {|a| a[:name] =~ rexp })
715
+ return render_list(cdn.search(pattern))
467
716
  end
468
717
 
469
718
  def do_find_library(cdn_code, library)
@@ -475,10 +724,12 @@ END
475
724
  s << "#{ver}\n"
476
725
  end if d[:versions]
477
726
  else
478
- s << "name: #{d[:name]}\n"
479
- s << "desc: #{d[:desc]}\n" if d[:desc]
480
- s << "tags: #{d[:tags]}\n" if d[:tags]
481
- s << "site: #{d[:site]}\n" if d[:site]
727
+ s << "name: #{d[:name]}\n"
728
+ s << "desc: #{d[:desc]}\n" if d[:desc]
729
+ s << "tags: #{d[:tags]}\n" if d[:tags]
730
+ s << "site: #{d[:site]}\n" if d[:site]
731
+ s << "info: #{d[:info]}\n" if d[:info]
732
+ s << "license: #{d[:license]}\n" if d[:license]
482
733
  s << "snippet: |\n" << d[:snippet].gsub(/^/, ' ') if d[:snippet]
483
734
  s << "versions:\n"
484
735
  d[:versions].each do |ver|
@@ -490,6 +741,7 @@ END
490
741
 
491
742
  def do_get_library(cdn_code, library, version)
492
743
  cdn = find_cdn(cdn_code)
744
+ version = _latest_version(cdn, library) if version == 'latest'
493
745
  d = cdn.get(library, version)
494
746
  s = ""
495
747
  if @quiet
@@ -499,9 +751,13 @@ END
499
751
  else
500
752
  s << "name: #{d[:name]}\n"
501
753
  s << "version: #{d[:version]}\n"
502
- s << "desc: #{d[:desc]}\n" if d[:desc]
503
- s << "tags: #{d[:tags]}\n" if d[:tags]
504
- s << "site: #{d[:site]}\n" if d[:site]
754
+ s << "desc: #{d[:desc]}\n" if d[:desc]
755
+ s << "tags: #{d[:tags]}\n" if d[:tags]
756
+ s << "site: #{d[:site]}\n" if d[:site]
757
+ s << "info: #{d[:info]}\n" if d[:info]
758
+ s << "npmpkg: #{d[:npmpkg]}\n" if d[:npmpkg]
759
+ s << "default: #{d[:default]}\n" if d[:default]
760
+ s << "license: #{d[:license]}\n" if d[:license]
505
761
  s << "snippet: |\n" << d[:snippet].gsub(/^/, ' ') if d[:snippet]
506
762
  s << "urls:\n" if d[:urls]
507
763
  d[:urls].each do |url|
@@ -513,10 +769,18 @@ END
513
769
 
514
770
  def do_download_library(cdn_code, library, version, basedir)
515
771
  cdn = find_cdn(cdn_code)
772
+ version = _latest_version(cdn, library) if version == 'latest'
516
773
  cdn.download(library, version, basedir, quiet: @quiet)
517
774
  return nil
518
775
  end
519
776
 
777
+ private
778
+
779
+ def _latest_version(cdn, library)
780
+ d = cdn.find(library)
781
+ return d[:versions].first
782
+ end
783
+
520
784
  end
521
785
 
522
786