occi-core 4.1.3 → 4.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.
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