400 lines
8.3 KiB
Ruby
Executable file
400 lines
8.3 KiB
Ruby
Executable file
#!/usr/bin/env ruby
|
|
|
|
Main = self
|
|
public
|
|
|
|
require 'Paludis'
|
|
require 'getoptlong'
|
|
require 'pathname'
|
|
require 'shellwords'
|
|
require 'readline'
|
|
begin
|
|
require 'irb-pager'
|
|
rescue LoadError
|
|
STDERR.puts <<EOF
|
|
Loading IRB::Pager failed. Please install it first:
|
|
sudo gem install irb-pager
|
|
EOF
|
|
raise
|
|
end
|
|
|
|
include Paludis
|
|
|
|
class Object
|
|
def with &block
|
|
yield self
|
|
end
|
|
def self.with &block
|
|
yield self
|
|
end
|
|
end
|
|
|
|
def path_cmd cmd
|
|
ENV['PATH'].split( ':').each do |dir|
|
|
dir = Pathname.new dir
|
|
next unless dir.directory?
|
|
dir.each_child {|f| return f if cmd == f.basename.to_s and f.file? }
|
|
end
|
|
nil
|
|
end
|
|
|
|
def system *a
|
|
Kernel.system *a.map(&:to_s)
|
|
end
|
|
|
|
class UsageError <Exception
|
|
end
|
|
|
|
class NoSelection <UsageError
|
|
def initialize x = nil
|
|
super( x || 'Select a file first.')
|
|
end
|
|
end
|
|
|
|
if cmd = ENV['ECLECTIC_CONFIG_DIFF_COMMAND']
|
|
def diff( f1, f2) system "#{cmd} #{Shellwords.escape f1} #{Shellwords.escape f2}" end
|
|
elsif path_cmd 'git'
|
|
def diff( f1, f2) system *%w[git --no-pager --git-dir=: diff --color-words], f1, f2 end
|
|
elsif path_cmd 'colordiff'
|
|
def diff( f1, f2) system 'colordiff', f1, f2 end
|
|
else
|
|
def diff( f1, f2) system 'diff', f1, f2 end
|
|
end
|
|
|
|
Log.instance.with do |nst|
|
|
nst.log_level = LogLevel::Warning
|
|
nst.program_name = $0
|
|
end
|
|
|
|
env = EnvironmentFactory.instance.create ''
|
|
dirs = Pathname.new( env.fetch_repository( 'installed')['location'].parse_value).join( '.cache', 'all_CONFIG_PROTECT').each_line.map {|l| Pathname.new l.chomp }
|
|
|
|
def find_cfgs dirs, &exe
|
|
block_given? or return to_enum( __method__, dirs)
|
|
dirs.each do |dir|
|
|
dir.find do |e|
|
|
yield e if /^\._cfg...._/ =~ e.basename.to_s and e.file?
|
|
end if dir.exist?
|
|
end
|
|
end
|
|
|
|
def accept_cfg config, update
|
|
now = Time.now
|
|
now = Time.at now+1 while (backup = config.dirname + "._cfg_backuped_#{now.strftime '%FT%T'}_#{config.basename}").exist?
|
|
puts "#{config}: backup to [#{backup}]"
|
|
config.rename backup
|
|
puts "#{config}: accept [#{update}]"
|
|
update.rename config
|
|
end
|
|
|
|
def reject_cfg config, update
|
|
now = Time.now
|
|
now = Time.at now+1 while (backup = config.dirname + "._cfg_rejected_#{now.strftime '%FT%T'}_#{config.basename}").exist?
|
|
puts "#{config}: reject [#{update}] backup to [#{backup}]"
|
|
update.rename backup
|
|
end
|
|
|
|
def list_keys cmds
|
|
len = cmds.keys.map do |cmd|
|
|
cmd.map {|c| c.sub( /(?<!\\)\\/, '').gsub "\\\\", "\\" }.join( ', ').length
|
|
end.max+2
|
|
cmds.each do |cmd, text|
|
|
l = cmd.map {|c| c.sub( /(?<!\\)\\/, '').gsub "\\\\", "\\" }.join( ', ').length
|
|
cmd = cmd.map do |c|
|
|
c.sub( /(?<!\\)\\([^\\])/, "\e[1m\\1\e[0m").gsub "\\\\", "\\"
|
|
end.join ', '
|
|
printf " [%s]%s%s\n", cmd, ' '*(len-l), text
|
|
end
|
|
end
|
|
|
|
def list_cfgs selected, list
|
|
keys = {}
|
|
list.keys.sort.map do |i|
|
|
file = list[i]
|
|
keys[["\\#{i}"]] = (selected == file ? '* ' : '')+file.to_s
|
|
end
|
|
list_keys keys
|
|
end
|
|
|
|
class Commands < Hash
|
|
class Builder
|
|
def initialize cmds
|
|
@cmds = cmds
|
|
end
|
|
|
|
def self.build cmds, &run
|
|
r = new cmds
|
|
r.instance_eval &run
|
|
r
|
|
end
|
|
|
|
def help text = nil
|
|
@help = text
|
|
end
|
|
|
|
def on *names, &run
|
|
options = names.last.is_a?( Hash) ? names.pop.dup : {}
|
|
names = names.flatten.compact
|
|
if names.empty?
|
|
@fallback_cmd = Cmd.new nil, @help, run
|
|
else
|
|
cmd = Cmd.new names, @help, &run
|
|
names.each do |name|
|
|
name = name.to_s
|
|
if /\\(.)/ =~ name
|
|
@cmds[$1.to_sym] = cmd
|
|
name = name.gsub /\\/, ''
|
|
end
|
|
@cmds[name.to_sym] = cmd
|
|
end
|
|
end
|
|
ensure
|
|
@help = nil
|
|
end
|
|
end
|
|
|
|
class Cmd <Proc
|
|
attr_reader :names, :help
|
|
|
|
def initialize names, help, &exe
|
|
@names, @help = names, help
|
|
super &exe
|
|
end
|
|
end
|
|
|
|
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
|
|
|
|
attr_accessor :fallback_cmd
|
|
|
|
def self.new &builder
|
|
r = super()
|
|
Builder.build r, &builder
|
|
r
|
|
end
|
|
|
|
def === x
|
|
include?( x.to_sym) or super
|
|
end
|
|
|
|
def list_keys
|
|
h = {}
|
|
self.values.map {|c| h[c.names] = c.help }
|
|
Main::list_keys( h)
|
|
end
|
|
end
|
|
|
|
class FileList
|
|
attr_reader :found
|
|
def initialize found
|
|
@found = found
|
|
end
|
|
|
|
def list
|
|
return @list if @list
|
|
list = {}
|
|
found.each_with_index do |(file, cfgs), i|
|
|
i += 1
|
|
list[i] = file
|
|
cfgs.sort!
|
|
end
|
|
@list = list
|
|
end
|
|
end
|
|
|
|
class UI
|
|
attr_accessor :need_to_print
|
|
attr_reader :selected, :cmds, :list, :found, :dirs, :ignore_list
|
|
def selected= x
|
|
@selected, @need_to_print = x, !!x
|
|
end
|
|
|
|
def initialize cmds, dirs
|
|
@ignore_list = []
|
|
@cmds, @dirs = cmds, dirs
|
|
end
|
|
|
|
def prepare
|
|
@need_to_print = false
|
|
@found = Hash.new {|h,k| h[k] = [] }
|
|
@list = {}
|
|
find_cfgs( dirs).each do |cfg|
|
|
conf = cfg.dirname + cfg.basename.to_s.sub( /^\._cfg...._/, '')
|
|
next if ignore_list.include? conf
|
|
found[conf] += [cfg]
|
|
end
|
|
if found.empty?
|
|
puts "No files to update."
|
|
exit 0
|
|
end
|
|
found.each_with_index do |(file, cfgs), i|
|
|
i += 1
|
|
list[i] = file
|
|
cfgs.sort!
|
|
end
|
|
self.selected = nil unless found.has_key? selected
|
|
end
|
|
|
|
def run
|
|
prepare
|
|
prompt.chomp.split( /\s+/).each &method( :execute)
|
|
instance_eval &cmds[:diff] if need_to_print
|
|
rescue Interrupt
|
|
rescue UsageError
|
|
puts $!.message
|
|
end
|
|
|
|
def prompt
|
|
prompt =
|
|
if selected
|
|
"(cfg:\e[1m#{selected}\e[0m)# "
|
|
else
|
|
puts
|
|
list_cfgs selected, list
|
|
puts
|
|
"(select?)# "
|
|
end
|
|
Readline.completion_proc = lambda do |str|
|
|
reg = /^#{Regexp.quote str}/
|
|
list.keys.select {|i| reg =~ i.to_s } +
|
|
found.keys.grep( reg) +
|
|
cmds.keys.grep( reg) +
|
|
found.keys.select {|f| reg =~ f.basename.to_s }
|
|
end
|
|
line = Readline.readline( prompt, true) or exit
|
|
Readline::HISTORY.pop if /^\s*$/ =~ line or (Readline::HISTORY.length >= 2 && Readline::HISTORY[-2] == line)
|
|
line
|
|
end
|
|
|
|
def execute x
|
|
case x
|
|
when /^\d+$/
|
|
m = list[x.to_i]
|
|
raise UsageError, "Unknown index #{x}" unless m
|
|
self.selected = m
|
|
when cmds
|
|
instance_eval &cmds[x.to_sym]
|
|
else
|
|
m = Pathname.new x
|
|
unless (f = found[m]).empty?
|
|
self.selected = m
|
|
else
|
|
raise UsageError, "Unknown command #{x}"
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
auto_action_file = Pathname.new '/etc/paludis/configs_auto_action'
|
|
|
|
cmds = Commands.new do
|
|
help 'Help'
|
|
on %w[\help \?] do
|
|
puts
|
|
cmds.list_keys
|
|
puts
|
|
end
|
|
|
|
help 'Displays difference of selected file'
|
|
on '\diff' do
|
|
raise NoSelection unless selected
|
|
puts
|
|
IRB::Pager::pager( pager: 'less -R') { diff selected, found[selected][0] }
|
|
puts
|
|
self.need_to_print = false
|
|
end
|
|
|
|
help 'List all files need updates'
|
|
on '\list' do
|
|
list_cfgs selected, list if selected
|
|
end
|
|
|
|
help 'Accepts selected file'
|
|
on %w[\accept \yes] do
|
|
raise NoSelection unless selected
|
|
accept_cfg selected, found[selected][0]
|
|
selected = nil
|
|
end
|
|
|
|
help 'Rejects selected file'
|
|
on %w[\reject \no] do
|
|
raise NoSelection unless selected
|
|
reject_cfg selected, found[selected][0]
|
|
selected = nil
|
|
end
|
|
|
|
help 'Ignore selected file for this session (do not anything with this file)'
|
|
on %w[\later \ignore] do
|
|
raise NoSelection unless selected
|
|
ignore_list.push selected
|
|
self.need_to_print = false
|
|
self.selected = nil
|
|
end
|
|
|
|
help 'Quit'
|
|
on %w[\quit \exit] do
|
|
exit 0
|
|
end
|
|
|
|
on 'auto-action' do
|
|
unless auto_action_file.exist?
|
|
raise UsageError, "No config for auto-action found: #{auto_action_file}"
|
|
end
|
|
auto_action_file.each_line do |line|
|
|
next if /^\s*#/ =~ line
|
|
action, file = line.chomp!.split( "\t", 2)
|
|
file = Pathname.new file
|
|
if found.has_key? file
|
|
case action
|
|
when *%w[a accept]
|
|
accept_cfg file, found[file][-1]
|
|
found[file][0...-1].each {|f| reject_cfg file, f }
|
|
when *%w[r reject]
|
|
found[file].each {|f| reject_cfg file, f }
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
ui = UI.new cmds, dirs
|
|
|
|
opts = GetoptLong.new(
|
|
[ '-h', '--help', GetoptLong::NO_ARGUMENT ],
|
|
[ '-A', '--auto-action', GetoptLong::OPTIONAL_ARGUMENT ]
|
|
)
|
|
opts.ordering = GetoptLong::REQUIRE_ORDER
|
|
opts.each do |opt, arg|
|
|
case opt
|
|
when '-h'
|
|
puts <<EOF
|
|
Usage: #{ENV['cave']||'cave'} #{File.basename $0} [-h] [-A]
|
|
|
|
Without options, interactive user interface will be started
|
|
Options:
|
|
-A, --auto-action[=file] runs automatical actions from file or #{auto_action_file}
|
|
|
|
EOF
|
|
exit
|
|
when '-A'
|
|
auto_action_file = Pathname.new arg if arg and not arg.empty?
|
|
ui.prepare
|
|
ui.execute 'auto-action'
|
|
exit
|
|
end
|
|
end
|
|
|
|
loop { ui.run }
|