compo 0.3.1 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +8 -0
- data/lib/compo/branches/array.rb +17 -0
- data/lib/compo/branches/branch.rb +35 -0
- data/lib/compo/branches/constant.rb +34 -0
- data/lib/compo/branches/hash.rb +17 -0
- data/lib/compo/branches/leaf.rb +15 -0
- data/lib/compo/branches.rb +15 -0
- data/lib/compo/composites/array.rb +105 -0
- data/lib/compo/composites/composite.rb +183 -0
- data/lib/compo/composites/hash.rb +72 -0
- data/lib/compo/composites/leaf.rb +64 -0
- data/lib/compo/composites/parentless.rb +133 -0
- data/lib/compo/composites.rb +20 -0
- data/lib/compo/finders/url.rb +166 -0
- data/lib/compo/finders.rb +10 -0
- data/lib/compo/mixins/movable.rb +55 -0
- data/lib/compo/mixins/parent_tracker.rb +74 -0
- data/lib/compo/mixins/url_referenceable.rb +70 -0
- data/lib/compo/mixins.rb +9 -0
- data/lib/compo/version.rb +1 -1
- data/lib/compo.rb +4 -21
- data/spec/array_branch_spec.rb +4 -2
- data/spec/array_composite_shared_examples.rb +2 -2
- data/spec/array_composite_spec.rb +1 -1
- data/spec/branch_shared_examples.rb +38 -5
- data/spec/branch_spec.rb +1 -1
- data/spec/composite_shared_examples.rb +1 -1
- data/spec/composite_spec.rb +1 -1
- data/spec/constant_branch_spec.rb +18 -0
- data/spec/hash_branch_spec.rb +4 -2
- data/spec/hash_composite_shared_examples.rb +3 -3
- data/spec/hash_composite_spec.rb +1 -1
- data/spec/leaf_branch_spec.rb +9 -0
- data/spec/{null_composite_shared_examples.rb → leaf_composite_shared_examples.rb} +1 -1
- data/spec/leaf_composite_spec.rb +7 -0
- data/spec/movable_shared_examples.rb +3 -3
- data/spec/movable_spec.rb +1 -1
- data/spec/parent_tracker_spec.rb +2 -15
- data/spec/parentless_spec.rb +2 -2
- data/spec/url_finder_shared_examples.rb +104 -0
- data/spec/url_finder_spec.rb +25 -114
- data/spec/url_referenceable_spec.rb +1 -1
- metadata +30 -21
- data/lib/compo/array_branch.rb +0 -16
- data/lib/compo/array_composite.rb +0 -103
- data/lib/compo/branch.rb +0 -18
- data/lib/compo/composite.rb +0 -181
- data/lib/compo/hash_branch.rb +0 -17
- data/lib/compo/hash_composite.rb +0 -70
- data/lib/compo/leaf.rb +0 -14
- data/lib/compo/movable.rb +0 -53
- data/lib/compo/null_composite.rb +0 -62
- data/lib/compo/parent_tracker.rb +0 -80
- data/lib/compo/parentless.rb +0 -131
- data/lib/compo/url_finder.rb +0 -164
- data/lib/compo/url_referenceable.rb +0 -68
- data/spec/leaf_spec.rb +0 -9
- data/spec/null_composite_spec.rb +0 -7
data/lib/compo/parentless.rb
DELETED
@@ -1,131 +0,0 @@
|
|
1
|
-
require 'compo'
|
2
|
-
|
3
|
-
module Compo
|
4
|
-
# A Composite that represents the non-existent parent of an orphan
|
5
|
-
#
|
6
|
-
# Parentless is the parent assigned when an object is removed from a
|
7
|
-
# Composite, and should be the default parent of an object that can be
|
8
|
-
# added to one. It exists to make some operations easier, such as URL
|
9
|
-
# creation.
|
10
|
-
class Parentless
|
11
|
-
include Composite
|
12
|
-
|
13
|
-
# Creates a new instance of Parentless and adds an item to it
|
14
|
-
#
|
15
|
-
# This effectively removes the item's parent.
|
16
|
-
#
|
17
|
-
# If this method is passed nil, then nothing happens.
|
18
|
-
#
|
19
|
-
# @api public
|
20
|
-
# @example Makes a new Parentless for an item.
|
21
|
-
# Parentless.for(item)
|
22
|
-
# @example Does nothing.
|
23
|
-
# Parentless.for(nil)
|
24
|
-
#
|
25
|
-
# @param item [Object] The item to be reparented to a Parentless.
|
26
|
-
#
|
27
|
-
# @return [void]
|
28
|
-
def self.for(item)
|
29
|
-
new.add(nil, item) unless item.nil?
|
30
|
-
end
|
31
|
-
|
32
|
-
# 'Removes' a child from this Parentless
|
33
|
-
#
|
34
|
-
# This always succeeds, and never triggers any other action.
|
35
|
-
#
|
36
|
-
# @api public
|
37
|
-
# @example 'Removes' a child.
|
38
|
-
# parentless.remove(child)
|
39
|
-
#
|
40
|
-
# @param child [Object] The child to 'remove' from this Parentless.
|
41
|
-
#
|
42
|
-
# @return [Object] The child.
|
43
|
-
def remove(child)
|
44
|
-
child
|
45
|
-
end
|
46
|
-
|
47
|
-
# Returns the empty hash
|
48
|
-
#
|
49
|
-
# @api public
|
50
|
-
# @example Gets the children
|
51
|
-
# parentless.children
|
52
|
-
# #=> {}
|
53
|
-
#
|
54
|
-
# @return [Hash] The empty hash.
|
55
|
-
def children
|
56
|
-
{}
|
57
|
-
end
|
58
|
-
|
59
|
-
# Returns the URL of this Parentless
|
60
|
-
#
|
61
|
-
# This is always the empty string.
|
62
|
-
#
|
63
|
-
# @api public
|
64
|
-
# @example Gets the URL of a Parentless
|
65
|
-
# parentless.url
|
66
|
-
# #=> ''
|
67
|
-
#
|
68
|
-
# @return [Hash] The empty string.
|
69
|
-
def url
|
70
|
-
''
|
71
|
-
end
|
72
|
-
|
73
|
-
# Given the ID of a child in this Parentless, returns that child's URL
|
74
|
-
#
|
75
|
-
# This is always the empty string. This is so that children of orphan
|
76
|
-
# objects have URLs starting with /their_id.
|
77
|
-
#
|
78
|
-
# @api public
|
79
|
-
# @example Gets the URL of the child of a Parentless.
|
80
|
-
# parentless.child_url(:child_id)
|
81
|
-
# #=> ''
|
82
|
-
#
|
83
|
-
# @return [Hash] The empty string.
|
84
|
-
def child_url(_)
|
85
|
-
''
|
86
|
-
end
|
87
|
-
|
88
|
-
# Returns the parent of this Parentless
|
89
|
-
#
|
90
|
-
# This is always the same Parentless, for convenience's sake. Technically,
|
91
|
-
# as a null object, Parentless has no parent.
|
92
|
-
#
|
93
|
-
# @api public
|
94
|
-
# @example Gets the 'parent' of a Parentless.
|
95
|
-
# parentless.parent
|
96
|
-
#
|
97
|
-
# @return [self]
|
98
|
-
def parent
|
99
|
-
self
|
100
|
-
end
|
101
|
-
|
102
|
-
protected
|
103
|
-
|
104
|
-
# 'Adds' a child to this Parentless
|
105
|
-
#
|
106
|
-
# This always succeeds.
|
107
|
-
#
|
108
|
-
# @api private
|
109
|
-
#
|
110
|
-
# @param id [Object] Ignored.
|
111
|
-
# @param child [Object] The object to 'add' to this Parentless.
|
112
|
-
#
|
113
|
-
# @return [Object] The child.
|
114
|
-
def add!(_, child)
|
115
|
-
child
|
116
|
-
end
|
117
|
-
|
118
|
-
# Creates an ID function for the given child
|
119
|
-
#
|
120
|
-
# The returned proc is O(1), and always returns nil.
|
121
|
-
#
|
122
|
-
# @api private
|
123
|
-
#
|
124
|
-
# @param child [Object] The child whose ID is to be returned by the proc.
|
125
|
-
#
|
126
|
-
# @return [Proc] A proc returning nil.
|
127
|
-
def id_function(_)
|
128
|
-
-> { nil }
|
129
|
-
end
|
130
|
-
end
|
131
|
-
end
|
data/lib/compo/url_finder.rb
DELETED
@@ -1,164 +0,0 @@
|
|
1
|
-
module Compo
|
2
|
-
# An 'UrlFinder' finds an object in a composite tree via its URL
|
3
|
-
#
|
4
|
-
# It is a method object that implements only the finding of a specific URL.
|
5
|
-
# UrlFinders are *not* thread-safe.
|
6
|
-
class UrlFinder
|
7
|
-
# Initialises a UrlFinder
|
8
|
-
#
|
9
|
-
# @api public
|
10
|
-
# @example Initialises an UrlFinder with default missing resource handling.
|
11
|
-
# UrlFinder.new(composite, 'a/b/c')
|
12
|
-
# @example Initialises an UrlFinder returning a default value.
|
13
|
-
# UrlFinder.new(composite, 'a/b/c', missing_proc=->(_) { :default })
|
14
|
-
#
|
15
|
-
# @param root [Composite] A composite object serving as the root of the
|
16
|
-
# search tree, and the URL.
|
17
|
-
#
|
18
|
-
# @param url [String] A partial URL that follows this model object's URL
|
19
|
-
# to form the URL of the resource to locate. Can be nil, in which case
|
20
|
-
# this object is returned.
|
21
|
-
#
|
22
|
-
# @param missing_proc [Proc] A proc to call, with the requested URL, if the
|
23
|
-
# resource could not be found. If nil (the default), this raises a string
|
24
|
-
# exception.
|
25
|
-
def initialize(root, url, missing_proc: nil)
|
26
|
-
@root = root
|
27
|
-
@url = url
|
28
|
-
@missing_proc = missing_proc || method(:default_missing_proc)
|
29
|
-
|
30
|
-
reset
|
31
|
-
end
|
32
|
-
|
33
|
-
# Finds the model object at a URL, given a model root
|
34
|
-
#
|
35
|
-
# @api public
|
36
|
-
# @example Finds a URL with default missing resource handling.
|
37
|
-
# UrlFinder.find(composite, 'a/b/c') { |item| p item }
|
38
|
-
#
|
39
|
-
# @param (see #initialize)
|
40
|
-
#
|
41
|
-
# @yieldparam (see #run)
|
42
|
-
#
|
43
|
-
# @return [Object] The return value of the block.
|
44
|
-
def self.find(*args, &block)
|
45
|
-
new(*args).run(&block)
|
46
|
-
end
|
47
|
-
|
48
|
-
# Attempts to find a child resource with the given partial URL
|
49
|
-
#
|
50
|
-
# If the resource is found, it will be yielded to the attached block;
|
51
|
-
# otherwise, an exception will be raised.
|
52
|
-
#
|
53
|
-
# @api public
|
54
|
-
# @example Runs an UrlFinder, returning the item unchanged.
|
55
|
-
# finder.run { |item| item }
|
56
|
-
# #=> item
|
57
|
-
#
|
58
|
-
# @yieldparam resource [ModelObject] The resource found.
|
59
|
-
# @yieldparam args [Array] The splat from above.
|
60
|
-
#
|
61
|
-
# @return [Object] The return value of the block.
|
62
|
-
def run
|
63
|
-
# We're traversing down the URL by repeatedly splitting it into its
|
64
|
-
# head (part before the next /) and tail (part after). While we still
|
65
|
-
# have a tail, then the URL still needs walking down.
|
66
|
-
reset
|
67
|
-
descend until hit_end_of_url?
|
68
|
-
yield @resource
|
69
|
-
end
|
70
|
-
|
71
|
-
private
|
72
|
-
|
73
|
-
# Performs a descending step in the URL finder
|
74
|
-
#
|
75
|
-
# This tries to move down a level of the URL hierarchy, fetches the
|
76
|
-
# resource at that level, and fails according to @missing_proc if there is
|
77
|
-
# no such resource.
|
78
|
-
#
|
79
|
-
# @api private
|
80
|
-
#
|
81
|
-
# @return [Void]
|
82
|
-
def descend
|
83
|
-
descend_url
|
84
|
-
next_resource
|
85
|
-
fail_with_no_resource if @resource.nil?
|
86
|
-
end
|
87
|
-
|
88
|
-
# Seeks to the next resource pointed at by @next_id
|
89
|
-
#
|
90
|
-
# @api private
|
91
|
-
#
|
92
|
-
# @return [Void]
|
93
|
-
def next_resource
|
94
|
-
@resource = @resource.get_child_such_that { |id| id.to_s == @next_id }
|
95
|
-
end
|
96
|
-
|
97
|
-
# Fails, using @missing_proc, due to a missing resource
|
98
|
-
#
|
99
|
-
# @api private
|
100
|
-
#
|
101
|
-
# @return [Void]
|
102
|
-
def fail_with_no_resource
|
103
|
-
# If the proc returns a value instead of raising an error, then set
|
104
|
-
# things up so that value is yielded in place of the missing resource.
|
105
|
-
@tail = nil
|
106
|
-
@resource = @missing_proc.call(@url)
|
107
|
-
end
|
108
|
-
|
109
|
-
# Default value for @missing_proc
|
110
|
-
#
|
111
|
-
# @api private
|
112
|
-
#
|
113
|
-
# @param url [String] The URL whose finding failed.
|
114
|
-
#
|
115
|
-
# @return [Void]
|
116
|
-
def default_missing_proc(url)
|
117
|
-
fail("Could not find resource: #{url}")
|
118
|
-
end
|
119
|
-
|
120
|
-
# Decides whether we have reached the end of the URL
|
121
|
-
#
|
122
|
-
# @api private
|
123
|
-
#
|
124
|
-
# @return [Boolean] Whether we have hit the end of the URL.
|
125
|
-
def hit_end_of_url?
|
126
|
-
@tail.nil? || @tail.empty?
|
127
|
-
end
|
128
|
-
|
129
|
-
# Splits the tail on the next URL level
|
130
|
-
#
|
131
|
-
# @api private
|
132
|
-
#
|
133
|
-
# @return [Void]
|
134
|
-
def descend_url
|
135
|
-
@next_id, @tail = @tail.split('/', 2)
|
136
|
-
end
|
137
|
-
|
138
|
-
# Resets this UrlFinder so it can be used again
|
139
|
-
#
|
140
|
-
# @api private
|
141
|
-
#
|
142
|
-
# @return [Void]
|
143
|
-
def reset
|
144
|
-
@next_id, @tail = nil, trimmed_url
|
145
|
-
@resource = @root
|
146
|
-
end
|
147
|
-
|
148
|
-
# Removes any leading or trailing slash from the URL, returning the result
|
149
|
-
#
|
150
|
-
# This only removes one leading or trailing slash. Thus, '///' will be
|
151
|
-
# returned as '/'.
|
152
|
-
#
|
153
|
-
# @api private
|
154
|
-
#
|
155
|
-
# @return [String] The URL with no trailing or leading slash.
|
156
|
-
def trimmed_url
|
157
|
-
first, last = 0, @url.length
|
158
|
-
first += 1 if @url.start_with?('/')
|
159
|
-
last -= 1 if @url.end_with?('/')
|
160
|
-
|
161
|
-
@url[first...last]
|
162
|
-
end
|
163
|
-
end
|
164
|
-
end
|
@@ -1,68 +0,0 @@
|
|
1
|
-
module Compo
|
2
|
-
# Adds ID-based 'URL's to Compo classes
|
3
|
-
#
|
4
|
-
# For the purposes of this module, a URL is a string of slash-delimited IDs
|
5
|
-
# representing the location of a Composite in the tree structure formed by
|
6
|
-
# its ancestors. Depending on the types of IDs used in the structure, the
|
7
|
-
# URLs may not actually be literal Uniform Resource Locators.
|
8
|
-
#
|
9
|
-
# This module expects its includer to define #parent and #id. These are
|
10
|
-
# defined, for example, by the Compo::ParentTracker mixin.
|
11
|
-
module UrlReferenceable
|
12
|
-
extend Forwardable
|
13
|
-
|
14
|
-
# Returns the URL of this object
|
15
|
-
#
|
16
|
-
# The #url of a Composite is defined inductively as '' for composites that
|
17
|
-
# have no parent, and the joining of the parent URL and the current ID
|
18
|
-
# otherwise.
|
19
|
-
#
|
20
|
-
# The result of #url can be used to give a URL hierarchy to Composites.
|
21
|
-
#
|
22
|
-
# @api public
|
23
|
-
# @example Gets the URL of an object with no parent.
|
24
|
-
# orphan.url
|
25
|
-
# #=> ''
|
26
|
-
# @example Gets the URL of an object with a parent.
|
27
|
-
# leaf.url
|
28
|
-
# #=> 'grandparent_id/parent_id/id'
|
29
|
-
#
|
30
|
-
# @return [String] The URL of this object.
|
31
|
-
def url
|
32
|
-
parent.child_url(id)
|
33
|
-
end
|
34
|
-
|
35
|
-
# Returns the URL of a child of this object, with the given ID
|
36
|
-
#
|
37
|
-
# This defaults to joining the ID to this object's URL with a slash.
|
38
|
-
#
|
39
|
-
# @api public
|
40
|
-
# @example Gets the URL of the child of an object without a parent.
|
41
|
-
# orphan.child_url(:id)
|
42
|
-
# #=> '/id'
|
43
|
-
# @example Gets the URL of the child of an object with a parent.
|
44
|
-
# leaf.child_url(:id)
|
45
|
-
# #=> 'grandparent_id/parent_id/id'
|
46
|
-
#
|
47
|
-
# @param child_id [Object] The ID of the child whose URL is sought.
|
48
|
-
#
|
49
|
-
# @return [String] The URL of the child with the given ID.
|
50
|
-
def child_url(child_id)
|
51
|
-
[url, child_id].join('/')
|
52
|
-
end
|
53
|
-
|
54
|
-
# Returns the URL of this object's parent
|
55
|
-
#
|
56
|
-
# @api public
|
57
|
-
# @example Gets the parent URL of an object with no parent.
|
58
|
-
# orphan.parent_url
|
59
|
-
# #=> nil
|
60
|
-
# @example Gets the URL of an object with a parent.
|
61
|
-
# leaf.parent_url
|
62
|
-
# #=> 'grandparent_id/parent_id'
|
63
|
-
#
|
64
|
-
# @return [String] The URL of this object's parent, or nil if there is no
|
65
|
-
# parent.
|
66
|
-
def_delegator :parent, :url, :parent_url
|
67
|
-
end
|
68
|
-
end
|
data/spec/leaf_spec.rb
DELETED