diff options
author | hut <hut@lavabit.com> | 2009-05-09 00:00:00 +0200 |
---|---|---|
committer | hut <hut@lavabit.com> | 2009-05-09 00:00:00 +0200 |
commit | a0de7f95bc7525b99b2c2e16f566e0ee367e9c3c (patch) | |
tree | 62d1cdf9523f4e8fad28ca7df0e1d696c991d2db | |
parent | 34bfb32ecf2cea5e5de95980beedb681139d9c01 (diff) | |
download | ranger-a0de7f95bc7525b99b2c2e16f566e0ee367e9c3c.tar.gz |
lots of changes. version 0.2.1 v0.2.1
-rw-r--r-- | ChangeLog | 46 | ||||
-rw-r--r-- | Readme | 0 | ||||
-rw-r--r-- | code/action.rb | 69 | ||||
-rw-r--r-- | code/bars.rb | 134 | ||||
-rw-r--r-- | code/debug.rb | 24 | ||||
-rw-r--r-- | code/directory.rb (renamed from code/extensions.rb) | 381 | ||||
-rw-r--r-- | code/draw.rb | 194 | ||||
-rw-r--r-- | code/extensions/basic.rb | 149 | ||||
-rw-r--r-- | code/extensions/fileutils.rb | 1726 | ||||
-rw-r--r-- | code/fm.rb | 57 | ||||
-rw-r--r-- | code/keys.rb | 238 | ||||
-rw-r--r-- | code/screensaver/clock.rb | 10 | ||||
-rw-r--r-- | code/types.rb | 106 | ||||
-rwxr-xr-x | data/generate.rb | 21 | ||||
-rw-r--r-- | data/mime.dat | bin | 0 -> 10452 bytes | |||
-rw-r--r-- | data/mime.types | 769 | ||||
-rwxr-xr-x | fm | 35 | ||||
-rw-r--r-- | interface/ncurses.rb | 110 |
18 files changed, 3562 insertions, 507 deletions
diff --git a/ChangeLog b/ChangeLog index 375565f6..6c2edddd 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,14 +1,54 @@ +Version 0.2.1 + +2009-05-09 hut + Oh no. I neglegted to write change logs... in a nutshell, i changed: + New Hotkey: [r]...flags...[r] which runs the selected file with flags + like [d] to detach the process or [t] to run it in a seperate terminal + For that I also changed Action.run() + +2009-04-27 hut + Minor changes to the way directories and file infos are read + entries are not instantly scheduled upon encounter + Rewritten "ascend" method, which uses Action::run() now (still theres a lot + to be done) + Directory::entry#mimetype() instead of Fm.MIMETYPES[Directory::entry#ext()] + +2009-04-18 hut + Found reproducable bug which leaves it (after closing) running and using up + 99% CPU. It happens when the window managers way of closing windows by + pressing the X button or pressing Alt+F4 is used to quit. + New Hotkeys: [S-TAB] is a shortcut for [`][`] and [TAB] for [`][9] + Better distinguishable columns + MIME support, allowing sorting by MIME-filetype + TODO: fall back to sorting by name for two files of identical filetype + Sorting by extension + Minor bugs fixed + +2009-04-16 hut + New Combination [c][u][t] to mark files for moving + Replaced system() calls with cp or mv with the use of FileUtils + Modified FileUtils for showing its progress the progressbar + Fixed threading problem which made it impossible to stop copying/moving + Fixed priority problems, fluent UI while copying. + + TODO: enqueue file operations for better performance + +2009-04-15 hut + + Added possibility to run fm with a filename in argument which runs the + file, like an enter-press inside fm would run it. + Version 0.2 2009-04-10 hut Generating Previews in the background - Help screen - Tooltips for (S) and (t) + Help screen with [?] + Tooltips for [S] and [t] Added more filetype associations Split up fm.rb into several files 2009-04-09 hut - New Hotkey (S) to change sorting order + New Hotkey [S] to change sorting order Generalized file-modifying tasks in the Action module 2009-04-08 hut diff --git a/Readme b/Readme new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/Readme diff --git a/code/action.rb b/code/action.rb new file mode 100644 index 00000000..e463e852 --- /dev/null +++ b/code/action.rb @@ -0,0 +1,69 @@ +module Action + def self.copy(files, path) + files = [files] unless files.is_a? Array + unless files.empty? + CopyBar2.new(files, path) + end + end + + def self.move(files, path) + files = [files] unless files.is_a? Array + unless files.empty? + MoveBar2.new(files, path) + end + end + + def self.run(hash = {}) + unless OpenStruct === hash + hash = OpenStruct.new(hash) + end + + cf = Fm.currentfile + + all = hash.all.or true + files = hash.files.or(all ? Fm.selection : [cf]) + mode = hash.mode.or 0 + newway = hash.newway.or false + + return false if files.nil? + + if newway + hash = Fm.filehandler(files, hash) + handler = hash.exec + else + handler, wait = Fm.getfilehandler(*files) + end + + return false unless handler + + wait = hash.wait.or wait + new_term = hash.new_term.or false + detach = hash.detach.or false + + log handler + if detach + run_detached(handler, new_term) + else + run_inside(handler, wait) + end + return true + end + + def self.run_detached(what, new_term) + if new_term + p = fork { exec('x-terminal-emulator', '-e', 'bash', '-c', what) } +# Process.detach(p) + else + p = fork { exec "#{what} 2>> /dev/null >> /dev/null" } + Process.detach(p) + end + end + + def self.run_inside(what, wait) + closei + system(*what) + gets if wait + starti + end +end + diff --git a/code/bars.rb b/code/bars.rb new file mode 100644 index 00000000..61d14572 --- /dev/null +++ b/code/bars.rb @@ -0,0 +1,134 @@ +class Bar + def initialize( text = '' ) + @text = text + @text_prefix = nil + @max = 0 + @done = 0 + @counter = 0 + @thread = nil + @update_proc = nil + Fm.bar_add(self) + end + + def kill(evil = true) + Fm.bar_del(self) + Fm.force_update + + @thread.kill + end + + def update(&block) + if block + @update_proc = block + elsif @update_proc + @update_proc.call(self) + end + end + + def set_text_prefix(text) + @text_prefix = text + end + def set_text(text) + @text_prefix = nil + @text = text + end + alias text= set_text + + attr_accessor :thread, :counter, :max +end + +class CopyBar < Bar + def initialize( text = '' ) + super + + @update_proc = proc do |b| + begin + b.done = File.size(fname).to_f / finished + rescue + b.done = 0 + end + end + end +end + +class Bar2 + def kill(evil = true) + Fm.bar_del(self) + Fm.force_update + + @thread.kill + end + + def set_text_prefix(text) + @text_prefix = text + end + def set_text(text) + @text_prefix = nil + @text = text + end + alias text= set_text + + attr_accessor :thread, :counter, :max + def done + if @counter.is_a? MutableNumber + @counter.value.to_f / @max + else + 0 + end + end + + def update() end + + def text + if @text_prefix + "#{@text_prefix} #{(@counter.value.to_f * 10000/ @max).round.to_f/100}%" + elsif @text + @text + else + "" + end + end +end + +class CopyOrMoveBar < Bar2 + def initialize(files, path) + path = path.path unless path.is_a? String + Fm.bar_add(self) + log([files, path]) + + + @thread = Thread.new do + begin + for file in files + file = file.path unless file.is_a? String + if File.exists?(file) + run_operation(file, path) + end + end + rescue + log $! + log $!.backtrace + ensure + kill(false) + end + end + @thread.priority = Fm::COPY_PRIORITY + end +end + +class CopyBar2 < CopyOrMoveBar + def run_operation(file, path) + if File.directory?(file) + FileUtils.cp_r_in_bar(self, file, path) + else + FileUtils.cp_in_bar(self, file, path) + end + end +end + +class MoveBar2 < CopyOrMoveBar + def run_operation(file, path) + FileUtils.mv_in_bar(self, file, path) + end +end + diff --git a/code/debug.rb b/code/debug.rb index 6261ed0e..d3a97190 100644 --- a/code/debug.rb +++ b/code/debug.rb @@ -1,4 +1,3 @@ - require 'pp' module Debug @@ -25,6 +24,28 @@ module Debug end if LOG_LEVEL > 0 + def bm(descr="benchmark", &block) + # Benchmark + t1 = Time.now + yield + dur = Time.now-t1 + + # substract the durtation of a "bm(..) do end" + dur -= bm_dummy(descr) do end + + # Format the duration + dur *= 1000 + dur = dur > 0 ? dur : 0 + dur = '%0.3f' % dur + logerr("#{descr}: #{dur}ms") + end + + def bm_dummy(descr="benchmark", &block) + t1 = Time.now + yield + return (Time.now-t1) + end + def __log__(obj, level) if level <= LOG_LEVEL obj = obj.nil? ? "checkpoint at #{Time.now}" : obj @@ -63,5 +84,6 @@ module Debug def log(a=nil) end def logpp(a=nil) end def trace() end + def bm(*args, &block) yield end end end diff --git a/code/extensions.rb b/code/directory.rb index a15fc952..c42392aa 100644 --- a/code/extensions.rb +++ b/code/directory.rb @@ -1,184 +1,6 @@ -class Bar - def initialize( text = '' ) - @text = text - @done = 0 - @thread = nil - @update_proc = nil - Fm.bar_add(self) - end - - def kill(evil = true) - Fm.bar_del(self) - Fm.force_update - - @thread.kill - end - - def update(&block) - if block - @update_proc = block - elsif @update_proc - @update_proc.call(self) - end - end - - attr_accessor :text, :done, :thread -end - -class CopyBar < Bar - def initialize( text = '' ) - super - - @update_proc = proc do |b| - begin - b.done = File.size(fname).to_f / finished - rescue - b.done = 0 - end - end - end -end - -module Action -# def self.get_all_files(path) -# glob = Dir.new(path).to_a -# end - - def self.make_a_bar_for_one(text, command) - bar = CopyBar.new(test) - - finished = File.size(from[0]).to_f - fname = File.join(to, File.basename(from[0])) - - bar.thread = Thread.new do - begin - system('ionice', '-c3', command, *(from + [to])) - ensure - bar.kill(false) - end - end - end - - def self.copy(from, to) -# log [from, to] - -# if String === from[0] -# from[0] = Directory::Entry.new(from[0]) -# end - - if from.size == 1 and from[0].file? - from = from[0] - bar = Bar.new("Copying...") - finished = from.size.to_f - fname = File.join(to, from.basename) - - bar.update do |b| - begin - b.done = File.size(fname).to_f / finished - rescue - b.done = 0 - end - end - - bar.thread = Thread.new do - begin - system('cp', from.to_s, to) - ensure - bar.kill(false) - end - end - - else - bar = Bar.new("Copying...") - from = from.dup - from = [from] unless Array === from - finished = Dir.number_of_files(*from.map{|x| x.to_s}) - count = 0 - - bar.update do |b| - begin - b.done = count / finished - rescue - b.done = 0 - end - end - - from.map!{|x| x.to_s} - bar.thread = Thread.new do - begin - system('cp', '-r', *(from + [to.to_s])) -# IO.popen("cp -vr #{from.join(' ')} #{to.sh}") do |f| -# IO.popen(['cp', '-vr', *(from + [to])]) do |f| -# count += 1 while f.gets =~ /' -> `/ -# end - ensure - bar.kill(false) - end - end - end - end - - def self.move(from, to) -# log [from, to] - -# if String === from[0] -# from[0] = Directory::Entry.new(from[0]) -# end - - if from.size == 1 and from[0].file? - from = from[0] - bar = Bar.new("Moving...") - finished = from.size.to_f - fname = File.join(to, from.basename) - - bar.update do |b| - begin - b.done = File.size(fname).to_f / finished - rescue - b.done = 0 - end - end - - bar.thread = Thread.new do - begin - system('mv', from.to_s, to) - ensure - bar.kill(false) - end - end - - else - bar = Bar.new("Moving...") - from = from.dup - from = [from] unless Array === from - finished = Dir.number_of_files(*from.map{|x| x.to_s}) - count = 0 - - bar.update do |b| - begin - b.done = count / finished - rescue - b.done = 0 - end - end - - from.map!{|x| x.to_s} - bar.thread = Thread.new do - begin - system('mv', *(from + [to.to_s])) -# IO.popen("mv -v #{from.join(' ')} #{to.sh}") do |f| -# count += 1 while f.gets =~ /' -> `/ -# end - ensure - bar.kill(false) - end - end - end - end -end - class Directory BAD_TIME = Time.at(0) + MOVIE_EXTENSIONS = %w(avi mpg mpeg mp4 mp5 ogv ogm wmv mkv flv fid vob div divx) class Entry #{{{ # Let's just cache every shit, because i don't want # to call File methods all the time @@ -194,6 +16,9 @@ class Directory @dirname = File.dirname(dirname) @basename = File.basename(dirname) end + @name, @ext = @basename.split_at_last_dot +# @ext = @basename.from_last('.') || '' + @movie = MOVIE_EXTENSIONS.include?(@ext) @size = 0 @exists = false @rights = '----------' @@ -208,8 +33,10 @@ class Directory @marked = false end - attr_reader(:basename, :mtime, :rights, :type, :path, - :infostring, :readlink, :basename, :size, :ctime) + attr_reader *%w( + basename mtime rights type path ext + infostring readlink basename size ctime name + ) attr_accessor(:marked) @@ -217,11 +44,19 @@ class Directory def exists?() @exists end def marked?() @marked end def symlink?() @symlink end + def movie?() @movie end def broken_symlink?() @symlink and !@exists end def dir?() @type == :dir end def file?() @type == :file end def writable?() @writable end def executable?() @executable end + def mimetype() + if @type == :dir + nil + else + Fm::MIMETYPES[@ext] + end + end def delete! if @type == :dir @@ -242,17 +77,7 @@ class Directory end def sh - res = @path.dup - res.gsub!('\\\\', "\000") - res.gsub!(' ', '\\ ') - res.gsub!('(', '\\(') - res.gsub!('&', '\\&') - res.gsub!(')', '\\)') - res.gsub!('*', '\\*') - res.gsub!('\'', '\\\'') - res.gsub!('"', '\\"') - res.gsub!("\000", '\\\\') - return res + @path.sh end def in? path @@ -322,6 +147,9 @@ class Directory @file_size = 0 @pointed_file = nil @width = 1000 + @read = false + @empty = true + @scheduled = false refresh end @@ -334,6 +162,7 @@ class Directory else @files.reject!{|x| x[0] == ?. or x == 'lost+found'} end + if @files.empty? @files = ['.'] end @@ -342,14 +171,60 @@ class Directory @files.map!{|basename| Entry.new(@path, basename)} end - attr_reader(:path, :files, :pos, :width, :files_raw, :file_size) + attr_reader(:path, :files, :pos, :width, :files_raw, + :file_size, :read) + attr_accessor(:scheduled) + + def scheduled?() @scheduled end + def read?() @read end def pos=(x) +# if @files.size <= 1 or x < 0 +# x = 0 +# elsif x > @files.size +# x = @files.size - 1 +# end @pos = x + make_sure_cursor_is_in_range() @pointed_file = @files[x] resize end + def recheck_stuff() +# log "pointed file: #@pointed_file" +# log @files_raw +# log "" + if test = @files_raw.index(@pointed_file) +# log("if") + @pos = test + else +# log("else") + make_sure_cursor_is_in_range() + end + end + + def make_sure_cursor_is_in_range() + if @files.size <= 1 or @pos < 0 + @pos = 0 + elsif @pos > @files.size + @pos = @files.size - 1 + end + end + + def find_file(x) + x = File.basename(x) + + files.each_with_index do |file, i| + if file.basename == x + self.pos = i + end + end + end + + def empty?() + Dir.entries(@path).size <= 2 + end + def restore() for f in @files f.marked = false @@ -389,6 +264,7 @@ class Directory f.refresh @file_size += f.size if f.file? end + @read = true end # def refresh() @@ -421,19 +297,29 @@ class Directory end def schedule() + @scheduled = true Fm.schedule(self) end def refresh!() + oldfile = @pointed_file read_dir get_file_info sort + + if @files.include? oldfile + self.pointed_file = oldfile + end end def sort_sub(x, y) case OPTIONS['sort'] when :name x.basename <=> y.basename + when :ext + x.ext <=> y.ext + when :type + x.ext.filetype <=> y.ext.filetype when :size x.size <=> y.size when :ctime @@ -462,112 +348,3 @@ class Directory end end - -class File - MODES_HASH = { - '0' => '---', - '1' => '--x', - '2' => '-w-', - '3' => '-wx', - '4' => 'r--', - '5' => 'r-x', - '6' => 'rw-', - '7' => 'rwx' - } - def self.modestr(f) - unless exists?(f) - return '----------' - end - - if symlink?(f) - result = 'l' - elsif directory?(f) - result = 'd' - else - result = '-' - end - - s = ("%o" % File.stat(f).mode)[-3..-1] - for m in s.each_char - result << MODES_HASH[m] - end - - result - end -end - -class Dir - def self.number_of_files(*dirs) - n = 0 - dirs.each do |entry| - if File.directory?(entry) - n += 1 + number_of_files(*(Dir.new(entry).to_a - ['.', '..']).map\ - {|x| File.join entry, x } ) - else - n += 1 - end - end - return n - end -end - -class Numeric - def limit(max, min = 0) - self < min ? min : (self > max ? max : self) - end - - def bytes space = true, n_round = 2 - n = 1024 - a = %w(B K M G T Y) - - i = 0 - flt = self.to_f - - while flt > n and i < a.length - 1 - flt /= n - i += 1 - end - -# flt = flt.round(n_round) - r = 10 ** n_round - flt *= r - flt = flt.round.to_f / r - int = flt.to_i - flt = int if int == flt - - return flt.to_s + (space ? ' ' + a[i] : a[i]) - end -end - -class Array - def wrap(n) - n.times { push shift } - end -end - -class String - def clear - replace String.new - end - if RUBY_VERSION < '1.9' - def ord - self[0] - end - end - - def sh - res = self.dup - res.gsub!('\\\\', "\000") - res.gsub!(' ', '\\ ') - res.gsub!('(', '\\(') - res.gsub!('&', '\\&') - res.gsub!(')', '\\)') - res.gsub!('*', '\\*') - res.gsub!('\'', '\\\'') - res.gsub!('"', '\\"') - res.gsub!("\000", '\\\\') - return res - end - -end - diff --git a/code/draw.rb b/code/draw.rb index 1a2236cc..dac6c25c 100644 --- a/code/draw.rb +++ b/code/draw.rb @@ -1,5 +1,5 @@ module Fm - DONT_PREVIEW_THESE_FILES = /\.(avi|[mj]pe?g|iso|mp\d|og[gmv]|wm[av]|mkv|torrent|so|class|flv|png|bmp|zip|rar|7z|tar|gz|vob|divx?)$/i + DONT_PREVIEW_THESE_FILES = /\.(avi|[mj]pe?g|iso|mp\d|og[gmv]|wm[av]|mkv|torrent|so|class|flv|png|bmp|vob|divx?)$/i def self.column_put_file(n, file) i = 0 @@ -7,85 +7,140 @@ module Fm m = lines - 2 color 7 bold false - File.open(file.path, 'r') do |f| - check = true - left, wid = get_boundaries(n) - f.lines.each do |l| - if check - check = false - break unless l.each_char.all? {|x| x[0] > 0 and x[0] < 128} - end - puti i+1, left, l.gsub("\t"," ")[0, wid-1].ljust(wid) + left, wid = get_boundaries(n) + if file.ext =~ /(?:rar|zip|7z|tar|gz)$/ and file.size < 10485760 + text = `aunpack -l #{file.sh} 2>> /dev/null` + text.each_line do |l| + puti i+1, left, l[0, wid-1].ljust(wid) i += 1 break if i == m end + else + File.open(file.path, 'r') do |f| + check = true + left, wid = get_boundaries(n) + f.lines.each do |l| + if check + check = false + break unless l.each_char.all? {|x| x[0] > 0 and x[0] < 128} + end + puti i+1, left, l.gsub("\t"," ")[0, wid-1].ljust(wid) + i += 1 + break if i == m + end + end end end column_clear(n, i) end def self.put_directory(c, d) - l = 0 + l = 1 if d infos = (c == COLUMNS - 2) left, wid = get_boundaries(c) - offset = get_offset(d, lines) - (lines - 1).times do |l| - lpo = l + offset - bg = -1 - break if (f = d.files[lpo]) == nil - - dir = false - if f.symlink? - bld = true - if f.broken_symlink? - clr = [1, bg] + if d.read? and not d.empty? + + offset = get_offset(d, lines) + (lines - 1).times do |l| + lpo = l + offset + bg = -1 + break if (f = d.files[lpo]) == nil +# log f + + dir = false + if f.symlink? + bld = true + if f.broken_symlink? + clr = [1, bg] + else + clr = [6, bg] + end + dir = f.dir? + elsif f.dir? + bld = true + dir = true + clr = [4, bg] + elsif f.movie? + bld = true + clr = [5, bg] + elsif f.executable? + bld = true + clr = [2, bg] else - clr = [6, bg] + bld = false + clr = [7, bg] end - dir = f.dir? - elsif f.dir? - bld = true - dir = true - clr = [4, bg] - elsif f.executable? - bld = true - clr = [2, bg] - else - bld = false - clr = [7, bg] - end - fn = f.basename - if f.marked? - fn = "*#{fn}" - end - if infos - myinfo = " #{f.infostring} " - str = fn[0, wid-1].ljust(wid+1) - if str.size > myinfo.size - str[-myinfo.size..-1] = myinfo - yes = true + fn = f.basename + if f.marked? + fn = "* #{fn}" + end + if infos + myinfo = " #{f.infostring} " + str = fn[0, wid-1].ljust(wid+1) + if str.size > myinfo.size + str[-myinfo.size..-1] = myinfo + yes = true + else + yes = false + end + puti l+1, left, str + if dir and yes + args = l+1, left+wid-myinfo.size, myinfo.size, *clr + color_bold_at(*args) + end else - yes = false + puti l+1, left, fn[0, wid-1].ljust(wid+1) end - puti l+1, left, str - if dir and yes - args = l+1, left+wid-myinfo.size, myinfo.size, *clr - color_bold_at(*args) + + args = l+1, left, fn.size.limit(wid-1), *clr + + if d.pos == lpo + if c == COLUMNS - 2 +# puti l+1, left-1, '^' +# puti l+1, left+args[2], '$' + + args[4] = 0 +# args[1] -= 1 +# if args[2] < 5 +# args[2] = 7 +# else +# args[2] += 1 +# end +# color_bold_at(l+1, left-1, 1, 0, 0) +# color_bold_at(l+1, left+args[2], 1, 0, 0) + color_reverse_bold_at(*args) + + # ... + args[1] -= 1; args[2] += 2 + color_bold_at(*args) + args[1] += 1; args[2] -= 2 + color_reverse_bold_at(*args) + else + color_reverse_at(*args) + end +# if f.marked? +# args[1] += 1 +# args[2] = 1 +# args[3] = 1 +# color_reverse_at(*args) +# end + else + if bld then color_at(*args) else color_at(*args) end +# if bld then color_bold_at(*args) else color_at(*args) end end - else - puti l+1, left, fn[0, wid-1].ljust(wid+1) end + elsif d.read? and d.empty? + puti l, left, 'empty'.ljust(wid+1) - args = l+1, left, fn.size.limit(wid), *clr + elsif not d.read? + puti l, left, 'reading...'.ljust(wid+1) + d.schedule unless d.scheduled? + else + puti l, left, 'ERROR'.ljust(wid+1) - if d.pos == lpo - color_reverse_at(*args) - else - if bld then color_bold_at(*args) else color_at(*args) end - end end end @@ -140,7 +195,9 @@ module Fm bold false @cur_y = get_boundaries(COLUMNS-2)[0] - if @buffer == '?' + if @buffer =~ /^block/ + screensaver + elsif @buffer == '?' cleari puti 0, " - - - Help - - -" puti 2, " h/j/k/l: Movement J/K: fast Movement" @@ -177,12 +234,22 @@ module Fm puti 8, " gh: go to ~/" puti 9, " gt: go to ~/.trash/" else + @pwd.recheck_stuff() + cf = currentfile + + if cf and s0 = cf.mimetype + puti 0, cols-s0.size, s0 + end + s1 = " " s2 = "#{@path.last.path}#{"/" unless @path.size == 1}" - cf = currentfile s3 = "#{cf ? cf.basename : ''}" - puti 0, (s1 + s2 + s3).ljust(cols) + if s0 + puti 0, (s1 + s2 + s3).ljust(cols-s0.size) + else + puti 0, (s1 + s2 + s3).ljust(cols) + end bg = -1 color_at 0, 0, -1, 7, bg @@ -227,6 +294,7 @@ module Fm puti btm, "Toggle (h)idden_files (d)irs_first (c)olor (f)ilepreview" else # log(@pwd) +# log "Buffer: #{@buffer}" puti btm, "#@buffer #{@pwd.file_size.bytes(false)},#{@pwd.size},#{@pwd.pos+1} ".rjust(cols) more = '' if cf.symlink? @@ -255,10 +323,10 @@ module Fm done = bar.done c = (done * cols).to_i unless done == 0 - color_at l, 0, c, 0, 2 + color_at l, 0, c, 0, 4 end unless done == cols - color_at l, c, -1, 0, 3 + color_at l, c, -1, 0, 6 end end end diff --git a/code/extensions/basic.rb b/code/extensions/basic.rb new file mode 100644 index 00000000..c822e9f6 --- /dev/null +++ b/code/extensions/basic.rb @@ -0,0 +1,149 @@ +# Generic extensions of the language + +class MutableNumber + attr_accessor :value + + def initialize(n=0) + @value = n + end + def add(n=1) @value += n end + def sub(n=1) @value -= n end +end + +class Array + def wrap(n) + # TODO: this can be done better... + n.times { push shift } + end +end + +class String + def clear + self.replace("") + end + + if RUBY_VERSION < '1.9' + def ord + self[0] + end + end + + def from_first(str) + self.include?(str) ? self [ self.index(str) + str.size .. -1 ] : nil + end + + def from_last(str) + self.include?(str) ? self [ self.rindex(str) + str.size .. -1 ] : nil + end + + def split_at_last_dot() + if ix = self.rindex('.') + return self[0...ix], self[ix+1..-1] + else + return self, '' + end + end + + def before_last(str) + self.include?(str) ? self [ 0 .. rindex(str) - str.size ] : self + end + + def filetype() + Fm::MIMETYPES[self] || 'unknown' + end + + def sh + res = self.dup + res.gsub!('\\\\', "\000") + res.gsub!(' ', '\\ ') + res.gsub!('(', '\\(') + res.gsub!('&', '\\\&') + res.gsub!(')', '\\)') + res.gsub!('*', '\\*') + res.gsub!('\'', "\\\\'") + res.gsub!('"', '\\"') + res.gsub!("\000", '\\\\') + return res + end +end + +class Numeric + def limit(max, min = 0) + self < min ? min : (self > max ? max : self) + end + + def bytes space = true, n_round = 2 + n = 1024 + a = %w(B K M G T Y) + + i = 0 + flt = self.to_f + + while flt > n and i < a.length - 1 + flt /= n + i += 1 + end + +# flt = flt.round(n_round) + r = 10 ** n_round + flt *= r + flt = flt.round.to_f / r + int = flt.to_i + flt = int if int == flt + + return flt.to_s + (space ? ' ' + a[i] : a[i]) + end +end + +class Dir + def self.number_of_files(*dirs) + n = 0 + dirs.each do |entry| + if File.directory?(entry) + n += 1 + number_of_files(*(Dir.new(entry).to_a - ['.', '..']).map\ + {|x| File.join entry, x } ) + else + n += 1 + end + end + return n + end +end + +class File + MODES_HASH = { + '0' => '---', + '1' => '--x', + '2' => '-w-', + '3' => '-wx', + '4' => 'r--', + '5' => 'r-x', + '6' => 'rw-', + '7' => 'rwx' + } + def self.modestr(f) + unless exists?(f) + return '----------' + end + + if symlink?(f) + result = 'l' + elsif directory?(f) + result = 'd' + else + result = '-' + end + + s = ("%o" % File.stat(f).mode)[-3..-1] + for m in s.each_char + result << MODES_HASH[m] + end + + result + end +end + + +class Object; def or(value) self end end +class NilClass; def or(value) value end end + diff --git a/code/extensions/fileutils.rb b/code/extensions/fileutils.rb new file mode 100644 index 00000000..701fcde7 --- /dev/null +++ b/code/extensions/fileutils.rb @@ -0,0 +1,1726 @@ +# +# = fileutils.rb +# +# Copyright (c) 2000-2006 Minero Aoki +# +# This program is free software. +# You can distribute/modify this program under the same terms of ruby. +# +# == module FileUtils +# +# Namespace for several file utility methods for copying, moving, removing, etc. +# +# === Module Functions +# +# cd(dir, options) +# cd(dir, options) {|dir| .... } +# pwd() +# mkdir(dir, options) +# mkdir(list, options) +# mkdir_p(dir, options) +# mkdir_p(list, options) +# rmdir(dir, options) +# rmdir(list, options) +# ln(old, new, options) +# ln(list, destdir, options) +# ln_s(old, new, options) +# ln_s(list, destdir, options) +# ln_sf(src, dest, options) +# cp(src, dest, options) +# cp(list, dir, options) +# cp_r(src, dest, options) +# cp_r(list, dir, options) +# mv(src, dest, options) +# mv(list, dir, options) +# rm(list, options) +# rm_r(list, options) +# rm_rf(list, options) +# install(src, dest, mode = <src's>, options) +# chmod(mode, list, options) +# chmod_R(mode, list, options) +# chown(user, group, list, options) +# chown_R(user, group, list, options) +# touch(list, options) +# +# The <tt>options</tt> parameter is a hash of options, taken from the list +# <tt>:force</tt>, <tt>:noop</tt>, <tt>:preserve</tt>, and <tt>:verbose</tt>. +# <tt>:noop</tt> means that no changes are made. The other two are obvious. +# Each method documents the options that it honours. +# +# All methods that have the concept of a "source" file or directory can take +# either one file or a list of files in that argument. See the method +# documentation for examples. +# +# There are some `low level' methods, which do not accept any option: +# +# copy_entry(src, dest, preserve = false, dereference = false) +# copy_file(src, dest, preserve = false, dereference = true) +# copy_stream(srcstream, deststream) +# remove_entry(path, force = false) +# remove_entry_secure(path, force = false) +# remove_file(path, force = false) +# compare_file(path_a, path_b) +# compare_stream(stream_a, stream_b) +# uptodate?(file, cmp_list) +# +# == module FileUtils::Verbose +# +# This module has all methods of FileUtils module, but it outputs messages +# before acting. This equates to passing the <tt>:verbose</tt> flag to methods +# in FileUtils. +# +# == module FileUtils::NoWrite +# +# This module has all methods of FileUtils module, but never changes +# files/directories. This equates to passing the <tt>:noop</tt> flag to methods +# in FileUtils. +# +# == module FileUtils::DryRun +# +# This module has all methods of FileUtils module, but never changes +# files/directories. This equates to passing the <tt>:noop</tt> and +# <tt>:verbose</tt> flags to methods in FileUtils. +# + +module FileUtils + + def self.private_module_function(name) #:nodoc: + module_function name + private_class_method name + end + + # This hash table holds command options. + OPT_TABLE = {} #:nodoc: internal use only + + # + # Options: (none) + # + # Returns the name of the current directory. + # + def pwd + Dir.pwd + end + module_function :pwd + + alias getwd pwd + module_function :getwd + + # + # Options: verbose + # + # Changes the current directory to the directory +dir+. + # + # If this method is called with block, resumes to the old + # working directory after the block execution finished. + # + # FileUtils.cd('/', :verbose => true) # chdir and report it + # + def cd(dir, options = {}, &block) # :yield: dir + fu_check_options options, OPT_TABLE['cd'] + fu_output_message "cd #{dir}" if options[:verbose] + Dir.chdir(dir, &block) + fu_output_message 'cd -' if options[:verbose] and block + end + module_function :cd + + alias chdir cd + module_function :chdir + + OPT_TABLE['cd'] = + OPT_TABLE['chdir'] = [:verbose] + + # + # Options: (none) + # + # Returns true if +newer+ is newer than all +old_list+. + # Non-existent files are older than any file. + # + # FileUtils.uptodate?('hello.o', %w(hello.c hello.h)) or \ + # system 'make hello.o' + # + def uptodate?(new, old_list, options = nil) + raise ArgumentError, 'uptodate? does not accept any option' if options + + return false unless File.exist?(new) + new_time = File.mtime(new) + old_list.each do |old| + if File.exist?(old) + return false unless new_time > File.mtime(old) + end + end + true + end + module_function :uptodate? + + # + # Options: mode noop verbose + # + # Creates one or more directories. + # + # FileUtils.mkdir 'test' + # FileUtils.mkdir %w( tmp data ) + # FileUtils.mkdir 'notexist', :noop => true # Does not really create. + # FileUtils.mkdir 'tmp', :mode => 0700 + # + def mkdir(list, options = {}) + fu_check_options options, OPT_TABLE['mkdir'] + list = fu_list(list) + fu_output_message "mkdir #{options[:mode] ? ('-m %03o ' % options[:mode]) : ''}#{list.join ' '}" if options[:verbose] + return if options[:noop] + + list.each do |dir| + fu_mkdir dir, options[:mode] + end + end + module_function :mkdir + + OPT_TABLE['mkdir'] = [:mode, :noop, :verbose] + + # + # Options: mode noop verbose + # + # Creates a directory and all its parent directories. + # For example, + # + # FileUtils.mkdir_p '/usr/local/lib/ruby' + # + # causes to make following directories, if it does not exist. + # * /usr + # * /usr/local + # * /usr/local/lib + # * /usr/local/lib/ruby + # + # You can pass several directories at a time in a list. + # + def mkdir_p(list, options = {}) + fu_check_options options, OPT_TABLE['mkdir_p'] + list = fu_list(list) + fu_output_message "mkdir -p #{options[:mode] ? ('-m %03o ' % options[:mode]) : ''}#{list.join ' '}" if options[:verbose] + return *list if options[:noop] + + list.map {|path| path.sub(%r</\z>, '') }.each do |path| + # optimize for the most common case + begin + fu_mkdir path, options[:mode] + next + rescue SystemCallError + next if File.directory?(path) + end + + stack = [] + until path == stack.last # dirname("/")=="/", dirname("C:/")=="C:/" + stack.push path + path = File.dirname(path) + end + stack.reverse_each do |path| + begin + fu_mkdir path, options[:mode] + rescue SystemCallError => err + raise unless File.directory?(path) + end + end + end + + return *list + end + module_function :mkdir_p + + alias mkpath mkdir_p + alias makedirs mkdir_p + module_function :mkpath + module_function :makedirs + + OPT_TABLE['mkdir_p'] = + OPT_TABLE['mkpath'] = + OPT_TABLE['makedirs'] = [:mode, :noop, :verbose] + + def fu_mkdir(path, mode) #:nodoc: + path = path.sub(%r</\z>, '') + if mode + Dir.mkdir path, mode + File.chmod mode, path + else + Dir.mkdir path + end + end + private_module_function :fu_mkdir + + # + # Options: noop, verbose + # + # Removes one or more directories. + # + # FileUtils.rmdir 'somedir' + # FileUtils.rmdir %w(somedir anydir otherdir) + # # Does not really remove directory; outputs message. + # FileUtils.rmdir 'somedir', :verbose => true, :noop => true + # + def rmdir(list, options = {}) + fu_check_options options, OPT_TABLE['rmdir'] + list = fu_list(list) + fu_output_message "rmdir #{list.join ' '}" if options[:verbose] + return if options[:noop] + list.each do |dir| + Dir.rmdir dir.sub(%r</\z>, '') + end + end + module_function :rmdir + + OPT_TABLE['rmdir'] = [:noop, :verbose] + + # + # Options: force noop verbose + # + # <b><tt>ln(old, new, options = {})</tt></b> + # + # Creates a hard link +new+ which points to +old+. + # If +new+ already exists and it is a directory, creates a link +new/old+. + # If +new+ already exists and it is not a directory, raises Errno::EEXIST. + # But if :force option is set, overwrite +new+. + # + # FileUtils.ln 'gcc', 'cc', :verbose => true + # FileUtils.ln '/usr/bin/emacs21', '/usr/bin/emacs' + # + # <b><tt>ln(list, destdir, options = {})</tt></b> + # + # Creates several hard links in a directory, with each one pointing to the + # item in +list+. If +destdir+ is not a directory, raises Errno::ENOTDIR. + # + # include FileUtils + # cd '/sbin' + # FileUtils.ln %w(cp mv mkdir), '/bin' # Now /sbin/cp and /bin/cp are linked. + # + def ln(src, dest, options = {}) + fu_check_options options, OPT_TABLE['ln'] + fu_output_message "ln#{options[:force] ? ' -f' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose] + return if options[:noop] + fu_each_src_dest0(src, dest) do |s,d| + remove_file d, true if options[:force] + File.link s, d + end + end + module_function :ln + + alias link ln + module_function :link + + OPT_TABLE['ln'] = + OPT_TABLE['link'] = [:force, :noop, :verbose] + + # + # Options: force noop verbose + # + # <b><tt>ln_s(old, new, options = {})</tt></b> + # + # Creates a symbolic link +new+ which points to +old+. If +new+ already + # exists and it is a directory, creates a symbolic link +new/old+. If +new+ + # already exists and it is not a directory, raises Errno::EEXIST. But if + # :force option is set, overwrite +new+. + # + # FileUtils.ln_s '/usr/bin/ruby', '/usr/local/bin/ruby' + # FileUtils.ln_s 'verylongsourcefilename.c', 'c', :force => true + # + # <b><tt>ln_s(list, destdir, options = {})</tt></b> + # + # Creates several symbolic links in a directory, with each one pointing to the + # item in +list+. If +destdir+ is not a directory, raises Errno::ENOTDIR. + # + # If +destdir+ is not a directory, raises Errno::ENOTDIR. + # + # FileUtils.ln_s Dir.glob('bin/*.rb'), '/home/aamine/bin' + # + def ln_s(src, dest, options = {}) + fu_check_options options, OPT_TABLE['ln_s'] + fu_output_message "ln -s#{options[:force] ? 'f' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose] + return if options[:noop] + fu_each_src_dest0(src, dest) do |s,d| + remove_file d, true if options[:force] + File.symlink s, d + end + end + module_function :ln_s + + alias symlink ln_s + module_function :symlink + + OPT_TABLE['ln_s'] = + OPT_TABLE['symlink'] = [:force, :noop, :verbose] + + # + # Options: noop verbose + # + # Same as + # #ln_s(src, dest, :force) + # + def ln_sf(src, dest, options = {}) + fu_check_options options, OPT_TABLE['ln_sf'] + options = options.dup + options[:force] = true + ln_s src, dest, options + end + module_function :ln_sf + + OPT_TABLE['ln_sf'] = [:noop, :verbose] + + # + # Options: preserve noop verbose + # + # Copies a file content +src+ to +dest+. If +dest+ is a directory, + # copies +src+ to +dest/src+. + # + # If +src+ is a list of files, then +dest+ must be a directory. + # + # FileUtils.cp 'eval.c', 'eval.c.org' + # FileUtils.cp %w(cgi.rb complex.rb date.rb), '/usr/lib/ruby/1.6' + # FileUtils.cp %w(cgi.rb complex.rb date.rb), '/usr/lib/ruby/1.6', :verbose => true + # FileUtils.cp 'symlink', 'dest' # copy content, "dest" is not a symlink + # + def cp(src, dest, options = {}) + fu_check_options options, OPT_TABLE['cp'] + fu_output_message "cp#{options[:preserve] ? ' -p' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose] + return if options[:noop] + fu_each_src_dest(src, dest) do |s, d| + copy_file s, d, options[:preserve] + end + end + module_function :cp + + # changed + def cp_in_bar(bar, src, dest, options = {}) + fu_check_options options, OPT_TABLE['cp'] + fu_output_message "cp#{options[:preserve] ? ' -p' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose] + return if options[:noop] + fu_each_src_dest(src, dest) do |s, d| + copy_file_in_bar bar, s, d, options[:preserve] + end + end + module_function :cp_in_bar + + alias copy cp + module_function :copy + + OPT_TABLE['cp'] = + OPT_TABLE['copy'] = [:preserve, :noop, :verbose] + + # + # Options: preserve noop verbose dereference_root remove_destination + # + # Copies +src+ to +dest+. If +src+ is a directory, this method copies + # all its contents recursively. If +dest+ is a directory, copies + # +src+ to +dest/src+. + # + # +src+ can be a list of files. + # + # # Installing ruby library "mylib" under the site_ruby + # FileUtils.rm_r site_ruby + '/mylib', :force + # FileUtils.cp_r 'lib/', site_ruby + '/mylib' + # + # # Examples of copying several files to target directory. + # FileUtils.cp_r %w(mail.rb field.rb debug/), site_ruby + '/tmail' + # FileUtils.cp_r Dir.glob('*.rb'), '/home/aamine/lib/ruby', :noop => true, :verbose => true + # + # # If you want to copy all contents of a directory instead of the + # # directory itself, c.f. src/x -> dest/x, src/y -> dest/y, + # # use following code. + # FileUtils.cp_r 'src/.', 'dest' # cp_r('src', 'dest') makes src/dest, + # # but this doesn't. + # + def cp_r(src, dest, options = {}) + fu_check_options options, OPT_TABLE['cp_r'] + fu_output_message "cp -r#{options[:preserve] ? 'p' : ''}#{options[:remove_destination] ? ' --remove-destination' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose] + return if options[:noop] + options[:dereference_root] = true unless options.key?(:dereference_root) + fu_each_src_dest(src, dest) do |s, d| + copy_entry s, d, options[:preserve], options[:dereference_root], options[:remove_destination] + end + end + module_function :cp_r + + #changed + def cp_r_in_bar(bar, src, dest, options = {}) + fu_check_options options, OPT_TABLE['cp_r'] + fu_output_message "cp -r#{options[:preserve] ? 'p' : ''}#{options[:remove_destination] ? ' --remove-destination' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose] + return if options[:noop] + options[:dereference_root] = true unless options.key?(:dereference_root) + fu_each_src_dest(src, dest) do |s, d| + copy_entry_in_bar bar, s, d, options[:preserve], options[:dereference_root], options[:remove_destination] + end + end + module_function :cp_r_in_bar + + OPT_TABLE['cp_r'] = [:preserve, :noop, :verbose, + :dereference_root, :remove_destination] + + # + # Copies a file system entry +src+ to +dest+. + # If +src+ is a directory, this method copies its contents recursively. + # This method preserves file types, c.f. symlink, directory... + # (FIFO, device files and etc. are not supported yet) + # + # Both of +src+ and +dest+ must be a path name. + # +src+ must exist, +dest+ must not exist. + # + # If +preserve+ is true, this method preserves owner, group, permissions + # and modified time. + # + # If +dereference_root+ is true, this method dereference tree root. + # + # If +remove_destination+ is true, this method removes each destination file before copy. + # + def copy_entry(src, dest, preserve = false, dereference_root = false, remove_destination = false) + Entry_.new(src, nil, dereference_root).traverse do |ent| + destent = Entry_.new(dest, ent.rel, false) + File.unlink destent.path if remove_destination && File.file?(destent.path) + ent.copy destent.path + ent.copy_metadata destent.path if preserve + end + end + module_function :copy_entry + + #changed + def copy_entry_in_bar(bar, src, dest, preserve = false, dereference_root = false, remove_destination = false) + Entry_.new(src, nil, dereference_root).traverse do |ent| + destent = Entry_.new(dest, ent.rel, false) + File.unlink destent.path if remove_destination && File.file?(destent.path) + ent.copy_in_bar bar, destent.path + ent.copy_metadata destent.path if preserve + end + end + module_function :copy_entry_in_bar + + # + # Copies file contents of +src+ to +dest+. + # Both of +src+ and +dest+ must be a path name. + # + def copy_file(src, dest, preserve = false, dereference = true) + ent = Entry_.new(src, nil, dereference) + ent.copy_file dest + ent.copy_metadata dest if preserve + end + module_function :copy_file + + # changed + def copy_file_in_bar(bar, src, dest, preserve = false, dereference = true) + ent = Entry_.new(src, nil, dereference) + ent.copy_file_in_bar bar, dest + ent.copy_metadata dest if preserve + end + module_function :copy_file_in_bar + + # + # Copies stream +src+ to +dest+. + # +src+ must respond to #read(n) and + # +dest+ must respond to #write(str). + # + def copy_stream(src, dest) + fu_copy_stream0 src, dest, fu_stream_blksize(src, dest) + end + module_function :copy_stream + + # + # Options: force noop verbose + # + # Moves file(s) +src+ to +dest+. If +file+ and +dest+ exist on the different + # disk partition, the file is copied instead. + # + # FileUtils.mv 'badname.rb', 'goodname.rb' + # FileUtils.mv 'stuff.rb', '/notexist/lib/ruby', :force => true # no error + # + # FileUtils.mv %w(junk.txt dust.txt), '/home/aamine/.trash/' + # FileUtils.mv Dir.glob('test*.rb'), 'test', :noop => true, :verbose => true + # + def mv(src, dest, options = {}) + fu_check_options options, OPT_TABLE['mv'] + fu_output_message "mv#{options[:force] ? ' -f' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose] + return if options[:noop] + fu_each_src_dest(src, dest) do |s, d| + destent = Entry_.new(d, nil, true) + begin + if destent.exist? + if destent.directory? + raise Errno::EEXIST, dest + else + destent.remove_file if rename_cannot_overwrite_file? + end + end + begin + File.rename s, d + rescue Errno::EXDEV + copy_entry s, d, true + if options[:secure] + remove_entry_secure s, options[:force] + else + remove_entry s, options[:force] + end + end + rescue SystemCallError + raise unless options[:force] + end + end + end + module_function :mv + + def mv_in_bar(bar, src, dest, options = {}) + fu_check_options options, OPT_TABLE['mv'] + fu_output_message "mv#{options[:force] ? ' -f' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose] + return if options[:noop] + fu_each_src_dest(src, dest) do |s, d| + destent = Entry_.new(d, nil, true) + begin + if destent.exist? + if destent.directory? + raise Errno::EEXIST, dest + else + destent.remove_file if rename_cannot_overwrite_file? + end + end + begin + File.rename s, d + rescue Errno::EXDEV + copy_entry_in_bar bar, s, d, true + if options[:secure] + remove_entry_secure s, options[:force] + else + remove_entry s, options[:force] + end + end + rescue SystemCallError + raise unless options[:force] + end + end + end + module_function :mv_in_bar + + alias move mv + module_function :move + + OPT_TABLE['mv'] = + OPT_TABLE['move'] = [:force, :noop, :verbose, :secure] + + def rename_cannot_overwrite_file? #:nodoc: + /djgpp|cygwin|mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM + end + private_module_function :rename_cannot_overwrite_file? + + # + # Options: force noop verbose + # + # Remove file(s) specified in +list+. This method cannot remove directories. + # All StandardErrors are ignored when the :force option is set. + # + # FileUtils.rm %w( junk.txt dust.txt ) + # FileUtils.rm Dir.glob('*.so') + # FileUtils.rm 'NotExistFile', :force => true # never raises exception + # + def rm(list, options = {}) + fu_check_options options, OPT_TABLE['rm'] + list = fu_list(list) + fu_output_message "rm#{options[:force] ? ' -f' : ''} #{list.join ' '}" if options[:verbose] + return if options[:noop] + + list.each do |path| + remove_file path, options[:force] + end + end + module_function :rm + + alias remove rm + module_function :remove + + OPT_TABLE['rm'] = + OPT_TABLE['remove'] = [:force, :noop, :verbose] + + # + # Options: noop verbose + # + # Equivalent to + # + # #rm(list, :force => true) + # + def rm_f(list, options = {}) + fu_check_options options, OPT_TABLE['rm_f'] + options = options.dup + options[:force] = true + rm list, options + end + module_function :rm_f + + alias safe_unlink rm_f + module_function :safe_unlink + + OPT_TABLE['rm_f'] = + OPT_TABLE['safe_unlink'] = [:noop, :verbose] + + # + # Options: force noop verbose secure + # + # remove files +list+[0] +list+[1]... If +list+[n] is a directory, + # removes its all contents recursively. This method ignores + # StandardError when :force option is set. + # + # FileUtils.rm_r Dir.glob('/tmp/*') + # FileUtils.rm_r '/', :force => true # :-) + # + # WARNING: This method causes local vulnerability + # if one of parent directories or removing directory tree are world + # writable (including /tmp, whose permission is 1777), and the current + # process has strong privilege such as Unix super user (root), and the + # system has symbolic link. For secure removing, read the documentation + # of #remove_entry_secure carefully, and set :secure option to true. + # Default is :secure=>false. + # + # NOTE: This method calls #remove_entry_secure if :secure option is set. + # See also #remove_entry_secure. + # + def rm_r(list, options = {}) + fu_check_options options, OPT_TABLE['rm_r'] + # options[:secure] = true unless options.key?(:secure) + list = fu_list(list) + fu_output_message "rm -r#{options[:force] ? 'f' : ''} #{list.join ' '}" if options[:verbose] + return if options[:noop] + list.each do |path| + if options[:secure] + remove_entry_secure path, options[:force] + else + remove_entry path, options[:force] + end + end + end + module_function :rm_r + + OPT_TABLE['rm_r'] = [:force, :noop, :verbose, :secure] + + # + # Options: noop verbose secure + # + # Equivalent to + # + # #rm_r(list, :force => true) + # + # WARNING: This method causes local vulnerability. + # Read the documentation of #rm_r first. + # + def rm_rf(list, options = {}) + fu_check_options options, OPT_TABLE['rm_rf'] + options = options.dup + options[:force] = true + rm_r list, options + end + module_function :rm_rf + + alias rmtree rm_rf + module_function :rmtree + + OPT_TABLE['rm_rf'] = + OPT_TABLE['rmtree'] = [:noop, :verbose, :secure] + + # + # This method removes a file system entry +path+. +path+ shall be a + # regular file, a directory, or something. If +path+ is a directory, + # remove it recursively. This method is required to avoid TOCTTOU + # (time-of-check-to-time-of-use) local security vulnerability of #rm_r. + # #rm_r causes security hole when: + # + # * Parent directory is world writable (including /tmp). + # * Removing directory tree includes world writable directory. + # * The system has symbolic link. + # + # To avoid this security hole, this method applies special preprocess. + # If +path+ is a directory, this method chown(2) and chmod(2) all + # removing directories. This requires the current process is the + # owner of the removing whole directory tree, or is the super user (root). + # + # WARNING: You must ensure that *ALL* parent directories are not + # world writable. Otherwise this method does not work. + # Only exception is temporary directory like /tmp and /var/tmp, + # whose permission is 1777. + # + # WARNING: Only the owner of the removing directory tree, or Unix super + # user (root) should invoke this method. Otherwise this method does not + # work. + # + # For details of this security vulnerability, see Perl's case: + # + # http://www.cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2005-0448 + # http://www.cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2004-0452 + # + # For fileutils.rb, this vulnerability is reported in [ruby-dev:26100]. + # + def remove_entry_secure(path, force = false) + unless fu_have_symlink? + remove_entry path, force + return + end + fullpath = File.expand_path(path) + st = File.lstat(fullpath) + unless st.directory? + File.unlink fullpath + return + end + # is a directory. + parent_st = File.stat(File.dirname(fullpath)) + unless fu_world_writable?(parent_st) + remove_entry path, force + return + end + unless parent_st.sticky? + raise ArgumentError, "parent directory is world writable, FileUtils#remove_entry_secure does not work; abort: #{path.inspect} (parent directory mode #{'%o' % parent_st.mode})" + end + # freeze tree root + euid = Process.euid + File.open(fullpath + '/.') {|f| + unless fu_stat_identical_entry?(st, f.stat) + # symlink (TOC-to-TOU attack?) + File.unlink fullpath + return + end + f.chown euid, -1 + f.chmod 0700 + } + # ---- tree root is frozen ---- + root = Entry_.new(path) + root.preorder_traverse do |ent| + if ent.directory? + ent.chown euid, -1 + ent.chmod 0700 + end + end + root.postorder_traverse do |ent| + begin + ent.remove + rescue + raise unless force + end + end + rescue + raise unless force + end + module_function :remove_entry_secure + + def fu_world_writable?(st) + (st.mode & 0002) != 0 + end + private_module_function :fu_world_writable? + + def fu_have_symlink? #:nodoc + File.symlink nil, nil + rescue NotImplementedError + return false + rescue + return true + end + private_module_function :fu_have_symlink? + + def fu_stat_identical_entry?(a, b) #:nodoc: + a.dev == b.dev and a.ino == b.ino + end + private_module_function :fu_stat_identical_entry? + + # + # This method removes a file system entry +path+. + # +path+ might be a regular file, a directory, or something. + # If +path+ is a directory, remove it recursively. + # + # See also #remove_entry_secure. + # + def remove_entry(path, force = false) + Entry_.new(path).postorder_traverse do |ent| + begin + ent.remove + rescue + raise unless force + end + end + rescue + raise unless force + end + module_function :remove_entry + + # + # Removes a file +path+. + # This method ignores StandardError if +force+ is true. + # + def remove_file(path, force = false) + Entry_.new(path).remove_file + rescue + raise unless force + end + module_function :remove_file + + # + # Removes a directory +dir+ and its contents recursively. + # This method ignores StandardError if +force+ is true. + # + def remove_dir(path, force = false) + remove_entry path, force # FIXME?? check if it is a directory + end + module_function :remove_dir + + # + # Returns true if the contents of a file A and a file B are identical. + # + # FileUtils.compare_file('somefile', 'somefile') #=> true + # FileUtils.compare_file('/bin/cp', '/bin/mv') #=> maybe false + # + def compare_file(a, b) + return false unless File.size(a) == File.size(b) + File.open(a, 'rb') {|fa| + File.open(b, 'rb') {|fb| + return compare_stream(fa, fb) + } + } + end + module_function :compare_file + + alias identical? compare_file + alias cmp compare_file + module_function :identical? + module_function :cmp + + # + # Returns true if the contents of a stream +a+ and +b+ are identical. + # + def compare_stream(a, b) + bsize = fu_stream_blksize(a, b) + sa = sb = nil + while sa == sb + sa = a.read(bsize) + sb = b.read(bsize) + unless sa and sb + if sa.nil? and sb.nil? + return true + end + end + end + false + end + module_function :compare_stream + + # + # Options: mode preserve noop verbose + # + # If +src+ is not same as +dest+, copies it and changes the permission + # mode to +mode+. If +dest+ is a directory, destination is +dest+/+src+. + # This method removes destination before copy. + # + # FileUtils.install 'ruby', '/usr/local/bin/ruby', :mode => 0755, :verbose => true + # FileUtils.install 'lib.rb', '/usr/local/lib/ruby/site_ruby', :verbose => true + # + def install(src, dest, options = {}) + fu_check_options options, OPT_TABLE['install'] + fu_output_message "install -c#{options[:preserve] && ' -p'}#{options[:mode] ? (' -m 0%o' % options[:mode]) : ''} #{[src,dest].flatten.join ' '}" if options[:verbose] + return if options[:noop] + fu_each_src_dest(src, dest) do |s, d| + unless File.exist?(d) and compare_file(s, d) + remove_file d, true + st = File.stat(s) if options[:preserve] + copy_file s, d + File.utime st.atime, st.mtime, d if options[:preserve] + File.chmod options[:mode], d if options[:mode] + end + end + end + module_function :install + + OPT_TABLE['install'] = [:mode, :preserve, :noop, :verbose] + + # + # Options: noop verbose + # + # Changes permission bits on the named files (in +list+) to the bit pattern + # represented by +mode+. + # + # FileUtils.chmod 0755, 'somecommand' + # FileUtils.chmod 0644, %w(my.rb your.rb his.rb her.rb) + # FileUtils.chmod 0755, '/usr/bin/ruby', :verbose => true + # + def chmod(mode, list, options = {}) + fu_check_options options, OPT_TABLE['chmod'] + list = fu_list(list) + fu_output_message sprintf('chmod %o %s', mode, list.join(' ')) if options[:verbose] + return if options[:noop] + list.each do |path| + Entry_.new(path).chmod mode + end + end + module_function :chmod + + OPT_TABLE['chmod'] = [:noop, :verbose] + + # + # Options: noop verbose force + # + # Changes permission bits on the named files (in +list+) + # to the bit pattern represented by +mode+. + # + # FileUtils.chmod_R 0700, "/tmp/app.#{$$}" + # + def chmod_R(mode, list, options = {}) + fu_check_options options, OPT_TABLE['chmod_R'] + list = fu_list(list) + fu_output_message sprintf('chmod -R%s %o %s', + (options[:force] ? 'f' : ''), + mode, list.join(' ')) if options[:verbose] + return if options[:noop] + list.each do |root| + Entry_.new(root).traverse do |ent| + begin + ent.chmod mode + rescue + raise unless options[:force] + end + end + end + end + module_function :chmod_R + + OPT_TABLE['chmod_R'] = [:noop, :verbose, :force] + + # + # Options: noop verbose + # + # Changes owner and group on the named files (in +list+) + # to the user +user+ and the group +group+. +user+ and +group+ + # may be an ID (Integer/String) or a name (String). + # If +user+ or +group+ is nil, this method does not change + # the attribute. + # + # FileUtils.chown 'root', 'staff', '/usr/local/bin/ruby' + # FileUtils.chown nil, 'bin', Dir.glob('/usr/bin/*'), :verbose => true + # + def chown(user, group, list, options = {}) + fu_check_options options, OPT_TABLE['chown'] + list = fu_list(list) + fu_output_message sprintf('chown %s%s', + [user,group].compact.join(':') + ' ', + list.join(' ')) if options[:verbose] + return if options[:noop] + uid = fu_get_uid(user) + gid = fu_get_gid(group) + list.each do |path| + Entry_.new(path).chown uid, gid + end + end + module_function :chown + + OPT_TABLE['chown'] = [:noop, :verbose] + + # + # Options: noop verbose force + # + # Changes owner and group on the named files (in +list+) + # to the user +user+ and the group +group+ recursively. + # +user+ and +group+ may be an ID (Integer/String) or + # a name (String). If +user+ or +group+ is nil, this + # method does not change the attribute. + # + # FileUtils.chown_R 'www', 'www', '/var/www/htdocs' + # FileUtils.chown_R 'cvs', 'cvs', '/var/cvs', :verbose => true + # + def chown_R(user, group, list, options = {}) + fu_check_options options, OPT_TABLE['chown_R'] + list = fu_list(list) + fu_output_message sprintf('chown -R%s %s%s', + (options[:force] ? 'f' : ''), + [user,group].compact.join(':') + ' ', + list.join(' ')) if options[:verbose] + return if options[:noop] + uid = fu_get_uid(user) + gid = fu_get_gid(group) + return unless uid or gid + list.each do |root| + Entry_.new(root).traverse do |ent| + begin + ent.chown uid, gid + rescue + raise unless options[:force] + end + end + end + end + module_function :chown_R + + OPT_TABLE['chown_R'] = [:noop, :verbose, :force] + + begin + require 'etc' + + def fu_get_uid(user) #:nodoc: + return nil unless user + user = user.to_s + if /\A\d+\z/ =~ user + then user.to_i + else Etc.getpwnam(user).uid + end + end + private_module_function :fu_get_uid + + def fu_get_gid(group) #:nodoc: + return nil unless group + if /\A\d+\z/ =~ group + then group.to_i + else Etc.getgrnam(group).gid + end + end + private_module_function :fu_get_gid + + rescue LoadError + # need Win32 support??? + + def fu_get_uid(user) #:nodoc: + user # FIXME + end + private_module_function :fu_get_uid + + def fu_get_gid(group) #:nodoc: + group # FIXME + end + private_module_function :fu_get_gid + end + + # + # Options: noop verbose + # + # Updates modification time (mtime) and access time (atime) of file(s) in + # +list+. Files are created if they don't exist. + # + # FileUtils.touch 'timestamp' + # FileUtils.touch Dir.glob('*.c'); system 'make' + # + def touch(list, options = {}) + fu_check_options options, OPT_TABLE['touch'] + list = fu_list(list) + created = nocreate = options[:nocreate] + t = options[:mtime] + if options[:verbose] + fu_output_message "touch #{nocreate ? ' -c' : ''}#{t ? t.strftime(' -t %Y%m%d%H%M.%S') : ''}#{list.join ' '}" + end + return if options[:noop] + list.each do |path| + created = nocreate + begin + File.utime(t, t, path) + rescue Errno::ENOENT + raise if created + File.open(path, 'a') { + ; + } + created = true + retry if t + end + end + end + module_function :touch + + OPT_TABLE['touch'] = [:noop, :verbose, :mtime, :nocreate] + + private + + module StreamUtils_ + private + + def fu_windows? + /mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM + end + + # changed + def fu_copy_stream0_in_bar(bar, src, dest, blksize) #:nodoc: + report = false + if File.size?(src) + report = true + ticks = File.size(src) / blksize + bar.max = ticks + bar.counter = i = MutableNumber.new(0) + log('.') + end + txt = "cp #{File.basename(src.path)} ..." + bar.set_text_prefix(txt) + + while s = src.read(blksize) + dest.write s + i.add 1 + end + end + + def fu_copy_stream0(src, dest, blksize) #:nodoc: + # FIXME: readpartial? + while s = src.read(blksize) + dest.write s + end + end + + def fu_stream_blksize(*streams) + streams.each do |s| + next unless s.respond_to?(:stat) + size = fu_blksize(s.stat) + return size if size + end + fu_default_blksize() + end + + def fu_blksize(st) + s = st.blksize + return nil unless s + return nil if s == 0 + s + end + + def fu_default_blksize + 1024 + end + end + + include StreamUtils_ + extend StreamUtils_ + + class Entry_ #:nodoc: internal use only + include StreamUtils_ + + def initialize(a, b = nil, deref = false) + @prefix = @rel = @path = nil + if b + @prefix = a + @rel = b + else + @path = a + end + @deref = deref + @stat = nil + @lstat = nil + end + + def inspect + "\#<#{self.class} #{path()}>" + end + + def path + if @path + @path.to_str + else + join(@prefix, @rel) + end + end + + def prefix + @prefix || @path + end + + def rel + @rel + end + + def dereference? + @deref + end + + def exist? + lstat! ? true : false + end + + def file? + s = lstat! + s and s.file? + end + + def directory? + s = lstat! + s and s.directory? + end + + def symlink? + s = lstat! + s and s.symlink? + end + + def chardev? + s = lstat! + s and s.chardev? + end + + def blockdev? + s = lstat! + s and s.blockdev? + end + + def socket? + s = lstat! + s and s.socket? + end + + def pipe? + s = lstat! + s and s.pipe? + end + + S_IF_DOOR = 0xD000 + + def door? + s = lstat! + s and (s.mode & 0xF000 == S_IF_DOOR) + end + + def entries + Dir.entries(path())\ + .reject {|n| n == '.' or n == '..' }\ + .map {|n| Entry_.new(prefix(), join(rel(), n.untaint)) } + end + + def stat + return @stat if @stat + if lstat() and lstat().symlink? + @stat = File.stat(path()) + else + @stat = lstat() + end + @stat + end + + def stat! + return @stat if @stat + if lstat! and lstat!.symlink? + @stat = File.stat(path()) + else + @stat = lstat! + end + @stat + rescue SystemCallError + nil + end + + def lstat + if dereference? + @lstat ||= File.stat(path()) + else + @lstat ||= File.lstat(path()) + end + end + + def lstat! + lstat() + rescue SystemCallError + nil + end + + def chmod(mode) + if symlink? + File.lchmod mode, path() if have_lchmod? + else + File.chmod mode, path() + end + end + + def chown(uid, gid) + if symlink? + File.lchown uid, gid, path() if have_lchown? + else + File.chown uid, gid, path() + end + end + + #changed + def copy_in_bar(bar, dest) + case + when file? + copy_file_in_bar bar, dest + when directory? + begin + Dir.mkdir dest + rescue + raise unless File.directory?(dest) + end + when symlink? + File.symlink File.readlink(path()), dest + when chardev? + raise "cannot handle device file" unless File.respond_to?(:mknod) + mknod dest, ?c, 0666, lstat().rdev + when blockdev? + raise "cannot handle device file" unless File.respond_to?(:mknod) + mknod dest, ?b, 0666, lstat().rdev + when socket? + raise "cannot handle socket" unless File.respond_to?(:mknod) + mknod dest, nil, lstat().mode, 0 + when pipe? + raise "cannot handle FIFO" unless File.respond_to?(:mkfifo) + mkfifo dest, 0666 + when door? + raise "cannot handle door: #{path()}" + else + raise "unknown file type: #{path()}" + end + end + + def copy(dest) + case + when file? + copy_file dest + when directory? + begin + Dir.mkdir dest + rescue + raise unless File.directory?(dest) + end + when symlink? + File.symlink File.readlink(path()), dest + when chardev? + raise "cannot handle device file" unless File.respond_to?(:mknod) + mknod dest, ?c, 0666, lstat().rdev + when blockdev? + raise "cannot handle device file" unless File.respond_to?(:mknod) + mknod dest, ?b, 0666, lstat().rdev + when socket? + raise "cannot handle socket" unless File.respond_to?(:mknod) + mknod dest, nil, lstat().mode, 0 + when pipe? + raise "cannot handle FIFO" unless File.respond_to?(:mkfifo) + mkfifo dest, 0666 + when door? + raise "cannot handle door: #{path()}" + else + raise "unknown file type: #{path()}" + end + end + + def copy_file(dest) + st = stat() + File.open(path(), 'rb') {|r| + File.open(dest, 'wb', st.mode) {|w| + fu_copy_stream0 r, w, (fu_blksize(st) || fu_default_blksize()) + } + } + end + + # changed + def copy_file_in_bar(bar, dest) + st = stat() + File.open(path(), 'rb') {|r| + File.open(dest, 'wb', st.mode) {|w| + fu_copy_stream0_in_bar bar, r, w, (fu_blksize(st) || fu_default_blksize()) + } + } + end + + def copy_metadata(path) + st = lstat() + File.utime st.atime, st.mtime, path + begin + File.chown st.uid, st.gid, path + rescue Errno::EPERM + # clear setuid/setgid + File.chmod st.mode & 01777, path + else + File.chmod st.mode, path + end + end + + def remove + if directory? + remove_dir1 + else + remove_file + end + end + + def remove_dir1 + platform_support { + Dir.rmdir path().sub(%r</\z>, '') + } + end + + def remove_file + platform_support { + File.unlink path + } + end + + def platform_support + return yield unless fu_windows? + first_time_p = true + begin + yield + rescue Errno::ENOENT + raise + rescue => err + if first_time_p + first_time_p = false + begin + File.chmod 0700, path() # Windows does not have symlink + retry + rescue SystemCallError + end + end + raise err + end + end + + def preorder_traverse + stack = [self] + while ent = stack.pop + yield ent + stack.concat ent.entries.reverse if ent.directory? + end + end + + alias traverse preorder_traverse + + def postorder_traverse + if directory? + entries().each do |ent| + ent.postorder_traverse do |e| + yield e + end + end + end + yield self + end + + private + + $fileutils_rb_have_lchmod = nil + + def have_lchmod? + # This is not MT-safe, but it does not matter. + if $fileutils_rb_have_lchmod == nil + $fileutils_rb_have_lchmod = check_have_lchmod? + end + $fileutils_rb_have_lchmod + end + + def check_have_lchmod? + return false unless File.respond_to?(:lchmod) + File.lchmod 0 + return true + rescue NotImplementedError + return false + end + + $fileutils_rb_have_lchown = nil + + def have_lchown? + # This is not MT-safe, but it does not matter. + if $fileutils_rb_have_lchown == nil + $fileutils_rb_have_lchown = check_have_lchown? + end + $fileutils_rb_have_lchown + end + + def check_have_lchown? + return false unless File.respond_to?(:lchown) + File.lchown nil, nil + return true + rescue NotImplementedError + return false + end + + def join(dir, base) + return dir.to_str if not base or base == '.' + return base.to_str if not dir or dir == '.' + File.join(dir, base) + end + end # class Entry_ + + def fu_list(arg) #:nodoc: + [arg].flatten.map {|path| path.to_str } + end + private_module_function :fu_list + + def fu_each_src_dest(src, dest) #:nodoc: + fu_each_src_dest0(src, dest) do |s, d| + raise ArgumentError, "same file: #{s} and #{d}" if fu_same?(s, d) + yield s, d + end + end + private_module_function :fu_each_src_dest + + def fu_each_src_dest0(src, dest) #:nodoc: + if src.is_a?(Array) + src.each do |s| + s = s.to_str + yield s, File.join(dest, File.basename(s)) + end + else + src = src.to_str + if File.directory?(dest) + yield src, File.join(dest, File.basename(src)) + else + yield src, dest.to_str + end + end + end + private_module_function :fu_each_src_dest0 + + def fu_same?(a, b) #:nodoc: + if fu_have_st_ino? + st1 = File.stat(a) + st2 = File.stat(b) + st1.dev == st2.dev and st1.ino == st2.ino + else + File.expand_path(a) == File.expand_path(b) + end + rescue Errno::ENOENT + return false + end + private_module_function :fu_same? + + def fu_have_st_ino? #:nodoc: + not fu_windows? + end + private_module_function :fu_have_st_ino? + + def fu_check_options(options, optdecl) #:nodoc: + h = options.dup + optdecl.each do |opt| + h.delete opt + end + raise ArgumentError, "no such option: #{h.keys.join(' ')}" unless h.empty? + end + private_module_function :fu_check_options + + def fu_update_option(args, new) #:nodoc: + if args.last.is_a?(Hash) + args[-1] = args.last.dup.update(new) + else + args.push new + end + args + end + private_module_function :fu_update_option + + @fileutils_output = $stderr + @fileutils_label = '' + + def fu_output_message(msg) #:nodoc: + @fileutils_output ||= $stderr + @fileutils_label ||= '' + @fileutils_output.puts @fileutils_label + msg + end + private_module_function :fu_output_message + + # + # Returns an Array of method names which have any options. + # + # p FileUtils.commands #=> ["chmod", "cp", "cp_r", "install", ...] + # + def FileUtils.commands + OPT_TABLE.keys + end + + # + # Returns an Array of option names. + # + # p FileUtils.options #=> ["noop", "force", "verbose", "preserve", "mode"] + # + def FileUtils.options + OPT_TABLE.values.flatten.uniq.map {|sym| sym.to_s } + end + + # + # Returns true if the method +mid+ have an option +opt+. + # + # p FileUtils.have_option?(:cp, :noop) #=> true + # p FileUtils.have_option?(:rm, :force) #=> true + # p FileUtils.have_option?(:rm, :perserve) #=> false + # + def FileUtils.have_option?(mid, opt) + li = OPT_TABLE[mid.to_s] or raise ArgumentError, "no such method: #{mid}" + li.include?(opt) + end + + # + # Returns an Array of option names of the method +mid+. + # + # p FileUtils.options(:rm) #=> ["noop", "verbose", "force"] + # + def FileUtils.options_of(mid) + OPT_TABLE[mid.to_s].map {|sym| sym.to_s } + end + + # + # Returns an Array of method names which have the option +opt+. + # + # p FileUtils.collect_method(:preserve) #=> ["cp", "cp_r", "copy", "install"] + # + def FileUtils.collect_method(opt) + OPT_TABLE.keys.select {|m| OPT_TABLE[m].include?(opt) } + end + + METHODS = singleton_methods() - %w( private_module_function + commands options have_option? options_of collect_method ) + + # + # This module has all methods of FileUtils module, but it outputs messages + # before acting. This equates to passing the <tt>:verbose</tt> flag to + # methods in FileUtils. + # + module Verbose + include FileUtils + @fileutils_output = $stderr + @fileutils_label = '' + ::FileUtils.collect_method(:verbose).each do |name| + module_eval(<<-EOS, __FILE__, __LINE__ + 1) + def #{name}(*args) + super(*fu_update_option(args, :verbose => true)) + end + private :#{name} + EOS + end + extend self + class << self + ::FileUtils::METHODS.each do |m| + public m + end + end + end + + # + # This module has all methods of FileUtils module, but never changes + # files/directories. This equates to passing the <tt>:noop</tt> flag + # to methods in FileUtils. + # + module NoWrite + include FileUtils + @fileutils_output = $stderr + @fileutils_label = '' + ::FileUtils.collect_method(:noop).each do |name| + module_eval(<<-EOS, __FILE__, __LINE__ + 1) + def #{name}(*args) + super(*fu_update_option(args, :noop => true)) + end + private :#{name} + EOS + end + extend self + class << self + ::FileUtils::METHODS.each do |m| + public m + end + end + end + + # + # This module has all methods of FileUtils module, but never changes + # files/directories, with printing message before acting. + # This equates to passing the <tt>:noop</tt> and <tt>:verbose</tt> flag + # to methods in FileUtils. + # + module DryRun + include FileUtils + @fileutils_output = $stderr + @fileutils_label = '' + ::FileUtils.collect_method(:noop).each do |name| + module_eval(<<-EOS, __FILE__, __LINE__ + 1) + def #{name}(*args) + super(*fu_update_option(args, :noop => true, :verbose => true)) + end + private :#{name} + EOS + end + extend self + class << self + ::FileUtils::METHODS.each do |m| + public m + end + end + end +end + +# vi: sts=2 ts=2 sw=2 diff --git a/code/fm.rb b/code/fm.rb index e5b66aad..d6c8cb85 100644 --- a/code/fm.rb +++ b/code/fm.rb @@ -9,6 +9,9 @@ OPTIONS = { module Fm + SCHEDULER_PRIORITY = -1 + COPY_PRIORITY = -2 + SCHEDULED = [] COLUMNS = 4 UPDATE_SIGNAL = 31 @@ -53,9 +56,26 @@ module Fm draw end +# for i in 1..20 +# eval "Signal.trap(#{i}) do +# log #{i} +# exit if #{i} == 9 end" +# end + + boot_up(pwd) + end + + attr_reader(:dirs, :pwd) + + def self.pwd() @pwd end + def self.boot_up(pwd=nil) + pwd ||= @pwd.path || Dir.getwd # This thread inspects directories @scheduler_active = false + if defined? @scheduler and Thread === @scheduler + @scheduler.kill + end @scheduler = Thread.new do while true Thread.stop @@ -70,11 +90,12 @@ module Fm end end end + @scheduler.priority = SCHEDULER_PRIORITY @dirs = Hash.new() do |hash, key| hash[key] = newdir = Directory.new(key) - schedule newdir +# newdir.schedule newdir end @@ -85,8 +106,6 @@ module Fm @scheduler.run end - attr_reader(:dirs, :pwd) - def self.force_update # Send a signal to this process Process.kill(UPDATE_SIGNAL, PID) @@ -110,6 +129,7 @@ module Fm end def self.main_loop + bool = false while true if @pwd.size == 0 or @pwd.pos < 0 @pwd.pos = 0 @@ -119,17 +139,24 @@ module Fm begin # @mutex.synchronize { + log "drawing" draw() # } rescue Interrupt on_interrupt rescue Exception -# log($!) -# log(caller) + log($!) + log(caller) end begin - key = geti +# unless bool +# bool = true + key = geti +# else +# key = geti +# key = 'j' +# end # @mutex.synchronize { press(key) # } @@ -141,6 +168,8 @@ module Fm def self.current_path() @pwd.path end + def self.reset_title() set_title("fm: #{@pwd.path}") end + def self.enter_dir_safely(dir) dir = File.expand_path(dir) if File.exists?(dir) and File.directory?(dir) @@ -149,6 +178,9 @@ module Fm enter_dir(dir) return true rescue + log("NIGGER" * 100) + log($!) + log(caller) enter_dir(olddir) return false end @@ -157,7 +189,7 @@ module Fm def self.enter_dir(dir) @pwd.restore if @pwd - @marks = 0 + @marked = [] dir = File.expand_path(dir) oldpath = @path.dup @@ -176,12 +208,13 @@ module Fm end @pwd = @path.last + @pwd.pos = @pwd.pos @pwd.files_raw.dup.each do |x| @dirs[x] if File.directory?(x) end - set_title "fm: #{@pwd.path}" + reset_title() if @path.size < oldpath.size @pwd.pos = @pwd.files_raw.index(oldpath.last.path) || 0 @@ -200,8 +233,16 @@ module Fm end def self.currentfile() @pwd.files[@pwd.pos] end + def self.selection() + if @marked.empty? + [currentfile] + else + @marked.dup + end + end def self.schedule(dir) + dir.scheduled = true SCHEDULED << dir @scheduler.run end diff --git a/code/keys.rb b/code/keys.rb index d6ae01ea..edcbab53 100644 --- a/code/keys.rb +++ b/code/keys.rb @@ -2,11 +2,13 @@ module Fm # ALL combinations of multiple keys (but without the last letter) # or regexps which match combinations need to be in here! COMBS = %w( - g d y c Z delet cu - t S ? ?g ?f + g d df y c Z delet cu + ter ta S ? ?g ?f :q - /[m`']/ /[fF/!].*/ + /[m`']/ /[fF/!q].*/ + /[ri]\d*\w*[^ri]/ /(cw|cd|mv).*/ + /b(l(o(c(k(.*)?)?)?)?)?/ /m(k(d(i(r(.*)?)?)?)?)?/ /r(e(n(a(m(e(.*)?)?)?)?)?)?/ ) @@ -54,6 +56,16 @@ module Fm end end + def self.find_newest() + newest = nil + for f in @pwd.files + if newest.nil? or newest.ctime < f.ctime + newest = f + end + end + @pwd.pointed_file = newest.path + end + def self.hints(str) begin rx = Regexp.new(str, Regexp::IGNORECASE) @@ -70,6 +82,7 @@ module Fm g = File.basename(f) if g =~ rx unless pointed + log "point at #{f}" @pwd.pointed_file = f pointed = true end @@ -99,6 +112,8 @@ module Fm else @buffer.slice! -1 end + elsif key == '<c-u>' + @buffer = '' else @buffer << key end @@ -123,26 +138,59 @@ module Fm starti - when /S(.)/ + when /^S(.)$/ OPTIONS['sort_reverse'] = $1.ord.between?(65, 90) case $1 when 'n' OPTIONS['sort'] = :name + when 'e' + OPTIONS['sort'] = :ext + when 't' + OPTIONS['sort'] = :type when 's' OPTIONS['sort'] = :size - when 'e' - OPTIONS['sort'] = :extension when 'm' OPTIONS['sort'] = :mtime - when 'c', 't' + when 'c' OPTIONS['sort'] = :ctime end @pwd.schedule - when 'r', 'R' + when 'tar' + closei + system('tar', 'cvvf', 'pack.tar', *selection.map{|x| x.basename}) + @pwd.refresh! + starti + + when 'R' @pwd.refresh! + when '@', '.' + if defined? @record + @buffer = '' + memo = '' + @record.each_char do |c| + if memo.empty? + if c == '<' + memo << c + else + press c + end + else + memo << c + if c == '>' + press memo + memo.clear + end + end + end + end + + when /^q.+q$/ + @record = @buffer[1...-1] + @buffer = '' + when 'x' @bars.first.kill unless @bars.empty? @@ -156,23 +204,19 @@ module Fm @pwd.pos -= lines/2 when 'cp', 'yy' - if @marked.empty? - @copy = [currentfile] - else - @copy = @marked.dup - end + @copy = selection @cut = false when 'cut' - if @marked.empty? - @copy = [currentfile] - else - @copy = @marked.dup - end + @copy = selection @cut = true when 'n' - search(@search_string, 1) + if @search_string.empty? + find_newest + else + search(@search_string, 1) + end when 'N' search(@search_string, 0, true) @@ -206,6 +250,9 @@ module Fm end end + when 'A' + @buffer = "cw #{currentfile.name}" + when /^f(.+)$/ str = $1 if str =~ /^\s?(.*)(L|;|<cr>|<esc>)$/ @@ -245,6 +292,9 @@ module Fm @pwd.schedule end end + + when /^block.*stop$/ + @buffer = '' when /^!(.+)$/ str = $1 @@ -269,14 +319,41 @@ module Fm end end - when /^(?:mv|cw|rename)(.+)$/ - str = $1 + when /^(mv|cw|rename)(.+)$/ + str = $2 + if $1 == 'mv' + if str =~ /['`"]([\w\d])/ + if path = @memory[$1] + str = '' + @buffer.clear + if File.exists?(path) and File.directory?(path) + Action.move(selection, path) + end + end + end + end + log str if str =~ /^\s?(.*)(<cr>|<esc>)$/ @buffer = '' if $2 == '<cr>' - Action.move(currentfile, $1) + files = selection + if files.size == 1 + fn = $1 + log "!!! #{fn}" + unless fn.include? '.' + if ext = files.first.basename.from_last('.') + fn << ".#{ext}" + end + log "??? #{ext}" + end + Action.move(files, fn) + @pwd.refresh! + @pwd.find_file(fn) + else + Action.move(files, $1) + @pwd.refresh! + end end - @pwd.schedule end when 'tc' @@ -294,7 +371,7 @@ module Fm @pwd.schedule when 'delete' - files = @marked.empty? ? [currentfile] : @marked + files = selection @marked = [] for f in files if f and f.exists? and f.dir? @@ -310,6 +387,10 @@ module Fm else Action.copy(@copy, @pwd.path) end + @pwd.refresh! + if @copy.size == 1 + @pwd.find_file(@copy[0].basename) + end when /^[`'](.)$/ if dir = @memory[$1] and not @pwd.path == dir @@ -317,12 +398,22 @@ module Fm enter_dir_safely(dir) end - when '<tab>' + when '<s-tab>' if dir = @memory['`'] and not @pwd.path == dir remember_dir enter_dir_safely(dir) end + when '<tab>' + if dir = @memory['9'] and dir != '/' + unless @pwd.path == dir + enter_dir_safely(dir) + end + elsif dir = @memory['`'] and not @pwd.path == dir + remember_dir + enter_dir_safely(dir) + end + when /^m(.)$/ @memory[$1] = @pwd.path @@ -369,13 +460,16 @@ module Fm end @pwd.schedule - when 'dD' + when 'dD', 'dfd' cf = currentfile if cf and cf.exists? cf.delete! @pwd.schedule end + when 'term' + fork do exec 'x-terminal-emulator' end + when 'g0' remember_dir enter_dir('/') @@ -418,31 +512,93 @@ module Fm end when '<cr>', 'l', ';', 'L', '<right>' - ascend(@buffer=='L') + ascend(@buffer=='L', @buffer=='l') + + # a = run all + # d or e = detach + # t = run in a terminal + # w = wait for <enter> after execution + # capital letter inverts + when /^[ri](\d*)([adetw]*)[ri]$/ + if $2.empty? + f = @marked.empty?? currentfile : @marked.first + flags = get_default_flags(f) + else + flags = $2 + end + opt = OpenStruct.new + opt.newway = true + + opt.mode = $1.to_i unless $1.empty? + + # Set options based on flags + + if flags =~ /a/ + opt.all = true + end + if flags =~ /[de]/ + opt.detach = true + end + if flags =~ /t/ + opt.new_term = true + opt.detach = true + end + if flags =~ /w/ + opt.wait = true + end - when 'q', 'ZZ', "\004" + if flags =~ /A/ + opt.all = false + end + if flags =~ /[DE]/ + opt.detach = false + end + if flags =~ /T/ + opt.new_term = false + end + if flags =~ /W/ + opt.wait = false + end + + Action.run(opt.__table__) + +# when 'ra' +# unless File.directory?(currentfile.path) +# Action.run(:all=>true) +# end + + when 'ZZ', '<c-d>', ':q<cr>' exit + + when '<c-r>' + Fm.boot_up + + when "-", "=" + val = "2#{key=='-' ? '-' : '+'}" + system("amixer", "-q", "set", "PCM", val, "unmute") + + else +# log key.ord end @buffer = '' unless @buffer == '' or @buffer =~ REGX end - def self.ascend(wait = false) - cf = currentfile - enter = enter_dir_safely(cf.path) - unless cf.nil? or enter - handler, wait = getfilehandler(currentfile) - if handler - closei - log handler - system(handler) - gets if wait - starti - return true + def self.ascend(wait = false, all=false) + if all and !@marked.empty? + closei + system(*['mplayer', '-fs', *@marked.map{|x| x.path}]) + starti + return true + else + cf = currentfile + enter = enter_dir_safely(cf.path) + unless enter + return Action.run(:detach=>false) end + return false end - return false end def self.descend diff --git a/code/screensaver/clock.rb b/code/screensaver/clock.rb new file mode 100644 index 00000000..6289c809 --- /dev/null +++ b/code/screensaver/clock.rb @@ -0,0 +1,10 @@ +def screensaver + cleari + + str = Time.now.to_s + s = Fm.cols - str.size + s = 1 if s < 0 + + puti((Fm.lines.to_f/2).floor, s/2, str) +# puti(rand(Fm.lines), rand(Fm.cols), 'MEDITONSIN') +end diff --git a/code/types.rb b/code/types.rb index 0fc0bf7c..96d67d06 100644 --- a/code/types.rb +++ b/code/types.rb @@ -1,5 +1,96 @@ module Fm - def self.getfilehandler_frompath(file) + MIMETYPES = Marshal.load(File.read( + File.join(FM_DIR, 'data', 'mime.dat'))) + + def self.get_default_flags(file) + case file.mimetype + when /^(?:image|video)\//; 'd' + when 'application/pdf'; 'd' + else '' end + end + + def self.filehandler(files, hash) + str = files.map{|x| x.sh}.join(' ') + type = files.first.mimetype + name = files.first.basename +# mode = hash.mode + + use = lambda do |sym| + hash.exec = App.send(sym, hash, name, str, files) + end + + case type + when /^(video|audio)\// + use.call :mplayer + when "application/pdf" + use.call :evince + when /^(image)\// + use.call :image + else + case name + when /\.(swc|smc)/ + use.call :zsnes + end + end + + return hash + end + + module App + def image(hash, name, str, file) + case hash.mode + when 4; "feh --bg-scale #{str}" + when 5; "feh --bg-tile #{str}" + when 6; "feh --bg-center #{str}" + when 2; "gimp #{str}" + when 1; "feh -F #{str}" + else "feh #{str}" + end + end + def evince(hash, name, str, file) + "evince #{str}" + end + def mplayer(*args) + hash = args[0] = args[0].dup + str = args[2] + + if hash.detach + flags = '-msglevel all=-1' + else + flags = '' + end + + case hash.mode + when nil + if name =~ /720p/ + hash.mode = 1 + else + hash.mode = 0 + end + mplayer(*args) + when 0 + return "mplayer #{flags} -fs -sid 0 #{str}" + when 1 + return "mplayer #{flags} -vm sdl -sid 0 #{str}" + end + end + def zsnes(hash, name, str, files) + case hash.mode + when 1 + return "zsnes -ad sdl -o #{str}" + else + return "zsnes -ad sdl -u -o #{str}" + end + end + + module_function *%w* + mplayer zsnes evince image + * + end + + def self.getfilehandler_frompath(*files) + file = files.first + n = files.size case file when /\.(part|avi|mpe?[g\d]|flv|fid|mkv|mov|wm[av]|vob|php|divx?|og[gmv])$/i if file =~ /720p/ @@ -8,6 +99,12 @@ module Fm return "mplayer -fs #{file.sh}", false end + when /\.java$/ + return "javac #{file.sh}", true + + when /\.class$/ + return log "java #{file.sh.before_last('.')}" + when /\.part$/ test = getfilehandler_frompath($`) if test @@ -23,10 +120,10 @@ module Fm when "Makefile" return "make" - when /\.(jpe?g|png)$/i + when /\.(jpe?g|png|gif)$/i return "feh #{file.sh}", false - when /\.html?$/i + when /\.(html?|swf)$/i return "firefox #{file.sh}" when /\.pdf$/i @@ -39,7 +136,8 @@ module Fm return "aplay -q #{file.sh}" when /\.m3u$/i - return "cmus-remote -c && cmus-remote -P #{file} && cmus-remote -C 'set play_library=false' && sleep 0.3 && cmus-remote -n", false + return "/home/hut/bin/loadplaylist #{file.sh}" +# return "cmus-remote -c && cmus-remote -P #{file} && cmus-remote -C 'set play_library=false' && sleep 0.3 && cmus-remote -n", false end diff --git a/data/generate.rb b/data/generate.rb new file mode 100755 index 00000000..c6038b3a --- /dev/null +++ b/data/generate.rb @@ -0,0 +1,21 @@ +#!/usr/bin/ruby + +file = File.read(ARGV[0] || "mime.types") + +table = {} +for l in file.lines + next if l[0] == ?# + next unless l.size > 3 + next unless l.include? ?\t + + left, *exts = l.split(/\s+/) +# print exts.inspect + for ext in exts + table[ext] = left + end +end + +File.open('mime.dat', 'w') do |f| + f.write Marshal.dump(table) +end + diff --git a/data/mime.dat b/data/mime.dat new file mode 100644 index 00000000..78b0de8c --- /dev/null +++ b/data/mime.dat Binary files differdiff --git a/data/mime.types b/data/mime.types new file mode 100644 index 00000000..866db2c2 --- /dev/null +++ b/data/mime.types @@ -0,0 +1,769 @@ +############################################################################### +# +# MIME-TYPES and the extensions that represent them +# +# This file is part of the "mime-support" package. Please send email (not a +# bug report) to mime-support@packages.debian.org if you would like new types +# and/or extensions to be added. +# +# The reason that all types are managed by the mime-support package instead +# allowing individual packages to install types in much the same way as they +# add entries in to the mailcap file is so these types can be referenced by +# other programs (such as a web server) even if the specific support package +# for that type is not installed. +# +# Users can add their own types if they wish by creating a ".mime.types" +# file in their home directory. Definitions included there will take +# precedence over those listed here. +# +# Note: Compression schemes like "gzip", "bzip", and "compress" are not +# actually "mime-types". They are "encodings" and hence must _not_ have +# entries in this file to map their extensions. The "mime-type" of an +# encoded file refers to the type of data that has been encoded, not the +# type of encoding. +# +############################################################################### + + +application/activemessage +application/andrew-inset ez +application/annodex anx +application/applefile +application/atom+xml atom +application/atomcat+xml atomcat +application/atomserv+xml atomsrv +application/atomicmail +application/batch-SMTP +application/beep+xml +application/bbolin lin +application/cals-1840 +application/cap cap pcap +application/commonground +application/cu-seeme cu +application/cybercash +application/davmount+xml davmount +application/dca-rft +application/dec-dx +application/docbook+xml +application/dsptype tsp +application/dvcs +application/ecmascript es +application/edi-consent +application/edi-x12 +application/edifact +application/eshop +application/font-tdpfr +application/futuresplash spl +application/ghostview +application/hta hta +application/http +application/hyperstudio +application/iges +application/index +application/index.cmd +application/index.obj +application/index.response +application/index.vnd +application/iotp +application/ipp +application/isup +application/java-archive jar +application/java-serialized-object ser +application/java-vm class +application/javascript js +application/m3g m3g +application/mac-binhex40 hqx +application/mac-compactpro cpt +application/macwriteii +application/marc +application/mathematica nb nbp +application/ms-tnef +application/msaccess mdb +application/msword doc dot +application/news-message-id +application/news-transmission +application/ocsp-request +application/ocsp-response +application/octet-stream bin +application/oda oda +application/ogg ogx +application/parityfec +application/pdf pdf +application/pgp-encrypted +application/pgp-keys key +application/pgp-signature pgp +application/pics-rules prf +application/pkcs10 +application/pkcs7-mime +application/pkcs7-signature +application/pkix-cert +application/pkix-crl +application/pkixcmp +application/postscript ps ai eps espi epsf eps2 eps3 +application/prs.alvestrand.titrax-sheet +application/prs.cww +application/prs.nprend +application/qsig +application/rar rar +application/rdf+xml rdf +application/remote-printing +application/riscos +application/rss+xml rss +application/rtf rtf +application/sdp +application/set-payment +application/set-payment-initiation +application/set-registration +application/set-registration-initiation +application/sgml +application/sgml-open-catalog +application/sieve +application/slate +application/smil smi smil +application/timestamp-query +application/timestamp-reply +application/vemmi +application/whoispp-query +application/whoispp-response +application/wita +application/x400-bp +application/xhtml+xml xhtml xht +application/xml xml xsl xsd +application/xml-dtd +application/xml-external-parsed-entity +application/xspf+xml xspf +application/zip zip +application/vnd.3M.Post-it-Notes +application/vnd.accpac.simply.aso +application/vnd.accpac.simply.imp +application/vnd.acucobol +application/vnd.aether.imp +application/vnd.anser-web-certificate-issue-initiation +application/vnd.anser-web-funds-transfer-initiation +application/vnd.audiograph +application/vnd.bmi +application/vnd.businessobjects +application/vnd.canon-cpdl +application/vnd.canon-lips +application/vnd.cinderella cdy +application/vnd.claymore +application/vnd.commerce-battelle +application/vnd.commonspace +application/vnd.comsocaller +application/vnd.contact.cmsg +application/vnd.cosmocaller +application/vnd.ctc-posml +application/vnd.cups-postscript +application/vnd.cups-raster +application/vnd.cups-raw +application/vnd.cybank +application/vnd.dna +application/vnd.dpgraph +application/vnd.dxr +application/vnd.ecdis-update +application/vnd.ecowin.chart +application/vnd.ecowin.filerequest +application/vnd.ecowin.fileupdate +application/vnd.ecowin.series +application/vnd.ecowin.seriesrequest +application/vnd.ecowin.seriesupdate +application/vnd.enliven +application/vnd.epson.esf +application/vnd.epson.msf +application/vnd.epson.quickanime +application/vnd.epson.salt +application/vnd.epson.ssf +application/vnd.ericsson.quickcall +application/vnd.eudora.data +application/vnd.fdf +application/vnd.ffsns +application/vnd.flographit +application/vnd.framemaker +application/vnd.fsc.weblaunch +application/vnd.fujitsu.oasys +application/vnd.fujitsu.oasys2 +application/vnd.fujitsu.oasys3 +application/vnd.fujitsu.oasysgp +application/vnd.fujitsu.oasysprs +application/vnd.fujixerox.ddd +application/vnd.fujixerox.docuworks +application/vnd.fujixerox.docuworks.binder +application/vnd.fut-misnet +application/vnd.google-earth.kml+xml kml +application/vnd.google-earth.kmz kmz +application/vnd.grafeq +application/vnd.groove-account +application/vnd.groove-identity-message +application/vnd.groove-injector +application/vnd.groove-tool-message +application/vnd.groove-tool-template +application/vnd.groove-vcard +application/vnd.hhe.lesson-player +application/vnd.hp-HPGL +application/vnd.hp-PCL +application/vnd.hp-PCLXL +application/vnd.hp-hpid +application/vnd.hp-hps +application/vnd.httphone +application/vnd.hzn-3d-crossword +application/vnd.ibm.MiniPay +application/vnd.ibm.afplinedata +application/vnd.ibm.modcap +application/vnd.informix-visionary +application/vnd.intercon.formnet +application/vnd.intertrust.digibox +application/vnd.intertrust.nncp +application/vnd.intu.qbo +application/vnd.intu.qfx +application/vnd.irepository.package+xml +application/vnd.is-xpr +application/vnd.japannet-directory-service +application/vnd.japannet-jpnstore-wakeup +application/vnd.japannet-payment-wakeup +application/vnd.japannet-registration +application/vnd.japannet-registration-wakeup +application/vnd.japannet-setstore-wakeup +application/vnd.japannet-verification +application/vnd.japannet-verification-wakeup +application/vnd.koan +application/vnd.lotus-1-2-3 +application/vnd.lotus-approach +application/vnd.lotus-freelance +application/vnd.lotus-notes +application/vnd.lotus-organizer +application/vnd.lotus-screencam +application/vnd.lotus-wordpro +application/vnd.mcd +application/vnd.mediastation.cdkey +application/vnd.meridian-slingshot +application/vnd.mif +application/vnd.minisoft-hp3000-save +application/vnd.mitsubishi.misty-guard.trustweb +application/vnd.mobius.daf +application/vnd.mobius.dis +application/vnd.mobius.msl +application/vnd.mobius.plc +application/vnd.mobius.txf +application/vnd.motorola.flexsuite +application/vnd.motorola.flexsuite.adsi +application/vnd.motorola.flexsuite.fis +application/vnd.motorola.flexsuite.gotap +application/vnd.motorola.flexsuite.kmr +application/vnd.motorola.flexsuite.ttc +application/vnd.motorola.flexsuite.wem +application/vnd.mozilla.xul+xml xul +application/vnd.ms-artgalry +application/vnd.ms-asf +application/vnd.ms-excel xls xlb xlt +application/vnd.ms-lrm +application/vnd.ms-pki.seccat cat +application/vnd.ms-pki.stl stl +application/vnd.ms-powerpoint ppt pps +application/vnd.ms-project +application/vnd.ms-tnef +application/vnd.ms-works +application/vnd.mseq +application/vnd.msign +application/vnd.music-niff +application/vnd.musician +application/vnd.netfpx +application/vnd.noblenet-directory +application/vnd.noblenet-sealer +application/vnd.noblenet-web +application/vnd.novadigm.EDM +application/vnd.novadigm.EDX +application/vnd.novadigm.EXT +application/vnd.oasis.opendocument.chart odc +application/vnd.oasis.opendocument.database odb +application/vnd.oasis.opendocument.formula odf +application/vnd.oasis.opendocument.graphics odg +application/vnd.oasis.opendocument.graphics-template otg +application/vnd.oasis.opendocument.image odi +application/vnd.oasis.opendocument.presentation odp +application/vnd.oasis.opendocument.presentation-template otp +application/vnd.oasis.opendocument.spreadsheet ods +application/vnd.oasis.opendocument.spreadsheet-template ots +application/vnd.oasis.opendocument.text odt +application/vnd.oasis.opendocument.text-master odm +application/vnd.oasis.opendocument.text-template ott +application/vnd.oasis.opendocument.text-web oth +application/vnd.osa.netdeploy +application/vnd.palm +application/vnd.pg.format +application/vnd.pg.osasli +application/vnd.powerbuilder6 +application/vnd.powerbuilder6-s +application/vnd.powerbuilder7 +application/vnd.powerbuilder7-s +application/vnd.powerbuilder75 +application/vnd.powerbuilder75-s +application/vnd.previewsystems.box +application/vnd.publishare-delta-tree +application/vnd.pvi.ptid1 +application/vnd.pwg-xhtml-print+xml +application/vnd.rapid +application/vnd.rim.cod cod +application/vnd.s3sms +application/vnd.seemail +application/vnd.shana.informed.formdata +application/vnd.shana.informed.formtemplate +application/vnd.shana.informed.interchange +application/vnd.shana.informed.package +application/vnd.smaf mmf +application/vnd.sss-cod +application/vnd.sss-dtf +application/vnd.sss-ntf +application/vnd.stardivision.calc sdc +application/vnd.stardivision.chart sds +application/vnd.stardivision.draw sda +application/vnd.stardivision.impress sdd +application/vnd.stardivision.math sdf +application/vnd.stardivision.writer sdw +application/vnd.stardivision.writer-global sgl +application/vnd.street-stream +application/vnd.sun.xml.calc sxc +application/vnd.sun.xml.calc.template stc +application/vnd.sun.xml.draw sxd +application/vnd.sun.xml.draw.template std +application/vnd.sun.xml.impress sxi +application/vnd.sun.xml.impress.template sti +application/vnd.sun.xml.math sxm +application/vnd.sun.xml.writer sxw +application/vnd.sun.xml.writer.global sxg +application/vnd.sun.xml.writer.template stw +application/vnd.svd +application/vnd.swiftview-ics +application/vnd.symbian.install sis +application/vnd.triscape.mxs +application/vnd.trueapp +application/vnd.truedoc +application/vnd.tve-trigger +application/vnd.ufdl +application/vnd.uplanet.alert +application/vnd.uplanet.alert-wbxml +application/vnd.uplanet.bearer-choice +application/vnd.uplanet.bearer-choice-wbxml +application/vnd.uplanet.cacheop +application/vnd.uplanet.cacheop-wbxml +application/vnd.uplanet.channel +application/vnd.uplanet.channel-wbxml +application/vnd.uplanet.list +application/vnd.uplanet.list-wbxml +application/vnd.uplanet.listcmd +application/vnd.uplanet.listcmd-wbxml +application/vnd.uplanet.signal +application/vnd.vcx +application/vnd.vectorworks +application/vnd.vidsoft.vidconference +application/vnd.visio vsd +application/vnd.vividence.scriptfile +application/vnd.wap.sic +application/vnd.wap.slc +application/vnd.wap.wbxml wbxml +application/vnd.wap.wmlc wmlc +application/vnd.wap.wmlscriptc wmlsc +application/vnd.webturbo +application/vnd.wordperfect wpd +application/vnd.wordperfect5.1 wp5 +application/vnd.wrq-hp3000-labelled +application/vnd.wt.stf +application/vnd.xara +application/vnd.xfdl +application/vnd.yellowriver-custom-menu +application/x-123 wk +application/x-7z-compressed 7z +application/x-abiword abw +application/x-apple-diskimage dmg +application/x-bcpio bcpio +application/x-bittorrent torrent +application/x-cab cab +application/x-cbr cbr +application/x-cbz cbz +application/x-cdf cdf cda +application/x-cdlink vcd +application/x-chess-pgn pgn +application/x-core +application/x-cpio cpio +application/x-csh csh +application/x-debian-package deb udeb +application/x-director dcr dir dxr +application/x-dms dms +application/x-doom wad +application/x-dvi dvi +application/x-httpd-eruby rhtml +application/x-executable +application/x-font pfa pfb gsf pcf pcf.Z +application/x-freemind mm +application/x-futuresplash spl +application/x-gnumeric gnumeric +application/x-go-sgf sgf +application/x-graphing-calculator gcf +application/x-gtar gtar tgz taz +application/x-hdf hdf +application/x-httpd-php phtml pht php +application/x-httpd-php-source phps +application/x-httpd-php3 php3 +application/x-httpd-php3-preprocessed php3p +application/x-httpd-php4 php4 +application/x-ica ica +application/x-info info +application/x-internet-signup ins isp +application/x-iphone iii +application/x-iso9660-image iso +application/x-jam jam +application/x-java-applet +application/x-java-bean +application/x-java-jnlp-file jnlp +application/x-jmol jmz +application/x-kchart chrt +application/x-kdelnk +application/x-killustrator kil +application/x-koan skp skd skt skm +application/x-kpresenter kpr kpt +application/x-kspread ksp +application/x-kword kwd kwt +application/x-latex latex +application/x-lha lha +application/x-lyx lyx +application/x-lzh lzh +application/x-lzx lzx +application/x-maker frm maker frame fm fb book fbdoc +application/x-mif mif +application/x-ms-wmd wmd +application/x-ms-wmz wmz +application/x-msdos-program com exe bat dll +application/x-msi msi +application/x-netcdf nc +application/x-ns-proxy-autoconfig pac dat +application/x-nwc nwc +application/x-object o +application/x-oz-application oza +application/x-pkcs7-certreqresp p7r +application/x-pkcs7-crl crl +application/x-python-code pyc pyo +application/x-qgis qgs shp shx +application/x-quicktimeplayer qtl +application/x-redhat-package-manager rpm +application/x-ruby rb +application/x-rx +application/x-sh sh +application/x-shar shar +application/x-shellscript +application/x-shockwave-flash swf swfl +application/x-stuffit sit sitx +application/x-sv4cpio sv4cpio +application/x-sv4crc sv4crc +application/x-tar tar +application/x-tcl tcl +application/x-tex-gf gf +application/x-tex-pk pk +application/x-texinfo texinfo texi +application/x-trash ~ % bak old sik +application/x-troff t tr roff +application/x-troff-man man +application/x-troff-me me +application/x-troff-ms ms +application/x-ustar ustar +application/x-videolan +application/x-wais-source src +application/x-wingz wz +application/x-x509-ca-cert crt +application/x-xcf xcf +application/x-xfig fig +application/x-xpinstall xpi + +audio/32kadpcm +audio/3gpp +audio/amr amr +audio/amr-wb awb +audio/amr amr +audio/amr-wb awb +audio/annodex axa +audio/basic au snd +audio/flac flac +audio/g.722.1 +audio/l16 +audio/midi mid midi kar +audio/mp4a-latm +audio/mpa-robust +audio/mpeg mpga mpega mp2 mp3 m4a +audio/mpegurl m3u +audio/ogg oga ogg spx +audio/parityfec +audio/prs.sid sid +audio/telephone-event +audio/tone +audio/vnd.cisco.nse +audio/vnd.cns.anp1 +audio/vnd.cns.inf1 +audio/vnd.digital-winds +audio/vnd.everad.plj +audio/vnd.lucent.voice +audio/vnd.nortel.vbk +audio/vnd.nuera.ecelp4800 +audio/vnd.nuera.ecelp7470 +audio/vnd.nuera.ecelp9600 +audio/vnd.octel.sbc +audio/vnd.qcelp +audio/vnd.rhetorex.32kadpcm +audio/vnd.vmx.cvsd +audio/x-aiff aif aiff aifc +audio/x-gsm gsm +audio/x-mpegurl m3u +audio/x-ms-wma wma +audio/x-ms-wax wax +audio/x-pn-realaudio-plugin +audio/x-pn-realaudio ra rm ram +audio/x-realaudio ra +audio/x-scpls pls +audio/x-sd2 sd2 +audio/x-wav wav + +chemical/x-alchemy alc +chemical/x-cache cac cache +chemical/x-cache-csf csf +chemical/x-cactvs-binary cbin cascii ctab +chemical/x-cdx cdx +chemical/x-cerius cer +chemical/x-chem3d c3d +chemical/x-chemdraw chm +chemical/x-cif cif +chemical/x-cmdf cmdf +chemical/x-cml cml +chemical/x-compass cpa +chemical/x-crossfire bsd +chemical/x-csml csml csm +chemical/x-ctx ctx +chemical/x-cxf cxf cef +#chemical/x-daylight-smiles smi +chemical/x-embl-dl-nucleotide emb embl +chemical/x-galactic-spc spc +chemical/x-gamess-input inp gam gamin +chemical/x-gaussian-checkpoint fch fchk +chemical/x-gaussian-cube cub +chemical/x-gaussian-input gau gjc gjf +chemical/x-gaussian-log gal +chemical/x-gcg8-sequence gcg +chemical/x-genbank gen +chemical/x-hin hin +chemical/x-isostar istr ist +chemical/x-jcamp-dx jdx dx +chemical/x-kinemage kin +chemical/x-macmolecule mcm +chemical/x-macromodel-input mmd mmod +chemical/x-mdl-molfile mol +chemical/x-mdl-rdfile rd +chemical/x-mdl-rxnfile rxn +chemical/x-mdl-sdfile sd sdf +chemical/x-mdl-tgf tgf +#chemical/x-mif mif +chemical/x-mmcif mcif +chemical/x-mol2 mol2 +chemical/x-molconn-Z b +chemical/x-mopac-graph gpt +chemical/x-mopac-input mop mopcrt mpc zmt +chemical/x-mopac-out moo +chemical/x-mopac-vib mvb +chemical/x-ncbi-asn1 asn +chemical/x-ncbi-asn1-ascii prt ent +chemical/x-ncbi-asn1-binary val aso +chemical/x-ncbi-asn1-spec asn +chemical/x-pdb pdb ent +chemical/x-rosdal ros +chemical/x-swissprot sw +chemical/x-vamas-iso14976 vms +chemical/x-vmd vmd +chemical/x-xtel xtel +chemical/x-xyz xyz + +image/cgm +image/g3fax +image/gif gif +image/ief ief +image/jpeg jpeg jpg jpe +image/naplps +image/pcx pcx +image/png png +image/prs.btif +image/prs.pti +image/svg+xml svg svgz +image/tiff tiff tif +image/vnd.cns.inf2 +image/vnd.djvu djvu djv +image/vnd.dwg +image/vnd.dxf +image/vnd.fastbidsheet +image/vnd.fpx +image/vnd.fst +image/vnd.fujixerox.edmics-mmr +image/vnd.fujixerox.edmics-rlc +image/vnd.mix +image/vnd.net-fpx +image/vnd.svf +image/vnd.wap.wbmp wbmp +image/vnd.xiff +image/x-cmu-raster ras +image/x-coreldraw cdr +image/x-coreldrawpattern pat +image/x-coreldrawtemplate cdt +image/x-corelphotopaint cpt +image/x-icon ico +image/x-jg art +image/x-jng jng +image/x-ms-bmp bmp +image/x-photoshop psd +image/x-portable-anymap pnm +image/x-portable-bitmap pbm +image/x-portable-graymap pgm +image/x-portable-pixmap ppm +image/x-rgb rgb +image/x-xbitmap xbm +image/x-xpixmap xpm +image/x-xwindowdump xwd + +inode/chardevice +inode/blockdevice +inode/directory-locked +inode/directory +inode/fifo +inode/socket + +message/delivery-status +message/disposition-notification +message/external-body +message/http +message/s-http +message/news +message/partial +message/rfc822 eml + +model/iges igs iges +model/mesh msh mesh silo +model/vnd.dwf +model/vnd.flatland.3dml +model/vnd.gdl +model/vnd.gs-gdl +model/vnd.gtw +model/vnd.mts +model/vnd.vtu +model/vrml wrl vrml + +multipart/alternative +multipart/appledouble +multipart/byteranges +multipart/digest +multipart/encrypted +multipart/form-data +multipart/header-set +multipart/mixed +multipart/parallel +multipart/related +multipart/report +multipart/signed +multipart/voice-message + +text/calendar ics icz +text/css css +text/csv csv +text/directory +text/english +text/enriched +text/h323 323 +text/html html htm shtml +text/iuls uls +text/mathml mml +text/parityfec +text/plain asc txt text pot brf +text/prs.lines.tag +text/rfc822-headers +text/richtext rtx +text/rtf +text/scriptlet sct wsc +text/t140 +text/texmacs tm ts +text/tab-separated-values tsv +text/uri-list +text/vnd.abc +text/vnd.curl +text/vnd.DMClientScript +text/vnd.flatland.3dml +text/vnd.fly +text/vnd.fmi.flexstor +text/vnd.in3d.3dml +text/vnd.in3d.spot +text/vnd.IPTC.NewsML +text/vnd.IPTC.NITF +text/vnd.latex-z +text/vnd.motorola.reflex +text/vnd.ms-mediapackage +text/vnd.sun.j2me.app-descriptor jad +text/vnd.wap.si +text/vnd.wap.sl +text/vnd.wap.wml wml +text/vnd.wap.wmlscript wmls +text/x-bibtex bib +text/x-boo boo +text/x-c++hdr h++ hpp hxx hh +text/x-c++src c++ cpp cxx cc +text/x-chdr h +text/x-component htc +text/x-crontab +text/x-csh csh +text/x-csrc c +text/x-dsrc d +text/x-diff diff patch +text/x-haskell hs +text/x-java java +text/x-literate-haskell lhs +text/x-makefile +text/x-moc moc +text/x-pascal p pas +text/x-pcs-gcd gcd +text/x-perl pl pm +text/x-python py +text/x-scala scala +text/x-server-parsed-html +text/x-setext etx +text/x-sh sh +text/x-tcl tcl tk +text/x-tex tex ltx sty cls +text/x-vcalendar vcs +text/x-vcard vcf + +video/3gpp 3gp +video/annodex axv +video/dl dl +video/dv dif dv +video/fli fli +video/gl gl +video/mpeg mpeg mpg mpe +video/mkv mkv +video/mp4 mp4 +video/quicktime qt mov +video/mp4v-es +video/ogg ogv +video/parityfec +video/pointer +video/vnd.fvt +video/vnd.motorola.video +video/vnd.motorola.videop +video/vnd.mpegurl mxu +video/vnd.mts +video/vnd.nokia.interleaved-multimedia +video/vnd.vivo +video/x-flv flv +video/x-la-asf lsf lsx +video/x-mng mng +video/x-ms-asf asf asx +video/x-ms-wm wm +video/x-ms-wmv wmv +video/x-ms-wmx wmx +video/x-ms-wvx wvx +video/x-msvideo avi +video/x-sgi-movie movie +video/x-matroska mpv + +x-conference/x-cooltalk ice + +x-epoc/x-sisx-app sisx +x-world/x-vrml vrm vrml wrl diff --git a/fm b/fm index 1129a9d4..5aa8bae3 100755 --- a/fm +++ b/fm @@ -8,6 +8,7 @@ LOG_LEVEL = 3 #LOG_LEVEL = 0 + def File::resolve_symlink( path = __FILE__ ) path = readlink(path) while symlink?(path) expand_path(path) @@ -17,30 +18,57 @@ def require_from_here ( *list ) require File.join( FM_DIR, *list ) end +def fj( *args ) File.join( *args ) end + $: << FM_DIR = File::dirname(File::resolve_symlink) +#SCREENSAVER = fj FM_DIR, 'code', 'screensaver', 'clock.rb' + PID = Process.pid if ARGV.size > 0 + case ARGV.first + when '-k' + exec "killall -9 fm" + end pwd = ARGV.first if pwd =~ /^file:\/\// pwd = $' end + + unless File.exists?(pwd) + pwd = nil + end + else pwd = nil end #require 'ftools' require 'pp' +require 'ostruct' +class OpenStruct; def __table__() @table end end require 'thread' require_from_here 'interface/ncurses.rb' +require_from_here 'code/extensions/basic.rb' +require_from_here 'code/extensions/fileutils.rb' require_from_here 'code/fm.rb' require_from_here 'code/keys.rb' require_from_here 'code/types.rb' +require_from_here 'code/bars.rb' +require_from_here 'code/action.rb' require_from_here 'code/draw.rb' -require_from_here 'code/extensions.rb' +require_from_here 'code/directory.rb' require_from_here 'code/debug.rb' + +# Screensaver +require_from_here 'code/screensaver/clock.rb' + +unless ARGV.empty? or File.directory?(pwd) + exec(Fm.getfilehandler_frompath(pwd)) +end + include Interface include Debug @@ -59,10 +87,13 @@ begin Fm.initialize( pwd ) Fm.main_loop ensure - closei + log "exiting!" + log "" + closei if Interface.running? Fm.dump ERROR_STREAM.close + # Kill all other threads for thr in Thread.list unless thr == Thread.current thr.kill diff --git a/interface/ncurses.rb b/interface/ncurses.rb index a383a570..fe208e53 100644 --- a/interface/ncurses.rb +++ b/interface/ncurses.rb @@ -19,13 +19,16 @@ module Interface '<down>' when ?\e '<esc>' +# keep spaces as they are, makes more sense # when ?\s # '<space>' - when ?\t + when Ncurses::KEY_BTAB + '<s-tab>' + when 9 '<tab>' - when 32 - ' ' - when 0..127 + when 1..26 # CTRL + ... + "<c-#{(key+96).chr}>" + when 32..127 key.chr else log(key) @@ -33,62 +36,14 @@ module Interface end end -# def key c#{{{ -# case c -# when 12 -# :redraw -# when ?\n -# :enter -# when ?\b, Ncurses::KEY_BACKSPACE -# :backspace -# when 32 -# :space -# when ?\t -# :tab -# when Ncurses::KEY_BTAB -# :TAB -# when ?\e -# :escape -# when 0..127 -# c -# when Ncurses::KEY_F1..Ncurses::KEY_F30 -# ('F' + (c-Ncurses::KEY_F1+1).to_s).to_sym -# when Ncurses::KEY_HOME -# :home -# when Ncurses::KEY_END -# :end -# when Ncurses::KEY_RESIZE -# :resize -# when Ncurses::KEY_DC -# :delete -# when Ncurses::KEY_ENTER -# ?\n -# when Ncurses::KEY_RIGHT -# :right -# when Ncurses::KEY_LEFT -# :left -# when Ncurses::KEY_UP -# :up -# when Ncurses::KEY_DOWN -# :down -# when Ncurses::KEY_NPAGE -# :pagedown -# when Ncurses::KEY_PPAGE -# :pageup -# when Ncurses::KEY_IC -# :insert -# else -## c -# :error -# end -# end#}}} - def self.included(this) @@window = Ncurses.initscr starti end + # (Re)Start the Interface def starti + @@running = true @@screen = Ncurses.stdscr @@screen.keypad(true) Ncurses.start_color @@ -96,17 +51,21 @@ module Interface Ncurses.noecho Ncurses.curs_set 0 - Ncurses.halfdelay(10000) + Ncurses.halfdelay(0) @@colortable = [] end + # Close the Interface def closei + @@running = false Ncurses.echo Ncurses.cbreak Ncurses.curs_set 1 Ncurses.endwin end + def running?() @@running end + def cleari Ncurses.mvaddstr(0, 0, ' ' * (lines * cols)) end @@ -116,7 +75,7 @@ module Interface end def set_title(x) - print "\e]2;#{x}\b" + print "\e]2;#{x}\007" end def lines @@ -151,48 +110,32 @@ module Interface end end - def color_at y, x=0, len=-1, fg=-1, bg=-1 + def color_at y, x=0, len=-1, fg=-1, bg=-1, attributes=0 if OPTIONS['color'] if y < 0 then y += Ncurses.LINES end - Ncurses.mvchgat(y, x, len, 0, get_color(fg, bg), nil) + Ncurses.mvchgat(y, x, len, attributes, get_color(fg, bg), nil) end end def color_bold_at y, x=0, len=-1, fg=-1, bg=-1 - if OPTIONS['color'] - if y < 0 then y += Ncurses.LINES end - Ncurses.mvchgat(y, x, len, Ncurses::A_BOLD, get_color(fg, bg), nil) - end + color_at(y, x, len, fg, bg, attributes = Ncurses::A_BOLD) end - def color_reverse_at y, x=0, len=-1, fg=-1, bg=-1 + def color_reverse_bold_at y, x=0, len=-1, fg=-1, bg=-1 if OPTIONS['color'] - if y < 0 then y += Ncurses.LINES end - Ncurses.mvchgat(y, x, len, Ncurses::A_REVERSE, get_color(fg, bg), nil) + color_at(y, x, len, fg, bg, Ncurses::A_REVERSE | Ncurses::A_BOLD) else - Ncurses.mvchgat(y, x, len, Ncurses::A_REVERSE, 0, nil) + Ncurses.mvchgat(y, x, len, Ncurses::A_REVERSE | Ncurses::A_BOLD, 0, nil) end end + alias color_bold_reverse_at color_reverse_bold_at -# runi(:command => String/Array, :wait=>false) -# runi('ä́', 'b', 'c') - def runi(hash, *args) - wait = false - if Array === hash - command = hash - elsif String === hash - command = [hash, *args] + def color_reverse_at y, x=0, len=-1, fg=-1, bg=-1 + if OPTIONS['color'] + color_at(y, x, len, fg, bg, Ncurses::A_REVERSE) else - command = hash[:command] or return false - wait = hash[:wait] if hash.has_key? :wait + Ncurses.mvchgat(y, x, len, Ncurses::A_REVERSE, 0, nil) end - - closei - - system(*command) - gets if wait - - starti end def get_color(fg, bg) @@ -214,6 +157,7 @@ module Interface Ncurses.attroff(Ncurses::A_BOLD) end end + def reverse(b = true) if b Ncurses.attron(Ncurses::A_REVERSE) |