traversal 0.0.1
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.
- data/.gitignore +6 -0
- data/Gemfile +9 -0
- data/README.rdoc +96 -0
- data/Rakefile +1 -0
- data/lib/traversal.rb +11 -0
- data/lib/traversal/acts_as_traversable.rb +39 -0
- data/lib/traversal/description.rb +133 -0
- data/lib/traversal/iterator.rb +90 -0
- data/lib/traversal/version.rb +3 -0
- data/spec/test_helper.rb +8 -0
- data/spec/traversal_spec.rb +151 -0
- data/traversal.gemspec +24 -0
- metadata +101 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
== Synopsys
|
2
|
+
Simple traversal API for pure Ruby objects. Also it can be used it with ActiveRecord or DataMapper (or any other ORM).
|
3
|
+
|
4
|
+
== Installation
|
5
|
+
Install it via rubygems:
|
6
|
+
gem install traversal
|
7
|
+
|
8
|
+
In your Gemfile:
|
9
|
+
gem 'traversal'
|
10
|
+
|
11
|
+
== Usage
|
12
|
+
Imagine tree(-ish) structure:
|
13
|
+
plants:
|
14
|
+
vegetables:
|
15
|
+
- cucumber
|
16
|
+
- tomato
|
17
|
+
fruits:
|
18
|
+
- apple
|
19
|
+
- banana
|
20
|
+
|
21
|
+
In Ruby it'll look like this:
|
22
|
+
class Node
|
23
|
+
attr_reader :name, :children
|
24
|
+
|
25
|
+
def initialize(name)
|
26
|
+
@name = name
|
27
|
+
@children = []
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# lets recreate tree structure from above
|
32
|
+
root = Node.new('plants')
|
33
|
+
veg = Node.new('vegetables')
|
34
|
+
fruits = Node.new('fruits')
|
35
|
+
cucumber = Node.new('cucumber')
|
36
|
+
tomato = Node.new('tomato')
|
37
|
+
apple = Node.new('apple')
|
38
|
+
banana = Node.new('banana')
|
39
|
+
|
40
|
+
root.children << veg << fruits
|
41
|
+
veg.children << cucumber << tomato
|
42
|
+
fruits.children << apple << banana
|
43
|
+
|
44
|
+
So, we have a simple tree with <code>root</code> element on the top of it.
|
45
|
+
Now let's create a <b>traversal description</b>.
|
46
|
+
require 'traversal'
|
47
|
+
|
48
|
+
traversal = Traversal::Description.new
|
49
|
+
traversal.traverse(root). # start from root node
|
50
|
+
follow(:children) # move forward via children relations
|
51
|
+
|
52
|
+
It's a minimal traversal description. It has <b>start node</b> and <b>relation</b> pointer (<code>children</code>, in this case).
|
53
|
+
Traversal description is <code>Enumerable</code> object. Let's examine our traverse:
|
54
|
+
traversal.map { |n| n.name } # should be equal to [root, vegetables, cucumber, tomato, fruits, apple, banana]
|
55
|
+
|
56
|
+
Let's look closer:
|
57
|
+
1. We are starting from <code>root</code> node. It's first element.
|
58
|
+
2. Traversal cursor moves to the first child of <code>root</code>: <code>vegetables</code>
|
59
|
+
3. By default cursor moves deeper, to first child of <code>vegetables</code> node: <code>cucumber</code>
|
60
|
+
4. <code>cucumber</code> has no children, traversal cursor moves to the next child of <code>vegetables</code>: <code>tomato</code>
|
61
|
+
5. Traversal cursor moves to the next child of <code>root</code> - <code>fruits</code>
|
62
|
+
6. Traversal cursor visits children of <code>fruits</code>: <code>apple</code> and <code>banana</code>
|
63
|
+
7. All nodes are visited, cursor closed.
|
64
|
+
|
65
|
+
If you want the cursor to visit all children before visiting grandchildren, then you have to declare <code>breadth_first</code> traversal visiting strategy:
|
66
|
+
traversal.breadth_first # in opposite of traversal.depth_first
|
67
|
+
|
68
|
+
You can exclude nodes (but allow cursor to follow relations) from final result:
|
69
|
+
traversal.exclude { |node| node.children.length > 0 } # all nodes with children will be excluded from result
|
70
|
+
|
71
|
+
You can prune away any node children (but leave the node in final result):
|
72
|
+
traversal.prune { |node| node.name == "vegetables" }.
|
73
|
+
map(&:name) # will produce [root, vegetables, fruits, apple, banana]
|
74
|
+
|
75
|
+
Or, you can exclude node and prune away it's children:
|
76
|
+
traversal.prune_and_exclude { |node| node.name == "vegetables" }.
|
77
|
+
map(&:name) # will produce [root, fruits, apple, banana]
|
78
|
+
|
79
|
+
Also, you can mark any node as "loop terminator":
|
80
|
+
traversal.stop_before { |node| node.name == "vegetables" }.to_a # will produce only [root]
|
81
|
+
traversal.stop_after { |node| node.name == "vegetables" }.to_a # will produce [root, vegetables]
|
82
|
+
|
83
|
+
== Real world example
|
84
|
+
require "traversable"
|
85
|
+
|
86
|
+
class Page < ActiveRecord::Base
|
87
|
+
acts_as_tree
|
88
|
+
acts_as_traversable
|
89
|
+
end
|
90
|
+
|
91
|
+
lim = 15
|
92
|
+
|
93
|
+
Page.root.traverse(:children).
|
94
|
+
exclude(:root?).
|
95
|
+
exclude_and_prune(:is_deleted?).
|
96
|
+
stop_after { (lmt -= 1) == 0 }
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/lib/traversal.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require "traversal/version"
|
2
|
+
|
3
|
+
module Traversal
|
4
|
+
autoload :Description, "traversal/description"
|
5
|
+
autoload :Iterator, "traversal/iterator"
|
6
|
+
autoload :ActsAsTraversable, "traversal/acts_as_traversable"
|
7
|
+
|
8
|
+
class IncompleteDescription < Exception; end
|
9
|
+
end
|
10
|
+
|
11
|
+
Object.extend(Traversal::ActsAsTraversable)
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Traversal
|
2
|
+
module ActsAsTraversable
|
3
|
+
# == Synopsys
|
4
|
+
# Mix-in <tt>#traverse</tt> method
|
5
|
+
#
|
6
|
+
# == Example
|
7
|
+
# class TreeNode
|
8
|
+
# attr_accessor :siblings
|
9
|
+
#
|
10
|
+
# acts_as_traversable
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# t = TreeNode.new
|
14
|
+
# t.traverse # equivalent to Traversal::Description.new.traverse(t)
|
15
|
+
# t.traverse(:siblings) # equivalent to Traversal::Description.new.traverse(t).follow(:siblings)
|
16
|
+
def acts_as_traversable
|
17
|
+
include InstanceMethods
|
18
|
+
end
|
19
|
+
alias acts_like_traversable acts_as_traversable
|
20
|
+
|
21
|
+
module InstanceMethods
|
22
|
+
# Shortcut method, simplified interface to Traversal::Description
|
23
|
+
#
|
24
|
+
# == Example
|
25
|
+
# class TreeNode
|
26
|
+
# attr_accessor :siblings
|
27
|
+
#
|
28
|
+
# acts_as_traversable
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# t = TreeNode.new
|
32
|
+
# t.traverse # equivalent to Traversal::Description.new.traverse(t)
|
33
|
+
# t.traverse(:siblings) # equivalent to Traversal::Description.new.traverse(t).follow(:siblings)
|
34
|
+
def traverse(relation = nil)
|
35
|
+
Traversal::Description.new.traverse(self).tap { |desc| desc.follow(relation) if relation }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
module Traversal
|
3
|
+
# Traversal description
|
4
|
+
class Description
|
5
|
+
include Enumerable
|
6
|
+
|
7
|
+
DEPTH_FIRST = 0
|
8
|
+
BREADTH_FIRST = 1
|
9
|
+
|
10
|
+
attr_reader :start_node, :relation
|
11
|
+
|
12
|
+
# Create blank traversal description
|
13
|
+
def initialize
|
14
|
+
@exclude = []
|
15
|
+
@prune = []
|
16
|
+
@stop_before = []
|
17
|
+
@stop_after = []
|
18
|
+
|
19
|
+
@start_node = nil
|
20
|
+
@relation = nil
|
21
|
+
|
22
|
+
@order = DEPTH_FIRST
|
23
|
+
end
|
24
|
+
|
25
|
+
# Declare a traversal start point. From which node you want to follow relations?
|
26
|
+
def traverse(start_node)
|
27
|
+
tap { @start_node = start_node }
|
28
|
+
end
|
29
|
+
|
30
|
+
# Declare which relation you want to follow in your traversal.
|
31
|
+
# It can be Symbol, Method, Proc or block.
|
32
|
+
#
|
33
|
+
# Relation should return something enumerable, otherwise it will be ignored in traversal.
|
34
|
+
#
|
35
|
+
# == Example
|
36
|
+
# traversal.follow(:children) # for each node will call node#children method
|
37
|
+
# traversal.follow { |node| node.children } # same effect
|
38
|
+
def follow(relation = nil, &blk)
|
39
|
+
tap { @relation = condition("Relation", relation, &blk) }
|
40
|
+
end
|
41
|
+
|
42
|
+
# Declare exclude condition. Which nodes you want to
|
43
|
+
# exclude (ignore them but not their relations) from your traversal?
|
44
|
+
def exclude(cond = nil, &blk)
|
45
|
+
tap { @exclude << condition("Exclude condition", cond, &blk) }
|
46
|
+
end
|
47
|
+
|
48
|
+
# Declare prune condition. Which nodes relations you want to ignore?
|
49
|
+
#
|
50
|
+
# Example:
|
51
|
+
# traversal.follow(:children).
|
52
|
+
# prune { |node| node.name == "A" } # node "A" will be included, but not its children
|
53
|
+
def prune(cond = nil, &blk)
|
54
|
+
tap { @prune << condition("Prune condition", cond, &blk) }
|
55
|
+
end
|
56
|
+
|
57
|
+
# Declare exclude AND prune condition.
|
58
|
+
# Matching node and its relations will be excluded from traversal.
|
59
|
+
def exclude_and_prune(cond = nil, &blk)
|
60
|
+
exclude(cond, &blk)
|
61
|
+
prune(cond, &blk)
|
62
|
+
end
|
63
|
+
alias prune_and_exclude exclude_and_prune
|
64
|
+
|
65
|
+
# Declare +stop pre-condition+.
|
66
|
+
# When met, matched node will be excluded from traversal and iteration will be stopped.
|
67
|
+
def stop_before(cond = nil, &blk)
|
68
|
+
tap { @stop_before << condition("Stop condition", cond, &blk) }
|
69
|
+
end
|
70
|
+
|
71
|
+
# Declare +stop post-condition+.
|
72
|
+
# When met, matched node will be included in traversal and iteration will be stopped.
|
73
|
+
def stop_after(cond = nil, &blk)
|
74
|
+
tap { @stop_after << condition("Stop condition", cond, &blk) }
|
75
|
+
end
|
76
|
+
|
77
|
+
# Declare traversal order strategy as +depth first+
|
78
|
+
def depth_first
|
79
|
+
tap { @order = DEPTH_FIRST }
|
80
|
+
end
|
81
|
+
|
82
|
+
# Declare traversal order strategy as +breadth first+
|
83
|
+
def breadth_first
|
84
|
+
tap { @order = BREADTH_FIRST }
|
85
|
+
end
|
86
|
+
|
87
|
+
def each
|
88
|
+
assert_complete_description
|
89
|
+
|
90
|
+
iter = Traversal::Iterator.new(self)
|
91
|
+
|
92
|
+
if iterator?
|
93
|
+
iter.each do |node|
|
94
|
+
yield node
|
95
|
+
end
|
96
|
+
else
|
97
|
+
iter
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# Predicates section
|
102
|
+
|
103
|
+
# Does node matches one of stop conditions?
|
104
|
+
def stop?(node, type = :before) #:nodoc:
|
105
|
+
(type == :after ? @stop_after : @stop_before).any? { |cond| cond[node] }
|
106
|
+
end
|
107
|
+
|
108
|
+
def exclude?(node) #:nodoc:
|
109
|
+
@exclude.any? { |cond| cond[node] }
|
110
|
+
end
|
111
|
+
|
112
|
+
def prune?(node) #:nodoc:
|
113
|
+
@prune.any? { |cond| cond[node] }
|
114
|
+
end
|
115
|
+
|
116
|
+
def breadth_first? #:nodoc:
|
117
|
+
@order == BREADTH_FIRST
|
118
|
+
end
|
119
|
+
|
120
|
+
private
|
121
|
+
def condition(name, arg, &blk) #:nodoc:
|
122
|
+
raise TypeError, "#{name} must be Symbol, Method, Proc or block" unless
|
123
|
+
(arg ||= blk).respond_to?(:to_proc)
|
124
|
+
|
125
|
+
arg.to_proc
|
126
|
+
end
|
127
|
+
|
128
|
+
def assert_complete_description #:nodoc:
|
129
|
+
raise IncompleteDescription, "Traversal description should contain start node. Use #traverse method" unless @start_node
|
130
|
+
raise IncompleteDescription, "Traversal description should contain relation. Use #follow method" unless @relation
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
require "enumerator"
|
4
|
+
|
5
|
+
module Traversal
|
6
|
+
# Traversal iterator.
|
7
|
+
class Iterator < Enumerator
|
8
|
+
# Create new traversal iterator from traversal description
|
9
|
+
def initialize(description)
|
10
|
+
raise TypeError,
|
11
|
+
'Traversal::Description expected, %s given' % description.class.name \
|
12
|
+
unless description.is_a?(Traversal::Description)
|
13
|
+
|
14
|
+
@description = description
|
15
|
+
start_node = @description.start_node
|
16
|
+
|
17
|
+
# Create Enumerator
|
18
|
+
super() do |yielder|
|
19
|
+
@yielder = yielder
|
20
|
+
|
21
|
+
begin
|
22
|
+
yield_node(start_node)
|
23
|
+
|
24
|
+
expand_node(start_node)
|
25
|
+
rescue StopIteration
|
26
|
+
# ignore
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
def push(*args) #:nodoc:
|
33
|
+
@yielder.yield(*args)
|
34
|
+
end
|
35
|
+
|
36
|
+
def yield_node(node) #:nodoc:
|
37
|
+
# check stop pre-condition
|
38
|
+
raise StopIteration if @description.stop?(node, :before)
|
39
|
+
|
40
|
+
# do yield
|
41
|
+
push(node) unless @description.exclude?(node)
|
42
|
+
|
43
|
+
# check stop post-condition
|
44
|
+
raise StopIteration if @description.stop?(node, :after)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Expand node
|
48
|
+
def expand_node(node) #:nodoc:
|
49
|
+
if @description.breadth_first?
|
50
|
+
expand_breadth(node)
|
51
|
+
else
|
52
|
+
expand_depth(node)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Expand node with DEPTH_FIRST strategy
|
57
|
+
def expand_depth(node) #:nodoc:
|
58
|
+
relations_for(node).each do |rel|
|
59
|
+
yield_node(rel)
|
60
|
+
|
61
|
+
expand_node(rel) unless @description.prune?(rel)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Expand node with BREADTH_FIRST strategy
|
66
|
+
def expand_breadth(node) #:nodoc:
|
67
|
+
cached_relations = []
|
68
|
+
|
69
|
+
# 1. yield direct relations first
|
70
|
+
relations_for(node).each do |rel|
|
71
|
+
yield_node(rel)
|
72
|
+
|
73
|
+
# memoize relation for next iteration
|
74
|
+
cached_relations << rel unless @description.prune?(rel)
|
75
|
+
end
|
76
|
+
|
77
|
+
# 2. dig deeper
|
78
|
+
cached_relations.each do |rel|
|
79
|
+
expand_breadth(rel)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Expand relations for node
|
84
|
+
def relations_for(node) #:nodoc:
|
85
|
+
relation = @description.relation[node]
|
86
|
+
|
87
|
+
relation.is_a?(Enumerable) ? relation : []
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
data/spec/test_helper.rb
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
require File.expand_path('../test_helper', __FILE__)
|
2
|
+
|
3
|
+
# Tree structure:
|
4
|
+
#
|
5
|
+
# +root+
|
6
|
+
# / \
|
7
|
+
# +a+ +b+
|
8
|
+
# / \ \
|
9
|
+
# +c+ +d+ +f+
|
10
|
+
# / / \
|
11
|
+
# +e+ +g+ +h+
|
12
|
+
|
13
|
+
class Node
|
14
|
+
attr_accessor :children
|
15
|
+
attr_accessor :level
|
16
|
+
attr_accessor :id
|
17
|
+
acts_as_traversable
|
18
|
+
|
19
|
+
def initialize(id, level)
|
20
|
+
@id = id
|
21
|
+
@level = level
|
22
|
+
@children = []
|
23
|
+
end
|
24
|
+
|
25
|
+
def inspect
|
26
|
+
"#<Node: #{id.to_s}>"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# create tree
|
31
|
+
root = Node.new(0, 0)
|
32
|
+
a = Node.new(1, 1)
|
33
|
+
b = Node.new(2, 1)
|
34
|
+
c = Node.new(3, 2)
|
35
|
+
d = Node.new(4, 2)
|
36
|
+
e = Node.new(5, 3)
|
37
|
+
f = Node.new(6, 2)
|
38
|
+
g = Node.new(7, 3)
|
39
|
+
h = Node.new(8, 3)
|
40
|
+
|
41
|
+
# link nodes
|
42
|
+
root.children << a << b
|
43
|
+
a.children << c << d
|
44
|
+
c.children << e
|
45
|
+
b.children << f
|
46
|
+
f.children << g << h
|
47
|
+
|
48
|
+
describe Traversal do
|
49
|
+
let(:iter) { Traversal::Description.new }
|
50
|
+
|
51
|
+
it "should traverse all descendants" do
|
52
|
+
# traverse whole tree, with default strategy (depth first)
|
53
|
+
iter.follow(:children).
|
54
|
+
traverse(root).
|
55
|
+
to_a.
|
56
|
+
should eq([root, a, c, e, d, b, f, g, h])
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should exclude some nodes" do
|
60
|
+
# this traverse will exclude only +c+ node, but not it's children
|
61
|
+
iter.traverse(root).
|
62
|
+
follow(:children).
|
63
|
+
exclude { |node| node == c }.
|
64
|
+
to_a.should eq([root, a, e, d, b, f, g, h])
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should disjunct excludes" do
|
68
|
+
# this traverse will exclude +c+ and +d+ nodes, but not their children
|
69
|
+
iter.traverse(root).
|
70
|
+
follow(:children).
|
71
|
+
exclude { |node| node == c }.
|
72
|
+
exclude { |node| node == d }.
|
73
|
+
to_a.should eq([root, a, e, b, f, g, h])
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should prune some nodes" do
|
77
|
+
# this traverse will exclude all +c+ children
|
78
|
+
iter.traverse(root).
|
79
|
+
follow(:children).
|
80
|
+
prune { |node| node == c }.
|
81
|
+
to_a.should eq([root, a, c, d, b, f, g, h])
|
82
|
+
end
|
83
|
+
|
84
|
+
it "should disjunct prunes" do
|
85
|
+
# this traverse will exclude all +c+ and +f+ children
|
86
|
+
iter.traverse(root).
|
87
|
+
follow(:children).
|
88
|
+
prune { |node| node == c }.
|
89
|
+
prune { |node| node == f }.
|
90
|
+
to_a.should eq([root, a, c, d, b, f])
|
91
|
+
end
|
92
|
+
|
93
|
+
it "should exclude and prune some nodes" do
|
94
|
+
# this traverse will exclude +c+ and its children
|
95
|
+
iter.traverse(root).
|
96
|
+
follow(:children).
|
97
|
+
exclude_and_prune { |node| node == c }.
|
98
|
+
to_a.should eq([root, a, d, b, f, g, h])
|
99
|
+
end
|
100
|
+
|
101
|
+
it "should stop traversal after some condition met" do
|
102
|
+
# this traversal will stop after visiting +d+ node
|
103
|
+
iter.traverse(root).
|
104
|
+
follow(:children).
|
105
|
+
stop_after { |node| node == d }.
|
106
|
+
to_a.should eq([root, a, c, e, d])
|
107
|
+
end
|
108
|
+
|
109
|
+
it "should stop traversal before some condition met" do
|
110
|
+
# this traversal will stop before visiting +d+ node
|
111
|
+
iter.traverse(root).
|
112
|
+
follow(:children).
|
113
|
+
stop_before { |node| node == d }.
|
114
|
+
to_a.should eq([root, a, c, e])
|
115
|
+
end
|
116
|
+
|
117
|
+
it "should traverse with depth_first strategy" do
|
118
|
+
iter.traverse(root).
|
119
|
+
follow(:children).
|
120
|
+
depth_first.
|
121
|
+
exclude { |node| node.level == 3 }.
|
122
|
+
to_a.should eq([root, a, c, d, b, f])
|
123
|
+
end
|
124
|
+
|
125
|
+
it "should traverse with breadth_first strategy" do
|
126
|
+
iter.traverse(root).
|
127
|
+
follow(:children).
|
128
|
+
breadth_first.
|
129
|
+
exclude { |node| node.level == 3 }.
|
130
|
+
to_a.should eq([root, a, b, c, d, f])
|
131
|
+
end
|
132
|
+
|
133
|
+
it "should raise exception when no start node given" do
|
134
|
+
lambda { iter.follow(:children).to_a }.should raise_error(Traversal::IncompleteDescription)
|
135
|
+
end
|
136
|
+
|
137
|
+
it "should raise exception when no relations given" do
|
138
|
+
lambda { iter.traverse(root).to_a }.should raise_error(Traversal::IncompleteDescription)
|
139
|
+
end
|
140
|
+
|
141
|
+
it "should have shortcut" do
|
142
|
+
root.should respond_to(:traverse)
|
143
|
+
|
144
|
+
root.traverse(:children).count.should eq(9)
|
145
|
+
end
|
146
|
+
|
147
|
+
it "should return Enumerable when called without block" do
|
148
|
+
iter.traverse(root).follow(:children).each.should be_a(Enumerable)
|
149
|
+
|
150
|
+
end
|
151
|
+
end
|
data/traversal.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "traversal/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "traversal"
|
7
|
+
s.version = Traversal::VERSION
|
8
|
+
s.authors = ["Alexey Mikhaylov"]
|
9
|
+
s.email = ["amikhailov83@gmail.com"]
|
10
|
+
s.homepage = "https://github.com/take-five/traversal"
|
11
|
+
s.summary = %q{Simple traversal API for pure Ruby objects}
|
12
|
+
s.date = "2012-01-22"
|
13
|
+
|
14
|
+
s.rubyforge_project = "traversal"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
s.add_development_dependency "rspec"
|
22
|
+
s.add_development_dependency "simplecov"
|
23
|
+
# s.add_runtime_dependency "rest-client"
|
24
|
+
end
|
metadata
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: traversal
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
version: 0.0.1
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Alexey Mikhaylov
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2012-01-22 00:00:00 +06:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: rspec
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
segments:
|
29
|
+
- 0
|
30
|
+
version: "0"
|
31
|
+
type: :development
|
32
|
+
version_requirements: *id001
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: simplecov
|
35
|
+
prerelease: false
|
36
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
37
|
+
none: false
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
segments:
|
42
|
+
- 0
|
43
|
+
version: "0"
|
44
|
+
type: :development
|
45
|
+
version_requirements: *id002
|
46
|
+
description:
|
47
|
+
email:
|
48
|
+
- amikhailov83@gmail.com
|
49
|
+
executables: []
|
50
|
+
|
51
|
+
extensions: []
|
52
|
+
|
53
|
+
extra_rdoc_files: []
|
54
|
+
|
55
|
+
files:
|
56
|
+
- .gitignore
|
57
|
+
- Gemfile
|
58
|
+
- README.rdoc
|
59
|
+
- Rakefile
|
60
|
+
- lib/traversal.rb
|
61
|
+
- lib/traversal/acts_as_traversable.rb
|
62
|
+
- lib/traversal/description.rb
|
63
|
+
- lib/traversal/iterator.rb
|
64
|
+
- lib/traversal/version.rb
|
65
|
+
- spec/test_helper.rb
|
66
|
+
- spec/traversal_spec.rb
|
67
|
+
- traversal.gemspec
|
68
|
+
has_rdoc: true
|
69
|
+
homepage: https://github.com/take-five/traversal
|
70
|
+
licenses: []
|
71
|
+
|
72
|
+
post_install_message:
|
73
|
+
rdoc_options: []
|
74
|
+
|
75
|
+
require_paths:
|
76
|
+
- lib
|
77
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
78
|
+
none: false
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
segments:
|
83
|
+
- 0
|
84
|
+
version: "0"
|
85
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
86
|
+
none: false
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
segments:
|
91
|
+
- 0
|
92
|
+
version: "0"
|
93
|
+
requirements: []
|
94
|
+
|
95
|
+
rubyforge_project: traversal
|
96
|
+
rubygems_version: 1.3.7
|
97
|
+
signing_key:
|
98
|
+
specification_version: 3
|
99
|
+
summary: Simple traversal API for pure Ruby objects
|
100
|
+
test_files: []
|
101
|
+
|