lib/dotfile_linker.rb in dotfile_linker-0.0.2 vs lib/dotfile_linker.rb in dotfile_linker-1.0.0

- old
+ new

@@ -1,69 +1,144 @@ require 'dotfile_linker/version' require 'optparse' +require 'fileutils' require 'colorize' class String def human_filename - self.gsub(%r{^(/[^/]+){2}}, '~') + self.sub(/^#{ ENV['HOME'] }/, '~') end end module DotfileLinker - BLACKLIST = %w{ .git } - @@options = {} + class InvalidDotfilesDir < RuntimeError; end - def self.parse_options - optparse = OptionParser.new do |opts| - opts.on('-d', '--delete', 'Delete symlinks') { @@options[:delete_mode] = true } - opts.on_tail('-v', '--version', 'Show version') { puts VERSION; exit } - opts.on_tail('-h', '--help', 'Show this message') { puts opts; exit } + class Linker + BLACKLIST = %w{ .git } + + def initialize + @options = {} end - optparse.parse! - end - def self.exclude_file?(filename) - filename =~ /^\.\.?$/ or BLACKLIST.include?(filename) - end + def dotfiles_dir + @options[:path] || Dir.pwd + end - def self.positive_user_response? - case gets.strip - when /^y/i - true - when /^n/i - false - else - puts 'Exiting' - exit + def home_dir + ENV['HOME'] end - end - def self.link_file(filename) - unless exclude_file?(filename) - symlink_file = File.expand_path("~/#{ filename }") - actual_file = File.expand_path(filename) - if @@options[:delete_mode] - if File.symlink?(symlink_file) - puts "delete symlink #{ symlink_file.human_filename.magenta }? (y/n)" - File.delete(symlink_file) if positive_user_response? - end + def ignore_file_name + File.expand_path("~/.dotfile_linker_ignore") + end + + def raise_if_home_and_dotfiles_dir_match + if File.expand_path(home_dir) == File.expand_path(dotfiles_dir) + raise InvalidDotfilesDir, "Please specify your dotfiles directory by running `link_dotfiles` from that path, or providing a --path flag".red + end + end + + def user_response(message) + puts message + case gets.strip + when /^y/i + :yes + when /^n/i + :no + when /^i/i + :ignore + when /^q/i + :quit else - unless File.symlink?(symlink_file) - puts "link %s -> %s? (y/n)" % [symlink_file.human_filename.magenta, actual_file.human_filename.cyan] - File.symlink(actual_file, symlink_file) if positive_user_response? + user_response("Please enter a valid response") + end + end + + def user_response_or_exit(message) + response = user_response(message) + if response == :quit + puts "Exiting" + exit + end + response + end + + def parse_options + optparse = OptionParser.new do |opts| + opts.on('-p', '--path PATH', String, 'Use [PATH] as dotfiles directory (instead of current directory)') { |path| @options[:path] = File.expand_path(path) } + opts.on_tail('-u', '--unlink', 'Unlink mode') { @options[:unlink_mode] = true } + opts.on_tail('-v', '--version', 'Show version') { puts VERSION; exit } + opts.on_tail('-h', '--help', 'Show this message') { puts opts; exit } + end + optparse.parse! + end + + def ignore_list + @ignore_list ||= + begin + File.open(ignore_file_name, 'rb').lines.to_a.map(&:chomp) + rescue Errno::ENOENT + [] end + end + + def ignore_file(filename) + File.open(ignore_file_name, 'a') do |f| + f.puts filename end end - end - def self.link_files - Dir.foreach(Dir.pwd) { |filename| link_file(filename) } - end + def exclude_file?(filename) + filename =~ /^\.\.?$/ or BLACKLIST.include?(filename) or ignore_list.include?(filename) + end - def self.start - parse_options - link_files - puts 'Done' - rescue Interrupt - # do nothing + def link_file(filename) + home_dir_file_path = File.expand_path("~/#{ filename }") + dotfiles_dir_file_path = File.expand_path("#{ dotfiles_dir }/#{ filename }") + unless File.symlink?(home_dir_file_path) || exclude_file?(filename) + case user_response_or_exit("move and link #{ home_dir_file_path.human_filename.magenta } -> #{ dotfiles_dir_file_path.human_filename.cyan }? (y/n/i[gnore])") + when :yes + FileUtils.mv(home_dir_file_path, dotfiles_dir_file_path, :verbose => true) + FileUtils.ln_s(dotfiles_dir_file_path, home_dir_file_path, :verbose => true) + when :ignore + ignore_file(filename) + puts "ignored #{filename.cyan}" + end + end + end + + def link_files + Dir.foreach(home_dir) { |filename| link_file(filename) } + end + + def unlink_file(filename) + home_dir_symlink_path = File.expand_path("~/#{ filename }") + dotfiles_dir_file_path = File.expand_path("#{ dotfiles_dir }/#{ filename }") + if File.symlink?(home_dir_symlink_path) + case user_response_or_exit("unlink #{ home_dir_symlink_path.human_filename.magenta } and restore #{ dotfiles_dir_file_path.human_filename.cyan }? (y/n)") + when :yes + FileUtils.rm(home_dir_symlink_path, :verbose => true) + FileUtils.mv(dotfiles_dir_file_path, home_dir_symlink_path, :verbose => true) + end + end + end + + def unlink_files + Dir.foreach(dotfiles_dir) { |filename| unlink_file(filename) } + end + + def start + raise_if_home_and_dotfiles_dir_match + parse_options + if @options[:unlink_mode] + unlink_files + else + link_files + end + puts 'Finished' + rescue Interrupt + # do nothing + rescue InvalidDotfilesDir => e + puts e.message + end end end