action_tree 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.yardopts +3 -5
- data/README.md +6 -77
- data/Rakefile +16 -3
- data/TODO +84 -26
- data/VERSION +1 -1
- data/lib/action_tree.rb +46 -27
- data/lib/action_tree/basic.rb +36 -0
- data/lib/action_tree/basic/helpers.rb +20 -0
- data/lib/action_tree/basic/node.rb +294 -103
- data/lib/action_tree/basic/query.rb +211 -0
- data/lib/action_tree/basic/shared.rb +25 -0
- data/lib/action_tree/capture_hash.rb +28 -3
- data/lib/action_tree/components/rack.rb +52 -0
- data/lib/action_tree/components/rack/request.rb +59 -0
- data/lib/action_tree/components/rack/response.rb +10 -0
- data/lib/action_tree/components/tilt.rb +65 -0
- data/lib/action_tree/errors.rb +12 -0
- data/lib/action_tree/eval_scope.rb +18 -16
- data/spec/00_foundations/capture_hash_spec.rb +47 -0
- data/spec/00_foundations/eval_scope_spec.rb +80 -0
- data/spec/{02_node_spec.rb → 01_node/02_node_spec.rb} +8 -13
- data/spec/{03_match_spec.rb → 02_query/query_spec.rb} +5 -4
- data/spec/{04_integration_spec.rb → 03_action_tree_basic/04_integration_spec.rb} +10 -8
- data/spec/{p01_tilt_spec.rb → 04_components/template_spec.rb} +5 -9
- metadata +66 -20
- data/MANUAL.md +0 -277
- data/lib/action_tree/basic/match.rb +0 -132
- data/lib/action_tree/dialect_helper.rb +0 -12
- data/lib/action_tree/plugins/tilt.rb +0 -65
- data/spec/01_support_lib_spec.rb +0 -41
data/.yardopts
CHANGED
data/README.md
CHANGED
@@ -1,88 +1,17 @@
|
|
1
|
-
[**CLICK HERE TO READ THE MANUAL**](http://rdoc.info/github/jbe/action_tree/master/file/MANUAL.md)
|
2
|
-
*ALPHA VERSION. Documentation may be out of date.*
|
3
|
-
<pre>
|
4
|
-
\/ | |/
|
5
|
-
\/ / \||/ /_/___/_
|
6
|
-
\/ |/ \/
|
7
|
-
_\__\_\ | /_____/_
|
8
|
-
\ | / /
|
9
|
-
__ _-----` |{,-----------~
|
10
|
-
\ }{
|
11
|
-
}{{ ACTION TREE:
|
12
|
-
}}{
|
13
|
-
{{} a dry request router/controller
|
14
|
-
, -=-~{ .-^- _
|
15
|
-
ejm `}
|
16
|
-
{
|
17
|
-
</pre>
|
18
1
|
|
19
|
-
|
2
|
+
ActionTree
|
3
|
+
==========
|
20
4
|
|
21
|
-
|
22
|
-
----------
|
5
|
+
ActionTree helps you write DRY and concise controller logic. It provides a compact DSL for defining actions and mapping them against the requests that invoke them.
|
23
6
|
|
7
|
+
[**Manual**](https://github.com/jbe/action_tree/wiki)
|
24
8
|
|
25
|
-
|
9
|
+
[**API docs**](http://rubydoc.info/github/jbe/action_tree/master/frames)
|
26
10
|
|
27
|
-
|
28
|
-
welcome
|
29
|
-
|-- alligators
|
30
|
-
| |-- list
|
31
|
-
| |-- create
|
32
|
-
| `-- (with an id)
|
33
|
-
| |-- edit
|
34
|
-
| `-- hug
|
35
|
-
|
36
|
-
A map like this not only shows paths, but also relationships between actions. For instance, `edit` and `hug` retrieve a specific alligator (with an id) before doing anything else. Similarly, every request beneath `/alligators/` should be counted, to see if we're on Digg yet.
|
37
|
-
|
38
|
-
So let's put those two in `before` hooks in an ActionTree:
|
39
|
-
|
40
|
-
routes = ActionTree.new do
|
41
|
-
action { "Welcome to the alligator manager #{@name}!" }
|
42
|
-
|
43
|
-
with 'alligators' do
|
44
|
-
before { @count = (@count || 0) + 1 }
|
45
|
-
|
46
|
-
action { Alligator.all }
|
47
|
-
action('create') { Alligator.create }
|
48
|
-
|
49
|
-
with :id do
|
50
|
-
before { @alligator = Alligator.get(@id) }
|
51
|
-
action { 'this is ' + @alligator }
|
52
|
-
action('edit') { "editing #{@alligator}" }
|
53
|
-
action('hug') { "#{@alligator} is pleased." }
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
We can then match and run actions:
|
59
|
-
|
60
|
-
routes.match('/') #=> #<ActionTree::Match >
|
61
|
-
routes.match('/').found? #=> true
|
62
|
-
|
63
|
-
@name = 'Zap'
|
64
|
-
routes.match('/').run(self) # => "Welcome to the alligator manager, Zap."
|
65
|
-
|
66
|
-
routes.match('/alligators/4').run # => "this is #<Alligator 4>"
|
67
|
-
router.match('/alligators/8/hug').run # => "<#Alligator 8> is pleased."
|
68
|
-
|
69
|
-
routes.match('nonexistant/route') # => #<ActionTree::NotFound>
|
70
|
-
routes.match('nonexistant/route').found? #=> false
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
Available dialects
|
77
|
-
------------------
|
78
|
-
|
79
|
-
[**at-rack**](https://github.com/jbe/at-rack) turns ActionTree into a Sinatra-like framework, and allows you to mount trees as rack apps.
|
80
|
-
|
81
|
-
**at-websocket** is planned to make writing websocket servers easier.
|
11
|
+
*NB: Alpha stage. Unstable API.*
|
82
12
|
|
83
13
|
|
84
14
|
|
85
15
|
---
|
86
16
|
|
87
17
|
Copyright (c) 2010-2011 Jostein Berre Eliassen. See [LICENSE](http://rdoc.info/github/jbe/action_tree/master/file/LICENSE) for details.
|
88
|
-
|
data/Rakefile
CHANGED
@@ -5,8 +5,8 @@ begin
|
|
5
5
|
require 'jeweler'
|
6
6
|
Jeweler::Tasks.new do |gem|
|
7
7
|
gem.name = "action_tree"
|
8
|
-
gem.summary = %Q{Versatile
|
9
|
-
gem.description = %Q{ActionTree is a router. It provides a compact DSL for defining
|
8
|
+
gem.summary = %Q{Versatile and DRY controllers and routing.}
|
9
|
+
gem.description = %Q{ActionTree is a DRY request router. It provides a compact DSL for defining actions and mapping requests to invoke them.}
|
10
10
|
gem.email = "post@jostein.be"
|
11
11
|
gem.homepage = "http://github.com/jbe/action_tree"
|
12
12
|
gem.authors = ["jbe"]
|
@@ -14,6 +14,10 @@ begin
|
|
14
14
|
#gem.required_ruby_version = '>= 1.8.7'
|
15
15
|
gem.add_dependency 'backports'
|
16
16
|
|
17
|
+
gem.add_development_dependency 'rspec'
|
18
|
+
gem.add_development_dependency 'yard'
|
19
|
+
gem.add_development_dependency 'yard-rspec'
|
20
|
+
|
17
21
|
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
18
22
|
end
|
19
23
|
Jeweler::GemcutterTasks.new
|
@@ -31,10 +35,19 @@ end
|
|
31
35
|
require 'rspec/core/rake_task'
|
32
36
|
RSpec::Core::RakeTask.new(:spec)
|
33
37
|
|
38
|
+
require 'yard'
|
39
|
+
YARD::Rake::YardocTask.new do |t|
|
40
|
+
#t.files = ['lib/**/*.rb', OTHER_PATHS] # optional
|
41
|
+
#t.options = ['--any', '--extra', '--opts'] # optional
|
42
|
+
end
|
34
43
|
|
35
44
|
task :spec => :check_dependencies
|
36
|
-
task :default => :spec
|
37
45
|
|
46
|
+
task :docs => :yard
|
47
|
+
|
48
|
+
task :build => :docs
|
49
|
+
|
50
|
+
task :default => :spec
|
38
51
|
|
39
52
|
|
40
53
|
#namespace :test do
|
data/TODO
CHANGED
@@ -1,32 +1,90 @@
|
|
1
1
|
|
2
2
|
|
3
3
|
|
4
|
+
porting sinatra
|
5
|
+
code: l 59
|
6
|
+
HTTP Verb helpers
|
7
|
+
halt(code)
|
8
|
+
specs: l 0
|
4
9
|
|
5
|
-
dialect mixins
|
6
|
-
templating
|
7
|
-
|
8
|
-
rewrite tests
|
9
|
-
tests for:
|
10
|
-
variable scope copy
|
11
|
-
after hooks
|
12
|
-
action namespaces
|
13
|
-
running different action layers
|
14
|
-
get, put, post, delete
|
15
|
-
postprocessors
|
16
|
-
mounting
|
17
|
-
layers & application
|
18
|
-
|
19
|
-
|
20
|
-
documentation for:
|
21
|
-
postprocessors
|
22
|
-
action namespaces
|
23
|
-
new mounting
|
24
|
-
layer application
|
25
|
-
ActionTree.layer
|
26
|
-
Node.apply(layer)
|
27
|
-
mounting versus layer application
|
28
|
-
get, put, post, and update helpers
|
29
|
-
DSL shorthands/aliases
|
30
|
-
applying plugins
|
31
10
|
|
11
|
+
code:
|
12
|
+
Basic
|
13
|
+
finish documenting Node
|
14
|
+
Give Match a wash
|
15
|
+
document Match
|
16
|
+
Templates
|
17
|
+
raise errors:
|
18
|
+
template not found
|
19
|
+
|
20
|
+
display errors?
|
32
21
|
|
22
|
+
|
23
|
+
documentation
|
24
|
+
Rack
|
25
|
+
use Rack::ShowExceptions for html error pages.
|
26
|
+
`use` belongs in config.ru :)
|
27
|
+
|
28
|
+
ideas
|
29
|
+
`at-rack-serve` command-line tool
|
30
|
+
|
31
|
+
|
32
|
+
ActionTree spec outline
|
33
|
+
=======================
|
34
|
+
|
35
|
+
00 Foundations [DONE]
|
36
|
+
|
37
|
+
01 Node
|
38
|
+
|
39
|
+
#inspect
|
40
|
+
#printout
|
41
|
+
|
42
|
+
#locate
|
43
|
+
|
44
|
+
- for string, symbol, sinatra-style, nasty
|
45
|
+
sinatra-style regexp, and nasty regexp
|
46
|
+
#match?
|
47
|
+
#capture_names
|
48
|
+
#read_captures
|
49
|
+
|
50
|
+
dsl
|
51
|
+
#route
|
52
|
+
#mount
|
53
|
+
when children have the same name
|
54
|
+
#add_child
|
55
|
+
|
56
|
+
02 Match
|
57
|
+
|
58
|
+
#run
|
59
|
+
not_found
|
60
|
+
actions
|
61
|
+
namespaces
|
62
|
+
exception handling
|
63
|
+
scope
|
64
|
+
user supplied
|
65
|
+
hash sets instance variables
|
66
|
+
module is extended
|
67
|
+
array is both
|
68
|
+
captures
|
69
|
+
sinatra style
|
70
|
+
regex style
|
71
|
+
accumulate
|
72
|
+
|
73
|
+
|
74
|
+
03 ActionTree::Basic
|
75
|
+
scenario with:
|
76
|
+
captures: string, symbol, nasty sinatra-style
|
77
|
+
building
|
78
|
+
using: array paths, regex paths, string paths, symbol paths
|
79
|
+
|
80
|
+
config: inheritance, overwrite
|
81
|
+
|
82
|
+
actions: namespaces, after, before, postprocessors, deep
|
83
|
+
|
84
|
+
fancy: mounting
|
85
|
+
|
86
|
+
run-time: user vars, priority, @config
|
87
|
+
|
88
|
+
04 Components
|
89
|
+
tilt
|
90
|
+
http verb
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.2.0
|
data/lib/action_tree.rb
CHANGED
@@ -1,45 +1,64 @@
|
|
1
1
|
|
2
|
+
#
|
3
|
+
# \/ | |/
|
4
|
+
# \/ / \||/ /_/___/_
|
5
|
+
# \/ |/ \/
|
6
|
+
# _\__\_\ | /_____/_
|
7
|
+
# \ | / /
|
8
|
+
# __ _-----` |{,-----------~
|
9
|
+
# \ }{
|
10
|
+
# }{{
|
11
|
+
# }}{ ActionTree (c) 2011
|
12
|
+
# {{} Jostein Berre Eliassen
|
13
|
+
# , -=-~{ .-^- _
|
14
|
+
# ejm `}
|
15
|
+
# {
|
16
|
+
#
|
17
|
+
|
2
18
|
require 'set'
|
3
19
|
require 'backports'
|
4
20
|
|
21
|
+
# The main ActionTree namespace. Provides convenient
|
22
|
+
# shortcuts to common operations. Contains utility classes
|
23
|
+
# and dialect modules. For more on dialects, see {Basic}.
|
24
|
+
#
|
25
|
+
# @see https://github.com/jbe/action_tree/wiki The ActionTree Wiki
|
26
|
+
|
5
27
|
module ActionTree
|
6
28
|
|
7
29
|
require 'action_tree/eval_scope'
|
8
30
|
require 'action_tree/capture_hash'
|
9
|
-
require 'action_tree/
|
10
|
-
|
11
|
-
module Plugins
|
12
|
-
require 'action_tree/plugins/tilt'
|
13
|
-
end
|
31
|
+
require 'action_tree/errors'
|
32
|
+
require 'action_tree/basic'
|
14
33
|
|
34
|
+
include Errors
|
15
35
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
end
|
20
|
-
def apply(mod)
|
21
|
-
self::Node.send(:include, mod::NodeMixin)
|
22
|
-
self::Match.send(:include, mod::MatchMixin)
|
23
|
-
self::Match::DEFAULT_HELPERS << mod::Helpers
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
# load basic dialect
|
28
|
-
module Basic
|
29
|
-
extend Dialect
|
30
|
-
require "action_tree/basic/node"
|
31
|
-
require "action_tree/basic/match"
|
32
|
-
end
|
36
|
+
# @private
|
37
|
+
# Named procs that can be applied to trees.
|
38
|
+
MACROS = {}
|
33
39
|
|
34
|
-
#
|
40
|
+
# Create a new {Basic} ActionTree.
|
41
|
+
#
|
42
|
+
# @yield Constructs the tree from the given block of directives
|
43
|
+
#
|
44
|
+
# @example
|
45
|
+
# a = ActionTree.new do
|
46
|
+
# action('hi') { "Hello!" }
|
47
|
+
# end
|
48
|
+
# a.match('hi').run #=> "Hello!"
|
49
|
+
#
|
50
|
+
# @see Basic::Node
|
51
|
+
#
|
52
|
+
# @return [Basic::Node] Root node of the created tree
|
35
53
|
def self.new(&blk)
|
36
54
|
Basic.new(&blk)
|
37
55
|
end
|
38
56
|
|
39
|
-
#
|
40
|
-
|
41
|
-
|
42
|
-
|
57
|
+
# Create an optionally named macro. If a name is given, the macro is remembered by that name, and can later be referenced using {Basic::Node#apply}.
|
58
|
+
#
|
59
|
+
# @return [Proc] The macro
|
60
|
+
def self.macro(name=nil, &blk)
|
61
|
+
name ? (MACROS[name] = blk) : blk
|
43
62
|
end
|
44
63
|
|
45
64
|
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
|
2
|
+
# The basic ActionTree namespace that other dialects build upon. Contains default config values, default helpers, and the {Node} and {Query} classes that make up ActionTree's main logic.
|
3
|
+
#
|
4
|
+
# @see https://github.com/jbe/action_tree/wiki/Dialects-and-components Wiki page about dialects and components
|
5
|
+
#
|
6
|
+
|
7
|
+
module ActionTree::Basic
|
8
|
+
|
9
|
+
CONFIG = {}
|
10
|
+
require 'action_tree/basic/helpers'
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
|
14
|
+
# Create a new ActionTree (or rather, {Node})
|
15
|
+
# of this dialect
|
16
|
+
def new(*prms, &blk)
|
17
|
+
self::Node.new(*prms, &blk)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Use a component. The component will be used by all
|
21
|
+
# descending dialects, and must not be used again.
|
22
|
+
def use(component)
|
23
|
+
self::Node.send :include, component::NodeMethods
|
24
|
+
self::Query.send :include, component::QueryMethods
|
25
|
+
self::Helpers.send :include, component::Helpers
|
26
|
+
self::CONFIG.merge! component::CONFIG
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
extend ClassMethods
|
31
|
+
|
32
|
+
require "action_tree/basic/shared"
|
33
|
+
require "action_tree/basic/node"
|
34
|
+
require "action_tree/basic/query"
|
35
|
+
end
|
36
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
|
2
|
+
# Contains the default helpers available inside an ActionTree
|
3
|
+
# execution scope.
|
4
|
+
#
|
5
|
+
# @see https://github.com/jbe/action_tree/wiki/Basic-usage Wiki on basic usage
|
6
|
+
|
7
|
+
module ActionTree::Basic::Helpers
|
8
|
+
|
9
|
+
# Writes a configuration value if it
|
10
|
+
# has not been set already
|
11
|
+
def default(key, value)
|
12
|
+
@_conf[key] ||= value
|
13
|
+
end
|
14
|
+
|
15
|
+
# Writes or overwrites a configuration value
|
16
|
+
def set(key, value)
|
17
|
+
@_conf[key] = value
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -1,167 +1,358 @@
|
|
1
1
|
|
2
|
+
|
3
|
+
# Every ActionTree fundamentally consists of Nodes. The {Node} class
|
4
|
+
# contains the methods used to build trees (the DSL). It is the
|
5
|
+
# core of ActionTree.
|
6
|
+
#
|
7
|
+
# @see https://github.com/jbe/action_tree/wiki The ActionTree Wiki
|
8
|
+
|
2
9
|
class ActionTree::Basic::Node
|
3
10
|
|
4
|
-
include
|
11
|
+
include ActionTree::Basic::Shared
|
12
|
+
include ActionTree::Errors
|
13
|
+
|
14
|
+
# The token that request path fragments will be matched against.
|
15
|
+
# @return [String, Symbol, Regex]
|
16
|
+
attr_reader :token
|
17
|
+
|
18
|
+
# The nodes children; representing its direct sub-paths.
|
19
|
+
# @return [Set]
|
20
|
+
attr_reader :children
|
21
|
+
|
22
|
+
# The module containing any user defined helpers.
|
23
|
+
# @return [Module]
|
24
|
+
attr_reader :helper_scope
|
25
|
+
|
26
|
+
# The configuration values set for the node.
|
27
|
+
# @return [Hash]
|
28
|
+
attr_reader :config
|
29
|
+
|
30
|
+
# The actions defined for the node. `nil` is used for the default action.
|
31
|
+
# @return [Hash]
|
32
|
+
attr_reader :actions
|
33
|
+
|
34
|
+
# The procs to be invoked before the action is run.
|
35
|
+
# @return [Array]
|
36
|
+
attr_reader :before_hooks
|
37
|
+
|
38
|
+
# The procs to be invoked after the action is run.
|
39
|
+
# @return [Array]
|
40
|
+
attr_reader :after_hooks
|
41
|
+
|
42
|
+
# The procs to chain-process the result of running the action.
|
43
|
+
# @return [Array]
|
44
|
+
attr_reader :processors
|
45
|
+
|
46
|
+
# Same as {Node#processors}, but also processes child nodes.
|
47
|
+
# @return [Array]
|
48
|
+
attr_reader :deep_processors
|
5
49
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
attr_accessor :not_found_handler
|
50
|
+
# The hash of procs that will handle exceptions in child node actions.
|
51
|
+
# @return [Hash] where `ErrorClass => proc {|err| handler }`
|
52
|
+
attr_accessor :exception_handlers
|
10
53
|
|
11
54
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
@
|
20
|
-
@
|
21
|
-
@
|
22
|
-
|
55
|
+
# Create a new Node or tree of Nodes.
|
56
|
+
#
|
57
|
+
# @param [nil, String, Symbol, Regexp] token An optional node token. Not needed for root nodes unless the it is later mounted as a child somewhere.
|
58
|
+
#
|
59
|
+
# @yield Takes an optional block to be evaluated inside the Node. This is used to construct the ActionTree, using the "DSL".
|
60
|
+
#
|
61
|
+
def initialize(token=nil, &blk)
|
62
|
+
@token = token # string, symbol or regex
|
63
|
+
@children = Set.new # set of nodes
|
64
|
+
@helper_scope = Module.new
|
65
|
+
@config = {}
|
66
|
+
@actions = {} # {:name => proc {...}}
|
67
|
+
@before_hooks = [] # [procs] without params
|
68
|
+
@after_hooks = [] # [procs] without params
|
69
|
+
@processors = [] # [procs] with one param
|
70
|
+
@deep_processors = [] # ditto
|
71
|
+
@exception_handlers = {} # {ErrClass => proc {...}}
|
72
|
+
route([], &blk) if blk
|
23
73
|
end
|
24
74
|
|
25
|
-
# INSPECTION
|
26
75
|
|
76
|
+
## INSPECTION
|
77
|
+
|
78
|
+
# String representation for inspecting the node.
|
79
|
+
# @return [String]
|
27
80
|
def inspect
|
28
81
|
"#<#{self.class}:#{self.object_id.to_s(16)} #{@token.inspect} >"
|
29
82
|
end
|
30
83
|
|
84
|
+
# A tree-like multiline string of descending nodes.
|
85
|
+
# @return [String]
|
31
86
|
def printout(stack='')
|
32
87
|
stack + @token.inspect + "\n" +
|
33
88
|
@children.map {|c| c.printout(stack + ' ') }.join
|
34
89
|
end
|
35
90
|
|
36
|
-
# MATCHING
|
37
|
-
|
38
|
-
def regexp
|
39
|
-
@regexp ||=
|
40
|
-
Regexp.new('^' + case @token
|
41
|
-
when Regexp then "(#{@token.source})"
|
42
|
-
when Symbol then '(.+)'
|
43
|
-
when String then @token.gsub(/:\w+/, '(.+)')
|
44
|
-
#when nil then '\/*'
|
45
|
-
end + '$')
|
46
|
-
end
|
47
91
|
|
48
|
-
|
49
|
-
!!path_fragment.match(regexp)
|
50
|
-
end
|
92
|
+
## DSL
|
51
93
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
94
|
+
# Find or create a descending node.
|
95
|
+
#
|
96
|
+
# @param [Array, String, Symbol, Regexp, nil] location
|
97
|
+
# A Node location relative to this Node
|
98
|
+
#
|
99
|
+
# @see https://github.com/jbe/action_tree/wiki/Definitions Location format specification in the Wiki
|
100
|
+
#
|
101
|
+
# @return [Node] at the given relative location.
|
102
|
+
# Created automatically if it does not exist.
|
103
|
+
def locate(location)
|
104
|
+
loc = parse_path(location)
|
105
|
+
if loc.empty? then self else
|
106
|
+
token = loc.shift
|
107
|
+
(get_child(token) || make_child(token)).locate(loc)
|
58
108
|
end
|
59
109
|
end
|
60
|
-
|
61
|
-
|
62
|
-
#
|
63
|
-
|
110
|
+
|
111
|
+
# Evaluate the given block in a descending node,
|
112
|
+
# at the specified `location`.
|
113
|
+
#
|
114
|
+
# @param location (see #locate)
|
115
|
+
# @yield Takes a block to be evaluated at `location`
|
116
|
+
#
|
117
|
+
# @return [Node] self
|
64
118
|
def route(location=nil, &blk)
|
65
|
-
|
119
|
+
if blk.nil? && location.is_a?(Proc)
|
120
|
+
blk = location
|
121
|
+
location = nil
|
122
|
+
end
|
123
|
+
locate(location).instance_eval(&blk)
|
66
124
|
self
|
67
125
|
end
|
68
|
-
alias :with
|
69
|
-
alias :w
|
70
|
-
alias :r
|
71
|
-
|
72
|
-
|
126
|
+
alias :with :route
|
127
|
+
alias :w :route
|
128
|
+
alias :r :route
|
129
|
+
|
130
|
+
# Apply a (named) `Proc`, referred to as a macro.
|
131
|
+
#
|
132
|
+
# @param [Proc, Object] macro
|
133
|
+
# A Proc or the name of a stored proc (defined using
|
134
|
+
# {ActionTree.macro}) to be evaluated in the node.
|
135
|
+
#
|
136
|
+
def apply(macro)
|
137
|
+
case macro
|
138
|
+
when Proc then route(¯o)
|
139
|
+
else route(&::ActionTree::MACROS[macro])
|
140
|
+
end
|
141
|
+
end
|
73
142
|
|
74
|
-
|
75
|
-
|
143
|
+
# Define an optionally namespaced action, optionally at
|
144
|
+
# the specified `location`.
|
145
|
+
#
|
146
|
+
# @param location (see #locate)
|
147
|
+
# @param namespace The name of the action. Used to
|
148
|
+
# place actions within a namespace (e.g. for HTTP verbs).
|
149
|
+
#
|
150
|
+
# @yield Takes a block containing the action.
|
151
|
+
#
|
152
|
+
# @return [Node] self
|
153
|
+
def action(location=nil, namespace=nil, &blk)
|
154
|
+
locate(location).actions[namespace] = blk
|
76
155
|
self
|
77
156
|
end
|
78
157
|
alias :a :action
|
79
|
-
alias :o :action
|
80
|
-
def get( loc=nil, &blk); action(loc, :get, &blk); end
|
81
|
-
def put( loc=nil, &blk); action(loc, :put, &blk); end
|
82
|
-
def post( loc=nil, &blk); action(loc, :post, &blk); end
|
83
|
-
def delete(loc=nil, &blk); action(loc, :delete, &blk); end
|
84
158
|
|
159
|
+
# Define helper methods
|
160
|
+
# Open a scope for defining helper methods, optionally at the specified `location`. They will be available to this node, as well as all descendants.
|
161
|
+
#
|
162
|
+
# @param location (see #locate)
|
163
|
+
#
|
164
|
+
# @yield Takes a block containing helper method definitions.
|
165
|
+
#
|
166
|
+
# @example
|
167
|
+
# a = ActionTree.new do
|
168
|
+
# helpers do
|
169
|
+
# def sing
|
170
|
+
# 'lala'
|
171
|
+
# end
|
172
|
+
# end
|
173
|
+
#
|
174
|
+
# action { sing }
|
175
|
+
# end
|
176
|
+
#
|
177
|
+
# a.match('/').run # => "lala"
|
178
|
+
#
|
85
179
|
def helpers(location=nil, &blk)
|
86
|
-
|
180
|
+
locate(location).helper_scope.module_eval(&blk)
|
87
181
|
end
|
88
182
|
|
183
|
+
# Add a before hook, optionally at the specified `location`. It will apply to this node, as well as all descendants.
|
184
|
+
#
|
185
|
+
# @param location (see #locate)
|
186
|
+
#
|
187
|
+
# @yield Takes a block containing the hook to be run.
|
89
188
|
def before(location=nil, &blk)
|
90
|
-
|
189
|
+
locate(location).before_hooks << blk
|
91
190
|
end
|
92
191
|
alias :b :before
|
93
192
|
|
193
|
+
# Add an after hook, optionally at the specified `location`. Like the {Node#before} hook, it will also apply to all descendants.
|
194
|
+
#
|
195
|
+
# @param location (see #locate)
|
196
|
+
#
|
197
|
+
# @yield (see #before)
|
94
198
|
def after(location=nil, &blk)
|
95
|
-
|
199
|
+
locate(location).after_hooks << blk
|
200
|
+
end
|
201
|
+
alias :af :after
|
202
|
+
|
203
|
+
# Add an exception handler
|
204
|
+
#
|
205
|
+
# @example
|
206
|
+
# handle(MyException) do |err|
|
207
|
+
# "oops, #{err}"
|
208
|
+
# end
|
209
|
+
def handle(error_class, &blk)
|
210
|
+
unless error_class.ancestors.include?(Exception)
|
211
|
+
raise ArgumentError, "#{error_class.inspect} is not an exception."
|
212
|
+
end
|
213
|
+
@exception_handlers[error_class] = blk
|
214
|
+
end
|
215
|
+
alias :h :handle
|
216
|
+
|
217
|
+
# Add a processor
|
218
|
+
def processor(location=nil, &blk)
|
219
|
+
locate(location).processors << blk
|
96
220
|
end
|
221
|
+
alias :p :processor
|
97
222
|
|
98
|
-
|
99
|
-
|
223
|
+
# Add a deep processor
|
224
|
+
def deep_processor(location=nil, &blk)
|
225
|
+
locate(location).deep_processors << blk
|
100
226
|
end
|
101
|
-
alias :
|
227
|
+
alias :dp :deep_processor
|
102
228
|
|
103
|
-
|
104
|
-
|
229
|
+
# Set a configuration value
|
230
|
+
def set(key, value)
|
231
|
+
config[key] = value
|
105
232
|
end
|
106
|
-
alias :p :postprocessor
|
107
233
|
|
234
|
+
# Add the children of another Node to the children
|
235
|
+
# of this Node.
|
108
236
|
def mount(node, location=nil)
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
237
|
+
locate(location).add_child(*node.children)
|
238
|
+
end
|
239
|
+
|
240
|
+
# Add one or several children to the node, provided
|
241
|
+
# they are compatible (i.e. of the same dialect).
|
242
|
+
def add_child(*nodes)
|
243
|
+
nodes.each {|n| validate_child(n) }
|
244
|
+
children << nodes
|
114
245
|
end
|
115
246
|
|
116
|
-
|
117
|
-
# LOOKUPS
|
118
247
|
|
119
|
-
|
120
|
-
|
248
|
+
## MATCHING METHODS
|
249
|
+
|
250
|
+
# Match a against a request path, building a chain of {Query} objects.
|
251
|
+
#
|
252
|
+
# @param [String, Array] path The lookup path to query.
|
253
|
+
# @param namespace The action namespace.
|
254
|
+
#
|
255
|
+
# @return [Query] the result (even when not found).
|
256
|
+
def match(path=[], namespace=nil)
|
257
|
+
dialect::Query.new(self, nil, nil, namespace).match(path)
|
121
258
|
end
|
259
|
+
alias :query :match
|
122
260
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
261
|
+
|
262
|
+
|
263
|
+
# === End of public API ===
|
264
|
+
|
265
|
+
|
266
|
+
# Check if a path fragment matches the node token.
|
267
|
+
# @private
|
268
|
+
def match?(path_fragment)
|
269
|
+
path_fragment.match(regexp)
|
270
|
+
end
|
271
|
+
|
272
|
+
# The names that this node will capture to.
|
273
|
+
# @private
|
274
|
+
def capture_names
|
275
|
+
@capture_names ||= case @token
|
276
|
+
when Regexp then ['match']
|
277
|
+
when Symbol then [@token.to_s]
|
278
|
+
when String
|
279
|
+
@token.scan(/:\w+/).map {|m| m[1..-1] }
|
128
280
|
end
|
129
281
|
end
|
130
282
|
|
283
|
+
# Reads captures from a path fragment, storing them
|
284
|
+
# under their names in a {CaptureHash}
|
285
|
+
# @private
|
286
|
+
def read_captures(fragment, hsh)
|
287
|
+
return hsh if capture_names.empty?
|
288
|
+
captures = fragment.match(regexp).captures
|
289
|
+
capture_names.each_with_index do |name, i|
|
290
|
+
hsh.add(name, captures[i])
|
291
|
+
end; hsh
|
292
|
+
end
|
131
293
|
|
132
294
|
private
|
133
295
|
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
296
|
+
# regexp to see if a path fragment matches
|
297
|
+
# this node's token
|
298
|
+
def regexp
|
299
|
+
@regexp ||=
|
300
|
+
Regexp.new('^' + case @token
|
301
|
+
when Regexp then "(#{@token.source})"
|
302
|
+
when Symbol then '(.+)'
|
303
|
+
when String then @token.gsub(/:\w+/, '(.+)')
|
304
|
+
end + '$')
|
305
|
+
end
|
139
306
|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
307
|
+
def get_child(token)
|
308
|
+
@children.each do |child|
|
309
|
+
return child if child.token == token
|
310
|
+
end; nil
|
311
|
+
end
|
312
|
+
|
313
|
+
def make_child(token)
|
314
|
+
child = self.class.new(token)
|
315
|
+
@children << child
|
316
|
+
child
|
317
|
+
end
|
318
|
+
|
319
|
+
# convert any valid path syntax to array syntax
|
320
|
+
def parse_path(path)
|
321
|
+
case path
|
322
|
+
when nil then []
|
323
|
+
when Array then path
|
324
|
+
when Regexp, Symbol then [path]
|
325
|
+
when String then parse_string_path(path)
|
326
|
+
else
|
327
|
+
raise "invalid path #{path}"
|
144
328
|
end
|
329
|
+
end
|
145
330
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
when String then parse_string_path(path)
|
153
|
-
else
|
154
|
-
raise "invalid path #{path}"
|
331
|
+
# convert a string to array path syntax
|
332
|
+
def parse_string_path(path)
|
333
|
+
path.gsub(/(^\/)|(\/$)/, ''). # remove trailing slashes
|
334
|
+
split('/').map do |str| # split tokens
|
335
|
+
str.match(/^:\w+$/) ? # replace ':c' with :c
|
336
|
+
str[1..-1].to_sym : str
|
155
337
|
end
|
156
|
-
|
338
|
+
end
|
157
339
|
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
340
|
+
# raises errors if the provided node for some
|
341
|
+
# reason cannot be added as a child to this node.
|
342
|
+
def validate_child(node)
|
343
|
+
case
|
344
|
+
when !self.compatible_with(node)
|
345
|
+
raise "cannot add child with class " +
|
346
|
+
"#{node.class} to #{self.inspect}"
|
347
|
+
when children.map(&:token).include?(node.token)
|
348
|
+
raise 'cannot add child with occupied token: ' +
|
349
|
+
node.token.inspect
|
350
|
+
else true
|
165
351
|
end
|
352
|
+
end
|
353
|
+
|
354
|
+
def compatible_with?(node)
|
355
|
+
node.class == self.class
|
356
|
+
end
|
166
357
|
end
|
167
358
|
|