mojombo-ernie 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 +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
|