summaryrefslogtreecommitdiff
path: root/core.rb
blob: b62e1767a2701af0b46192cbdae6ad818288d4a5 (plain)
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
class Op
  def check
    raise "#{self.class} doesn't have a (required) 'check' method"
  end
  def do
    raise "#{self.class} doesn't have a (required) 'do' method"
  end
  def undo
    raise "#{self.class} doesn't have a (required) 'undo' method"
  end
end

def defop(name, args, &block)
  name = name.to_sym
  eval "class #{name} < Op; end"  # Must use `eval' to access
  cls = eval "#{name}"            # the global namespace
  cls.instance_eval do
    args.map {|a| attr_reader a.to_sym }
  end
  cls.define_method :initialize do |**kwargs|
    args.each {|arg| instance_variable_set :"@#{arg}", kwargs[arg.to_sym] }
  end
  cls.define_method :to_s do inspect end
  cls.define_method :to_str do to_s end
  cls.instance_eval &block
end

defop :Write, [:file, :contents] do
  define_method :check do
    if File.exist? @file
      if (File.read @file) != @contents
        raise "File `#{@file}' exists but has unexpected contents"
      else true
      end
    end
  end
  define_method :do do
    write @file, @contents
  end
  define_method :undo do
    rm @file
  end
end

defop :Deploy, [:ops, :save] do
  define_method :initialize do |save: nil, &block|
    @ops = []
    @save = save
    self.instance_eval &block
  end
  define_method :check do
    @ops.all? {|op| op.check } and
      (@save ? self.save.check : true)
  end
  define_method :do do
    self.load.undo if @save and File.exist? @save
    @ops.each {|op| op.check || op.do }
    self.save.do if @save
  end
  define_method :undo do
    @ops.reverse.each {|op| op.check && op.undo }
    self.save.undo if @save
  end
  define_method :save do
    (Write.new file: @save,
               contents: self.to_yaml)
  end
  define_method :load do
    YAML.load_file(
      @save,
      permitted_classes: ([Op] + Op.subclasses))
  end
end

def deploy(...)
  Deploy.new(...)
end

def defdeploy(name, &block)
  Deploy.define_method name, &block
end

defdeploy :op do |cls, **args|
  @ops.push cls.new(**args)
end