homesick 1.1.5 → 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.
data/homesick.gemspec CHANGED
@@ -1,105 +1,34 @@
1
- # Generated by jeweler
2
- # DO NOT EDIT THIS FILE DIRECTLY
3
- # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
- # -*- encoding: utf-8 -*-
5
- # stub: homesick 1.1.5 ruby lib
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/homesick/version'
6
4
 
7
5
  Gem::Specification.new do |s|
8
- s.name = "homesick".freeze
9
- s.version = "1.1.5"
6
+ s.name = 'homesick'
7
+ s.version = Homesick::Version::STRING
8
+ s.authors = ['Joshua Nichols', 'Yusuke Murata']
9
+ s.email = ['josh@technicalpickles.com', 'info@muratayusuke.com']
10
+ s.homepage = 'http://github.com/technicalpickles/homesick'
11
+ s.summary = "Your home directory is your castle. Don't leave your dotfiles behind."
12
+ s.description = 'Homesick is sorta like rip, but for dotfiles. It uses git to clone a ' \
13
+ 'repository containing dotfiles, and saves them in ~/.homesick. It then ' \
14
+ 'allows you to symlink all the dotfiles into place with a single command.'
15
+ s.license = 'MIT'
16
+ s.metadata = { 'rubygems_mfa_required' => 'true' }
10
17
 
11
- s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
12
- s.require_paths = ["lib".freeze]
13
- s.authors = ["Joshua Nichols".freeze, "Yusuke Murata".freeze]
14
- s.date = "2017-03-23"
15
- s.description = "\n Your home directory is your castle. Don't leave your dotfiles behind.\n \n\n Homesick is sorta like rip, but for dotfiles. It uses git to clone a repository containing dotfiles, and saves them in ~/.homesick. It then allows you to symlink all the dotfiles into place with a single command. \n\n ".freeze
16
- s.email = ["josh@technicalpickles.com".freeze, "info@muratayusuke.com".freeze]
17
- s.executables = ["homesick".freeze]
18
- s.extra_rdoc_files = [
19
- "ChangeLog.markdown",
20
- "LICENSE",
21
- "README.markdown"
22
- ]
23
- s.files = [
24
- ".document",
25
- ".rspec",
26
- ".rubocop.yml",
27
- ".travis.yml",
28
- "ChangeLog.markdown",
29
- "Gemfile",
30
- "Guardfile",
31
- "LICENSE",
32
- "README.markdown",
33
- "Rakefile",
34
- "bin/homesick",
35
- "homesick.gemspec",
36
- "lib/homesick.rb",
37
- "lib/homesick/actions/file_actions.rb",
38
- "lib/homesick/actions/git_actions.rb",
39
- "lib/homesick/cli.rb",
40
- "lib/homesick/utils.rb",
41
- "lib/homesick/version.rb",
42
- "spec/homesick_cli_spec.rb",
43
- "spec/spec.opts",
44
- "spec/spec_helper.rb"
45
- ]
46
- s.homepage = "http://github.com/technicalpickles/homesick".freeze
47
- s.licenses = ["MIT".freeze]
48
- s.rubygems_version = "2.6.11".freeze
49
- s.summary = "Your home directory is your castle. Don't leave your dotfiles behind.".freeze
18
+ s.required_ruby_version = '>= 3.2'
50
19
 
51
- if s.respond_to? :specification_version then
52
- s.specification_version = 4
20
+ s.files = `git ls-files`.split("\n")
21
+ s.executables = ['homesick']
22
+ s.require_paths = ['lib']
53
23
 
