compo 0.3.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +8 -0
  3. data/lib/compo/branches/array.rb +17 -0
  4. data/lib/compo/branches/branch.rb +35 -0
  5. data/lib/compo/branches/constant.rb +34 -0
  6. data/lib/compo/branches/hash.rb +17 -0
  7. data/lib/compo/branches/leaf.rb +15 -0
  8. data/lib/compo/branches.rb +15 -0
  9. data/lib/compo/composites/array.rb +105 -0
  10. data/lib/compo/composites/composite.rb +183 -0
  11. data/lib/compo/composites/hash.rb +72 -0
  12. data/lib/compo/composites/leaf.rb +64 -0
  13. data/lib/compo/composites/parentless.rb +133 -0
  14. data/lib/compo/composites.rb +20 -0
  15. data/lib/compo/finders/url.rb +166 -0
  16. data/lib/compo/finders.rb +10 -0
  17. data/lib/compo/mixins/movable.rb +55 -0
  18. data/lib/compo/mixins/parent_tracker.rb +74 -0
  19. data/lib/compo/mixins/url_referenceable.rb +70 -0
  20. data/lib/compo/mixins.rb +9 -0
  21. data/lib/compo/version.rb +1 -1
  22. data/lib/compo.rb +4 -21
  23. data/spec/array_branch_spec.rb +4 -2
  24. data/spec/array_composite_shared_examples.rb +2 -2
  25. data/spec/array_composite_spec.rb +1 -1
  26. data/spec/branch_shared_examples.rb +38 -5
  27. data/spec/branch_spec.rb +1 -1
  28. data/spec/composite_shared_examples.rb +1 -1
  29. data/spec/composite_spec.rb +1 -1
  30. data/spec/constant_branch_spec.rb +18 -0
  31. data/spec/hash_branch_spec.rb +4 -2
  32. data/spec/hash_composite_shared_examples.rb +3 -3
  33. data/spec/hash_composite_spec.rb +1 -1
  34. data/spec/leaf_branch_spec.rb +9 -0
  35. data/spec/{null_composite_shared_examples.rb → leaf_composite_shared_examples.rb} +1 -1
  36. data/spec/leaf_composite_spec.rb +7 -0
  37. data/spec/movable_shared_examples.rb +3 -3
  38. data/spec/movable_spec.rb +1 -1
  39. data/spec/parent_tracker_spec.rb +2 -15
  40. data/spec/parentless_spec.rb +2 -2
  41. data/spec/url_finder_shared_examples.rb +104 -0
  42. data/spec/url_finder_spec.rb +25 -114
  43. data/spec/url_referenceable_spec.rb +1 -1
  44. metadata +30 -21
  45. data/lib/compo/array_branch.rb +0 -16
  46. data/lib/compo/array_composite.rb +0 -103
  47. data/lib/compo/branch.rb +0 -18
  48. data/lib/compo/composite.rb +0 -181
  49. data/lib/compo/hash_branch.rb +0 -17
  50. data/lib/compo/hash_composite.rb +0 -70
  51. data/lib/compo/leaf.rb +0 -14
  52. data/lib/compo/movable.rb +0 -53
  53. data/lib/compo/null_composite.rb +0 -62
  54. data/lib/compo/parent_tracker.rb +0 -80
  55. data/lib/compo/parentless.rb +0 -131
  56. data/lib/compo/url_finder.rb +0 -164
  57. data/lib/compo/url_referenceable.rb +0 -68
  58. data/spec/leaf_spec.rb +0 -9
  59. data/spec/null_composite_spec.rb +0 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 463952c33e420e5ab401ec9774208262b4606ebc
4
- data.tar.gz: 59ff0efef46f9db2fd9fdf72c463fccb46b18b96
3
+ metadata.gz: 9a7465849db65d98eec1007ce000a7a75fa78309
4
+ data.tar.gz: 6472acc91b29bda5ba2c3886a0a50a88f28f8c74
5
5
  SHA512:
