@@ -6,4 +6,4 @@ $LOAD_PATH.unshift lib.to_s
|
|||||||
|
|
||||||
require 'homesick'
|
require 'homesick'
|
||||||
|
|
||||||
Homesick.start
|
Homesick::CLI.start
|
||||||
|
|||||||
344
lib/homesick.rb
344
lib/homesick.rb
@@ -1,343 +1,15 @@
|
|||||||
# -*- encoding : utf-8 -*-
|
# -*- encoding : utf-8 -*-
|
||||||
require 'thor'
|
require 'homesick/shell'
|
||||||
|
require 'homesick/actions/file_actions'
|
||||||
# Homesick's command line interface (with some helper methods)
|
require 'homesick/actions/git_actions'
|
||||||
class Homesick < Thor
|
require 'homesick/version'
|
||||||
autoload :Shell, 'homesick/shell'
|
require 'homesick/utils'
|
||||||
autoload :Actions, 'homesick/actions'
|
require 'homesick/cli'
|
||||||
autoload :Version, 'homesick/version'
|
|
||||||
autoload :Utils, 'homesick/utils'
|
|
||||||
|
|
||||||
include Thor::Actions
|
|
||||||
include Homesick::Actions
|
|
||||||
include Homesick::Version
|
|
||||||
include Homesick::Utils
|
|
||||||
|
|
||||||
add_runtime_options!
|
|
||||||
|
|
||||||
|
# Homesick's top-level module
|
||||||
|
module Homesick
|
||||||
GITHUB_NAME_REPO_PATTERN = /\A([A-Za-z0-9_-]+\/[A-Za-z0-9_-]+)\Z/
|
GITHUB_NAME_REPO_PATTERN = /\A([A-Za-z0-9_-]+\/[A-Za-z0-9_-]+)\Z/
|
||||||
SUBDIR_FILENAME = '.homesick_subdir'
|
SUBDIR_FILENAME = '.homesick_subdir'
|
||||||
|
|
||||||
DEFAULT_CASTLE_NAME = 'dotfiles'
|
DEFAULT_CASTLE_NAME = 'dotfiles'
|
||||||
|
|
||||||
map '-v' => :version
|
|
||||||
map '--version' => :version
|
|
||||||
# Retain a mapped version of the symlink command for compatibility.
|
|
||||||
map symlink: :link
|
|
||||||
|
|
||||||
def initialize(args = [], options = {}, config = {})
|
|
||||||
super
|
|
||||||
self.shell = Homesick::Shell.new
|
|
||||||
end
|
|
||||||
|
|
||||||
desc 'clone URI', 'Clone +uri+ as a castle for homesick'
|
|
||||||
def clone(uri)
|
|
||||||
inside repos_dir do
|
|
||||||
destination = nil
|
|
||||||
if File.exist?(uri)
|
|
||||||
uri = Pathname.new(uri).expand_path
|
|
||||||
fail "Castle already cloned to #{uri}" if uri.to_s.start_with?(repos_dir.to_s)
|
|
||||||
|
|
||||||
destination = uri.basename
|
|
||||||
|
|
||||||
ln_s uri, destination
|
|
||||||
elsif uri =~ GITHUB_NAME_REPO_PATTERN
|
|
||||||
destination = Pathname.new(uri).basename
|
|
||||||
git_clone "https://github.com/#{Regexp.last_match[1]}.git",
|
|
||||||
destination: destination
|
|
||||||
elsif uri =~ /%r([^%r]*?)(\.git)?\Z/ || uri =~ /[^:]+:([^:]+)(\.git)?\Z/
|
|
||||||
destination = Pathname.new(Regexp.last_match[1])
|
|
||||||
git_clone uri
|
|
||||||
else
|
|
||||||
fail "Unknown URI format: #{uri}"
|
|
||||||
end
|
|
||||||
|
|
||||||
setup_castle(destination)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
desc 'rc CASTLE', 'Run the .homesickrc for the specified castle'
|
|
||||||
def rc(name = DEFAULT_CASTLE_NAME)
|
|
||||||
inside repos_dir do
|
|
||||||
destination = Pathname.new(name)
|
|
||||||
homesickrc = destination.join('.homesickrc').expand_path
|
|
||||||
if homesickrc.exist?
|
|
||||||
proceed = shell.yes?("#{name} has a .homesickrc. Proceed with evaling it? (This could be destructive)")
|
|
||||||
if proceed
|
|
||||||
shell.say_status 'eval', homesickrc
|
|
||||||
inside destination do
|
|
||||||
eval homesickrc.read, binding, homesickrc.expand_path.to_s
|
|
||||||
end
|
|
||||||
else
|
|
||||||
shell.say_status 'eval skip',
|
|
||||||
"not evaling #{homesickrc}, #{destination} may need manual configuration",
|
|
||||||
:blue
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
desc 'pull CASTLE', 'Update the specified castle'
|
|
||||||
method_option :all,
|
|
||||||
type: :boolean,
|
|
||||||
default: false,
|
|
||||||
required: false,
|
|
||||||
desc: 'Update all cloned castles'
|
|
||||||
def pull(name = DEFAULT_CASTLE_NAME)
|
|
||||||
if options[:all]
|
|
||||||
inside_each_castle do |castle|
|
|
||||||
shell.say castle.to_s.gsub(repos_dir.to_s + '/', '') + ':'
|
|
||||||
update_castle castle
|
|
||||||
end
|
|
||||||
else
|
|
||||||
update_castle name
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
desc 'commit CASTLE MESSAGE', "Commit the specified castle's changes"
|
|
||||||
def commit(name = DEFAULT_CASTLE_NAME, message = nil)
|
|
||||||
commit_castle name, message
|
|
||||||
end
|
|
||||||
|
|
||||||
desc 'push CASTLE', 'Push the specified castle'
|
|
||||||
def push(name = DEFAULT_CASTLE_NAME)
|
|
||||||
push_castle name
|
|
||||||
end
|
|
||||||
|
|
||||||
desc 'unlink CASTLE', 'Unsymlinks all dotfiles from the specified castle'
|
|
||||||
def unlink(name = DEFAULT_CASTLE_NAME)
|
|
||||||
check_castle_existance(name, 'symlink')
|
|
||||||
|
|
||||||
inside castle_dir(name) do
|
|
||||||
subdirs = subdirs(name)
|
|
||||||
|
|
||||||
# unlink files
|
|
||||||
unsymlink_each(name, castle_dir(name), subdirs)
|
|
||||||
|
|
||||||
# unlink files in subdirs
|
|
||||||
subdirs.each do |subdir|
|
|
||||||
unsymlink_each(name, subdir, subdirs)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
desc 'link CASTLE', 'Symlinks all dotfiles from the specified castle'
|
|
||||||
method_option :force,
|
|
||||||
default: false,
|
|
||||||
desc: 'Overwrite existing conflicting symlinks without prompting.'
|
|
||||||
def link(name = DEFAULT_CASTLE_NAME)
|
|
||||||
check_castle_existance(name, 'symlink')
|
|
||||||
|
|
||||||
inside castle_dir(name) do
|
|
||||||
subdirs = subdirs(name)
|
|
||||||
|
|
||||||
# link files
|
|
||||||
symlink_each(name, castle_dir(name), subdirs)
|
|
||||||
|
|
||||||
# link files in subdirs
|
|
||||||
subdirs.each do |subdir|
|
|
||||||
symlink_each(name, subdir, subdirs)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
desc 'track FILE CASTLE', 'add a file to a castle'
|
|
||||||
def track(file, castle = DEFAULT_CASTLE_NAME)
|
|
||||||
castle = Pathname.new(castle)
|
|
||||||
file = Pathname.new(file.chomp('/'))
|
|
||||||
check_castle_existance(castle, 'track')
|
|
||||||
|
|
||||||
absolute_path = file.expand_path
|
|
||||||
relative_dir = absolute_path.relative_path_from(home_dir).dirname
|
|
||||||
castle_path = Pathname.new(castle_dir(castle)).join(relative_dir)
|
|
||||||
FileUtils.mkdir_p castle_path
|
|
||||||
|
|
||||||
# Are we already tracking this or anything inside it?
|
|
||||||
target = Pathname.new(castle_path.join(file.basename))
|
|
||||||
if target.exist?
|
|
||||||
if absolute_path.directory?
|
|
||||||
move_dir_contents(target, absolute_path)
|
|
||||||
absolute_path.rmtree
|
|
||||||
subdir_remove(castle, relative_dir + file.basename)
|
|
||||||
|
|
||||||
elsif more_recent? absolute_path, target
|
|
||||||
target.delete
|
|
||||||
mv absolute_path, castle_path
|
|
||||||
else
|
|
||||||
shell.say_status(:track,
|
|
||||||
"#{target} already exists, and is more recent than #{file}. Run 'homesick SYMLINK CASTLE' to create symlinks.",
|
|
||||||
:blue) unless options[:quiet]
|
|
||||||
end
|
|
||||||
else
|
|
||||||
mv absolute_path, castle_path
|
|
||||||
end
|
|
||||||
|
|
||||||
inside home_dir do
|
|
||||||
absolute_path = castle_path + file.basename
|
|
||||||
home_path = home_dir + relative_dir + file.basename
|
|
||||||
ln_s absolute_path, home_path
|
|
||||||
end
|
|
||||||
|
|
||||||
inside castle_path do
|
|
||||||
git_add absolute_path
|
|
||||||
end
|
|
||||||
|
|
||||||
# are we tracking something nested? Add the parent dir to the manifest
|
|
||||||
subdir_add(castle, relative_dir) unless relative_dir.eql?(Pathname.new('.'))
|
|
||||||
end
|
|
||||||
|
|
||||||
desc 'list', 'List cloned castles'
|
|
||||||
def list
|
|
||||||
inside_each_castle do |castle|
|
|
||||||
say_status castle.relative_path_from(repos_dir).to_s,
|
|
||||||
`git config remote.origin.url`.chomp,
|
|
||||||
:cyan
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
desc 'status CASTLE', 'Shows the git status of a castle'
|
|
||||||
def status(castle = DEFAULT_CASTLE_NAME)
|
|
||||||
check_castle_existance(castle, 'status')
|
|
||||||
inside repos_dir.join(castle) do
|
|
||||||
git_status
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
desc 'diff CASTLE', 'Shows the git diff of uncommitted changes in a castle'
|
|
||||||
def diff(castle = DEFAULT_CASTLE_NAME)
|
|
||||||
check_castle_existance(castle, 'diff')
|
|
||||||
inside repos_dir.join(castle) do
|
|
||||||
git_diff
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
desc 'show_path CASTLE', 'Prints the path of a castle'
|
|
||||||
def show_path(castle = DEFAULT_CASTLE_NAME)
|
|
||||||
check_castle_existance(castle, 'show_path')
|
|
||||||
say repos_dir.join(castle)
|
|
||||||
end
|
|
||||||
|
|
||||||
desc 'generate PATH', 'generate a homesick-ready git repo at PATH'
|
|
||||||
def generate(castle)
|
|
||||||
castle = Pathname.new(castle).expand_path
|
|
||||||
|
|
||||||
github_user = `git config github.user`.chomp
|
|
||||||
github_user = nil if github_user == ''
|
|
||||||
github_repo = castle.basename
|
|
||||||
|
|
||||||
empty_directory castle
|
|
||||||
inside castle do
|
|
||||||
git_init
|
|
||||||
if github_user
|
|
||||||
url = "git@github.com:#{github_user}/#{github_repo}.git"
|
|
||||||
git_remote_add 'origin', url
|
|
||||||
end
|
|
||||||
|
|
||||||
empty_directory 'home'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
desc 'destroy CASTLE', 'Delete all symlinks and remove the cloned repository'
|
|
||||||
def destroy(name)
|
|
||||||
check_castle_existance name, 'destroy'
|
|
||||||
|
|
||||||
if shell.yes?('This will destroy your castle irreversible! Are you sure?')
|
|
||||||
unlink(name)
|
|
||||||
rm_rf repos_dir.join(name)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
desc 'cd CASTLE', 'Open a new shell in the root of the given castle'
|
|
||||||
def cd(castle = DEFAULT_CASTLE_NAME)
|
|
||||||
check_castle_existance castle, 'cd'
|
|
||||||
castle_dir = repos_dir.join(castle)
|
|
||||||
say_status "cd #{castle_dir.realpath}",
|
|
||||||
"Opening a new shell in castle '#{castle}'. To return to the original one exit from the new shell.",
|
|
||||||
:green
|
|
||||||
inside castle_dir do
|
|
||||||
system(ENV['SHELL'])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
desc 'open CASTLE',
|
|
||||||
'Open your default editor in the root of the given castle'
|
|
||||||
def open(castle = DEFAULT_CASTLE_NAME)
|
|
||||||
unless ENV['EDITOR']
|
|
||||||
say_status :error,
|
|
||||||
'The $EDITOR environment variable must be set to use this command',
|
|
||||||
:red
|
|
||||||
|
|
||||||
exit(1)
|
|
||||||
end
|
|
||||||
check_castle_existance castle, 'open'
|
|
||||||
castle_dir = repos_dir.join(castle)
|
|
||||||
say_status "#{ENV['EDITOR']} #{castle_dir.realpath}",
|
|
||||||
"Opening the root directory of castle '#{castle}' in editor '#{ENV['EDITOR']}'.",
|
|
||||||
:green
|
|
||||||
inside castle_dir do
|
|
||||||
system(ENV['EDITOR'])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
desc 'exec CASTLE COMMAND',
|
|
||||||
'Execute a single shell command inside the root of a castle'
|
|
||||||
method_option :pretend,
|
|
||||||
type: :boolean,
|
|
||||||
default: false,
|
|
||||||
required: false,
|
|
||||||
desc: 'Perform a dry run of the command'
|
|
||||||
method_option :quiet,
|
|
||||||
type: :boolean,
|
|
||||||
default: false,
|
|
||||||
required: false,
|
|
||||||
desc: 'Run a command without any output from Homesick'
|
|
||||||
def exec(castle, *args)
|
|
||||||
check_castle_existance castle, 'exec'
|
|
||||||
unless args.count > 0
|
|
||||||
say_status :error,
|
|
||||||
'You must pass a shell command to execute',
|
|
||||||
:red
|
|
||||||
exit(1)
|
|
||||||
end
|
|
||||||
full_command = args.join(' ')
|
|
||||||
say_status "exec '#{full_command}'",
|
|
||||||
"#{options[:pretend] ? 'Would execute' : 'Executing command'} '#{full_command}' in castle '#{castle}'",
|
|
||||||
:green unless options[:quiet]
|
|
||||||
inside repos_dir.join(castle) do
|
|
||||||
system(full_command) unless options[:pretend]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
desc 'exec_all COMMAND',
|
|
||||||
'Execute a single shell command inside the root of every cloned castle'
|
|
||||||
method_option :pretend,
|
|
||||||
type: :boolean,
|
|
||||||
default: false,
|
|
||||||
required: false,
|
|
||||||
desc: 'Perform a dry run of the command'
|
|
||||||
method_option :quiet,
|
|
||||||
type: :boolean,
|
|
||||||
default: false,
|
|
||||||
required: false,
|
|
||||||
desc: 'Run a command without any output from Homesick'
|
|
||||||
def exec_all(*args)
|
|
||||||
unless args.count > 0
|
|
||||||
say_status :error,
|
|
||||||
'You must pass a shell command to execute',
|
|
||||||
:red
|
|
||||||
exit(1)
|
|
||||||
end
|
|
||||||
full_command = args.join(' ')
|
|
||||||
inside_each_castle do |castle|
|
|
||||||
say_status "exec '#{full_command}'",
|
|
||||||
"#{options[:pretend] ? 'Would execute' : 'Executing command'} '#{full_command}' in castle '#{castle}'",
|
|
||||||
:green unless options[:quiet]
|
|
||||||
system(full_command) unless options[:pretend]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
desc 'version', 'Display the current version of homesick'
|
|
||||||
def version
|
|
||||||
say Homesick::Version::STRING
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,176 +0,0 @@
|
|||||||
# -*- encoding : utf-8 -*-
|
|
||||||
class Homesick
|
|
||||||
# Git-related and file-related helper methods for the Homesick class
|
|
||||||
module Actions
|
|
||||||
# TODO: move this to be more like thor's template, empty_directory, etc
|
|
||||||
def git_clone(repo, config = {})
|
|
||||||
config ||= {}
|
|
||||||
destination = config[:destination] || File.basename(repo, '.git')
|
|
||||||
|
|
||||||
destination = Pathname.new(destination) unless destination.kind_of?(Pathname)
|
|
||||||
FileUtils.mkdir_p destination.dirname
|
|
||||||
|
|
||||||
if destination.directory?
|
|
||||||
say_status :exist, destination.expand_path, :blue unless options[:quiet]
|
|
||||||
else
|
|
||||||
say_status 'git clone',
|
|
||||||
"#{repo} to #{destination.expand_path}",
|
|
||||||
:green unless options[:quiet]
|
|
||||||
system "git clone -q --config push.default=upstream --recursive #{repo} #{destination}" unless options[:pretend]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def git_init(path = '.')
|
|
||||||
path = Pathname.new(path)
|
|
||||||
|
|
||||||
inside path do
|
|
||||||
if path.join('.git').exist?
|
|
||||||
say_status 'git init', 'already initialized', :blue unless options[:quiet]
|
|
||||||
else
|
|
||||||
say_status 'git init', '' unless options[:quiet]
|
|
||||||
system 'git init >/dev/null' unless options[:pretend]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def git_remote_add(name, url)
|
|
||||||
existing_remote = `git config remote.#{name}.url`.chomp
|
|
||||||
existing_remote = nil if existing_remote == ''
|
|
||||||
|
|
||||||
if existing_remote
|
|
||||||
say_status 'git remote', "#{name} already exists", :blue unless options[:quiet]
|
|
||||||
else
|
|
||||||
say_status 'git remote', "add #{name} #{url}" unless options[:quiet]
|
|
||||||
system "git remote add #{name} #{url}" unless options[:pretend]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def git_submodule_init(config = {})
|
|
||||||
say_status 'git submodule', 'init', :green unless options[:quiet]
|
|
||||||
system 'git submodule --quiet init' unless options[:pretend]
|
|
||||||
end
|
|
||||||
|
|
||||||
def git_submodule_update(config = {})
|
|
||||||
say_status 'git submodule', 'update', :green unless options[:quiet]
|
|
||||||
system 'git submodule --quiet update --init --recursive >/dev/null 2>&1' unless options[:pretend]
|
|
||||||
end
|
|
||||||
|
|
||||||
def git_pull(config = {})
|
|
||||||
say_status 'git pull', '', :green unless options[:quiet]
|
|
||||||
system 'git pull --quiet' unless options[:pretend]
|
|
||||||
end
|
|
||||||
|
|
||||||
def git_push(config = {})
|
|
||||||
say_status 'git push', '', :green unless options[:quiet]
|
|
||||||
system 'git push' unless options[:pretend]
|
|
||||||
end
|
|
||||||
|
|
||||||
def git_commit_all(config = {})
|
|
||||||
say_status 'git commit all', '', :green unless options[:quiet]
|
|
||||||
if config[:message]
|
|
||||||
system "git commit -a -m '#{config[:message]}'" unless options[:pretend]
|
|
||||||
else
|
|
||||||
system 'git commit -v -a' unless options[:pretend]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def git_add(file, config = {})
|
|
||||||
say_status 'git add file', '', :green unless options[:quiet]
|
|
||||||
system "git add '#{file}'" unless options[:pretend]
|
|
||||||
end
|
|
||||||
|
|
||||||
def git_status(config = {})
|
|
||||||
say_status 'git status', '', :green unless options[:quiet]
|
|
||||||
system 'git status' unless options[:pretend]
|
|
||||||
end
|
|
||||||
|
|
||||||
def git_diff(config = {})
|
|
||||||
say_status 'git diff', '', :green unless options[:quiet]
|
|
||||||
system 'git diff' unless options[:pretend]
|
|
||||||
end
|
|
||||||
|
|
||||||
def mv(source, destination, config = {})
|
|
||||||
source = Pathname.new(source)
|
|
||||||
destination = Pathname.new(destination + source.basename)
|
|
||||||
|
|
||||||
if destination.exist?
|
|
||||||
say_status :conflict, "#{destination} exists", :red unless options[:quiet]
|
|
||||||
|
|
||||||
FileUtils.mv source, destination if (options[:force] || shell.file_collision(destination) { source }) && !options[:pretend]
|
|
||||||
else
|
|
||||||
# this needs some sort of message here.
|
|
||||||
FileUtils.mv source, destination unless options[:pretend]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def rm_rf(dir)
|
|
||||||
say_status "rm -rf #{dir}", '', :green unless options[:quiet]
|
|
||||||
FileUtils.rm_r dir, force: true
|
|
||||||
end
|
|
||||||
|
|
||||||
def rm_link(target)
|
|
||||||
target = Pathname.new(target)
|
|
||||||
|
|
||||||
if target.symlink?
|
|
||||||
say_status :unlink, "#{target.expand_path}", :green unless options[:quiet]
|
|
||||||
FileUtils.rm_rf target
|
|
||||||
else
|
|
||||||
say_status :conflict, "#{target} is not a symlink", :red unless options[:quiet]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def rm(file)
|
|
||||||
say_status "rm #{file}", '', :green unless options[:quiet]
|
|
||||||
FileUtils.rm file, force: true
|
|
||||||
end
|
|
||||||
|
|
||||||
def rm_r(dir)
|
|
||||||
say_status "rm -r #{dir}", '', :green unless options[:quiet]
|
|
||||||
FileUtils.rm_r dir
|
|
||||||
end
|
|
||||||
|
|
||||||
def ln_s(source, destination, config = {})
|
|
||||||
source = Pathname.new(source)
|
|
||||||
destination = Pathname.new(destination)
|
|
||||||
FileUtils.mkdir_p destination.dirname
|
|
||||||
|
|
||||||
action = if destination.symlink? && destination.readlink == source
|
|
||||||
:identical
|
|
||||||
elsif destination.symlink?
|
|
||||||
:symlink_conflict
|
|
||||||
elsif destination.exist?
|
|
||||||
:conflict
|
|
||||||
else
|
|
||||||
:success
|
|
||||||
end
|
|
||||||
|
|
||||||
handle_symlink_action action, source, destination
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_symlink_action(action, source, destination)
|
|
||||||
case action
|
|
||||||
when :identical
|
|
||||||
say_status :identical, destination.expand_path, :blue unless options[:quiet]
|
|
||||||
when :symlink_conflict
|
|
||||||
say_status :conflict,
|
|
||||||
"#{destination} exists and points to #{destination.readlink}",
|
|
||||||
:red unless options[:quiet]
|
|
||||||
|
|
||||||
FileUtils.rm destination
|
|
||||||
FileUtils.ln_s source, destination, force: true unless options[:pretend]
|
|
||||||
when :conflict
|
|
||||||
say_status :conflict, "#{destination} exists", :red unless options[:quiet]
|
|
||||||
|
|
||||||
if collision_accepted?
|
|
||||||
FileUtils.rm_r destination, force: true unless options[:pretend]
|
|
||||||
FileUtils.ln_s source, destination, force: true unless options[:pretend]
|
|
||||||
end
|
|
||||||
else
|
|
||||||
say_status :symlink,
|
|
||||||
"#{source.expand_path} to #{destination.expand_path}",
|
|
||||||
:green unless options[:quiet]
|
|
||||||
FileUtils.ln_s source, destination unless options[:pretend]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
91
lib/homesick/actions/file_actions.rb
Normal file
91
lib/homesick/actions/file_actions.rb
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
# -*- encoding : utf-8 -*-
|
||||||
|
module Homesick
|
||||||
|
module Actions
|
||||||
|
# File-related helper methods for Homesick
|
||||||
|
module FileActions
|
||||||
|
def mv(source, destination, config = {})
|
||||||
|
source = Pathname.new(source)
|
||||||
|
destination = Pathname.new(destination + source.basename)
|
||||||
|
|
||||||
|
if destination.exist?
|
||||||
|
say_status :conflict, "#{destination} exists", :red unless options[:quiet]
|
||||||
|
|
||||||
|
FileUtils.mv source, destination if (options[:force] || shell.file_collision(destination) { source }) && !options[:pretend]
|
||||||
|
else
|
||||||
|
# this needs some sort of message here.
|
||||||
|
FileUtils.mv source, destination unless options[:pretend]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def rm_rf(dir)
|
||||||
|
say_status "rm -rf #{dir}", '', :green unless options[:quiet]
|
||||||
|
FileUtils.rm_r dir, force: true
|
||||||
|
end
|
||||||
|
|
||||||
|
def rm_link(target)
|
||||||
|
target = Pathname.new(target)
|
||||||
|
|
||||||
|
if target.symlink?
|
||||||
|
say_status :unlink, "#{target.expand_path}", :green unless options[:quiet]
|
||||||
|
FileUtils.rm_rf target
|
||||||
|
else
|
||||||
|
say_status :conflict, "#{target} is not a symlink", :red unless options[:quiet]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def rm(file)
|
||||||
|
say_status "rm #{file}", '', :green unless options[:quiet]
|
||||||
|
FileUtils.rm file, force: true
|
||||||
|
end
|
||||||
|
|
||||||
|
def rm_r(dir)
|
||||||
|
say_status "rm -r #{dir}", '', :green unless options[:quiet]
|
||||||
|
FileUtils.rm_r dir
|
||||||
|
end
|
||||||
|
|
||||||
|
def ln_s(source, destination, config = {})
|
||||||
|
source = Pathname.new(source)
|
||||||
|
destination = Pathname.new(destination)
|
||||||
|
FileUtils.mkdir_p destination.dirname
|
||||||
|
|
||||||
|
action = if destination.symlink? && destination.readlink == source
|
||||||
|
:identical
|
||||||
|
elsif destination.symlink?
|
||||||
|
:symlink_conflict
|
||||||
|
elsif destination.exist?
|
||||||
|
:conflict
|
||||||
|
else
|
||||||
|
:success
|
||||||
|
end
|
||||||
|
|
||||||
|
handle_symlink_action action, source, destination
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_symlink_action(action, source, destination)
|
||||||
|
case action
|
||||||
|
when :identical
|
||||||
|
say_status :identical, destination.expand_path, :blue unless options[:quiet]
|
||||||
|
when :symlink_conflict
|
||||||
|
say_status :conflict,
|
||||||
|
"#{destination} exists and points to #{destination.readlink}",
|
||||||
|
:red unless options[:quiet]
|
||||||
|
|
||||||
|
FileUtils.rm destination
|
||||||
|
FileUtils.ln_s source, destination, force: true unless options[:pretend]
|
||||||
|
when :conflict
|
||||||
|
say_status :conflict, "#{destination} exists", :red unless options[:quiet]
|
||||||
|
|
||||||
|
if collision_accepted?
|
||||||
|
FileUtils.rm_r destination, force: true unless options[:pretend]
|
||||||
|
FileUtils.ln_s source, destination, force: true unless options[:pretend]
|
||||||
|
end
|
||||||
|
else
|
||||||
|
say_status :symlink,
|
||||||
|
"#{source.expand_path} to #{destination.expand_path}",
|
||||||
|
:green unless options[:quiet]
|
||||||
|
FileUtils.ln_s source, destination unless options[:pretend]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
94
lib/homesick/actions/git_actions.rb
Normal file
94
lib/homesick/actions/git_actions.rb
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
# -*- encoding : utf-8 -*-
|
||||||
|
module Homesick
|
||||||
|
module Actions
|
||||||
|
# Git-related helper methods for Homesick
|
||||||
|
module GitActions
|
||||||
|
# TODO: move this to be more like thor's template, empty_directory, etc
|
||||||
|
def git_clone(repo, config = {})
|
||||||
|
config ||= {}
|
||||||
|
destination = config[:destination] || File.basename(repo, '.git')
|
||||||
|
|
||||||
|
destination = Pathname.new(destination) unless destination.kind_of?(Pathname)
|
||||||
|
FileUtils.mkdir_p destination.dirname
|
||||||
|
|
||||||
|
if destination.directory?
|
||||||
|
say_status :exist, destination.expand_path, :blue unless options[:quiet]
|
||||||
|
else
|
||||||
|
say_status 'git clone',
|
||||||
|
"#{repo} to #{destination.expand_path}",
|
||||||
|
:green unless options[:quiet]
|
||||||
|
system "git clone -q --config push.default=upstream --recursive #{repo} #{destination}" unless options[:pretend]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def git_init(path = '.')
|
||||||
|
path = Pathname.new(path)
|
||||||
|
|
||||||
|
inside path do
|
||||||
|
if path.join('.git').exist?
|
||||||
|
say_status 'git init', 'already initialized', :blue unless options[:quiet]
|
||||||
|
else
|
||||||
|
say_status 'git init', '' unless options[:quiet]
|
||||||
|
system 'git init >/dev/null' unless options[:pretend]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def git_remote_add(name, url)
|
||||||
|
existing_remote = `git config remote.#{name}.url`.chomp
|
||||||
|
existing_remote = nil if existing_remote == ''
|
||||||
|
|
||||||
|
if existing_remote
|
||||||
|
say_status 'git remote', "#{name} already exists", :blue unless options[:quiet]
|
||||||
|
else
|
||||||
|
say_status 'git remote', "add #{name} #{url}" unless options[:quiet]
|
||||||
|
system "git remote add #{name} #{url}" unless options[:pretend]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def git_submodule_init(config = {})
|
||||||
|
say_status 'git submodule', 'init', :green unless options[:quiet]
|
||||||
|
system 'git submodule --quiet init' unless options[:pretend]
|
||||||
|
end
|
||||||
|
|
||||||
|
def git_submodule_update(config = {})
|
||||||
|
say_status 'git submodule', 'update', :green unless options[:quiet]
|
||||||
|
system 'git submodule --quiet update --init --recursive >/dev/null 2>&1' unless options[:pretend]
|
||||||
|
end
|
||||||
|
|
||||||
|
def git_pull(config = {})
|
||||||
|
say_status 'git pull', '', :green unless options[:quiet]
|
||||||
|
system 'git pull --quiet' unless options[:pretend]
|
||||||
|
end
|
||||||
|
|
||||||
|
def git_push(config = {})
|
||||||
|
say_status 'git push', '', :green unless options[:quiet]
|
||||||
|
system 'git push' unless options[:pretend]
|
||||||
|
end
|
||||||
|
|
||||||
|
def git_commit_all(config = {})
|
||||||
|
say_status 'git commit all', '', :green unless options[:quiet]
|
||||||
|
if config[:message]
|
||||||
|
system "git commit -a -m '#{config[:message]}'" unless options[:pretend]
|
||||||
|
else
|
||||||
|
system 'git commit -v -a' unless options[:pretend]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def git_add(file, config = {})
|
||||||
|
say_status 'git add file', '', :green unless options[:quiet]
|
||||||
|
system "git add '#{file}'" unless options[:pretend]
|
||||||
|
end
|
||||||
|
|
||||||
|
def git_status(config = {})
|
||||||
|
say_status 'git status', '', :green unless options[:quiet]
|
||||||
|
system 'git status' unless options[:pretend]
|
||||||
|
end
|
||||||
|
|
||||||
|
def git_diff(config = {})
|
||||||
|
say_status 'git diff', '', :green unless options[:quiet]
|
||||||
|
system 'git diff' unless options[:pretend]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
336
lib/homesick/cli.rb
Normal file
336
lib/homesick/cli.rb
Normal file
@@ -0,0 +1,336 @@
|
|||||||
|
# -*- encoding : utf-8 -*-
|
||||||
|
require 'thor'
|
||||||
|
|
||||||
|
module Homesick
|
||||||
|
# Homesick's command line interface
|
||||||
|
class CLI < Thor
|
||||||
|
include Thor::Actions
|
||||||
|
include Homesick::Actions::FileActions
|
||||||
|
include Homesick::Actions::GitActions
|
||||||
|
include Homesick::Version
|
||||||
|
include Homesick::Utils
|
||||||
|
|
||||||
|
add_runtime_options!
|
||||||
|
|
||||||
|
map '-v' => :version
|
||||||
|
map '--version' => :version
|
||||||
|
# Retain a mapped version of the symlink command for compatibility.
|
||||||
|
map symlink: :link
|
||||||
|
|
||||||
|
def initialize(args = [], options = {}, config = {})
|
||||||
|
super
|
||||||
|
self.shell = Homesick::Shell.new
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'clone URI', 'Clone +uri+ as a castle for homesick'
|
||||||
|
def clone(uri)
|
||||||
|
inside repos_dir do
|
||||||
|
destination = nil
|
||||||
|
if File.exist?(uri)
|
||||||
|
uri = Pathname.new(uri).expand_path
|
||||||
|
fail "Castle already cloned to #{uri}" if uri.to_s.start_with?(repos_dir.to_s)
|
||||||
|
|
||||||
|
destination = uri.basename
|
||||||
|
|
||||||
|
ln_s uri, destination
|
||||||
|
elsif uri =~ GITHUB_NAME_REPO_PATTERN
|
||||||
|
destination = Pathname.new(uri).basename
|
||||||
|
git_clone "https://github.com/#{Regexp.last_match[1]}.git",
|
||||||
|
destination: destination
|
||||||
|
elsif uri =~ /%r([^%r]*?)(\.git)?\Z/ || uri =~ /[^:]+:([^:]+)(\.git)?\Z/
|
||||||
|
destination = Pathname.new(Regexp.last_match[1])
|
||||||
|
git_clone uri
|
||||||
|
else
|
||||||
|
fail "Unknown URI format: #{uri}"
|
||||||
|
end
|
||||||
|
|
||||||
|
setup_castle(destination)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'rc CASTLE', 'Run the .homesickrc for the specified castle'
|
||||||
|
def rc(name = DEFAULT_CASTLE_NAME)
|
||||||
|
inside repos_dir do
|
||||||
|
destination = Pathname.new(name)
|
||||||
|
homesickrc = destination.join('.homesickrc').expand_path
|
||||||
|
if homesickrc.exist?
|
||||||
|
proceed = shell.yes?("#{name} has a .homesickrc. Proceed with evaling it? (This could be destructive)")
|
||||||
|
if proceed
|
||||||
|
shell.say_status 'eval', homesickrc
|
||||||
|
inside destination do
|
||||||
|
eval homesickrc.read, binding, homesickrc.expand_path.to_s
|
||||||
|
end
|
||||||
|
else
|
||||||
|
shell.say_status 'eval skip',
|
||||||
|
"not evaling #{homesickrc}, #{destination} may need manual configuration",
|
||||||
|
:blue
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'pull CASTLE', 'Update the specified castle'
|
||||||
|
method_option :all,
|
||||||
|
type: :boolean,
|
||||||
|
default: false,
|
||||||
|
required: false,
|
||||||
|
desc: 'Update all cloned castles'
|
||||||
|
def pull(name = DEFAULT_CASTLE_NAME)
|
||||||
|
if options[:all]
|
||||||
|
inside_each_castle do |castle|
|
||||||
|
shell.say castle.to_s.gsub(repos_dir.to_s + '/', '') + ':'
|
||||||
|
update_castle castle
|
||||||
|
end
|
||||||
|
else
|
||||||
|
update_castle name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'commit CASTLE MESSAGE', "Commit the specified castle's changes"
|
||||||
|
def commit(name = DEFAULT_CASTLE_NAME, message = nil)
|
||||||
|
commit_castle name, message
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'push CASTLE', 'Push the specified castle'
|
||||||
|
def push(name = DEFAULT_CASTLE_NAME)
|
||||||
|
push_castle name
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'unlink CASTLE', 'Unsymlinks all dotfiles from the specified castle'
|
||||||
|
def unlink(name = DEFAULT_CASTLE_NAME)
|
||||||
|
check_castle_existance(name, 'symlink')
|
||||||
|
|
||||||
|
inside castle_dir(name) do
|
||||||
|
subdirs = subdirs(name)
|
||||||
|
|
||||||
|
# unlink files
|
||||||
|
unsymlink_each(name, castle_dir(name), subdirs)
|
||||||
|
|
||||||
|
# unlink files in subdirs
|
||||||
|
subdirs.each do |subdir|
|
||||||
|
unsymlink_each(name, subdir, subdirs)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'link CASTLE', 'Symlinks all dotfiles from the specified castle'
|
||||||
|
method_option :force,
|
||||||
|
default: false,
|
||||||
|
desc: 'Overwrite existing conflicting symlinks without prompting.'
|
||||||
|
def link(name = DEFAULT_CASTLE_NAME)
|
||||||
|
check_castle_existance(name, 'symlink')
|
||||||
|
|
||||||
|
inside castle_dir(name) do
|
||||||
|
subdirs = subdirs(name)
|
||||||
|
|
||||||
|
# link files
|
||||||
|
symlink_each(name, castle_dir(name), subdirs)
|
||||||
|
|
||||||
|
# link files in subdirs
|
||||||
|
subdirs.each do |subdir|
|
||||||
|
symlink_each(name, subdir, subdirs)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'track FILE CASTLE', 'add a file to a castle'
|
||||||
|
def track(file, castle = DEFAULT_CASTLE_NAME)
|
||||||
|
castle = Pathname.new(castle)
|
||||||
|
file = Pathname.new(file.chomp('/'))
|
||||||
|
check_castle_existance(castle, 'track')
|
||||||
|
|
||||||
|
absolute_path = file.expand_path
|
||||||
|
relative_dir = absolute_path.relative_path_from(home_dir).dirname
|
||||||
|
castle_path = Pathname.new(castle_dir(castle)).join(relative_dir)
|
||||||
|
FileUtils.mkdir_p castle_path
|
||||||
|
|
||||||
|
# Are we already tracking this or anything inside it?
|
||||||
|
target = Pathname.new(castle_path.join(file.basename))
|
||||||
|
if target.exist?
|
||||||
|
if absolute_path.directory?
|
||||||
|
move_dir_contents(target, absolute_path)
|
||||||
|
absolute_path.rmtree
|
||||||
|
subdir_remove(castle, relative_dir + file.basename)
|
||||||
|
|
||||||
|
elsif more_recent? absolute_path, target
|
||||||
|
target.delete
|
||||||
|
mv absolute_path, castle_path
|
||||||
|
else
|
||||||
|
shell.say_status(:track,
|
||||||
|
"#{target} already exists, and is more recent than #{file}. Run 'homesick SYMLINK CASTLE' to create symlinks.",
|
||||||
|
:blue) unless options[:quiet]
|
||||||
|
end
|
||||||
|
else
|
||||||
|
mv absolute_path, castle_path
|
||||||
|
end
|
||||||
|
|
||||||
|
inside home_dir do
|
||||||
|
absolute_path = castle_path + file.basename
|
||||||
|
home_path = home_dir + relative_dir + file.basename
|
||||||
|
ln_s absolute_path, home_path
|
||||||
|
end
|
||||||
|
|
||||||
|
inside castle_path do
|
||||||
|
git_add absolute_path
|
||||||
|
end
|
||||||
|
|
||||||
|
# are we tracking something nested? Add the parent dir to the manifest
|
||||||
|
subdir_add(castle, relative_dir) unless relative_dir.eql?(Pathname.new('.'))
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'list', 'List cloned castles'
|
||||||
|
def list
|
||||||
|
inside_each_castle do |castle|
|
||||||
|
say_status castle.relative_path_from(repos_dir).to_s,
|
||||||
|
`git config remote.origin.url`.chomp,
|
||||||
|
:cyan
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'status CASTLE', 'Shows the git status of a castle'
|
||||||
|
def status(castle = DEFAULT_CASTLE_NAME)
|
||||||
|
check_castle_existance(castle, 'status')
|
||||||
|
inside repos_dir.join(castle) do
|
||||||
|
git_status
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'diff CASTLE', 'Shows the git diff of uncommitted changes in a castle'
|
||||||
|
def diff(castle = DEFAULT_CASTLE_NAME)
|
||||||
|
check_castle_existance(castle, 'diff')
|
||||||
|
inside repos_dir.join(castle) do
|
||||||
|
git_diff
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'show_path CASTLE', 'Prints the path of a castle'
|
||||||
|
def show_path(castle = DEFAULT_CASTLE_NAME)
|
||||||
|
check_castle_existance(castle, 'show_path')
|
||||||
|
say repos_dir.join(castle)
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'generate PATH', 'generate a homesick-ready git repo at PATH'
|
||||||
|
def generate(castle)
|
||||||
|
castle = Pathname.new(castle).expand_path
|
||||||
|
|
||||||
|
github_user = `git config github.user`.chomp
|
||||||
|
github_user = nil if github_user == ''
|
||||||
|
github_repo = castle.basename
|
||||||
|
|
||||||
|
empty_directory castle
|
||||||
|
inside castle do
|
||||||
|
git_init
|
||||||
|
if github_user
|
||||||
|
url = "git@github.com:#{github_user}/#{github_repo}.git"
|
||||||
|
git_remote_add 'origin', url
|
||||||
|
end
|
||||||
|
|
||||||
|
empty_directory 'home'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'destroy CASTLE', 'Delete all symlinks and remove the cloned repository'
|
||||||
|
def destroy(name)
|
||||||
|
check_castle_existance name, 'destroy'
|
||||||
|
|
||||||
|
if shell.yes?('This will destroy your castle irreversible! Are you sure?')
|
||||||
|
unlink(name)
|
||||||
|
rm_rf repos_dir.join(name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'cd CASTLE', 'Open a new shell in the root of the given castle'
|
||||||
|
def cd(castle = DEFAULT_CASTLE_NAME)
|
||||||
|
check_castle_existance castle, 'cd'
|
||||||
|
castle_dir = repos_dir.join(castle)
|
||||||
|
say_status "cd #{castle_dir.realpath}",
|
||||||
|
"Opening a new shell in castle '#{castle}'. To return to the original one exit from the new shell.",
|
||||||
|
:green
|
||||||
|
inside castle_dir do
|
||||||
|
system(ENV['SHELL'])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'open CASTLE',
|
||||||
|
'Open your default editor in the root of the given castle'
|
||||||
|
def open(castle = DEFAULT_CASTLE_NAME)
|
||||||
|
unless ENV['EDITOR']
|
||||||
|
say_status :error,
|
||||||
|
'The $EDITOR environment variable must be set to use this command',
|
||||||
|
:red
|
||||||
|
|
||||||
|
exit(1)
|
||||||
|
end
|
||||||
|
check_castle_existance castle, 'open'
|
||||||
|
castle_dir = repos_dir.join(castle)
|
||||||
|
say_status "#{ENV['EDITOR']} #{castle_dir.realpath}",
|
||||||
|
"Opening the root directory of castle '#{castle}' in editor '#{ENV['EDITOR']}'.",
|
||||||
|
:green
|
||||||
|
inside castle_dir do
|
||||||
|
system(ENV['EDITOR'])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'exec CASTLE COMMAND',
|
||||||
|
'Execute a single shell command inside the root of a castle'
|
||||||
|
method_option :pretend,
|
||||||
|
type: :boolean,
|
||||||
|
default: false,
|
||||||
|
required: false,
|
||||||
|
desc: 'Perform a dry run of the command'
|
||||||
|
method_option :quiet,
|
||||||
|
type: :boolean,
|
||||||
|
default: false,
|
||||||
|
required: false,
|
||||||
|
desc: 'Run a command without any output from Homesick'
|
||||||
|
def exec(castle, *args)
|
||||||
|
check_castle_existance castle, 'exec'
|
||||||
|
unless args.count > 0
|
||||||
|
say_status :error,
|
||||||
|
'You must pass a shell command to execute',
|
||||||
|
:red
|
||||||
|
exit(1)
|
||||||
|
end
|
||||||
|
full_command = args.join(' ')
|
||||||
|
say_status "exec '#{full_command}'",
|
||||||
|
"#{options[:pretend] ? 'Would execute' : 'Executing command'} '#{full_command}' in castle '#{castle}'",
|
||||||
|
:green unless options[:quiet]
|
||||||
|
inside repos_dir.join(castle) do
|
||||||
|
system(full_command) unless options[:pretend]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'exec_all COMMAND',
|
||||||
|
'Execute a single shell command inside the root of every cloned castle'
|
||||||
|
method_option :pretend,
|
||||||
|
type: :boolean,
|
||||||
|
default: false,
|
||||||
|
required: false,
|
||||||
|
desc: 'Perform a dry run of the command'
|
||||||
|
method_option :quiet,
|
||||||
|
type: :boolean,
|
||||||
|
default: false,
|
||||||
|
required: false,
|
||||||
|
desc: 'Run a command without any output from Homesick'
|
||||||
|
def exec_all(*args)
|
||||||
|
unless args.count > 0
|
||||||
|
say_status :error,
|
||||||
|
'You must pass a shell command to execute',
|
||||||
|
:red
|
||||||
|
exit(1)
|
||||||
|
end
|
||||||
|
full_command = args.join(' ')
|
||||||
|
inside_each_castle do |castle|
|
||||||
|
say_status "exec '#{full_command}'",
|
||||||
|
"#{options[:pretend] ? 'Would execute' : 'Executing command'} '#{full_command}' in castle '#{castle}'",
|
||||||
|
:green unless options[:quiet]
|
||||||
|
system(full_command) unless options[:pretend]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'version', 'Display the current version of homesick'
|
||||||
|
def version
|
||||||
|
say Homesick::Version::STRING
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
class Homesick
|
require 'thor'
|
||||||
|
|
||||||
|
module Homesick
|
||||||
# Hack in support for diffing symlinks
|
# Hack in support for diffing symlinks
|
||||||
class Shell < Thor::Shell::Color
|
class Shell < Thor::Shell::Color
|
||||||
def show_diff(destination, content)
|
def show_diff(destination, content)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# -*- encoding : utf-8 -*-
|
# -*- encoding : utf-8 -*-
|
||||||
class Homesick
|
module Homesick
|
||||||
# Various utility methods that are used by Homesick
|
# Various utility methods that are used by Homesick
|
||||||
module Utils
|
module Utils
|
||||||
protected
|
protected
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# -*- encoding : utf-8 -*-
|
# -*- encoding : utf-8 -*-
|
||||||
class Homesick
|
module Homesick
|
||||||
# A representation of Homesick's version number in constants, including a
|
# A representation of Homesick's version number in constants, including a
|
||||||
# String of the entire version number
|
# String of the entire version number
|
||||||
module Version
|
module Version
|
||||||
|
|||||||
@@ -2,13 +2,13 @@
|
|||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
require 'capture-output'
|
require 'capture-output'
|
||||||
|
|
||||||
describe 'homesick' do
|
describe Homesick::CLI do
|
||||||
let(:home) { create_construct }
|
let(:home) { create_construct }
|
||||||
after { home.destroy! }
|
after { home.destroy! }
|
||||||
|
|
||||||
let(:castles) { home.directory('.homesick/repos') }
|
let(:castles) { home.directory('.homesick/repos') }
|
||||||
|
|
||||||
let(:homesick) { Homesick.new }
|
let(:homesick) { Homesick::CLI.new }
|
||||||
|
|
||||||
before { allow(homesick).to receive(:repos_dir).and_return(castles) }
|
before { allow(homesick).to receive(:repos_dir).and_return(castles) }
|
||||||
|
|
||||||
@@ -176,7 +176,7 @@ describe 'homesick' do
|
|||||||
end
|
end
|
||||||
|
|
||||||
context 'when forced' do
|
context 'when forced' do
|
||||||
let(:homesick) { Homesick.new [], force: true }
|
let(:homesick) { Homesick::CLI.new [], force: true }
|
||||||
|
|
||||||
it 'can override symlinks to directories' do
|
it 'can override symlinks to directories' do
|
||||||
somewhere_else = create_construct
|
somewhere_else = create_construct
|
||||||
Reference in New Issue
Block a user