54
- if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
55
- s.add_runtime_dependency(%q<thor>.freeze, [">= 0.14.0"])
56
- s.add_development_dependency(%q<capture-output>.freeze, ["~> 1.0.0"])
57
- s.add_development_dependency(%q<coveralls>.freeze, [">= 0"])
58
- s.add_development_dependency(%q<guard>.freeze, [">= 0"])
59
- s.add_development_dependency(%q<guard-rspec>.freeze, [">= 0"])
60
- s.add_development_dependency(%q<jeweler>.freeze, [">= 1.6.2"])
61
- s.add_development_dependency(%q<rake>.freeze, [">= 0.8.7"])
62
- s.add_development_dependency(%q<rb-readline>.freeze, ["~> 0.5.0"])
63
- s.add_development_dependency(%q<rspec>.freeze, ["~> 3.5.0"])
64
- s.add_development_dependency(%q<rubocop>.freeze, [">= 0"])
65
- s.add_development_dependency(%q<test_construct>.freeze, [">= 0"])
66
- s.add_development_dependency(%q<libnotify>.freeze, [">= 0"])
67
- s.add_development_dependency(%q<terminal-notifier-guard>.freeze, ["~> 1.7.0"])
68
- s.add_development_dependency(%q<listen>.freeze, ["< 3"])
69
- s.add_development_dependency(%q<rack>.freeze, ["< 2"])
70
- else
71
- s.add_dependency(%q<thor>.freeze, [">= 0.14.0"])
72
- s.add_dependency(%q<capture-output>.freeze, ["~> 1.0.0"])
73
- s.add_dependency(%q<coveralls>.freeze, [">= 0"])
74
- s.add_dependency(%q<guard>.freeze, [">= 0"])
75
- s.add_dependency(%q<guard-rspec>.freeze, [">= 0"])
76
- s.add_dependency(%q<jeweler>.freeze, [">= 1.6.2"])
77
- s.add_dependency(%q<rake>.freeze, [">= 0.8.7"])
78
- s.add_dependency(%q<rb-readline>.freeze, ["~> 0.5.0"])
79
- s.add_dependency(%q<rspec>.freeze, ["~> 3.5.0"])
80
- s.add_dependency(%q<rubocop>.freeze, [">= 0"])
81
- s.add_dependency(%q<test_construct>.freeze, [">= 0"])
82
- s.add_dependency(%q<libnotify>.freeze, [">= 0"])
83
- s.add_dependency(%q<terminal-notifier-guard>.freeze, ["~> 1.7.0"])
84
- s.add_dependency(%q<listen>.freeze, ["< 3"])
85
- s.add_dependency(%q<rack>.freeze, ["< 2"])
86
- end
87
- else
88
- s.add_dependency(%q<thor>.freeze, [">= 0.14.0"])
89
- s.add_dependency(%q<capture-output>.freeze, ["~> 1.0.0"])
90
- s.add_dependency(%q<coveralls>.freeze, [">= 0"])
91
- s.add_dependency(%q<guard>.freeze, [">= 0"])
92
- s.add_dependency(%q<guard-rspec>.freeze, [">= 0"])
93
- s.add_dependency(%q<jeweler>.freeze, [">= 1.6.2"])
94
- s.add_dependency(%q<rake>.freeze, [">= 0.8.7"])
95
- s.add_dependency(%q<rb-readline>.freeze, ["~> 0.5.0"])
96
- s.add_dependency(%q<rspec>.freeze, ["~> 3.5.0"])
97
- s.add_dependency(%q<rubocop>.freeze, [">= 0"])
98
- s.add_dependency(%q<test_construct>.freeze, [">= 0"])
99
- s.add_dependency(%q<libnotify>.freeze, [">= 0"])
100
- s.add_dependency(%q<terminal-notifier-guard>.freeze, ["~> 1.7.0"])
101
- s.add_dependency(%q<listen>.freeze, ["< 3"])
102
- s.add_dependency(%q<rack>.freeze, ["< 2"])
103
- end
104
- end
24
+ s.add_dependency 'thor', '~> 1.0'
105
25
 
26
+ s.add_development_dependency 'bundler-audit', '~> 0.9'
27
+ s.add_development_dependency 'capture-output', '~> 1.0'
28
+ s.add_development_dependency 'mutant-rspec'
29
+ s.add_development_dependency 'rake'
30
+ s.add_development_dependency 'rspec', '~> 3.0'
31
+ s.add_development_dependency 'rubocop'
32
+ s.add_development_dependency 'rubocop-rake'
33
+ s.add_development_dependency 'test_construct'
34
+ end
@@ -1,18 +1,17 @@
1
- # -*- encoding : utf-8 -*-
1
+ # frozen_string_literal: true
2
+
2
3
  module Homesick
3
4
  module Actions
4
5
  # File-related helper methods for Homesick
5
6
  module FileActions
7
+ protected
8
+
6
9
  def mv(source, destination)
7
10
  source = Pathname.new(source)
8
11
  destination = Pathname.new(destination + source.basename)
