private_please 0.0.2 → 0.0.3

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 (39) hide show
  1. data/.ruby-version +1 -0
  2. data/CHANGELOG +6 -0
  3. data/README.md +51 -7
  4. data/TODO +11 -13
  5. data/lib/private_please/candidate.rb +41 -0
  6. data/lib/private_please/report/reporter.rb +52 -0
  7. data/lib/private_please/report/templates/simple.txt.erb +61 -0
  8. data/lib/private_please/ruby_backports.rb +22 -0
  9. data/lib/private_please/storage/calls_store.rb +41 -0
  10. data/lib/private_please/storage/candidates_store.rb +48 -0
  11. data/lib/private_please/storage/methods_names.rb +9 -0
  12. data/lib/private_please/storage/methods_names_bucket.rb +69 -0
  13. data/lib/private_please/tracking/extension.rb +19 -0
  14. data/lib/private_please/tracking/instrumentor.rb +71 -0
  15. data/lib/private_please/tracking/instruments_all_below.rb +41 -0
  16. data/lib/private_please/tracking/line_change_tracker.rb +36 -0
  17. data/lib/private_please/version.rb +1 -1
  18. data/lib/private_please.rb +33 -50
  19. data/private_please.gemspec +1 -0
  20. data/sample.rb +68 -0
  21. data/spec/01_marking_candidate_methods_to_observe_spec.rb +153 -0
  22. data/spec/02_logging_calls_on_candidate_methods_spec.rb +39 -0
  23. data/spec/04_instrumented_program_activity_observation_result_spec.rb +89 -0
  24. data/spec/fixtures/sample_class_for_report.rb +54 -0
  25. data/spec/fixtures/sample_class_with_all_calls_combinations.rb +69 -0
  26. data/spec/spec_helper.rb +52 -1
  27. data/spec/units/calls_store_spec.rb +15 -0
  28. data/spec/units/candidates_store_spec.rb +55 -0
  29. metadata +58 -28
  30. data/lib/private_please/candidates.rb +0 -37
  31. data/lib/private_please/configuration.rb +0 -21
  32. data/lib/private_please/line_change_tracker.rb +0 -19
  33. data/lib/private_please/recorder.rb +0 -34
  34. data/lib/private_please/report/template.txt.erb +0 -15
  35. data/lib/private_please/report.rb +0 -47
  36. data/spec/01_marking_methods_spec.rb +0 -30
  37. data/spec/02_calling_methods_spec.rb +0 -58
  38. data/spec/03_configuration_spec.rb +0 -52
  39. data/spec/04_at_exit_report_printing_spec.rb +0 -47
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 1.8.7
data/CHANGELOG CHANGED
@@ -1,3 +1,9 @@
1
+ v0.0.3
2
+ - private_please without parameters observes all the methods defined afterwards
3
+ - PP is active once you require the gem
4
+ - modules' instance methods are observable too
5
+ - big code cleanup (development)
6
+
1
7
  v0.0.2
2
8
  - only display the report if PrivatePlease.active?
3
9
 
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # PrivatePlease
2
2
 
3
- TODO: Write a gem description
3
+ limitation : Ruby 1.8.7
4
4
 
5
5
  ## Installation
6
6
 
@@ -8,17 +8,61 @@ Add this line to your application's Gemfile:
8
8
 
9
9
  gem 'private_please'
10
10
 
11
- And then execute:
11
+ ## Usage
12
12
 
