angelo 0.3.3 → 0.4.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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +1 -4
- data/CHANGELOG.md +22 -0
- data/Gemfile +5 -4
- data/README.md +101 -57
- data/angelo.gemspec +13 -10
- data/lib/angelo.rb +5 -2
- data/lib/angelo/base.rb +136 -54
- data/lib/angelo/main.rb +47 -0
- data/lib/angelo/minitest/helpers.rb +51 -23
- data/lib/angelo/params_parser.rb +33 -17
- data/lib/angelo/responder.rb +10 -9
- data/lib/angelo/responder/eventsource.rb +21 -20
- data/lib/angelo/responder/websocket.rb +1 -2
- data/lib/angelo/stash.rb +5 -3
- data/lib/angelo/tilt/erb.rb +38 -11
- data/lib/angelo/version.rb +1 -1
- data/test/angelo/erb_spec.rb +65 -3
- data/test/angelo/mustermann_spec.rb +1 -8
- data/test/angelo/options_spec.rb +19 -0
- data/test/angelo/params_spec.rb +27 -2
- data/test/angelo_spec.rb +18 -0
- data/test/main/app.rb +38 -0
- data/test/main/main_spec.rb +101 -0
- data/test/spec_helper.rb +7 -39
- metadata +37 -6
- data/lib/angelo/mustermann.rb +0 -98
data/lib/angelo/main.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require "angelo"
|
2
|
+
|
3
|
+
module Angelo
|
4
|
+
class Base
|
5
|
+
@angelo_main = true
|
6
|
+
|
7
|
+
# Takes a block and/or modules that define methods that can be
|
8
|
+
# used from within request handlers. Methods can also be defined
|
9
|
+
# at the top level but they get defined as Object instance methods
|
10
|
+
# which isn't good.
|
11
|
+
|
12
|
+
module DSL
|
13
|
+
def helpers(*args, &block)
|
14
|
+
args.each do |mod|
|
15
|
+
include(mod)
|
16
|
+
end
|
17
|
+
block && class_exec(&block)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# The intent here is to add the DSL to the top-level object "main" by
|
25
|
+
# creating an anonymous Angelo::Base subclass and forwarding the DSL
|
26
|
+
# methods to it from the top-level object. Then run the app at exit.
|
27
|
+
#
|
28
|
+
# If angelo/main is required from somewhere other than the top level,
|
29
|
+
# all bets are off.
|
30
|
+
|
31
|
+
if self.to_s == "main"
|
32
|
+
# We are probably at the top level.
|
33
|
+
|
34
|
+
require "forwardable"
|
35
|
+
self.extend Forwardable
|
36
|
+
@angelo_app = Class.new(Angelo::Base)
|
37
|
+
self.def_delegators :@angelo_app, *Angelo::Base::DSL.instance_methods
|
38
|
+
|
39
|
+
at_exit do
|
40
|
+
# Don't run @angelo_app on uncaught exceptions including exit
|
41
|
+
# being called which raises SystemExit. The rationale being that
|
42
|
+
# exit means exit, not "run the server".
|
43
|
+
if $!.nil?
|
44
|
+
@angelo_app.run!
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -9,25 +9,25 @@ module Angelo
|
|
9
9
|
|
10
10
|
attr_reader :last_response
|
11
11
|
|
12
|
-
def define_app &block
|
13
|
-
|
12
|
+
def define_app app = nil, &block
|
14
13
|
before do
|
15
|
-
app
|
16
|
-
|
17
|
-
|
14
|
+
if app.nil? && block
|
15
|
+
app = Class.new Angelo::Base do
|
16
|
+
content_type :html # reset
|
17
|
+
class_eval &block
|
18
|
+
end
|
19
|
+
end
|
18
20
|
Celluloid.logger.level = ::Logger::ERROR # see spec_helper.rb:9
|
19
21
|
|
20
|
-
app.class_eval &block
|
21
22
|
@server = Angelo::Server.new app
|
22
23
|
app.server = @server
|
23
|
-
$reactor = Reactor.new
|
24
|
+
$reactor = Reactor.new if $reactor == nil || !$reactor.alive?
|
24
25
|
end
|
25
26
|
|
26
27
|
after do
|
27
28
|
sleep 0.1
|
28
29
|
@server.terminate if @server and @server.alive?
|
29
30
|
end
|
30
|
-
|
31
31
|
end
|
32
32
|
|
33
33
|
def hc
|
@@ -40,12 +40,8 @@ module Angelo
|
|
40
40
|
url
|
41
41
|
end
|
42
42
|
|
43
|
-
def hc_req method, path, params = {}, headers = {}
|
44
|
-
@last_response =
|
45
|
-
hc.__send__ method, url(path), params, headers, &Proc.new
|
46
|
-
else
|
47
|
-
hc.__send__ method, url(path), params, headers
|
48
|
-
end
|
43
|
+
def hc_req method, path, params = {}, headers = {}, &block
|
44
|
+
@last_response = hc.__send__ method, url(path), params, headers, &block
|
49
45
|
end
|
50
46
|
private :hc_req
|
51
47
|
|
@@ -70,15 +66,8 @@ module Angelo
|
|
70
66
|
private :http_req
|
71
67
|
|
72
68
|
[:get, :post, :put, :delete, :options, :head].each do |m|
|
73
|
-
define_method m do |path, params = {}, headers = {}|
|
74
|
-
|
75
|
-
# http_req m, path, params, headers
|
76
|
-
|
77
|
-
if block_given?
|
78
|
-
hc_req m, path, params, headers, &Proc.new
|
79
|
-
else
|
80
|
-
hc_req m, path, params, headers
|
81
|
-
end
|
69
|
+
define_method m do |path, params = {}, headers = {}, &block|
|
70
|
+
hc_req m, path, params, headers, &block
|
82
71
|
end
|
83
72
|
end
|
84
73
|
|
@@ -114,6 +103,44 @@ module Angelo
|
|
114
103
|
last_response.headers['Content-Type'].split(';').must_include JSON_TYPE
|
115
104
|
end
|
116
105
|
|
106
|
+
module Cellper
|
107
|
+
|
108
|
+
@@stop = false
|
109
|
+
@@testers = {}
|
110
|
+
|
111
|
+
def define_action sym, &block
|
112
|
+
define_method sym, &block
|
113
|
+
end
|
114
|
+
|
115
|
+
def remove_action sym
|
116
|
+
remove_method sym
|
117
|
+
end
|
118
|
+
|
119
|
+
def unstop!
|
120
|
+
@@stop = false
|
121
|
+
end
|
122
|
+
|
123
|
+
def stop!
|
124
|
+
@@stop = true
|
125
|
+
end
|
126
|
+
|
127
|
+
def stop?
|
128
|
+
@@stop
|
129
|
+
end
|
130
|
+
|
131
|
+
def testers; @@testers; end
|
132
|
+
end
|
133
|
+
|
134
|
+
class Reactor
|
135
|
+
include Celluloid::IO
|
136
|
+
extend Cellper
|
137
|
+
end
|
138
|
+
|
139
|
+
class ActorPool
|
140
|
+
include Celluloid
|
141
|
+
extend Cellper
|
142
|
+
end
|
143
|
+
|
117
144
|
end
|
118
145
|
|
119
146
|
class WebsocketHelper
|
@@ -173,4 +200,5 @@ module Angelo
|
|
173
200
|
end
|
174
201
|
|
175
202
|
end
|
203
|
+
|
176
204
|
end
|
data/lib/angelo/params_parser.rb
CHANGED
@@ -9,7 +9,7 @@ module Angelo
|
|
9
9
|
EMPTY_JSON = '{}'
|
10
10
|
|
11
11
|
def parse_formencoded str
|
12
|
-
str.split(AMPERSAND).reduce(
|
12
|
+
str.split(AMPERSAND).reduce(SymHash.new) do |p, kv|
|
13
13
|
key, value = kv.split(EQUALS).map {|s| CGI.unescape s}
|
14
14
|
p[key] = value
|
15
15
|
p
|
@@ -22,28 +22,18 @@ module Angelo
|
|
22
22
|
|
23
23
|
def parse_post_body
|
24
24
|
body = request.body.to_s
|
25
|
-
qs = parse_query_string
|
26
25
|
case
|
27
26
|
when form_encoded?
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
body = EMPTY_JSON if body.empty?
|
32
|
-
body = JSON.parse body
|
33
|
-
recurse_symhash qs.merge! body
|
27
|
+
parse_formencoded body
|
28
|
+
when json? && !body.empty?
|
29
|
+
SymHash.new JSON.parse body
|
34
30
|
else
|
35
|
-
|
31
|
+
{}
|
36
32
|
end
|
37
33
|
end
|
38
34
|
|
39
|
-
def
|
40
|
-
|
41
|
-
if Hash === v
|
42
|
-
h[k] = Responder.symhash.merge! v
|
43
|
-
recurse_symhash h[k]
|
44
|
-
end
|
45
|
-
end
|
46
|
-
h
|
35
|
+
def parse_query_string_and_post_body
|
36
|
+
parse_query_string.merge! parse_post_body
|
47
37
|
end
|
48
38
|
|
49
39
|
def form_encoded?
|
@@ -64,4 +54,30 @@ module Angelo
|
|
64
54
|
|
65
55
|
end
|
66
56
|
|
57
|
+
class SymHash < Hash
|
58
|
+
|
59
|
+
# Returns a Hash that allows values to be fetched with String or
|
60
|
+
# Symbol keys.
|
61
|
+
def initialize h = nil
|
62
|
+
super(){|hash,key| hash[key.to_s] if Symbol === key}
|
63
|
+
unless h.nil?
|
64
|
+
merge! h
|
65
|
+
|
66
|
+
# Replace values that are Hashes with SymHashes, recursively.
|
67
|
+
each do |k,v|
|
68
|
+
self[k] = case v
|
69
|
+
when Hash
|
70
|
+
SymHash.new(v)
|
71
|
+
when Array
|
72
|
+
v.map {|e| Hash === e ? SymHash.new(e) : e}
|
73
|
+
else
|
74
|
+
v
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
67
83
|
end
|
data/lib/angelo/responder.rb
CHANGED
@@ -25,17 +25,13 @@ module Angelo
|
|
25
25
|
@default_headers
|
26
26
|
end
|
27
27
|
|
28
|
-
def symhash
|
29
|
-
Hash.new {|hash,key| hash[key.to_s] if Symbol === key }
|
30
|
-
end
|
31
|
-
|
32
28
|
end
|
33
29
|
|
34
|
-
attr_accessor :connection, :request
|
30
|
+
attr_accessor :connection, :mustermann, :request
|
35
31
|
attr_writer :base
|
36
32
|
|
37
33
|
def initialize &block
|
38
|
-
@response_handler =
|
34
|
+
@response_handler = block
|
39
35
|
end
|
40
36
|
|
41
37
|
def reset!
|
@@ -48,7 +44,7 @@ module Angelo
|
|
48
44
|
def handle_request
|
49
45
|
if @response_handler
|
50
46
|
@base.filter :before
|
51
|
-
@body = catch(:halt) { @
|
47
|
+
@body = catch(:halt) { @base.instance_exec(&@response_handler) || EMPTY_STRING }
|
52
48
|
|
53
49
|
# TODO any real reason not to run afters with SSE?
|
54
50
|
case @body
|
@@ -164,8 +160,13 @@ module Angelo
|
|
164
160
|
@body = EMPTY_STRING
|
165
161
|
|
166
162
|
else
|
167
|
-
|
168
|
-
|
163
|
+
if respond_with? :json and @body.respond_to? :to_json
|
164
|
+
@body = @body.to_json
|
165
|
+
raise "uhhh? #{@body}" unless String === @body
|
166
|
+
else
|
167
|
+
unless @chunked and @body.respond_to? :each
|
168
|
+
raise RequestError.new "what is this? #{@body}"
|
169
|
+
end
|
169
170
|
end
|
170
171
|
end
|
171
172
|
|
@@ -13,29 +13,30 @@ module Angelo
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def handle_request
|
16
|
-
|
17
|
-
if @response_handler
|
18
|
-
@base.filter :before
|
19
|
-
@body = catch(:halt) { @base.eventsource &@response_handler.bind(@base) }
|
20
|
-
if HALT_STRUCT === @body
|
21
|
-
raise RequestError.new 'unknown sse error' unless @body.body == :sse
|
22
|
-
end
|
23
|
-
|
24
|
-
# TODO any real reason not to run afters with SSE?
|
25
|
-
# @base.filter :after
|
26
|
-
|
27
|
-
respond
|
28
|
-
else
|
16
|
+
if !@response_handler
|
29
17
|
raise NotImplementedError
|
18
|
+
end
|
19
|
+
@base.filter :before
|
20
|
+
@body = catch(:halt) do
|
21
|
+
@base.eventsource do |socket|
|
22
|
+
@base.instance_exec(socket, &@response_handler)
|
30
23
|
end
|
31
|
-
rescue IOError => ioe
|
32
|
-
warn "#{ioe.class} - #{ioe.message}"
|
33
|
-
rescue RequestError => re
|
34
|
-
headers SSE_HEADER
|
35
|
-
handle_error re, re.type
|
36
|
-
rescue => e
|
37
|
-
handle_error e
|
38
24
|
end
|
25
|
+
if HALT_STRUCT === @body
|
26
|
+
raise RequestError.new 'unknown sse error' unless @body.body == :sse
|
27
|
+
end
|
28
|
+
|
29
|
+
# TODO any real reason not to run afters with SSE?
|
30
|
+
# @base.filter :after
|
31
|
+
|
32
|
+
respond
|
33
|
+
rescue IOError => ioe
|
34
|
+
warn "#{ioe.class} - #{ioe.message}"
|
35
|
+
rescue RequestError => re
|
36
|
+
headers SSE_HEADER
|
37
|
+
handle_error re, re.type
|
38
|
+
rescue => e
|
39
|
+
handle_error e
|
39
40
|
end
|
40
41
|
|
41
42
|
def respond
|
@@ -22,10 +22,9 @@ module Angelo
|
|
22
22
|
begin
|
23
23
|
if @response_handler
|
24
24
|
Angelo.log @connection, @request, @websocket, :switching_protocols
|
25
|
-
@bound_response_handler ||= @response_handler.bind @base
|
26
25
|
@websocket.on_pong &Responder::Websocket.on_pong
|
27
26
|
@base.filter :before
|
28
|
-
@
|
27
|
+
@base.instance_exec(@websocket, &@response_handler)
|
29
28
|
@base.filter :after
|
30
29
|
else
|
31
30
|
raise NotImplementedError
|
data/lib/angelo/stash.rb
CHANGED
@@ -127,9 +127,11 @@ module Angelo
|
|
127
127
|
extend Stash::ClassMethods
|
128
128
|
include Stash
|
129
129
|
|
130
|
-
def event
|
131
|
-
|
132
|
-
|
130
|
+
def event *args
|
131
|
+
name, data = args
|
132
|
+
raise ArgumentError if @context == :default and data.nil?
|
133
|
+
data, name = name, @context if data.nil?
|
134
|
+
each {|s| s.write Angelo::Base.sse_event(name, data)}
|
133
135
|
nil
|
134
136
|
end
|
135
137
|
|
data/lib/angelo/tilt/erb.rb
CHANGED
@@ -13,16 +13,13 @@ module Angelo
|
|
13
13
|
# hrm, sneaky
|
14
14
|
#
|
15
15
|
def self.included base
|
16
|
-
|
17
|
-
# TODO: remove at 0.4
|
18
|
-
warn "[DEPRECATED] Angelo::Tilt::ERB will be included by default in angelo >= 0.4"
|
19
|
-
raise "Angelo requires Tilt >= 2.0, you have #{::Tilt::VERSION}" unless ::Tilt::VERSION.to_i >= 2
|
20
|
-
|
21
16
|
base.extend ClassMethods
|
22
17
|
end
|
23
18
|
|
24
19
|
module ClassMethods
|
25
20
|
|
21
|
+
@reload_templates = false
|
22
|
+
|
26
23
|
def view_glob *glob
|
27
24
|
File.join views_dir, *glob
|
28
25
|
end
|
@@ -33,6 +30,7 @@ module Angelo
|
|
33
30
|
return h if (block_given? && yield(v))
|
34
31
|
sym.gsub! File::SEPARATOR, UNDERSCORE
|
35
32
|
sym.gsub! /\.\w+?\.erb$/, EMPTY_STRING
|
33
|
+
sym.gsub! /^#{LAYOUTS_DIR}#{UNDERSCORE}/, EMPTY_STRING
|
36
34
|
h[sym.to_sym] = ::Tilt::ERBTemplate.new v
|
37
35
|
h
|
38
36
|
end
|
@@ -40,22 +38,51 @@ module Angelo
|
|
40
38
|
|
41
39
|
def templates type = DEFAULT_TYPE
|
42
40
|
@templates ||= {}
|
43
|
-
|
41
|
+
if reload_templates?
|
42
|
+
@templates[type] = load_templates(type)
|
43
|
+
else
|
44
|
+
@templates[type] ||= load_templates(type)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def load_templates type = DEFAULT_TYPE
|
49
|
+
templatify('**', "*.#{type}.erb") do |v|
|
44
50
|
v =~ /^#{LAYOUTS_DIR}#{File::SEPARATOR}/
|
45
51
|
end
|
46
52
|
end
|
47
53
|
|
48
54
|
def layout_templates type = DEFAULT_TYPE
|
49
|
-
|
55
|
+
if reload_templates?
|
56
|
+
@layout_templates = load_layout_templates(type)
|
57
|
+
else
|
58
|
+
@layout_templates ||= load_layout_templates(type)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def load_layout_templates type = DEFAULT_TYPE
|
63
|
+
templatify LAYOUTS_DIR, "*.#{type}.erb"
|
50
64
|
end
|
51
65
|
|
52
66
|
def default_layout type = DEFAULT_TYPE
|
53
67
|
@default_layout ||= {}
|
54
|
-
if
|
55
|
-
|
56
|
-
|
68
|
+
if reload_templates?
|
69
|
+
@default_layout[type] = load_default_layout(type)
|
70
|
+
else
|
71
|
+
@default_layout[type] ||= load_default_layout(type)
|
57
72
|
end
|
58
|
-
|
73
|
+
end
|
74
|
+
|
75
|
+
def load_default_layout type = DEFAULT_TYPE
|
76
|
+
l = view_glob(DEFAULT_LAYOUT % type)
|
77
|
+
::Tilt::ERBTemplate.new l if File.exist? l
|
78
|
+
end
|
79
|
+
|
80
|
+
def reload_templates! on = true
|
81
|
+
@reload_templates = on
|
82
|
+
end
|
83
|
+
|
84
|
+
def reload_templates?
|
85
|
+
@reload_templates
|
59
86
|
end
|
60
87
|
|
61
88
|
end
|
data/lib/angelo/version.rb
CHANGED