compo 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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