angelo 0.3.1 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
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