require 'thor' class Homesick < Thor include Thor::Actions # Hack in support for diffing symlinks class Shell < Thor::Shell::Color def show_diff(destination, content) destination = Pathname.new(destination) if destination.symlink? say "- #{destination.readlink}", :red, true say "+ #{content.expand_path}", :green, true else super end end end def initialize(args=[], options={}, config={}) super self.shell = Homesick::Shell.new end GIT_URI_PATTERN = /^git:\/\// GITHUB_NAME_REPO_PATTERN = /([A-Za-z_-]+)\/([A-Za-z_-]+)/ desc "clone URI", "Clone +uri+ as a castle for homesick" def clone(uri) empty_directory repos_dir, :verbose => false inside repos_dir do if uri =~ GIT_URI_PATTERN git_clone uri elsif uri =~ GITHUB_NAME_REPO_PATTERN git_clone "git://github.com/#{$1}/#{$2}.git", "#{$1}_#{$2}" end end end desc "link NAME", "Symlinks all dotfiles from the specified castle" def link(home) inside castle_dir(home) do files = Pathname.glob('.*')[2..-1] # skip . and .., ie the first two files.each do |path| absolute_path = path.expand_path inside user_dir do adjusted_path = (user_dir + path).basename symlink absolute_path, adjusted_path end end end end desc "list", "List cloned castles" def list inside repos_dir do Pathname.glob('*') do |home| puts home end end end no_tasks do # class method, so it's convenient to stub out during tests def self.user_dir @user_dir ||= Pathname.new('~').expand_path end def self.repos_dir @repos_dir ||= Pathname.new('~/.homesick/repos').expand_path end def repos_dir self.class.repos_dir end end protected # TODO move this to be more like thor's template, empty_directory, etc def git_clone(repo, config = {}) config ||= {} destination = config[:destination] || begin repo =~ /([^\/]+)\.git$/ $1 end destination = Pathname.new(destination) unless destination.kind_of?(Pathname) if ! destination.directory? say_status 'git clone', "#{repo} to #{destination.expand_path}", :green if config.fetch(:verbose, true) system "git clone #{repo} #{destination}" unless options[:pretend] else say_status :exist, destination.expand_path, :blue end end def symlink(source, destination, config = {}) destination = Pathname.new(destination) if destination.symlink? if destination.readlink == source say_status :identical, destination.expand_path, :blue else say_status :conflict, "#{destination} exists and points to #{destination.readlink}", :red if shell.file_collision(destination) { source } system "ln -sf #{source} #{destination}" end end else say_status :symlink, "#{source.expand_path} to #{destination.expand_path}", :green system "ln -s #{source} #{destination}" end end def user_dir self.class.user_dir end def castle_dir(name) repos_dir.join(name, 'home') end end