traits 0.9.0 → 0.9.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.
- 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
|