occi-core 4.1.3 → 4.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (125) hide show
  1. data/.gitignore +1 -0
  2. data/Gemfile +8 -0
  3. data/README.md +49 -17
  4. data/lib/occi/collection.rb +37 -21
  5. data/lib/occi/core/action.rb +5 -5
  6. data/lib/occi/core/action_instance.rb +45 -3
  7. data/lib/occi/core/actions.rb +2 -1
  8. data/lib/occi/core/attributes.rb +253 -73
  9. data/lib/occi/core/categories.rb +1 -0
  10. data/lib/occi/core/category.rb +25 -8
  11. data/lib/occi/core/entities.rb +1 -0
  12. data/lib/occi/core/entity.rb +51 -74
  13. data/lib/occi/core/kind.rb +15 -11
  14. data/lib/occi/core/kinds.rb +1 -1
  15. data/lib/occi/core/link.rb +14 -15
  16. data/lib/occi/core/links.rb +1 -1
  17. data/lib/occi/core/mixin.rb +5 -5
  18. data/lib/occi/core/mixins.rb +2 -2
  19. data/lib/occi/core/properties.rb +90 -12
  20. data/lib/occi/core/resource.rb +7 -3
  21. data/lib/occi/core/resources.rb +2 -2
  22. data/lib/occi/errors/attribute_definitions_converted_error.rb +5 -0
  23. data/lib/occi/errors/attribute_missing_error.rb +5 -0
  24. data/lib/occi/errors/attribute_name_invalid_error.rb +5 -0
  25. data/lib/occi/errors/attribute_not_defined_error.rb +5 -0
  26. data/lib/occi/errors/attribute_property_type_error.rb +5 -0
  27. data/lib/occi/errors/attribute_type_error.rb +5 -0
  28. data/lib/occi/errors/kind_not_defined_error.rb +5 -0
  29. data/lib/occi/errors/parser_input_error.rb +5 -0
  30. data/lib/occi/errors/parser_type_error.rb +5 -0
  31. data/lib/occi/errors.rb +1 -0
  32. data/lib/occi/extensions/hashie.rb +25 -0
  33. data/lib/occi/helpers/comparators/action_instance.rb +22 -0
  34. data/lib/occi/helpers/comparators/attributes.rb +22 -0
  35. data/lib/occi/helpers/comparators/categories.rb +22 -0
  36. data/lib/occi/helpers/comparators/category.rb +22 -0
  37. data/lib/occi/helpers/comparators/collection.rb +40 -0
  38. data/lib/occi/helpers/comparators/entities.rb +22 -0
  39. data/lib/occi/helpers/comparators/entity.rb +22 -0
  40. data/lib/occi/helpers/comparators/properties.rb +26 -0
  41. data/lib/occi/helpers/comparators.rb +1 -0
  42. data/lib/occi/infrastructure/compute.rb +11 -9
  43. data/lib/occi/infrastructure/network.rb +27 -27
  44. data/lib/occi/infrastructure/networkinterface.rb +22 -23
  45. data/lib/occi/infrastructure/os_tpl.rb +1 -1
  46. data/lib/occi/infrastructure/resource_tpl.rb +1 -1
  47. data/lib/occi/infrastructure/storage.rb +7 -6
  48. data/lib/occi/infrastructure/storagelink.rb +4 -4
  49. data/lib/occi/log.rb +13 -10
  50. data/lib/occi/model.rb +9 -8
  51. data/lib/occi/parser/json.rb +11 -9
  52. data/lib/occi/parser/ova.rb +12 -6
  53. data/lib/occi/parser/ovf.rb +173 -116
  54. data/lib/occi/parser/text/constants.rb +87 -0
  55. data/lib/occi/parser/text.rb +161 -200
  56. data/lib/occi/parser/xml.rb +10 -8
  57. data/lib/occi/parser.rb +100 -50
  58. data/lib/occi/settings.rb +2 -1
  59. data/lib/occi/version.rb +1 -1
  60. data/lib/occi-core.rb +6 -4
  61. data/occi-core.gemspec +0 -7
  62. data/spec/occi/collection_samples/collection1.json +1 -0
  63. data/spec/occi/collection_samples/directory2/collection2.json +1 -0
  64. data/spec/occi/collection_spec.rb +961 -31
  65. data/spec/occi/core/action_instance_spec.rb +317 -0
  66. data/spec/occi/core/action_spec.rb +71 -0
  67. data/spec/occi/core/attributes_spec.rb +582 -27
  68. data/spec/occi/core/category_spec.rb +194 -18
  69. data/spec/occi/core/entities_spec.rb +96 -0
  70. data/spec/occi/core/entity_spec.rb +317 -28
  71. data/spec/occi/core/kind_spec.rb +127 -16
  72. data/spec/occi/core/link_spec.rb +35 -0
  73. data/spec/occi/core/links_spec.rb +130 -0
  74. data/spec/occi/core/mixins_spec.rb +107 -0
  75. data/spec/occi/core/properties_spec.rb +167 -0
  76. data/spec/occi/core/resource_spec.rb +23 -9
  77. data/spec/occi/core_spec.rb +12 -0
  78. data/spec/occi/infrastructure/compute_spec.rb +218 -18
  79. data/spec/occi/infrastructure/network_spec.rb +96 -0
  80. data/spec/occi/infrastructure/networkinterface_spec.rb +96 -0
  81. data/spec/occi/infrastructure/storage_spec.rb +33 -0
  82. data/spec/occi/infrastructure/storagelink_spec.rb +45 -0
  83. data/spec/occi/log_spec.rb +104 -1
  84. data/spec/occi/model_spec.rb +251 -39
  85. data/spec/occi/{test.json → parser/json_samples/test.json} +0 -0
  86. data/spec/occi/parser/ova_samples/test.dump +0 -0
  87. data/spec/occi/{test.ova → parser/ova_samples/test.ova} +0 -0
  88. data/spec/occi/parser/ovf_samples/test.dump +0 -0
  89. data/spec/occi/{test.ovf → parser/ovf_samples/test.ovf} +0 -0
  90. data/spec/occi/parser/text_samples/occi_categories.dump +0 -0
  91. data/spec/occi/parser/text_samples/occi_categories.text +2 -0
  92. data/spec/occi/parser/text_samples/occi_compute_rocci_server.dump +0 -0
  93. data/spec/occi/parser/text_samples/occi_compute_rocci_server.resource.dump +0 -0
  94. data/spec/occi/parser/text_samples/occi_compute_rocci_server.text +10 -0
  95. data/spec/occi/parser/text_samples/occi_link_resource_instance.dump +0 -0
  96. data/spec/occi/parser/text_samples/occi_link_resource_instance.text +7 -0
  97. data/spec/occi/parser/text_samples/occi_link_simple.dump +0 -0
  98. data/spec/occi/parser/text_samples/occi_link_simple.link_string.dump +0 -0
  99. data/spec/occi/parser/text_samples/occi_link_simple.text +1 -0
  100. data/spec/occi/parser/text_samples/occi_link_w_attributes.dump +0 -0
  101. data/spec/occi/parser/text_samples/occi_link_w_attributes.text +7 -0
  102. data/spec/occi/parser/text_samples/occi_link_w_category.dump +0 -0
  103. data/spec/occi/parser/text_samples/occi_link_w_category.text +3 -0
  104. data/spec/occi/parser/text_samples/occi_model_rocci_server.dump +0 -0
  105. data/spec/occi/parser/text_samples/occi_model_rocci_server.text +51 -0
  106. data/spec/occi/parser/text_samples/occi_network_rocci_server.dump +0 -0
  107. data/spec/occi/parser/text_samples/occi_network_rocci_server.resource.dump +0 -0
  108. data/spec/occi/parser/text_samples/occi_network_rocci_server.text +11 -0
  109. data/spec/occi/parser/text_samples/occi_resource_w_attributes.dump +0 -0
  110. data/spec/occi/parser/text_samples/occi_resource_w_attributes.text +11 -0
  111. data/spec/occi/parser/text_samples/occi_resource_w_inline_links.dump +0 -0
  112. data/spec/occi/parser/text_samples/occi_resource_w_inline_links.text +16 -0
  113. data/spec/occi/parser/text_samples/occi_resource_w_inline_links_only.dump +0 -0
  114. data/spec/occi/parser/text_samples/occi_resource_w_inline_links_only.text +13 -0
  115. data/spec/occi/parser/text_samples/occi_storage_rocci_server.dump +0 -0
  116. data/spec/occi/parser/text_samples/occi_storage_rocci_server.resource.dump +0 -0
  117. data/spec/occi/parser/text_samples/occi_storage_rocci_server.text +9 -0
  118. data/spec/occi/parser/text_spec.rb +274 -78
  119. data/spec/occi/parser/xml_samples/test.xml +352 -0
  120. data/spec/occi/parser_spec.rb +255 -104
  121. data/spec/occi-core_spec.rb +31 -0
  122. data/spec/spec_helper.rb +6 -2
  123. metadata +110 -111
  124. checksums.yaml +0 -7
  125. data/spec/occi/core/attribute_spec.rb +0 -0
