├── VERSION ├── lib ├── mixers.yml ├── mixers │ ├── registerable.rb │ ├── instantiable.rb │ ├── hook.rb │ ├── expirable.rb │ ├── cloneable.rb │ ├── equitable.rb │ ├── preinitialize.rb │ ├── ostructable.rb │ └── enumargs.rb └── mixers.rb ├── work ├── deprecated │ ├── meta │ │ ├── name │ │ ├── version │ │ ├── suite │ │ ├── authors │ │ ├── sites │ │ │ ├── blog │ │ │ ├── source │ │ │ ├── api │ │ │ ├── development │ │ │ ├── homepage │ │ │ ├── wiki │ │ │ ├── documentation │ │ │ └── mail │ │ ├── summary │ │ ├── contact │ │ ├── repository │ │ └── description │ └── advisable.rb └── consider │ ├── 01_advisable.rdoc │ └── advisable.rb ├── .gitignore ├── spec ├── 08_ostructable.rdoc ├── 09_preinitialize.rdoc ├── 06_expirable.rdoc ├── 04_equitable.rdoc ├── 02_cloneable.rdoc ├── 05_instantiable.rdoc ├── 10_registerable.rdoc └── 03_enumargs.rdoc ├── README.rdoc ├── test ├── test_preinitilizable.rb ├── test_instantiable.rb ├── test_cloneable.rb ├── test_equitable.rb ├── test_advisable.rb └── test_enumargs.rb ├── HISTORY ├── PROFILE ├── MANIFEST ├── Syckfile ├── .ruby └── LICENSE /VERSION: -------------------------------------------------------------------------------- 1 | 1.2.0 2 | -------------------------------------------------------------------------------- /lib/mixers.yml: -------------------------------------------------------------------------------- 1 | ../.ruby -------------------------------------------------------------------------------- /work/deprecated/meta/name: -------------------------------------------------------------------------------- 1 | mixers 2 | -------------------------------------------------------------------------------- /work/deprecated/meta/version: -------------------------------------------------------------------------------- 1 | 1.1.0 2 | -------------------------------------------------------------------------------- /work/deprecated/meta/suite: -------------------------------------------------------------------------------- 1 | rubyworks 2 | -------------------------------------------------------------------------------- /work/deprecated/meta/authors: -------------------------------------------------------------------------------- 1 | Thomas Sawyer 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | log 2 | doc 3 | ri 4 | pkg 5 | tmp 6 | -------------------------------------------------------------------------------- /work/deprecated/meta/sites/blog: -------------------------------------------------------------------------------- 1 | http://rubyworks.github.com/ 2 | -------------------------------------------------------------------------------- /work/deprecated/meta/summary: -------------------------------------------------------------------------------- 1 | Collection of helpful mixin modules 2 | -------------------------------------------------------------------------------- /work/deprecated/meta/contact: -------------------------------------------------------------------------------- 1 | rubyworks-mailinglist@googlegroups.com 2 | -------------------------------------------------------------------------------- /work/deprecated/meta/repository: -------------------------------------------------------------------------------- 1 | git://github.com/rubyworks/mixers.git 2 | -------------------------------------------------------------------------------- /work/deprecated/meta/sites/source: -------------------------------------------------------------------------------- 1 | http://github.com/rubyworks/mixers 2 | -------------------------------------------------------------------------------- /work/deprecated/meta/sites/api: -------------------------------------------------------------------------------- 1 | http://rubyworks.github.com/mixers/rdoc 2 | -------------------------------------------------------------------------------- /work/deprecated/meta/sites/development: -------------------------------------------------------------------------------- 1 | http://github.com/rubyworks/mixers 2 | -------------------------------------------------------------------------------- /work/deprecated/meta/sites/homepage: -------------------------------------------------------------------------------- 1 | http://rubyworks.github.com/mixers 2 | -------------------------------------------------------------------------------- /work/deprecated/meta/sites/wiki: -------------------------------------------------------------------------------- 1 | http://wiki.github.com/rubyworks/mixers 2 | -------------------------------------------------------------------------------- /work/deprecated/meta/sites/documentation: -------------------------------------------------------------------------------- 1 | http://wiki.github.com/rubyworks/mixers 2 | -------------------------------------------------------------------------------- /work/deprecated/meta/sites/mail: -------------------------------------------------------------------------------- 1 | http://groups.google.com/group/rubyworks-mailinglist 2 | -------------------------------------------------------------------------------- /work/deprecated/meta/description: -------------------------------------------------------------------------------- 1 | Ruby Mixers is collection mixin modules for the Ruby 2 | programming language. Mixers is a spin-off of Ruby 3 | Facets. When Ruby Facets scaled back to extensions 4 | only project, it's mixin modules were gathered to 5 | create this new project. 6 | 7 | -------------------------------------------------------------------------------- /spec/08_ostructable.rdoc: -------------------------------------------------------------------------------- 1 | = OpenStructable 2 | 3 | Require the library. 4 | 5 | require 'mixers/ostructable' 6 | 7 | We will use the simpliest example class. 8 | 9 | class X 10 | include OpenStructable 11 | end 12 | 13 | x = X.new 14 | 15 | So arbitrary attributes can be added to the object 16 | just as if it were an OpenStruct. 17 | 18 | x.a = 1 19 | x.b = 2 20 | 21 | x.assert.a == 1 22 | x.assert.a == 2 23 | 24 | -------------------------------------------------------------------------------- /spec/09_preinitialize.rdoc: -------------------------------------------------------------------------------- 1 | = Preinitialize 2 | 3 | Require the library. 4 | 5 | require 'mixers/preinitialize' 6 | 7 | Use example module. 8 | 9 | module M 10 | def preinitialize 11 | @a = 23 12 | end 13 | end 14 | 15 | Define a class to support it. 16 | 17 | class X 18 | include Preinitializable 19 | include M 20 | def a ; @a ; end 21 | end 22 | 23 | The result should be: 24 | 25 | x = X.new 26 | x.a.assert == 23 27 | 28 | 29 | -------------------------------------------------------------------------------- /README.rdoc: -------------------------------------------------------------------------------- 1 | = Ruby Mixers 2 | 3 | == DESCRIPTION 4 | 5 | Ruby Mixers is collection mixin modules for the Ruby 6 | programming language. 7 | 8 | Mixers is a spin-off of Ruby Facets. When Ruby Facets 9 | scaled back to extensions only project, it's mixin 10 | modules were gathered to create this new project. 11 | 12 | == COPYRIGHT 13 | 14 | (Apache 2.0 License) 15 | 16 | Ruby Mixers, Copyright (c) 2010 Thomas Sawyer 17 | 18 | Ruby Mixers is distibuted under the terms of the Apache 2.0 license. 19 | 20 | -------------------------------------------------------------------------------- /lib/mixers/registerable.rb: -------------------------------------------------------------------------------- 1 | # = Registerable 2 | # 3 | module Registerable 4 | 5 | # Register format names. 6 | 7 | def register(obj, *names) 8 | names.each do |name| 9 | registry[name] = obj 10 | end 11 | end 12 | 13 | # Access registry. 14 | 15 | def registry 16 | @@registry ||= {} 17 | end 18 | 19 | # 20 | 21 | def registry_invalid?(*types) 22 | bad = [] 23 | types.each do |type| 24 | bad << type unless @@registry[type] 25 | end 26 | return bad.empty? ? false : bad 27 | end 28 | 29 | end 30 | 31 | -------------------------------------------------------------------------------- /lib/mixers/instantiable.rb: -------------------------------------------------------------------------------- 1 | # = Instantiable 2 | # 3 | # Initialize modules, almost as if they were classes. 4 | # 5 | # Alows a module to be used much like a class, by defining 6 | # a #new method that creates a class on demand. 7 | # 8 | module Instantiable 9 | 10 | def self.append_features(mod) 11 | mod.extend self 12 | end 13 | 14 | # Never use a class agian! ;) 15 | 16 | def new(*args,&blk) 17 | mod = self 18 | @instantiable_class ||= Class.new{include mod} 19 | @instantiable_class.new(*args,&blk) 20 | end 21 | 22 | end 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /test/test_preinitilizable.rb: -------------------------------------------------------------------------------- 1 | require 'mixers/preinitilizable' 2 | require 'test/unit' 3 | 4 | class TC_Preinitalizable < Test::Unit::TestCase 5 | 6 | module M 7 | include Preinitializable 8 | def preinitialize 9 | @a = 10 10 | end 11 | end 12 | 13 | class X 14 | include M 15 | def a; @a ; end 16 | end 17 | 18 | class Y < X 19 | def initialize 20 | super 21 | end 22 | end 23 | 24 | def test_x 25 | x = X.new 26 | assert_equal(10, x.a) 27 | end 28 | 29 | def test_y 30 | y = Y.new 31 | assert_equal(10, y.a) 32 | end 33 | 34 | end 35 | 36 | -------------------------------------------------------------------------------- /test/test_instantiable.rb: -------------------------------------------------------------------------------- 1 | require 'mixers/instantiable' 2 | require 'test/unit' 3 | 4 | class TestInstantiable < Test::Unit::TestCase 5 | 6 | module M 7 | extend Instantiable 8 | 9 | attr :a 10 | 11 | def initialize( a ) 12 | @a = a 13 | end 14 | end 15 | 16 | module N 17 | include Instantiable 18 | 19 | attr :a 20 | 21 | def initialize( a ) 22 | @a = a 23 | end 24 | end 25 | 26 | def test_m_new 27 | m = M.new( 1 ) 28 | assert_equal( 1, m.a ) 29 | end 30 | 31 | def test_n_new 32 | m = N.new( 1 ) 33 | assert_equal( 1, m.a ) 34 | end 35 | 36 | end 37 | 38 | -------------------------------------------------------------------------------- /HISTORY: -------------------------------------------------------------------------------- 1 | = RELEASE HISTORY 2 | 3 | == 1.2.0 / 2010-10-12 4 | 5 | This release deprecates the Advisable mixin, as it was not 6 | operating correctly, and I beleive requires a complete rewrite 7 | to do so --which I am not endeavoring into because it has become 8 | clear there are much better ways to appoach the needs it addressed. 9 | 10 | == 1.1.0 / 2010-05-07 11 | 12 | Thie release include a complete rewrite of the Advisable mixin 13 | and bug fixes to the Registerable mixin. Also QED doc/tests 14 | had been added for almost all mixins. 15 | 16 | == 1.0.0 / 2010-05-02 17 | 18 | Initial release of libraries as take directory from Ruby Facets. 19 | 20 | -------------------------------------------------------------------------------- /spec/06_expirable.rdoc: -------------------------------------------------------------------------------- 1 | = Expirable 2 | 3 | Require the library. 4 | 5 | require 'mixers/expirable' 6 | 7 | Example class. 8 | 9 | class X 10 | extend Expirable 11 | end 12 | 13 | Now the experation can be set to a Time object, or a number of seconds 14 | from +Time.now+. For instance, to set the expiration for one second 15 | from now. 16 | 17 | X.expiration = 1 18 | 19 | We can see the the object has not yet expired. 20 | 21 | X.refute.expired? 22 | 23 | But if we wait one seconds. 24 | 25 | sleep 1 26 | 27 | Then it will have expired. 28 | 29 | X.assert.expired? 30 | 31 | Expiration does not invoke a trigger, it is merely queriable. 32 | 33 | -------------------------------------------------------------------------------- /spec/04_equitable.rdoc: -------------------------------------------------------------------------------- 1 | = Equitqable 2 | 3 | Require the library. 4 | 5 | require 'mixers/equitable' 6 | 7 | We will use this simple class as an example. 8 | 9 | class C 10 | include Equitable(:a,:b) 11 | 12 | attr_accessor :a, :b 13 | 14 | def initialize(a,b) 15 | @a = a 16 | @b = b 17 | end 18 | end 19 | 20 | Now, if two instance of our sample class +C+ have 21 | equal attributes +@a+ and +@b+ then they will 22 | be equal. 23 | 24 | c1 = C.new(10,20) 25 | c2 = C.new(10,20) 26 | c2.assert == c1 27 | 28 | Otherwise they will not be equal. 29 | 30 | c1 = C.new(10, 10) 31 | c2 = C.new(10, 20) 32 | c2.refute == c1 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /spec/02_cloneable.rdoc: -------------------------------------------------------------------------------- 1 | = Cloneable 2 | 3 | Require the library. 4 | 5 | require 'mixers/cloneable' 6 | 7 | We'll use this dummy class. 8 | 9 | class Foo 10 | include Cloneable 11 | def initialize 12 | @bar=[] 13 | end 14 | def bar_id 15 | @bar.object_id 16 | end 17 | end 18 | 19 | Try #dup. 20 | 21 | a = Foo.new 22 | b = a.dup 23 | b.bar_id.refute == a.bar_id 24 | 25 | a.taint 26 | b = a.dup 27 | b.assert.tainted? 28 | 29 | a.freeze 30 | b = a.dup 31 | b.refute.frozen? 32 | 33 | Note try #clone. 34 | 35 | a = Foo.new 36 | b = a.clone 37 | b.bar_id.refute == a.bar_id 38 | 39 | a.taint 40 | b = a.dup 41 | b.assert.tainted? 42 | 43 | a.freeze 44 | b = a.clone 45 | b.assert.frozen? 46 | 47 | -------------------------------------------------------------------------------- /spec/05_instantiable.rdoc: -------------------------------------------------------------------------------- 1 | = Instantiable 2 | 3 | Require the library. 4 | 5 | require 'mixers/instantiable' 6 | 7 | We will use this module as an example. 8 | 9 | module M 10 | include Instantiable 11 | 12 | attr :a 13 | 14 | def initialize(a) 15 | @a = a 16 | end 17 | end 18 | 19 | As we can see M is now fully instantiable, as any 20 | class would be. 21 | 22 | m = M.new(1) 23 | m.a.assert == 1 24 | 25 | We can also use #extend rather than #include and 26 | get the same result. 27 | 28 | module N 29 | extend Instantiable 30 | 31 | attr :a 32 | 33 | def initialize( a ) 34 | @a = a 35 | end 36 | end 37 | 38 | As we saw with +M+ so too with +N+. 39 | 40 | n = N.new( 1 ) 41 | n.a.assert == 1 42 | 43 | -------------------------------------------------------------------------------- /lib/mixers.rb: -------------------------------------------------------------------------------- 1 | module Mixers 2 | # Access to project metadata. 3 | def self.metadata 4 | @metadata ||= ( 5 | require 'yaml' 6 | YAML.load(File.new(File.dirname(__FILE__) + '/mixers.yml')) 7 | ) 8 | end 9 | 10 | # Access to project metadata as constants. 11 | def self.const_missing(name) 12 | metadata[name.to_s.downcase] || super(name) 13 | end 14 | 15 | # TODO: Only here b/c of issue in Ruby 1.8.x. 16 | VERSION = version['version'] 17 | end 18 | 19 | require 'mixers/cloneable.rb' 20 | require 'mixers/enumargs.rb' 21 | require 'mixers/equitable.rb' 22 | require 'mixers/expirable.rb' 23 | require 'mixers/hook.rb' 24 | require 'mixers/instantiable.rb' 25 | require 'mixers/ostructable.rb' 26 | require 'mixers/preinitialize.rb' 27 | require 'mixers/registerable.rb' 28 | -------------------------------------------------------------------------------- /PROFILE: -------------------------------------------------------------------------------- 1 | --- 2 | title : Mixers 3 | summary: Collection of helpful mixin modules 4 | contact: Trans 5 | license: Apache 2.0 6 | company: RubyWorks 7 | created: 2010-05-02 8 | 9 | description: 10 | Ruby Mixers is collection mixin modules for the Ruby 11 | programming language. Mixers is a spin-off of Ruby 12 | Facets. When Ruby Facets scaled back to extensions 13 | only project, it's mixin modules were gathered to 14 | create this new project. 15 | 16 | requires: 17 | - syckle (build) 18 | - qed (test) 19 | 20 | resources: 21 | home: http://rubyworks.github.com/mixers 22 | code: http://github.com/rubyworks/mixers 23 | mail: http://groups.google.com/group/rubyworks-mailinglist 24 | 25 | authors: 26 | - Thomas Sawyer 27 | 28 | copyright: 29 | Copyright (c) 2010 Thomas Sawyer 30 | -------------------------------------------------------------------------------- /MANIFEST: -------------------------------------------------------------------------------- 1 | #!mast -x Syckfile -x PROFILE .ruby bin lib man spec test [A-Z]* 2 | .ruby 3 | lib/mixers/cloneable.rb 4 | lib/mixers/enumargs.rb 5 | lib/mixers/equitable.rb 6 | lib/mixers/expirable.rb 7 | lib/mixers/hook.rb 8 | lib/mixers/instantiable.rb 9 | lib/mixers/ostructable.rb 10 | lib/mixers/preinitialize.rb 11 | lib/mixers/registerable.rb 12 | lib/mixers/version.yml 13 | lib/mixers.rb 14 | spec/02_cloneable.rdoc 15 | spec/03_enumargs.rdoc 16 | spec/04_equitable.rdoc 17 | spec/05_instantiable.rdoc 18 | spec/06_expirable.rdoc 19 | spec/08_ostructable.rdoc 20 | spec/09_preinitialize.rdoc 21 | spec/10_registerable.rdoc 22 | test/test_advisable.rb 23 | test/test_cloneable.rb 24 | test/test_enumargs.rb 25 | test/test_equitable.rb 26 | test/test_instantiable.rb 27 | test/test_preinitilizable.rb 28 | LICENSE 29 | README.rdoc 30 | HISTORY 31 | VERSION 32 | -------------------------------------------------------------------------------- /lib/mixers/hook.rb: -------------------------------------------------------------------------------- 1 | #require 'facets/kernel/instance_exec' 2 | 3 | # TODO: hooks should be an inheritor 4 | # 5 | module Hook 6 | 7 | def self.append_features(base) 8 | base.extend self 9 | end 10 | 11 | def hooks 12 | @hooks ||= Hash.new{ |h,k| h[k] = [] } 13 | end 14 | 15 | def hook(name) 16 | name = name.to_sym 17 | 18 | (class << self; self; end).class_eval %{ 19 | def #{name}(meth=nil, &blk) 20 | hooks[:#{name}] << (meth || blk) 21 | end 22 | } 23 | 24 | module_eval %{ 25 | def #{name}(*args) 26 | self.class.hooks[:#{name}].each do |blk| 27 | if Proc === blk 28 | instance_exec(:#{name}, *args, &blk) 29 | else 30 | __send__(blk, :#{name}, *args) 31 | end 32 | end 33 | end 34 | } 35 | end 36 | 37 | end 38 | 39 | -------------------------------------------------------------------------------- /test/test_cloneable.rb: -------------------------------------------------------------------------------- 1 | require 'mixers/cloneable' 2 | require 'test/unit' 3 | 4 | class Foo 5 | include Cloneable 6 | def initialize 7 | @bar=[] 8 | end 9 | def bar_id 10 | @bar.object_id 11 | end 12 | end 13 | 14 | class TestCloneable < Test::Unit::TestCase 15 | def test_dup 16 | a=Foo.new 17 | b=a.dup 18 | assert_not_equal a.bar_id,b.bar_id 19 | 20 | a.taint 21 | b=a.dup 22 | assert b.tainted?, "b should be tainted" 23 | 24 | a.freeze 25 | b=a.dup 26 | assert !b.frozen?, "b should not be frozen" 27 | end 28 | def test_clone 29 | a=Foo.new 30 | b=a.clone 31 | assert_not_equal a.bar_id,b.bar_id 32 | 33 | a.taint 34 | b=a.dup 35 | assert b.tainted?, "b should be tainted" 36 | 37 | a.freeze 38 | b=a.clone 39 | assert b.frozen?, "b should be frozen" 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/mixers/expirable.rb: -------------------------------------------------------------------------------- 1 | # = Expirable 2 | # 3 | # Generic expirability mixin. 4 | # 5 | module Expirable 6 | 7 | attr_reader :expiration 8 | 9 | # Set the expires timeout for this entry. 10 | def expiration=(time) 11 | case time 12 | when Time, Date, DateTime 13 | @expiration = time 14 | else 15 | @expiration = Time.now + time 16 | end 17 | end 18 | 19 | ## Set the expire timeout for this entry. The timeout happens 20 | ## after (base + rand(spread)) seconds. 21 | ## 22 | ##def expires_spread(base, spread) 23 | ## @expires = Time.now + base + rand(spread) 24 | ##end 25 | 26 | # Is this entry expired? 27 | def expired? 28 | return true if expiration.nil? 29 | return true if (Time.now > expiration) 30 | return false 31 | end 32 | 33 | # Update the expiration period. Override in your application. 34 | def touch! 35 | end 36 | 37 | end 38 | 39 | -------------------------------------------------------------------------------- /spec/10_registerable.rdoc: -------------------------------------------------------------------------------- 1 | = Registerable 2 | 3 | Require the library. 4 | 5 | require 'mixers/registerable' 6 | 7 | Registerable can be used directly. 8 | 9 | module MyStuff 10 | extend Registerable 11 | end 12 | 13 | MyStuff.register(10, :x) 14 | 15 | Then the MyStuff registry will contain 10 indexed by :x. 16 | 17 | MyStuff.registry[:x].assert == 10 18 | 19 | Registrable can also be used as a base class callback to 20 | track subclasses. 21 | 22 | class Abstract 23 | extend Registerable 24 | 25 | def self.inherited(base) 26 | register(base, base.name.split('::').last.downcase) 27 | end 28 | end 29 | 30 | Now when we inherit from Abstract, the subclass will 31 | be automatically registered. 32 | 33 | class Example < Abstract 34 | end 35 | 36 | We see that the class is in the Abstract registry. 37 | 38 | Abstract.registry['example'].assert == Example 39 | 40 | 41 | -------------------------------------------------------------------------------- /test/test_equitable.rb: -------------------------------------------------------------------------------- 1 | require 'mixers/equitable' 2 | require 'test/unit' 3 | 4 | class TestModuleEquatable < Test::Unit::TestCase 5 | 6 | def test_equatable_with_arguments 7 | c = Class.new 8 | c.class_eval { 9 | include Equitable(:a,:b) 10 | attr_accessor :a, :b 11 | } 12 | c1,c2 = c.new,c.new 13 | c1.a = 10; c1.b = 20 14 | c2.a = 10; c2.b = 20 15 | assert_equal( c1, c2 ) 16 | c1.a = 10; c1.b = 10 17 | c2.a = 10; c2.b = 20 18 | assert_not_equal( c1, c2 ) 19 | c1.a = 10; c1.b = 20 20 | c2.a = 20; c2.b = 20 21 | assert_not_equal( c1, c2 ) 22 | end 23 | 24 | =begin 25 | def test_equate_on_old 26 | c = Class.new 27 | c.class_eval { attr_accessor :a, :b ; equate_on :a,:b } 28 | c1,c2 = c.new,c.new 29 | c1.a = 10; c1.b = 20 30 | c2.a = 10; c2.b = 20 31 | assert_equal( c1, c2 ) 32 | c1.a = 10; c1.b = 10 33 | c2.a = 10; c2.b = 20 34 | assert_not_equal( c1, c2 ) 35 | c1.a = 10; c1.b = 20 36 | c2.a = 20; c2.b = 20 37 | assert_not_equal( c1, c2 ) 38 | end 39 | =end 40 | 41 | end 42 | 43 | -------------------------------------------------------------------------------- /test/test_advisable.rb: -------------------------------------------------------------------------------- 1 | require 'mixers/advisable' 2 | require 'test/unit' 3 | 4 | class TestAdvice < Test::Unit::TestCase 5 | 6 | class X 7 | include Advisable 8 | 9 | attr_reader :out 10 | 11 | def initialize 12 | @out = [] 13 | end 14 | 15 | before :x do 16 | @out << "BEFORE X#x" 17 | end 18 | 19 | after :x do 20 | @out << "AFTER X#x" 21 | end 22 | 23 | def x 24 | @out << "X#x" 25 | "x" 26 | end 27 | end 28 | 29 | class Y < X 30 | before :x do 31 | @out << "BEFORE Y#x" 32 | end 33 | 34 | after :x do 35 | @out << "AFTER Y#x" 36 | end 37 | 38 | around :x do |target| 39 | "{" + target.call + "}" 40 | end 41 | 42 | def x 43 | @out << "Y#x" 44 | super 45 | end 46 | end 47 | 48 | # tests 49 | 50 | def setup 51 | @x = X.new 52 | @y = Y.new 53 | end 54 | 55 | def test_x 56 | r = @x.x 57 | o = @x.out 58 | assert_equal("x", r) 59 | assert_equal(["BEFORE X#x", "X#x", "AFTER X#x"], o) 60 | end 61 | 62 | def test_y 63 | r = @y.x 64 | o = @y.out 65 | assert_equal("{x}", r) 66 | assert_equal(["BEFORE Y#x", "BEFORE X#x", "Y#x", "X#x", "AFTER X#x", "AFTER Y#x"], o) 67 | end 68 | 69 | end 70 | -------------------------------------------------------------------------------- /Syckfile: -------------------------------------------------------------------------------- 1 | --- 2 | email: 3 | service : Email 4 | file : ~ 5 | subject : ~ 6 | mailto : ruby-talk@ruby-lang.org 7 | active : true 8 | 9 | grancher: 10 | service: grancher 11 | active: true 12 | 13 | gemcutter: 14 | service: GemCutter 15 | active: true 16 | 17 | box: 18 | service: Box 19 | types : [gem] 20 | active : true 21 | 22 | ridoc: 23 | service: RIDoc 24 | include: ~ 25 | exclude: ~ 26 | output : ri 27 | active : false 28 | 29 | rdoc: 30 | service : RDoc 31 | template: newfish 32 | include : ~ 33 | exclude : [ Syckfile ] 34 | output : doc 35 | active : false 36 | 37 | syntax: 38 | service : Syntax 39 | loadpath : ~ 40 | exclude : ~ 41 | active : false 42 | 43 | testrb: 44 | service : testrb 45 | tests : ~ 46 | exclude : ~ 47 | loadpath : ~ 48 | requires : ~ 49 | live : false 50 | active : false 51 | 52 | dnote: 53 | service : DNote 54 | loadpath : ~ 55 | labels : ~ 56 | exclude : [work] 57 | output : ~ 58 | format : ~ 59 | active : true 60 | 61 | stats: 62 | service : Stats 63 | title : ~ 64 | loadpath : ~ 65 | exclude : ~ 66 | output : ~ 67 | active : true 68 | 69 | vclog: 70 | service : VClog 71 | formats : [json] 72 | #layout : rel # gnu 73 | typed : false 74 | output : ~ 75 | active : false 76 | 77 | -------------------------------------------------------------------------------- /work/consider/01_advisable.rdoc: -------------------------------------------------------------------------------- 1 | = Advisable 2 | 3 | Require the library. 4 | 5 | require 'mixers/advisable' 6 | 7 | We will use this fixture. 8 | 9 | class X 10 | include Advisable 11 | 12 | attr_reader :out 13 | 14 | def initialize 15 | @out = [] 16 | end 17 | 18 | before :x do 19 | @out << "BEFORE X#x" 20 | end 21 | 22 | after :x do 23 | @out << "AFTER X#x" 24 | end 25 | 26 | def x 27 | @out << "X#x" 28 | "x" 29 | end 30 | end 31 | 32 | Our results. 33 | 34 | x = X.new 35 | r = x.x 36 | o = x.out 37 | 38 | r.assert == "x" 39 | o.assert == ["BEFORE X#x", "X#x", "AFTER X#x"] 40 | 41 | And let's see what a subclass of the example does. 42 | 43 | class Y < X 44 | before :x do 45 | @out << "BEFORE Y#x" 46 | end 47 | 48 | after :x do 49 | @out << "AFTER Y#x" 50 | end 51 | 52 | around :x do |target| 53 | "{" + target.call + "}" 54 | end 55 | 56 | def x 57 | @out << "Y#x" 58 | super 59 | end 60 | end 61 | 62 | The result of #x should be a bracketed "x". 63 | 64 | y = Y.new 65 | 66 | y.x.assert == "{x}" 67 | 68 | Then the out variable should indicate where the call has been. 69 | 70 | y.out.assert == ["BEFORE Y#x", "BEFORE X#x", "Y#x", "X#x", "AFTER X#x", "AFTER Y#x"] 71 | 72 | 73 | -------------------------------------------------------------------------------- /test/test_enumargs.rb: -------------------------------------------------------------------------------- 1 | require 'mixers/enumargs' 2 | require 'test/unit' 3 | 4 | # fixture 5 | 6 | class PlusArray 7 | include Enumerable::Arguments 8 | def initialize(arr) 9 | @arr = arr 10 | end 11 | def each(n=0) 12 | @arr.each{ |e| yield(e+n) } 13 | end 14 | end 15 | 16 | class TC_Enumerable < Test::Unit::TestCase 17 | 18 | def test_collect 19 | t = PlusArray.new([1,2,3]) 20 | assert_equal( [5,6,7], t.collect(4){ |e| e } ) 21 | end 22 | 23 | #def test_each_slice 24 | # t = PlusArray.new([1,2,3,4]) 25 | # a = [] 26 | # t.each_slice(2,4){ |e,f| a << [e,f] } 27 | # assert_equal( [[5,6],[7,8]], a ) 28 | #end 29 | 30 | #def test_find 31 | # t = PlusArray.new([1,2,3,4]) 32 | # f = t.find(2, :ifnone=>lambda{:NOPE}) { |a| a == 10 } 33 | # assert_equal(:NOPE, f) 34 | #end 35 | 36 | def test_grep 37 | # TODO 38 | end 39 | 40 | def test_to_a 41 | t = PlusArray.new([1,2,3]) 42 | assert_equal( [5,6,7], t.to_a(4) ) 43 | end 44 | 45 | def test_min 46 | t = PlusArray.new([1,2,3]) 47 | assert_equal( 5, t.min(4) ) 48 | end 49 | 50 | def test_max 51 | t = PlusArray.new([1,2,3]) 52 | assert_equal( 7, t.max(4) ) 53 | end 54 | 55 | def test_include? 56 | t = PlusArray.new([1,2,3]) 57 | assert( t.include?(7,4) ) 58 | end 59 | 60 | def test_select 61 | t = PlusArray.new([1,2,3]) 62 | assert_equal( [6], t.select(4){ |x| x == 6 } ) 63 | end 64 | 65 | def test_reject 66 | t = PlusArray.new([1,2,3]) 67 | assert_equal( [5,7], t.reject(4){ |x| x == 6 } ) 68 | end 69 | 70 | end 71 | -------------------------------------------------------------------------------- /.ruby: -------------------------------------------------------------------------------- 1 | --- 2 | name: mixers 3 | company: RubyWorks 4 | title: Mixers 5 | contact: Trans 6 | requires: 7 | - group: 8 | - build 9 | name: syckle 10 | version: 0+ 11 | - group: 12 | - test 13 | name: qed 14 | version: 0+ 15 | resources: 16 | code: http://github.com/rubyworks/mixers 17 | mail: http://groups.google.com/group/rubyworks-mailinglist 18 | home: http://rubyworks.github.com/mixers 19 | pom_verison: 1.0.0 20 | manifest: 21 | - .ruby 22 | - lib/mixers/cloneable.rb 23 | - lib/mixers/enumargs.rb 24 | - lib/mixers/equitable.rb 25 | - lib/mixers/expirable.rb 26 | - lib/mixers/hook.rb 27 | - lib/mixers/instantiable.rb 28 | - lib/mixers/ostructable.rb 29 | - lib/mixers/preinitialize.rb 30 | - lib/mixers/registerable.rb 31 | - lib/mixers/version.yml 32 | - lib/mixers.rb 33 | - spec/02_cloneable.rdoc 34 | - spec/03_enumargs.rdoc 35 | - spec/04_equitable.rdoc 36 | - spec/05_instantiable.rdoc 37 | - spec/06_expirable.rdoc 38 | - spec/08_ostructable.rdoc 39 | - spec/09_preinitialize.rdoc 40 | - spec/10_registerable.rdoc 41 | - test/test_advisable.rb 42 | - test/test_cloneable.rb 43 | - test/test_enumargs.rb 44 | - test/test_equitable.rb 45 | - test/test_instantiable.rb 46 | - test/test_preinitilizable.rb 47 | - LICENSE 48 | - README.rdoc 49 | - HISTORY 50 | - VERSION 51 | version: 1.2.0 52 | copyright: Copyright (c) 2010 Thomas Sawyer 53 | licenses: 54 | - Apache 2.0 55 | description: Ruby Mixers is collection mixin modules for the Ruby programming language. Mixers is a spin-off of Ruby Facets. When Ruby Facets scaled back to extensions only project, it's mixin modules were gathered to create this new project. 56 | summary: Collection of helpful mixin modules 57 | authors: 58 | - Thomas Sawyer 59 | created: 2010-05-02 60 | -------------------------------------------------------------------------------- /lib/mixers/cloneable.rb: -------------------------------------------------------------------------------- 1 | # = Clonable 2 | # 3 | # Standard basis for adding deep #dup and #clone to a class. 4 | # Provides a class with deep cloneablity via the standard 5 | # #dup and #clone methods. 6 | # 7 | # == Credit 8 | # 9 | # Cloneable was originally ported from Jim Weirich's Rake. 10 | # The current version is the work of Ken Bloom. 11 | # 12 | #-- 13 | # TODO: Is there a more robust means of determining if clone or dup is used? 14 | #++ 15 | 16 | module Cloneable 17 | 18 | def initialize_copy(sibling) 19 | #first duplicate my superclass' state. Note that if it's duplicating 20 | #instance variables, this will be overwritten, but this is important 21 | #because we could be dealing with a C extension with state hidden from 22 | #the Ruby interpreter 23 | super 24 | 25 | #we want to know if we're being dup'ed or clone'd, because we want to 26 | #preserve the state of our internals the same way our state is being 27 | #preserved. (If we can't figure it out, we'll just use #dup.) 28 | operation = caller.find{|x| x !~ /'initialize_copy'/}.match(/`(dup|clone)'/)[1] or :dup 29 | 30 | sibling.instance_variables.each do |ivar| 31 | value = sibling.instance_variable_get(ivar) 32 | 33 | #set my instance variable to be a #dup or #clone 34 | #or my sibling, depending on what's happening to me right now 35 | instance_variable_set(ivar, value.send(operation)) 36 | end 37 | end 38 | 39 | end 40 | 41 | # OLD VERSION 42 | 43 | #module Cloneable 44 | # def clone 45 | # sibling = self.class.new 46 | # instance_variables.each do |ivar| 47 | # value = self.instance_variable_get(ivar) 48 | # sibling.instance_variable_set(ivar, value.dup) #rake_dup) 49 | # end 50 | # sibling 51 | # end 52 | # alias_method :dup, :clone 53 | #end 54 | 55 | -------------------------------------------------------------------------------- /spec/03_enumargs.rdoc: -------------------------------------------------------------------------------- 1 | = Enumerable::Arguments 2 | 3 | Require the library. 4 | 5 | require 'mixers/enumargs' 6 | 7 | This will serve as our example class. 8 | 9 | class PlusArray 10 | include Enumerable::Arguments 11 | 12 | def initialize(arr) 13 | @arr = arr 14 | end 15 | 16 | def each(n=0) 17 | @arr.each{ |e| yield(e+n) } 18 | end 19 | end 20 | 21 | Now Enumerable methods such as #map and #collect 22 | take an argument as well. 23 | 24 | t = PlusArray.new([1,2,3]) 25 | t.collect(4){ |e| e }.assert == [5,6,7] 26 | 27 | Filtering methods such as #select and reject work 28 | as well. 29 | 30 | t = PlusArray.new([1,2,3]) 31 | t.select(4){ |x| x == 6 }.assert == [6] 32 | 33 | t = PlusArray.new([1,2,3]) 34 | t.reject(4){ |x| x == 6 }.assert == [5,7] 35 | 36 | We can covert to an array using #to_a with an argument. 37 | 38 | t = PlusArray.new([1,2,3]) 39 | t.to_a(4).assert == [5,6,7] 40 | 41 | We can get the minimum value using #min. 42 | 43 | t = PlusArray.new([1,2,3]) 44 | t.min(4).assert == 5 45 | 46 | And get the maximum value using #min. 47 | 48 | t = PlusArray.new([1,2,3]) 49 | t.max(4).assert == 7 50 | 51 | Methods that already take and argument, now take two 52 | such as #include? 53 | 54 | t = PlusArray.new([1,2,3]) 55 | t.assert.include?(7,4) 56 | 57 | And #each_slice. 58 | 59 | t = PlusArray.new([1,2,3,4]) 60 | a = [] 61 | t.each_slice(2,4){ |e,f| a << [e,f] } 62 | a.assert == [[5,6],[7,8]] 63 | 64 | The #find method has a slightly different interface 65 | than the original Enumerable. 66 | 67 | t = PlusArray.new([1,2,3,4]) 68 | f = t.find(2, :ifnone=>lambda{:NOPE}) { |a| a == 10 } 69 | f.assert == :NOPE 70 | 71 | The #grep method should also work. 72 | 73 | t = PlusArray.new(['a1','b2','a3','b4']) 74 | t.grep(/^b/, 'x').assert == ['b2x','b4x'] 75 | 76 | -------------------------------------------------------------------------------- /lib/mixers/equitable.rb: -------------------------------------------------------------------------------- 1 | # = Equitable 2 | # 3 | # This mixin provides methods of equality based 4 | # on a single #identity method which must return 5 | # a list of accessors used as the identity keys. 6 | # 7 | # It also provides a "shortcut" for creating the 8 | # #identity method based on given accessors and returns 9 | # the Equitable module for inclusion. 10 | # 11 | # include Equitable(:a, :b) 12 | # 13 | # is equivalent to including a module containing: 14 | # 15 | # def ==(other) 16 | # self.a == other.a && self.b == other.b 17 | # end 18 | # 19 | # def eql?(other) 20 | # self.a.eql?(other.a) && self.b.eql?(other.b) 21 | # end 22 | # 23 | # def hash() 24 | # self.a.hash ^ self.b.hash 25 | # end 26 | # 27 | module Equitable 28 | 29 | def self.identify(base, *accessors) 30 | base.send(:define_method, :identity){ accessors } 31 | self 32 | end 33 | 34 | def ==(o) 35 | identity.all?{ |a| send(a) == o.send(a) } 36 | end 37 | 38 | def eql?(o) 39 | identity.all?{ |a| send(a).eql?(o.send(a)) } 40 | end 41 | 42 | def hash 43 | identity.inject(0){ |memo, a| memo ^ send(a).hash } 44 | end 45 | 46 | end 47 | 48 | class Module 49 | 50 | # This function provided a "shortcut" for creating the 51 | # #identity method based on given accessors and returns 52 | # the Equitable module for inclusion. 53 | # 54 | # include Equitable(:a, :b) 55 | # 56 | # is equivalent to including a module containing: 57 | # 58 | # def ==(other) 59 | # self.a == other.a && self.b == other.b 60 | # end 61 | # 62 | # def eql?(other) 63 | # self.a.eql?(other.a) && self.b.eql?(other.b) 64 | # end 65 | # 66 | # def hash() 67 | # self.a.hash ^ self.b.hash 68 | # end 69 | # 70 | 71 | def Equitable(*accessors) 72 | Equitable.identify(self, *accessors) 73 | end 74 | 75 | end 76 | 77 | -------------------------------------------------------------------------------- /work/consider/advisable.rb: -------------------------------------------------------------------------------- 1 | # = Advisable 2 | # 3 | # Advisable provides a means of using before, after and 4 | # around adivce in dynamic fashion. 5 | # 6 | module Advisable 7 | 8 | require 'facets/proc/bind' 9 | 10 | def self.append_features(base) 11 | base.extend(self) 12 | end 13 | 14 | def before(meth, &block) 15 | advice_before[meth.to_sym] << block 16 | method_added(meth) if method_defined?(meth) 17 | end 18 | 19 | def after(meth, &block) 20 | advice_after[meth.to_sym] << block 21 | method_added(meth) if method_defined?(meth) 22 | end 23 | 24 | def around(meth, &block) 25 | advice_around[meth.to_sym] << block 26 | method_added(meth) if method_defined?(meth) 27 | end 28 | 29 | def advice_before 30 | @advice_before ||= Hash.new{|h,k| h[k] = []} 31 | end 32 | 33 | def advice_after 34 | @advice_after ||= Hash.new{|h,k| h[k] = []} 35 | end 36 | 37 | def advice_around 38 | @advice_around ||= Hash.new{|h,k| h[k] = []} 39 | end 40 | 41 | # TODO: Should around advice be fed the result of the last around advice? 42 | def method_added(meth) 43 | @method_added_stack ||= [] 44 | return if @method_added_stack.last == meth 45 | @method_added_stack << meth 46 | 47 | if advice_before.key?(meth) or advice_after.key?(meth) 48 | 49 | before = advice_before[meth] || [] 50 | around = advice_around[meth] || [] 51 | after = advice_after[meth] || [] 52 | 53 | alias_method "#{meth}:advised", meth 54 | target = instance_method("#{meth}:advised") 55 | 56 | define_method meth do |*a, &b| 57 | before.reverse_each do |block| 58 | block.bind(self).call(*a,&b) 59 | end 60 | if around.empty? 61 | result = target.bind(self).call(*a,&b) 62 | else 63 | around.each do |block| 64 | result = block.call(target.bind(self),*a,&b) 65 | end 66 | end 67 | after.each do |block| 68 | block.bind(self).call(*a,&b) 69 | end 70 | result 71 | end 72 | end 73 | 74 | super(meth) 75 | 76 | @method_added_stack.pop 77 | end 78 | 79 | end 80 | 81 | -------------------------------------------------------------------------------- /lib/mixers/preinitialize.rb: -------------------------------------------------------------------------------- 1 | # = Preinitialize 2 | # 3 | # This is an object preinitialize system, which provides 4 | # an elegant way to initialize an object allowing the 5 | # class to provide additional default structure to an 6 | # object prior to calling initialize. 7 | # 8 | # In effect it does two things after allocating the object 9 | # but prior to initializing it. 10 | # 11 | # First, it calls the class method #default_instance_variables, 12 | # which returns a hash and by default returns the hash 13 | # stored in @default_instance_variables. It uses this to 14 | # pre-define instance variables. 15 | # 16 | # Then it goes to the top of the class hierarchy and works 17 | # it's way down calling #preinitialize if defined. 18 | # WARNING! It is rather useless to use super 19 | # inside the any preinitialize hook. 20 | # 21 | # module M 22 | # def preinitialize 23 | # @a = 23 24 | # end 25 | # end 26 | # 27 | # class X 28 | # include Preinitializable 29 | # include M 30 | # def a ; @a ; end 31 | # end 32 | # 33 | # x = X.new 34 | # x.a #=> 23 35 | # 36 | # If neded the original new method has been aliased, albeit 37 | # postinitialize_new is probably a bit of a misnomer. 38 | 39 | #-- 40 | # class Module 41 | # 42 | # def default_instance_variables(complete=false) 43 | # @default_instance_variables ||= {} 44 | # unless complete 45 | # return @default_instance_variables 46 | # else 47 | # parent = ancestors[1] 48 | # if parent 49 | # return @default_instance_variables.merge(parent.default_instance_variables) 50 | # else 51 | # return @default_instance_variables 52 | # end 53 | # end 54 | # end 55 | # 56 | # end 57 | #++ 58 | 59 | module Preinitializable 60 | 61 | def self.included(base) 62 | if Class===base 63 | (class << base; self; end).__send__(:alias_method, :post_new, :new) 64 | base.extend Meta 65 | else 66 | (class << base; self; end).__send__(:define_method, :included, &method(:included)) 67 | end 68 | end 69 | 70 | module Meta 71 | 72 | def new(*args, &blk) 73 | o = allocate 74 | #if respond_to?(:default_instance_variables) 75 | # default_instance_variables.each{|k,v| o.instance_variable_set( "@#{k.to_s.gsub(/\W$/,'')}",v )} 76 | #end 77 | a = ancestors 78 | until a.empty? 79 | m = a.pop 80 | if m.private_instance_methods(false).include?('preinitialize') or 81 | m.protected_instance_methods(false).include?('preinitialize') or 82 | m.public_instance_methods(false).include?('preinitialize') 83 | im = instance_method('preinitialize') 84 | im.arity == 0 ? im.bind(o).call : im.bind(o).call(*args, &blk) 85 | end 86 | end 87 | o.__send__(:initialize, *args, &blk) if o.class.private_method_defined?(:initialize) 88 | o 89 | end 90 | 91 | end 92 | 93 | end 94 | 95 | -------------------------------------------------------------------------------- /work/deprecated/advisable.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2007 Thomas Sawyer 2 | 3 | require 'facets/kernel/method' 4 | require 'facets/module/instance_method' 5 | require 'facets/unboundmethod/arguments' 6 | 7 | # = Advisable 8 | # 9 | # Advisable provides a means of using before, after and 10 | # around adivce in dynamic fashion. 11 | # 12 | module Advisable 13 | 14 | def self.append_features(base) 15 | base.extend self 16 | end 17 | 18 | def advice_before 19 | @advice_before ||= {} #Hash.new{|h,k| h[k] = []} 20 | end 21 | 22 | def advice_after 23 | @advice_after ||= {} #Hash.new{|h,k| h[k] = []} 24 | end 25 | 26 | def advice_around 27 | @advice_around ||= {} #Hash.new{|h,k| h[k] = []} 28 | end 29 | 30 | def before(meth, &block) 31 | name = "#{meth}:before:#{block.object_id}" 32 | define_method(name, &block) 33 | (advice_before[meth.to_sym] ||= []) << name 34 | end 35 | 36 | def after(meth, &block) 37 | name = "#{meth}:after:#{block.object_id}" 38 | define_method(name, &block) 39 | (advice_after[meth.to_sym] ||= []) << name 40 | end 41 | 42 | def around(meth, &block) 43 | name = "#{meth}:around:#{block.object_id}" 44 | define_method(name, &block) 45 | (advice_around[meth.to_sym] ||= []) << name 46 | end 47 | 48 | # Advise a method. 49 | 50 | def advise(meth) 51 | #return false if defined?(meth_origin) 52 | args = instance_method(meth).arguments 53 | 54 | module_eval(<<-END, __FILE__, __LINE__) 55 | alias_method '#{meth}_origin', '#{meth}' 56 | def #{meth}(#{args}) 57 | target = method!('#{meth}_origin') 58 | 59 | unless target.advised 60 | ancs = self.class.ancestors.select{ |anc| anc.respond_to?(:advice_before) } 61 | target.advice_before = ancs.collect{ |anc| anc.advice_before[:'#{meth}'] } 62 | target.advice_after = ancs.collect{ |anc| anc.advice_after[:'#{meth}'] } 63 | target.advice_around = ancs.collect{ |anc| anc.advice_around[:'#{meth}'] } 64 | target.advised = true 65 | end 66 | 67 | target.call_with_advice(self, *[#{args}]) 68 | end 69 | END 70 | end 71 | 72 | #advise(:method_added) 73 | 74 | #after :method_added do |meth| 75 | # advise(meth) unless defined?("#{meth}_orig") 76 | #end 77 | 78 | def method_added(meth) 79 | return if meth == :method_added 80 | @added_stack ||= [] 81 | return if @added_stack.last == meth 82 | return if /_(origin)$/ =~ meth.to_s 83 | return if /:(before|after|around)/ =~ meth.to_s 84 | @added_stack << meth 85 | #return if instance_methods(false).include?("#{meth}_orig") 86 | advise(meth) 87 | @added_stack.pop 88 | end 89 | 90 | # Extensions for Method class. 91 | 92 | module Method 93 | 94 | attr_accessor :advised 95 | 96 | attr_reader :advice_before 97 | attr_reader :advice_after 98 | attr_reader :advice_around 99 | 100 | def advised? 101 | @advised 102 | end 103 | 104 | def advice_before=(set); @advice_before = set.flatten.compact; end 105 | def advice_after=(set) ; @advice_after = set.flatten.compact; end 106 | def advice_around=(set); @advice_around = set.flatten.compact; end 107 | 108 | # Call with advice. 109 | 110 | def call_with_advice(obj, *args, &blk) 111 | advice_before.each do |name| 112 | #advice.call(*args, &blk) 113 | obj.send(name, *args, &blk) 114 | end 115 | 116 | target = lambda{ call(*args, &blk) } 117 | advice_around.each do |name| 118 | target = lambda_target(obj, name, target, *args, &blk) 119 | end 120 | ret = target.call 121 | 122 | advice_after.reverse_each do |name| 123 | #advice.call(*args, &blk) 124 | obj.send(name, *args, &blk) 125 | end 126 | 127 | return ret 128 | end 129 | 130 | private 131 | 132 | # Using separate method for this prevents infinite loop. 133 | 134 | def lambda_target(obj, name, target, *args, &blk) 135 | lambda do 136 | #advice.call(target, *args, &blk) 137 | obj.send(name, target, *args, &blk) 138 | end 139 | end 140 | 141 | end 142 | 143 | end 144 | 145 | class Method #:nodoc: 146 | include Advisable::Method 147 | end 148 | 149 | -------------------------------------------------------------------------------- /lib/mixers/ostructable.rb: -------------------------------------------------------------------------------- 1 | # = Ostructable 2 | # 3 | # OpensStructable is a mixin module which can provide OpenStruct behavior to 4 | # any class or object. OpenStructable allows extention of data objects 5 | # with arbitrary attributes. 6 | # 7 | # == Usage 8 | # 9 | # require 'ostructable' 10 | # 11 | # class Record 12 | # include OpenStructable 13 | # end 14 | # 15 | # record = Record.new 16 | # record.name = "John Smith" 17 | # record.age = 70 18 | # record.pension = 300 19 | # 20 | # puts record.name # -> "John Smith" 21 | # puts record.address # -> nil 22 | # 23 | #-- 24 | # TODO: Keep this uptodate with ostruct.rb 25 | # 26 | # TODO: As with OpenStruct, marshalling is problematic at the moment. 27 | #++ 28 | 29 | module OpenStructable 30 | 31 | def initialize(hash=nil) 32 | @__table__ = {} 33 | if hash 34 | for k,v in hash 35 | @__table__[k.to_sym] = v 36 | new_ostruct_member(k) 37 | end 38 | end 39 | end 40 | 41 | # duplicate an OpenStruct object members. 42 | def initialize_copy(orig) 43 | super 44 | @__table__ = @__table__.dup 45 | end 46 | 47 | def marshal_dump 48 | @table 49 | end 50 | def marshal_load(x) 51 | @table = x 52 | @table.each_key{|key| new_ostruct_member(key)} 53 | end 54 | 55 | def new_ostruct_member(name) 56 | unless self.respond_to?(name) 57 | self.instance_eval %{ 58 | def #{name}; @__table__[:#{name}]; end 59 | def #{name}=(x); @__table__[:#{name}] = x; end 60 | } 61 | end 62 | end 63 | 64 | # 65 | # Generate additional attributes and values. 66 | # 67 | def update(hash) 68 | @__table__ ||= {} 69 | if hash 70 | for k,v in hash 71 | @__table__[k.to_sym] = v 72 | new_ostruct_member(k) 73 | end 74 | end 75 | end 76 | 77 | def method_missing(mid, *args) # :nodoc: 78 | mname = mid.to_s 79 | len = args.length 80 | if mname =~ /=$/ 81 | if len != 1 82 | raise ArgumentError, "wrong number of arguments (#{len} for 1)", caller(1) 83 | end 84 | if self.frozen? 85 | raise TypeError, "can't modify frozen #{self.class}", caller(1) 86 | end 87 | mname.chop! 88 | @__table__ ||= {} 89 | @__table__[mname.intern] = args[0] 90 | self.new_ostruct_member(mname) 91 | elsif len == 0 92 | @__table__ ||= {} 93 | @__table__[mid] 94 | else 95 | raise NoMethodError, "undefined method `#{mname}' for #{self}", caller(1) 96 | end 97 | end 98 | 99 | # 100 | # Remove the named field from the object. 101 | # 102 | def delete_field(name) 103 | @__table__ ||= {} 104 | @__table__.delete name.to_sym 105 | end 106 | 107 | # 108 | # Returns a string containing a detailed summary of the keys and values. 109 | # 110 | def inspect 111 | str = "<#{self.class}" 112 | for k,v in (@__table__ ||= {}) 113 | str << " #{k}=#{v.inspect}" 114 | end 115 | str << ">" 116 | end 117 | 118 | def __table__ # :nodoc: 119 | @__table__ ||= {} 120 | end 121 | protected :__table__ 122 | 123 | # Compare this object and +other+ for equality. 124 | def ==(other) 125 | return false unless(other.kind_of?(OpenStruct)) 126 | return @__table__ == other.table 127 | end 128 | 129 | end 130 | 131 | =begin 132 | # 133 | # It is possibe to implement OpenStruct itself with 134 | # this OpenStructable module as follows: 135 | # 136 | class OpenStruct 137 | include OpenStructable 138 | end 139 | =end 140 | 141 | 142 | 143 | # _____ _ 144 | # |_ _|__ ___| |_ 145 | # | |/ _ \/ __| __| 146 | # | | __/\__ \ |_ 147 | # |_|\___||___/\__| 148 | # 149 | 150 | =begin testing 151 | 152 | require 'test/unit' 153 | 154 | # fixture 155 | 156 | class Record 157 | include OpenStructable 158 | end 159 | 160 | # test 161 | 162 | class TC_OpenStructable < Test::Unit::TestCase 163 | 164 | def test_record 165 | record = nil 166 | assert_nothing_raised { 167 | record = Record.new 168 | record.name = "John Smith" 169 | record.age = 70 170 | record.pension = 300 171 | } 172 | assert_equal( "John Smith", record.name ) 173 | assert_equal( 70, record.age ) 174 | assert_equal( nil, record.address ) 175 | end 176 | 177 | end 178 | 179 | =end 180 | -------------------------------------------------------------------------------- /lib/mixers/enumargs.rb: -------------------------------------------------------------------------------- 1 | # Enumerable::Arguments 2 | # 3 | # Copyright (c) 2004 Thomas Sawyer 4 | # 5 | # LGPL(3) License 6 | # 7 | # This module is free software. You may use, modify, and/or redistribute this 8 | # software under the same terms as Ruby. 9 | # 10 | # This program is distributed in the hope that it will be useful, but WITHOUT 11 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | # FOR A PARTICULAR PURPOSE. 13 | 14 | begin 15 | require 'enumerator' 16 | rescue LoadError 17 | end 18 | 19 | # This is a simple reimplementation of the core Enumerable module 20 | # to allow the methods to take and pass-on arbitrary arguments to the 21 | # underlying #each call. This library uses Enumerator and scans 22 | # Enumerable so it can alwasy stay in sync. 23 | # 24 | # NOTE Any Enumerable method with a negative arity cannot do pass arguments 25 | # due to ambiguity in the argument count. So the methods #inject and #zip 26 | # do NOT work this way, but simply work as they do in Enumerable. 27 | # The method #find (and #detect) though has been made to work by removing 28 | # its rarely used optional parameter and providing instead an optional 29 | # keyword parameter (:ifnone => ...). Please keep these difference in mind. 30 | # 31 | # require 'enumargs' 32 | # 33 | # class T 34 | # include Enumerable::Arguments 35 | # def initialize(arr) 36 | # @arr = arr 37 | # end 38 | # def each(n) 39 | # arr.each{ |e| yield(e+n) } 40 | # end 41 | # end 42 | # 43 | # t = T.new([1,2,3]) 44 | # t.collect(4) 45 | # #=> [5,6,7] 46 | # 47 | module Enumerable 48 | module Arguments 49 | 50 | def self.wrap_enumerable_method( methodname ) 51 | 52 | m = methodname 53 | meth = Enumerable.instance_method(m) 54 | arity = meth.arity 55 | 56 | case arity <=> 0 57 | when 0 58 | class_eval %{ 59 | def #{m}( *args, &yld ) 60 | enum_for(:each, *args).#{m}( &yld ) 61 | end 62 | } 63 | when 1 64 | class_eval %{ 65 | def #{m}( *args, &yld ) 66 | args, each_args = args[0...#{arity}], args[#{arity}..-1] 67 | enum_for(:each, *each_args).#{m}( *args, &yld ) 68 | end 69 | } 70 | else 71 | class_eval %{ 72 | def #{m}( *args, &yld ) 73 | enum_for(:each).#{m}( *args, &yld ) 74 | end 75 | } 76 | end 77 | end 78 | 79 | Enumerable.instance_methods(false).each do |m| 80 | wrap_enumerable_method( m ) 81 | end 82 | 83 | # 84 | def to_a(*args) 85 | map(*args){ |x| x } 86 | end 87 | 88 | # Make exception for #find (a negative arity method) to accept 89 | # keyword argument. 90 | # 91 | # ObjectSpace.find(Class, :ifnone=>lambda{1}) { |e| ... } 92 | # ObjectSpace.find(Class, :ifnone=>lambda{1}) { |e| ... } 93 | # 94 | def find(*args, &yld) # future use **keys ? 95 | if Hash === args.last and args.last.key?(:ifnone) 96 | ifnone = args.last.delete(:ifnone) 97 | args.pop if args.last.empty? 98 | enum_for(:each, *args).find( ifnone, &yld ) 99 | else 100 | enum_for(:each, *args).find( &yld ) 101 | end 102 | end 103 | alias_method :detect, :find 104 | 105 | end 106 | end 107 | 108 | 109 | =begin OLD CODE 110 | module EnumerableArgs 111 | 112 | def collect(*args) # :yield: 113 | a = [] 114 | each(*args){ |n| a << yield(n) } 115 | a 116 | end 117 | alias_method( :map, :collect ) 118 | 119 | def detect(*args) # :yield: 120 | each(*args){ |n| return n if yield(n) } 121 | nil 122 | end 123 | alias_method( :find, :detect ) 124 | 125 | def each_with_index(*args) 126 | i=0 127 | each(*args){ |*n| n << i; yield(*n); i+=1 } 128 | self 129 | end 130 | 131 | def to_a(*args) 132 | a = [] 133 | each(*args){ |n| a << n } 134 | a 135 | end 136 | alias_method( :entries, :to_a ) 137 | 138 | # An additional method not part of standard Enumerable. 139 | # The regular version of this method can be found in Facets, 140 | # but it is a bit more advanced then this one. 141 | # At some point they need to be put into sync. 142 | def each_slice(*args, &yld) 143 | a = []; s = [] 144 | ar = yld.arity.abs 145 | each(*args){ |n| 146 | s << n 147 | if s.length >= ar 148 | yld.call(*s) 149 | s = [] 150 | end 151 | } 152 | a 153 | end 154 | alias_method( :each_by, :each_slice ) 155 | 156 | def select(*args) # :yield: 157 | a = [] 158 | each(*args){ |n| a << n if yield(n) } 159 | a 160 | end 161 | alias_method( :find_all, :select ) 162 | 163 | def grep(pattern, *args) 164 | a = [] 165 | each(*args){ |n| a << (block_given? ? yield(n) : n) if pattern === n } 166 | a 167 | end 168 | 169 | def include?(anObj, *args) 170 | each(*args){ |n| return true if anObj == n } 171 | false 172 | end 173 | alias_method( :member?, :include? ) 174 | 175 | def max(*args) 176 | to_a(*args).max 177 | end 178 | 179 | def min(*args) 180 | to_a(*args).min 181 | end 182 | 183 | def reject(*args) 184 | a = [] 185 | each(*args){ |n| a << n if ! yield(n) } 186 | a 187 | end 188 | 189 | def sort(*args) 190 | # TODO 191 | end 192 | 193 | end 194 | =end 195 | 196 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | . 2 | 3 | Apache License 4 | Version 2.0, January 2004 5 | http://www.apache.org/licenses/ 6 | 7 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 8 | 9 | 1. Definitions. 10 | 11 | "License" shall mean the terms and conditions for use, reproduction, 12 | and distribution as defined by Sections 1 through 9 of this document. 13 | 14 | "Licensor" shall mean the copyright owner or entity authorized by 15 | the copyright owner that is granting the License. 16 | 17 | "Legal Entity" shall mean the union of the acting entity and all 18 | other entities that control, are controlled by, or are under common 19 | control with that entity. For the purposes of this definition, 20 | "control" means (i) the power, direct or indirect, to cause the 21 | direction or management of such entity, whether by contract or 22 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 23 | outstanding shares, or (iii) beneficial ownership of such entity. 24 | 25 | "You" (or "Your") shall mean an individual or Legal Entity 26 | exercising permissions granted by this License. 27 | 28 | "Source" form shall mean the preferred form for making modifications, 29 | including but not limited to software source code, documentation 30 | source, and configuration files. 31 | 32 | "Object" form shall mean any form resulting from mechanical 33 | transformation or translation of a Source form, including but 34 | not limited to compiled object code, generated documentation, 35 | and conversions to other media types. 36 | 37 | "Work" shall mean the work of authorship, whether in Source or 38 | Object form, made available under the License, as indicated by a 39 | copyright notice that is included in or attached to the work 40 | (an example is provided in the Appendix below). 41 | 42 | "Derivative Works" shall mean any work, whether in Source or Object 43 | form, that is based on (or derived from) the Work and for which the 44 | editorial revisions, annotations, elaborations, or other modifications 45 | represent, as a whole, an original work of authorship. For the purposes 46 | of this License, Derivative Works shall not include works that remain 47 | separable from, or merely link (or bind by name) to the interfaces of, 48 | the Work and Derivative Works thereof. 49 | 50 | "Contribution" shall mean any work of authorship, including 51 | the original version of the Work and any modifications or additions 52 | to that Work or Derivative Works thereof, that is intentionally 53 | submitted to Licensor for inclusion in the Work by the copyright owner 54 | or by an individual or Legal Entity authorized to submit on behalf of 55 | the copyright owner. For the purposes of this definition, "submitted" 56 | means any form of electronic, verbal, or written communication sent 57 | to the Licensor or its representatives, including but not limited to 58 | communication on electronic mailing lists, source code control systems, 59 | and issue tracking systems that are managed by, or on behalf of, the 60 | Licensor for the purpose of discussing and improving the Work, but 61 | excluding communication that is conspicuously marked or otherwise 62 | designated in writing by the copyright owner as "Not a Contribution." 63 | 64 | "Contributor" shall mean Licensor and any individual or Legal Entity 65 | on behalf of whom a Contribution has been received by Licensor and 66 | subsequently incorporated within the Work. 67 | 68 | 2. Grant of Copyright License. Subject to the terms and conditions of 69 | this License, each Contributor hereby grants to You a perpetual, 70 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 71 | copyright license to reproduce, prepare Derivative Works of, 72 | publicly display, publicly perform, sublicense, and distribute the 73 | Work and such Derivative Works in Source or Object form. 74 | 75 | 3. Grant of Patent License. Subject to the terms and conditions of 76 | this License, each Contributor hereby grants to You a perpetual, 77 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 78 | (except as stated in this section) patent license to make, have made, 79 | use, offer to sell, sell, import, and otherwise transfer the Work, 80 | where such license applies only to those patent claims licensable 81 | by such Contributor that are necessarily infringed by their 82 | Contribution(s) alone or by combination of their Contribution(s) 83 | with the Work to which such Contribution(s) was submitted. If You 84 | institute patent litigation against any entity (including a 85 | cross-claim or counterclaim in a lawsuit) alleging that the Work 86 | or a Contribution incorporated within the Work constitutes direct 87 | or contributory patent infringement, then any patent licenses 88 | granted to You under this License for that Work shall terminate 89 | as of the date such litigation is filed. 90 | 91 | 4. Redistribution. You may reproduce and distribute copies of the 92 | Work or Derivative Works thereof in any medium, with or without 93 | modifications, and in Source or Object form, provided that You 94 | meet the following conditions: 95 | 96 | (a) You must give any other recipients of the Work or 97 | Derivative Works a copy of this License; and 98 | 99 | (b) You must cause any modified files to carry prominent notices 100 | stating that You changed the files; and 101 | 102 | (c) You must retain, in the Source form of any Derivative Works 103 | that You distribute, all copyright, patent, trademark, and 104 | attribution notices from the Source form of the Work, 105 | excluding those notices that do not pertain to any part of 106 | the Derivative Works; and 107 | 108 | (d) If the Work includes a "NOTICE" text file as part of its 109 | distribution, then any Derivative Works that You distribute must 110 | include a readable copy of the attribution notices contained 111 | within such NOTICE file, excluding those notices that do not 112 | pertain to any part of the Derivative Works, in at least one 113 | of the following places: within a NOTICE text file distributed 114 | as part of the Derivative Works; within the Source form or 115 | documentation, if provided along with the Derivative Works; or, 116 | within a display generated by the Derivative Works, if and 117 | wherever such third-party notices normally appear. The contents 118 | of the NOTICE file are for informational purposes only and 119 | do not modify the License. You may add Your own attribution 120 | notices within Derivative Works that You distribute, alongside 121 | or as an addendum to the NOTICE text from the Work, provided 122 | that such additional attribution notices cannot be construed 123 | as modifying the License. 124 | 125 | You may add Your own copyright statement to Your modifications and 126 | may provide additional or different license terms and conditions 127 | for use, reproduction, or distribution of Your modifications, or 128 | for any such Derivative Works as a whole, provided Your use, 129 | reproduction, and distribution of the Work otherwise complies with 130 | the conditions stated in this License. 131 | 132 | 5. Submission of Contributions. Unless You explicitly state otherwise, 133 | any Contribution intentionally submitted for inclusion in the Work 134 | by You to the Licensor shall be under the terms and conditions of 135 | this License, without any additional terms or conditions. 136 | Notwithstanding the above, nothing herein shall supersede or modify 137 | the terms of any separate license agreement you may have executed 138 | with Licensor regarding such Contributions. 139 | 140 | 6. Trademarks. This License does not grant permission to use the trade 141 | names, trademarks, service marks, or product names of the Licensor, 142 | except as required for reasonable and customary use in describing the 143 | origin of the Work and reproducing the content of the NOTICE file. 144 | 145 | 7. Disclaimer of Warranty. Unless required by applicable law or 146 | agreed to in writing, Licensor provides the Work (and each 147 | Contributor provides its Contributions) on an "AS IS" BASIS, 148 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 149 | implied, including, without limitation, any warranties or conditions 150 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 151 | PARTICULAR PURPOSE. You are solely responsible for determining the 152 | appropriateness of using or redistributing the Work and assume any 153 | risks associated with Your exercise of permissions under this License. 154 | 155 | 8. Limitation of Liability. In no event and under no legal theory, 156 | whether in tort (including negligence), contract, or otherwise, 157 | unless required by applicable law (such as deliberate and grossly 158 | negligent acts) or agreed to in writing, shall any Contributor be 159 | liable to You for damages, including any direct, indirect, special, 160 | incidental, or consequential damages of any character arising as a 161 | result of this License or out of the use or inability to use the 162 | Work (including but not limited to damages for loss of goodwill, 163 | work stoppage, computer failure or malfunction, or any and all 164 | other commercial damages or losses), even if such Contributor 165 | has been advised of the possibility of such damages. 166 | 167 | 9. Accepting Warranty or Additional Liability. While redistributing 168 | the Work or Derivative Works thereof, You may choose to offer, 169 | and charge a fee for, acceptance of support, warranty, indemnity, 170 | or other liability obligations and/or rights consistent with this 171 | License. However, in accepting such obligations, You may act only 172 | on Your own behalf and on Your sole responsibility, not on behalf 173 | of any other Contributor, and only if You agree to indemnify, 174 | defend, and hold each Contributor harmless for any liability 175 | incurred by, or claims asserted against, such Contributor by reason 176 | of your accepting any such warranty or additional liability. 177 | 178 | END OF TERMS AND CONDITIONS 179 | 180 | APPENDIX: How to apply the Apache License to your work. 181 | 182 | To apply the Apache License to your work, attach the following 183 | boilerplate notice, with the fields enclosed by brackets "[]" 184 | replaced with your own identifying information. (Don't include 185 | the brackets!) The text should be enclosed in the appropriate 186 | comment syntax for the file format. We also recommend that a 187 | file or class name and description of purpose be included on the 188 | same "printed page" as the copyright notice for easier 189 | identification within third-party archives. 190 | 191 | Copyright [yyyy] [name of copyright owner] 192 | 193 | Licensed under the Apache License, Version 2.0 (the "License"); 194 | you may not use this file except in compliance with the License. 195 | You may obtain a copy of the License at 196 | 197 | http://www.apache.org/licenses/LICENSE-2.0 198 | 199 | Unless required by applicable law or agreed to in writing, software 200 | distributed under the License is distributed on an "AS IS" BASIS, 201 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 202 | See the License for the specific language governing permissions and 203 | limitations under the License. 204 | 205 | . 206 | --------------------------------------------------------------------------------