9
- case
10
- when destination.exist? && (options[:force] || shell.file_collision(destination) { source })
11
- say_status :conflict, "#{destination} exists", :red
12
- FileUtils.mv source, destination unless options[:pretend]
13
- else
14
- FileUtils.mv source, destination unless options[:pretend]
15
- end
12
+ collision = destination.exist? && (options[:force] || shell.file_collision(destination) { source })
13
+ say_status :conflict, "#{destination} exists", :red if collision
14
+ FileUtils.mv source, destination unless options[:pretend]
16
15
  end
17
16
 
18
17
  def rm_rf(dir)
@@ -24,7 +23,7 @@ module Homesick
24
23
  target = Pathname.new(target)
25
24
 
26
25
  if target.symlink?
27
- say_status :unlink, "#{target.expand_path}", :green
26
+ say_status :unlink, target.expand_path.to_s, :green
28
27
  FileUtils.rm_rf target
29
28
  else
30
29
  say_status :conflict, "#{target} is not a symlink", :red
@@ -42,45 +41,40 @@ module Homesick
42
41
  end
43
42
 
44
43
  def ln_s(source, destination)
45
- source = Pathname.new(source).realpath
44
+ source = Pathname.new(source).realpath
46
45
  destination = Pathname.new(destination)
47
46
  FileUtils.mkdir_p destination.dirname
48
47
 
49
- action = if destination.symlink? && destination.readlink == source
50
- :identical
51
- elsif destination.symlink?
52
- :symlink_conflict
53
- elsif destination.exist?
54
- :conflict
55
- else
56
- :success
57
- end
48
+ action = :success
49
+ action = :identical if destination.symlink? && destination.readlink == source
50
+ action = :symlink_conflict if destination.symlink?
51
+ action = :conflict if destination.exist?
58
52
 
59
53
  handle_symlink_action action, source, destination
60
54
  end
61
55
 
62
56
  def handle_symlink_action(action, source, destination)
63
- case action
64
- when :identical
57
+ if action == :identical
65
58
  say_status :identical, destination.expand_path, :blue
66
- when :symlink_conflict, :conflict
67
- if action == :conflict
68
- say_status :conflict, "#{destination} exists", :red
69
- else
70
- say_status :conflict,
71
- "#{destination} exists and points to #{destination.readlink}",
72
- :red
73
- end
74
- if collision_accepted?(destination, source)
75
- FileUtils.rm_r destination, force: true unless options[:pretend]
76
- FileUtils.ln_s source, destination, force: true unless options[:pretend]
77
- end
59
+ return
60
+ end
61
+ message = generate_symlink_message action, source, destination
62
+ if %i[symlink_conflict conflict].include?(action)
63
+ say_status :conflict, message, :red
64
+ return unless collision_accepted?(destination, source)
65
+
66
+ FileUtils.rm_r destination, force: true unless options[:pretend]
78
67
  else
79
- say_status :symlink,
80
- "#{source.expand_path} to #{destination.expand_path}",
81
- :green
82
- FileUtils.ln_s source, destination unless options[:pretend]
68
+ say_status :symlink, message, :green
83
69
  end
70
+ FileUtils.ln_s source, destination, force: true unless options[:pretend]
71
+ end
72
+
73
+ def generate_symlink_message(action, source, destination)
74
+ message = "#{source.expand_path} to #{destination.expand_path}"
75
+ message = "#{destination} exists and points to #{destination.readlink}" if action == :symlink_conflict
76
+ message = "#{destination} exists" if action == :conflict
77
+ message
84
78
  end
85
79
  end
86
80
  end
@@ -1,4 +1,5 @@
1
- # -*- encoding : utf-8 -*-
1
+ # frozen_string_literal: true
2
+
2
3
  module Homesick
3
4
  module Actions
4
5
  # Git-related helper methods for Homesick
@@ -8,35 +9,37 @@ module Homesick
8
9
  major: 1,
9
10
  minor: 8,
10
11
  patch: 0
11
- }
12
+ }.freeze
12
13
  STRING = MIN_VERSION.values.join('.')
13
14
 
14
15
  def git_version_correct?
15
16
  info = `git --version`.scan(/(\d+)\.(\d+)\.(\d+)/).flatten.map(&:to_i)
16
17
  return false unless info.count == 3
