angelo 0.3.1 → 0.3.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 82990928178a891fac3eb49454474d3e5136ab87
4
- data.tar.gz: 9fe7dec6dfffd783e3a804f5766b31be2c024491
3
+ metadata.gz: 6f5dba260998174e061615d20082882201023653
4
+ data.tar.gz: 95f76832e3a797bc886e93a822ee541640f18df7
5
5
  SHA512:
6
- metadata.gz: f2da2084362c910fcd9b4766dec667d574409c6fe3f100554ffc1c72679648ffef54d386a340c4b686aa8896205f956baf8b51f0b48d6a465ee4003448ce4d80
7
- data.tar.gz: d0d2410e21038dd060f5f384bb033571481a9088ada01f8b838be59558ea436101935991f428f254902c858ac590f00ffa0cb8a651572c5aa57ba46e2fb471c9
6
+ metadata.gz: 1cda696fa0204160276711d8927393d680b28149af53d564421a227768e57601e30f6d9e70876c1fb805ffc96df534785965ccee4fa9c825db751ee6af390e0d
7
+ data.tar.gz: 1d062a3d517d8c3a463ca339f8344c6b4b501a89d187a201f6ccb6fd243f8559d236605254ec2baae190b62adef5fb9df069dab71eb7f90988d6d3077146643b
data/.travis.yml CHANGED
@@ -4,4 +4,5 @@ rvm:
4
4
  - "2.0.0"
5
5
  - "2.1.2"
6
6
  - "2.1.3"
7
+ - "2.1.5"
7
8
  script: rake
data/CHANGELOG.md CHANGED
@@ -1,6 +1,13 @@
1
1
  changelog
2
2
  =========
3
3
 