6
- metadata.gz: ead1e453e9ba923f8dcb12b2c9ec211d8d8946323557175eb82c0236939afc42314bc83c139f4e4076b1d3c0f4850910afcad3a29ed4b67ef9309e209eb97dbc
7
- data.tar.gz: 890b61d951ad12e56aa2f329506f803dbde105c8541afd2332fb520f973a745fbacd845a7b1f213f69230cfa51e4c326c9704427561a78c754fd6ed69ee1d14c
6
+ metadata.gz: 84cfa4992537c356070d0a2885cff6bfd3e6e39050bdb6986ac413d1c6920dc05316b945e5f2e1f7fe79fc044d318d602c3a6a9befc404ca9814b622a238737e
7
+ data.tar.gz: edd697d7696e8e96783d4e02bfc37852df79abbacab5b992c1c6b8d0c95002763416caaf9e2bc2e6f51f1c98960612783cc182339e6b0956ce6ef95873cad675
data/CHANGELOG CHANGED
@@ -1,3 +1,11 @@
1
+ 0.4.0 (2014-05-22) ‘De Vorzon’
2
+ Big release with quite a few changes:
3
+ - (BACKWARDS INCOMPATIBILITY) Reorganise classes into submodules. This has
4
+ changed the name of pretty much every class in Compo.
5
+ - Null composites are now renamed to be Leaf composites, for consistency.
6
+ - Add branch#find_url, as a shortcut for invoking a UrlFinder.
7
+ - Add Compo::Branches::Constant, a Leaf branch containing a constant value.
8
+
1
9
  0.3.1 (2014-05-21)
2
10
  - Add UrlFinder class, for finding children inside a composite structure via
3
11
  their URLs.
