Class: Occi::Core::Parsers::Text::Entity

Inherits:
Object
  • Object
show all
Includes:
Helpers::ArgumentValidator, Helpers::ErrorHandler, Yell::Loggable
Defined in:
lib/occi/core/parsers/text/entity.rb

Overview

Static parsing class responsible for extracting entities from plain text. Class supports 'text/plain' via `plain`. No other formats are supported.

Author:

Constant Summary collapse

DELEGATED =

Shortcuts to interesting methods on logger

%i[debug? info? warn? error? fatal?].freeze
ATTRIBUTE_REGEXP =

Regexp constants

/#{Constants::REGEXP_ATTRIBUTE}/
/#{Constants::REGEXP_LINK}/
DEFAULT_LAMBDA =

Typecasting lambdas

->(val) { raise "#{self} -> Cannot typecast #{val.inspect} to an unknown type" }
FLOAT_LAMBDA =
->(val) { Float(val) rescue raise(Occi::Core::Errors::ParsingError, "Wrong value #{val}") }
JSON_LAMBDA =
->(val) { JSON.parse(val.gsub('\"', '"')) }
TYPECASTER_HASH =
{
  IPAddr  => ->(val) { IPAddr.new val },
  URI     => ->(val) { URI.parse val },
  String  => ->(val) { val },
  Float   => FLOAT_LAMBDA,
  Numeric => FLOAT_LAMBDA,
  Integer => ->(val) { Integer(val) rescue raise(Occi::Core::Errors::ParsingError, "Wrong value #{val}") },
  Boolean => ->(val) { val.casecmp('true') || val.casecmp('yes') },
  Array   => JSON_LAMBDA,
  Hash    => JSON_LAMBDA
}.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Helpers::ErrorHandler

#handle, included

Constructor Details

#initialize(args = {}) ⇒ Entity

Constructs an instance of the entity parser. Only entities (their kinds) defined by the model are allowed.

Options Hash (args):


48
49
50
51
52
53
54
55
# File 'lib/occi/core/parsers/text/entity.rb', line 48

def initialize(args = {})
  pre_initialize(args)
  default_args! args

  @model = args.fetch(:model)

  post_initialize(args)
end

Instance Attribute Details

#modelOcci::Core::Model, Occi::Infrastructure::Model

model to use as a primary reference point


11
12
13
# File 'lib/occi/core/parsers/text/entity.rb', line 11

def model
  @model
end

Class Method Details

.typecasterHash

Constructs a map pointing from expected attribute types to conversion lambdas.


245
246
247
# File 'lib/occi/core/parsers/text/entity.rb', line 245

def typecaster
  Hash.new(DEFAULT_LAMBDA).merge(TYPECASTER_HASH)
end

Instance Method Details

#plain(lines) ⇒ Occi::Core::Entity

Builds an entity instances from the lines provided as input.


61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/occi/core/parsers/text/entity.rb', line 61

def plain(lines)
  cats = plain_categories(lines)
  kind = cats.detect { |c| c.is_a?(Occi::Core::Kind) }
  raise Occi::Core::Errors::ParsingError, 'Entity does not specify its kind' unless kind
  logger.debug "Identified entity kind #{kind.inspect}" if logger_debug?

  entity = @_ib.build(kind.identifier)
  cats.each { |cat| cat.is_a?(Occi::Core::Mixin) && entity << cat }

  plain_attributes! lines, entity.attributes
  plain_links! lines, entity

  logger.debug "Created instance #{entity.inspect}" if logger_debug?
  entity
end

#plain_action!(md, entity) ⇒ Object

Looks up the action mentioned in the given action “link” and assigns it to the given partially constructed entity instance.


162
163
164
# File 'lib/occi/core/parsers/text/entity.rb', line 162

def plain_action!(md, entity)
  entity << handle(Occi::Core::Errors::ParsingError) { model.find_by_identifier!(md[:rel]) }
end

#plain_attributes!(lines, attributes) ⇒ Hash

Parses attributes from entity lines. Every attribute value is typed according to the attribute specification provided by the model (in the defined kind).


96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/occi/core/parsers/text/entity.rb', line 96

def plain_attributes!(lines, attributes)
  lines.each do |line|
    next unless line.start_with?(TextParser::ATTRIBUTE_KEYS.first)

    name, value = raw_attribute(line)
    unless attributes[name]
      raise Occi::Core::Errors::ParsingError,
            "Attribute #{name.inspect} is not allowed for this entity"
    end

    attributes[name].value = handle(Occi::Core::Errors::ParsingError) do
      typecast value, attributes[name].attribute_definition.type
    end
  end

  attributes
end

#plain_categories(lines) ⇒ Array

Parses categories from entity lines. Every category is looked up in the model.


81
82
83
84
85
86
87
88
# File 'lib/occi/core/parsers/text/entity.rb', line 81

