aboutsummaryrefslogtreecommitdiff
path: root/lib/leap_cli/config/base.rb
blob: c7f4bc9cd45d73e04f2752f04a52addd448e82c8 (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
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
module LeapCli
  module Config
    #
    # This class represents the configuration for a single node, service, or tag.
    #
    class Base < Hash

      def initialize(manager=nil, node=nil)
        @manager = manager
        @node = node || self
      end

      ##
      ## FETCHING VALUES
      ##

      #
      # lazily eval dynamic values when we encounter them.
      #
      def [](key)
        value = fetch(key, nil)
        if value.is_a? Array
          value
        elsif value.nil?
          nil
        else
          if value =~ /^= (.*)$/
            begin
              value = eval($1)
              self[key] = value
            rescue Exception => exc
              puts "Eval error in '#{name}'"
              puts "   string: #{$1}"
              puts "   error: #{exc}"
            end
          end
          value
        end
      end

      def name
        @node['name']
      end

      #
      # make hash addressable like an object (e.g. obj['name'] available as obj.name)
      #
      def method_missing(method, *args, &block)
        method = method.to_s
        if self.has_key?(method)
          self[method]
        elsif @node != self
          @node.send(method) # send call up the tree...
        else
          raise NoMethodError.new(method)
        end
      end

      #
      # a deep (recursive) merge with another hash or node.
      #
      def deep_merge!(hsh)
        hsh.each do |key,new_value|
          old_value = self[key]
          if old_value.is_a?(Hash) || new_value.is_a?(Hash)
            # merge hashes
            value = Base.new(@manager, @node)
            old_value.is_a?(Hash) ? value.deep_merge!(old_value) : (value[key] = old_value if old_value.any?)
            new_value.is_a?(Hash) ? value.deep_merge!(new_value) : (value[key] = new_value if new_value.any?)
          elsif old_value.is_a?(Array) || new_value.is_a?(Array)
            # merge arrays
            value = []
            old_value.is_a?(Array) ? value += old_value : value << old_value
            new_value.is_a?(Array) ? value += new_value : value << new_value
            value.compact!
          elsif new_value.nil?
            value = old_value
          elsif old_value.nil?
            value = new_value
          elsif old_value.is_a?(Boolean) && new_value.is_a?(Boolean)
            value = new_value
          elsif old_value.class != new_value.class
            raise 'Type mismatch. Cannot merge %s with %s. Key value is %s, name is %s.' % [old_value.class, new_value.class, key, name]
          else
            value = new_value
          end
          self[key] = value
        end
        self
      end

      #def deep_merge!(new_node)
      #  new_node.each do |key, value|
      #    if value.is_a? self.class
      #      value = Base.new(@manager, @node).deep_merge!(value)
      #    self[key] = new_node[key]
      #  end
      #  self
      #end

      #
      # like a normal deep_merge, but replace any hash it encounters with a Config::Base
      #
      #def deep_merge(other_hash)
      #  p [self['name'], other_hash['name']]
      #  self.merge(other_hash) do |key, oldval, newval|
      #    oldval = oldval.to_hash if oldval.respond_to?(:to_hash)
      #    newval = newval.to_hash if newval.respond_to?(:to_hash)
      #    p key
      #    p oldval.class
      #    p newval.class
      #    if oldval.class.to_s == 'Hash' && newval.class.to_s == 'Hash'
      #      oldval.deep_merge(newval)
      #    elsif newval.class.to_s == 'Hash'
      #      p key
      #      Base.new(@manager, node).replace(newval)
      #    else
      #      newval
      #    end
      #  end
      #end
      #
      #def deep_merge!(other_hash)
      #  replace(deep_merge(other_hash))
      #end

      private

      ##
      ## MACROS
      ## these are methods used when eval'ing a value in the .json configuration
      ##

      #
      # inserts the contents of a file
      #
      def file(filename)
        filepath = Path.find_file(name, filename)
        if filepath
          File.read(filepath)
        else
          log0('no such file, "%s"' % filename)
          ""
        end
      end

    end # class
  end # module
end # module