otaku 0.1.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.
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