def plain_categories(lines)
  categories = lines.map do |line|
    next unless line.start_with?(TextParser::CATEGORY_KEYS.first)
    cat = Category.plain_category(line, false)
    handle(Occi::Core::Errors::ParsingError) { model.find_by_identifier!("#{cat[:scheme]}#{cat[:term]}") }
  end
  categories.compact
end

#plain_link!(md, entity) ⇒ Object

Constructs a single link instance. This includes both action “links” and ordinary OCCI links.


153
154
155
# File 'lib/occi/core/parsers/text/entity.rb', line 153

def plain_link!(md, entity)
  md[:uri].include?('?action=') ? plain_action!(md, entity) : plain_oglink!(md, entity)
end

#plain_links!(lines, entity) ⇒ Occi::Core::Entity

Parses links attached to the entity instance. This includes both action “links” and ordinary OCCI links between resources.


133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/occi/core/parsers/text/entity.rb', line 133

def plain_links!(lines, entity)
  lines.each do |line|
    next unless line.start_with?(TextParser::LINK_KEYS.first)
    logger.debug "Parsing link line #{line.inspect}" if logger_debug?

    matched = line.match(LINK_REGEXP)
    unless matched
      raise Occi::Core::Errors::ParsingError, "#{line.inspect} does not match expectations for Link"
    end

    plain_link! matched, entity
  end

  entity
end

#plain_oglink!(md, entity) ⇒ Object

Constructs a single link instance. Supports only ordinary OCCI links between resources.


170
171
172
173
174
175
176
177
178
179
180
181
182
183
# File 'lib/occi/core/parsers/text/entity.rb', line 170

def plain_oglink!(md, entity)
  unless entity.respond_to?(:links)
    raise Occi::Core::Errors::ParsingError,
          "Cannot assign links to entity #{entity.id} which does not support them"
  end

  link = plain_oglink_instance(md)
  link.location = handle(Occi::Core::Errors::ParsingError) { URI.parse md[:self] }
  entity.add_link link

  plain_oglink_attributes! md, link

  entity
end

Attaches attributes to an existing link instance.


211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
# File 'lib/occi/core/parsers/text/entity.rb', line 211

def plain_oglink_attributes!(md, link)
  if md[:attributes].blank?
    raise Occi::Core::Errors::ParsingError, "Link #{link.id} is missing attribute information"
  end
  logger.debug "Parsing inline link attributes from line #{md[:attributes].inspect}" if logger_debug?

  regexp = Regexp.new "(\\s*#{Constants::REGEXP_ATTRIBUTE_REPR})"
  line = md[:attributes].strip.gsub(/^;\s*/, '')
  plain_attributes!(
    line.scan(regexp).map { |attrb| "#{TextParser::ATTRIBUTE_KEYS.first}: #{attrb.first}" },
    link.attributes
  )

  link
end

Constructs a single link instance based on the provided data. The returned instance does include action instance attributes!


190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# File 'lib/occi/core/parsers/text/entity.rb', line 190

def plain_oglink_instance(md)
  if md[:category].blank? || md[:self].blank?
    raise Occi::Core::Errors::ParsingError, "Link #{md[:uri].inspect} missing type and location information"
  end

  categories = md[:category].split
  target_kind = handle(Occi::Core::Errors::ParsingError) { model.find_by_identifier!(md[:rel]) }
  link = @_ib.build(categories.shift, target_kind: target_kind)
  categories.each do |mxn|
    logger.debug "Adding mixin #{mxn.inspect} to link instance" if logger_debug?
    link << handle(Occi::Core::Errors::ParsingError) { model.find_by_identifier!(mxn) }
  end

  logger.debug "Created link instance #{link.inspect} from #{md.inspect}" if logger_debug?
  link
end

#raw_attribute(line) ⇒ Array

Parses a single attribute line to identify name and value.


118
119
120
121
122
123
124
125
# File 'lib/occi/core/parsers/text/entity.rb', line 118

def raw_attribute(line)
  logger.debug "Parsing attribute line #{line.inspect}" if logger_debug?
  matched = line.match(ATTRIBUTE_REGEXP)
  unless matched
    raise Occi::Core::Errors::ParsingError, "#{line.inspect} does not match expectations for Attribute"
  end
  [matched[:name], matched[:string] || matched[:number] || matched[:bool]]
end

#typecast(value, type) ⇒ Object

Typecasts attribute values from String to the desired type.


232
233
234
235
236
237
238
239
# File 'lib/occi/core/parsers/text/entity.rb', line 232

def typecast(value, type)
  if value.nil? || type.nil?
    raise Occi::Core::Errors::ParsingError, 'Cannot typecast (un)set value to (un)set type'
  end

  logger.debug "Typecasting value #{value.inspect} to #{type}" if logger_debug?
  self.class.typecaster[type].call(value)
end