@@ -5,6 +5,18 @@ module Occi
5
5
  attr_accessor :converted
6
6
 
7
7
  include Occi::Helpers::Inspect
8
+ include Occi::Helpers::Comparators::Attributes
9
+
10
+ def initialize(source_hash = {}, default = nil, &blk)
11
+ raise ArgumentError, 'Source_hash is a mandatory argument!' unless source_hash
12
+ raise ArgumentError, 'Source_hash must be a hash-like structure!' unless source_hash.kind_of?(Hash)
13
+
14
+ # All internal Hashie::Mash elements in source_hash have to be re-typed
15
+ # to Occi::Core::Attributes, so we have to rebuild the object from scratch
16
+ source_hash = source_hash.to_hash unless source_hash.kind_of?(Occi::Core::Attributes)
17
+
18
+ super(source_hash, default, &blk)
19
+ end
8
20
 
9
21
  def converted?
10
22
  @converted||=false
@@ -14,7 +26,8 @@ module Occi
14
26
  if key.to_s.include? '.'
15
27
  key, string = key.to_s.split('.', 2)
16
28
  attributes = super(key)
17
- raise "Attribute with key #{key} not found" unless attributes
29
+ raise Occi::Errors::AttributeMissingError,
30
+ "Attribute with key #{key} not found" unless attributes
18
31
  attributes[string]
