paperclip 4.2.4 → 4.3.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of paperclip might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.hound.yml +1066 -0
- data/.rubocop.yml +1 -0
- data/.travis.yml +5 -3
- data/Appraisals +1 -6
- data/CONTRIBUTING.md +3 -3
- data/Gemfile +1 -0
- data/LICENSE +1 -1
- data/NEWS +14 -1
- data/README.md +137 -77
- data/RELEASING.md +17 -0
- data/Rakefile +1 -1
- data/features/basic_integration.feature +7 -4
- data/features/step_definitions/attachment_steps.rb +7 -1
- data/gemfiles/3.2.gemfile +2 -1
- data/gemfiles/4.1.gemfile +1 -0
- data/gemfiles/4.2.gemfile +2 -1
- data/lib/paperclip.rb +13 -3
- data/lib/paperclip/attachment.rb +3 -1
- data/lib/paperclip/attachment_registry.rb +1 -1
- data/lib/paperclip/content_type_detector.rb +26 -11
- data/lib/paperclip/file_command_content_type_detector.rb +6 -8
- data/lib/paperclip/glue.rb +1 -1
- data/lib/paperclip/has_attached_file.rb +2 -1
- data/lib/paperclip/interpolations.rb +1 -1
- data/lib/paperclip/io_adapters/abstract_adapter.rb +1 -0
- data/lib/paperclip/media_type_spoof_detector.rb +1 -1
- data/lib/paperclip/rails_environment.rb +25 -0
- data/lib/paperclip/storage/fog.rb +4 -4
- data/lib/paperclip/storage/s3.rb +2 -3
- data/lib/paperclip/thumbnail.rb +2 -3
- data/lib/paperclip/version.rb +1 -1
- data/lib/tasks/paperclip.rake +16 -0
- data/paperclip.gemspec +3 -4
- data/spec/paperclip/attachment_definitions_spec.rb +1 -1
- data/spec/paperclip/attachment_registry_spec.rb +56 -13
- data/spec/paperclip/attachment_spec.rb +57 -19
- data/spec/paperclip/content_type_detector_spec.rb +8 -1
- data/spec/paperclip/file_command_content_type_detector_spec.rb +0 -1
- data/spec/paperclip/interpolations_spec.rb +2 -9
- data/spec/paperclip/io_adapters/abstract_adapter_spec.rb +2 -1
- data/spec/paperclip/io_adapters/file_adapter_spec.rb +4 -1
- data/spec/paperclip/io_adapters/stringio_adapter_spec.rb +4 -0
- data/spec/paperclip/media_type_spoof_detector_spec.rb +16 -2
- data/spec/paperclip/rails_environment_spec.rb +33 -0
- data/spec/paperclip/storage/fog_spec.rb +11 -2
- data/spec/paperclip/storage/s3_spec.rb +43 -25
- data/spec/paperclip/tempfile_factory_spec.rb +4 -0
- data/spec/paperclip/thumbnail_spec.rb +16 -0
- data/spec/paperclip/url_generator_spec.rb +1 -1
- data/spec/support/fake_model.rb +4 -0
- data/spec/support/fixtures/empty.xlsx +0 -0
- data/spec/support/matchers/have_column.rb +11 -2
- metadata +30 -12
- data/RUNNING_TESTS.md +0 -4
- data/gemfiles/4.0.gemfile +0 -19
- data/spec/support/mock_model.rb +0 -2
data/RELEASING.md
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
Releasing paperclip
|
2
|
+
|
3
|
+
1. Update `lib/paperclip/version.rb` file accordingly.
|
4
|
+
2. Update `NEWS` to reflect the changes since last release.
|
5
|
+
3. Commit changes. There shouldn’t be code changes, and thus CI doesn’t need to
|
6
|
+
run, you can then add “[ci skip]” to the commit message.
|
7
|
+
4. Tag the release: `git tag -m 'vVERSION' vVERSION`
|
8
|
+
5. Push changes: `git push --tags`
|
9
|
+
6. Build and publish the gem:
|
10
|
+
|
11
|
+
```bash
|
12
|
+
gem build paperclip.gemspec
|
13
|
+
gem push paperclip-VERSION.gem
|
14
|
+
```
|
15
|
+
|
16
|
+
7. Announce the new release, making sure to say “thank you” to the contributors
|
17
|
+
who helped shape this version.
|
data/Rakefile
CHANGED
@@ -12,17 +12,20 @@ Feature: Rails integration
|
|
12
12
|
Scenario: Configure defaults for all attachments through Railtie
|
13
13
|
Given I add this snippet to config/application.rb:
|
14
14
|
"""
|
15
|
-
config.paperclip_defaults = {
|
15
|
+
config.paperclip_defaults = {
|
16
|
+
:url => "/paperclip/custom/:attachment/:style/:filename",
|
17
|
+
:validate_media_type => false
|
18
|
+
}
|
16
19
|
"""
|
17
20
|
And I attach :attachment
|
18
21
|
And I start the rails application
|
19
22
|
When I go to the new user page
|
20
23
|
And I fill in "Name" with "something"
|
21
|
-
And I attach the file "spec/support/fixtures/
|
24
|
+
And I attach the file "spec/support/fixtures/animated.unknown" to "Attachment"
|
22
25
|
And I press "Submit"
|
23
26
|
Then I should see "Name: something"
|
24
|
-
And I should see an image with a path of "/paperclip/custom/attachments/original/
|
25
|
-
And the file at "/paperclip/custom/attachments/original/
|
27
|
+
And I should see an image with a path of "/paperclip/custom/attachments/original/animated.unknown"
|
28
|
+
And the file at "/paperclip/custom/attachments/original/animated.unknown" should be the same as "spec/support/fixtures/animated.unknown"
|
26
29
|
|
27
30
|
Scenario: Add custom processors
|
28
31
|
Given I add a "test" processor in "lib/paperclip"
|
@@ -49,7 +49,13 @@ end
|
|
49
49
|
|
50
50
|
Then /^the attachment should have the same content type as the fixture "([^"]*)"$/ do |filename|
|
51
51
|
in_current_dir do
|
52
|
-
|
52
|
+
begin
|
53
|
+
# Use mime/types/columnar if available, for reduced memory usage
|
54
|
+
require "mime/types/columnar"
|
55
|
+
rescue LoadError
|
56
|
+
require "mime/types"
|
57
|
+
end
|
58
|
+
|
53
59
|
attachment_content_type = `bundle exec #{runner_command} "puts User.last.attachment_content_type"`.strip
|
54
60
|
attachment_content_type.should == MIME::Types.type_for(filename).first.content_type
|
55
61
|
end
|
data/gemfiles/3.2.gemfile
CHANGED
@@ -8,12 +8,13 @@ gem "activerecord-jdbcsqlite3-adapter", :platforms => :jruby
|
|
8
8
|
gem "rubysl", :platforms => :rbx
|
9
9
|
gem "racc", :platforms => :rbx
|
10
10
|
gem "pry"
|
11
|
-
gem "rails", "~> 3.2.
|
11
|
+
gem "rails", "~> 3.2.15"
|
12
12
|
gem "paperclip", :path => "../"
|
13
13
|
|
14
14
|
group :development, :test do
|
15
15
|
gem "mime-types", "~> 1.16"
|
16
16
|
gem "builder"
|
17
|
+
gem "rubocop", :require => false
|
17
18
|
end
|
18
19
|
|
19
20
|
gemspec :path => "../"
|
data/gemfiles/4.1.gemfile
CHANGED
data/gemfiles/4.2.gemfile
CHANGED
@@ -8,12 +8,13 @@ gem "activerecord-jdbcsqlite3-adapter", :platforms => :jruby
|
|
8
8
|
gem "rubysl", :platforms => :rbx
|
9
9
|
gem "racc", :platforms => :rbx
|
10
10
|
gem "pry"
|
11
|
-
gem "rails", "~> 4.2.0
|
11
|
+
gem "rails", "~> 4.2.0"
|
12
12
|
gem "paperclip", :path => "../"
|
13
13
|
|
14
14
|
group :development, :test do
|
15
15
|
gem "mime-types", "~> 1.16"
|
16
16
|
gem "builder"
|
17
|
+
gem "rubocop", :require => false
|
17
18
|
end
|
18
19
|
|
19
20
|
gemspec :path => "../"
|
data/lib/paperclip.rb
CHANGED
@@ -55,11 +55,21 @@ require 'paperclip/helpers'
|
|
55
55
|
require 'paperclip/has_attached_file'
|
56
56
|
require 'paperclip/attachment_registry'
|
57
57
|
require 'paperclip/filename_cleaner'
|
58
|
-
require '
|
58
|
+
require 'paperclip/rails_environment'
|
59
|
+
|
60
|
+
begin
|
61
|
+
# Use mime/types/columnar if available, for reduced memory usage
|
62
|
+
require "mime/types/columnar"
|
63
|
+
rescue LoadError
|
64
|
+
require "mime/types"
|
65
|
+
end
|
66
|
+
|
67
|
+
require 'mimemagic'
|
68
|
+
require 'mimemagic/overlay'
|
59
69
|
require 'logger'
|
60
70
|
require 'cocaine'
|
61
71
|
|
62
|
-
require 'paperclip/railtie' if defined?(Rails)
|
72
|
+
require 'paperclip/railtie' if defined?(Rails::Railtie)
|
63
73
|
|
64
74
|
# The base module that gets included in ActiveRecord::Base. See the
|
65
75
|
# documentation for Paperclip::ClassMethods for more useful information.
|
@@ -136,7 +146,7 @@ module Paperclip
|
|
136
146
|
# user.avatar.url # => "/avatars/23/normal_me.png"
|
137
147
|
# * +keep_old_files+: Keep the existing attachment files (original + resized) from
|
138
148
|
# being automatically deleted when an attachment is cleared or updated. Defaults to +false+.
|
139
|
-
# * +preserve_files+: Keep the existing attachment files in all cases, even if the parent
|
149
|
+
# * +preserve_files+: Keep the existing attachment files in all cases, even if the parent
|
140
150
|
# record is destroyed. Defaults to +false+.
|
141
151
|
# * +whiny+: Will raise an error if Paperclip cannot post_process an uploaded file due
|
142
152
|
# to a command line error. This will override the global setting for this attachment.
|
data/lib/paperclip/attachment.rb
CHANGED
@@ -137,6 +137,8 @@ module Paperclip
|
|
137
137
|
# +#for(style_name, options_hash)+
|
138
138
|
|
139
139
|
def url(style_name = default_style, options = {})
|
140
|
+
return nil if @instance.new_record?
|
141
|
+
|
140
142
|
if options == true || options == false # Backwards compatibility.
|
141
143
|
@url_generator.for(style_name, default_options.merge(:timestamp => options))
|
142
144
|
else
|
@@ -528,7 +530,7 @@ module Paperclip
|
|
528
530
|
@queued_for_write[name] = Paperclip.io_adapters.for(@queued_for_write[name])
|
529
531
|
unadapted_file.close if unadapted_file.respond_to?(:close)
|
530
532
|
@queued_for_write[name]
|
531
|
-
rescue Paperclip::
|
533
|
+
rescue Paperclip::Errors::NotIdentifiedByImageMagickError => e
|
532
534
|
log("An error was received while processing: #{e.inspect}")
|
533
535
|
(@errors[:processing] ||= []) << e.message if @options[:whiny]
|
534
536
|
ensure
|
@@ -52,7 +52,7 @@ module Paperclip
|
|
52
52
|
|
53
53
|
def definitions_for(klass)
|
54
54
|
klass.ancestors.each_with_object({}) do |ancestor, inherited_definitions|
|
55
|
-
inherited_definitions.
|
55
|
+
inherited_definitions.deep_merge! @attachments[ancestor]
|
56
56
|
end
|
57
57
|
end
|
58
58
|
end
|
@@ -2,7 +2,7 @@ module Paperclip
|
|
2
2
|
class ContentTypeDetector
|
3
3
|
# The content-type detection strategy is as follows:
|
4
4
|
#
|
5
|
-
# 1. Blank/Empty files: If there's no
|
5
|
+
# 1. Blank/Empty files: If there's no filepath or the file is empty,
|
6
6
|
# provide a sensible default (application/octet-stream or inode/x-empty)
|
7
7
|
#
|
8
8
|
# 2. Calculated match: Return the first result that is found by both the
|
@@ -20,8 +20,8 @@ module Paperclip
|
|
20
20
|
EMPTY_TYPE = "inode/x-empty"
|
21
21
|
SENSIBLE_DEFAULT = "application/octet-stream"
|
22
22
|
|
23
|
-
def initialize(
|
24
|
-
@
|
23
|
+
def initialize(filepath)
|
24
|
+
@filepath = filepath
|
25
25
|
end
|
26
26
|
|
27
27
|
# Returns a String describing the file's content type
|
@@ -33,32 +33,47 @@ module Paperclip
|
|
33
33
|
elsif calculated_type_matches.any?
|
34
34
|
calculated_type_matches.first
|
35
35
|
else
|
36
|
-
|
36
|
+
type_from_file_contents || SENSIBLE_DEFAULT
|
37
37
|
end.to_s
|
38
38
|
end
|
39
39
|
|
40
40
|
private
|
41
41
|
|
42
|
+
def blank_name?
|
43
|
+
@filepath.nil? || @filepath.empty?
|
44
|
+
end
|
45
|
+
|
42
46
|
def empty_file?
|
43
|
-
File.exist?(@
|
47
|
+
File.exist?(@filepath) && File.size(@filepath) == 0
|
44
48
|
end
|
45
49
|
|
46
50
|
alias :empty? :empty_file?
|
47
51
|
|
48
|
-
def
|
49
|
-
|
52
|
+
def calculated_type_matches
|
53
|
+
possible_types.select do |content_type|
|
54
|
+
content_type == type_from_file_contents
|
55
|
+
end
|
50
56
|
end
|
51
57
|
|
52
58
|
def possible_types
|
53
|
-
MIME::Types.type_for(@
|
59
|
+
MIME::Types.type_for(@filepath).collect(&:content_type)
|
54
60
|
end
|
55
61
|
|
56
|
-
def
|
57
|
-
|
62
|
+
def type_from_file_contents
|
63
|
+
type_from_mime_magic || type_from_file_command
|
64
|
+
rescue Errno::ENOENT => e
|
65
|
+
Paperclip.log("Error while determining content type: #{e}")
|
66
|
+
SENSIBLE_DEFAULT
|
67
|
+
end
|
68
|
+
|
69
|
+
def type_from_mime_magic
|
70
|
+
@type_from_mime_magic ||=
|
71
|
+
MimeMagic.by_magic(File.open(@filepath)).try(:type)
|
58
72
|
end
|
59
73
|
|
60
74
|
def type_from_file_command
|
61
|
-
@type_from_file_command ||=
|
75
|
+
@type_from_file_command ||=
|
76
|
+
FileCommandContentTypeDetector.new(@filepath).detect
|
62
77
|
end
|
63
78
|
end
|
64
79
|
end
|
@@ -13,20 +13,18 @@ module Paperclip
|
|
13
13
|
private
|
14
14
|
|
15
15
|
def type_from_file_command
|
16
|
+
# On BSDs, `file` doesn't give a result code of 1 if the file doesn't exist.
|
16
17
|
type = begin
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
end
|
18
|
+
Paperclip.run("file", "-b --mime :file", file: @filename)
|
19
|
+
rescue Cocaine::CommandLineError => e
|
20
|
+
Paperclip.log("Error while determining content type: #{e}")
|
21
|
+
SENSIBLE_DEFAULT
|
22
|
+
end
|
23
23
|
|
24
24
|
if type.nil? || type.match(/\(.*?\)/)
|
25
25
|
type = SENSIBLE_DEFAULT
|
26
26
|
end
|
27
27
|
type.split(/[:;\s]+/)[0]
|
28
28
|
end
|
29
|
-
|
30
29
|
end
|
31
30
|
end
|
32
|
-
|
data/lib/paperclip/glue.rb
CHANGED
@@ -8,7 +8,7 @@ module Paperclip
|
|
8
8
|
base.extend ClassMethods
|
9
9
|
base.send :include, Callbacks
|
10
10
|
base.send :include, Validators
|
11
|
-
base.send :include, Schema
|
11
|
+
base.send :include, Schema
|
12
12
|
|
13
13
|
locale_path = Dir.glob(File.dirname(__FILE__) + "/locales/*.{rb,yml}")
|
14
14
|
I18n.load_path += locale_path unless I18n.load_path.include?(locale_path)
|
@@ -79,7 +79,8 @@ module Paperclip
|
|
79
79
|
end
|
80
80
|
|
81
81
|
def add_required_validations
|
82
|
-
|
82
|
+
options = Paperclip::Attachment.default_options.deep_merge(@options)
|
83
|
+
if options[:validate_media_type] != false
|
83
84
|
name = @name
|
84
85
|
@klass.validates_media_type_spoof_detection name,
|
85
86
|
:if => ->(instance){ instance.send(name).dirty? }
|
@@ -6,6 +6,7 @@ module Paperclip
|
|
6
6
|
|
7
7
|
attr_reader :content_type, :original_filename, :size
|
8
8
|
delegate :binmode, :binmode?, :close, :close!, :closed?, :eof?, :path, :rewind, :unlink, :to => :@tempfile
|
9
|
+
alias :length :size
|
9
10
|
|
10
11
|
def fingerprint
|
11
12
|
@fingerprint ||= Digest::MD5.file(path).to_s
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Paperclip
|
2
|
+
class RailsEnvironment
|
3
|
+
def self.get
|
4
|
+
new.get
|
5
|
+
end
|
6
|
+
|
7
|
+
def get
|
8
|
+
if rails_exists? && rails_environment_exists?
|
9
|
+
Rails.env
|
10
|
+
else
|
11
|
+
nil
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def rails_exists?
|
18
|
+
Object.const_defined?("Rails")
|
19
|
+
end
|
20
|
+
|
21
|
+
def rails_environment_exists?
|
22
|
+
Rails.respond_to?(:env)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -141,8 +141,9 @@ module Paperclip
|
|
141
141
|
|
142
142
|
def expiring_url(time = (Time.now + 3600), style_name = default_style)
|
143
143
|
time = convert_time(time)
|
144
|
-
|
145
|
-
|
144
|
+
http_url_method = "get_#{scheme}_url"
|
145
|
+
if path(style_name) && directory.files.respond_to?(http_url_method)
|
146
|
+
expiring_url = directory.files.public_send(http_url_method, path(style_name), time)
|
146
147
|
|
147
148
|
if @options[:fog_host]
|
148
149
|
expiring_url.gsub!(/#{host_name_for_directory}/, dynamic_fog_host_for_style(style_name))
|
@@ -156,8 +157,7 @@ module Paperclip
|
|
156
157
|
|
157
158
|
def parse_credentials(creds)
|
158
159
|
creds = find_credentials(creds).stringify_keys
|
159
|
-
|
160
|
-
(creds[env] || creds).symbolize_keys
|
160
|
+
(creds[RailsEnvironment.get] || creds).symbolize_keys
|
161
161
|
end
|
162
162
|
|
163
163
|
def copy_to_local_file(style, local_dest_path)
|
data/lib/paperclip/storage/s3.rb
CHANGED
@@ -4,7 +4,7 @@ module Paperclip
|
|
4
4
|
# distribution. You can find out more about it at http://aws.amazon.com/s3
|
5
5
|
#
|
6
6
|
# To use Paperclip with S3, include the +aws-sdk+ gem in your Gemfile:
|
7
|
-
# gem 'aws-sdk'
|
7
|
+
# gem 'aws-sdk', '~> 1.6'
|
8
8
|
# There are a few S3-specific options for has_attached_file:
|
9
9
|
# * +s3_credentials+: Takes a path, a File, a Hash or a Proc. The path (or File) must point
|
10
10
|
# to a YAML file containing the +access_key_id+ and +secret_access_key+ that Amazon
|
@@ -288,8 +288,7 @@ module Paperclip
|
|
288
288
|
def parse_credentials creds
|
289
289
|
creds = creds.respond_to?('call') ? creds.call(self) : creds
|
290
290
|
creds = find_credentials(creds).stringify_keys
|
291
|
-
|
292
|
-
(creds[env] || creds).symbolize_keys
|
291
|
+
(creds[RailsEnvironment.get] || creds).symbolize_keys
|
293
292
|
end
|
294
293
|
|
295
294
|
def exists?(style = default_style)
|
data/lib/paperclip/thumbnail.rb
CHANGED
@@ -29,7 +29,6 @@ module Paperclip
|
|
29
29
|
super
|
30
30
|
|
31
31
|
geometry = options[:geometry].to_s
|
32
|
-
@file = file
|
33
32
|
@crop = geometry[-1,1] == '#'
|
34
33
|
@target_geometry = options.fetch(:string_geometry_parser, Geometry).parse(geometry)
|
35
34
|
@current_geometry = options.fetch(:file_geometry_parser, Geometry).from_file(@file)
|
@@ -64,8 +63,8 @@ module Paperclip
|
|
64
63
|
# that contains the new image.
|
65
64
|
def make
|
66
65
|
src = @file
|
67
|
-
|
68
|
-
dst.
|
66
|
+
filename = [@basename, @format ? ".#{@format}" : ""].join
|
67
|
+
dst = TempfileFactory.new.generate(filename)
|
69
68
|
|
70
69
|
begin
|
71
70
|
parameters = []
|
data/lib/paperclip/version.rb
CHANGED
data/lib/tasks/paperclip.rake
CHANGED
@@ -108,4 +108,20 @@ namespace :paperclip do
|
|
108
108
|
end
|
109
109
|
end
|
110
110
|
end
|
111
|
+
|
112
|
+
desc "find missing attachments. Useful to know which attachments are broken"
|
113
|
+
task :find_broken_attachments => :environment do
|
114
|
+
klass = Paperclip::Task.obtain_class
|
115
|
+
names = Paperclip::Task.obtain_attachments(klass)
|
116
|
+
names.each do |name|
|
117
|
+
Paperclip.each_instance_with_attachment(klass, name) do |instance|
|
118
|
+
attachment = instance.send(name)
|
119
|
+
if attachment.exists?
|
120
|
+
print "."
|
121
|
+
else
|
122
|
+
Paperclip::Task.log_error("#{instance.class}##{attachment.name}, #{instance.id}, #{attachment.url}")
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
111
127
|
end
|
data/paperclip.gemspec
CHANGED
@@ -12,8 +12,6 @@ Gem::Specification.new do |s|
|
|
12
12
|
s.description = "Easy upload management for ActiveRecord"
|
13
13
|
s.license = "MIT"
|
14
14
|
|
15
|
-
s.rubyforge_project = "paperclip"
|
16
|
-
|
17
15
|
s.files = `git ls-files`.split("\n")
|
18
16
|
s.test_files = `git ls-files -- {spec,features}/*`.split("\n")
|
19
17
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
@@ -26,6 +24,7 @@ Gem::Specification.new do |s|
|
|
26
24
|
s.add_dependency('activesupport', '>= 3.2.0')
|
27
25
|
s.add_dependency('cocaine', '~> 0.5.5')
|
28
26
|
s.add_dependency('mime-types')
|
27
|
+
s.add_dependency('mimemagic', '0.3.0')
|
29
28
|
|
30
29
|
s.add_development_dependency('activerecord', '>= 3.2.0')
|
31
30
|
s.add_development_dependency('shoulda')
|
@@ -34,11 +33,11 @@ Gem::Specification.new do |s|
|
|
34
33
|
s.add_development_dependency('mocha')
|
35
34
|
s.add_development_dependency('aws-sdk', '>= 1.5.7', "<= 2.0")
|
36
35
|
s.add_development_dependency('bourne')
|
37
|
-
s.add_development_dependency('cucumber', '~> 1.3.
|
36
|
+
s.add_development_dependency('cucumber', '~> 1.3.18')
|
38
37
|
s.add_development_dependency('aruba')
|
39
38
|
s.add_development_dependency('nokogiri')
|
40
39
|
# Ruby version < 1.9.3 can't install capybara > 2.0.3.
|
41
|
-
s.add_development_dependency('capybara'
|
40
|
+
s.add_development_dependency('capybara')
|
42
41
|
s.add_development_dependency('bundler')
|
43
42
|
s.add_development_dependency('fog', '~> 1.0')
|
44
43
|
s.add_development_dependency('launchy')
|