cdnget 0.3.1 → 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.
data/bin/cdnget 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