action_tree 0.1.1 → 0.2.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.
- 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
|
|