hassox-pancake 0.1.6
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/LICENSE +20 -0
- data/README.textile +95 -0
- data/Rakefile +56 -0
- data/TODO +18 -0
- data/bin/jeweler +19 -0
- data/bin/pancake-gen +17 -0
- data/bin/rubyforge +19 -0
- data/lib/pancake.rb +48 -0
- data/lib/pancake/bootloaders.rb +180 -0
- data/lib/pancake/configuration.rb +140 -0
- data/lib/pancake/core_ext/class.rb +44 -0
- data/lib/pancake/core_ext/object.rb +22 -0
- data/lib/pancake/core_ext/symbol.rb +15 -0
- data/lib/pancake/errors.rb +61 -0
- data/lib/pancake/generators.rb +8 -0
- data/lib/pancake/generators/base.rb +12 -0
- data/lib/pancake/generators/flat_generator.rb +17 -0
- data/lib/pancake/generators/short_generator.rb +17 -0
- data/lib/pancake/generators/stack_generator.rb +17 -0
- data/lib/pancake/generators/templates/common/dotgitignore +22 -0
- data/lib/pancake/generators/templates/common/dothtaccess +17 -0
- data/lib/pancake/generators/templates/flat/%stack_name%/%stack_name%.rb.tt +8 -0
- data/lib/pancake/generators/templates/flat/%stack_name%/.gitignore +21 -0
- data/lib/pancake/generators/templates/flat/%stack_name%/config.ru.tt +12 -0
- data/lib/pancake/generators/templates/flat/%stack_name%/pancake.init.tt +1 -0
- data/lib/pancake/generators/templates/flat/%stack_name%/public/.empty_directory +0 -0
- data/lib/pancake/generators/templates/flat/%stack_name%/tmp/.empty_directory +0 -0
- data/lib/pancake/generators/templates/short/%stack_name%/.gitignore +21 -0
- data/lib/pancake/generators/templates/short/%stack_name%/LICENSE.tt +20 -0
- data/lib/pancake/generators/templates/short/%stack_name%/README.tt +7 -0
- data/lib/pancake/generators/templates/short/%stack_name%/Rakefile.tt +48 -0
- data/lib/pancake/generators/templates/short/%stack_name%/VERSION.tt +1 -0
- data/lib/pancake/generators/templates/short/%stack_name%/lib/%stack_name%.rb.tt +5 -0
- data/lib/pancake/generators/templates/short/%stack_name%/lib/%stack_name%/%stack_name%.rb.tt +6 -0
- data/lib/pancake/generators/templates/short/%stack_name%/lib/%stack_name%/config.ru.tt +12 -0
- data/lib/pancake/generators/templates/short/%stack_name%/lib/%stack_name%/gems/cache/.empty_directory +0 -0
- data/lib/pancake/generators/templates/short/%stack_name%/lib/%stack_name%/mounts/.empty_directory +0 -0
- data/lib/pancake/generators/templates/short/%stack_name%/lib/%stack_name%/public/.empty_directory +0 -0
- data/lib/pancake/generators/templates/short/%stack_name%/lib/%stack_name%/tmp/.empty_directory +0 -0
- data/lib/pancake/generators/templates/short/%stack_name%/pancake.init.tt +1 -0
- data/lib/pancake/generators/templates/short/%stack_name%/spec/%stack_name%_spec.rb.tt +7 -0
- data/lib/pancake/generators/templates/short/%stack_name%/spec/spec_helper.rb.tt +9 -0
- data/lib/pancake/generators/templates/stack/%stack_name%/.gitignore +21 -0
- data/lib/pancake/generators/templates/stack/%stack_name%/LICENSE.tt +20 -0
- data/lib/pancake/generators/templates/stack/%stack_name%/README.tt +7 -0
- data/lib/pancake/generators/templates/stack/%stack_name%/Rakefile.tt +48 -0
- data/lib/pancake/generators/templates/stack/%stack_name%/VERSION.tt +1 -0
- data/lib/pancake/generators/templates/stack/%stack_name%/lib/%stack_name%.rb.tt +3 -0
- data/lib/pancake/generators/templates/stack/%stack_name%/lib/%stack_name%/config.ru.tt +12 -0
- data/lib/pancake/generators/templates/stack/%stack_name%/lib/%stack_name%/config/environments/development.rb.tt +18 -0
- data/lib/pancake/generators/templates/stack/%stack_name%/lib/%stack_name%/config/environments/production.rb.tt +18 -0
- data/lib/pancake/generators/templates/stack/%stack_name%/lib/%stack_name%/config/router.rb.tt +6 -0
- data/lib/pancake/generators/templates/stack/%stack_name%/lib/%stack_name%/gems/cache/.empty_directory +0 -0
- data/lib/pancake/generators/templates/stack/%stack_name%/lib/%stack_name%/mounts/.empty_directory +0 -0
- data/lib/pancake/generators/templates/stack/%stack_name%/lib/%stack_name%/public/.empty_directory +0 -0
- data/lib/pancake/generators/templates/stack/%stack_name%/lib/%stack_name%/tmp/.empty_directory +0 -0
- data/lib/pancake/generators/templates/stack/%stack_name%/pancake.init.tt +1 -0
- data/lib/pancake/generators/templates/stack/%stack_name%/spec/%stack_name%_spec.rb.tt +7 -0
- data/lib/pancake/generators/templates/stack/%stack_name%/spec/spec_helper.rb.tt +9 -0
- data/lib/pancake/hooks/inheritable_inner_classes.rb +60 -0
- data/lib/pancake/hooks/on_inherit.rb +34 -0
- data/lib/pancake/master.rb +87 -0
- data/lib/pancake/middleware.rb +354 -0
- data/lib/pancake/mime_types.rb +265 -0
- data/lib/pancake/mixins/publish.rb +125 -0
- data/lib/pancake/mixins/publish/action_options.rb +104 -0
- data/lib/pancake/mixins/render.rb +61 -0
- data/lib/pancake/mixins/request_helper.rb +92 -0
- data/lib/pancake/mixins/stack_helper.rb +44 -0
- data/lib/pancake/mixins/url.rb +10 -0
- data/lib/pancake/more/controller.rb +4 -0
- data/lib/pancake/more/controller/base.rb +34 -0
- data/lib/pancake/paths.rb +218 -0
- data/lib/pancake/router.rb +99 -0
- data/lib/pancake/stack/app.rb +10 -0
- data/lib/pancake/stack/bootloader.rb +79 -0
- data/lib/pancake/stack/configuration.rb +44 -0
- data/lib/pancake/stack/middleware.rb +0 -0
- data/lib/pancake/stack/router.rb +18 -0
- data/lib/pancake/stack/stack.rb +57 -0
- data/lib/pancake/stacks/short.rb +2 -0
- data/lib/pancake/stacks/short/controller.rb +105 -0
- data/lib/pancake/stacks/short/stack.rb +194 -0
- data/spec/helpers/helpers.rb +20 -0
- data/spec/helpers/matchers.rb +25 -0
- data/spec/pancake/bootloaders_spec.rb +109 -0
- data/spec/pancake/configuration_spec.rb +177 -0
- data/spec/pancake/fixtures/foo_stack/pancake.init +0 -0
- data/spec/pancake/fixtures/paths/controllers/controller1.rb +0 -0
- data/spec/pancake/fixtures/paths/controllers/controller2.rb +0 -0
- data/spec/pancake/fixtures/paths/controllers/controller3.rb +0 -0
- data/spec/pancake/fixtures/paths/models/model1.rb +0 -0
- data/spec/pancake/fixtures/paths/models/model2.rb +0 -0
- data/spec/pancake/fixtures/paths/models/model3.rb +0 -0
- data/spec/pancake/fixtures/paths/stack/controllers/controller1.rb +0 -0
- data/spec/pancake/fixtures/paths/stack/models/model3.rb +0 -0
- data/spec/pancake/fixtures/paths/stack/views/view1.erb +0 -0
- data/spec/pancake/fixtures/paths/stack/views/view1.rb +0 -0
- data/spec/pancake/fixtures/paths/stack/views/view2.erb +0 -0
- data/spec/pancake/fixtures/paths/stack/views/view2.haml +0 -0
- data/spec/pancake/fixtures/render_templates/context_template.html.erb +1 -0
- data/spec/pancake/fixtures/render_templates/erb_template.html.erb +1 -0
- data/spec/pancake/fixtures/render_templates/erb_template.json.erb +1 -0
- data/spec/pancake/fixtures/render_templates/haml_template.html.haml +1 -0
- data/spec/pancake/fixtures/render_templates/haml_template.xml.haml +1 -0
- data/spec/pancake/hooks/on_inherit_spec.rb +65 -0
- data/spec/pancake/inheritance_spec.rb +100 -0
- data/spec/pancake/middleware_spec.rb +401 -0
- data/spec/pancake/mime_types_spec.rb +234 -0
- data/spec/pancake/mixins/publish_spec.rb +94 -0
- data/spec/pancake/mixins/render_spec.rb +55 -0
- data/spec/pancake/mixins/stack_helper_spec.rb +46 -0
- data/spec/pancake/pancake_spec.rb +31 -0
- data/spec/pancake/paths_spec.rb +210 -0
- data/spec/pancake/stack/app_spec.rb +28 -0
- data/spec/pancake/stack/bootloader_spec.rb +41 -0
- data/spec/pancake/stack/middleware_spec.rb +0 -0
- data/spec/pancake/stack/router_spec.rb +266 -0
- data/spec/pancake/stack/stack_configuration_spec.rb +101 -0
- data/spec/pancake/stack/stack_spec.rb +55 -0
- data/spec/pancake/stacks/short/controller_spec.rb +287 -0
- data/spec/pancake/stacks/short/router_spec.rb +132 -0
- data/spec/pancake/stacks/short/stack_spec.rb +40 -0
- data/spec/spec_helper.rb +21 -0
- metadata +238 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
module Pancake
|
|
2
|
+
# A simple rack application
|
|
3
|
+
OK_APP = lambda{|env| Rack::Response.new("OK", 200, {"Content-Type" => "text/plain"}).finish}
|
|
4
|
+
MISSING_APP = lambda{|env| Rack::Response.new("NOT FOUND", 404, {"Content-Type" => "text/plain"}).finish}
|
|
5
|
+
|
|
6
|
+
extend Middleware
|
|
7
|
+
|
|
8
|
+
class << self
|
|
9
|
+
attr_accessor :root
|
|
10
|
+
|
|
11
|
+
# Start Pancake. This provides a full pancake stack to use inside a rack application
|
|
12
|
+
#
|
|
13
|
+
# @param [Hash] opts
|
|
14
|
+
# @option opts [String] :root The root of the pancake stack
|
|
15
|
+
#
|
|
16
|
+
# @example Starting a pancake stack
|
|
17
|
+
# Pancake.start(:root => "/path/to/root"){ MyApp # App to use}
|
|
18
|
+
#
|
|
19
|
+
# @api public
|
|
20
|
+
# @author Daniel Neighman
|
|
21
|
+
def start(opts, &block)
|
|
22
|
+
raise "You must specify a root directory for pancake" unless opts[:root]
|
|
23
|
+
self.root = opts[:root]
|
|
24
|
+
|
|
25
|
+
# Build Pancake
|
|
26
|
+
the_app = instance_eval(&block)
|
|
27
|
+
Pancake::Middleware.build(the_app, middlewares)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Provides the environment for the currently running pancake
|
|
31
|
+
#
|
|
32
|
+
# @return [String] The currently running environment
|
|
33
|
+
# @api public
|
|
34
|
+
# @author Daniel Neighman
|
|
35
|
+
def env
|
|
36
|
+
ENV['RACK_ENV'] ||= "development"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# A helper method to get the expanded directory name of a __FILE__
|
|
40
|
+
#
|
|
41
|
+
# @return [String] an expanded version of file
|
|
42
|
+
# @api public
|
|
43
|
+
# @author Daniel Neighman
|
|
44
|
+
def get_root(file)
|
|
45
|
+
File.expand_path(File.dirname(file))
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Labels that specify what kind of stack you're intending on loading.
|
|
49
|
+
# This is a simliar concept to environments but it is in fact seperate conceptually.
|
|
50
|
+
#
|
|
51
|
+
# The reasoning is that you may want to use a particular stack type or types.
|
|
52
|
+
# By using stack labels, you can define middleware to be active.
|
|
53
|
+
#
|
|
54
|
+
# @example
|
|
55
|
+
# Pancake.stack_labels == [:development, :demo]
|
|
56
|
+
#
|
|
57
|
+
# # This would activate middleware marked with :development or :demo or the implicit :any label
|
|
58
|
+
#
|
|
59
|
+
# @return [Array<Symbol>]
|
|
60
|
+
# An array of labels to activate
|
|
61
|
+
# The default is [:production]
|
|
62
|
+
# @see Pancake.stack_labels= to set the labels for this stack
|
|
63
|
+
# @see Pancake::Middleware#stack to see how to specify middleware to be active for the given labels
|
|
64
|
+
# @api public
|
|
65
|
+
# @author Daniel Neighman
|
|
66
|
+
def stack_labels
|
|
67
|
+
return @stack_labels unless @stack_labels.nil? || @stack_labels.empty?
|
|
68
|
+
self.stack_labels = [:production]
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Sets the stack labels to activate the associated middleware
|
|
72
|
+
#
|
|
73
|
+
# @param [Array<Symbol>, Symbol] An array of labels or a single label, specifying the middlewares to activate
|
|
74
|
+
#
|
|
75
|
+
# @example
|
|
76
|
+
# Pancake.stack_labels = [:demo, :production]
|
|
77
|
+
#
|
|
78
|
+
# @see Pancake.stack_labels
|
|
79
|
+
# @see Pancake::Middleware#stack
|
|
80
|
+
# @api public
|
|
81
|
+
# @author Daniel Neighman
|
|
82
|
+
def stack_labels=(*labels)
|
|
83
|
+
@stack_labels = labels.flatten.compact
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
end # self
|
|
87
|
+
end # Pancake
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
module Pancake
|
|
2
|
+
# Provides a mixin to use on any class to give it middleware management capabilities.
|
|
3
|
+
# This module provides a rich featureset for defining a middleware stack.
|
|
4
|
+
#
|
|
5
|
+
# Middlware can be set before, or after other middleware, can be tagged / named,
|
|
6
|
+
# and can be declared to only be active in certain types of stacks.
|
|
7
|
+
module Middleware
|
|
8
|
+
|
|
9
|
+
# When extending a base class with the Pancake::Middleware,
|
|
10
|
+
# an inner class StackMiddleware is setup on the base class.
|
|
11
|
+
# This inner class is where all the inforamation is stored on the stack to be defined
|
|
12
|
+
# The inner StackMiddleware class is also set to be inherited
|
|
13
|
+
# with the base class (and all children classes)
|
|
14
|
+
# So that each class gets its own copy and may maintain the base
|
|
15
|
+
# stack from the parent, but edit it in the child.
|
|
16
|
+
def self.extended(base)
|
|
17
|
+
base.class_eval <<-RUBY
|
|
18
|
+
class StackMiddleware < Pancake::Middleware::StackMiddleware; end
|
|
19
|
+
RUBY
|
|
20
|
+
if base.is_a?(Class)
|
|
21
|
+
base.inheritable_inner_classes :StackMiddleware
|
|
22
|
+
end
|
|
23
|
+
super
|
|
24
|
+
end # self.extended
|
|
25
|
+
|
|
26
|
+
# Build a middleware stack given an application and some middleware classes
|
|
27
|
+
#
|
|
28
|
+
# @param [Object] app a rack application to wrap in the middlware list
|
|
29
|
+
# @param [Array<StackMiddleware>] mwares an array of
|
|
30
|
+
# StackMiddleware instances where each instance
|
|
31
|
+
# defines a middleware to use in constructing the stack
|
|
32
|
+
#
|
|
33
|
+
# @example
|
|
34
|
+
# Pancake::Middleware.build(@app, [MWare_1, MWare_2])
|
|
35
|
+
# @return [Object]
|
|
36
|
+
# An application instance of the first middleware defined in the array
|
|
37
|
+
# The application should be an instance that conforms to Rack specifications
|
|
38
|
+
#
|
|
39
|
+
# @api public
|
|
40
|
+
# @since 0.1.0
|
|
41
|
+
# @author Daniel Neighman
|
|
42
|
+
def self.build(app, mwares)
|
|
43
|
+
mwares.reverse.inject(app) do |a, m|
|
|
44
|
+
case m.middleware.instance_method(:initialize).arity
|
|
45
|
+
when 1
|
|
46
|
+
m.middleware.new(a,&m.block)
|
|
47
|
+
when -2
|
|
48
|
+
m.middleware.new(a, m.options, &m.block)
|
|
49
|
+
else
|
|
50
|
+
raise "Don't know how to initialize #{m.middleware}"
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# @param [Array<Symbol>] labels An array of labels specifying the stack labels to use to build the middlware list
|
|
56
|
+
#
|
|
57
|
+
# @example
|
|
58
|
+
# MyApp.middlewares(:production) # provides all middlewares matching the :production label, or the implicit :any label
|
|
59
|
+
# MyApp.middlewares(:development, :demo) # provides all middlewares matching the :development or :demo or implicit :any label
|
|
60
|
+
#
|
|
61
|
+
# @return [Array<StackMiddleware>]
|
|
62
|
+
# An array of middleware specifications in the order they should be used to wrap the application
|
|
63
|
+
#
|
|
64
|
+
# @see Pancake::Middleware::StackMiddleware
|
|
65
|
+
# @see Pancake.stack_labels for a decription of stack_labels
|
|
66
|
+
# @api public
|
|
67
|
+
# @since 0.1.0
|
|
68
|
+
# @author Daniel Neighman
|
|
69
|
+
def middlewares(*labels)
|
|
70
|
+
labels = labels.flatten
|
|
71
|
+
self::StackMiddleware.middlewares(*labels)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Useful for adding additional information into your middleware stack definition
|
|
75
|
+
#
|
|
76
|
+
# @param [Object] name
|
|
77
|
+
# The name of a given middleware. Each piece of middleware has a name in the stack.
|
|
78
|
+
# By naming middleware we can refer to it later, swap it out for a different class or even just remove it from the stack.
|
|
79
|
+
# @param [Hash] opts An options hash
|
|
80
|
+
# @option opts [Array<Symbol>] :labels ([:any])
|
|
81
|
+
# An array of symbols, or a straight symbol that defines what stacks this middleware sould be active in
|
|
82
|
+
# @option opts [Object] :before
|
|
83
|
+
# Sets this middlware to be run after the middleware named. Name is either the name given to the
|
|
84
|
+
# middleware stack, or the Middleware class itself.
|
|
85
|
+
# @option opts [Object] :after
|
|
86
|
+
# Sets this middleware to be run after the middleware name. Name is either the name given to the
|
|
87
|
+
# middleware stack or the Middleware class itself.
|
|
88
|
+
#
|
|
89
|
+
# @example Declaring un-named middleware via the stack
|
|
90
|
+
# MyClass.stack.use(MyMiddleware)
|
|
91
|
+
#
|
|
92
|
+
# This middleware will be named MyMiddleware, and can be specified with (:before | :after) => MyMiddleware
|
|
93
|
+
#
|
|
94
|
+
# @example Declaring a named middleware via the stack
|
|
95
|
+
# MyClass.stack(:foo).use(MyMiddleware)
|
|
96
|
+
#
|
|
97
|
+
# This middleware will be named :foo and can be specified with (:before | :after) => :foo
|
|
98
|
+
#
|
|
99
|
+
# @example Declaring a named middleware with a :before key
|
|
100
|
+
# MyClass.stack(:foo, :before => :bar).use(MyMiddleware)
|
|
101
|
+
#
|
|
102
|
+
# This middleware will be named :foo and will be run before the middleware named :bar
|
|
103
|
+
# If :bar is not run, :foo will not be run either
|
|
104
|
+
#
|
|
105
|
+
# @example Declaring a named middlware with an :after key
|
|
106
|
+
# MyClass.stack(:foo, :after => :bar).use(MyMiddleware)
|
|
107
|
+
#
|
|
108
|
+
# This middleware will be named :foo and will be run after the middleware named :bar
|
|
109
|
+
# If :bar is not run, :foo will not be run either
|
|
110
|
+
#
|
|
111
|
+
# @example Declaring a named middleware with some labels
|
|
112
|
+
# MyClass.stack(:foo, :lables => [:demo, :production, :staging]).use(MyMiddleware)
|
|
113
|
+
#
|
|
114
|
+
# This middleware will only be run when pancake is set with the :demo, :production or :staging labels
|
|
115
|
+
#
|
|
116
|
+
# @example A full example
|
|
117
|
+
# MyClass.stack(:foo, :labels => [:staging, :development], :after => :session).use(MyMiddleware)
|
|
118
|
+
#
|
|
119
|
+
#
|
|
120
|
+
# @see Pancake::Middleware#use
|
|
121
|
+
# @api public
|
|
122
|
+
# @since 0.1.0
|
|
123
|
+
# @author Daniel Neighman
|
|
124
|
+
def stack(name = nil, opts = {})
|
|
125
|
+
if self::StackMiddleware._mwares[name] && mw = self::StackMiddleware._mwares[name]
|
|
126
|
+
unless mw.stack == self
|
|
127
|
+
mw = self::StackMiddleware._mwares[name] = self::StackMiddleware._mwares[name].dup
|
|
128
|
+
end
|
|
129
|
+
mw
|
|
130
|
+
else
|
|
131
|
+
self::StackMiddleware.new(name, self, opts)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Adds middleware to the current stack definition
|
|
136
|
+
#
|
|
137
|
+
# @param [Class] middleware The middleware class to use in the stack
|
|
138
|
+
# @param [Hash] opts An options hash that is passed through to the middleware when it is instantiated
|
|
139
|
+
#
|
|
140
|
+
# @yield The block is provided to the middlewares #new method when it is initialized
|
|
141
|
+
#
|
|
142
|
+
# @example Bare use call
|
|
143
|
+
# MyApp.use(MyMiddleware, :some => :option){ # middleware initialization block here }
|
|
144
|
+
#
|
|
145
|
+
# @example Use call after a stack call
|
|
146
|
+
# MyApp.stack(:foo).use(MyMiddleware, :some => :option){ # middleware initialization block here }
|
|
147
|
+
#
|
|
148
|
+
# @see Pancake::Middleware#stack
|
|
149
|
+
# @api public
|
|
150
|
+
# @since 0.1.0
|
|
151
|
+
# @author Daniel Neighman
|
|
152
|
+
def use(middleware, opts = {}, &block)
|
|
153
|
+
stack(middleware).use(middleware, opts, &block)
|
|
154
|
+
end # use
|
|
155
|
+
|
|
156
|
+
# StackMiddleware manages the definition of the middleware stack for a given class.
|
|
157
|
+
# It's instances are responsible for the definition of a single piece of middleware, and the class
|
|
158
|
+
# is responsible for specifying the full stack for a given class.
|
|
159
|
+
#
|
|
160
|
+
# When Pancake::Middleware extends a class, an inner class is created in that class called StackMiddleware.
|
|
161
|
+
# That StackMiddleware class inherits from Pancake::Middleware::StackMiddleware.
|
|
162
|
+
#
|
|
163
|
+
# @example The setup when Pancake::Middleware is extended
|
|
164
|
+
# MyClass.extend Pancake::Middleware
|
|
165
|
+
# # sets up
|
|
166
|
+
#
|
|
167
|
+
# class MyClass
|
|
168
|
+
# class StackMiddleware < Pancake::Middleware::StackMiddleware; end
|
|
169
|
+
# end
|
|
170
|
+
#
|
|
171
|
+
# This is then set is an inheritable inner class on the extended class, such that when it is inherited,
|
|
172
|
+
# the StackMiddleware class is inherited to an inner class of the same name on the child.
|
|
173
|
+
class StackMiddleware
|
|
174
|
+
# @api private
|
|
175
|
+
class_inheritable_reader :_central_mwares, :_mwares, :_before, :_after
|
|
176
|
+
@_central_mwares, @_before, @_after, @_mwares = [], {}, {}, {}
|
|
177
|
+
|
|
178
|
+
# @api private
|
|
179
|
+
attr_reader :middleware, :name
|
|
180
|
+
# @api private
|
|
181
|
+
attr_accessor :config, :block, :stack, :options
|
|
182
|
+
|
|
183
|
+
class << self
|
|
184
|
+
def use(mware, opts, &block)
|
|
185
|
+
new(mware).use(mware, opts, &block)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Resets this stack middlware. Useful for specs
|
|
189
|
+
def reset!
|
|
190
|
+
_central_mwares.clear
|
|
191
|
+
_mwares.clear
|
|
192
|
+
_before.clear
|
|
193
|
+
_after.clear
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Get the middleware list for this StackMiddleware for the given labels
|
|
197
|
+
#
|
|
198
|
+
# @param [Symbol] labels The label or list of labels to construct a stack from.
|
|
199
|
+
#
|
|
200
|
+
# @example Specified labels
|
|
201
|
+
# MyClass::StackMiddleware.middlewares(:production, :demo)
|
|
202
|
+
#
|
|
203
|
+
# @example No Labels Specified
|
|
204
|
+
# MyClass::StackMiddleware.middlewares
|
|
205
|
+
#
|
|
206
|
+
# This will include all defined middlewares in the given stack
|
|
207
|
+
#
|
|
208
|
+
# @return [Array<StackMiddleware>]
|
|
209
|
+
# An array of the middleware definitions to use in the order that they should be applied
|
|
210
|
+
# Takes into account all :before, :after settings and only constructs the stack where
|
|
211
|
+
# the labels are applied
|
|
212
|
+
#
|
|
213
|
+
# @see Pancake.stack_labels for a description on stack labels
|
|
214
|
+
# @api public
|
|
215
|
+
# @since 0.1.0
|
|
216
|
+
# @author Daniel Neighman
|
|
217
|
+
def middlewares(*labels)
|
|
218
|
+
_central_mwares.map do |name|
|
|
219
|
+
map_middleware(name, *labels)
|
|
220
|
+
end.flatten
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# Map the middleware for a given <name>ed middleware. Applies the before and after groups of middlewares
|
|
224
|
+
#
|
|
225
|
+
# @param [Object] name The name of the middleware to map the before and after groups to
|
|
226
|
+
# @param [Symbol] labels A label or list of labels to use to construct the middleware stack
|
|
227
|
+
#
|
|
228
|
+
# @example
|
|
229
|
+
# MyClass::StackMiddleware.map_middleware(:foo, :production, :demo)
|
|
230
|
+
#
|
|
231
|
+
# Constructs the middleware list based on the middleware named :foo, including all :before, and :after groups
|
|
232
|
+
#
|
|
233
|
+
# @return [Array<StackMiddleware>]
|
|
234
|
+
# Provides an array of StackMiddleware instances in the array [<before :foo>, <:foo>, <after :foo>]
|
|
235
|
+
#
|
|
236
|
+
# @api private
|
|
237
|
+
# @since 0.1.0
|
|
238
|
+
# @author Daniel Neighman
|
|
239
|
+
def map_middleware(name, *labels)
|
|
240
|
+
result = []
|
|
241
|
+
_before[name] ||= []
|
|
242
|
+
_after[name] ||= []
|
|
243
|
+
if _mwares[name] && _mwares[name].use_for_labels?(*labels)
|
|
244
|
+
result << _before[name].map{|n| map_middleware(n)}
|
|
245
|
+
result << _mwares[name]
|
|
246
|
+
result << _after[name].map{|n| map_middleware(n)}
|
|
247
|
+
result.flatten
|
|
248
|
+
end
|
|
249
|
+
result
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
# Provides access to a named middleware
|
|
253
|
+
#
|
|
254
|
+
# @param [Object] name The name of the defined middleware
|
|
255
|
+
#
|
|
256
|
+
# @return [StackMiddleware] The middleware definition associated with <name>
|
|
257
|
+
#
|
|
258
|
+
# @api public
|
|
259
|
+
# @since 0.1.0
|
|
260
|
+
# @author Daniel Neighman
|
|
261
|
+
def [](name)
|
|
262
|
+
_mwares[name]
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
# Provides access to a named middleware
|
|
267
|
+
#
|
|
268
|
+
# @see Pancake::Middleware::StackMiddleware.[] for an explaination
|
|
269
|
+
# @since 0.1.0
|
|
270
|
+
# @author Daniel Neighman
|
|
271
|
+
def [](name)
|
|
272
|
+
self.class._mwares[name]
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
# @param [Object] name a name for this middleware definition. Usually a symbol, but could be the class.
|
|
276
|
+
# @param [Object] stack the stack owner of this middleware.
|
|
277
|
+
# @param [Hash] options an options hash. Provide labels for this middleware.
|
|
278
|
+
# @option options [Array] :labels ([:any])
|
|
279
|
+
# The labels that are associated with this middleware
|
|
280
|
+
# @option options [Object] :before A middleware name to add this middleware before
|
|
281
|
+
# @option options [Object] :after A middleware name to add this middleware after
|
|
282
|
+
#
|
|
283
|
+
# @see Pancake::Middleware.stack_labels
|
|
284
|
+
# @api private
|
|
285
|
+
# @author Daniel Neighman
|
|
286
|
+
def initialize(name, stack, options = {})
|
|
287
|
+
@name, @stack, @options = name, stack, options
|
|
288
|
+
@options[:labels] ||= [:any]
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
# Delete this middleware from the current stack
|
|
292
|
+
#
|
|
293
|
+
# @api public
|
|
294
|
+
# @since 0.1.0
|
|
295
|
+
# @author Daniel Neighman
|
|
296
|
+
def delete!
|
|
297
|
+
self.class._mwares.delete(name)
|
|
298
|
+
self.class._before.delete(name)
|
|
299
|
+
self.class._after.delete(name)
|
|
300
|
+
self.class._central_mwares.delete(name)
|
|
301
|
+
self
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
# Specify the actual middleware definition to use
|
|
305
|
+
#
|
|
306
|
+
# @param [Class] mware A Middleware class to use. This should be a class of Middleware which conforms to the Rack spec
|
|
307
|
+
# @param [Hash] config A configuration hash to give to the middleware class on initialization
|
|
308
|
+
# @yield The block is passed to the middleware on initialization
|
|
309
|
+
#
|
|
310
|
+
# @see Pancake::Middleware.use
|
|
311
|
+
# @api public
|
|
312
|
+
# @since 0.1.0
|
|
313
|
+
# @author Daniel Neighman
|
|
314
|
+
def use(mware, config = {}, &block)
|
|
315
|
+
@middleware, @config, @block = mware, config, block
|
|
316
|
+
@name = @middleware if name.nil?
|
|
317
|
+
if options[:before]
|
|
318
|
+
raise "#{options[:before].inspect} middleware is not defined for this stack" unless self.class._mwares.keys.include?(options[:before])
|
|
319
|
+
self.class._before[options[:before]] ||= []
|
|
320
|
+
self.class._before[options[:before]] << name
|
|
321
|
+
elsif options[:after]
|
|
322
|
+
raise "#{options[:after].inspect} middleware is not defined for this stack" unless self.class._mwares.keys.include?(options[:after])
|
|
323
|
+
self.class._after[options[:after]] ||= []
|
|
324
|
+
self.class._after[options[:after]] << name
|
|
325
|
+
else
|
|
326
|
+
self.class._central_mwares << name unless self.class._central_mwares.include?(name)
|
|
327
|
+
end
|
|
328
|
+
self.class._mwares[name] = self
|
|
329
|
+
self
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
# Checks if this middleware definition should be included from the labels given
|
|
333
|
+
# @param [Symbol] labels The label or list of labels to check if this middleware should be included
|
|
334
|
+
#
|
|
335
|
+
# @return [Boolean] true if this middlware should be included
|
|
336
|
+
#
|
|
337
|
+
# @api private
|
|
338
|
+
# @since 0.1.0
|
|
339
|
+
# @author Daniel Neighman
|
|
340
|
+
def use_for_labels?(*labels)
|
|
341
|
+
return true if labels.empty? || options[:labels].nil? || options[:labels].include?(:any)
|
|
342
|
+
!(options[:labels] & labels).empty?
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
# @api private
|
|
346
|
+
def dup
|
|
347
|
+
result = super
|
|
348
|
+
result.config = result.config.dup
|
|
349
|
+
result.options = result.options.dup
|
|
350
|
+
result
|
|
351
|
+
end
|
|
352
|
+
end
|
|
353
|
+
end # Middleware
|
|
354
|
+
end # Pancake
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
require 'set'
|
|
2
|
+
require 'rack/accept_media_types'
|
|
3
|
+
module Pancake
|
|
4
|
+
module MimeTypes
|
|
5
|
+
# A collection of all the mime types that pancake knows about
|
|
6
|
+
#
|
|
7
|
+
# @returns [Array<Pancake::MimeTypes::Type>] an array of
|
|
8
|
+
# Pancake::MimeTypes::Type objects that pancake knows about. To
|
|
9
|
+
# add a new type to the collection, simply create a new
|
|
10
|
+
# Pancake::MimeTypes::Type
|
|
11
|
+
#
|
|
12
|
+
# @see Pancake::MimeTypes::Type
|
|
13
|
+
# @api public
|
|
14
|
+
# @author Daniel Neighman
|
|
15
|
+
def self.types
|
|
16
|
+
@types || reset! && @types
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Finds a Pancake::MimeTypes::Type object by the provided
|
|
20
|
+
# extension
|
|
21
|
+
#
|
|
22
|
+
# @param ext [String,Symbol] - The extension to look for
|
|
23
|
+
# @return [Pancake::MimeTypes::Type, nil] - The corresponding mime
|
|
24
|
+
# type object or nil if there were none found that match the
|
|
25
|
+
# provided extension
|
|
26
|
+
#
|
|
27
|
+
# @example
|
|
28
|
+
# Pancake::MimeTypes.type_by_extension("html") # => A
|
|
29
|
+
# Pancake::MimeTypes::Type object for the .html extension
|
|
30
|
+
#
|
|
31
|
+
# @see Pancake::MimeTypes::Type
|
|
32
|
+
# @api public
|
|
33
|
+
# @author Daniel Neighman
|
|
34
|
+
def self.type_by_extension(ext)
|
|
35
|
+
ext = ext.to_s
|
|
36
|
+
types.detect{|t| t.extension == ext}
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Pancake manages mime types by grouping them together.
|
|
40
|
+
#
|
|
41
|
+
# @return [Set<Pancake::MimeTypes::Type>] - A enumerable of Type
|
|
42
|
+
# objects associated with this group
|
|
43
|
+
#
|
|
44
|
+
# @see Pancake::MimeTypes.group
|
|
45
|
+
# @api public
|
|
46
|
+
# @author Daniel Neighman
|
|
47
|
+
def self.groups
|
|
48
|
+
@groups || reset! && @groups
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Pancake::MimeTypes are managed by grouping them together.
|
|
52
|
+
# Each group may consist of many mime types by extension
|
|
53
|
+
# By Accessing a group that doesn't yet exist, the group is
|
|
54
|
+
# created and initialized with the matching type wwith extension
|
|
55
|
+
# A group may have many mime type / accept types associated with
|
|
56
|
+
# it
|
|
57
|
+
#
|
|
58
|
+
# @param name [String,Symbol] - The name of the group
|
|
59
|
+
#
|
|
60
|
+
# @example
|
|
61
|
+
# Pancake::MimeTypes.group(:html)
|
|
62
|
+
#
|
|
63
|
+
# @return [Enumerable<Pancake::MimeTypes::Type>] - returns all
|
|
64
|
+
# mime types in the specified group. If the group does not exist,
|
|
65
|
+
# or has not been accessed, the group will be created on the fly
|
|
66
|
+
# with the first mime type that matches the group name
|
|
67
|
+
#
|
|
68
|
+
# @see Pancake::MimeTypes.group_as
|
|
69
|
+
# @api public
|
|
70
|
+
# @author Daniel Neighman
|
|
71
|
+
def self.group(name)
|
|
72
|
+
groups[name.to_s]
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Creates a group of mime types. Any group of mimes can be
|
|
76
|
+
# arbitrarily grouped under the specified name. The group is
|
|
77
|
+
# initilized with the mime type whose extension matches the name,
|
|
78
|
+
# and any further extensions have their mime types added to the
|
|
79
|
+
# group
|
|
80
|
+
#
|
|
81
|
+
# @param name [String,Symbol] - the name of the group to
|
|
82
|
+
# create/append
|
|
83
|
+
# @param exts [List of String,Symbol] - a list of extensions to
|
|
84
|
+
# associate with this mime type
|
|
85
|
+
#
|
|
86
|
+
# @see Pancake::MimeTypes.group
|
|
87
|
+
# @api public
|
|
88
|
+
# @author Daniel Neighman
|
|
89
|
+
def self.group_as(name, *exts)
|
|
90
|
+
exts.each do |ext|
|
|
91
|
+
group(name) << type_by_extension(ext) unless group(name).include?(type_by_extension(ext))
|
|
92
|
+
end
|
|
93
|
+
group(name)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Resets the Pancake::MimeType cache and re-creates the
|
|
97
|
+
# Pancake::MimeTypes default types and groups
|
|
98
|
+
# Good for use in specs
|
|
99
|
+
# @api private
|
|
100
|
+
def self.reset!
|
|
101
|
+
@types = []
|
|
102
|
+
@negotiated_accept_types = nil
|
|
103
|
+
@groups = Hash.new do |h,k|
|
|
104
|
+
k = k.to_s
|
|
105
|
+
h[k] = []
|
|
106
|
+
t = Pancake::MimeTypes.type_by_extension(k)
|
|
107
|
+
h[k] << t unless t.nil?
|
|
108
|
+
h[k]
|
|
109
|
+
end
|
|
110
|
+
reset_mime_types!
|
|
111
|
+
reset_mime_groups!
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Used in specs to reset the mime types back to their
|
|
115
|
+
# corresponding originals in Rack::Mime::MIME_TYPES
|
|
116
|
+
# @api private
|
|
117
|
+
def self.reset_mime_types!
|
|
118
|
+
# Setup the mime types based on the rack mime types
|
|
119
|
+
Rack::Mime::MIME_TYPES.each do |ext, type|
|
|
120
|
+
ext =~ /\.(.*)$/
|
|
121
|
+
e = $1
|
|
122
|
+
t = type_by_extension(ext)
|
|
123
|
+
if t
|
|
124
|
+
t.type_strings << type
|
|
125
|
+
else
|
|
126
|
+
t = Type.new(e, type)
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Used in specs to reset the default mime groups of
|
|
132
|
+
# pancake::MimeTypes
|
|
133
|
+
# @api private
|
|
134
|
+
def self.reset_mime_groups!
|
|
135
|
+
# html
|
|
136
|
+
group_as(:html, "html", "htm", "xhtml")
|
|
137
|
+
group_as(:text, "text", "txt")
|
|
138
|
+
type_by_extension("xml").type_strings << "text/xml"
|
|
139
|
+
|
|
140
|
+
group_as(:svg, "svgz")
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Negotiates the type and group that the accept_type string
|
|
144
|
+
# matches
|
|
145
|
+
# @param type [String] the accept_type header string from the
|
|
146
|
+
# request
|
|
147
|
+
# @param provided [list of String,Symbol] - A list of strings /
|
|
148
|
+
# symbols of the included groups to use to try and match this
|
|
149
|
+
# accept type header.
|
|
150
|
+
#
|
|
151
|
+
# @example
|
|
152
|
+
# accept_type = "text/xml,text/html"
|
|
153
|
+
# result = Pancake::MimeTypes.negotiate_accept_type(accept_type, :html,
|
|
154
|
+
# :text)
|
|
155
|
+
# result # => [:html, <#Pancake::MimeTypes::Type for html>]
|
|
156
|
+
#
|
|
157
|
+
# @return [Array] An array with the group name, and mime type
|
|
158
|
+
# @api public
|
|
159
|
+
# @author Daniel Neighman
|
|
160
|
+
def self.negotiate_accept_type(type, *provided)
|
|
161
|
+
accepted_types = Rack::AcceptMediaTypes.new(type)
|
|
162
|
+
provided = provided.flatten
|
|
163
|
+
key = [accepted_types, provided]
|
|
164
|
+
return negotiated_accept_types[key] if negotiated_accept_types[key]
|
|
165
|
+
|
|
166
|
+
accepted_type = nil
|
|
167
|
+
|
|
168
|
+
if accepted_types.include?("*/*")
|
|
169
|
+
name = provided.first
|
|
170
|
+
accepted_type = group(name).first
|
|
171
|
+
negotiated_accept_types[key] = [name, accepted_type.type_strings.first, accepted_type]
|
|
172
|
+
return negotiated_accept_types[key]
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Check to see if any accepted types match
|
|
176
|
+
accepted_types.each do |at|
|
|
177
|
+
provided.flatten.each do |name|
|
|
178
|
+
accepted_type = match_content_type(at, name)
|
|
179
|
+
if accepted_type
|
|
180
|
+
at = accepted_type.type_strings.first if at == "*/*"
|
|
181
|
+
if accepted_types.join.size > 4096
|
|
182
|
+
# Don't save the key if it's larger than 4 k.
|
|
183
|
+
# This could hit a dos attack if it's repeatedly hit
|
|
184
|
+
# with anything large
|
|
185
|
+
negotiated_accept_types[key] = [name, at, accepted_type]
|
|
186
|
+
end
|
|
187
|
+
return [name, at, accepted_type]
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
nil
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# Negotiates the content type based on the extension and the
|
|
195
|
+
# provided groups to see if there is a match.
|
|
196
|
+
#
|
|
197
|
+
# @param ext [String] The extension to negotiate
|
|
198
|
+
# @param provided [Symbol] A list of symbols each of which is the
|
|
199
|
+
# name of a mime group
|
|
200
|
+
#
|
|
201
|
+
# @return [Symbol, String, Pancake::MimeTypes::Type] The first
|
|
202
|
+
# parameter returned is the group name that matched. The second,
|
|
203
|
+
# is the Content-Type to respond to the client with, the third,
|
|
204
|
+
# the raw pancake mime type
|
|
205
|
+
#
|
|
206
|
+
# @see Pancake::MimeTypes.group
|
|
207
|
+
# @see Pancake::MimeTypes::Type
|
|
208
|
+
# @api public
|
|
209
|
+
def self.negotiate_by_extension(ext, *provided)
|
|
210
|
+
provided = provided.flatten
|
|
211
|
+
key = [ext, provided]
|
|
212
|
+
return negotiated_accept_types[key] if negotiated_accept_types[key]
|
|
213
|
+
|
|
214
|
+
result = nil
|
|
215
|
+
provided.each do |name|
|
|
216
|
+
group(name).each do |type|
|
|
217
|
+
if type.extension == ext
|
|
218
|
+
result = [name, type.type_strings.first, type]
|
|
219
|
+
negotiated_accept_types[key] = result
|
|
220
|
+
return result
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
result
|
|
225
|
+
end # self.negotiate_by_extension
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
# A basic type for mime types
|
|
229
|
+
# Each type can have an extension and many type strings that
|
|
230
|
+
# correspond to the mime type that would be specified in a request
|
|
231
|
+
# When a Pancake::MimeTypes::Type is created, it is added to the
|
|
232
|
+
# Pancake::MimeTypes.types collection
|
|
233
|
+
# @api public
|
|
234
|
+
class Type
|
|
235
|
+
attr_reader :type_strings, :extension
|
|
236
|
+
|
|
237
|
+
def initialize(extension, *type_strings)
|
|
238
|
+
@extension = extension
|
|
239
|
+
@type_strings = []
|
|
240
|
+
type_strings.flatten.each do |ts|
|
|
241
|
+
@type_strings << ts
|
|
242
|
+
end
|
|
243
|
+
MimeTypes.types << self
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
private
|
|
248
|
+
# Checks to see if a group matches an accept type string
|
|
249
|
+
# @api private
|
|
250
|
+
def self.match_content_type(accept_type, key)
|
|
251
|
+
group(key).detect do |t|
|
|
252
|
+
t.type_strings.include?(accept_type) || accept_type == "*/*"
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
# Provides a cache for already negotiated types
|
|
257
|
+
# @api private
|
|
258
|
+
def self.negotiated_accept_types
|
|
259
|
+
@negotiated_accept_types ||= {}
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
end # MimeTypes
|
|
263
|
+
end # Pancake
|
|
264
|
+
|
|
265
|
+
|