19
32
  else
20
33
  super(key)
@@ -27,34 +40,8 @@ module Occi
27
40
  super(key, Occi::Core::Attributes.new) unless self[key].kind_of? Occi::Core::Attributes
28
41
  self[key][string] = value
29
42
  else
30
- property_key = '_' + key.to_s
31
- case value
32
- when Occi::Core::Attributes
33
- super(key, value)
34
- when Occi::Core::Properties
35
- super(key, value.clone)
36
- super(property_key, value.clone)
37
- when Hash
38
- properties = Occi::Core::Properties.new(value)
39
- super(key, properties.clone)
40
- super(property_key, properties.clone)
41
- when Occi::Core::Entity
42
- raise "value #{value} derived from Occi::Core::Entity assigned but attribute of type #{self[property_key].type} required" unless self[property_key].type == 'string' if self[property_key]
43
- raise "value #{value} does not match pattern" unless value.to_s.match '^' + self[property_key].pattern + '$' if Occi::Settings.verify_attribute_pattern if self[property_key]
44
- super(key, value)
45
- when String
46
- raise "value #{value} of type String assigned but attribute of type #{self[property_key].type} required" unless self[property_key].type == 'string' if self[property_key]
47
- raise "value #{value} does not match pattern" unless value.match '^' + self[property_key].pattern + '$' if Occi::Settings.verify_attribute_pattern if self[property_key]
48
- super(key, value)
49
- when Numeric
50
- raise "value #{value} of type String assigned but attribute of type #{self[property_key].type} required" unless self[property_key].type == 'number' if self[property_key]
51
- raise "value #{value} does not match pattern" unless value.to_s.match '^' + self[property_key].pattern + '$' if Occi::Settings.verify_attribute_pattern if self[property_key]
52
- super(key, value)
53
- when NilClass
54
- super(key, value)
55
- else
56
- raise "value #{value} of type #{value.class} not supported as attribute"
57
- end
43
+ property_key = "_#{key.to_s}"
44
+ validate_and_assign(key, value, property_key)
58
45
  end
59
46
  end
60
47
 
@@ -62,10 +49,10 @@ module Occi
62
49
  attributes.keys.each do |key|
63
50
  if self.keys.include? key
64
51
  case self[key]
65
- when Occi::Core::Attributes
66
- self[key].remove attributes[key]
67
- else
68
- self.delete(key)
52
+ when Occi::Core::Attributes
53
+ self[key].remove attributes[key]
54
+ else
55
+ self.delete(key)
69
56
  end