4
+ ### 0.3.2 27 nov 2014 ¡gracias!
5
+
6
+ thanks: @mighe, @artworx
7
+
8
+ * send_file now accepts both full paths and paths relative to app file's dir (#20)
9
+ * erb templates now sorted by type, finer control of response template type (#19)
10
+
4
11
  ### 0.3.1 10 nov 2014 yep, same day - jeez
5
12
 
6
13
  * refactor views dir and public dir setting into top level DSLish methods
data/Gemfile CHANGED
@@ -5,8 +5,8 @@ gem 'tilt', '~>2.0'
5
5
  gem 'mime-types', '~>2.4'
6
6
  gem 'websocket-driver', '~>0.3'
7
7
 
8
- platform :ruby_20, :ruby_21 do
9
- gem 'mustermann', '~>0.3'
8
+ platform :ruby_21 do
9
+ gem 'mustermann', '~>0.4'
10
10
  end
11
11
 
12
12
  group :development do
data/README.md CHANGED
@@ -433,6 +433,19 @@ class Foo < Angelo::Base
433
433
  end
434
434
  ```
435
435
 
436
+ The Angleo::Tilt::ERB module and the `erb` method do some extra work for you:
437
+
438
+ * templates are pre-compiled, sorted by type.
439
+ * template type is determined by word between name and .erb (ex: `index.html.erb`
440
+ is `:index` name and `:html` type)
441
+ * the template chosen to render is determined based on:
442
+ * `:type` option passed to `erb` helper
443
+ * `Accept` request header value
444
+ * `Content-Type` response header value
445
+ * default to `:html`
446
+
447
+ See [views](https://github.com/kenichi/angelo/tree/master/test/test_app_root/views) for examples.
448
+
436
449
  ### [Mustermann](https://github.com/rkh/mustermann)
437
450
 
438
451
  To make routes blocks match path with Mustermann patterns
data/lib/angelo/base.rb CHANGED
@@ -258,7 +258,7 @@ module Angelo
258
258
  block[socket]
259
259
  rescue Reel::SocketError, IOError, SystemCallError => e
260
260
  # probably closed on client
261
- warn e.message if report_errors
261
+ warn e.message if report_errors?
262
262
  socket.close unless socket.closed?
263
263
  rescue => e
264
264
  error e.inspect
@@ -271,7 +271,8 @@ module Angelo
271
271
  end
272
272
 
273
273
  def send_file local_file, opts = {}
274
- lp = self.class.local_path local_file
274
+ lp = local_file[0] == File::SEPARATOR ? local_file : File.expand_path(File.join(self.class.root, local_file))
275
+ halt 404 unless File.exist? lp
275
276
 
276
277
  # Content-Type
277
278
  #
@@ -7,6 +7,7 @@ module Angelo
7
7
 
8
8
  attr_writer :default_headers
9
9
 
10
+ # top-level setter
10
11
  def content_type type
11
12
  dhs = self.default_headers
12
13
  case type
@@ -102,6 +103,7 @@ module Angelo
102
103
  @headers
103
104
  end
104
105
 
106
+ # route handler helper
105
107
  def content_type type
106
108
  case type
107
109
  when :json
@@ -110,6 +112,8 @@ module Angelo
110
112
  headers CONTENT_TYPE_HEADER_KEY => HTML_TYPE
111
113
  when :js
112
114
  headers CONTENT_TYPE_HEADER_KEY => JS_TYPE
115
+ when :xml
116
+ headers CONTENT_TYPE_HEADER_KEY => XML_TYPE
113
117
  else
114
118
  raise ArgumentError.new "invalid content_type: #{type}"
115
119
  end
@@ -5,6 +5,11 @@ module Angelo
5
5
  module Tilt
6
6
  module ERB
7
7
 
8
+ DEFAULT_LAYOUT = 'layout.%s.erb'
9
+ DEFAULT_TYPE = :html
10
+ LAYOUTS_DIR = 'layouts'
11
+ ACCEPT_ALL = '*/*'
12
+
8
13
  # hrm, sneaky
9
14
  #
10
15
  def self.included base
@@ -13,67 +18,83 @@ module Angelo
13
18
 
14
19
  module ClassMethods
15
20
 
16
- DEFAULT_LAYOUT = 'layout.html.erb'
17
-
18
21
  def view_glob *glob
19
22
  File.join views_dir, *glob
20
23
  end
21
24
 
22
25
  def templatify *glob
23
26
  Dir[view_glob *glob].reduce({}) do |h,v|
24
- sym = v.gsub views_dir + '/', ''
27
+ sym = v.gsub views_dir + File::SEPARATOR, ''
25
28
  return h if (block_given? && yield(v))
26
- sym.gsub! '/', '_'
27
- sym.gsub! /\.\w+?\.erb$/, ''
29
+ sym.gsub! File::SEPARATOR, UNDERSCORE
30
+ sym.gsub! /\.\w+?\.erb$/, EMPTY_STRING
28
31
  h[sym.to_sym] = ::Tilt::ERBTemplate.new v
29
32
  h
30
33
  end
31
34
  end
32
35
 
33
- def templates
34
- @templates ||= templatify('**', '*.erb'){|v| v =~ /^layouts\//}
36
+ def templates type = DEFAULT_TYPE
37
+ @templates ||= {}
38
+ @templates[type] ||= templatify('**', "*.#{type}.erb") do |v|
39
+ v =~ /^#{LAYOUTS_DIR}#{File::SEPARATOR}/
40
+ end
35
41
  end
36
42
 
37
- def layout_templates
38
- @layout_templates ||= templatify 'layouts', '*.erb'
43
+ def layout_templates type = DEFAULT_TYPE
44
+ @layout_templates ||= templatify LAYOUTS_DIR, "*.#{type}.erb"
39
45
  end
40
46
 
41
- def default_layout
42
- if @default_layout.nil?
43
- l = view_glob(DEFAULT_LAYOUT)
44
- @default_layout = ::Tilt::ERBTemplate.new l if File.exist? l
47
+ def default_layout type = DEFAULT_TYPE
48
+ @default_layout ||= {}
49
+ if @default_layout[type].nil?
50
+ l = view_glob(DEFAULT_LAYOUT % type)
51
+ @default_layout[type] = ::Tilt::ERBTemplate.new l if File.exist? l
45
52
  end
46
- @default_layout
53
+ @default_layout[type]
47
54
  end
48
55
 
49
56
  end
50
57
 
51
58
  def erb view, opts = {locals: {}}
59
+ type = opts[:type] || template_type
60
+ content_type type
52
61
  locals = Hash === opts[:locals] ? opts[:locals] : {}
53
62
  render = case view
54
63
  when String
55
64
  ->{ view }
56
65
  when Symbol
57
- ->{self.class.templates[view].render self, locals}
66
+ ->{self.class.templates(type)[view].render self, locals}
58
67
  end
59
68
  case opts[:layout]
60
69
  when false
61
70
  render[]
62
71
  when Symbol
63
- if lt = self.class.layout_templates[opts[:layout]]
72
+ if lt = self.class.layout_templates(type)[opts[:layout]]
64
73
  lt.render self, locals, &render
65
74
  else
66
75
  raise ArgumentError.new "unknown layout - :#{opts[:layout]}"
67
76
  end
68
77
  else
69
- if self.class.default_layout
70
- self.class.default_layout.render self, locals, &render
78
+ if self.class.default_layout(type)
79
+ self.class.default_layout(type).render self, locals, &render
71
80
  else
72
81
  render[]
73
82
  end
74
83
  end
75
84
  end
76
85
 
86
+ def template_type
87
+ accept = request.headers[ACCEPT_REQUEST_HEADER_KEY]
88
+ mt = if accept.nil? or accept == ACCEPT_ALL
89
+ MIME::Types[headers[CONTENT_TYPE_HEADER_KEY]]
90
+ else
91
+ MIME::Types[request.headers[ACCEPT_REQUEST_HEADER_KEY]]
92
+ end
93
+ mt.first.extensions.first.to_sym
94
+ rescue
95
+ DEFAULT_TYPE
96
+ end
97
+
77
98
  end
78
99
  end
79
100
  end
@@ -1,3 +1,3 @@
1
1
  module Angelo
2
- VERSION = '0.3.1'
2
+ VERSION = '0.3.2'
3
3
  end
data/lib/angelo.rb CHANGED
@@ -18,6 +18,8 @@ module Angelo
18
18
  HTTPABLE = [:get, :post, :put, :delete, :options]
19
19
  STATICABLE = [:get, :head]
20
20
 
21
+ ACCEPT_REQUEST_HEADER_KEY = 'Accept'
22
+
21
23
  CONTENT_TYPE_HEADER_KEY = 'Content-Type'
22
24
  CONTENT_DISPOSITION_HEADER_KEY = 'Content-Disposition'
23
25
  CONTENT_LENGTH_HEADER_KEY = 'Content-Length'
@@ -31,7 +33,8 @@ module Angelo
31
33
  JSON_TYPE = 'application/json'
32
34
  FORM_TYPE = 'application/x-www-form-urlencoded'
33
35
  FILE_TYPE = 'application/octet-stream'
34
- JS_TYPE = 'text/javascript'
36
+ JS_TYPE = 'application/javascript'
37
+ XML_TYPE = 'application/xml'
35
38
 
36
39
  DEFAULT_ADDR = '127.0.0.1'
37
40
  DEFAULT_PORT = 4567
@@ -4,6 +4,54 @@ require 'angelo/tilt/erb'
4
4
  describe Angelo::Base do
5
5
  describe Angelo::Tilt::ERB do
6
6
 
7
+ expected_html = <<HTML
8
+ <!doctype html>
9
+ <html>
10
+ <head>
11
+ <title>test</title>
12
+ </head>
13
+ <body>
14
+ foo - asdf
15
+ locals :bar - bat
16
+
17
+ </body>
18
+ </html>
19
+ HTML
20
+
21
+ expected_xml = <<XML
22
+ <foo bar="bat">asdf</foo>
23
+ XML
24
+
25
+ expected_json = <<JSON
26
+ {"foo": "asdf", "bar": ["bat"]}
27
+ JSON
28
+
29
+ expected_javascript = <<JS
30
+ (function() {
31
+ var foo = "asdf";
32
+ var bar = "bat";
33
+
34
+ })();
35
+ JS
36
+
37
+ expected_html_nl = <<HTML
38
+ foo - asdf
39
+ locals :bar - bat
40
+ HTML
41
+
42
+ expected_xml_nl = <<XML
43
+ <foo bar="bat">asdf</foo>
44
+ XML
45
+
46
+ expected_json_nl = <<JSON
47
+ {"foo": "asdf", "bar": ["bat"]}
48
+ JSON
49
+
50
+ expected_javascript_nl = <<JS
51
+ var foo = "asdf";
52
+ var bar = "bat";
53
+ JS
54
+
7
55
  define_app do
8
56
 
9
57
  include Angelo::Tilt::ERB
@@ -25,24 +73,40 @@ describe Angelo::Base do
25
73
  erb :index, layout: false, locals: {bar: 'bat'}
26
74
  end
27
75
 
76
+ get '/index.html' do
77
+ set_vars
78
+ content_type :html
79
+ erb :index, locals: {bar: 'bat'}, layout: !!params[:layout]
80
+ end
81
+
82
+ get '/index.json' do
83
+ set_vars
84
+ content_type :json
85
+ erb :index, locals: {bar: 'bat'}, layout: !!params[:layout]
86
+ end
87
+
88
+ get '/index.js' do
89
+ set_vars
90
+ content_type :js
91
+ erb :index, locals: {bar: 'bat'}, layout: !!params[:layout]
92
+ end
93
+
94
+ get '/index.xml' do
95
+ set_vars
96
+ content_type :xml
97
+ erb :index, locals: {bar: 'bat'}, layout: !!params[:layout]
98
+ end
99
+
100
+ get '/by_type' do
101
+ set_vars
102
+ erb :index, locals: {bar: 'bat'}, type: params[:type].to_sym
103
+ end
104
+
28
105
  end
29
106
 
30
107
  it 'renders templates with layout' do
31
108
  get '/', foo: 'asdf'
32
- expected = <<HTML
33
- <!doctype html>
34
- <html>
35
- <head>
36
- <title>test</title>
37
- </head>
38
- <body>
39
- foo - asdf
40
- locals :bar - bat
41
-
42
- </body>
43
- </html>
44
- HTML
45
- last_response_must_be_html expected
109
+ last_response_must_be_html expected_html
46
110
  end
47
111
 
48
112
  it 'renders templates without layout' do
@@ -54,5 +118,106 @@ HTML
54
118
  last_response_must_be_html expected
55
119
  end
56
120
 
121
+ it 'renders templates by Accept header html' do
122
+ get '/', {foo: 'asdf'}, {'Accept' => 'text/html'}
123
+ last_response_must_be_html expected_html
124
+ end
125
+
126
+ it 'renders templates by Accept header xml' do
127
+ get '/', {foo: 'asdf'}, {'Accept' => 'application/xml'}
128
+ last_response.body.must_equal expected_xml
129
+ last_response.headers['Content-Type'].must_equal 'application/xml'
130
+ end
131
+
132
+ it 'renders templates by Accept header javascript' do
133
+ get '/', {foo: 'asdf'}, {'Accept' => 'application/javascript'}
134
+ last_response.body.must_equal expected_javascript
135
+ last_response.headers['Content-Type'].must_equal 'application/javascript'
136
+ end
137
+
138
+ it 'renders templates by Accept header json' do
139
+ get '/', {foo: 'asdf'}, {'Accept' => 'application/json'}
140
+ last_response.body.must_equal expected_json
141
+ last_response.headers['Content-Type'].must_equal 'application/json'
142
+ end
143
+
144
+ it 'renders html template when unknown Accept header type' do
145
+ get '/', {foo: 'asdf'}, {'Accept' => 'forget/about/it'}
146
+ last_response_must_be_html expected_html
147
+ end
148
+
149
+ # content_type
150
+
151
+ it 'renders templates by content_type :html' do
152
+ get '/index.html', foo: 'asdf', layout: true
153
+ last_response_must_be_html expected_html
154
+ end
155
+
156
+ it 'renders templates by content_type :xml' do
157
+ get '/index.xml', foo: 'asdf', layout: true
158
+ last_response.body.must_equal expected_xml
159
+ last_response.headers['Content-Type'].must_equal 'application/xml'
160
+ end
161
+
162
+ it 'renders templates by content_type :javascript' do
163
+ get '/index.js', foo: 'asdf', layout: true
164
+ last_response.body.must_equal expected_javascript
165
+ last_response.headers['Content-Type'].must_equal 'application/javascript'
166
+ end
167
+
168
+ it 'renders templates by content_type :json' do
169
+ get '/index.json', foo: 'asdf', layout: true
170
+ last_response.body.must_equal expected_json
171
+ last_response.headers['Content-Type'].must_equal 'application/json'
172
+ end
173
+
174
+ it 'renders templates by content_type :html' do
175
+ get '/index.html', foo: 'asdf'
176
+ last_response_must_be_html expected_html_nl
177
+ end
178
+
179
+ it 'renders templates by content_type :xml' do
180
+ get '/index.xml', foo: 'asdf'
181
+ last_response.body.must_equal expected_xml_nl
182
+ last_response.headers['Content-Type'].must_equal 'application/xml'
183
+ end
184
+
185
+ it 'renders templates by content_type :javascript' do
186
+ get '/index.js', foo: 'asdf'
187
+ last_response.body.must_equal expected_javascript_nl
188
+ last_response.headers['Content-Type'].must_equal 'application/javascript'
189
+ end
190
+
191
+ it 'renders templates by content_type :json' do
192
+ get '/index.json', foo: 'asdf'
193
+ last_response.body.must_equal expected_json_nl
194
+ last_response.headers['Content-Type'].must_equal 'application/json'
195
+ end
196
+
197
+ # opts[:type]
198
+
199
+ it 'renders templates by opts[:type] :html' do
200
+ get '/by_type', foo: 'asdf', type: 'html'
201
+ last_response_must_be_html expected_html
202
+ end
203
+
204
+ it 'renders templates by opts[:type] :xml' do
205
+ get '/by_type', foo: 'asdf', type: 'xml'
206
+ last_response.body.must_equal expected_xml
207
+ last_response.headers['Content-Type'].must_equal 'application/xml'
208
+ end
209
+
210
+ it 'renders templates by opts[:type] :javascript' do
211
+ get '/by_type', foo: 'asdf', type: 'js'
212
+ last_response.body.must_equal expected_javascript
213
+ last_response.headers['Content-Type'].must_equal 'application/javascript'
214
+ end
215
+
216
+ it 'renders templates by opts[:type] :json' do
217
+ get '/by_type', foo: 'asdf', type: 'json'
218
+ last_response.body.must_equal expected_json
219
+ last_response.headers['Content-Type'].must_equal 'application/json'
220
+ end
221
+
57
222
  end
58
223
  end
@@ -1,4 +1,4 @@
1
- if RUBY_VERSION =~ /^2\./ and RUBY_PLATFORM != 'java'
1
+ if RUBY_VERSION =~ /^2\.(\d)/ and $1.to_i > 0 and RUBY_PLATFORM != 'java'
2
2
 
3
3
  require_relative '../spec_helper'
4
4
  require 'angelo/mustermann'
@@ -19,15 +19,23 @@ describe Angelo::Server do
19
19
  end
20
20
 
21
21
  get '/img' do
22
- send_file 'what.png'
22
+ send_file 'public/what.png'
23
23
  end
24
24
 
25
25
  get '/what' do
26
- send_file 'what.png', disposition: :attachment
26
+ send_file 'public/what.png', disposition: :attachment
27
27
  end
28
28
 
29
29
  get '/attachment.png' do
30
- send_file 'what.png', filename: 'attachment.png'
30
+ send_file 'public/what.png', filename: 'attachment.png'
31
+ end
32
+
33
+ get '/does_not_exist' do
34
+ send_file 'does_not_exist'
35
+ end
36
+
37
+ get '/etc/passwd' do
38
+ send_file '/etc/passwd'
31
39
  end
32
40
 
33
41
  end
@@ -100,5 +108,22 @@ describe Angelo::Server do
100
108
  last_response.headers['Content-Disposition'].must_equal 'attachment; filename="attachment.png"'
101
109
  end
102
110
 
111
+ it '404s when send_file is called with a non-existent file' do
112
+ get '/does_not_exist'
113
+ last_response.status.must_equal 404
114
+ end
115
+
116
+ it 'sends fully pathed fileds' do
117
+ get '/etc/passwd'
118
+ if File.exist?('/etc/passwd')
119
+ last_response.status.must_equal 200
120
+ last_response.headers['Content-Type'].must_equal 'text/html'
121
+ last_response.headers['Content-Disposition'].must_be_nil
122
+ last_response.body.must_equal File.read('/etc/passwd')
123
+ else
124
+ last_response.status.must_equal 404
125
+ end
126
+ end
127
+
103
128
  end
104
129
  end
@@ -0,0 +1,2 @@
1
+ var foo = "<%= @foo %>";
2
+ var bar = "<%= bar %>";
@@ -0,0 +1 @@
1
+ {"foo": "<%= @foo %>", "bar": ["<%= bar %>"]}
@@ -0,0 +1 @@
1
+ <foo bar="<%= bar %>"><%= @foo %></foo>
@@ -0,0 +1,3 @@
1
+ (function() {
2
+ <%= yield %>
3
+ })();
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: angelo
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kenichi Nakamura
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-11-11 00:00:00.000000000 Z
11
+ date: 2014-11-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: reel
@@ -82,7 +82,11 @@ files:
82
82
  - test/test_app_root/public/test.js
83
83
  - test/test_app_root/public/what.png
84
84
  - test/test_app_root/views/index.html.erb
85
+ - test/test_app_root/views/index.js.erb
86
+ - test/test_app_root/views/index.json.erb
87
+ - test/test_app_root/views/index.xml.erb
85
88
  - test/test_app_root/views/layout.html.erb
89
+ - test/test_app_root/views/layout.js.erb
86
90
  homepage: https://github.com/kenichi/angelo
87
91
  licenses:
88
92
  - apache
@@ -103,7 +107,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
103
107
  version: '0'
104
108
  requirements: []
105
109
  rubyforge_project:
106
- rubygems_version: 2.4.1
110
+ rubygems_version: 2.4.4
107
111
  signing_key:
108
112
  specification_version: 4
109
113
  summary: A Sinatra-esque DSL for Reel