modular_tree 0.0.3 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
 - data/lib/modular_tree/algorithms.rb +60 -25
 - data/lib/modular_tree/filter.rb +27 -51
 - data/lib/modular_tree/implementations.rb +6 -3
 - data/lib/modular_tree/matcher.rb +64 -0
 - data/lib/modular_tree/properties.rb +2 -1
 - data/lib/modular_tree/version.rb +1 -1
 - data/lib/modular_tree.rb +1 -0
 - metadata +3 -2
 
    
        checksums.yaml
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ---
         
     | 
| 
       2 
2 
     | 
    
         
             
            SHA256:
         
     | 
| 
       3 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       4 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: 420d72c774f0602ebf2b50b7346ee64759aaf48f89b8e041c80fe76464dbd379
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: 4ceb72733d99138a6244c4b163b46d0fb10fcdf20b8f7d61e582903bab750bc8
         
     | 
| 
       5 
5 
     | 
    
         
             
            SHA512:
         
     | 
| 
       6 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       7 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: a5b2b90bcaaa71fd2754093cbd3ccca1fea3c9193c3261a82911bd10c7c8c6e74476804006238215aaf448062424dbfeef606550b69f378fd742d159d58b05b2
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: 8330f74bf4368cc7d56f35b42e48ff4f1dd51641ed94f05f4340f222eef22d62d0a3b32c883e2e6d916b78762a3d23870e62131b3e2f461c10ccabf16070d6ce
         
     | 
| 
         @@ -49,8 +49,9 @@ module Tree 
     | 
|
| 
       49 
49 
     | 
    
         
             
                include NodeProperty
         
     | 
| 
       50 
50 
     | 
    
         
             
                include BranchesProperty
         
     | 
| 
       51 
51 
     | 
    
         | 
| 
       52 
     | 
    
         
            -
                # True if the node doesn't contain any branches (#empty? for trees)
         
     | 
| 
       53 
     | 
    
         
            -
                 
     | 
| 
      
 52 
     | 
    
         
            +
                # True if the node doesn't contain any branches (#empty? for trees).
         
     | 
| 
      
 53 
     | 
    
         
            +
                # Default implementation in BranchesProperty
         
     | 
| 
      
 54 
     | 
    
         
            +
            #   def bare? = raise NotImplemented
         
     | 
| 
       54 
55 
     | 
    
         | 
| 
       55 
56 
     | 
    
         
             
                # The number of nodes in the tree. Note that this can be an expensive
         
     | 
| 
       56 
57 
     | 
    
         
             
                # operation because every node has to be visited
         
     | 
| 
         @@ -61,9 +62,10 @@ module Tree 
     | 
|
| 
       61 
62 
     | 
    
         
             
                def descendants(*filter) = each(filter, this: false)
         
     | 
| 
       62 
63 
     | 
    
         | 
| 
       63 
64 
     | 
    
         
             
                # Implementation of Enumerable#each extended with filters. The block is
         
     | 
| 
       64 
     | 
    
         
            -
                # called with value, key, and parent as arguments  
     | 
| 
       65 
     | 
    
         
            -
                # ignore key and/or parent 
     | 
| 
       66 
     | 
    
         
            -
                 
     | 
| 
      
 65 
     | 
    
         
            +
                # called with value, key, and parent as arguments but it may choose to
         
     | 
| 
      
 66 
     | 
    
         
            +
                # ignore the key and/or parent argument. Returns an enumerator of values
         
     | 
| 
      
 67 
     | 
    
         
            +
                # without a block
         
     | 
| 
      
 68 
     | 
    
         
            +
                def each(*filter, this: true, &block) = common_each(*filter, :node_value, :do_each_preorder, this, &block)
         
     | 
| 
       67 
69 
     | 
    
         | 
| 
       68 
70 
     | 
    
         
             
                # Implementation of Enumerable#select extended with a single filter. As
         
     | 
| 
       69 
71 
     | 
    
         
             
                # #each, the block is called with value, key, and parent arguments
         
     | 
| 
         @@ -83,22 +85,49 @@ module Tree 
     | 
|
| 
       83 
85 
     | 
    
         
             
                def preorder(*filter, this: true) = each(*filter, this: this)
         
     | 
| 
       84 
86 
     | 
    
         | 
| 
       85 
87 
     | 
    
         
             
                # Post-order enumerator of selected nodes
         
     | 
