angelo 0.4.1 → 0.5.0

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: 97eb8856cc0b4c9756d65b3c76f78bab570357b8
4
- data.tar.gz: 1af8e5001731811ee47ba1f1009e18f06a84bbf5
3
+ metadata.gz: 29657358947009a8401893f8db01b0c3522706ed
4
+ data.tar.gz: 721f0e9ef56f280f761518a620aa2a7ef562ae57
5
5
  SHA512:
6
- metadata.gz: 53958da09949dc42c914752ba7142c08464f1d088d1e7d3b7d84cb438274e0b6518d32b26cffeb911df4261a2d2e3ad81f774ad0e9361c78b690ac0508d3313a
7
- data.tar.gz: f8fa0eabc251e38884a6d58f5f99ad2d70ba1a9d9acad2cb3d3723bc15341b4fafd86dafd76309d4ac3ecbed9ab9d870e9e9405b28dcc7b7697e14bfba2c8faa
6
+ metadata.gz: 6519c472ea84aa9098fd2bb186323a21b7c216d7e3b6e4927666b77f0226bb57c379159784a38d83305cf64908defb2d9c3ebcfb0d126c59916ec35c00ea5069
7
+ data.tar.gz: 110fffc25ad3e75c459aa0a484f52b2c73b8b9a318b5c8c904ec45135abfdd5aca658219910a0d993c6befe1493e9286e76b895528eeadd0fe79eeb74be58c30
data/.travis.yml CHANGED
@@ -1,10 +1,6 @@
1
1
  sudo: false
2
2
  language: ruby
3
3
  rvm:
4
- - "2.1.5"
5
- - "2.2.0"
6
- - "rbx-2"
4
+ - "2.2.4"
5
+ - "2.3.0"
7
6
  script: rake
8
- matrix:
9
- allow_failures:
10
- - rvm: rbx-2
data/CHANGELOG.md CHANGED
@@ -1,7 +1,21 @@
1
1
  changelog
2
2
  =========
3
3
 