17
- current_version = Hash[[:major, :minor, :patch].zip(info)]
18
- return true if current_version.eql?(MIN_VERSION)
19
- return true if current_version[:major] > MIN_VERSION[:major]
20
- return true if current_version[:major] == MIN_VERSION[:major] && current_version[:minor] > MIN_VERSION[:minor]
21
- return true if current_version[:major] == MIN_VERSION[:major] && current_version[:minor] == MIN_VERSION[:minor] && current_version[:patch] >= MIN_VERSION[:patch]
22
- false
18
+
19
+ current_version = %i[major minor patch].zip(info).to_h
20
+ major_equals = current_version.eql?(MIN_VERSION)
21
+ major_greater = current_version[:major] > MIN_VERSION[:major]
22
+ minor_greater = current_version[:major] == MIN_VERSION[:major] &&
23
+ current_version[:minor] > MIN_VERSION[:minor]
24
+ patch_greater = current_version[:major] == MIN_VERSION[:major] &&
25
+ current_version[:minor] == MIN_VERSION[:minor] &&
26
+ current_version[:patch] >= MIN_VERSION[:patch]
27
+
28
+ major_equals || major_greater || minor_greater || patch_greater
23
29
  end
24
30
 
25
- # TODO: move this to be more like thor's template, empty_directory, etc
26
31
  def git_clone(repo, config = {})
27
- config ||= {}
28
32
  destination = config[:destination] || File.basename(repo, '.git')
29
-
30
33
  destination = Pathname.new(destination) unless destination.is_a?(Pathname)
31
34
  FileUtils.mkdir_p destination.dirname
32
35
 
33
36
  if destination.directory?
34
37
  say_status :exist, destination.expand_path, :blue
35
38
  else
36
- say_status 'git clone',
37
- "#{repo} to #{destination.expand_path}",
38
- :green
39
- system "git clone -q --config push.default=upstream --recursive #{repo} #{destination}"
39
+ say_status 'git clone', "#{repo} to #{destination.expand_path}", :green
40
+ unless options[:pretend]
41
+ system "git clone -q --config push.default=upstream --recursive #{repo} #{destination}"
42
+ end
40
43
  end
41
44
  end
42
45
 
data/lib/homesick/cli.rb CHANGED
@@ -1,4 +1,6 @@
1
- # -*- encoding : utf-8 -*-
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
2
4
  require 'thor'
3
5
 
4
6
  module Homesick
@@ -12,6 +14,10 @@ module Homesick
12
14
 
13
15
  add_runtime_options!
14
16
 
17
+ def self.exit_on_failure?
18
+ true
19
+ end
20
+
15
21
  map '-v' => :version
16
22
  map '--version' => :version
17
23
  # Retain a mapped version of the symlink command for compatibility.
@@ -21,45 +27,20 @@ module Homesick
21
27
  super
22
28
  # Check if git is installed
23
29
  unless git_version_correct?
24
- say_status :error, "Git version >= #{Homesick::Actions::GitActions::STRING} must be installed to use Homesick", :red
30
+ say_status :error,
31
+ "Git version >= #{Homesick::Actions::GitActions::STRING} must be installed to use Homesick",
32
+ :red
25
33
  exit(1)
26
34
  end
27
- # Hack in support for diffing symlinks
28
- # Also adds support for checking if destination or content is a directory
29
- shell_metaclass = class << shell; self; end
30
- shell_metaclass.send(:define_method, :show_diff) do |destination, content|
31
- destination = Pathname.new(destination)
32
- content = Pathname.new(content)
33
- return 'Unable to create diff: destination or content is a directory' if destination.directory? || content.directory?
34
- return super(destination, content) unless destination.symlink?
35
- say "- #{destination.readlink}", :red, true
36
- say "+ #{content.expand_path}", :green, true
37
- end
35
+ configure_symlinks_diff
38
36
  end
39
37
 
40
38
  desc 'clone URI CASTLE_NAME', 'Clone +uri+ as a castle with name CASTLE_NAME for homesick'
41
- def clone(uri, destination=nil)
39
+ def clone(uri, destination = nil)
42
40
  destination = Pathname.new(destination) unless destination.nil?
43
41
 
44
42
  inside repos_dir do
