oahu-dragonfly 0.8.2

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 (159) hide show
  1. data/.rspec +1 -0
  2. data/.yardopts +24 -0
  3. data/Gemfile +30 -0
  4. data/History.md +323 -0
  5. data/LICENSE +20 -0
  6. data/README.md +88 -0
  7. data/Rakefile +50 -0
  8. data/VERSION +1 -0
  9. data/config.ru +14 -0
  10. data/docs.watchr +1 -0
  11. data/dragonfly.gemspec +297 -0
  12. data/extra_docs/Analysers.md +66 -0
  13. data/extra_docs/Caching.md +23 -0
  14. data/extra_docs/Configuration.md +124 -0
  15. data/extra_docs/Couch.md +49 -0
  16. data/extra_docs/DataStorage.md +153 -0
  17. data/extra_docs/Encoding.md +67 -0
  18. data/extra_docs/GeneralUsage.md +121 -0
  19. data/extra_docs/Generators.md +60 -0
  20. data/extra_docs/Heroku.md +50 -0
  21. data/extra_docs/ImageMagick.md +125 -0
  22. data/extra_docs/Index.md +33 -0
  23. data/extra_docs/MimeTypes.md +40 -0
  24. data/extra_docs/Models.md +272 -0
  25. data/extra_docs/Mongo.md +45 -0
  26. data/extra_docs/Processing.md +77 -0
  27. data/extra_docs/Rack.md +52 -0
  28. data/extra_docs/Rails2.md +57 -0
  29. data/extra_docs/Rails3.md +62 -0
  30. data/extra_docs/Sinatra.md +25 -0
  31. data/extra_docs/URLs.md +169 -0
  32. data/features/images.feature +47 -0
  33. data/features/no_processing.feature +14 -0
  34. data/features/rails_3.0.5.feature +8 -0
  35. data/features/steps/common_steps.rb +8 -0
  36. data/features/steps/dragonfly_steps.rb +66 -0
  37. data/features/steps/rails_steps.rb +28 -0
  38. data/features/support/env.rb +13 -0
  39. data/features/support/setup.rb +32 -0
  40. data/fixtures/rails_3.0.5/files/app/models/album.rb +7 -0
  41. data/fixtures/rails_3.0.5/files/app/views/albums/new.html.erb +7 -0
  42. data/fixtures/rails_3.0.5/files/app/views/albums/show.html.erb +6 -0
  43. data/fixtures/rails_3.0.5/files/config/initializers/dragonfly.rb +4 -0
  44. data/fixtures/rails_3.0.5/files/features/manage_album_images.feature +38 -0
  45. data/fixtures/rails_3.0.5/files/features/step_definitions/helper_steps.rb +7 -0
  46. data/fixtures/rails_3.0.5/files/features/step_definitions/image_steps.rb +25 -0
  47. data/fixtures/rails_3.0.5/files/features/support/paths.rb +17 -0
  48. data/fixtures/rails_3.0.5/files/features/text_images.feature +7 -0
  49. data/fixtures/rails_3.0.5/template.rb +20 -0
  50. data/irbrc.rb +18 -0
  51. data/lib/dragonfly.rb +55 -0
  52. data/lib/dragonfly/active_model_extensions.rb +13 -0
  53. data/lib/dragonfly/active_model_extensions/attachment.rb +250 -0
  54. data/lib/dragonfly/active_model_extensions/attachment_class_methods.rb +148 -0
  55. data/lib/dragonfly/active_model_extensions/class_methods.rb +95 -0
  56. data/lib/dragonfly/active_model_extensions/instance_methods.rb +28 -0
  57. data/lib/dragonfly/active_model_extensions/validations.rb +41 -0
  58. data/lib/dragonfly/analyser.rb +58 -0
  59. data/lib/dragonfly/analysis/file_command_analyser.rb +32 -0
  60. data/lib/dragonfly/analysis/image_magick_analyser.rb +6 -0
  61. data/lib/dragonfly/app.rb +172 -0
  62. data/lib/dragonfly/config/heroku.rb +19 -0
  63. data/lib/dragonfly/config/image_magick.rb +6 -0
  64. data/lib/dragonfly/config/rails.rb +20 -0
  65. data/lib/dragonfly/configurable.rb +207 -0
  66. data/lib/dragonfly/core_ext/array.rb +7 -0
  67. data/lib/dragonfly/core_ext/hash.rb +7 -0
  68. data/lib/dragonfly/core_ext/object.rb +12 -0
  69. data/lib/dragonfly/core_ext/string.rb +9 -0
  70. data/lib/dragonfly/core_ext/symbol.rb +9 -0
  71. data/lib/dragonfly/data_storage.rb +9 -0
  72. data/lib/dragonfly/data_storage/couch_data_store.rb +64 -0
  73. data/lib/dragonfly/data_storage/file_data_store.rb +141 -0
  74. data/lib/dragonfly/data_storage/mongo_data_store.rb +86 -0
  75. data/lib/dragonfly/data_storage/s3data_store.rb +145 -0
  76. data/lib/dragonfly/encoder.rb +13 -0
  77. data/lib/dragonfly/encoding/image_magick_encoder.rb +6 -0
  78. data/lib/dragonfly/function_manager.rb +71 -0
  79. data/lib/dragonfly/generation/image_magick_generator.rb +6 -0
  80. data/lib/dragonfly/generator.rb +9 -0
  81. data/lib/dragonfly/hash_with_css_style_keys.rb +21 -0
  82. data/lib/dragonfly/image_magick/analyser.rb +51 -0
  83. data/lib/dragonfly/image_magick/config.rb +41 -0
  84. data/lib/dragonfly/image_magick/encoder.rb +57 -0
  85. data/lib/dragonfly/image_magick/generator.rb +145 -0
  86. data/lib/dragonfly/image_magick/processor.rb +99 -0
  87. data/lib/dragonfly/image_magick/utils.rb +72 -0
  88. data/lib/dragonfly/image_magick_utils.rb +4 -0
  89. data/lib/dragonfly/job.rb +451 -0
  90. data/lib/dragonfly/job_builder.rb +39 -0
  91. data/lib/dragonfly/job_definitions.rb +26 -0
  92. data/lib/dragonfly/job_endpoint.rb +15 -0
  93. data/lib/dragonfly/loggable.rb +28 -0
  94. data/lib/dragonfly/middleware.rb +20 -0
  95. data/lib/dragonfly/processing/image_magick_processor.rb +6 -0
  96. data/lib/dragonfly/processor.rb +9 -0
  97. data/lib/dragonfly/rails/images.rb +27 -0
  98. data/lib/dragonfly/response.rb +97 -0
  99. data/lib/dragonfly/routed_endpoint.rb +40 -0
  100. data/lib/dragonfly/serializer.rb +32 -0
  101. data/lib/dragonfly/server.rb +113 -0
  102. data/lib/dragonfly/simple_cache.rb +23 -0
  103. data/lib/dragonfly/temp_object.rb +175 -0
  104. data/lib/dragonfly/url_mapper.rb +78 -0
  105. data/samples/beach.png +0 -0
  106. data/samples/egg.png +0 -0
  107. data/samples/round.gif +0 -0
  108. data/samples/sample.docx +0 -0
  109. data/samples/taj.jpg +0 -0
  110. data/spec/dragonfly/active_model_extensions/model_spec.rb +1426 -0
  111. data/spec/dragonfly/active_model_extensions/spec_helper.rb +91 -0
  112. data/spec/dragonfly/analyser_spec.rb +123 -0
  113. data/spec/dragonfly/analysis/file_command_analyser_spec.rb +48 -0
  114. data/spec/dragonfly/app_spec.rb +135 -0
  115. data/spec/dragonfly/configurable_spec.rb +461 -0
  116. data/spec/dragonfly/core_ext/array_spec.rb +19 -0
  117. data/spec/dragonfly/core_ext/hash_spec.rb +19 -0
  118. data/spec/dragonfly/core_ext/string_spec.rb +17 -0
  119. data/spec/dragonfly/core_ext/symbol_spec.rb +17 -0
  120. data/spec/dragonfly/data_storage/couch_data_store_spec.rb +76 -0
  121. data/spec/dragonfly/data_storage/file_data_store_spec.rb +296 -0
  122. data/spec/dragonfly/data_storage/mongo_data_store_spec.rb +57 -0
  123. data/spec/dragonfly/data_storage/s3_data_store_spec.rb +258 -0
  124. data/spec/dragonfly/data_storage/shared_data_store_examples.rb +77 -0
  125. data/spec/dragonfly/function_manager_spec.rb +154 -0
  126. data/spec/dragonfly/hash_with_css_style_keys_spec.rb +24 -0
  127. data/spec/dragonfly/image_magick/analyser_spec.rb +64 -0
  128. data/spec/dragonfly/image_magick/encoder_spec.rb +41 -0
  129. data/spec/dragonfly/image_magick/generator_spec.rb +172 -0
  130. data/spec/dragonfly/image_magick/processor_spec.rb +233 -0
  131. data/spec/dragonfly/image_magick/utils_spec.rb +18 -0
  132. data/spec/dragonfly/job_builder_spec.rb +37 -0
  133. data/spec/dragonfly/job_definitions_spec.rb +35 -0
  134. data/spec/dragonfly/job_endpoint_spec.rb +173 -0
  135. data/spec/dragonfly/job_spec.rb +1046 -0
  136. data/spec/dragonfly/loggable_spec.rb +80 -0
  137. data/spec/dragonfly/middleware_spec.rb +47 -0
  138. data/spec/dragonfly/routed_endpoint_spec.rb +48 -0
  139. data/spec/dragonfly/serializer_spec.rb +61 -0
  140. data/spec/dragonfly/server_spec.rb +278 -0
  141. data/spec/dragonfly/simple_cache_spec.rb +27 -0
  142. data/spec/dragonfly/temp_object_spec.rb +306 -0
  143. data/spec/dragonfly/url_mapper_spec.rb +126 -0
  144. data/spec/functional/deprecations_spec.rb +51 -0
  145. data/spec/functional/image_magick_app_spec.rb +27 -0
  146. data/spec/functional/model_urls_spec.rb +85 -0
  147. data/spec/functional/remote_on_the_fly_spec.rb +51 -0
  148. data/spec/functional/to_response_spec.rb +31 -0
  149. data/spec/spec_helper.rb +51 -0
  150. data/spec/support/argument_matchers.rb +19 -0
  151. data/spec/support/image_matchers.rb +47 -0
  152. data/spec/support/simple_matchers.rb +53 -0
  153. data/yard/handlers/configurable_attr_handler.rb +38 -0
  154. data/yard/setup.rb +15 -0
  155. data/yard/templates/default/fulldoc/html/css/common.css +107 -0
  156. data/yard/templates/default/layout/html/layout.erb +89 -0
  157. data/yard/templates/default/module/html/configuration_summary.erb +31 -0
  158. data/yard/templates/default/module/setup.rb +17 -0
  159. metadata +544 -0