70
57
  end
71
58
  end
@@ -74,12 +61,12 @@ module Occi
74
61
 
75
62
  def convert(attributes=Occi::Core::Attributes.new(self))
76
63
  attributes.each_pair do |key, value|
77
- next if attributes.key?(key[1..-1])
64
+ next if key =~ /^_/
78
65
  case value
79
- when Occi::Core::Attributes
80
- value.convert!
81
- else
82
- attributes[key] = nil
66
+ when Occi::Core::Attributes
67
+ value.convert!
68
+ else
69
+ attributes[key] = nil
83
70
  end
84
71
  end
85
72
  attributes.converted = true
@@ -90,13 +77,13 @@ module Occi
90
77
  convert self
91
78
  end
92
79
 
93
- # @return [Array] key value pair of full attribute names with their corresponding values
80
+ # @return [Hash] key value pair of full attribute names with their corresponding values
94
81
  def names
95
82
  hash = {}
96
83
  self.each_key do |key|
97
- next if self.key?(key[1..-1])
84
+ next if key =~ /^_/
98
85
  if self[key].kind_of? Occi::Core::Attributes
99
- self[key].names.each_pair { |k, v| hash[key + '.' + k] = v unless v.blank? }
86
+ self[key].names.each_pair { |k, v| hash["#{key}.#{k}"] = v unless v.nil? }
100
87
  else
101
88
  hash[key] = self[key]
102
89
  end
@@ -105,45 +92,89 @@ module Occi
105
92
  end
106
93
 
107
94
  # @param [Hash] attributes
108
- # @return [Occi::Core::Properties] parsed Properties
109
- def self.parse(hash)
95
+ # @return [Occi::Core::Attributes] parsed attributes with properties
96
+ def self.parse_properties(hash)
110
97
  hash ||= {}
98
+ raise Occi::Errors::ParserInputError,
99
+ 'Hash must be a hash-like structure!' unless hash.respond_to?(:each_pair)
100
+
111
101
  attributes = Occi::Core::Attributes.new
112
102
  hash.each_pair do |key, value|
113
- if [:Type, :Required, :Mutable, :Default, :Description, :Pattern, :type, :required, :mutable, :default, :description, :pattern].any? { |k| value.key?(k) and not value[k].kind_of? Hash }
114
- value[:type] ||= value[:Type] ||= "string"
115
- value[:required] ||= value[:Required] ||= false
116
- value[:mutable] ||= value[:Mutable] ||= false
117
- value[:default] = value[:Default] if value[:Default]
118
- value[:description] = value[:Description] if value[:Description]
119
- value[:pattern] ||= value[:Pattern] ||= ".*"
120
- value.delete :Type
121
- value.delete :Required
122
- value.delete :Mutable
123
- value.delete :Default
124
- value.delete :Description
125
- value.delete :Pattern
126
- attributes[key] = Occi::Core::Properties.new value
103
+ if Occi::Core::Properties.contains_props?(value)
104
+ attributes[key] = Occi::Core::Properties.new(value)
127
105
  else
128
- attributes[key] = self.parse attributes[key]
106
+ attributes[key] = self.parse_properties(attributes[key])
129
107
  end
130
108
  end
109
+
131
110
  attributes
132
111
  end
133
112
 
134
113
  # @param [Hash] attributes key value pair of full attribute names with their corresponding values
135
- # @return [Occi::Core::Properties]
114
+ # @return [Occi::Core::Attributes]
136
115
  def self.split(attributes)
137
116
  attribute = Attributes.new
138
117
  attributes.each do |name, value|
139
118
  key, _, rest = name.partition('.')
140
- if rest.empty?
119
+ if rest.blank?
141
120
  attribute[key] = value
142
121
  else
143
122
  attribute.merge! Attributes.new(key => self.split(rest => value))
144
123
  end
145
124
  end
