2013-02-06 11:53:08 +01:00
|
|
|
#!/usr/bin/env ruby
|
|
|
|
# -*- encoding : utf-8 -*-
|
|
|
|
|
|
|
|
require 'irb-pager'
|
|
|
|
require 'shellwords'
|
|
|
|
|
|
|
|
class Commands < Hash
|
|
|
|
class CommandError < Exception
|
|
|
|
end
|
|
|
|
class ExpectingCommandError < CommandError
|
|
|
|
def initialize *args
|
|
|
|
args = ['Command expected'] if args.empty?
|
|
|
|
super *args
|
|
|
|
end
|
|
|
|
end
|
|
|
|
class UnknownCommandError < CommandError
|
|
|
|
def initialize *args
|
|
|
|
args = ['This Command i do not know'] if args.empty?
|
|
|
|
super *args
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2013-02-15 21:33:55 +01:00
|
|
|
attr_accessor :exe, :prefix, :fallback_cmd
|
2013-02-06 11:53:08 +01:00
|
|
|
|
|
|
|
def self.arg_unshift arg, args
|
|
|
|
arg = arg.is_a?(Array) ? arg : [arg]
|
|
|
|
i = 1+args.index {|x|/^[^-]/=~x}
|
|
|
|
args[0...i] + arg + args[i..-1]
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.new prefix, exe
|
|
|
|
r = super()
|
|
|
|
r.exe = exe
|
|
|
|
r.prefix = prefix
|
|
|
|
r.on {|cmd, *argv| raise UnknownCommandError, 'Unknown Command: %s' % cmd }
|
|
|
|
r
|
|
|
|
end
|
|
|
|
|
2013-02-15 21:33:55 +01:00
|
|
|
def sym_name name
|
|
|
|
name.to_s.to_sym
|
|
|
|
end
|
|
|
|
|
2013-02-06 11:53:08 +01:00
|
|
|
def on *names, &run
|
2013-02-15 21:33:55 +01:00
|
|
|
options = names.last.is_a?(Hash) ? names.pop.dup : {}
|
2013-02-06 11:53:08 +01:00
|
|
|
if names.empty?
|
2013-02-15 21:33:55 +01:00
|
|
|
@fallback_cmd = run
|
2013-02-06 11:53:08 +01:00
|
|
|
else
|
2013-02-15 21:33:55 +01:00
|
|
|
names.each {|name| self[sym_name name] = run }
|
2013-02-06 11:53:08 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def cmd argv
|
|
|
|
if @prefix == @exe
|
|
|
|
raise ExpectingCommandError if argv.empty?
|
|
|
|
[argv[0].to_sym, *argv[1..-1]]
|
|
|
|
else
|
|
|
|
@exe =~ /^(?:#{Regexp.escape @prefix}-)?(.*)$/
|
|
|
|
[$1.to_sym, *argv]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def each all=nil, &block
|
|
|
|
if not block_given?
|
|
|
|
Enumerator.new self, :each, all
|
|
|
|
elsif all
|
|
|
|
super(&block)
|
|
|
|
else
|
|
|
|
super() {|k,v| yield k,v unless /^-/ =~ k.to_s }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def run *argv
|
|
|
|
c, *argv = self.cmd( argv)
|
2013-02-15 21:33:55 +01:00
|
|
|
(self[c] || @fallback_cmd).call c, *argv
|
2013-02-06 11:53:08 +01:00
|
|
|
rescue CommandError
|
2013-02-15 21:33:55 +01:00
|
|
|
STDERR.puts $!.message, $!.backtrace
|
2013-02-06 11:53:08 +01:00
|
|
|
exit 1
|
|
|
|
end
|
|
|
|
alias call run
|
|
|
|
|
|
|
|
def to_proc
|
|
|
|
method(:call).to_proc
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2013-02-15 21:33:55 +01:00
|
|
|
# RunCave
|
|
|
|
# =======
|
|
|
|
#
|
|
|
|
# Prepare cave-commands.
|
|
|
|
|
2013-02-06 11:53:08 +01:00
|
|
|
class RunCave
|
|
|
|
class CommandExpected <Exception
|
|
|
|
end
|
|
|
|
class ResumeFileExpected <Exception
|
|
|
|
end
|
|
|
|
|
|
|
|
attr_accessor :preargs, :cmd, :args
|
|
|
|
attr_reader :dummy, :resumable
|
|
|
|
attr_writer :resume_file
|
|
|
|
def initialize preargs = nil, cmd = nil, args = nil
|
2013-02-06 13:50:11 +01:00
|
|
|
@resume_file, @preargs, @cmd, @args = nil, preargs || [], cmd || [], args || []
|
|
|
|
@colored = @dummy = @resumable = nil
|
2013-02-06 11:53:08 +01:00
|
|
|
end
|
|
|
|
|
2013-02-06 13:50:11 +01:00
|
|
|
def can_be_dummy
|
|
|
|
@dummy = true unless false == @dummy
|
|
|
|
self
|
|
|
|
end
|
2013-02-06 11:53:08 +01:00
|
|
|
def dummy?() @dummy end
|
|
|
|
def dummy!() @dummy = true; self end
|
2013-02-06 13:50:11 +01:00
|
|
|
def dummy=(v) @dummy = !!v; self end
|
2013-02-06 11:53:08 +01:00
|
|
|
|
2013-02-06 13:50:11 +01:00
|
|
|
def can_be_resumable
|
|
|
|
@resumable = true unless false == @resumable
|
|
|
|
self
|
|
|
|
end
|
2013-02-06 11:53:08 +01:00
|
|
|
def resumable?() @resumable end
|
|
|
|
def resumable!() @resumable = true; self end
|
2013-02-06 13:50:11 +01:00
|
|
|
def resumable=(v) @resumable = !!v; self end
|
|
|
|
|
|
|
|
def can_be_colored
|
|
|
|
@colored = true unless false == @colored
|
|
|
|
self
|
|
|
|
end
|
|
|
|
def colored?() @colored end
|
|
|
|
def colored!() @colored = true; self end
|
|
|
|
def colored=(v) @colored = !!v; self end
|
2013-02-06 11:53:08 +01:00
|
|
|
|
|
|
|
def prepare_resume_file
|
|
|
|
resumable? ? ['--resume-file', @resume_file || raise(ResumeFileExpected)] : []
|
|
|
|
end
|
|
|
|
|
2013-02-06 13:50:11 +01:00
|
|
|
def prepare_colored
|
|
|
|
colored? ? ['--colour', 'yes'] : []
|
|
|
|
end
|
|
|
|
|
2013-02-06 11:53:08 +01:00
|
|
|
def prepare
|
|
|
|
raise CommandExpected, "Set #{self}.cmd = yourcommand." if @cmd.nil? or @cmd.empty?
|
2013-02-06 13:50:11 +01:00
|
|
|
[
|
|
|
|
:cave, prepare_colored, @preargs, @cmd, prepare_resume_file, *@args
|
|
|
|
].flatten.select{|x|x}.map {|x| x.to_s }
|
2013-02-06 11:53:08 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
def run
|
|
|
|
a = prepare
|
|
|
|
if dummy?
|
|
|
|
puts a.shelljoin
|
|
|
|
0
|
|
|
|
else
|
|
|
|
Kernel.system *a
|
2013-02-15 21:33:55 +01:00
|
|
|
$? && $?.exitstatus
|
2013-02-06 11:53:08 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def this cmd, *args
|
|
|
|
@cmd, @args = cmd, args
|
|
|
|
self
|
|
|
|
end
|
|
|
|
alias method_missing this
|
|
|
|
|
|
|
|
def resume_file f
|
|
|
|
if f.nil?
|
|
|
|
@resume_file
|
|
|
|
else
|
|
|
|
@resume_file = f
|
|
|
|
self
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def run_exit
|
2013-02-15 21:33:55 +01:00
|
|
|
exit run || 130
|
2013-02-06 11:53:08 +01:00
|
|
|
end
|
|
|
|
alias call run_exit
|
|
|
|
end
|
|
|
|
|
2013-02-15 21:33:55 +01:00
|
|
|
# Truckle
|
|
|
|
# =======
|
|
|
|
#
|
|
|
|
# cave-wrapper
|
|
|
|
|
|
|
|
class Truckle
|
|
|
|
class ExecutionError < Exception
|
2013-02-06 11:53:08 +01:00
|
|
|
end
|
|
|
|
|
2013-02-15 21:33:55 +01:00
|
|
|
# Calls exe while IRB::Pager redirect output to your PAGER.
|
|
|
|
def pager *args, &exe
|
|
|
|
if @nopager
|
|
|
|
exe.call
|
|
|
|
else
|
|
|
|
IRB::Pager::pager *args, &exe
|
|
|
|
end
|
|
|
|
end
|
2013-02-06 11:53:08 +01:00
|
|
|
|
2013-02-15 21:33:55 +01:00
|
|
|
# Restarts, if not @nosudo and not root yet, your Truckle as root
|
|
|
|
def sudo *args, &exe
|
|
|
|
if @nosudo or 0 == Process.egid
|
|
|
|
exe.call
|
|
|
|
else
|
|
|
|
exepath = File.join File.dirname( @argv0), @cmds.prefix
|
|
|
|
args = [:sudo, exepath, @params, *args].flatten.select{|x|x}.map {|x| x.to_s }
|
|
|
|
Kernel.exec *args
|
|
|
|
raise ExecutionError, "Can't exec #{args.shelljoin}"
|
|
|
|
end
|
|
|
|
end
|
2013-02-06 11:53:08 +01:00
|
|
|
|
2013-02-15 21:33:55 +01:00
|
|
|
# Your command will be called - if it will be called - with sudo.
|
|
|
|
def sudod exe = nil, &block
|
|
|
|
exe ||= block
|
|
|
|
lambda {|*args| sudo( *args) { exe.call *args } }
|
|
|
|
end
|
2013-02-06 11:53:08 +01:00
|
|
|
|
2013-02-15 21:33:55 +01:00
|
|
|
# Yout command will be called - if it will be called - with pager.
|
|
|
|
def pagered exe = nil, &block
|
|
|
|
exe ||= block
|
|
|
|
lambda {|*args| pager { exe.call *args } }
|
2013-02-06 13:50:11 +01:00
|
|
|
end
|
|
|
|
|
2013-02-15 21:33:55 +01:00
|
|
|
# Yout command will be called - if it will be called - with sudo first and second pager.
|
|
|
|
#
|
|
|
|
# cmds.on :hamster, &sudo_pagered {|cmd, *args| puts "Your hamster said: \"#{args.join ' '}\"" }
|
|
|
|
#
|
|
|
|
# It is shorter than:
|
|
|
|
#
|
|
|
|
# cmds.on( :hamster) {|cmd, *args| sudo( cmd, *args) { pager { puts "Your hamster said: \"#{args.join ' '}\"" } } }
|
|
|
|
def sudo_pagered exe = nil, &block
|
|
|
|
exe ||= block
|
|
|
|
lambda {|*args| sudo( *args) { pager { exe.call *args } } }
|
|
|
|
end
|
|
|
|
|
|
|
|
def markdown_format t, colored = nil
|
|
|
|
t = t.gsub( /^\t+/) {|indent| ' '*indent.length}
|
|
|
|
if colored
|
|
|
|
t.gsub! /^([^\n]+)\n===+/m, "\n«««««« \033[1;4;33m\\1\033[0m »»»»»»"
|
|
|
|
t.gsub! /^([^\n]+)\n---+/m, "\n««« \033[1;33m\\1\033[0m »»»"
|
|
|
|
t.gsub! /`([^`]+)`/, "\033[1;44m \\1 \033[0m"
|
|
|
|
end
|
|
|
|
t = t[1..-1] while t.start_with? "\n"
|
|
|
|
t
|
|
|
|
end
|
|
|
|
|
|
|
|
def initialize argv0
|
|
|
|
ENV['LESS'] = "-FR #{ENV['LESS']}"
|
|
|
|
|
|
|
|
@argv0 = argv0
|
|
|
|
@exename = File.basename argv0
|
|
|
|
|
|
|
|
# initialize our program.
|
|
|
|
@cave = RunCave.new
|
|
|
|
@cmds = Commands.new 'truckle', @exename
|
|
|
|
|
|
|
|
prepare_commands
|
|
|
|
end
|
|
|
|
|
|
|
|
def helptext
|
|
|
|
cmd = @cmds.prefix
|
|
|
|
markdown_format <<EOF, @cave.colored?
|
2013-02-06 11:53:08 +01:00
|
|
|
Usage
|
|
|
|
=====
|
|
|
|
|
|
|
|
#{cmd} Command *ARGS
|
|
|
|
|
|
|
|
Resumable
|
|
|
|
=========
|
|
|
|
|
|
|
|
You do not need to set a resume-file. #{cmd} will determine it automaticaly. First, you can give a first argument for tagging. Tag must be numerical!
|
|
|
|
|
2013-02-06 13:50:11 +01:00
|
|
|
#{cmd} 321 resolve netcat6
|
2013-02-06 11:53:08 +01:00
|
|
|
#{cmd} 321 do
|
|
|
|
|
|
|
|
If you do not give a tag, #{cmd} will use the actual terminal-device-number. If it isn't possible to determine terminal, the parent-pid will be used.
|
|
|
|
|
|
|
|
Like cave but different
|
|
|
|
=======================
|
|
|
|
|
2013-02-06 13:50:11 +01:00
|
|
|
Some commands will be displayed by a pager, so you can scroll up and down like
|
|
|
|
|
|
|
|
truckle resolve WHAT # cave -cy resolve WHAT | less -R
|
|
|
|
truckle remove WHAT # cave -cy uninstall WHAT | less -R
|
|
|
|
|
|
|
|
Some commands are not displayed by a pager, but will execute:
|
|
|
|
|
|
|
|
truckle install WHAT # cave -cy resolve -x WHAT
|
|
|
|
truckle uninstall WHAT # cave -cy uninstall -x WHAT
|
|
|
|
|
|
|
|
«do» and «resume» are special, to execute the last command:
|
2013-02-06 11:53:08 +01:00
|
|
|
|
2013-02-06 13:50:11 +01:00
|
|
|
truckle resume # | do # cave resume
|
2013-02-15 21:33:55 +01:00
|
|
|
trdo # | tresume # shortcuts
|
|
|
|
|
|
|
|
Environment
|
|
|
|
===========
|
|
|
|
|
|
|
|
- `DUMMY=1` will prevent any doing. But it will print, which command would be executed, if not DUMMY=1.
|
|
|
|
- `NOSUDO=1` will prevent of using sudo, if you are not root.
|
|
|
|
- `PAGER=more` will change the pager to more, instead of less.
|
2013-02-06 11:53:08 +01:00
|
|
|
|
2013-02-06 13:50:11 +01:00
|
|
|
Commands
|
|
|
|
========
|
2013-02-06 11:53:08 +01:00
|
|
|
|
2013-02-06 13:50:11 +01:00
|
|
|
Most commands are like by cave.
|
2013-02-06 11:53:08 +01:00
|
|
|
|
2013-02-06 13:50:11 +01:00
|
|
|
- `search` is pagered.
|
|
|
|
- `show` is pagered.
|
|
|
|
- `resolve` is pagered and resumable. Do _not_ use `-x`! Use `resume`.
|
|
|
|
- `install` is resumable and like `cave resolve -x`.
|
|
|
|
- `upgrade` is pagered and resumable and like `cave resolve -c world`. Do _not_ use `-x`! Use `resume`.
|
|
|
|
- `remove` is pagered, resumable and like `cave uninstall`. Do _not_ use `-x`! Use `resume`.
|
|
|
|
- `uninstall` is resumable and like `cave uninstall -x`.
|
|
|
|
- `fix-linkage` is pagered and resumable.
|
|
|
|
- `do` and `resume` are resumable and will resume or execute the last command.
|
2013-02-06 11:53:08 +01:00
|
|
|
|
2013-02-06 13:50:11 +01:00
|
|
|
- `--list-commands` will list all possible commands.
|
|
|
|
- `--install-commands` will install all commands as truckle-COMMAND.
|
2013-02-06 11:53:08 +01:00
|
|
|
EOF
|
2013-02-15 21:33:55 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
def prepare_commands
|
|
|
|
cmds, cave = @cmds, @cave
|
|
|
|
|
|
|
|
# default: simple use cave
|
|
|
|
cmds.on &sudod {|*args| cave.this(*args).() }
|
|
|
|
|
|
|
|
cmds.on :help, '-h', '--help', &pagered { STDOUT.puts helptext }
|
|
|
|
cmds.on :sync, &sudod { cave.sync.() }
|
|
|
|
cmds.on :search, :show, &sudo_pagered {|*args| cave.this(*args).() }
|
|
|
|
cmds.on :resolve, &sudo_pagered {|*args| cave.resumable!.this(*args).() }
|
|
|
|
cmds.on 'fix-linkage', &sudo_pagered {|*args|
|
|
|
|
cave.resumable!.this *args
|
|
|
|
cave.args.push '--', cave.prepare_resume_file
|
|
|
|
cave.resumable = false
|
|
|
|
cave.()
|
|
|
|
}
|
|
|
|
cmds.on :remove, &sudo_pagered {|cmd, *args| cave.resumable!.uninstall(*args).()}
|
|
|
|
cmds.on :upgrade, &sudo_pagered {|cmd, *args| cave.resumable!.resolve( '-c', :world, *args).() }
|
|
|
|
cmds.on :install, &sudod {|cmd, *argv| cave.resumable!.resolve( '-x', *argv).() }
|
|
|
|
cmds.on :uninstall, &sudod {|cmd, *argv| cave.resumable!.uninstall( '-x', *argv).() }
|
|
|
|
|
|
|
|
cmds.on :do, :resume, &sudod {|cmd, *args| cave.resumable!.resume( *args).() }
|
|
|
|
|
|
|
|
cmds.on '--list-commands', &pagered { puts cmds.map{|k,v|k} }
|
|
|
|
cmds.on '--install-commands', &sudod {
|
|
|
|
dir, exe = File.split( argv0)
|
|
|
|
cmds.each do |k,v|
|
|
|
|
k = k.to_s
|
|
|
|
k = File.join dir, "#{cmds.prefix}-#{k}"
|
|
|
|
File.symlink exe, k unless File.exist? k
|
|
|
|
end
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
def setup_params options, argv
|
|
|
|
on_off_auto = lambda do
|
|
|
|
argv.shift
|
|
|
|
case argv[0]
|
|
|
|
when 'on', '1', 'true', 't' then true
|
|
|
|
when 'off', '0', 'false', 'f' then false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
param = lambda { argv.shift; argv[0] }
|
|
|
|
|
|
|
|
until argv.empty?
|
|
|
|
case opt = argv[0]
|
|
|
|
when '--list-commands', '--install-commands'
|
|
|
|
options[:cmd] = opt
|
|
|
|
when '--dummy' then options[:dummy] = true
|
|
|
|
when '--resume-file' then options[:resume_file] = param.call
|
|
|
|
when '--tag' then options[:tag] = param.call
|
|
|
|
when /^\d+$/ then options[:tag] = opt
|
|
|
|
when '--pager' then options[:pager] = on_off_auto.call
|
|
|
|
when '--color' then options[:color] = on_off_auto.call
|
|
|
|
when '--sudo' then options[:sudo] = on_off_auto.call
|
|
|
|
else return
|
|
|
|
end
|
|
|
|
argv.shift
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def setup_env options, argv
|
|
|
|
options[:dummy] = true if %w[1 true yes].include?( ENV['DUMMY'].to_s.downcase)
|
|
|
|
options[:sudo] = false if %w[1 true yes].include?( ENV['NOSUDO'].to_s.downcase)
|
|
|
|
options[:tty] = true if STDOUT.tty?
|
|
|
|
end
|
|
|
|
|
|
|
|
def setup_run options, argv
|
|
|
|
on_off_auto = lambda {|x| nil == x ? true : x }
|
|
|
|
argv.unshift options[:cmd] if options[:cmd]
|
|
|
|
@cave.dummy! if options[:dummy]
|
|
|
|
@cave.can_be_colored if on_off_auto.( options[:color]) and options[:tty]
|
|
|
|
@nopager = true unless on_off_auto.( options[:pager]) or options[:tty]
|
|
|
|
@nosudo = true unless on_off_auto.( options[:sudo])
|
|
|
|
|
|
|
|
resumefilesuffix = if options[:resume_file]
|
|
|
|
options[:resume_file]
|
|
|
|
elsif options[:tag]
|
|
|
|
"tag-#{options[:tag]}"
|
|
|
|
elsif STDOUT.tty? and STDIN.tty? and STDOUT.stat.rdev == STDIN.stat.rdev
|
|
|
|
"dev-#{STDOUT.stat.rdev}"
|
|
|
|
else
|
|
|
|
"ppd-#{Process.ppid}"
|
|
|
|
end
|
|
|
|
@cave.resume_file = options[:resume_file] || "/tmp/truckle-resume-#{resumefilesuffix}"
|
|
|
|
end
|
|
|
|
|
|
|
|
def setup_recall options, argv
|
|
|
|
on_off_auto = lambda {|n,x| case x when true then [n, 'on'] when false then [n, 'off'] end }
|
|
|
|
param = lambda {|n,x| n if x }
|
|
|
|
|
|
|
|
@params = [
|
|
|
|
param.( '--dummy', options[:dummy]),
|
|
|
|
param.( ['--resume-file', options[:resume_file]], options[:resume_file]),
|
|
|
|
param.( ['--tag', options[:tag]], options[:tag]),
|
|
|
|
on_off_auto.( '--sudo', options[:sudo]),
|
|
|
|
on_off_auto.( '--color', options[:color]),
|
|
|
|
on_off_auto.( '--pager', options[:pager])
|
|
|
|
].flatten.select{|x|x}.map(&:to_s)
|
|
|
|
end
|
|
|
|
|
|
|
|
def run *argv
|
|
|
|
options = {}
|
|
|
|
setup_env options, argv
|
|
|
|
setup_params options, argv
|
|
|
|
setup_recall options, argv
|
|
|
|
setup_run options, argv
|
|
|
|
|
|
|
|
@cmds.run *argv
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.run argv0, *argv
|
|
|
|
self.new( argv0).run *argv
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
class Shortcut
|
|
|
|
attr_reader :argv0, :argv
|
|
|
|
def initialize argv0, *argv
|
|
|
|
@argv0, @argv = argv0, argv
|
|
|
|
end
|
|
|
|
|
|
|
|
def exec exe, *args
|
|
|
|
exe = File.join File.dirname( @argv0), exe.to_s
|
|
|
|
Kernel.exec exe, *args.flatten.select{|x|x}.map(&:to_s)
|
|
|
|
end
|
|
|
|
|
|
|
|
def run
|
|
|
|
exe = File.basename @argv0
|
|
|
|
case exe.to_sym
|
|
|
|
when :trdo then exec :truckle, :do, *@argv
|
|
|
|
when :tresume then exec :truckle, :resume, *@argv
|
|
|
|
end
|
|
|
|
end
|
|
|
|
alias call run
|
2013-02-06 11:53:08 +01:00
|
|
|
|
2013-02-15 21:33:55 +01:00
|
|
|
def self.run argv0, *argv
|
|
|
|
self.new( argv0, *argv).run
|
2013-02-06 11:53:08 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2013-02-15 21:33:55 +01:00
|
|
|
Shortcut.run $0, *ARGV
|
|
|
|
Truckle.run $0, *ARGV
|