Module: Can
- Defined in:
- lib/can.rb,
lib/info.rb,
lib/list.rb,
lib/empty.rb,
lib/trash.rb,
lib/untrash.rb,
lib/can/version.rb,
lib/can/argparse.rb
Defined Under Namespace
Modules: ArgParse
Constant Summary collapse
- XDG_DATA_HOME_DEFAULT =
File.join(ENV['HOME'], '.local/share')
- XDG_DATA_HOME =
ENV['XDG_DATA_HOME'] || XDG_DATA_HOME_DEFAULT
- HOME_TRASH_DIRECTORY =
File.join(XDG_DATA_HOME, 'Trash')
- HOME_TRASH_INFO_DIRECTORY =
File.join(HOME_TRASH_DIRECTORY, 'info')
- HOME_TRASH_FILES_DIRECTORY =
File.join(HOME_TRASH_DIRECTORY, 'files')
- VERSION =
'0.1.7'- USAGE =
'Usage: can [OPTION] [FILE]...'- MODES =
{ list: ['-l', '--list', 'list files in the trash'], info: ['-n', '--info', 'see information about a trashed file'], untrash: ['-u', '--untrash', 'restore a trashed file'], empty: ['-e', '--empty', 'permanently remove a file from the trash; use with no arguments to empty entire trashcan'] }.freeze
- OPTIONS =
{ force: ['-f', '--force', 'ignore nonexistent files and arguments, never prompt'], prompt: ['-i', nil, 'prompt before every trashing'], recursive: ['-r', '--recursive', 'trash directories and their contents recursively'] }.freeze
- ALL_FLAGS =
MODES.merge(OPTIONS).freeze
Class Method Summary collapse
- .can ⇒ Object
- .empty ⇒ Object
-
.info ⇒ Object
TODO: Parse the .trashinfo files to make them more human readable.
- .init_dirs ⇒ Object
- .list ⇒ Object
- .trash ⇒ Object
- .untrash ⇒ Object
Class Method Details
.can ⇒ Object
28 29 30 31 32 33 34 35 36 37 38 |
# File 'lib/can.rb', line 28 def self.can @options = ArgParse.init_args mode = ArgParse.mode @options init_dirs send mode $exit = EXIT_SUCCESS if @options.include?(:force) end |
.empty ⇒ Object
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
# File 'lib/empty.rb', line 4 def self.empty # Remove everything in the files and info directory if ARGV.empty? FileUtils.rm_r Dir.glob("#{HOME_TRASH_INFO_DIRECTORY}/*"), secure: true FileUtils.rm_r Dir.glob("#{HOME_TRASH_FILES_DIRECTORY}/*"), secure: true else ARGV.each do |filename| trashinfo_filename = "#{filename}.trashinfo" file_path = File.join(HOME_TRASH_FILES_DIRECTORY, filename) trashinfo_file_path = File.join(HOME_TRASH_INFO_DIRECTORY, trashinfo_filename) FileUtils.remove_entry_secure file_path FileUtils.remove_entry_secure trashinfo_file_path end end end |
.info ⇒ Object
TODO: Parse the .trashinfo files to make them more human readable. Also, display the filename above the information with empty lines between consecutive info blocks.
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
# File 'lib/info.rb', line 7 def self.info # Fails with a fatal error even with --force, intended # behavior. if ARGV.empty? Error.fatal 'missing operand' else ARGV.each_with_index do |file, i| trashinfo_filename = "#{file}.trashinfo" trashinfo_path = File.join(HOME_TRASH_INFO_DIRECTORY, trashinfo_filename) unless File.exist? trashinfo_path Error.nonfatal "no such file in trashcan: '#{file}'" next end trashinfo = Trashinfo.parse(File.read(trashinfo_path)) # TODO: Checking if i is not zero every single # iteration is a little inefficient. Maybe there is a # better way to do this? puts if i != 0 puts <<~INFO #{file}: Path: #{trashinfo[:path]} Deletion Date: #{trashinfo[:deletion_date]} INFO end end end |
.init_dirs ⇒ Object
23 24 25 26 |
# File 'lib/can.rb', line 23 def self.init_dirs FileUtils.mkpath HOME_TRASH_FILES_DIRECTORY FileUtils.mkpath HOME_TRASH_INFO_DIRECTORY end |
.list ⇒ Object
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
# File 'lib/list.rb', line 4 def self.list # Given no args, show every trashed file if ARGV.empty? puts Dir.children(HOME_TRASH_FILES_DIRECTORY) # Given a regex pattern as an arg, print trashed files # that fit elsif ARGV.length == 1 regex = Regexp.new(ARGV[0]) puts( Dir.children(HOME_TRASH_FILES_DIRECTORY).select do |file| regex =~ file end ) else raise StandardError, "can: mode --list expects 0 to 1 arguments, given #{ARGV.length}" end end |
.trash ⇒ Object
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
# File 'lib/trash.rb', line 25 def self.trash Error.fatal 'missing operand' if ARGV.empty? && !@options.include?(:force) ARGV.each do |path| # TODO: If both `-f` and `-i` are used, can should # prompt if `-i` is used last. If `-f` is used last, # can should not prompt trashings. This follows the # behavior of rm. unless File.exist?(path) Error.nonfatal "cannot trash '#{path}': No such file or directory" unless @options.include? :force next end # If --recursive is not used and a directory is given as an # argument, a non-zero error code should be returned # regardless if --force is used. if File.directory?(path) && !File.symlink?(path) Error.nonfatal "cannot remove '#{path}': Is a directory" unless @options.include? :recursive next end # TODO: Highline.agree prints to stdout, when it should # print to stderr. It also uses `puts`, while this use # case should use `print`. next if @options.include?(:prompt) && !(HighLine.agree "can: remove file '#{path}'?") filename = File.basename path trashinfo_string = Trashinfo.new path existing_trash_files = Dir.children HOME_TRASH_FILES_DIRECTORY # The File.basename function only strips the last # extension. These functions are needed to support files # with multiple extensions, like file.txt.bkp basename = strip_extensions(filename) exts = gather_extensions(filename) # Most implementations add a number as the first # extension to prevent file conflicts i = 0 while existing_trash_files.include?(filename) i += 1 filename = "#{basename}.#{i}#{exts}" end FileUtils.mv(path, File.join(HOME_TRASH_FILES_DIRECTORY, filename)) trashinfo_filename = "#{filename}.trashinfo" trashinfo_out_path = File.join(HOME_TRASH_INFO_DIRECTORY, trashinfo_filename) File.new(trashinfo_out_path, 'w').syswrite(trashinfo_string) end end |
.untrash ⇒ Object
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
# File 'lib/untrash.rb', line 4 def self.untrash ARGV.each do |filename| file_path = File.join(HOME_TRASH_FILES_DIRECTORY, filename) unless File.exist? file_path unless @options.include? :force Error.nonfatal "cannot untrash '#{filename}': No such file or directory in trash" end next end trashinfo_filename = "#{filename}.trashinfo" trashinfo_path = File.join(HOME_TRASH_INFO_DIRECTORY, trashinfo_filename) trashinfo = Trashinfo.parse(File.read(trashinfo_path)) original_path = trashinfo[:path] # TODO: Implement more thorough error handling if File.exist? original_path Error.nonfatal "cannot untrash '#{filename}' to '#{original_path}': File exists" next end # TODO: Make sure ctime, atime, mtime, do not change FileUtils.mv file_path, original_path FileUtils.rm trashinfo_path end end |