fog-dragonfly 0.8.1

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 (155) hide show
  1. data/.specopts +2 -0
  2. data/.yardopts +23 -0
  3. data/Gemfile +23 -0
  4. data/Gemfile.rails.2.3.5 +14 -0
  5. data/History.md +266 -0
  6. data/LICENSE +20 -0
  7. data/README.md +88 -0
  8. data/Rakefile +92 -0
  9. data/VERSION +1 -0
  10. data/config.ru +13 -0
  11. data/docs.watchr +1 -0
  12. data/dragonfly.gemspec +293 -0
  13. data/extra_docs/Analysers.md +108 -0
  14. data/extra_docs/Caching.md +23 -0
  15. data/extra_docs/Configuration.md +138 -0
  16. data/extra_docs/DataStorage.md +136 -0
  17. data/extra_docs/Encoding.md +96 -0
  18. data/extra_docs/GeneralUsage.md +121 -0
  19. data/extra_docs/Generators.md +102 -0
  20. data/extra_docs/Heroku.md +50 -0
  21. data/extra_docs/Index.md +36 -0
  22. data/extra_docs/MimeTypes.md +40 -0
  23. data/extra_docs/Models.md +266 -0
  24. data/extra_docs/Mongo.md +45 -0
  25. data/extra_docs/Processing.md +130 -0
  26. data/extra_docs/Rack.md +52 -0
  27. data/extra_docs/Rails2.md +55 -0
  28. data/extra_docs/Rails3.md +62 -0
  29. data/extra_docs/Sinatra.md +25 -0
  30. data/extra_docs/URLs.md +169 -0
  31. data/features/3.0.3.feature +8 -0
  32. data/features/images.feature +47 -0
  33. data/features/no_processing.feature +14 -0
  34. data/features/rails_2.3.5.feature +7 -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 +39 -0
  38. data/features/support/env.rb +40 -0
  39. data/fixtures/files/app/models/album.rb +3 -0
  40. data/fixtures/files/app/views/albums/new.html.erb +4 -0
  41. data/fixtures/files/app/views/albums/show.html.erb +4 -0
  42. data/fixtures/files/config/initializers/dragonfly.rb +4 -0
  43. data/fixtures/files/features/manage_album_images.feature +12 -0
  44. data/fixtures/files/features/step_definitions/image_steps.rb +15 -0
  45. data/fixtures/files/features/support/paths.rb +15 -0
  46. data/fixtures/files/features/text_images.feature +7 -0
  47. data/fixtures/rails_2.3.5/template.rb +10 -0
  48. data/fixtures/rails_3.0.3/template.rb +20 -0
  49. data/irbrc.rb +17 -0
  50. data/lib/dragonfly.rb +45 -0
  51. data/lib/dragonfly/active_model_extensions.rb +13 -0
  52. data/lib/dragonfly/active_model_extensions/attachment.rb +169 -0
  53. data/lib/dragonfly/active_model_extensions/class_methods.rb +45 -0
  54. data/lib/dragonfly/active_model_extensions/instance_methods.rb +28 -0
  55. data/lib/dragonfly/active_model_extensions/validations.rb +37 -0
  56. data/lib/dragonfly/analyser.rb +59 -0
  57. data/lib/dragonfly/analysis/file_command_analyser.rb +32 -0
  58. data/lib/dragonfly/analysis/image_magick_analyser.rb +47 -0
  59. data/lib/dragonfly/analysis/r_magick_analyser.rb +63 -0
  60. data/lib/dragonfly/app.rb +182 -0
  61. data/lib/dragonfly/config/heroku.rb +19 -0
  62. data/lib/dragonfly/config/image_magick.rb +41 -0
  63. data/lib/dragonfly/config/r_magick.rb +46 -0
  64. data/lib/dragonfly/config/rails.rb +17 -0
  65. data/lib/dragonfly/configurable.rb +119 -0
  66. data/lib/dragonfly/core_ext/object.rb +8 -0
  67. data/lib/dragonfly/core_ext/string.rb +9 -0
  68. data/lib/dragonfly/core_ext/symbol.rb +9 -0
  69. data/lib/dragonfly/data_storage.rb +9 -0
  70. data/lib/dragonfly/data_storage/file_data_store.rb +114 -0
  71. data/lib/dragonfly/data_storage/mongo_data_store.rb +82 -0
  72. data/lib/dragonfly/data_storage/s3data_store.rb +115 -0
  73. data/lib/dragonfly/encoder.rb +13 -0
  74. data/lib/dragonfly/encoding/image_magick_encoder.rb +57 -0
  75. data/lib/dragonfly/encoding/r_magick_encoder.rb +61 -0
  76. data/lib/dragonfly/function_manager.rb +69 -0
  77. data/lib/dragonfly/generation/hash_with_css_style_keys.rb +23 -0
  78. data/lib/dragonfly/generation/image_magick_generator.rb +140 -0
  79. data/lib/dragonfly/generation/r_magick_generator.rb +155 -0
  80. data/lib/dragonfly/generator.rb +9 -0
  81. data/lib/dragonfly/image_magick_utils.rb +81 -0
  82. data/lib/dragonfly/job.rb +371 -0
  83. data/lib/dragonfly/job_builder.rb +39 -0
  84. data/lib/dragonfly/job_definitions.rb +26 -0
  85. data/lib/dragonfly/job_endpoint.rb +15 -0
  86. data/lib/dragonfly/loggable.rb +28 -0
  87. data/lib/dragonfly/middleware.rb +34 -0
  88. data/lib/dragonfly/processing/image_magick_processor.rb +99 -0
  89. data/lib/dragonfly/processing/r_magick_processor.rb +126 -0
  90. data/lib/dragonfly/processor.rb +9 -0
  91. data/lib/dragonfly/r_magick_utils.rb +48 -0
  92. data/lib/dragonfly/rails/images.rb +22 -0
  93. data/lib/dragonfly/response.rb +82 -0
  94. data/lib/dragonfly/routed_endpoint.rb +40 -0
  95. data/lib/dragonfly/serializer.rb +32 -0
  96. data/lib/dragonfly/simple_cache.rb +23 -0
  97. data/lib/dragonfly/simple_endpoint.rb +63 -0
  98. data/lib/dragonfly/temp_object.rb +220 -0
  99. data/samples/beach.png +0 -0
  100. data/samples/egg.png +0 -0
  101. data/samples/round.gif +0 -0
  102. data/samples/sample.docx +0 -0
  103. data/samples/taj.jpg +0 -0
  104. data/spec/argument_matchers.rb +19 -0
  105. data/spec/dragonfly/active_model_extensions/active_model_setup.rb +97 -0
  106. data/spec/dragonfly/active_model_extensions/active_record_setup.rb +85 -0
  107. data/spec/dragonfly/active_model_extensions/model_spec.rb +723 -0
  108. data/spec/dragonfly/active_model_extensions/spec_helper.rb +11 -0
  109. data/spec/dragonfly/analyser_spec.rb +123 -0
  110. data/spec/dragonfly/analysis/file_command_analyser_spec.rb +57 -0
  111. data/spec/dragonfly/analysis/image_magick_analyser_spec.rb +15 -0
  112. data/spec/dragonfly/analysis/r_magick_analyser_spec.rb +27 -0
  113. data/spec/dragonfly/analysis/shared_analyser_spec.rb +51 -0
  114. data/spec/dragonfly/app_spec.rb +280 -0
  115. data/spec/dragonfly/config/r_magick_spec.rb +25 -0
  116. data/spec/dragonfly/configurable_spec.rb +220 -0
  117. data/spec/dragonfly/core_ext/string_spec.rb +17 -0
  118. data/spec/dragonfly/core_ext/symbol_spec.rb +17 -0
  119. data/spec/dragonfly/data_storage/data_store_spec.rb +76 -0
  120. data/spec/dragonfly/data_storage/file_data_store_spec.rb +169 -0
  121. data/spec/dragonfly/data_storage/mongo_data_store_spec.rb +38 -0
  122. data/spec/dragonfly/data_storage/s3_data_store_spec.rb +94 -0
  123. data/spec/dragonfly/deprecation_spec.rb +20 -0
  124. data/spec/dragonfly/encoding/image_magick_encoder_spec.rb +41 -0
  125. data/spec/dragonfly/encoding/r_magick_encoder_spec.rb +37 -0
  126. data/spec/dragonfly/function_manager_spec.rb +154 -0
  127. data/spec/dragonfly/generation/hash_with_css_style_keys_spec.rb +24 -0
  128. data/spec/dragonfly/generation/image_magick_generator_spec.rb +12 -0
  129. data/spec/dragonfly/generation/r_magick_generator_spec.rb +24 -0
  130. data/spec/dragonfly/generation/shared_generator_spec.rb +91 -0
  131. data/spec/dragonfly/image_magick_utils_spec.rb +16 -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 +120 -0
  135. data/spec/dragonfly/job_spec.rb +773 -0
  136. data/spec/dragonfly/loggable_spec.rb +80 -0
  137. data/spec/dragonfly/middleware_spec.rb +68 -0
  138. data/spec/dragonfly/processing/image_magick_processor_spec.rb +29 -0
  139. data/spec/dragonfly/processing/r_magick_processor_spec.rb +26 -0
  140. data/spec/dragonfly/processing/shared_processing_spec.rb +215 -0
  141. data/spec/dragonfly/routed_endpoint_spec.rb +48 -0
  142. data/spec/dragonfly/serializer_spec.rb +61 -0
  143. data/spec/dragonfly/simple_cache_spec.rb +27 -0
  144. data/spec/dragonfly/simple_endpoint_spec.rb +89 -0
  145. data/spec/dragonfly/temp_object_spec.rb +352 -0
  146. data/spec/image_matchers.rb +47 -0
  147. data/spec/simple_matchers.rb +44 -0
  148. data/spec/spec_helper.rb +58 -0
  149. data/yard/handlers/configurable_attr_handler.rb +38 -0
  150. data/yard/setup.rb +15 -0
  151. data/yard/templates/default/fulldoc/html/css/common.css +107 -0
  152. data/yard/templates/default/layout/html/layout.erb +87 -0
  153. data/yard/templates/default/module/html/configuration_summary.erb +31 -0
  154. data/yard/templates/default/module/setup.rb +17 -0
  155. metadata +550 -0
