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.
@@ -1,5 +1,5 @@
1
1
  $__TRAIT_DEBUG__ = ENV['__TRAIT_DEBUG__'] || ENV['TRAIT_DEBUG'] || ENV['DEBUG']
2
- $__TRAIT_VERSION__ = "0.9.0"
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
- def trait_init opts = {}
674
- #--{{{
675
- opts.each do |k,v|
676
- k = "#{ k }"
677
- if respond_to? k
678
- send k, v
679
- else
680
- raise ArgumentError, "invalid trait -- #{ self.class }##{ k }"
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
@@ -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
+
@@ -0,0 +1,14 @@
1
+ require 'traits'
2
+ #
3
+ # multiple traits can be defined at once using a list/array of string/sybmol
4
+ # arguments
5
+ #
6
+ class C
7
+ has :t0, :t1
8
+ has %w( t2 t3 )
9
+ end
10
+
11
+ obj = C::new
12
+ obj.t0 = 4
13
+ obj.t3 = 2
14
+ print obj.t0, obj.t3, "\n"
@@ -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
@@ -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
@@ -0,0 +1,10 @@
1
+ require 'traits'
2
+ #
3
+ # shorhands exit to enter 'class << self' in order to define class traits
4
+ #
5
+ class C
6
+ class_trait 'a' => 4
7
+ c_has :b => 2
8
+ end
9
+
10
+ print C::a, C::b, "\n"
@@ -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
@@ -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
@@ -0,0 +1,17 @@
1
+ require 'traits'
2
+ #
3
+ # a depth first search path is used to find defaults
4
+ #
5
+ class C
6
+ has 'a' => 42
7
+ end
8
+ class K < C; end
9
+
10
+ k = K::new
11
+ p k.a
12
+
13
+ #
14
+ # once assigned this is short-circuited
15
+ #
16
+ k.a = 'forty-two'
17
+ p k.a
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,15 @@
1
+ require 'traits'
2
+ #
3
+ # even defining single methods on object behaves
4
+ #
5
+ a = []
6
+
7
+ class << a
8
+ trait 'singleton_class' => class << self;self;end
9
+
10
+ class << self
11
+ class_trait 'x' => 42
12
+ end
13
+ end
14
+
15
+ p a.singleton_class.x
@@ -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'
@@ -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
@@ -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