Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

isolate/ignore constructor options, to deprecate the old @@_not_isolated... #6

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 16 additions & 7 deletions README.rdoc
Original file line number Diff line number Diff line change
Expand Up @@ -29,20 +29,29 @@ proc's scope, thus, achieving a snapshot effect.
s_proc.call # >> "lx, ix, cx, gx"
v_proc.call # >> "ly, iy, cy, gy"

Sometimes, we may want global variables to behave as truely global, meaning we don't want
to isolate globals at all, this can be done by declaring @@_not_isolated_vars within the
code block:
Sometimes, we may want global variables to behave as truly global, meaning we don't want
to isolate globals at all, this can be done by specifying :ignore option in the constructor call:

s_proc = SerializableProc.new do
@@_not_isolated_vars = :global # globals won't be isolated
s_proc = SerializableProc.new(ignore: :global) do
$stdout << "WakeUp !!" # $stdout is the $stdout in the execution context
end

Supported values are :global, :class, :instance, :local & :all, with :all overriding
all others. The following declares all variables as not isolatable:

s_proc = SerializableProc.new do
@@_not_isolated_vars = :all
s_proc = SerializableProc.new(ignore: :all) do
...
end

You can also set :isolate option explicitly in the constructor to specify which variable types should be isolated. The supported values as same as for the :ignore option. The following will isolate only locals:

s_proc = SerializableProc.new(isolate: :local) do
...
end

Note that the :ignore option will be processed on top of the :isolate option. The following will isolate only locals:

s_proc = SerializableProc.new(isolate: :all, ignore: [:global, :class, :instance]) do
...
end

Expand Down
35 changes: 24 additions & 11 deletions lib/serializable_proc.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,12 @@
# s_proc.call # >> "lx, ix, cx, gx"
# v_proc.call # >> "ly, iy, cy, gy"
#
# It is possible to fine-tune how variables isolation is being applied by declaring
# @@_not_isolated_vars within the code block:
# It is possible to fine-tune how variables isolation is being applied by specifying :ignore
# option to the constructor.
#
# x, @x, @@x, $x = 'lx', 'ix', 'cx', 'gx'
#
# s_proc = SerializableProc.new do
# @@_not_isolated_vars = :all
# s_proc = SerializableProc.new(ignore: :all) do
# [x, @x, @@x, $x].join(', ')
# end
#
Expand All @@ -49,11 +48,25 @@
# overriding all others. This can also be used as a workaround for variables that cannot
# be serialized:
#
# SerializableProc.new do
# @@_not_isolated_vars = :global # don't isolate globals
# $stdout << 'WAKE UP !!' # $stdout won't be isolated (avoid marshal error)
# SerializableProc.new(ignore: :global) do
# $stdout << 'WAKE UP !!' # global $stdout won't be isolated (avoid marshal error)
# end
#
# You can also set :isolate option explicitly in the constructor to specify which variable
# types should be isolated. The supported values as same as for the :ignore option.
# The following will isolate only locals:
#
# s_proc = SerializableProc.new(isolate: :local) do
# ...
# end
#
# Note that the :ignore option will be processed on top of the :isolate option.
# The following will isolate only locals:
#
# s_proc = SerializableProc.new(isolate: :all, ignore: [:global, :class, :instance]) do
# ...
# end
#
# Note that it is strongly-advised to append Kernel.binding as the last parameter when
# invoking the proc to avoid unnecessary nasty surprises. (see #call for more details)
#
Expand Down Expand Up @@ -88,7 +101,8 @@ class SerializableProc
# def action(&block) ; SerializableProc.new(&block) ; end
# action { ... }
#
def initialize(&block)
def initialize(options = {}, &block)
@isolate, @ignore = options[:isolate], options[:ignore]
e_code, e_sexp = block.to_source, block.to_sexp
r_sexp, r_code = isolated_sexp_and_code(e_sexp)
@arity, @file, @line = block.arity, *block.source_location
Expand Down Expand Up @@ -197,13 +211,12 @@ def arity
# SerializableProc.new{|i| (['hello'] * i).join(' ') }.call(2)
# # >> 'hello hello'
#
# In the case where variables have been declared not-isolated with @@_not_isolated_vars,
# In the case where variables have been declared non-isolated with the :isolate/:ignore options,
# invoking requires passing in +Kernel.binding+ as the last parameter avoid unexpected
# surprises:
#
# x, @x, @@x, $x = 'lx', 'ix', 'cx', 'gx'
# s_proc = SerializableProc.new do
# @@_not_isolated_vars = :global, :class, :instance, :local
# s_proc = SerializableProc.new(ignore: [:global, :class, :instance, :local]) do
# [x, @x, @@x, $x].join(', ')
# end
#
Expand Down
20 changes: 13 additions & 7 deletions lib/serializable_proc/isolatable.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
class SerializableProc
module Isolatable

