lib/krikri/parser.rb in krikri-0.8.8 vs lib/krikri/parser.rb in krikri-0.8.9
- old
+ new
@@ -139,24 +139,40 @@
##
# A specialized Array object for containing Parser::Values. Provides methods
# for accessing and filtering values that can be chained.
#
+ # @example chaining methods to select values
+ #
# my_value_array.field('dc:creator', 'foaf:name')
# .match_attribute('first_name').values
#
# Methods defined on this class should return another ValueArray, an Array
# of literal values (retrieved from Parser::Value#value), or a single
# literal value.
+ #
+ # Uses `@top` to track a base node for recovery via `#else`. Methods that
+ # return a `ValueArray` should pass `@top` down to the new instance.
+ #
+ # @example conditional recovery. `#if` sets `@top`, `#else` rewinds if empty
+ #
+ # my_value_array.field('dc:creator', 'foaf:name').if.field('empty:field')
+ # .else { |vs| vs.field('some:otherField') }
+ #
class ValueArray
include Enumerable
delegate :[], :each, :empty?, :to_a, :to_ary,
- :to => :@array
+ :to => :@array
- def initialize(array = [])
+ ##
+ # @param array [Array] an array of values to delegate array operations to
+ # @param top [ValueArray] an instance of this class to use as a recovery
+ # node in, e.g. `#else`. Defaults to `self` if none is passed.
+ def initialize(array = [], top = nil)
@array = array
+ @top = top || self
end
##
# @see Array#[]=
# @raise [InvalidParserValueError] when the value is not a Parser::Value
@@ -177,11 +193,11 @@
##
# @see Array#concat
# @return [ValueArray]
def concat(*args, &block)
- self.class.new(@array.concat(*args, &block))
+ self.class.new(@array.concat(*args, &block), @top)
end
##
# @return [Array] literal values from the objects in this array.
# @see Parser::Value#value
@@ -195,13 +211,12 @@
#
# @return [ValueArray] an array containing the nodes available in a
# particular field.
def field(*args)
result = self
- args.each do |name|
- result = result.get_field(name)
- end
+ args.each { |name| result = result.get_field(name) }
+
result
end
##
# Accesses the union of multiple specified fields.
@@ -210,75 +225,123 @@
# given fields.
def fields(*args)
results = args.map do |f|
field(*Array(f))
end
- self.class.new(results.flatten)
+ self.class.new(results.flatten, @top)
end
##
+ # Sets the top of the call chain to self and returns or yields self
+ #
+ # @example with method chain syntax
+ # value_array.if.field(:a_field).else do |arry|
+ # arry.field(:alternate_field)
+ # end
+ #
+ # @example with block syntax
+ # value_array.if { |arry| arry.field(:a_field) }
+ # .else { |arry| arry.field(:alternate_field) }
+ #
+ # @yield gives self
+ # @yieldparam arry [ValueArray] self
+ #
+ # @return [ValueArray] the result of the block, if given; or self with @top set
+ def if(&block)
+ @top = self
+ return yield self if block_given?
+ self
+ end
+
+ ##
+ # Short circuits if `self` is not empty, else passes the top of the call
+ # chain (`@top`) to the given block.
+ #
+ # @example usage with `#if`
+ # value_array.if { |arry| arry.field(:a_field) }
+ # .else { |arry| arry.field(:alternate_field) }
+ #
+ # # use other filters at will
+ # value_array.if.field(:a_field).reject { |v| v == 'SKIP ME' }
+ # .else { |arry| arry.field(:alternate_field) }
+ #
+ # @example standalone use; resetting to record root
+ # value_array.field(:a_field).else { |arry| arry.field(:alternate_field) }
+ #
+ # @yield gives `@top` if self is empty
+ # @yieldparam arry [ValueArray] the value of `@top`
+ #
+ # @return [ValueArray] `self` unless empty; otherwise the result of the
+ # block
+ def else(&block)
+ raise ArgumentError, 'No block given for `#else`' unless block_given?
+ return self unless self.empty?
+ yield @top
+ end
+
+ ##
# Retrieves the first element of a ValueArray. Uses an optional argument
# to specify how many items to return. By design, it behaves similarly
# to Array#first, but it intentionally doesn't override it.
#
# @return [ValueArray] a Krikri::Parser::ValueArray for first n elements
def first_value(*args)
return self.class.new(@array.first(*args)) unless args.empty?
- self.class.new([@array.first].compact)
+ self.class.new([@array.first].compact, @top)
end
##
# Retrieves the last element of a ValueArray. Uses an optional argument
# to specify how many items to return. By design, it behaves similarly
# to Array#last, but it intentionally doesn't override it.
#
# @return [ValueArray] a Krikri::Parser::ValueArray for last n elements
def last_value(*args)
return self.class.new(@array.last(*args)) unless args.empty?
- self.class.new([@array.last].compact)
+ self.class.new([@array.last].compact, @top)
end
##
# @see Array#concat
# @return [ValueArray]
def flatten(*args, &block)
- self.class.new(@array.flatten(*args, &block))
+ self.class.new(@array.flatten(*args, &block), @top)
end
##
# Wraps the result of Array#map in a ValueArray
#
# @see Array#map
# @return [ValueArray]
def map(*args, &block)
- self.class.new(@array.map(*args, &block))
+ self.class.new(@array.map(*args, &block), @top)
end
##
# Wraps the result of Array#select in a ValueArray
#
# @see Array#select
# @return [ValueArray]
def select(*args, &block)
- self.class.new(@array.select(*args, &block))
+ self.class.new(@array.select(*args, &block), @top)
end
##
# Wraps the result of Array#reject in a ValueArray
#
# @see Array#reject
# @return [ValueArray]
def reject(*args, &block)
- self.class.new(@array.reject(*args, &block))
+ self.class.new(@array.reject(*args, &block), @top)
end
##
# @example selecting by presence of an attribute; returns all nodes where
# `#attribute?(:type)` is true
#
- # match_attribute(:type)
+ # match_attribute(:type)
#
# @example selecting by the value of an attribute; returns all nodes with
# `#attribute(:type) == other`
#
# match_attribute(:type, other)
@@ -290,19 +353,19 @@
#
# @example selecting by block against an attribute; returns all nodes with
# `block.call(attribute(:type)) == other` is true
#
# match_attribute(:type, 'moomin') { |value| value.downcase }
- #
+ #
# @param name [#to_sym] an attribute name
# @param other [Object] an object to check for equality with the
# values from the given attribute.
#
# @yield [value] yields each value with the attribute in name to the block
#
# @return [ValueArray] an array containing nodes for which the specified
- # attribute has a value matching the given attribute name, object, and
+ # attribute has a value matching the given attribute name, object, and
# block.
def match_attribute(name, other = nil, &block)
select(&compare_to_attribute(name, other, &block))
end
@@ -312,11 +375,11 @@
# values from the given attribute.
#
# @yield [value] yields each value with the attribute in name to the block
#
# @return [ValueArray] an array containing nodes for which the specified
- # attribute does not have a value matching the given attribute name,
+ # attribute does not have a value matching the given attribute name,
# object, and block.
#
# @see #match_attribute for examples; this calls #reject, where it calls
# #select.
def reject_attribute(name, other = nil, &block)
@@ -333,14 +396,14 @@
end
protected
def get_field(name)
- self.class.new(flat_map { |val| val[name] })
+ self.class.new(flat_map { |val| val[name] }, @top)
end
private
-
+
##
# @see #match_attribute, #reject_attribute
def compare_to_attribute(name, other, &block)
lambda do |v|
next unless v.attribute?(name.to_sym)