45
- if File.exist?(uri)
46
- uri = Pathname.new(uri).expand_path
47
- fail "Castle already cloned to #{uri}" if uri.to_s.start_with?(repos_dir.to_s)
48
-
49
- destination = uri.basename if destination.nil?
50
-
51
- ln_s uri, destination
52
- elsif uri =~ GITHUB_NAME_REPO_PATTERN
53
- destination = Pathname.new(uri).basename if destination.nil?
54
- git_clone "https://github.com/#{Regexp.last_match[1]}.git",
55
- destination: destination
56
- elsif uri =~ /%r([^%r]*?)(\.git)?\Z/ || uri =~ /[^:]+:([^:]+)(\.git)?\Z/
57
- destination = Pathname.new(Regexp.last_match[1].gsub(/\.git$/, '')).basename if destination.nil?
58
- git_clone uri, destination: destination
59
- else
60
- fail "Unknown URI format: #{uri}"
61
- end
62
-
43
+ destination = clone_from_uri(uri, destination)
63
44
  setup_castle(destination)
64
45
  end
65
46
  end
@@ -74,11 +55,19 @@ module Homesick
74
55
  destination = Pathname.new(name)
75
56
  homesickrc = destination.join('.homesickrc').expand_path
76
57
  return unless homesickrc.exist?
77
- proceed = options[:force] || shell.yes?("#{name} has a .homesickrc. Proceed with evaling it? (This could be destructive)")
78
- return say_status 'eval skip', "not evaling #{homesickrc}, #{destination} may need manual configuration", :blue unless proceed
58
+
59
+ proceed = options[:force] ||
60
+ shell.yes?("#{name} has a .homesickrc. Proceed with evaling it? (This could be destructive)")
61
+ unless proceed
62
+ return say_status 'eval skip',
63
+ "not evaling #{homesickrc}, #{destination} may need manual configuration",
64
+ :blue
65
+ end
66
+
79
67
  say_status 'eval', homesickrc
80
68
  inside destination do
81
- eval homesickrc.read, binding, homesickrc.expand_path.to_s
69
+ ctx = Homesick::RC::Context.new(destination.expand_path)
70
+ ctx.instance_eval(homesickrc.read, homesickrc.expand_path.to_s)
82
71
  end
83
72
  end
84
73
  end
@@ -92,7 +81,7 @@ module Homesick
92
81
  def pull(name = DEFAULT_CASTLE_NAME)
93
82
  if options[:all]
94
83
  inside_each_castle do |castle|
95
- say castle.to_s.gsub(repos_dir.to_s + '/', '') + ':'
84
+ say "#{castle.to_s.gsub("#{repos_dir}/", '')}:"
96
85
  update_castle castle
97
86
  end
98
87
  else
@@ -135,11 +124,12 @@ module Homesick
135
124
  def link(name = DEFAULT_CASTLE_NAME)
136
125
  check_castle_existance(name, 'symlink')
137
126
 
138
- inside castle_dir(name) do
127
+ castle_path = castle_dir(name)
128
+ inside castle_path do
139
129
  subdirs = subdirs(name)
140
130
 
141
131
  # link files
142
- symlink_each(name, castle_dir(name), subdirs)
132
+ symlink_each(name, castle_path, subdirs)
143
133
 
144
134
  # link files in subdirs
145
135
  subdirs.each do |subdir|
@@ -159,22 +149,9 @@ module Homesick
159
149
  castle_path = Pathname.new(castle_dir(castle)).join(relative_dir)
160
150
  FileUtils.mkdir_p castle_path
161
151
 
162
- # Are we already tracking this or anything inside it?
163
152
  target = Pathname.new(castle_path.join(file.basename))
164
153
  if target.exist?
165
- if absolute_path.directory?
166
- move_dir_contents(target, absolute_path)
167
- absolute_path.rmtree
168
- subdir_remove(castle, relative_dir + file.basename)
169
-
170
- elsif more_recent? absolute_path, target
171
- target.delete
172
- mv absolute_path, castle_path
173
- else
174
- say_status(:track,
175
- "#{target} already exists, and is more recent than #{file}. Run 'homesick SYMLINK CASTLE' to create symlinks.",
176
- :blue)
177
- end
154
+ handle_existing_track_target(castle, absolute_path, castle_path, relative_dir, file, target)
178
155
  else
179
156
  mv absolute_path, castle_path
180
157
  end
@@ -189,7 +166,6 @@ module Homesick
189
166
  git_add absolute_path
190
167
  end
191
168
 
192
- # are we tracking something nested? Add the parent dir to the manifest
193
169
  subdir_add(castle, relative_dir) unless relative_dir.eql?(Pathname.new('.'))
194
170
  end
