# frozen-string-literal: true
#
# Copyright (C) 2019 Thomas Baron
#
# This file is part of term_utils.
#
# term_utils is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, version 3 of the License.
#
# term_utils is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with term_utils. If not, see .
module TermUtils
module AP
# Represents an argument parsing Result.
class Result
# @return [TermUtils::PropertyTreeNode]
attr_accessor :value
# Constructs a new Result.
# @param syntax [Darn::AP::Syntax] Syntax.
# @param element [Darn::AP::Element] Optional Element.
# @param value [Darn::PropertyTreeNode] Optional value.
def initialize(syntax, element = nil, value = nil)
@syntax = syntax.dup
@element = element ? element.dup : nil
if value
@value = value.dup
@value.key = nil
else
@value = TermUtils::PropertyTreeNode.new
end
end
# Collects IDs.
# @param id [Symbol, Array] ID path.
# @return [Array]
def collect(id, &block)
node = @value
if id
id = [id] if id.is_a? Symbol
node = @value.find_node(id)
end
res = []
return res unless node && node.child_nodes
if block
node.child_nodes.each do |n|
res << n.key if block.call(n.key)
end
else
node.child_nodes.each do |n|
res << n.key
end
end
res
end
# Tests whether a given level/parameter/article is present in the result value.
# @param id [Symbol, Array] ID path.
# @return [Boolean]
def present?(id)
if id.is_a? Symbol
!!@value.child_node(id)
elsif id.is_a? Array
@value.node_exists?(id)
end
end
# Evaluates the number of occurrences of a given level, parameter or article.
# @param id [Symbol, Array]
# @return [Integer]
def eval_occurs(id)
id = [id] if id.is_a? Symbol
obj = fetch_syntax_object(id)
raise TermUtils::AP::NoSuchValueError, "no such syntax object" unless obj
node = @value.find_node(id)
return 0 unless node
return 1 if obj.occur_bounded? && (obj.max_occurs == 1)
# Parameter is multiple.
node.child_nodes ? node.child_nodes.length : 0
end
# Fetches a value.
# @param id [Symbol, Array, nil]
# @param opts [Hash] `:index`, `:multi`.
# @option opts [Integer] :index The index of a multiple-occurrence level/parameter/article.
# @option opts [Boolean] :multi Whether an array of values shall be returned instead of a single value.
# @return [Object]
# @raise [TermUtils::AP::NoSuchValueError]
def fetch_value(id, opts = {})
index = opts.fetch(:index, nil)
multi = opts.fetch(:multi, false)
unless id
node = @value
if node && node.child_nodes && index
node = node.child_node(index)
end
raise TermUtils::AP::NoSuchValueError, "no such value" unless node
vals = node.collect_values
raise TermUtils::AP::NoSuchValueError, "no such value" if vals.empty?
return multi ? vals : vals.first
end
id = [id] if id.is_a? Symbol
obj = fetch_syntax_object(id)
raise TermUtils::AP::NoSuchValueError, "no such syntax object" unless obj
node = @value.find_node(id)
if node && node.child_nodes && index
node = node.child_node(index)
end
raise TermUtils::AP::NoSuchValueError, "no such value" unless node
catch :value do
if obj.is_a? TermUtils::AP::Parameter
raise TermUtils::AP::NoSuchValueError, "parameter has no article" if obj.articles.empty?
raise TermUtils::AP::NoSuchValueError, "no such value" if node.leaf?
vals = node.collect_values
raise TermUtils::AP::NoSuchValueError, "no such value" if vals.empty?
if multi
throw :value, vals
else
throw :value, vals.first
end
elsif obj.is_a? TermUtils::AP::Article
# raise TermUtils::AP::NoSuchValueError, "no such value" if node.leaf?
vals = node.collect_values
raise TermUtils::AP::NoSuchValueError, "no such value" if vals.empty?
if multi
throw :value, vals
else
throw :value, vals.first
end
end
raise TermUtils::AP::NoSuchValueError, "wrong id"
end
end
# Shifts this one.
# @param id [Symbol, Array]
# @param opts [Hash] `:index`, `:multi`.
# @option opts [Integer] :index The index of a multiple-occurrence level/parameter/article.
# @return [TermUtils::AP::Result]
# @raise [TermUtils::AP::NoSuchValueError]
def shift(id, opts = {})
index = opts.fetch(:index, nil)
id = [id] if id.is_a? Symbol
obj = fetch_syntax_object(id)
raise TermUtils::AP::NoSuchValueError, "no such syntax object" unless obj
node = @value.find_node(id)
if node && node.child_nodes && index
node = node.child_node(index)
end
raise TermUtils::AP::NoSuchValueError, "no such value" unless node
catch :value do
if obj.is_a? TermUtils::AP::Level
throw :value, TermUtils::AP::Result.new(obj.syntax, nil, node)
elsif obj.is_a? TermUtils::AP::Parameter
throw :value, TermUtils::AP::Result.new(@syntax, obj, node)
elsif obj.is_a? TermUtils::AP::Article
throw :value, TermUtils::AP::Result.new(@syntax, obj, node)
end
raise TermUtils::AP::NoSuchValueError, "wrong id"
end
end
private
# Fetches a given syntax object.
# @param id_path [Array]
# @return [TermUtils::AP::Level, TermUtils::AP::Parameter, TermUtils::AP::Article, nil]
def fetch_syntax_object(id_path)
id = id_path.dup
id = [@element.id].concat(id) if @element
catch :done do
fetch_syntax_object0(@syntax, id)
end
end
# Fetches a given syntax object.
# @param syntax [TermUtils::AP::Syntax]
# @param id_path [Array]
# @return [TermUtils::AP::Level, TermUtils::AP::Parameter, TermUtils::AP::Article, nil]
def fetch_syntax_object0(syntax, id_path)
id = id_path.shift
syntax.elements.each do |e|
next unless e.id == id
throw :done, e if id_path.empty?
if e.is_a? TermUtils::AP::Level
fetch_syntax_object0(e.syntax, id_path)
elsif e.is_a? TermUtils::AP::Parameter
throw :done if id_path.empty?
id = id_path.shift
throw :done unless id_path.empty?
e.articles.each do |a|
throw :done, a if a.id = id
end
throw :done
end
end
end
end
end
end