compo 0.3.1 → 0.4.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/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