195
171
 
@@ -261,14 +237,14 @@ module Homesick
261
237
  "Opening a new shell in castle '#{castle}'. To return to the original one exit from the new shell.",
262
238
  :green
263
239
  inside castle_dir do
264
- system(ENV['SHELL'])
240
+ system(ENV.fetch('SHELL', nil))
265
241
  end
266
242
  end
267
243
 
268
244
  desc 'open CASTLE',
269
245
  'Open your default editor in the root of the given castle'
270
246
  def open(castle = DEFAULT_CASTLE_NAME)
271
- unless ENV['EDITOR']
247
+ unless ENV.fetch('EDITOR', nil)
272
248
  say_status :error,
273
249
  'The $EDITOR environment variable must be set to use this command',
274
250
  :red
@@ -277,11 +253,11 @@ module Homesick
277
253
  end
278
254
  check_castle_existance castle, 'open'
279
255
  castle_dir = repos_dir.join(castle)
280
- say_status "#{castle_dir.realpath}: #{ENV['EDITOR']} .",
281
- "Opening the root directory of castle '#{castle}' in editor '#{ENV['EDITOR']}'.",
256
+ say_status "#{castle_dir.realpath}: #{ENV.fetch('EDITOR', nil)} .",
257
+ "Opening the root directory of castle '#{castle}' in editor '#{ENV.fetch('EDITOR', nil)}'.",
282
258
  :green
283
259
  inside castle_dir do
284
- system("#{ENV['EDITOR']} .")
260
+ system("#{ENV.fetch('EDITOR', nil)} .")
285
261
  end
286
262
  end
287
263
 
@@ -289,15 +265,16 @@ module Homesick
289
265
  'Execute a single shell command inside the root of a castle'
290
266
  def exec(castle, *args)
291
267
  check_castle_existance castle, 'exec'
292
- unless args.count > 0
268
+ unless args.any?
293
269
  say_status :error,
294
270
  'You must pass a shell command to execute',
295
271
  :red
296
272
  exit(1)
297
273
  end
298
274
  full_command = args.join(' ')
275
+ action = options[:pretend] ? 'Would execute' : 'Executing command'
299
276
  say_status "exec '#{full_command}'",
300
- "#{options[:pretend] ? 'Would execute' : 'Executing command'} '#{full_command}' in castle '#{castle}'",
277
+ "#{action} '#{full_command}' in castle '#{castle}'",
301
278
  :green
302
279
  inside repos_dir.join(castle) do
303
280
  system(full_command)
@@ -307,7 +284,7 @@ module Homesick
307
284
  desc 'exec_all COMMAND',
308
285
  'Execute a single shell command inside the root of every cloned castle'
309
286
  def exec_all(*args)
310
- unless args.count > 0
287
+ unless args.any?
311
288
  say_status :error,
312
289
  'You must pass a shell command to execute',
313
290
  :red
@@ -315,8 +292,9 @@ module Homesick
315
292
  end
316
293
  full_command = args.join(' ')
317
294
  inside_each_castle do |castle|
295
+ action = options[:pretend] ? 'Would execute' : 'Executing command'
318
296
  say_status "exec '#{full_command}'",
319
- "#{options[:pretend] ? 'Would execute' : 'Executing command'} '#{full_command}' in castle '#{castle}'",
297
+ "#{action} '#{full_command}' in castle '#{castle}'",
320
298
  :green
321
299
  system(full_command)
322
300
  end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pathname'
4
+
5
+ module Homesick
6
+ module RC
7
+ # Evaluation context for .homesickrc scripts.
8
+ #
9
+ # Runs castle setup scripts in a clean object rather than in the
10
+ # Homesick::CLI binding, so scripts cannot access CLI internals.
11
+ # Standard Ruby library methods (File, Dir, system, etc.) remain
12
+ # available to scripts.
13
+ class Context
14
+ # @param castle_path [Pathname, String] absolute path to the castle root
15
+ def initialize(castle_path)
16
+ @castle_path = Pathname.new(castle_path)
17
+ end
18
+
19
+ # The absolute path of the castle being configured.
20
+ attr_reader :castle_path
21
+
22
+ # Execute a shell command.
23
+ # @param command [String]
24
+ # @return [Boolean] true if the command succeeded
25
+ def run(command)
26
+ system(command)
27
+ end
28
+ end
29
+ end
30
+ end