homesick 1.1.6 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.github/dependabot.yml +9 -0
- data/.github/workflows/ci.yml +67 -0
- data/.github/workflows/mutant-nightly.yml +31 -0
- data/.github/workflows/release.yml +27 -0
- data/.gitignore +52 -0
- data/.mutant.yml +26 -0
- data/.rubocop.yml +37 -14
- data/ChangeLog.markdown +28 -1
- data/Gemfile +3 -34
- data/Gemfile.lock +128 -0
- data/Guardfile +6 -4
- data/README.markdown +7 -11
- data/Rakefile +8 -62
- data/bin/homesick +1 -0
- data/homesick.gemspec +28 -99
- data/lib/homesick/actions/file_actions.rb +31 -37
- data/lib/homesick/actions/git_actions.rb +18 -15
- data/lib/homesick/cli.rb +39 -62
- data/lib/homesick/rc.rb +30 -0
- data/lib/homesick/utils.rb +94 -52
- data/lib/homesick/version.rb +5 -4
- data/lib/homesick.rb +19 -1
- data/spec/homesick_cli_spec.rb +74 -47
- data/spec/homesick_rc_spec.rb +59 -0
- data/spec/spec_helper.rb +1 -2
- metadata +37 -120
- data/.travis.yml +0 -6
data/lib/homesick/utils.rb
CHANGED
|
@@ -1,29 +1,14 @@
|
|
|
1
|
-
#
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
2
3
|
require 'pathname'
|
|
3
4
|
|
|
4
5
|
module Homesick
|
|
5
6
|
# Various utility methods that are used by Homesick
|
|
6
7
|
module Utils
|
|
7
|
-
QUIETABLE = [:say_status]
|
|
8
|
-
|
|
9
|
-
PRETENDABLE = [:system]
|
|
10
|
-
|
|
11
|
-
QUIETABLE.each do |method_name|
|
|
12
|
-
define_method(method_name) do |*args|
|
|
13
|
-
super(*args) unless options[:quiet]
|
|
14
|
-
end
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
PRETENDABLE.each do |method_name|
|
|
18
|
-
define_method(method_name) do |*args|
|
|
19
|
-
super(*args) unless options[:pretend]
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
|
|
23
8
|
protected
|
|
24
9
|
|
|
25
10
|
def home_dir
|
|
26
|
-
@home_dir ||= Pathname.new(
|
|
11
|
+
@home_dir ||= Pathname.new(Dir.home).realpath
|
|
27
12
|
end
|
|
28
13
|
|
|
29
14
|
def repos_dir
|
|
@@ -36,6 +21,7 @@ module Homesick
|
|
|
36
21
|
|
|
37
22
|
def check_castle_existance(name, action)
|
|
38
23
|
return if castle_dir(name).exist?
|
|
24
|
+
|
|
39
25
|
say_status :error,
|
|
40
26
|
"Could not #{action} #{name}, expected #{castle_dir(name)} to exist and contain dotfiles",
|
|
41
27
|
:red
|
|
@@ -115,7 +101,7 @@ module Homesick
|
|
|
115
101
|
def subdir_remove(castle, path)
|
|
116
102
|
subdir_filepath = subdir_file(castle)
|
|
117
103
|
if subdir_filepath.exist?
|
|
118
|
-
lines =
|
|
104
|
+
lines = File.readlines(subdir_filepath).delete_if do |line|
|
|
119
105
|
line == "#{path}\n"
|
|
120
106
|
end
|
|
121
107
|
File.open(subdir_filepath, 'w') { |manfile| manfile.puts lines }
|
|
@@ -149,44 +135,48 @@ module Homesick
|
|
|
149
135
|
end
|
|
150
136
|
|
|
151
137
|
def collision_accepted?(destination, source)
|
|
152
|
-
|
|
138
|
+
unless destination.instance_of?(Pathname) && source.instance_of?(Pathname)
|
|
139
|
+
raise 'Arguments must be instances of Pathname, ' \
|
|
140
|
+
"#{destination.class.name} and #{source.class.name} given"
|
|
141
|
+
end
|
|
142
|
+
|
|
153
143
|
options[:force] || shell.file_collision(destination) { source }
|
|
154
144
|
end
|
|
155
145
|
|
|
156
|
-
def
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
end
|
|
174
|
-
|
|
175
|
-
# ignore dirs written in subdir file
|
|
176
|
-
matched = false
|
|
177
|
-
ignore_dirs.uniq.each do |ignore_dir|
|
|
178
|
-
if absolute_path == castle_home.join(ignore_dir)
|
|
179
|
-
matched = true
|
|
180
|
-
break
|
|
181
|
-
end
|
|
182
|
-
end
|
|
183
|
-
next if matched
|
|
146
|
+
def clone_from_uri(uri, destination)
|
|
147
|
+
if File.exist?(uri)
|
|
148
|
+
uri_path = Pathname.new(uri).expand_path
|
|
149
|
+
raise "Castle already cloned to #{uri_path}" if uri_path.to_s.start_with?(repos_dir.to_s)
|
|
150
|
+
|
|
151
|
+
destination = uri_path.basename if destination.nil?
|
|
152
|
+
ln_s uri_path, destination
|
|
153
|
+
elsif uri =~ GITHUB_NAME_REPO_PATTERN
|
|
154
|
+
destination = Pathname.new(uri).basename if destination.nil?
|
|
155
|
+
git_clone "https://github.com/#{Regexp.last_match[1]}.git",
|
|
156
|
+
destination: destination
|
|
157
|
+
elsif uri =~ /%r([^%r]*?)(\.git)?\Z/ || uri =~ /[^:]+:([^:]+)(\.git)?\Z/
|
|
158
|
+
destination = Pathname.new(Regexp.last_match[1].gsub(/\.git$/, '')).basename if destination.nil?
|
|
159
|
+
git_clone uri, destination: destination
|
|
160
|
+
else
|
|
161
|
+
raise "Unknown URI format: #{uri}"
|
|
162
|
+
end
|
|
184
163
|
|
|
185
|
-
|
|
186
|
-
|
|
164
|
+
destination
|
|
165
|
+
end
|
|
187
166
|
|
|
188
|
-
|
|
189
|
-
|
|
167
|
+
def handle_existing_track_target(castle, absolute_path, castle_path, relative_dir, file, target)
|
|
168
|
+
if absolute_path.directory?
|
|
169
|
+
move_dir_contents(target, absolute_path)
|
|
170
|
+
absolute_path.rmtree
|
|
171
|
+
subdir_remove(castle, relative_dir + file.basename)
|
|
172
|
+
elsif more_recent? absolute_path, target
|
|
173
|
+
target.delete
|
|
174
|
+
mv absolute_path, castle_path
|
|
175
|
+
else
|
|
176
|
+
say_status(:track,
|
|
177
|
+
"#{target} already exists, and is more recent than #{file}. " \
|
|
178
|
+
"Run 'homesick SYMLINK CASTLE' to create symlinks.",
|
|
179
|
+
:blue)
|
|
190
180
|
end
|
|
191
181
|
end
|
|
192
182
|
|
|
@@ -212,5 +202,57 @@ module Homesick
|
|
|
212
202
|
|
|
213
203
|
rc(path)
|
|
214
204
|
end
|
|
205
|
+
|
|
206
|
+
def each_file(castle, basedir, subdirs)
|
|
207
|
+
absolute_basedir = Pathname.new(basedir).expand_path
|
|
208
|
+
castle_home = castle_dir(castle)
|
|
209
|
+
inside basedir do |destination_root|
|
|
210
|
+
FileUtils.cd(destination_root) unless destination_root == FileUtils.pwd
|
|
211
|
+
files = Pathname.glob('*', File::FNM_DOTMATCH)
|
|
212
|
+
.reject { |a| ['.', '..'].include?(a.to_s) }
|
|
213
|
+
.reject { |path| matches_ignored_dir? castle_home, path.expand_path, subdirs }
|
|
214
|
+
files.each do |path|
|
|
215
|
+
absolute_path = path.expand_path
|
|
216
|
+
|
|
217
|
+
relative_dir = absolute_basedir.relative_path_from(castle_home)
|
|
218
|
+
home_path = home_dir.join(relative_dir).join(path)
|
|
219
|
+
|
|
220
|
+
yield(absolute_path, home_path)
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def matches_ignored_dir?(castle_home, absolute_path, subdirs)
|
|
226
|
+
# make ignore dirs
|
|
227
|
+
ignore_dirs = []
|
|
228
|
+
subdirs.each do |subdir|
|
|
229
|
+
# ignore all parent of each line in subdir file
|
|
230
|
+
Pathname.new(subdir).ascend do |p|
|
|
231
|
+
ignore_dirs.push(p)
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# ignore dirs written in subdir file
|
|
236
|
+
ignore_dirs.uniq.each do |ignore_dir|
|
|
237
|
+
return true if absolute_path == castle_home.join(ignore_dir)
|
|
238
|
+
end
|
|
239
|
+
false
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def configure_symlinks_diff
|
|
243
|
+
# Hack in support for diffing symlinks
|
|
244
|
+
# Also adds support for checking if destination or content is a directory
|
|
245
|
+
shell_metaclass = class << shell; self; end
|
|
246
|
+
shell_metaclass.send(:define_method, :show_diff) do |destination, source|
|
|
247
|
+
destination = Pathname.new(destination)
|
|
248
|
+
source = Pathname.new(source)
|
|
249
|
+
return 'Unable to create diff: destination or content is a directory' \
|
|
250
|
+
if destination.directory? || source.directory?
|
|
251
|
+
return super(destination, File.binread(source)) unless destination.symlink?
|
|
252
|
+
|
|
253
|
+
say "- #{destination.readlink}", :red, true
|
|
254
|
+
say "+ #{source.expand_path}", :green, true
|
|
255
|
+
end
|
|
256
|
+
end
|
|
215
257
|
end
|
|
216
258
|
end
|
data/lib/homesick/version.rb
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
#
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
2
3
|
module Homesick
|
|
3
4
|
# A representation of Homesick's version number in constants, including a
|
|
4
5
|
# String of the entire version number
|
|
5
6
|
module Version
|
|
6
|
-
MAJOR =
|
|
7
|
-
MINOR =
|
|
8
|
-
PATCH =
|
|
7
|
+
MAJOR = 2
|
|
8
|
+
MINOR = 0
|
|
9
|
+
PATCH = 0
|
|
9
10
|
|
|
10
11
|
STRING = [MAJOR, MINOR, PATCH].compact.join('.')
|
|
11
12
|
end
|
data/lib/homesick.rb
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
#
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
2
3
|
require 'homesick/actions/file_actions'
|
|
3
4
|
require 'homesick/actions/git_actions'
|
|
4
5
|
require 'homesick/version'
|
|
6
|
+
require 'homesick/rc'
|
|
5
7
|
require 'homesick/utils'
|
|
6
8
|
require 'homesick/cli'
|
|
9
|
+
require 'fileutils'
|
|
7
10
|
|
|
8
11
|
# Homesick's top-level module
|
|
9
12
|
module Homesick
|
|
@@ -11,4 +14,19 @@ module Homesick
|
|
|
11
14
|
SUBDIR_FILENAME = '.homesick_subdir'
|
|
12
15
|
|
|
13
16
|
DEFAULT_CASTLE_NAME = 'dotfiles'
|
|
17
|
+
QUIETABLE = [:say_status].freeze
|
|
18
|
+
|
|
19
|
+
PRETENDABLE = [:system].freeze
|
|
20
|
+
|
|
21
|
+
QUIETABLE.each do |method_name|
|
|
22
|
+
define_method(method_name) do |*args|
|
|
23
|
+
super(*args) unless options[:quiet]
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
PRETENDABLE.each do |method_name|
|
|
28
|
+
define_method(method_name) do |*args|
|
|
29
|
+
super(*args) unless options[:pretend]
|
|
30
|
+
end
|
|
31
|
+
end
|
|
14
32
|
end
|
data/spec/homesick_cli_spec.rb
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
#
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
2
3
|
require 'spec_helper'
|
|
3
4
|
require 'capture-output'
|
|
4
5
|
require 'pathname'
|
|
@@ -158,8 +159,8 @@ describe Homesick::CLI do
|
|
|
158
159
|
|
|
159
160
|
it 'accepts a destination', :focus do
|
|
160
161
|
expect(homesick).to receive(:git_clone)
|
|
161
|
-
|
|
162
|
-
|
|
162
|
+
.with('https://github.com/wfarr/dotfiles.git',
|
|
163
|
+
destination: Pathname.new('other-name'))
|
|
163
164
|
|
|
164
165
|
homesick.clone 'wfarr/dotfiles', 'other-name'
|
|
165
166
|
end
|
|
@@ -168,6 +169,14 @@ describe Homesick::CLI do
|
|
|
168
169
|
describe 'rc' do
|
|
169
170
|
let(:castle) { given_castle('glencairn') }
|
|
170
171
|
|
|
172
|
+
context 'when no .homesickrc is present' do
|
|
173
|
+
it 'does nothing' do
|
|
174
|
+
expect_any_instance_of(Thor::Shell::Basic).not_to receive(:yes?)
|
|
175
|
+
expect(homesick).not_to receive(:say_status).with('eval', anything)
|
|
176
|
+
homesick.rc castle
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
171
180
|
context 'when told to do so' do
|
|
172
181
|
before do
|
|
173
182
|
expect_any_instance_of(Thor::Shell::Basic).to receive(:yes?).with(be_a(String)).and_return(true)
|
|
@@ -246,6 +255,34 @@ describe Homesick::CLI do
|
|
|
246
255
|
expect(home.join('bin').readlink).to eq(dotfile)
|
|
247
256
|
end
|
|
248
257
|
|
|
258
|
+
context 'when a conflict exists' do
|
|
259
|
+
it 'does not replace the existing file when collision is declined' do
|
|
260
|
+
dotfile = castle.file('.some_dotfile')
|
|
261
|
+
existing = home.file('.some_dotfile', 'original content')
|
|
262
|
+
|
|
263
|
+
allow(homesick.shell).to receive(:file_collision).and_return(false)
|
|
264
|
+
|
|
265
|
+
homesick.link('glencairn')
|
|
266
|
+
|
|
267
|
+
expect(existing.symlink?).to eq(false)
|
|
268
|
+
expect(existing.read).to eq('original content')
|
|
269
|
+
_ = dotfile # ensure castle file exists
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
it 'replaces the existing file when collision is accepted' do
|
|
273
|
+
dotfile = castle.file('.some_dotfile')
|
|
274
|
+
existing = home.file('.some_dotfile', 'original content')
|
|
275
|
+
|
|
276
|
+
allow(homesick.shell).to receive(:file_collision).and_return(true)
|
|
277
|
+
|
|
278
|
+
homesick.link('glencairn')
|
|
279
|
+
|
|
280
|
+
expect(home.join('.some_dotfile').symlink?).to eq(true)
|
|
281
|
+
expect(home.join('.some_dotfile').readlink).to eq(dotfile)
|
|
282
|
+
_ = existing # ensure conflicting file was created
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
|
|
249
286
|
context 'when forced' do
|
|
250
287
|
let(:homesick) { Homesick::CLI.new [], force: true }
|
|
251
288
|
|
|
@@ -334,29 +371,21 @@ describe Homesick::CLI do
|
|
|
334
371
|
|
|
335
372
|
context 'when call and some files conflict' do
|
|
336
373
|
it 'shows differences for conflicting text files' do
|
|
337
|
-
contents = {:
|
|
374
|
+
contents = { castle: 'castle has new content', home: 'home already has content' }
|
|
338
375
|
|
|
339
376
|
dotfile = castle.file('text')
|
|
340
|
-
File.
|
|
341
|
-
|
|
342
|
-
end
|
|
343
|
-
File.open(home.join('text').to_s, 'w') do |f|
|
|
344
|
-
f.write contents[:home]
|
|
345
|
-
end
|
|
377
|
+
File.write(dotfile.to_s, contents[:castle])
|
|
378
|
+
File.write(home.join('text').to_s, contents[:home])
|
|
346
379
|
message = Capture.stdout { homesick.shell.show_diff(home.join('text'), dotfile) }
|
|
347
380
|
expect(message.b).to match(/- ?#{contents[:home]}\n.*\+ ?#{contents[:castle]}$/m)
|
|
348
381
|
end
|
|
349
382
|
it 'shows message or differences for conflicting binary files' do
|
|
350
383
|
# content which contains NULL character, without any parentheses, braces, ...
|
|
351
|
-
contents = {:
|
|
384
|
+
contents = { castle: (0..255).step(30).map(&:chr).join, home: (0..255).step(30).reverse_each.map(&:chr).join }
|
|
352
385
|
|
|
353
386
|
dotfile = castle.file('binary')
|
|
354
|
-
File.
|
|
355
|
-
|
|
356
|
-
end
|
|
357
|
-
File.open(home.join('binary').to_s, 'w') do |f|
|
|
358
|
-
f.write contents[:home]
|
|
359
|
-
end
|
|
387
|
+
File.write(dotfile.to_s, contents[:castle])
|
|
388
|
+
File.write(home.join('binary').to_s, contents[:home])
|
|
360
389
|
message = Capture.stdout { homesick.shell.show_diff(home.join('binary'), dotfile) }
|
|
361
390
|
if homesick.shell.is_a?(Thor::Shell::Color)
|
|
362
391
|
expect(message.b).to match(/- ?#{contents[:home]}\n.*\+ ?#{contents[:castle]}$/m)
|
|
@@ -480,7 +509,7 @@ describe Homesick::CLI do
|
|
|
480
509
|
some_rc_file = home.file '.some_rc_file'
|
|
481
510
|
homesick.track(some_rc_file.to_s, 'castle_repo')
|
|
482
511
|
text = Capture.stdout { homesick.status('castle_repo') }
|
|
483
|
-
expect(text).to match(%r{Changes to be committed:.*new file:\s*home
|
|
512
|
+
expect(text).to match(%r{Changes to be committed:.*new file:\s*home/.some_rc_file}m)
|
|
484
513
|
end
|
|
485
514
|
end
|
|
486
515
|
|
|
@@ -532,9 +561,9 @@ describe Homesick::CLI do
|
|
|
532
561
|
|
|
533
562
|
it 'prints an error message when trying to pull a non-existant castle' do
|
|
534
563
|
expect(homesick).to receive('say_status').once
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
564
|
+
.with(:error,
|
|
565
|
+
/Could not pull castle_repo, expected .* to exist and contain dotfiles/,
|
|
566
|
+
:red)
|
|
538
567
|
expect { homesick.pull 'castle_repo' }.to raise_error(SystemExit)
|
|
539
568
|
end
|
|
540
569
|
|
|
@@ -544,9 +573,9 @@ describe Homesick::CLI do
|
|
|
544
573
|
given_castle('glencairn')
|
|
545
574
|
allow(homesick).to receive(:system).exactly(2).times.with('git pull --quiet')
|
|
546
575
|
allow(homesick).to receive(:system).exactly(2).times
|
|
547
|
-
|
|
576
|
+
.with('git submodule --quiet init')
|
|
548
577
|
allow(homesick).to receive(:system).exactly(2).times
|
|
549
|
-
|
|
578
|
+
.with('git submodule --quiet update --init --recursive >/dev/null 2>&1')
|
|
550
579
|
Capture.stdout do
|
|
551
580
|
Capture.stderr { homesick.invoke 'pull', [], all: true }
|
|
552
581
|
end
|
|
@@ -563,7 +592,7 @@ describe Homesick::CLI do
|
|
|
563
592
|
|
|
564
593
|
it 'prints an error message when trying to push a non-existant castle' do
|
|
565
594
|
expect(homesick).to receive('say_status').once
|
|
566
|
-
|
|
595
|
+
.with(:error, /Could not push castle_repo, expected .* to exist and contain dotfiles/, :red)
|
|
567
596
|
expect { homesick.push 'castle_repo' }.to raise_error(SystemExit)
|
|
568
597
|
end
|
|
569
598
|
end
|
|
@@ -715,23 +744,21 @@ describe Homesick::CLI do
|
|
|
715
744
|
it "cd's to the root directory of the given castle" do
|
|
716
745
|
given_castle('castle_repo')
|
|
717
746
|
expect(homesick).to receive('inside').once.with(kind_of(Pathname)).and_yield
|
|
718
|
-
expect(homesick).to receive('system').once.with(ENV
|
|
747
|
+
expect(homesick).to receive('system').once.with(ENV.fetch('SHELL', nil))
|
|
719
748
|
Capture.stdout { homesick.cd 'castle_repo' }
|
|
720
749
|
end
|
|
721
750
|
|
|
722
751
|
it 'returns an error message when the given castle does not exist' do
|
|
723
752
|
expect(homesick).to receive('say_status').once
|
|
724
|
-
|
|
753
|
+
.with(:error, /Could not cd castle_repo, expected .* to exist and contain dotfiles/, :red)
|
|
725
754
|
expect { homesick.cd 'castle_repo' }.to raise_error(SystemExit)
|
|
726
755
|
end
|
|
727
756
|
end
|
|
728
757
|
|
|
729
758
|
describe 'open' do
|
|
730
759
|
it 'opens the system default editor in the root of the given castle' do
|
|
731
|
-
# Make sure calls to ENV use default values for most things...
|
|
732
|
-
allow(ENV).to receive(:[]).and_call_original
|
|
733
760
|
# Set a default value for 'EDITOR' just in case none is set
|
|
734
|
-
allow(ENV).to receive(:
|
|
761
|
+
allow(ENV).to receive(:fetch).with('EDITOR', nil).and_return('vim')
|
|
735
762
|
given_castle 'castle_repo'
|
|
736
763
|
expect(homesick).to receive('inside').once.with(kind_of(Pathname)).and_yield
|
|
737
764
|
expect(homesick).to receive('system').once.with('vim .')
|
|
@@ -740,17 +767,17 @@ describe Homesick::CLI do
|
|
|
740
767
|
|
|
741
768
|
it 'returns an error message when the $EDITOR environment variable is not set' do
|
|
742
769
|
# Set the default editor to make sure it fails.
|
|
743
|
-
allow(ENV).to receive(:
|
|
770
|
+
allow(ENV).to receive(:fetch).with('EDITOR', nil).and_return(nil)
|
|
744
771
|
expect(homesick).to receive('say_status').once
|
|
745
|
-
|
|
772
|
+
.with(:error, 'The $EDITOR environment variable must be set to use this command', :red)
|
|
746
773
|
expect { homesick.open 'castle_repo' }.to raise_error(SystemExit)
|
|
747
774
|
end
|
|
748
775
|
|
|
749
776
|
it 'returns an error message when the given castle does not exist' do
|
|
750
777
|
# Set a default just in case none is set
|
|
751
|
-
allow(ENV).to receive(:
|
|
778
|
+
allow(ENV).to receive(:fetch).with('EDITOR', nil).and_return('vim')
|
|
752
779
|
allow(homesick).to receive('say_status').once
|
|
753
|
-
|
|
780
|
+
.with(:error, /Could not open castle_repo, expected .* to exist and contain dotfiles/, :red)
|
|
754
781
|
expect { homesick.open 'castle_repo' }.to raise_error(SystemExit)
|
|
755
782
|
end
|
|
756
783
|
end
|
|
@@ -769,7 +796,7 @@ describe Homesick::CLI do
|
|
|
769
796
|
it 'executes a single command with no arguments inside a given castle' do
|
|
770
797
|
allow(homesick).to receive('inside').once.with(kind_of(Pathname)).and_yield
|
|
771
798
|
allow(homesick).to receive('say_status').once
|
|
772
|
-
|
|
799
|
+
.with(be_a(String), be_a(String), :green)
|
|
773
800
|
allow(homesick).to receive('system').once.with('ls')
|
|
774
801
|
Capture.stdout { homesick.exec 'castle_repo', 'ls' }
|
|
775
802
|
end
|
|
@@ -777,14 +804,14 @@ describe Homesick::CLI do
|
|
|
777
804
|
it 'executes a single command with arguments inside a given castle' do
|
|
778
805
|
allow(homesick).to receive('inside').once.with(kind_of(Pathname)).and_yield
|
|
779
806
|
allow(homesick).to receive('say_status').once
|
|
780
|
-
|
|
807
|
+
.with(be_a(String), be_a(String), :green)
|
|
781
808
|
allow(homesick).to receive('system').once.with('ls -la')
|
|
782
809
|
Capture.stdout { homesick.exec 'castle_repo', 'ls', '-la' }
|
|
783
810
|
end
|
|
784
811
|
|
|
785
812
|
it 'raises an error when the method is called without a command' do
|
|
786
813
|
allow(homesick).to receive('say_status').once
|
|
787
|
-
|
|
814
|
+
.with(:error, be_a(String), :red)
|
|
788
815
|
allow(homesick).to receive('exit').once.with(1)
|
|
789
816
|
Capture.stdout { homesick.exec 'castle_repo' }
|
|
790
817
|
end
|
|
@@ -792,9 +819,9 @@ describe Homesick::CLI do
|
|
|
792
819
|
context 'pretend' do
|
|
793
820
|
it 'does not execute a command when the pretend option is passed' do
|
|
794
821
|
allow(homesick).to receive('say_status').once
|
|
795
|
-
|
|
822
|
+
.with(be_a(String), match(/.*Would execute.*/), :green)
|
|
796
823
|
expect(homesick).to receive('system').never
|
|
797
|
-
Capture.stdout { homesick.invoke 'exec', %w
|
|
824
|
+
Capture.stdout { homesick.invoke 'exec', %w[castle_repo ls -la], pretend: true }
|
|
798
825
|
end
|
|
799
826
|
end
|
|
800
827
|
|
|
@@ -802,8 +829,8 @@ describe Homesick::CLI do
|
|
|
802
829
|
it 'does not print status information when quiet is passed' do
|
|
803
830
|
expect(homesick).to receive('say_status').never
|
|
804
831
|
allow(homesick).to receive('system').once
|
|
805
|
-
|
|
806
|
-
Capture.stdout { homesick.invoke 'exec', %w
|
|
832
|
+
.with('ls -la')
|
|
833
|
+
Capture.stdout { homesick.invoke 'exec', %w[castle_repo ls -la], quiet: true }
|
|
807
834
|
end
|
|
808
835
|
end
|
|
809
836
|
end
|
|
@@ -817,7 +844,7 @@ describe Homesick::CLI do
|
|
|
817
844
|
it 'executes a command without arguments inside the root of each cloned castle' do
|
|
818
845
|
allow(homesick).to receive('inside_each_castle').exactly(:twice).and_yield('castle_repo').and_yield('another_castle_repo')
|
|
819
846
|
allow(homesick).to receive('say_status').at_least(:once)
|
|
820
|
-
|
|
847
|
+
.with(be_a(String), be_a(String), :green)
|
|
821
848
|
allow(homesick).to receive('system').at_least(:once).with('ls')
|
|
822
849
|
Capture.stdout { homesick.exec_all 'ls' }
|
|
823
850
|
end
|
|
@@ -825,14 +852,14 @@ describe Homesick::CLI do
|
|
|
825
852
|
it 'executes a command with arguments inside the root of each cloned castle' do
|
|
826
853
|
allow(homesick).to receive('inside_each_castle').exactly(:twice).and_yield('castle_repo').and_yield('another_castle_repo')
|
|
827
854
|
allow(homesick).to receive('say_status').at_least(:once)
|
|
828
|
-
|
|
855
|
+
.with(be_a(String), be_a(String), :green)
|
|
829
856
|
allow(homesick).to receive('system').at_least(:once).with('ls -la')
|
|
830
857
|
Capture.stdout { homesick.exec_all 'ls', '-la' }
|
|
831
858
|
end
|
|
832
859
|
|
|
833
860
|
it 'raises an error when the method is called without a command' do
|
|
834
861
|
allow(homesick).to receive('say_status').once
|
|
835
|
-
|
|
862
|
+
.with(:error, be_a(String), :red)
|
|
836
863
|
allow(homesick).to receive('exit').once.with(1)
|
|
837
864
|
Capture.stdout { homesick.exec_all }
|
|
838
865
|
end
|
|
@@ -840,9 +867,9 @@ describe Homesick::CLI do
|
|
|
840
867
|
context 'pretend' do
|
|
841
868
|
it 'does not execute a command when the pretend option is passed' do
|
|
842
869
|
allow(homesick).to receive('say_status').at_least(:once)
|
|
843
|
-
|
|
870
|
+
.with(be_a(String), match(/.*Would execute.*/), :green)
|
|
844
871
|
expect(homesick).to receive('system').never
|
|
845
|
-
Capture.stdout { homesick.invoke 'exec_all', %w
|
|
872
|
+
Capture.stdout { homesick.invoke 'exec_all', %w[ls -la], pretend: true }
|
|
846
873
|
end
|
|
847
874
|
end
|
|
848
875
|
|
|
@@ -850,8 +877,8 @@ describe Homesick::CLI do
|
|
|
850
877
|
it 'does not print status information when quiet is passed' do
|
|
851
878
|
expect(homesick).to receive('say_status').never
|
|
852
879
|
allow(homesick).to receive('system').at_least(:once)
|
|
853
|
-
|
|
854
|
-
Capture.stdout { homesick.invoke 'exec_all', %w
|
|
880
|
+
.with('ls -la')
|
|
881
|
+
Capture.stdout { homesick.invoke 'exec_all', %w[ls -la], quiet: true }
|
|
855
882
|
end
|
|
856
883
|
end
|
|
857
884
|
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
require 'tmpdir'
|
|
5
|
+
require 'pathname'
|
|
6
|
+
|
|
7
|
+
describe Homesick::RC::Context do
|
|
8
|
+
let(:tmpdir) { Pathname.new(Dir.mktmpdir) }
|
|
9
|
+
let(:home) { tmpdir } # satisfies spec_helper's global ENV['HOME'] hook
|
|
10
|
+
after { tmpdir.rmtree }
|
|
11
|
+
|
|
12
|
+
# RC::Context does not emit say_status; suppress the global silence! hook.
|
|
13
|
+
def silence!; end
|
|
14
|
+
|
|
15
|
+
subject(:context) { described_class.new(tmpdir) }
|
|
16
|
+
|
|
17
|
+
describe '#castle_path' do
|
|
18
|
+
it 'returns a Pathname equal to the given castle path' do
|
|
19
|
+
expect(context.castle_path).to eq(tmpdir)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it 'returns a Pathname instance' do
|
|
23
|
+
expect(context.castle_path).to be_a(Pathname)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it 'accepts a string path and coerces it to Pathname' do
|
|
27
|
+
ctx = described_class.new(tmpdir.to_s)
|
|
28
|
+
expect(ctx.castle_path).to eq(tmpdir)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
describe '#run' do
|
|
33
|
+
it 'executes the given shell command and returns true on success' do
|
|
34
|
+
marker = tmpdir.join('ran')
|
|
35
|
+
result = context.run("touch #{marker}")
|
|
36
|
+
expect(result).to be(true)
|
|
37
|
+
expect(marker).to exist
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it 'returns false when the command fails' do
|
|
41
|
+
result = context.run('false')
|
|
42
|
+
expect(result).to be(false)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
describe 'instance_eval isolation' do
|
|
47
|
+
it 'does not expose Homesick::CLI methods to the script' do
|
|
48
|
+
# check_castle_existance is a CLI method; the context should not have it
|
|
49
|
+
expect { context.instance_eval('check_castle_existance("x", "y")', __FILE__, __LINE__) }
|
|
50
|
+
.to raise_error(NoMethodError)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it 'allows standard Ruby file operations' do
|
|
54
|
+
marker = tmpdir.join('from_script')
|
|
55
|
+
context.instance_eval { File.write(marker.to_s, 'ok') }
|
|
56
|
+
expect(marker.read).to eq('ok')
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
data/spec/spec_helper.rb
CHANGED