diff options
| author | Paul Buetow <paul@buetow.org> | 2026-03-01 23:09:35 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-03-01 23:09:35 +0200 |
| commit | 98936da70412f87051e6237b882ce8f78d2431a2 (patch) | |
| tree | 41270dd75d0c65a12aab1552fe0611f1e7998746 /lib/dslkeywords/file.rb | |
| parent | 35f2d625367e6e476bcac7bf5b25a5cb4579fe92 (diff) | |
refactor: split file.rb into per-class files under lib/dslkeywords/
file.rb was a ~400-line monolith holding seven unrelated classes/modules.
Extract each into its own file so each file has a single responsibility
and stays within the 50-line guideline:
file_backup.rb — FileBackup mixin
symlink.rb — Symlink class + DSL#symlink
touch.rb — Touch class + DSL#touch
directory.rb — Directory class + DSL#directory
file.rb keeps BasicFile, BaseFile, File, and DSL#file.
dsl.rb gains explicit require_relative lines for the new files.
No logic was changed; all 29 tests continue to pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'lib/dslkeywords/file.rb')
| -rw-r--r-- | lib/dslkeywords/file.rb | 207 |
1 files changed, 10 insertions, 197 deletions
diff --git a/lib/dslkeywords/file.rb b/lib/dslkeywords/file.rb index bf31e6a..273968c 100644 --- a/lib/dslkeywords/file.rb +++ b/lib/dslkeywords/file.rb @@ -4,53 +4,13 @@ require 'fileutils' require_relative 'resource' require_relative '../chained' +require_relative 'file_backup' module RCM - # Backup the file on change - module FileBackup - # TODO: Make protected? - def backup!(file_path, checksum = nil) - return if @without_backup - - suffix = if ::File.file?(file_path) - checksum.nil? ? Digest::SHA256.file(file_path).hexdigest : checksum - else - Time.now.strftime('%s-%L') - end - make_backup!(file_path, suffix) - end - - 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, checksum_b] - end - - private - - def make_backup!(file_path, suffix) - backup_dir = create_backup_directory!(file_path) - backup_path = "#{backup_dir}/#{::File.basename(file_path)}.#{suffix}" - return if ::File.exist?(backup_path) - - do? "Backing up #{file_path} -> #{backup_path}" do - ::File.rename(file_path, backup_path) - end - end - - def create_backup_directory!(file_path) - backup_dir = "#{::File.dirname(file_path)}/.rcmbackup" - return backup_dir if ::File.directory?(backup_dir) - - do? "Creating backup directory #{backup_dir}" do - Dir.mkdir(backup_dir) - end - - backup_dir - end - end - - # Base for BaseFile and Directory + # Base class shared by all file-system resources (files, symlinks, + # touch, directories). Manages path, state (:present/:absent/:purged), + # permissions (mode/owner/group), parent-directory lifecycle, and the + # FileBackup mixin. class BasicFile < Resource include Chained include FileBackup @@ -95,7 +55,7 @@ module RCM set_owner!(stat) end - # Validate whether we can use this up in this context or not + # Reject DSL options that are not valid for this resource type. def validate(method, what, *valids) return what if valids.include?(what) @@ -149,7 +109,8 @@ module RCM end end - # Base for File and Symlink + # Intermediate base for resources that have file content and support + # :sourcefile / :template sourcing, and absent-state deletion. class BaseFile < BasicFile class UnsupportedOperation < StandardError; end @@ -168,7 +129,8 @@ module RCM end end - # Managing files + # Manages regular files: write content, ensure/remove individual lines, + # delete. Writes via a temp file so the final rename is atomic. class File < BaseFile def line(line) = @ensure_line = line @@ -237,125 +199,6 @@ module RCM end end - # Manage symlinks - class Symlink < BaseFile - def evaluate! - return unless super - return evaluate_absent! if %i[absent purged].include?(@is) - return if ::File.symlink?(@file_path) && ::File.readlink(@file_path) == content - - create_parent_directory! if @manage_directory - do? "Creating symlink #{@file_path}" do - FileUtils.ln_sf(content, @file_path) - end - ensure - permissions! - end - end - - # Emtpy file - class Touch < BaseFile - def is(what) = @is = validate(__method__, what.to_sym, :present, :absent, :purged, :updated) - - def evaluate! - return unless super - return evaluate_absent! if %i[absent purged].include?(@is) - return if ::File.file?(@file_path) && @is != :updated - - create_parent_directory! if @manage_directory - do? "Touching #{@file_path}" do - FileUtils.touch(@file_path) - end - ensure - permissions! - end - end - - class Directory < BaseFile - def recursively = @recursively = true - - def evaluate! - return unless super - - case @is - when :present - evaluate_present! - when :absent, :purged - evaluate_absent! - end - ensure - permissions! - end - - private - - def evaluate_present! - if ::File.directory?(@file_path) - return @recursively ? evaluate_present_recursively! : nil - end - - create_parent_directory! if @manage_directory - - do? "Creating directory #{@file_path}" do - Dir.mkdir(@file_path) - end - end - - def evaluate_absent! - return unless ::File.directory?(@file_path) - - backup!(@file_path) - @recursively = true if @is == :purged - what = @is == :purged ? 'Purging' : 'Deleting' - - do? "#{what} directory #{@file_path}" do - if ::File.directory?(@file_path) - @recursively ? FileUtils.rm_r(@file_path) : Dir.delete(@file_path) - end - end - cleanup_parent_directory! if @manage_directory - end - - def evaluate_present_recursively! - source_path = content - raise "Source #{source_path} is not a directory!" unless ::File.directory?(source_path) - - if ::File.exist?(@file_path) - raise "Destination #{@file_path} is not a directory!" unless ::File.directory?(@file_path) - - backup_recursively!(source_path, @file_path) unless @without_backup - end - - do? "Copying #{source_path} -> #{@file_path} recursively" do - if ::File.directory?(@file_path) - Dir["#{source_path}/*"].each { FileUtils.cp_r(_1, @file_path) } - else - FileUtils.cp_r(source_path, @file_path) - end - end - end - - # TODO: Unit test this - def backup_recursively!(source, dest) - Dir.foreach(source) do |entry| - next if ['.', '..'].include?(entry) - - source_path = ::File.join(source, entry) - dest_path = ::File.join(dest, entry) - - if ::File.directory?(source_path) && !::File.directory?(dest_path) - raise "Unable to copy directory #{source_path} into non-directory #{dest_path}" - elsif !::File.directory?(source_path) && ::File.directory?(dest_path) - raise "Unable to copy non-directory #{source_path} into directory #{dest_path}" - elsif ::File.directory?(source_path) && ::File.directory?(dest_path) - backup_recursively!(source_path, dest_path) - else - backup!(dest_path) - end - end - end - end - class DSL # Add file keyword to the DSL def file(file_path = nil, &block) @@ -367,35 +210,5 @@ module RCM self << f f end - - def symlink(file_path = nil, &block) - return :symlink if file_path.nil? - return unless @conds_met - - s = Symlink.new(file_path) - s.content(s.instance_eval(&block)) - self << s - s - end - - def touch(file_path = nil, &block) - return :touch if file_path.nil? - return unless @conds_met - - t = Touch.new(file_path) - t.instance_eval(&block) if block - self << t - t - end - - def directory(file_path = nil, &block) - return :directory if file_path.nil? - return unless @conds_met - - d = Directory.new(file_path) - d.content(d.instance_eval(&block)) - self << d - d - end end end |