| 
       86 
     | 
    
         
            -
                def postorder(*filter, this: true) = common_each(*filter, : 
     | 
| 
      
 88 
     | 
    
         
            +
                def postorder(*filter, this: true) = common_each(*filter, :node_value, :each_postorder, this)
         
     | 
| 
       87 
89 
     | 
    
         | 
| 
       88 
90 
     | 
    
         
             
                # Enumerator of edges in the tree. Edges are [previous-matching-node,
         
     | 
| 
       89 
91 
     | 
    
         
             
                # matching-node] tuples. Top-level nodes have previous-matching-node set to
         
     | 
| 
       90 
92 
     | 
    
         
             
                # nil
         
     | 
| 
       91 
93 
     | 
    
         
             
                #
         
     | 
| 
      
 94 
     | 
    
         
            +
                # FIXME: Not working right now. Not sure how #edges relates to #pairs
         
     | 
| 
       92 
95 
     | 
    
         
             
                def edges(*filter, this: true, &block)
         
     | 
| 
       93 
     | 
    
         
            -
                  if block_given
         
     | 
| 
      
 96 
     | 
    
         
            +
                  if block_given?
         
     | 
| 
       94 
97 
     | 
    
         
             
                    each(*filter, this: this) { |node, _, parent| yield parent, node }
         
     | 
| 
       95 
98 
     | 
    
         
             
                  else
         
     | 
| 
       96 
99 
     | 
    
         
             
                    Pairs.new { |enum| each(*filter, this: this) { |node, _, parent| enum << [parent, node] } }
         
     | 
| 
       97 
100 
     | 
    
         
             
                  end
         
     | 
| 
       98 
101 
     | 
    
         
             
                end
         
     | 
| 
       99 
102 
     | 
    
         | 
| 
       100 
     | 
    
         
            -
            # 
     | 
| 
       101 
     | 
    
         
            -
            # 
     | 
| 
      
 103 
     | 
    
         
            +
                # Find nodes matching +filter+ and call +traverse_block+ with a node and a
         
     | 
| 
      
 104 
     | 
    
         
            +
                # block argument. +traverse_block+ can recurse into children by calling the
         
     | 
| 
      
 105 
     | 
    
         
            +
                # supplied inner block
         
     | 
| 
      
 106 
     | 
    
         
            +
                #
         
     | 
| 
      
 107 
     | 
    
         
            +
                # An example of how to create a nested array implementation:
         
     | 
| 
      
 108 
     | 
    
         
            +
                #
         
     | 
| 
      
 109 
     | 
    
         
            +
                #   root.traverse(true) { |node, inner| [node, inner.call(node)] }
         
     | 
| 
      
 110 
     | 
    
         
            +
                #
         
     | 
| 
      
 111 
     | 
    
         
            +
                def traverse(*filter, this: true, &traverse_block)
         
     | 
| 
      
 112 
     | 
    
         
            +
                  filter = self.class.filter(*filter)
         
     | 
| 
      
 113 
     | 
    
         
            +
                  inner_block_proxy = [nil] # Hack because you can't refer to a lambda within its declaration
         
     | 
| 
      
 114 
     | 
    
         
            +
                  inner_block = inner_block_proxy[0] = lambda { |node|
         
     | 
| 
      
 115 
     | 
    
         
            +
                    node.nodes(filter, this: false).map { |n| traverse_block.call(n, inner_block_proxy.first) }
         
     | 
| 
      
 116 
     | 
    
         
            +
                  }
         
     | 
| 
      
 117 
     | 
    
         
            +
                  nodes(filter, this: this).map { |node| traverse_block.call(node, inner_block) }
         
     | 
| 
      
 118 
     | 
    
         
            +
                end
         
     | 
| 
      
 119 
     | 
    
         
            +
                
         
     | 
| 
      
 120 
     | 
    
         
            +
                # Return array of [previous-matching-node, matching-node] tuples
         
     | 
| 
      
 121 
     | 
    
         
            +
                def pairs(first_matcher_expr, second_matcher_expr, this: true)
         
     | 
| 
      
 122 
     | 
    
         
            +
                  first_matcher = Matcher.new(first_matcher_expr)
         
     | 
| 
      
 123 
     | 
    
         
            +
                  second_matcher = Matcher.new(second_matcher_expr)
         
     | 
| 
      
 124 
     | 
    
         
            +
                  or_matcher = first_matcher | second_matcher # avoids re-computing this value over and over
         
     | 
| 
      
 125 
     | 
    
         
            +
                  result = []
         
     | 
| 
      
 126 
     | 
    
         
            +
                  nodes(first_matcher, false, this: this) { |node|
         
     | 
| 
      
 127 
     | 
    
         
            +
                    node.do_pairs(result, first_matcher, or_matcher)
         
     | 
| 
      
 128 
     | 
    
         
            +
                  }
         
     | 
| 
      
 129 
     | 
    
         
            +
                  result
         
     | 
| 
      
 130 
     | 
    
         
            +
                end
         
     | 
| 
       102 
131 
     | 
    
         | 
| 
       103 
132 
     | 
    
         
             
                # Traverse the tree top-down while accumulating information in an
         
     | 
| 
       104 
133 
     | 
    
         
             
                # accumulator object. The block takes a [accumulator, node] tuple and is
         
     | 
| 
         @@ -126,10 +155,7 @@ module Tree 
     | 
|
| 
       126 
155 
     | 
    
         
             
                  do_aggregate(filter, this, &block)
         
     | 
| 
       127 
156 
     | 
    
         
             
                end
         
     | 
| 
       128 
157 
     | 
    
         | 
| 
       129 
     | 
    
         
            -
             
     | 
| 
       130 
     | 
    
         
            -
                def propagate(*filter, this: true, &block)
         
     | 
| 
       131 
     | 
    
         
            -
                  
         
     | 
| 
       132 
     | 
    
         
            -
                end
         
     | 
| 
      
 158 
     | 
    
         
            +
            #   def propagate(*filter, this: true, &block) raise NotImplemented end
         
     | 
| 
       133 
159 
     | 
    
         | 
| 
       134 
160 
     | 
    
         
             
                # tree.each(DocumentNode).select(BriefNode).each { |doc, brief| ... }
         
     | 
| 
       135 
161 
     | 
    
         
             
                # tree.edges(DocumentNode, BriefNode).group.all? { |doc, briefs| briefs.size <= 1 }
         
     | 
| 
         @@ -142,7 +168,7 @@ module Tree 
     | 
|
| 
       142 
168 
     | 
    
         
             
                    args.size == 1 or raise ArgumentError
         
     | 
| 
       143 
169 
     | 
    
         
             
                    args.first
         
     | 
| 
       144 
170 
     | 
    
         
             
                  else
         
     | 
| 
       145 
     | 
    
         
            -
                    Filter.new(*args) 
     | 
| 
      
 171 
     | 
    
         
            +
                    Filter.new(*args)
         
     | 
| 
       146 
172 
     | 
    
         
             
                  end
         
     | 
| 
       147 
173 
     | 
    
         
             
                end
         
     | 
| 
       148 
174 
     | 
    
         | 
| 
         @@ -156,6 +182,16 @@ module Tree 
     | 
|
| 
       156 
182 
     | 
    
         
             
                  end
         
     | 
| 
       157 
183 
     | 
    
         
             
                end
         
     | 
| 
       158 
184 
     | 
    
         | 
| 
      
 185 
     | 
    
         
            +
                def do_pairs(acc, first_matcher, or_matcher) # self is matched by first_matcher
         
     | 
| 
      
 186 
     | 
    
         
            +
                  nodes(or_matcher, false, this: false) { |node|
         
     | 
| 
      
 187 
     | 
    
         
            +
                    if first_matcher.match?(node)
         
     | 
| 
      
 188 
     | 
    
         
            +
                      node.do_pairs(acc, first_matcher, or_matcher)
         
     | 
| 
      
 189 
     | 
    
         
            +
                    else
         
     | 
| 
      
 190 
     | 
    
         
            +
                      acc << [self, node]
         
     | 
| 
      
 191 
     | 
    
         
            +
                    end
         
     | 
| 
      
 192 
     | 
    
         
            +
                  }
         
     | 
| 
      
 193 
     | 
    
         
            +
                end
         
     | 
| 
      
 194 
     | 
    
         
            +
             
     | 
| 
       159 
195 
     | 
    
         
             
                # TODO: Split into automatically generated variants
         
     | 
| 
       160 
196 
     | 
    
         
             
                def do_each_preorder(enum, filter, value_method, key, parent, this, &block)
         
     | 
| 
       161 
197 
     | 
    
         
             
                  select, traverse = filter.match(self)
         
     | 
| 
         @@ -192,15 +228,15 @@ module Tree 
     | 
|
| 
       192 
228 
     | 
    
         
             
                  end
         
     | 
| 
       193 
229 
     | 
    
         
             
                end
         
     | 
| 
       194 
230 
     | 
    
         | 
| 
       195 
     | 
    
         
            -
             
     | 
| 
       196 
     | 
    
         
            -
             
     | 
| 
       197 
     | 
    
         
            -
             
     | 
| 
       198 
     | 
    
         
            -
             
     | 
| 
       199 
     | 
    
         
            -
             
     | 
| 
       200 
     | 
    
         
            -
             
     | 
| 
       201 
     | 
    
         
            -
             
     | 
| 
       202 
     | 
    
         
            -
             
     | 
| 
       203 
     | 
    
         
            -
             
     | 
| 
      
 231 
     | 
    
         
            +
            #   def do_propagate(filter, this, key, parent, &block)
         
     | 
| 
      
 232 
     | 
    
         
            +
            #     select, traverse = filter.match(self)
         
     | 
| 
      
 233 
     | 
    
         
            +
            #     if select && this
         
     | 
| 
      
 234 
     | 
    
         
            +
            #       return if yield(self, key, parent)
         
     | 
| 
      
 235 
     | 
    
         
            +
            #     end
         
     | 
| 
      
 236 
     | 
    
         
            +
            #     if !this || traverse
         
     | 
| 
      
 237 
     | 
    
         
            +
            #       each_branch { |branch, key| branch.do_propagate(filter, key, self.value) }
         
     | 
| 
      
 238 
     | 
    
         
            +
            #     end
         
     | 
| 
      
 239 
     | 
    
         
            +
            #   end
         
     | 
| 
       204 
240 
     | 
    
         | 
| 
       205 
241 
     | 
    
         
             
                def do_accumulate(filter, this, acc, &block)
         
     | 
| 
       206 
242 
     | 
    
         
             
                  select, traverse = filter.match(self)
         
     | 
| 
         @@ -214,9 +250,8 @@ module Tree 
     | 
|
| 
       214 
250 
     | 
    
         
             
                  values = traverse ? each_branch { |branch| 
         
     | 
| 
       215 
251 
     | 
    
         
             
                    r = branch.do_aggregate(filter, true, &block) 
         
     | 
| 
       216 
252 
     | 
    
         
             
                  }.to_a : []
         
     | 
| 
       217 
     | 
    
         
            -
                  yield(self. 
     | 
| 
      
 253 
     | 
    
         
            +
                  yield(self.node_value, values)
         
     | 
| 
       218 
254 
     | 
    
         
             
                end
         
     | 
| 
       219 
     | 
    
         
            -
             
     | 
| 
       220 
255 
     | 
    
         
             
              end
         
     | 
| 
       221 
256 
     | 
    
         | 
| 
       222 
257 
     | 
    
         
             
              module DownTreeAlgorithms
         
     | 
    
        data/lib/modular_tree/filter.rb
    CHANGED
    
    | 
         @@ -1,86 +1,62 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            module Tree
         
     | 
| 
       2 
2 
     | 
    
         
             
              class Filter
         
     | 
| 
      
 3 
     | 
    
         
            +
                # :call-seq:
         
     | 
| 
      
 4 
     | 
    
         
            +
                #   Filter.new(select_expr, traverse_expr)
         
     | 
| 
      
 5 
     | 
    
         
            +
                #   Filter.new(select_expr, &block)
         
     | 
| 
      
 6 
     | 
    
         
            +
                #   Filter.new(&block)
         
     | 
| 
      
 7 
     | 
    
         
            +
                #
         
     | 
| 
       3 
8 
     | 
    
         
             
                # Create a node filter. The filter is initialized by a select expression
         
     | 
| 
       4 
9 
     | 
    
         
             
                # and a traverse expression.  The select expression decides if the node
         
     | 
| 
       5 
10 
     | 
    
         
             
                # should be given to the block (or submitted to an enumerator) and the
         
     | 
| 
       6 
11 
     | 
    
         
             
                # traverse expression decides if the child nodes should be traversed
         
     | 
| 
       7 
12 
     | 
    
         
             
                # recursively
         
     | 
| 
       8 
13 
     | 
    
         
             
                #
         
     | 
| 
       9 
     | 
    
         
            -
                # The expressions can be a  
     | 
| 
       10 
     | 
    
         
            -
                #  
     | 
| 
       11 
     | 
    
         
            -
                # nil. These values have 
     | 
| 
      
 14 
     | 
    
         
            +
                # The expressions can be a Matcher object or one of Matcher's initializers:
         
     | 
| 
      
 15 
     | 
    
         
            +
                # Proc, Symbol, Class, or an array of classes. In addition, +select+ can be
         
     | 
| 
      
 16 
     | 
    
         
            +
                # true, and +traverse+ can be true, false, or nil. These values have
         
     | 
| 
      
 17 
     | 
    
         
            +
                # special meanings:
         
     | 
| 
       12 
18 
     | 
    
         
             
                #
         
     | 
| 
       13 
19 
     | 
    
         
             
                #   when +select+ is
         
     | 
| 
       14 
20 
     | 
    
         
             
                #     true    Select always. This is the default
         
     | 
| 
      
 21 
     | 
    
         
            +
                #     false   This is an allowed value but it doesn't select any node
         
     | 
| 
       15 
22 
     | 
    
         
             
                #
         
     | 
| 
       16 
23 
     | 
    
         
             
                #   when +traverse+ is
         
     | 
| 
       17 
24 
     | 
    
         
             
                #     true    Traverse always. This is the default
         
     | 
| 
       18 
25 
     | 
    
         
             
                #     false   Traverse only if select didn't match
         
     | 
| 
       19 
     | 
    
         
            -
                #     nil     Expects +select+ to  
     | 
| 
       20 
     | 
    
         
            -
                #              
     | 
| 
      
 26 
     | 
    
         
            +
                #     nil     Expects +select+ to be a Proc object that returns a [select,
         
     | 
| 
      
 27 
     | 
    
         
            +
                #             traverse] tuple of booleans
         
     | 
| 
       21 
28 
     | 
    
         
             
                #
         
     | 
| 
       22 
29 
     | 
    
         
             
                # If the expression is a Proc object, it will be called with the current
         
     | 
| 
       23 
30 
     | 
    
         
             
                # node as argument. If the return value is true, the node is
         
     | 
| 
       24 
31 
     | 
    
         
             
                # selected/traversed and skipped otherwise. If the expression is a method
         
     | 
| 
       25 
32 
     | 
    
         
             
                # name (Symbol), the method will be called on each node with no arguments.
         
     | 
| 
       26 
33 
     | 
    
         
             
                # It is not an error if the method doesn't exists on the a given node but
         
     | 
| 
       27 
     | 
    
         
            -
                # the node is not selected/traversed
         
     | 
| 
      
 34 
     | 
    
         
            +
                # the node is not selected/traversed. If the expression is a class or an
         
     | 
| 
      
 35 
     | 
    
         
            +
                # array of classes, a given node matches if it is an instance of one of the
         
     | 
| 
      
 36 
     | 
    
         
            +
                # classes or any subclass
         
     | 
| 
       28 
37 
     | 
    
         
             
                #
         
     | 
| 
       29 
     | 
    
         
            -
                # If  
     | 
| 
       30 
     | 
    
         
            -
                #  
     | 
| 
      
 38 
     | 
    
         
            +
                # If a block is given, it is supposed to return a [select, traverse] tuple
         
     | 
| 
      
 39 
     | 
    
         
            +
                # of booleans
         
     | 
| 
       31 
40 
     | 
    
         
             
                #
         
     | 
| 
       32 
41 
     | 
    
         
             
                # Filters should not have side-effects because they can be used in
         
     | 
| 
       33 
42 
     | 
    
         
             
                # enumerators that doesn't execute the filter unless the enumerator is
         
     | 
| 
       34 
43 
     | 
    
         
             
                # evaluated
         
     | 
| 
       35 
44 
     | 
    
         
             
                #
         
     | 
| 
       36 
     | 
    
         
            -
                 
     | 
| 
       37 
     | 
    
         
            -
             
     | 
| 
       38 
     | 
    
         
            -
             
     | 
| 
       39 
     | 
    
         
            -
                   
     | 
| 
       40 
     | 
    
         
            -
             
     | 
| 
       41 
     | 
    
         
            -
             
     | 
| 
       42 
     | 
    
         
            -
             
     | 
| 
       43 
     | 
    
         
            -
             
     | 
| 
       44 
     | 
    
         
            -
             
     | 
| 
       45 
     | 
    
         
            -
             
     | 
| 
       46 
     | 
    
         
            -
                          case traverse
         
     | 
| 
       47 
     | 
    
         
            -
                            when Proc; lambda { |node| [select.call(node), traverse.call(node)] }
         
     | 
| 
       48 
     | 
    
         
            -
                            when true; lambda { |node| [select.call(node), true] }
         
     | 
| 
       49 
     | 
    
         
            -
                            when false; lambda { |node| r = select.call(node); [r, !r] }
         
     | 
| 
       50 
     | 
    
         
            -
                            when nil; lambda { |node| select.call(node) }
         
     | 
| 
       51 
     | 
    
         
            -
                          end
         
     | 
| 
       52 
     | 
    
         
            -
                        when true
         
     | 
| 
       53 
     | 
    
         
            -
                          case traverse
         
     | 
| 
       54 
     | 
    
         
            -
                            when Proc; lambda { |node| [true, traverse.call(node)] }
         
     | 
| 
       55 
     | 
    
         
            -
                            when true; lambda { |_| [true, true] }
         
     | 
| 
       56 
     | 
    
         
            -
                            when false; lambda { |_| [true, false] } # effectively same as #children.each
         
     | 
| 
       57 
     | 
    
         
            -
                            when nil; raise ArgumentError
         
     | 
| 
       58 
     | 
    
         
            -
                          end
         
     | 
| 
       59 
     | 
    
         
            -
                      end
         
     | 
| 
      
 45 
     | 
    
         
            +
                def initialize(select_expr = nil, traverse_expr = nil, &block)
         
     | 
| 
      
 46 
     | 
    
         
            +
                  if select_expr.nil? && block_given?
         
     | 
| 
      
 47 
     | 
    
         
            +
                    @matcher = block
         
     | 
| 
      
 48 
     | 
    
         
            +
                  else
         
     | 
| 
      
 49 
     | 
    
         
            +
                    select_expr = true if select_expr.nil?
         
     | 
| 
      
 50 
     | 
    
         
            +
                    select = Matcher.new(select_expr)
         
     | 
| 
      
 51 
     | 
    
         
            +
                    traverse_expr = (block_given? ? block : true) if traverse_expr.nil?
         
     | 
| 
      
 52 
     | 
    
         
            +
                    traverse = traverse_expr ? Matcher.new(traverse_expr) : !select
         
     | 
| 
      
 53 
     | 
    
         
            +
                    @matcher = lambda { |node| [select.match?(node), traverse.match?(node)] }
         
     | 
| 
      
 54 
     | 
    
         
            +
                  end
         
     | 
| 
       60 
55 
     | 
    
         
             
                end
         
     | 
| 
       61 
56 
     | 
    
         | 
| 
       62 
57 
     | 
    
         
             
                # Match +node+ against the filter and return a [select, traverse] tuple of booleans
         
     | 
| 
       63 
58 
     | 
    
         
             
                def match(node) = @matcher.call(node)
         
     | 
| 
       64 
59 
     | 
    
         | 
| 
       65 
     | 
    
         
            -
                # Create a proc if arg is a Symbol or an Array of classes. Pass through
         
     | 
| 
       66 
     | 
    
         
            -
                # Proc objects, true, false, and nil
         
     | 
| 
       67 
     | 
    
         
            -
                def mk_lambda(arg) = self.class.mk_lambda(arg)
         
     | 
| 
       68 
     | 
    
         
            -
                def self.mk_lambda(arg)
         
     | 
| 
       69 
     | 
    
         
            -
                  case arg
         
     | 
| 
       70 
     | 
    
         
            -
                    when Proc, true, false, nil
         
     | 
| 
       71 
     | 
    
         
            -
                      arg
         
     | 
| 
       72 
     | 
    
         
            -
                    when Symbol
         
     | 
| 
       73 
     | 
    
         
            -
                      lambda { |node| node.respond_to?(arg) && node.send(arg) }
         
     | 
| 
       74 
     | 
    
         
            -
                    when Class
         
     | 
| 
       75 
     | 
    
         
            -
                      lambda { |node| node.is_a? arg }
         
     | 
| 
       76 
     | 
    
         
            -
                    when Array
         
     | 
| 
       77 
     | 
    
         
            -
                      arg.all? { |a| a.is_a? Class } or raise ArgumentError, "Array elements should be classes"
         
     | 
| 
       78 
     | 
    
         
            -
                      lambda { |node| arg.any? { |a| node.is_a? a } }
         
     | 
| 
       79 
     | 
    
         
            -
                  else
         
     | 
| 
       80 
     | 
    
         
            -
                    raise ArgumentError
         
     | 
| 
       81 
     | 
    
         
            -
                  end
         
     | 
| 
       82 
     | 
    
         
            -
                end
         
     | 
| 
       83 
     | 
    
         
            -
             
     | 
| 
       84 
60 
     | 
    
         
             
                ALL = Filter.new
         
     | 
| 
       85 
61 
     | 
    
         
             
              end
         
     | 
| 
       86 
62 
     | 
    
         
             
            end
         
     | 
| 
         @@ -29,7 +29,7 @@ module Tree 
     | 
|
| 
       29 
29 
     | 
    
         
             
                include Implementation
         
     | 
| 
       30 
30 
     | 
    
         
             
                def node = self
         
     | 
| 
       31 
31 
     | 
    
         
             
                def this = self
         
     | 
| 
       32 
     | 
    
         
            -
                def  
     | 
| 
      
 32 
     | 
    
         
            +
                def node_value = self
         
     | 
| 
       33 
33 
     | 
    
         
             
              end
         
     | 
| 
       34 
34 
     | 
    
         | 
| 
       35 
35 
     | 
    
         
             
              module ParentImplementation
         
     | 
| 
         @@ -69,7 +69,7 @@ module Tree 
     | 
|
| 
       69 
69 
     | 
    
         | 
| 
       70 
70 
     | 
    
         
             
                def node = array
         
     | 
| 
       71 
71 
     | 
    
         
             
                def this = array.first
         
     | 
| 
       72 
     | 
    
         
            -
                def  
     | 
| 
      
 72 
     | 
    
         
            +
                def node_value = array.first # FIXME What is this? It is a problem several places
         
     | 
| 
       73 
73 
     | 
    
         | 
| 
       74 
74 
     | 
    
         
             
                attr_accessor :array
         
     | 
| 
       75 
75 
     | 
    
         | 
| 
         @@ -168,7 +168,10 @@ module Tree 
     | 
|
| 
       168 
168 
     | 
    
         
             
                def each_child(&block) = @children.map(&block)
         
     | 
| 
       169 
169 
     | 
    
         
             
                def attach(child) = @children << child
         
     | 
| 
       170 
170 
     | 
    
         | 
| 
       171 
     | 
    
         
            -
                # Can be used with any array implementation
         
     | 
| 
      
 171 
     | 
    
         
            +
                # Can be used with any array implementation. +where+ is either an Integer
         
     | 
| 
      
 172 
     | 
    
         
            +
                # index or an object
         
     | 
| 
      
 173 
     | 
    
         
            +
                #
         
     | 
| 
      
 174 
     | 
    
         
            +
                # TODO: Rename #attach. #insert & #append are Enumerable operations
         
     | 
| 
       172 
175 
     | 
    
         
             
                def insert(where, child) = insert_append(:insert, where, child)
         
     | 
| 
       173 
176 
     | 
    
         
             
                def append(where, child) = insert_append(:append, where, child)
         
     | 
| 
       174 
177 
     | 
    
         | 
| 
         @@ -0,0 +1,64 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Tree
         
     | 
| 
      
 2 
     | 
    
         
            +
              class AbstractMatcher
         
     | 
| 
      
 3 
     | 
    
         
            +
                def match(node) = abstract_method
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
                def |(other) = MatchExpression::OrExpr.new(self, other)
         
     | 
| 
      
 6 
     | 
    
         
            +
                def &(other) = MatchExpression::AndExpr.new(self, other)
         
     | 
| 
      
 7 
     | 
    
         
            +
                def !() = MatchExpression::NegationExpr.new(self)
         
     | 
| 
      
 8 
     | 
    
         
            +
              end
         
     | 
| 
      
 9 
     | 
    
         
            +
              
         
     | 
| 
      
 10 
     | 
    
         
            +
              class Matcher < AbstractMatcher
         
     | 
| 
      
 11 
     | 
    
         
            +
                def match(node) = match?(node)
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                def match?(node)
         
     | 
| 
      
 14 
     | 
    
         
            +
                  case @expr
         
     | 
| 
      
 15 
     | 
    
         
            +
                    when Proc
         
     | 
| 
      
 16 
     | 
    
         
            +
                      @expr.call(node)
         
     | 
| 
      
 17 
     | 
    
         
            +
                    when Symbol
         
     | 
| 
      
 18 
     | 
    
         
            +
                      node.respond_to?(@expr) && node.send(@expr)
         
     | 
| 
      
 19 
     | 
    
         
            +
                    when Class
         
     | 
| 
      
 20 
     | 
    
         
            +
                      node.is_a? @expr
         
     | 
| 
      
 21 
     | 
    
         
            +
                    when Array
         
     | 
| 
      
 22 
     | 
    
         
            +
                      @expr.any? { |klass| node.is_a? klass }
         
     | 
| 
      
 23 
     | 
    
         
            +
                    when true, false
         
     | 
| 
      
 24 
     | 
    
         
            +
                      @expr
         
     | 
| 
      
 25 
     | 
    
         
            +
                  else
         
     | 
| 
      
 26 
     | 
    
         
            +
                    raise ArgumentError, @expr.inspect
         
     | 
| 
      
 27 
     | 
    
         
            +
                  end
         
     | 
| 
      
 28 
     | 
    
         
            +
                end
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                def initialize(expr = nil, &block)
         
     | 
| 
      
 31 
     | 
    
         
            +
                  constrain expr, Proc, Symbol, Class, [Class], true, false, nil
         
     | 
| 
      
 32 
     | 
    
         
            +
                  expr.nil? == block_given? or raise ArgumentError
         
     | 
| 
      
 33 
     | 
    
         
            +
                  @expr = (expr.nil? ? block : expr)
         
     | 
| 
      
 34 
     | 
    
         
            +
                end
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                def self.new(expr = nil, *args, &block)
         
     | 
| 
      
 37 
     | 
    
         
            +
                  expr.is_a?(AbstractMatcher) ? expr : super
         
     | 
| 
      
 38 
     | 
    
         
            +
                end
         
     | 
| 
      
 39 
     | 
    
         
            +
              end
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
              module MatchExpression
         
     | 
| 
      
 42 
     | 
    
         
            +
                class BinaryExpr < AbstractMatcher
         
     | 
| 
      
 43 
     | 
    
         
            +
                  def initialize(left, right)
         
     | 
| 
      
 44 
     | 
    
         
            +
                    constrain left, Matcher
         
     | 
| 
      
 45 
     | 
    
         
            +
                    constrain right, Matcher
         
     | 
| 
      
 46 
     | 
    
         
            +
                    @left, @right = left, right
         
     | 
| 
      
 47 
     | 
    
         
            +
                  end
         
     | 
| 
      
 48 
     | 
    
         
            +
                end
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
                class AndExpr < BinaryExpr
         
     | 
| 
      
 51 
     | 
    
         
            +
                  def match?(node) = @left.match?(node) && @right.match?(node)
         
     | 
| 
      
 52 
     | 
    
         
            +
                end
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
                class OrExpr < BinaryExpr
         
     | 
| 
      
 55 
     | 
    
         
            +
                  def match?(node) = @left.match?(node) || @right.match?(node) ? true : false
         
     | 
| 
      
 56 
     | 
    
         
            +
                end
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
                class NegationExpr < AbstractMatcher
         
     | 
| 
      
 59 
     | 
    
         
            +
                  def initialize(matcher) = @matcher = matcher
         
     | 
| 
      
 60 
     | 
    
         
            +
                  def match?(node) = !@matcher.match?(node)
         
     | 
| 
      
 61 
     | 
    
         
            +
                end
         
     | 
| 
      
 62 
     | 
    
         
            +
              end
         
     | 
| 
      
 63 
     | 
    
         
            +
            end
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
         @@ -13,7 +13,7 @@ module Tree 
     | 
|
| 
       13 
13 
     | 
    
         
             
                def node = abstract_method
         
     | 
| 
       14 
14 
     | 
    
         | 
| 
       15 
15 
     | 
    
         
             
                # FIXME: What is this. Also describe all properties in this file
         
     | 
| 
       16 
     | 
    
         
            -
                def  
     | 
| 
      
 16 
     | 
    
         
            +
                def node_value = abstract_method
         
     | 
| 
       17 
17 
     | 
    
         
             
              end
         
     | 
| 
       18 
18 
     | 
    
         | 
| 
       19 
19 
     | 
    
         
             
              module StemProperty # Aka. 'parent'
         
     | 
| 
         @@ -22,6 +22,7 @@ module Tree 
     | 
|
| 
       22 
22 
     | 
    
         | 
| 
       23 
23 
     | 
    
         
             
              module BranchesProperty
         
     | 
| 
       24 
24 
     | 
    
         
             
                def branches = abstract_method
         
     | 
| 
      
 25 
     | 
    
         
            +
                def bare? = branches.empty?
         
     | 
| 
       25 
26 
     | 
    
         
             
                def each_branch(&block) = branches.each(&block)
         
     | 
| 
       26 
27 
     | 
    
         
             
              end
         
     | 
| 
       27 
28 
     | 
    
         | 
    
        data/lib/modular_tree/version.rb
    CHANGED
    
    
    
        data/lib/modular_tree.rb
    CHANGED
    
    
    
        metadata
    CHANGED
    
    | 
         @@ -1,14 +1,14 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            --- !ruby/object:Gem::Specification
         
     | 
| 
       2 
2 
     | 
    
         
             
            name: modular_tree
         
     | 
| 
       3 
3 
     | 
    
         
             
            version: !ruby/object:Gem::Version
         
     | 
| 
       4 
     | 
    
         
            -
              version: 0.0 
     | 
| 
      
 4 
     | 
    
         
            +
              version: 0.2.0
         
     | 
| 
       5 
5 
     | 
    
         
             
            platform: ruby
         
     | 
| 
       6 
6 
     | 
    
         
             
            authors:
         
     | 
| 
       7 
7 
     | 
    
         
             
            - Claus Rasmussen
         
     | 
| 
       8 
8 
     | 
    
         
             
            autorequire:
         
     | 
| 
       9 
9 
     | 
    
         
             
            bindir: exe
         
     | 
| 
       10 
10 
     | 
    
         
             
            cert_chain: []
         
     | 
| 
       11 
     | 
    
         
            -
            date: 2022-12- 
     | 
| 
      
 11 
     | 
    
         
            +
            date: 2022-12-15 00:00:00.000000000 Z
         
     | 
| 
       12 
12 
     | 
    
         
             
            dependencies:
         
     | 
| 
       13 
13 
     | 
    
         
             
            - !ruby/object:Gem::Dependency
         
     | 
| 
       14 
14 
     | 
    
         
             
              name: constrain
         
     | 
| 
         @@ -82,6 +82,7 @@ files: 
     | 
|
| 
       82 
82 
     | 
    
         
             
            - lib/modular_tree/algorithms.rb
         
     | 
| 
       83 
83 
     | 
    
         
             
            - lib/modular_tree/filter.rb
         
     | 
| 
       84 
84 
     | 
    
         
             
            - lib/modular_tree/implementations.rb
         
     | 
| 
      
 85 
     | 
    
         
            +
            - lib/modular_tree/matcher.rb
         
     | 
| 
       85 
86 
     | 
    
         
             
            - lib/modular_tree/pairs.rb
         
     | 
| 
       86 
87 
     | 
    
         
             
            - lib/modular_tree/pool.rb
         
     | 
| 
       87 
88 
     | 
    
         
             
            - lib/modular_tree/properties.rb
         
     |