otaku 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
data/HISTORY.txt ADDED
@@ -0,0 +1,4 @@
1
+ === 0.1.0 2010-07-18
2
+
3
+ first gem release! [#ngty]
4
+
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 NgTzeYang
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,96 @@
1
+ = OTAKU
2
+
3
+ Dead simple server/client service built using eventmachine.
4
+
5
+ == Introduction
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.
8
+
9
+ == Getting Started
10
+
11
+ It's hosted on rubygems.org:
12
+
13
+ $ sudo gem install otaku
14
+
15
+ == Using It
16
+
17
+ === 1. Starting service & defining handler
18
+
19
+ require 'otaku'
20
+
21
+ Otaku.start do |data|
22
+ result = '~ %s ~' % data
23
+ end
24
+
25
+ === 2. Sending processing request
26
+
27
+ require 'otaku'
28
+
29
+ Otaku.process('hello')
30
+ # >> '~ hello ~'
31
+
32
+ === Unfortunately ...
33
+
34
+ Most of the times, we won't have anything as simple as above, the following illustrates the problem of contextual reference:
35
+
36
+ mark = '*'
37
+ Otaku.start do |data|
38
+ '%s %s %s' % [mark, data, mark]
39
+ end
40
+
41
+ Otaku.process('hello') # failure !!
42
+
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:
44
+
45
+ Otaku.start(:mark => '*') do |data|
46
+ '%s %s %s' % [mark, data, mark]
47
+ end
48
+
49
+ Otaku.process('hello') # >> '* hello *'
50
+
51
+ == Configuraing It
52
+
53
+ Otaku ships with the following defaults:
54
+
55
+ Otaku.address # >> '127.0.0.1'
56
+ Otaku.port # >> 10999
57
+ Otaku.init_wait_time # >> 2
58
+ Otaku.log_file # >> '/tmp/otaku.log'
59
+
60
+ Configuring can be done via:
61
+
62
+ === 1. Configuration Proc
63
+
64
+ Otaku.configure do |config|
65
+ config.init_wait_time = 10
66
+ # (more typing, more customizing)
67
+ end
68
+
69
+ === 2. Configuration Hash
70
+
71
+ Otaku.configure({
72
+ :init_wait_time => 10
73
+ # (more typing, more customizing)
74
+ })
75
+
76
+ === 3. Writer Method
77
+
78
+ Otaku.init_wait_time = 10
79
+
80
+ == TODO
81
+
82
+ 1. Currently, only integration testing is done ...
83
+
84
+ == Note on Patches/Pull Requests
85
+
86
+ * Fork the project.
87
+ * Make your feature addition or bug fix.
88
+ * Add tests for it. This is important so I don't break it in a
89
+ future version unintentionally.
90
+ * Commit, do not mess with rakefile, version, or history.
91
+ (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)
92
+ * Send me a pull request. Bonus points for topic branches.
93
+
94
+ == Copyright
95
+
96
+ Copyright (c) since 2010 NgTzeYang. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,82 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "otaku"
8
+ gem.summary = %Q{Dead simple server/client service built using eventmachine}
9
+ gem.description = %Q{}
10
+ gem.email = "ngty77@gmail.com"
11
+ gem.homepage = "http://github.com/ngty/otaku"
12
+ gem.authors = ["NgTzeYang"]
13
+ gem.add_development_dependency "bacon", ">= 0"
14
+ gem.add_dependency "eventmachine", ">= 0.12.10"
15
+ gem.add_dependency "ruby2ruby", ">= 1.2.4"
16
+ # TODO: Should eventually remove requirement for ParseTree !!
17
+ gem.add_dependency "ParseTree", ">= 3.0.5"
18
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
19
+ end
20
+ Jeweler::GemcutterTasks.new
21
+ rescue LoadError
22
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
23
+ end
24
+
25
+ require 'rake/testtask'
26
+ Rake::TestTask.new(:spec) do |spec|
27
+ spec.libs << 'lib' << 'spec'
28
+ spec.pattern = 'spec/**/*_spec.rb'
29
+ spec.verbose = true
30
+ end
31
+
32
+ begin
33
+ require 'rcov/rcovtask'
34
+ Rcov::RcovTask.new do |spec|
35
+ spec.libs << 'spec'
36
+ spec.pattern = 'spec/**/*_spec.rb'
37
+ spec.verbose = true
38
+ end
39
+ rescue LoadError
40
+ task :rcov do
41
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
42
+ end
43
+ end
44
+
45
+ task :spec => :check_dependencies
46
+
47
+ begin
48
+ require 'reek/adapters/rake_task'
49
+ Reek::RakeTask.new do |t|
50
+ t.fail_on_error = true
51
+ t.verbose = false
52
+ t.source_files = 'lib/**/*.rb'
53
+ end
54
+ rescue LoadError
55
+ task :reek do
56
+ abort "Reek is not available. In order to run reek, you must: sudo gem install reek"
57
+ end
58
+ end
59
+
60
+ begin
61
+ require 'roodi'
62
+ require 'roodi_task'
63
+ RoodiTask.new do |t|
64
+ t.verbose = false
65
+ end
66
+ rescue LoadError
67
+ task :roodi do
68
+ abort "Roodi is not available. In order to run roodi, you must: sudo gem install roodi"
69
+ end
70
+ end
71
+
72
+ task :default => :spec
73
+
74
+ require 'rake/rdoctask'
75
+ Rake::RDocTask.new do |rdoc|
76
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
77
+
78
+ rdoc.rdoc_dir = 'rdoc'
79
+ rdoc.title = "otaku #{version}"
80
+ rdoc.rdoc_files.include('README*')
81
+ rdoc.rdoc_files.include('lib/**/*.rb')
82
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
data/lib/otaku.rb ADDED
@@ -0,0 +1,235 @@
1
+ require 'rubygems'
2
+ require 'eventmachine'
3
+ require 'logger'
4
+ require 'base64'
5
+ require 'ruby2ruby'
6
+
7
+ # TODO: Preliminary try, should be removed eventually !! Should use RubyParser instead.
8
+ require 'parse_tree'
9
+ require 'parse_tree_extensions'
10
+
11
+ module Otaku
12
+
13
+ class HandlerNotDefinedError < Exception ; end
14
+ class DataProcessError < Exception ; end
15
+
16
+ # //////////////////////////////////////////////////////////////////////////////////////////
17
+ # Otaku
18
+ # //////////////////////////////////////////////////////////////////////////////////////////
19
+
20
+ DEFAULTS = {
21
+ :address => '127.0.0.1',
22
+ :port => 10999,
23
+ :log_file => '/tmp/otaku.log',
24
+ :init_wait_time => 2
25
+ }
26
+
27
+ class << self
28
+
29
+ attr_accessor *DEFAULTS.keys
30
+
31
+ def configure(config = {}, &block)
32
+ block_given? ? yield(self) :
33
+ config.each{|name, val| send(:"#{name}=", val) }
34
+ end
35
+
36
+ def config
37
+ DEFAULTS.keys.inject({}) do |memo, name|
38
+ memo.merge(name => send(name))
39
+ end
40
+ end
41
+
42
+ def start(context = {}, &handler)
43
+ raise HandlerNotDefinedError unless block_given?
44
+ Server.handler = Handler.new(context, handler)
45
+ Server.start
46
+ end
47
+
48
+ def stop
49
+ Server.stop
50
+ end
51
+
52
+ def process(data)
53
+ Client.get(data)
54
+ end
55
+
56
+ Otaku.configure(DEFAULTS)
57
+
58
+ end
59
+
60
+ # //////////////////////////////////////////////////////////////////////////////////////////
61
+ # Otaku::Encoder
62
+ # //////////////////////////////////////////////////////////////////////////////////////////
63
+
64
+ module Encoder
65
+ class << self
66
+
67
+ def encode(thing)
68
+ Base64.encode64(Marshal.dump(thing))
69
+ end
70
+
71
+ def decode(thing)
72
+ Marshal.load(Base64.decode64(thing))
73
+ end
74
+
75
+ end
76
+ end
77
+
78
+ # //////////////////////////////////////////////////////////////////////////////////////////
79
+ # Otaku::Handler
80
+ # //////////////////////////////////////////////////////////////////////////////////////////
81
+
82
+ class Handler
83
+
84
+ def initialize(context, handler)
85
+ @context = __context_as_code__(context)
86
+ @proc = __proc_as_code__(handler)
87
+ end
88
+
89
+ def [](data)
90
+ eval(@context).instance_exec(data, &eval(@proc))
91
+ end
92
+
93
+ private
94
+
95
+ def __proc_as_code__(block)
96
+ Ruby2Ruby.new.process(block.to_sexp)
97
+ end
98
+
99
+ def __context_as_code__(methods_hash)
100
+ 'Class.new{ %s }.new' %
101
+ methods_hash.map do |method, val|
102
+ "def #{method}; Encoder.decode(%|#{Encoder.encode(val).gsub('|','\|')}|); end"
103
+ end.join('; ')
104
+ end
105
+
106
+ end
107
+
108
+ # //////////////////////////////////////////////////////////////////////////////////////////
109
+ # Otaku::Server
110
+ # //////////////////////////////////////////////////////////////////////////////////////////
111
+
112
+ module Server
113
+
114
+ class << self
115
+
116
+ attr_accessor :handler
117
+
118
+ def start(other_process=false)
119
+ other_process ? run_evented_server : (
120
+ # print '[Otaku] initializing at %s:%s ... ' % [Otaku.address, Otaku.port] # DBUG
121
+ run_server_script
122
+ # puts 'done [pid#%s]' % @process.pid # DEBUG
123
+ )
124
+ end
125
+
126
+ def run_evented_server
127
+ log 'started with pid #%s' % Process.pid,
128
+ 'listening at %s:%s' % [Otaku.address, Otaku.port]
129
+ EventMachine::run { EventMachine::start_server(Otaku.address, Otaku.port, EM) }
130
+ end
131
+
132
+ def run_server_script
133
+ args = Encoder.encode({
134
+ :config => Otaku.config,
135
+ :handler => @handler
136
+ })
137
+ @process = IO.popen(%|ruby #{__FILE__} "#{args.gsub('"','\"')}"|,'r')
138
+ sleep Otaku.init_wait_time
139
+ end
140
+
141
+ def stop
142
+ Process.kill('SIGHUP', @process.pid) if @process
143
+ end
144
+
145
+ def log(*msgs)
146
+ @logger ||= Logger.new(Otaku.log_file)
147
+ msgs.each{|msg| @logger << "[Otaku] %s\n" % msg }
148
+ end
149
+
150
+ def cleanup
151
+ @logger.close
152
+ end
153
+
154
+ end
155
+
156
+ private
157
+
158
+ module EM #:nodoc:
159
+
160
+ def receive_data(data)
161
+ log 'receives data: %s' % data.inspect
162
+ result = process_data(data)
163
+ log 'returning result: %s' % result.inspect
164
+ send_data(Encoder.encode(result))
165
+ end
166
+
167
+ def process_data(data)
168
+ begin
169
+ Server.handler[data]
170
+ rescue
171
+ error = DataProcessError.new($!.inspect)
172
+ log(error.inspect)
173
+ error
174
+ end
175
+ end
176
+
177
+ def log(*msg)
178
+ Server.log(*msg)
179
+ end
180
+
181
+ end
182
+
183
+ end
184
+
185
+ # //////////////////////////////////////////////////////////////////////////////////////////
186
+ # Otaku::Client
187
+ # //////////////////////////////////////////////////////////////////////////////////////////
188
+
189
+ module Client
190
+
191
+ class << self
192
+ def get(data)
193
+ EventMachine::run do
194
+ EventMachine::connect(Otaku.address, Otaku.port, EM).
195
+ execute(data) do |data|
196
+ @result = Encoder.decode(data)
197
+ end
198
+ end
199
+ @result
200
+ end
201
+ end
202
+
203
+ private
204
+
205
+ module EM #:nodoc:
206
+
207
+ def receive_data(data)
208
+ result = @callback.call(data)
209
+ result.is_a?(DataProcessError) ? raise(result) : result
210
+ EventMachine::stop_event_loop # ends loop & resumes program flow
211
+ end
212
+
213
+ def execute(method, &callback)
214
+ @callback = callback
215
+ send_data(method)
216
+ end
217
+
218
+ end
219
+
220
+ end
221
+ end
222
+
223
+ if $0 == __FILE__ && (encoded_data = ARGV[0])
224
+ begin
225
+ include Otaku
226
+ data = Encoder.decode(encoded_data)
227
+ Otaku.configure(data[:config])
228
+ Server.handler = data[:handler]
229
+ Server.start(true)
230
+ rescue
231
+ Server.log($!.inspect)
232
+ ensure
233
+ Server.cleanup
234
+ end
235
+ end
@@ -0,0 +1,30 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe "Otaku doing processing" do
4
+
5
+ after do
6
+ Otaku.stop
7
+ end
8
+
9
+ should 'succeed when using proc that has no contextual reference' do
10
+ Otaku.start{|data| '~ %s ~' % data }
11
+ Otaku.process('hello').should.equal '~ hello ~'
12
+ end
13
+
14
+ should 'fail when using proc that has contextual reference yet has no specified context' do
15
+ mark = '*'
16
+ Otaku.start{|data| '%s %s %s' % [mark, data, mark] }
17
+ lambda { Otaku.process('hello') }.should.raise(Otaku::DataProcessError).
18
+ message.should.match(/#<NameError: undefined local variable or method `mark' for /)
19
+ end
20
+
21
+ should 'succeed when using proc that has contextual reference & has context specified' do
22
+ Otaku.start(:mark => '*') {|data| '%s %s %s' % [mark, data, mark] }
23
+ Otaku.process('hello').should.equal('* hello *')
24
+ end
25
+
26
+ should 'raise Otaku::HandlerNotDefinedError when processing wo specified proc' do
27
+ lambda { Otaku.start }.should.raise(Otaku::HandlerNotDefinedError)
28
+ end
29
+
30
+ end
@@ -0,0 +1,8 @@
1
+ require 'rubygems'
2
+ require 'bacon'
3
+
4
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ require 'otaku'
7
+
8
+ Bacon.summary_on_exit
metadata ADDED
@@ -0,0 +1,139 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: otaku
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - NgTzeYang
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-07-18 00:00:00 +08:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: bacon
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :development
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: eventmachine
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ hash: 59
44
+ segments:
45
+ - 0
46
+ - 12
47
+ - 10
48
+ version: 0.12.10
49
+ type: :runtime
50
+ version_requirements: *id002
51
+ - !ruby/object:Gem::Dependency
52
+ name: ruby2ruby
53
+ prerelease: false
54
+ requirement: &id003 !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ hash: 23
60
+ segments:
61
+ - 1
62
+ - 2
63
+ - 4
64
+ version: 1.2.4
65
+ type: :runtime
66
+ version_requirements: *id003
67
+ - !ruby/object:Gem::Dependency
68
+ name: ParseTree
69
+ prerelease: false
70
+ requirement: &id004 !ruby/object:Gem::Requirement
71
+ none: false
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ hash: 13
76
+ segments:
77
+ - 3
78
+ - 0
79
+ - 5
80
+ version: 3.0.5
81
+ type: :runtime
82
+ version_requirements: *id004
83
+ description: ""
84
+ email: ngty77@gmail.com
85
+ executables: []
86
+
87
+ extensions: []
88
+
89
+ extra_rdoc_files:
90
+ - LICENSE
91
+ - README.rdoc
92
+ files:
93
+ - .document
94
+ - .gitignore
95
+ - HISTORY.txt
96
+ - LICENSE
97
+ - README.rdoc
98
+ - Rakefile
99
+ - VERSION
100
+ - lib/otaku.rb
101
+ - spec/processing_spec.rb
102
+ - spec/spec_helper.rb
103
+ has_rdoc: true
104
+ homepage: http://github.com/ngty/otaku
105
+ licenses: []
106
+
107
+ post_install_message:
108
+ rdoc_options:
109
+ - --charset=UTF-8
110
+ require_paths:
111
+ - lib
112
+ required_ruby_version: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ hash: 3
118
+ segments:
119
+ - 0
120
+ version: "0"
121
+ required_rubygems_version: !ruby/object:Gem::Requirement
122
+ none: false
123
+ requirements:
124
+ - - ">="
125
+ - !ruby/object:Gem::Version
126
+ hash: 3
127
+ segments:
128
+ - 0
129
+ version: "0"
130
+ requirements: []
131
+
132
+ rubyforge_project:
133
+ rubygems_version: 1.3.7
134
+ signing_key:
135
+ specification_version: 3
136
+ summary: Dead simple server/client service built using eventmachine
137
+ test_files:
138
+ - spec/processing_spec.rb
139
+ - spec/spec_helper.rb