README.md in angelo-0.3.3 vs README.md in angelo-0.4.0
- old
+ new
@@ -1,47 +1,48 @@
Angelo
======
[![Build Status](https://travis-ci.org/kenichi/angelo.png?branch=master)](https://travis-ci.org/kenichi/angelo)
-A [Sinatra](https://github.com/sinatra/sinatra)-esque DSL for [Reel](https://github.com/celluloid/reel).
+A [Sinatra](https://github.com/sinatra/sinatra)-like DSL for [Reel](https://github.com/celluloid/reel).
### tl;dr
* websocket support via `websocket('/path'){|s| ... }` route builder
* SSE support via `eventsource('/path'){|s| ... }` route builder
* contextual websocket/sse stashing via `websockets` and `sses` helpers
* `task` handling via `async` and `future` helpers
* no rack
-* optional tilt/erb support
-* optional mustermann support
+* tilt/erb support
+* mustermann support
### What is Angelo?
Just like Sinatra, Angelo gives you an expressive DSL for creating web applications. There are some
-notable differences, but the basics remain the same. It mostly follows the subclass style of Sinatra:
-you must define a subclass of `Angelo::Base` but also `.run` on that class for the service to start.
+notable differences, but the basics remain the same: you can either create a "classic" application
+by requiring 'angelo/main' and using the DSL at the top level of your script, or a "modular"
+application by requiring 'angelo', subclassing `Angelo::Base`, and calling `.run!` on that class for the
+service to start.
In addition, and perhaps more importantly, **Angelo is built upon Reel, which is, in turn, built upon
Celluloid::IO and gives you a reactor with evented IO in Ruby!**
-Note: There currently is no "standalone" capability where one can define route handlers at the top level.
+Things will feel very familiar to anyone experienced with Sinatra. You can define
+route handlers denoted by HTTP verb and path with parameters set from path matching (using
+[Mustermann](#mustermann)), the query string, and post body.
+A route block may return:
-Things will feel very familiar to anyone experienced with Sinatra. Inside the subclass, you can define
-route handlers denoted by HTTP verb and path. One acceptable return value from a route block is the body
-of the response in full as a `String`. Another is a `Hash` if the content type is set to `:json`. Finally,
-you may return any object that responds to `#each(&block)` if the transfer encoding is set to `:chunked`.
+* The body of the response in full as a `String`.
+* A `Hash` (or anything that `respond_to? :to_json`) if the content type is set to `:json`.
+* Any object that responds to `#each(&block)` if the transfer encoding is set to `:chunked`.
There is also a `chunked_response` helper that will take a block, set the transfer encoding, and return
-an appropriate object for you.
+an appropriate object.
-There is also [Mustermann](#mustermann) support for full-on, Sinatra-like path
-matching and params.
-
-Angelo also features `before` and `after` blocks, just like Sinatra. Filters are ordered as defined,
+Angelo also features `before` and `after` filter blocks, just like Sinatra. Filters are ordered as defined,
and called in that order. When defined without a path, they run for all matched requests. With a path,
-the path must match exactly for the block to be called. If `Angelo::Mustermann` is included, the paths
-are interpreted as a Mustermann pattern and params are merged. For more info on the difference in how
-after blocks are handled, see the Errors section below for more info.
+the path is interpreted as a Mustermann pattern and params are merged. `before` filters can set instance
+variables which can be used in the route block and the `after` filter.
+For more info on the difference in how after blocks are handled, see the Errors section below for more info.
### Websockets!
One of the main motivations for Angelo was the ability to define websocket handlers with ease. Through
the addition of a `websocket` route builder and a `websockets` helper, Angelo attempts to make it easy
@@ -50,14 +51,13 @@
##### Route Builder
The `websocket` route builder accepts a path and a block, and passes the actual websocket to the block
as the only argument. This socket is an instance of Reel's
[WebSocket](https://github.com/celluloid/reel/blob/master/lib/reel/websocket.rb) class, and, as such,
-responds to methods like `on_message` and `on_close`. A service-wide `on_pong` handler (defined at the
+responds to methods like `on_message` and `on_close`. A service-wide `on_pong` handler may be defined
+to customize the behavior when a pong frame comes back from a connected websocket client.
-a connected websocket client.
-
##### `websockets` helper
Angelo includes a "stash" helper for connected websockets. One can `<<` a websocket into `websockets`
from inside a websocket handler block. These can "later" be iterated over so one can do things like
emit a message on every connected websocket when the service receives a POST request.
@@ -94,19 +94,19 @@
end
Foo.run!
```
-In this case, any clients that connected to a websocket at the path '/' would be stashed in the
-default websockets array; clients that connected to '/bar' would be stashed into the `:bar` section.
+In this case, any clients that connect to a websocket at the path '/' will be stashed in the
+default websockets array; clients that connect to '/bar' will be stashed in the `:bar` section.
-Each "section" can accessed with a familiar, `Hash`-like syntax, and can be iterated over with
+Each "section" is accessed with a familiar, `Hash`-like syntax, and can be iterated over with
a `.each` block.
-When a `POST /` with a 'foo' param is received, any value is messaged out to any '/' connected
+When a `POST /` with a 'foo' param is received, any value is messaged out to all '/' connected
websockets. When a `POST /bar` with a 'bar' param is received, any value is messaged out to all
-websockets that connected to '/bar'.
+websockets connected to '/bar'.
### SSE - Server-Sent Events
The `eventsource` route builder also accepts a path and a block, and passes the socket to the block,
just like the `websocket` builder. This socket is actually the raw `Celluloid::IO::TCPSocket` and is
@@ -174,16 +174,16 @@
```
##### `sses` helper
Angelo also includes a "stash" helper for SSE connections. One can `<<` a socket into `sses` from
-inside an `eventsource` handler block. These can also be "later" be iterated over so one can do things
+inside an `eventsource` handler block. These can "later" be iterated over so one can do things
like emit a message on every SSE connection when the service receives a POST request.
-The `sses` helper includes the same a context ability as the `websockets` helper. Also, by default,
+The `sses` helper includes the same context ability as the `websockets` helper. Also, by default,
the helper will `reject!` any closed sockets before returning, just like `websockets`. You may
-optionally pass `false` to the helper to skip this step.In addition, the `sses` stash includes
+optionally pass `false` to the helper to skip this step. In addition, the `sses` stash includes
methods for easily sending events or messages to all stashed connections. **Note that the
`Stash::SSE#event` method only works on non-default contexts and uses the context name as the event
name.**
```ruby
@@ -225,11 +225,11 @@
do not support EventSource yet, since this will respond with a non-"text/event-stream" Content-Type if
'sse' is not present in the params.
##### `EventSource#on_close` helper
-When inside an eventsource block, you can use may want to do something specific when a client closes the
+When inside an eventsource block, you may want to do something specific when a client closes the
connection. For this case, there are `on_close` and `on_close=` methods on the object passed to the block
that will get called if the client closes the socket. The assignment method takes a proc object and the
other one takes a block:
```ruby
@@ -250,27 +250,27 @@
sses << es
end
```
-Note the use of the optional parameter the stashes here; by default, stash accessors (`websockets` and
+Note the use of the optional parameter of the stashes here; by default, stash accessors (`websockets` and
`sses`) will `reject!` any closed sockets before letting you in. If you pass `false` to the stash
accessors, they will skip the `reject!` step.
### Tasks + Async / Future
-Angelo is built on Reel and Celluloid::IO, giving your web application class the ability to define
+Angelo is built on Reel and Celluloid::IO, giving your web application the ability to define
"tasks" and call them from route handler blocks in an `async` or `future` style.
##### `task` builder
You can define a task on the reactor using the `task` class method and giving it a symbol and a
block. The block can take arguments that you can pass later, with `async` or `future`.
```ruby
# defining a task on the reactor called `:in_sec` which will sleep for
-# given number of seconds, then return the given message.
+# the given number of seconds, then return the given message.
#
task :in_sec do |sec, msg|
sleep sec.to_i
msg
end
@@ -298,13 +298,13 @@
```
##### `future` helper
Just like `async`, this comes from Celluloid as well. It behaves exactly like `async`, with the
-notable exception of returing a "future" object that you can call `#value` on later to retreive
-the return value of the task. Once `#value` is called, things will "block" until the task is
-finished.
+notable exception of returning a "future" object that you can call `#value` on later to retrieve
+the return value of the task. Calling `#value` will "block" until the task is finished, while the
+reactor continues to process requests.
```ruby
get '/' do
# run the task defined above asynchronously, return immediately
#
@@ -322,21 +322,21 @@
Angelo gives you two ordained methods of stopping route processing:
* raise an instance of `RequestError`
* `halt` with a status code and message
-The main difference is that `halt` will still run an `after` block, and raising `RequestError`
-will bypass the `after` block.
+The main difference is that `halt` will still run `after` blocks, and raising `RequestError`
+will bypass `after` blocks.
Any other exceptions or errors raised by your route handler will be handled with a 500 status
code and the message will be the body of the response.
#### RequestError
Raising an instance of `Angelo::RequestError` causes a 400 status code response, and the message
in the instance is the body of the the response. If the route or class was set to respond with
-JSON, the body is converted to a JSON object with one key, `error`, that has a value of the message.
+JSON, the body is converted to a JSON object with one key, `error`, that has the value of the message.
If the message is a `Hash`, the hash is converted to a JSON object, or to a string for other content
types.
If you want to return a different status code, you can pass it as a second argument to
`RequestError.new`. See example below.
@@ -412,19 +412,12 @@
everything's fine
```
### [Tilt](https://github.com/rtomayko/tilt) / ERB
-To make `erb` available in route blocks
-
-1. add `tilt` to your `Gemfile`: `gem 'tilt'`
-2. require `angelo/tilt/erb`
-3. include `Angelo::Tilt::ERB` in your app
-
```ruby
class Foo < Angelo::Base
- include Angelo::Tilt::ERB
views_dir 'some/other/path' # defaults to './views'
get '/' do
erb :index
@@ -446,21 +439,12 @@
See [views](https://github.com/kenichi/angelo/tree/master/test/test_app_root/views) for examples.
### [Mustermann](https://github.com/rkh/mustermann)
-To make routes blocks match path with Mustermann patterns
-
-1. be using ruby >=2.0.0
-2. add 'mustermann' to to your `Gemfile`: `platform(:ruby_20){ gem 'mustermann' }`
-3. require `angelo/mustermann`
-4. include `Angelo::Mustermann` in your app
-
```ruby
class Foo < Angelo::Base
- include Angelo::Tilt::ERB
- include Angelo::Mustermann
get '/:foo/things/:bar' do
# `params` is merged with the Mustermann object#params hash, so
# a "GET /some/things/are_good?foo=other&bar=are_bad" would have:
@@ -484,21 +468,81 @@
end
end
```
+### Classic vs. modular apps
+
+Like Sinatra, Angelo apps can be written in either "classic" style or
+so-called "modular" style. Which style you use is more of a personal
+preference than anything else.
+
+A classic-style app requires "angelo/main" and defines the app
+directly at the top level using the DSL. In addition, classic apps:
+
+* Can use a `helpers` block to define methods that can be called from
+filters and route handlers. The `helpers` method can also include methods
+from one or more modules passed as arguments instead of or in addition
+to taking a block.
+* Parse optional command-line options "-o addr" and "-p port" to set
+the bind address and listen port, respectively.
+* Are run automatically.
+
+*Note: unlike Sinatra, define a classic app by requiring "angelo/main"
+and a modular app by requiring "angelo". Sinatra uses "sinatra" and
+"sinatra/base" to do the same things.*
+
+Here's a classic app:
+
+```ruby
+require 'angelo/main'
+
+helpers do
+ def say_hello
+ "Hello"
+ end
+end
+
+get "/hello" do
+ "#{say_hello} to you, too."
+end
+```
+
+And the same app in modular style:
+
+```ruby
+require 'angelo'
+
+class HelloApp < Angelo::Base
+ def say_hello
+ "Hello"
+ end
+
+ get "/hello" do
+ "#{say_hello} to you, too."
+ end
+end
+
+HelloApp.run!
+```
+
+### Documentation
+
+**I'm bad at documentation and I feel bad.**
+
+Others have helped, and there is a YaRD plugin for Angelo [here](https://github.com/artcom/yard-angelo)
+if you would like to document your apps built with Angelo. (thanks: @katjaeinsfeld, @artcom)
+
### WORK LEFT TO DO
Lots of work left to do!
### Full-ish example
```ruby
require 'angelo'
-require 'angelo/mustermann'
class Foo < Angelo::Base
- include Angelo::Mustermann
# just some constants to use in routes later...
#
TEST = {foo: "bar", baz: 123, bat: false}.to_json
HEART = '<3'