mozart 0.0.1 → 0.0.2
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.
- data/example.rb +48 -0
- data/lib/mozart.rb +7 -5
- data/lib/mozart/composable.rb +29 -0
- data/lib/mozart/composite.rb +39 -0
- data/lib/mozart/context.rb +23 -0
- data/lib/mozart/environment.rb +6 -0
- data/lib/mozart/single_assignment.rb +20 -0
- data/lib/mozart/value.rb +33 -0
- data/lib/mozart/version.rb +1 -1
- data/mozart.gemspec +2 -2
- data/test/composite_test.rb +47 -0
- data/test/context_test.rb +20 -0
- data/test/suite.rb +2 -0
- data/test/value_test.rb +39 -0
- metadata +20 -6
- data/Rakefile +0 -2
data/example.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
require_relative "lib/mozart"
|
2
|
+
|
3
|
+
class BasicCalculator
|
4
|
+
include Mozart::Environment
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
array = []
|
8
|
+
|
9
|
+
features << X(:push, array)
|
10
|
+
|
11
|
+
_(:data, X(:reduce, :clear, array))
|
12
|
+
end
|
13
|
+
|
14
|
+
def sum
|
15
|
+
_(:data).reduce(0, :+).tap { _(:data).clear }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
stack = BasicCalculator.new
|
20
|
+
stack.push(10)
|
21
|
+
stack.push(20)
|
22
|
+
stack.push(30)
|
23
|
+
|
24
|
+
p stack.sum
|
25
|
+
|
26
|
+
stack.push(20)
|
27
|
+
stack.push(50)
|
28
|
+
|
29
|
+
p stack.sum
|
30
|
+
|
31
|
+
|
32
|
+
class Person
|
33
|
+
include Mozart::Composable
|
34
|
+
|
35
|
+
def initialize(params)
|
36
|
+
features << V(:name, :email, params)
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_s
|
40
|
+
"#{name} <#{email}>"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
person = Person.new(:name => "Gregory Brown",
|
46
|
+
:email => "gregory.t.brown@gmail.com")
|
47
|
+
|
48
|
+
puts person
|
data/lib/mozart.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
1
|
+
require_relative "mozart/version"
|
2
|
+
require_relative "mozart/composite"
|
3
|
+
require_relative "mozart/composable"
|
4
|
+
require_relative "mozart/value"
|
5
|
+
require_relative "mozart/context"
|
6
|
+
require_relative "mozart/single_assignment"
|
7
|
+
require_relative "mozart/environment"
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require_relative "composite"
|
2
|
+
|
3
|
+
module Mozart
|
4
|
+
module Composable
|
5
|
+
private
|
6
|
+
|
7
|
+
def X(*method_names, target)
|
8
|
+
Mozart.context(*method_names).new(target)
|
9
|
+
end
|
10
|
+
|
11
|
+
def V(*field_names, params)
|
12
|
+
Mozart.value(*field_names).new(params)
|
13
|
+
end
|
14
|
+
|
15
|
+
def features
|
16
|
+
@features ||= Mozart::Composite.new
|
17
|
+
end
|
18
|
+
|
19
|
+
def respond_to_missing?(m, *a, &b)
|
20
|
+
features.receives?(m)
|
21
|
+
end
|
22
|
+
|
23
|
+
def method_missing(m, *a, &b)
|
24
|
+
return super unless features.receives?(m)
|
25
|
+
|
26
|
+
features.dispatch(m, *a, &b)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Mozart
|
2
|
+
AmbiguousMessageError = Class.new(StandardError)
|
3
|
+
|
4
|
+
class Composite
|
5
|
+
def initialize
|
6
|
+
self.parts = []
|
7
|
+
end
|
8
|
+
|
9
|
+
def <<(part)
|
10
|
+
parts << part
|
11
|
+
end
|
12
|
+
|
13
|
+
def receives?(message)
|
14
|
+
!!recipient(message)
|
15
|
+
end
|
16
|
+
|
17
|
+
def dispatch(message, *a, &b)
|
18
|
+
target = recipient(message)
|
19
|
+
|
20
|
+
unless target
|
21
|
+
raise NotImplementedError, "No recipient implements #{message}"
|
22
|
+
end
|
23
|
+
|
24
|
+
target.public_send(message, *a, &b)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def recipient(message)
|
30
|
+
target, *rest = parts.select { |part| part.respond_to?(message) }
|
31
|
+
|
32
|
+
raise AmbiguousMessageError unless rest.empty?
|
33
|
+
|
34
|
+
target
|
35
|
+
end
|
36
|
+
|
37
|
+
attr_accessor :parts
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Mozart
|
2
|
+
def self.context(*accepted_methods, &class_definition)
|
3
|
+
Class.new do
|
4
|
+
def initialize(target)
|
5
|
+
self.target = target
|
6
|
+
end
|
7
|
+
|
8
|
+
define_method :respond_to_missing? do |m, *a|
|
9
|
+
accepted_methods.include?(m)
|
10
|
+
end
|
11
|
+
|
12
|
+
def method_missing(m, *a, &b)
|
13
|
+
raise NotImplementedError unless respond_to_missing?(m)
|
14
|
+
|
15
|
+
target.public_send(m, *a, &b)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
attr_accessor :target
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Mozart
|
2
|
+
module SingleAssignment
|
3
|
+
def _(*args)
|
4
|
+
@__internals__ ||= {}
|
5
|
+
|
6
|
+
case args.count
|
7
|
+
when 1
|
8
|
+
@__internals__[args.first]
|
9
|
+
when 2
|
10
|
+
if @__internals__.key?(args.first)
|
11
|
+
raise "Single assignment only!"
|
12
|
+
else
|
13
|
+
@__internals__[args.first] = args.last
|
14
|
+
end
|
15
|
+
else
|
16
|
+
raise ArgumentError
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/mozart/value.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
module Mozart
|
2
|
+
def self.value(*field_names, &block)
|
3
|
+
Class.new do
|
4
|
+
define_method(:initialize) do |params={}|
|
5
|
+
raise ArgumentError unless params.keys == field_names
|
6
|
+
|
7
|
+
self.__data__ = {}
|
8
|
+
|
9
|
+
params.each { |k,v| __data__[k] = v }
|
10
|
+
|
11
|
+
__data__
|
12
|
+
end
|
13
|
+
|
14
|
+
def ==(other)
|
15
|
+
self.class == other.class && __data__ == other.__data__
|
16
|
+
end
|
17
|
+
|
18
|
+
alias_method :eql?, :==
|
19
|
+
|
20
|
+
def hash
|
21
|
+
__data__.hash
|
22
|
+
end
|
23
|
+
|
24
|
+
field_names.each do |name|
|
25
|
+
define_method(name) { __data__[name] }
|
26
|
+
end
|
27
|
+
|
28
|
+
protected
|
29
|
+
|
30
|
+
attr_accessor :__data__
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/mozart/version.rb
CHANGED
data/mozart.gemspec
CHANGED
@@ -4,8 +4,8 @@ require File.expand_path('../lib/mozart/version', __FILE__)
|
|
4
4
|
Gem::Specification.new do |gem|
|
5
5
|
gem.authors = ["Gregory Brown"]
|
6
6
|
gem.email = ["gregory.t.brown@gmail.com"]
|
7
|
-
gem.description = %q{
|
8
|
-
gem.summary = %q{
|
7
|
+
gem.description = %q{Tools for making object composition easier}
|
8
|
+
gem.summary = %q{Tools for making object composition easier}
|
9
9
|
gem.homepage = "https://github.com/elm-city-craftworks/mozart"
|
10
10
|
|
11
11
|
gem.files = `git ls-files`.split($\)
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require "minitest/autorun"
|
2
|
+
|
3
|
+
require_relative "../lib/mozart/composite"
|
4
|
+
require_relative "../lib/mozart/value"
|
5
|
+
|
6
|
+
describe Mozart::Composite do
|
7
|
+
let(:composite) { Mozart::Composite.new }
|
8
|
+
|
9
|
+
let(:parts) do
|
10
|
+
[ Mozart.value(:foo).new(:foo => :returned_by_foo),
|
11
|
+
Mozart.value(:bar).new(:bar => :returned_by_bar) ]
|
12
|
+
end
|
13
|
+
|
14
|
+
it "knows what messages it can receive" do
|
15
|
+
parts.each { |part| composite << part }
|
16
|
+
|
17
|
+
assert composite.receives?(:foo), "Expected composite to receive foo"
|
18
|
+
assert composite.receives?(:bar), "Expected composite to receive bar"
|
19
|
+
refute composite.receives?(:baz), "Did not expect composite to receive baz"
|
20
|
+
end
|
21
|
+
|
22
|
+
it "dispatches messages to its parts" do
|
23
|
+
parts.each { |part| composite << part }
|
24
|
+
|
25
|
+
composite.dispatch(:foo).must_equal(:returned_by_foo)
|
26
|
+
composite.dispatch(:bar).must_equal(:returned_by_bar)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "raises an exception for unhandled messages" do
|
30
|
+
assert_raises(NotImplementedError) do
|
31
|
+
composite.dispatch(:baz)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
it "raises an exception for messages with multiple recipients" do
|
36
|
+
composite << Mozart.value(:foo).new(:foo => true)
|
37
|
+
composite << Mozart.value(:foo).new(:foo => false)
|
38
|
+
|
39
|
+
assert_raises(Mozart::AmbiguousMessageError) do
|
40
|
+
composite.dispatch(:foo)
|
41
|
+
end
|
42
|
+
|
43
|
+
assert_raises(Mozart::AmbiguousMessageError) do
|
44
|
+
composite.receives?(:foo)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require "minitest/autorun"
|
2
|
+
require_relative "../lib/mozart/context"
|
3
|
+
|
4
|
+
describe "Mozart.context" do
|
5
|
+
|
6
|
+
it "must restrict method calls" do
|
7
|
+
stack_builder = Mozart.context(:push, :pop)
|
8
|
+
stack = stack_builder.new([])
|
9
|
+
|
10
|
+
stack.push(2)
|
11
|
+
stack.push(3)
|
12
|
+
|
13
|
+
stack.pop.must_equal 3
|
14
|
+
stack.pop.must_equal 2
|
15
|
+
|
16
|
+
assert_raises(NotImplementedError) do
|
17
|
+
stack[0]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/test/suite.rb
ADDED
data/test/value_test.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require "minitest/autorun"
|
2
|
+
require_relative "../lib/mozart/value"
|
3
|
+
|
4
|
+
describe "Mozart.value" do
|
5
|
+
it "produces Struct-like classes" do
|
6
|
+
pos_builder = Mozart.value(:x, :y)
|
7
|
+
|
8
|
+
pos1 = pos_builder.new(:x => 10, :y => 20)
|
9
|
+
pos1.x.must_equal(10)
|
10
|
+
pos1.y.must_equal(20)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "implements equality" do
|
14
|
+
pos_builder = Mozart.value(:x, :y)
|
15
|
+
|
16
|
+
pos1 = pos_builder.new(:x => 10, :y => 15)
|
17
|
+
pos2 = pos_builder.new(:x => pos1.x, :y => pos1.y)
|
18
|
+
pos3 = pos_builder.new(:x => pos1.x + 1, :y => pos1.y)
|
19
|
+
|
20
|
+
pos1.must_equal(pos2)
|
21
|
+
|
22
|
+
pos1.wont_equal(pos3)
|
23
|
+
pos2.wont_equal(pos3)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "implements hash" do
|
27
|
+
pos_builder = Mozart.value(:x, :y)
|
28
|
+
|
29
|
+
pos1 = pos_builder.new(:x => 10, :y => 15)
|
30
|
+
pos2 = pos_builder.new(:x => pos1.x, :y => pos1.y)
|
31
|
+
pos3 = pos_builder.new(:x => pos1.x + 1, :y => pos1.y)
|
32
|
+
|
33
|
+
data = { pos1 => true }
|
34
|
+
|
35
|
+
assert data[pos1]
|
36
|
+
assert data[pos2]
|
37
|
+
refute data[pos3]
|
38
|
+
end
|
39
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mozart
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,9 +9,9 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-07-
|
12
|
+
date: 2012-07-06 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
|
-
description:
|
14
|
+
description: Tools for making object composition easier
|
15
15
|
email:
|
16
16
|
- gregory.t.brown@gmail.com
|
17
17
|
executables: []
|
@@ -22,10 +22,20 @@ files:
|
|
22
22
|
- Gemfile
|
23
23
|
- LICENSE
|
24
24
|
- README.md
|
25
|
-
-
|
25
|
+
- example.rb
|
26
26
|
- lib/mozart.rb
|
27
|
+
- lib/mozart/composable.rb
|
28
|
+
- lib/mozart/composite.rb
|
29
|
+
- lib/mozart/context.rb
|
30
|
+
- lib/mozart/environment.rb
|
31
|
+
- lib/mozart/single_assignment.rb
|
32
|
+
- lib/mozart/value.rb
|
27
33
|
- lib/mozart/version.rb
|
28
34
|
- mozart.gemspec
|
35
|
+
- test/composite_test.rb
|
36
|
+
- test/context_test.rb
|
37
|
+
- test/suite.rb
|
38
|
+
- test/value_test.rb
|
29
39
|
homepage: https://github.com/elm-city-craftworks/mozart
|
30
40
|
licenses: []
|
31
41
|
post_install_message:
|
@@ -49,5 +59,9 @@ rubyforge_project:
|
|
49
59
|
rubygems_version: 1.8.24
|
50
60
|
signing_key:
|
51
61
|
specification_version: 3
|
52
|
-
summary:
|
53
|
-
test_files:
|
62
|
+
summary: Tools for making object composition easier
|
63
|
+
test_files:
|
64
|
+
- test/composite_test.rb
|
65
|
+
- test/context_test.rb
|
66
|
+
- test/suite.rb
|
67
|
+
- test/value_test.rb
|
data/Rakefile
DELETED