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 CHANGED
@@ -1,6 +1,3 @@
1
-
2
-
3
-
4
1
  --title "ActionTree"
5
2
 
6
3
  --protected
@@ -8,7 +5,8 @@
8
5
  --markup markdown
9
6
  --markup-provider maruku
10
7
 
11
- lib/**/*.rb -
8
+ --plugin rspec
9
+
10
+ lib/**/*.rb spec/**/*.rb -
12
11
  README.md
13
- MANUAL.md
14
12
  LICENSE
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
- $ gem install action_tree
2
+ ActionTree
3
+ ==========
20
4
 
21
- Quickstart
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
- The Local Office of Alligators, division of information, subdivision of web2.0 and social media, have drawn a (naive) map for their new alligator web API:
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
- &nbsp;
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
  &nbsp;
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 routing dry enough to kill cacti.}
9
- gem.description = %Q{ActionTree is a router. It provides a compact DSL for defining routes that map paths to actions. It was designed as a router for modern ruby mini-frameworks.}
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.1
1
+ 0.2.0
@@ -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/dialect_helper'
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
- module Dialect
17
- def new(*prms, &blk)
18
- self::Node.new(*prms, &blk)
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
- # shorthand
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
- # layer proc wrapper
40
- class Layer < Proc; end
41
- def layer(*prms, &blk)
42
- Layer.new(*prms, &blk)
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 ::ActionTree::DialectHelper
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
- attr_reader :token, :children, :helper_scope,
7
- :action_namespaces, :before_hooks,
8
- :after_hooks, :postprocessors
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
- def initialize(path=[], &blk)
13
- path = parse_path(path)
14
- @token = path.shift
15
- @children = Set.new
16
- @helper_scope = Module.new
17
- @before_hooks = []
18
- @after_hooks = []
19
- @postprocessors = []
20
- @action_namespaces = {}
21
- @not_found_handler = nil
22
- route(path, &blk) if block_given?
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
- def match?(path_fragment)
49
- !!path_fragment.match(regexp)
50
- end
92
+ ## DSL
51
93
 
52
- def capture_names
53
- case @token
54
- when Regexp then ['match']
55
- when Symbol then [@token.to_s]
56
- when String
57
- @token.scan(/:\w+/).map {|m| m[1..-1] }
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
- # DSL
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
- descend(location).instance_eval(&blk) if blk
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 :route
69
- alias :w :route
70
- alias :r :route
71
- alias :_ :route
72
- def apply(layer); route(&layer); end
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(&macro)
139
+ else route(&::ActionTree::MACROS[macro])
140
+ end
141
+ end
73
142
 
74
- def action(location=nil, namespace=:default, &blk)
75
- descend(location).action_namespaces[namespace] = blk
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
- descend(location).helper_scope.module_eval(&blk)
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
- descend(location).before_hooks << blk if blk
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
- descend(location).after_hooks << blk if blk
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
- def not_found(location=nil, &blk)
99
- descend(location).not_found_handler = blk if blk
223
+ # Add a deep processor
224
+ def deep_processor(location=nil, &blk)
225
+ locate(location).deep_processors << blk
100
226
  end
101
- alias :x :not_found
227
+ alias :dp :deep_processor
102
228
 
103
- def postprocessor(location=nil, &blk)
104
- descend(location).postprocessors << blk if blk
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
- if node.token
110
- descend(location).children < node
111
- else
112
- raise 'root nodes can not be mounted. apply a layer instead.'
113
- end
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
- def match(path=[])
120
- dialect::Match.new(self, nil, nil).match(path)
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
- def descend(path)
124
- loc = parse_path(path)
125
- if loc.empty? then self else
126
- token = loc.shift
127
- (get_child(token) || make_child(token)).descend(loc)
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
- def get_child(token)
135
- @children.each do |child|
136
- return child if child.token == token
137
- end; nil
138
- end
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
- def make_child(token)
141
- child = self.class.new(token)
142
- @children << child
143
- child
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
- # convert any valid path syntax to array syntax
147
- def parse_path(path)
148
- case path
149
- when nil then []
150
- when Array then path
151
- when Regexp, Symbol then [path]
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
- end
338
+ end
157
339
 
158
- # convert a string to array path syntax
159
- def parse_string_path(path)
160
- path.gsub(/(^\/)|(\/$)/, ''). # remove trailing slashes
161
- split('/').map do |str| # split tokens
162
- str.match(/^:\w+$/) ? # replace ':c' with :c
163
- str[1..-1].to_sym : str
164
- end
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