cb 1.0.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 ADDED
@@ -0,0 +1,42 @@
1
+ Simple native Callback object for Ruby MRI
2
+ (c) 2009 Lourens Naudé (methodmissing), James Tucker (raggi) and coderrr
3
+
4
+ http://github.com/methodmissing/cb
5
+
6
+ This library works with Ruby 1.8 and 1.9 and is a more efficient
7
+ implementation of the following Ruby code :
8
+
9
+ class RubyCallback
10
+ def initialize(object = nil, method = :call, &b)
11
+ @object, @method = object, method
12
+ @object ||= b
13
+ end
14
+
15
+ def call(*args)
16
+ @object.__send__(@method, *args)
17
+ end
18
+ end
19
+
20
+ module Kernel
21
+ private
22
+ def RubyCallback(object = nil, method = :call, &b)
23
+ RubyCallback.new(object, method, &b)
24
+ end
25
+ end
26
+
27
+ Concept, ideas and API design James's - any pointers for better GC integration much appreciated.
28
+
29
+ Examples :
30
+
31
+ 'hai'.callback(:gsub).call('h', 'b') #=> 'bai'
32
+ Callback( 'bai', :to_s ).call #=> 'hai'
33
+ Callback{ 'hai' }.call #=> 'hai'
34
+ Callback( 'bai', :gsub ).call( 'b', 'h' ) #=> 'hai'
35
+
36
+ To run the test suite:
37
+
38
+ rake
39
+
40
+ To run the benchmarks:
41
+
42
+ rake bench
data/Rakefile ADDED
@@ -0,0 +1,171 @@
1
+ #!/usr/bin/env rake
2
+ require 'rake/testtask'
3
+ require 'rake/clean'
4
+
5
+ def spec(file = Dir['*.gemspec'].first)
6
+ @spec ||=
7
+ begin
8
+ require 'rubygems/specification'
9
+ Thread.abort_on_exception = true
10
+ data = File.read(file)
11
+ spec = nil
12
+ Thread.new { spec = eval("$SAFE = 3\n#{data}") }.join
13
+ spec.instance_variable_set(:@filename, file)
14
+ def spec.filename; @filename; end
15
+ spec
16
+ end
17
+ end
18
+
19
+ def manifest; @manifest ||= `git ls-files`.split("\n").reject{|s|s=~/\.gemspec$|\.gitignore$/}; end
20
+
21
+ @gem_package_task_type = begin
22
+ require 'rubygems/package_task'
23
+ Gem::PackageTask
24
+ rescue LoadError
25
+ require 'rake/gempackagetask'
26
+ Rake::GemPackageTask
27
+ end
28
+ def gem_task; @gem_task ||= @gem_package_task_type.new(spec); end
29
+ gem_task.define
30
+ Rake::Task[:clobber].enhance [:clobber_package]
31
+
32
+ rdoc_task_type = begin
33
+ require 'rdoc/task'
34
+ RDoc::Task
35
+ rescue LoadError
36
+ require 'rake/rdoctask'
37
+ Rake::RDocTask
38
+ end
39
+ df = begin; require 'rdoc/generator/darkfish'; true; rescue LoadError; end
40
+ rdtask = rdoc_task_type.new do |rd|
41
+ rd.title = spec.name
42
+ rd.main = spec.extra_rdoc_files.first
43
+ lib_rexp = spec.require_paths.map { |p| Regexp.escape p }.join('|')
44
+ rd.rdoc_files.include(*manifest.grep(/^(?:#{lib_rexp})/))
45
+ rd.rdoc_files.include(*spec.extra_rdoc_files)
46
+ rd.template = 'darkfish' if df
47
+ end
48
+
49
+ Rake::Task[:clobber].enhance [:clobber_rdoc]
50
+
51
+ desc 'Generate and open documentation'
52
+ task :docs => :rdoc do
53
+ path = rdtask.send :rdoc_target
54
+ case RUBY_PLATFORM
55
+ when /darwin/ ; sh "open #{path}"
56
+ when /mswin|mingw/ ; sh "start #{path}"
57
+ else
58
+ sh "firefox #{path}"
59
+ end
60
+ end
61
+
62
+ desc "Regenerate gemspec"
63
+ task :gemspec => spec.filename
64
+
65
+ task spec.filename do
66
+ spec.files = manifest
67
+ spec.test_files = FileList['{test,spec}/**/{test,spec}_*.rb']
68
+ open(spec.filename, 'w') { |w| w.write spec.to_ruby }
69
+ end
70
+
71
+ desc "Bump version from #{spec.version} to #{spec.version.to_s.succ}"
72
+ task :bump do
73
+ spec.version = spec.version.to_s.succ
74
+ end
75
+
76
+ desc "Tag version #{spec.version}"
77
+ task :tag do
78
+ tagged = Dir.new('.git/refs/tags').entries.include? spec.version.to_s
79
+ if tagged
80
+ warn "Tag #{spec.version} already exists"
81
+ else
82
+ # TODO release message in tag message
83
+ sh "git tag #{spec.version}"
84
+ end
85
+ end
86
+
87
+ if spec.rubyforge_project
88
+ require 'yaml'
89
+ require 'rake/contrib/sshpublisher'
90
+ desc "Publish rdoc to rubyforge"
91
+ task :publish => rdtask.name do
92
+ rf_cfg = File.expand_path '~/.rubyforge/user-config.yml'
93
+ host = "#{YAML.load_file(rf_cfg)['username']}@rubyforge.org"
94
+ remote_dir = "/var/www/gforge-projects/#{spec.rubyforge_project}/#{spec.name}/"
95
+ Rake::SshDirPublisher.new(host, remote_dir, rdtask.rdoc_dir).upload
96
+ end
97
+
98
+ desc "Release #{gem_task.gem_file} to rubyforge"
99
+ task :release => [:tag, :gem, :publish] do |t|
100
+ sh "rubyforge add_release #{spec.rubyforge_project} #{spec.name} #{spec.version} #{gem_task.package_dir}/#{gem_task.gem_file}"
101
+ end
102
+ else
103
+ warn "Cannot release to rubyforge - no rubyforge_project in gemspec"
104
+ end
105
+
106
+ CB_ROOT = 'ext/mri_callback'
107
+
108
+ desc 'Default: test'
109
+ task :default => :test
110
+
111
+ desc 'Run callback tests.'
112
+ Rake::TestTask.new(:test) do |t|
113
+ t.libs << CB_ROOT
114
+ t.pattern = 'test/test_*.rb'
115
+ t.verbose = true
116
+ end
117
+ task :test
118
+
119
+ namespace :build do
120
+ file "#{CB_ROOT}/mri_callback.c"
121
+ file "#{CB_ROOT}/extconf.rb"
122
+ file "#{CB_ROOT}/Makefile" => %W(#{CB_ROOT}/mri_callback.c #{CB_ROOT}/extconf.rb) do
123
+ Dir.chdir(CB_ROOT) do
124
+ ruby 'extconf.rb'
125
+ end
126
+ end
127
+
128
+ desc "generate makefile"
129
+ task :makefile => %W(#{CB_ROOT}/Makefile #{CB_ROOT}/mri_callback.c)
130
+
131
+ dlext = Config::CONFIG['DLEXT']
132
+ file "#{CB_ROOT}/mri_callback.#{dlext}" => %W(#{CB_ROOT}/Makefile #{CB_ROOT}/mri_callback.c) do
133
+ Dir.chdir(CB_ROOT) do
134
+ sh 'make' # TODO - is there a config for which make somewhere?
135
+ end
136
+ end
137
+
138
+ desc "compile callback extension"
139
+ task :compile => "#{CB_ROOT}/mri_callback.#{dlext}"
140
+
141
+ task :clean do
142
+ Dir.chdir(CB_ROOT) do
143
+ sh 'make clean'
144
+ end if File.exists?("#{CB_ROOT}/Makefile")
145
+ end
146
+
147
+ CLEAN.include("#{CB_ROOT}/Makefile")
148
+ CLEAN.include("#{CB_ROOT}/mri_callback.#{dlext}")
149
+ end
150
+
151
+ task :clean => %w(build:clean)
152
+
153
+ desc "compile"
154
+ task :build => %w(build:compile)
155
+
156
+ task :install do |t|
157
+ Dir.chdir(CB_ROOT) do
158
+ sh 'sudo make install'
159
+ end
160
+ end
161
+
162
+ desc "clean build install"
163
+ task :setup => %w(clean build install)
164
+
165
+ desc "run benchmarks"
166
+ task :bench do |t|
167
+ ruby "-Ilib:ext/mri_callback:bench", "bench/bench.rb"
168
+ puts
169
+ ruby "-Ilib:ext/mri_callback:bench", "bench/raggi_bench.rb"
170
+ end
171
+ task :bench
data/bench/bench.rb ADDED
@@ -0,0 +1,22 @@
1
+ require "benchmark"
2
+ require "callback"
3
+ require "pure_ruby_cb"
4
+
5
+ class Object
6
+ public :method
7
+ end
8
+
9
+ def meth
10
+ 'hai'
11
+ end
12
+
13
+ TESTS = 10_000
14
+ Benchmark.bmbm do |results|
15
+ results.report("Method object:") { TESTS.times { method(:meth).call } }
16
+ results.report("Callback{ 'hai' }.call") { TESTS.times { Callback{ 'hai' }.call } }
17
+ results.report("RubyCallback{ 'hai' }.call") { TESTS.times { RubyCallback{ 'hai' }.call } }
18
+ results.report("Callback( 'bai', :to_s ).call") { TESTS.times { Callback( 'bai', :to_s ).call } }
19
+ results.report("RubyCallback( 'bai', :to_s ).call") { TESTS.times { RubyCallback( 'bai', :to_s ).call } }
20
+ results.report("Callback( 'bai', :gsub ).call( 'b', 'h' )") { TESTS.times { Callback( 'bai', :gsub ).call( 'b', 'h' ) } }
21
+ results.report("RubyCallback( 'bai', :gsub ).call( 'b', 'h' )") { TESTS.times { RubyCallback( 'bai', :gsub).call( 'b', 'h' ) } }
22
+ end
@@ -0,0 +1,17 @@
1
+ class RubyCallback
2
+ def initialize(object = nil, method = :call, &b)
3
+ @object, @method = object, method
4
+ @object ||= b
5
+ end
6
+
7
+ def call(*args)
8
+ @object.__send__(@method, *args)
9
+ end
10
+ end
11
+
12
+ module Kernel
13
+ private
14
+ def RubyCallback(object = nil, method = :call, &b)
15
+ RubyCallback.new(object, method, &b)
16
+ end
17
+ end
@@ -0,0 +1,24 @@
1
+ require "benchmark"
2
+ require "callback"
3
+ require "pure_ruby_cb"
4
+
5
+ @s = 'foo'
6
+ def me; @s.to_s; end
7
+ ld = lambda { @s.to_s }
8
+ cb = Callback &ld
9
+ c2 = Callback( @s, :to_s )
10
+ rb = RubyCallback { @s.to_s }
11
+ farm = @s.callback(:to_s)
12
+ meth = @s.send(:method, :to_s)
13
+
14
+ tests = 100_000
15
+ puts "# Calling"
16
+ Benchmark.bmbm do |results|
17
+ results.report("method invoked:") { tests.times { me } }
18
+ results.report("ruby method obj:") { tests.times { meth.call } }
19
+ results.report("object native callback:") { tests.times { farm.call } }
20
+ results.report("callback native:") { tests.times { c2.call } }
21
+ results.report("callback block:") { tests.times { cb.call } }
22
+ results.report("pure ruby callback:") { tests.times { rb.call } }
23
+ results.report("lambda:") { tests.times { ld.call } }
24
+ end
@@ -0,0 +1,5 @@
1
+ require 'mkmf'
2
+
3
+ dir_config('mri_callback')
4
+
5
+ create_makefile('mri_callback')
@@ -0,0 +1,85 @@
1
+ #include "ruby.h"
2
+
3
+ #ifndef RSTRING_PTR
4
+ #define RSTRING_PTR(obj) RSTRING(obj)->ptr
5
+ #endif
6
+
7
+ typedef struct {
8
+ VALUE object;
9
+ ID method;
10
+ } RCallback;
11
+
12
+ #define GetCallbackStruct(obj) (Check_Type(obj, T_DATA), (RCallback*)DATA_PTR(obj))
13
+
14
+ VALUE rb_cCallback;
15
+ static ID id_call;
16
+
17
+ static void mark_mri_callback(RCallback* cb)
18
+ {
19
+ rb_gc_mark(cb->object);
20
+ }
21
+
22
+ static void free_mri_callback(RCallback* cb)
23
+ {
24
+ xfree(cb);
25
+ }
26
+
27
+ static VALUE callback_alloc _((VALUE));
28
+ static VALUE
29
+ callback_alloc( VALUE klass )
30
+ {
31
+ VALUE cb;
32
+ RCallback* cbs;
33
+ cb = Data_Make_Struct(klass, RCallback, mark_mri_callback, free_mri_callback, cbs);
34
+ cbs->object = Qnil;
35
+ cbs->method = 0;
36
+
37
+ return cb;
38
+ }
39
+
40
+ static VALUE
41
+ rb_mri_callback_new()
42
+ {
43
+ return callback_alloc(rb_cCallback);
44
+ }
45
+
46
+ static VALUE rb_mri_callback_initialize( int argc, VALUE *argv, VALUE cb )
47
+ {
48
+ VALUE object;
49
+ VALUE method;
50
+ ID meth;
51
+ RCallback* cbs = GetCallbackStruct(cb);
52
+
53
+ if (rb_block_given_p()) {
54
+ if (argc == 1) rb_raise(rb_eArgError, "wrong number of arguments");
55
+ cbs->object = rb_block_proc();
56
+ cbs->method = id_call;
57
+ }else {
58
+ rb_scan_args(argc, argv, "02", &object, &method);
59
+ cbs->object = object;
60
+ meth = rb_to_id(method);
61
+ if (!rb_respond_to(object,meth)) rb_raise(rb_eArgError, "object does not respond to %s", RSTRING_PTR(rb_obj_as_string(method)));
62
+ cbs->method = meth;
63
+ }
64
+
65
+ OBJ_FREEZE(cb);
66
+ return cb;
67
+ }
68
+
69
+ static VALUE rb_mri_callback_call( VALUE cb, VALUE args )
70
+ {
71
+ RCallback* cbs = GetCallbackStruct(cb);
72
+ return rb_apply(cbs->object, cbs->method, args);
73
+ }
74
+
75
+ void
76
+ Init_mri_callback()
77
+ {
78
+ id_call = rb_intern("call");
79
+
80
+ rb_cCallback = rb_define_class("Callback", rb_cObject);
81
+ rb_define_alloc_func(rb_cCallback, callback_alloc);
82
+
83
+ rb_define_method(rb_cCallback,"initialize", rb_mri_callback_initialize, -1);
84
+ rb_define_method(rb_cCallback,"call", rb_mri_callback_call, -2);
85
+ }
data/lib/cb.rb ADDED
@@ -0,0 +1,72 @@
1
+ begin
2
+ require 'mri_callback'
3
+ rescue LoadError
4
+ end
5
+
6
+ # A simple __send__ based callback implementation, simply Object Orients
7
+ # something equivalent to:
8
+ #
9
+ # lambda { |*args| object.__send__ method, *args }
10
+ class Callback
11
+ class << self; attr_reader :extension_loaded; end
12
+
13
+ begin
14
+ cb = new(self, :instance_variable_set)
15
+ cb.respond_to?(:call) && cb.call(:@extension_loaded, true)
16
+ rescue ArgumentError
17
+ @extension_loaded = false
18
+ end
19
+
20
+ unless @extension_loaded
21
+ warn "Using pure ruby Callback"
22
+ def initialize(object, method)
23
+ @object, @method = object, method
24
+ end
25
+
26
+ def call(*args)
27
+ @object.__send__(@method, *args)
28
+ end
29
+ end
30
+ end
31
+
32
+ # A quick monkey patch providing a public equivalent of the #method private
33
+ # method aliased to #callback. This is intended for use as a persistent
34
+ # callback object. It should be noted that using Method objects under MRI has
35
+ # in the past lead to user induced leaks. One must be careful when using it.
36
+ class Object
37
+ alias callback method
38
+
39
+ # Allow users to call object.callback(:method_name) to grab a Method object.
40
+ public :callback
41
+ end
42
+
43
+ # Utility method for coercing arguments to an object that responds to #call
44
+ # Accepts an object and a method name to send to, or a block, or an object
45
+ # that responds to call.
46
+ #
47
+ # cb = Callback{ |msg| puts(msg) }
48
+ # cb.call('hello world')
49
+ #
50
+ # cb = Callback(Object, :puts)
51
+ # cb.call('hello world')
52
+ #
53
+ # cb = Callback(proc{ |msg| puts(msg) })
54
+ # cb.call('hello world')
55
+ module Kernel
56
+ def Callback(object = nil, method = nil, &blk)
57
+ if object && method
58
+ Callback.new(object, method)
59
+ else
60
+ if object.respond_to? :call
61
+ object
62
+ else
63
+ if object && blk || blk.nil?
64
+ raise(ArgumentError, "Callback required an object responding to #call or an object and a method, or a block")
65
+ else
66
+ blk
67
+ end
68
+ end
69
+ end
70
+ end
71
+ private :Callback
72
+ end
data/test/test_cb.rb ADDED
@@ -0,0 +1,78 @@
1
+ require "test/unit"
2
+ require "cb"
3
+
4
+ class TestCallbackPureRuby < Test::Unit::TestCase
5
+ def test_Callback_with_lambda
6
+ ran = false
7
+ l = lambda { ran = true }
8
+ Callback(l).call
9
+ assert ran
10
+ end
11
+
12
+ def cb
13
+ @ran = true
14
+ end
15
+
16
+ def test_Callback_with_object_and_method
17
+ @ran = false
18
+ Callback(self, :cb).call
19
+ assert @ran
20
+ end
21
+
22
+ def test_Callback_with_block
23
+ ran = false
24
+ Callback { ran = true }.call
25
+ assert ran
26
+ end
27
+
28
+ def test_Callback_with_no_arguments
29
+ assert_raises(ArgumentError) do
30
+ Callback()
31
+ end
32
+ end
33
+
34
+ def test_Object_callback
35
+ @ran = false
36
+ self.callback(:cb).call
37
+ assert @ran
38
+ end
39
+
40
+ def test_block
41
+ cb = Callback{ 'hai' }
42
+ assert_equal 'hai', cb.call
43
+ end
44
+
45
+ def test_init_object_call_with_no_args
46
+ cb = Callback( 'bai', :to_s )
47
+ assert_equal 'bai', cb.call
48
+ end
49
+
50
+ def test_init_object_call_with_args
51
+ cb = Callback( 'bai', :gsub )
52
+ assert_equal 'hai', cb.call( 'b', 'h' )
53
+ end
54
+
55
+ def test_many_calls
56
+ cb = Callback { :foo }
57
+ 100_000.times { cb.call }
58
+ end
59
+
60
+ def test_arguments
61
+ assert_raises ArgumentError do
62
+ Callback( 'hai' ){}
63
+ end
64
+ end
65
+
66
+ def test_farmed_from_object
67
+ cb = 'hai'.callback(:to_s)
68
+ assert_equal 'hai', cb.call
69
+ ocb = 'bai'.callback(:gsub)
70
+ assert_equal 'hai', ocb.call('b', 'h')
71
+ end
72
+
73
+ def test_invalid_method
74
+ assert_raises NameError do
75
+ 'str'.callback(:undefined)
76
+ end
77
+ end
78
+ end
metadata ADDED
@@ -0,0 +1,65 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cb
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.1
5
+ platform: ruby
6
+ authors:
7
+ - "Lourens Naud\xC3\xA9 (methodmissing)"
8
+ - James Tucker (raggi)
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2009-08-22 00:00:00 +01:00
14
+ default_executable:
15
+ dependencies: []
16
+
17
+ description: Simple native Callback object for Ruby MRI (1.8.{6,7} and 1.9.2)
18
+ email: lourens@methodmissing.com
19
+ executables: []
20
+
21
+ extensions:
22
+ - ext/mri_callback/extconf.rb
23
+ extra_rdoc_files:
24
+ - README
25
+ files:
26
+ - README
27
+ - Rakefile
28
+ - bench/bench.rb
29
+ - bench/pure_ruby_cb.rb
30
+ - bench/raggi_bench.rb
31
+ - ext/mri_callback/extconf.rb
32
+ - ext/mri_callback/mri_callback.c
33
+ - lib/cb.rb
34
+ - test/test_cb.rb
35
+ has_rdoc: true
36
+ homepage: http://github.com/methodmissing/cb
37
+ licenses: []
38
+
39
+ post_install_message:
40
+ rdoc_options:
41
+ - --main
42
+ - README
43
+ require_paths:
44
+ - lib
45
+ required_ruby_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: "0"
50
+ version:
51
+ required_rubygems_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: "0"
56
+ version:
57
+ requirements: []
58
+
59
+ rubyforge_project:
60
+ rubygems_version: 1.3.5
61
+ signing_key:
62
+ specification_version: 3
63
+ summary: Native MRI callback
64
+ test_files:
65
+ - test/test_cb.rb