ISOLATION_TYPES = [:local, :class, :instance, :global]
MAPPERS = {'l' => '', 'c' => '@@', 'i' => '@', 'g' => '$'}
ISOLATION_VAR = :@@_not_isolated_vars
BLOCK_SCOPES = [:class, :sclass, :defn, :module]
Expand Down Expand Up @@ -33,13 +34,18 @@ def isolated_sexp_and_code(sexp)
private

def isolated_types(sexp)
if (declarative = isolatable_declarative(sexp)).empty?
MAPPERS.keys
elsif declarative.include?('all')
[]
else
MAPPERS.keys - declarative.map{|e| e[0].chr }
end
isolated_types = ISOLATION_TYPES # by default, isolate all

# accept single symbols as well as arrays
@isolate = [@isolate].flatten if @isolate
@ignore = [@ignore].flatten if @ignore

isolated_types = @isolate if @isolate && [email protected]?(:all)
# backwards-compatibility for inline @@_not_isolated_vars syntax
@ignore ||= isolatable_declarative(sexp).map(&:to_sym)
ignored_types = @ignore.include?(:all) ? ISOLATION_TYPES : @ignore if @ignore

((isolated_types - ignored_types) & ISOLATION_TYPES).map{|e| e[0].chr }
end

def isolated_sexp_arry(array)
Expand Down
88 changes: 88 additions & 0 deletions spec/isolation/isolation_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
require File.join(File.dirname(__FILE__), '..', 'spec_helper')

describe 'Isolating variables (new syntax)' do

extend SerializableProc::Spec::Helpers

should 'isolate all vars if isolation not specified' do
x, @x, @@x, $x = 'lx', 'ix', 'cx', 'gx'
should_have_expected_binding \
SerializableProc.new {
[x, @x, @@x, $x]
}, {:lvar_x => x, :ivar_x => @x, :cvar_x => @@x, :gvar_x => $x}
end

should 'isolate no vars if isolate is []' do
x, @x, @@x, $x = 'lx', 'ix', 'cx', 'gx'
should_have_expected_binding \
SerializableProc.new(isolate: []) {
[x, @x, @@x, $x]
}, {}
end

should 'isolate all vars if isolate: :all' do
x, @x, @@x, $x = 'lx', 'ix', 'cx', 'gx'
should_have_expected_binding \
SerializableProc.new(isolate: :all) {
[x, @x, @@x, $x]
}, {:lvar_x => x, :ivar_x => @x, :cvar_x => @@x, :gvar_x => $x}
end

should 'isolate all vars if isolate: [:all]' do
x, @x, @@x, $x = 'lx', 'ix', 'cx', 'gx'
should_have_expected_binding \
SerializableProc.new(isolate: [:all]) {
[x, @x, @@x, $x]
}, {:lvar_x => x, :ivar_x => @x, :cvar_x => @@x, :gvar_x => $x}
end

should 'ignore no vars if ignore: :all' do
x, @x, @@x, $x = 'lx', 'ix', 'cx', 'gx'
should_have_expected_binding \
SerializableProc.new(ignore: :all) {
[x, @x, @@x, $x]
}, {}
end

should 'isolate locals if isolate: :all and ignore: all except locals' do
x, @x, @@x, $x = 'lx', 'ix', 'cx', 'gx'
should_have_expected_binding \
SerializableProc.new(isolate: :all, ignore: [:global, :class, :instance]) {
[x, @x, @@x, $x]
}, {:lvar_x => x}
end

should 'isolate no vars if ignore same as isolate (ignore overrides isolate)' do
x, @x, @@x, $x = 'lx', 'ix', 'cx', 'gx'
should_have_expected_binding \
SerializableProc.new(isolate: :local, ignore: :local) {
[x, @x, @@x, $x]
}, {}
end

should 'isolate all except for the ones specified in ignore if isolate not specified' do
x, @x, @@x, $x = 'lx', 'ix', 'cx', 'gx'
should_have_expected_binding \
SerializableProc.new(ignore: :local) {
[x, @x, @@x, $x]
}, {:ivar_x => @x, :cvar_x => @@x, :gvar_x => $x}
end

should 'use @not_isolated_vars in case @ignore not specified' do
x, @x, @@x, $x = 'lx', 'ix', 'cx', 'gx'
should_have_expected_binding \
SerializableProc.new(isolate: :all) {
@@_not_isolated_vars = :local, :global
[x, @x, @@x, $x]
}, {:ivar_x => @x, :cvar_x => @@x}
end

should 'use @ignore over @not_isolated_vars in case @ignore is specified' do
x, @x, @@x, $x = 'lx', 'ix', 'cx', 'gx'
should_have_expected_binding \
SerializableProc.new(isolate: :all, ignore: :local) {
@@_not_isolated_vars = :local, :global
[x, @x, @@x, $x]
}, {:ivar_x => @x, :cvar_x => @@x, :gvar_x => $x}
end
end