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.
Files changed (125) hide show
  1. data/LICENSE +20 -0
  2. data/README.textile +95 -0
  3. data/Rakefile +56 -0
  4. data/TODO +18 -0
  5. data/bin/jeweler +19 -0
  6. data/bin/pancake-gen +17 -0
  7. data/bin/rubyforge +19 -0
  8. data/lib/pancake.rb +48 -0
  9. data/lib/pancake/bootloaders.rb +180 -0
  10. data/lib/pancake/configuration.rb +140 -0
  11. data/lib/pancake/core_ext/class.rb +44 -0
  12. data/lib/pancake/core_ext/object.rb +22 -0
  13. data/lib/pancake/core_ext/symbol.rb +15 -0
  14. data/lib/pancake/errors.rb +61 -0
  15. data/lib/pancake/generators.rb +8 -0
  16. data/lib/pancake/generators/base.rb +12 -0
  17. data/lib/pancake/generators/flat_generator.rb +17 -0
  18. data/lib/pancake/generators/short_generator.rb +17 -0
  19. data/lib/pancake/generators/stack_generator.rb +17 -0
  20. data/lib/pancake/generators/templates/common/dotgitignore +22 -0
  21. data/lib/pancake/generators/templates/common/dothtaccess +17 -0
  22. data/lib/pancake/generators/templates/flat/%stack_name%/%stack_name%.rb.tt +8 -0
  23. data/lib/pancake/generators/templates/flat/%stack_name%/.gitignore +21 -0
  24. data/lib/pancake/generators/templates/flat/%stack_name%/config.ru.tt +12 -0
  25. data/lib/pancake/generators/templates/flat/%stack_name%/pancake.init.tt +1 -0
  26. data/lib/pancake/generators/templates/flat/%stack_name%/public/.empty_directory +0 -0
  27. data/lib/pancake/generators/templates/flat/%stack_name%/tmp/.empty_directory +0 -0
  28. data/lib/pancake/generators/templates/short/%stack_name%/.gitignore +21 -0
  29. data/lib/pancake/generators/templates/short/%stack_name%/LICENSE.tt +20 -0
  30. data/lib/pancake/generators/templates/short/%stack_name%/README.tt +7 -0
  31. data/lib/pancake/generators/templates/short/%stack_name%/Rakefile.tt +48 -0
  32. data/lib/pancake/generators/templates/short/%stack_name%/VERSION.tt +1 -0
  33. data/lib/pancake/generators/templates/short/%stack_name%/lib/%stack_name%.rb.tt +5 -0
  34. data/lib/pancake/generators/templates/short/%stack_name%/lib/%stack_name%/%stack_name%.rb.tt +6 -0
  35. data/lib/pancake/generators/templates/short/%stack_name%/lib/%stack_name%/config.ru.tt +12 -0
  36. data/lib/pancake/generators/templates/short/%stack_name%/lib/%stack_name%/gems/cache/.empty_directory +0 -0
  37. data/lib/pancake/generators/templates/short/%stack_name%/lib/%stack_name%/mounts/.empty_directory +0 -0
  38. data/lib/pancake/generators/templates/short/%stack_name%/lib/%stack_name%/public/.empty_directory +0 -0
  39. data/lib/pancake/generators/templates/short/%stack_name%/lib/%stack_name%/tmp/.empty_directory +0 -0
  40. data/lib/pancake/generators/templates/short/%stack_name%/pancake.init.tt +1 -0
  41. data/lib/pancake/generators/templates/short/%stack_name%/spec/%stack_name%_spec.rb.tt +7 -0
  42. data/lib/pancake/generators/templates/short/%stack_name%/spec/spec_helper.rb.tt +9 -0
  43. data/lib/pancake/generators/templates/stack/%stack_name%/.gitignore +21 -0
  44. data/lib/pancake/generators/templates/stack/%stack_name%/LICENSE.tt +20 -0
  45. data/lib/pancake/generators/templates/stack/%stack_name%/README.tt +7 -0
  46. data/lib/pancake/generators/templates/stack/%stack_name%/Rakefile.tt +48 -0
  47. data/lib/pancake/generators/templates/stack/%stack_name%/VERSION.tt +1 -0
  48. data/lib/pancake/generators/templates/stack/%stack_name%/lib/%stack_name%.rb.tt +3 -0
  49. data/lib/pancake/generators/templates/stack/%stack_name%/lib/%stack_name%/config.ru.tt +12 -0
  50. data/lib/pancake/generators/templates/stack/%stack_name%/lib/%stack_name%/config/environments/development.rb.tt +18 -0
  51. data/lib/pancake/generators/templates/stack/%stack_name%/lib/%stack_name%/config/environments/production.rb.tt +18 -0
  52. data/lib/pancake/generators/templates/stack/%stack_name%/lib/%stack_name%/config/router.rb.tt +6 -0
  53. data/lib/pancake/generators/templates/stack/%stack_name%/lib/%stack_name%/gems/cache/.empty_directory +0 -0
  54. data/lib/pancake/generators/templates/stack/%stack_name%/lib/%stack_name%/mounts/.empty_directory +0 -0
  55. data/lib/pancake/generators/templates/stack/%stack_name%/lib/%stack_name%/public/.empty_directory +0 -0
  56. data/lib/pancake/generators/templates/stack/%stack_name%/lib/%stack_name%/tmp/.empty_directory +0 -0
  57. data/lib/pancake/generators/templates/stack/%stack_name%/pancake.init.tt +1 -0
  58. data/lib/pancake/generators/templates/stack/%stack_name%/spec/%stack_name%_spec.rb.tt +7 -0
  59. data/lib/pancake/generators/templates/stack/%stack_name%/spec/spec_helper.rb.tt +9 -0
  60. data/lib/pancake/hooks/inheritable_inner_classes.rb +60 -0
  61. data/lib/pancake/hooks/on_inherit.rb +34 -0
  62. data/lib/pancake/master.rb +87 -0
  63. data/lib/pancake/middleware.rb +354 -0
  64. data/lib/pancake/mime_types.rb +265 -0
  65. data/lib/pancake/mixins/publish.rb +125 -0
  66. data/lib/pancake/mixins/publish/action_options.rb +104 -0
  67. data/lib/pancake/mixins/render.rb +61 -0
  68. data/lib/pancake/mixins/request_helper.rb +92 -0
  69. data/lib/pancake/mixins/stack_helper.rb +44 -0
  70. data/lib/pancake/mixins/url.rb +10 -0
  71. data/lib/pancake/more/controller.rb +4 -0
  72. data/lib/pancake/more/controller/base.rb +34 -0
  73. data/lib/pancake/paths.rb +218 -0
  74. data/lib/pancake/router.rb +99 -0
  75. data/lib/pancake/stack/app.rb +10 -0
  76. data/lib/pancake/stack/bootloader.rb +79 -0
  77. data/lib/pancake/stack/configuration.rb +44 -0
  78. data/lib/pancake/stack/middleware.rb +0 -0
  79. data/lib/pancake/stack/router.rb +18 -0
  80. data/lib/pancake/stack/stack.rb +57 -0
  81. data/lib/pancake/stacks/short.rb +2 -0
  82. data/lib/pancake/stacks/short/controller.rb +105 -0
  83. data/lib/pancake/stacks/short/stack.rb +194 -0
  84. data/spec/helpers/helpers.rb +20 -0
  85. data/spec/helpers/matchers.rb +25 -0
  86. data/spec/pancake/bootloaders_spec.rb +109 -0
  87. data/spec/pancake/configuration_spec.rb +177 -0
  88. data/spec/pancake/fixtures/foo_stack/pancake.init +0 -0
  89. data/spec/pancake/fixtures/paths/controllers/controller1.rb +0 -0
  90. data/spec/pancake/fixtures/paths/controllers/controller2.rb +0 -0
  91. data/spec/pancake/fixtures/paths/controllers/controller3.rb +0 -0
  92. data/spec/pancake/fixtures/paths/models/model1.rb +0 -0
  93. data/spec/pancake/fixtures/paths/models/model2.rb +0 -0
  94. data/spec/pancake/fixtures/paths/models/model3.rb +0 -0
  95. data/spec/pancake/fixtures/paths/stack/controllers/controller1.rb +0 -0
  96. data/spec/pancake/fixtures/paths/stack/models/model3.rb +0 -0
  97. data/spec/pancake/fixtures/paths/stack/views/view1.erb +0 -0
  98. data/spec/pancake/fixtures/paths/stack/views/view1.rb +0 -0
  99. data/spec/pancake/fixtures/paths/stack/views/view2.erb +0 -0
  100. data/spec/pancake/fixtures/paths/stack/views/view2.haml +0 -0
  101. data/spec/pancake/fixtures/render_templates/context_template.html.erb +1 -0
  102. data/spec/pancake/fixtures/render_templates/erb_template.html.erb +1 -0
  103. data/spec/pancake/fixtures/render_templates/erb_template.json.erb +1 -0
  104. data/spec/pancake/fixtures/render_templates/haml_template.html.haml +1 -0
  105. data/spec/pancake/fixtures/render_templates/haml_template.xml.haml +1 -0
  106. data/spec/pancake/hooks/on_inherit_spec.rb +65 -0
  107. data/spec/pancake/inheritance_spec.rb +100 -0
  108. data/spec/pancake/middleware_spec.rb +401 -0
  109. data/spec/pancake/mime_types_spec.rb +234 -0
  110. data/spec/pancake/mixins/publish_spec.rb +94 -0
  111. data/spec/pancake/mixins/render_spec.rb +55 -0
  112. data/spec/pancake/mixins/stack_helper_spec.rb +46 -0
  113. data/spec/pancake/pancake_spec.rb +31 -0
  114. data/spec/pancake/paths_spec.rb +210 -0
  115. data/spec/pancake/stack/app_spec.rb +28 -0
  116. data/spec/pancake/stack/bootloader_spec.rb +41 -0
  117. data/spec/pancake/stack/middleware_spec.rb +0 -0
  118. data/spec/pancake/stack/router_spec.rb +266 -0
  119. data/spec/pancake/stack/stack_configuration_spec.rb +101 -0
  120. data/spec/pancake/stack/stack_spec.rb +55 -0
  121. data/spec/pancake/stacks/short/controller_spec.rb +287 -0
  122. data/spec/pancake/stacks/short/router_spec.rb +132 -0
  123. data/spec/pancake/stacks/short/stack_spec.rb +40 -0
  124. data/spec/spec_helper.rb +21 -0
  125. 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
+