lib/dagger/default.rb in ruby-dagger-0.1.1 vs lib/dagger/default.rb in ruby-dagger-0.2.0
- old
+ new
@@ -1,22 +1,35 @@
+# frozen_string_literal: true
+
require 'set'
+require 'key_tree/refinements'
require_relative 'context'
require_relative 'generator'
module Dagger
# Default value generator for a dictionary
class Default
- # Initialize a default value generator for a +dictionary+
+ using KeyTree::Refinements
+
+ def self.proc(*args)
+ new(*args).default_proc
+ end
+
+ # Initialize a default value generator for a +vertex+
#
# :call-seq:
- # new(dictionary) => Dagger::Default
+ # new(graph, vertex) => Dagger::Default
# new(*, cached: false)
# new(*, rule_prefix: '_default')
- def initialize(dictionary, cached: false, rule_prefix: '_default')
- @dictionary = dictionary
+ def initialize(vertex,
+ cached: false,
+ fallback: nil,
+ rule_prefix: '_default')
+ @vertex = vertex
@cached = cached
- @rule_prefix = KeyTree::Path[rule_prefix]
+ @fallback = fallback
+ @rule_prefix = rule_prefix.to_key_path
@default_proc = ->(tree, key) { generate(tree, key) }
@locks = Set[]
end
attr_reader :default_proc
@@ -30,61 +43,84 @@
end
attr_writer :cached
# Generate a default value for a +key+, possibly caching the
# result in the +tree+.
- # Raises a +KeyError+ f the default value cannot be generated.
#
# :call-seq:
# generate(tree, key) => value || KeyError
+ #
+ # Raises a +KeyError+ if the default value cannot be generated.
+ # Raises a +RuntimeError+ in case of a deadlock for +key+.
def generate(tree, key)
- key = KeyTree::Path[key] unless key.is_a? KeyTree::Path
- raise %(deadlock detected: "#{key}") unless @locks.add?(key)
+ key = key.to_key_path
+ with_locked_key(key) { |locked_key| cached_value(tree, locked_key) }
+ end
- return result = process(key) unless cached?
- tree[key] = result unless result.nil?
+ private
+
+ # Process a +block+ with mutex for +key+
+ #
+ # :call-seq:
+ # with_locked_key(key &->(locked_key)) => result
+ def with_locked_key(key)
+ raise %(deadlock detected: "#{key}") unless @locks.add?(key)
+ yield(key)
ensure
@locks.delete(key)
end
- private
+ # Return the default value for +key+, caching it in +tree+ if enabled.
+ #
+ # :call-seq:
+ # cached_value(tree, key) => value || KeyError
+ def cached_value(tree, key)
+ result = process(key)
+ return result unless result.nil?
+ result = @fallback&.[](key)
+ ensure
+ tree[key] = result if cached? && !result.nil?
+ end
# Process value generation rules for +context+, raising a +KeyError+
# if a value could not be generated. Catches :result thows from rule
# processing.
#
# :call-seq:
# yield => value || KeyError
def process(key)
catch do |ball|
- default_rules(key).each do |rule|
- context = Context.new(result: ball, dictionary: @dictionary)
-
+ default_rules(key)&.each do |rule|
+ context = Context.new(dictionary: @vertex,
+ result: ball,
+ vertex: @vertex)
process_rule_chain(rule, context)
end
- raise KeyError, %(no rule succeeded for "#{key}")
+ nil
end
end
# Return the default value generation rules for a +key+.
#
# :call-seq:
- # default_rules(key) => Array of Hash || KeyError
+ # default_rules(key) => Array of Hash
def default_rules(key)
- @dictionary.fetch(@rule_prefix + key)
+ result = @vertex.fetch(@rule_prefix + key, nil)
+ result
end
# Process the methods in a rule chain
#
# :call-seq:
# process_rule_chain(rule_chain, context)
def process_rule_chain(rule_chain, context)
- rule_chain.each do |key, arg|
- klass = Dagger::Generate.const_get(camelize(key))
- klass[context, arg, &->(value) { throw context.result, value }]
+ catch do |ball|
+ context.stop = ball
+ rule_chain.each do |key, arg|
+ klass = Dagger::Generate.const_get(camelize(key))
+ klass[context, arg, &->(value) { throw context.result, value }]
+ end
end
- rescue StopIteration
- nil
end
# Convert snake_case to CamelCase
#
# :call-seq: