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
|
# frozen_string_literal: true
# rubocop:disable Style/ClassVars
require_relative 'config'
require_relative 'options'
require_relative 'log'
require_relative 'chained'
require_relative 'dslkeywords/agent'
require_relative 'dslkeywords/prompt'
require_relative 'dslkeywords/file'
require_relative 'dslkeywords/symlink'
require_relative 'dslkeywords/touch'
require_relative 'dslkeywords/directory'
require_relative 'dslkeywords/given'
require_relative 'dslkeywords/notify'
# Ruby Configiration Management system
module RCM
# Here all starts
class DSL
attr_reader :id, :conds_met
def self.reset!
@@rcm_counter = -1
@@objs = {}
end
def self.object(id) = @@objs[id]
reset!
include Config
include Options
include Log
include Chained
class DuplicateResource < StandardError; end
class DuplicateDefinition < StandardError; end
class NoSuchAgentDefinition < StandardError; end
class NoSuchPromptDefinition < StandardError; end
def initialize(reset)
DSL.reset! if reset
@id = "dsl(#{@@rcm_counter += 1})"
@conds_met = true
@scheduled = []
yield self if block_given?
end
def to_s = @id
def evaluate! = @scheduled.each(&:evaluate!)
def <<(obj) = register(obj)
def register(obj, schedule: obj.is_a?(Resource), duplicate_error: DuplicateResource)
raise duplicate_error, "#{obj.id} already declared!" if @@objs.key?(obj.id)
@@objs[obj.id] = obj
@scheduled << obj if schedule
obj
end
def object!(klass, name, error_class:, kind:)
@@objs.fetch(klass.id_for(name)) do
raise error_class, "No such #{kind} '#{name}'"
end
end
private
# Shared helper for all file-system keyword registrations.
# Returns the keyword symbol when called without a path (used by the
# Chained DSL to identify resource types without creating an object).
# Otherwise guards on @conds_met, instantiates klass, lets the caller
# configure the object, registers it, and returns it.
#
# The block is always yielded — callers that accept an optional DSL
# block must guard for nil themselves inside the closure, e.g.
# register_keyword(Touch, :touch, path) { |t| t.instance_eval(&block) if block }
def register_keyword(klass, name, path)
return name if path.nil?
return unless @conds_met
obj = klass.new(path)
obj.dsl = self if obj.respond_to?(:dsl=)
yield obj
register(obj)
end
end
end
# rubocop:enable Style/ClassVars
def configure(reset: false, &block)
# Parse ARGV and load config.toml each time configure is called so that
# scripts and test suites that call configure multiple times always
# start from a consistent, freshly-loaded state.
RCM::Options.parse!
RCM::Config.load!
RCM::DSL.new(reset) do |rcm|
rcm.info('Configuring...')
rcm.instance_eval(&block)
rcm.evaluate! if rcm.conds_met
end
end
def configure_from_scratch(&block) = configure(reset: true, &block)
|