13
- $ bundle
13
+ ```ruby
14
14
 
15
- Or install it yourself as:
15
+ require 'private_please' # step 1
16
16
 
17
- $ gem install private_please
17
+ class CouldBeMorePrivate
18
+ def to_s # not observed -> won't appear in the report.
19
+ # ...
20
+ end
18
21
 
19
- ## Usage
22
+ private_please # step 2 : start observing
23
+
24
+ def do_the_thing # is called by class' users => must stay public (case #1)
25
+ part_1
26
+ part_2
27
+ end
28
+ def part_1 ; end # only called by #do_the_thing => should be private (case #2)
29
+ def part_2 ; end # only called by #do_the_thing => should be private (case #2)
30
+
31
+ def part_3 ; end # is never used -> will be detected. (case #3)
32
+ end
33
+
34
+ c = CouldBeMorePrivate.new
35
+ c.do_the_thing # step 3 : execute the code, so PP can observe and deduce.
36
+ ```
37
+ A report is automatically printed in the console when the program exits.
38
+ For the code above, the report would be :
39
+
40
+ ====================================================================================
41
+ = PrivatePlease report : =
42
+ ====================================================================================
43
+
44
+ **********************************************************
45
+ CouldBeMorePrivate
46
+ **********************************************************
47
+
48
+ * Good candidates : can be made private :
49
+ ------------------------------------------
50
+
51
+ #part_1
52
+ #part_2
53
+
54
+ * Bad candidates : must stay public/protected
55
+ ------------------------------------------
56
+
57
+ #do_the_thing
58
+
59
+ * Methods that were never called
60
+ ------------------------------------------
61
+
62
+ #part_3
63
+
64
+ ====================================================================================
20
65
 
21
- TODO: Write usage instructions here
22
66
 
23
67
  ## Contributing
24
68
 
data/TODO CHANGED
@@ -1,22 +1,20 @@
1
1
 
2
- DSL
3
- - global method marking :
4
-
5
- ex:
2
+ install only in classes (? and modules)
3
+ def self.install
4
+ Object.send :include, PrivatePlease::Tracking::Extension
5
+ ^^^^^^
6
+ TOO WIDE
7
+ end
6
8
 
7
- private_please
8
- def foo ..
9
- def bar ..
10
- public
11
9
 
12
- is the same as :
13
- ...
14
- def foo ..
15
- def bar ..
16
- private_please :foo, :bar
10
+ DSL
11
+ - observe class methods too
17
12
 
18
13
  Report :
14
+ - option : show file and line number of good candidate definition
15
+ - option : show code snippet around the good candidate
19
16
  - display list of methods that were never called
17
+ - display candidates that are already private
20
18
 
21
19
  Doc
22
20
  - write README
@@ -0,0 +1,41 @@
1
+ # Holds the details of 1 method that was marked via `private_please`.
2
+
3
+ module PrivatePlease
4
+ class Candidate
5
+
6
+ def initialize(klass, method_name, is_instance_method)
7
+ @klass, @method_name, @is_instance_method = klass, method_name, is_instance_method
8
+ @klass_name = klass.to_s
9
+ end
10
+
11
+ def self.for_instance_method(klass, method_name)
12
+ new(klass, method_name, true)
13
+ end
14
+
15
+ def self.for_class_method(klass, method_name)
16
+ new(klass, method_name, false)
17
+ end
18
+
19
+ #----------------------------------------------------------------------------
20
+ # QUERIES:
21
+ #----------------------------------------------------------------------------
22
+
23
+ attr_reader :klass,
24
+ :klass_name,
25
+ :method_name,
26
+ :is_instance_method
27
+
28
+ alias_method :instance_method?, :is_instance_method
29
+
30
+ def already_instrumented?
31
+ candidates_store.stored?(self)
32
+ end
33
+
34
+ #----------------------------------------------------------------------------
35
+ private
36
+
37
+ def candidates_store
38
+ PrivatePlease.candidates_store
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,52 @@
1
+ require 'erb'
2
+ module PrivatePlease ; module Report
3
+ class Reporter
4
+
5
+ TEMPLATE_PATH = File.expand_path(File.dirname(__FILE__) + '/templates/simple.txt.erb')
6
+
7
+ attr_reader :candidates_store, :calls_store,
8
+ :good_candidates, :bad_candidates,
9
+ :good_candidates_c, :bad_candidates_c,
10
+ :never_called_candidates, :never_called_candidates_c,
11
+ :building_time
12
+
13
+
14
+ def initialize(candidates_store, calls_store)
15
+ @candidates_store = candidates_store
16
+ @calls_store = calls_store
17
+
18
+ prepare_report_data
19
+ end
20
+
21
+ def to_s
22
+ erb = ERB.new(File.read(TEMPLATE_PATH), 0, "%<>")
23
+ erb.result(binding)
24
+ end
25
+
26
+ private
27
+
28
+ def prepare_report_data
29
+ start_time = Time.now
30
+ @bad_candidates = calls_store.external_calls .clone
31
+ @bad_candidates_c = calls_store.class_external_calls.clone
32
+ # TODO : optimize
33
+ @good_candidates = calls_store.internal_calls .clone.remove(@bad_candidates)
34
+ @good_candidates_c= calls_store.class_internal_calls.clone.remove(@bad_candidates_c)
35
+
36
+ @never_called_candidates = candidates_store.instance_methods.clone.
37
+ remove(@good_candidates).
38
+ remove(@bad_candidates )
39
+
40
+ @never_called_candidates_c = candidates_store.class_methods.clone.
41
+ remove(@good_candidates_c).
42
+ remove(@bad_candidates_c )
43
+ @building_time = Time.now - start_time
44
+
45
+ @candidates_classes_names = (candidates_store.instance_methods.classes_names +
46
+ candidates_store.class_methods .classes_names ).uniq.sort
47
+ @good_candidates_classes_names = (@good_candidates_c.classes_names + @good_candidates.classes_names).uniq.sort
48
+ @bad_candidates_classes_names = (@bad_candidates_c .classes_names + @bad_candidates .classes_names).uniq.sort
49
+ @never_called_candidates_classes_names = (@never_called_candidates_c .classes_names + @never_called_candidates.classes_names).uniq.sort
50
+ end
51
+ end
52
+ end end
@@ -0,0 +1,61 @@
1
+ ====================================================================================
2
+ = PrivatePlease report : =
3
+ ====================================================================================
4
+ % @candidates_classes_names .sort.each do |class_name|
5
+ %
6
+ % good_lines =
7
+ % [good_candidates_c.get_methods_names(class_name).collect{|n|".#{n}"},
8
+ % good_candidates .get_methods_names(class_name).collect{|n|"##{n}"}
9
+ % ].reject(&:empty?)
10
+ % good_lines.insert(1, '') if good_lines.length == 2
11
+ % good_lines.flatten!
12
+ %
13
+ % bad_lines =
14
+ % [bad_candidates_c.get_methods_names(class_name).collect{|n|".#{n}"},
15
+ % bad_candidates .get_methods_names(class_name).collect{|n|"##{n}"}
16
+ % ].reject(&:empty?)
17
+ % bad_lines.insert(1, '') if bad_lines.length == 2
18
+ % bad_lines.flatten!
19
+
20
+ % never_called_lines =
21
+ % [never_called_candidates_c.get_methods_names(class_name).collect{|n|".#{n}"},
22
+ % never_called_candidates .get_methods_names(class_name).collect{|n|"##{n}"}
23
+ % ]
24
+ % never_called_lines.insert(1, '') if never_called_lines.length == 2
25
+
26
+ %
27
+ **********************************************************
28
+ <%= class_name
29
+ %>
30
+ **********************************************************
31
+ %
32
+ % unless good_lines.empty?
33
+
34
+ * Good candidates : can be made private :
35
+ ------------------------------------------
36
+
37
+ % good_lines.each do |line|
38
+ <%= line %>
39
+ % end
40
+ % end
41
+ % unless bad_lines.empty?
42
+
43
+ * Bad candidates : must stay public/protected
44
+ ------------------------------------------
45
+
46
+ % bad_lines.each do |line|
47
+ <%= line %>
48
+ % end
49
+ % end
50
+ %
51
+ % unless never_called_lines.empty?
52
+
53
+ * Methods that were never called
54
+ ------------------------------------------
55
+ % never_called_lines.each do |line|
56
+ <%= line %>
57
+ % end
58
+ % end
59
+
60
+ % end
61
+ ====================================================================================
@@ -0,0 +1,22 @@
1
+ # Unify the Ruby APIs across versions
2
+ #
3
+
4
+ # src : https://github.com/marcandre/backports/blob/master/lib/backports/1.9.1/kernel/define_singleton_method.rb
5
+ unless Kernel.method_defined? :define_singleton_method
6
+ module Kernel
7
+ def define_singleton_method(*args, &block)
8
+ class << self
9
+ self
10
+ end.send(:define_method, *args, &block)
11
+ end
12
+ end
13
+ end
14
+
15
+ # src: https://github.com/marcandre/backports/blob/master/lib/backports/1.9.2/kernel/singleton_class.rb
16
+ unless Kernel.method_defined? :singleton_class
17
+ module Kernel
18
+ def singleton_class
19
+ class << self; self; end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,41 @@
1
+ # As the main program runs, the various method calls are logged here
2
+ # (only for the methods marked with `private_please`)
3
+
4
+ module PrivatePlease
5
+ module Storage
6
+
7
+ class CallsStore
8
+
9
+ def initialize
10
+ @internal_calls = MethodsNamesBucket.new
11
+ @external_calls = MethodsNamesBucket.new
12
+ @class_internal_calls = MethodsNamesBucket.new
13
+ @class_external_calls = MethodsNamesBucket.new
14
+ end
15
+
16
+ #--------------------------------------------------------------------------
17
+ # QUERIES:
18
+ #--------------------------------------------------------------------------
19
+
20
+ attr_reader :internal_calls,
21
+ :external_calls,
22
+ :class_internal_calls,
23
+ :class_external_calls
24
+
25
+ #--------------------------------------------------------------------------
26
+ # COMMANDS:
27
+ #--------------------------------------------------------------------------
28
+
29
+ def store_outside_call(candidate)
30
+ bucket = candidate.instance_method? ? external_calls : class_external_calls
31
+ bucket.add_method_name(candidate.klass_name, candidate.method_name)
32
+ end
33
+
34
+ def store_inside_call(candidate)
35
+ bucket = candidate.instance_method? ? internal_calls : class_internal_calls
36
+ bucket.add_method_name(candidate.klass_name, candidate.method_name)
37
+ end
38
+ end
39
+
40
+ end
41
+ end
@@ -0,0 +1,48 @@
1
+ # Holds in 2 "buckets" the details (class name + methods names) of the
2
+ # methods that are candidate for privatization.
3
+ # Those methods were marked via `private_please` in the code.
4
+ # Instance methods and class methods are kept separate.
5
+
6
+ module PrivatePlease
7
+ module Storage
8
+
9
+ class CandidatesStore
10
+
11
+ def initialize
12
+ @instance_methods = MethodsNamesBucket.new
13
+ @class_methods = MethodsNamesBucket.new
14
+ end
15
+
16
+ #--------------------------------------------------------------------------
17
+ # QUERIES:
18
+ #--------------------------------------------------------------------------
19
+
20
+ attr_reader :instance_methods,
21
+ :class_methods
22
+
23
+ def empty?
24
+ instance_methods.empty? && class_methods.empty?
25
+ end
26
+
27
+ def stored?(candidate)
28
+ bucket_for(candidate).get_methods_names(candidate.klass_name).include?(candidate.method_name)
29
+ end
30
+
31
+ #--------------------------------------------------------------------------
32
+ # COMMANDS:
33
+ #--------------------------------------------------------------------------
34
+
35
+ def store(candidate)
36
+ bucket_for(candidate).add_method_name(candidate.klass_name, candidate.method_name)
37
+ end
38
+
39
+ #--------------------------------------------------------------------------
40
+ private
41
+
42
+ def bucket_for(candidate)
43
+ candidate.instance_method? ? @instance_methods : @class_methods
44
+ end
45
+
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,9 @@
1
+ module PrivatePlease
2
+ module Storage
3
+
4
+ class MethodsNames < Set
5
+ # based on Set => no risk of duplicates when using #add
6
+ end
7
+
8
+ end
9
+ end
@@ -0,0 +1,69 @@
1
+ # Associates and indexes classes (name) and some of their methods (names).
2
+ # Ex:
3
+ # +-------------+---------------------------------------+
4
+ # | class name => 1+ methods names |
5
+ # +-------------+---------------------------------------+
6
+ # | 'Foo' | MethodsNames.new('foo to_baz to_bar') |
7
+ # | 'Bar' | MethodsNames.new('qux') |
8
+ # +-------------+---------------------------------------+
9
+
10
+ module PrivatePlease
11
+ module Storage
12
+
13
+ class MethodsNamesBucket < Hash
14
+
15
+ def initialize
16
+ super{|hash, class_name|
17
+ #hash[class_name] = PrivatePlease::Storage::MethodsNames.new
18
+ hash.set_methods_names(class_name, MethodsNames.new)
19
+ }
20
+ end
21
+
22
+ def clone
23
+ (self.class).new.tap do |klone|
24
+ classes_names.each do |class_name|
25
+ klone.set_methods_names(class_name, (self).get_methods_names(class_name))
26
+ end
27
+ end
28
+ end
29
+
30
+ #--------------------------------------------------------------------------
31
+ # QUERIES:
32
+ #--------------------------------------------------------------------------
33
+
34
+ alias_method :classes_names, :keys
35
+ alias_method :get_methods_names, :[]
36
+ # undef :[], :keys # should be undef-ed, but that complexifies the test #TODO : undef :[], :keys
37
+ # ( => must define ==(other) and cast type of 'other')
38
+
39
+ #--------------------------------------------------------------------------
40
+ # COMMANDS:
41
+ #--------------------------------------------------------------------------
42
+
43
+ alias_method :set_methods_names, :[]=
44
+ undef :[]=
45
+
46
+ def add_method_name(class_name, method_name)
47
+ self.get_methods_names(class_name).add(method_name)
48
+ end
49
+
50
+ def remove(other)
51
+ other.classes_names.each do |cn|
52
+ next if (methods_to_remove = other.get_methods_names(cn)).empty?
53
+ next if (methods_before = self .get_methods_names(cn)).empty?
54
+ difference = methods_before - methods_to_remove
55
+ self.set_methods_names(cn, difference)
56
+ end
57
+ prune!
58
+ self
59
+ end
60
+
61
+ private
62
+
63
+ def prune!
64
+ self.reject!{|_, v|v.empty?}
65
+ end
66
+ end
67
+
68
+ end
69
+ end
@@ -0,0 +1,19 @@
1
+ module PrivatePlease ; module Tracking
2
+
3
+ module Extension
4
+
5
+ def private_please(*methods_to_observe)
6
+ parameterless_call = methods_to_observe.empty?
7
+ klass = self
8
+
9
+ if parameterless_call
10
+ klass.send :include, PrivatePlease::Tracking::InstrumentsAllBelow
11
+
12
+ else
13
+ Instrumentor.instrument_instance_methods_for_pp_observation(klass, methods_to_observe)
14
+ end
15
+ end
16
+
17
+ end
18
+
19
+ end end
@@ -0,0 +1,71 @@
1
+ module PrivatePlease ; module Tracking
2
+
3
+ module Instrumentor
4
+
5
+ def self.instrument_instance_methods_for_pp_observation(klass, methods_to_observe)
6
+ class_instance_methods = klass.instance_methods.collect(&:to_sym)
7
+ methods_to_observe = methods_to_observe.collect(&:to_sym)
8
+ # reject invalid methods names
9
+ methods_to_observe.reject! do |m|
10
+ already_defined_instance_method = class_instance_methods.include?(m)
11
+ invalid = !already_defined_instance_method
12
+ end
13
+
14
+ methods_to_observe.each do |method_name|
15
+ candidate = Candidate.for_instance_method(klass, method_name)
16
+ instrument_candidate_for_pp_observation(candidate) # end
17
+ end
18
+ end
19
+
20
+
21
+ def self.instrument_candidate_for_pp_observation(candidate)
22
+ return if candidate.already_instrumented?
23
+ PrivatePlease.remember_candidate(candidate)
24
+
25
+ klass, method_name = candidate.klass, candidate.method_name
26
+ candidate.instance_method? ?
27
+ instrument_instance_method_with_pp_observation(klass, method_name) :
28
+ instrument_class_method_with_pp_observation( klass, method_name)
29
+ end
30
+
31
+
32
+
33
+ def self.instrument_class_method_with_pp_observation(klass, method_name)
34
+ orig_method = klass.singleton_class.instance_method(method_name)
35
+ klass.class_eval <<RUBY
36
+ define_singleton_method(method_name) do |*args, &blk| # def self.observed_method_i(..)
37
+ set_trace_func(nil) #don't track activity while here #
38
+ #
39
+ zelf_class=self #
40
+ candidate = PrivatePlease::Candidate.for_class_method(zelf_class, method_name)
41
+ PrivatePlease.after_method_call(candidate, LineChangeTracker.outside_class_method_call_detected?(zelf_class))
42
+ #
43
+ set_trace_func(LineChangeTracker::MY_TRACE_FUN) #
44
+ # make the call : #
45
+ orig_method.bind(self).call(*args, &blk) # <call original method>
46
+ end # end
47
+ RUBY
48
+ end
49
+ private_class_method :instrument_class_method_with_pp_observation
50
+
51
+
52
+ def self.instrument_instance_method_with_pp_observation(klass, method_name)
53
+ orig_method = klass.instance_method(method_name)
54
+ klass.class_eval <<RUBY
55
+ define_method(method_name) do |*args, &blk| # def observed_method_i(..)
56
+ set_trace_func(nil) #don't track activity while here #
57
+ #
58
+ candidate = PrivatePlease::Candidate.for_instance_method(self.class, method_name)
59
+ PrivatePlease.after_method_call(candidate, LineChangeTracker.outside_instance_method_call_detected?(self)) #
60
+ #
61
+ set_trace_func(LineChangeTracker::MY_TRACE_FUN) #
62
+ # make the call : #
63
+ orig_method.bind(self).call(*args, &blk) # <call original method>
64
+ end # end
65
+ RUBY
66
+ end
67
+ private_class_method :instrument_instance_method_with_pp_observation
68
+
69
+ end
70
+
71
+ end end
@@ -0,0 +1,41 @@
1
+ # Usage :
2
+ # class MarkingTest::Automatic2
3
+ # def foo ; end <---- but not this one.
4
+ # include PrivatePlease::Tracking::InstrumentsAllBelow <-- add this line
5
+ #
6
+ # def baz ; end <---- to observe this method
7
+ # protected
8
+ # def self.qux ; end <---- and this one too
9
+ # end
10
+
11
+ module PrivatePlease ; module Tracking
12
+
13
+ module InstrumentsAllBelow
14
+ include PrivatePlease::Tracking::Extension
15
+
16
+ def self.included(base)
17
+
18
+ def base.singleton_method_added(method_name)
19
+ return if [:method_added, :singleton_method_added].include?(method_name)
20
+ return if [:included].include?(method_name) && !self.is_a?(Class)
21
+
22
+ is_private_class_method = singleton_class.private_method_defined?(method_name)
23
+ return if is_private_class_method
24
+
25
+ candidate = Candidate.for_class_method(klass = self, method_name)
26
+ Tracking::Instrumentor.instrument_candidate_for_pp_observation(candidate)
27
+ end
28
+
29
+
30
+ def base.method_added(method_name)
31
+ is_private_instance_method = self.private_method_defined?(method_name)
32
+ return if is_private_instance_method
33
+
34
+ candidate = Candidate.for_instance_method(klass = self, method_name)
35
+ Tracking::Instrumentor.instrument_candidate_for_pp_observation(candidate)
36
+ end
37
+ end
38
+
39
+ end
40
+
41
+ end end
@@ -0,0 +1,36 @@
1
+ module PrivatePlease ; module Tracking
2
+
3
+ class LineChangeTracker
4
+ class << self
5
+ attr_accessor :prev_prev_self, :prev_self, :curr_self
6
+ @@prev_self = @@curr_self = nil
7
+ end
8
+
9
+ MY_TRACE_FUN = lambda do |event, file, line, id, binding, klass|
10
+ return unless 'line'==event
11
+ LineChangeTracker.prev_prev_self = LineChangeTracker.prev_self
12
+ LineChangeTracker.prev_self = LineChangeTracker.curr_self
13
+ LineChangeTracker.curr_self = (eval 'self', binding)
14
+ #puts "my : #{event} in #{file}/#{line} id:#{id} klass:#{klass} - self = #{(eval'self', binding).inspect}"
15
+ end
16
+
17
+ def self.outside_instance_method_call_detected?(zelf)
18
+ caller_class != zelf.class
19
+ end
20
+
21
+ def self.outside_class_method_call_detected?(zelf_class)
22
+ caller_class != zelf_class
23
+ end
24
+
25
+ private
26
+
27
+ def self.caller_class
28
+ call_initiator = LineChangeTracker.prev_self
29
+ (caller_is_class_method = call_initiator.is_a?(Class)) ?
30
+ call_initiator :
31
+ call_initiator.class
32
+ end
33
+ end
34
+ end end
35
+
36
+ set_trace_func(PrivatePlease::Tracking::LineChangeTracker::MY_TRACE_FUN) #
@@ -1,3 +1,3 @@
1
1
  module PrivatePlease
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.3"
3
3
  end