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
@@ -1,250 +1,211 @@
1
+ Dir[File.join(File.dirname(__FILE__), 'text', '*.rb')].each { |file| require file.gsub('.rb', '') }
2
+
1
3
  module Occi
2
4
  module Parser
3
5
  module Text
4
6
 
5
- # Regular expressions
6
- REGEXP_QUOTED_STRING = /([^"\\]|\\.)*/
7
- REGEXP_LOALPHA = /[a-z]/
8
- REGEXP_DIGIT = /[0-9]/
9
- REGEXP_INT = /#{REGEXP_DIGIT}+/
10
- REGEXP_FLOAT = /#{REGEXP_INT}\.#{REGEXP_INT}/
11
- REGEXP_NUMBER = /#{REGEXP_FLOAT}|#{REGEXP_INT}/
12
- REGEXP_BOOL = /true|false/
13
-
14
- # Regular expressions for OCCI
15
- if Occi::Settings.compatibility
16
- # Compatibility with terms starting with a number
17
- REGEXP_TERM = /(#{REGEXP_LOALPHA}|#{REGEXP_DIGIT})(#{REGEXP_LOALPHA}|#{REGEXP_DIGIT}|-|_)*/
18
- else
19
- REGEXP_TERM = /#{REGEXP_LOALPHA}(#{REGEXP_LOALPHA}|#{REGEXP_DIGIT}|-|_)*/
20
- end
21
- REGEXP_SCHEME = /#{URI::ABS_URI_REF}#/
22
- REGEXP_TYPE_IDENTIFIER = /#{REGEXP_SCHEME}#{REGEXP_TERM}/
23
- REGEXP_CLASS = /action|mixin|kind/
24
-
25
- REGEXP_ATTR_COMPONENT = /#{REGEXP_LOALPHA}(#{REGEXP_LOALPHA}|#{REGEXP_DIGIT}|-|_)*/
26
- REGEXP_ATTRIBUTE_NAME = /#{REGEXP_ATTR_COMPONENT}(\.#{REGEXP_ATTR_COMPONENT})*/
27
- REGEXP_ATTRIBUTE_PROPERTY = /immutable|required/
28
- REGEXP_ATTRIBUTE_DEF = /(#{REGEXP_ATTRIBUTE_NAME})(\{#{REGEXP_ATTRIBUTE_PROPERTY}(\s+#{REGEXP_ATTRIBUTE_PROPERTY})*\})?/
29
- REGEXP_ATTRIBUTE_LIST = /#{REGEXP_ATTRIBUTE_DEF}(\s+#{REGEXP_ATTRIBUTE_DEF})*/
30
- REGEXP_ATTRIBUTE_REPR = /#{REGEXP_ATTRIBUTE_NAME}=("#{REGEXP_QUOTED_STRING}"|#{REGEXP_NUMBER}|#{REGEXP_BOOL})/
31
-
32
- REGEXP_ACTION = /#{REGEXP_TYPE_IDENTIFIER}/
33
- REGEXP_ACTION_LIST = /#{REGEXP_ACTION}(\s+#{REGEXP_ACTION})*/
34
-
35
- REGEXP_RESOURCE_TYPE = /#{REGEXP_TYPE_IDENTIFIER}(\s+#{REGEXP_TYPE_IDENTIFIER})*/
36
- REGEXP_LINK_INSTANCE = /#{URI::URI_REF}/
37
- REGEXP_LINK_TYPE = /#{REGEXP_TYPE_IDENTIFIER}(\s+#{REGEXP_TYPE_IDENTIFIER})*/
38
-
39
- # Regular expression for OCCI Categories
40
- if Occi::Settings.compatibility
41
- REGEXP_CATEGORY = "Category:\\s*(?<term>#{REGEXP_TERM})" << # term (mandatory)
42
- ";\\s*scheme=\"(?<scheme>#{REGEXP_SCHEME})#{REGEXP_TERM}?\"" << # scheme (mandatory)
43
- ";\\s*class=\"(?<class>#{REGEXP_CLASS})\"" << # class (mandatory)
44
- "(;\\s*title=\"(?<title>#{REGEXP_QUOTED_STRING})\")?" << # title (optional)
45
- "(;\\s*rel=\"(?<rel>#{REGEXP_TYPE_IDENTIFIER})\")?"<< # rel (optional)
46
- "(;\\s*location=\"(?<location>#{URI::URI_REF})\")?" << # location (optional)
47
- "(;\\s*attributes=\"(?<attributes>#{REGEXP_ATTRIBUTE_LIST})\")?" << # attributes (optional)
48
- "(;\\s*actions=\"(?<actions>#{REGEXP_ACTION_LIST})\")?" << # actions (optional)
49
- ';?' # additional semicolon at the end (not specified, for interoperability)
50
- else
51
- REGEXP_CATEGORY = "Category:\\s*(?<term>#{REGEXP_TERM})" << # term (mandatory)
52
- ";\\s*scheme=\"(?<scheme>#{REGEXP_SCHEME})\"" << # scheme (mandatory)
53
- ";\\s*class=\"(?<class>#{REGEXP_CLASS})\"" << # class (mandatory)
54
- "(;\\s*title=\"(?<title>#{REGEXP_QUOTED_STRING})\")?" << # title (optional)
55
- "(;\\s*rel=\"(?<rel>#{REGEXP_TYPE_IDENTIFIER})\")?"<< # rel (optional)
56
- "(;\\s*location=\"(?<location>#{URI::URI_REF})\")?" << # location (optional)
57
- "(;\\s*attributes=\"(?<attributes>#{REGEXP_ATTRIBUTE_LIST})\")?" << # attributes (optional)
58
- "(;\\s*actions=\"(?<actions>#{REGEXP_ACTION_LIST})\")?" << # actions (optional)
59
- ';?' # additional semicolon at the end (not specified, for interoperability)
60
- end
61
-
62
- # Regular expression for OCCI Link Instance References
63
- if Occi::Settings.compatibility
64
- REGEXP_LINK = "Link:\\s*\\<(?<uri>#{URI::URI_REF})\\>" << # uri (mandatory)
65
- ";\\s*rel=\"(?<rel>#{REGEXP_RESOURCE_TYPE})\"" << # rel (mandatory)
66
- "(;\\s*self=\"(?<self>#{REGEXP_LINK_INSTANCE})\")?" << # self (optional)
67
- "(;\\s*category=\"(?<category>(;?\\s*(#{REGEXP_LINK_TYPE}))+)\")?" << # category (optional)
68
- "(?<attributes>(;?\\s*(#{REGEXP_ATTRIBUTE_REPR}))*)" << # attributes (optional)
69
- ';?' # additional semicolon at the end (not specified, for interoperability)
70
- else
71
- REGEXP_LINK = "Link:\\s*\\<(?<uri>#{URI::URI_REF})\\>" << # uri (mandatory)
72
- ";\\s*rel=\"(?<rel>#{REGEXP_RESOURCE_TYPE})\"" << # rel (mandatory)
73
- "(;\\s*self=\"(?<self>#{REGEXP_LINK_INSTANCE})\")?" << # self (optional)
74
- "(;\\s*category=\"(?<category>(;?\\s*(#{REGEXP_LINK_TYPE}))+)\")?" << # category (optional)
75
- "(?<attributes>(;\\s*(#{REGEXP_ATTRIBUTE_REPR}))*)" << # attributes (optional)
76
- ';?' # additional semicolon at the end (not specified, for interoperability)
77
- end
7
+ class << self
78
8
 
79
- # Regular expression for OCCI Entity Attributes
80
- REGEXP_ATTRIBUTE = "X-OCCI-Attribute:\\s*(?<name>#{REGEXP_ATTRIBUTE_NAME})=(\"(?<string>#{REGEXP_QUOTED_STRING})\"|(?<number>#{REGEXP_NUMBER})|(?<bool>#{REGEXP_BOOL}))" <<
81
- ';?' # additional semicolon at the end (not specified, for interoperability)
9
+ include Occi::Parser::Text::Constants
82
10
 
83
- # Regular expression for OCCI Location
84
- REGEXP_LOCATION = "X-OCCI-Location:\\s*(?<location>#{URI::URI_REF})" <<
85
- ';?' # additional semicolon at the end (not specified, for interoperability)
11
+ def categories(lines)
12
+ Occi::Log.debug "[#{self}] Parsing through Occi::Parser::Text.categories"
13
+ collection = Occi::Collection.new
86
14
 
15
+ block = Proc.new { |line|
16
+ line.strip!
17
+ category = category(line) if line.start_with? 'Category:'
18
+ collection << category if category.kind_of? Occi::Core::Category
19
+ }
87
20
 
88
- def self.categories(lines)
89
- collection = Occi::Collection.new
90
- lines.each do |line|
91
- line.strip!
92
- category = self.category(line) if line.start_with? 'Category:'
93
- collection << category if category.kind_of? Occi::Core::Category
21
+ lines.respond_to?(:each) ? lines.each(&block) : lines.each_line(&block)
22
+ collection
94
23
  end
95
- collection
96
- end
97
24
 
98
- def self.resource(lines)
99
- collection = Occi::Collection.new
100
- resource = Occi::Core::Resource.new
101
- lines.each do |line|
102
- line.strip!
103
- case line
25
+ def resource(lines)
26
+ Occi::Log.debug "[#{self}] Parsing through Occi::Parser::Text.resource"
27
+ collection = Occi::Collection.new
28
+ resource = Occi::Core::Resource.new
29
+ resource.id = nil
30
+ links = []
31
+
32
+ block = Proc.new { |line|
33
+ line.strip!
34
+ case line
104
35
  when /^Category:/
105
- category = self.category(line)
106
- resource.kind = category if category.kind_of? Occi::Core::Kind
36
+ category = category(line)
37
+
38
+ if category.kind_of? Occi::Core::Kind
39
+ resource = Occi::Core::Entity.new(category.type_identifier)
40
+ resource.kind = category
41
+ end
107
42
  resource.mixins << category if category.kind_of? Occi::Core::Mixin
108
43
  when /^X-OCCI-Attribute:/
109
- resource.attributes.merge! self.attribute(line)
44
+ resource.attributes.merge! attribute(line)
110
45
  when /^Link:/
111
- link = self.link_string(line, resource)
46
+ link = link_string(line, resource)
112
47
  resource.links << link
113
- collection << link
48
+ links << link
49
+ end
50
+ }
51
+ lines.respond_to?(:each) ? lines.each(&block) : lines.each_line(&block)
52
+
53
+ if resource.kind_of?(Occi::Core::Resource) && !resource.empty?
54
+ collection << resource
55
+ links.each { |link| collection << link }
114
56
  end
57
+
58
+ collection
115
59
  end
116
- collection << resource if resource.kind_of? Occi::Core::Resource
117
- collection
118
- end
119
60
 
120
- def self.link(lines)
121
- collection = Occi::Collection.new
122
- link = Occi::Core::Link.new
123
- lines.each do |line|
124
- line.strip!
125
- case line
61
+ def link(lines)
62
+ Occi::Log.debug "[#{self}] Parsing through Occi::Parser::Text.link"
63
+ collection = Occi::Collection.new
64
+ link = Occi::Core::Link.new
65
+ link.id = nil
66
+
67
+ block = Proc.new { |line|
68
+ line.strip!
69
+ case line
126
70
  when /^Category:/
127
- category = self.category(line)
71
+ category = category(line)
128
72
  link.kind = category if category.kind_of? Occi::Core::Kind
129
73
  link.mixins << category if category.kind_of? Occi::Core::Mixin
130
74
  when /^X-OCCI-Attribute:/
131
- link.attributes.merge! self.attribute(line)
132
- end
75
+ link.attributes.merge! attribute(line)
76
+ end
77
+ }
78
+ lines.respond_to?(:each) ? lines.each(&block) : lines.each_line(&block)
79
+
80
+ collection << link if link.kind_of?(Occi::Core::Link) && !link.empty?
81
+ collection
133
82
  end
134
- collection << link if link.kind_of? Occi::Core::Link
135
- collection
136
- end
137
83
 
138
- def self.locations(lines)
139
- locations = []
140
- lines.each do |line|
141
- line.strip!
142
- locations << self.location(line) if line.start_with? 'X-OCCI-Location:'
84
+ def locations(lines)
85
+ Occi::Log.debug "[#{self}] Parsing through Occi::Parser::Text.locations"
86
+ locations = []
87
+
88
+ block = Proc.new { |line|
89
+ line.strip!
90
+ locations << location(line) if line.start_with? 'X-OCCI-Location:'
91
+ }
92
+ lines.respond_to?(:each) ? lines.each(&block) : lines.each_line(&block)
93
+
94
+ locations
143
95
  end
144
- locations
145
- end
146
96
 
147
- private
148
-
149
- def self.category(string)
150
- # create regular expression from regexp string
151
- regexp = Regexp.new(REGEXP_CATEGORY)
152
- # match string to regular expression
153
- match = regexp.match string
154
-
155
- raise "could not match #{string}" unless match
156
-
157
- term = match[:term]
158
- scheme = match[:scheme]
159
- title = match[:title]
160
- related = match[:rel].to_s.split
161
- attributes = Occi::Core::Attributes.new
162
- if match[:attributes]
163
- matched_attributes = match[:attributes].gsub(/\{(immutable|required)\s+(required|immutable)\}/, '{\1_\2}')
164
- matched_attributes.split.each do |attribute|
165
- attribute.gsub! /\{(immutable|required)_(required|immutable)\}/, '{\1 \2}'
166
- property_string = attribute[/#{REGEXP_ATTRIBUTE_DEF}/, -2]
167
- properties = Occi::Core::Properties.new
168
- if property_string
169
- properties.required = true if property_string.include? 'required'
170
- properties.mutable = false if property_string.include? 'immutable'
97
+ def category(string)
98
+ Occi::Log.debug "[#{self}] Parsing through Occi::Parser::Text.category"
99
+ # create regular expression from regexp string
100
+ regexp = Regexp.new( Occi::Settings.compatibility ? REGEXP_CATEGORY : REGEXP_CATEGORY_STRICT )
101
+ # match string to regular expression
102
+ match = regexp.match string
103
+
104
+ raise Occi::Errors::ParserInputError, "could not match #{string}" unless match
105
+
106
+ term = match[:term].downcase
107
+ scheme = match[:scheme]
108
+ title = match[:title]
109
+ related = match[:rel].to_s.split
110
+
111
+ attributes = Occi::Core::Attributes.new
112
+ if match[:attributes]
113
+ match[:attributes].split.each do |attribute|
114
+ property_string = attribute[/#{REGEXP_ATTRIBUTE_DEF}/, -2]
115
+ properties = Occi::Core::Properties.new
116
+
117
+ if property_string
118
+ properties.required = true if property_string.include? 'required'
119
+ properties.mutable = false if property_string.include? 'immutable'
120
+ end
121
+
122
+ name = attribute[/#{REGEXP_ATTRIBUTE_DEF}/, 1]
123
+ attributes.merge! name.split('.').reverse.inject(properties) { |a, n| Occi::Core::Attributes.new(n => a) }
171
124
  end
172
- name = attribute[/#{REGEXP_ATTRIBUTE_DEF}/, 1]
173
- attributes.merge! name.split('.').reverse.inject(properties) { |a, n| Occi::Core::Attributes.new(n => a) }
174
125
  end
175
- end
176
- actions = match[:actions].to_s.split
177
- location = match[:location]
178
- case match[:class]
126
+ actions = match[:actions].to_s.split
127
+ location = match[:location]
128
+
129
+ case match[:class]
179
130
  when 'kind'
131
+ Occi::Log.debug "[#{self}] class #{match[:class]} identified as kind"
180
132
  Occi::Core::Kind.new scheme, term, title, attributes, related, actions, location
181
133
  when 'mixin'
134
+ Occi::Log.debug "[#{self}] class #{match[:class]} identified as mixin"
182
135
  Occi::Core::Mixin.new scheme, term, title, attributes, related, actions, location
183
136
  when 'action'
137
+ Occi::Log.debug "[#{self}] class #{match[:class]} identified as action"
184
138
  Occi::Core::Action.new scheme, term, title, attributes
185
139
  else
186
- raise "Category with class #{match[:class]} not recognized in string: #{string}"
140
+ raise Occi::Errors::ParserInputError, "Category with class #{match[:class]} not recognized in string: #{string}"
141
+ end
187
142
  end
188
- end
189
143
 
190
- def self.attribute(string)
191
- # create regular expression from regexp string
192
- regexp = Regexp.new(REGEXP_ATTRIBUTE)
193
- # match string to regular expression
194
- match = regexp.match string
144
+ def attribute(string)
145
+ Occi::Log.debug "[#{self}] Parsing through Occi::Parser::Text.attribute"
146
+ # create regular expression from regexp string
147
+ regexp = Regexp.new(REGEXP_ATTRIBUTE)
148
+ # match string to regular expression
149
+ match = regexp.match string
150
+
151
+ raise Occi::Errors::ParserInputError, "could not match #{string}" unless match
195
152
 
196
- raise "could not match #{string}" unless match
153
+ value = match[:string] if match[:string]
197
154
 
198
- value = match[:string] if match[:string]
155
+ if match[:number]
156
+ match[:number].include?('.') ? value = match[:number].to_f : value = match[:number].to_i
157
+ end
199
158
 
200
- if match[:number]
201
- match[:number].include?('.') ? value = match[:number].to_f : value = match[:number].to_i
159
+ value = match[:bool] == "true" if match[:bool]
160
+ Occi::Core::Attributes.split match[:name] => value
202
161
  end
203
162
 
204
- value = match[:bool] == "true" if match[:bool]
205
- Occi::Core::Attributes.split match[:name] => value
206
- end
163
+ def link_string(string, source)
164
+ Occi::Log.debug "[#{self}] Parsing through Occi::Parser::Text.link_string"
165
+ # create regular expression from regexp string
166
+ regexp = Regexp.new( Occi::Settings.compatibility ? REGEXP_LINK : REGEXP_LINK_STRICT )
167
+ # match string to regular expression
168
+ match = regexp.match string
207
169
 
208
- def self.link_string(string, source)
209
- # create regular expression from regexp string
210
- regexp = Regexp.new(REGEXP_LINK)
211
- # match string to regular expression
212
- match = regexp.match string
213
-
214
- raise "could not match #{string}" unless match
215
-
216
- target = match[:uri]
217
- rel = match[:rel]
218
- if match[:category].blank?
219
- kind = Occi::Core::Link.kind
220
- else
221
- categories = match[:category].split
222
- kind = categories.shift
223
- mixins = categories
170
+ raise Occi::Errors::ParserInputError, "could not match #{string}" unless match
171
+
172
+ target = match[:uri]
173
+ rel = match[:rel]
174
+ if match[:category].blank?
175
+ kind = Occi::Core::Link.kind
176
+ else
177
+ categories = match[:category].split
178
+ kind = categories.shift
179
+ mixins = categories
180
+ end
181
+ actions = nil
182
+ location = match[:self]
183
+
184
+ # create an array of the list of attributes
185
+ attributes = []
186
+ regexp=Regexp.new '(\\s*'+REGEXP_ATTRIBUTE_REPR.to_s+')'
187
+ attr_line = match[:attributes].sub(/^\s*;\s*/, ' ')
188
+ attributes = attr_line.scan(regexp).collect {|matches| matches.first}
189
+
190
+ # parse each attribute and create an OCCI Attribute object from it
191
+ attributes = attributes.inject(Hashie::Mash.new) { |hsh, attribute|
192
+ hsh.merge!(Occi::Parser::Text.attribute("X-OCCI-Attribute: #{attribute}"))
193
+ }
194
+ Occi::Core::Link.new kind, mixins, attributes, actions, rel, target, source, location
224
195
  end
225
- actions = nil
226
- location = match[:self]
227
-
228
- # create an array of the list of attributes
229
- attributes = []
230
- regexp=Regexp.new '(\\s*'+REGEXP_ATTRIBUTE_REPR.to_s+')'
231
- attr_line = match[:attributes].sub(/^\s*;\s*/, ' ')
232
- attributes = attr_line.scan(regexp).collect {|matches| matches.first}
233
-
234
- # parse each attribute and create an OCCI Attribute object from it
235
- attributes = attributes.inject(Hashie::Mash.new) { |hsh, attribute| hsh.merge!(Occi::Parser::Text.attribute('X-OCCI-Attribute: ' + attribute)) }
236
- Occi::Core::Link.new kind, mixins, attributes, actions, rel, target, source, location
237
- end
238
196
 
239
- def self.location(string)
240
- # create regular expression from regexp string
241
- regexp = Regexp.new(REGEXP_LOCATION)
242
- # match string to regular expression
243
- match = regexp.match string
197
+ def location(string)
198
+ Occi::Log.debug "[#{self}] Parsing through Occi::Parser::Text.location"
199
+ # create regular expression from regexp string
200
+ regexp = Regexp.new(REGEXP_LOCATION)
201
+ # match string to regular expression
202
+ match = regexp.match string
244
203
 
245
- raise "could not match #{string}" unless match
204
+ raise Occi::Errors::ParserInputError, "could not match #{string}" unless match
205
+
206
+ match[:location]
207
+ end
246
208
 
247
- match[:location]
248
209
  end
249
210
 
250
211
  end
@@ -4,14 +4,16 @@ module Occi
4
4
  # @param [String] string
5
5
  # @return [Occi::Collection]
6
6
  def self.collection(string)
7
- collection = Occi::Collection.new
8
- hash = Hashie::Mash.new(Hash.from_xml(Nokogiri::XML(string)))
9
- collection.kinds.merge hash.kinds.collect { |kind| Occi::Core::Kind.new(kind.scheme, kind.term, kind.title, kind.attributes, kind.related, kind.actions) } if hash.kinds
10
- collection.mixins.merge hash.mixins.collect { |mixin| Occi::Core::Mixin.new(mixin.scheme, mixin.term, mixin.title, mixin.attributes, mixin.related, mixin.actions) } if hash.mixins
11
- collection.actions.merge hash.actions.collect { |action| Occi::Core::Action.new(action.scheme, action.term, action.title, action.attributes) } if hash.actions
12
- collection.resources.merge hash.resources.collect { |resource| Occi::Core::Resource.new(resource.kind, resource.mixins, resource.attributes, resource.actions, resource.links) } if hash.resources
13
- collection.links.merge hash.links.collect { |link| Occi::Core::Link.new(link.kind, link.mixins, link.attributes) } if hash.links
14
- collection
7
+
8
+ begin
9
+ parsed_xml = Nokogiri::XML(string) { |config| config.strict.nonet }
10
+ rescue Nokogiri::XML::SyntaxError => perr
11
+ Occi::Log.error "[#{self}] Failed to parse XML input: #{perr.message}"
12
+ raise Occi::Errors::ParserInputError, perr.message
13
+ end
14
+
15
+ hash = Hashie::Mash.new(Hash.from_xml(parsed_xml))
16
+ Occi::Collection.new(hash)
15
17
  end
16
18
  end
17
19
  end
data/lib/occi/parser.rb CHANGED
@@ -1,51 +1,86 @@
1
- require 'occi/parser/text'
2
- require 'occi/parser/json'
3
- require 'occi/parser/xml'
4
- require 'occi/parser/ova'
5
- require 'occi/parser/ovf'
1
+ Dir[File.join(File.dirname(__FILE__), 'parser', '*.rb')].each { |file| require file.gsub('.rb', '') }
6
2
 
7
3
  module Occi
8
4
  module Parser
9
5
 
10
- # Parses an OCCI message and extracts OCCI relevant information
11
- # @param [String] media_type the media type of the OCCI message
12
- # @param [String] body the body of the OCCI message
13
- # @param [true, false] category for text/plain and text/occi media types information e.g. from the HTTP request location is needed to determine if the OCCI message includes a category or an entity
14
- # @param [Occi::Core::Resource,Occi::Core::Link] entity_type entity type to use for parsing of text plain entities
15
- # @param [Hash] header optional header of the OCCI message
16
- # @return [Occi::Collection] list consisting of an array of locations and the OCCI object collection
17
- def self.parse(media_type, body, category=false, entity_type=Occi::Core::Resource, header={})
18
- Occi::Log.debug '### Parsing request data to OCCI Collection ###'
19
- collection = Occi::Collection.new
20
-
21
- # remove trailing HTTP_ prefix if present
22
- header = Hash[header.map { |k, v| [k.gsub('HTTP_', '').upcase, v] }]
23
-
24
- if category
25
- collection = Occi::Parser::Text.categories(header.map { |k, v| v.to_s.split(',').collect { |w| "#{k}: #{w}" } }.flatten)
26
- else
27
- if entity_type == Occi::Core::Resource
28
- collection = Occi::Parser::Text.resource(header.map { |k, v| v.to_s.split(',').collect { |w| "#{k}: #{w}" } }.flatten)
29
- elsif entity_type == Occi::Core::Link
30
- collection = Occi::Parser::Text.link(header.map { |k, v| v.to_s.split(',').collect { |w| "#{k}: #{w}" } }.flatten)
31
- end
6
+ class << self
7
+
8
+ OCCI_HEADERS = ['Category', 'Link', 'X-OCCI-Location', 'X-OCCI-Attribute', 'Location'].freeze
9
+
10
+ # Parses an OCCI message and extracts OCCI relevant information
11
+ # @param [String] media_type the media type of the OCCI message
12
+ # @param [String] body the body of the OCCI message
13
+ # @param [true, false] category for text/plain and text/occi media types information e.g. from the HTTP request location is needed to determine if the OCCI message includes a category or an entity
14
+ # @param [Occi::Core::Resource,Occi::Core::Link] entity_type entity type to use for parsing of text plain entities
15
+ # @param [Hash] header optional header of the OCCI message
16
+ # @return [Occi::Collection] list consisting of an array of locations and the OCCI object collection
17
+ def parse(media_type, body, category=false, entity_type=Occi::Core::Resource, header={})
18
+ Occi::Log.debug "[#{self}] Parsing request data to OCCI Collection"
19
+ header = headers_to_arys(header)
20
+
21
+ Occi::Log.debug "[#{self}] Parsing headers: #{header.inspect}"
22
+ collection = parse_headers(header, category, entity_type)
23
+
24
+ Occi::Log.debug "[#{self}] Parsing #{media_type} from body"
25
+ coll_body = parse_body(media_type, body, category, entity_type)
26
+ collection.merge! coll_body if coll_body && !coll_body.empty?
27
+
28
+ collection
32
29
  end
33
30
 
34
- case media_type
31
+ def locations(media_type, body, header)
32
+ locations = []
33
+ locations << header['Location'] if header['Location'] && !header['Location'].blank?
34
+ header = headers_to_arys(header)
35
+
36
+ Occi::Log.debug "[#{self}] Parsing locations from request headers: #{header.inspect}"
37
+ locations << Occi::Parser::Text.locations(header)
38
+
39
+ Occi::Log.debug "[#{self}] Parsing #{media_type} locations from body"
40
+ case media_type
35
41
  when 'text/uri-list'
42
+ locations << body.split("\n")
43
+ when 'text/plain', nil
44
+ locations << Occi::Parser::Text.locations(body.split "\n")
45
+ else
36
46
  nil
47
+ end
48
+
49
+ locations.flatten
50
+ end
51
+
52
+ private
53
+
54
+ def parse_headers(header, category, entity_type)
55
+ if category
56
+ Occi::Log.debug "[#{self}] Parsing categories from headers"
57
+ collection = Occi::Parser::Text.categories(header)
58
+ else
59
+ if entity_type == Occi::Core::Resource
60
+ Occi::Log.debug "[#{self}] Parsing a resource from headers"
61
+ collection = Occi::Parser::Text.resource(header)
62
+ elsif entity_type == Occi::Core::Link
63
+ Occi::Log.debug "[#{self}] Parsing a link from headers"
64
+ collection = Occi::Parser::Text.link(header)
65
+ else
66
+ raise Occi::Errors::ParserTypeError, "Entity type '#{entity_type}' not supported"
67
+ end
68
+ end
69
+
70
+ collection
71
+ end
72
+
73
+ def parse_body(media_type, body, category, entity_type)
74
+ collection = Occi::Collection.new
75
+
76
+ case media_type
77
+ when 'text/uri-list'
78
+ raise Occi::Errors::ParserTypeError, "Type 'text/uri-list' not supported by parse(). Call method #{self}.locations() to parse URI lists"
37
79
  when 'text/occi'
80
+ Occi::Log.warn "Input type text/occi was passed to the parser in request body. All text/occi content MUST be passed in headers. Request body was not processed. The unprocessed content follows\n#{body}" unless body.blank?
38
81
  nil
39
82
  when 'text/plain', nil
40
- if category
41
- collection = Occi::Parser::Text.categories body.split "\n"
42
- else
43
- if entity_type == Occi::Core::Resource
44
- collection = Occi::Parser::Text.resource body.split "\n"
45
- elsif entity_type == Occi::Core::Link
46
- collection = Occi::Parser::Text.link body.split "\n"
47
- end
48
- end
83
+ collection = parse_body_plain(body, category, entity_type)
49
84
  when 'application/occi+json', 'application/json'
50
85
  collection = Occi::Parser::Json.collection body
51
86
  when 'application/occi+xml', 'application/xml'
@@ -55,24 +90,39 @@ module Occi
55
90
  when 'application/ova'
56
91
  collection = Occi::Parser::Ova.collection body
57
92
  else
58
- raise "Content Type not supported"
93
+ raise Occi::Errors::ParserTypeError, "Content type #{media_type} not supported"
94
+ end
95
+
96
+ collection
59
97
  end
60
- collection
61
- end
62
98
 
63
- def self.locations(media_type, body, header)
64
- locations = Occi::Parser::Text.locations header.map { |k, v| v.to_s.split(',').collect { |w| "#{k}: #{w}" } }.flatten
65
- locations << header['Location'] if !header['Location'].nil? && header['Location'].any?
66
- case media_type
67
- when 'text/uri-list'
68
- locations << body.split("\n")
69
- when 'text/plain', nil
70
- locations << Occi::Parser::Text.locations(body.split "\n")
99
+ def parse_body_plain(body, category, entity_type)
100
+ if category
101
+ collection = Occi::Parser::Text.categories body.split "\n"
71
102
  else
72
- nil
103
+ if entity_type == Occi::Core::Resource
104
+ collection = Occi::Parser::Text.resource body.split "\n"
105
+ elsif entity_type == Occi::Core::Link
106
+ collection = Occi::Parser::Text.link body.split "\n"
107
+ else
108
+ raise Occi::Errors::ParserTypeError, "Entity type #{entity_type} not supported"
109
+ end
110
+ end
111
+
112
+ collection
113
+ end
114
+
115
+ def headers_to_arys(header)
116
+ # remove the HTTP_ prefix if present and capitalize keys
117
+ header = Hash[header.map { |k, v| [k.gsub('HTTP_', '').capitalize, v] }]
118
+ header['X-OCCI-Location'] = header['X-occi-location'] if header['X-occi-location']
119
+ header['X-OCCI-Attribute'] = header['X-occi-attribute'] if header['X-occi-attribute']
120
+
121
+ header.delete_if { |k, v| v.blank? || !OCCI_HEADERS.include?(k) }
122
+
123
+ header.map { |k, v| v.to_s.split(',').collect { |w| "#{k}: #{w}" } }.flatten
73
124
  end
74
125
 
75
- locations.flatten
76
126
  end
77
127
 
78
128
  end
data/lib/occi/settings.rb CHANGED
@@ -2,8 +2,9 @@ module Occi
2
2
  class Settings < Settingslogic
3
3
  gem_root = File.expand_path '../../..', __FILE__
4
4
 
5
+ source "#{ENV['HOME']}/.occi" if File.readable?("#{ENV['HOME']}/.occi")
5
6
  source "#{gem_root}/config/occi.yml"
6
- source open(ENV['HOME']+'/.occi') if File.file?(ENV['HOME']+'/.occi')
7
+
7
8
  namespace 'core'
8
9
  end
9
10
  end
data/lib/occi/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Occi
2
- VERSION = "4.1.3" unless defined?(::Occi::VERSION)
2
+ VERSION = "4.2.0" unless defined?(::Occi::VERSION)
3
3
  end