@@ -0,0 +1,17 @@
1
+ require 'compo/branches/branch'
2
+ require 'compo/composites'
3
+
4
+ module Compo
5
+ module Branches
6
+ # A simple implementation of a branch, whose children are stored in an Array
7
+ #
8
+ # An array branch is a composite object that may be moved into other
9
+ # composite objects. It stores its children as an Array, and the ID of each
10
+ # child is its current index in the array. Inserting and removing items
11
+ # into the branch may change the IDs of items with higher indices in the
12
+ # array.
13
+ class Array < Compo::Composites::Array
14
+ include Branch
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,35 @@
1
+ require 'compo/mixins'
2
+ require 'compo/finders'
3
+
4
+ module Compo
5
+ module Branches
6
+ # A movable, URL referenceable parent tracker
7
+ #
8
+ # A Branch represents a fully-featured part of a composite object. This
9
+ # abstract pattern is implemented concretely by Leaf (a Branch with no
10
+ # children), Array (a Branch with a list of numerically identified
11
+ # children), and Hash (a Branch with a hash of key-identified children).
12
+ module Branch
13
+ include Mixins::Movable
14
+ include Mixins::ParentTracker
15
+ include Mixins::UrlReferenceable
16
+
17
+ # Traverses this Branch and its children following a URL
18
+ #
19
+ # See Compo::Finders::Url for more information about what this means.
20
+ # This is a convenience wrapper over that method object, and is equivalent
21
+ # to 'Compo::Finders::Url.find(self, *args, &block)'.
22
+ #
23
+ # @api public
24
+ # @example From this Branch, find the item at 'a/b/1'.
25
+ # branch.on_url('a/b/1') { |item| p item }
26
+ #
27
+ # @yieldparam [Object] The found resource.
28
+ #
29
+ # @return [Object] Whatever is returned by the block.
30
+ def find_url(*args, &block)
31
+ Compo::Finders::Url.find(self, *args, &block)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,34 @@
1
+ require 'compo/branches/leaf'
2
+
3
+ module Compo
4
+ module Branches
5
+ # A Leaf containing a constant value
6
+ #
7
+ # The value can be retrieved using #value.
8
+ class Constant < Leaf
9
+ # Initialises the Constant
10
+ #
11
+ # @api public
12
+ # @example Initialising a Constant with a given value.
13
+ # Constant.new(:value)
14
+ #
15
+ # @param value [Object] The value of the constant.
16
+ #
17
+ def initialize(value)
18
+ super()
19
+ @value = value
20
+ end
21
+
22
+ # Returns the current value of this Constant
23
+ #
24
+ # @api public
25
+ # @example Retrieving a Constant's value.
26
+ # const = Constant.new(:spoon)
27
+ # const.value
28
+ # #=> :spoon
29
+ #
30
+ # @return [Object] The Constant's internal value.
31
+ attr_reader :value
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,17 @@
1
+ require 'compo/branches/branch'
2
+ require 'compo/composites'
3
+
4
+ module Compo
5
+ module Branches
6
+ # A simple implementation of a branch, whose children are stored in an Hash
7
+ #
8
+ # A hash branch is a composite object that may be moved into other composite
9
+ # objects. It stores its children as an Hash, and the ID of each child is
10
+ # its hash key. Inserting and removing items into the branch will not
11
+ # change the IDs of other items, but inserting with an existing key will
12
+ # remove the previous occupant.
13
+ class Hash < Compo::Composites::Hash
14
+ include Branch
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,15 @@
1
+ require 'compo/branches/branch'
2
+ require 'compo/composites'
3
+
4
+ module Compo
5
+ module Branches
6
+ # A simple implementation of a leaf node
7
+ #
8
+ # Leaves have no children, but can be moved to one. They implement the
9
+ # Composite API, but all additions and removals fail, and the Leaf always
10
+ # reports no children.
11
+ class Leaf < Compo::Composites::Leaf
12
+ include Branch
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ require 'compo/branches/array'
2
+ require 'compo/branches/branch'
3
+ require 'compo/branches/hash'
4
+ require 'compo/branches/leaf'
5
+ require 'compo/branches/constant'
6
+
7
+ module Compo
8
+ # Module implementing the branch classes
9
+ #
10
+ # A branch is a fully-featured part of a Compo composite object. It tracks
11
+ # both children (if applicable) and its parent (if any), and supports a range
12
+ # of helper methods for traversing the composite tree.
13
+ module Branches
14
+ end
15
+ end
@@ -0,0 +1,105 @@
1
+ require 'forwardable'
2
+ require 'compo/composites/composite'
3
+
4
+ module Compo
5
+ module Composites
6
+ # Implementation of Composite that stores its children in an Array.
7
+ #
8
+ # IDs for items entering an Array must be numeric, and will change if
9
+ # an item with an ID less than the item in question is deleted or inserted.
10
+ # This means the ID function for objects in an Array may report
11
+ # different values at different times.
12
+ #
13
+ # Adding an object at an occupied ID moves the occupant and those at
14
+ # successive IDs up by one.
15
+ class Array
16
+ include Composite
17
+ extend Forwardable
18
+
19
+ # Initialises an array composite
20
+ #
21
+ # @api public
22
+ # @example Initializes an array composite.
23
+ # comp = Compo::Composites::Array.new
24
+ def initialize
25
+ @children = []
26
+ end
27
+
28
+ # Returns the array composite's children, as a Hash
29
+ #
30
+ # @api public
31
+ # @example Gets the children of an empty array composite.
32
+ # comp.children
33
+ # #=> {}
34
+ # @example Gets the children of a populated array composite.
35
+ # comp.children
36
+ # #=> {0: :first, 1: :second}
37
+ #
38
+ # @return [Hash] The Hash mapping the IDs of children to their values.
39
+ def children
40
+ ::Hash[(0...@children.size).zip(@children)]
41
+ end
42
+
43
+ private
44
+
45
+ def_delegator :@children, :delete, :remove!
46
+ def_delegator :@children, :delete_at, :remove_id!
47
+
48
+ # Inserts a child into the array composite with the given ID
49
+ #
50
+ # You probably want to use #add instead.
51
+ #
52
+ # @api private
53
+ #
54
+ # @param id [Object] The ID under which the child is to be added.
55
+ # @param child [Object] The child to add to the array composite.
56
+ #
57
+ # @return [Object] The newly added child, or nil if the ID was invalid.
58
+ def add!(id, child)
59
+ valid_id?(id) ? do_insert(id, child) : nil
60
+ end
61
+
62
+ # Checks to see if the given ID is valid
63
+ #
64
+ # A valid ID for ArrayComposites is a number between 0 and the current
65
+ # size of the children list.
66
+ #
67
+ # @api private
68
+ #
69
+ # @param id [Object] The candidate ID.
70
+ #
71
+ # @return [Boolean] True if the ID is valid; false if not.
72
+ def valid_id?(id)
73
+ id.is_a?(Numeric) && (0..@children.size).cover?(id)
74
+ end
75
+
76
+ # Actually performs the insertion of an item into the array
77
+ #
78
+ # @api private
79
+ #
80
+ # @param id [Numeric] The index into the array at which the child is to
81
+ # be inserted.
82
+ # @param child [Object] The object to insert into the children array.
83
+ #
84
+ # @return [Object] The inserted child.
85
+ def do_insert(id, child)
86
+ @children.insert(id, child)
87
+ child
88
+ end
89
+
90
+ # Creates an ID function for the given child
91
+ #
92
+ # The returned proc is O(n), as it checks the child array at each call to
93
+ # find the current ID of the child.
94
+ #
95
+ # @api private
96
+ #
97
+ # @param child [Object] The child whose ID is to be returned by the proc.
98
+ #
99
+ # @return [Proc] A proc returning the child's ID.
100
+ def id_function(object)
101
+ proc { @children.index(object) }
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,183 @@
1
+ require 'forwardable'
2
+
3
+ module Compo
4
+ module Composites
5
+ # Mixin for objects that can contain other objects
6
+ #
7
+ # Objects implementing this interface should implement add!, remove! or
8
+ # remove_id!, and id_function.
9
+ #
10
+ # add! - Given a desired ID and child, adds the child to the children
11
+ # of this object; returns the child if successful, nil
12
+ # otherwise.
13
+ # remove! - Given a child, removes and returns it from the children; if
14
+ # not provided, this is implemented in terms of remove_id!.
15
+ # remove_id! - Given an ID, removes and returns the child with this ID from
16
+ # the children; if not provided, this is implemented in terms
17
+ # of remove!.
18
+ # children - Returns the children, as a Hash mapping from current IDs to
19
+ # their child values.
20
+ # id_function - Given a newly inserted child, returns a proc that will
21
+ # always return the child's current ID so long as it is part
22
+ # of the Composite.
23
+ module Composite
24
+ extend Forwardable
25
+ include Enumerable
26
+
27
+ # Adds a child to this Composite
28
+ #
29
+ # @api public
30
+ # @example Adds a child with intended id 3.
31
+ # composite.add_child(3, leaf)
32
+ #
33
+ # @param id [Object] The intended ID of the child in this Composite.
34
+ # The actual ID may not be the same as this; consult the proc supplied
35
+ # to the child via #update_parent.
36
+ # @param child [Object] The child to add to this Composite.
37
+ #
38
+ # @return [Object] The added child if successful; nil otherwise.
39
+ def add(id, child)
40
+ add!(id, child).tap(&method(:assign_parent_to))
41
+ end
42
+
43
+ # Removes a child from this Composite directly
44
+ #
45
+ # This method can fail (for example, if the child does not exist in the
46
+ # Composite).
47
+ #
48
+ # @api public
49
+ # @example Removes a child.
50
+ # composite.remove(child)
51
+ #
52
+ # @param child [Object] The child to remove from this object.
53
+ #
54
+ # @return [Object] The removed child if successful; nil otherwise.
55
+ def remove(child)
56
+ remove!(child).tap(&method(:remove_parent_of))
57
+ end
58
+
59
+ # Removes a child from this Composite, given its ID
60
+ #
61
+ # This method can fail (for example, if the ID does not exist in the
62
+ # Composite).
63
+ #
64
+ # @api public
65
+ # @example Removes the child with ID :foo.
66
+ # composite.remove_id(:foo)
67
+ #
68
+ # @param id The ID of the child to remove from this object.
69
+ #
70
+ # @return [Object] The removed child if successful; nil otherwise.
71
+ def remove_id(id)
72
+ remove_id!(id).tap(&method(:remove_parent_of))
73
+ end
74
+
75
+ # Gets the child in this Composite with the given ID
76
+ #
77
+ # The ID is compared directly against the IDs of the children of this
78
+ # composite. To use a predicate to find an ID, use #get_child_such_that.
79
+ #
80
+ # @api public
81
+ # @example Gets the child with ID :in, if children is {in: 3}.
82
+ # composite.get_child(:in)
83
+ # #=> 3
84
+ # @example Fails to get the child with ID :out, if children is {in: 3}.
85
+ # composite.get_child(:out)
86
+ # #=> nil
87
+ # @example Fails to get the child with ID '1', if children is {1 => 3}.
88
+ # composite.get_child('1')
89
+ # #=> nil
90
+ #
91
+ # @param id [Object] The ID of the child to get from this Composite.
92
+ #
93
+ # @return [Object] The child if successful; nil otherwise.
94
+ def get_child(id)
95
+ children[id]
96
+ end
97
+
98
+ # Gets the child in this Composite whose ID matches a given predicate
99
+ #
100
+ # If multiple children match this predicate, the result is the first child
101
+ # in the hash.
102
+ #
103
+ # @api public
104
+ # @example Gets the child with ID :in, if children is {in: 3}.
105
+ # composite.get_child_such_that { |x| x == :in }
106
+ # #=> 3
107
+ # @example Fails to get the child with ID :out, if children is {in: 3}.
108
+ # composite.get_child_such_that { |x| x == :out }
109
+ # #=> nil
110
+ # @example Get the child with an ID whose string form is '1', if children
111
+ # is {1 => 3}.
112
+ # composite.get_child_such_that { |x| x.to_s == '3' }
113
+ # #=> 3
114
+ #
115
+ # @yieldparam id [Object] An ID to check against the predicate.
116
+ #
117
+ # @return [Object] The child if successful; nil otherwise.
118
+ def get_child_such_that(&block)
119
+ child = children.each.find { |k, _| block.call(k) }
120
+ (_, value) = child unless child.nil?
121
+ value
122
+ end
123
+
124
+ def_delegator :children, :each
125
+
126
+ protected
127
+
128
+ # Assigns this object to a child as its parent
129
+ #
130
+ # This also updates its ID function to point to the child's ID under this
131
+ # parent.
132
+ #
133
+ # @api private
134
+ #
135
+ # @param child [Object] The child whose parent assignment is being set.
136
+ #
137
+ # @return [void]
138
+ def assign_parent_to(child)
139
+ child.update_parent(self, id_function(child)) unless child.nil?
140
+ end
141
+
142
+ # Removes a child's parent assignment
143
+ #
144
+ # This also clears its ID function.
145
+ #
146
+ # @api private
147
+ #
148
+ # @param child [Object] The child whose parent assignment is being set.
149
+ #
150
+ # @return [void]
151
+ def remove_parent_of(child)
152
+ Parentless.for(child)
153
+ end
154
+
155
+ # Default implementation of #remove! in terms of #remove_id!
156
+ #
157
+ # Either this or #remove_id! must be overridden by the implementing class.
158
+ #
159
+ # @api private
160
+ #
161
+ # @param child [Object] The child to remove from this object.
162
+ #
163
+ # @return [void]
164
+ def remove!(child)
165
+ remove_id!(children.key(child))
166
+ end
167
+
168
+ # Default implementation of #remove_id! in terms of #remove!
169
+ #
170
+ # Either this or #remove! must be overridden by the implementing class.
171
+ #
172
+ # @api private
173
+ #
174
+ # @param id [Object] The current ID of the child to remove from this
175
+ # object.
176
+ #
177
+ # @return [void]
178
+ def remove_id!(id)
179
+ remove!(get_child(id))
180
+ end
181
+ end
182
+ end
183
+ end
@@ -0,0 +1,72 @@
1
+ require 'forwardable'
2
+ require 'compo/composites/composite'
3
+
4
+ module Compo
5
+ module Composites
6
+ # Implementation of Composite that stores its children in a Hash.
7
+ #
8
+ # IDs for items entering a ListComposite may be any permissible hash.
9
+ #
10
+ # Adding an item at an occupied ID removes the occupant.
11
+ class Hash
12
+ include Composite
13
+ extend Forwardable
14
+
15
+ # Initialises a hash composite
16
+ #
17
+ # @api public
18
+ # @example Initializes a hash composite.
19
+ # comp = Compo::Composites::Hash.new
20
+ def initialize
21
+ @children = {}
22
+ end
23
+
24
+ # Returns the hash composite's children, as a Hash
25
+ #
26
+ # @api public
27
+ # @example Gets the children of an empty hash composite.
28
+ # comp.children
29
+ # #=> {}
30
+ # @example Gets the children of a populated hash composite.
31
+ # comp.children
32
+ # #=> {foo: 3, bar: 5}
33
+ #
34
+ # @return [Hash] The Hash mapping the IDs of children to their values.
35
+ attr_reader :children
36
+
37
+ private
38
+
39
+ # Inserts a child into the hash composite with the given ID
40
+ #
41
+ # You probably want to use #add instead.
42
+ #
43
+ # @api private
44
+ #
45
+ # @param id [Object] The ID under which the child is to be added.
46
+ # @param child [Object] The child to add to the hash composite.
47
+ #
48
+ # @return [Object] The newly added child.
49
+ def add!(id, child)
50
+ remove_id(id)
51
+ @children[id] = child
52
+ end
53
+
54
+ def_delegator :@children, :delete, :remove_id!
55
+
56
+ # Creates an ID function for the given child
57
+ #
58
+ # The returned proc is O(1), as it stores the ID assigned to the child at
59
+ # calling time under the assumption that it will not change until removal.
60
+ #
61
+ # @api private
62
+ #
63
+ # @param child [Object] The child whose ID is to be returned by the proc.
64
+ #
65
+ # @return [Proc] A proc returning the child's ID.
66
+ def id_function(child)
67
+ id = @children.key(child)
68
+ proc { id }
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,64 @@
1
+ require 'compo/composites/composite'
2
+
3
+ module Compo
4
+ module Composites
5
+ # A Composite that cannot have children
6
+ #
7
+ # Add/remove operations on a leaf composite always fail, and #children
8
+ # always returns the empty hash.
9
+ #
10
+ # This is useful for leaf classes that still need to expose the composite
11
+ # API.
12
+ class Leaf
13
+ include Composite
14
+
15
+ # Returns the empty hash
16
+ #
17
+ # @api public
18
+ # @example Gets the children
19
+ # comp.children
20
+ # #=> {}
21
+ #
22
+ # @return [Hash] The empty hash.
23
+ def children
24
+ {}
25
+ end
26
+
27
+ private
28
+
29
+ # Fails to add a child into the leaf
30
+ #
31
+ # @api private
32
+ #
33
+ # @param id [Object] Ignored.
34
+ # @param child [Object] Ignored.
35
+ #
36
+ # @return [nil]
37
+ def add!(_, _)
38
+ nil
39
+ end
40
+
41
+ # Fails to remove the given child
42
+ #
43
+ # @api private
44
+ #
45
+ # @param child [Object] Ignored.
46
+ #
47
+ # @return [nil]
48
+ def remove!(_)
49
+ nil
50
+ end
51
+
52
+ # Fails to remove the child with the given ID
53
+ #
54
+ # @api private
55
+ #
56
+ # @param id [Object] Ignored.
57
+ #
58
+ # @return [nil]
59
+ def remove_id!(_)
60
+ nil
61
+ end
62
+ end
63
+ end
64
+ end