1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
|
require 'digest'
require 'erb'
require 'fileutils'
require_relative 'resource'
module RCM
# Backup the file on change
module FileBackup
def backup!(file_path, checksum)
return if @without_backup
backup_dir = "#{::File.dirname(file_path)}/.rcm"
Dir.mkdir(backup_dir) unless ::File.directory?(backup_dir)
backup_path = "#{backup_dir}/#{::File.basename(file_path)}.#{checksum}"
return if ::File.exist?(backup_path)
info("Backing up #{file_path} -> #{backup_path}")
::File.rename(file_path, backup_path)
end
end
# Managing files
class File < Resource
include FileBackup
class UnsupportedOperation < StandardError; end
def initialize(file_path)
super(file_path)
@file_path = file_path
@is = :installed
end
def content(text = nil)
return @content if text.nil?
@content = text.instance_of?(Array) ? text.join("\n") : text
end
def line(line) = @ensure_line = line
def path(file_path = nil) = file_path.nil? ? @file_path : @file_path = file_path
def is(what) = @is = validate_op(__method__, what, present, absent)
def manage(what) = @manage_directory = validate_op(__method__, what, directory) == directory
def without(what) = @without_backup = validate_op(__method__, what, backup) == backup
def from(what) = @from = validate_op(__method__, what, sourcefile, template)
def method_missing(method_name, *args)
if %i[present absent directory backup sourcefile template].include?(method_name)
method_name
else
super
end
end
def respond_to_missing? = true
def evaluate!
return unless super
return evaluate_ensure_line! unless @ensure_line.nil?
return evaluate_absent! if %i[absent].include?(@is)
write!(real_content)
end
private
def validate_op(matter, what, *valids)
what = what.to_sym
raise UnsupportedOperation, "Unsupported '#{matter}' operation #{what}" unless valids.include?(what)
what
end
def evaluate_ensure_line!
return evaluate_ensure_line_absent! if %i[absent].include?(@is)
return write!(@ensure_line) unless ::File.file?(@file_path)
return if ::File.readlines(@file_path, chomp: true).include?(@ensure_line)
::File.open(@file_path, 'a') do |fd|
fd.puts(@ensure_line)
end
end
def evaluate_ensure_line_absent!
return unless ::File.file?(@file_path)
write!(::File.readlines(@file_path, chomp: true).reject do |line|
line == @ensure_line
end.join("\n"))
end
def evaluate_absent!
if ::File.exist?(@file_path)
info("Deleting #{@file_path}")
::File.delete(@file_path)
end
return unless @manage_directory
parent_dir = ::File.dirname(@file_path)
while Dir.empty?(parent_dir)
info("Deleting empty parent directory #{parent_dir}")
Dir.rmdir(parent_dir)
parent_dir = ::File.dirname(parent_dir)
end
end
def write!(text)
info "Managing file #{@file_path}"
create_parent_directory!
debug text if option :debug
tmp_path = "#{@file_path}.rcmtmp"
::File.write(tmp_path, text)
if ::File.file?(@file_path)
checksum = Digest::SHA256.file(@file_path).hexdigest
tmp_checksum = Digest::SHA256.file(tmp_path).hexdigest
if tmp_checksum == checksum
::File.delete(tmp_path) # File has not changed, not doing anything
return
end
backup!(@file_path, checksum) # File changed, backup!
end
::File.rename(tmp_path, @file_path)
end
def create_parent_directory!
dirname = ::File.dirname(@file_path)
return if ::File.directory?(dirname) || !@manage_directory
info "Creating parent directory #{dirname}"
FileUtils.mkdir_p(dirname)
end
def real_content
text = @from == sourcefile ? ::File.read(@content) : @content
@from == template ? ERB.new(text).result : text
end
end
# Add file keyword to the DSL
class DSL
def file(file_path, &block)
return unless @conds_met
f = File.new(file_path)
f.content(f.instance_eval(&block))
self << f
f
end
end
end
|