otaku 0.2.2 → 0.3.0

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.
@@ -1,3 +1,11 @@
1
+ === 0.3.0 2010-08-05
2
+
3
+ = Features
4
+ * Proc can now be passed as a context to Otaku.start [#ngty]
5
+
6
+ = Maintenance
7
+ * Cleaning up Otaku::Handler [#ngty]
8
+
1
9
  === 0.2.2 2010-07-21
2
10
 
3
11
  = Features
@@ -4,7 +4,11 @@ Dead simple server/client service built using eventmachine.
4
4
 
5
5
  == Introduction
6
6
 
7
- Otaku's original intent is to support testing of cross-process stubbing in cross-stub (http://github.com/ngty/cross-stub). It's usefulness in other aspects of my hacking life prompts me to extract it out, & package it as a generic solution. Its primary intent is to be dead simple to use & easy to customize, of course, both criteria subjected to very my own tastes.
7
+ Otaku's original intent is to support testing of cross-process stubbing in
8
+ cross-stub (http://github.com/ngty/cross-stub). It's usefulness in other
9
+ aspects of my hacking life prompts me to extract it out, & package it as a
10
+ generic solution. Its primary intent is to be dead simple to use & easy to
11
+ customize, of course, both criteria subjected to very my own tastes.
8
12
 
9
13
  == Getting Started
10
14
 
@@ -31,7 +35,8 @@ It's hosted on rubygems.org:
31
35
 
32
36
  === Unfortunately ...
33
37
 
34
- Most of the times, we won't have anything as simple as above, the following illustrates the problem of contextual reference:
38
+ Most of the times, we won't have anything as simple as above, the following
39
+ illustrates the problem of contextual reference:
35
40
 
36
41
  mark = '*'
37
42
  Otaku.start do |data|
@@ -40,7 +45,9 @@ Most of the times, we won't have anything as simple as above, the following illu
40
45
 
41
46
  Otaku.process('hello') # failure !!
42
47
 
43
- The reason is that the proc that we passed to Otaku.start is being marshalled while being passed to the server as a handler, in the process, the contextual references are lost. A workaround for this problem is:
48
+ The reason is that the proc that we passed to Otaku.start is being marshalled
49
+ while being passed to the server as a handler, in the process, the contextual
50
+ references are lost. A workaround for this problem is:
44
51
 
45
52
  Otaku.start(:mark => '*') do |data|
46
53
  '%s %s %s' % [mark, data, mark]
@@ -48,6 +55,11 @@ The reason is that the proc that we passed to Otaku.start is being marshalled wh
48
55
 
49
56
  Otaku.process('hello') # >> '* hello *'
50
57
 
58
+ Anything that can be marshalled can be passed as a context. The exception is
59
+ proc, which cannot be marshalled, yet it can still be passed in. However, in
60
+ the process, all contextual references within the proc are lost, so u may
61
+ want to exercise care when playing with proc.
62
+
51
63
  == Configuraing It
52
64
 
53
65
  Otaku ships with the following defaults:
@@ -88,8 +100,9 @@ Configuring can be done via:
88
100
  * Make your feature addition or bug fix.
89
101
  * Add tests for it. This is important so I don't break it in a
90
102
  future version unintentionally.
91
- * Commit, do not mess with rakefile, version, or history.
92
- (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
103
+ * Commit, do not mess with rakefile, version, or history. (if you want to have
104
+ your own version, that is fine but bump version in a commit by itself I can
105
+ ignore when I pull)
93
106
  * Send me a pull request. Bonus points for topic branches.
94
107
 
95
108
  == Copyright
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.2
1
+ 0.3.0
@@ -0,0 +1,17 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'otaku')
2
+
3
+ Otaku.port = 10901
4
+
5
+ Otaku.start do |test_file|
6
+ unless $o_stdout
7
+ $o_stdout, $o_stderr = $stdout, $stderr
8
+ $stdout, $stderr = StringIO.new, StringIO.new
9
+ end
10
+ MiniTest::Unit::TestCase.reset
11
+ require File.join(File.dirname(__FILE__), test_file)
12
+ MiniTest::Unit.new.run
13
+ $stdout.rewind
14
+ $stdout.lines
15
+ end
16
+
17
+
@@ -0,0 +1,56 @@
1
+ require 'rubygems'
2
+ require 'pp'
3
+ require File.join(File.dirname(__FILE__), '..', '..', 'lib', 'otaku')
4
+
5
+ Otaku.port = 10901
6
+
7
+ Otaku.start do |test_file|
8
+
9
+ unless $o_stdout
10
+ $o_stdout, $o_stderr = $stdout, $stderr
11
+ $stdout, $stderr = StringIO.new, StringIO.new
12
+ end
13
+
14
+ require 'rubygems'
15
+ require 'minitest/unit'
16
+ MiniTest::Unit::TestCase.reset
17
+ require test_file
18
+ MiniTest::Unit.new.run
19
+
20
+ results = ($stdout.rewind; $stdout).lines.inject({}) do |memo, line|
21
+ case line
22
+ when /(\d+) assertions, (\d+) failures, (\d+) errors, (\d+) skips/
23
+ a, f, e, s = [$1, $2, $3, $4].map(&:to_i)
24
+ memo[:stats] = {:assertions => a, :failures => f, :errors => e, :skips => s})
25
+ when /Finished in ([\d\.]+) seconds/
26
+ memo[:time_taken] = "#{$1} seconds"
27
+ else
28
+ (memo[:buffer] ||= []) << line
29
+ end
30
+ memo
31
+ end
32
+
33
+ unless [:errors, :failures].all?{|type| results[:stats][type].zero? }
34
+ results[:errors] = (results.delete(:buffer) || []).inject([]) do |errors, line|
35
+ latest = errors[-1]
36
+ if title = line[/^ (\d+\) (Error|Failure)\:)/,1]
37
+ errors << [title]
38
+ elsif latest && [1,2].include?(latest.size)
39
+ latest << line.strip
40
+ elsif latest && latest.size == 3 && line.include?(test_file)
41
+ latest << [test_file, line[/\:(\d+)\:/,1]].join(':')
42
+ end
43
+ errors
44
+ end
45
+ end
46
+
47
+ results
48
+ end
49
+
50
+ Dir.glob(File.join(File.expand_path(File.dirname(__FILE__)), 'tests', '*.rb')) do |file|
51
+ pp Otaku.process(file)
52
+ end
53
+
54
+ at_exit do
55
+ Otaku.stop
56
+ end
@@ -0,0 +1,11 @@
1
+ require 'rubygems'
2
+ require 'pp'
3
+
4
+ Dir.glob(File.join(File.expand_path(File.dirname(__FILE__)), 'tests', '*.rb')) do |file|
5
+ require 'rubygems'
6
+ require 'minitest/unit'
7
+ MiniTest::Unit::TestCase.reset
8
+ require file
9
+ MiniTest::Unit.new.run
10
+ end
11
+
@@ -0,0 +1,9 @@
1
+ require 'minitest/unit'
2
+
3
+ class TestA < MiniTest::Unit::TestCase
4
+
5
+ def test_a
6
+ assert true
7
+ end
8
+
9
+ end
@@ -0,0 +1,9 @@
1
+ require 'minitest/unit'
2
+
3
+ class TestB < MiniTest::Unit::TestCase
4
+
5
+ def test_b
6
+ assert true
7
+ end
8
+
9
+ end
@@ -1,4 +1,5 @@
1
1
  require 'rubygems'
2
+ require 'forwardable'
2
3
  require 'eventmachine'
3
4
  require 'logger'
4
5
  require 'base64'
@@ -1,124 +1,32 @@
1
+ require 'otaku/handler/magic_proc'
2
+ require 'otaku/handler/context'
3
+ require 'otaku/handler/processor'
4
+
1
5
  module Otaku
2
6
  class Handler
3
7
 
8
+ attr_reader :context, :processor
9
+
4
10
  def initialize(context, handler)
5
- @context = __context_as_code__(context)
6
- magic_proc = MagicProc.new(handler)
7
- @proc, @file, @line = [:code, :file, :line].map{|meth| magic_proc.send(meth) }
11
+ @context = Context.new(context)
12
+ @processor = Processor.new(handler)
8
13
  end
9
14
 
10
15
  def process(data)
11
- (instance = eval(@context, nil, '(generated class)', 1)).
12
- instance_exec(data, &(block = eval(@proc, nil, @file, @line)))
16
+ @context.eval!.instance_exec(data, &@processor.eval!)
13
17
  end
14
18
 
15
19
  def root
16
- File.dirname(@file)
20
+ File.dirname(@processor.file)
17
21
  end
18
22
 
19
- private
20
-
21
- def __context_as_code__(methods_hash)
22
- 'Class.new{ %s }.new' %
23
- methods_hash.map do |method, val|
24
- "def #{method}; Encoder.decode(%|#{Encoder.encode(val).gsub('|','\|')}|); end"
25
- end.sort.join('; ')
26
- end
27
-
28
- class MagicProc
29
-
30
- RUBY_PARSER = RubyParser.new
31
- RUBY_2_RUBY = Ruby2Ruby.new
32
-
33
- attr_reader :file, :line, :code
34
-
35
- def initialize(block)
36
- @block = block
37
- extract_file_and_line_and_code
38
- end
39
-
40
- private
41
-
42
- def extract_file_and_line_and_code
43
- code, remaining = code_fragments
44
-
45
- while frag = remaining[frag_regexp,1]
46
- begin
47
- sexp = RUBY_PARSER.parse(replace_magic_vars(code += frag))
48
- if sexp.inspect =~ sexp_regexp
49
- @code = revert_magic_vars(RUBY_2_RUBY.process(sexp)).sub('proc {','lambda {')
50
- break
51
- end
52
- rescue SyntaxError, Racc::ParseError, NoMethodError
53
- remaining.sub!(frag,'')
54
- end
55
- end
56
- end
57
-
58
- def code_fragments
59
- ignore, start_marker, arg =
60
- [:ignore, :start_marker, :arg].map{|key| code_match_args[key] }
61
- [
62
- "proc #{start_marker} |#{arg}| ",
63
- source_code.sub(ignore, '')
64
- ]
65
- end
66
-
67
- def sexp_regexp
68
- @sexp_regexp ||= (
69
- Regexp.new([
70
- Regexp.quote("s(:iter, s(:call, nil, :"),
71
- "(proc|lambda)",
72
- Regexp.quote(", s(:arglist)), s(:lasgn, :#{code_match_args[:arg]}), s("),
73
- ].join)
74
- )
75
- end
76
-
77
- def frag_regexp
78
- @frag_regexp ||= (
79
- end_marker = {'do' => 'end', '{' => '\}'}[code_match_args[:start_marker]]
80
- /^(.*?\W#{end_marker})/m
81
- )
82
- end
83
-
84
- def code_regexp
85
- @code_regexp ||=
86
- /^(.*?(Otaku\.start.*?|lambda|proc|Proc\.new)\s*(do|\{)\s*\|(\w+)\|\s*)/m
87
- end
88
-
89
- def code_match_args
90
- @code_match_args ||= (
91
- args = source_code.match(code_regexp)
92
- {
93
- :ignore => args[1],
94
- :start_marker => args[3],
95
- :arg => args[4]
96
- }
97
- )
98
- end
99
-
100
- def source_code
101
- @source_code ||= (
102
- file, line = /^#<Proc:0x[0-9A-Fa-f]+@(.+):(\d+).*?>$/.match(@block.inspect)[1..2]
103
- @file = File.expand_path(file)
104
- @line = line.to_i
105
- File.readlines(@file)[@line.pred .. -1].join
106
- )
107
- end
108
-
109
- def replace_magic_vars(code)
110
- %w{__FILE__ __LINE__}.inject(code) do |code, var|
111
- code.gsub(var, "%|((#{var}))|")
112
- end
113
- end
114
-
115
- def revert_magic_vars(code)
116
- %w{__FILE__ __LINE__}.inject(code) do |code, var|
117
- code.gsub(%|"((#{var}))"|, var)
118
- end
119
- end
23
+ def marshal_dump
24
+ [@context, @processor]
25
+ end
120
26
 
121
- end
27
+ def marshal_load(data)
28
+ @context, @processor = data
29
+ end
122
30
 
123
31
  end
124
32
  end
@@ -0,0 +1,43 @@
1
+ module Otaku
2
+ class Handler
3
+ class Context #:nodoc:
4
+
5
+ attr_reader :code
6
+
7
+ def initialize(methods_hash)
8
+ @code = 'Class.new{ %s }.new' %
9
+ methods_hash.map do |method, val|
10
+ 'def %s; %s; end' % [method, method_body(val)]
11
+ end.sort.join('; ')
12
+ end
13
+
14
+ def eval!
15
+ eval(@code, nil, '(generated class)', 1)
16
+ end
17
+
18
+ def marshal_dump
19
+ [@code]
20
+ end
21
+
22
+ def marshal_load(data)
23
+ @code, _ = data
24
+ end
25
+
26
+ private
27
+
28
+ def method_body(val)
29
+ if val.is_a?(Proc)
30
+ "Encoder.decode(%|#{Encoder.encode(magic_proc(val)).gsub('|','\|')}|).eval!"
31
+ else
32
+ "Encoder.decode(%|#{Encoder.encode(val).gsub('|','\|')}|)"
33
+ end
34
+ end
35
+
36
+ def magic_proc(block)
37
+ MagicProc.new(block)
38
+ end
39
+
40
+ end
41
+ end
42
+ end
43
+
@@ -0,0 +1,116 @@
1
+ module Otaku
2
+ class Handler
3
+ class MagicProc #:nodoc:
4
+
5
+ RUBY_PARSER = RubyParser.new
6
+ RUBY_2_RUBY = Ruby2Ruby.new
7
+
8
+ attr_reader :file, :line, :code
9
+
10
+ def initialize(block)
11
+ @cache = {:block => block}
12
+ extract_file_and_line_and_code
13
+ @cache = nil
14
+ end
15
+
16
+ def eval!
17
+ eval(@code, nil, @file, @line)
18
+ end
19
+
20
+ def marshal_dump
21
+ [@code, @file, @line]
22
+ end
23
+
24
+ def marshal_load(data)
25
+ @code, @file, @line = data
26
+ end
27
+
28
+ private
29
+
30
+ def extract_file_and_line_and_code
31
+ code, remaining = code_fragments
32
+
33
+ while frag = remaining[frag_regexp,1]
34
+ begin
35
+ sexp = RUBY_PARSER.parse(replace_magic_vars(code += frag))
36
+ if sexp.inspect =~ sexp_regexp
37
+ @code = revert_magic_vars(RUBY_2_RUBY.process(sexp)).sub('proc {','lambda {')
38
+ break
39
+ end
40
+ rescue SyntaxError, Racc::ParseError, NoMethodError
41
+ remaining.sub!(frag,'')
42
+ end
43
+ end
44
+ end
45
+
46
+ def code_fragments
47
+ ignore, start_marker, arg =
48
+ [:ignore, :start_marker, :arg].map{|key| code_match_args[key] }
49
+ [
50
+ arg ? "proc #{start_marker} |#{arg}|" : "proc #{start_marker}",
51
+ source_code.sub(ignore, '')
52
+ ]
53
+ end
54
+
55
+ def sexp_regexp
56
+ @cache[:sexp_regexp] ||= (
57
+ Regexp.new([
58
+ Regexp.quote("s(:iter, s(:call, nil, :"),
59
+ "(proc|lambda)",
60
+ Regexp.quote(", s(:arglist)), "),
61
+ '(%s|%s|%s)' % [
62
+ Regexp.quote('s(:masgn, s(:array, s('),
63
+ Regexp.quote('s(:lasgn, :'),
64
+ Regexp.quote('nil, s(')
65
+ ]
66
+ ].join)
67
+ )
68
+ end
69
+
70
+ def frag_regexp
71
+ @cache[:frag_regexp] ||= (
72
+ end_marker = {'do' => 'end', '{' => '\}'}[code_match_args[:start_marker]]
73
+ /^(.*?\W#{end_marker})/m
74
+ )
75
+ end
76
+
77
+ def code_regexp
78
+ @cache[:code_regexp] ||=
79
+ /^(.*?(lambda|proc|Proc\.new)?\s*(do|\{)\s*(\|(.*?)\|\s*)?)/m
80
+ end
81
+
82
+ def code_match_args
83
+ @cache[:code_match_args] ||= (
84
+ args = source_code.match(code_regexp)
85
+ {
86
+ :ignore => args[1],
87
+ :start_marker => args[3],
88
+ :arg => args[5]
89
+ }
90
+ )
91
+ end
92
+
93
+ def source_code
94
+ @cache[:source_code] ||= (
95
+ file, line = /^#<Proc:0x[0-9A-Fa-f]+@(.+):(\d+).*?>$/.match(@cache[:block].inspect)[1..2]
96
+ @file = File.expand_path(file)
97
+ @line = line.to_i
98
+ File.readlines(@file)[@line.pred .. -1].join
99
+ )
100
+ end
101
+
102
+ def replace_magic_vars(code)
103
+ %w{__FILE__ __LINE__}.inject(code) do |code, var|
104
+ code.gsub(var, "%|((#{var}))|")
105
+ end
106
+ end
107
+
108
+ def revert_magic_vars(code)
109
+ %w{__FILE__ __LINE__}.inject(code) do |code, var|
110
+ code.gsub(%|"((#{var}))"|, var)
111
+ end
112
+ end
113
+
114
+ end
115
+ end
116
+ end