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.
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