4
- ### 0.4.1
4
+ ### 0.5.0 mar 2016
5
+
6
+ thanks: @tommay, @gunnarmarten, @nagius
7
+
8
+ * add Angelo::Templates as a better interface to Tilt
9
+ * haml / markdown support
10
+ * JSON array post body support (#48)
11
+ * add `request_body` helper for access to JSON array post bodies or `request.body.to_s`
12
+ * move `reload_templates!` to `Angelo::Base::DSL`
13
+ * support `Regexp` as only arg to `before` and `after` filters
14
+ * add post override support via `X-Angelo-PostOverride` header
15
+ * update celluloid API usage (#58)
16
+ * add `default_headers` DSL method (#61)
17
+
18
+ ### 0.4.1 8 feb 2015
5
19
 
6
20
  thanks: @tommay
7
21
 
data/Gemfile CHANGED
@@ -1,14 +1,10 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- gem 'reel', '~>0.5'
4
- gem 'tilt', '~>2.0'
5
- gem 'mime-types', '~>2.4'
6
- gem 'websocket-driver', '~>0.3'
7
- gem 'mustermann', '~>0.4'
3
+ gem 'reel', '~>0.6.1'
4
+ gemspec
8
5
 
9
6
  group :development do
10
7
  gem 'pry', '~>0.10'
11
- gem 'pry-nav', '~>0.2'
12
8
  end
13
9
 
14
10
  group :profile do
@@ -22,6 +18,6 @@ group :test do
22
18
  gem 'minitest', '~>5.4'
23
19
 
24
20
  platform :mri do
25
- gem 'simplecov', '~>0.9.1'
21
+ gem 'simplecov', '~>0.11'
26
22
  end
27
23
  end
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright 2015 Kenichi Nakamura
1
+ Copyright 2013-2015 Kenichi Nakamura
2
2
 
3
3
  Licensed under the Apache License, Version 2.0 (the "License");
4
4
  you may not use this file except in compliance with the License.
data/README.md CHANGED
@@ -12,7 +12,7 @@ A [Sinatra](https://github.com/sinatra/sinatra)-like DSL for [Reel](https://gith
12
12
  * contextual websocket/sse stashing via `websockets` and `sses` helpers
13
13
  * `task` handling via `async` and `future` helpers
14
14
  * no rack
15
- * tilt/erb support
15
+ * erb, haml, and markdown support
16
16
  * mustermann support
17
17
 
18
18
  ### What is Angelo?
@@ -22,7 +22,7 @@ notable differences, but the basics remain the same: you can either create a "cl
22
22
  by requiring 'angelo/main' and using the DSL at the top level of your script, or a "modular"
23
23
  application by requiring 'angelo', subclassing `Angelo::Base`, and calling `.run!` on that class for the
24
24
  service to start.
25
- In addition, and perhaps more importantly, **Angelo is built upon Reel, which is, in turn, built upon
25
+ In addition, and perhaps more importantly, **Angelo is built on Reel, which is built on
26
26
  Celluloid::IO and gives you a reactor with evented IO in Ruby!**
27
27
 
28
28
  Things will feel very familiar to anyone experienced with Sinatra. You can define
@@ -525,6 +525,21 @@ end
525
525
  HelloApp.run!
526
526
  ```
527
527
 
528
+ ### JSON HTTP API
529
+
530
+ If you post JSON data with a JSON Content-Type, angelo will:
531
+
532
+ * merge objects into the `params` SymHash
533
+ * parse arrays and make them available via `request_body`
534
+
535
+ N.B. `request_body` is functionally equivalent to `request.body.to_s` otherwise.
536
+
537
+ If your `content_type` is set to `:json`, angelo will convert:
538
+
539
+ * anything returned from a route block that `respond_to? :to_json`
540
+ * `RequestError` message data
541
+ * `halt` data
542
+
528
543
  ### Documentation
529
544
 
530
545
  **I'm bad at documentation and I feel bad.**
@@ -665,11 +680,33 @@ Foo.run!
665
680
 
666
681
  ### Contributing
667
682
 
668
- YES, HAVE SOME
683
+ Anyone is welcome to contribute. Conduct is guided by the [Contributor Covenant](http://contributor-covenant.org).
684
+ See `code_of_conduct.md`.
685
+
686
+ To contribute to Angelo, please:
687
+
688
+ * fork the repository to your GitHub account
689
+ * create a branch for the feature or fix
690
+ * commit your changes to that branch, please include tests if applicable
691
+ * submit a Pull Request back to the main repository's `master` branch
669
692
 
670
- * :fork_and_knife: Fork this repo, make changes, send PR!
671
- * :shipit: if Good stuff?
693
+ After review and acceptance, your changes will be merged and noted in `CHANGLOG.md`.
694
+
695
+ ### Testing
696
+
697
+ Unit tests are done with Minitest. Run them with :
698
+
699
+ ```
700
+ bundle install
701
+ rake test
702
+ ```
672
703
 
673
704
  ### License
674
705
 
675
706
  [Apache 2.0](LICENSE)
707
+
708
+ ### Name
709
+
710
+ Why the name "Angelo"? Since the project mimics Sinatra's DSL, I thought it best to keep a reference to
711
+ The Chairman in the name. It turns out that Frank Sinatra won an Academy Award for his role 'Angelo
712
+ Maggio' in 'From Here to Eternity'. I appropriated the name since this is like Sinatra on Reel (film).
data/angelo.gemspec CHANGED
@@ -13,7 +13,7 @@ Gem::Specification.new do |gem|
13
13
  gem.version = Angelo::VERSION
14
14
  gem.license = 'apache'
15
15
  gem.required_ruby_version = '>= 2.1.0'
16
- gem.add_runtime_dependency 'reel', '~>0.5'
16
+ gem.add_runtime_dependency 'reel', '~>0.6.1'
17
17
  gem.add_runtime_dependency 'tilt', '~>2.0'
18
18
  gem.add_runtime_dependency 'mustermann', '~>0.4'
19
19
  gem.add_runtime_dependency 'mime-types', '~>2.4'
@@ -0,0 +1,50 @@
1
+ # Contributor Code of Conduct
2
+
3
+ As contributors and maintainers of this project, and in the interest of
4
+ fostering an open and welcoming community, we pledge to respect all people who
5
+ contribute through reporting issues, posting feature requests, updating
6
+ documentation, submitting pull requests or patches, and other activities.
7
+
8
+ We are committed to making participation in this project a harassment-free
9
+ experience for everyone, regardless of level of experience, gender, gender
10
+ identity and expression, sexual orientation, disability, personal appearance,
11
+ body size, race, ethnicity, age, religion, or nationality.
12
+
13
+ Examples of unacceptable behavior by participants include:
14
+
15
+ * The use of sexualized language or imagery
16
+ * Personal attacks
17
+ * Trolling or insulting/derogatory comments
18
+ * Public or private harassment
19
+ * Publishing other's private information, such as physical or electronic
20
+ addresses, without explicit permission
21
+ * Other unethical or unprofessional conduct
22
+
23
+ Project maintainers have the right and responsibility to remove, edit, or
24
+ reject comments, commits, code, wiki edits, issues, and other contributions
25
+ that are not aligned to this Code of Conduct, or to ban temporarily or
26
+ permanently any contributor for other behaviors that they deem inappropriate,
27
+ threatening, offensive, or harmful.
28
+
29
+ By adopting this Code of Conduct, project maintainers commit themselves to
30
+ fairly and consistently applying these principles to every aspect of managing
31
+ this project. Project maintainers who do not follow or enforce the Code of
32
+ Conduct may be permanently removed from the project team.
33
+
34
+ This Code of Conduct applies both within project spaces and in public spaces
35
+ when an individual is representing the project or its community.
36
+
37
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
38
+ reported by contacting the project <a href="mailto:kenichi.nakamura@gmail.com">maintainer</a>. All
39
+ complaints will be reviewed and investigated and will result in a response that
40
+ is deemed necessary and appropriate to the circumstances. Maintainers are
41
+ obligated to maintain confidentiality with regard to the reporter of an
42
+ incident.
43
+
44
+
45
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
46
+ version 1.3.0, available at
47
+ [http://contributor-covenant.org/version/1/3/0/][version]
48
+
49
+ [homepage]: http://contributor-covenant.org
50
+ [version]: http://contributor-covenant.org/version/1/3/0/
data/lib/angelo.rb CHANGED
@@ -18,9 +18,13 @@ module Angelo
18
18
 
19
19
  HTTPABLE = [:get, :post, :put, :delete, :options]
20
20
  STATICABLE = [:get, :head]
21
+ POST_OVERRIDABLE = [:put, :delete]
21
22
 
22
23
  ACCEPT_REQUEST_HEADER_KEY = 'Accept'
23
24
 
25
+ POST_OVERRIDE_REQUEST_HEADER_KEY = 'X-Angelo-PostOverride'
26
+ REAL_IP_REQUEST_HEADER_KEY = 'X-Real-IP'
27
+
24
28
  CONTENT_TYPE_HEADER_KEY = 'Content-Type'
25
29
  CONTENT_DISPOSITION_HEADER_KEY = 'Content-Disposition'
26
30
  CONTENT_LENGTH_HEADER_KEY = 'Content-Length'
@@ -79,22 +83,23 @@ module Angelo
79
83
 
80
84
  end
81
85
 
82
- def self.log connection, request, socket, status, body_size = '-'
86
+ def self.log meth, connection, request, socket, status, body_size = '-'
83
87
 
84
- remote_ip = ->{
85
- if socket.nil?
86
- connection.remote_ip rescue 'unknown'
87
- else
88
- socket.peeraddr(false)[3]
89
- end
90
- }
88
+ remote_ip = case
89
+ when request.headers[REAL_IP_REQUEST_HEADER_KEY]
90
+ request.headers[REAL_IP_REQUEST_HEADER_KEY]
91
+ when socket
92
+ socket.peeraddr(false)[3]
93
+ else
94
+ connection.remote_ip rescue 'unknown'
95
+ end
91
96
 
92
- Celluloid::Logger.__send__ Angelo.response_log_level, LOG_FORMAT % [
93
- remote_ip[],
94
- request.method,
97
+ Celluloid::Internals::Logger.__send__ Angelo.response_log_level, LOG_FORMAT % [
98
+ remote_ip,
99
+ (meth && meth.upcase) || request.method,
95
100
  request.url,
96
101
  request.version,
97
- Symbol === status ? HTTP::Response::SYMBOL_TO_STATUS_CODE[status] : status,
102
+ Symbol === status ? HTTP::Response::Status::SYMBOL_CODES[status] : status,
98
103
  body_size
99
104
  ]
100
105
 
@@ -133,6 +138,7 @@ require 'angelo/server'
133
138
  require 'angelo/responder'
134
139
  require 'angelo/responder/eventsource'
135
140
  require 'angelo/responder/websocket'
141
+ require 'angelo/templates'
136
142
  require 'angelo/tilt/erb'
137
143
  require 'angelo/base'
138
144
  require 'angelo/stash'
data/lib/angelo/base.rb CHANGED
@@ -3,60 +3,21 @@ module Angelo
3
3
  class Base
4
4
  extend Forwardable
5
5
  include ParamsParser
6
- include Celluloid::Logger
6
+ include Celluloid::Internals::Logger
7
+ include Templates
7
8
  include Tilt::ERB
8
9
  include Mustermann
9
10
 
10
- def_delegators :@responder, :content_type, :headers, :mustermann, :redirect, :request, :transfer_encoding
11
+ def_delegators :@responder, :content_type, :headers, :mustermann, :redirect, :redirect!, :request, :transfer_encoding
11
12
  def_delegators :@klass, :public_dir, :report_errors?, :sse_event, :sse_message, :sses, :websockets
12
13
 
13
14
  attr_accessor :responder
14
-
15
- class << self
16
-
17
- attr_accessor :app_file, :server
18
-
19
- def inherited subclass
20
-
21
- # Set app_file by groveling up the caller stack until we find
22
- # the first caller from a directory different from __FILE__.
23
- # This allows base.rb to be required from an arbitrarily deep
24
- # nesting of require "angelo/<whatever>" and still set
25
- # app_file correctly.
26
- #
27
- subclass.app_file = caller_locations.map(&:absolute_path).find do |f|
28
- !f.start_with?(File.dirname(__FILE__) + File::SEPARATOR)
29
- end
30
-
31
- # bring RequestError into this namespace
32
- #
33
- subclass.class_eval 'class RequestError < Angelo::RequestError; end'
34
-
35
- subclass.addr DEFAULT_ADDR
36
- subclass.port DEFAULT_PORT
37
-
38
- subclass.ping_time DEFAULT_PING_TIME
39
- subclass.log_level DEFAULT_LOG_LEVEL
40
-
41
- # Parse command line options if angelo/main has been required.
42
- # They could also be parsed in run, but this makes them
43
- # available to and overridable by the DSL.
44
- #
45
- subclass.parse_options(ARGV.dup) if @angelo_main
46
-
47
- class << subclass
48
-
49
- def root
50
- @root ||= File.expand_path '..', app_file
51
- end
52
-
53
- end
54
-
55
- end
56
- end
15
+ attr_writer :request_body
57
16
 
58
17
  # Methods defined in module DSL will be available in the DSL both
59
- # in Angelo::Base subclasses and in the top level DSL.
18
+ # in Angelo::Base subclasses as class methods and, if angelo/main
19
+ # is required, in the top level DSL by forwarding to an anonymous
20
+ # Angelo::Base subclass.
60
21
 
61
22
  module DSL
62
23
  def addr a = nil
@@ -64,6 +25,11 @@ module Angelo
64
25
  @addr
65
26
  end
66
27
 
28
+ def port p = nil
29
+ @port = p if p
30
+ @port
31
+ end
32
+
67
33
  def log_level ll = nil
68
34
  @log_level = ll if ll
69
35
  @log_level
@@ -74,20 +40,17 @@ module Angelo
74
40
  @ping_time
75
41
  end
76
42
 
77
- def port p = nil
78
- @port = p if p
79
- @port
80
- end
81
-
82
43
  def views_dir d = nil
83
44
  @views_dir = d if d
84
- @views_dir ||= DEFAULT_VIEWS_DIR
85
45
  File.join root, @views_dir
86
46
  end
87
47
 
48
+ def reload_templates!(on = true)
49
+ @reload_templates = on
50
+ end
51
+
88
52
  def public_dir d = nil
89
53
  @public_dir = d if d
90
- @public_dir ||= DEFAULT_PUBLIC_DIR
91
54
  File.join root, @public_dir
92
55
  end
93
56
 
@@ -98,13 +61,13 @@ module Angelo
98
61
  HTTPABLE.each do |m|
99
62
  define_method m do |path, opts = {}, &block|
100
63
  path = ::Mustermann.new path, opts
101
- routes[m][path] = Responder.new &block
64
+ routes[m][path] = Responder.new m, &block
102
65
  end
103
66
  end
104
67
 
105
68
  def websocket path, &block
106
69
  path = ::Mustermann.new path
107
- routes[:websocket][path] = Responder::Websocket.new &block
70
+ routes[:websocket][path] = Responder::Websocket.new nil, &block
108
71
  end
109
72
 
110
73
  def eventsource path, headers = nil, &block
@@ -125,21 +88,61 @@ module Angelo
125
88
  end
126
89
 
127
90
  def on_pong &block
128
- Responder::Websocket.on_pong = block
91
+ @on_pong = block if block
92
+ @on_pong
129
93
  end
130
94
 
131
95
  def content_type type
132
96
  Responder.content_type type
133
97
  end
134
98
 
99
+ def default_headers hs
100
+ Responder.default_headers = Responder.default_headers.merge hs
101
+ end
135
102
  end
136
103
 
137
- # Make the DSL methods available to subclass-level code.
138
- # main.rb makes them available to the top level.
104
+ class << self
105
+ include DSL
139
106
 
140
- extend DSL
107
+ attr_accessor :app_file, :server
108
+
109
+ def inherited subclass
110
+
111
+ # Set app_file by groveling up the caller stack until we find
112
+ # the first caller from a directory different from __FILE__.
113
+ # This allows base.rb to be required from an arbitrarily deep
114
+ # nesting of require "angelo/<whatever>" and still set
115
+ # app_file correctly.
116
+ #
117
+ subclass.app_file = caller_locations.map(&:absolute_path).find do |f|
118
+ !f.start_with?(File.dirname(__FILE__) + File::SEPARATOR)
119
+ end
120
+
121
+ # bring RequestError into this namespace
122
+ #
123
+ subclass.class_eval 'class RequestError < Angelo::RequestError; end'
124
+
125
+ subclass.addr DEFAULT_ADDR
126
+ subclass.port DEFAULT_PORT
127
+
128
+ subclass.ping_time DEFAULT_PING_TIME
129
+ subclass.log_level DEFAULT_LOG_LEVEL
130
+
131
+ subclass.views_dir DEFAULT_VIEWS_DIR
132
+ subclass.public_dir DEFAULT_PUBLIC_DIR
133
+
134
+ # Parse command line options if angelo/main has been required.
135
+ # They could also be parsed in run, but this makes them
136
+ # available to and overridable by the DSL.
137
+ #
138
+ subclass.parse_options(ARGV.dup) if @angelo_main
139
+
140
+ end
141
+
142
+ def root
143
+ @root ||= File.expand_path '..', app_file
144
+ end
141
145
 
142
- class << self
143
146
  def report_errors?
144
147
  !!@report_errors
145
148
  end
@@ -149,12 +152,15 @@ module Angelo
149
152
  end
150
153
 
151
154
  def filters
152
- @filters ||= {before: {default: []}, after: {default: []}}
155
+ @filters ||= {
156
+ before: Hash.new{|h,k| h[k] = []},
157
+ after: Hash.new{|h,k| h[k] = []},
158
+ }
153
159
  end
154
160
 
155
161
  def filter which, opts = {}, &block
156
162
  case opts
157
- when String
163
+ when String, Regexp
158
164
  filter_by which, opts, block
159
165
  when Hash
160
166
  if opts[:path]
@@ -167,7 +173,6 @@ module Angelo
167
173
 
168
174
  def filter_by which, path, block
169
175
  pattern = ::Mustermann.new path
170
- filters[which][pattern] ||= []
171
176
  filters[which][pattern] << block
172
177
  end
173
178
 
@@ -247,12 +252,16 @@ module Angelo
247
252
  if Symbol === key
248
253
  k = key.to_s.upcase
249
254
  k.gsub! UNDERSCORE, DASH
250
- rhv = request.headers.select {|header_key,v| header_key.upcase == k}
251
- hash[key] = rhv.values.first
255
+ _, value = request.headers.find {|header_key,v| header_key.upcase == k}
256
+ hash[key] = value
252
257
  end
253
258
  end
254
259
  end
255
260
 
261
+ def request_body
262
+ @request_body ||= request.body.to_s
263
+ end
264
+
256
265
  task :handle_websocket do |ws|
257
266
  begin
258
267
  while !ws.closed? do
@@ -266,9 +275,7 @@ module Angelo
266
275
 
267
276
  task :ping_websockets do
268
277
  every(@base.ping_time) do
269
- websockets.all_each do |ws|
270
- ws.socket << ::WebSocket::Message.ping.to_data
271
- end
278
+ websockets.all_each {|ws| ws.ping &@base.on_pong }
272
279
  end
273
280
  end
274
281
 
@@ -353,11 +360,11 @@ module Angelo
353
360
  when :default
354
361
  filters.each {|filter| instance_eval &filter}
355
362
  when ::Mustermann
356
- if pattern.match request.path
357
- @pre_filter_params = params
358
- @params = @pre_filter_params.merge pattern.params(request.path)
363
+ if mustermann_params = pattern.params(request.path)
364
+ pre_filter_params = params
365
+ @params = pre_filter_params.merge mustermann_params
359
366
  filters.each {|filter| instance_eval &filter}
360
- @params = @pre_filter_params
367
+ @params = pre_filter_params
361
368
  end
362
369
  end
363
370
  end
@@ -402,11 +409,8 @@ module Angelo
402
409
  end
403
410
 
404
411
  def [] route
405
- responder = nil
406
- if mustermann = @hash.keys.select {|k| k.match(route)}.first
407
- responder = @hash.fetch mustermann
408
- responder.mustermann = mustermann
409
- end
412
+ mustermann, responder = @hash.find {|k,v| k.match(route)}
413
+ responder.mustermann = mustermann if mustermann
410
414
  responder
411
415
  end
412
416