@@ -0,0 +1,39 @@
1
+ module Dragonfly
2
+ class JobBuilder
3
+
4
+ def initialize(&block)
5
+ @block = block
6
+ end
7
+
8
+ def build(job, *args)
9
+ evaluate_block(job, false, *args)
10
+ end
11
+
12
+ def build!(job, *args)
13
+ evaluate_block(job, true, *args)
14
+ end
15
+
16
+ Job.step_names.each do |step|
17
+
18
+ # fetch, process, etc.
19
+ define_method step do |*args|
20
+ if @perform_with_bangs
21
+ @job.send("#{step}!", *args)
22
+ else
23
+ @job = @job.send(step, *args)
24
+ end
25
+ end
26
+
27
+ end
28
+
29
+ private
30
+
31
+ def evaluate_block(job, perform_with_bangs, *args)
32
+ @job = job
33
+ @perform_with_bangs = perform_with_bangs
34
+ instance_exec(*args, &@block)
35
+ @job
36
+ end
37
+
38
+ end
39
+ end
@@ -0,0 +1,26 @@
1
+ module Dragonfly
2
+ class JobDefinitions < Module
3
+
4
+ def initialize
5
+ @job_definitions = {}
6
+ end
7
+
8
+ def add(name, &definition_proc)
9
+ job_definitions[name] = JobBuilder.new(&definition_proc)
10
+ jd = job_definitions # Needed because we're about to change 'self'
11
+
12
+ define_method name do |*args|
13
+ jd[name].build(self, *args)
14
+ end
15
+
16
+ define_method "#{name}!" do |*args|
17
+ jd[name].build!(self, *args)
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ attr_reader :job_definitions
24
+
25
+ end
26
+ end
@@ -0,0 +1,15 @@
1
+ module Dragonfly
2
+ class JobEndpoint
3
+
4
+ def initialize(job)
5
+ @job = job
6
+ end
7
+
8
+ def call(env={})
9
+ Response.new(job, env).to_response
10
+ end
11
+
12
+ attr_reader :job
13
+
14
+ end
15
+ end
@@ -0,0 +1,28 @@
1
+ require 'logger'
2
+
3
+ module Dragonfly
4
+ module Loggable
5
+
6
+ def log
7
+ case @log_object
8
+ when nil
9
+ @log_object = Logger.new($stdout)
10
+ when Proc
11
+ @log_object[]
12
+ when Logger
13
+ @log_object
14
+ end
15
+ end
16
+
17
+ def log=(object)
18
+ @log_object = object
19
+ end
20
+
21
+ attr_reader :log_object
22
+
23
+ def use_same_log_as(object)
24
+ self.log = proc{ object.log }
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,20 @@
1
+ module Dragonfly
2
+ class Middleware
3
+
4
+ def initialize(app, dragonfly_app_name, deprecated_arg=nil)
5
+ raise ArgumentError, "mounting Dragonfly::Middleware with a mount point is deprecated - just use Dragonfly::Middleware, #{dragonfly_app_name.inspect}" if deprecated_arg
6
+ @app = app
7
+ @dragonfly_app_name = dragonfly_app_name
8
+ end
9
+
10
+ def call(env)
11
+ response = Dragonfly[@dragonfly_app_name].call(env)
12
+ if response[1]['X-Cascade'] == 'pass'
13
+ @app.call(env)
14
+ else
15
+ response
16
+ end
17
+ end
18
+
19
+ end
20
+ end
@@ -0,0 +1,6 @@
1
+ module Dragonfly
2
+ module Processing
3
+ puts "WARNING: Dragonfly::Processing::ImageMagickProcessor is DEPRECATED and will soon be removed. Please use Dragonfly::ImageMagick::Processor instead."
4
+ ImageMagickProcessor = ImageMagick::Processor
5
+ end
6
+ end
@@ -0,0 +1,9 @@
1
+ module Dragonfly
2
+ class Processor < FunctionManager
3
+
4
+ def process(temp_object, method, *args)
5
+ call_last(method, temp_object, *args)
6
+ end
7
+
8
+ end
9
+ end
@@ -0,0 +1,27 @@
1
+ require 'dragonfly'
2
+ require 'uri'
3
+
4
+ ### The dragonfly app ###
5
+ app = Dragonfly[:images]
6
+ app.configure_with(:rails)
7
+ app.configure_with(:imagemagick)
8
+
9
+ ### Extend active record ###
10
+ if defined?(ActiveRecord::Base)
11
+ app.define_macro(ActiveRecord::Base, :image_accessor)
12
+ app.define_macro(ActiveRecord::Base, :file_accessor)
13
+ end
14
+
15
+ ### Insert the middleware ###
16
+ Rails.application.middleware.insert 0, 'Dragonfly::Middleware', :images
17
+
18
+ begin
19
+ require 'rack/cache'
20
+ Rails.application.middleware.insert_before 'Dragonfly::Middleware', 'Rack::Cache', {
21
+ :verbose => true,
22
+ :metastore => URI.encode("file:#{Rails.root}/tmp/dragonfly/cache/meta"), # URI encoded because Windows
23
+ :entitystore => URI.encode("file:#{Rails.root}/tmp/dragonfly/cache/body") # has problems with spaces
24
+ }
25
+ rescue LoadError => e
26
+ app.log.warn("Warning: couldn't find rack-cache for caching dragonfly content")
27
+ end
@@ -0,0 +1,97 @@
1
+ require 'uri'
2
+
3
+ module Dragonfly
4
+ class Response
5
+
6
+ DEFAULT_FILENAME = proc{|job, request|
7
+ if job.basename
8
+ extname = job.encoded_extname || (".#{job.ext}" if job.ext)
9
+ "#{job.basename}#{extname}"
10
+ end
11
+ }
12
+
13
+ def initialize(job, env)
14
+ @job, @env = job, env
15
+ @app = @job.app
16
+ end
17
+
18
+ def to_response
19
+ if !(request.head? || request.get?)
20
+ [405, method_not_allowed_headers, ["#{request.request_method} method not allowed"]]
21
+ elsif etag_matches?
22
+ [304, cache_headers, []]
23
+ elsif request.head?
24
+ [200, success_headers.merge(cache_headers), []]
25
+ elsif request.get?
26
+ [200, success_headers.merge(cache_headers), job.result]
27
+ end
28
+ rescue DataStorage::DataNotFound => e
29
+ app.log.warn(e.message)
30
+ [404, {"Content-Type" => 'text/plain'}, ['Not found']]
31
+ end
32
+
33
+ def served?
34
+ request.get? && !etag_matches?
35
+ end
36
+
37
+ private
38
+
39
+ attr_reader :job, :env, :app
40
+
41
+ def request
42
+ @request ||= Rack::Request.new(env)
43
+ end
44
+
45
+ def cache_headers
46
+ {
47
+ "Cache-Control" => "public, max-age=#{app.cache_duration}",
48
+ "ETag" => %("#{job.unique_signature}")
49
+ }
50
+ end
51
+
52
+ def etag_matches?
53
+ return @etag_matches unless @etag_matches.nil?
54
+ if_none_match = env['HTTP_IF_NONE_MATCH']
55
+ @etag_matches = if if_none_match
56
+ if_none_match.tr!('"','')
57
+ if_none_match.split(',').include?(job.unique_signature) || if_none_match == '*'
58
+ else
59
+ false
60
+ end
61
+ end
62
+
63
+ def success_headers
64
+ {
65
+ "Content-Type" => job.mime_type,
66
+ "Content-Length" => job.size.to_s
67
+ }.merge(content_disposition_header)
68
+ end
69
+
70
+ def content_disposition_header
71
+ parts = []
72
+ parts << content_disposition if content_disposition
73
+ parts << %(filename="#{URI.encode(filename)}") if filename
74
+ parts.any? ? {"Content-Disposition" => parts.join('; ')} : {}
75
+ end
76
+
77
+ def method_not_allowed_headers
78
+ {
79
+ 'Content-Type' => 'text/plain',
80
+ 'Allow' => 'GET, HEAD'
81
+ }
82
+ end
83
+
84
+ def content_disposition
85
+ @content_disposition ||= evaluate(app.content_disposition)
86
+ end
87
+
88
+ def filename
89
+ @filename ||= evaluate(app.content_filename)
90
+ end
91
+
92
+ def evaluate(attribute)
93
+ attribute.respond_to?(:call) ? attribute.call(job, request) : attribute
94
+ end
95
+
96
+ end
97
+ end
@@ -0,0 +1,40 @@
1
+ module Dragonfly
2
+ class RoutedEndpoint
3
+
4
+ class NoRoutingParams < RuntimeError; end
5
+
6
+ def initialize(app, &block)
7
+ @app = app
8
+ @block = block
9
+ end
10
+
11
+ def call(env)
12
+ params = symbolize_keys_of Rack::Request.new(env).params
13
+ job = @block.call(params.merge(routing_params(env)), @app)
14
+ Response.new(job, env).to_response
15
+ rescue Job::NoSHAGiven => e
16
+ [400, {"Content-Type" => 'text/plain'}, ["You need to give a SHA parameter"]]
17
+ rescue Job::IncorrectSHA => e
18
+ [400, {"Content-Type" => 'text/plain'}, ["The SHA parameter you gave (#{e}) is incorrect"]]
19
+ end
20
+
21
+ private
22
+
23
+ def routing_params(env)
24
+ env['rack.routing_args'] ||
25
+ env['action_dispatch.request.path_parameters'] ||
26
+ env['router.params'] ||
27
+ env['usher.params'] ||
28
+ env['dragonfly.params'] ||
29
+ raise(NoRoutingParams, "couldn't find any routing parameters in env #{env.inspect}")
30
+ end
31
+
32
+ def symbolize_keys_of(hash)
33
+ hash.inject({}) do |h, (key, value)|
34
+ h[(key.to_sym rescue key) || key] = value
35
+ h
36
+ end
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,32 @@
1
+ # encoding: utf-8
2
+ require 'base64'
3
+
4
+ module Dragonfly
5
+ module Serializer
6
+
7
+ # Exceptions
8
+ class BadString < RuntimeError; end
9
+
10
+ extend self # So we can do Serializer.b64_encode, etc.
11
+
12
+ def b64_encode(string)
13
+ Base64.encode64(string).tr("\n=",'')
14
+ end
15
+
16
+ def b64_decode(string)
17
+ padding_length = string.length % 4
18
+ Base64.decode64(string + '=' * padding_length)
19
+ end
20
+
21
+ def marshal_encode(object)
22
+ b64_encode(Marshal.dump(object))
23
+ end
24
+
25
+ def marshal_decode(string)
26
+ Marshal.load(b64_decode(string))
27
+ rescue TypeError, ArgumentError => e
28
+ raise BadString, "couldn't decode #{string} - got #{e}"
29
+ end
30
+
31
+ end
32
+ end
@@ -0,0 +1,113 @@
1
+ module Dragonfly
2
+ class Server
3
+
4
+ include Loggable
5
+ include Configurable
6
+
7
+ configurable_attr :dragonfly_url, '/dragonfly'
8
+ configurable_attr :protect_from_dos_attacks, false
9
+ configurable_attr :url_format, '/:job/:basename.:format'
10
+ configurable_attr :url_host
11
+
12
+ extend Forwardable
13
+ def_delegator :url_mapper, :params_in_url
14
+
15
+ def initialize(app)
16
+ @app = app
17
+ use_same_log_as(app)
18
+ use_as_fallback_config(app)
19
+ end
20
+
21
+ def before_serve(&block)
22
+ self.before_serve_callback = block
23
+ end
24
+ configuration_method :before_serve
25
+
26
+ def after_serve(&block)
27
+ self.after_serve_callback = block
28
+ end
29
+ configuration_method :after_serve
30
+
31
+ def call(env)
32
+ if dragonfly_url == env["PATH_INFO"]
33
+ dragonfly_response
34
+ elsif (params = url_mapper.params_for(env["PATH_INFO"], env["QUERY_STRING"])) && params['job']
35
+ job = Job.deserialize(params['job'], app)
36
+ job.validate_sha!(params['sha']) if protect_from_dos_attacks
37
+ response = Response.new(job, env)
38
+ catch(:halt) do
39
+ if before_serve_callback && response.served?
40
+ before_serve_callback.call(job, env)
41
+ end
42
+ resp = response.to_response
43
+ if after_serve_callback && resp[0] == 200 && resp[2].is_a?(Dragonfly::TempObject)
44
+ after_serve_callback.call(job, env)
45
+ end
46
+ resp
47
+ end
48
+ else
49
+ [404, {'Content-Type' => 'text/plain', 'X-Cascade' => 'pass'}, ['Not found']]
50
+ end
51
+ rescue Serializer::BadString, Job::InvalidArray => e
52
+ log.warn(e.message)
53
+ [404, {'Content-Type' => 'text/plain'}, ['Not found']]
54
+ rescue Job::NoSHAGiven => e
55
+ [400, {"Content-Type" => 'text/plain'}, ["You need to give a SHA parameter"]]
56
+ rescue Job::IncorrectSHA => e
57
+ [400, {"Content-Type" => 'text/plain'}, ["The SHA parameter you gave (#{e}) is incorrect"]]
58
+ end
59
+
60
+ def url_for(job, opts={})
61
+ opts = opts.dup
62
+ host = opts.delete(:host) || url_host
63
+ params = stringify_keys(opts)
64
+ params['job'] = job.serialize
65
+ params['sha'] = job.sha if protect_from_dos_attacks
66
+ url = url_mapper.url_for(params)
67
+ "#{host}#{url}"
68
+ end
69
+
70
+ private
71
+
72
+ attr_reader :app
73
+ attr_accessor :before_serve_callback
74
+ attr_accessor :after_serve_callback
75
+
76
+ def url_mapper
77
+ @url_mapper ||= UrlMapper.new(url_format,
78
+ :job => '\w',
79
+ :basename => '[^\/]',
80
+ :format => '[^\.]'
81
+ )
82
+ end
83
+
84
+ def stringify_keys(params)
85
+ params.inject({}) do |hash, (k, v)|
86
+ hash[k.to_s] = v
87
+ hash
88
+ end
89
+ end
90
+
91
+ def dragonfly_response
92
+ body = <<-DRAGONFLY
93
+ _o|o_
94
+ _~~---._( )_.---~~_
95
+ ( . \\ / . )
96
+ `-.~--' |=| '--~.-'
97
+ _~-.~'" /|=|\\ "'~.-~_
98
+ ( ./ |=| \\. )
99
+ `~~`"` |=| `"'ME"
100
+ |-|
101
+ <->
102
+ V
103
+ DRAGONFLY
104
+ [200, {
105
+ 'Content-Type' => 'text/plain',
106
+ 'Content-Size' => body.bytesize.to_s
107
+ },
108
+ [body]
109
+ ]
110
+ end
111
+
112
+ end
113
+ end