angelo 0.4.1 → 0.5.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 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