mantra 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/.rspec +1 -0
- data/.travis.yml +8 -2
- data/README.md +126 -16
- data/bin/mantra +17 -0
- data/ci/README.md +6 -0
- data/ci/docker/Dockerfile +20 -0
- data/ci/docker/build-docker-image +10 -0
- data/ci/pipeline.yml +42 -0
- data/ci/scripts/run-tests +3 -0
- data/ci/stub.example.yml +6 -0
- data/ci/stub.yml +33 -0
- data/ci/tasks/run-tests-dev.yml +10 -0
- data/ci/tasks/run-tests-stable.yml +13 -0
- data/lib/mantra/certificate.rb +35 -0
- data/lib/mantra/certificates/cortege.rb +64 -0
- data/lib/mantra/certificates/group.rb +26 -0
- data/lib/mantra/command.rb +51 -0
- data/lib/mantra/commands/find.rb +38 -0
- data/lib/mantra/commands/transform.rb +38 -0
- data/lib/mantra/helpers/object_with_type.rb +60 -0
- data/lib/mantra/helpers/regexp_helper.rb +12 -0
- data/lib/mantra/helpers/template_helper.rb +70 -0
- data/lib/mantra/manifest/array_element.rb +84 -0
- data/lib/mantra/manifest/element.rb +175 -0
- data/lib/mantra/manifest/ext.rb +33 -0
- data/lib/mantra/manifest/hash_element.rb +88 -0
- data/lib/mantra/manifest/leaf_element.rb +38 -0
- data/lib/mantra/manifest/root_element.rb +38 -0
- data/lib/mantra/manifest/scope/array_scope.rb +53 -0
- data/lib/mantra/manifest/scope/empty_scope.rb +24 -0
- data/lib/mantra/manifest/scope/hash_scope.rb +20 -0
- data/lib/mantra/manifest/scope/leaf_scope.rb +0 -0
- data/lib/mantra/manifest/scope.rb +94 -0
- data/lib/mantra/manifest.rb +47 -0
- data/lib/mantra/merge_tool/spiff.rb +23 -0
- data/lib/mantra/merge_tool.rb +13 -0
- data/lib/mantra/transform/extract.rb +10 -0
- data/lib/mantra/transform/extract_certificates.rb +18 -0
- data/lib/mantra/transform/extract_certificates_to_files.rb +12 -0
- data/lib/mantra/transform/extract_section.rb +10 -0
- data/lib/mantra/transform/inputs/any.rb +10 -0
- data/lib/mantra/transform/inputs/array.rb +16 -0
- data/lib/mantra/transform/inputs/file.rb +16 -0
- data/lib/mantra/transform/inputs/folder.rb +9 -0
- data/lib/mantra/transform/inputs/hash.rb +16 -0
- data/lib/mantra/transform/inputs/string.rb +9 -0
- data/lib/mantra/transform/merge.rb +26 -0
- data/lib/mantra/transform/replace.rb +34 -0
- data/lib/mantra/transform/templatize_ip_address.rb +129 -0
- data/lib/mantra/transform/templatize_value.rb +63 -0
- data/lib/mantra/transform.rb +118 -0
- data/lib/mantra/version.rb +1 -1
- data/lib/mantra.rb +9 -1
- data/mantra.gemspec +6 -5
- metadata +69 -8
- data/bin/console +0 -14
- data/bin/setup +0 -8
@@ -0,0 +1,60 @@
|
|
1
|
+
module Mantra
|
2
|
+
module Helpers
|
3
|
+
module ObjectWithType
|
4
|
+
|
5
|
+
class UnknownType < Exception; end
|
6
|
+
class UnspecifiedType < Exception; end
|
7
|
+
|
8
|
+
def self.included(base)
|
9
|
+
base.send :extend, ClassMethods
|
10
|
+
base.send :include, InstanceMethods
|
11
|
+
end
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
def type(type=nil)
|
15
|
+
type.nil? ? @type : (@type = type.to_sym)
|
16
|
+
end
|
17
|
+
|
18
|
+
def alias_type(alias_type=nil)
|
19
|
+
alias_type.nil? ? @alias_type : (@alias_type = alias_type.to_sym)
|
20
|
+
end
|
21
|
+
|
22
|
+
def create(options)
|
23
|
+
type = options[:type] || options["type"]
|
24
|
+
raise UnspecifiedType.new("options hash should contain type") if type.nil?
|
25
|
+
subclass = self.find_by_type(type.to_sym)
|
26
|
+
if subclass.nil?
|
27
|
+
raise UnknownType.new("unknown type #{type}")
|
28
|
+
else
|
29
|
+
subclass.new(options)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def find_by_type(type)
|
34
|
+
self.subclasses.find { |s| s.type == type || s.alias_type == type }
|
35
|
+
end
|
36
|
+
|
37
|
+
def inherited(subclass)
|
38
|
+
subclasses << subclass
|
39
|
+
super
|
40
|
+
end
|
41
|
+
|
42
|
+
def subclasses
|
43
|
+
@subclasses ||= []
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
module InstanceMethods
|
48
|
+
def type
|
49
|
+
self.class.type
|
50
|
+
end
|
51
|
+
|
52
|
+
attr_accessor :options
|
53
|
+
|
54
|
+
def initialize(options={})
|
55
|
+
@options = options
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
class IpAddressTemplate
|
2
|
+
class Scope < String
|
3
|
+
attr_accessor :scope, :value
|
4
|
+
def initialize(options)
|
5
|
+
@value = options[:value]
|
6
|
+
@scope = options[:scope]
|
7
|
+
super(options[:scope])
|
8
|
+
end
|
9
|
+
def is_scope?
|
10
|
+
true
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class Value < String
|
15
|
+
def is_scope?
|
16
|
+
false
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_accessor :quads
|
21
|
+
def initialize(ip_address)
|
22
|
+
@quads = ip_address.split(".").map { |q| Value.new(q) }
|
23
|
+
end
|
24
|
+
|
25
|
+
def replace_with_scope(range, scope)
|
26
|
+
range.to_a.each do |i|
|
27
|
+
@quads[i] = Scope.new(scope: scope, value: @quads[i])
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
alias_method :replace_quad_range_with_scope, :replace_with_scope
|
32
|
+
|
33
|
+
def parts
|
34
|
+
result = [@quads.first]
|
35
|
+
@quads[1..-1].each do |q|
|
36
|
+
if q.is_scope? && result.last.is_scope? && q.scope == result.last.scope
|
37
|
+
result.last.value = [result.last.value, q.value].join(".")
|
38
|
+
else
|
39
|
+
result << Value.new(".") << q
|
40
|
+
end
|
41
|
+
end
|
42
|
+
result
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
module Mantra
|
47
|
+
module Helpers
|
48
|
+
module TemplateHelper
|
49
|
+
|
50
|
+
def is_scope?(v)
|
51
|
+
v.respond_to?(:is_scope?) && v.is_scope?
|
52
|
+
end
|
53
|
+
|
54
|
+
def templatize(parts)
|
55
|
+
merged_parts = [parts.shift]
|
56
|
+
parts.each do |p|
|
57
|
+
if !is_scope?(p) && !is_scope?(merged_parts.last)
|
58
|
+
merged_parts.last.concat(p)
|
59
|
+
else
|
60
|
+
merged_parts << p
|
61
|
+
end
|
62
|
+
end
|
63
|
+
merged_parts.select { |p| !p.empty? }.map do |p|
|
64
|
+
is_scope?(p) ? p : "\"#{p}\""
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module Mantra
|
2
|
+
class Manifest
|
3
|
+
class ArrayElement < Element
|
4
|
+
|
5
|
+
type :array
|
6
|
+
|
7
|
+
def content=(content)
|
8
|
+
@content = content.map do |item|
|
9
|
+
Element.create(item, self)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def merge(element, options={})
|
14
|
+
raise merge_conflict_error(element) unless self.can_merge?(element)
|
15
|
+
elements_to_add = element.content.dup
|
16
|
+
merge_by_name(elements_to_add, options)
|
17
|
+
merge_by_value(elements_to_add, options)
|
18
|
+
self.content.concat(elements_to_add)
|
19
|
+
self
|
20
|
+
end
|
21
|
+
|
22
|
+
def find_children_by_scope(scope)
|
23
|
+
return [] unless scope.array?
|
24
|
+
self.content.map do |element|
|
25
|
+
if scope.match?(element)
|
26
|
+
if scope.has_next?
|
27
|
+
element.find_children_by_scope(scope.next)
|
28
|
+
else
|
29
|
+
element
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end.flatten.compact
|
33
|
+
end
|
34
|
+
|
35
|
+
def each(path, &block)
|
36
|
+
elements = select(path)
|
37
|
+
elements.each(&block)
|
38
|
+
end
|
39
|
+
|
40
|
+
def selector_for(element)
|
41
|
+
self.content.index(element).to_s
|
42
|
+
end
|
43
|
+
|
44
|
+
def merge_by_value(elements, options={})
|
45
|
+
self.content.each do |self_element|
|
46
|
+
element_with_the_same_value = elements.find do |element_to_add|
|
47
|
+
self_element.content == element_to_add.content
|
48
|
+
end
|
49
|
+
unless element_with_the_same_value.nil?
|
50
|
+
self_element.merge(element_with_the_same_value, options)
|
51
|
+
elements.delete(element_with_the_same_value)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def children
|
57
|
+
self.content
|
58
|
+
end
|
59
|
+
|
60
|
+
def merge_by_name(elements, options={})
|
61
|
+
self.content.each do |self_element|
|
62
|
+
element_with_the_same_name = elements.find do |element_to_add|
|
63
|
+
self_element.has_equal_name?(element_to_add)
|
64
|
+
end
|
65
|
+
unless element_with_the_same_name.nil?
|
66
|
+
self_element.merge(element_with_the_same_name, options)
|
67
|
+
elements.delete(element_with_the_same_name)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def to_ruby_object
|
73
|
+
self.content.map { |element| element.to_ruby_object }
|
74
|
+
end
|
75
|
+
|
76
|
+
def traverse(&block)
|
77
|
+
self.content.each do |value|
|
78
|
+
value.traverse(&block)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,175 @@
|
|
1
|
+
module Mantra
|
2
|
+
class Manifest
|
3
|
+
class Element
|
4
|
+
|
5
|
+
class MergeConflictError < Exception; end
|
6
|
+
class UnknownScopeError < Exception; end
|
7
|
+
|
8
|
+
include Helpers::ObjectWithType
|
9
|
+
include Helpers::RegexpHelper
|
10
|
+
|
11
|
+
attr_accessor :content, :parent
|
12
|
+
|
13
|
+
def self.create(content, parent = nil)
|
14
|
+
return RootElement.new(content, parent) if parent.nil?
|
15
|
+
case content
|
16
|
+
when Element then Element.create(content.content, parent)
|
17
|
+
when Array then ArrayElement.new(content, parent)
|
18
|
+
when Hash then HashElement.new(content, parent)
|
19
|
+
else LeafElement.new(content, parent)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def initialize(content, parent)
|
24
|
+
self.content = content
|
25
|
+
self.parent = parent
|
26
|
+
end
|
27
|
+
|
28
|
+
# this method mimics ruby Hash#fetch method
|
29
|
+
def fetch(scope, &block)
|
30
|
+
current_scope = Scope.parse(scope)
|
31
|
+
element = self.root? ? self.content : self
|
32
|
+
current_scope.filter(element, &block)
|
33
|
+
end
|
34
|
+
|
35
|
+
alias_method :select, :fetch
|
36
|
+
|
37
|
+
def children
|
38
|
+
raise "not implemented"
|
39
|
+
end
|
40
|
+
|
41
|
+
def content=(content)
|
42
|
+
raise "#content=() method is not implemented"
|
43
|
+
end
|
44
|
+
|
45
|
+
def name
|
46
|
+
raise "unknown name"
|
47
|
+
end
|
48
|
+
|
49
|
+
def find(string_scope)
|
50
|
+
self.fetch(string_scope).first
|
51
|
+
end
|
52
|
+
|
53
|
+
def get(string_scope)
|
54
|
+
result = self.find(string_scope)
|
55
|
+
result.nil? ? nil : result.to_ruby_object
|
56
|
+
end
|
57
|
+
|
58
|
+
def has_name?
|
59
|
+
false
|
60
|
+
end
|
61
|
+
|
62
|
+
def has_equal_name?(element)
|
63
|
+
false
|
64
|
+
end
|
65
|
+
|
66
|
+
%i(leaf hash array root).each do |type|
|
67
|
+
define_method "#{type}?" do
|
68
|
+
self.type == type
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def path
|
73
|
+
case self.parent.type
|
74
|
+
when :hash then "#{self.parent.path}#{self.parent.parent.root? ? "" : "."}#{self.selector}"
|
75
|
+
when :array then "#{self.parent.path}[#{self.selector}]"
|
76
|
+
when :leaf then raise "leaf can't be parent"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def selector
|
81
|
+
case self.parent.type
|
82
|
+
when :array
|
83
|
+
self.has_name? ? "name=#{self.name}" : ""
|
84
|
+
when :hash
|
85
|
+
self.parent.selector_for(self)
|
86
|
+
when :root
|
87
|
+
""
|
88
|
+
else
|
89
|
+
raise "don't know how to build selector"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def merge(element, options={})
|
94
|
+
raise "not implemented"
|
95
|
+
end
|
96
|
+
|
97
|
+
def to_ruby_object
|
98
|
+
raise "not implemented"
|
99
|
+
end
|
100
|
+
|
101
|
+
def merge_conflict_error(element)
|
102
|
+
object = element.respond_to?(:to_ruby_object) ? element.to_ruby_object : element.inspect
|
103
|
+
MergeConflictError.new("merge conflicts: types: #{self.class} against #{element.class}, values: #{self.to_ruby_object} with #{object}")
|
104
|
+
end
|
105
|
+
|
106
|
+
def can_merge?(element)
|
107
|
+
if element.respond_to?(:type)
|
108
|
+
self.type == element.type
|
109
|
+
else
|
110
|
+
self.class == element.class
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def delete(selector)
|
115
|
+
raise "not implemented"
|
116
|
+
end
|
117
|
+
|
118
|
+
def method_missing(method_name, *arguments, &block)
|
119
|
+
if self.content.respond_to?(method_name)
|
120
|
+
self.content.send(method_name, *arguments, &block)
|
121
|
+
else
|
122
|
+
super
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def respond_to_missing?(method_name, include_private = false)
|
127
|
+
self.content.respond_to?(method_name) || super
|
128
|
+
end
|
129
|
+
|
130
|
+
def traverse(&block)
|
131
|
+
raise "not implemented"
|
132
|
+
end
|
133
|
+
|
134
|
+
def match_selector?(selector)
|
135
|
+
selector.empty?
|
136
|
+
end
|
137
|
+
|
138
|
+
def path_exist?(path)
|
139
|
+
!self.select(path).empty?
|
140
|
+
end
|
141
|
+
|
142
|
+
def add_node(selector, value)
|
143
|
+
raise "not implemented"
|
144
|
+
end
|
145
|
+
|
146
|
+
def self.element_with_selector(selector, value)
|
147
|
+
object_to_add = {}
|
148
|
+
parts = selector.split(".")
|
149
|
+
last_key = parts.pop
|
150
|
+
last_node = parts.inject(object_to_add) do |h, part|
|
151
|
+
h[part] = {}; h[part]
|
152
|
+
end
|
153
|
+
last_node[last_key] = value
|
154
|
+
Element.create(object_to_add)
|
155
|
+
end
|
156
|
+
|
157
|
+
def split_selector(selector, matcher_regex)
|
158
|
+
matcher = selector.match(matcher_regex)
|
159
|
+
raise UnknownScopeError.new("Unknown selector: #{selector}") if matcher.nil?
|
160
|
+
head_selector = matcher[1]
|
161
|
+
tail_selector = matcher[2]
|
162
|
+
return head_selector, tail_selector
|
163
|
+
end
|
164
|
+
|
165
|
+
def array_selector?(selector)
|
166
|
+
!!selector.match(/\[(.+\=.+|\d+)?\].*/)
|
167
|
+
end
|
168
|
+
|
169
|
+
def hash_selector?(selector)
|
170
|
+
!!selector.match(/[a-z0-9]+/)
|
171
|
+
end
|
172
|
+
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Mantra
|
2
|
+
class Manifest
|
3
|
+
module Ext
|
4
|
+
def self.included(base)
|
5
|
+
base.send :extend, ClassMethods
|
6
|
+
base.send :include, InstanceMethods
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
end
|
11
|
+
|
12
|
+
module InstanceMethods
|
13
|
+
|
14
|
+
def properties
|
15
|
+
job_properties = Element.create({}).content
|
16
|
+
job_properties_array = self.jobs.map do |job|
|
17
|
+
job.hash? ? job.content["properties"] : nil
|
18
|
+
end.compact
|
19
|
+
job_properties_array.each do |jp|
|
20
|
+
job_properties.merge(jp)
|
21
|
+
end
|
22
|
+
manifest_properties = self.root.child.hash? ? self.root.child.content["properties"] : Element.create({}).content
|
23
|
+
manifest_properties.merge(job_properties)
|
24
|
+
Element.create(manifest_properties)
|
25
|
+
end
|
26
|
+
|
27
|
+
def jobs
|
28
|
+
self.root.content.hash? ? self.root.content.content["jobs"] : nil
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module Mantra
|
2
|
+
class Manifest
|
3
|
+
class HashElement < Element
|
4
|
+
type :hash
|
5
|
+
|
6
|
+
def content=(content) # content is hash
|
7
|
+
@content = content.to_a.inject({}) do |result, pair|
|
8
|
+
key, value = *pair
|
9
|
+
result[key] = Element.create(value, self)
|
10
|
+
result
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def merge(element, options={})
|
15
|
+
raise merge_conflict_error(element) unless self.can_merge?(element)
|
16
|
+
self.content.merge!(element.content) do |_, self_element, element_to_merge|
|
17
|
+
self_element.merge(element_to_merge, options)
|
18
|
+
end
|
19
|
+
self
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_ruby_object
|
23
|
+
self.content.to_a.inject({}) do |result, pair|
|
24
|
+
key, element = *pair
|
25
|
+
result[key] = element.to_ruby_object
|
26
|
+
result
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def has_equal_name?(element)
|
31
|
+
self.has_name? && element.has_name? && self.name == element.name
|
32
|
+
end
|
33
|
+
|
34
|
+
def has_name?
|
35
|
+
self.content.has_key?("name")
|
36
|
+
end
|
37
|
+
|
38
|
+
def children
|
39
|
+
self.content.values
|
40
|
+
end
|
41
|
+
|
42
|
+
def name
|
43
|
+
raise "name should be a value, not node" if !self.content["name"].leaf?
|
44
|
+
self.content["name"].content || raise("no name for #{self.inspect}")
|
45
|
+
end
|
46
|
+
|
47
|
+
def selector_for(element)
|
48
|
+
self.content.each_pair do |key, value|
|
49
|
+
return key if value == element
|
50
|
+
end
|
51
|
+
raise "can't find key for object: #{element.to_ruby_object.inspect}"
|
52
|
+
end
|
53
|
+
|
54
|
+
def method_missing(method_name, *arguments, &block)
|
55
|
+
if self.content.has_key?(method_name.to_s) && arguments.empty?
|
56
|
+
self.content[method_name.to_s]
|
57
|
+
else
|
58
|
+
super
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def respond_to_missing?(method_name, include_private = false)
|
63
|
+
self.content.has_key?(method_name.to_s) || super
|
64
|
+
end
|
65
|
+
|
66
|
+
def traverse(&block)
|
67
|
+
self.content.each_value do |value|
|
68
|
+
value.traverse(&block)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def find_children_by_scope(scope)
|
73
|
+
return [] unless scope.hash?
|
74
|
+
self.content.each_pair.map do |pair|
|
75
|
+
key, value = *pair
|
76
|
+
if scope.match?(key, value)
|
77
|
+
if scope.has_next?
|
78
|
+
element.find_children_by_scope(scope.next)
|
79
|
+
else
|
80
|
+
element
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end.flatten.compact
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Mantra
|
2
|
+
class Manifest
|
3
|
+
class LeafElement < Element
|
4
|
+
|
5
|
+
type :leaf
|
6
|
+
|
7
|
+
def content=(content)
|
8
|
+
@content = content
|
9
|
+
end
|
10
|
+
|
11
|
+
def merge(element, options={})
|
12
|
+
raise merge_conflict_error(element) unless self.can_merge?(element)
|
13
|
+
if !options[:force]
|
14
|
+
raise MergeConflictError.new("value conflict detected: #{self.content} != #{element.content}") if self.content != element.content
|
15
|
+
end
|
16
|
+
self
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_ruby_object
|
20
|
+
self.content
|
21
|
+
end
|
22
|
+
|
23
|
+
def traverse(&block)
|
24
|
+
block.call(self)
|
25
|
+
end
|
26
|
+
|
27
|
+
def add_node(selector, value)
|
28
|
+
raise UnknownScopeError.new("Can't add nodes to leaf") unless selector.empty?
|
29
|
+
raise MergeConflictError.new("Leaf already exists with another value: #{self.content} != #{value}") unless self.content != value
|
30
|
+
end
|
31
|
+
|
32
|
+
def children
|
33
|
+
[]
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Mantra
|
2
|
+
class Manifest
|
3
|
+
class RootElement < Element
|
4
|
+
|
5
|
+
type :root
|
6
|
+
|
7
|
+
def content=(content)
|
8
|
+
@content = Element.create(content, self)
|
9
|
+
end
|
10
|
+
|
11
|
+
def path
|
12
|
+
""
|
13
|
+
end
|
14
|
+
|
15
|
+
def merge(element, options={})
|
16
|
+
raise merge_conflict_error(element) unless self.can_merge?(element)
|
17
|
+
self.content.merge(element.content, options)
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_ruby_object
|
21
|
+
self.content.to_ruby_object
|
22
|
+
end
|
23
|
+
|
24
|
+
def traverse(&block)
|
25
|
+
self.content.traverse(&block)
|
26
|
+
end
|
27
|
+
|
28
|
+
def child
|
29
|
+
self.content
|
30
|
+
end
|
31
|
+
|
32
|
+
def children
|
33
|
+
self.content
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Mantra
|
2
|
+
class Manifest
|
3
|
+
class Scope
|
4
|
+
class ArrayScope < Scope
|
5
|
+
class UnknownFunction < Exception; end
|
6
|
+
|
7
|
+
type :array
|
8
|
+
|
9
|
+
# returns array of children that are
|
10
|
+
def _filter(element)
|
11
|
+
case scope
|
12
|
+
when /^\d+$/
|
13
|
+
index = scope.to_i
|
14
|
+
[element.content[index]]
|
15
|
+
when /^.+\=.+$/
|
16
|
+
key_wildcard, value_wildcard = scope.split("=")
|
17
|
+
key_matcher, value_matcher = to_regexp(key_wildcard), to_regexp(value_wildcard)
|
18
|
+
element.content.select { |e| e.hash? }.select do |e|
|
19
|
+
e.each_pair.any? do |pair|
|
20
|
+
k, v = *pair
|
21
|
+
k.match(key_matcher) && v.match(value_matcher)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
when /^\:\:.+$/
|
25
|
+
function_name = self.scope[2..-1]
|
26
|
+
if !allowed_function_names.include?(function_name)
|
27
|
+
raise ScopeParseError.new("Unknown function: #{function_name}")
|
28
|
+
end
|
29
|
+
self.send(function_name, element)
|
30
|
+
when ""
|
31
|
+
element.content
|
32
|
+
else
|
33
|
+
raise ScopeParseError.new("Don't know how to apply scope to array " +
|
34
|
+
"(scope: #{self.scope.inspect}, array: #{element.to_ruby_object.inspect})")
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def allowed_function_names
|
39
|
+
%w(last first)
|
40
|
+
end
|
41
|
+
|
42
|
+
def last(element)
|
43
|
+
[element.content.last]
|
44
|
+
end
|
45
|
+
|
46
|
+
def first(element)
|
47
|
+
[element.content.first]
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Mantra
|
2
|
+
class Manifest
|
3
|
+
class Scope
|
4
|
+
class EmptyScope < Scope
|
5
|
+
|
6
|
+
type :empty
|
7
|
+
|
8
|
+
# returns array of children that matches this scope
|
9
|
+
def _filter(element)
|
10
|
+
[element]
|
11
|
+
end
|
12
|
+
|
13
|
+
def last?
|
14
|
+
true
|
15
|
+
end
|
16
|
+
|
17
|
+
def has_same_type?(element)
|
18
|
+
true
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Mantra
|
2
|
+
class Manifest
|
3
|
+
class Scope
|
4
|
+
class HashScope < Scope
|
5
|
+
|
6
|
+
type :hash
|
7
|
+
|
8
|
+
# returns array of children that matches this scope
|
9
|
+
def _filter(element)
|
10
|
+
scope_regexp = to_regexp(scope)
|
11
|
+
element.content.each_pair.map do |pair|
|
12
|
+
key, value = *pair
|
13
|
+
key.match(scope_regexp) ? value : nil
|
14
|
+
end.compact
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
File without changes
|