derailleur 0.0.5

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/Rakefile ADDED
@@ -0,0 +1,69 @@
1
+
2
+ require 'rubygems'
3
+ require 'rake/gempackagetask'
4
+
5
+ $LOAD_PATH.unshift('lib')
6
+ require 'derailleur'
7
+
8
+ spec = Gem::Specification.new do |s|
9
+
10
+ s.name = 'derailleur'
11
+ s.rubyforge_project = 'derailleur'
12
+ s.version = Derailleur::VERSION
13
+ s.author = Derailleur::AUTHORS.first
14
+ s.homepage = Derailleur::WEBSITE
15
+ s.summary = "A super-fast Rack web framework"
16
+ s.email = "crapooze@gmail.com"
17
+ s.platform = Gem::Platform::RUBY
18
+
19
+ s.files = [
20
+ 'Rakefile',
21
+ 'TODO',
22
+ 'lib/derailleur.rb',
23
+ 'lib/derailleur/base/application.rb',
24
+ 'lib/derailleur/base/grease.rb',
25
+ 'lib/derailleur/base/context.rb',
26
+ 'lib/derailleur/base/handler_generator.rb',
27
+ 'lib/derailleur/core/application.rb',
28
+ 'lib/derailleur/core/array_trie.rb',
29
+ 'lib/derailleur/core/dispatcher.rb',
30
+ 'lib/derailleur/core/errors.rb',
31
+ 'lib/derailleur/core/handler.rb',
32
+ 'lib/derailleur/core/hash_trie.rb',
33
+ 'lib/derailleur/core/trie.rb',
34
+ ]
35
+
36
+ s.require_path = 'lib'
37
+ s.bindir = 'bin'
38
+ s.executables = []
39
+ s.has_rdoc = true
40
+ end
41
+
42
+ Rake::GemPackageTask.new(spec) do |pkg|
43
+ pkg.need_tar = true
44
+ end
45
+
46
+ task :gem => ["pkg/#{spec.name}-#{spec.version}.gem"] do
47
+ puts "generated #{spec.version}"
48
+ end
49
+
50
+
51
+ desc "run an example"
52
+ task :example, :ex, :server, :port do |t, params|
53
+ path = "./example/#{params[:ex]}.rb"
54
+ servername = params[:server] || 'thin'
55
+ port = params[:port] || '3000'
56
+ if File.file? path
57
+ require path
58
+ require 'rack'
59
+ app = Rack::Builder.new {
60
+ run ExampleApplication
61
+ }
62
+ server = Rack::Handler.get(servername)
63
+ server.run(app, :Port => port.to_i)
64
+ else
65
+ puts "no such example: #{path}
66
+ use ls example to see the possibilities"
67
+
68
+ end
69
+ end
data/TODO ADDED
@@ -0,0 +1 @@
1
+ * separate classes to hold handoff and grafting logic in trienodes
data/lib/derailleur.rb ADDED
@@ -0,0 +1,9 @@
1
+
2
+ module Derailleur
3
+ VERSION = "0.0.5"
4
+ AUTHORS = ['crapooze']
5
+ WEBSITE = "http://github.com/crapooze/derailleur"
6
+ LICENCE = "MIT"
7
+ autoload :Application, 'derailleur/base/application'
8
+ autoload :Grease, 'derailleur/base/grease'
9
+ end
@@ -0,0 +1,66 @@
1
+
2
+ require 'derailleur/core/application'
3
+
4
+ module Derailleur
5
+ module Application
6
+ # registers a handler for the GET HTTP method
7
+ # the handler is either content OR the passed block
8
+ # if content is true and there is a block, then the handler will be the content
9
+ # params is a hash of parameters, the only parameter supported is:
10
+ # * :overwrite , if true, then you can rewrite a handler
11
+ def get(path, content=nil, params={}, &blk)
12
+ register_route(path, :GET, content, params, &blk)
13
+ end
14
+
15
+ # removes a handler for the GET HTTP method
16
+ def unget(path)
17
+ unregister_route(path, :GET)
18
+ end
19
+
20
+ # registers a handler for the HEAD HTTP method
21
+ # see get for the use of parameters
22
+ def head(path, content=nil, params={}, &blk)
23
+ register_route(path, :HEAD, content, params, &blk)
24
+ end
25
+
26
+ # removes a handler for the HEAD HTTP method
27
+ def unhead(path)
28
+ unregister_route(path, :HEAD)
29
+ end
30
+
31
+ # registers a handler for the POST HTTP method
32
+ # see get for the use of parameters
33
+ def post(path, content=nil, params={}, &blk)
34
+ register_route(path, :POST, content, params, &blk)
35
+ end
36
+
37
+ # removes a handler for the POST HTTP method
38
+ def unpost(path)
39
+ unregister_route(path, :POST)
40
+ end
41
+
42
+ # registers a handler for the PUT HTTP method
43
+ # see get for the use of parameters
44
+ def put(path, content=nil, params={}, &blk)
45
+ register_route(path, :PUT, content, params, &blk)
46
+ end
47
+
48
+ # removes a handler for the PUT HTTP method
49
+ def unput(path)
50
+ unregister_route(path, :PUT)
51
+ end
52
+
53
+ # registers a handler for the DELETE HTTP method
54
+ # see get for the use of parameters
55
+ def delete(path, content=nil, params={}, &blk)
56
+ register_route(path, :DELETE, content, params, &blk)
57
+ end
58
+
59
+ # removes a handler for the DELETE HTTP method
60
+ def undelete(path)
61
+ unregister_route(path, :DELETE)
62
+ end
63
+
64
+ #no TRACE CONNECT
65
+ end
66
+ end
@@ -0,0 +1,63 @@
1
+
2
+ module Derailleur
3
+ # A context is the place where we handle an incoming HTTP Request.
4
+ # much like a Rack application, it has an env and the result must
5
+ # be an array of three items: [status, headers, content]
6
+ # The content is just an instance_evaluation of a callback passed during
7
+ # initialization.
8
+ class Context
9
+ # The Rack environment
10
+ attr_reader :env
11
+
12
+ # The Derailleur context
13
+ attr_reader :ctx
14
+
15
+ # A hash representing the HTTP response's headers
16
+ attr_reader :headers
17
+
18
+ # The HTTP response's status
19
+ attr_accessor :status
20
+
21
+ # The body of the HTTP response, must comply with Rack's specification
22
+ attr_accessor :content
23
+
24
+ # The block of code that will be instance_evaled to produce the HTTP
25
+ # response's body
26
+ attr_reader :blk
27
+
28
+ def initialize(env, ctx, &blk)
29
+ @status = 200
30
+ @env = env
31
+ @ctx = ctx
32
+ @blk = blk
33
+ @content = nil
34
+ @headers = {'Content-Type' => 'text/plain'}
35
+ end
36
+
37
+ # Simply instance_evaluates the block blk in the context of self
38
+ def evaluate!
39
+ @content = instance_eval &blk
40
+ end
41
+
42
+ # The Derailleur::Application for this context
43
+ def app
44
+ ctx['derailleur']
45
+ end
46
+
47
+ # The parameters as parsed from the URL/HTTP-path chunks
48
+ def params
49
+ ctx['derailleur.params']
50
+ end
51
+
52
+ # Method that wraps the status, headers and content into an array
53
+ # for Rack specification.
54
+ def result
55
+ [status, headers, content]
56
+ end
57
+
58
+ # Returns the extension name of the HTTP path
59
+ def extname
60
+ File.extname(env['PATH_INFO'])
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,108 @@
1
+
2
+ require 'derailleur/base/application'
3
+
4
+ module Derailleur
5
+ autoload :HandlerGenerator, 'derailleur/base/handler_generator'
6
+ # The Grease is the module that helps you code at a high level,
7
+ # with blocks.
8
+ # Basically, including this method in a class makes the HTTP
9
+ # methods definition available as registrations.
10
+ # Then, instanciating the class, it will actually register the routes
11
+ # in the objects. Including Grease will redefine the "initialize method",
12
+ # do do not include Grease after defining it. Do the opposite.
13
+ # See also Grease#initialize_HTTP (you should call it if you don't use
14
+ # Grease's initialize method.
15
+ module Grease
16
+ include Derailleur::Application
17
+
18
+ Registration = Struct.new(:sym, :path, :handler)
19
+
20
+ def self.included(klass)
21
+ klass.extend ClassMethods
22
+ end
23
+
24
+ # Transform registration definitions into actual registrations.
25
+ def register_registrations_of(obj)
26
+ obj.registrations.each do |reg|
27
+ send(reg.sym, reg.path, reg.handler)
28
+ end
29
+ end
30
+
31
+ # Initialiazes HTTP routes by transforming all the registration definitions
32
+ # in the class of the object into itself.
33
+ def initialize_HTTP
34
+ register_registrations_of(self.class)
35
+ end
36
+
37
+ # If there is no initializer, will create one.
38
+ # If there is one, then it will try to call super.
39
+ # As a result include this module into classes *before* defining initialize,
40
+ # or in classes in which you don't care about initialize.
41
+ def initialize(*args, &blk)
42
+ super
43
+ initialize_HTTP
44
+ end
45
+
46
+ module ClassMethods
47
+ # Copies the registrations of an object.
48
+ # no check is done on duplicate registrations.
49
+ def inherit_HTTP_method(mod)
50
+ mod.registrations.each do |reg|
51
+ registrations << reg.dup
52
+ end
53
+ end
54
+
55
+ # Includes a module and then copies its registrations with
56
+ # inherit_HTTP_method
57
+ def include_and_inherit_HTTP_method(mod)
58
+ include mod
59
+ inherit_HTTP_method(mod)
60
+ end
61
+
62
+ # Returns (and create if needed) the list of registrations
63
+ def registrations
64
+ @registrations ||= []
65
+ end
66
+
67
+ attr_writer :handler_generator
68
+
69
+ # The class to use to create handlers that will catch requests.
70
+ def handler_generator
71
+ @handler_generator ||= Derailleur::HandlerGenerator
72
+ end
73
+
74
+ # Sets a specification to registers a handler at the given path and HTTP
75
+ # request "GET". For the default handler_generator
76
+ # (Derailleur::HandlerGenerator), the block blk will be evaluated in a
77
+ # Derailleur::Context .
78
+ def get(path, params=nil, &blk)
79
+ registrations << Registration.new(:get, path,
80
+ handler_generator.for(params, &blk))
81
+ end
82
+
83
+ # Same as get but for HEAD
84
+ def head(path, params=nil, &blk)
85
+ registrations << Registration.new(:head, path, params,
86
+ handler_generator.for(params, &blk))
87
+ end
88
+
89
+ # Same as get but for POST
90
+ def post(path, params=nil, &blk)
91
+ registrations << Registration.new(:post, path, params,
92
+ handler_generator.for(params, &blk))
93
+ end
94
+
95
+ # Same as get but for PUT
96
+ def put(path, params=nil, &blk)
97
+ registrations << Registration.new(:put, path, params,
98
+ handler_generator.for(params, &blk))
99
+ end
100
+
101
+ # Same as get but for DELETE
102
+ def delete(path, params=nil, &blk)
103
+ registrations << Registration.new(:delete, path, params,
104
+ handler_generator.for(params, &blk))
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,22 @@
1
+
2
+ require 'derailleur/core/handler'
3
+ require 'derailleur/base/context'
4
+
5
+ module Derailleur
6
+ module HandlerGenerator
7
+ # Creates a new subclass of Derailleur::Handler that will create a
8
+ # Derailleur::Context which in turn will evaluate the block.
9
+ # The dummy_params parameter is not used, but we need it because
10
+ # it is the interface for Derailleur.Grease (in case you want to use
11
+ # a custom HandlerGenerator)
12
+ def self.for(dummy_params={}, &blk)
13
+ handler = Class.new(Derailleur::Handler)
14
+ handler.send(:define_method, :to_rack_output) do
15
+ context = Context.new(env, ctx, &blk)
16
+ context.evaluate!
17
+ context.result
18
+ end
19
+ handler
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,200 @@
1
+
2
+ require 'derailleur/core/errors'
3
+
4
+ module Derailleur
5
+ autoload :InternalErrorHandler, 'derailleur/core/handler'
6
+ autoload :DefaultRackHandler, 'derailleur/core/handler'
7
+ autoload :ArrayTrie, 'derailleur/core/array_trie'
8
+ autoload :Dispatcher, 'derailleur/core/dispatcher'
9
+
10
+ # In Derailleur, an application is an object extending the Application
11
+ # module. It will have routes which hold handlers. By default, we use
12
+ # Derailleur's components, but you can easily modify your application to use
13
+ # custom routes' node, HTTP methods dispatcher, and handlers.
14
+ module Application
15
+
16
+ attr_writer :default_handler, :default_root_node_type, :default_dispatcher,
17
+ :default_internal_error_handler
18
+
19
+ # The default error handler ( Derailleur::InternalErrorHandler )
20
+ def default_internal_error_handler
21
+ @default_internal_error_handler ||= InternalErrorHandler
22
+ end
23
+
24
+ # The default root node type ( Derailleur::ArrayTrie )
25
+ # You could change it to ( Derailleur::HashTrie ) for better performance.
26
+ # The rule of thumb is: benchmark your application with both and pick the
27
+ # best one.
28
+ def default_root_node_type
29
+ @default_root_node_type ||= ArrayTrie
30
+ end
31
+
32
+ # The default node type (is the node_type of the default_root_node_type )
33
+ def default_node_type
34
+ default_root_node_type.node_type
35
+ end
36
+
37
+ # The default handler ( Derailleur::DefaultRackHandler )
38
+ # See Derailleur::Handler to understand how to build your own
39
+ def default_handler
40
+ @default_handler ||= DefaultRackHandler
41
+ end
42
+
43
+ # The default HTTP method dispatcher ( Derailleur::Dispatcher )
44
+ # See Derailleur::Dispatcher if you want a personal one
45
+ def default_dispatcher
46
+ @default_dispatcher ||= Dispatcher
47
+ end
48
+
49
+ # An object representing the routes. Usually, it is the root of a Trie
50
+ def routes
51
+ @routes ||= default_root_node_type.new
52
+ end
53
+
54
+ # Normalize a path by making sure it starts with '/'
55
+ def normalize(path)
56
+ File.join('/', path)
57
+ end
58
+
59
+ # Chunks a path, splitting around '/' separators
60
+ # there always is an empty name
61
+ # 'foo/bar' => ['', 'foo', 'bar']
62
+ def chunk_path(path)
63
+ normalize(path).split('/')
64
+ end
65
+
66
+ # Builds a route by appending nodes on the path.
67
+ # The implementation of nodes should create missing nodes on the path.
68
+ # See ArrayTrieNode#<< or HashTrieNode#<<
69
+ # Returns the last node for this path
70
+ def build_route(path)
71
+ current_node = routes
72
+ chunk_path(path).each do |chunk|
73
+ current_node = current_node << chunk
74
+ end
75
+ current_node
76
+ end
77
+
78
+ # Return the node corresponding to a given path.
79
+ # Will (optionally) consecutively yield all the [node, chunk_name]
80
+ # this is useful when you want to interpret the members of the path
81
+ # as a parameter.
82
+ def get_route(path)
83
+ current_node = routes
84
+ chunk_path(path).each do |chunk|
85
+ unless current_node.absorbent?
86
+ current_node = current_node.child_for_name(chunk)
87
+ raise NoSuchRoute, "no such path #{path}" unless current_node
88
+ end
89
+ yield current_node, chunk if block_given?
90
+ end
91
+ current_node
92
+ end
93
+
94
+
95
+ # Registers an handler for a given path.
96
+ # The path will be interpreted as an absolute path prefixed by '/' .
97
+ #
98
+ # Usually you will not use this method but a method from the
99
+ # base/application.rb code (with the name of the
100
+ # HTTP method: e.g. get post put)
101
+ #
102
+ # The method argument is the HTTP method name as a symbol (e.g. :GET)
103
+ # (handler || blk) is the handler set. i.e., if there's both a handler
104
+ # and a block, the block will be ignored.
105
+ #
106
+ # Params is hash of parameters, currently, the only key
107
+ # looked at is :overwrite to overwrite an handler for an
108
+ # existing path/method pair.
109
+ #
110
+ # Internally, the path will be created node by node when nodes for this
111
+ # path are missing.
112
+ # A default_dispatcher will be here to map the various HTTP methods for the
113
+ # same path to their respective handlers.
114
+ def register_route(path, method=:default, handler=nil, params={}, &blk)
115
+ if path == '*'
116
+ raise ArgumentError.new("Cannot register on #{path} because of ambiguity, in Derailleur, '*' translated'/*' would not catch the path '/' like '/foo/*' doesn't catch '/foo'")
117
+ end
118
+ handler = handler || blk
119
+ node = build_route(path)
120
+ node.content ||= default_dispatcher.new
121
+ if (params[:overwrite]) or (not node.content.has_handler?(method))
122
+ node.content.set_handler(method, handler)
123
+ else
124
+ raise RouteObjectAlreadyPresent, "could not overwrite #{method} handler at path #{path}"
125
+ end
126
+ node
127
+ end
128
+
129
+ # Removes an handler for a path/method pair.
130
+ # The path will be interpreted as an absolute path prefixed with '/'
131
+ def unregister_route(path, method=:default)
132
+ node = get_route(normalize(path))
133
+ if node.children.empty?
134
+ if node.content.no_handler?
135
+ node.prune!
136
+ else
137
+ node.content.set_handler(method, nil)
138
+ end
139
+ else
140
+ node.hand_off_to! default_node_type.new(node.name)
141
+ end
142
+ end
143
+
144
+ # Split a whole branch of the application at the given path, and graft the
145
+ # branch to the app in second parameter.
146
+ # This method does NOT prevents you from cancelling handlers in the second
147
+ # app if any. Because it does not check for handlers in the receiving
148
+ # branch. Use with care. See ArrayNode#graft!
149
+ def split_to!(path, app)
150
+ app_node = app.build_route(path)
151
+
152
+ split_node = get_route(normalize(path))
153
+ split_node.prune!
154
+
155
+ app_node.graft!(split_node)
156
+ end
157
+
158
+ # Method implemented to comply to the Rack specification. see
159
+ # http://rack.rubyforge.org/doc/files/SPEC.html to understand what to
160
+ # return.
161
+ #
162
+ # If everything goes right, an instance of default_handler will serve
163
+ # the request.
164
+ #
165
+ # The routing handler will be created with three params
166
+ # - the application handler contained in the dispatcher
167
+ # - the Rack env
168
+ # - a context hash with three keys:
169
+ # * 'derailleur' at self, i.e., a reference to the application
170
+ # * 'derailleur.params' with the parameters/spalt in the path
171
+ # * 'derailleur.node' the node responsible for handling this path
172
+ #
173
+ # If there is any exception during this, it will be catched and the
174
+ # default_internal_error_handler will be called.
175
+ def call(env)
176
+ begin
177
+ path = env['PATH_INFO'].sub(/\.\w+$/,'') #ignores the extension if any
178
+ ctx = {}
179
+ params = {:splat => []}
180
+ route = get_route(path) do |node, val|
181
+ if node.wildcard?
182
+ params[node.name] = val
183
+ elsif node.absorbent?
184
+ params[:splat] << val
185
+ end
186
+ end
187
+ ctx['derailleur.node'] = route
188
+ ctx['derailleur.params'] = params
189
+ ctx['derailleur'] = self
190
+ dispatcher = route.content
191
+ raise NoSuchRoute, "no dispatcher for #{path}" if dispatcher.nil?
192
+ handler = dispatcher.send(env['REQUEST_METHOD'])
193
+ raise NoSuchRoute, "no handler for valid path: #{path}" if handler.nil?
194
+ default_handler.new(handler, env, ctx).to_rack_output
195
+ rescue Exception => err
196
+ default_internal_error_handler.new(err, env, ctx).to_rack_output
197
+ end
198
+ end
199
+ end
200
+ end
@@ -0,0 +1,179 @@
1
+
2
+ require 'derailleur/core/trie'
3
+
4
+ module Derailleur
5
+ class ArrayTrieNode < TrieNode
6
+ # A fallback children, in case there is no child matching, this
7
+ # one is found.
8
+ attr_accessor :fallback_child
9
+
10
+ # An array of normal chidrens
11
+ attr_reader :normal_children
12
+
13
+ def initialize(name, parent=nil)
14
+ super
15
+ @normal_children = []
16
+ end
17
+
18
+ # List the children, including the fallback_child (in last position) if any
19
+ def children
20
+ ary = normal_children.dup
21
+ ary << fallback_child if fallback_child
22
+ ary
23
+ end
24
+
25
+ # Tries adding a new children built from the name
26
+ # * if the name is for a normal child
27
+ # * if there already is a normal_children node with this name, returns it
28
+ # * otherwise creates one
29
+ # * if the name is for a fallback child
30
+ # * if there is no fallback child, creates one
31
+ # * otherwise
32
+ # * if the wording are identical, will just return the existing one
33
+ # * if the wording are different, will raise an ArgumentError
34
+ def << name
35
+ node = child_with_exact_name(name)
36
+ if node
37
+ node
38
+ else
39
+ new_node = ArrayTrieNode.new(name, self)
40
+ ret = if new_node.normal?
41
+ normal_children << new_node
42
+ new_node
43
+ elsif fallback_child
44
+ raise ArgumentError, "there already is a fallback child in #{self}" unless fallback_child.name == new_node.name
45
+ fallback_child
46
+ else
47
+ @fallback_child = new_node
48
+ end
49
+ ret
50
+ end
51
+ end
52
+
53
+ # Returns the normal child with the name passed in parameter
54
+ # or nil if there is no such child
55
+ def child_with_exact_name(name)
56
+ normal_children.find{|n| n.name == name}
57
+ end
58
+
59
+ # Returns the child whose name match the parameter,
60
+ # or the fallback one, or nil if there is no such child
61
+ def child_for_name(name)
62
+ child = child_with_exact_name(name)
63
+ if child
64
+ child
65
+ else
66
+ fallback_child
67
+ end
68
+ end
69
+
70
+ # Like Enumerable#map, but recursively creates a new array per child
71
+ # it looks like this:
72
+ # [root, [child1, [child11, child12]], [child2]]
73
+ def tree_map(&blk)
74
+ children.inject([yield(self)]) do |trunk, child|
75
+ trunk << child.tree_map(&blk)
76
+ end
77
+ end
78
+
79
+ # Pruning the node means cutting the tree by removing the link between this
80
+ # node and its parent. The pruned node thus becomes a root and could be
81
+ # placed somewhere else. References to this node in the former parent node
82
+ # are cleaned during the process. The process of pruning is recursive and
83
+ # stops whenever it encounters a not useless node (see useless?)
84
+ def prune!
85
+ return if root? #you cannot prune the root
86
+ if normal?
87
+ parent.normal_children.delete(self)
88
+ else
89
+ parent.fallback_child = nil
90
+ end
91
+ old_parent = parent
92
+ @parent = nil
93
+ old_parent.prune! if old_parent.useless?
94
+ end
95
+
96
+ # Hands off the role of this node to another, single, independent, simple,
97
+ # and useless node (as opposed to useful and complex ones, e.g., a complete
98
+ # branch of the tree). This is mainly useful for doing a sort of reset on
99
+ # the node's state without caring much about the content of the current
100
+ # node, but you only care about the state of the new node. You can see it
101
+ # like (or actually perform) "changing the class" of the node on the fly.
102
+ #
103
+ # To avoid weird situations, hand off must be licit:
104
+ # - the other node must be useless to avoid losing its useful content
105
+ # - the nodes must be compatible to prevent weird situations (see
106
+ # compatible_handoff?)
107
+ #
108
+ # Otherwise, an error explaining why the handoff is not licit will be
109
+ # raised.
110
+ #
111
+ # If the handoff can take place, it happens as follow:
112
+ # - the other node will copy current's children
113
+ # - this node will clear its references to its children
114
+ #
115
+ # Then, if there is a parent to current node, parent's reference will be modified
116
+ # to point to the replacing node. Finally, the other node will copy
117
+ # current's parent, and the reference to the parent will be cleared in this
118
+ # node.
119
+ def hand_off_to!(other)
120
+ verify_hand_off_to(other)
121
+
122
+ other.normal_children.replace normal_children
123
+ other.fallback_child = fallback_child
124
+ @normal_children = []
125
+ @fallback_child = nil
126
+ if parent
127
+ if normal?
128
+ parent.normal_children.delete(self)
129
+ parent.normal_children << other
130
+ else
131
+ parent.fallback_child = other
132
+ end
133
+ other.parent = parent
134
+ @parent = nil
135
+ end
136
+ end
137
+
138
+ # grafting another node means replacing this node in the tree by the other
139
+ # node, including its hierarchy (i.e., you can place branches). this
140
+ # process works by replacing the reference to this node by the other node
141
+ # in this parent this process also updates the other node's parent
142
+ #
143
+ # one can only graft normal node in place of normal nodes, and vice versa,
144
+ # an incompatible grafting will raise an ArgumentError
145
+ # moreover, because of the semantics, grafting to a root will raise an error
146
+ #
147
+ # see-also compatible_graft?
148
+ def graft!(other)
149
+ verify_graft(other)
150
+
151
+ other.parent = parent
152
+ if other.normal?
153
+ parent.normal_children << other
154
+ parent.normal_children.delete(self)
155
+ else
156
+ parent.fallback_child = other
157
+ end
158
+ end
159
+ end
160
+
161
+ class ArrayTrie < ArrayTrieNode
162
+ def self.node_type
163
+ ArrayTrieNode
164
+ end
165
+
166
+ def initialize
167
+ @parent = nil
168
+ @normal_children = []
169
+ end
170
+
171
+ def name
172
+ nil
173
+ end
174
+
175
+ def prune!
176
+ nil
177
+ end
178
+ end
179
+ end
@@ -0,0 +1,41 @@
1
+
2
+ module Derailleur
3
+ HTTP_METHODS = [:GET, :HEAD, :POST, :PUT, :DELETE, :default]
4
+ Dispatcher = Struct.new(*HTTP_METHODS) do
5
+ # returns the handler for an HTTP method, or, the default one
6
+ def get_handler(method)
7
+ send(method) || send(:default)
8
+ end
9
+
10
+ # returns true if a handler is set for the HTTP method in param
11
+ # it is an alternative to get_handler which would return a true value
12
+ # if there is a default handler
13
+ def has_handler?(method)
14
+ send(method) && true
15
+ end
16
+
17
+ # sets a handler for the HTTP method in param to val
18
+ def set_handler(method, val)
19
+ send("#{method}=", val)
20
+ end
21
+
22
+ # Returns an array of pairs of array with [HTTP-verbs, handler]
23
+ # an extra item is the :default handler
24
+ # NB: could also be a hash, but I'm fine with it
25
+ def handlers
26
+ HTTP_METHODS.map do |sym|
27
+ [sym, send(sym)]
28
+ end
29
+ end
30
+
31
+ # returns an array like handlers but restricted to the not nil values
32
+ def handlers_set
33
+ handlers.reject{|sym, handler| handler.nil?}
34
+ end
35
+
36
+ # Returns true if there is no handler set for this dispatcher
37
+ def no_handler?
38
+ handlers_set.empty?
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,31 @@
1
+
2
+ module Derailleur
3
+ # Errors raising in Derailleur are subclasses of StandardError.
4
+ # Basically, they have an http_status code. This is useful for the various
5
+ # errors handlers people may write.
6
+ class ApplicationError < StandardError
7
+ def http_status
8
+ 500
9
+ end
10
+ end
11
+
12
+ # This error is mainly for people implementing handlers wich instanciates
13
+ # other handlers. The default handler uses it if it tries to instanciate
14
+ # another handler before forwarding to it.
15
+ class InvalidHandler < ApplicationError
16
+ end
17
+
18
+ # A classical kind of not found error in HTTP.
19
+ class NoSuchRoute < ApplicationError
20
+ def http_status
21
+ 404
22
+ end
23
+ end
24
+
25
+ # This errors shows up when you try to register twice on the same route
26
+ # (i.e., path + verb).
27
+ # Unless you modify the routing on the fly, you should not see this error
28
+ # very often.
29
+ class RouteObjectAlreadyPresent < ApplicationError
30
+ end
31
+ end
@@ -0,0 +1,127 @@
1
+
2
+ require 'derailleur/core/errors'
3
+
4
+ module Derailleur
5
+ class Handler
6
+ attr_accessor :env, :ctx, :object
7
+ def initialize(obj=nil, env=nil, ctx=nil)
8
+ @env = env
9
+ @ctx = ctx
10
+ @object = obj
11
+ end
12
+
13
+ # Accessors, and to_rack_output for things behaving like RackHandler
14
+ module Rack
15
+ attr_accessor :status, :headers, :page
16
+
17
+ # Returns an status, headers, and page array conforming to the Rack
18
+ # specification
19
+ def to_rack_output
20
+ [status, headers, page]
21
+ end
22
+
23
+ # Sets to default values the Rack specification fields
24
+ # [200, {}, '']
25
+ def initialize_rack
26
+ @status = 200
27
+ @headers = {}
28
+ @page = ''
29
+ end
30
+ end
31
+ end
32
+
33
+ # The rack handler class instanciates the status, headers, and page to
34
+ # default values.
35
+ class RackHandler < Handler
36
+ include Handler::Rack
37
+
38
+ def initialize(obj=nil, env=nil, ctx=nil)
39
+ super
40
+ initialize_rack
41
+ end
42
+ end
43
+
44
+ # The default rack handler is a handler that does several default things:
45
+ # - if the object respond to to_rack_output (like, another handler)
46
+ # it will set the handler's env to the current one (the handler must also
47
+ # respond to :env= and :ctx=) and call its to_rack_output method
48
+ # - if it's a Proc, it calls the proc passing the env and ctx as
49
+ # block parameters
50
+ # - if it's a subclass of Handler, it instantiates it
51
+ # (without object, and with the same env and ctx)
52
+ class DefaultRackHandler < RackHandler
53
+ def to_rack_output
54
+ if object.respond_to? :to_rack_output
55
+ do_forward_output
56
+ else
57
+ do_non_forward_output
58
+ end
59
+ end
60
+
61
+ def do_forward_output
62
+ object.env = env
63
+ object.ctx = ctx
64
+ object.to_rack_output
65
+ end
66
+
67
+ def do_proc_output
68
+ object.call(env, ctx)
69
+ end
70
+
71
+ def do_handler_instantiation_output
72
+ object.new(nil, env, ctx).to_rack_output
73
+ end
74
+
75
+ def do_non_forward_output
76
+ ret = case object
77
+ when Proc
78
+ do_proc_output
79
+ when Class
80
+ if object.ancestors.include? RackHandler
81
+ do_handler_instantiation_output
82
+ else
83
+ raise InvalidHandler, "invalid handler: #{object}"
84
+ end
85
+ else
86
+ raise InvalidHandler, "invalid handler: #{object}"
87
+ end
88
+ ret
89
+ end
90
+ end
91
+
92
+ # This handler just call the result to a Rack-like application handler
93
+ # (the handler object must conform to the Rack specification)
94
+ # In that case, the Rack application has no view on the ctx variable anymore.
95
+ class RackApplicationHandler < RackHandler
96
+ # Simply delegates the method call to the enclosed object.
97
+ def to_rack_output
98
+ object.call(env)
99
+ end
100
+ end
101
+
102
+ # This handler returns the error as text/plain object
103
+ # If the error respond_to http_status, then it will modify the
104
+ # HTTP status code accordingly.
105
+ class InternalErrorHandler < RackHandler
106
+ alias :err :object
107
+
108
+ def initialize(err, env, ctx)
109
+ super
110
+ if err.respond_to? :http_status
111
+ @status = err.http_status
112
+ else
113
+ @status = 500
114
+ end
115
+ end
116
+
117
+ def headers
118
+ {'Content-Type' => 'text/plain'}
119
+ end
120
+
121
+ def page
122
+ [err.class.name,
123
+ err.message,
124
+ err.backtrace].join("\n")
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,112 @@
1
+
2
+ require 'derailleur/core/trie'
3
+
4
+ module Derailleur
5
+ class HashTrieNode < TrieNode
6
+ attr_reader :children_hash
7
+
8
+ def initialize(name, parent=nil)
9
+ super
10
+ @children_hash = {}
11
+ end
12
+
13
+ def children
14
+ ret = @children_hash.values
15
+ ret << @children_hash.default if @children_hash.default
16
+ ret
17
+ end
18
+
19
+ def << name
20
+ node = exact_child_for_name(name)
21
+ if node
22
+ node
23
+ else
24
+ new_node = HashTrieNode.new(name, self)
25
+ ret = if new_node.normal?
26
+ children_hash[name] = new_node
27
+ elsif children_hash.default
28
+ raise ArgumentError, "there already is a fallback child in #{self}" unless children_hash.default.name == new_node.name
29
+ children_hash.default
30
+ else
31
+ children_hash.default = new_node
32
+ end
33
+ ret
34
+ end
35
+ end
36
+
37
+ def exact_child_for_name(name)
38
+ children_hash.values.find{|v| v.name == name}
39
+ end
40
+
41
+ def child_for_name(name)
42
+ children_hash[name]
43
+ end
44
+
45
+ def tree_map(&blk)
46
+ children_hash.values.inject([yield(self)]) do |trunk, child|
47
+ trunk << child.tree_map(&blk)
48
+ end
49
+ end
50
+
51
+ # pruning an already pruned node will crash: it has no parent already
52
+ # also prunes recursively on useless parents
53
+ # will stop naturally on roots because they have an overloaded prune!
54
+ def prune!
55
+ return if root?
56
+ if normal?
57
+ parent.children_hash.delete(name)
58
+ else
59
+ parent.children_hash.default = nil
60
+ end
61
+ old_parent = parent
62
+ @parent = nil
63
+ old_parent.prune! if old_parent.useless?
64
+ end
65
+
66
+ def hand_off_to!(other)
67
+ verify_hand_off_to(other)
68
+
69
+ other.children_hash.replace @children_hash
70
+ @children_hash = {}
71
+ if parent
72
+ if normal?
73
+ parent.children_hash[name] = other
74
+ else
75
+ parent.children_hash.default = @children_hash.default
76
+ end
77
+ other.parent = parent
78
+ @parent = nil
79
+ end
80
+ end
81
+
82
+ def graft!(other)
83
+ verify_graft(other)
84
+
85
+ other.parent = parent
86
+ if other.normal?
87
+ parent.children_hash[name] = other
88
+ else
89
+ parent.children_hash.default = other
90
+ end
91
+ end
92
+ end
93
+
94
+ class HashTrie < HashTrieNode
95
+ def self.node_type
96
+ HashTrieNode
97
+ end
98
+
99
+ def initialize
100
+ @parent = nil
101
+ @children_hash = {}
102
+ end
103
+
104
+ def name
105
+ nil
106
+ end
107
+
108
+ def prune!
109
+ nil
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,172 @@
1
+
2
+ module Derailleur
3
+ class TrieNode
4
+ # The name for this node
5
+ attr_reader :name
6
+
7
+ # The children of this node
8
+ attr_reader :children
9
+
10
+ # The children of this node
11
+ attr_accessor :content
12
+
13
+ # The parent of this node, or nil if this node is a root
14
+ attr_accessor :parent
15
+
16
+ # This is the default children for the TrieNode
17
+ # (i.e., we record only the parent)
18
+ # That's why TrieNode is mainly a parent class and should be
19
+ # subclassed.
20
+ class EmptyClass
21
+ def self.empty?
22
+ true
23
+ end
24
+ end
25
+
26
+ # Creates a new node whose name and optional parent are set from
27
+ # the parameters.
28
+ # There is a check wether the parent is absorbent (which disallow childrens)
29
+ # The children is set to EmptyClass, which is a placeholder for subclasses
30
+ #
31
+ def initialize(name, parent=nil)
32
+ raise ArgumentError, "cannot be the child of #{parent} because it is an absorbent node" if parent and parent.absorbent?
33
+ @parent = parent
34
+ @children = EmptyClass
35
+ set_normal!
36
+ self.name = name
37
+ end
38
+
39
+ # sets the name of the node and also does other stuff with special names:
40
+ # - '*' means absorbent
41
+ # - /^:/ means wildcard
42
+ # - otherwise it's normal
43
+ def name=(val)
44
+ set_val = if val
45
+ case val
46
+ when '*'
47
+ set_absorbent!
48
+ when /^:/
49
+ set_wildcard!
50
+ else
51
+ set_normal!
52
+ end
53
+ val
54
+ else
55
+ set_normal!
56
+ nil
57
+ end
58
+ @name = set_val
59
+ end
60
+
61
+ private
62
+
63
+ # Set this node to be an absorbent one.
64
+ # In the semantics, an absorbent node means that an
65
+ # entity traversing the trie should stop on this node and should not go any
66
+ # deeper (i.e., an absorbent node is generally a leaf in the tree, although
67
+ # no checking enforces this rule).
68
+ def set_absorbent!
69
+ set_normal!
70
+ @absorbent = true
71
+ end
72
+
73
+ # Set this node to be a wildcard one. A wildcard node is a node whose name
74
+ # is an identifier local to the context of the application traversing it.
75
+ # The semantics often is "any name".
76
+ def set_wildcard!
77
+ set_normal!
78
+ @wildcard = true
79
+ end
80
+
81
+ # Set this node as normal. That is, cancel any wildcard or absorbent
82
+ # status.
83
+ def set_normal!
84
+ @wildcard = false
85
+ @absorbent = false
86
+ end
87
+
88
+ public
89
+
90
+ # A wildcard node is a node whose name was set with a trailing ':'
91
+ def wildcard?
92
+ @wildcard
93
+ end
94
+
95
+ # An absorbent node is a node which cannot be parent, its name is '*'.
96
+ def absorbent?
97
+ @absorbent
98
+ end
99
+
100
+ # A normal node is not absorbent and node wildcard
101
+ def normal?
102
+ (not wildcard?) and (not absorbent?)
103
+ end
104
+
105
+ # Compares a node with another, based on their name
106
+ def <=> other
107
+ name <=> other.name
108
+ end
109
+
110
+ # A useless node has no content and no children, basically
111
+ # arriving at this point, there's no handler you can ever find.
112
+ def useless?
113
+ content.nil? and children.empty?
114
+ end
115
+
116
+ # Returns the root of the tree structure (recursive)
117
+ def root
118
+ parent ? parent.root : self
119
+ end
120
+
121
+ # Returns wether or not this node is a root node (i.e., parent is nil)
122
+ def root?
123
+ parent.nil?
124
+ end
125
+
126
+ # Returns true wether this node could handoff its position to another one.
127
+ # It is the case if both nodes are normals or both are non-normal.
128
+ def compatible_handoff?(other)
129
+ (normal? and other.normal?) or
130
+ ((not normal?) and (not other.normal?))
131
+ end
132
+
133
+ # Raise argument errors if the handoff to other is not licit.
134
+ def verify_hand_off_to(other)
135
+ raise ArgumentError, "cannot hand off to node: #{other} because it is useful" unless other.useless?
136
+ raise ArgumentError, "uncompatible handoff between #{self} and #{other}" unless compatible_handoff?(other)
137
+ end
138
+
139
+ # Placeholder for the handoff logic, this method should be overridden by
140
+ # subclasses.
141
+ def hand_off_to!(other)
142
+ verify_hand_off_to(other)
143
+ other.children.replace children
144
+ other.parent = parent
145
+ @children = EmptyClass
146
+ @parent = nil
147
+ end
148
+
149
+ # Returns true wether the other node could be grafted over this one.
150
+ # It is the case if both nodes are normals or both are non-normal.
151
+ def compatible_graft?(other)
152
+ (normal? and other.normal?) or
153
+ ((not normal?) and (not other.normal?))
154
+ end
155
+
156
+ # Raise argument errors if the grafting of the other node is not licit.
157
+ def verify_graft(other)
158
+ raise ArgumentError, "incompatible grafting" unless compatible_graft?(other)
159
+ raise ArgumentError, "cannot graft a root" if root?
160
+ end
161
+
162
+ # Placeholder for the handoff logic, this method should be overridden by
163
+ # subclasses.
164
+ def graft!(other)
165
+ verify_graft(other)
166
+ other.parent = parent
167
+ parent.children.add other
168
+ parent.children.delete self
169
+ end
170
+
171
+ end
172
+ end
metadata ADDED
@@ -0,0 +1,80 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: derailleur
3
+ version: !ruby/object:Gem::Version
4
+ hash: 21
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 5
10
+ version: 0.0.5
11
+ platform: ruby
12
+ authors:
13
+ - crapooze
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-02-12 00:00:00 +01:00
19
+ default_executable:
20
+ dependencies: []
21
+
22
+ description:
23
+ email: crapooze@gmail.com
24
+ executables: []
25
+
26
+ extensions: []
27
+
28
+ extra_rdoc_files: []
29
+
30
+ files:
31
+ - Rakefile
32
+ - TODO
33
+ - lib/derailleur.rb
34
+ - lib/derailleur/base/application.rb
35
+ - lib/derailleur/base/grease.rb
36
+ - lib/derailleur/base/context.rb
37
+ - lib/derailleur/base/handler_generator.rb
38
+ - lib/derailleur/core/application.rb
39
+ - lib/derailleur/core/array_trie.rb
40
+ - lib/derailleur/core/dispatcher.rb
41
+ - lib/derailleur/core/errors.rb
42
+ - lib/derailleur/core/handler.rb
43
+ - lib/derailleur/core/hash_trie.rb
44
+ - lib/derailleur/core/trie.rb
45
+ has_rdoc: true
46
+ homepage: http://github.com/crapooze/derailleur
47
+ licenses: []
48
+
49
+ post_install_message:
50
+ rdoc_options: []
51
+
52
+ require_paths:
53
+ - lib
54
+ required_ruby_version: !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ hash: 3
60
+ segments:
61
+ - 0
62
+ version: "0"
63
+ required_rubygems_version: !ruby/object:Gem::Requirement
64
+ none: false
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ hash: 3
69
+ segments:
70
+ - 0
71
+ version: "0"
72
+ requirements: []
73
+
74
+ rubyforge_project: derailleur
75
+ rubygems_version: 1.3.7
76
+ signing_key:
77
+ specification_version: 3
78
+ summary: A super-fast Rack web framework
79
+ test_files: []
80
+