traits 0.9.0 → 0.9.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README +686 -0
- data/README.tmpl +136 -0
- data/gemspec.rb +23 -0
- data/gen_readme.rb +29 -0
- data/install.rb +201 -0
- data/lib/{traits-0.9.0.rb → traits-0.9.1.rb} +36 -10
- data/lib/traits.rb +36 -10
- data/sample/a.rb +22 -0
- data/sample/b.rb +14 -0
- data/sample/c.rb +49 -0
- data/sample/d.rb +30 -0
- data/sample/e.rb +10 -0
- data/sample/f.rb +25 -0
- data/sample/g.rb +16 -0
- data/sample/h.rb +17 -0
- data/sample/i.rb +36 -0
- data/sample/j.rb +23 -0
- data/sample/k.rb +36 -0
- data/sample/l.rb +15 -0
- data/sample/m.rb +24 -0
- data/sample/n.rb +37 -0
- data/sample/p.rb +23 -0
- metadata +25 -3
data/lib/traits.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
$__TRAIT_DEBUG__ = ENV['__TRAIT_DEBUG__'] || ENV['TRAIT_DEBUG'] || ENV['DEBUG']
|
2
|
-
$__TRAIT_VERSION__ = "0.9.
|
2
|
+
$__TRAIT_VERSION__ = "0.9.1"
|
3
3
|
|
4
4
|
class Object
|
5
5
|
#--{{{
|
@@ -670,18 +670,44 @@ end
|
|
670
670
|
|
671
671
|
module TraitInit
|
672
672
|
#--{{{
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
680
|
-
|
673
|
+
module InstaceMethods
|
674
|
+
def trait_init *argv
|
675
|
+
#--{{{
|
676
|
+
args, opts = argv.partition{|arg| not Hash === arg}
|
677
|
+
args.flatten!
|
678
|
+
opts = opts.inject({}){|h,h2| h.update h2}
|
679
|
+
msgs = r_traits
|
680
|
+
args.each{|arg| send msgs.shift, v}
|
681
|
+
opts.each do |k,v|
|
682
|
+
k = "#{ k }"
|
683
|
+
if respond_to? k
|
684
|
+
send k, v
|
685
|
+
else
|
686
|
+
raise ArgumentError, "invalid trait -- #{ self.class }##{ k }"
|
687
|
+
end
|
681
688
|
end
|
689
|
+
#--}}}
|
682
690
|
end
|
691
|
+
alias_method "traitinit", "trait_init"
|
692
|
+
end
|
693
|
+
module ClassMethods
|
694
|
+
def trait_initialize *a, &b
|
695
|
+
traits *a unless a.empty?
|
696
|
+
module_eval{
|
697
|
+
def initialize(*a, &b)
|
698
|
+
super() if defined? super
|
699
|
+
trait_init *a
|
700
|
+
end
|
701
|
+
}
|
702
|
+
end
|
703
|
+
alias_method "traitinitialize", "trait_initialize"
|
704
|
+
end
|
705
|
+
def self.included other
|
706
|
+
#--{{{
|
707
|
+
other.extend ClassMethods
|
708
|
+
other.module_eval{ include InstaceMethods }
|
709
|
+
super
|
683
710
|
#--}}}
|
684
711
|
end
|
685
|
-
alias_method "traitinit", "trait_init"
|
686
712
|
#--}}}
|
687
713
|
end
|
data/sample/a.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'traits'
|
2
|
+
#
|
3
|
+
# defining a trait is like attr_accessor in the simple case
|
4
|
+
#
|
5
|
+
class C
|
6
|
+
trait :t
|
7
|
+
end
|
8
|
+
|
9
|
+
o = C::new
|
10
|
+
o.t = 42
|
11
|
+
p o.t
|
12
|
+
|
13
|
+
#
|
14
|
+
# and can be made even shorter
|
15
|
+
#
|
16
|
+
|
17
|
+
class B; has :x; end
|
18
|
+
|
19
|
+
o = B::new
|
20
|
+
o.x = 42
|
21
|
+
p o.x
|
22
|
+
|
data/sample/b.rb
ADDED
data/sample/c.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'traits'
|
2
|
+
#
|
3
|
+
# a hash argument can be used to specify default values
|
4
|
+
#
|
5
|
+
class C
|
6
|
+
has 'a' => 4, :b => 2
|
7
|
+
end
|
8
|
+
|
9
|
+
o = C::new
|
10
|
+
print o.a, o.b, "\n"
|
11
|
+
|
12
|
+
#
|
13
|
+
# and these traits are smartly inherited
|
14
|
+
#
|
15
|
+
class K < C; end
|
16
|
+
|
17
|
+
o = K::new
|
18
|
+
o.a = 40
|
19
|
+
p( o.a + o.b ) # note that we pick up a default b from C class here since it
|
20
|
+
# has not been set
|
21
|
+
|
22
|
+
o.a = 42
|
23
|
+
o.b = nil
|
24
|
+
p( o.b || o.a ) # but not here since we've explicitly set it to nil
|
25
|
+
|
26
|
+
#
|
27
|
+
# if a block is specifed as the default the initialization of the default value
|
28
|
+
# is deferred until needed which makes for quite natural trait definitions. the
|
29
|
+
# block is passed 'self' so references to the current object can be made. (if
|
30
|
+
# this were not done 'self' in the block would be bound to the class!)
|
31
|
+
#
|
32
|
+
|
33
|
+
class C
|
34
|
+
class << self
|
35
|
+
has('classname'){ name.upcase }
|
36
|
+
end
|
37
|
+
|
38
|
+
has('classname'){ self.class.classname.downcase }
|
39
|
+
end
|
40
|
+
|
41
|
+
class B < C; end
|
42
|
+
|
43
|
+
o = C::new
|
44
|
+
p C::classname
|
45
|
+
p o.classname
|
46
|
+
|
47
|
+
o = B::new
|
48
|
+
p B::classname
|
49
|
+
p o.classname
|
data/sample/d.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'traits'
|
2
|
+
#
|
3
|
+
# all behaviours work within class scope (metal/singleton-class) to define
|
4
|
+
# class methods
|
5
|
+
#
|
6
|
+
class C
|
7
|
+
class << self
|
8
|
+
traits 'a' => 4, 'b' => 2
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
print C::a, C::b, "\n"
|
13
|
+
|
14
|
+
#
|
15
|
+
# singleton methods can even be defined on objects
|
16
|
+
#
|
17
|
+
|
18
|
+
class << (a = %w[dog cat ostrich])
|
19
|
+
has 'category' => 'pets'
|
20
|
+
end
|
21
|
+
p a.category
|
22
|
+
|
23
|
+
#
|
24
|
+
# and modules
|
25
|
+
#
|
26
|
+
module Mmmm
|
27
|
+
class << self; trait 'good' => 'bacon'; end
|
28
|
+
end
|
29
|
+
|
30
|
+
p Mmmm.good
|
data/sample/e.rb
ADDED
data/sample/f.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'traits'
|
2
|
+
#
|
3
|
+
# as traits are defined they are remembered and can be accessed
|
4
|
+
#
|
5
|
+
class C
|
6
|
+
class_trait :first_class_method
|
7
|
+
trait :first_instance_method
|
8
|
+
end
|
9
|
+
|
10
|
+
class C
|
11
|
+
class_trait :second_class_method
|
12
|
+
trait :second_instance_method
|
13
|
+
end
|
14
|
+
|
15
|
+
#
|
16
|
+
# readers and writers are remembered separatedly
|
17
|
+
#
|
18
|
+
p C::class_reader_traits
|
19
|
+
p C::instance_writer_traits
|
20
|
+
|
21
|
+
#
|
22
|
+
# and can be gotten together at class or instance level
|
23
|
+
#
|
24
|
+
p C::class_traits
|
25
|
+
p C::traits
|
data/sample/g.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'traits'
|
2
|
+
#
|
3
|
+
# another neat feature is that they are remembered per hierarchy
|
4
|
+
#
|
5
|
+
class C
|
6
|
+
class_traits :base_class_method
|
7
|
+
trait :base_instance_method
|
8
|
+
end
|
9
|
+
|
10
|
+
class K < C
|
11
|
+
class_traits :derived_class_method
|
12
|
+
trait :derived_instance_method
|
13
|
+
end
|
14
|
+
|
15
|
+
p C::class_traits
|
16
|
+
p K::class_traits
|
data/sample/h.rb
ADDED
data/sample/i.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'traits'
|
2
|
+
#
|
3
|
+
# getters and setters can be defined separately
|
4
|
+
#
|
5
|
+
class C
|
6
|
+
has_r :r
|
7
|
+
end
|
8
|
+
class D
|
9
|
+
has_w :w
|
10
|
+
end
|
11
|
+
|
12
|
+
#
|
13
|
+
# defining a reader trait still defines __public__ query and __private__ writer
|
14
|
+
# methods
|
15
|
+
#
|
16
|
+
class C
|
17
|
+
def using_private_writer_and_query
|
18
|
+
p r?
|
19
|
+
self.r = 42
|
20
|
+
p r
|
21
|
+
end
|
22
|
+
end
|
23
|
+
C::new.using_private_writer_and_query
|
24
|
+
|
25
|
+
#
|
26
|
+
# defining a writer trait still defines __private__ query and __private__ reader
|
27
|
+
# methods
|
28
|
+
#
|
29
|
+
class D
|
30
|
+
def using_private_reader
|
31
|
+
p w?
|
32
|
+
self.w = 'forty-two'
|
33
|
+
p w
|
34
|
+
end
|
35
|
+
end
|
36
|
+
D::new.using_private_reader
|
data/sample/j.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'traits'
|
2
|
+
#
|
3
|
+
# getters delegate to setters iff called with arguments
|
4
|
+
#
|
5
|
+
class AbstractWidget
|
6
|
+
class_trait 'color' => 'pinky-green'
|
7
|
+
class_trait 'size' => 42
|
8
|
+
class_trait 'shape' => 'square'
|
9
|
+
|
10
|
+
# we define instance traits which get their default from the class
|
11
|
+
%w( color size shape ).each{|t| trait(t){self.class.send t}}
|
12
|
+
|
13
|
+
def inspect
|
14
|
+
"color <#{ color }> size <#{ size }> shape <#{ shape }>"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class BlueWidget < AbstractWidget
|
19
|
+
color 'blue'
|
20
|
+
size 420
|
21
|
+
end
|
22
|
+
|
23
|
+
p BlueWidget::new
|
data/sample/k.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'traits'
|
2
|
+
#
|
3
|
+
# the rememberance of traits can make generic intializers pretty slick
|
4
|
+
#
|
5
|
+
class C
|
6
|
+
#
|
7
|
+
# define class traits with defaults
|
8
|
+
#
|
9
|
+
class_traits(
|
10
|
+
'a' => 40,
|
11
|
+
'b' => 1,
|
12
|
+
'c' => 0
|
13
|
+
)
|
14
|
+
|
15
|
+
#
|
16
|
+
# define instance traits whose defaults come from readable class ones
|
17
|
+
#
|
18
|
+
class_rtraits.each{|ct| instance_trait ct => send(ct)}
|
19
|
+
|
20
|
+
#
|
21
|
+
# any option we respond_to? clobbers defaults
|
22
|
+
#
|
23
|
+
def initialize opts = {}
|
24
|
+
opts.each{|k,v| send(k,v) if respond_to? k}
|
25
|
+
end
|
26
|
+
|
27
|
+
#
|
28
|
+
# show anything we can read
|
29
|
+
#
|
30
|
+
def inspect
|
31
|
+
self.class.rtraits.inject(0){|n,t| n += send(t)}
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
c = C::new 'c' => 1
|
36
|
+
p c
|
data/sample/l.rb
ADDED
data/sample/m.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'traits'
|
2
|
+
#
|
3
|
+
# pre and post hooks can be passed a proc or the name of a method, the arity is
|
4
|
+
# detected and the proc/method sent either the value, or the name/value pair
|
5
|
+
#
|
6
|
+
|
7
|
+
class C
|
8
|
+
HOOK_A = lambda{|value| puts "HOOK_A : #{ value }"}
|
9
|
+
HOOK_B = lambda{|name, value| puts "HOOK_B : #{ name } = #{ value }"}
|
10
|
+
|
11
|
+
def hook_a value
|
12
|
+
puts "hook_a : #{ value }"
|
13
|
+
end
|
14
|
+
def hook_b name, value
|
15
|
+
puts "hook_b : #{ name } = #{ value }"
|
16
|
+
end
|
17
|
+
|
18
|
+
trait 'x', 'pre' => HOOK_A, 'post' => 'hook_b'
|
19
|
+
trait 'y', 'pre' => HOOK_B, 'post' => 'hook_a'
|
20
|
+
end
|
21
|
+
|
22
|
+
c = C::new
|
23
|
+
c.x = 42
|
24
|
+
c.y = 'forty-two'
|
data/sample/n.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'traits'
|
2
|
+
#
|
3
|
+
# two kinds of in-place modifications are supported : casting and munging.
|
4
|
+
# casting is a hook that requires either a proc or the name of a method that
|
5
|
+
# will be used to convert the objects type. munging is similar execpt the
|
6
|
+
# method is called on the object itself. like all hooks, lists may be provided
|
7
|
+
# instead of a single argument
|
8
|
+
#
|
9
|
+
# you'll notice that the hooks and methods defined here are not strictly needed,
|
10
|
+
# but are for illustration purposes only. note that all hooks operate in the
|
11
|
+
# context of self - they have access to instance vars, etc., like instance_eval
|
12
|
+
#
|
13
|
+
|
14
|
+
class C
|
15
|
+
INT = lambda{|i| int i}
|
16
|
+
def int i
|
17
|
+
Integer i
|
18
|
+
end
|
19
|
+
trait 'a', 'cast' => 'int'
|
20
|
+
trait 'b', 'cast' => INT
|
21
|
+
trait 'c', 'munge' => 'to_i'
|
22
|
+
trait 'd', 'cast' => 'Integer'
|
23
|
+
trait 'e', 'munge' => %w( to_i abs )
|
24
|
+
end
|
25
|
+
|
26
|
+
c = C::new
|
27
|
+
|
28
|
+
c.a = '42'
|
29
|
+
p c.a
|
30
|
+
c.b = '42'
|
31
|
+
p c.b
|
32
|
+
c.c = '42'
|
33
|
+
p c.c
|
34
|
+
c.d = '42'
|
35
|
+
p c.d
|
36
|
+
c.e = '-42'
|
37
|
+
p c.e
|
data/sample/p.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'traits'
|
2
|
+
#
|
3
|
+
# the TraitInit module provide a simple method for initializing an object's
|
4
|
+
# traits from an options hash
|
5
|
+
#
|
6
|
+
|
7
|
+
class C
|
8
|
+
include TraitInit
|
9
|
+
|
10
|
+
LIST_OF_INTS = lambda{|a| Array === a and a.map{|i| Integer === i}.all?}
|
11
|
+
LIST_OF_STRINGS = lambda{|a| Array === a and a.map{|s| String === s}.all?}
|
12
|
+
|
13
|
+
trait :li, :validate => LIST_OF_INTS
|
14
|
+
trait :ls, :validate => LIST_OF_STRINGS
|
15
|
+
|
16
|
+
def initialize opts = {}
|
17
|
+
trait_init opts
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
c = C::new "li" => [4, 2], "ls" => %w[4 2]
|
22
|
+
p c.li.join
|
23
|
+
p c.ls.join
|