146
- return attribute
125
+
126
+ attribute
127
+ end
128
+
129
+ # @return [String]
130
+ def to_string
131
+ attributes = ';'
132
+ attributes << to_header.gsub(',', ';')
133
+
134
+ attributes == ';' ? '' : attributes
135
+ end
136
+
137
+ # @return [String]
138
+ def to_string_short
139
+ any? ? ";attributes=#{names.keys.join(' ').inspect}" : ""
140
+ end
141
+
142
+ # @return [String]
143
+ def to_text
144
+ text = ""
145
+ names.each_pair do |name, value|
146
+ # TODO: find a better way to skip properties
147
+ next if name.include? '._'
148
+ case value
149
+ when Occi::Core::Entity
150
+ text << "\nX-OCCI-Attribute: #{name}=\"#{value.location}\""
151
+ when Occi::Core::Category
152
+ text << "\nX-OCCI-Attribute: #{name}=\"#{value.type_identifier}\""
153
+ else
154
+ text << "\nX-OCCI-Attribute: #{name}=#{value.inspect}"
155
+ end
156
+ end
157
+
158
+ text
159
+ end
160
+
161
+ # @return [String] of attributes put in an array and then concatenated into a string
162
+ def to_header
163
+ attributes = []
164
+ names.each_pair do |name, value|
165
+ # TODO: find a better way to skip properties
166
+ next if name.include? '._'
167
+ case value
168
+ when Occi::Core::Entity
169
+ attributes << "#{name}=\"#{value.location}\""
170
+ when Occi::Core::Category
171
+ attributes << "#{name}=\"#{value.type_identifier}\""
172
+ else
173
+ attributes << "#{name}=#{value.inspect}"
174
+ end
175
+ end
176
+
177
+ attributes.join(',')
147
178
  end
148
179
 
149
180
  def to_json(*a)
@@ -153,24 +184,173 @@ module Occi
153
184
  # @param [Hash] options
154
185
  # @return [Hashie::Mash] json representation
155
186
  def as_json(options={})
156
- hash = {}
187
+ hash = Hashie::Mash.new
157
188
  self.each_pair do |key, value|
158
- next if self.key?(key[1..-1])
189
+ next if key =~ /^_/
190
+ # TODO: find a better way to skip properties
191
+ next if key.start_with? '_'
192
+
159
193
  case value
160
- when Occi::Core::Attributes
161
- hash[key] = value.as_json unless value.as_json.size == 0
162
- when Occi::Core::Entity
163
- hash[key] = value.to_s unless value.to_s.empty?
164
- when Occi::Core::Category
165
- hash[key] = value.to_s
166
- else
167
- hash[key] = value.as_json if value
194
+ when Occi::Core::Attributes
195
+ hash[key] = value.as_json if value && value.as_json.size > 0
196
+ when Occi::Core::Entity
197
+ hash[key] = value.to_s unless value.blank?
198
+ when Occi::Core::Category
199
+ hash[key] = value.to_s
200
+ else
201
+ hash[key] = value.as_json unless value.nil?
168
202
  end
169
203
  end
204
+
170
205
  hash
171
206
  end
172
207
 
173
- end
208
+ # @param [Occi::Core::Attributes] definitions
209
+ # @param [true,false] set_defaults
210
+ # @return [Occi::Core::Attributes] attributes with their defaults set
211
+ def check(definitions, set_defaults = false)
212
+ attributes = Occi::Core::Attributes.new(self)
213
+ attributes.check!(definitions, set_defaults)
214
+ attributes
215
+ end
174
216
 
