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