summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-03-01 18:38:49 +0200
committerPaul Buetow <paul@buetow.org>2026-03-01 18:38:49 +0200
commit08129b43792eeec92eacb9f47ce3f879489aa03c (patch)
treef527ba7c71c58536fa68b0acaef41dcd56a3cfc6
parent861e526d9ad9228f2b740dbcf00cf39d8fe652f2 (diff)
make rcm usable as a gem from any directory, fix bugs and tests
- Add lib/rcm.rb entry point and bin/rcm CLI executable - Update gemspec: v0.1.0, proper files list, executables, runtime deps - Support standalone arg parsing and --hosts filtering in options - Fix inverted logic and typo in FileBackup#different? (== vs !=, cecksum_b) - Fix unqualified File.directory? resolving to RCM::File in Directory - Fix test_chown assertions running before evaluate! creates files - Add setup to file tests to prevent order-dependent failures Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
-rwxr-xr-xbin/rcm18
-rw-r--r--lib/dslkeywords/file.rb4
-rw-r--r--lib/dslkeywords/given.rb4
-rw-r--r--lib/options.rb33
-rw-r--r--lib/rcm.rb1
-rw-r--r--rcm.gemspec8
-rw-r--r--test/lib/dslkeywords/file_test.rb6
-rw-r--r--test/lib/dslkeywords/mode_test.rb24
8 files changed, 74 insertions, 24 deletions
diff --git a/bin/rcm b/bin/rcm
new file mode 100755
index 0000000..2a7b6cd
--- /dev/null
+++ b/bin/rcm
@@ -0,0 +1,18 @@
+#!/usr/bin/env ruby
+# CLI entry point for RCM configuration management.
+# Usage: rcm <config.rb> [--dry] [--debug] [--hosts host1,host2]
+
+if ARGV.empty? || ARGV.first.start_with?('-')
+ warn "Usage: rcm <config.rb> [--dry] [--debug] [--hosts host1,host2]"
+ exit 1
+end
+
+config_file = ARGV.shift
+
+unless File.exist?(config_file)
+ warn "Error: file not found: #{config_file}"
+ exit 1
+end
+
+require 'rcm'
+load config_file
diff --git a/lib/dslkeywords/file.rb b/lib/dslkeywords/file.rb
index aa1b725..9a44f72 100644
--- a/lib/dslkeywords/file.rb
+++ b/lib/dslkeywords/file.rb
@@ -23,7 +23,7 @@ module RCM
def different?(file_a, file_b)
checksum_a = Digest::SHA256.file(file_a).hexdigest
checksum_b = Digest::SHA256.file(file_b).hexdigest
- [checksum_a == checksum_b, checksum_a, cecksum_b]
+ [checksum_a != checksum_b, checksum_a, checksum_b]
end
private
@@ -320,7 +320,7 @@ module RCM
end
do? "Copying #{source_path} -> #{@file_path} resursively" do
- if File.directory?(@file_path)
+ if ::File.directory?(@file_path)
Dir["#{source_path}/*"].each { FileUtils.cp_r(_1, @file_path) }
else
FileUtils.cp_r(source_path, @file_path)
diff --git a/lib/dslkeywords/given.rb b/lib/dslkeywords/given.rb
index fa14e68..ba61703 100644
--- a/lib/dslkeywords/given.rb
+++ b/lib/dslkeywords/given.rb
@@ -17,6 +17,10 @@ module RCM
def met?
return false if @conds.key?(:hostname) && Socket.gethostname != @conds[:hostname].to_s
+ # When --hosts is specified, only run on the listed hosts
+ hosts = option(:hosts)
+ return false if hosts.any? && !hosts.include?(Socket.gethostname)
+
true
end
end
diff --git a/lib/options.rb b/lib/options.rb
index 401d135..6ce2479 100644
--- a/lib/options.rb
+++ b/lib/options.rb
@@ -1,16 +1,33 @@
require 'optparse'
module RCM
- # Command line options
+ # Command line options, supports both Rake mode (args after --)
+ # and standalone mode (direct args). Unknown options are ignored
+ # so that test runners and other tools can pass their own flags.
module Options
- @@options = { debug: false, dry: false }
+ @@options = { debug: false, dry: false, hosts: [] }
- if (after_double_dash = ARGV.slice_before('--').to_a.last&.drop(1))
- OptionParser.new do |opts|
- opts.banner = 'Usage: rake [task] -- [options]'
- opts.on('-v', '--[no-]debug', 'debug output') { |v| @@options[:debug] = v }
- opts.on('-d', '--dry', 'dry mode') { |v| @@options[:dry] = v }
- end.parse!(after_double_dash)
+ parser = OptionParser.new do |opts|
+ opts.banner = 'Usage: rake [task] -- [options] OR ruby config.rb [options]'
+ opts.on('-v', '--[no-]debug', 'debug output') { |v| @@options[:debug] = v }
+ opts.on('-d', '--dry', 'dry mode') { |v| @@options[:dry] = v }
+ opts.on('--hosts HOSTS', 'comma-separated list of target hostnames') do |v|
+ @@options[:hosts] = v.split(',').map(&:strip)
+ end
+ end
+
+ # Rake passes args after '--'; standalone scripts pass args directly.
+ args = if ARGV.include?('--')
+ ARGV.slice_before('--').to_a.last.drop(1)
+ else
+ ARGV.dup
+ end
+
+ # Ignore unknown options (e.g. from test runners or other tools)
+ begin
+ parser.parse!(args)
+ rescue OptionParser::InvalidOption
+ retry
end
def option(key)
diff --git a/lib/rcm.rb b/lib/rcm.rb
new file mode 100644
index 0000000..641b02d
--- /dev/null
+++ b/lib/rcm.rb
@@ -0,0 +1 @@
+require_relative 'dsl'
diff --git a/rcm.gemspec b/rcm.gemspec
index 0c771df..a24b5dd 100644
--- a/rcm.gemspec
+++ b/rcm.gemspec
@@ -1,13 +1,17 @@
Gem::Specification.new do |s|
s.required_ruby_version = '>= 3.3.0'
s.name = 'rcm'
- s.version = '0.0.0'
+ s.version = '0.1.0'
s.licenses = ['BSD3']
s.summary = "Ruby Configuration Management system"
s.description = "To configure my stuff"
s.authors = ["Paul Buetow"]
s.email = 'rcm@dev.buetow.org'
- s.files = ["lib/dsl.rb"]
+ s.files = Dir['lib/**/*.rb']
+ s.executables = ['rcm']
s.homepage = 'https://codeberg.org/snonux/rcm'
s.metadata = { "source_code_uri" => "https://codeberg.org/snonux/rcm" }
+
+ s.add_runtime_dependency 'toml', '~> 0.3'
+ s.add_runtime_dependency 'erb'
end
diff --git a/test/lib/dslkeywords/file_test.rb b/test/lib/dslkeywords/file_test.rb
index 35a0f21..a290627 100644
--- a/test/lib/dslkeywords/file_test.rb
+++ b/test/lib/dslkeywords/file_test.rb
@@ -12,6 +12,12 @@ class RCMFileTest < Minitest::Test
FileUtils.rm_r(DIR_PATH) if File.directory?(DIR_PATH)
end
+ # Clean up shared temp file between tests to prevent order-dependent failures
+ def setup
+ File.unlink(FILE_PATH) if File.file?(FILE_PATH)
+ FileUtils.rm_r(DIR_PATH) if File.directory?(DIR_PATH)
+ end
+
def test_create_file_from_string
text = 'Hello World!'
configure_from_scratch do
diff --git a/test/lib/dslkeywords/mode_test.rb b/test/lib/dslkeywords/mode_test.rb
index a4bf099..6e93bde 100644
--- a/test/lib/dslkeywords/mode_test.rb
+++ b/test/lib/dslkeywords/mode_test.rb
@@ -48,12 +48,12 @@ class RCMModeTest < Minitest::Test
end
def test_chown
- configure_from_scratch do
- # Well, test only makes sense that it doesn't throw any exception, as test
- # can't change files to other owners as test will likely run as non-root.
- user_name = Etc.getlogin
- group_name = Etc.getgrgid(Process.gid).name
+ # Test only verifies no exception is thrown, as test runs as non-root
+ # and can't change files to other owners.
+ user_name = Etc.getlogin
+ group_name = Etc.getgrgid(Process.gid).name
+ configure_from_scratch do
touch FILE1_PATH do
owner user_name
group group_name
@@ -62,14 +62,14 @@ class RCMModeTest < Minitest::Test
owner user_name
group group_name
end
+ end
- stat = File.stat(FILE1_PATH)
- assert_equal user_name, Etc.getpwuid(stat.uid)
- assert_equal group_name, Etc.getgrgid(stat.gid)
+ stat = File.stat(FILE1_PATH)
+ assert_equal user_name, Etc.getpwuid(stat.uid).name
+ assert_equal group_name, Etc.getgrgid(stat.gid).name
- stat = File.stat(DIR_PATH)
- assert_equal user_name, Etc.getpwuid(stat.uid)
- assert_equal group_name, Etc.getgrgid(stat.gid)
- end
+ stat = File.stat(DIR_PATH)
+ assert_equal user_name, Etc.getpwuid(stat.uid).name
+ assert_equal group_name, Etc.getgrgid(stat.gid).name
end
end