@@ -0,0 +1,82 @@
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 etag_matches?
20
+ # Not Modified
21
+ [304, cache_headers, []]
22
+ else
23
+ # Success
24
+ [200, success_headers.merge(cache_headers), job.result]
25
+ end
26
+ rescue DataStorage::DataNotFound => e
27
+ [404, {"Content-Type" => 'text/plain'}, [e.message]]
28
+ end
29
+
30
+ private
31
+
32
+ attr_reader :job, :env, :app
33
+
34
+ def request
35
+ @request ||= Rack::Request.new(env)
36
+ end
37
+
38
+ def cache_headers
39
+ {
40
+ "Cache-Control" => "public, max-age=#{app.cache_duration}",
41
+ "ETag" => %("#{job.unique_signature}")
42
+ }
43
+ end
44
+
45
+ def etag_matches?
46
+ if_none_match = env['HTTP_IF_NONE_MATCH']
47
+ if if_none_match
48
+ if_none_match.tr!('"','')
49
+ if_none_match.split(',').include?(job.unique_signature) || if_none_match == '*'
50
+ else
51
+ false
52
+ end
53
+ end
54
+
55
+ def success_headers
56
+ {
57
+ "Content-Type" => job.resolve_mime_type,
58
+ "Content-Length" => job.size.to_s
59
+ }.merge(content_disposition_header)
60
+ end
61
+
62
+ def content_disposition_header
63
+ parts = []
64
+ parts << content_disposition if content_disposition
65
+ parts << %(filename="#{URI.encode(filename)}") if filename
66
+ parts.any? ? {"Content-Disposition" => parts.join('; ')} : {}
67
+ end
68
+
69
+ def content_disposition
70
+ @content_disposition ||= evaluate(app.content_disposition)
71
+ end
72
+
73
+ def filename
74
+ @filename ||= evaluate(app.content_filename)
75
+ end
76
+
77
+ def evaluate(attribute)
78
+ attribute.respond_to?(:call) ? attribute.call(job, request) : attribute
79
+ end
80
+
81
+ end
82
+ 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,23 @@
1
+ module Dragonfly
2
+ class SimpleCache < Hash
3
+
4
+ def initialize(max_size)
5
+ @max_size = max_size
6
+ @keys = []
7
+ end
8
+
9
+ attr_reader :max_size
10
+
11
+ def []=(key, value)
12
+ if !has_key?(key)
13
+ @keys << key
14
+ if size == max_size
15
+ key_to_purge = @keys.shift
16
+ delete(key_to_purge)
17
+ end
18
+ end
19
+ super
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,63 @@
1
+ module Dragonfly
2
+ class SimpleEndpoint
3
+
4
+ include Loggable
5
+
6
+ # Instance methods
7
+
8
+ def initialize(app)
9
+ @app = app
10
+ use_same_log_as(app)
11
+ end
12
+
13
+ def call(env)
14
+ request = Rack::Request.new(env)
15
+
16
+ case request.path_info
17
+ when '', '/', app.url_path_prefix
18
+ dragonfly_response
19
+ else
20
+ job = Job.from_path(request.path_info, app)
21
+ job.validate_sha!(request['s']) if app.protect_from_dos_attacks
22
+ Response.new(job, env).to_response
23
+ end
24
+ rescue Serializer::BadString, Job::InvalidArray => e
25
+ log.warn(e.message)
26
+ [404, {'Content-Type' => 'text/plain'}, ['Not found']]
27
+ rescue Job::NoSHAGiven => e
28
+ [400, {"Content-Type" => 'text/plain'}, ["You need to give a SHA parameter"]]
29
+ rescue Job::IncorrectSHA => e
30
+ [400, {"Content-Type" => 'text/plain'}, ["The SHA parameter you gave (#{e}) is incorrect"]]
31
+ end
32
+
33
+ def required_params_for(job)
34
+ {'s' => job.sha}
35
+ end
36
+
37
+ private
38
+
39
+ attr_reader :app
40
+
41
+ def dragonfly_response
42
+ body = <<-DRAGONFLY
43
+ _o|o_
44
+ _~~---._( )_.---~~_
45
+ ( . \\ / . )
46
+ `-.~--' |=| '--~.-'
47
+ _~-.~'" /|=|\\ "'~.-~_
48
+ ( ./ |=| \\. )
49
+ `~~`"` |=| `"'ME"
50
+ |-|
51
+ <->
52
+ V
53
+ DRAGONFLY
54
+ [200, {
55
+ 'Content-Type' => 'text/plain',
56
+ 'Content-Size' => body.bytesize.to_s
57
+ },
58
+ [body]
59
+ ]
60
+ end
61
+
62
+ end
63
+ end
@@ -0,0 +1,220 @@
1
+ require 'stringio'
2
+ require 'tempfile'
3
+
4
+ module Dragonfly
5
+
6
+ # A TempObject is used for HOLDING DATA.
7
+ # It's the thing that is passed between the datastore, the processor and the encoder, and is useful
8
+ # for separating how the data was created and how it is later accessed.
9
+ #
10
+ # You can initialize it various ways:
11
+ #
12
+ # temp_object = Dragonfly::TempObject.new('this is the content') # with a String
13
+ # temp_object = Dragonfly::TempObject.new(File.new('path/to/content')) # with a File
14
+ # temp_object = Dragonfly::TempObject.new(some_tempfile) # with a Tempfile
15
+ # temp_object = Dragonfly::TempObject.new(some_other_temp_object) # with another TempObject
16
+ #
17
+ # However, no matter how it was initialized, you can always access the data a number of ways:
18
+ #
19
+ # temp_object.data # returns a data string
20
+ # temp_object.file # returns a file object holding the data
21
+ # temp_object.path # returns a path for the file
22
+ #
23
+ # The data/file are created lazily, something which you may wish to take advantage of.
24
+ #
25
+ # For example, if a TempObject is initialized with a file, and temp_object.data is never called, then
26
+ # the data string will never be loaded into memory.
27
+ #
28
+ # Conversely, if the TempObject is initialized with a data string, and neither temp_object.file nor temp_object.path
29
+ # are ever called, then the filesystem will never be hit.
30
+ #
31
+ class TempObject
32
+
33
+ # Class configuration
34
+ class << self
35
+
36
+ include Configurable
37
+ configurable_attr :block_size, 8192
38
+
39
+ end
40
+
41
+ # Instance Methods
42
+
43
+ def initialize(obj, opts={})
44
+ opts ||= {} # in case it's nil
45
+ initialize_from_object!(obj)
46
+ validate_options!(opts)
47
+ extract_attributes_from(opts)
48
+ end
49
+
50
+ def data
51
+ @data ||= initialized_data || file{|f| f.read }
52
+ end
53
+
54
+ def tempfile
55
+ @tempfile ||= begin
56
+ case initialized_with
57
+ when :tempfile
58
+ @tempfile = initialized_tempfile
59
+ @tempfile.close
60
+ when :data
61
+ @tempfile = new_tempfile(initialized_data)
62
+ when :file
63
+ @tempfile = copy_to_tempfile(initialized_file.path)
64
+ end
65
+ @tempfile
66
+ end
67
+ end
68
+
69
+ def file(&block)
70
+ f = tempfile.open
71
+ tempfile.binmode
72
+ if block_given?
73
+ ret = yield f
74
+ tempfile.close
75
+ else
76
+ ret = f
77
+ end
78
+ ret
79
+ end
80
+
81
+ def path
82
+ tempfile.path
83
+ end
84
+
85
+ def size
86
+ if initialized_data
87
+ initialized_data.bytesize
88
+ else
89
+ File.size(path)
90
+ end
91
+ end
92
+
93
+ attr_accessor :name, :format
94
+ alias _format format
95
+ attr_writer :meta
96
+
97
+ def meta
98
+ @meta ||= {}
99
+ end
100
+
101
+ def basename
102
+ File.basename(name, '.*') if name
103
+ end
104
+
105
+ def ext
106
+ File.extname(name)[/\.(.*)/, 1] if name
107
+ end
108
+
109
+ def each(&block)
110
+ to_io do |io|
111
+ while part = io.read(block_size)
112
+ yield part
113
+ end
114
+ end
115
+ end
116
+
117
+ def to_file(path)
118
+ if initialized_data
119
+ File.open(path, 'wb'){|f| f.write(initialized_data) }
120
+ else
121
+ FileUtils.cp(self.path, path)
122
+ end
123
+ File.new(path, 'rb')
124
+ end
125
+
126
+ def to_io(&block)
127
+ if initialized_data
128
+ StringIO.open(initialized_data, 'rb', &block)
129
+ else
130
+ file(&block)
131
+ end
132
+ end
133
+
134
+ def attributes
135
+ {
136
+ :name => name,
137
+ :meta => meta,
138
+ :format => format
139
+ }
140
+ end
141
+
142
+ def extract_attributes_from(hash)
143
+ self.name = hash.delete(:name) unless hash[:name].blank?
144
+ self.format = hash.delete(:format) unless hash[:format].blank?
145
+ self.meta.merge!(hash.delete(:meta)) unless hash[:meta].blank?
146
+ end
147
+
148
+ def inspect
149
+ content_string = case initialized_with
150
+ when :data
151
+ data_string = size > 20 ? "#{initialized_data[0..20]}..." : initialized_data
152
+ "data=#{data_string.inspect}"
153
+ when :file then "file=#{initialized_file.inspect}"
154
+ when :tempfile then "tempfile=#{initialized_tempfile.inspect}"
155
+ end
156
+ to_s.sub(/>$/, " #{content_string}, @meta=#{@meta.inspect}, @name=#{@name.inspect} >")
157
+ end
158
+
159
+ protected
160
+
161
+ attr_accessor :initialized_data, :initialized_tempfile, :initialized_file
162
+
163
+ private
164
+
165
+ def initialize_from_object!(obj)
166
+ if obj.is_a? TempObject
167
+ @initialized_data = obj.initialized_data
168
+ @initialized_tempfile = copy_to_tempfile(obj.initialized_tempfile.path) if obj.initialized_tempfile
169
+ @initialized_file = obj.initialized_file
170
+ elsif obj.is_a? String
171
+ @initialized_data = obj
172
+ elsif obj.is_a? Tempfile
173
+ @initialized_tempfile = obj
174
+ elsif obj.is_a? File
175
+ @initialized_file = obj
176
+ self.name = File.basename(obj.path)
177
+ elsif obj.respond_to?(:tempfile)
178
+ @initialized_tempfile = obj.tempfile
179
+ else
180
+ raise ArgumentError, "#{self.class.name} must be initialized with a String, a File, a Tempfile, another TempObject, or something that responds to .tempfile"
181
+ end
182
+ self.name = obj.original_filename if obj.respond_to?(:original_filename)
183
+ end
184
+
185
+ def initialized_with
186
+ if initialized_tempfile
187
+ :tempfile
188
+ elsif initialized_data
189
+ :data
190
+ elsif initialized_file
191
+ :file
192
+ end
193
+ end
194
+
195
+ def block_size
196
+ self.class.block_size
197
+ end
198
+
199
+ def copy_to_tempfile(path)
200
+ tempfile = new_tempfile
201
+ FileUtils.cp File.expand_path(path), tempfile.path
202
+ tempfile
203
+ end
204
+
205
+ def validate_options!(opts)
206
+ valid_keys = [:name, :meta, :format]
207
+ invalid_keys = opts.keys - valid_keys
208
+ raise ArgumentError, "Unrecognised options #{invalid_keys.inspect}" if invalid_keys.any?
209
+ end
210
+
211
+ def new_tempfile(content=nil)
212
+ tempfile = Tempfile.new('dragonfly')
213
+ tempfile.binmode
214
+ tempfile.write(content) if content
215
+ tempfile.close
216
+ tempfile
217
+ end
218
+
219
+ end
220
+ end