cdnget 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/MIT-LICENSE +21 -0
- data/README.md +45 -0
- data/Rakefile +144 -0
- data/bin/cdnget +530 -0
- data/cdnget.gemspec +28 -0
- data/lib/cdnget.rb +530 -0
- data/test/cdnget_test.rb +471 -0
- metadata +80 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 9204fce30417104ddfeb1a49af1c1e770c630cc1
|
4
|
+
data.tar.gz: 1e9dc0c2d1fba34882a0dd5c6fc26a8ccc6edb57
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 087992a00501a6d1f3dd179a741b708eed1c80266666c0a61f8465325d2c7f0f7592ecc8015c3edb7e0987613a811e92fba0e4b7ecdb058a54b68f3f8a0b6f43
|
7
|
+
data.tar.gz: 1bed28d10dc90f2e2ab241178dc74a9762a866efe5860f31c2eab01a4f6377978653756932ddd8a2a170bbd9061fa3a77a78ecb5493dbf05b48ce0fdf76a27ce
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
$Copyright: copyright(c) 2016 kuwata-lab.com all rights reserved $
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
CDNget
|
2
|
+
======
|
3
|
+
|
4
|
+
($Release: 0.1.0 $)
|
5
|
+
|
6
|
+
CDNget is a utility script to download files from CDNJS, jsDelivr or Google.
|
7
|
+
|
8
|
+
|
9
|
+
Install
|
10
|
+
-------
|
11
|
+
|
12
|
+
$ gem install cdnget
|
13
|
+
|
14
|
+
Or:
|
15
|
+
|
16
|
+
$ curl -sO https://github.com/kwatch/cdnget/raw/ruby-release/bin/cdnget
|
17
|
+
$ chmod a+x cdnget
|
18
|
+
$ sudo cp cdnget /usr/local/bin
|
19
|
+
|
20
|
+
CDNget is implemented in Ruby and requires Ruby >= 2.0.
|
21
|
+
|
22
|
+
|
23
|
+
Usage
|
24
|
+
-----
|
25
|
+
|
26
|
+
$ cdnget # list CDN (cdnjs/jsdelivr/google)
|
27
|
+
$ cdnget [-q] cdnjs # list libraries
|
28
|
+
$ cdnget [-q] cdnjs '*jquery*' # search libraries
|
29
|
+
$ cdnget [-q] cdnjs jquery # list library versions
|
30
|
+
$ cdnget [-q] cdnjs jquery 2.2.0 # list library files
|
31
|
+
$ cdnget [-q] cdnjs jquery 2.2.0 /tmp # download files
|
32
|
+
/tmp/jquery/2.2.0/jquery.js ... Done (258,388 byte)
|
33
|
+
/tmp/jquery/2.2.0/jquery.min.js ... Done (85,589 byte)
|
34
|
+
/tmp/jquery/2.2.0/jquery.min.map ... Done (129,544 byte)
|
35
|
+
|
36
|
+
$ ls /tmp/jquery/2.2.0
|
37
|
+
jquery.js jquery.min.js jquery.min.map
|
38
|
+
|
39
|
+
|
40
|
+
Copyright and License
|
41
|
+
---------------------
|
42
|
+
|
43
|
+
$Copyright: copyright(c) 2016 kuwata-lab.com all rights reserved $
|
44
|
+
|
45
|
+
$License: MIT License $
|
data/Rakefile
ADDED
@@ -0,0 +1,144 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
###
|
4
|
+
|
5
|
+
RELEASE = '$Release: 0.1.0 $'.split()[1]
|
6
|
+
COPYRIGHT = 'copyright(c) 2016 kuwata-lab.com all rights reserved'
|
7
|
+
LICENSE = 'MIT License'
|
8
|
+
|
9
|
+
PROJECT = 'cdnget'
|
10
|
+
|
11
|
+
|
12
|
+
### helpers
|
13
|
+
|
14
|
+
def run(command)
|
15
|
+
print '[rake]$ '; sh command
|
16
|
+
end
|
17
|
+
|
18
|
+
def edit_content(content, release=RELEASE)
|
19
|
+
s = content
|
20
|
+
s = s.gsub /\$Release\:.*?\$/, "$Release\: #{release} $"
|
21
|
+
s = s.gsub /\$Copyright\:.*?\$/, "$Copyright\: #{COPYRIGHT} $"
|
22
|
+
s = s.gsub /\$License\:.*?\$/, "$License\: #{LICENSE} $"
|
23
|
+
s = s.gsub /\$Release\$/, release
|
24
|
+
s = s.gsub /\$Copyright\$/, COPYRIGHT
|
25
|
+
s = s.gsub /\$License\$/, LICENSE
|
26
|
+
s
|
27
|
+
end
|
28
|
+
|
29
|
+
def load_gemspec
|
30
|
+
spec = eval File.read("#{PROJECT}.gemspec", encoding: 'utf-8')
|
31
|
+
return spec
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
### tasks
|
36
|
+
|
37
|
+
|
38
|
+
#task :default => :test
|
39
|
+
|
40
|
+
desc "run test scripts"
|
41
|
+
task :test do
|
42
|
+
#sh "ruby -r minitest/autorun test/*_test.rb"
|
43
|
+
run "ruby test/#{PROJECT}_test.rb"
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
desc "remove *.rbc"
|
48
|
+
task :clean do
|
49
|
+
rm_f [Dir.glob("lib/**/*.rbc"), Dir.glob("test/**/*.rbc")]
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
desc "how to release"
|
54
|
+
task :howtorelease do
|
55
|
+
rel = ENV['rel'] || RELEASE
|
56
|
+
ver = rel.sub(/\.\d+$/,'')
|
57
|
+
patch_zero = !! (rel =~ /\.0$/)
|
58
|
+
puts <<END
|
59
|
+
$ git branch | grep '\*'
|
60
|
+
* ruby-dev
|
61
|
+
$ git checkout #{patch_zero ? '-b ': ''}ruby-#{ver}
|
62
|
+
$ rake edit rel=#{rel}
|
63
|
+
$ git diff
|
64
|
+
$ git commit -a -m "ruby: release preparation for #{rel}"
|
65
|
+
$ rake package
|
66
|
+
$ rake publish
|
67
|
+
END
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
desc "create 'bin/cdnget'"
|
72
|
+
task :bin do
|
73
|
+
material = "lib/cdnget.rb"
|
74
|
+
product = "bin/cdnget"
|
75
|
+
run %Q`cat #{material} | awk '{c=""}/if __FILE__/{f=1;c="#"}f&&/end/{c="#"}{print c $0}' > #{product}`
|
76
|
+
run %Q`chmod a+x #{product}`
|
77
|
+
end
|
78
|
+
|
79
|
+
|
80
|
+
desc "edit '$Release\:...$' in files"
|
81
|
+
task :edit => :bin do
|
82
|
+
rel = ENV['rel']
|
83
|
+
unless rel
|
84
|
+
$stderr.puts "*** Release number is not speicified"
|
85
|
+
$stderr.puts "*** Usage: rake edit rel=X.X.X"
|
86
|
+
raise StandardError
|
87
|
+
end
|
88
|
+
spec = load_gemspec()
|
89
|
+
spec.files.each do |fpath|
|
90
|
+
if fpath =~ /\.(gz|jpg|png|form)\z/
|
91
|
+
puts "[ignore] #{fpath}"
|
92
|
+
next
|
93
|
+
end
|
94
|
+
s1 = File.open(fpath, encoding: 'utf-8') {|f| f.read() }
|
95
|
+
s2 = edit_content(s1, rel)
|
96
|
+
if s1 == s2
|
97
|
+
puts "[skip] #{fpath}"
|
98
|
+
else
|
99
|
+
puts "[Edit] #{fpath}"
|
100
|
+
File.open(fpath, 'w', encoding: 'utf-8') {|f| f.write(s2) }
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
|
106
|
+
desc "create rubygem pacakge"
|
107
|
+
task :package do
|
108
|
+
spec = load_gemspec()
|
109
|
+
name = spec.name
|
110
|
+
ver = spec.version
|
111
|
+
run "gem build #{name}.gemspec"
|
112
|
+
puts ""
|
113
|
+
puts "## files"
|
114
|
+
puts spec.files.collect {|x| "- #{x}" }
|
115
|
+
end
|
116
|
+
|
117
|
+
|
118
|
+
desc "release gem"
|
119
|
+
task :publish do
|
120
|
+
diff = `git diff --stat`
|
121
|
+
if diff && ! diff.empty?
|
122
|
+
raise "[ERROR] Please commit changes before publishing gem."
|
123
|
+
end
|
124
|
+
#
|
125
|
+
spec = load_gemspec()
|
126
|
+
name = spec.name
|
127
|
+
ver = spec.version
|
128
|
+
#
|
129
|
+
gem = "#{name}-#{ver}.gem"
|
130
|
+
File.exist?(gem) or
|
131
|
+
raise "[ERROR] run 'rake package' before publishing gem."
|
132
|
+
mtime = File.mtime(gem)
|
133
|
+
! spec.files.any? {|fpath| File.mtime(fpath) >= mtime } or
|
134
|
+
raise "[ERROR] run 'rake package' again because gem file seems obsoleted."
|
135
|
+
#
|
136
|
+
print "Upload #{gem}. OK? [y/N]: "
|
137
|
+
if $stdin.gets() =~ /[yY]/
|
138
|
+
run "gem push #{gem}"
|
139
|
+
run "git push -u origin `git rev-parse --abbrev-ref HEAD`"
|
140
|
+
run "git push -f origin ruby-release"
|
141
|
+
run "git tag ruby-#{ver}"
|
142
|
+
run "git push --tags"
|
143
|
+
end
|
144
|
+
end
|
data/bin/cdnget
ADDED
@@ -0,0 +1,530 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
|
4
|
+
##
|
5
|
+
## Download files from CDN (CDNJS, Google, jsDelivr).
|
6
|
+
##
|
7
|
+
## - CDNJS (https://cdnjs.com/)
|
8
|
+
## - Google (https://developers.google.com/speed/libraries/)
|
9
|
+
## - jsDelivr (https://www.jsdelivr.com/)
|
10
|
+
##
|
11
|
+
## Example:
|
12
|
+
## $ cdnget # list public CDN
|
13
|
+
## $ cdnget [-q] cdnjs # list libraries
|
14
|
+
## $ 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
|
17
|
+
##
|
18
|
+
|
19
|
+
require 'open-uri'
|
20
|
+
require 'json'
|
21
|
+
require 'fileutils'
|
22
|
+
|
23
|
+
|
24
|
+
module CDNGet
|
25
|
+
|
26
|
+
|
27
|
+
RELEASE = '$Release: 0.1.0 $'.split()[1]
|
28
|
+
|
29
|
+
CLASSES = []
|
30
|
+
|
31
|
+
|
32
|
+
class Base
|
33
|
+
|
34
|
+
def self.inherited(klass)
|
35
|
+
CLASSES << klass
|
36
|
+
end
|
37
|
+
|
38
|
+
def list()
|
39
|
+
raise NotImplementedError.new("#{self.class.name}#list(): not implemented yet.")
|
40
|
+
end
|
41
|
+
|
42
|
+
def find(library)
|
43
|
+
raise NotImplementedError.new("#{self.class.name}#find(): not implemented yet.")
|
44
|
+
end
|
45
|
+
|
46
|
+
def get(library, version)
|
47
|
+
raise NotImplementedError.new("#{self.class.name}#get(): not implemented yet.")
|
48
|
+
end
|
49
|
+
|
50
|
+
def download(library, version, basedir=".", quiet: false)
|
51
|
+
validate(library, version)
|
52
|
+
File.exist?(basedir) or
|
53
|
+
raise CommandError.new("#{basedir}: not exist.")
|
54
|
+
File.directory?(basedir) or
|
55
|
+
raise CommandError.new("#{basedir}: not a directory.")
|
56
|
+
target_dir = File.join(basedir, library, version)
|
57
|
+
d = get(library, version)
|
58
|
+
d[:files].each do |file|
|
59
|
+
filepath = File.join(target_dir, file)
|
60
|
+
dirpath = File.dirname(filepath)
|
61
|
+
print "#{filepath} ..." unless quiet
|
62
|
+
url = File.join(d[:baseurl], file) # not use URI.join!
|
63
|
+
content = fetch(url)
|
64
|
+
content = content.force_encoding('ascii-8bit')
|
65
|
+
print " Done (#{format_integer(content.bytesize)} byte)" unless quiet
|
66
|
+
FileUtils.mkdir_p(dirpath) unless File.exist?(dirpath)
|
67
|
+
unchanged = File.exist?(filepath) && File.read(filepath, mode: 'rb') == content
|
68
|
+
if unchanged
|
69
|
+
print " (Unchanged)" unless quiet
|
70
|
+
else
|
71
|
+
File.open(filepath, 'wb') {|f| f.write(content) }
|
72
|
+
end
|
73
|
+
puts() unless quiet
|
74
|
+
end
|
75
|
+
nil
|
76
|
+
end
|
77
|
+
|
78
|
+
protected
|
79
|
+
|
80
|
+
def fetch(url, library=nil)
|
81
|
+
begin
|
82
|
+
html = open(url, 'rb') {|f| f.read() }
|
83
|
+
return html
|
84
|
+
rescue OpenURI::HTTPError => ex
|
85
|
+
raise CommandError.new("GET #{url} : #{ex.message}")
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def validate(library, version)
|
90
|
+
if library
|
91
|
+
library =~ /\A[-.\w]+\z/ or
|
92
|
+
raise ArgumentError.new("#{library.inspect}: Unexpected library name.")
|
93
|
+
end
|
94
|
+
if version
|
95
|
+
version =~ /\A\d+(\.\d+)+([-.\w]+)?/ or
|
96
|
+
raise ArgumentError.new("#{version.inspect}: Unexpected version number.")
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def format_integer(value)
|
101
|
+
return value.to_s.reverse.scan(/..?.?/).collect {|s| s.reverse }.reverse.join(',')
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
|
106
|
+
|
107
|
+
class HttpError < StandardError
|
108
|
+
end
|
109
|
+
|
110
|
+
|
111
|
+
class CDNJS < Base # TODO: use jsdelivr api
|
112
|
+
CODE = "cdnjs"
|
113
|
+
SITE_URL = 'https://cdnjs.com/'
|
114
|
+
|
115
|
+
def fetch(url, library=nil)
|
116
|
+
begin
|
117
|
+
html = open(url, 'rb') {|f| f.read() }
|
118
|
+
if library
|
119
|
+
html =~ /<h1\b.*?>(.*?)<\/h1>/ or
|
120
|
+
raise CommandError.new("#{library}: Library not found.")
|
121
|
+
library == $1.strip() or
|
122
|
+
raise CommandError.new("#{library}: Library not found (maybe '#{$1.strip()}'?).")
|
123
|
+
end
|
124
|
+
return html
|
125
|
+
rescue OpenURI::HTTPError => ex
|
126
|
+
raise HttpError.new("GET #{url} : #{ex.message}")
|
127
|
+
end
|
128
|
+
end
|
129
|
+
protected :fetch
|
130
|
+
|
131
|
+
def list
|
132
|
+
libs = []
|
133
|
+
html = fetch("https://cdnjs.com/libraries")
|
134
|
+
html.scan(/<td\b(.*?)<\/td>/m) do |str,|
|
135
|
+
name = desc = nil
|
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
|
140
|
+
return libs.sort_by {|d| d[:name] }.uniq
|
141
|
+
end
|
142
|
+
|
143
|
+
def find(library)
|
144
|
+
validate(library, nil)
|
145
|
+
html = fetch("https://cdnjs.com/libraries/#{library}", library)
|
146
|
+
versions = []
|
147
|
+
html.scan(/<option value="([^"]+)" *(?:selected)?>/) do |ver,|
|
148
|
+
versions << ver
|
149
|
+
end
|
150
|
+
desc = tags = nil
|
151
|
+
if html =~ /<p>(.*?)<\/p>\s*<em>(.*?)<\/em>/
|
152
|
+
desc = $1
|
153
|
+
tags = $2
|
154
|
+
end
|
155
|
+
return {
|
156
|
+
name: library,
|
157
|
+
desc: desc,
|
158
|
+
tags: tags,
|
159
|
+
versions: versions,
|
160
|
+
}
|
161
|
+
end
|
162
|
+
|
163
|
+
def get(library, version)
|
164
|
+
validate(library, version)
|
165
|
+
html = fetch("https://cdnjs.com/libraries/#{library}/#{version}", library)
|
166
|
+
baseurl = "https://cdnjs.cloudflare.com/ajax/libs/#{library}/#{version}"
|
167
|
+
urls = []
|
168
|
+
files = []
|
169
|
+
rexp = %r`>(#{Regexp.escape(baseurl)}/([^<]+))<\/p>`
|
170
|
+
html.scan(%r`>(#{Regexp.escape(baseurl)}/([^<]+))<\/p>`) do |url, file|
|
171
|
+
urls << url .gsub(///, '/')
|
172
|
+
files << file.gsub(///, '/')
|
173
|
+
end
|
174
|
+
return {
|
175
|
+
name: library,
|
176
|
+
version: version,
|
177
|
+
urls: urls,
|
178
|
+
files: files,
|
179
|
+
baseurl: baseurl,
|
180
|
+
}
|
181
|
+
end
|
182
|
+
|
183
|
+
end
|
184
|
+
|
185
|
+
|
186
|
+
class JSDelivr < Base
|
187
|
+
CODE = "jsdelivr"
|
188
|
+
SITE_URL = "https://www.jsdelivr.com/"
|
189
|
+
API_URL = "https://api.jsdelivr.com/v1/jsdelivr/libraries"
|
190
|
+
|
191
|
+
def list
|
192
|
+
json = fetch("#{API_URL}?fields=name,description,homepage")
|
193
|
+
arr = JSON.load(json)
|
194
|
+
return arr.collect {|d|
|
195
|
+
{name: d["name"], desc: d["description"], site: d["homepage"] }
|
196
|
+
}
|
197
|
+
end
|
198
|
+
|
199
|
+
def find(library)
|
200
|
+
validate(library, nil)
|
201
|
+
json = fetch("#{API_URL}?name=#{library}&fields=name,description,homepage,versions")
|
202
|
+
arr = JSON.load(json)
|
203
|
+
d = arr.first or
|
204
|
+
raise CommandError.new("#{library}: Library not found.")
|
205
|
+
return {
|
206
|
+
name: d['name'],
|
207
|
+
desc: d['description'],
|
208
|
+
site: d['homepage'],
|
209
|
+
versions: d['versions'],
|
210
|
+
}
|
211
|
+
end
|
212
|
+
|
213
|
+
def get(library, version)
|
214
|
+
validate(library, version)
|
215
|
+
baseurl = "https://cdn.jsdelivr.net/#{library}/#{version}"
|
216
|
+
url = "#{API_URL}/#{library}/#{version}"
|
217
|
+
json = fetch("#{API_URL}/#{library}/#{version}")
|
218
|
+
files = JSON.load(json)
|
219
|
+
! files.empty? or
|
220
|
+
raise CommandError.new("#{library}: Library not found.")
|
221
|
+
urls = files.collect {|x| "#{baseurl}/#{x}" }
|
222
|
+
return {
|
223
|
+
name: library,
|
224
|
+
version: version,
|
225
|
+
urls: urls,
|
226
|
+
files: files,
|
227
|
+
baseurl: baseurl,
|
228
|
+
}
|
229
|
+
end
|
230
|
+
|
231
|
+
end
|
232
|
+
|
233
|
+
|
234
|
+
class GoogleCDN < Base # TODO: use jsdelivr api
|
235
|
+
CODE = "google"
|
236
|
+
SITE_URL = 'https://developers.google.com/speed/libraries/'
|
237
|
+
|
238
|
+
def list
|
239
|
+
libs = []
|
240
|
+
html = fetch("https://developers.google.com/speed/libraries/")
|
241
|
+
rexp = %r`"https://ajax\.googleapis\.com/ajax/libs/([^/]+)/([^/]+)/([^"]+)"`
|
242
|
+
html.scan(rexp) do |lib, ver, file|
|
243
|
+
libs << {name: lib, desc: "latest version: #{ver}" }
|
244
|
+
end
|
245
|
+
return libs.sort_by {|d| d[:name] }.uniq
|
246
|
+
end
|
247
|
+
|
248
|
+
def find(library)
|
249
|
+
validate(library, nil)
|
250
|
+
html = fetch("https://developers.google.com/speed/libraries/")
|
251
|
+
rexp = %r`"https://ajax\.googleapis\.com/ajax/libs/#{library}/`
|
252
|
+
site_url = nil
|
253
|
+
versions = []
|
254
|
+
urls = []
|
255
|
+
found = false
|
256
|
+
html.scan(/<h3\b.*?>.*?<\/h3>\s*<dl>(.*?)<\/dl>/m) do |text,|
|
257
|
+
if text =~ rexp
|
258
|
+
found = true
|
259
|
+
if text =~ /<dt>.*?snippet:<\/dt>\s*<dd>(.*?)<\/dd>/m
|
260
|
+
s = $1
|
261
|
+
s.scan(/\b(?:src|href)="([^"]*?)"/) do |href,|
|
262
|
+
urls << href
|
263
|
+
end
|
264
|
+
end
|
265
|
+
if text =~ /<dt>site:<\/dt>\s*<dd>(.*?)<\/dd>/m
|
266
|
+
s = $1
|
267
|
+
if s =~ %r`href="([^"]+)"`
|
268
|
+
site_url = $1
|
269
|
+
end
|
270
|
+
end
|
271
|
+
text.scan(/<dt>(?:stable |unstable )?versions:<\/dt>\s*<dd\b.*?>(.*?)<\/dd>/m) do
|
272
|
+
s = $1
|
273
|
+
vers = s.split(/,/).collect {|x| x.strip() }
|
274
|
+
versions.concat(vers)
|
275
|
+
end
|
276
|
+
break
|
277
|
+
end
|
278
|
+
end
|
279
|
+
found or
|
280
|
+
raise CommandError.new("#{library}: Library not found.")
|
281
|
+
return {
|
282
|
+
name: library,
|
283
|
+
site: site_url,
|
284
|
+
urls: urls,
|
285
|
+
versions: versions,
|
286
|
+
}
|
287
|
+
end
|
288
|
+
|
289
|
+
def get(library, version)
|
290
|
+
validate(library, version)
|
291
|
+
d = find(library)
|
292
|
+
d[:versions].find(version) or
|
293
|
+
raise CommandError.new("#{version}: No such version of #{library}.")
|
294
|
+
urls = d[:urls]
|
295
|
+
if urls
|
296
|
+
rexp = /(\/libs\/#{library})\/[^\/]+/
|
297
|
+
urls = urls.collect {|x| x.gsub(rexp, "\\1/#{version}") }
|
298
|
+
end
|
299
|
+
baseurl = "https://ajax.googleapis.com/ajax/libs/#{library}/#{version}"
|
300
|
+
files = urls ? urls.collect {|x| x[baseurl.length..-1] } : nil
|
301
|
+
return {
|
302
|
+
name: d[:name],
|
303
|
+
site: d[:site],
|
304
|
+
urls: urls,
|
305
|
+
files: files,
|
306
|
+
baseurl: baseurl,
|
307
|
+
version: version,
|
308
|
+
}
|
309
|
+
end
|
310
|
+
|
311
|
+
end
|
312
|
+
|
313
|
+
|
314
|
+
#class JQueryCDN < Base
|
315
|
+
# CODE = "jquery"
|
316
|
+
# SITE_URL = 'https://code.jquery.com/'
|
317
|
+
#end
|
318
|
+
|
319
|
+
|
320
|
+
#class ASPNetCDN < Base
|
321
|
+
# CODE = "aspnet"
|
322
|
+
# SITE_URL = 'https://www.asp.net/ajax/cdn/'
|
323
|
+
#end
|
324
|
+
|
325
|
+
|
326
|
+
class CommandError < StandardError
|
327
|
+
end
|
328
|
+
|
329
|
+
|
330
|
+
class Main
|
331
|
+
|
332
|
+
def initialize(script=nil)
|
333
|
+
@script = script || File.basename($0)
|
334
|
+
end
|
335
|
+
|
336
|
+
def help_message
|
337
|
+
script = @script
|
338
|
+
return <<END
|
339
|
+
#{script} -- download files from public CDN
|
340
|
+
|
341
|
+
Usage: #{script} [options] [CDN] [library] [version] [directory]
|
342
|
+
|
343
|
+
Options:
|
344
|
+
-h, --help : help
|
345
|
+
-v, --version : version
|
346
|
+
-q, --quiet : minimal output
|
347
|
+
|
348
|
+
Example:
|
349
|
+
$ #{script} # list public CDN
|
350
|
+
$ #{script} [-q] cdnjs # list libraries
|
351
|
+
$ #{script} [-q] cdnjs jquery # list versions
|
352
|
+
$ #{script} [-q] cdnjs jquery 2.2.0 # list files
|
353
|
+
$ #{script} [-q] cdnjs jquery 2.2.0 /tmp # download files
|
354
|
+
END
|
355
|
+
end
|
356
|
+
|
357
|
+
def self.main(args=nil)
|
358
|
+
args ||= ARGV
|
359
|
+
s = self.new().run(*args)
|
360
|
+
puts s if s
|
361
|
+
exit 0
|
362
|
+
rescue CommandError => ex
|
363
|
+
$stderr.puts ex.message
|
364
|
+
exit 1
|
365
|
+
end
|
366
|
+
|
367
|
+
def run(*args)
|
368
|
+
cmdopts = parse_cmdopts(args, "hvq", ["help", "version", "quiet"])
|
369
|
+
return help_message() if cmdopts['h'] || cmdopts['help']
|
370
|
+
return RELEASE + "\n" if cmdopts['v'] || cmdopts['version']
|
371
|
+
@quiet = cmdopts['quiet'] || cmdopts['q']
|
372
|
+
#
|
373
|
+
validate(args[1], args[2])
|
374
|
+
#
|
375
|
+
case args.length
|
376
|
+
when 0
|
377
|
+
return do_list_cdns()
|
378
|
+
when 1
|
379
|
+
cdn_code = args[0]
|
380
|
+
return do_list_libraries(cdn_code)
|
381
|
+
when 2
|
382
|
+
cdn_code, library = args
|
383
|
+
if library.include?('*')
|
384
|
+
return do_search_libraries(cdn_code, library)
|
385
|
+
else
|
386
|
+
return do_find_library(cdn_code, library)
|
387
|
+
end
|
388
|
+
when 3
|
389
|
+
cdn_code, library, version = args
|
390
|
+
return do_get_library(cdn_code, library, version)
|
391
|
+
when 4
|
392
|
+
cdn_code, library, version, basedir = args
|
393
|
+
do_download_library(cdn_code, library, version, basedir)
|
394
|
+
return ""
|
395
|
+
else
|
396
|
+
raise CommandError.new("'#{args[4]}': Too many arguments.")
|
397
|
+
end
|
398
|
+
end
|
399
|
+
|
400
|
+
def validate(library, version)
|
401
|
+
if library && ! library.include?('*')
|
402
|
+
library =~ /\A[-.\w]+\z/ or
|
403
|
+
raise CommandError.new("#{library}: Unexpected library name.")
|
404
|
+
end
|
405
|
+
if version
|
406
|
+
version =~ /\A[-.\w]+\z/ or
|
407
|
+
raise CommandError.new("#{version}: Unexpected version number.")
|
408
|
+
end
|
409
|
+
end
|
410
|
+
|
411
|
+
def parse_cmdopts(cmdargs, short_opts, long_opts)
|
412
|
+
cmdopts = {}
|
413
|
+
while cmdargs[0] && cmdargs[0].start_with?('-')
|
414
|
+
cmdarg = cmdargs.shift
|
415
|
+
if cmdarg == '--'
|
416
|
+
break
|
417
|
+
elsif cmdarg.start_with?('--')
|
418
|
+
cmdarg =~ /\A--(\w[-\w]+)(=.*?)?/ or
|
419
|
+
raise CommandError.new("#{cmdarg}: invalid command option.")
|
420
|
+
name = $1
|
421
|
+
value = $2 ? $2[1..-1] : true
|
422
|
+
long_opts.include?(name) or
|
423
|
+
raise CommandError.new("#{cmdarg}: unknown command option.")
|
424
|
+
cmdopts[name] = value
|
425
|
+
elsif cmdarg.start_with?('-')
|
426
|
+
cmdarg[1..-1].each_char do |c|
|
427
|
+
short_opts.include?(c) or
|
428
|
+
raise CommandError.new("-#{c}: unknown command option.")
|
429
|
+
cmdopts[c] = true
|
430
|
+
end
|
431
|
+
else
|
432
|
+
raise "unreachable"
|
433
|
+
end
|
434
|
+
end
|
435
|
+
return cmdopts
|
436
|
+
end
|
437
|
+
|
438
|
+
def find_cdn(cdn_code)
|
439
|
+
klass = CLASSES.find {|c| c::CODE == cdn_code } or
|
440
|
+
raise CommandError.new("#{cdn_code}: no such CDN.")
|
441
|
+
return klass.new
|
442
|
+
end
|
443
|
+
|
444
|
+
def render_list(list)
|
445
|
+
if @quiet
|
446
|
+
return list.collect {|d| "#{d[:name]}\n" }.join()
|
447
|
+
else
|
448
|
+
return list.collect {|d| "%-20s # %s\n" % [d[:name], d[:desc]] }.join()
|
449
|
+
end
|
450
|
+
end
|
451
|
+
|
452
|
+
def do_list_cdns
|
453
|
+
if @quiet
|
454
|
+
return CLASSES.map {|c| "#{c::CODE}\n" }.join()
|
455
|
+
else
|
456
|
+
return CLASSES.map {|c| "%-10s # %s\n" % [c::CODE, c::SITE_URL] }.join()
|
457
|
+
end
|
458
|
+
end
|
459
|
+
|
460
|
+
def do_list_libraries(cdn_code)
|
461
|
+
cdn = find_cdn(cdn_code)
|
462
|
+
return render_list(cdn.list)
|
463
|
+
end
|
464
|
+
|
465
|
+
def do_search_libraries(cdn_code, pattern)
|
466
|
+
cdn = find_cdn(cdn_code)
|
467
|
+
rexp_str = pattern.split('*', -1).collect {|x| Regexp.escape(x) }.join('.*')
|
468
|
+
rexp = Regexp.compile("\\A#{rexp_str}\\z", Regexp::IGNORECASE)
|
469
|
+
return render_list(cdn.list.select {|a| a[:name] =~ rexp })
|
470
|
+
end
|
471
|
+
|
472
|
+
def do_find_library(cdn_code, library)
|
473
|
+
cdn = find_cdn(cdn_code)
|
474
|
+
d = cdn.find(library)
|
475
|
+
s = ""
|
476
|
+
if @quiet
|
477
|
+
d[:versions].each do |ver|
|
478
|
+
s << "#{ver}\n"
|
479
|
+
end if d[:versions]
|
480
|
+
else
|
481
|
+
s << "name: #{d[:name]}\n"
|
482
|
+
s << "desc: #{d[:desc]}\n" if d[:desc]
|
483
|
+
s << "tags: #{d[:tags]}\n" if d[:tags]
|
484
|
+
s << "site: #{d[:site]}\n" if d[:site]
|
485
|
+
s << "snippet: |\n" << d[:snippet].gsub(/^/, ' ') if d[:snippet]
|
486
|
+
s << "versions:\n"
|
487
|
+
d[:versions].each do |ver|
|
488
|
+
s << " - #{ver}\n"
|
489
|
+
end if d[:versions]
|
490
|
+
end
|
491
|
+
return s
|
492
|
+
end
|
493
|
+
|
494
|
+
def do_get_library(cdn_code, library, version)
|
495
|
+
cdn = find_cdn(cdn_code)
|
496
|
+
d = cdn.get(library, version)
|
497
|
+
s = ""
|
498
|
+
if @quiet
|
499
|
+
d[:urls].each do |url|
|
500
|
+
s << "#{url}\n"
|
501
|
+
end if d[:urls]
|
502
|
+
else
|
503
|
+
s << "name: #{d[:name]}\n"
|
504
|
+
s << "version: #{d[:version]}\n"
|
505
|
+
s << "tags: #{d[:tags]}\n" if d[:tags]
|
506
|
+
s << "site: #{d[:site]}\n" if d[:site]
|
507
|
+
s << "snippet: |\n" << d[:snippet].gsub(/^/, ' ') if d[:snippet]
|
508
|
+
s << "urls:\n" if d[:urls]
|
509
|
+
d[:urls].each do |url|
|
510
|
+
s << " - #{url}\n"
|
511
|
+
end if d[:urls]
|
512
|
+
end
|
513
|
+
return s
|
514
|
+
end
|
515
|
+
|
516
|
+
def do_download_library(cdn_code, library, version, basedir)
|
517
|
+
cdn = find_cdn(cdn_code)
|
518
|
+
cdn.download(library, version, basedir, quiet: @quiet)
|
519
|
+
return nil
|
520
|
+
end
|
521
|
+
|
522
|
+
end
|
523
|
+
|
524
|
+
|
525
|
+
end
|
526
|
+
|
527
|
+
|
528
|
+
#if __FILE__ == $0
|
529
|
+
CDNGet::Main.main()
|
530
|
+
#end
|