source_proc 0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. data/LICENSE +15 -0
  2. data/lib/source_proc.rb +131 -0
  3. data/test/source_proc_test.rb +61 -0
  4. metadata +56 -0
data/LICENSE ADDED
@@ -0,0 +1,15 @@
1
+ Copyright (c) 2007 Samuel Lebeau
2
+
3
+ This program is free software; you can redistribute it and/or modify
4
+ it under the terms of the GNU General Public License as published by
5
+ the Free Software Foundation; either version 2 of the License, or
6
+ (at your option) any later version.
7
+
8
+ This program is distributed in the hope that it will be useful,
9
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ GNU General Public License for more details.
12
+
13
+ You should have received a copy of the GNU General Public License
14
+ along with this program; if not, write to the Free Software
15
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
@@ -0,0 +1,131 @@
1
+ require 'rubygems'
2
+ require 'forwardable'
3
+ require 'binding_of_caller'
4
+
5
+ # SourceProc objects are objects acting like Proc ones that can return
6
+ # back their own source code.
7
+ # They have to be created with a source string instead of a block, then
8
+ # all proc method calls are delegated to an internal Proc object created
9
+ # by evaluating this string.
10
+ #
11
+ # == Creation
12
+ #
13
+ # SourceProc objects can be created 2 ways:
14
+ # add = SourceProc.new('|a, b| a + b')
15
+ # or:
16
+ # add = slambda '|a, b| a + b'
17
+ #
18
+ # The first form creates its internal proc with <tt>Proc.new</tt>,
19
+ # the second one with <tt>lambda,</tt> so Ruby semantics are preserved
20
+ # and differences between procs and lambdas are kept.
21
+ #
22
+ # == Usage
23
+ #
24
+ # add.call(3, 4) # => 7
25
+ # add.arity # => 2
26
+ # add.source # => '|a, b| a + b'
27
+ #
28
+ # Be careful, SourceProc isn't a subclass of Proc, so
29
+ # add.is_a?(Proc) # => false
30
+ #
31
+ # Better way is to test if we can invoke 'call' on it
32
+ # add.respond_to(:call) # => true
33
+ #
34
+ # == Bindings
35
+ #
36
+ # When created inside a method, SourceProc objects will be given the current binding.
37
+ # For instance:
38
+ #
39
+ # str = 'Hello'
40
+ #
41
+ # def foo
42
+ # slambda '|arg| str + arg'
43
+ # end
44
+ #
45
+ # foo.call(' world !') # => 'Hello world !'
46
+ # eval('str', foo.binding) # => 'Hello'
47
+ #
48
+ # Due to the implementation of Binding.of_caller, the current binding will not be
49
+ # given if SourceProc objects are created outside a method.
50
+ # You can however pass a Binding object as second argument which will be used instead.
51
+ #
52
+ # Example:
53
+ #
54
+ # str = 'Hello'
55
+ #
56
+ # my_proc = slambda '|arg| str + arg'
57
+ # my_proc.call(' world !') # => Error
58
+ #
59
+ # # will give what is expected
60
+ # my_proc = slambda '|arg| str + arg', binding
61
+ # my_proc.call(' world !') # => 'Hello world !'
62
+ #
63
+ # == Marshalling
64
+ #
65
+ # You can marshall SourceProc objects and restore them back,
66
+ # except all bindings will be lost as Binding objects cannot be marshalled.
67
+ # This could however be usefull if you restore them in the same context
68
+ # you marshalled them.
69
+
70
+ class SourceProc
71
+ extend Forwardable
72
+
73
+ def SourceProc.new(source, binding = nil, is_lambda = false)
74
+ return super if binding
75
+ Binding.of_caller { |binding| super(source, binding, is_lambda) }
76
+ end
77
+
78
+ def SourceProc._load(string)
79
+ # obviously loosing the original binding
80
+ eval(string)
81
+ end
82
+
83
+ def initialize(source, binding, is_lambda)
84
+ @source, @is_lambda = source.freeze, is_lambda.freeze
85
+ @proc = eval(expression, binding)
86
+ end
87
+
88
+ def_delegators :@proc, :to_proc, :arity, :binding, :call, :[], :==
89
+
90
+ attr_reader :source
91
+
92
+ def lambda?
93
+ @is_lambda
94
+ end
95
+
96
+ def proc?
97
+ !@is_lambda
98
+ end
99
+
100
+ def dup
101
+ self.class.new(@source, @proc.binding, @is_lambda)
102
+ end
103
+
104
+ def to_s(*args)
105
+ (@is_lambda ? 'slambda(' : 'SourceProc.new(') << source.inspect << ')'
106
+ end
107
+ alias _dump to_s
108
+
109
+ private
110
+ def method_missing?(meth_id, *args, &block)
111
+ @proc.respond_to?(meth_id) ? @proc.send(meth_id, *args, &block) : super
112
+ end
113
+
114
+ def expression
115
+ (@is_lambda ? 'lambda ' : 'Proc.new ') << source_with_braces
116
+ end
117
+
118
+ def source_with_braces
119
+ @source.index("\n") ? "do #@source\nend" : "{ #@source }"
120
+ end
121
+ end
122
+
123
+ module Kernel
124
+ # convenient SourceProc creator for lambdas
125
+ def slambda(source, binding = nil)
126
+ return SourceProc.new(source, binding, true) if binding
127
+ Binding.of_caller { |binding| SourceProc.new(source, binding, true) }
128
+ end
129
+ # deprecated
130
+ alias sproc slambda
131
+ end
@@ -0,0 +1,61 @@
1
+ require 'test/unit'
2
+ require 'source_proc'
3
+
4
+ class SourceProcTest < Test::Unit::TestCase
5
+ def max_source
6
+ 'a > b ? a : b'
7
+ end
8
+
9
+ def test_creation
10
+ src = "|a, b| #{max_source}"
11
+ max = SourceProc.new(src)
12
+ max2 = slambda(src)
13
+ assert_equal(src, max.source)
14
+ assert_equal(src, max2.source)
15
+ assert(max2.lambda?)
16
+ assert_equal(4, max.call(3, 4))
17
+ assert_equal(4, max2.call(3, 4))
18
+ assert_nothing_raised(ArgumentError) { assert_equal(4, max.call(3, 4, 5)) }
19
+ assert_raise(ArgumentError) { max2.call(3, 4, 5) }
20
+ end
21
+
22
+ def remote_call(proc)
23
+ proc.call
24
+ end
25
+
26
+ def test_bindings
27
+ a, b = 128, 5
28
+ max = slambda(max_source)
29
+ assert_equal(128, max.call)
30
+ assert_equal(128, remote_call(max))
31
+ array = []
32
+ push = slambda 'array.push(1664)'
33
+ push.call
34
+ assert_equal(1, array.size)
35
+ eval('array.push(0 + 0)', push.binding)
36
+ assert_equal(2, array.size)
37
+ end
38
+
39
+ def test_marshalling
40
+ f = slambda('|x, y| x + y')
41
+ g = SourceProc.new('ENV')
42
+ assert_nothing_raised do
43
+ f = Marshal.load(Marshal.dump(f))
44
+ g = Marshal.load(Marshal.dump(g))
45
+ end
46
+ assert_kind_of(SourceProc, f)
47
+ assert(f.lambda?)
48
+ assert(!g.lambda?)
49
+ assert_equal('|x, y| x + y', f.source)
50
+ assert_equal(2020, f.call(2012, 8))
51
+ assert_equal(ENV, g.call)
52
+ end
53
+
54
+ def test_dup
55
+ f, g = slambda('|x| x'), nil
56
+ assert_nothing_raised(Exception) { g = f.dup }
57
+ assert_equal(f.source, g.source)
58
+ assert_equal(g.call(true))
59
+ end
60
+
61
+ end
metadata ADDED
@@ -0,0 +1,56 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.9.4
3
+ specification_version: 1
4
+ name: source_proc
5
+ version: !ruby/object:Gem::Version
6
+ version: "0.1"
7
+ date: 2007-08-08 00:00:00 +02:00
8
+ summary: Proc objects that can give back their source code.
9
+ require_paths:
10
+ - lib
11
+ email: sam@gotfresh.info
12
+ homepage: http://i.gotfresh.info
13
+ rubyforge_project:
14
+ description: SourceProc objects are created with a source string and binding and delegate calls to the corresponding Proc object letting you access to the original source code.
15
+ autorequire:
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ post_install_message:
29
+ authors:
30
+ - Samuel Lebeau
31
+ files:
32
+ - LICENSE
33
+ - lib/source_proc.rb
34
+ - test/source_proc_test.rb
35
+ test_files: []
36
+
37
+ rdoc_options: []
38
+
39
+ extra_rdoc_files: []
40
+
41
+ executables: []
42
+
43
+ extensions: []
44
+
45
+ requirements: []
46
+
47
+ dependencies:
48
+ - !ruby/object:Gem::Dependency
49
+ name: call_stack
50
+ version_requirement:
51
+ version_requirements: !ruby/object:Gem::Version::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: 0.1.0.0
56
+ version: