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.
@@ -1,29 +1,14 @@
1
- # -*- encoding : utf-8 -*-
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(ENV['HOME'] || '~').realpath
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 = IO.readlines(subdir_filepath).delete_if do |line|
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
- fail "Arguments must be instances of Pathname, #{destination.class.name} and #{source.class.name} given" unless destination.instance_of?(Pathname) && source.instance_of?(Pathname)
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 each_file(castle, basedir, subdirs)
157
- absolute_basedir = Pathname.new(basedir).expand_path
158
- inside basedir do
159
- files = Pathname.glob('{.*,*}').reject do |a|
160
- ['.', '..'].include?(a.to_s)
161
- end
162
- files.each do |path|
163
- absolute_path = path.expand_path
164
- castle_home = castle_dir(castle)
165
-
166
- # make ignore dirs
167
- ignore_dirs = []
168
- subdirs.each do |subdir|
169
- # ignore all parent of each line in subdir file
170
- Pathname.new(subdir).ascend do |p|
171
- ignore_dirs.push(p)
172
- end
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
- relative_dir = absolute_basedir.relative_path_from(castle_home)
186
- home_path = home_dir.join(relative_dir).join(path)
164
+ destination
165
+ end
187
166
 
188
- yield(absolute_path, home_path)
189
- end
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
@@ -1,11 +1,12 @@
1
- # -*- encoding : utf-8 -*-
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 = 1
7
- MINOR = 1
8
- PATCH = 6
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
- # -*- encoding : utf-8 -*-
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
@@ -1,4 +1,5 @@
1
- # -*- encoding : utf-8 -*-
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
- .with('https://github.com/wfarr/dotfiles.git',
162
- destination: Pathname.new('other-name'))
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 = {:castle => 'castle has new content', :home => 'home already has content'}
374
+ contents = { castle: 'castle has new content', home: 'home already has content' }
338
375
 
339
376
  dotfile = castle.file('text')
340
- File.open(dotfile.to_s, 'w') do |f|
341
- f.write contents[:castle]
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 = {:castle => (0..255).step(30).map{|e| e.chr}.join(), :home => (0..255).step(30).reverse_each.map{|e| e.chr}.join()}
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.open(dotfile.to_s, 'w') do |f|
355
- f.write contents[:castle]
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\/.some_rc_file}m)
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
- .with(:error,
536
- /Could not pull castle_repo, expected .* to exist and contain dotfiles/,
537
- :red)
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
- .with('git submodule --quiet init')
576
+ .with('git submodule --quiet init')
548
577
  allow(homesick).to receive(:system).exactly(2).times
549
- .with('git submodule --quiet update --init --recursive >/dev/null 2>&1')
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
- .with(:error, /Could not push castle_repo, expected .* to exist and contain dotfiles/, :red)
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['SHELL'])
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
- .with(:error, /Could not cd castle_repo, expected .* to exist and contain dotfiles/, :red)
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(:[]).with('EDITOR').and_return('vim')
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(:[]).with('EDITOR').and_return(nil)
770
+ allow(ENV).to receive(:fetch).with('EDITOR', nil).and_return(nil)
744
771
  expect(homesick).to receive('say_status').once
745
- .with(:error, 'The $EDITOR environment variable must be set to use this command', :red)
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(:[]).with('EDITOR').and_return('vim')
778
+ allow(ENV).to receive(:fetch).with('EDITOR', nil).and_return('vim')
752
779
  allow(homesick).to receive('say_status').once
753
- .with(:error, /Could not open castle_repo, expected .* to exist and contain dotfiles/, :red)
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
- .with(be_a(String), be_a(String), :green)
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
- .with(be_a(String), be_a(String), :green)
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
- .with(:error, be_a(String), :red)
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
- .with(be_a(String), match(/.*Would execute.*/), :green)
822
+ .with(be_a(String), match(/.*Would execute.*/), :green)
796
823
  expect(homesick).to receive('system').never
797
- Capture.stdout { homesick.invoke 'exec', %w(castle_repo ls -la), pretend: true }
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
- .with('ls -la')
806
- Capture.stdout { homesick.invoke 'exec', %w(castle_repo ls -la), quiet: true }
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
- .with(be_a(String), be_a(String), :green)
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
- .with(be_a(String), be_a(String), :green)
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
- .with(:error, be_a(String), :red)
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
- .with(be_a(String), match(/.*Would execute.*/), :green)
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(ls -la), pretend: true }
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
- .with('ls -la')
854
- Capture.stdout { homesick.invoke 'exec_all', %w(ls -la), quiet: true }
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
@@ -1,5 +1,4 @@
1
- require 'coveralls'
2
- Coveralls.wear!
1
+ # frozen_string_literal: true
3
2
 
4
3
  $LOAD_PATH.unshift(File.dirname(__FILE__))
5
4
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))