diff --git a/README.rdoc b/README.rdoc index 3ee7614..1f9c600 100644 --- a/README.rdoc +++ b/README.rdoc @@ -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 diff --git a/lib/serializable_proc.rb b/lib/serializable_proc.rb index 14d37ea..0c3388a 100644 --- a/lib/serializable_proc.rb +++ b/lib/serializable_proc.rb @@ -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 # @@ -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) # @@ -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 @@ -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 # diff --git a/lib/serializable_proc/isolatable.rb b/lib/serializable_proc/isolatable.rb index 459db76..f4dafc5 100644 --- a/lib/serializable_proc/isolatable.rb +++ b/lib/serializable_proc/isolatable.rb @@ -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] @@ -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 && !@isolate.include?(: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) diff --git a/spec/isolation/isolation_spec.rb b/spec/isolation/isolation_spec.rb new file mode 100644 index 0000000..0e34aff --- /dev/null +++ b/spec/isolation/isolation_spec.rb @@ -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