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