217
+ # @param [Occi::Core::Attributes] definitions
218
+ # @param [true,false] set_defaults
219
+ # Assigns default values to attributes
220
+ def check!(definitions, set_defaults = false)
221
+ raise Occi::Errors::AttributeDefinitionsConvrertedError,
222
+ "Definition attributes must not be converted" if definitions.converted?
223
+
224
+ # Start with checking for missing attributes
225
+ add_missing_attributes(self, definitions, set_defaults)
226
+
227
+ # Then check all attributes against definitions
228
+ check_wrt_definitions(self, definitions, set_defaults)
229
+
230
+ # Delete remaining empty attributes
231
+ delete_empty(self)
232
+ end
233
+
234
+ private
235
+
236
+ def validate_and_assign(key, value, property_key)
237
+ raise Occi::Errors::AttributeNameInvalidError,
238
+ "Attribute names (as in \"#{key}\") must not begin with underscores" if key =~ /^_/
239
+ case value
240
+ when Occi::Core::Attributes
241
+ add_to_hashie(key, value)
242
+ when Occi::Core::Properties
243
+ add_to_hashie(key, value.clone)
244
+ add_to_hashie(property_key, value.clone)
245
+ when Hash
246
+ properties = Occi::Core::Properties.new(value)
247
+ add_to_hashie(key, properties.clone)
248
+ add_to_hashie(property_key, properties.clone)
249
+ when Occi::Core::Entity
250
+ match_type(value, self[property_key], 'string') if self[property_key]
251
+ add_to_hashie(key, value)
252
+ when Occi::Core::Category
253
+ match_type(value, self[property_key], 'string') if self[property_key]
254
+ add_to_hashie(key, value)
255
+ when String
256
+ match_type(value, self[property_key], 'string') if self[property_key]
257
+ add_to_hashie(key, value)
258
+ when Numeric
259
+ match_type(value, self[property_key], 'number') if self[property_key]
260
+ add_to_hashie(key, value)
261
+ when FalseClass, TrueClass
262
+ match_type(value, self[property_key], 'boolean') if self[property_key]
263
+ add_to_hashie(key, value)
264
+ when NilClass
265
+ add_to_hashie(key, value)
266
+ else
267
+ raise Occi::Errors::AttributeTypeError,
268
+ "Value #{value} of type #{value.class} not supported as attribute"
269
+ end
270
+ end
271
+
272
+ def add_to_hashie(*args)
273
+ Hashie::Mash.instance_method(:[]=).bind(self).call(*args)
274
+ end
275
+
276
+ def match_type(value, property, expected_type)
277
+ raise Occi::Errors::AttributeTypeError,
278
+ "Value #{value} derived from #{value.class} assigned " \
279
+ "but attribute of type #{property.type} required" unless property.type == expected_type
280
+ match_pattern(property.pattern, value)
281
+ end
282
+
283
+ def match_pattern(pattern, value)
284
+ return if pattern.blank?
285
+
286
+ if Occi::Settings.verify_attribute_pattern && !Occi::Settings.compatibility
287
+ raise Occi::Errors::AttributeTypeError,
288
+ "Value #{value.to_s} does not match pattern #{pattern}" unless value.to_s.match "^#{pattern}$"
289
+ else
290
+ Occi::Log.warn "[#{self.class}] Skipping pattern checks on attributes, turn off " \
291
+ "the compatibility mode and enable the attribute pattern check in settings!"
292
+ end
293
+ end
294
+
295
+ def add_missing_attributes(attributes, definitions, set_defaults)
296
+ definitions.each_key do |key|
297
+ next if key =~ /^_/
298
+
299
+ if definitions[key].kind_of? Occi::Core::Attributes
300
+ add_missing_attributes(attributes[key], definitions[key], set_defaults)
301
+ elsif attributes[key].nil?
302
+
303
+ if definitions[key].default.nil?
304
+ raise Occi::Errors::AttributeMissingError,
305
+ "Required attribute #{key} not specified" if definitions[key].required
306
+ else
307
+ attributes[key] = definitions[key].default if definitions[key].required || set_defaults
308
+ end
309
+
310
+ end
311
+ end
312
+ end
313
+
314
+ def check_wrt_definitions(attributes, definitions, set_defaults)
315
+ attributes.each_key do |key|
316
+ next if key =~ /^_/
317
+
318
+ #Raise exception for attributes not defined at all
319
+ raise Occi::Errors::AttributeNotDefinedError,
320
+ "Attribute #{key} not found in definitions" unless definitions.key?(key)
321
+
322
+ if attributes[key].kind_of? Occi::Core::Attributes
323
+ check_wrt_definitions(attributes[key], definitions[key], set_defaults)
324
+ else
325
+ next if attributes[key].nil? # I will be removed in the next step
326
+
327
+ #Check value types
328
+ definitions[key].check_value_for_type(attributes[key], key)
329
+
330
+ # Check patterns
331
+ if definitions[key].pattern
332
+ if Occi::Settings.verify_attribute_pattern && !Occi::Settings.compatibility
333
+ raise Occi::Errors::AttributeTypeError,
334
+ "Attribute #{key} with value #{attributes[key]} does not " \
335
+ "match pattern #{definitions[key].pattern}" unless attributes[key].to_s.match "^#{definitions[key].pattern}$"
336
+ else
337
+ Occi::Log.warn "[#{self.class}] [#{key}] Skipping pattern checks on attributes, turn off " \
338
+ "the compatibility mode and enable the attribute pattern check in settings!"
339
+ end
340
+ end
341
+ end
342
+ end
343
+ end
344
+
345
+ def delete_empty(attributes)
346
+ attributes.each_key do |key|
347
+ if attributes[key].kind_of? Occi::Core::Attributes
348
+ delete_empty(attributes[key])
349
+ else
350
+ attributes.delete(key) if attributes[key].nil?
351
+ end
352
+ end
353
+ end
354
+ end
175
355
  end
