mojombo-ernie 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +2 -0
- data/LICENSE +20 -0
- data/README.md +79 -0
- data/Rakefile +64 -0
- data/VERSION.yml +4 -0
- data/bin/ernie +67 -0
- data/ebin/ernie_server_app.app +2 -0
- data/elib/ernie_server.erl +104 -0
- data/elib/ernie_server_app.erl +13 -0
- data/elib/ernie_server_sup.erl +21 -0
- data/elib/port_wrapper.erl +63 -0
- data/ext/Makefile +2 -0
- data/ext/extconf.rb +1 -0
- data/lib/ernie.rb +126 -0
- data/test/ernie_test.rb +18 -0
- data/test/test_helper.rb +12 -0
- metadata +80 -0
data/.document
ADDED
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Tom Preston-Werner
|
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.md
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
Ernie
|
2
|
+
=====
|
3
|
+
|
4
|
+
By Tom Preston-Werner (tom@mojombo.com)
|
5
|
+
|
6
|
+
WARNING: This software is alpha and should not be used in production without
|
7
|
+
extensive testing. You should not consider this project production ready until
|
8
|
+
it is released as 1.0.
|
9
|
+
|
10
|
+
|
11
|
+
Description
|
12
|
+
-----------
|
13
|
+
|
14
|
+
Ernie is a BERT-RPC server implementation that uses an Erlang server to accept incoming connections, and then delegates the request to a Ruby handler via Erlectricity.
|
15
|
+
|
16
|
+
|
17
|
+
Installation
|
18
|
+
------------
|
19
|
+
|
20
|
+
You must have Erlang installed before installing Ernie.
|
21
|
+
|
22
|
+
gem install mojombo-ernie -s http://gems.github.com
|
23
|
+
|
24
|
+
|
25
|
+
Running
|
26
|
+
-------
|
27
|
+
|
28
|
+
Usage: ernie [options] <handler>
|
29
|
+
-n, --name NAME Node name
|
30
|
+
-p, --port PORT Port
|
31
|
+
-d, --detached Run as a daemon
|
32
|
+
-P, --pidfile PIDFILE Location to write pid file.
|
33
|
+
|
34
|
+
|
35
|
+
Example Handler
|
36
|
+
---------------
|
37
|
+
|
38
|
+
require 'ernie'
|
39
|
+
|
40
|
+
mod(:calc) do
|
41
|
+
fun(:add) do |a, b|
|
42
|
+
a + b
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
Example BERT-RPC call for above example
|
48
|
+
---------------------------------------
|
49
|
+
|
50
|
+
-> {call, calc, add, [1, 2]}
|
51
|
+
|
52
|
+
<- {reply, 3}
|
53
|
+
|
54
|
+
|
55
|
+
Contribute
|
56
|
+
----------
|
57
|
+
|
58
|
+
If you'd like to hack on Ernie, start by forking my repo on GitHub:
|
59
|
+
|
60
|
+
http://github.com/mojombo/ernie
|
61
|
+
|
62
|
+
To get all of the dependencies, install the gem first. The best way to get
|
63
|
+
your changes merged back into core is as follows:
|
64
|
+
|
65
|
+
1. Clone down your fork
|
66
|
+
1. Create a topic branch to contain your change
|
67
|
+
1. Hack away
|
68
|
+
1. Add tests and make sure everything still passes by running `rake`
|
69
|
+
1. If you are adding new functionality, document it in the README.md
|
70
|
+
1. Do not change the version number, I will do that on my end
|
71
|
+
1. If necessary, rebase your commits into logical chunks, without errors
|
72
|
+
1. Push the branch up to GitHub
|
73
|
+
1. Send me (mojombo) a pull request for your branch
|
74
|
+
|
75
|
+
|
76
|
+
Copyright
|
77
|
+
---------
|
78
|
+
|
79
|
+
Copyright (c) 2009 Tom Preston-Werner. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "ernie"
|
8
|
+
gem.summary = %Q{TODO}
|
9
|
+
gem.email = "tom@mojombo.com"
|
10
|
+
gem.homepage = "http://github.com/mojombo/ernie"
|
11
|
+
gem.authors = ["Tom Preston-Werner"]
|
12
|
+
gem.files.include(["ext"])
|
13
|
+
gem.extensions << 'ext/extconf.rb'
|
14
|
+
gem.add_dependency('erlectricity', '>= 1.0.1')
|
15
|
+
|
16
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
17
|
+
end
|
18
|
+
rescue LoadError
|
19
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
20
|
+
end
|
21
|
+
|
22
|
+
require 'rake/testtask'
|
23
|
+
Rake::TestTask.new(:test) do |test|
|
24
|
+
test.libs << 'lib' << 'test'
|
25
|
+
test.pattern = 'test/**/*_test.rb'
|
26
|
+
test.verbose = true
|
27
|
+
end
|
28
|
+
|
29
|
+
begin
|
30
|
+
require 'rcov/rcovtask'
|
31
|
+
Rcov::RcovTask.new do |test|
|
32
|
+
test.libs << 'test'
|
33
|
+
test.pattern = 'test/**/*_test.rb'
|
34
|
+
test.verbose = true
|
35
|
+
end
|
36
|
+
rescue LoadError
|
37
|
+
task :rcov do
|
38
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
task :default => :test
|
43
|
+
|
44
|
+
# require 'rake/rdoctask'
|
45
|
+
# Rake::RDocTask.new do |rdoc|
|
46
|
+
# if File.exist?('VERSION.yml')
|
47
|
+
# config = YAML.load(File.read('VERSION.yml'))
|
48
|
+
# version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
|
49
|
+
# else
|
50
|
+
# version = ""
|
51
|
+
# end
|
52
|
+
#
|
53
|
+
# rdoc.rdoc_dir = 'rdoc'
|
54
|
+
# rdoc.title = "ernie #{version}"
|
55
|
+
# rdoc.rdoc_files.include('README*')
|
56
|
+
# rdoc.rdoc_files.include('lib/**/*.rb')
|
57
|
+
# end
|
58
|
+
|
59
|
+
task :ebuild do
|
60
|
+
ERLC_TEST_FLAGS = ""
|
61
|
+
ERLC_FLAGS = "-o ../ebin"
|
62
|
+
cd "elib"
|
63
|
+
sh "erlc #{ERLC_FLAGS} #{ERLC_TEST_FLAGS} #{Dir["**/*.erl"].join(" ")}"
|
64
|
+
end
|
data/VERSION.yml
ADDED
data/bin/ernie
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$:.unshift(File.join(File.dirname(__FILE__), *%w[.. lib]))
|
4
|
+
ERNIE_ROOT = File.join(File.dirname(__FILE__), *%w[..])
|
5
|
+
|
6
|
+
DEFAULT_ERLANG_CODEPATHS = %w[ebin]
|
7
|
+
|
8
|
+
def rel(path)
|
9
|
+
File.join(ERNIE_ROOT, path)
|
10
|
+
end
|
11
|
+
|
12
|
+
def code_paths
|
13
|
+
DEFAULT_ERLANG_CODEPATHS.map {|n| "-pz #{rel(n)}" }.join(" ") + " \\"
|
14
|
+
end
|
15
|
+
|
16
|
+
require 'optparse'
|
17
|
+
require 'pp'
|
18
|
+
|
19
|
+
help = <<HELP
|
20
|
+
Ernie is an Erlang/Ruby BERT-RPC Server.
|
21
|
+
|
22
|
+
Basic Command Line Usage:
|
23
|
+
ernie [options] <path to handler file>
|
24
|
+
|
25
|
+
Options:
|
26
|
+
HELP
|
27
|
+
|
28
|
+
options = {}
|
29
|
+
OptionParser.new do |opts|
|
30
|
+
opts.banner = help
|
31
|
+
|
32
|
+
opts.on("-p PORT", "--port PORT", "Port") do |x|
|
33
|
+
options[:port] = x
|
34
|
+
end
|
35
|
+
|
36
|
+
opts.on("-d", "--detached", "Run as a daemon") do
|
37
|
+
options[:detached] = true
|
38
|
+
end
|
39
|
+
|
40
|
+
opts.on("-P", "--pidfile PIDFILE", "Location to write pid file.") do |x|
|
41
|
+
options[:pidfile] = x
|
42
|
+
end
|
43
|
+
end.parse!
|
44
|
+
|
45
|
+
handler = ARGV[0]
|
46
|
+
|
47
|
+
unless handler
|
48
|
+
puts "A handler must be specified: ernie /path/to/handler.rb"
|
49
|
+
exit(1)
|
50
|
+
end
|
51
|
+
|
52
|
+
port = options[:port] || 8000
|
53
|
+
pidfile = options[:pidfile] ? "-ernie_server_app pidfile \"'#{options[:pidfile]}'\"" : ''
|
54
|
+
detached = options[:detached] ? '-detached' : ''
|
55
|
+
|
56
|
+
cmd = %Q{erl -boot start_sasl \
|
57
|
+
#{detached} \
|
58
|
+
+Bc \
|
59
|
+
+K true \
|
60
|
+
-smp enable \
|
61
|
+
#{code_paths}
|
62
|
+
#{pidfile} \
|
63
|
+
-ernie_server_app port #{port} \
|
64
|
+
-ernie_server_app handler '"#{handler}"' \
|
65
|
+
-run ernie_server_app boot}.squeeze(' ')
|
66
|
+
puts cmd
|
67
|
+
exec(cmd)
|
@@ -0,0 +1,104 @@
|
|
1
|
+
-module(ernie_server).
|
2
|
+
-behaviour(gen_server).
|
3
|
+
|
4
|
+
%% api
|
5
|
+
-export([start_link/1, start/1]).
|
6
|
+
|
7
|
+
%% gen_server callbacks
|
8
|
+
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
9
|
+
terminate/2, code_change/3]).
|
10
|
+
|
11
|
+
-record(state, {lsock = undefined,
|
12
|
+
ducky = undefined}).
|
13
|
+
|
14
|
+
%%====================================================================
|
15
|
+
%% API
|
16
|
+
%%====================================================================
|
17
|
+
|
18
|
+
start_link(Args) ->
|
19
|
+
gen_server:start_link({global, ?MODULE}, ?MODULE, Args, []).
|
20
|
+
|
21
|
+
start(Args) ->
|
22
|
+
gen_server:start({global, ?MODULE}, ?MODULE, Args, []).
|
23
|
+
|
24
|
+
%%====================================================================
|
25
|
+
%% gen_server callbacks
|
26
|
+
%%====================================================================
|
27
|
+
|
28
|
+
%%--------------------------------------------------------------------
|
29
|
+
%% Function: init(Args) -> {ok, State} |
|
30
|
+
%% {ok, State, Timeout} |
|
31
|
+
%% ignore |
|
32
|
+
%% {stop, Reason}
|
33
|
+
%% Description: Initiates the server
|
34
|
+
%%--------------------------------------------------------------------
|
35
|
+
init([Port, Handler]) ->
|
36
|
+
process_flag(trap_exit, true),
|
37
|
+
error_logger:info_msg("~p starting~n", [?MODULE]),
|
38
|
+
Ducky = port_wrapper:wrap("ruby " ++ Handler),
|
39
|
+
{ok, LSock} = try_listen(Port, 500),
|
40
|
+
spawn(fun() -> loop(LSock, Ducky) end),
|
41
|
+
{ok, #state{lsock = LSock, ducky = Ducky}}.
|
42
|
+
|
43
|
+
%%--------------------------------------------------------------------
|
44
|
+
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
|
45
|
+
%% {reply, Reply, State, Timeout} |
|
46
|
+
%% {noreply, State} |
|
47
|
+
%% {noreply, State, Timeout} |
|
48
|
+
%% {stop, Reason, Reply, State} |
|
49
|
+
%% {stop, Reason, State}
|
50
|
+
%% Description: Handling call messages
|
51
|
+
%%--------------------------------------------------------------------
|
52
|
+
handle_call(_Request, _From, State) ->
|
53
|
+
{reply, ok, State}.
|
54
|
+
|
55
|
+
%%--------------------------------------------------------------------
|
56
|
+
%% Function: handle_cast(Msg, State) -> {noreply, State} |
|
57
|
+
%% {noreply, State, Timeout} |
|
58
|
+
%% {stop, Reason, State}
|
59
|
+
%% Description: Handling cast messages
|
60
|
+
%%--------------------------------------------------------------------
|
61
|
+
handle_cast(_Msg, State) -> {noreply, State}.
|
62
|
+
|
63
|
+
handle_info(Msg, State) ->
|
64
|
+
error_logger:error_msg("Unexpected message: ~p~n", [Msg]),
|
65
|
+
{noreply, State}.
|
66
|
+
|
67
|
+
terminate(_Reason, _State) -> ok.
|
68
|
+
code_change(_OldVersion, State, _Extra) -> {ok, State}.
|
69
|
+
|
70
|
+
%%====================================================================
|
71
|
+
%% Internal
|
72
|
+
%%====================================================================
|
73
|
+
|
74
|
+
try_listen(Port, 0) ->
|
75
|
+
error_logger:error_msg("Could not listen on port ~p~n", [Port]),
|
76
|
+
{error, "Could not listen on port"};
|
77
|
+
try_listen(Port, Times) ->
|
78
|
+
Res = gen_tcp:listen(Port, [binary, {packet, 4}, {active, false}]),
|
79
|
+
case Res of
|
80
|
+
{ok, LSock} ->
|
81
|
+
error_logger:info_msg("Listening on port ~p~n", [Port]),
|
82
|
+
{ok, LSock};
|
83
|
+
{error, Reason} ->
|
84
|
+
error_logger:info_msg("Could not listen on port ~p: ~p~n", [Port, Reason]),
|
85
|
+
timer:sleep(5000),
|
86
|
+
try_listen(Port, Times - 1)
|
87
|
+
end.
|
88
|
+
|
89
|
+
loop(LSock, Ducky) ->
|
90
|
+
{ok, Sock} = gen_tcp:accept(LSock),
|
91
|
+
spawn(fun() -> handle_method(Sock, Ducky) end),
|
92
|
+
loop(LSock, Ducky).
|
93
|
+
|
94
|
+
handle_method(Sock, Ducky) ->
|
95
|
+
case gen_tcp:recv(Sock, 0) of
|
96
|
+
{ok, BinaryTerm} ->
|
97
|
+
% error_logger:info_msg("From Internet: ~p~n", [BinaryTerm]),
|
98
|
+
{ok, Data} = port_wrapper:rpc(Ducky, BinaryTerm),
|
99
|
+
% error_logger:info_msg("From Port: ~p~n", [Data]),
|
100
|
+
gen_tcp:send(Sock, Data),
|
101
|
+
ok = gen_tcp:close(Sock);
|
102
|
+
{error, closed} ->
|
103
|
+
ok = gen_tcp:close(Sock)
|
104
|
+
end.
|
@@ -0,0 +1,21 @@
|
|
1
|
+
-module(ernie_server_sup).
|
2
|
+
-behaviour(supervisor).
|
3
|
+
-export([start_link/0, init/1]).
|
4
|
+
|
5
|
+
start_link() ->
|
6
|
+
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
|
7
|
+
|
8
|
+
init([]) ->
|
9
|
+
{ok, Port} = application:get_env(ernie_server_app, port),
|
10
|
+
io:format("Using port ~p~n", [Port]),
|
11
|
+
{ok, Handler} = application:get_env(ernie_server_app, handler),
|
12
|
+
io:format("Using handler ~p~n", [Handler]),
|
13
|
+
case application:get_env(ernie_server_app, pidfile) of
|
14
|
+
{ok, Location} ->
|
15
|
+
Pid = os:getpid(),
|
16
|
+
ok = file:write_file(Location, list_to_binary(Pid));
|
17
|
+
undefined -> ok
|
18
|
+
end,
|
19
|
+
{ok, {{one_for_one, 1, 60},
|
20
|
+
[{ernie_server, {ernie_server, start_link, [[Port, Handler]]},
|
21
|
+
permanent, brutal_kill, worker, [ernie_server]}]}}.
|
@@ -0,0 +1,63 @@
|
|
1
|
+
-module(port_wrapper).
|
2
|
+
-export([wrap/1, wrap/2, wrap_link/1, wrap_link/2, send/2, shutdown/1, rpc/2]).
|
3
|
+
|
4
|
+
wrap(Command) ->
|
5
|
+
spawn(fun() -> process_flag(trap_exit, true), Port = create_port(Command), loop(Port, infinity, Command) end).
|
6
|
+
wrap(Command, Timeout) ->
|
7
|
+
spawn(fun() -> process_flag(trap_exit, true), Port = create_port(Command), loop(Port, Timeout, Command) end).
|
8
|
+
|
9
|
+
wrap_link(Command) ->
|
10
|
+
spawn_link(fun() -> process_flag(trap_exit, true), Port = create_port(Command), link(Port), loop(Port, infinity, Command) end).
|
11
|
+
wrap_link(Command, Timeout) ->
|
12
|
+
spawn_link(fun() -> process_flag(trap_exit, true), Port = create_port(Command), link(Port), loop(Port, Timeout, Command) end).
|
13
|
+
|
14
|
+
rpc(WrappedPort, Message) ->
|
15
|
+
send(WrappedPort, Message),
|
16
|
+
receive
|
17
|
+
{WrappedPort, Result} -> {ok, Result}
|
18
|
+
after 15000 ->
|
19
|
+
{error, timed_out, WrappedPort}
|
20
|
+
end.
|
21
|
+
|
22
|
+
send(WrappedPort, Message) ->
|
23
|
+
WrappedPort ! {self(), {command, Message}},
|
24
|
+
WrappedPort.
|
25
|
+
|
26
|
+
shutdown(WrappedPort) ->
|
27
|
+
WrappedPort ! shutdown,
|
28
|
+
true.
|
29
|
+
|
30
|
+
create_port(Command) ->
|
31
|
+
open_port({spawn, Command}, [{packet, 4}, nouse_stdio, exit_status, binary]).
|
32
|
+
|
33
|
+
loop(Port, Timeout, Command) ->
|
34
|
+
receive
|
35
|
+
noose ->
|
36
|
+
port_close(Port),
|
37
|
+
noose;
|
38
|
+
shutdown ->
|
39
|
+
port_close(Port),
|
40
|
+
exit(shutdown);
|
41
|
+
{Source, {command, Message}} ->
|
42
|
+
Port ! {self(), {command, Message}},
|
43
|
+
receive
|
44
|
+
{Port, {data, Result}} ->
|
45
|
+
Source ! {self(), Result}
|
46
|
+
after Timeout ->
|
47
|
+
error_logger:error_msg("Port Wrapper ~p timed out in mid operation (~p)!~n", [self(),Message]),
|
48
|
+
% We timed out, which means we need to close and then restart the port
|
49
|
+
port_close(Port), % Should SIGPIPE the child.
|
50
|
+
exit(timed_out)
|
51
|
+
end,
|
52
|
+
loop(Port,Timeout,Command);
|
53
|
+
{Port, {exit_status, _Code}} ->
|
54
|
+
% Hard and Unanticipated Crash
|
55
|
+
error_logger:error_msg( "Port closed! ~p~n", [Port] ),
|
56
|
+
exit({error, _Code});
|
57
|
+
{'EXIT',_Pid,shutdown} ->
|
58
|
+
port_close(Port),
|
59
|
+
exit(shutdown);
|
60
|
+
Any ->
|
61
|
+
error_logger:warning_msg("PortWrapper ~p got unexpected message: ~p~n", [self(), Any]),
|
62
|
+
loop(Port, Timeout, Command)
|
63
|
+
end.
|
data/ext/Makefile
ADDED
data/ext/extconf.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# does nothing, Makefile is handwritten
|
data/lib/ernie.rb
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'erlectricity'
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
class Ernie
|
6
|
+
class << self
|
7
|
+
attr_accessor :mods, :current_mod, :logger
|
8
|
+
end
|
9
|
+
|
10
|
+
self.mods = {}
|
11
|
+
self.current_mod = nil
|
12
|
+
self.logger = nil
|
13
|
+
|
14
|
+
def self.mod(name, block)
|
15
|
+
m = Mod.new(name)
|
16
|
+
self.current_mod = m
|
17
|
+
self.mods[name] = m
|
18
|
+
block.call
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.fun(name, block)
|
22
|
+
self.current_mod.fun(name, block)
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.logfile(file)
|
26
|
+
self.logger = Logger.new(file)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.log(text)
|
30
|
+
self.logger.info(text) if self.logger
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.convert(item)
|
34
|
+
if item.instance_of?(Hash)
|
35
|
+
a = [:dict]
|
36
|
+
item.each_pair { |k, v| a << [convert(k), convert(v)] }
|
37
|
+
a
|
38
|
+
elsif item.instance_of?(Array)
|
39
|
+
item.map { |x| convert(x) }
|
40
|
+
else
|
41
|
+
item
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.deconvert(item)
|
46
|
+
if item.instance_of?(Array)
|
47
|
+
if item.first == :dict
|
48
|
+
item[1..-1].inject({}) do |acc, x|
|
49
|
+
acc[deconvert(x[0])] = deconvert(x[1]); acc
|
50
|
+
end
|
51
|
+
else
|
52
|
+
item.map { |x| deconvert(x) }
|
53
|
+
end
|
54
|
+
else
|
55
|
+
item
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.dispatch(mod, fun, args)
|
60
|
+
xargs = deconvert(args)
|
61
|
+
self.log("-- " + [mod, fun, xargs].inspect)
|
62
|
+
res = self.mods[mod].funs[fun].call(*xargs)
|
63
|
+
convert(res)
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.start
|
67
|
+
self.log("Starting")
|
68
|
+
self.log(self.mods.inspect)
|
69
|
+
receive do |f|
|
70
|
+
f.when([:call, Symbol, Symbol, Array]) do |mod, fun, args|
|
71
|
+
self.log("-> " + [:call, mod, fun, args].inspect)
|
72
|
+
begin
|
73
|
+
res = self.dispatch(mod, fun, args)
|
74
|
+
xres = [:reply, res]
|
75
|
+
self.log("<- " + xres.inspect)
|
76
|
+
f.send!(xres)
|
77
|
+
rescue Object => e
|
78
|
+
xres = [:error, [:user, 0, e.message]]
|
79
|
+
self.log("<- " + xres.inspect)
|
80
|
+
self.log(e.backtrace.join("\n"))
|
81
|
+
f.send!(xres)
|
82
|
+
end
|
83
|
+
f.receive_loop
|
84
|
+
end
|
85
|
+
|
86
|
+
f.when(Any) do |any|
|
87
|
+
self.log("-> " + any.inspect)
|
88
|
+
xres = [:error, [:server, 0, "Invalid request: #{any.inspect}"]]
|
89
|
+
self.log("<- " + xres.inspect)
|
90
|
+
f.send!(xres)
|
91
|
+
f.receive_loop
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
class Ernie::Mod
|
98
|
+
attr_accessor :name, :funs
|
99
|
+
|
100
|
+
def initialize(name)
|
101
|
+
self.name = name
|
102
|
+
self.funs = {}
|
103
|
+
end
|
104
|
+
|
105
|
+
def fun(name, block)
|
106
|
+
self.funs[name] = block
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# Root level calls
|
111
|
+
|
112
|
+
def mod(name, &block)
|
113
|
+
Ernie.mod(name, block)
|
114
|
+
end
|
115
|
+
|
116
|
+
def fun(name, &block)
|
117
|
+
Ernie.fun(name, block)
|
118
|
+
end
|
119
|
+
|
120
|
+
def logfile(name)
|
121
|
+
Ernie.logfile(name)
|
122
|
+
end
|
123
|
+
|
124
|
+
at_exit do
|
125
|
+
Ernie.start unless $test
|
126
|
+
end
|
data/test/ernie_test.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ErnieTest < Test::Unit::TestCase
|
4
|
+
context "mod" do
|
5
|
+
should "add a mod to the mods hash" do
|
6
|
+
mod(:foo) { }
|
7
|
+
assert Ernie.mods[:foo]
|
8
|
+
assert Ernie.mods[:foo].instance_of?(Ernie::Mod)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
context "fun" do
|
13
|
+
should "add a fun to the funs hash of the mod" do
|
14
|
+
mod(:foo) { fun(:bar) { } }
|
15
|
+
assert Ernie.mods[:foo].funs[:bar]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/test/test_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mojombo-ernie
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tom Preston-Werner
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-05-18 00:00:00 -07:00
|
13
|
+
default_executable: ernie
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: erlectricity
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 1.0.1
|
24
|
+
version:
|
25
|
+
description:
|
26
|
+
email: tom@mojombo.com
|
27
|
+
executables:
|
28
|
+
- ernie
|
29
|
+
extensions:
|
30
|
+
- ext/extconf.rb
|
31
|
+
extra_rdoc_files:
|
32
|
+
- LICENSE
|
33
|
+
- README.md
|
34
|
+
files:
|
35
|
+
- .document
|
36
|
+
- .gitignore
|
37
|
+
- LICENSE
|
38
|
+
- README.md
|
39
|
+
- Rakefile
|
40
|
+
- VERSION.yml
|
41
|
+
- bin/ernie
|
42
|
+
- ebin/ernie_server_app.app
|
43
|
+
- elib/ernie_server.erl
|
44
|
+
- elib/ernie_server_app.erl
|
45
|
+
- elib/ernie_server_sup.erl
|
46
|
+
- elib/port_wrapper.erl
|
47
|
+
- ext/Makefile
|
48
|
+
- ext/extconf.rb
|
49
|
+
- lib/ernie.rb
|
50
|
+
- test/ernie_test.rb
|
51
|
+
- test/test_helper.rb
|
52
|
+
has_rdoc: true
|
53
|
+
homepage: http://github.com/mojombo/ernie
|
54
|
+
post_install_message:
|
55
|
+
rdoc_options:
|
56
|
+
- --charset=UTF-8
|
57
|
+
require_paths:
|
58
|
+
- lib
|
59
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: "0"
|
64
|
+
version:
|
65
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: "0"
|
70
|
+
version:
|
71
|
+
requirements: []
|
72
|
+
|
73
|
+
rubyforge_project:
|
74
|
+
rubygems_version: 1.2.0
|
75
|
+
signing_key:
|
76
|
+
specification_version: 2
|
77
|
+
summary: TODO
|
78
|
+
test_files:
|
79
|
+
- test/ernie_test.rb
|
80
|
+
- test/test_helper.rb
|