jumoku 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README.md +112 -0
- data/Rakefile +25 -0
- data/lib/jumoku.rb +21 -0
- data/lib/jumoku/builders/raw_tree.rb +243 -0
- data/lib/jumoku/builders/tree.rb +336 -0
- data/lib/jumoku/classes/tree_classes.rb +7 -0
- data/lib/jumoku/ext/ext.rb +50 -0
- data/lib/jumoku/raw_tree_node.rb +27 -0
- data/lib/jumoku/support/branch.rb +4 -0
- data/lib/jumoku/support/support.rb +15 -0
- data/lib/jumoku/tree_api.rb +27 -0
- data/lib/jumoku/version.rb +6 -0
- metadata +111 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) Jean-Denis Vauguet <jd@vauguet.fr>
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
![Texte alternatif](http://img547.imageshack.us/img547/2932/logoJumoku.png "Jumoku logo")
|
2
|
+
|
3
|
+
**Build and manipulate tree structures in Ruby.**
|
4
|
+
|
5
|
+
## Synopsis
|
6
|
+
|
7
|
+
Jumoku provides you with tree structures and related tools to perform manipulation and computation the easy way. Trees are a subset of graphs, which are used in a whole slew of computer science and mathematical problems: network modelization, datasets storage, scientific computation, load balancing, games and AI designs, … Trees are frequently used to mimic hierarchal structures such as filesystems, or modelize decisionnal patterns, for instance.
|
8
|
+
|
9
|
+
Jumoku is built upon [Graphy](http://github.com/bruce/graphy "Graphy on Github"), a ruby-powered Graph Theory library, and aims at being a fully-fledged, pithy solution for tree-like structures managment. See below for additionnal information about graphs, trees, arborescences, why they're different and how to make good use of them.
|
10
|
+
|
11
|
+
## A few words about *trees*
|
12
|
+
|
13
|
+
A Tree is a graph subject to three basic constraints: nodes are all connected, they must not form any loop, and the branches binding nodes have no preferential direction. A tree is not compelled to have a root node and leaves as you may think *prima facie*. Trees with such features are called arborescences and Jumoku has support for them, too.
|
14
|
+
|
15
|
+
Jumoku (*currently under early development stage*) provides you with the following structures:
|
16
|
+
|
17
|
+
* RawTree: a tree-graph with only basic features
|
18
|
+
* **Tree**: a tree-graph with extended features built upon RawTree. That's what you'd want to use as a basic tree structure
|
19
|
+
* AVLTree (*not yet*)
|
20
|
+
* RedBlackTree (*not yet*)
|
21
|
+
* **Arborescence** (*not yet*): the structure everybody thinks of when asked about "trees", with a root, internal nodes and leaves
|
22
|
+
|
23
|
+
You can also extend those structures with hybrid behaviors (not Graph Theory compliant but may be useful):
|
24
|
+
|
25
|
+
* Directed (*not yet*): relax the *undirected* constraint
|
26
|
+
* Loopy (*not yet*): relax the *acyclic* constraint
|
27
|
+
* Atomic (*not yet*): relax the *connected* constraint
|
28
|
+
|
29
|
+
## Basic usage
|
30
|
+
|
31
|
+
To create an instance of a tree, you may use either inheritance or mixins.
|
32
|
+
|
33
|
+
### Inheritance or direct initialization
|
34
|
+
|
35
|
+
``` ruby
|
36
|
+
class MyTree < Jumoku::Tree; end
|
37
|
+
tree = MyTree.new
|
38
|
+
|
39
|
+
# or even simpler:
|
40
|
+
tree = Jumoku::Tree.new
|
41
|
+
```
|
42
|
+
|
43
|
+
### Mixing-in a "builder" module
|
44
|
+
|
45
|
+
``` ruby
|
46
|
+
class MyTree
|
47
|
+
include Jumoku::RawTreeBuilder
|
48
|
+
end
|
49
|
+
tree = MyTree.new
|
50
|
+
```
|
51
|
+
|
52
|
+
Actually the RawTree class is nothing but the mixin code snippet above.
|
53
|
+
|
54
|
+
### What you get
|
55
|
+
|
56
|
+
`tree` is now a Tree object, shipping with some default options:
|
57
|
+
|
58
|
+
* it is a valid tree *per se* (an undirected, acyclic, connected graph)
|
59
|
+
* Jumoku API's methods will ensure it remains so
|
60
|
+
* it has no constraint on its branching number (the number of branches per node)
|
61
|
+
|
62
|
+
## Install, first steps and bootstraping
|
63
|
+
|
64
|
+
In order to play with Jumoku, you must:
|
65
|
+
|
66
|
+
``` bash
|
67
|
+
gem install jumoku
|
68
|
+
```
|
69
|
+
|
70
|
+
Now you can play in IRB:
|
71
|
+
|
72
|
+
``` bash
|
73
|
+
$ irb
|
74
|
+
ruby-1.9.1-p378 > require 'Jumoku'
|
75
|
+
=> true
|
76
|
+
ruby-1.9.1-p378 > include Jumoku # so you won't have to prefix everything with "Jumoku::"
|
77
|
+
=> Object
|
78
|
+
ruby-1.9.1-p378 > t = Tree.new
|
79
|
+
=> #<Jumoku::Tree:0x000000020d5ac8 @vertex_dict={}, @vertex_labels={}, @edge_labels={}, @allow_loops=false, @parallel_edges=false, @edgelist_class=Set>
|
80
|
+
ruby-1.9.1-p378 > t.methods
|
81
|
+
=> # lot of stuff hopefully :)
|
82
|
+
```
|
83
|
+
|
84
|
+
A good way to get you started is by [reading the doc online](http://rdoc.info/projects/chikamichi/Jumoku "Jumoku on rdoc.info"). You can locally generate the doc with any of the following commands (you'll need to have the `yard` gem installed):
|
85
|
+
|
86
|
+
``` bash
|
87
|
+
rake yard
|
88
|
+
yardoc
|
89
|
+
```
|
90
|
+
|
91
|
+
Be sure to take a look at the tests in `spec/` as well, as they document the public API extensively.
|
92
|
+
|
93
|
+
## What about graphs?
|
94
|
+
|
95
|
+
Trees are just simple graphs, with specific constraints on edges (branches) and vertices (nodes). In graph theory, a tree is defined as a graph which is undirected, acyclic and connected. A forest is a disjoint union of trees. Let's review those constraints:
|
96
|
+
|
97
|
+
* undirected: the branches of a tree have no direction;
|
98
|
+
* acyclic: a cycle is defined as a path such that the starting node and the ending node are the same. Such paths are forbidden in a tree;
|
99
|
+
* connected: a tree is such that pair of distinct nodes can be connected through some path (not a cycle).
|
100
|
+
|
101
|
+
This is restrictive, but not *that* restrictive. Instinctively, a tree structure would rather be described as an arborescence, with a root and some leaves. That's what you would use to use to modelize nested directories, for instance. Such a structure has additional constraints, and it makes sense to derive it from the more generalist tree*-as-in-graph-theory* structure, under the name "arborescence".
|
102
|
+
|
103
|
+
Therefore, it is easy to implement those structures using graphs. Fortunately, several ruby graph libraries exist (and even a ruby binding for the igraph C backend, but that'd be a burden to use). Jumoku makes use of the most up-to-date project among those available, [Graphy](http://github.com/bruce/graphy "Graphy on Github").
|
104
|
+
|
105
|
+
## License
|
106
|
+
|
107
|
+
See the LICENSE file.
|
108
|
+
|
109
|
+
## Contributing
|
110
|
+
|
111
|
+
Fork, hack, request!
|
112
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'rubygems'
|
3
|
+
begin
|
4
|
+
require 'bundler/setup'
|
5
|
+
rescue LoadError
|
6
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
7
|
+
end
|
8
|
+
|
9
|
+
require 'rake'
|
10
|
+
require 'rake/rdoctask'
|
11
|
+
|
12
|
+
require 'rspec/core'
|
13
|
+
require 'rspec/core/rake_task'
|
14
|
+
|
15
|
+
RSpec::Core::RakeTask.new(:spec)
|
16
|
+
|
17
|
+
task :default => :spec
|
18
|
+
|
19
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
20
|
+
rdoc.rdoc_dir = 'rdoc'
|
21
|
+
rdoc.title = 'Logg'
|
22
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
23
|
+
rdoc.rdoc_files.include('README.rdoc')
|
24
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
25
|
+
end
|
data/lib/jumoku.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
$:.unshift File.dirname(__FILE__) unless
|
2
|
+
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
3
|
+
|
4
|
+
require 'pathname'
|
5
|
+
require 'pp'
|
6
|
+
require 'active_support'
|
7
|
+
require 'facets'
|
8
|
+
require 'graphy'
|
9
|
+
|
10
|
+
module Jumoku
|
11
|
+
# jumoku internals: graph builders and additionnal behaviors
|
12
|
+
autoload :TreeAPI, 'jumoku/tree_api'
|
13
|
+
autoload :RawTreeBuilder, 'jumoku/builders/raw_tree'
|
14
|
+
autoload :TreeBuilder, 'jumoku/builders/tree'
|
15
|
+
autoload :RawTree, 'jumoku/classes/tree_classes'
|
16
|
+
autoload :Tree, 'jumoku/classes/tree_classes'
|
17
|
+
autoload :Branch, 'jumoku/support/branch'
|
18
|
+
|
19
|
+
require 'jumoku/support/support'
|
20
|
+
require 'jumoku/ext/ext'
|
21
|
+
end
|
@@ -0,0 +1,243 @@
|
|
1
|
+
module Jumoku
|
2
|
+
# This module provides the basic routines needed to implement the specialized builders:
|
3
|
+
# {TreeBuilder}, {BinaryTreeBuilder}, … each of them streamlining {RawTreeBuilder}'s
|
4
|
+
# behavior. Those implementations are under the control of the {TreeAPI}.
|
5
|
+
#
|
6
|
+
# A {RawTree} sticks to the standard definition of trees in Graph Theory: undirected,
|
7
|
+
# connected, acyclic graphs. Using Graphy::UndirectedGraphBuilder as its backend,
|
8
|
+
# {RawTreeBuilder} ensures the two remaining constraints are satisfied (connected and
|
9
|
+
# acyclic). {RawTreeBuilder RawTree} offers limited functionalities, therefore the main
|
10
|
+
# tree structure you'll likely to use is its extended version, {TreeBuilder Tree}. A
|
11
|
+
# {Tree} share the same behavior but is a fully-fledged object with user-friendly public
|
12
|
+
# methods built upon {RawTree}'s internals.
|
13
|
+
#
|
14
|
+
# Note that a node can be any Object. There is no "node type", therefore arguments which
|
15
|
+
# are expected to be nodes are simply labelled as "`node`". A nice object type to us as
|
16
|
+
# a node would be an OpenStruct or an
|
17
|
+
# [OpenObject](http://facets.rubyforge.org/apidoc/api/more/classes/OpenObject.html)
|
18
|
+
# (from the Facets library), as it turns nodes into versatile handlers.
|
19
|
+
#
|
20
|
+
# This builder defines a few methods not required by the API so as to maintain consistency
|
21
|
+
# in the DSL.
|
22
|
+
module RawTreeBuilder
|
23
|
+
include Graphy::UndirectedGraphBuilder
|
24
|
+
|
25
|
+
# This method is called by the specialized implementations
|
26
|
+
# upon tree creation.
|
27
|
+
#
|
28
|
+
# Initialization parameters can include:
|
29
|
+
#
|
30
|
+
# * an array of branches to add
|
31
|
+
# * one or several trees to copy (will be merged if multiple)
|
32
|
+
#
|
33
|
+
# @param *params [Hash] the initialization parameters
|
34
|
+
# @return [RawTree]
|
35
|
+
def initialize(*params)
|
36
|
+
class << self
|
37
|
+
self
|
38
|
+
end.module_eval do
|
39
|
+
# Ensure the builder abides by its API requirements.
|
40
|
+
include Jumoku::TreeAPI
|
41
|
+
end
|
42
|
+
super(*params) # Delegates to Graphy.
|
43
|
+
end
|
44
|
+
|
45
|
+
# Ensure trees are seen as undirected graphs.
|
46
|
+
#
|
47
|
+
# @return [Boolean] false
|
48
|
+
def directed?
|
49
|
+
return false
|
50
|
+
end
|
51
|
+
# FIXME: should be able to reach Graphy's method ?!
|
52
|
+
|
53
|
+
# Adds the node to the tree.
|
54
|
+
#
|
55
|
+
# For convenience, you may pass a branch as the parameter,
|
56
|
+
# which one node already exists in the tree and the other is
|
57
|
+
# to be added.
|
58
|
+
#
|
59
|
+
# @overload add_node!(n)
|
60
|
+
# @param [node] n
|
61
|
+
# @overload add_node!(b)
|
62
|
+
# @param [Branch] b Branch[node i, node j, label l = nil]; if i (j) already exists, then j (i) must not exist
|
63
|
+
# @return [RawTree] self
|
64
|
+
def add_node! u, v = nil
|
65
|
+
if nodes.empty?
|
66
|
+
add_vertex! u
|
67
|
+
elsif u.is_a? Jumoku::Branch
|
68
|
+
add_branch! u
|
69
|
+
elsif not v.nil?
|
70
|
+
add_branch! u, v
|
71
|
+
else
|
72
|
+
# Ensure the connected constraint.
|
73
|
+
raise RawTreeError, "In order to add a node to the tree, you must specify another node to attach to."
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Adds a new branch to the tree.
|
78
|
+
#
|
79
|
+
# As a tree is an undirected structure, the order of the parameters is of
|
80
|
+
# no importance, that is: `add_branch!(u,v) == add_branch!(v,u)`.
|
81
|
+
#
|
82
|
+
# @overload add_branch!(i, j, l = nil)
|
83
|
+
# @param [node] i
|
84
|
+
# @param [node] j
|
85
|
+
# @param [Label] l
|
86
|
+
# @overload add_branch!(b)
|
87
|
+
# @param [Branch] b Branch[node i, node j, label l = nil]; if i (j) already exists, then j (i) must not exist
|
88
|
+
# @return [RawTree] self
|
89
|
+
def add_branch! u, v = nil, l = nil
|
90
|
+
if has_node? u and has_node? v
|
91
|
+
unless has_branch? u, v
|
92
|
+
# Ensure the acyclic constraint.
|
93
|
+
raise RawTreeError, "Can't form a cycle within a tree."
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# TODO: DRY this up.
|
98
|
+
if u.is_a? Jumoku::Branch
|
99
|
+
v = u.target
|
100
|
+
u = u.source
|
101
|
+
end
|
102
|
+
|
103
|
+
if has_node? u or has_node? v or nodes.empty?
|
104
|
+
add_edge! u, v, l
|
105
|
+
else
|
106
|
+
# Ensure the connected constraint.
|
107
|
+
raise RawTreeError, "Can't add a dead branch to a tree."
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# Removes a node from the tree.
|
112
|
+
#
|
113
|
+
# You cannot remove non terminal nodes as it would break the
|
114
|
+
# connectivity constraint of the tree.
|
115
|
+
#
|
116
|
+
# I may add an option which would allow to force removal
|
117
|
+
# of internal nodes and return two new trees from this
|
118
|
+
# destructive operation.
|
119
|
+
#
|
120
|
+
# @param [node] u
|
121
|
+
# @return [RawTree] self
|
122
|
+
def remove_node!(u, force = false)
|
123
|
+
if terminal? u or force
|
124
|
+
remove_vertex! u
|
125
|
+
else
|
126
|
+
# Ensure the connected constraint.
|
127
|
+
raise RawTreeError, "Can't remove a non terminal node in a tree"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# Removes a branch from the tree.
|
132
|
+
#
|
133
|
+
# You cannot remove non terminal branches as it would break
|
134
|
+
# the connectivity constraint of the tree.
|
135
|
+
#
|
136
|
+
# I may add an option which would allow to force removal
|
137
|
+
# of internal nodes and return two new trees from this
|
138
|
+
# destructive operation.
|
139
|
+
#
|
140
|
+
# @overload remove_branch!(i, j)
|
141
|
+
# @param [node] i
|
142
|
+
# @param [node] j
|
143
|
+
# @overload remove_branch!(b)
|
144
|
+
# @param [Branch] b
|
145
|
+
# @return [RawTree] self
|
146
|
+
def remove_branch! u, v = nil
|
147
|
+
if u.is_a? Jumoku::Branch
|
148
|
+
v = u.target
|
149
|
+
u = u.source
|
150
|
+
end
|
151
|
+
|
152
|
+
if has_node? u and has_node? v
|
153
|
+
if terminal? u and terminal? v
|
154
|
+
remove_edge! u, v
|
155
|
+
# empty the tree if u and v are the last two nodes, for they're
|
156
|
+
# not connected anymore
|
157
|
+
if [u, v].all? { |node| nodes.include? node } and nodes.size == 2
|
158
|
+
remove_node! u, true
|
159
|
+
remove_node! v, true
|
160
|
+
end
|
161
|
+
elsif terminal? u and not terminal? v
|
162
|
+
remove_node! u
|
163
|
+
elsif terminal? v and not terminal? u
|
164
|
+
remove_node! v
|
165
|
+
else
|
166
|
+
raise RawTreeError, "Can't remove a non terminal branch in a tree."
|
167
|
+
end
|
168
|
+
else
|
169
|
+
raise RawTreeError, "Can't remove a branch which does not exist."
|
170
|
+
end
|
171
|
+
end
|
172
|
+
# TODO: add an option to force nodes deletion upon branch deletion
|
173
|
+
# (:and_nodes => true) ; called by a wrapping method
|
174
|
+
# remove_branch_and_nodes, alias break_branch
|
175
|
+
# (and their ! roomates).
|
176
|
+
|
177
|
+
# The nodes of the tree in a 1D array.
|
178
|
+
#
|
179
|
+
# @return [Array(node)]
|
180
|
+
def nodes
|
181
|
+
vertices
|
182
|
+
end
|
183
|
+
|
184
|
+
# The terminal nodes of the tree in a 1D array.
|
185
|
+
#
|
186
|
+
# @return [Array(node)] only terminal nodes (empty array if no terminal nodes,
|
187
|
+
# but should never happen in a tree).
|
188
|
+
def terminal_nodes
|
189
|
+
nodes.inject([]) do |t_nodes, node|
|
190
|
+
t_nodes << node if terminal?(node)
|
191
|
+
t_nodes # must return t_nodes explicitily because
|
192
|
+
# (rubydoc Enumerable#inject) "at each step, memo is set to the value returned by the block"
|
193
|
+
# (sets t_nodes to nil otherwise).
|
194
|
+
end
|
195
|
+
end
|
196
|
+
alias boundaries terminal_nodes
|
197
|
+
|
198
|
+
# The branches of the tree in a 1D array.
|
199
|
+
#
|
200
|
+
# @return [Array(Branch)]
|
201
|
+
def branches
|
202
|
+
edges
|
203
|
+
end
|
204
|
+
|
205
|
+
# Aliasing.
|
206
|
+
alias has_node? has_vertex?
|
207
|
+
alias has_branch? has_edge?
|
208
|
+
|
209
|
+
# Tree helpers.
|
210
|
+
|
211
|
+
# Checks whether the tree is *really* a valid tree, that is if the
|
212
|
+
# following conditions are fulfilled:
|
213
|
+
#
|
214
|
+
# * undirected
|
215
|
+
# * acyclic
|
216
|
+
# * connected
|
217
|
+
#
|
218
|
+
# @return [Boolean]
|
219
|
+
def valid?
|
220
|
+
acyclic? and connected? and not directed?
|
221
|
+
end
|
222
|
+
|
223
|
+
# Is the node a terminal node?
|
224
|
+
#
|
225
|
+
# @return [Boolean]
|
226
|
+
def terminal? u
|
227
|
+
if has_node? u
|
228
|
+
# root is always terminal, otherwise check whether degree is unitary
|
229
|
+
nodes == [u] ? true : (degree(u) == 1)
|
230
|
+
else
|
231
|
+
raise RawTreeNodeError, "Not a node of this tree."
|
232
|
+
end
|
233
|
+
end
|
234
|
+
alias has_terminal_node? terminal?
|
235
|
+
|
236
|
+
# Is the tree empty?
|
237
|
+
#
|
238
|
+
# @return [Boolean]
|
239
|
+
def empty?
|
240
|
+
nodes.empty?
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
@@ -0,0 +1,336 @@
|
|
1
|
+
module Jumoku
|
2
|
+
# This builder extends the cheap implementation {RawTreeBuilder}, which
|
3
|
+
# purpose is to implement the {TreeAPI}. {TreeBuilder} provides extended
|
4
|
+
# functionalities and acts as the main tree structure you may use, either
|
5
|
+
# by mixing-in this module or by inheritance of the associated {Tree} class.
|
6
|
+
#
|
7
|
+
# tree = Tree.new
|
8
|
+
#
|
9
|
+
# # or
|
10
|
+
#
|
11
|
+
# class MyTree < Tree
|
12
|
+
# # your stuff
|
13
|
+
# end
|
14
|
+
# tree = MyTree.new
|
15
|
+
#
|
16
|
+
# # or
|
17
|
+
#
|
18
|
+
# class MyTree
|
19
|
+
# include TreeBuilder
|
20
|
+
# # your stuff
|
21
|
+
# end
|
22
|
+
# tree = MyTree.new
|
23
|
+
module TreeBuilder
|
24
|
+
include RawTreeBuilder
|
25
|
+
|
26
|
+
def initialize(*params)
|
27
|
+
super(*params)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Non destructive version of {RawTreeBuilder#add_node!} (works on a copy of the tree).
|
31
|
+
#
|
32
|
+
# @overload add_node!(n)
|
33
|
+
# @param [node] n
|
34
|
+
# @overload add_node!(b)
|
35
|
+
# @param [Branch] b Branch[node i, node j, label l = nil]; if i (j) already exists, then j (i) must not exist
|
36
|
+
# @return [Tree] a modified copy of `self`
|
37
|
+
# @see RawTreeBuilder#add_node!
|
38
|
+
def add_node(n, l = nil)
|
39
|
+
x = self.class.new(self)
|
40
|
+
x.add_node!(n, l)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Non destructive version {RawTreeBuilder#add_branch!} (works on a copy of the tree).
|
44
|
+
#
|
45
|
+
# @overload add_branch!(i, j, l = nil)
|
46
|
+
# @param [node] i
|
47
|
+
# @param [node] j
|
48
|
+
# @param [Label] l
|
49
|
+
# @overload add_branch!(b)
|
50
|
+
# @param [Branch] b Branch[node i, node j, label l = nil]; if i (j) already exists, then j (i) must not exist
|
51
|
+
# @return [Tree] a modified copy of `self`
|
52
|
+
# @see RawTreeBuilder#add_branch!
|
53
|
+
def add_branch(i, j = nil, l = nil)
|
54
|
+
x = self.class.new(self)
|
55
|
+
x.add_branch!(i, j, l)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Non destructive version of {RawTreeBuilder#remove_node!} (works on a copy of the tree).
|
59
|
+
#
|
60
|
+
# @param [node] n
|
61
|
+
# @return [Tree] a modified copy of `self`
|
62
|
+
# @see RawTreeBuilder#remove_node!
|
63
|
+
def remove_node(n)
|
64
|
+
x = self.class.new(self)
|
65
|
+
x.remove_node!(n)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Non destructive version {RawTreeBuilder#remove_branch!} (works on a copy of the tree).
|
69
|
+
#
|
70
|
+
# You cannot remove non terminal branches as it would break the connectivity constraint
|
71
|
+
# of the tree.
|
72
|
+
#
|
73
|
+
# @param [node] i
|
74
|
+
# @param [node] j
|
75
|
+
# @return [Tree] a modified copy of `self`
|
76
|
+
# @see RawTreeBuilder#remove_branch!
|
77
|
+
def remove_branch(i, j = nil, *params)
|
78
|
+
x = self.class.new(self)
|
79
|
+
x.remove_branch!(i, j)
|
80
|
+
end
|
81
|
+
|
82
|
+
# Adds all specified nodes to the node set.
|
83
|
+
#
|
84
|
+
# The nodes defines implicit branches, that is it is mandatory to provide
|
85
|
+
# an odd number of connected nodes which do not form cycles. You may specify
|
86
|
+
# Branch objects within the list, though.
|
87
|
+
#
|
88
|
+
# Valid usage:
|
89
|
+
#
|
90
|
+
# tree = Tree.new # an empty tree
|
91
|
+
# tree.add_nodes!(1,2, 2,3, 3,4) # tree.nodes => [1, 2, 3, 4]
|
92
|
+
# # branches are (1-2), (2-3), (3-4)
|
93
|
+
# tree.add_nodes!(1,2, 2,3, Branch.new(3,4), 10,1)
|
94
|
+
# tree.add_nodes! [1,2, 2,3, 3,4]
|
95
|
+
#
|
96
|
+
# Invalid usages:
|
97
|
+
#
|
98
|
+
# tree = Tree.new # an empty tree
|
99
|
+
# tree.add_nodes!(1, 2, 3) # even number of nodes
|
100
|
+
# tree.add_nodes!(1,2, 2,3, 3,1) # cycle
|
101
|
+
# tree.add_nodes!(1,2, 4,5) # not connected
|
102
|
+
#
|
103
|
+
# A helper exist to make it easy to add nodes in a suite.
|
104
|
+
# See {TreeBuilder#add_consecutive_nodes! add_consecutive_nodes!}.
|
105
|
+
#
|
106
|
+
# @param [#each] *a an Enumerable nodes set
|
107
|
+
# @return [Tree] `self`
|
108
|
+
def add_nodes!(*a)
|
109
|
+
a = a.flatten.expand_branches!
|
110
|
+
if a.size == 1 # This allows for using << to add the root as well,
|
111
|
+
if empty? # so that you may write:
|
112
|
+
add_node! a.only # Tree.new << :root << [:root, 1] << [1,2, 2,3, Branch.new(3,4)]...
|
113
|
+
else
|
114
|
+
raise RawTreeError, "Cannot add a node without a neighbour."
|
115
|
+
end
|
116
|
+
else
|
117
|
+
a.expand_branches!.each_nodes_pair { |pair| add_branch! pair[0], pair[1] }
|
118
|
+
end
|
119
|
+
self
|
120
|
+
end
|
121
|
+
alias << add_nodes!
|
122
|
+
|
123
|
+
# Same as {TreeBuilder#add_nodes! add_nodes!} but works on a copy of the receiver.
|
124
|
+
#
|
125
|
+
# @param [#each] *a an Enumerable nodes set
|
126
|
+
# @return [Tree] a modified copy of `self`
|
127
|
+
def add_nodes(*a)
|
128
|
+
x = self.class.new(self)
|
129
|
+
x.add_nodes!(*a)
|
130
|
+
end
|
131
|
+
|
132
|
+
# Allows for adding consecutive nodes, each nodes being connected to their previous and
|
133
|
+
# next neighbours.
|
134
|
+
#
|
135
|
+
# You may pass {Branch branches} within the nodes.
|
136
|
+
#
|
137
|
+
# Tree.new.add_consecutive_nodes!(1, 2, 3, :four, Branch.new(:foo, "bar")).branches
|
138
|
+
# # => (1=2, 2=3, 3=:four, :four=:foo, :foo="bar")
|
139
|
+
#
|
140
|
+
# @param [Array(nodes)] *a flat array of unique nodes
|
141
|
+
# @return [Tree] `self`
|
142
|
+
def add_consecutive_nodes!(*a)
|
143
|
+
# FIXME: really this may not be as efficient as it could be.
|
144
|
+
# We may get rid of nodes duplication (no expand_branches!)
|
145
|
+
# and add nodes by pair, shifting one node at a time from the list.
|
146
|
+
add_nodes!(a.expand_branches!.create_nodes_pairs_list)
|
147
|
+
self
|
148
|
+
end
|
149
|
+
|
150
|
+
# Same as {TreeBuilder#add_consecutive_nodes! add_consecutive_nodes!} but
|
151
|
+
# works on a copy of the receiver.
|
152
|
+
#
|
153
|
+
# @param [Array(nodes)] *a flat array of unique nodes
|
154
|
+
# @return [Tree] a modified copy of `self`
|
155
|
+
def add_consecutive_nodes(*a)
|
156
|
+
x = self.class.new(self)
|
157
|
+
x.add_consecutive_nodes!(*a)
|
158
|
+
end
|
159
|
+
|
160
|
+
# Adds all branches mentionned in the specified Enumerable to the branch set.
|
161
|
+
#
|
162
|
+
# Elements of the Enumerable can be either two-element arrays or instances of
|
163
|
+
# {Branch}.
|
164
|
+
#
|
165
|
+
# @param [#each] *a an Enumerable branches set
|
166
|
+
# @return [Tree] `self`
|
167
|
+
def add_branches!(*a)
|
168
|
+
a.expand_branches!.each_nodes_pair { |pair| add_branch! pair[0], pair[1] }
|
169
|
+
self
|
170
|
+
end
|
171
|
+
|
172
|
+
# Same as {TreeBuilder#add_branches! add_branches!} but works on a copy of the receiver.
|
173
|
+
#
|
174
|
+
# @param [#each] *a an Enumerable branches set
|
175
|
+
# @return [Tree] a modified copy of `self`
|
176
|
+
def add_branches(*a)
|
177
|
+
x = self.class.new(self)
|
178
|
+
x.add_branches!(*a)
|
179
|
+
end
|
180
|
+
|
181
|
+
# Removes all nodes mentionned in the specified Enumerable from the tree.
|
182
|
+
#
|
183
|
+
# The process relies on {RawTreeBuilder#remove_node! remove_node!}.
|
184
|
+
#
|
185
|
+
# @param [#each] *a an Enumerable nodes set
|
186
|
+
# @return [Tree] `self`
|
187
|
+
def remove_nodes!(*a)
|
188
|
+
a.flatten.each { |v| remove_node! v }
|
189
|
+
self
|
190
|
+
end
|
191
|
+
alias delete_nodes! remove_nodes!
|
192
|
+
|
193
|
+
# Same as {TreeBuilder#remove_nodes! remove_nodes!} but works on a copy of the receiver.
|
194
|
+
#
|
195
|
+
# @param [#each] *a a node Enumerable set
|
196
|
+
# @return [Tree] a modified copy of `self`
|
197
|
+
def remove_nodes(*a)
|
198
|
+
x = self.class.new(self)
|
199
|
+
x.remove_nodes!(*a)
|
200
|
+
end
|
201
|
+
alias delete_nodes remove_nodes
|
202
|
+
|
203
|
+
# Removes all branches mentionned in the specified Enumerable from the tree.
|
204
|
+
#
|
205
|
+
# @param [#each] *a an Enumerable branches set
|
206
|
+
# @return [Tree] `self`
|
207
|
+
def remove_branches!(*a)
|
208
|
+
a.expand_branches!.each_nodes_pair { |pair| remove_branch! pair[0], pair[1] }
|
209
|
+
self
|
210
|
+
end
|
211
|
+
alias delete_branches! remove_branches!
|
212
|
+
|
213
|
+
# Same as {TreeBuilder#remove_branches! remove_branches!} but works on a copy of the receiver.
|
214
|
+
#
|
215
|
+
# @param [#each] *a an Enumerable branches set
|
216
|
+
# @return [Tree] a modified copy of `self`
|
217
|
+
# @see RawTreeBuilder#remove_branch!
|
218
|
+
def remove_branches(*a)
|
219
|
+
x = self.class.new(self)
|
220
|
+
x.remove_branches!(*a)
|
221
|
+
end
|
222
|
+
alias delete_branches remove_branches
|
223
|
+
|
224
|
+
# Returns true if the specified node belongs to the tree.
|
225
|
+
#
|
226
|
+
# This is a default implementation that is of O(n) average complexity.
|
227
|
+
# If a subclass uses a hash to store nodes, then this can be
|
228
|
+
# made into an O(1) average complexity operation.
|
229
|
+
#
|
230
|
+
# @param [node] n
|
231
|
+
# @return [Boolean]
|
232
|
+
def node?(n)
|
233
|
+
nodes.include?(n)
|
234
|
+
end
|
235
|
+
alias has_node? node?
|
236
|
+
|
237
|
+
# Returns true if all specified nodes belong to the tree.
|
238
|
+
#
|
239
|
+
# @param [nodes]
|
240
|
+
# @return [Boolean]
|
241
|
+
def nodes?(*maybe_nodes)
|
242
|
+
maybe_nodes.all? { |node| nodes.include? node }
|
243
|
+
end
|
244
|
+
alias has_nodes? nodes?
|
245
|
+
|
246
|
+
# Returns true if any of the specified nodes belongs to the tree.
|
247
|
+
#
|
248
|
+
# @param [nodes]
|
249
|
+
# @return [Boolean]
|
250
|
+
def nodes_among?(*maybe_nodes)
|
251
|
+
maybe_nodes.any? { |node| nodes.include? node }
|
252
|
+
end
|
253
|
+
alias has_nodes_among? nodes_among?
|
254
|
+
alias has_node_among? nodes_among?
|
255
|
+
# FIXME: these helpers could be backported into Graphy.
|
256
|
+
|
257
|
+
# Returns true if i or (i,j) is a {Branch branch} of the tree.
|
258
|
+
#
|
259
|
+
# @overload branch?(a)
|
260
|
+
# @param [Branch] a
|
261
|
+
# @overload branch?(i, j)
|
262
|
+
# @param [node] i
|
263
|
+
# @param [node] j
|
264
|
+
# @return [Boolean]
|
265
|
+
def branch?(*args)
|
266
|
+
branches.include?(edge_convert(*args))
|
267
|
+
end
|
268
|
+
alias has_branch? branch?
|
269
|
+
|
270
|
+
# Returns true if the specified branches are a subset of or match exactly the
|
271
|
+
# branches set of the tree (no ordering criterion).
|
272
|
+
#
|
273
|
+
# Labels are not part of the matching constraint: only connected nodes
|
274
|
+
# matter in defining branch equality.
|
275
|
+
#
|
276
|
+
# @param [*Branch, *nodes] *maybe_branches a list of branches, either as Branch
|
277
|
+
# objects or as nodes pairs
|
278
|
+
# @return [Boolean]
|
279
|
+
def branches?(*maybe_branches)
|
280
|
+
list = maybe_branches.create_branches_list
|
281
|
+
all = true
|
282
|
+
|
283
|
+
# Branch objects are really Edge objects within Graphy, therefore
|
284
|
+
# cannot rely on #eql? to compare those structures and must drop
|
285
|
+
# down to the attributes.
|
286
|
+
list.each do |e| # Jumoku::Branch structs
|
287
|
+
all = branches.any? do |b| # Graphy::Edge structs
|
288
|
+
(b[:source] == e[:source]) and (b[:target] == e[:target])
|
289
|
+
end
|
290
|
+
end
|
291
|
+
all
|
292
|
+
end
|
293
|
+
alias has_branches? branches?
|
294
|
+
|
295
|
+
# Returns true if actual branches are included in the specified set of branches
|
296
|
+
# (no ordering criterion).
|
297
|
+
#
|
298
|
+
# Labels are not part of the matching constraint: only connected nodes
|
299
|
+
# matter in defining equality.
|
300
|
+
#
|
301
|
+
# @param [*Branch, *nodes] *maybe_branches a list of branches, either as Branch
|
302
|
+
# objects or as nodes pairs
|
303
|
+
# @return [Boolean]
|
304
|
+
def branches_among?(*maybe_branches)
|
305
|
+
list = maybe_branches.create_branches_list
|
306
|
+
all = true
|
307
|
+
|
308
|
+
# Branch objects are really Edge objects within Graphy, therefore
|
309
|
+
# cannot rely on #eql? to compare those structures and must drop
|
310
|
+
# down to the attributes.
|
311
|
+
branches.each do |e| # Graphy::Edge structs
|
312
|
+
all = list.any? do |b| # Jumoku::Branch structs
|
313
|
+
(b[:source] == e[:source]) and (b[:target] == e[:target])
|
314
|
+
end
|
315
|
+
end
|
316
|
+
all
|
317
|
+
end
|
318
|
+
alias has_branches_among? branches_among?
|
319
|
+
|
320
|
+
# Number of nodes.
|
321
|
+
#
|
322
|
+
# @return [Integer]
|
323
|
+
def num_nodes
|
324
|
+
nodes.size
|
325
|
+
end
|
326
|
+
alias number_of_nodes num_nodes
|
327
|
+
|
328
|
+
# Number of branches.
|
329
|
+
#
|
330
|
+
# @return [Integer]
|
331
|
+
def num_branches
|
332
|
+
branches.size
|
333
|
+
end
|
334
|
+
alias number_of_branches num_branches
|
335
|
+
end
|
336
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
class Array
|
2
|
+
# Takes a flat array of nodes pairs and yield
|
3
|
+
# them to the block.
|
4
|
+
def each_nodes_pair
|
5
|
+
raise if self.size % 2 != 0
|
6
|
+
each_by(2) { |pair| yield pair }
|
7
|
+
end
|
8
|
+
|
9
|
+
# Fetches the positions of Branch items in an array,
|
10
|
+
# and expands them in place as nodes pairs.
|
11
|
+
#
|
12
|
+
# expand_branches!(1,2, Branch.new(2, 3), 3,4, 4,5)
|
13
|
+
# # => 1,2, 2,3, 3,4, 4,5
|
14
|
+
#
|
15
|
+
# @param [*nodes, *Branch] *a a flat list of nodes pairs and branches
|
16
|
+
# @return [*nodes] a flat list of nodes pairs
|
17
|
+
def expand_branches!
|
18
|
+
branches_positions = self.dup.map_send(:"is_a?", Jumoku::Branch).map_with_index { |e,i| i if e == true }.compact
|
19
|
+
# obviously positions will gain an offset of 1 for each expanded branch,
|
20
|
+
# so let's correct this right away
|
21
|
+
branches_positions = branches_positions.map_with_index { |e,i| e += i }
|
22
|
+
branches_positions.each do |pos|
|
23
|
+
branch = self.delete_at pos
|
24
|
+
self.insert pos, branch.source
|
25
|
+
self.insert pos+1, branch.target
|
26
|
+
end
|
27
|
+
return self
|
28
|
+
end
|
29
|
+
|
30
|
+
def create_pairs
|
31
|
+
self.map_with_index { |e,i| ((pairs ||= []) << e).concat(self.values_at(i+1)) }[0..-2]
|
32
|
+
end
|
33
|
+
|
34
|
+
# Creates nodes pairs from a flat array specifying unique nodes.
|
35
|
+
#
|
36
|
+
# b = [1, 2, 3, "foo", :bar, Array.new]
|
37
|
+
# b.create_nodes_pairs
|
38
|
+
# => [1, 2, 2, 3, 3, "foo", "foo", :bar, :bar, []]
|
39
|
+
#
|
40
|
+
# @return [Array(nodes)]
|
41
|
+
def create_nodes_pairs_list
|
42
|
+
self.create_pairs.flatten(1)
|
43
|
+
end
|
44
|
+
|
45
|
+
def create_branches_list
|
46
|
+
branches = []
|
47
|
+
self.expand_branches!.each_by(2) { |pair| branches << Jumoku::Branch.new(pair[0], pair[1]) }
|
48
|
+
branches
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Jumoku
|
2
|
+
module RawTree
|
3
|
+
|
4
|
+
# This module describes a raw tree node.
|
5
|
+
#
|
6
|
+
# You can include it in a class of your own to make it act
|
7
|
+
# as a raw tree node, while customizing behaviors.
|
8
|
+
module Node
|
9
|
+
|
10
|
+
# Creates a new node.
|
11
|
+
#
|
12
|
+
# @param [#to_s, #to_sym] name a valid name, considering the :names_as options
|
13
|
+
# passed to the raw tree you want to create a node for.
|
14
|
+
# @param [Object] data the data field type to use. Defaults to an empty OpenObject
|
15
|
+
# @param [Array(#to_s)] children an array of children nodes ids
|
16
|
+
#
|
17
|
+
# @return [OpenObject] the node as an OpenObject instance
|
18
|
+
def create name, data = OpenObject.new, children = []
|
19
|
+
@name = name
|
20
|
+
@data = data
|
21
|
+
@children = children
|
22
|
+
|
23
|
+
OpenObject.new(:name => @name, :data => @data, :children => @children)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Jumoku
|
2
|
+
# Base error class for the library.
|
3
|
+
class JumokuError < StandardError; end
|
4
|
+
|
5
|
+
# Raised in several circumstances when the basic constraints related
|
6
|
+
# to a tree are not fulfilled (undirected, acyclic, connected).
|
7
|
+
|
8
|
+
class RawTreeError < JumokuError; end
|
9
|
+
# Error related to nodes.
|
10
|
+
|
11
|
+
class RawTreeNodeError < RawTreeError; end
|
12
|
+
# Raised if one attempts to add an already existing branch to a tree.
|
13
|
+
|
14
|
+
class BranchAlreadyExistError < JumokuError; end
|
15
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Jumoku
|
2
|
+
# This module defines the minimum set of functions required to make a tree that can
|
3
|
+
# use the algorithms defined by this library.
|
4
|
+
#
|
5
|
+
# Each implementation module must implement the following routines:
|
6
|
+
#
|
7
|
+
# * add_node!(n, l = nil) — adds a node to the tree and return the tree; l is an optional label (see Graphy library).
|
8
|
+
# * add_branch!(i, j = nil, l = nil) — adds a branch to the tree and return the tree. i can be a {Branch}, or (i,j) a node pair; l is an optional label.
|
9
|
+
# * remove_node!(n) — removes a node from the tree and return the tree.
|
10
|
+
# * remove_branch!(i, j = nil) — removes an edge from the graph and return the tree. i can be a {Branch}, or (i,j) a node pair.
|
11
|
+
# * nodes — returns an array of all nodes.
|
12
|
+
# * terminal_nodes — returns an array of all terminal nodes.
|
13
|
+
# * branches — returns an array of all branches.
|
14
|
+
module TreeAPI
|
15
|
+
|
16
|
+
# @raise if the API is not completely implemented
|
17
|
+
def self.included(klass)
|
18
|
+
@api_methods ||= [:add_node!, :add_branch!, :remove_node!, :remove_branch!, :nodes, :terminal_nodes, :branches]
|
19
|
+
ruby_18 { @api_methods.each { |m| m.to_s } }
|
20
|
+
|
21
|
+
@api_methods.each do |meth|
|
22
|
+
raise "Must implement #{meth}" unless klass.instance_methods.include?(meth)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
metadata
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: jumoku
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.1.1
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Jean-Denis Vauguet <jd@vauguet.fr>
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-07-06 00:00:00 +02:00
|
14
|
+
default_executable:
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: activesupport
|
18
|
+
prerelease: false
|
19
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
20
|
+
none: false
|
21
|
+
requirements:
|
22
|
+
- - ">="
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: "0"
|
25
|
+
type: :runtime
|
26
|
+
version_requirements: *id001
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: facets
|
29
|
+
prerelease: false
|
30
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
31
|
+
none: false
|
32
|
+
requirements:
|
33
|
+
- - ">="
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: "0"
|
36
|
+
type: :runtime
|
37
|
+
version_requirements: *id002
|
38
|
+
- !ruby/object:Gem::Dependency
|
39
|
+
name: rspec
|
40
|
+
prerelease: false
|
41
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: "0"
|
47
|
+
type: :development
|
48
|
+
version_requirements: *id003
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: yard
|
51
|
+
prerelease: false
|
52
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: "0"
|
58
|
+
type: :development
|
59
|
+
version_requirements: *id004
|
60
|
+
description: Jumoku provides you with tree behaviors to mixin and tree classes to inherit from. Raw tree, common binary trees, custom trees...
|
61
|
+
email: jd@vauguet.fr
|
62
|
+
executables: []
|
63
|
+
|
64
|
+
extensions: []
|
65
|
+
|
66
|
+
extra_rdoc_files: []
|
67
|
+
|
68
|
+
files:
|
69
|
+
- lib/jumoku.rb
|
70
|
+
- lib/jumoku/tree_api.rb
|
71
|
+
- lib/jumoku/ext/ext.rb
|
72
|
+
- lib/jumoku/builders/raw_tree.rb
|
73
|
+
- lib/jumoku/builders/tree.rb
|
74
|
+
- lib/jumoku/raw_tree_node.rb
|
75
|
+
- lib/jumoku/classes/tree_classes.rb
|
76
|
+
- lib/jumoku/support/support.rb
|
77
|
+
- lib/jumoku/support/branch.rb
|
78
|
+
- lib/jumoku/version.rb
|
79
|
+
- LICENSE
|
80
|
+
- Rakefile
|
81
|
+
- README.md
|
82
|
+
has_rdoc: true
|
83
|
+
homepage: http://github.com/chikamichi/jumoku
|
84
|
+
licenses: []
|
85
|
+
|
86
|
+
post_install_message:
|
87
|
+
rdoc_options: []
|
88
|
+
|
89
|
+
require_paths:
|
90
|
+
- lib
|
91
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
92
|
+
none: false
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: "0"
|
97
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
98
|
+
none: false
|
99
|
+
requirements:
|
100
|
+
- - ">="
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: "0"
|
103
|
+
requirements: []
|
104
|
+
|
105
|
+
rubyforge_project:
|
106
|
+
rubygems_version: 1.6.1
|
107
|
+
signing_key:
|
108
|
+
specification_version: 3
|
109
|
+
summary: A fully fledged tree library for Ruby.
|
110
|
+
test_files: []
|
111
|
+
|