lib/term_utils/ap/result.rb in term_utils-0.3.2 vs lib/term_utils/ap/result.rb in term_utils-0.4.0

- old
+ new

@@ -1,9 +1,9 @@ # frozen-string-literal: true + +# Copyright (C) 2020 Thomas Baron # -# 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. @@ -13,185 +13,232 @@ # 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 <https://www.gnu.org/licenses/>. + module TermUtils module AP # Represents an argument parsing Result. class Result - # @return [TermUtils::PropertyTreeNode] - attr_accessor :value + # @return [Syntax] + attr_reader :parameter + # @return [Array<ParameterResult>] + attr_reader :results + # @return [Array<String>] + attr_accessor :remaining_arguments + # 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 + # @param syntax [Syntax] + def initialize(syntax) + @syntax = syntax + @results = [] + @remaining_arguments = nil end - # Collects IDs. - # @param id [Symbol, Array<Symbol>] ID path. - # @return [Array<Symbol>] - 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) + + # Adds a ParameterResult. + # @param result [ParameterResult] + def add_result(result) + @results << result + end + + # Returns the first ParameterResult for a given parameter id. + # @param id [Symbol] + # @return [ParameterResult] + def find_parameter(id) + @results.find { |r| r.param_id == id } + end + + # Returns all ParameterResult(s) for a given parameter id. + # @param id [Symbol] + # @return [Array<ParameterResult>] + def find_parameters(id) + @results.find_all { |r| r.param_id == id } + end + + # Walks through this one. + def walk(&block) + walker = TermUtils::AP::Walker.new + block.call(walker) + @results.each do |p| + p.results.each do |a| + walker.notify_article(a) end - else - node.child_nodes.each do |n| - res << n.key - end + walker.notify_parameter(p) end - res + walker.notify_finished(@remaining_arguments) end - # Tests whether a given level/parameter/article is present in the result value. - # @param id [Symbol, Array<Symbol>] 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 + + # Represents a result for a parameter. + class ParameterResult + # @return [Parameter] + attr_accessor :parameter + # @return [Array<ArticleResult>] + attr_accessor :results + + # Constructs a new ParameterResult. + # @param parent [Result] + # @param parameter [Parameter] + def initialize(parent, parameter) + @parent = parent + @parent.add_result(self) + @parameter = parameter + @results = [] end - # Evaluates the number of occurrences of a given level, parameter or article. - # @param id [Symbol, Array<Symbol>] - # @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 + + # Adds an ArticleResult. + # @param result [ArticleResult] + def add_result(result) + @results << result end - # Fetches a value. - # @param id [Symbol, Array<Symbol>, 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 [Symbol] + def param_id + @parameter.id + end + + # Returns the first ArticleResult for a given article id. + # @param id [Symbol] + # @return [ArticleResult] + def find_article(id) + @results.find { |r| r.art_id == id } + end + + # Returns all ArticleResult(s) for a given article id. + # @param id [Symbol] + # @return [Array<Result>] + def find_articles(id) + @results.find_all { |r| r.art_id == id } + end + + # Returns the value of the first ArticleResult. + # @param id [Symbol] Filter of article id. # @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 + def value(id = nil) + return @results.first.value unless id + + find_article(id).value + end + + # Returns the value of all ArticleResult(s). + # @param id [Symbol] Filter of article id. + # @return [Array<Object>] + def values(id = nil) + return @results.map(&:value) unless id + + vals = [] + @results.each do |r| + next if r.art_id != id + + vals << r.values 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) + vals + end + end + + # Represents a result for an article. + class ArticleResult + # @return [ParameterResult] + attr_accessor :parent + # @return [Article] + attr_accessor :article + # @return [Object] + attr_accessor :value + + # Constructs a new ArticleResult. + # @param parent [ParameterResult] + # @param article [Article] + # @param value [Object] + def initialize(parent, article, value) + @parent = parent + @parent.add_result(self) + @article = article + @value = value + end + + # @return [Symbol] + def art_id + @article.id + end + end + + # Represents a Result Walker. + class Walker + # Constructs a new Walker. + def initialize + @anonymous_parameter_hook = nil + @anonymous_article_hook = nil + @parameter_hooks = {} + @finished_hook = nil + end + + # Registers a parameter hook. + def parameter(param_id = nil, &block) + unless param_id + # Anonymous parameter hook + @anonymous_parameter_hook = block + return 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 + + @parameter_hooks[param_id] = TermUtils::AP::ParameterWalkerHooks.new unless @parameter_hooks.key?(param_id) + @parameter_hooks[param_id].hook = block end - # Shifts this one. - # @param id [Symbol, Array<Symbol>] - # @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) + + # Registers an article hook. + def article(param_id = nil, art_id = nil, &block) + unless param_id + # Anonymous article hook + @anonymous_article_hook = block + return 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" + + unless art_id + # Anonymous article hook + @parameter_hooks[param_id] = TermUtils::AP::ParameterWalkerHooks.new unless @parameter_hooks.key?(param_id) + @parameter_hooks[param_id].anonymous_article_hook = block + return end + + @parameter_hooks[param_id] = TermUtils::AP::ParameterWalkerHooks.new unless @parameter_hooks.key?(param_id) + @parameter_hooks[param_id].article_hooks ||= {} + @parameter_hooks[param_id].article_hooks[art_id] = block end - private - # Fetches a given syntax object. - # @param id_path [Array<Symbol>] - # @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 + + # Registers a walk finished hook. + def finished(&block) + @finished_hook = block end - # Fetches a given syntax object. - # @param syntax [TermUtils::AP::Syntax] - # @param id_path [Array<Symbol>] - # @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 + + # Calls parameter hooks. + def notify_parameter(parameter) + # (1of2) ID parameter hook + param_hooks = @parameter_hooks[parameter.param_id] + param_hooks.hook.call(parameter) if param_hooks && param_hooks.hook + # (2of2) Anonymous parameter hook + @anonymous_parameter_hook.call(parameter) if @anonymous_parameter_hook + end + + # Calls article hooks. + def notify_article(article) + # (1of2) ID article hook + param_hooks = @parameter_hooks[article.parent.param_id] + if param_hooks + # ID article hook + param_hooks.article_hooks[article.art_id].call(article) if param_hooks.article_hooks && param_hooks.article_hooks.key?(article.art_id) + # Anonymous article hook + param_hooks.anonymous_article_hook.call(article) if param_hooks.anonymous_article_hook end + # (2of2) Anonymous article hook + @anonymous_article_hook.call(article) if @anonymous_article_hook end + + # Calls finished hook. + def notify_finished(remaining_arguments) + @finished_hook.call(remaining_arguments) if @finished_hook + end end + + # Parameter hooks for Walker. + ParameterWalkerHooks = Struct.new('ParameterWalkerHooks', :hook, :anonymous_article_hook, :article_hooks) end end