176
- end
356
+ end
@@ -3,6 +3,7 @@ module Occi
3
3
  class Categories < Set
4
4
 
5
5
  include Occi::Helpers::Inspect
6
+ include Occi::Helpers::Comparators::Categories
6
7
 
7
8
  attr_accessor :model
8
9
 
@@ -3,6 +3,7 @@ module Occi
3
3
  class Category
4
4
 
5
5
  include Occi::Helpers::Inspect
6
+ include Occi::Helpers::Comparators::Category
6
7
 
7
8
  attr_accessor :scheme, :term, :title, :attributes, :model
8
9
 
@@ -14,21 +15,26 @@ module Occi
14
15
  term='category',
15
16
  title=nil,
16
17
  attributes=Occi::Core::Attributes.new)
17
- scheme += '#' unless scheme.end_with? '#'
18
+ raise ArgumentError, 'scheme and term cannot be nil' unless scheme && term
19
+ raise ArgumentError, 'scheme and term cannot be empty' if scheme.empty? || term.empty?
20
+ attributes ||= Occi::Core::Attributes.new
21
+
22
+ scheme << '#' unless scheme.end_with? '#'
18
23
  @scheme = scheme
19
24
  @term = term
20
25
  @title = title
26
+
21
27
  case attributes
22
28
  when Occi::Core::Attributes
23
- @attributes = attributes
29
+ @attributes = Occi::Core::Attributes.new(attributes)
24
30
  else
25
- @attributes = Occi::Core::Attributes.parse attributes
31
+ @attributes = Occi::Core::Attributes.parse_properties attributes
26
32
  end
27
33
  end
28
34
 
29
35
  # @return [String] Type identifier of the Category.
30
36
  def type_identifier
31
- self.scheme + self.term
37
+ "#{self.scheme}#{self.term}"
32
38
  end
33
39
 
34
40
  # @param options [Hash]
@@ -38,25 +44,25 @@ module Occi
38
44
  category.scheme = self.scheme
39
45
  category.term = self.term
40
46
  category.title = self.title if self.title
41
- category.attributes = self.attributes if self.attributes.any?
47
+ category.attributes = self.attributes.any? ? self.attributes.as_json : Occi::Core::Attributes.new.as_json
42
48
  category
43
49
  end
44
50
 
45
51
  # @return [String] Short text representation of the Category.
46
52
  def to_string_short
47
- self.term + ';scheme=' + self.scheme.inspect + ';class=' + self.class.name.demodulize.downcase.inspect
53
+ "#{self.term};scheme=#{self.scheme.inspect};class=#{self.class.name.demodulize.downcase.inspect}"
48
54
  end
49
55
 
50
56
  # @return [String] Full text representation of the Category.
51
57
  def to_string
52
58
  string = self.to_string_short
53
- string << ';title=' + self.title.inspect if self.title
59
+ string << ";title=#{self.title.inspect}" if self.title
54
60
  string
55
61
  end
56
62
 
57
63
  # @return [String] Text representation of the Category.
58
64
  def to_text
59
- 'Category: ' + self.to_string
65
+ "Category: #{self.to_string}"
60
66
  end
61
67
 
62
68
  # @return [Hash] Hash containing the HTTP headers of the text/occi rendering.
@@ -74,6 +80,17 @@ module Occi
74
80
  self.type_identifier
75
81
  end
76
82
 
83
+ # @return [Bool] Indicating whether this category is "empty", i.e. required attributes are blank
84
+ def empty?
85
+ term.blank? || scheme.blank?
86
+ end
87
+
88
+ # @param term [String] Term to check.
89
+ # @return [Bool] Indicating whether term consists exclusively of valid characters.
90
+ def self.valid_term?(term)
91
+ term =~ /^[a-z][a-z0-9_-]*$/
92
+ end
93
+
77
94
  end
78
95
  end
79
96
  end
@@ -3,6 +3,7 @@ module Occi
3
3
  class Entities < Set
4
4
 
5
5
  include Occi::Helpers::Inspect
6
+ include Occi::Helpers::Comparators::Entities
6
7
 
7
8
  attr_accessor :model
8
9