extensional 1.3.1

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.
@@ -0,0 +1,4 @@
1
+ === 1.3.1 / 2010-09-30
2
+
3
+ * Initial public release
4
+
data/LEGAL ADDED
@@ -0,0 +1,5 @@
1
+ LEGAL NOTICE INFORMATION
2
+ ------------------------
3
+
4
+ All the files in this distribution are covered under either the MIT
5
+ license (see the file LICENSE).
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2010 Oregon Health & Sciences University Knight Cancer Institute
2
+
3
+ Permission is hereby granted, free of charge, to any person
4
+ obtaining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use,
7
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following
10
+ conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,76 @@
1
+ extensional: Class extent collector
2
+ ===================================
3
+
4
+ **Git**: [http://github.com/caruby/extensional](http://github.com/caruby/extensional)
5
+ **Author**: OHSU Knight Cancer Institute
6
+ **Copyright**: 2010
7
+ **License**: MIT License
8
+ **Latest Version**: 1.3.1
9
+ **Release Date**: September 30th 2010
10
+
11
+ Synopsis
12
+ --------
13
+
14
+ The extensional gem adds the ability of a Class to collect its instances.
15
+ An extensional class is an Enumerable whose each method iterates over its instances.
16
+ An optional callback block enables associative access to instances by key.
17
+
18
+ Feature List
19
+ ------------
20
+
21
+ 1. Add instances to a Class extent.
22
+
23
+ 2. Associative access by a designated key.
24
+
25
+ 3. Custom unit definition.
26
+
27
+ 4. Measurement parser.
28
+
29
+ Installing
30
+ ----------
31
+
32
+ To install the extensional gem, use the following command:
33
+
34
+ $ gem install extensional
35
+
36
+ (Add `sudo` if you're installing under a POSIX system as root)
37
+
38
+ Alternatively, if you've checked the source out directly, you can call
39
+ `rake install` from the root project directory.
40
+
41
+ Usage
42
+ -----
43
+
44
+ An extensional class is an Enumerable whose <tt>each</tt> method iterates over its instances, e.g.:
45
+
46
+ class Person
47
+ make_extensional
48
+ end
49
+ Employee.new(20195) #=> Employee@2653
50
+ Employee.to_a #=> [Employee@2653]
51
+
52
+ An optional callback block adds associative access to instances by key using the class <tt>for</tt>
53
+ method, e.g.:
54
+
55
+ class Employee
56
+ make_extensional { |hash, emp| hash[emp.number] = emp }
57
+ ...
58
+ end
59
+ Employee.for(20196) #=> nil
60
+ Employee.new(20196) #=> Employee@2654
61
+ Employee.for(20196) #=> Employee@2654
62
+
63
+ The Class.make_extensional RDoc includes additional examples.
64
+
65
+ Changelog
66
+ ---------
67
+
68
+ - **September.30.10**: 2010.1 release
69
+ - Initial public release
70
+
71
+ Copyright
72
+ ---------
73
+
74
+ extensional &copy; 2010 by [Oregon Health & Sciences University](mailto:loneyf@ohsu.edu).
75
+ extensional is licensed under the MIT license. Please see the LICENSE and LEGAL
76
+ files for more information.
@@ -0,0 +1,45 @@
1
+ require 'forwardable'
2
+ require 'set'
3
+ require 'extensional/class'
4
+
5
+ # An Extensional Class collects its instances.
6
+ # See Class#make_extensional for the usage model.
7
+ module Extensional
8
+ include Enumerable
9
+
10
+ # This Extensional implementation version.
11
+ VERSION = '1.3.1'
12
+
13
+ # Returns this Extensional class's Extent.
14
+ def extent
15
+ @extent
16
+ end
17
+
18
+ # Returns the instance with the given key arguments, or nil if no association is found.
19
+ #
20
+ # Raises NotImplementedError if the class extent does not implement associative access.
21
+ def for(*key)
22
+ unless @extent.associative? then
23
+ raise NotImplementedError.new("Associative access by key is not implemented for #{self}")
24
+ end
25
+ # extract the key argument from a key array with zero or one members;
26
+ # if key has more than one member, the key remains an array of all arguments
27
+ key = key.first if key.size == 1
28
+ @extent.find(key)
29
+ end
30
+
31
+ # Adds an instance of this class to the class extent.
32
+ def <<(instance)
33
+ @extent << instance
34
+ end
35
+
36
+ # Calls block an each instance in this class's extent.
37
+ def each(&block)
38
+ @extent.each(&block)
39
+ end
40
+
41
+ # Clears this class's extent.
42
+ def clear
43
+ @extent.clear
44
+ end
45
+ end
@@ -0,0 +1,68 @@
1
+ require 'extensional/extent'
2
+
3
+ class Class
4
+ # Adds the ability of a Class to collect its instances. An extensional class is an Enumerable
5
+ # whose +each+ method iterates over its instances, e.g.:
6
+ # class Person
7
+ # make_extensional
8
+ # def initialize(ssn)
9
+ # @ssn = ssn
10
+ # Person << self
11
+ # end
12
+ # def to_s
13
+ # "#{self.class.name}{ssn=>#{@ssn}}"
14
+ # end
15
+ # ...
16
+ # end
17
+ # Person.new('555-55-5555')
18
+ # Person.join(', ') #=> Person{ssn=>555-55-5555}
19
+ #
20
+ # The optional callback block adds associative access to instances by key using the class #for method.
21
+ # The callback block is called when an instance is added to the extent. The block arguments include the
22
+ # key=>instance association hash and the new instance, e.g.:
23
+ # class Person
24
+ # make_extensional { |hash, person| hash[person.ssn] = person }
25
+ # ...
26
+ # end
27
+ # Person.for('555-55-5555') #=> nil
28
+ # Person.new('555-55-5555')
29
+ # Person.for('555-55-5555') #=> Person{ssn=>555-55-5555}
30
+ #
31
+ # The optional hash argument is the association hash. If the association hash is created with a block,
32
+ # then the instance is added to the hash on demand when the #for method is called in accordance with
33
+ # the standard Hash#new behavior, e.g.:
34
+ # class Person
35
+ # make_extensional(Hash.new { |hash, ssn| hash[snn] = new(ssn) })
36
+ # ...
37
+ # end
38
+ # Person.for('555-55-5555') #=> Person{ssn=>555-55-5555}
39
+ # Person.join(', ') #=> Person{ssn=>555-55-5555}
40
+ # Person.new('666-66-6666') # added to the extent but not the association since there is no callback block
41
+ # Person.map { |person| person.ssn }.sort #=> ["555-55-5555", "666-66-6666"]
42
+ # Person.for('666-66-6666') #=> nil
43
+ #
44
+ # The hash factory block above only adds an instance to the extent association if it is accessed using the
45
+ # #for class method. If an instance can be created both on demand and independently of the #for method,
46
+ # then specify both a factory block and a callback block, e.g.:
47
+ # class Person
48
+ # make_extensional(Hash.new { |hash, ssn| new(ssn) }) {|hash, person| hash[person.snn] = person }
49
+ # ...
50
+ # end
51
+ # Person.for('555-55-5555') #=> Person{ssn=>555-55-5555}
52
+ # Person.new('666-66-6666') # added to both the extent and the association
53
+ # Person.map { |person| person.ssn }.sort #=> ["555-55-5555", "666-66-6666"]
54
+ # Person.for('666-66-6666') #=> Person{ssn=>666-66-6666}
55
+ #
56
+ # Note that hash assignment is not needed in the hash initializer method, since there is a callback block
57
+ # assigns the hash key to the new instance.
58
+ #
59
+ # This latter make_extensional alternative is the most flexible strategy, since it provides for both
60
+ # iterative and associative access.
61
+ #
62
+ # If neither a callback block nor a hash is given, then associative access is disabled as described
63
+ # in the Extensional#for method.
64
+ def make_extensional(hash=nil, &block) # :yields: hash, instance
65
+ @extent = Extent.new(hash, &block)
66
+ extend(Extensional)
67
+ end
68
+ end
@@ -0,0 +1,48 @@
1
+ require 'forwardable'
2
+ require 'set'
3
+
4
+ # An Extent is a thin Array wrapper with optional associative access.
5
+ class Extent
6
+ include Enumerable
7
+
8
+ # The key=>instance association hash.
9
+ attr_reader :association
10
+
11
+ # Creates a new Extent. The block is called when an instance is added to the extent.
12
+ def initialize(hash=nil, &block) # :yields: hash, instance
13
+ @extent = Set.new
14
+ @association = hash
15
+ @association ||= {} if block_given?
16
+ @callback = block
17
+ end
18
+
19
+ # Adds the given obj to this extent.
20
+ def <<(obj)
21
+ @callback.call(@association, obj) if @callback
22
+ @extent << obj
23
+ end
24
+
25
+ # Calls the given block an each instance in this extent.
26
+ def each(&block)
27
+ @extent.each(&block)
28
+ end
29
+
30
+ # Returns whether this Extent implements associative access by key.
31
+ def associative?
32
+ not @association.nil?
33
+ end
34
+
35
+ # Returns the instance for the given key.
36
+ #
37
+ # Raises NotImplementedError if this extent does not implement associative access.
38
+ def find(key)
39
+ raise NotImplementedError.new("Associative access by key is not implemented for this class extent") unless associative?
40
+ @association[key]
41
+ end
42
+
43
+ # Removes all instances from this Extent's extent and association.
44
+ def clear
45
+ @extent.clear
46
+ @association.clear if associative?
47
+ end
48
+ end
@@ -0,0 +1,105 @@
1
+ $:.unshift 'lib'
2
+
3
+ require 'extensional'
4
+
5
+ class Extended
6
+ make_extensional
7
+
8
+ def initialize
9
+ Extended << self
10
+ end
11
+ end
12
+
13
+ class Associative
14
+ make_extensional { |hash, obj| hash[obj.label] = obj }
15
+
16
+ attr_reader :label
17
+
18
+ def initialize(label)
19
+ @label = label
20
+ Associative << self
21
+ end
22
+ end
23
+
24
+ class CompoundKey
25
+ make_extensional { |hash, obj| hash[obj.key] = obj }
26
+
27
+ attr_reader :key
28
+
29
+ def initialize(x, y)
30
+ @key = [x, y]
31
+ CompoundKey << self
32
+ end
33
+ end
34
+
35
+ class CreateOnAccess
36
+ make_extensional(Hash.new { |hash, label| hash[label] = CreateOnAccess.new(label) })
37
+
38
+ def initialize(label)
39
+ @label = label
40
+ end
41
+ end
42
+
43
+ class AssociativeCreateOnAccess
44
+ make_extensional(Hash.new { |hash, label| AssociativeCreateOnAccess.new(label) }) { |hash, obj| hash[obj.label] = obj }
45
+
46
+ attr_reader :label
47
+
48
+ def initialize(label)
49
+ @label = label
50
+ AssociativeCreateOnAccess << self
51
+ end
52
+ end
53
+
54
+ class A
55
+ make_extensional
56
+ def initialize
57
+ self.class << self
58
+ end
59
+ end
60
+
61
+ class B < A
62
+ make_extensional
63
+ end
64
+
65
+ class C < A
66
+ make_extensional
67
+ end
68
+
69
+ class ExtensionalTest < Test::Unit::TestCase
70
+ def test_extent
71
+ assert_equal(Extended.new, Extended.to_a.first, "Instance not added to extension")
72
+ end
73
+
74
+ def test_associative
75
+ obj = Associative.new(:test)
76
+ assert_same(obj, Associative.for(:test), "Instance not accessible by label")
77
+ end
78
+
79
+ def test_compound_key
80
+ obj = CompoundKey.new(:x, :y)
81
+ assert_same(obj, CompoundKey.for(:x, :y), "Instance not accessible by compound key")
82
+ end
83
+
84
+ def test_hash_factory
85
+ obj = CreateOnAccess.for(:test)
86
+ assert_not_nil(obj, "Instance not created")
87
+ end
88
+
89
+ def test_associative_hash_factory
90
+ obj = AssociativeCreateOnAccess.for(:on_demand)
91
+ assert_not_nil(obj, "Instance not created")
92
+ assert_same(obj, AssociativeCreateOnAccess.for(:on_demand), "Instance created on demand not accessible by label")
93
+ obj = AssociativeCreateOnAccess.new(:by_new)
94
+ assert(AssociativeCreateOnAccess.extent.association.has_key?(:by_new), "Instance not accessible by label")
95
+ end
96
+
97
+ def test_isolation
98
+ a = A.new
99
+ b = B.new
100
+ c = C.new
101
+ assert_equal([a], A.to_a, "Superclass extension incorrect")
102
+ assert_equal([b], B.to_a, "Subclass extension incorrect")
103
+ assert_equal([c], C.to_a, "Subclass extension incorrect")
104
+ end
105
+ end
metadata ADDED
@@ -0,0 +1,74 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: extensional
3
+ version: !ruby/object:Gem::Version
4
+ hash: 25
5
+ prerelease: false
6
+ segments:
7
+ - 1
8
+ - 3
9
+ - 1
10
+ version: 1.3.1
11
+ platform: ruby
12
+ authors:
13
+ - OHSU
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-09-30 00:00:00 -07:00
19
+ default_executable:
20
+ dependencies: []
21
+
22
+ description: " The extensional gem adds the ability of a Class to collect its instances.\n An extensional class is an Enumerable whose each method iterates over its instances.\n An optional callback block enables associative access to instances by key.\n"
23
+ email: caruby.org@gmail.com
24
+ executables: []
25
+
26
+ extensions: []
27
+
28
+ extra_rdoc_files: []
29
+
30
+ files:
31
+ - lib/extensional/class.rb
32
+ - lib/extensional/extent.rb
33
+ - lib/extensional.rb
34
+ - test/lib/extensional_test.rb
35
+ - History.txt
36
+ - LEGAL
37
+ - LICENSE
38
+ - README.md
39
+ has_rdoc: extensional
40
+ homepage: http://github.com/caruby/extensional/
41
+ licenses: []
42
+
43
+ post_install_message:
44
+ rdoc_options: []
45
+
46
+ require_paths:
47
+ - lib
48
+ required_ruby_version: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ hash: 3
54
+ segments:
55
+ - 0
56
+ version: "0"
57
+ required_rubygems_version: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ hash: 3
63
+ segments:
64
+ - 0
65
+ version: "0"
66
+ requirements: []
67
+
68
+ rubyforge_project: caruby
69
+ rubygems_version: 1.3.7
70
+ signing_key:
71
+ specification_version: 3
72
+ summary: Collects Class instances.
73
+ test_files: []
74
+