compo 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 13ca1304045a3fb0ac403495a4c838b53aeecb65
4
+ data.tar.gz: 06039743fc1c5f084a2906d548ecf322b8a12aaf
5
+ SHA512:
6
+ metadata.gz: 6de424684aa210f5f48c2d422dee5e44310ccacf6f2623d180c0f2934fa55ee7191b7a479b56badd9220ef1c0ee2dcb907d3df69e3f5063ea4079e2e8448a823
7
+ data.tar.gz: 6fd54c3de144b876874596fcc446e9b6a4f922f0c89fbaf4d2f6f423bc98d7f3fc10c58bc4d01fcc6d2a2d9d3a7c4beb1d7936684780965fe0ae6b9b389ea167
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.sublime*
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format Fuubar
2
+ --color
data/CHANGELOG ADDED
@@ -0,0 +1,2 @@
1
+ 0.1.0 (2013-12-26)
2
+ - Initial version.
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in compo.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Matt Windsor
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # Compo
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'compo'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install compo
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new
5
+
6
+ task :default => :spec
7
+ task :test => :spec
data/compo.gemspec ADDED
@@ -0,0 +1,33 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'compo/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'compo'
8
+ spec.version = Compo::VERSION
9
+ spec.authors = ['Matt Windsor']
10
+ spec.email = ['matt.windsor@ury.org.uk']
11
+ spec.description = %q{
12
+ Compo provides mixins and classes that assist in implementing a variant of
13
+ the Composite design pattern, in which each child has an ID that uniquely
14
+ identifies it inside the parent's child set.
15
+ }
16
+ spec.summary = 'Composite pattern style mixins with IDs'
17
+ spec.homepage = 'http://github.com/CaptainHayashi/compo'
18
+ spec.license = 'MIT'
19
+
20
+ spec.files = `git ls-files`.split($/)
21
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
22
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
23
+ spec.require_paths = ['lib']
24
+
25
+ spec.add_development_dependency 'backports'
26
+ spec.add_development_dependency 'bundler', '~> 1.3'
27
+ spec.add_development_dependency 'fuubar'
28
+ spec.add_development_dependency 'rake'
29
+ spec.add_development_dependency 'rspec'
30
+ spec.add_development_dependency 'simplecov'
31
+ spec.add_development_dependency 'yard'
32
+ spec.add_development_dependency 'yardstick'
33
+ end
@@ -0,0 +1,103 @@
1
+ require 'forwardable'
2
+ require 'compo/composite'
3
+
4
+ module Compo
5
+ # Implementation of Composite that stores its children in an Array.
6
+ #
7
+ # IDs for items entering a ListComposite must be numeric, and will change if
8
+ # an item with an ID less than the item in question is deleted or inserted.
9
+ # This means the ID function for objects in a ListComposite may report
10
+ # different values at different times.
11
+ #
12
+ # Adding an object at an occupied ID moves the occupant and those at
13
+ # successive IDs up by one.
14
+ class ArrayComposite
15
+ include Composite
16
+ extend Forwardable
17
+
18
+ # Initialises an ArrayComposite
19
+ #
20
+ # @api public
21
+ # @example Initializes an ArrayComposite.
22
+ # comp = ArrayComposite.new
23
+ def initialize
24
+ @children = []
25
+ end
26
+
27
+ # Returns the ArrayComposite's children, as a Hash
28
+ #
29
+ # @api public
30
+ # @example Gets the children of an empty ArrayComposite.
31
+ # comp.children
32
+ # #=> {}
33
+ # @example Gets the children of a populated ArrayComposite.
34
+ # comp.children
35
+ # #=> {0: :first, 1: :second}
36
+ #
37
+ # @return [Hash] The Hash mapping the IDs of children to their values.
38
+ def children
39
+ Hash[(0...@children.size).zip(@children)]
40
+ end
41
+
42
+ private
43
+
44
+ def_delegator :@children, :delete, :remove!
45
+ def_delegator :@children, :delete_at, :remove_id!
46
+
47
+ # Inserts a child into the ArrayComposite with the given ID
48
+ #
49
+ # You probably want to use #add instead.
50
+ #
51
+ # @api private
52
+ #
53
+ # @param id [Object] The ID under which the child is to be added.
54
+ # @param child [Object] The child to add to the ArrayComposite.
55
+ #
56
+ # @return [Object] The newly added child, or nil if the ID was invalid.
57
+ def add!(id, child)
58
+ valid_id?(id) ? do_insert(id, child) : nil
59
+ end
60
+
61
+ # Checks to see if the given ID is valid
62
+ #
63
+ # A valid ID for ArrayComposites is a number between 0 and the current
64
+ # size of the children list.
65
+ #
66
+ # @api private
67
+ #
68
+ # @param id [Object] The candidate ID.
69
+ #
70
+ # @return [Boolean] True if the ID is valid; false if not.
71
+ def valid_id?(id)
72
+ id.is_a?(Numeric) && (0..@children.size).cover?(id)
73
+ end
74
+
75
+ # Actually performs the insertion of an item into the array
76
+ #
77
+ # @api private
78
+ #
79
+ # @param id [Numeric] The index into the array at which the child is to be
80
+ # inserted.
81
+ # @param child [Object] The object to insert into the children array.
82
+ #
83
+ # @return [Object] The inserted child.
84
+ def do_insert(id, child)
85
+ @children.insert(id, child)
86
+ child
87
+ end
88
+
89
+ # Creates an ID function for the given child
90
+ #
91
+ # The returned proc is O(n), as it checks the child array at each call to
92
+ # find the current ID of the child.
93
+ #
94
+ # @api private
95
+ #
96
+ # @param child [Object] The child whose ID is to be returned by the proc.
97
+ #
98
+ # @return [Proc] A proc returning the child's ID.
99
+ def id_function(object)
100
+ proc { @children.index(object) }
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,130 @@
1
+ module Compo
2
+ # Mixin for objects that can contain other objects
3
+ #
4
+ # Objects implementing this interface should implement add!, remove! or
5
+ # remove_id!, and id_function:
6
+ #
7
+ # add! - Given a desired ID and child, adds the child to the children
8
+ # of this object; returns the child if successful, nil
9
+ # otherwise.
10
+ # remove! - Given a child, removes and returns it from the children; if
11
+ # not provided, this is implemented in terms of remove_id!.
12
+ # remove_id! - Given an ID, removes and returns the child with this ID from
13
+ # the children; if not provided, this is implemented in terms
14
+ # of remove!.
15
+ # children - Returns the children, as a Hash mapping from current IDs to
16
+ # their child values.
17
+ # id_function - Given a newly inserted child, returns a proc that will
18
+ # always return the child's current ID so long as it is part
19
+ # of the Composite.
20
+ module Composite
21
+ extend Forwardable
22
+ include Enumerable
23
+
24
+ # Adds a child to this Composite
25
+ #
26
+ # @api public
27
+ # @example Adds a child with intended id 3.
28
+ # composite.add_child(3, leaf)
29
+ #
30
+ # @param id [Object] The intended ID of the child in this Composite.
31
+ # The actual ID may not be the same as this; consult the proc supplied
32
+ # to the child via #update_parent.
33
+ # @param child [Object] The child to add to this Composite.
34
+ #
35
+ # @return [Object] The added child if successful; nil otherwise.
36
+ def add(id, child)
37
+ add!(id, child).tap(&method(:assign_parent))
38
+ end
39
+
40
+ # Removes a child from this Composite directly
41
+ #
42
+ # This method can fail (for example, if the child does not exist in the
43
+ # Composite).
44
+ #
45
+ # @api public
46
+ # @example Removes a child.
47
+ # composite.remove(child)
48
+ #
49
+ # @param child [Object] The child to remove from this object.
50
+ #
51
+ # @return [Object] The removed child if successful; nil otherwise.
52
+ def remove(child)
53
+ remove!(child).tap(&method(:remove_parent))
54
+ end
55
+
56
+ # Removes a child from this Composite, given its ID
57
+ #
58
+ # This method can fail (for example, if the ID does not exist in the
59
+ # Composite).
60
+ #
61
+ # @api public
62
+ # @example Removes the child with ID :foo.
63
+ # composite.remove_id(:foo)
64
+ #
65
+ # @param id The ID of the child to remove from this object.
66
+ #
67
+ # @return [Object] The removed child if successful; nil otherwise.
68
+ def remove_id(id)
69
+ remove_id!(id).tap(&method(:remove_parent))
70
+ end
71
+
72
+ def_delegator :children, :each
73
+
74
+ protected
75
+
76
+ # Assigns this object to a child as its parent
77
+ #
78
+ # This also updates its ID function to point to the child's ID under this
79
+ # parent.
80
+ #
81
+ # @api private
82
+ #
83
+ # @param child [Object] The child whose parent assignment is being set.
84
+ #
85
+ # @return [void]
86
+ def assign_parent(child)
87
+ child.update_parent(self, id_function(child)) unless child.nil?
88
+ end
89
+
90
+ # Removes a child's parent assignment
91
+ #
92
+ # This also clears its ID function.
93
+ #
94
+ # @api private
95
+ #
96
+ # @param child [Object] The child whose parent assignment is being set.
97
+ #
98
+ # @return [void]
99
+ def remove_parent(child)
100
+ child.remove_parent unless child.nil?
101
+ end
102
+
103
+ # Default implementation of #remove! in terms of #remove_id!
104
+ #
105
+ # Either this or #remove_id! must be overridden by the implementing class.
106
+ #
107
+ # @api private
108
+ #
109
+ # @param child [Object] The child to remove from this object.
110
+ #
111
+ # @return [void]
112
+ def remove!(child)
113
+ remove_id!(children.key(child))
114
+ end
115
+
116
+ # Default implementation of #remove_id! in terms of #remove!
117
+ #
118
+ # Either this or #remove! must be overridden by the implementing class.
119
+ #
120
+ # @api private
121
+ #
122
+ # @param id [Object] The current ID of the child to remove from this
123
+ # object.
124
+ #
125
+ # @return [void]
126
+ def remove_id!(id)
127
+ remove!(get_child(id))
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,70 @@
1
+ require 'forwardable'
2
+ require 'compo/composite'
3
+
4
+ module Compo
5
+ # Implementation of Composite that stores its children in a Hash.
6
+ #
7
+ # IDs for items entering a ListComposite may be any permissible hash.
8
+ #
9
+ # Adding an item at an occupied ID removes the occupant.
10
+ class HashComposite
11
+ include Composite
12
+ extend Forwardable
13
+
14
+ # Initialises a HashComposite
15
+ #
16
+ # @api public
17
+ # @example Initializes a HashComposite.
18
+ # comp = HashComposite.new
19
+ def initialize
20
+ @children = {}
21
+ end
22
+
23
+ # Returns the HashComposite's children, as a Hash
24
+ #
25
+ # @api public
26
+ # @example Gets the children of an empty HashComposite.
27
+ # comp.children
28
+ # #=> {}
29
+ # @example Gets the children of a populated HashComposite.
30
+ # comp.children
31
+ # #=> {foo: 3, bar: 5}
32
+ #
33
+ # @return [Hash] The Hash mapping the IDs of children to their values.
34
+ attr_reader :children
35
+
36
+ private
37
+
38
+ # Inserts a child into the HashComposite with the given ID
39
+ #
40
+ # You probably want to use #add instead.
41
+ #
42
+ # @api private
43
+ #
44
+ # @param id [Object] The ID under which the child is to be added.
45
+ # @param child [Object] The child to add to the HashComposite.
46
+ #
47
+ # @return [Object] The newly added child.
48
+ def add!(id, child)
49
+ remove_id(id)
50
+ @children[id] = child
51
+ end
52
+
53
+ def_delegator :@children, :delete, :remove_id!
54
+
55
+ # Creates an ID function for the given child
56
+ #
57
+ # The returned proc is O(1), as it stores the ID assigned to the child at
58
+ # calling time under the assumption that it will not change until removal.
59
+ #
60
+ # @api private
61
+ #
62
+ # @param child [Object] The child whose ID is to be returned by the proc.
63
+ #
64
+ # @return [Proc] A proc returning the child's ID.
65
+ def id_function(child)
66
+ id = @children.key(child)
67
+ proc { id }
68
+ end
69
+ end
70
+ end
data/lib/compo/leaf.rb ADDED
@@ -0,0 +1,22 @@
1
+ require 'compo/movable'
2
+ require 'compo/null_composite'
3
+ require 'compo/parent_tracker'
4
+
5
+ module Compo
6
+ # A simple implementation of a leaf
7
+ #
8
+ # Leaves have no children, but can be moved to one.
9
+ class Leaf < NullComposite
10
+ include Movable
11
+ include ParentTracker
12
+
13
+ # Initialises the Leaf
14
+ #
15
+ # @api public
16
+ # @example Creates a new Leaf.
17
+ # leaf.new
18
+ def initialize
19
+ remove_parent
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,53 @@
1
+ module Compo
2
+ # Helper mixin for objects that can be moved into other objects
3
+ #
4
+ # This mixin defines a method, #move_to, which handles removing a child
5
+ # from its current parent and adding it to a new one.
6
+ #
7
+ # It expects the current parent to be reachable from #parent.
8
+ module Movable
9
+ # Moves this model object to a new parent with a new ID
10
+ #
11
+ # @api public
12
+ # @example Moves the object to a new parent.
13
+ # movable.move_to(parent, :id)
14
+ # @example Moves the object out of its parent (deleting it, if there are
15
+ # no other live references).
16
+ # movable.move_to(nil, nil)
17
+ #
18
+ # @param new_parent [ModelObject] The new parent for this object (can be
19
+ # nil).
20
+ # @param new_id [Object] The new ID under which the object will exist in
21
+ # the parent.
22
+ #
23
+ # @return [self]
24
+ def move_to(new_parent, new_id)
25
+ move_from_old_parent
26
+ move_to_new_parent(new_parent, new_id)
27
+ self
28
+ end
29
+
30
+ private
31
+
32
+ # Performs the move from an old parent, if necessary
33
+ #
34
+ # @api private
35
+ #
36
+ # @return [void]
37
+ def move_from_old_parent
38
+ parent.remove(self) unless parent.nil?
39
+ end
40
+
41
+ # Performs the move to a new parent, if necessary
42
+ #
43
+ # @api private
44
+ #
45
+ # @param new_parent [Composite] The target parent of the move.
46
+ # @param new_id [Object] The intended new ID of this child.
47
+ #
48
+ # @return [void]
49
+ def move_to_new_parent(new_parent, new_id)
50
+ new_parent.add(new_id, self) unless new_parent.nil?
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,63 @@
1
+ require 'forwardable'
2
+ require 'compo/composite'
3
+
4
+ module Compo
5
+ # Null implementation of Composite
6
+ #
7
+ # Add/remove operations on NullComposite always fail, and #children always
8
+ # returns the empty hash.
9
+ #
10
+ # This is useful for leaf classes that still need to expose the composite
11
+ # API.
12
+ class NullComposite
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 NullComposite
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
@@ -0,0 +1,64 @@
1
+ require 'forwardable'
2
+
3
+ module Compo
4
+ # Basic implementation of parent tracking as a mixin
5
+ #
6
+ # This implements #parent, #update_parent and #remove_parent to track the
7
+ # current parent and ID function as instance variables. It also implements
8
+ # #parent, and #id in terms of the ID function.
9
+ #
10
+ # Subclasses should call #remove_parent in their #initialize methods, to
11
+ # set the parent and ID function to their default, empty values.
12
+ module ParentTracker
13
+ extend Forwardable
14
+
15
+ # Gets this object's current ID
16
+ #
17
+ # @api public
18
+ # @example Gets the object's parent while it has none.
19
+ # parent_tracker.parent
20
+ # #=> nil
21
+ # @example Gets the object's parent while it has one.
22
+ # parent_tracker.parent
23
+ # #=> :the_current_parent
24
+ #
25
+ # @return [Composite] The current parent.
26
+ attr_reader :parent
27
+
28
+ # Gets this object's current ID
29
+ #
30
+ # @api public
31
+ # @example Gets the object's ID while it has no parent.
32
+ # parent_tracker.id
33
+ # #=> nil
34
+ # @example Gets the object's ID while it has a parent.
35
+ # parent_tracker.id
36
+ # #=> :the_current_id
37
+ #
38
+ # @return [Object] The current ID.
39
+ def_delegator :@id_function, :call, :id
40
+
41
+ # Updates this object's parent and ID function
42
+ #
43
+ # @api public
44
+ # @example Update this Leaf's parent and ID function.
45
+ # parent_tracker.update_parent(new_parent, new_id_function)
46
+ #
47
+ # @return [void]
48
+ def update_parent(new_parent, new_id_function)
49
+ @parent = new_parent
50
+ @id_function = new_id_function
51
+ end
52
+
53
+ # Blanks out this object's parent and ID function
54
+ #
55
+ # @api public
56
+ # @example Update this Leaf's parent and ID function.
57
+ # movable.update_parent(new_parent, new_id_function)
58
+ #
59
+ # @return [void]
60
+ def remove_parent
61
+ update_parent(nil, ->{ nil })
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,4 @@
1
+ # The current gem version. See CHANGELOG for information.
2
+ module Compo
3
+ VERSION = '0.1.0'
4
+ end
data/lib/compo.rb ADDED
@@ -0,0 +1,12 @@
1
+ require 'compo/array_composite'
2
+ require 'compo/composite'
3
+ require 'compo/hash_composite'
4
+ require 'compo/leaf'
5
+ require 'compo/movable'
6
+ require 'compo/null_composite'
7
+ require 'compo/parent_tracker'
8
+ require 'compo/version'
9
+
10
+ # The main module for Compo.
11
+ module Compo
12
+ end