Class: Markbridge::Normalizer::RuleSet

Inherits:
Object
  • Object
show all
Defined in:
lib/markbridge/normalizer/rule_set.rb

Overview

Maps a node and its ancestor stack to a strategy.

Matching is exact-class (equivalent to instance_of?): rules are keyed by Class and looked up via node.class, so an anonymous Class.new(AST::Element) or any future subclass never accidentally matches a rule written for the base class. Registering a rule for a (parent, child) pair that already has one replaces it, so later layers (Discourse, a consumer's #rule) override earlier ones.

Constant Summary collapse

NO_MATCH =
[nil, nil].freeze

Instance Method Summary collapse

Constructor Details

#initializeRuleSet

Returns a new instance of RuleSet.



16
17
18
19
# File 'lib/markbridge/normalizer/rule_set.rb', line 16

def initialize
  @by_parent = {} # parent_class => { child_class => strategy }
  @child_classes = Set.new
end

Instance Method Details

#add(parent:, child:, strategy:) ⇒ self

Register (or replace) a rule.

Parameters:

  • parent (Class)

    ancestor AST class

  • child (Class)

    contained AST class

  • strategy (Symbol, #call)

    a strategy symbol or callable

Returns:

  • (self)


27
28
29
30
31
32
# File 'lib/markbridge/normalizer/rule_set.rb', line 27

def add(parent:, child:, strategy:)
  validate_strategy!(strategy)
  (@by_parent[parent] ||= {})[child] = strategy
  @child_classes << child
  self
end

#freezeObject

Freeze so a shared instance raises if something tries to change it. Freezing @by_parent and its inner hashes is enough: #add writes there before it touches @child_classes, so a frozen instance raises on the @by_parent write first. @child_classes is never reached, so it does not need freezing.



57
58
59
60
61
# File 'lib/markbridge/normalizer/rule_set.rb', line 57

def freeze
  @by_parent.each_value(&:freeze)
  @by_parent.freeze
  super
end

#resolve(child, ancestors) ⇒ Array(Object, AST::Element), Array(nil, nil)

Resolve the strategy for child given its ancestor stack (root first). Returns [strategy, boundary] where boundary is the outermost ancestor whose class has a rule for +child+'s class, or NO_MATCH (+[nil, nil]+) when nothing matches.

Parameters:

Returns:



42
43
44
45
46
47
48
49
50
# File 'lib/markbridge/normalizer/rule_set.rb', line 42

def resolve(child, ancestors)
  child_class = child.class
  # Skip the ancestor scan for a class no rule targets (most nodes, for
  # example plain text). The scan below returns the same result for such
  # a class, so this only saves work.
  return NO_MATCH unless @child_classes.include?(child_class)

  scan_ancestors(child_class, ancestors)
end