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
@@ -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
@@ -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
@@ -1,9 +0,0 @@
1
- require 'spec_helper'
2
- require 'compo'
3
- require 'branch_shared_examples'
4
- require 'null_composite_shared_examples'
5
-
6
- describe Compo::Leaf do
7
- it_behaves_like 'a branch'
8
- it_behaves_like 'a null composite'
9
- end
@@ -1,7 +0,0 @@
1
- require 'spec_helper'
2
- require 'compo'
3
- require 'null_composite_shared_examples'
4
-
5
- describe Compo::NullComposite do
6
- it_behaves_like 'a null composite'
7
- end