ghcurl 0.8.1 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (10) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +25 -0
  3. data/Gemfile +6 -6
  4. data/LICENSE +22 -22
  5. data/README.md +102 -103
  6. data/Rakefile +3 -3
  7. data/bin/ghcurl +584 -525
  8. data/ghcurl.gemspec +40 -0
  9. data/lib/ghcurl.rb +79 -71
  10. metadata +10 -8
data/bin/ghcurl CHANGED
@@ -1,525 +1,584 @@
1
- #!/usr/bin/env ruby
2
- # ------------------------------------------------------
3
- # File : ghcurl.rb
4
- # Authors : ccmywish <ccmywish@qq.com>
5
- # Created on : <2022-04-12>
6
- # Last modified : <2022-04-30>
7
- #
8
- # ghcurl:
9
- #
10
- # Download files and install from Github releases
11
- #
12
- # ------------------------------------------------------
13
-
14
- require 'ghcurl'
15
- require 'highline'
16
- require 'cliswitch'
17
- require 'fileutils'
18
-
19
- module Ghcurl
20
-
21
- WAREHOUSE = File.expand_path("~/.cache/ghcurl")
22
- BIN_PATH = ENV['GHCURL_BIN_PATH'].chomp('/') || "/usr/local/bin"
23
- HL = HighLine.new
24
-
25
- class Error < StandardError; end
26
-
27
-
28
- def bold(str) "\e[1m#{str}\e[0m" end
29
- def green(str) "\e[32m#{str}\e[0m" end
30
- def blue(str) "\e[34m#{str}\e[0m" end
31
-
32
-
33
- def log(msg)
34
- puts blue(bold("ghcurl: #{msg}"))
35
- end
36
-
37
-
38
- #
39
- # @return absolute download path
40
- #
41
- def curl(url, name, to)
42
- if to == nil
43
- to = WAREHOUSE + '/' + name
44
- elsif test 'd', to
45
- to = to.chomp('/') + '/' + name
46
- elsif to.include?('/')
47
- # noop
48
- else
49
- # just a rename in WAREHOUSE
50
- to = WAREHOUSE + '/' + name
51
- end
52
-
53
- cmd = "curl -L #{url} --create-dirs -o #{to}"
54
- status = system cmd
55
- if status == false || status.nil?
56
- log "Download error!"
57
- exit
58
- end
59
- log "Downloaded to #{to}"
60
- return to
61
- end
62
-
63
-
64
- def get_filters
65
-
66
- arches = [
67
- ['amd64', 'x86_64', 'x64'],
68
- ['386', 'i686'],
69
- ['arm64', 'aarch64'],
70
- ['armv6', 'arm'],
71
- ]
72
-
73
- oses = [
74
- ['linux'],
75
- ['freebsd'],
76
- ['mac', 'apple', 'darwin'],
77
- ['windows']
78
- ]
79
-
80
- fs = RUBY_PLATFORM.split('-')
81
- os = ""
82
- arch = ""
83
- fs.each do |f|
84
- case f
85
- when 'linux' then os = 'linux'
86
- when 'mac','macOS' then os = 'mac'
87
- when 'ucrt', 'mingw', 'windows' then os = 'windows'
88
- when 'x86_64' then arch = 'x86_64'
89
- when 'x86' then arch = 'x86'
90
- when 'amd64' then arch = 'amd64'
91
- when 'arm64' then arch = 'arm64'
92
- when 'armv6' then arch = 'armv6'
93
- end
94
- end
95
-
96
- approval, refuse = [], []
97
- # [os, arch].each { approval << _1 unless _1.nil? }
98
-
99
- if os
100
- i = oses.each_with_index do |type, index|
101
- break index if type.include? os
102
- end
103
- if !i.nil?
104
- tmp = oses.dup
105
- tmp.delete_at(i)
106
- refuse.concat tmp.flatten
107
- end
108
- end
109
-
110
-
111
- if arch
112
- i = arches.each_with_index do |type, index|
113
- break index if type.include? arch
114
- end
115
- if !i.nil?
116
- tmp = arches.dup
117
- tmp.delete_at(i)
118
- refuse.concat tmp.flatten
119
- end
120
- end
121
-
122
- # Debug
123
- # puts "=> Approval and refuse"
124
- # p [os, arch]
125
- # p refuse
126
-
127
- # Now we only refuse others
128
- return refuse
129
-
130
- end
131
-
132
-
133
- def download(repo, regexp, version: nil, download_to: nil)
134
-
135
- require 'octokit'
136
-
137
- # adjust repo name : user/repo
138
- if repo =~ /^https:\/\/github.com/
139
- require 'uri'
140
- uri = URI(repo)
141
- # index 1, take 2
142
- repo = uri.path.split('/')[1,2].join('/')
143
- elsif !repo.include?('/')
144
- got_repo = DEFAULT_WARES[repo.to_sym]
145
- if not got_repo
146
- user = HL.ask "Who developed the awesome #{repo}? "
147
- repo = user + '/' + repo
148
- else
149
- repo = got_repo
150
- log "Use the popular repo #{repo}"
151
- end
152
- end
153
-
154
- log "checking..."
155
- begin
156
- unless version
157
- doc = Octokit::Client.new.latest_release(repo)
158
- else
159
- doc = Octokit::Client.new.release_for_tag(repo, 'v' + version)
160
- end
161
- rescue Octokit::NotFound
162
- log "Not found #{repo} v#{version} !"
163
- exit
164
- rescue StandardError => e
165
- log e
166
- exit
167
- end
168
-
169
- links = doc.to_hash[:assets]
170
- if links.empty?
171
- log <<~EOE
172
- The search result is empty, check the args!
173
- repo: #{repo}
174
- version: #{version ? version:'nil'}
175
-
176
- Maybe there's no assets in this release
177
- EOE
178
- exit 0
179
- end
180
-
181
- links = links.map { _1[:browser_download_url] }
182
-
183
-
184
- if regexp
185
- filtered = links.select do
186
- _1 =~ /#{regexp}/
187
- end
188
- else
189
- refuse = get_filters()
190
- filtered = links.select do |l|
191
- refuse.all? do |f|
192
- l !~ /#{f}/
193
- end
194
- end
195
- end
196
-
197
-
198
- if filtered.size == 1
199
- link = filtered[0].split('/').last
200
- else
201
- if filtered.size == 0
202
- links_for_user = links.map { _1.split('/').last }
203
- else
204
- links_for_user = filtered.map { _1.split('/').last }
205
- end
206
- link = HL.choose do |menu|
207
- menu.index_color = :rgb_77bbff
208
- menu.prompt = "Which one do you want to download? "
209
- menu.choices( *links_for_user )
210
- end
211
- end
212
-
213
- url = links[0].split('/')[0..-2].join('/') + '/' + link
214
-
215
- log "Downloading #{url}"
216
-
217
- dl_name = link.split('/').last
218
-
219
- return curl(url, dl_name, download_to)
220
- end
221
-
222
-
223
- #
224
- # This function is a little confusing
225
- #
226
- # target: The absolute path to a downloaded file
227
- # name : The software name
228
- #
229
- def install(target, rename_as: nil, install_to: nil)
230
-
231
- if target.end_with?('.deb')
232
- log "Install the deb package"
233
- system "sudo dpkg -i #{target}"
234
- return
235
- end
236
-
237
- if target.end_with?('.rpm')
238
- log "Install the rpm package"
239
- system "sudo rpm -i #{target}"
240
- return
241
- end
242
-
243
-
244
- if install_to.nil?
245
- install_to = BIN_PATH
246
- end
247
-
248
-
249
- #
250
- # Handle zip situation
251
- #
252
- zip_flag = false
253
-
254
- if target.match?(/\.zip$/) or target.match?(/\.tar\.(\w){1,3}/)
255
-
256
- zip_flag = true
257
-
258
- # unzipped to dir
259
- unzip_dir = ""
260
- # unzipped files
261
- files = ""
262
- # the target unzipped file name (this includes the path prefix)
263
- unzip_name = ""
264
-
265
- # unzip
266
- if target.match? /\.zip$/
267
- log "Unzip zip file"
268
- unzip_dir = target.chomp('.zip')
269
- system "unzip -q #{target} -d #{unzip_dir}"
270
- end
271
-
272
- # .gz, .bz2, .xz
273
- if target.match? /\.tar\.(\w){1,3}/
274
- log "Unzip tar file"
275
- unzip_dir = target.split('.')[0..-3].join('.')
276
- FileUtils.mkdir_p(unzip_dir)
277
- system "tar xf #{target} --directory=#{unzip_dir}"
278
- end
279
-
280
-
281
- #
282
- # @return [Array] file names in the dir
283
- #
284
- def _iterate_dir(dir)
285
- result = []
286
- chd = Dir.children(dir)
287
- chd_files = chd.select{|f| File.file? "#{dir}/#{f}"}
288
-
289
- chd_files.each { result << "#{dir}/#{_1}"[2..] }
290
-
291
- chd_dirs = chd.select{|d| File.directory?("#{dir}/#{d}") && ( d != '.git')}
292
- chd_dirs.each do |d|
293
- sub_dir = "#{dir}/#{d}"
294
- result.concat _iterate_dir(sub_dir)
295
- end
296
- result
297
- end
298
-
299
-
300
- Dir.chdir unzip_dir do
301
- files = _iterate_dir('.')
302
- end
303
-
304
-
305
- if files.size > 1
306
- unzip_name = HL.choose do |menu|
307
- menu.index_color = :rgb_77bbff
308
- menu.prompt = "Which one do you want to install? "
309
- menu.choices( *files )
310
- end
311
- else
312
- unzip_name = files[0]
313
- end
314
-
315
- end # end of zipped file handle
316
-
317
-
318
- # Get software real name
319
- if zip_flag
320
- name = unzip_name.split('/').last
321
- else
322
- name = target.split('/').last
323
- end
324
-
325
-
326
- if (name.size > 10 ||
327
- name.include?('.') ||
328
- name.include?('-') ||
329
- name.include?('_')) && (not rename_as)
330
- log "Do you want to rename the '#{name}'?"
331
- re = HL.ask "Input new name, or leave it blank to not rename."
332
- if !re.empty?
333
- rename_as = re
334
- end
335
- end
336
-
337
-
338
- if zip_flag
339
- # Now:
340
- # target is /home/xx/xx.zip
341
- # unzip_dir is /home/xx/xx
342
- # unzip_file is abc/cde/file (new target)
343
- target = unzip_dir + '/' + unzip_name
344
- end
345
-
346
- log "Renamed as '#{rename_as}'" if rename_as
347
-
348
-
349
- case RUBY_PLATFORM
350
- when /ucrt/i, /mingw/i
351
- install_on_windows(target, name, install_to, rename_as)
352
- else
353
- install_on_nix(target, name, install_to, rename_as)
354
- end
355
- end
356
-
357
-
358
- #
359
- # @param target [String] the absulute path of to be installed software
360
- # @param name [String] the name of the software
361
- #
362
- def install_on_nix(target, name, install_to, rename_as)
363
- install_to = install_to.chomp('/')
364
-
365
- if test 'd', install_to
366
- log "Ready to install #{name}"
367
- if rename_as
368
- dest = "#{install_to}/#{rename_as}"
369
- system "sudo cp #{target} " + dest
370
- system "sudo chmod +x " + dest
371
- log "Installed as " + dest
372
- else
373
- cmd = "sudo install -Dt #{install_to} -m 755 #{target} "
374
- system cmd
375
- log "Install #{name} to " + install_to
376
- end
377
- log "Install finish!"
378
- else
379
- log "#{install_to} is not a directory!"
380
- end
381
-
382
- end
383
-
384
-
385
- def install_on_windows(target, name, install_to, rename_as)
386
- log "Sorry, not implemented yet on Windows! Can you help?"
387
- end
388
-
389
-
390
- #
391
- # For -l option
392
- #
393
- def list_wares
394
- FileUtils.mkdir_p(WAREHOUSE)
395
- puts blue("ghcurl: #{WAREHOUSE}")
396
- Dir.children(WAREHOUSE).each_with_index do |dict,i|
397
- puts "#{blue(i+1)}. #{bold(green(dict))}"
398
- end
399
- end
400
-
401
-
402
- #
403
- # For -d option
404
- #
405
- def delete_wares(name)
406
- begin
407
-
408
- if name.nil?
409
- re = HL.ask "Do you want to delete all downloaded files? [Y/n]"
410
- case re.downcase
411
- when '','y','ye','yes','true'
412
- FileUtils.rm_rf WAREHOUSE
413
- log "Delete all done"
414
- end
415
- else
416
- FileUtils.rm WAREHOUSE + '/' + name
417
- log "Delete #{name} done"
418
- end
419
-
420
- rescue Exception => e
421
- puts bold(red("ghcurl: #{e}"))
422
- list_wares
423
- end
424
-
425
- end # end def delete_wares
426
-
427
-
428
- def help
429
- puts <<~EOC
430
- ghcurl (v#{VERSION}): Download files and install from Github releases
431
-
432
- Default install to env 'GHCURL_BIN_PATH' or /usr/local/bin
433
-
434
- usage:
435
- ghcurl [user]/repo [regexp] => Search latest version with regexp to download
436
- ghcurl repo [re] -v tag => Download a specific tag version
437
- ghcurl repo [re] -o [path] => Download into path or rename
438
- ghcurl repo [re] -i [path] => Download and install to path
439
- ghcurl repo [re] -o [path] -i => Download into path and install
440
- ghcurl repo [re] -i -r name => Download and install as 'name'
441
- ghcurl -l => List downloaded files
442
- ghcurl -d [name] => Delete a downloaded file or all
443
- ghcurl -h => Print this help
444
-
445
- example:
446
- ghcurl bat => Search sharkdp/bat the latest
447
- ghcurl cli deb -i => Search cli/cli /deb/
448
- ghcurl rbspy/rbspy 'x86_64.*linux' -v0.11.1
449
- ghcurl dlvhdr/gh-dash linux-amd64 -i -r 'gd'
450
-
451
- EOC
452
- end
453
-
454
- end
455
-
456
-
457
-
458
- ####################
459
- # main: CLI Handling
460
- ####################
461
- extend Ghcurl
462
-
463
- class Ghcurl::CLI < CliSwitch
464
- option name: 'install', short: '-i', arg_required: 'optional'
465
- option name: 'output', short: '-o', long: '--output', arg_required: 'required'
466
- option name: 'rename', short: '-r', arg_required: 'required'
467
- option name: 'version', short: '-v', arg_required: 'required'
468
- option name: 'help', short: '-h', long: '--help', arg_required: 'noarg'
469
- option name: 'list', short: '-l', arg_required: 'noarg'
470
- option name: 'delete', short: '-d', arg_required: 'optional'
471
- end
472
-
473
- args, opts = Ghcurl::CLI.new.parse(ARGV)
474
-
475
- if args.empty? and opts.empty?
476
- help
477
- exit
478
- end
479
-
480
-
481
- repo_or_name, regexp = args[0], args[1]
482
- version = nil
483
- need_install = false
484
- download_to = nil
485
- install_to = nil
486
- rename_as = nil
487
-
488
- opts.each do
489
- case _1.name
490
- when 'help'
491
- help
492
- exit
493
- when 'list'
494
- list_wares
495
- exit
496
- when 'delete'
497
- delete_wares(_1.next_arg)
498
- exit
499
- when 'version'
500
- version = _1.next_arg
501
- when 'output'
502
- download_to = _1.next_arg
503
- when 'install'
504
- need_install = true
505
- install_to = _1.next_arg
506
- when 'rename'
507
- rename_as = _1.next_arg
508
- end
509
- end
510
-
511
- # Debug
512
- # p repo_or_name
513
- # p regexp
514
- # p need_install
515
- # p download_to
516
- # p install_to
517
- # p rename_as
518
-
519
- begin
520
- ware = download(repo_or_name, regexp, version: version, download_to: download_to)
521
- if need_install
522
- install(ware, rename_as: rename_as, install_to: install_to)
523
- end
524
- rescue Interrupt
525
- end
1
+ #!/usr/bin/env ruby
2
+ # ------------------------------------------------------
3
+ # File : ghcurl.rb
4
+ # Authors : ccmywish <ccmywish@qq.com>
5
+ # Created on : <2022-04-12>
6
+ # Last modified : <2022-11-13>
7
+ #
8
+ # ghcurl:
9
+ #
10
+ # Download files and install from Github releases
11
+ #
12
+ # ------------------------------------------------------
13
+
14
+ require 'ghcurl'
15
+ require 'highline'
16
+ require 'cliswitch'
17
+ require 'fileutils'
18
+
19
+ module Ghcurl
20
+
21
+ WAREHOUSE = File.expand_path("~/.cache/ghcurl")
22
+ if ENV['GHCURL_BIN_PATH']
23
+ BIN_PATH = ENV['GHCURL_BIN_PATH'].chomp('/')
24
+ else
25
+ BIN_PATH = "/usr/local/bin"
26
+ end
27
+ HL = HighLine.new
28
+
29
+ class Error < StandardError; end
30
+
31
+
32
+ def bold(str) "\e[1m#{str}\e[0m" end
33
+ def green(str) "\e[32m#{str}\e[0m" end
34
+ def blue(str) "\e[34m#{str}\e[0m" end
35
+
36
+
37
+ def log(msg)
38
+ puts blue(bold("ghcurl: #{msg}"))
39
+ end
40
+
41
+
42
+ #
43
+ # @return absolute download path
44
+ #
45
+ def curl(url, name, to)
46
+ if to == nil
47
+ to = WAREHOUSE + '/' + name
48
+ elsif test 'd', to
49
+ to = to.chomp('/') + '/' + name
50
+ elsif to.include?('/')
51
+ # noop
52
+ else
53
+ # just a rename in WAREHOUSE
54
+ to = WAREHOUSE + '/' + name
55
+ end
56
+
57
+ cmd = "curl -L #{url} --create-dirs -o #{to}"
58
+ status = system cmd
59
+ if status == false || status.nil?
60
+ log "Download error!"
61
+ exit
62
+ end
63
+ log "Downloaded to #{to}"
64
+ return to
65
+ end
66
+
67
+
68
+ def get_filters
69
+
70
+ arches = [
71
+ # NOTE: Here we can't add 'x86' to the first group!
72
+ # Because we finally use simple "string compare" to filter out
73
+ # the improper links.
74
+ # If added, when we filter out the 'x86'(first group),
75
+ # the leading 'x86' will remove 'x86_64'(the second group) too!!!
76
+ ['i386', 'i686' ],
77
+ ['x64', 'x86_64', 'amd64'],
78
+
79
+ ['armv6', 'arm' ],
80
+ ['armv7' ],
81
+ ['armv8', 'arm64', 'aarch64'],
82
+ ]
83
+
84
+ oses = [
85
+ ['linux'],
86
+ ['freebsd'],
87
+ ['mac', 'apple', 'darwin'],
88
+ ['windows']
89
+ ]
90
+
91
+ # OS and Arch filters
92
+ fs = RUBY_PLATFORM.split('-')
93
+ os = ""
94
+ arch = ""
95
+ fs.each do |f|
96
+ case f
97
+ when 'linux' then os = 'linux'
98
+ when 'mac', 'macOS' then os = 'mac'
99
+ when 'freebsd' then os = 'freebsd'
100
+ when 'ucrt', 'mingw', 'windows' then os = 'windows'
101
+ end
102
+ end
103
+
104
+ # Why we choose to REFUSE filters rather than APPROVE filters?
105
+ # The answer is here, you may notice, different developers decide to
106
+ # name their distribution differently according to the same arch.
107
+ #
108
+ # For example, if a bin file is called "ruby_analyse.exe"
109
+ # The developer may call it (ONLY ONE of below):
110
+ # 1. ruby_analyse-windows-x64.exe
111
+ # 2. ruby_analyse-windows-x86_64.exe
112
+ # 3. ruby_analyse-windows-amd64.exe
113
+ #
114
+ # Think of the situation:
115
+ # When we decide the native arch is "x86-64", and the developer gives
116
+ # the name 'xxx-amd64', we will filter the link out wrongly ....
117
+ #
118
+ # So, we can't target the download link via arch. However, we can
119
+ # see them a group of possible names, then REFUSE other name of
120
+ # other arches.
121
+ #
122
+ fs.each do |f|
123
+ case f
124
+ when 'x64', 'x86_64', 'amd64' then arch = 'x86_64'
125
+ when 'x86', 'i386', 'i686' then arch = 'x86'
126
+
127
+ when 'arm64', 'armv8', 'aarch64' then arch = 'arm64'
128
+ when 'armv6', 'arm' then arch = 'armv6'
129
+ when 'armv7' then arch = 'armv7'
130
+ end
131
+ end
132
+
133
+ approval, refuse = [], []
134
+ # [os, arch].each { approval << _1 unless _1.nil? }
135
+
136
+ if os
137
+ i = oses.each_with_index do |type, index|
138
+ break index if type.include? os
139
+ end
140
+ if !i.nil?
141
+ tmp = oses.dup
142
+ tmp.delete_at(i)
143
+ refuse.concat tmp.flatten
144
+ end
145
+ end
146
+
147
+
148
+ if arch
149
+ i = arches.each_with_index do |type, index|
150
+ break index if type.include? arch
151
+ end
152
+ if !i.nil?
153
+ tmp = arches.dup
154
+ tmp.delete_at(i)
155
+ refuse.concat tmp.flatten
156
+ end
157
+ end
158
+
159
+ # DEBUG
160
+ # puts "=> Approval and refuse"
161
+ # p [os, arch]
162
+ # p refuse
163
+
164
+ # Now we only refuse others
165
+ return refuse
166
+
167
+ end
168
+
169
+
170
+ def download(repo, regexp, version: nil, download_to: nil)
171
+
172
+ require 'octokit'
173
+
174
+ # adjust repo name : user/repo
175
+ if repo =~ /^https:\/\/github.com/
176
+ require 'uri'
177
+ uri = URI(repo)
178
+ # index 1, take 2
179
+ repo = uri.path.split('/')[1,2].join('/')
180
+ elsif !repo.include?('/')
181
+ got_repo = DEFAULT_WARES[repo.to_sym]
182
+ if not got_repo
183
+ user = HL.ask "Who developed the awesome #{repo}? "
184
+ repo = user + '/' + repo
185
+ else
186
+ repo = got_repo
187
+ log "Use the popular repo #{repo}"
188
+ end
189
+ end
190
+
191
+ log "checking..."
192
+ begin
193
+ unless version
194
+ doc = Octokit::Client.new.latest_release(repo)
195
+ else
196
+ doc = Octokit::Client.new.release_for_tag(repo, 'v' + version)
197
+ end
198
+ rescue Octokit::NotFound
199
+ log "Not found #{repo} v#{version} !"
200
+ exit
201
+ rescue StandardError => e
202
+ log e
203
+ exit
204
+ end
205
+
206
+ links = doc.to_hash[:assets]
207
+ if links.empty?
208
+ log <<~EOE
209
+ The search result is empty, check the args!
210
+ repo: #{repo}
211
+ version: #{version ? version:'nil'}
212
+
213
+ Maybe there's no assets in this release
214
+ EOE
215
+ exit 0
216
+ end
217
+
218
+ links = links.map { _1[:browser_download_url] }
219
+
220
+
221
+ if regexp
222
+ filtered = links.select do
223
+ _1 =~ /#{regexp}/
224
+ end
225
+ else
226
+ refuse = get_filters()
227
+ filtered = links.select do |l|
228
+ refuse.all? do |f|
229
+ l !~ /#{f}/
230
+ end
231
+ end
232
+ end
233
+
234
+
235
+ if filtered.size == 1
236
+ link = filtered[0].split('/').last
237
+ else
238
+ if filtered.size == 0
239
+ links_for_user = links.map { _1.split('/').last }
240
+ else
241
+ links_for_user = filtered.map { _1.split('/').last }
242
+ end
243
+ link = HL.choose do |menu|
244
+ menu.index_color = :rgb_77bbff
245
+ menu.prompt = "Which one do you want to download? "
246
+ menu.choices( *links_for_user )
247
+ end
248
+ end
249
+
250
+ url = links[0].split('/')[0..-2].join('/') + '/' + link
251
+
252
+ log "Downloading #{url}"
253
+
254
+ dl_name = link.split('/').last
255
+
256
+ return curl(url, dl_name, download_to)
257
+ end
258
+
259
+
260
+ #
261
+ # This function is a little confusing
262
+ #
263
+ # target: The absolute path to a downloaded file
264
+ # name : The software name
265
+ #
266
+ def install(target, rename_as: nil, install_to: nil)
267
+
268
+ if target.end_with?('.deb')
269
+ log "Install the deb package"
270
+ system "sudo dpkg -i #{target}"
271
+ return
272
+ end
273
+
274
+ if target.end_with?('.rpm')
275
+ log "Install the rpm package"
276
+ system "sudo rpm -i #{target}"
277
+ return
278
+ end
279
+
280
+
281
+ if install_to.nil?
282
+ install_to = BIN_PATH
283
+ end
284
+
285
+
286
+ #
287
+ # Handle zip situation
288
+ #
289
+ zip_flag = false
290
+
291
+ if target.match?(/\.zip$/) or target.match?(/\.tar\.(\w){1,3}/)
292
+
293
+ zip_flag = true
294
+
295
+ # unzipped to dir
296
+ unzip_dir = ""
297
+ # unzipped files
298
+ files = ""
299
+ # the target unzipped file name (this includes the path prefix)
300
+ unzip_name = ""
301
+
302
+ # unzip
303
+ if target.match? /\.zip$/
304
+ log "Unzip zip file"
305
+ unzip_dir = target.chomp('.zip')
306
+ system "unzip -q #{target} -d #{unzip_dir}"
307
+ end
308
+
309
+ # .gz, .bz2, .xz
310
+ if target.match? /\.tar\.(\w){1,3}/
311
+ log "Unzip tar file"
312
+ unzip_dir = target.split('.')[0..-3].join('.')
313
+ FileUtils.mkdir_p(unzip_dir)
314
+ system "tar xf #{target} --directory=#{unzip_dir}"
315
+ end
316
+
317
+
318
+ #
319
+ # @return [Array] file names in the dir
320
+ #
321
+ def _iterate_dir(dir)
322
+ result = []
323
+ chd = Dir.children(dir)
324
+ chd_files = chd.select{|f| File.file? "#{dir}/#{f}"}
325
+
326
+ chd_files.each { result << "#{dir}/#{_1}"[2..] }
327
+
328
+ chd_dirs = chd.select{|d| File.directory?("#{dir}/#{d}") && ( d != '.git')}
329
+ chd_dirs.each do |d|
330
+ sub_dir = "#{dir}/#{d}"
331
+ result.concat _iterate_dir(sub_dir)
332
+ end
333
+ result
334
+ end
335
+
336
+
337
+ Dir.chdir unzip_dir do
338
+ files = _iterate_dir('.')
339
+ end
340
+
341
+
342
+ if files.size > 1
343
+ unzip_name = HL.choose do |menu|
344
+ menu.index_color = :rgb_77bbff
345
+ menu.prompt = "Which one do you want to install? "
346
+ menu.choices( *files )
347
+ end
348
+ else
349
+ unzip_name = files[0]
350
+ end
351
+
352
+ end # end of zipped file handle
353
+
354
+
355
+ # Get software real name
356
+ if zip_flag
357
+ name = unzip_name.split('/').last
358
+ else
359
+ name = target.split('/').last
360
+ end
361
+
362
+
363
+ if (name.size > 10 ||
364
+ name.include?('.') ||
365
+ name.include?('-') ||
366
+ name.include?('_')) && (not rename_as)
367
+ log "Do you want to rename the '#{name}'?"
368
+ re = HL.ask "Input new name, or leave it blank to not rename."
369
+ if !re.empty?
370
+ rename_as = re
371
+ end
372
+ end
373
+
374
+
375
+ if zip_flag
376
+ # Now:
377
+ # target is /home/xx/xx.zip
378
+ # unzip_dir is /home/xx/xx
379
+ # unzip_file is abc/cde/file (new target)
380
+ target = unzip_dir + '/' + unzip_name
381
+ end
382
+
383
+ log "Renamed as '#{rename_as}'" if rename_as
384
+
385
+
386
+ case RUBY_PLATFORM
387
+ when /ucrt/i, /mingw/i
388
+ install_on_windows(target, name, install_to, rename_as)
389
+ else
390
+ install_on_nix(target, name, install_to, rename_as)
391
+ end
392
+ end
393
+
394
+
395
+ #
396
+ # @param target [String] the absulute path of to be installed software
397
+ # @param name [String] the name of the software
398
+ #
399
+ def install_on_nix(target, name, install_to, rename_as)
400
+ install_to = install_to.chomp('/')
401
+
402
+ if test 'd', install_to
403
+ log "Ready to install #{name}"
404
+ if rename_as
405
+ dest = "#{install_to}/#{rename_as}"
406
+ system "sudo cp #{target} " + dest
407
+ system "sudo chmod +x " + dest
408
+ log "Installed as " + dest
409
+ else
410
+ cmd = "sudo install -Dt #{install_to} -m 755 #{target} "
411
+ system cmd
412
+ log "Install #{name} to " + install_to
413
+ end
414
+ log "Install finish!"
415
+ else
416
+ log "#{install_to} is not a directory!"
417
+ end
418
+
419
+ end
420
+
421
+
422
+ def install_on_windows(target, name, install_to, rename_as)
423
+ if !ENV['GHCURL_BIN_PATH']
424
+ log "Sorry, you must specify 'GHCURL_BIN_PATH' environment variable to install on Windows!"
425
+ return
426
+ end
427
+
428
+ install_to = install_to.chomp('/')
429
+
430
+ if test 'd', install_to
431
+ log "Ready to install #{name}"
432
+ if rename_as
433
+ rename_as = rename_as + '.exe' unless rename_as.end_with?('.exe')
434
+ dest = "#{install_to}/#{rename_as}"
435
+ FileUtils.cp(target, dest)
436
+ log "Installed as " + dest
437
+ else
438
+ FileUtils.cp(target, install_to)
439
+ log "Install #{name} to " + install_to
440
+ end
441
+ log "Install finish!"
442
+ else
443
+ log "#{install_to} is not a directory!"
444
+ end
445
+
446
+ end
447
+
448
+
449
+ #
450
+ # For -l option
451
+ #
452
+ def list_wares
453
+ FileUtils.mkdir_p(WAREHOUSE)
454
+ puts blue("ghcurl: #{WAREHOUSE}")
455
+ Dir.children(WAREHOUSE).each_with_index do |dict,i|
456
+ puts "#{blue(i+1)}. #{bold(green(dict))}"
457
+ end
458
+ end
459
+
460
+
461
+ #
462
+ # For -d option
463
+ #
464
+ def delete_wares(name)
465
+ begin
466
+
467
+ if name.nil?
468
+ re = HL.ask "Do you want to delete all downloaded files? [Y/n]"
469
+ case re.downcase
470
+ when '','y','ye','yes','true'
471
+ FileUtils.rm_rf WAREHOUSE
472
+ log "Delete all done"
473
+ end
474
+ else
475
+ FileUtils.rm WAREHOUSE + '/' + name
476
+ log "Delete #{name} done"
477
+ end
478
+
479
+ rescue Exception => e
480
+ puts bold(red("ghcurl: #{e}"))
481
+ list_wares
482
+ end
483
+
484
+ end # end def delete_wares
485
+
486
+
487
+ def help
488
+ puts <<~EOC
489
+ ghcurl (v#{VERSION}): Download files and install from Github releases
490
+
491
+ Default install to env 'GHCURL_BIN_PATH' or /usr/local/bin
492
+
493
+ usage:
494
+ ghcurl [user]/repo [regexp] => Search latest version with regexp to download
495
+ ghcurl repo [re] -v tag => Download a specific tag version
496
+ ghcurl repo [re] -o [path] => Download into path or rename
497
+ ghcurl repo [re] -i [path] => Download and install to path
498
+ ghcurl repo [re] -o [path] -i => Download into path and install
499
+ ghcurl repo [re] -i -r name => Download and install as 'name'
500
+ ghcurl -l => List downloaded files
501
+ ghcurl -d [name] => Delete a downloaded file or all
502
+ ghcurl -h => Print this help
503
+
504
+ example:
505
+ ghcurl bat => Search sharkdp/bat the latest
506
+ ghcurl cli deb -i => Search cli/cli /deb/
507
+ ghcurl rbspy/rbspy 'x86_64.*linux' -v0.11.1
508
+ ghcurl dlvhdr/gh-dash linux-amd64 -i -r 'gd'
509
+
510
+ EOC
511
+ end
512
+
513
+ end
514
+
515
+
516
+
517
+ ####################
518
+ # main: CLI Handling
519
+ ####################
520
+ extend Ghcurl
521
+
522
+ class Ghcurl::CLI < CliSwitch
523
+ option name: 'install', short: '-i', arg_required: 'optional'
524
+ option name: 'output', short: '-o', long: '--output', arg_required: 'required'
525
+ option name: 'rename', short: '-r', arg_required: 'required'
526
+ option name: 'version', short: '-v', arg_required: 'required'
527
+ option name: 'help', short: '-h', long: '--help', arg_required: 'noarg'
528
+ option name: 'list', short: '-l', arg_required: 'noarg'
529
+ option name: 'delete', short: '-d', arg_required: 'optional'
530
+ end
531
+
532
+ args, opts = Ghcurl::CLI.new.parse(ARGV)
533
+
534
+ if args.empty? and opts.empty?
535
+ help
536
+ exit
537
+ end
538
+
539
+
540
+ repo_or_name, regexp = args[0], args[1]
541
+ version = nil
542
+ need_install = false
543
+ download_to = nil
544
+ install_to = nil
545
+ rename_as = nil
546
+
547
+ opts.each do
548
+ case _1.name
549
+ when 'help'
550
+ help
551
+ exit
552
+ when 'list'
553
+ list_wares
554
+ exit
555
+ when 'delete'
556
+ delete_wares(_1.next_arg)
557
+ exit
558
+ when 'version'
559
+ version = _1.next_arg
560
+ when 'output'
561
+ download_to = _1.next_arg
562
+ when 'install'
563
+ need_install = true
564
+ install_to = _1.next_arg
565
+ when 'rename'
566
+ rename_as = _1.next_arg
567
+ end
568
+ end
569
+
570
+ # Debug
571
+ # p repo_or_name
572
+ # p regexp
573
+ # p need_install
574
+ # p download_to
575
+ # p install_to
576
+ # p rename_as
577
+
578
+ begin
579
+ ware = download(repo_or_name, regexp, version: version, download_to: download_to)
580
+ if need_install
581
+ install(ware, rename_as: rename_as, install_to: install_to)
582
+ end
583
+ rescue Interrupt
584
+ end