source_proc 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.
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: