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 +5 -0
- data/.gitignore +21 -0
- data/HISTORY.txt +4 -0
- data/LICENSE +20 -0
- data/README.rdoc +96 -0
- data/Rakefile +82 -0
- data/VERSION +1 -0
- data/lib/otaku.rb +235 -0
- data/spec/processing_spec.rb +30 -0
- data/spec/spec_helper.rb +8 -0
- metadata +139 -0
data/.document
ADDED
data/.gitignore
ADDED
data/HISTORY.txt
ADDED
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
|
data/spec/spec_helper.rb
ADDED
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
|