pax_global_header00006660000000000000000000000064123034717030014512gustar00rootroot0000000000000052 comment=3a0978a390de9d96d05e5befc40b2fabc27644cc carrierwave-0.10.0/000077500000000000000000000000001230347170300141025ustar00rootroot00000000000000carrierwave-0.10.0/.gitignore000066400000000000000000000003001230347170300160630ustar00rootroot00000000000000doc .yardoc .DS_Store spec/public pkg doc more/activerecord/spec/db more/activerecord/spec/public more/datamapper/spec/public *.project spec/test.log spec/tmp *.swp .rvmrc .bundle Gemfile.lockcarrierwave-0.10.0/.travis.yml000066400000000000000000000011411230347170300162100ustar00rootroot00000000000000before_install: - gem install bundler notifications: email: false rvm: - 1.9.3 - 2.0.0 - 2.1.1 - ruby-head gemfile: - Gemfile - gemfiles/rails-3-2-stable.gemfile - gemfiles/rails-4-0-stable.gemfile - gemfiles/rails-4-1-stable.gemfile - gemfiles/rails-master.gemfile matrix: allow_failures: - rvm: ruby-head include: - rvm: 1.8.7 gemfile: gemfiles/rails-3-2-stable.gemfile - rvm: 1.9.2 gemfile: gemfiles/rails-3-2-stable.gemfile - rvm: ree gemfile: gemfiles/rails-3-2-stable.gemfile before_script: - "mysql -e 'create database carrierwave_test;'" carrierwave-0.10.0/CONTRIBUTING.md000066400000000000000000000021711230347170300163340ustar00rootroot00000000000000# Contributing to CarrierWave CarrierWave thrives on a large number of [contributors](https://github.com/carrierwaveuploader/carrierwave/contributors), and pull requests are very welcome. Before submitting a pull request, please make sure that your changes are well tested. First, make sure you have `imagemagick` and `ghostscript` installed. Then, you'll need to install bundler and the gem dependencies: `gem install bundler && bundle install` You should now be able to run the local tests: `bundle exec rake` You can also run the remote specs by creating a ~/.fog file: ```yaml :carrierwave: :aws_access_key_id: xxx :aws_secret_access_key: yyy :rackspace_username: xxx :rackspace_api_key: yyy :google_storage_access_key_id: xxx :google_storage_secret_access_key: yyy ``` You should now be able to run the remote tests: REMOTE=true bundle exec rake Please test with the latest Ruby 1.8.x and 1.9.x versions using RVM if possible. ## Running active record tests Make sure you have a local MySQL database named `carrierwave_test` with the username `root` and empty password. carrierwave-0.10.0/Gemfile000066400000000000000000000000471230347170300153760ustar00rootroot00000000000000source "https://rubygems.org" gemspec carrierwave-0.10.0/History.txt000066400000000000000000000454471230347170300163220ustar00rootroot00000000000000=== Version 0.9.0 2013-07-06 * [BREAKING CHANGE] Use integer time (UTC) to generate cache IDs [@bensie] * [changed] Recommend using ActionController::Base.helpers instead of Sprockets::Rails::Helper for asset pipeline [@c0] * [changed] Lots of URL encoding fixes [@taavo] * [added] Added #version_exists? method [@tmaier] * [added] Added configuration param (:fog_use_ssl_for_aws) to disable SSL for public_url [@pbzymek] * [added] Add Dutch i18n translations for errors [@vanderhoorn] * [added] Add Czech i18n translations for errors [@elmariofredo] * [added] Add German i18n translations for errors [@felixbuenemann] * [fixed] Gemspec error in Ruby 2.0.0 [@sanemat] * [fixed] Fixed bug in serializing to xml or json where both :only and :except are passed [@Knack] * [fixed] Fix recreate_versions! when version if proc returns false [@arthurnn] === Version 0.8.0 2013-01-08 * [BREAKING CHANGE] Remove 'fog_endpoint' in favor of 'host' and/or 'endpoint' in fog_credentials [bensie] * [changed] Remove autoload in favor of standard 'require' to help with thread safety [bensie] * [added] Allow recreating only specified versions instead of all versions [div] * [added] Add support for S3-compliant storage APIs that are not actually S3 [neuhausler] * [added] Add #extension CarrierWave::Storage::Fog::File for fetching a file extension [sweatypitts] * [fixed] Marshaling uploader objects no longer raises a TypeError on anonymous classes [bensie] === Version 0.7.1 2012-11-08 * [added] add a override to allow fog configuration per uploader [dei79] * [fixed] Fix a regression when removing uploads [mattolson] === Version 0.7.0 2012-10-19 * [BREAKING CHANGE] Rename 'fog_host' config option to 'asset_host' and add support for file storage [DouweM] * [changed] Changed after_destroy with after_commit ... :on => :destroy [Cristian Sorinel] * [changed] Do not handle any special cases for URL handling, keep the existing escape/unescape functionality and allow overriding [bensie] * [changed] Activerecord-deprecated_finders gem was renamed [bensie] * [changed] Removed unnecessary present? method from ActiveSupport [Yauheni Kryudziuk] * [changed] Use AWS S3 subdomain URL when directory name contains a period. [DouweM] * [added] Added resize_to_geometry_string RMagick method that will scale image [wprater] * [added] Made feature to blacklist certain extensions [thiagofm] * [added] Parse and pass fog_host option to ::Fog::Storage [Yauheni Kryudziuk] * [added] Add serialization spec for multiple uploaders. [malclocke] * [added] Add :read option to manipulate! [xtreme-tanzeeb-khalili] * [added] Add binary/octet-stream as generic mime type. [phiggins] * [added] Add 'fog_endpoint' config option to set an alternate Fog host. [DouweM] * [fixed] Fixed can't convert File into String [jnimety] * [fixed] Fixed an issue when parsing URL w/o schema. [Yauheni Kryudziuk] * [fixed] Fix reference to column in serializable_hash [malclocke] * [fixed] Fix inconsistence in file API [oelmekki] === Version 0.6.2 2012-04-12 * [fixed] Don't double-generate cache_id [skyeagle] * [added] Escape plus signs (+) in remote URLs [adrianpike] * [added] Enhance multi-page PDF support in RMagick [xtreme-tanzeeb-khalili] === Version 0.6.1 2012-04-02 * [fixed] Calling #serializable_hash with no options [matthewrudy] === Version 0.6.0 2012-03-27 * [BREAKING CHANGE] Require Rails 3.2 or Rails master (4.0) - depends on activesupport/activemodel [bensie] * [BREAKING CHANGE] Remove :S3 storage option in favor of Fog [bensie] * [BREAKING CHANGE] Remove :CloudFiles storage option in favor of Fog [bensie] * [changed] JSON / XML serialization hashes are consistent and work as expected with ActiveRecord's serializable_hash [bensie] * [added] fog_host now accepts a proc (useful for dynamic asset servers) [jtrim] * [added] Add ability to process a version from an existing version so you aren't always crunching the original, large file [ferblape] * [added] Allow brackets in remote URLs [ngauthier] * [added] CarrierWave::Storage::Fog::File#exists? to check the existence of the file without needing to fetch it [bensie] * [added] Gravity option on resize_to_fill (minimagick) [TheOddLinguist] * [added] Add query options for s3 to support response headers overwriting [meceo] * [added] Make storages File#url methods to work without any params [meceo] * [added] Set the CarrierWave.root correctly if Padrino is defined [futurechimp] * [added] Cache fog connections for improved performance [labocho] * [fixed] Fix slow fetching of content-length on remote file [geemus] * [fixed] Fog remote specs now passing and depend on Fog >= 1.3.1 [geemus] * [fixed] Fix an issue where multi-page PDFs can't be converted with RMagick [chanind] * [fixed] MiniMagick expects string args to mogrify commands [bensie] * [fixed] With Active Record ORM, setting remote_url marks mounted column as dirty [trevorturk] * [fixed] Fix possible race condition with CarrierWave.root [bensie] * [fixed] ActiveSupport::Memoizable deprecation warning [slbug] === Version 0.5.8 2011-11-10 * [added] Allow custom error messages [bartt] * [added] Add config.base_path to use as a prefix for uploader URLs [die-antwort] * [added] Support fog streaming uploads [chrisdurtschi] * [added] Support `move_to` in addition to the default `copy_to` when using the cache [jasonydes] * [fixed] Support for Sinatra 1.3 (with backward compatibility) [bensie] * [fixed] Fog get_object_url deprecated, use get_object_https_url or get_object_http_url [scottmessinger] === Version 0.5.7 2011-08-12 * [BREAKING CHANGE] Extracted Mongoid support into a separate gem (carrierwave-mongoid) [jnicklas] * [BREAKING CHANGE] Remove ImageScience support due to lack maintenance and 1.9.2 compatibility [jnicklas] * [BREAKING CHANGE] Combine delete_tmp_file_after_storage and delete_cache_id_after_storage options [bensie] * [changed] Cached and then remote-uploaded file will no longer have a content_type, please use CarrierWave::MimeTypes processor instead [trevorturk] * [changed] Allow writing over a previously assigned file when retrieving a remote file [Florent2] * [fixed] Fixed exception when nested or double-embedded Mongoid documents are saved [potatosalad] * [fixed] Fixed that store! can call process! twice [gzigzigzeo] * [fixed] Performance enhancements by reducing use of rescue [jamescook] === Version 0.5.6 2011-07-12 * [fixed] Remove cache file and directories after storing [scottwb] * [fixed] Add missing active_support/deprecation require [trevorturk] * [fixed] Remove redundant requires of sequel and datamapper [solnic] * [fixed] Running tests with REMOTE=true [geemus] === Version 0.5.5 2011-07-09 * [BREAKING CHANGE] Extracted DataMapper support into a separate gem (carrierwave-datamapper) [jnicklas] * [BREAKING CHANGE] Extracted Sequel support into a separate gem (carrierwave-sequel) [jnicklas] * [changed] Don't downcase filenames by default [bensie] * [changed] Orm mount modules default uploader to nil [jnicklas] * [changed] Remove alias_method :blank? from SanitizedFile to for performance re: issue #298 [trevorturk] * [added] Conditional processing of versions [gucki] * [added] Remove Remove previously stored files after Active Record mounted uploader update re: issue #75 [trevorturk] * [added] Remove Remove previously stored files after Mongoid mounted uploader update re: issue #75 [did] * [added] Added _identifier to retrieve identifier/filename [jnicklas] * [added] clean_cached_files! clears all files older than 24 hours by default, but time frame can now be customized [cover] * [added] Versions now implement an enable_processing method which uses the parent when no value is set [mariovisic] * [added] Delete cache_id garbage dirs, closes GH issue #338 [clyfe] * [added] Added CarrierWave::MimeTypes processor for more advanced content-type guessing [JangoSteve] * [fixed] Active Record's will_change! method works when mount_on option is used [indrekj] * [fixed] Fixed problem with accepting URL uploads when the URL was already escaped [cover] * [fixed] Fixed ability to override sanitize_regexp [trevorturk] * [fixed] Fix that cached and then remote-uploaded file should have content_type [trevorturk] * [fixed] Fix validates_size/length_of in Rails 3.0.6 and above, closes #342 [bensie] * [fixed] Various Active Support compatibility updates [slbug, bensie, et al] === Version 0.5.4 2011-05-18 * [changed] Fog: Performance enhancements for AWS and Google [geemus] * [changed] Fog: Try to use subdomain public url on s3 [geemus] * [changed] Memoize CarrierWave::Mounter#option for increased performance [ingemar] * [changed] Relax development gem dependency versions where possible and fix tests [trevorturk] * [changed] Upgrade to RSpec 2 [jnicklas] === Version 0.5.3 2011-03-22 * [changed] Cloud Files storage so delete and url return nil if object not found instead of exception [minter] * [added] New fog storage provider that supports Amazon S3, Rackspace Cloud Files, and Google Storare for Developers [geemus] * [added] cloud_files_auth_url and cloud_files_snet config options for Cloud Files [minter] * [added] process_uri method that can be overridden in your uploader to support downloads from non-standard urls [trevorturk] * [added] version urls to json output [karb] * [added] Active Record marks uploader column as changed when changed [josevalim] * [fixed] Cloud Files storage tests to use the new url format [minter] * [fixed] Moved raising of FormNotMultipart farther down to avoid errors with nested attribute forms [trevorturk] * [fixed] original_filename of remote uploads should be calculated from final (possibly redirected) URL [brady8] * [fixed] Fix calling :process! on files stored in remote solutions [alexcrichton] * [fixed] Fix paperclip compatibility mappings [valakirka] * [fixed] Ensure temporary files can be deleted on Windows [Eleo] === Version 0.5.2 2011-02-18 * [changed] Require active_support/core_ext/string/multibyte to fix downcasing unicode filenames during sanitizing [nashbridges] * [changed] Use fog ~> 0.4, Fog::AWS::Storage.new -> Fog::Storage.new(:provider => 'AWS') [trevorturk] * [changed] Use class_attribute (inheritable attributes are deprecated) [stephencelis] * [changed] extension_white_list no longer supports a single string, only an array of strings and/or Regexps [trevorturk] * [changed] Rackspace Cloud Files: only create container if container does not exist [edmundsalvacion] * [changed] GridFS: the path returned is no longer always nil, it is now the path in GridFS [alexcrichton] * [added] Ability to specify a Regexp in the extension_white_list [lwe] * [added] CarrierWave::SanitizedFile#sanitize_regexp public method to allow customizing [trevorturk] * [added] sanitize_regexp documentation to the README [nashbridges] * [added] Ability to use https for Amazon S3 URLs if config.s3_use_ssl is true [recruitmilitary] * [added] The s3_region configuration documentation to the README [mrsimo] * [fixed] Reprocessing remotely stored files [alexcrichton] * [fixed] Nested versioning processing [alexcrichton] * [fixed] An intermittent bug with ImageScience resize_to_fill method [LupineDev] * [fixed] DataMapper#save should remove the avatar if remove_avatar? returns true [wprater] === Version 0.5.1 2010-12-01 * [changed] s3_access renamed to s3_access_policy [Jonas Nicklas] * [changed] Depend on activesupport ~> 3.0 for Rails 3.1 compatibility [Trevor Turk] * [changed] Use fog >= 0.3.0, fix deprecation warnings [Paul Elliott] * [changed] Use mini_magick ~> 2.3, MiniMagick::Image.from_file becomes MiniMagick::Image.open [Fredrik Björk] * [changed] Convert generic MiniMagick::Invalid into ProcessingError [Alex Crichton] * [changed] Remove cached tmp file after storing for file store [Damien Mathieu] * [added] s3_region config option to set AWS S3 region [Roger Campos] * [added] Option to retain cached tmp file after storage (delete_tmp_file_after_storage) [Damien Mathieu] * [added] Transparent support for authenticated_read on S3 [Jonas Nicklas] * [fixed] Clean up internal require statements [Josh Kalderimis] * [fixed] Header support for S3 [Alex Crichton] * [fixed] Stack level too deep errors when using to_json [Trevor Turk] * [fixed] Documentation for mount_uploader [Nathan Kleyn] === Version 0.5 2010-09-23 * [changed] Use ActiveModel instead of ActiveRecord validations to support Mongoid validations as well [Jeroen van Dijk, saberma] * [changed] Support S3 file storage with the fog gem, instead of the aws gem (Trevor Turk) * [changed] Move translations to a YAML file (Josh Kalderimis) * [changed] Depend on activesupport ~> 3.0.0 instead of >= 3.0.0.rc (Trevor Turk) * [changed] Remove old Merb and Rails generators, support Rails 3 generators (Jonas Nicklas, Trevor Turk) * [changed] Replace Net::HTTP with open-url for remote file downloads (icebreaker) * [changed] Move translations to a YAML file (Josh Kalderimis) * [changed] Use gemspec to generate Gemfile contents (Jonas Nicklas) * [added] Add file size support for S3 storage (Pavel Chipiga) * [added] Add option for disabling multipart form check (Dennis Blöte) * [fixed] Correct naming of validators (Josh Kalderimis) * [fixed] Fix remote file downloader (Jonas Nicklas) * [fixed] Escape URLs passed to remote file downloader so URLs with spaces work (Mauricio Zaffari) * [fixed] Correct filename used in generators (Fred Wu) === Version 0.4.6 2010-07-20 * [removed] Support for MongoMapper, see: http://groups.google.com/group/carrierwave/browse_thread/thread/56df146b83878c22 * [changed] AWS support now uses the aws gem, instead of using aws-s3 or right-aws as previously * [added] cloud_files_cdn_host for Cloudfiles for performance gain * [added] #recreate_versions! to recreate versions from base file * [added] Support for MiniMagick in RSpec matchers * [added] RMagick's #resize_to_fill now takes an optional Gravity * [fixed] Pass through options to to_json * [fixed] Support new GridFS syntax for lates mongo gem * [fixed] Validation errors are internationalized when the error is thrown, not on load * [fixed] Rescue correct MiniMagick error * [fixed] Support DataMapper 1.0 * [fixed] SanitizedFile#copy_to preserves content_type. Should fix GridFS content type not being set. === Version 0.4.5 2010-02-20 * [added] Support for Rackspace Cloudfiles * [added] GridFS now accepts a port * [fixed] s3_headers is now properly initialized * [fixed] work around DataMapper's patching of core method === Version 0.4.4 2010-01-31 * [added] Support for downloading remote files * [added] CarrierWave.clean_cached_files! to remove old cached files * [added] Option to set headers for S3 * [added] GridStore now has authentication * [fixed] Rmagick convert method now does what it says * [fixed] Content type is stored on GridStore and Amazon S3 * [fixed] Metadata is no longer broken for S3 === Version 0.4.3 2009-12-19 * [fixed] cnamed URLs on S3 no longer have a third slash after http * [fixed] fixed deprecation warnings on Rails 2.3.5 === Version 0.4.2 2009-11-26 * [added] RightAWS as an alternative S3 implementation * [added] An option to enable/disable processing for tests * [added] Mongoid ORM support * [fixed] DataMapper now works both with and without dm-validations === Version 0.4.1 2009-10-26 * [changed] Major changes to the ImageScience module, it actually works now! * [fixed] Bug in configuration where it complais that it can't dup Symbol * [removed] Support for Sequel < 2.12 * [removed] `crop_resized` and `resize` aliases in RMagick, use `resize_to_fill` and `resize_to_fit` respectively === Version 0.4.0 2009-10-12 * [changed] the `public` option has been renamed `root` and the old `root` option was removed. No more ambiguity. * [changed] Major *breaking* changes to the configuration syntax. * [removed] support for `default_path` * [removed] the `cache_to_cache_dir` option * [removed] storage no longer calls `setup!` on storage engines * [added] Support for MongoDB's GridFS store === Version 0.3.4 2009-09-01 * [added] `default_url` as a replacement for `default_path` * [deprecated] `default_path` is deprecated === Version 0.3.4 2009-08-31 * [fixed] Deleting no longer causes TypeError in MongoMapper === Version 0.3.3 2009-08-29 * [added] Support for MongoMapper * [added] Support for CNamed Bucket URLs for Amazon S3 === Version 0.3.2 2009-07-18 Incremental upgrade * [added] Ruby 1.9 compatibility * [changed] Added Object#blank? implementation into CarrierWave, which removes any dpendencies on external libraries (extlib/activesupport) * [fixed] Performance issues with S3 support * [fixed] Sequel support for newer verions of Sequel (thanks Pavel!) === Version 0.3.1 2009-07-01 A bugfix release. Drop in compatible with 0.3.0. * [fixed] Saving a record with a mounted Uploader no longer removes uploaded file * [fixed] The file returned by S3 storage now has the path set to the full store path * [added] File returned by S3 storage now responds to S3 specific methods === 0.3 2009-06-20 This is a stabilization release. Most features are now working as expected and most bugs should be fixed. * [changed] Reworked how storage engines work, some internal API changes * [added] Macro-like methods for RMagick, no need to call #process any more! * [added] Ability to super to any Mount method * [fixed] Sequel support should now work as expected * [fixed] ActiveRecord no longer saves the record twice * [added] Added convenient macro style class methods to rmagick processing === 0.2.4 2009-06-11 * [added] `resize_to_limit` method for rmagick * [added] Now deletes files from Amazon S3 when record is destroyed === 0.2.3 2009-05-13 * [changed] Mount now no longer returns nil if there is no stored file, it returns a blank uploader instead * [added] Possibility to specify a default path * [added] Paperclip compatibility module === 0.2.1 2009-05-01 * [changed] Url method now optionally takes versions as parameters (like Paperclip) * [added] A field which allows files to be removed with a checkbox in mount * [added] Mount_on option for Mount, to be able to override the serialization column * [added] Added demeter friendly column_url method to Mount * [added] Option to not copy files to cache dir, to prevent writes on read only fs systems (this is a workaround and needs a better solution) === 0.2 2009-04-15 * [changed] The version is no longer stored in the store dir. This will break the paths for files uploaded with 0.1 * [changed] CarrierWave::Uploader is now a module, not a class, so you need to include it, not inherit from it. * [added] integrity checking in uploaders via a white list of extensions * [added] Validations for integrity and processing in ActiveRecord, activated by default * [added] Support for nested versions * [added] Permissions option to set the permissions of the uploaded files * [added] Support for Sequel * [added] CarrierWave::Uploader#read to read the contents of the uploaded files === 0.1 2009-03-12 This is a very experimental release that has not been well tested. All of the major features are in place though. Please note that there currently is a bug with load paths in Merb, which means you need to manually require uploaders. carrierwave-0.10.0/README.md000066400000000000000000000633711230347170300153730ustar00rootroot00000000000000# CarrierWave This gem provides a simple and extremely flexible way to upload files from Ruby applications. It works well with Rack based web applications, such as Ruby on Rails. [![Build Status](https://travis-ci.org/carrierwaveuploader/carrierwave.png?branch=master)](http://travis-ci.org/carrierwaveuploader/carrierwave) [![Code Climate](https://codeclimate.com/github/carrierwaveuploader/carrierwave.png)](https://codeclimate.com/github/carrierwaveuploader/carrierwave) ## Information * RDoc documentation [available on RubyDoc.info](http://rubydoc.info/gems/carrierwave/frames) * Source code [available on GitHub](http://github.com/carrierwaveuploader/carrierwave) * More information, known limitations, and how-tos [available on the wiki](https://github.com/carrierwaveuploader/carrierwave/wiki) ## Getting Help * Please ask the [Google Group](http://groups.google.com/group/carrierwave) for help if you have any questions. * Please report bugs on the [issue tracker](http://github.com/carrierwaveuploader/carrierwave/issues) but read the "getting help" section in the wiki first. ## Installation Install the latest stable release: [sudo] gem install carrierwave In Rails, add it to your Gemfile: ```ruby gem 'carrierwave' ``` Finally, restart the server to apply the changes. Note that CarrierWave is not compatible with Rails 2 as of version 0.5. If you want to use Rails 2, please use the 0.4-stable branch on GitHub. ## Getting Started Start off by generating an uploader: rails generate uploader Avatar this should give you a file in: app/uploaders/avatar_uploader.rb Check out this file for some hints on how you can customize your uploader. It should look something like this: ```ruby class AvatarUploader < CarrierWave::Uploader::Base storage :file end ``` You can use your uploader class to store and retrieve files like this: ```ruby uploader = AvatarUploader.new uploader.store!(my_file) uploader.retrieve_from_store!('my_file.png') ``` CarrierWave gives you a `store` for permanent storage, and a `cache` for temporary storage. You can use different stores, including filesystem and cloud storage. Most of the time you are going to want to use CarrierWave together with an ORM. It is quite simple to mount uploaders on columns in your model, so you can simply assign files and get going: ### ActiveRecord Make sure you are loading CarrierWave after loading your ORM, otherwise you'll need to require the relevant extension manually, e.g.: ```ruby require 'carrierwave/orm/activerecord' ``` Add a string column to the model you want to mount the uploader by creating a migration: rails g migration add_avatar_to_users avatar:string rake db:migrate Open your model file and mount the uploader: ```ruby class User < ActiveRecord::Base mount_uploader :avatar, AvatarUploader end ``` Now you can cache files by assigning them to the attribute, they will automatically be stored when the record is saved. ```ruby u = User.new u.avatar = params[:file] u.avatar = File.open('somewhere') u.save! u.avatar.url # => '/url/to/file.png' u.avatar.current_path # => 'path/to/file.png' u.avatar.identifier # => 'file.png' ``` ### DataMapper, Mongoid, Sequel Other ORM support has been extracted into separate gems: * [carrierwave-datamapper](https://github.com/carrierwaveuploader/carrierwave-datamapper) * [carrierwave-mongoid](https://github.com/carrierwaveuploader/carrierwave-mongoid) * [carrierwave-sequel](https://github.com/jnicklas/carrierwave-sequel) There are more extensions listed in [the wiki](https://github.com/carrierwaveuploader/carrierwave/wiki) ## Changing the storage directory In order to change where uploaded files are put, just override the `store_dir` method: ```ruby class MyUploader < CarrierWave::Uploader::Base def store_dir 'public/my/upload/directory' end end ``` This works for the file storage as well as Amazon S3 and Rackspace Cloud Files. Define `store_dir` as `nil` if you'd like to store files at the root level. If you store files outside the project root folder, you may want to define `cache_dir` in the same way: ```ruby class MyUploader < CarrierWave::Uploader::Base def cache_dir '/tmp/projectname-cache' end end ``` ## Securing uploads Certain file might be dangerous if uploaded to the wrong location, such as php files or other script files. CarrierWave allows you to specify a white-list of allowed extensions. If you're mounting the uploader, uploading a file with the wrong extension will make the record invalid instead. Otherwise, an error is raised. ```ruby class MyUploader < CarrierWave::Uploader::Base def extension_white_list %w(jpg jpeg gif png) end end ``` ### Filenames and unicode chars Another security issue you should care for is the file names (see [Ruby On Rails Security Guide](http://guides.rubyonrails.org/security.html#file-uploads)). By default, CarrierWave provides only English letters, arabic numerals and '-+_.' symbols as white-listed characters in the file name. If you want to support local scripts (Cyrillic letters, letters with diacritics and so on), you have to override `sanitize_regexp` method. It should return regular expression which would match all *non*-allowed symbols. With Ruby 1.9 and higher you can simply write (as it has [Oniguruma](http://oniguruma.rubyforge.org/oniguruma/) built-in): ```ruby CarrierWave::SanitizedFile.sanitize_regexp = /[^[:word:]\.\-\+]/ ``` With Ruby 1.8 you have to manually specify all character ranges. For example, for files which may contain Russian letters: ```ruby CarrierWave::SanitizedFile.sanitize_regexp = /[^a-zA-Zа-яА-ЯёЁ0-9\.\-\+_]/u ``` Also make sure that allowing non-latin characters won't cause a compatibility issue with a third-party plugins or client-side software. ## Setting the content type If you care about the content type of your files and notice that it's not being set as expected, you can configure your uploaders to use `CarrierWave::MimeTypes`. This adds a dependency on the [mime-types](http://rubygems.org/gems/mime-types) gem, but is recommended when using fog, and fog already has a dependency on mime-types. ```ruby require 'carrierwave/processing/mime_types' class MyUploader < CarrierWave::Uploader::Base include CarrierWave::MimeTypes process :set_content_type end ``` ## Adding versions Often you'll want to add different versions of the same file. The classic example is image thumbnails. There is built in support for this*: *Note: You must have Imagemagick and MiniMagick installed to do image resizing. MiniMagick is a Ruby interface for Imagemagick which is a C program. This is why MiniMagick fails on 'bundle install' without Imagemagick installed. Some documentation refers to RMagick instead of MiniMagick but MiniMagick is recommended. To install Imagemagick on OSX with homebrew type the following: ``` $ brew install imagemagick ``` ```ruby class MyUploader < CarrierWave::Uploader::Base include CarrierWave::MiniMagick process :resize_to_fit => [800, 800] version :thumb do process :resize_to_fill => [200,200] end end ``` When this uploader is used, an uploaded image would be scaled to be no larger than 800 by 800 pixels. A version called thumb is then created, which is scaled and cropped to exactly 200 by 200 pixels. The uploader could be used like this: ```ruby uploader = AvatarUploader.new uploader.store!(my_file) # size: 1024x768 uploader.url # => '/url/to/my_file.png' # size: 800x600 uploader.thumb.url # => '/url/to/thumb_my_file.png' # size: 200x200 ``` One important thing to remember is that process is called *before* versions are created. This can cut down on processing cost. It is possible to nest versions within versions: ```ruby class MyUploader < CarrierWave::Uploader::Base version :animal do version :human version :monkey version :llama end end ``` ### Conditional versions Occasionally you want to restrict the creation of versions on certain properties within the model or based on the picture itself. ```ruby class MyUploader < CarrierWave::Uploader::Base version :human, :if => :is_human? version :monkey, :if => :is_monkey? version :banner, :if => :is_landscape? protected def is_human? picture model.can_program?(:ruby) end def is_monkey? picture model.favorite_food == 'banana' end def is_landscape? picture image = MiniMagick::Image.open(picture.path) image[:width] > image[:height] end end ``` The `model` variable points to the instance object the uploader is attached to. ### Create versions from existing versions For performance reasons, it is often useful to create versions from existing ones instead of using the original file. If your uploader generates several versions where the next is smaller than the last, it will take less time to generate from a smaller, already processed image. ```ruby class MyUploader < CarrierWave::Uploader::Base version :thumb do process resize_to_fill: [280, 280] end version :small_thumb, :from_version => :thumb do process resize_to_fill: [20, 20] end end ``` The option `:from_version` uses the file cached in the `:thumb` version instead of the original version, potentially resulting in faster processing. ## Making uploads work across form redisplays Often you'll notice that uploaded files disappear when a validation fails. CarrierWave has a feature that makes it easy to remember the uploaded file even in that case. Suppose your `user` model has an uploader mounted on `avatar` file, just add a hidden field called `avatar_cache` (don't forget to add it to the attr_accessible list as necessary). In Rails, this would look like this: ```erb <%= form_for @user, :html => {:multipart => true} do |f| %>

<%= f.file_field :avatar %> <%= f.hidden_field :avatar_cache %>

<% end %> ```` It might be a good idea to show the user that a file has been uploaded, in the case of images, a small thumbnail would be a good indicator: ```erb <%= form_for @user, :html => {:multipart => true} do |f| %>

<%= image_tag(@user.avatar_url) if @user.avatar? %> <%= f.file_field :avatar %> <%= f.hidden_field :avatar_cache %>

<% end %> ``` ## Removing uploaded files If you want to remove a previously uploaded file on a mounted uploader, you can easily add a checkbox to the form which will remove the file when checked. ```erb <%= form_for @user, :html => {:multipart => true} do |f| %>

<%= image_tag(@user.avatar_url) if @user.avatar? %> <%= f.file_field :avatar %>

<% end %> ``` If you want to remove the file manually, you can call remove_avatar!, then save the object. ```erb @user.remove_avatar! @user.save #=> true ``` ## Uploading files from a remote location Your users may find it convenient to upload a file from a location on the Internet via a URL. CarrierWave makes this simple, just add the appropriate attribute to your form and you're good to go: ```erb <%= form_for @user, :html => {:multipart => true} do |f| %>

<%= image_tag(@user.avatar_url) if @user.avatar? %> <%= f.text_field :remote_avatar_url %>

<% end %> ``` If you're using ActiveRecord, CarrierWave will indicate invalid URLs and download failures automatically with attribute validation errors. If you aren't, or you disable CarrierWave's `validate_download` option, you'll need to handle those errors yourself. ## Providing a default URL In many cases, especially when working with images, it might be a good idea to provide a default url, a fallback in case no file has been uploaded. You can do this easily by overriding the `default_url` method in your uploader: ```ruby class MyUploader < CarrierWave::Uploader::Base def default_url "/images/fallback/" + [version_name, "default.png"].compact.join('_') end end ``` Or if you are using the Rails asset pipeline: ```ruby class MyUploader < CarrierWave::Uploader::Base def default_url ActionController::Base.helpers.asset_path("fallback/" + [version_name, "default.png"].compact.join('_')) end end ``` ## Recreating versions You might come to a situation where you want to retroactively change a version or add a new one. You can use the `recreate_versions!` method to recreate the versions from the base file. This uses a naive approach which will re-upload and process the specified version or all versions, if none is passed as an argument. When you are generating random unique filenames you have to call `save!` on the model after using `recreate_versions!`. This is necessary because `recreate_versions!` doesn't save the new filename to the database. Calling `save!` yourself will prevent that the database and file system are running out of sync. ```ruby instance = MyUploader.new instance.recreate_versions!(:thumb, :large) ``` Or on a mounted uploader: ```ruby User.all.each do |user| user.avatar.recreate_versions! end ``` Note: `recreate_versions!` will throw an exception on records without an image. To avoid this, scope the records to those with images or check if an image exists within the block. If you're using ActiveRecord, recreating versions for a user avatar might look like this: ```ruby User.all.each do |user| user.avatar.recreate_versions! if user.avatar? end ``` ## Configuring CarrierWave CarrierWave has a broad range of configuration options, which you can configure, both globally and on a per-uploader basis: ```ruby CarrierWave.configure do |config| config.permissions = 0666 config.directory_permissions = 0777 config.storage = :file end ``` Or alternatively: ```ruby class AvatarUploader < CarrierWave::Uploader::Base permissions 0777 end ``` If you're using Rails, create an initializer for this: config/initializers/carrierwave.rb ## Testing with CarrierWave It's a good idea to test your uploaders in isolation. In order to speed up your tests, it's recommended to switch off processing in your tests, and to use the file storage. In Rails you could do that by adding an initializer with: ```ruby if Rails.env.test? or Rails.env.cucumber? CarrierWave.configure do |config| config.storage = :file config.enable_processing = false end end ``` Remember, if you have already set `storage :something` in your uploader, the `storage` setting from this initializer will be ignored. If you need to test your processing, you should test it in isolation, and enable processing only for those tests that need it. CarrierWave comes with some RSpec matchers which you may find useful: ```ruby require 'carrierwave/test/matchers' describe MyUploader do include CarrierWave::Test::Matchers before do MyUploader.enable_processing = true @uploader = MyUploader.new(@user, :avatar) @uploader.store!(File.open(path_to_file)) end after do MyUploader.enable_processing = false @uploader.remove! end context 'the thumb version' do it "should scale down a landscape image to be exactly 64 by 64 pixels" do @uploader.thumb.should have_dimensions(64, 64) end end context 'the small version' do it "should scale down a landscape image to fit within 200 by 200 pixels" do @uploader.small.should be_no_larger_than(200, 200) end end it "should make the image readable only to the owner and not executable" do @uploader.should have_permissions(0600) end end ``` Setting the enable_processing flag on an uploader will prevent any of the versions from processing as well. Processing can be enabled for a single version by setting the processing flag on the version like so: ```ruby @uploader.thumb.enable_processing = true ``` ## Using Amazon S3 [Fog](http://github.com/fog/fog) is used to support Amazon S3. Ensure you have it in your Gemfile: ```ruby gem "fog", "~> 1.3.1" ``` You'll need to provide your fog_credentials and a fog_directory (also known as a bucket) in an initializer. For the sake of performance it is assumed that the directory already exists, so please create it if need be. You can also pass in additional options, as documented fully in lib/carrierwave/storage/fog.rb. Here's a full example: ```ruby CarrierWave.configure do |config| config.fog_credentials = { :provider => 'AWS', # required :aws_access_key_id => 'xxx', # required :aws_secret_access_key => 'yyy', # required :region => 'eu-west-1', # optional, defaults to 'us-east-1' :host => 's3.example.com', # optional, defaults to nil :endpoint => 'https://s3.example.com:8080' # optional, defaults to nil } config.fog_directory = 'name_of_directory' # required config.fog_public = false # optional, defaults to true config.fog_attributes = {'Cache-Control'=>'max-age=315576000'} # optional, defaults to {} end ``` In your uploader, set the storage to :fog ```ruby class AvatarUploader < CarrierWave::Uploader::Base storage :fog end ``` That's it! You can still use the `CarrierWave::Uploader#url` method to return the url to the file on Amazon S3. ## Using Rackspace Cloud Files [Fog](http://github.com/fog/fog) is used to support Rackspace Cloud Files. Ensure you have it in your Gemfile: ```ruby gem "fog" ``` You'll need to configure a directory (also known as a container), username and API key in the initializer. For the sake of performance it is assumed that the directory already exists, so please create it if need be. Using a US-based account: ```ruby CarrierWave.configure do |config| config.fog_credentials = { :provider => 'Rackspace', :rackspace_username => 'xxxxxx', :rackspace_api_key => 'yyyyyy', :rackspace_region => :ord # optional, defaults to :dfw } config.fog_directory = 'name_of_directory' end ``` Using a UK-based account: ```ruby CarrierWave.configure do |config| config.fog_credentials = { :provider => 'Rackspace', :rackspace_username => 'xxxxxx', :rackspace_api_key => 'yyyyyy', :rackspace_auth_url => Fog::Rackspace::UK_AUTH_ENDPOINT, :rackspace_region => :lon } config.fog_directory = 'name_of_directory' end ``` You can optionally include your CDN host name in the configuration. This is *highly* recommended, as without it every request requires a lookup of this information. ```ruby config.asset_host = "http://c000000.cdn.rackspacecloud.com" ``` In your uploader, set the storage to :fog ```ruby class AvatarUploader < CarrierWave::Uploader::Base storage :fog end ``` That's it! You can still use the `CarrierWave::Uploader#url` method to return the url to the file on Rackspace Cloud Files. ## Using Google Storage for Developers [Fog](http://github.com/fog/fog) is used to support Google Storage for Developers. Ensure you have it in your Gemfile: ```ruby gem "fog" ``` You'll need to configure a directory (also known as a bucket), access key id and secret access key in the initializer. For the sake of performance it is assumed that the directory already exists, so please create it if need be. Sign up [here](http://gs-signup-redirect.appspot.com/) and get your credentials [here](https://storage.cloud.google.com/m) under the section “Interoperable Access”. ```ruby CarrierWave.configure do |config| config.fog_credentials = { :provider => 'Google', :google_storage_access_key_id => 'xxxxxx', :google_storage_secret_access_key => 'yyyyyy' } config.fog_directory = 'name_of_directory' end ``` In your uploader, set the storage to :fog ```ruby class AvatarUploader < CarrierWave::Uploader::Base storage :fog end ``` That's it! You can still use the `CarrierWave::Uploader#url` method to return the url to the file on Google. ## Dynamic Asset Host The `asset_host` config property can be assigned a proc (or anything that responds to `call`) for generating the host dynamically. The proc-compliant object gets an instance of the current `CarrierWave::Storage::Fog::File` or `CarrierWave::SanitizedFile` as its only argument. ```ruby CarrierWave.configure do |config| config.asset_host = proc do |file| identifier = # some logic "http://#{identifier}.cdn.rackspacecloud.com" end end ``` ## Using RMagick If you're uploading images, you'll probably want to manipulate them in some way, you might want to create thumbnail images for example. CarrierWave comes with a small library to make manipulating images with RMagick easier, you'll need to include it in your Uploader: ```ruby class AvatarUploader < CarrierWave::Uploader::Base include CarrierWave::RMagick end ``` The RMagick module gives you a few methods, like `CarrierWave::RMagick#resize_to_fill` which manipulate the image file in some way. You can set a `process` callback, which will call that method any time a file is uploaded. There is a demonstration of convert here. Convert will only work if the file has the same file extension, thus the use of the filename method. ```ruby class AvatarUploader < CarrierWave::Uploader::Base include CarrierWave::RMagick process :resize_to_fill => [200, 200] process :convert => 'png' def filename super.chomp(File.extname(super)) + '.png' if original_filename.present? end end ``` Check out the manipulate! method, which makes it easy for you to write your own manipulation methods. ## Using MiniMagick MiniMagick is similar to RMagick but performs all the operations using the 'mogrify' command which is part of the standard ImageMagick kit. This allows you to have the power of ImageMagick without having to worry about installing all the RMagick libraries. See the MiniMagick site for more details: https://github.com/minimagick/minimagick And the ImageMagick command line options for more for whats on offer: http://www.imagemagick.org/script/command-line-options.php Currently, the MiniMagick carrierwave processor provides exactly the same methods as for the RMagick processor. ```ruby class AvatarUploader < CarrierWave::Uploader::Base include CarrierWave::MiniMagick process :resize_to_fill => [200, 200] end ``` ## Migrating from Paperclip If you are using Paperclip, you can use the provided compatibility module: ```ruby class AvatarUploader < CarrierWave::Uploader::Base include CarrierWave::Compatibility::Paperclip end ``` See the documentation for `CarrierWave::Compatibility::Paperclip` for more details. Be sure to use mount_on to specify the correct column: ```ruby mount_uploader :avatar, AvatarUploader, :mount_on => :avatar_file_name ``` Unfortunately attachment_fu differs too much in philosophy for there to be a sensible compatibility mode. Patches for migrating from other solutions will be happily accepted. ## i18n The Active Record validations use the Rails i18n framework. Add these keys to your translations file: ```yaml errors: messages: carrierwave_processing_error: "Cannot resize image." carrierwave_integrity_error: "Not an image." carrierwave_download_error: "Couldn't download image." extension_white_list_error: "You are not allowed to upload %{extension} files, allowed types: %{allowed_types}" extension_black_list_error: "You are not allowed to upload %{extension} files, prohibited types: %{prohibited_types}" ``` ## Large files By default, CarrierWave copies an uploaded file twice, first copying the file into the cache, then copying the file into the store. For large files, this can be prohibitively time consuming. You may change this behavior by overriding either or both of the `move_to_cache` and `move_to_store` methods: ```ruby class MyUploader < CarrierWave::Uploader::Base def move_to_cache true end def move_to_store true end end ``` When the `move_to_cache` and/or `move_to_store` methods return true, files will be moved (instead of copied) to the cache and store respectively. This has only been tested with the local filesystem store. ## Skipping ActiveRecord callbacks By default, mounting an uploader into an ActiveRecord model will add a few callbacks. For example, this code: ```ruby class User mount_uploader :avatar, AvatarUploader end ``` Will add these callbacks: ```ruby after_save :store_avatar! before_save :write_avatar_identifier after_commit :remove_avatar! :on => :destroy before_update :store_previous_model_for_avatar after_save :remove_previously_stored_avatar ``` If you want to skip any of these callbacks (eg. you want to keep the existing avatar, even after uploading a new one), you can use ActiveRecord’s `skip_callback` method. ```ruby class User mount_uploader :avatar, AvatarUploader skip_callback :save, :after, :remove_previously_stored_avatar end ``` ## Contributing to CarrierWave See [CONTRIBUTING.md](https://github.com/carrierwaveuploader/carrierwave/blob/master/CONTRIBUTING.md) ## License Copyright (c) 2008-2013 Jonas Nicklas Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. carrierwave-0.10.0/Rakefile000066400000000000000000000011341230347170300155460ustar00rootroot00000000000000# encoding: UTF-8 require 'rubygems' begin require 'bundler/setup' rescue LoadError puts 'You must `gem install bundler` and `bundle install` to run rake tasks' end require 'bundler' Bundler::GemHelper.install_tasks require 'rake' require 'rspec/core/rake_task' require 'cucumber' require 'cucumber/rake/task' desc "Run all examples" RSpec::Core::RakeTask.new(:spec) do |t| t.rspec_opts = %w[--color] end desc "Run cucumber features" Cucumber::Rake::Task.new(:features) do |t| t.cucumber_opts = "features --format progress" end task :default => [:spec, :features] carrierwave-0.10.0/carrierwave.gemspec000066400000000000000000000031321230347170300177600ustar00rootroot00000000000000# -*- encoding: utf-8 -*- lib = File.expand_path('../lib/', __FILE__) $:.unshift lib unless $:.include?(lib) require 'carrierwave/version' require 'date' Gem::Specification.new do |s| s.name = "carrierwave" s.version = CarrierWave::VERSION s.authors = ["Jonas Nicklas"] s.date = Date.today s.description = "Upload files in your Ruby applications, map them to a range of ORMs, store them on different backends." s.summary = "Ruby file upload library" s.email = ["jonas.nicklas@gmail.com"] s.extra_rdoc_files = ["README.md"] s.files = Dir.glob("{bin,lib}/**/*") + %w(README.md) s.homepage = %q{https://github.com/carrierwaveuploader/carrierwave} s.rdoc_options = ["--main"] s.require_paths = ["lib"] s.rubyforge_project = %q{carrierwave} s.rubygems_version = %q{1.3.5} s.specification_version = 3 s.licenses = ["MIT"] s.add_dependency "activesupport", ">= 3.2.0" s.add_dependency "activemodel", ">= 3.2.0" s.add_dependency "json", ">= 1.7" s.add_dependency "mime-types", ">= 1.16" s.add_development_dependency "mysql2" s.add_development_dependency "rails", ">= 3.2.0" s.add_development_dependency "cucumber", "~> 1.3.2" s.add_development_dependency "rspec", "~> 2.13.0" s.add_development_dependency "sham_rack" s.add_development_dependency "fog", ">= 1.3.1" s.add_development_dependency "mini_magick", ">= 3.6.0" s.add_development_dependency "rmagick" s.add_development_dependency "nokogiri", "~> 1.5.10" # 1.6 requires ruby > 1.8.7 s.add_development_dependency "timecop", "0.6.1" # 0.6.2 requires ruby > 1.8.7 s.add_development_dependency "generator_spec" end carrierwave-0.10.0/cucumber.yml000066400000000000000000000001141230347170300164260ustar00rootroot00000000000000default: --format pretty --no-source html: --format html --out features.htmlcarrierwave-0.10.0/features/000077500000000000000000000000001230347170300157205ustar00rootroot00000000000000carrierwave-0.10.0/features/caching.feature000066400000000000000000000032121230347170300206670ustar00rootroot00000000000000Feature: uploader with file storage In order to be able to temporarily store files to disk As a developer using CarrierWave I want to cache files Scenario: cache a file Given an uploader class that uses the 'file' storage And an instance of that class When I cache the file 'fixtures/bork.txt' Then there should be a file called 'bork.txt' somewhere in a subdirectory of 'public/uploads/tmp' And the file called 'bork.txt' in a subdirectory of 'public/uploads/tmp' should be identical to the file at 'fixtures/bork.txt' Scenario: cache two files in succession Given an uploader class that uses the 'file' storage And an instance of that class When I cache the file 'fixtures/bork.txt' Then there should be a file called 'bork.txt' somewhere in a subdirectory of 'public/uploads/tmp' And the file called 'bork.txt' in a subdirectory of 'public/uploads/tmp' should be identical to the file at 'fixtures/bork.txt' When I cache the file 'fixtures/monkey.txt' Then there should be a file called 'monkey.txt' somewhere in a subdirectory of 'public/uploads/tmp' And the file called 'monkey.txt' in a subdirectory of 'public/uploads/tmp' should be identical to the file at 'fixtures/monkey.txt' Scenario: retrieving a file from cache Given an uploader class that uses the 'file' storage And an instance of that class And the file 'fixtures/bork.txt' is cached file at 'public/uploads/tmp/1369894322-345-2255/bork.txt' When I retrieve the cache name '1369894322-345-2255/bork.txt' from the cache Then the uploader should have 'public/uploads/tmp/1369894322-345-2255/bork.txt' as its current path carrierwave-0.10.0/features/download.feature000066400000000000000000000015671230347170300211150ustar00rootroot00000000000000Feature: downloading files In order to allow users to upload remote files As a developer using CarrierWave I want to download files to the filesystem via HTTP Background: Given an uploader class that uses the 'file' storage And an instance of that class Scenario: download a file When I download the file 'http://s3.amazonaws.com/Monkey/testfile.txt' Then there should be a file called 'testfile.txt' somewhere in a subdirectory of 'public/uploads/tmp' And the file called 'testfile.txt' in a subdirectory of 'public/uploads/tmp' should contain 'S3 Remote File' Scenario: downloading a file then storing When I download the file 'http://s3.amazonaws.com/Monkey/testfile.txt' And I store the file Then there should be a file at 'public/uploads/testfile.txt' And the file at 'public/uploads/testfile.txt' should contain 'S3 Remote File' carrierwave-0.10.0/features/file_storage.feature000066400000000000000000000036401230347170300217430ustar00rootroot00000000000000Feature: uploader with file storage In order to be awesome As a developer using CarrierWave I want to upload files to the filesystem Background: Given an uploader class that uses the 'file' storage And an instance of that class Scenario: store a file When I store the file 'fixtures/bork.txt' Then there should be a file at 'public/uploads/bork.txt' And the file at 'public/uploads/bork.txt' should be identical to the file at 'fixtures/bork.txt' Scenario: store two files in succession When I store the file 'fixtures/bork.txt' Then there should be a file at 'public/uploads/bork.txt' And the file at 'public/uploads/bork.txt' should be identical to the file at 'fixtures/bork.txt' When I store the file 'fixtures/monkey.txt' Then there should be a file at 'public/uploads/monkey.txt' And the file at 'public/uploads/monkey.txt' should be identical to the file at 'fixtures/monkey.txt' Scenario: cache a file and then store it When I cache the file 'fixtures/bork.txt' Then there should be a file called 'bork.txt' somewhere in a subdirectory of 'public/uploads/tmp' And the file called 'bork.txt' in a subdirectory of 'public/uploads/tmp' should be identical to the file at 'fixtures/bork.txt' And there should not be a file at 'public/uploads/bork.txt' When I store the file Then there should be a file at 'public/uploads/bork.txt' And the file at 'public/uploads/bork.txt' should be identical to the file at 'fixtures/bork.txt' Scenario: retrieving a file from cache then storing Given the file 'fixtures/bork.txt' is cached file at 'public/uploads/tmp/1369894322-345-2255/bork.txt' When I retrieve the cache name '1369894322-345-2255/bork.txt' from the cache And I store the file Then there should be a file at 'public/uploads/bork.txt' And the file at 'public/uploads/bork.txt' should be identical to the file at 'fixtures/bork.txt' carrierwave-0.10.0/features/file_storage_overridden_filename.feature000066400000000000000000000040021230347170300260150ustar00rootroot00000000000000Feature: uploader with file storage and overriden filename In order to be awesome As a developer using CarrierWave I want to upload files to the filesystem with an overriden filename Background: Given an uploader class that uses the 'file' storage And that the uploader reverses the filename And an instance of that class Scenario: store a file When I store the file 'fixtures/bork.txt' Then there should be a file at 'public/uploads/txt.krob' And the file at 'public/uploads/txt.krob' should be identical to the file at 'fixtures/bork.txt' Scenario: store two files in succession When I store the file 'fixtures/bork.txt' Then there should be a file at 'public/uploads/txt.krob' And the file at 'public/uploads/txt.krob' should be identical to the file at 'fixtures/bork.txt' When I store the file 'fixtures/monkey.txt' Then there should be a file at 'public/uploads/txt.yeknom' And the file at 'public/uploads/txt.yeknom' should be identical to the file at 'fixtures/monkey.txt' Scenario: cache a file and then store it When I cache the file 'fixtures/bork.txt' Then there should be a file called 'bork.txt' somewhere in a subdirectory of 'public/uploads/tmp' And the file called 'bork.txt' in a subdirectory of 'public/uploads/tmp' should be identical to the file at 'fixtures/bork.txt' And there should not be a file at 'public/uploads/txt.krob' When I store the file Then there should be a file at 'public/uploads/txt.krob' And the file at 'public/uploads/txt.krob' should be identical to the file at 'fixtures/bork.txt' Scenario: retrieving a file from cache then storing Given the file 'fixtures/bork.txt' is cached file at 'public/uploads/tmp/1369894322-345-2255/bork.txt' When I retrieve the cache name '1369894322-345-2255/bork.txt' from the cache And I store the file Then there should be a file at 'public/uploads/txt.krob' And the file at 'public/uploads/txt.krob' should be identical to the file at 'fixtures/bork.txt' carrierwave-0.10.0/features/file_storage_overridden_store_dir.feature000066400000000000000000000041001230347170300262260ustar00rootroot00000000000000Feature: uploader with file storage and overridden store dir In order to be awesome As a developer using CarrierWave I want to upload files to the filesystem Background: Given an uploader class that uses the 'file' storage And that the uploader has the store_dir overridden to 'public/monkey/llama' And an instance of that class Scenario: store a file When I store the file 'fixtures/bork.txt' Then there should be a file at 'public/monkey/llama/bork.txt' And the file at 'public/monkey/llama/bork.txt' should be identical to the file at 'fixtures/bork.txt' Scenario: store two files in succession When I store the file 'fixtures/bork.txt' Then there should be a file at 'public/monkey/llama/bork.txt' And the file at 'public/monkey/llama/bork.txt' should be identical to the file at 'fixtures/bork.txt' When I store the file 'fixtures/monkey.txt' Then there should be a file at 'public/monkey/llama/monkey.txt' And the file at 'public/monkey/llama/monkey.txt' should be identical to the file at 'fixtures/monkey.txt' Scenario: cache a file and then store it When I cache the file 'fixtures/bork.txt' Then there should be a file called 'bork.txt' somewhere in a subdirectory of 'public/uploads/tmp' And the file called 'bork.txt' in a subdirectory of 'public/uploads/tmp' should be identical to the file at 'fixtures/bork.txt' And there should not be a file at 'public/monkey/llama/bork.txt' When I store the file Then there should be a file at 'public/monkey/llama/bork.txt' And the file at 'public/monkey/llama/bork.txt' should be identical to the file at 'fixtures/bork.txt' Scenario: retrieving a file from cache then storing Given the file 'fixtures/bork.txt' is cached file at 'public/uploads/tmp/1369894322-345-2255/bork.txt' When I retrieve the cache name '1369894322-345-2255/bork.txt' from the cache And I store the file Then there should be a file at 'public/monkey/llama/bork.txt' And the file at 'public/monkey/llama/bork.txt' should be identical to the file at 'fixtures/bork.txt' carrierwave-0.10.0/features/file_storage_reversing_processor.feature000066400000000000000000000047711230347170300261340ustar00rootroot00000000000000Feature: uploader with file storage and a processor that reverses the file In order to be awesome As a developer using CarrierWave I want to upload files to the filesystem Background: Given an uploader class that uses the 'file' storage And an instance of that class And the class has a method called 'reverse' that reverses the contents of a file And the class will process 'reverse' Scenario: store a file When I store the file 'fixtures/bork.txt' Then there should be a file at 'public/uploads/bork.txt' And the file at 'public/uploads/bork.txt' should not be identical to the file at 'fixtures/bork.txt' And the file at 'public/uploads/bork.txt' should be the reverse of the file at 'fixtures/bork.txt' Scenario: store two files in succession When I store the file 'fixtures/bork.txt' Then there should be a file at 'public/uploads/bork.txt' And the file at 'public/uploads/bork.txt' should not be identical to the file at 'fixtures/bork.txt' And the file at 'public/uploads/bork.txt' should be the reverse of the file at 'fixtures/bork.txt' When I store the file 'fixtures/monkey.txt' Then there should be a file at 'public/uploads/monkey.txt' And the file at 'public/uploads/monkey.txt' should not be identical to the file at 'fixtures/monkey.txt' And the file at 'public/uploads/monkey.txt' should be the reverse of the file at 'fixtures/monkey.txt' Scenario: cache a file and then store it When I cache the file 'fixtures/bork.txt' Then there should be a file called 'bork.txt' somewhere in a subdirectory of 'public/uploads/tmp' And the file called 'bork.txt' in a subdirectory of 'public/uploads/tmp' should not be identical to the file at 'fixtures/bork.txt' And there should not be a file at 'public/uploads/bork.txt' When I store the file Then there should be a file at 'public/uploads/bork.txt' And the file at 'public/uploads/bork.txt' should not be identical to the file at 'fixtures/bork.txt' And the file at 'public/uploads/bork.txt' should be the reverse of the file at 'fixtures/bork.txt' Scenario: retrieving a file from cache then storing Given the file 'fixtures/bork.txt' is cached file at 'public/uploads/tmp/1369894322-345-2255/bork.txt' When I retrieve the cache name '1369894322-345-2255/bork.txt' from the cache And I store the file Then there should be a file at 'public/uploads/bork.txt' And the file at 'public/uploads/bork.txt' should be identical to the file at 'fixtures/bork.txt' carrierwave-0.10.0/features/fixtures/000077500000000000000000000000001230347170300175715ustar00rootroot00000000000000carrierwave-0.10.0/features/fixtures/bork.txt000066400000000000000000000000161230347170300212640ustar00rootroot00000000000000this is a filecarrierwave-0.10.0/features/fixtures/monkey.txt000066400000000000000000000000241230347170300216300ustar00rootroot00000000000000this is another filecarrierwave-0.10.0/features/fixtures/upcased_bork.txt000066400000000000000000000000161230347170300227700ustar00rootroot00000000000000THIS IS A FILEcarrierwave-0.10.0/features/mount_activerecord.feature000066400000000000000000000045301230347170300231730ustar00rootroot00000000000000Feature: Mount an Uploader on ActiveRecord class In order to easily attach files to a form As a web developer using CarrierWave I want to mount an uploader on an ActiveRecord class Background: Given an uploader class that uses the 'file' storage And an activerecord class that uses the 'users' table And the uploader class is mounted on the 'avatar' column And an instance of the activerecord class Scenario: assign a file When I assign the file 'fixtures/bork.txt' to the 'avatar' column Then there should be a file called 'bork.txt' somewhere in a subdirectory of 'public/uploads/tmp' And the file called 'bork.txt' in a subdirectory of 'public/uploads/tmp' should be identical to the file at 'fixtures/bork.txt' Scenario: assign a file and save the record When I assign the file 'fixtures/bork.txt' to the 'avatar' column And I save the active record Then there should be a file at 'public/uploads/bork.txt' And the file at 'public/uploads/bork.txt' should be identical to the file at 'fixtures/bork.txt' And the url for the column 'avatar' should be '/uploads/bork.txt' Scenario: assign a file and retrieve it from cache When I assign the file 'fixtures/bork.txt' to the 'avatar' column And I retrieve the file later from the cache name for the column 'avatar' And I save the active record Then there should be a file at 'public/uploads/bork.txt' And the file at 'public/uploads/bork.txt' should be identical to the file at 'fixtures/bork.txt' And the url for the column 'avatar' should be '/uploads/bork.txt' Scenario: store a file and retrieve it later When I assign the file 'fixtures/bork.txt' to the 'avatar' column And I retrieve the file later from the cache name for the column 'avatar' And I save the active record Then there should be a file at 'public/uploads/bork.txt' When I reload the active record Then the url for the column 'avatar' should be '/uploads/bork.txt' Scenario: store a file and delete the record When I assign the file 'fixtures/bork.txt' to the 'avatar' column And I retrieve the file later from the cache name for the column 'avatar' And I save the active record Then there should be a file at 'public/uploads/bork.txt' When I delete the active record Then there should not be a file at 'public/uploads/bork.txt' carrierwave-0.10.0/features/step_definitions/000077500000000000000000000000001230347170300212665ustar00rootroot00000000000000carrierwave-0.10.0/features/step_definitions/activerecord_steps.rb000066400000000000000000000007451230347170300255110ustar00rootroot00000000000000# encoding: utf-8 Given /^an activerecord class that uses the '([^\']*)' table$/ do |name| @mountee_klass = Class.new(ActiveRecord::Base) @mountee_klass.table_name = name end Given /^an instance of the activerecord class$/ do @instance = @mountee_klass.new end When /^I save the active record$/ do @instance.save! end When /^I reload the active record$/ do @instance = @instance.class.find(@instance.id) end When /^I delete the active record$/ do @instance.destroy endcarrierwave-0.10.0/features/step_definitions/caching_steps.rb000066400000000000000000000006251230347170300244300ustar00rootroot00000000000000# encoding: utf-8 Given /^the file '(.*?)' is cached file at '(.*?)'$/ do |file, cached| FileUtils.mkdir_p(File.dirname(file_path(cached))) FileUtils.cp(file_path(file), file_path(cached)) end When /^I cache the file '(.*?)'$/ do |file| @uploader.cache!(File.open(file_path(file))) end When /^I retrieve the cache name '(.*?)' from the cache$/ do |name| @uploader.retrieve_from_cache!(name) endcarrierwave-0.10.0/features/step_definitions/datamapper_steps.rb000066400000000000000000000011761230347170300251540ustar00rootroot00000000000000# encoding: utf-8 Given /^a datamapper class that has a '([^\']*)' column$/ do |column| @mountee_klass = Class.new do include DataMapper::Resource storage_names[:default] = 'users' property :id, DataMapper::Types::Serial property column.to_sym, String end @mountee_klass.auto_migrate! end Given /^an instance of the datamapper class$/ do @instance = @mountee_klass.new end When /^I save the datamapper record$/ do @instance.save end When /^I reload the datamapper record$/ do @instance = @instance.class.first(:id => @instance.key) end When /^I delete the datamapper record$/ do @instance.destroy end carrierwave-0.10.0/features/step_definitions/download_steps.rb000066400000000000000000000005141230347170300246400ustar00rootroot00000000000000When /^I download the file '([^']+)'/ do |url| unless ENV['REMOTE'] == 'true' sham_rack_app = ShamRack.at('s3.amazonaws.com').stub sham_rack_app.register_resource('/Monkey/testfile.txt', 'S3 Remote File', 'text/plain') end @uploader.download!(url) unless ENV['REMOTE'] == 'true' ShamRack.unmount_all end end carrierwave-0.10.0/features/step_definitions/file_steps.rb000066400000000000000000000034461230347170300237570ustar00rootroot00000000000000# encoding: utf-8 ### # EXISTENCE Then /^there should be a file at '(.*?)'$/ do |file| File.exist?(file_path(file)).should be_true end Then /^there should not be a file at '(.*?)'$/ do |file| File.exist?(file_path(file)).should be_false end Then /^there should be a file called '(.*?)' somewhere in a subdirectory of '(.*?)'$/ do |file, directory| Dir.glob(File.join(file_path(directory), '**', file)).any?.should be_true end ### # IDENTICAL Then /^the file at '(.*?)' should be identical to the file at '(.*?)'$/ do |one, two| File.read(file_path(one)).should == File.read(file_path(two)) end Then /^the file at '(.*?)' should not be identical to the file at '(.*?)'$/ do |one, two| File.read(file_path(one)).should_not == File.read(file_path(two)) end Then /^the file called '(.*?)' in a subdirectory of '(.*?)' should be identical to the file at '(.*?)'$/ do |file, directory, other| File.read(Dir.glob(File.join(file_path(directory), '**', file)).first).should == File.read(file_path(other)) end Then /^the file called '(.*?)' in a subdirectory of '(.*?)' should not be identical to the file at '(.*?)'$/ do |file, directory, other| File.read(Dir.glob(File.join(file_path(directory), '**', file)).first).should_not == File.read(file_path(other)) end ### # CONTENT Then /^the file called '([^']+)' in a subdirectory of '([^']+)' should contain '([^']+)'$/ do |file, directory, content| File.read(Dir.glob(File.join(file_path(directory), '**', file)).first).should include(content) end Then /^the file at '([^']+)' should contain '([^']+)'$/ do |path, content| File.read(file_path(path)).should include(content) end ### # REVERSING Then /^the file at '(.*?)' should be the reverse of the file at '(.*?)'$/ do |one, two| File.read(file_path(one)).should == File.read(file_path(two)).reverse end carrierwave-0.10.0/features/step_definitions/general_steps.rb000066400000000000000000000054701230347170300244540ustar00rootroot00000000000000# encoding: utf-8 Given /^an uploader class that uses the '(.*?)' storage$/ do |kind| @klass = Class.new(CarrierWave::Uploader::Base) @klass.storage = kind.to_sym end Given /^an instance of that class$/ do @uploader = @klass.new end Given /^a processor method named :upcase$/ do @klass.class_eval do define_method(:upcase) do content = File.read(current_path) File.open(current_path, 'w') { |f| f.write content.upcase } end end end Then /^the contents of the file should be '(.*?)'$/ do |contents| @uploader.read.chomp.should == contents end Given /^that the uploader reverses the filename$/ do @klass.class_eval do def filename super.reverse unless super.blank? end end end Given /^that the uploader has the filename overridden to '(.*?)'$/ do |filename| @klass.class_eval do define_method(:filename) do filename end end end Given /^that the uploader has the store_dir overridden to '(.*?)'$/ do |store_dir| @klass.class_eval do define_method(:store_dir) do file_path(store_dir) end end end Given /^that the version '(.*?)' has the store_dir overridden to '(.*?)'$/ do |version, store_dir| @klass.versions[version.to_sym][:uploader].class_eval do define_method(:store_dir) do file_path(store_dir) end end end Given /^that the uploader class has a version named '([^\']+)'$/ do |name| @klass.version(name) end Given /^that the uploader class has a version named '([^\']+)' which process '([a-zA-Z0-9\_\?!]*)'$/ do |name, processor_name| @klass.version(name) do process processor_name.to_sym end end Given /^that the uploader class has a version named '([^\']+)' which is based on version '(.*?)'$/ do |name, based_version_name| @klass.version(name, {:from_version => based_version_name.to_sym}) end Given /^yo dawg, I put a version called '(.*?)' in your version called '(.*?)'$/ do |v2, v1| @klass.version(v1) do version(v2) end end Given /^the class has a method called 'reverse' that reverses the contents of a file$/ do @klass.class_eval do def reverse text = File.read(current_path) File.open(current_path, 'w') { |f| f.write(text.reverse) } end end end Given /^the class will process '([a-zA-Z0-9\_\?!]*)'$/ do |name| @klass.process name.to_sym end Then /^the uploader should have '(.*?)' as its current path$/ do |path| @uploader.current_path.should == file_path(path) end Then /^the uploader should have the url '(.*?)'$/ do |url| @uploader.url.should == url end Then /^the uploader's version '(.*?)' should have the url '(.*?)'$/ do |version, url| @uploader.versions[version.to_sym].url.should == url end Then /^the uploader's nested version '(.*?)' nested in '(.*?)' should have the url '(.*?)'$/ do |v2, v1, url| @uploader.versions[v1.to_sym].versions[v2.to_sym].url.should == url end carrierwave-0.10.0/features/step_definitions/mount_steps.rb000066400000000000000000000012361230347170300241750ustar00rootroot00000000000000# encoding: utf-8 When /^I assign the file '([^\']*)' to the '([^\']*)' column$/ do |path, column| @instance.send("#{column}=", File.open(file_path(path))) end Given /^the uploader class is mounted on the '([^\']*)' column$/ do |column| @mountee_klass.mount_uploader column.to_sym, @klass end When /^I retrieve the file later from the cache name for the column '([^\']*)'$/ do |column| new_instance = @instance.class.new new_instance.send("#{column}_cache=", @instance.send("#{column}_cache")) @instance = new_instance end Then /^the url for the column '([^\']*)' should be '([^\']*)'$/ do |column, url| @instance.send("#{column}_url").should == url end carrierwave-0.10.0/features/step_definitions/store_steps.rb000066400000000000000000000007141230347170300241670ustar00rootroot00000000000000# encoding: utf-8 Given /^the file '(.*?)' is stored at '(.*?)'$/ do |file, stored| FileUtils.mkdir_p(File.dirname(file_path(stored))) FileUtils.cp(file_path(file), file_path(stored)) end When /^I store the file$/ do @uploader.store! end When /^I store the file '(.*?)'$/ do |file| @uploader.store!(File.open(file_path(file))) end When /^I retrieve the file '(.*?)' from the store$/ do |identifier| @uploader.retrieve_from_store!(identifier) end carrierwave-0.10.0/features/support/000077500000000000000000000000001230347170300174345ustar00rootroot00000000000000carrierwave-0.10.0/features/support/activerecord.rb000066400000000000000000000005651230347170300224410ustar00rootroot00000000000000# encoding: utf-8 require 'carrierwave/mount' require File.join(File.dirname(__FILE__), '..', '..', 'spec', 'support', 'activerecord') class TestMigration < ActiveRecord::Migration def self.up create_table :users, :force => true do |t| t.column :avatar, :string end end def self.down drop_table :users end end Before do TestMigration.up end carrierwave-0.10.0/features/support/env.rb000066400000000000000000000006611230347170300205540ustar00rootroot00000000000000# encoding: utf-8 $:.unshift File.expand_path(File.join('..', '..', 'lib'), File.dirname(__FILE__)) require File.join(File.dirname(__FILE__), 'activerecord') require 'rspec' require 'carrierwave' require 'sham_rack' alias :running :lambda def file_path( *paths ) File.expand_path(File.join('..', *paths), File.dirname(__FILE__)) end CarrierWave.root = file_path('public') After do FileUtils.rm_rf(file_path("public")) end carrierwave-0.10.0/features/versions_basics.feature000066400000000000000000000060021230347170300224670ustar00rootroot00000000000000Feature: uploader with file storage and versions In order to be awesome As a developer using CarrierWave I want to upload files to the filesystem Background: Given an uploader class that uses the 'file' storage And that the uploader class has a version named 'thumb' And an instance of that class Scenario: store a file When I store the file 'fixtures/bork.txt' Then there should be a file at 'public/uploads/bork.txt' Then there should be a file at 'public/uploads/thumb_bork.txt' And the file at 'public/uploads/bork.txt' should be identical to the file at 'fixtures/bork.txt' And the file at 'public/uploads/thumb_bork.txt' should be identical to the file at 'fixtures/bork.txt' And the uploader should have the url '/uploads/bork.txt' And the uploader's version 'thumb' should have the url '/uploads/thumb_bork.txt' Scenario: cache a file and then store it When I cache the file 'fixtures/bork.txt' Then there should be a file called 'bork.txt' somewhere in a subdirectory of 'public/uploads/tmp' Then there should be a file called 'thumb_bork.txt' somewhere in a subdirectory of 'public/uploads/tmp' And the file called 'bork.txt' in a subdirectory of 'public/uploads/tmp' should be identical to the file at 'fixtures/bork.txt' And there should not be a file at 'public/uploads/bork.txt' And there should not be a file at 'public/uploads/thumb_bork.txt' When I store the file Then there should be a file at 'public/uploads/bork.txt' And there should be a file at 'public/uploads/thumb_bork.txt' And the file at 'public/uploads/bork.txt' should be identical to the file at 'fixtures/bork.txt' And the file at 'public/uploads/thumb_bork.txt' should be identical to the file at 'fixtures/bork.txt' And the uploader should have the url '/uploads/bork.txt' And the uploader's version 'thumb' should have the url '/uploads/thumb_bork.txt' Scenario: retrieving a file from cache then storing Given the file 'fixtures/bork.txt' is cached file at 'public/uploads/tmp/1369894322-345-2255/bork.txt' Given the file 'fixtures/monkey.txt' is cached file at 'public/uploads/tmp/1369894322-345-2255/thumb_bork.txt' When I retrieve the cache name '1369894322-345-2255/bork.txt' from the cache And I store the file Then there should be a file at 'public/uploads/bork.txt' Then there should be a file at 'public/uploads/thumb_bork.txt' And the file at 'public/uploads/bork.txt' should be identical to the file at 'fixtures/bork.txt' And the file at 'public/uploads/thumb_bork.txt' should be identical to the file at 'fixtures/monkey.txt' Scenario: retrieving a file from store Given the file 'fixtures/bork.txt' is stored at 'public/uploads/bork.txt' Given the file 'fixtures/monkey.txt' is stored at 'public/uploads/thumb_bork.txt' When I retrieve the file 'bork.txt' from the store Then the uploader should have the url '/uploads/bork.txt' And the uploader's version 'thumb' should have the url '/uploads/thumb_bork.txt' carrierwave-0.10.0/features/versions_caching_from_versions.feature000066400000000000000000000044351230347170300256020ustar00rootroot00000000000000Feature: uploader with file storage and versions with overridden store dir In order to be awesome As a developer using CarrierWave I want to upload files to the filesystem Background: Given an uploader class that uses the 'file' storage Given a processor method named :upcase And that the uploader class has a version named 'thumb' which process 'upcase' And that the version 'thumb' has the store_dir overridden to 'public/monkey/llama' And that the uploader class has a version named 'small_thumb' which is based on version 'thumb' And that the version 'small_thumb' has the store_dir overridden to 'public/monkey/toro' And an instance of that class Scenario: cache a file and then store it When I cache the file 'fixtures/bork.txt' Then there should be a file called 'bork.txt' somewhere in a subdirectory of 'public/uploads/tmp' Then there should be a file called 'thumb_bork.txt' somewhere in a subdirectory of 'public/uploads/tmp' Then there should be a file called 'small_thumb_bork.txt' somewhere in a subdirectory of 'public/uploads/tmp' And the file called 'bork.txt' in a subdirectory of 'public/uploads/tmp' should be identical to the file at 'fixtures/bork.txt' And the file called 'thumb_bork.txt' in a subdirectory of 'public/uploads/tmp' should be identical to the file at 'fixtures/upcased_bork.txt' And the file called 'small_thumb_bork.txt' in a subdirectory of 'public/uploads/tmp' should be identical to the file at 'fixtures/upcased_bork.txt' And there should not be a file at 'public/uploads/bork.txt' And there should not be a file at 'public/monkey/llama/thumb_bork.txt' And there should not be a file at 'public/monkey/toro/small_thumb_bork.txt' When I store the file Then there should be a file at 'public/uploads/bork.txt' Then there should be a file at 'public/monkey/llama/thumb_bork.txt' Then there should be a file at 'public/monkey/toro/small_thumb_bork.txt' And the file at 'public/uploads/bork.txt' should be identical to the file at 'fixtures/bork.txt' And the file at 'public/monkey/llama/thumb_bork.txt' should be identical to the file at 'fixtures/upcased_bork.txt' And the file at 'public/monkey/toro/small_thumb_bork.txt' should be identical to the file at 'fixtures/upcased_bork.txt'carrierwave-0.10.0/features/versions_nested_versions.feature000066400000000000000000000117411230347170300244430ustar00rootroot00000000000000Feature: uploader with nested versions In order to optimize performance for processing As a developer using CarrierWave I want to set nested versions Background: Given an uploader class that uses the 'file' storage And that the uploader class has a version named 'thumb' And yo dawg, I put a version called 'mini' in your version called 'thumb' And yo dawg, I put a version called 'micro' in your version called 'thumb' And an instance of that class Scenario: store a file When I store the file 'fixtures/bork.txt' Then there should be a file at 'public/uploads/bork.txt' Then there should be a file at 'public/uploads/thumb_bork.txt' Then there should be a file at 'public/uploads/thumb_mini_bork.txt' Then there should be a file at 'public/uploads/thumb_micro_bork.txt' And the file at 'public/uploads/bork.txt' should be identical to the file at 'fixtures/bork.txt' And the file at 'public/uploads/thumb_bork.txt' should be identical to the file at 'fixtures/bork.txt' And the file at 'public/uploads/thumb_mini_bork.txt' should be identical to the file at 'fixtures/bork.txt' And the file at 'public/uploads/thumb_micro_bork.txt' should be identical to the file at 'fixtures/bork.txt' And the uploader should have the url '/uploads/bork.txt' And the uploader's version 'thumb' should have the url '/uploads/thumb_bork.txt' And the uploader's nested version 'mini' nested in 'thumb' should have the url '/uploads/thumb_mini_bork.txt' And the uploader's nested version 'micro' nested in 'thumb' should have the url '/uploads/thumb_micro_bork.txt' Scenario: cache a file and then store it When I cache the file 'fixtures/bork.txt' Then there should be a file called 'bork.txt' somewhere in a subdirectory of 'public/uploads/tmp' Then there should be a file called 'thumb_bork.txt' somewhere in a subdirectory of 'public/uploads/tmp' And the file called 'bork.txt' in a subdirectory of 'public/uploads/tmp' should be identical to the file at 'fixtures/bork.txt' And there should not be a file at 'public/uploads/bork.txt' And there should not be a file at 'public/uploads/thumb_bork.txt' When I store the file Then there should be a file at 'public/uploads/bork.txt' And there should be a file at 'public/uploads/thumb_bork.txt' And the file at 'public/uploads/bork.txt' should be identical to the file at 'fixtures/bork.txt' And the file at 'public/uploads/thumb_bork.txt' should be identical to the file at 'fixtures/bork.txt' And the uploader should have the url '/uploads/bork.txt' And the uploader's version 'thumb' should have the url '/uploads/thumb_bork.txt' And the uploader's nested version 'mini' nested in 'thumb' should have the url '/uploads/thumb_mini_bork.txt' And the uploader's nested version 'micro' nested in 'thumb' should have the url '/uploads/thumb_micro_bork.txt' Scenario: retrieving a file from cache then storing Given the file 'fixtures/bork.txt' is cached file at 'public/uploads/tmp/1369894322-345-2255/bork.txt' Given the file 'fixtures/monkey.txt' is cached file at 'public/uploads/tmp/1369894322-345-2255/thumb_bork.txt' Given the file 'fixtures/bork.txt' is cached file at 'public/uploads/tmp/1369894322-345-2255/thumb_mini_bork.txt' Given the file 'fixtures/monkey.txt' is cached file at 'public/uploads/tmp/1369894322-345-2255/thumb_micro_bork.txt' When I retrieve the cache name '1369894322-345-2255/bork.txt' from the cache And I store the file Then there should be a file at 'public/uploads/bork.txt' Then there should be a file at 'public/uploads/thumb_bork.txt' Then there should be a file at 'public/uploads/thumb_mini_bork.txt' Then there should be a file at 'public/uploads/thumb_micro_bork.txt' And the file at 'public/uploads/bork.txt' should be identical to the file at 'fixtures/bork.txt' And the file at 'public/uploads/thumb_bork.txt' should be identical to the file at 'fixtures/monkey.txt' And the file at 'public/uploads/thumb_mini_bork.txt' should be identical to the file at 'fixtures/bork.txt' And the file at 'public/uploads/thumb_micro_bork.txt' should be identical to the file at 'fixtures/monkey.txt' Scenario: retrieving a file from store Given the file 'fixtures/bork.txt' is stored at 'public/uploads/bork.txt' Given the file 'fixtures/monkey.txt' is stored at 'public/uploads/thumb_bork.txt' Given the file 'fixtures/monkey.txt' is stored at 'public/uploads/thumb_mini_bork.txt' Given the file 'fixtures/monkey.txt' is stored at 'public/uploads/thumb_micro_bork.txt' When I retrieve the file 'bork.txt' from the store Then the uploader should have the url '/uploads/bork.txt' And the uploader's version 'thumb' should have the url '/uploads/thumb_bork.txt' And the uploader's nested version 'mini' nested in 'thumb' should have the url '/uploads/thumb_mini_bork.txt' And the uploader's nested version 'micro' nested in 'thumb' should have the url '/uploads/thumb_micro_bork.txt' carrierwave-0.10.0/features/versions_overridden_filename.feature000066400000000000000000000063741230347170300252400ustar00rootroot00000000000000Feature: uploader with file storage and overriden filename In order to customize the filaname of uploaded files As a developer using CarrierWave I want to upload files to the filesystem with an overriden filename and different verions Background: Given an uploader class that uses the 'file' storage And that the uploader class has a version named 'thumb' And that the uploader has the filename overridden to 'grark.png' And an instance of that class Scenario: store a file When I store the file 'fixtures/bork.txt' Then there should be a file at 'public/uploads/grark.png' Then there should be a file at 'public/uploads/thumb_grark.png' And the file at 'public/uploads/grark.png' should be identical to the file at 'fixtures/bork.txt' And the file at 'public/uploads/thumb_grark.png' should be identical to the file at 'fixtures/bork.txt' And the uploader should have the url '/uploads/grark.png' And the uploader's version 'thumb' should have the url '/uploads/thumb_grark.png' Scenario: cache a file and then store it When I cache the file 'fixtures/bork.txt' Then there should be a file called 'bork.txt' somewhere in a subdirectory of 'public/uploads/tmp' Then there should be a file called 'thumb_bork.txt' somewhere in a subdirectory of 'public/uploads/tmp' And the file called 'bork.txt' in a subdirectory of 'public/uploads/tmp' should be identical to the file at 'fixtures/bork.txt' And there should not be a file at 'public/uploads/grark.png' And there should not be a file at 'public/uploads/thumb_grark.png' When I store the file Then there should be a file at 'public/uploads/grark.png' And there should be a file at 'public/uploads/thumb_grark.png' And the file at 'public/uploads/grark.png' should be identical to the file at 'fixtures/bork.txt' And the file at 'public/uploads/thumb_grark.png' should be identical to the file at 'fixtures/bork.txt' And the uploader should have the url '/uploads/grark.png' And the uploader's version 'thumb' should have the url '/uploads/thumb_grark.png' Scenario: retrieving a file from cache then storing Given the file 'fixtures/bork.txt' is cached file at 'public/uploads/tmp/1369894322-345-2255/bork.txt' Given the file 'fixtures/monkey.txt' is cached file at 'public/uploads/tmp/1369894322-345-2255/thumb_bork.txt' When I retrieve the cache name '1369894322-345-2255/bork.txt' from the cache And I store the file Then there should be a file at 'public/uploads/grark.png' Then there should be a file at 'public/uploads/thumb_grark.png' And the file at 'public/uploads/grark.png' should be identical to the file at 'fixtures/bork.txt' And the file at 'public/uploads/thumb_grark.png' should be identical to the file at 'fixtures/monkey.txt' Scenario: retrieving a file from store Given the file 'fixtures/bork.txt' is stored at 'public/uploads/bork.txt' Given the file 'fixtures/monkey.txt' is stored at 'public/uploads/thumb_bork.txt' When I retrieve the file 'bork.txt' from the store Then the uploader should have the url '/uploads/bork.txt' And the uploader's version 'thumb' should have the url '/uploads/thumb_bork.txt' carrierwave-0.10.0/features/versions_overriden_store_dir.feature000066400000000000000000000051451230347170300253010ustar00rootroot00000000000000Feature: uploader with file storage and versions with overridden store dir In order to be awesome As a developer using CarrierWave I want to upload files to the filesystem Background: Given an uploader class that uses the 'file' storage And that the uploader class has a version named 'thumb' And that the version 'thumb' has the store_dir overridden to 'public/monkey/llama' And an instance of that class Scenario: store a file When I store the file 'fixtures/bork.txt' Then there should be a file at 'public/uploads/bork.txt' Then there should be a file at 'public/monkey/llama/thumb_bork.txt' And the file at 'public/uploads/bork.txt' should be identical to the file at 'fixtures/bork.txt' And the file at 'public/monkey/llama/thumb_bork.txt' should be identical to the file at 'fixtures/bork.txt' Scenario: cache a file and then store it When I cache the file 'fixtures/bork.txt' Then there should be a file called 'bork.txt' somewhere in a subdirectory of 'public/uploads/tmp' Then there should be a file called 'thumb_bork.txt' somewhere in a subdirectory of 'public/uploads/tmp' And the file called 'bork.txt' in a subdirectory of 'public/uploads/tmp' should be identical to the file at 'fixtures/bork.txt' And the file called 'thumb_bork.txt' in a subdirectory of 'public/uploads/tmp' should be identical to the file at 'fixtures/bork.txt' And there should not be a file at 'public/uploads/bork.txt' And there should not be a file at 'public/monkey/llama/thumb_bork.txt' When I store the file Then there should be a file at 'public/uploads/bork.txt' Then there should be a file at 'public/monkey/llama/thumb_bork.txt' And the file at 'public/uploads/bork.txt' should be identical to the file at 'fixtures/bork.txt' And the file at 'public/monkey/llama/thumb_bork.txt' should be identical to the file at 'fixtures/bork.txt' Scenario: retrieving a file from cache then storing Given the file 'fixtures/bork.txt' is cached file at 'public/uploads/tmp/1369894322-345-2255/bork.txt' Given the file 'fixtures/monkey.txt' is cached file at 'public/uploads/tmp/1369894322-345-2255/thumb_bork.txt' When I retrieve the cache name '1369894322-345-2255/bork.txt' from the cache And I store the file Then there should be a file at 'public/uploads/bork.txt' Then there should be a file at 'public/monkey/llama/thumb_bork.txt' And the file at 'public/uploads/bork.txt' should be identical to the file at 'fixtures/bork.txt' And the file at 'public/monkey/llama/thumb_bork.txt' should be identical to the file at 'fixtures/monkey.txt' carrierwave-0.10.0/gemfiles/000077500000000000000000000000001230347170300156755ustar00rootroot00000000000000carrierwave-0.10.0/gemfiles/rails-3-2-stable.gemfile000066400000000000000000000002541230347170300221110ustar00rootroot00000000000000source "https://rubygems.org" gem "rails", :git => "https://github.com/rails/rails.git", :branch => "3-2-stable" gem "carrierwave", :path => "../" gemspec :path => "../" carrierwave-0.10.0/gemfiles/rails-4-0-stable.gemfile000066400000000000000000000002301230347170300221020ustar00rootroot00000000000000source "https://rubygems.org" gem "rails", :github => "rails/rails", :branch => "4-0-stable" gem "carrierwave", :path => "../" gemspec :path => "../" carrierwave-0.10.0/gemfiles/rails-4-1-stable.gemfile000066400000000000000000000002301230347170300221030ustar00rootroot00000000000000source "https://rubygems.org" gem "rails", :github => "rails/rails", :branch => "4-1-stable" gem "carrierwave", :path => "../" gemspec :path => "../" carrierwave-0.10.0/gemfiles/rails-master.gemfile000066400000000000000000000003151230347170300216310ustar00rootroot00000000000000source "https://rubygems.org" gem "rails", :github => "rails/rails", :branch => "master" gem "arel", :github => "rails/arel", :branch => "master" gem "carrierwave", :path => "../" gemspec :path => "../" carrierwave-0.10.0/lib/000077500000000000000000000000001230347170300146505ustar00rootroot00000000000000carrierwave-0.10.0/lib/carrierwave.rb000066400000000000000000000043051230347170300175110ustar00rootroot00000000000000# encoding: utf-8 require 'fileutils' require 'active_support/core_ext/object/blank' require 'active_support/core_ext/class/attribute' require 'active_support/concern' module CarrierWave class << self attr_accessor :root, :base_path def configure(&block) CarrierWave::Uploader::Base.configure(&block) end def clean_cached_files!(seconds=60*60*24) CarrierWave::Uploader::Base.clean_cached_files!(seconds) end end end if defined?(Merb) CarrierWave.root = Merb.dir_for(:public) Merb::BootLoader.before_app_loads do # Setup path for uploaders and load all of them before classes are loaded Merb.push_path(:uploaders, Merb.root / 'app' / 'uploaders', '*.rb') Dir.glob(File.join(Merb.load_paths[:uploaders])).each {|f| require f } end elsif defined?(Rails) module CarrierWave class Railtie < Rails::Railtie initializer "carrierwave.setup_paths" do CarrierWave.root = Rails.root.join(Rails.public_path).to_s CarrierWave.base_path = ENV['RAILS_RELATIVE_URL_ROOT'] end initializer "carrierwave.active_record" do ActiveSupport.on_load :active_record do require 'carrierwave/orm/activerecord' end end ## # Loads the Carrierwave locale files before the Rails application locales # letting the Rails application overrite the carrierwave locale defaults config.before_configuration do I18n.load_path << File.join(File.dirname(__FILE__), "carrierwave", "locale", 'en.yml') end end end elsif defined?(Sinatra) if defined?(Padrino) && defined?(PADRINO_ROOT) CarrierWave.root = File.join(PADRINO_ROOT, "public") else CarrierWave.root = if Sinatra::Application.respond_to?(:public_folder) # Sinatra >= 1.3 Sinatra::Application.public_folder else # Sinatra < 1.3 Sinatra::Application.public end end end require "carrierwave/utilities" require "carrierwave/error" require "carrierwave/sanitized_file" require "carrierwave/mount" require "carrierwave/processing" require "carrierwave/version" require "carrierwave/storage" require "carrierwave/uploader" require "carrierwave/compatibility/paperclip" require "carrierwave/test/matchers" carrierwave-0.10.0/lib/carrierwave/000077500000000000000000000000001230347170300171625ustar00rootroot00000000000000carrierwave-0.10.0/lib/carrierwave/compatibility/000077500000000000000000000000001230347170300220335ustar00rootroot00000000000000carrierwave-0.10.0/lib/carrierwave/compatibility/paperclip.rb000066400000000000000000000072261230347170300243460ustar00rootroot00000000000000# encoding: utf-8 module CarrierWave module Compatibility ## # Mix this module into an Uploader to make it mimic Paperclip's storage paths # This will make your Uploader use the same default storage path as paperclip # does. If you need to override it, you can override the +paperclip_path+ method # and provide a Paperclip style path: # # class MyUploader < CarrierWave::Uploader::Base # include CarrierWave::Compatibility::Paperclip # # def paperclip_path # ":rails_root/public/uploads/:id/:attachment/:style_:basename.:extension" # end # end # # --- # # This file contains code taken from Paperclip # # LICENSE # # The MIT License # # Copyright (c) 2008 Jon Yurek and thoughtbot, inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # module Paperclip extend ActiveSupport::Concern DEFAULT_MAPPINGS = { :rails_root => lambda{|u, f| Rails.root.to_s }, :rails_env => lambda{|u, f| Rails.env }, :id_partition => lambda{|u, f| ("%09d" % u.model.id).scan(/\d{3}/).join("/")}, :id => lambda{|u, f| u.model.id }, :attachment => lambda{|u, f| u.mounted_as.to_s.downcase.pluralize }, :style => lambda{|u, f| u.paperclip_style }, :basename => lambda{|u, f| u.filename.gsub(/#{File.extname(u.filename)}$/, "") }, :extension => lambda{|u, d| File.extname(u.filename).gsub(/^\.+/, "")}, :class => lambda{|u, f| u.model.class.name.underscore.pluralize} } included do attr_accessor :filename class_attribute :mappings self.mappings ||= DEFAULT_MAPPINGS.dup end def store_path(for_file=filename) path = paperclip_path self.filename = for_file path ||= File.join(*[store_dir, paperclip_style.to_s, for_file].compact) interpolate_paperclip_path(path) end def store_dir ":rails_root/public/system/:attachment/:id" end def paperclip_default_style :original end def paperclip_path end def paperclip_style version_name || paperclip_default_style end module ClassMethods def interpolate(sym, &block) mappings[sym] = block end end private def interpolate_paperclip_path(path) mappings.each_pair.inject(path) do |agg, pair| agg.gsub(":#{pair[0]}") { pair[1].call(self, self.paperclip_style).to_s } end end end # Paperclip end # Compatibility end # CarrierWave carrierwave-0.10.0/lib/carrierwave/error.rb000066400000000000000000000003521230347170300206400ustar00rootroot00000000000000module CarrierWave class UploadError < StandardError; end class IntegrityError < UploadError; end class InvalidParameter < UploadError; end class ProcessingError < UploadError; end class DownloadError < UploadError; end end carrierwave-0.10.0/lib/carrierwave/locale/000077500000000000000000000000001230347170300204215ustar00rootroot00000000000000carrierwave-0.10.0/lib/carrierwave/locale/cs.yml000066400000000000000000000015111230347170300215470ustar00rootroot00000000000000cs: errors: messages: carrierwave_processing_error: se nepodařilo zpracovat carrierwave_integrity_error: není povolený typ souboru carrierwave_download_error: nemůže být stažen extension_white_list_error: "Není možné nahrávat %{extension} soubory, povolené typy: %{allowed_types}" extension_black_list_error: "Není možné nahrávat %{extension} soubory, zakázané typy: %{prohibited_types}" rmagick_processing_error: "Nepodařilo se upravit pomocí rmagick, možná se nejedná o obrázek? Hlášená Chyba: %{e}" mime_types_processing_error: "Nepodařilo se upravit s MIME::Types, možná se nejedná o content-type? Hlášená Chyba: %{e}" mini_magick_processing_error: "Nepodařilo se upravit pomocí MiniMagick, možná se nejedná o obrázek? Hlášená Chyba: %{e}" carrierwave-0.10.0/lib/carrierwave/locale/de.yml000066400000000000000000000015761230347170300215450ustar00rootroot00000000000000de: errors: messages: carrierwave_processing_error: konnte nicht verarbeitet werden carrierwave_integrity_error: ist kein erlaubter Dateityp carrierwave_download_error: konnte nicht heruntergeladen werden extension_white_list_error: "Sie sind nicht berechtigt %{extension} Dateien hochzuladen, erlaubte Typen: %{allowed_types}" extension_black_list_error: "Sie sind nicht berechtigt %{extension} Dateien hochzuladen, verbotene Typen: %{prohibited_types}" rmagick_processing_error: "Verarbeitung mit rmagick fehlgeschlagen, vielleicht ist es kein Bild? Original Fehler: %{e}" mime_types_processing_error: "Verarbeitung mit MIME::Types fehlgeschlagen, vielleicht kein gültiger content-type? Original Fehler: %{e}" mini_magick_processing_error: "Verarbeitung mit MiniMagick fehlgeschlagen, vielleicht ist es kein Bild? Original Fehler: %{e}" carrierwave-0.10.0/lib/carrierwave/locale/el.yml000066400000000000000000000022341230347170300215450ustar00rootroot00000000000000el: errors: messages: carrierwave_processing_error: "απέτυχε στην επεξεργασία" carrierwave_integrity_error: "δεν ανήκει σε επιτρεπτό τύπο αρχείου" carrierwave_download_error: "δεν ήταν δυνατό να μεταφορτωθεί" extension_white_list_error: "Δεν επιτρέπεται το ανέβασμα αρχείων %{extension}, επιτρεπτοί τύποι: %{allowed_types}" extension_black_list_error: "Δεν επιτρέπεται το ανέβασμα αρχείων %{extension}, μη επιτρεπτοί τύποι: %{prohibited_types}" rmagick_processing_error: "Απέτυχε ο χειρισμός με rmagick, ίσως δεν είναι εικόνα; Αρχικό Σφάλμα: %{e}" mime_types_processing_error: "Απέτυχε η επεξεργασία του αρχείου με MIME::Types, ίσως δεν έχει έγκυρο content-type; Αρχικό Σφάλμα: %{e}" mini_magick_processing_error: "Απέτυχε ο χειρισμός με MiniMagick, ίσως δεν είναι εικόνα; Αρχικό Σφάλμα: %{e}" carrierwave-0.10.0/lib/carrierwave/locale/en.yml000066400000000000000000000014651230347170300215540ustar00rootroot00000000000000en: errors: messages: carrierwave_processing_error: failed to be processed carrierwave_integrity_error: is not of an allowed file type carrierwave_download_error: could not be downloaded extension_white_list_error: "You are not allowed to upload %{extension} files, allowed types: %{allowed_types}" extension_black_list_error: "You are not allowed to upload %{extension} files, prohibited types: %{prohibited_types}" rmagick_processing_error: "Failed to manipulate with rmagick, maybe it is not an image? Original Error: %{e}" mime_types_processing_error: "Failed to process file with MIME::Types, maybe not valid content-type? Original Error: %{e}" mini_magick_processing_error: "Failed to manipulate with MiniMagick, maybe it is not an image? Original Error: %{e}" carrierwave-0.10.0/lib/carrierwave/locale/es.yml000066400000000000000000000016041230347170300215540ustar00rootroot00000000000000es: errors: messages: carrierwave_processing_error: no se pudo procesar carrierwave_integrity_error: no es de un tipo de archivo permitido carrierwave_download_error: no se pudo descargar extension_white_list_error: "No se pueden subir archivos de esta extensión %{extension}, las extensiones permitidas son: %{allowed_types}" extension_black_list_error: "No se pueden subir archivos de esta extensión %{extension}, las extensiones prohibidas son: %{allowed_types}" rmagick_processing_error: "No se pudo manipular con rmagick, quizá porque no es una imágen? Error original: %{e}" mime_types_processing_error: "No se pudo procesar archivo con MIME::Types, quizá no tiene el content-type correcto? Error original: %{e}" mini_magick_processing_error: "No se pudo manipular con MiniMagick, quizá porque no es una imágen? Error original: %{e}" carrierwave-0.10.0/lib/carrierwave/locale/fr.yml000066400000000000000000000017141230347170300215560ustar00rootroot00000000000000fr: errors: messages: carrierwave_processing_error: "Impossible de redimensionner l'image." carrierwave_integrity_error: "Ce n'est pas une image." carrierwave_download_error: "Impossible de télécharger l'image." extension_white_list_error: "Vous n'êtes pas autorisé à uploader des fichiers %{extension}, types autorisés: %{allowed_types}" extension_black_list_error: "Vous n'êtes pas autorisé à uploader des fichiers %{extension}, types interdits: %{prohibited_types}" rmagick_processing_error: "La manipulation d'image avec rmagick a échoué. Peut-être que ce n'est pas une image ? Erreur originale: %{e}" mime_types_processing_error: "Le traitement de fichier avec MIME::Types a échoué. Peut-être que ce n'est pas un type valide ? Erreur originale: %{e}" mini_magick_processing_error: "La manipulation d'image avec MiniMagick a échoué. Peut-être que ce n'est pas une image ? Erreur originale: %{e}" carrierwave-0.10.0/lib/carrierwave/locale/ja.yml000066400000000000000000000021411230347170300215340ustar00rootroot00000000000000ja: errors: messages: carrierwave_processing_error: 処理できませんでした carrierwave_integrity_error: は許可されていないファイルタイプです carrierwave_download_error: はダウンロードできません extension_white_list_error: "%{extension}ファイルのアップロードは許可されていません。アップロードできるファイルタイプ: %{allowed_types}" extension_black_list_error: "%{extension}ファイルのアップロードは許可されていません。アップロードできないファイルタイプ: %{prohibited_types}" rmagick_processing_error: "rmagickがファイルを処理できませんでした。画像を確認してください。エラーメッセージ: %{e}" mime_types_processing_error: "MIME::Typesのファイルを処理できませんでした。Content-Typeを確認してください。エラーメッセージ: %{e}" mini_magick_processing_error: "MiniMagickがファイルを処理できませんでした。画像を確認してください。エラーメッセージ: %{e}" carrierwave-0.10.0/lib/carrierwave/locale/nb.yml000066400000000000000000000014761230347170300215530ustar00rootroot00000000000000nb: errors: messages: carrierwave_processing_error: kunne ikke behandles carrierwave_integrity_error: er ikke en tillatt filtype carrierwave_download_error: kunne ikke lastes ned extension_white_list_error: "Du kan ikke laste opp %{extension}-filer, tillatte filtyper: %{allowed_types}" extension_black_list_error: "Du kan ikke laste opp %{extension}-filer, forbudte filtyper: %{prohibited_types}" rmagick_processing_error: "Kunne ikke manipulere med rmagick. Er du sikker på at det er et bilde? Feilmelding: %{e}" mime_types_processing_error: "Kunne ikke behandle fil med MIME::Types. Er du sikker på at content-type er korrekt? Feilmelding: %{e}" mini_magick_processing_error: "Kunne ikke manipulere med MiniMagick. Er du sikker på at det er et bilde? Feilmelding: %{e}" carrierwave-0.10.0/lib/carrierwave/locale/nl.yml000066400000000000000000000016461230347170300215640ustar00rootroot00000000000000nl: errors: messages: carrierwave_processing_error: kon niet worden verwerkt carrierwave_integrity_error: is niet van een toegestaan bestandstype carrierwave_download_error: kon niet gedownload worden extension_white_list_error: "Het is niet toegestaan om %{extension} bestanden te uploaden; toegestane bestandstypes: %{allowed_types}" extension_black_list_error: "Het is niet toegestaan om %{extension} bestanden te uploaden; verboden bestandstypes: %{prohibited_types}" rmagick_processing_error: "Bewerking met rmagick is mislukt, misschien is het geen afbeelding? Originele foutmelding: %{e}" mime_types_processing_error: "Verwerking van bestand met MIME::Types is mislukt, misschien is het geen geldig content-type? Originele foutmelding: %{e}" mini_magick_processing_error: "Bewerking met MiniMagick is mislukt, misschien is het geen afbeelding? Originele foutmelding: %{e}" carrierwave-0.10.0/lib/carrierwave/locale/pl.yml000066400000000000000000000016551230347170300215660ustar00rootroot00000000000000pl: errors: messages: carrierwave_processing_error: nie można przetworzyć carrierwave_integrity_error: niedozwolony typ pliku carrierwave_download_error: nie można pobrać pliku extension_white_list_error: "Nie można wgrać pliku o rozszerzeniu %{extension}, dozwolone typy plików: %{allowed_types}" extension_black_list_error: "Nie można wgrać pliku o rozszerzeniu %{extension}, zakazane typy plików: %{prohibited_types}" rmagick_processing_error: "Nie udało się przetworzyć pliku przy pomocy rmagick, może to nie jest obrazek? Oryginalna treść błędu: %{e}" mime_types_processing_error: "Nie udało się przetworzyć pliku przy pomocy MIME::Types, może content-type jest niepoprawny? Oryginalna treść błędu: %{e}" mini_magick_processing_error: "Nie udało się przetworzyć pliku przy pomocy MiniMagick, może to nie jest obrazek? Oryginalna treść błędu: %{e}" carrierwave-0.10.0/lib/carrierwave/locale/pt-BR.yml000066400000000000000000000015331230347170300220720ustar00rootroot00000000000000pt-BR: errors: messages: carrierwave_processing_error: falhou em ser processado carrierwave_integrity_error: não é um tipo de arquivo permitido carrierwave_download_error: não pôde ser baixado extension_white_list_error: "Não é permitido o envio de arquivos %{extension}, tipos permitidos: %{allowed_types}" extension_black_list_error: "Não é permitido o envio de arquivos %{extension}, tipos proibidos: %{prohibited_types}" rmagick_processing_error: "Falha ao manipular com RMagick, talvez arquivo não seja uma imagem? Erro original: %{e}" mime_types_processing_error: "Falha ao processar arquivo com MIME::Types, talvez content-type seja inválido? Erro original: %{e}" mini_magick_processing_error: "Falha ao manipular com MiniMagick, talvez arquivo não seja uma imagem? Erro original: %{e}" carrierwave-0.10.0/lib/carrierwave/locale/pt-PT.yml000066400000000000000000000017141230347170300221130ustar00rootroot00000000000000pt-PT: errors: messages: carrierwave_processing_error: falhou ao ser processado carrierwave_integrity_error: não é um tipo de ficheiro permitido carrierwave_download_error: não pôde ser transferido extension_white_list_error: "Não é permitido o envio de ficheiros com a extensão %{extension}, tipos de ficheiro permitidos: %{allowed_types}" extension_black_list_error: "Não é permitido o envio de ficheiros com a extensão %{extension}, tipos de ficheiro proibidos: %{prohibited_types}" rmagick_processing_error: "Ocorreu uma falha ao processar com rmagick, talvez o ficheiro não seja uma imagem? Erro original: %{e}" mime_types_processing_error: "Ocorreu uma falha ao processar com MIME::Types, talvez o parâmetro content-type não seja válido? Erro original: %{e}" mini_magick_processing_error: "Ocorreu uma falha ao processar com MiniMagick, talvez o ficheiro não seja uma imagem? Erro original: %{e}" carrierwave-0.10.0/lib/carrierwave/locale/ru.yml000066400000000000000000000022521230347170300215730ustar00rootroot00000000000000ru: errors: messages: carrierwave_processing_error: Невозможно обработать изображение carrierwave_integrity_error: Файл не является изображением carrierwave_download_error: Невозможно скачать файл extension_white_list_error: "Вы не можете загружать файлы типа %{extension}, разрешенные типы: %{allowed_types}" extension_black_list_error: "Вы не можете загружать файлы типа %{extension}, запрещенные типы: %{prohibited_types}" rmagick_processing_error: "Ошибка взаимодействия с RMagick, может быть это не изображение? Исходная ошибка: %{e}" mime_types_processing_error: "Не получилось обработать файл с MIME::Types, возможно неправильный content-type? Исходная ошибка: %{e}" mini_magick_processing_error: "Ошибка взаимодействия с MiniMagick, может быть это не изображение? Исходная ошибка: %{e}" carrierwave-0.10.0/lib/carrierwave/locale/sk.yml000066400000000000000000000015211230347170300215600ustar00rootroot00000000000000sk: errors: messages: carrierwave_processing_error: sa nepodarilo spracovať carrierwave_integrity_error: nie je povolený typ súboru carrierwave_download_error: nie je možné stiahnuť extension_white_list_error: "Nie je možné nahrávať %{extension} súbory, povolené typy: %{allowed_types}" extension_black_list_error: "Nie je možné nahrávať %{extension} súbory, zakázané typy: %{prohibited_types}" rmagick_processing_error: "Nepodarilo sa upraviť pomocou rmagick, možno nejde o obrázok? Hlásená chyba: %{e}" mime_types_processing_error: "Súbor sa nepodarilo spracovať pomocou MIME::Types, možno nejde o valídny content-type? Hlásená chyba: %{e}" mini_magick_processing_error: "Nepodarilo sa upraviť pomocou MiniMagick, možno nejde o obrázok? Hlásená chyba: %{e}" carrierwave-0.10.0/lib/carrierwave/locale/tr.yml000066400000000000000000000015571230347170300216010ustar00rootroot00000000000000tr: errors: messages: carrierwave_processing_error: işlenmesi sırasında hata oluştu carrierwave_integrity_error: izin verilebilir bir dosya türü değil carrierwave_download_error: indirilemedi extension_white_list_error: "%{extension} uzantılı dosyaları yükleme izniniz yok, izin verilen uzantılar: %{allowed_types}" extension_black_list_error: "%{extension} uzantılı dosyaları yükleme izniniz yok, izin verilmeyen uzantılar: %{prohibited_types}" rmagick_processing_error: "Resim rmagick ile düzenlenemedi, belkide resim değildir? Orjinal Hata: %{e}" mime_types_processing_error: "Dosya, MIME::Types kullanılarak işlenemedi, belkide geçerli bir içerik türü değildir? Orjinal Hata: %{e}" mini_magick_processing_error: "Resim MiniMagick ile düzenlenemedi, belkide resim değildir? Orjinal Hata: %{e}" carrierwave-0.10.0/lib/carrierwave/mount.rb000066400000000000000000000303401230347170300206510ustar00rootroot00000000000000# encoding: utf-8 module CarrierWave ## # If a Class is extended with this module, it gains the mount_uploader # method, which is used for mapping attributes to uploaders and allowing # easy assignment. # # You can use mount_uploader with pretty much any class, however it is # intended to be used with some kind of persistent storage, like an ORM. # If you want to persist the uploaded files in a particular Class, it # needs to implement a `read_uploader` and a `write_uploader` method. # module Mount ## # === Returns # # [Hash{Symbol => CarrierWave}] what uploaders are mounted on which columns # def uploaders @uploaders ||= superclass.respond_to?(:uploaders) ? superclass.uploaders.dup : {} end def uploader_options @uploader_options ||= superclass.respond_to?(:uploader_options) ? superclass.uploader_options.dup : {} end ## # Return a particular option for a particular uploader # # === Parameters # # [column (Symbol)] The column the uploader is mounted at # [option (Symbol)] The option, e.g. validate_integrity # # === Returns # # [Object] The option value # def uploader_option(column, option) if uploader_options[column].has_key?(option) uploader_options[column][option] else uploaders[column].send(option) end end ## # Mounts the given uploader on the given column. This means that assigning # and reading from the column will upload and retrieve files. Supposing # that a User class has an uploader mounted on image, you can assign and # retrieve files like this: # # @user.image # => # @user.image.store!(some_file_object) # # @user.image.url # => '/some_url.png' # # It is also possible (but not recommended) to omit the uploader, which # will create an anonymous uploader class. # # Passing a block makes it possible to customize the uploader. This can be # convenient for brevity, but if there is any significatnt logic in the # uploader, you should do the right thing and have it in its own file. # # === Added instance methods # # Supposing a class has used +mount_uploader+ to mount an uploader on a column # named +image+, in that case the following methods will be added to the class: # # [image] Returns an instance of the uploader only if anything has been uploaded # [image=] Caches the given file # # [image_url] Returns the url to the uploaded file # # [image_cache] Returns a string that identifies the cache location of the file # [image_cache=] Retrieves the file from the cache based on the given cache name # # [remote_image_url] Returns previously cached remote url # [remote_image_url=] Retrieve the file from the remote url # # [remove_image] An attribute reader that can be used with a checkbox to mark a file for removal # [remove_image=] An attribute writer that can be used with a checkbox to mark a file for removal # [remove_image?] Whether the file should be removed when store_image! is called. # # [store_image!] Stores a file that has been assigned with +image=+ # [remove_image!] Removes the uploaded file from the filesystem. # # [image_integrity_error] Returns an error object if the last file to be assigned caused an integrity error # [image_processing_error] Returns an error object if the last file to be assigned caused a processing error # [image_download_error] Returns an error object if the last file to be remotely assigned caused a download error # # [write_image_identifier] Uses the write_uploader method to set the identifier. # [image_identifier] Reads out the identifier of the file # # === Parameters # # [column (Symbol)] the attribute to mount this uploader on # [uploader (CarrierWave::Uploader)] the uploader class to mount # [options (Hash{Symbol => Object})] a set of options # [&block (Proc)] customize anonymous uploaders # # === Options # # [:mount_on => Symbol] if the name of the column to be serialized to differs you can override it using this option # [:ignore_integrity_errors => Boolean] if set to true, integrity errors will result in caching failing silently # [:ignore_processing_errors => Boolean] if set to true, processing errors will result in caching failing silently # # === Examples # # Mounting uploaders on different columns. # # class Song # mount_uploader :lyrics, LyricsUploader # mount_uploader :alternative_lyrics, LyricsUploader # mount_uploader :file, SongUploader # end # # This will add an anonymous uploader with only the default settings: # # class Data # mount_uploader :csv # end # # this will add an anonymous uploader overriding the store_dir: # # class Product # mount_uploader :blueprint do # def store_dir # 'blueprints' # end # end # end # def mount_uploader(column, uploader=nil, options={}, &block) include CarrierWave::Mount::Extension uploader = build_uploader(uploader, &block) uploaders[column.to_sym] = uploader uploader_options[column.to_sym] = options # Make sure to write over accessors directly defined on the class. # Simply super to the included module below. class_eval <<-RUBY, __FILE__, __LINE__+1 def #{column}; super; end def #{column}=(new_file); super; end RUBY # Mixing this in as a Module instead of class_evaling directly, so we # can maintain the ability to super to any of these methods from within # the class. mod = Module.new include mod mod.class_eval <<-RUBY, __FILE__, __LINE__+1 def #{column} _mounter(:#{column}).uploader end def #{column}=(new_file) _mounter(:#{column}).cache(new_file) end def #{column}? _mounter(:#{column}).present? end def #{column}_url(*args) _mounter(:#{column}).url(*args) end def #{column}_cache _mounter(:#{column}).cache_name end def #{column}_cache=(cache_name) _mounter(:#{column}).cache_name = cache_name end def remote_#{column}_url _mounter(:#{column}).remote_url end def remote_#{column}_url=(url) _mounter(:#{column}).remote_url = url end def remove_#{column} _mounter(:#{column}).remove end def remove_#{column}! _mounter(:#{column}).remove! end def remove_#{column}=(value) _mounter(:#{column}).remove = value end def remove_#{column}? _mounter(:#{column}).remove? end def store_#{column}! _mounter(:#{column}).store! end def #{column}_integrity_error _mounter(:#{column}).integrity_error end def #{column}_processing_error _mounter(:#{column}).processing_error end def #{column}_download_error _mounter(:#{column}).download_error end def write_#{column}_identifier _mounter(:#{column}).write_identifier end def #{column}_identifier _mounter(:#{column}).identifier end def store_previous_model_for_#{column} serialization_column = _mounter(:#{column}).serialization_column if #{column}.remove_previously_stored_files_after_update && send(:"\#{serialization_column}_changed?") @previous_model_for_#{column} ||= self.find_previous_model_for_#{column} end end def find_previous_model_for_#{column} self.class.find(to_key.first) end def remove_previously_stored_#{column} if @previous_model_for_#{column} && @previous_model_for_#{column}.#{column}.path != #{column}.path @previous_model_for_#{column}.#{column}.remove! @previous_model_for_#{column} = nil end end def mark_remove_#{column}_false _mounter(:#{column}).remove = false end RUBY end private def build_uploader(uploader, &block) return uploader if uploader && !block_given? uploader = Class.new(uploader || CarrierWave::Uploader::Base) const_set("Uploader#{uploader.object_id}".gsub('-', '_'), uploader) if block_given? uploader.class_eval(&block) uploader.recursively_apply_block_to_versions(&block) end uploader end module Extension ## # overwrite this to read from a serialized attribute # def read_uploader(column); end ## # overwrite this to write to a serialized attribute # def write_uploader(column, identifier); end private def _mounter(column) # We cannot memoize in frozen objects :( return Mounter.new(self, column) if frozen? @_mounters ||= {} @_mounters[column] ||= Mounter.new(self, column) end end # Extension # this is an internal class, used by CarrierWave::Mount so that # we don't pollute the model with a lot of methods. class Mounter #:nodoc: attr_reader :column, :record, :remote_url, :integrity_error, :processing_error, :download_error attr_accessor :remove def initialize(record, column, options={}) @record = record @column = column @options = record.class.uploader_options[column] end def write_identifier return if record.frozen? if remove? record.write_uploader(serialization_column, nil) elsif uploader.identifier.present? record.write_uploader(serialization_column, uploader.identifier) end end def identifier record.read_uploader(serialization_column) end def uploader @uploader ||= record.class.uploaders[column].new(record, column) @uploader.retrieve_from_store!(identifier) if @uploader.blank? && identifier.present? @uploader end def cache(new_file) uploader.cache!(new_file) @integrity_error = nil @processing_error = nil rescue CarrierWave::IntegrityError => e @integrity_error = e raise e unless option(:ignore_integrity_errors) rescue CarrierWave::ProcessingError => e @processing_error = e raise e unless option(:ignore_processing_errors) end def cache_name uploader.cache_name end def cache_name=(cache_name) uploader.retrieve_from_cache!(cache_name) unless uploader.cached? rescue CarrierWave::InvalidParameter end def remote_url=(url) return if url.blank? @remote_url = url @download_error = nil @integrity_error = nil uploader.download!(url) rescue CarrierWave::DownloadError => e @download_error = e raise e unless option(:ignore_download_errors) rescue CarrierWave::ProcessingError => e @processing_error = e raise e unless option(:ignore_processing_errors) rescue CarrierWave::IntegrityError => e @integrity_error = e raise e unless option(:ignore_integrity_errors) end def store! return if uploader.blank? if remove? uploader.remove! else uploader.store! end end def url(*args) uploader.url(*args) end def blank? uploader.blank? end def remove? remove.present? && remove !~ /\A0|false$\z/ end def remove! uploader.remove! end def serialization_column option(:mount_on) || column end attr_accessor :uploader_options private def option(name) self.uploader_options ||= {} self.uploader_options[name] ||= record.class.uploader_option(column, name) end end # Mounter end # Mount end # CarrierWave carrierwave-0.10.0/lib/carrierwave/orm/000077500000000000000000000000001230347170300177575ustar00rootroot00000000000000carrierwave-0.10.0/lib/carrierwave/orm/activerecord.rb000066400000000000000000000045011230347170300227560ustar00rootroot00000000000000# encoding: utf-8 require 'active_record' require 'carrierwave/validations/active_model' module CarrierWave module ActiveRecord include CarrierWave::Mount ## # See +CarrierWave::Mount#mount_uploader+ for documentation # def mount_uploader(column, uploader=nil, options={}, &block) super alias_method :read_uploader, :read_attribute alias_method :write_uploader, :write_attribute public :read_uploader public :write_uploader include CarrierWave::Validations::ActiveModel validates_integrity_of column if uploader_option(column.to_sym, :validate_integrity) validates_processing_of column if uploader_option(column.to_sym, :validate_processing) validates_download_of column if uploader_option(column.to_sym, :validate_download) after_save :"store_#{column}!" before_save :"write_#{column}_identifier" after_commit :"remove_#{column}!", :on => :destroy after_commit :"mark_remove_#{column}_false", :on => :update before_update :"store_previous_model_for_#{column}" after_save :"remove_previously_stored_#{column}" class_eval <<-RUBY, __FILE__, __LINE__+1 def #{column}=(new_file) column = _mounter(:#{column}).serialization_column send(:"\#{column}_will_change!") super end def remote_#{column}_url=(url) column = _mounter(:#{column}).serialization_column send(:"\#{column}_will_change!") super end def remove_#{column}! super _mounter(:#{column}).remove = true _mounter(:#{column}).write_identifier end def serializable_hash(options=nil) hash = {} except = options && options[:except] && Array.wrap(options[:except]).map(&:to_s) only = options && options[:only] && Array.wrap(options[:only]).map(&:to_s) self.class.uploaders.each do |column, uploader| if (!only && !except) || (only && only.include?(column.to_s)) || (!only && except && !except.include?(column.to_s)) hash[column.to_s] = _mounter(column).uploader.serializable_hash end end super(options).merge(hash) end RUBY end end # ActiveRecord end # CarrierWave ActiveRecord::Base.extend CarrierWave::ActiveRecord carrierwave-0.10.0/lib/carrierwave/processing.rb000066400000000000000000000002021230347170300216550ustar00rootroot00000000000000require "carrierwave/processing/rmagick" require "carrierwave/processing/mini_magick" require "carrierwave/processing/mime_types" carrierwave-0.10.0/lib/carrierwave/processing/000077500000000000000000000000001230347170300213365ustar00rootroot00000000000000carrierwave-0.10.0/lib/carrierwave/processing/mime_types.rb000066400000000000000000000043551230347170300240450ustar00rootroot00000000000000# encoding: utf-8 module CarrierWave ## # This module simplifies the use of the mime-types gem to intelligently # guess and set the content-type of a file. If you want to use this, you'll # need to require this file: # # require 'carrierwave/processing/mime_types' # # And then include it in your uploader: # # class MyUploader < CarrierWave::Uploader::Base # include CarrierWave::MimeTypes # end # # You can now use the provided helper: # # class MyUploader < CarrierWave::Uploader::Base # include CarrierWave::MimeTypes # # process :set_content_type # end # module MimeTypes extend ActiveSupport::Concern included do CarrierWave::Utilities::Deprecation.new "0.11.0", "CarrierWave::MimeTypes is deprecated and will be removed in the future, get the content_type from the SanitizedFile object directly." begin require "mime/types" rescue LoadError => e e.message << " (You may need to install the mime-types gem)" raise e end end module ClassMethods def set_content_type(override=false) process :set_content_type => override end end GENERIC_CONTENT_TYPES = %w[application/octet-stream binary/octet-stream] def generic_content_type? GENERIC_CONTENT_TYPES.include? file.content_type end ## # Changes the file content_type using the mime-types gem # # === Parameters # # [override (Boolean)] whether or not to override the file's content_type # if it is already set and not a generic content-type, # false by default # def set_content_type(override=false) if override || file.content_type.blank? || generic_content_type? new_content_type = ::MIME::Types.type_for(file.original_filename).first.to_s if file.respond_to?(:content_type=) file.content_type = new_content_type else file.instance_variable_set(:@content_type, new_content_type) end end rescue ::MIME::InvalidContentType => e raise CarrierWave::ProcessingError, I18n.translate(:"errors.messages.mime_types_processing_error", :e => e) end end # MimeTypes end # CarrierWave carrierwave-0.10.0/lib/carrierwave/processing/mini_magick.rb000066400000000000000000000204131230347170300241320ustar00rootroot00000000000000# encoding: utf-8 module CarrierWave ## # This module simplifies manipulation with MiniMagick by providing a set # of convenient helper methods. If you want to use them, you'll need to # require this file: # # require 'carrierwave/processing/mini_magick' # # And then include it in your uploader: # # class MyUploader < CarrierWave::Uploader::Base # include CarrierWave::MiniMagick # end # # You can now use the provided helpers: # # class MyUploader < CarrierWave::Uploader::Base # include CarrierWave::MiniMagick # # process :resize_to_fit => [200, 200] # end # # Or create your own helpers with the powerful manipulate! method. Check # out the ImageMagick docs at http://www.imagemagick.org/script/command-line-options.php for more # info # # class MyUploader < CarrierWave::Uploader::Base # include CarrierWave::MiniMagick # # process :radial_blur => 10 # # def radial_blur(amount) # manipulate! do |img| # img.radial_blur(amount) # img = yield(img) if block_given? # img # end # end # # === Note # # MiniMagick is a mini replacement for RMagick that uses the command line # tool "mogrify" for image manipulation. # # You can find more information here: # # http://mini_magick.rubyforge.org/ # and # https://github.com/minimagic/minimagick/ # # module MiniMagick extend ActiveSupport::Concern included do begin require "mini_magick" rescue LoadError => e e.message << " (You may need to install the mini_magick gem)" raise e end end module ClassMethods def convert(format) process :convert => format end def resize_to_limit(width, height) process :resize_to_limit => [width, height] end def resize_to_fit(width, height) process :resize_to_fit => [width, height] end def resize_to_fill(width, height, gravity='Center') process :resize_to_fill => [width, height, gravity] end def resize_and_pad(width, height, background=:transparent, gravity=::Magick::CenterGravity) process :resize_and_pad => [width, height, background, gravity] end end ## # Changes the image encoding format to the given format # # See http://www.imagemagick.org/script/command-line-options.php#format # # === Parameters # # [format (#to_s)] an abreviation of the format # # === Yields # # [MiniMagick::Image] additional manipulations to perform # # === Examples # # image.convert(:png) # def convert(format) @format = format manipulate! do |img| img.format(format.to_s.downcase) img = yield(img) if block_given? img end end ## # Resize the image to fit within the specified dimensions while retaining # the original aspect ratio. Will only resize the image if it is larger than the # specified dimensions. The resulting image may be shorter or narrower than specified # in the smaller dimension but will not be larger than the specified values. # # === Parameters # # [width (Integer)] the width to scale the image to # [height (Integer)] the height to scale the image to # # === Yields # # [MiniMagick::Image] additional manipulations to perform # def resize_to_limit(width, height) manipulate! do |img| img.resize "#{width}x#{height}>" img = yield(img) if block_given? img end end ## # Resize the image to fit within the specified dimensions while retaining # the original aspect ratio. The image may be shorter or narrower than # specified in the smaller dimension but will not be larger than the specified values. # # === Parameters # # [width (Integer)] the width to scale the image to # [height (Integer)] the height to scale the image to # # === Yields # # [MiniMagick::Image] additional manipulations to perform # def resize_to_fit(width, height) manipulate! do |img| img.resize "#{width}x#{height}" img = yield(img) if block_given? img end end ## # Resize the image to fit within the specified dimensions while retaining # the aspect ratio of the original image. If necessary, crop the image in the # larger dimension. # # === Parameters # # [width (Integer)] the width to scale the image to # [height (Integer)] the height to scale the image to # [gravity (String)] the current gravity suggestion (default: 'Center'; options: 'NorthWest', 'North', 'NorthEast', 'West', 'Center', 'East', 'SouthWest', 'South', 'SouthEast') # # === Yields # # [MiniMagick::Image] additional manipulations to perform # def resize_to_fill(width, height, gravity = 'Center') manipulate! do |img| cols, rows = img[:dimensions] img.combine_options do |cmd| if width != cols || height != rows scale_x = width/cols.to_f scale_y = height/rows.to_f if scale_x >= scale_y cols = (scale_x * (cols + 0.5)).round rows = (scale_x * (rows + 0.5)).round cmd.resize "#{cols}" else cols = (scale_y * (cols + 0.5)).round rows = (scale_y * (rows + 0.5)).round cmd.resize "x#{rows}" end end cmd.gravity gravity cmd.background "rgba(255,255,255,0.0)" cmd.extent "#{width}x#{height}" if cols != width || rows != height end img = yield(img) if block_given? img end end ## # Resize the image to fit within the specified dimensions while retaining # the original aspect ratio. If necessary, will pad the remaining area # with the given color, which defaults to transparent (for gif and png, # white for jpeg). # # See http://www.imagemagick.org/script/command-line-options.php#gravity # for gravity options. # # === Parameters # # [width (Integer)] the width to scale the image to # [height (Integer)] the height to scale the image to # [background (String, :transparent)] the color of the background as a hexcode, like "#ff45de" # [gravity (String)] how to position the image # # === Yields # # [MiniMagick::Image] additional manipulations to perform # def resize_and_pad(width, height, background=:transparent, gravity='Center') manipulate! do |img| img.combine_options do |cmd| cmd.thumbnail "#{width}x#{height}>" if background == :transparent cmd.background "rgba(255, 255, 255, 0.0)" else cmd.background background end cmd.gravity gravity cmd.extent "#{width}x#{height}" end img = yield(img) if block_given? img end end ## # Manipulate the image with MiniMagick. This method will load up an image # and then pass each of its frames to the supplied block. It will then # save the image to disk. # # === Gotcha # # This method assumes that the object responds to +current_path+. # Any class that this module is mixed into must have a +current_path+ method. # CarrierWave::Uploader does, so you won't need to worry about this in # most cases. # # === Yields # # [MiniMagick::Image] manipulations to perform # # === Raises # # [CarrierWave::ProcessingError] if manipulation failed. # def manipulate! cache_stored_file! if !cached? image = ::MiniMagick::Image.open(current_path) begin image.format(@format.to_s.downcase) if @format image = yield(image) image.write(current_path) image.run_command("identify", current_path) ensure image.destroy! end rescue ::MiniMagick::Error, ::MiniMagick::Invalid => e default = I18n.translate(:"errors.messages.mini_magick_processing_error", :e => e, :locale => :en) message = I18n.translate(:"errors.messages.mini_magick_processing_error", :e => e, :default => default) raise CarrierWave::ProcessingError, message end end # MiniMagick end # CarrierWave carrierwave-0.10.0/lib/carrierwave/processing/rmagick.rb000066400000000000000000000247701230347170300233120ustar00rootroot00000000000000# encoding: utf-8 module CarrierWave ## # This module simplifies manipulation with RMagick by providing a set # of convenient helper methods. If you want to use them, you'll need to # require this file: # # require 'carrierwave/processing/rmagick' # # And then include it in your uploader: # # class MyUploader < CarrierWave::Uploader::Base # include CarrierWave::RMagick # end # # You can now use the provided helpers: # # class MyUploader < CarrierWave::Uploader::Base # include CarrierWave::RMagick # # process :resize_to_fit => [200, 200] # end # # Or create your own helpers with the powerful manipulate! method. Check # out the RMagick docs at http://www.imagemagick.org/RMagick/doc/ for more # info # # class MyUploader < CarrierWave::Uploader::Base # include CarrierWave::RMagick # # process :do_stuff => 10.0 # # def do_stuff(blur_factor) # manipulate! do |img| # img = img.sepiatone # img = img.auto_orient # img = img.radial_blur(blur_factor) # end # end # end # # === Note # # You should be aware how RMagick handles memory. manipulate! takes care # of freeing up memory for you, but for optimum memory usage you should # use destructive operations as much as possible: # # DON'T DO THIS: # img = img.resize_to_fit # # DO THIS INSTEAD: # img.resize_to_fit! # # Read this for more information why: # # http://rubyforge.org/forum/forum.php?thread_id=1374&forum_id=1618 # module RMagick extend ActiveSupport::Concern included do begin require "RMagick" rescue LoadError => e e.message << " (You may need to install the rmagick gem)" raise e end end module ClassMethods def convert(format) process :convert => format end def resize_to_limit(width, height) process :resize_to_limit => [width, height] end def resize_to_fit(width, height) process :resize_to_fit => [width, height] end def resize_to_fill(width, height, gravity=::Magick::CenterGravity) process :resize_to_fill => [width, height, gravity] end def resize_and_pad(width, height, background=:transparent, gravity=::Magick::CenterGravity) process :resize_and_pad => [width, height, background, gravity] end def resize_to_geometry_string(geometry_string) process :resize_to_geometry_string => [geometry_string] end end ## # Changes the image encoding format to the given format # # See even http://www.imagemagick.org/RMagick/doc/magick.html#formats # # === Parameters # # [format (#to_s)] an abreviation of the format # # === Yields # # [Magick::Image] additional manipulations to perform # # === Examples # # image.convert(:png) # def convert(format) manipulate!(:format => format) @format = format end ## # Resize the image to fit within the specified dimensions while retaining # the original aspect ratio. Will only resize the image if it is larger than the # specified dimensions. The resulting image may be shorter or narrower than specified # in the smaller dimension but will not be larger than the specified values. # # === Parameters # # [width (Integer)] the width to scale the image to # [height (Integer)] the height to scale the image to # # === Yields # # [Magick::Image] additional manipulations to perform # def resize_to_limit(width, height) manipulate! do |img| geometry = Magick::Geometry.new(width, height, 0, 0, Magick::GreaterGeometry) new_img = img.change_geometry(geometry) do |new_width, new_height| img.resize(new_width, new_height) end destroy_image(img) new_img = yield(new_img) if block_given? new_img end end ## # From the RMagick documentation: "Resize the image to fit within the # specified dimensions while retaining the original aspect ratio. The # image may be shorter or narrower than specified in the smaller dimension # but will not be larger than the specified values." # # See even http://www.imagemagick.org/RMagick/doc/image3.html#resize_to_fit # # === Parameters # # [width (Integer)] the width to scale the image to # [height (Integer)] the height to scale the image to # # === Yields # # [Magick::Image] additional manipulations to perform # def resize_to_fit(width, height) manipulate! do |img| img.resize_to_fit!(width, height) img = yield(img) if block_given? img end end ## # From the RMagick documentation: "Resize the image to fit within the # specified dimensions while retaining the aspect ratio of the original # image. If necessary, crop the image in the larger dimension." # # See even http://www.imagemagick.org/RMagick/doc/image3.html#resize_to_fill # # === Parameters # # [width (Integer)] the width to scale the image to # [height (Integer)] the height to scale the image to # # === Yields # # [Magick::Image] additional manipulations to perform # def resize_to_fill(width, height, gravity=::Magick::CenterGravity) manipulate! do |img| img.crop_resized!(width, height, gravity) img = yield(img) if block_given? img end end ## # Resize the image to fit within the specified dimensions while retaining # the original aspect ratio. If necessary, will pad the remaining area # with the given color, which defaults to transparent (for gif and png, # white for jpeg). # # === Parameters # # [width (Integer)] the width to scale the image to # [height (Integer)] the height to scale the image to # [background (String, :transparent)] the color of the background as a hexcode, like "#ff45de" # [gravity (Magick::GravityType)] how to position the image # # === Yields # # [Magick::Image] additional manipulations to perform # def resize_and_pad(width, height, background=:transparent, gravity=::Magick::CenterGravity) manipulate! do |img| img.resize_to_fit!(width, height) new_img = ::Magick::Image.new(width, height) { self.background_color = background == :transparent ? 'rgba(255,255,255,0)' : background.to_s } if background == :transparent filled = new_img.matte_floodfill(1, 1) else filled = new_img.color_floodfill(1, 1, ::Magick::Pixel.from_color(background)) end destroy_image(new_img) filled.composite!(img, gravity, ::Magick::OverCompositeOp) destroy_image(img) filled = yield(filled) if block_given? filled end end ## # Resize the image per the provided geometry string. # # === Parameters # # [geometry_string (String)] the proportions in which to scale image # # === Yields # # [Magick::Image] additional manipulations to perform # def resize_to_geometry_string(geometry_string) manipulate! do |img| new_img = img.change_geometry(geometry_string) do |new_width, new_height| img.resize(new_width, new_height) end destroy_image(img) new_img = yield(new_img) if block_given? new_img end end ## # Manipulate the image with RMagick. This method will load up an image # and then pass each of its frames to the supplied block. It will then # save the image to disk. # # === Gotcha # # This method assumes that the object responds to +current_path+. # Any class that this module is mixed into must have a +current_path+ method. # CarrierWave::Uploader does, so you won't need to worry about this in # most cases. # # === Yields # # [Magick::Image] manipulations to perform # [Integer] Frame index if the image contains multiple frames # [Hash] options, see below # # === Options # # The options argument to this method is also yielded as the third # block argument. # # Currently, the following options are defined: # # ==== :write # A hash of assignments to be evaluated in the block given to the RMagick write call. # # An example: # # manipulate! do |img, index, options| # options[:write] = { # :quality => 50, # :depth => 8 # } # img # end # # This will translate to the following RMagick::Image#write call: # # image.write do |img| # self.quality = 50 # self.depth = 8 # end # # ==== :read # A hash of assignments to be given to the RMagick read call. # # The options available are identical to those for write, but are passed in directly, like this: # # manipulate! :read => { :density => 300 } # # ==== :format # Specify the output format. If unset, the filename extension is used to determine the format. # # === Raises # # [CarrierWave::ProcessingError] if manipulation failed. # def manipulate!(options={}, &block) cache_stored_file! if !cached? read_block = create_info_block(options[:read]) image = ::Magick::Image.read(current_path, &read_block) frames = ::Magick::ImageList.new image.each_with_index do |frame, index| frame = yield *[frame, index, options].take(block.arity) if block_given? frames << frame if frame end frames.append(true) if block_given? write_block = create_info_block(options[:write]) if options[:format] || @format frames.write("#{options[:format] || @format}:#{current_path}", &write_block) else frames.write(current_path, &write_block) end destroy_image(frames) rescue ::Magick::ImageMagickError => e raise CarrierWave::ProcessingError, I18n.translate(:"errors.messages.rmagick_processing_error", :e => e, :default => I18n.translate(:"errors.messages.rmagick_processing_error", :e => e, :locale => :en)) end private def create_info_block(options) return nil unless options assignments = options.map { |k, v| "self.#{k} = #{v}" } code = "lambda { |img| " + assignments.join(";") + "}" eval code end def destroy_image(image) image.destroy! if image.respond_to?(:destroy!) end end # RMagick end # CarrierWave carrierwave-0.10.0/lib/carrierwave/sanitized_file.rb000066400000000000000000000177001230347170300225050ustar00rootroot00000000000000# encoding: utf-8 require 'pathname' require 'active_support/core_ext/string/multibyte' require 'mime/types' module CarrierWave ## # SanitizedFile is a base class which provides a common API around all # the different quirky Ruby File libraries. It has support for Tempfile, # File, StringIO, Merb-style upload Hashes, as well as paths given as # Strings and Pathnames. # # It's probably needlessly comprehensive and complex. Help is appreciated. # class SanitizedFile attr_accessor :file class << self attr_writer :sanitize_regexp def sanitize_regexp @sanitize_regexp ||= /[^a-zA-Z0-9\.\-\+_]/ end end def initialize(file) self.file = file end ## # Returns the filename as is, without sanitizing it. # # === Returns # # [String] the unsanitized filename # def original_filename return @original_filename if @original_filename if @file and @file.respond_to?(:original_filename) @file.original_filename elsif path File.basename(path) end end ## # Returns the filename, sanitized to strip out any evil characters. # # === Returns # # [String] the sanitized filename # def filename sanitize(original_filename) if original_filename end alias_method :identifier, :filename ## # Returns the part of the filename before the extension. So if a file is called 'test.jpeg' # this would return 'test' # # === Returns # # [String] the first part of the filename # def basename split_extension(filename)[0] if filename end ## # Returns the file extension # # === Returns # # [String] the extension # def extension split_extension(filename)[1] if filename end ## # Returns the file's size. # # === Returns # # [Integer] the file's size in bytes. # def size if is_path? exists? ? File.size(path) : 0 elsif @file.respond_to?(:size) @file.size elsif path exists? ? File.size(path) : 0 else 0 end end ## # Returns the full path to the file. If the file has no path, it will return nil. # # === Returns # # [String, nil] the path where the file is located. # def path unless @file.blank? if is_path? File.expand_path(@file) elsif @file.respond_to?(:path) and not @file.path.blank? File.expand_path(@file.path) end end end ## # === Returns # # [Boolean] whether the file is supplied as a pathname or string. # def is_path? !!((@file.is_a?(String) || @file.is_a?(Pathname)) && !@file.blank?) end ## # === Returns # # [Boolean] whether the file is valid and has a non-zero size # def empty? @file.nil? || self.size.nil? || (self.size.zero? && ! self.exists?) end ## # === Returns # # [Boolean] Whether the file exists # def exists? return File.exists?(self.path) if self.path return false end ## # Returns the contents of the file. # # === Returns # # [String] contents of the file # def read if @content @content elsif is_path? File.open(@file, "rb") {|file| file.read} else @file.rewind if @file.respond_to?(:rewind) @content = @file.read @file.close if @file.respond_to?(:close) && @file.respond_to?(:closed?) && !@file.closed? @content end end ## # Moves the file to the given path # # === Parameters # # [new_path (String)] The path where the file should be moved. # [permissions (Integer)] permissions to set on the file in its new location. # [directory_permissions (Integer)] permissions to set on created directories. # def move_to(new_path, permissions=nil, directory_permissions=nil) return if self.empty? new_path = File.expand_path(new_path) mkdir!(new_path, directory_permissions) if exists? FileUtils.mv(path, new_path) unless new_path == path else File.open(new_path, "wb") { |f| f.write(read) } end chmod!(new_path, permissions) self.file = new_path self end ## # Creates a copy of this file and moves it to the given path. Returns the copy. # # === Parameters # # [new_path (String)] The path where the file should be copied to. # [permissions (Integer)] permissions to set on the copy # [directory_permissions (Integer)] permissions to set on created directories. # # === Returns # # @return [CarrierWave::SanitizedFile] the location where the file will be stored. # def copy_to(new_path, permissions=nil, directory_permissions=nil) return if self.empty? new_path = File.expand_path(new_path) mkdir!(new_path, directory_permissions) if exists? FileUtils.cp(path, new_path) unless new_path == path else File.open(new_path, "wb") { |f| f.write(read) } end chmod!(new_path, permissions) self.class.new({:tempfile => new_path, :content_type => content_type}) end ## # Removes the file from the filesystem. # def delete FileUtils.rm(self.path) if exists? end ## # Returns a File object, or nil if it does not exist. # # === Returns # # [File] a File object representing the SanitizedFile # def to_file return @file if @file.is_a?(File) File.open(path, "rb") if exists? end ## # Returns the content type of the file. # # === Returns # # [String] the content type of the file # def content_type return @content_type if @content_type if @file.respond_to?(:content_type) and @file.content_type @content_type = @file.content_type.to_s.chomp elsif path @content_type = ::MIME::Types.type_for(path).first.to_s end end ## # Sets the content type of the file. # # === Returns # # [String] the content type of the file # def content_type=(type) @content_type = type end ## # Used to sanitize the file name. Public to allow overriding for non-latin characters. # # === Returns # # [Regexp] the regexp for sanitizing the file name # def sanitize_regexp CarrierWave::SanitizedFile.sanitize_regexp end private def file=(file) if file.is_a?(Hash) @file = file["tempfile"] || file[:tempfile] @original_filename = file["filename"] || file[:filename] @content_type = file["content_type"] || file[:content_type] else @file = file @original_filename = nil @content_type = nil end end # create the directory if it doesn't exist def mkdir!(path, directory_permissions) options = {} options[:mode] = directory_permissions if directory_permissions FileUtils.mkdir_p(File.dirname(path), options) unless File.exists?(File.dirname(path)) end def chmod!(path, permissions) File.chmod(permissions, path) if permissions end # Sanitize the filename, to prevent hacking def sanitize(name) name = name.gsub("\\", "/") # work-around for IE name = File.basename(name) name = name.gsub(sanitize_regexp,"_") name = "_#{name}" if name =~ /\A\.+\z/ name = "unnamed" if name.size == 0 return name.mb_chars.to_s end def split_extension(filename) # regular expressions to try for identifying extensions extension_matchers = [ /\A(.+)\.(tar\.([glx]?z|bz2))\z/, # matches "something.tar.gz" /\A(.+)\.([^\.]+)\z/ # matches "something.jpg" ] extension_matchers.each do |regexp| if filename =~ regexp return $1, $2 end end return filename, "" # In case we weren't able to split the extension end end # SanitizedFile end # CarrierWave carrierwave-0.10.0/lib/carrierwave/storage.rb000066400000000000000000000002521230347170300211520ustar00rootroot00000000000000require "carrierwave/storage/abstract" require "carrierwave/storage/file" begin require "fog" rescue LoadError end require "carrierwave/storage/fog" if defined?(Fog) carrierwave-0.10.0/lib/carrierwave/storage/000077500000000000000000000000001230347170300206265ustar00rootroot00000000000000carrierwave-0.10.0/lib/carrierwave/storage/abstract.rb000066400000000000000000000010241230347170300227530ustar00rootroot00000000000000# encoding: utf-8 module CarrierWave module Storage ## # This file serves mostly as a specification for Storage engines. There is no requirement # that storage engines must be a subclass of this class. # class Abstract attr_reader :uploader def initialize(uploader) @uploader = uploader end def identifier uploader.filename end def store!(file) end def retrieve!(identifier) end end # Abstract end # Storage end # CarrierWave carrierwave-0.10.0/lib/carrierwave/storage/file.rb000066400000000000000000000032301230347170300220700ustar00rootroot00000000000000# encoding: utf-8 module CarrierWave module Storage ## # File storage stores file to the Filesystem (surprising, no?). There's really not much # to it, it uses the store_dir defined on the uploader as the storage location. That's # pretty much it. # class File < Abstract ## # Move the file to the uploader's store path. # # By default, store!() uses copy_to(), which operates by copying the file # from the cache to the store, then deleting the file from the cache. # If move_to_store() is overriden to return true, then store!() uses move_to(), # which simply moves the file from cache to store. Useful for large files. # # === Parameters # # [file (CarrierWave::SanitizedFile)] the file to store # # === Returns # # [CarrierWave::SanitizedFile] a sanitized file # def store!(file) path = ::File.expand_path(uploader.store_path, uploader.root) if uploader.move_to_store file.move_to(path, uploader.permissions, uploader.directory_permissions) else file.copy_to(path, uploader.permissions, uploader.directory_permissions) end end ## # Retrieve the file from its store path # # === Parameters # # [identifier (String)] the filename of the file # # === Returns # # [CarrierWave::SanitizedFile] a sanitized file # def retrieve!(identifier) path = ::File.expand_path(uploader.store_path(identifier), uploader.root) CarrierWave::SanitizedFile.new(path) end end # File end # Storage end # CarrierWave carrierwave-0.10.0/lib/carrierwave/storage/fog.rb000066400000000000000000000256121230347170300217340ustar00rootroot00000000000000# encoding: utf-8 require "fog" module CarrierWave module Storage ## # Stores things using the "fog" gem. # # fog supports storing files with AWS, Google, Local and Rackspace # # You need to setup some options to configure your usage: # # [:fog_credentials] host info and credentials for service # [:fog_directory] specifies name of directory to store data in, assumed to already exist # # [:fog_attributes] (optional) additional attributes to set on files # [:fog_public] (optional) public readability, defaults to true # [:fog_authenticated_url_expiration] (optional) time (in seconds) that authenticated urls # will be valid, when fog_public is false and provider is AWS or Google, defaults to 600 # [:fog_use_ssl_for_aws] (optional) #public_url will use https for the AWS generated URL] # # # AWS credentials contain the following keys: # # [:aws_access_key_id] # [:aws_secret_access_key] # [:region] (optional) defaults to 'us-east-1' # :region should be one of ['eu-west-1', 'us-east-1', 'ap-southeast-1', 'us-west-1', 'ap-northeast-1'] # # # Google credentials contain the following keys: # [:google_storage_access_key_id] # [:google_storage_secrete_access_key] # # # Local credentials contain the following keys: # # [:local_root] local path to files # # # Rackspace credentials contain the following keys: # # [:rackspace_username] # [:rackspace_api_key] # # # A full example with AWS credentials: # CarrierWave.configure do |config| # config.fog_credentials = { # :aws_access_key_id => 'xxxxxx', # :aws_secret_access_key => 'yyyyyy', # :provider => 'AWS' # } # config.fog_directory = 'directoryname' # config.fog_public = true # end # class Fog < Abstract class << self def connection_cache @connection_cache ||= {} end end ## # Store a file # # === Parameters # # [file (CarrierWave::SanitizedFile)] the file to store # # === Returns # # [CarrierWave::Storage::Fog::File] the stored file # def store!(file) f = CarrierWave::Storage::Fog::File.new(uploader, self, uploader.store_path) f.store(file) f end ## # Retrieve a file # # === Parameters # # [identifier (String)] unique identifier for file # # === Returns # # [CarrierWave::Storage::Fog::File] the stored file # def retrieve!(identifier) CarrierWave::Storage::Fog::File.new(uploader, self, uploader.store_path(identifier)) end def connection @connection ||= begin options = credentials = uploader.fog_credentials self.class.connection_cache[credentials] ||= ::Fog::Storage.new(options) end end class File include CarrierWave::Utilities::Uri ## # Current local path to file # # === Returns # # [String] a path to file # attr_reader :path ## # Return all attributes from file # # === Returns # # [Hash] attributes from file # def attributes file.attributes end ## # Return a temporary authenticated url to a private file, if available # Only supported for AWS, Rackspace and Google providers # # === Returns # # [String] temporary authenticated url # or # [NilClass] no authenticated url available # def authenticated_url(options = {}) if ['AWS', 'Google', 'Rackspace', 'OpenStack'].include?(@uploader.fog_credentials[:provider]) # avoid a get by using local references local_directory = connection.directories.new(:key => @uploader.fog_directory) local_file = local_directory.files.new(:key => path) if @uploader.fog_credentials[:provider] == "AWS" local_file.url(::Fog::Time.now + @uploader.fog_authenticated_url_expiration, options) elsif ['Rackspace', 'OpenStack'].include?(@uploader.fog_credentials[:provider]) connection.get_object_https_url(@uploader.fog_directory, path, ::Fog::Time.now + @uploader.fog_authenticated_url_expiration) else local_file.url(::Fog::Time.now + @uploader.fog_authenticated_url_expiration) end else nil end end ## # Lookup value for file content-type header # # === Returns # # [String] value of content-type # def content_type @content_type || file.content_type end ## # Set non-default content-type header (default is file.content_type) # # === Returns # # [String] returns new content type value # def content_type=(new_content_type) @content_type = new_content_type end ## # Remove the file from service # # === Returns # # [Boolean] true for success or raises error # def delete # avoid a get by just using local reference directory.files.new(:key => path).destroy end ## # Return extension of file # # === Returns # # [String] extension of file or nil if the file has no extension # def extension path_elements = path.split('.') path_elements.last if path_elements.size > 1 end ## # deprecated: All attributes from file (includes headers) # # === Returns # # [Hash] attributes from file # def headers location = caller.first warning = "[yellow][WARN] headers is deprecated, use attributes instead[/]" warning << " [light_black](#{location})[/]" Formatador.display_line(warning) attributes end def initialize(uploader, base, path) @uploader, @base, @path = uploader, base, path end ## # Read content of file from service # # === Returns # # [String] contents of file def read file.body end ## # Return size of file body # # === Returns # # [Integer] size of file body # def size file.content_length end ## # Check if the file exists on the remote service # # === Returns # # [Boolean] true if file exists or false def exists? !!directory.files.head(path) end ## # Write file to service # # === Returns # # [Boolean] true on success or raises error def store(new_file) fog_file = new_file.to_file @content_type ||= new_file.content_type @file = directory.files.create({ :body => fog_file ? fog_file : new_file.read, :content_type => @content_type, :key => path, :public => @uploader.fog_public }.merge(@uploader.fog_attributes)) fog_file.close if fog_file && !fog_file.closed? true end ## # Return a url to a public file, if available # # === Returns # # [String] public url # or # [NilClass] no public url available # def public_url encoded_path = encode_path(path) if host = @uploader.asset_host if host.respond_to? :call "#{host.call(self)}/#{encoded_path}" else "#{host}/#{encoded_path}" end else # AWS/Google optimized for speed over correctness case @uploader.fog_credentials[:provider] when 'AWS' # check if some endpoint is set in fog_credentials if @uploader.fog_credentials.has_key?(:endpoint) "#{@uploader.fog_credentials[:endpoint]}/#{@uploader.fog_directory}/#{encoded_path}" else protocol = @uploader.fog_use_ssl_for_aws ? "https" : "http" # if directory is a valid subdomain, use that style for access if @uploader.fog_directory.to_s =~ /^(?:[a-z]|\d(?!\d{0,2}(?:\d{1,3}){3}$))(?:[a-z0-9\.]|(?![\-])|\-(?![\.])){1,61}[a-z0-9]$/ "#{protocol}://#{@uploader.fog_directory}.s3.amazonaws.com/#{encoded_path}" else # directory is not a valid subdomain, so use path style for access "#{protocol}://s3.amazonaws.com/#{@uploader.fog_directory}/#{encoded_path}" end end when 'Google' "https://commondatastorage.googleapis.com/#{@uploader.fog_directory}/#{encoded_path}" else # avoid a get by just using local reference directory.files.new(:key => path).public_url end end end ## # Return url to file, if avaliable # # === Returns # # [String] url # or # [NilClass] no url available # def url(options = {}) if !@uploader.fog_public authenticated_url(options) else public_url end end ## # Return file name, if available # # === Returns # # [String] file name # or # [NilClass] no file name available # def filename(options = {}) if file_url = url(options) URI.decode(file_url).gsub(/.*\/(.*?$)/, '\1').split('?').first end end private ## # connection to service # # === Returns # # [Fog::#{provider}::Storage] connection to service # def connection @base.connection end ## # local reference to directory containing file # # === Returns # # [Fog::#{provider}::Directory] containing directory # def directory @directory ||= begin connection.directories.new( :key => @uploader.fog_directory, :public => @uploader.fog_public ) end end ## # lookup file # # === Returns # # [Fog::#{provider}::File] file data from remote service # def file @file ||= directory.files.head(path) end end end # Fog end # Storage end # CarrierWave carrierwave-0.10.0/lib/carrierwave/test/000077500000000000000000000000001230347170300201415ustar00rootroot00000000000000carrierwave-0.10.0/lib/carrierwave/test/matchers.rb000066400000000000000000000223531230347170300223010ustar00rootroot00000000000000# encoding: utf-8 module CarrierWave module Test ## # These are some matchers that can be used in RSpec specs, to simplify the testing # of uploaders. # module Matchers class BeIdenticalTo # :nodoc: def initialize(expected) @expected = expected end def matches?(actual) @actual = actual FileUtils.identical?(@actual, @expected) end def failure_message "expected #{@actual.inspect} to be identical to #{@expected.inspect}" end def negative_failure_message "expected #{@actual.inspect} to not be identical to #{@expected.inspect}" end def description "be identical to #{@expected.inspect}" end end def be_identical_to(expected) BeIdenticalTo.new(expected) end class HavePermissions # :nodoc: def initialize(expected) @expected = expected end def matches?(actual) @actual = actual # Satisfy expectation here. Return false or raise an error if it's not met. (File.stat(@actual.path).mode & 0777) == @expected end def failure_message "expected #{@actual.current_path.inspect} to have permissions #{@expected.to_s(8)}, but they were #{(File.stat(@actual.path).mode & 0777).to_s(8)}" end def negative_failure_message "expected #{@actual.current_path.inspect} not to have permissions #{@expected.to_s(8)}, but it did" end def description "have permissions #{@expected.to_s(8)}" end end def have_permissions(expected) HavePermissions.new(expected) end class HaveDirectoryPermissions # :nodoc: def initialize(expected) @expected = expected end def matches?(actual) @actual = actual # Satisfy expectation here. Return false or raise an error if it's not met. (File.stat(File.dirname @actual.path).mode & 0777) == @expected end def failure_message "expected #{File.dirname @actual.current_path.inspect} to have permissions #{@expected.to_s(8)}, but they were #{(File.stat(@actual.path).mode & 0777).to_s(8)}" end def negative_failure_message "expected #{File.dirname @actual.current_path.inspect} not to have permissions #{@expected.to_s(8)}, but it did" end def description "have permissions #{@expected.to_s(8)}" end end def have_directory_permissions(expected) HaveDirectoryPermissions.new(expected) end class BeNoLargerThan # :nodoc: def initialize(width, height) @width, @height = width, height end def matches?(actual) @actual = actual # Satisfy expectation here. Return false or raise an error if it's not met. image = ImageLoader.load_image(@actual.current_path) @actual_width = image.width @actual_height = image.height @actual_width <= @width && @actual_height <= @height end def failure_message "expected #{@actual.current_path.inspect} to be no larger than #{@width} by #{@height}, but it was #{@actual_width} by #{@actual_height}." end def negative_failure_message "expected #{@actual.current_path.inspect} to be larger than #{@width} by #{@height}, but it wasn't." end def description "be no larger than #{@width} by #{@height}" end end def be_no_larger_than(width, height) BeNoLargerThan.new(width, height) end class HaveDimensions # :nodoc: def initialize(width, height) @width, @height = width, height end def matches?(actual) @actual = actual # Satisfy expectation here. Return false or raise an error if it's not met. image = ImageLoader.load_image(@actual.current_path) @actual_width = image.width @actual_height = image.height @actual_width == @width && @actual_height == @height end def failure_message "expected #{@actual.current_path.inspect} to have an exact size of #{@width} by #{@height}, but it was #{@actual_width} by #{@actual_height}." end def negative_failure_message "expected #{@actual.current_path.inspect} not to have an exact size of #{@width} by #{@height}, but it did." end def description "have an exact size of #{@width} by #{@height}" end end def have_dimensions(width, height) HaveDimensions.new(width, height) end class HaveHeight # :nodoc: def initialize(height) @height = height end def matches?(actual) @actual = actual # Satisfy expectation here. Return false or raise an error if it's not met. image = ImageLoader.load_image(@actual.current_path) @actual_height = image.height @actual_height == @height end def failure_message "expected #{@actual.current_path.inspect} to have an exact size of #{@height}, but it was #{@actual_height}." end def negative_failure_message "expected #{@actual.current_path.inspect} not to have an exact size of #{@height}, but it did." end def description "have an exact height of #{@height}" end end def have_height(height) HaveHeight.new(height) end class HaveWidth # :nodoc: def initialize(width) @width = width end def matches?(actual) @actual = actual # Satisfy expectation here. Return false or raise an error if it's not met. image = ImageLoader.load_image(@actual.current_path) @actual_width = image.width @actual_width == @width end def failure_message "expected #{@actual.current_path.inspect} to have an exact size of #{@width}, but it was #{@actual_width}." end def negative_failure_message "expected #{@actual.current_path.inspect} not to have an exact size of #{@width}, but it did." end def description "have an exact width of #{@width}" end end def have_width(width) HaveWidth.new(width) end class BeNoWiderThan # :nodoc: def initialize(width) @width = width end def matches?(actual) @actual = actual # Satisfy expectation here. Return false or raise an error if it's not met. image = ImageLoader.load_image(@actual.current_path) @actual_width = image.width @actual_width <= @width end def failure_message "expected #{@actual.current_path.inspect} to be no wider than #{@width}, but it was #{@actual_width}." end def negative_failure_message "expected #{@actual.current_path.inspect} not to be wider than #{@width}, but it is." end def description "have a width less than or equal to #{@width}" end end def be_no_wider_than(width) BeNoWiderThan.new(width) end class BeNoTallerThan # :nodoc: def initialize(height) @height = height end def matches?(actual) @actual = actual # Satisfy expectation here. Return false or raise an error if it's not met. image = ImageLoader.load_image(@actual.current_path) @actual_height = image.height @actual_height <= @height end def failure_message "expected #{@actual.current_path.inspect} to be no taller than #{@height}, but it was #{@actual_height}." end def negative_failure_message "expected #{@actual.current_path.inspect} not to be taller than #{@height}, but it is." end def description "have a height less than or equal to #{@height}" end end def be_no_taller_than(height) BeNoTallerThan.new(height) end class ImageLoader # :nodoc: def self.load_image(filename) if defined? ::MiniMagick MiniMagickWrapper.new(filename) else unless defined? ::Magick begin require 'rmagick' rescue LoadError require 'RMagick' rescue LoadError puts "WARNING: Failed to require rmagick, image processing may fail!" end end MagickWrapper.new(filename) end end end class MagickWrapper # :nodoc: attr_reader :image def width image.columns end def height image.rows end def initialize(filename) @image = ::Magick::Image.read(filename).first end end class MiniMagickWrapper # :nodoc: attr_reader :image def width image[:width] end def height image[:height] end def initialize(filename) @image = ::MiniMagick::Image.open(filename) end end end # Matchers end # Test end # CarrierWave carrierwave-0.10.0/lib/carrierwave/uploader.rb000066400000000000000000000042621230347170300213260ustar00rootroot00000000000000# encoding: utf-8 require "carrierwave/uploader/configuration" require "carrierwave/uploader/callbacks" require "carrierwave/uploader/proxy" require "carrierwave/uploader/url" require "carrierwave/uploader/mountable" require "carrierwave/uploader/cache" require "carrierwave/uploader/store" require "carrierwave/uploader/download" require "carrierwave/uploader/remove" require "carrierwave/uploader/extension_whitelist" require "carrierwave/uploader/extension_blacklist" require "carrierwave/uploader/processing" require "carrierwave/uploader/versions" require "carrierwave/uploader/default_url" require "carrierwave/uploader/serialization" module CarrierWave ## # See CarrierWave::Uploader::Base # module Uploader ## # An uploader is a class that allows you to easily handle the caching and storage of # uploaded files. Please refer to the README for configuration options. # # Once you have an uploader you can use it in isolation: # # my_uploader = MyUploader.new # my_uploader.cache!(File.open(path_to_file)) # my_uploader.retrieve_from_store!('monkey.png') # # Alternatively, you can mount it on an ORM or other persistence layer, with # +CarrierWave::Mount#mount_uploader+. There are extensions for activerecord and datamapper # these are *very* simple (they are only a dozen lines of code), so adding your own should # be trivial. # class Base attr_reader :file include CarrierWave::Uploader::Configuration include CarrierWave::Uploader::Callbacks include CarrierWave::Uploader::Proxy include CarrierWave::Uploader::Url include CarrierWave::Uploader::Mountable include CarrierWave::Uploader::Cache include CarrierWave::Uploader::Store include CarrierWave::Uploader::Download include CarrierWave::Uploader::Remove include CarrierWave::Uploader::ExtensionWhitelist include CarrierWave::Uploader::ExtensionBlacklist include CarrierWave::Uploader::Processing include CarrierWave::Uploader::Versions include CarrierWave::Uploader::DefaultUrl include CarrierWave::Uploader::Serialization end # Base end # Uploader end # CarrierWave carrierwave-0.10.0/lib/carrierwave/uploader/000077500000000000000000000000001230347170300207755ustar00rootroot00000000000000carrierwave-0.10.0/lib/carrierwave/uploader/cache.rb000066400000000000000000000130121230347170300223620ustar00rootroot00000000000000# encoding: utf-8 module CarrierWave class FormNotMultipart < UploadError def message "You tried to assign a String or a Pathname to an uploader, for security reasons, this is not allowed.\n\n If this is a file upload, please check that your upload form is multipart encoded." end end ## # Generates a unique cache id for use in the caching system # # === Returns # # [String] a cache id in the format TIMEINT-PID-RND # def self.generate_cache_id Time.now.utc.to_i.to_s + '-' + Process.pid.to_s + '-' + ("%04d" % rand(9999)) end module Uploader module Cache extend ActiveSupport::Concern include CarrierWave::Uploader::Callbacks include CarrierWave::Uploader::Configuration module ClassMethods ## # Removes cached files which are older than one day. You could call this method # from a rake task to clean out old cached files. # # You can call this method directly on the module like this: # # CarrierWave.clean_cached_files! # # === Note # # This only works as long as you haven't done anything funky with your cache_dir. # It's recommended that you keep cache files in one place only. # def clean_cached_files!(seconds=60*60*24) Dir.glob(File.expand_path(File.join(cache_dir, '*'), CarrierWave.root)).each do |dir| time = dir.scan(/(\d+)-\d+-\d+/).first.map { |t| t.to_i } time = Time.at(*time) if time < (Time.now.utc - seconds) FileUtils.rm_rf(dir) end end end end ## # Returns true if the uploader has been cached # # === Returns # # [Bool] whether the current file is cached # def cached? @cache_id end ## # Caches the remotely stored file # # This is useful when about to process images. Most processing solutions # require the file to be stored on the local filesystem. # def cache_stored_file! cache! end def sanitized_file _content = file.read if _content.is_a?(File) # could be if storage is Fog sanitized = CarrierWave::Storage::Fog.new(self).retrieve!(File.basename(_content.path)) sanitized.read if sanitized.exists? else sanitized = SanitizedFile.new :tempfile => StringIO.new(file.read), :filename => File.basename(path), :content_type => file.content_type end sanitized end ## # Returns a String which uniquely identifies the currently cached file for later retrieval # # === Returns # # [String] a cache name, in the format YYYYMMDD-HHMM-PID-RND/filename.txt # def cache_name File.join(cache_id, full_original_filename) if cache_id and original_filename end ## # Caches the given file. Calls process! to trigger any process callbacks. # # By default, cache!() uses copy_to(), which operates by copying the file # to the cache, then deleting the original file. If move_to_cache() is # overriden to return true, then cache!() uses move_to(), which simply # moves the file to the cache. Useful for large files. # # === Parameters # # [new_file (File, IOString, Tempfile)] any kind of file object # # === Raises # # [CarrierWave::FormNotMultipart] if the assigned parameter is a string # def cache!(new_file = sanitized_file) new_file = CarrierWave::SanitizedFile.new(new_file) unless new_file.empty? raise CarrierWave::FormNotMultipart if new_file.is_path? && ensure_multipart_form with_callbacks(:cache, new_file) do self.cache_id = CarrierWave.generate_cache_id unless cache_id @filename = new_file.filename self.original_filename = new_file.filename if move_to_cache @file = new_file.move_to(cache_path, permissions, directory_permissions) else @file = new_file.copy_to(cache_path, permissions, directory_permissions) end end end end ## # Retrieves the file with the given cache_name from the cache. # # === Parameters # # [cache_name (String)] uniquely identifies a cache file # # === Raises # # [CarrierWave::InvalidParameter] if the cache_name is incorrectly formatted. # def retrieve_from_cache!(cache_name) with_callbacks(:retrieve_from_cache, cache_name) do self.cache_id, self.original_filename = cache_name.to_s.split('/', 2) @filename = original_filename @file = CarrierWave::SanitizedFile.new(cache_path) end end private def cache_path File.expand_path(File.join(cache_dir, cache_name), root) end attr_reader :cache_id, :original_filename # We can override the full_original_filename method in other modules alias_method :full_original_filename, :original_filename def cache_id=(cache_id) raise CarrierWave::InvalidParameter, "invalid cache id" unless cache_id =~ /\A[\d]+\-[\d]+\-[\d]{4}\z/ @cache_id = cache_id end def original_filename=(filename) raise CarrierWave::InvalidParameter, "invalid filename" if filename =~ CarrierWave::SanitizedFile.sanitize_regexp @original_filename = filename end end # Cache end # Uploader end # CarrierWave carrierwave-0.10.0/lib/carrierwave/uploader/callbacks.rb000066400000000000000000000017171230347170300232470ustar00rootroot00000000000000# encoding: utf-8 module CarrierWave module Uploader module Callbacks extend ActiveSupport::Concern included do class_attribute :_before_callbacks, :_after_callbacks, :instance_writer => false self._before_callbacks = Hash.new [] self._after_callbacks = Hash.new [] end def with_callbacks(kind, *args) self.class._before_callbacks[kind].each { |c| send c, *args } yield self.class._after_callbacks[kind].each { |c| send c, *args } end module ClassMethods def before(kind, callback) self._before_callbacks = self._before_callbacks. merge kind => _before_callbacks[kind] + [callback] end def after(kind, callback) self._after_callbacks = self._after_callbacks. merge kind => _after_callbacks[kind] + [callback] end end # ClassMethods end # Callbacks end # Uploader end # CarrierWave carrierwave-0.10.0/lib/carrierwave/uploader/configuration.rb000066400000000000000000000127401230347170300241750ustar00rootroot00000000000000module CarrierWave module Uploader module Configuration extend ActiveSupport::Concern included do class_attribute :_storage, :instance_writer => false add_config :root add_config :base_path add_config :asset_host add_config :permissions add_config :directory_permissions add_config :storage_engines add_config :store_dir add_config :cache_dir add_config :enable_processing add_config :ensure_multipart_form add_config :delete_tmp_file_after_storage add_config :move_to_cache add_config :move_to_store add_config :remove_previously_stored_files_after_update # fog add_config :fog_attributes add_config :fog_credentials add_config :fog_directory add_config :fog_public add_config :fog_authenticated_url_expiration add_config :fog_use_ssl_for_aws # Mounting add_config :ignore_integrity_errors add_config :ignore_processing_errors add_config :ignore_download_errors add_config :validate_integrity add_config :validate_processing add_config :validate_download add_config :mount_on # set default values reset_config end module ClassMethods ## # Sets the storage engine to be used when storing files with this uploader. # Can be any class that implements a #store!(CarrierWave::SanitizedFile) and a #retrieve! # method. See lib/carrierwave/storage/file.rb for an example. Storage engines should # be added to CarrierWave::Uploader::Base.storage_engines so they can be referred # to by a symbol, which should be more convenient # # If no argument is given, it will simply return the currently used storage engine. # # === Parameters # # [storage (Symbol, Class)] The storage engine to use for this uploader # # === Returns # # [Class] the storage engine to be used with this uploader # # === Examples # # storage :file # storage CarrierWave::Storage::File # storage MyCustomStorageEngine # def storage(storage = nil) if storage self._storage = storage.is_a?(Symbol) ? eval(storage_engines[storage]) : storage end _storage end alias_method :storage=, :storage def add_config(name) class_eval <<-RUBY, __FILE__, __LINE__ + 1 def self.eager_load_fog(fog_credentials) # see #1198. This will hopefully no longer be necessary after fog 2.0 Fog::Storage.new(fog_credentials) if fog_credentials.present? end def self.#{name}(value=nil) @#{name} = value if value eager_load_fog(value) if value && '#{name}' == 'fog_credentials' return @#{name} if self.object_id == #{self.object_id} || defined?(@#{name}) name = superclass.#{name} return nil if name.nil? && !instance_variable_defined?("@#{name}") @#{name} = name && !name.is_a?(Module) && !name.is_a?(Symbol) && !name.is_a?(Numeric) && !name.is_a?(TrueClass) && !name.is_a?(FalseClass) ? name.dup : name end def self.#{name}=(value) eager_load_fog(value) if '#{name}' == 'fog_credentials' @#{name} = value end def #{name}=(value) self.class.eager_load_fog(value) if '#{name}' == 'fog_credentials' @#{name} = value end def #{name} value = @#{name} if instance_variable_defined?(:@#{name}) value = self.class.#{name} unless instance_variable_defined?(:@#{name}) if value.instance_of?(Proc) value.arity >= 1 ? value.call(self) : value.call else value end end RUBY end def configure yield self end ## # sets configuration back to default # def reset_config configure do |config| config.permissions = 0644 config.directory_permissions = 0755 config.storage_engines = { :file => "CarrierWave::Storage::File", :fog => "CarrierWave::Storage::Fog" } config.storage = :file config.fog_attributes = {} config.fog_credentials = {} config.fog_public = true config.fog_authenticated_url_expiration = 600 config.fog_use_ssl_for_aws = true config.store_dir = 'uploads' config.cache_dir = 'uploads/tmp' config.delete_tmp_file_after_storage = true config.move_to_cache = false config.move_to_store = false config.remove_previously_stored_files_after_update = true config.ignore_integrity_errors = true config.ignore_processing_errors = true config.ignore_download_errors = true config.validate_integrity = true config.validate_processing = true config.validate_download = true config.root = lambda { CarrierWave.root } config.base_path = CarrierWave.base_path config.enable_processing = true config.ensure_multipart_form = true end end end end end end carrierwave-0.10.0/lib/carrierwave/uploader/default_url.rb000066400000000000000000000005541230347170300236340ustar00rootroot00000000000000# encoding: utf-8 module CarrierWave module Uploader module DefaultUrl def url(*args) super || default_url end ## # Override this method in your uploader to provide a default url # in case no file has been cached/stored yet. # def default_url; end end # DefaultPath end # Uploader end # CarrierWavecarrierwave-0.10.0/lib/carrierwave/uploader/download.rb000066400000000000000000000052541230347170300231370ustar00rootroot00000000000000# encoding: utf-8 require 'open-uri' module CarrierWave module Uploader module Download extend ActiveSupport::Concern include CarrierWave::Uploader::Callbacks include CarrierWave::Uploader::Configuration include CarrierWave::Uploader::Cache class RemoteFile def initialize(uri) @uri = uri end def original_filename filename = filename_from_header || File.basename(file.base_uri.path) mime_type = MIME::Types[file.content_type].first unless File.extname(filename).present? || mime_type.blank? filename = "#{filename}.#{mime_type.extensions.first}" end filename end def respond_to?(*args) super or file.respond_to?(*args) end def http? @uri.scheme =~ /^https?$/ end private def file if @file.blank? @file = Kernel.open(@uri.to_s) @file = @file.is_a?(String) ? StringIO.new(@file) : @file end @file rescue Exception => e raise CarrierWave::DownloadError, "could not download file: #{e.message}" end def filename_from_header if file.meta.include? 'content-disposition' match = file.meta['content-disposition'].match(/filename="?([^"]+)/) return match[1] unless match.nil? || match[1].empty? end end def method_missing(*args, &block) file.send(*args, &block) end end ## # Caches the file by downloading it from the given URL. # # === Parameters # # [url (String)] The URL where the remote file is stored # def download!(uri) processed_uri = process_uri(uri) file = RemoteFile.new(processed_uri) raise CarrierWave::DownloadError, "trying to download a file which is not served over HTTP" unless file.http? cache!(file) end ## # Processes the given URL by parsing and escaping it. Public to allow overriding. # # === Parameters # # [url (String)] The URL where the remote file is stored # def process_uri(uri) URI.parse(uri) rescue URI::InvalidURIError uri_parts = uri.split('?') # regexp from Ruby's URI::Parser#regexp[:UNSAFE], with [] specifically removed encoded_uri = URI.encode(uri_parts.shift, /[^\-_.!~*'()a-zA-Z\d;\/?:@&=+$,]/) encoded_uri << '?' << URI.encode(uri_parts.join('?')) if uri_parts.any? URI.parse(encoded_uri) rescue raise CarrierWave::DownloadError, "couldn't parse URL" end end # Download end # Uploader end # CarrierWave carrierwave-0.10.0/lib/carrierwave/uploader/extension_blacklist.rb000066400000000000000000000026761230347170300254010ustar00rootroot00000000000000module CarrierWave module Uploader module ExtensionBlacklist extend ActiveSupport::Concern included do before :cache, :check_blacklist! end ## # Override this method in your uploader to provide a black list of extensions which # are prohibited to be uploaded. Compares the file's extension case insensitive. # Furthermore, not only strings but Regexp are allowed as well. # # When using a Regexp in the black list, `\A` and `\z` are automatically added to # the Regexp expression, also case insensitive. # # === Returns # [NilClass, Array[String,Regexp]] a black list of extensions which are prohibited to be uploaded # # === Examples # # def extension_black_list # %w(swf tiff) # end # # Basically the same, but using a Regexp: # # def extension_black_list # [/swf/, 'tiff'] # end # def extension_black_list; end private def check_blacklist!(new_file) extension = new_file.extension.to_s if extension_black_list and extension_black_list.detect { |item| extension =~ /\A#{item}\z/i } raise CarrierWave::IntegrityError, I18n.translate(:"errors.messages.extension_black_list_error", :extension => new_file.extension.inspect, :prohibited_types => extension_black_list.join(", ")) end end end end end carrierwave-0.10.0/lib/carrierwave/uploader/extension_whitelist.rb000066400000000000000000000030121230347170300254260ustar00rootroot00000000000000# encoding: utf-8 module CarrierWave module Uploader module ExtensionWhitelist extend ActiveSupport::Concern included do before :cache, :check_whitelist! end ## # Override this method in your uploader to provide a white list of extensions which # are allowed to be uploaded. Compares the file's extension case insensitive. # Furthermore, not only strings but Regexp are allowed as well. # # When using a Regexp in the white list, `\A` and `\z` are automatically added to # the Regexp expression, also case insensitive. # # === Returns # # [NilClass, Array[String,Regexp]] a white list of extensions which are allowed to be uploaded # # === Examples # # def extension_white_list # %w(jpg jpeg gif png) # end # # Basically the same, but using a Regexp: # # def extension_white_list # [/jpe?g/, 'gif', 'png'] # end # def extension_white_list; end private def check_whitelist!(new_file) extension = new_file.extension.to_s if extension_white_list and not extension_white_list.detect { |item| extension =~ /\A#{item}\z/i } raise CarrierWave::IntegrityError, I18n.translate(:"errors.messages.extension_white_list_error", :extension => new_file.extension.inspect, :allowed_types => extension_white_list.join(", ")) end end end # ExtensionWhitelist end # Uploader end # CarrierWave carrierwave-0.10.0/lib/carrierwave/uploader/mountable.rb000066400000000000000000000022551230347170300233140ustar00rootroot00000000000000# encoding: utf-8 module CarrierWave module Uploader module Mountable attr_reader :model, :mounted_as ## # If a model is given as the first parameter, it will be stored in the uploader, and # available throught +#model+. Likewise, mounted_as stores the name of the column # where this instance of the uploader is mounted. These values can then be used inside # your uploader. # # If you do not wish to mount your uploaders with the ORM extensions in -more then you # can override this method inside your uploader. Just be sure to call +super+ # # === Parameters # # [model (Object)] Any kind of model object # [mounted_as (Symbol)] The name of the column where this uploader is mounted # # === Examples # # class MyUploader < CarrierWave::Uploader::Base # # def store_dir # File.join('public', 'files', mounted_as, model.permalink) # end # end # def initialize(model=nil, mounted_as=nil) @model = model @mounted_as = mounted_as end end # Mountable end # Uploader end # CarrierWave carrierwave-0.10.0/lib/carrierwave/uploader/processing.rb000066400000000000000000000046751230347170300235120ustar00rootroot00000000000000# encoding: utf-8 module CarrierWave module Uploader module Processing extend ActiveSupport::Concern include CarrierWave::Uploader::Callbacks included do class_attribute :processors, :instance_writer => false self.processors = [] after :cache, :process! end module ClassMethods ## # Adds a processor callback which applies operations as a file is uploaded. # The argument may be the name of any method of the uploader, expressed as a symbol, # or a list of such methods, or a hash where the key is a method and the value is # an array of arguments to call the method with # # === Parameters # # args (*Symbol, Hash{Symbol => Array[]}) # # === Examples # # class MyUploader < CarrierWave::Uploader::Base # # process :sepiatone, :vignette # process :scale => [200, 200] # process :scale => [200, 200], :if => :image? # process :sepiatone, :if => :image? # # def sepiatone # ... # end # # def vignette # ... # end # # def scale(height, width) # ... # end # # def image? # ... # end # # end # def process(*args) new_processors = args.inject({}) do |hash, arg| arg = { arg => [] } unless arg.is_a?(Hash) hash.merge!(arg) end condition = new_processors.delete(:if) new_processors.each do |processor, processor_args| self.processors += [[processor, processor_args, condition]] end end end # ClassMethods ## # Apply all process callbacks added through CarrierWave.process # def process!(new_file=nil) return unless enable_processing self.class.processors.each do |method, args, condition| if(condition) if condition.respond_to?(:call) next unless condition.call(self, :args => args, :method => method, :file => new_file) else next unless self.send(condition, new_file) end end self.send(method, *args) end end end # Processing end # Uploader end # CarrierWave carrierwave-0.10.0/lib/carrierwave/uploader/proxy.rb000066400000000000000000000033221230347170300225030ustar00rootroot00000000000000# encoding: utf-8 module CarrierWave module Uploader module Proxy ## # === Returns # # [Boolean] Whether the uploaded file is blank # def blank? file.blank? end ## # === Returns # # [String] the path where the file is currently located. # def current_path file.path if file.respond_to?(:path) end alias_method :path, :current_path ## # Returns a string that uniquely identifies the last stored file # # === Returns # # [String] uniquely identifies a file # def identifier storage.identifier if storage.respond_to?(:identifier) end ## # Read the contents of the file # # === Returns # # [String] contents of the file # def read file.read if file.respond_to?(:read) end ## # Fetches the size of the currently stored/cached file # # === Returns # # [Integer] size of the file # def size file.respond_to?(:size) ? file.size : 0 end ## # Return the size of the file when asked for its length # # === Returns # # [Integer] size of the file # # === Note # # This was added because of the way Rails handles length/size validations in 3.0.6 and above. # def length size end ## # Read the content type of the file # # === Returns # # [String] content type of the file # def content_type file.respond_to?(:content_type) ? file.content_type : nil end end # Proxy end # Uploader end # CarrierWave carrierwave-0.10.0/lib/carrierwave/uploader/remove.rb000066400000000000000000000006461230347170300226250ustar00rootroot00000000000000# encoding: utf-8 module CarrierWave module Uploader module Remove extend ActiveSupport::Concern include CarrierWave::Uploader::Callbacks ## # Removes the file and reset it # def remove! with_callbacks(:remove) do @file.delete if @file @file = nil @cache_id = nil end end end # Remove end # Uploader end # CarrierWave carrierwave-0.10.0/lib/carrierwave/uploader/serialization.rb000066400000000000000000000012771230347170300242060ustar00rootroot00000000000000# encoding: utf-8 require "json" require "active_support/core_ext/hash" module CarrierWave module Uploader module Serialization extend ActiveSupport::Concern def serializable_hash(options = nil) {"url" => url}.merge Hash[versions.map { |name, version| [name, { "url" => version.url }] }] end def as_json(options=nil) Hash[mounted_as || "uploader", serializable_hash] end def to_json(options=nil) JSON.generate(as_json) end def to_xml(options={}) merged_options = options.merge(:root => mounted_as || "uploader", :type => 'uploader') serializable_hash.to_xml(merged_options) end end end end carrierwave-0.10.0/lib/carrierwave/uploader/store.rb000066400000000000000000000057631230347170300224710ustar00rootroot00000000000000# encoding: utf-8 module CarrierWave module Uploader module Store extend ActiveSupport::Concern include CarrierWave::Uploader::Callbacks include CarrierWave::Uploader::Configuration include CarrierWave::Uploader::Cache ## # Override this in your Uploader to change the filename. # # Be careful using record ids as filenames. If the filename is stored in the database # the record id will be nil when the filename is set. Don't use record ids unless you # understand this limitation. # # Do not use the version_name in the filename, as it will prevent versions from being # loaded correctly. # # === Returns # # [String] a filename # def filename @filename end ## # Calculates the path where the file should be stored. If +for_file+ is given, it will be # used as the filename, otherwise +CarrierWave::Uploader#filename+ is assumed. # # === Parameters # # [for_file (String)] name of the file # # === Returns # # [String] the store path # def store_path(for_file=filename) File.join([store_dir, full_filename(for_file)].compact) end ## # Stores the file by passing it to this Uploader's storage engine. # # If new_file is omitted, a previously cached file will be stored. # # === Parameters # # [new_file (File, IOString, Tempfile)] any kind of file object # def store!(new_file=nil) cache!(new_file) if new_file && ((@cache_id != parent_cache_id) || @cache_id.nil?) if @file and @cache_id with_callbacks(:store, new_file) do new_file = storage.store!(@file) @file.delete if (delete_tmp_file_after_storage && ! move_to_store) delete_cache_id @file = new_file @cache_id = nil end end end ## # Deletes a cache id (tmp dir in cache) # def delete_cache_id if @cache_id path = File.expand_path(File.join(cache_dir, @cache_id), CarrierWave.root) begin Dir.rmdir(path) rescue Errno::ENOENT # Ignore: path does not exist rescue Errno::ENOTDIR # Ignore: path is not a dir rescue Errno::ENOTEMPTY, Errno::EEXIST # Ignore: dir is not empty end end end ## # Retrieves the file from the storage. # # === Parameters # # [identifier (String)] uniquely identifies the file to retrieve # def retrieve_from_store!(identifier) with_callbacks(:retrieve_from_store, identifier) do @file = storage.retrieve!(identifier) end end private def full_filename(for_file) for_file end def storage @storage ||= self.class.storage.new(self) end end # Store end # Uploader end # CarrierWave carrierwave-0.10.0/lib/carrierwave/uploader/url.rb000066400000000000000000000020111230347170300221160ustar00rootroot00000000000000# encoding: utf-8 module CarrierWave module Uploader module Url extend ActiveSupport::Concern include CarrierWave::Uploader::Configuration include CarrierWave::Utilities::Uri ## # === Parameters # # [Hash] optional, the query params (only AWS) # # === Returns # # [String] the location where this file is accessible via a url # def url(options = {}) if file.respond_to?(:url) and not file.url.blank? file.method(:url).arity == 0 ? file.url : file.url(options) elsif file.respond_to?(:path) path = encode_path(file.path.gsub(File.expand_path(root), '')) if host = asset_host if host.respond_to? :call "#{host.call(file)}#{path}" else "#{host}#{path}" end else (base_path || "") + path end end end def to_s url || '' end end # Url end # Uploader end # CarrierWave carrierwave-0.10.0/lib/carrierwave/uploader/versions.rb000066400000000000000000000225121230347170300231740ustar00rootroot00000000000000# encoding: utf-8 module CarrierWave module Uploader module Versions extend ActiveSupport::Concern include CarrierWave::Uploader::Callbacks included do class_attribute :versions, :version_names, :instance_reader => false, :instance_writer => false self.versions = {} self.version_names = [] attr_accessor :parent_cache_id after :cache, :assign_parent_cache_id after :cache, :cache_versions! after :store, :store_versions! after :remove, :remove_versions! after :retrieve_from_cache, :retrieve_versions_from_cache! after :retrieve_from_store, :retrieve_versions_from_store! end module ClassMethods ## # Adds a new version to this uploader # # === Parameters # # [name (#to_sym)] name of the version # [options (Hash)] optional options hash # [&block (Proc)] a block to eval on this version of the uploader # # === Examples # # class MyUploader < CarrierWave::Uploader::Base # # version :thumb do # process :scale => [200, 200] # end # # version :preview, :if => :image? do # process :scale => [200, 200] # end # # end # def version(name, options = {}, &block) name = name.to_sym build_version(name, options) unless versions[name] versions[name][:uploader].class_eval(&block) if block versions[name] end def recursively_apply_block_to_versions(&block) versions.each do |name, version| version[:uploader].class_eval(&block) version[:uploader].recursively_apply_block_to_versions(&block) end end private def build_version(name, options) uploader = Class.new(self) const_set("Uploader#{uploader.object_id}".gsub('-', '_'), uploader) uploader.version_names += [name] uploader.versions = {} uploader.processors = [] uploader.class_eval <<-RUBY, __FILE__, __LINE__ + 1 # Define the enable_processing method for versions so they get the # value from the parent class unless explicitly overwritten def self.enable_processing(value=nil) self.enable_processing = value if value if !@enable_processing.nil? @enable_processing else superclass.enable_processing end end # Regardless of what is set in the parent uploader, do not enforce the # move_to_cache config option on versions because it moves the original # file to the version's target file. # # If you want to enforce this setting on versions, override this method # in each version: # # version :thumb do # def move_to_cache # true # end # end # def move_to_cache false end RUBY class_eval <<-RUBY def #{name} versions[:#{name}] end RUBY # Add the current version hash to class attribute :versions current_version = { name => { :uploader => uploader, :options => options } } self.versions = versions.merge(current_version) end end # ClassMethods ## # Returns a hash mapping the name of each version of the uploader to an instance of it # # === Returns # # [Hash{Symbol => CarrierWave::Uploader}] a list of uploader instances # def versions return @versions if @versions @versions = {} self.class.versions.each do |name, version| @versions[name] = version[:uploader].new(model, mounted_as) end @versions end ## # === Returns # # [String] the name of this version of the uploader # def version_name self.class.version_names.join('_').to_sym unless self.class.version_names.blank? end ## # # === Parameters # # [name (#to_sym)] name of the version # # === Returns # # [Boolean] True when the version exists according to its :if condition # def version_exists?(name) name = name.to_sym return false unless self.class.versions.has_key?(name) condition = self.class.versions[name][:options][:if] if(condition) if(condition.respond_to?(:call)) condition.call(self, :version => name, :file => file) else send(condition, file) end else true end end ## # When given a version name as a parameter, will return the url for that version # This also works with nested versions. # When given a query hash as a parameter, will return the url with signature that contains query params # Query hash only works with AWS (S3 storage). # # === Example # # my_uploader.url # => /path/to/my/uploader.gif # my_uploader.url(:thumb) # => /path/to/my/thumb_uploader.gif # my_uploader.url(:thumb, :small) # => /path/to/my/thumb_small_uploader.gif # my_uploader.url(:query => {"response-content-disposition" => "attachment"}) # my_uploader.url(:version, :sub_version, :query => {"response-content-disposition" => "attachment"}) # # === Parameters # # [*args (Symbol)] any number of versions # OR/AND # [Hash] query params # # === Returns # # [String] the location where this file is accessible via a url # def url(*args) if (version = args.first) && version.respond_to?(:to_sym) raise ArgumentError, "Version #{version} doesn't exist!" if versions[version.to_sym].nil? # recursively proxy to version versions[version.to_sym].url(*args[1..-1]) elsif args.first super(args.first) else super end end ## # Recreate versions and reprocess them. This can be used to recreate # versions if their parameters somehow have changed. # def recreate_versions!(*versions) # Some files could possibly not be stored on the local disk. This # doesn't play nicely with processing. Make sure that we're only # processing a cached file # # The call to store! will trigger the necessary callbacks to both # process this version and all sub-versions if versions.any? file = sanitized_file if !cached? store_versions!(file, versions) else cache! if !cached? store! end end private def assign_parent_cache_id(file) active_versions.each do |name, uploader| uploader.parent_cache_id = @cache_id end end def active_versions versions.select do |name, uploader| version_exists?(name) end end def full_filename(for_file) [version_name, super(for_file)].compact.join('_') end def full_original_filename [version_name, super].compact.join('_') end def cache_versions!(new_file) # We might have processed the new_file argument after the callbacks were # initialized, so get the actual file based off of the current state of # our file processed_parent = SanitizedFile.new :tempfile => self.file, :filename => new_file.original_filename active_versions.each do |name, v| next if v.cached? v.send(:cache_id=, cache_id) # If option :from_version is present, create cache using cached file from # version indicated if self.class.versions[name][:options] && self.class.versions[name][:options][:from_version] # Maybe the reference version has not been cached yet unless versions[self.class.versions[name][:options][:from_version]].cached? versions[self.class.versions[name][:options][:from_version]].cache!(processed_parent) end processed_version = SanitizedFile.new :tempfile => versions[self.class.versions[name][:options][:from_version]], :filename => new_file.original_filename v.cache!(processed_version) else v.cache!(processed_parent) end end end def store_versions!(new_file, versions=nil) if versions active = Hash[active_versions] versions.each { |v| active[v].try(:store!, new_file) } unless active.empty? else active_versions.each { |name, v| v.store!(new_file) } end end def remove_versions! versions.each { |name, v| v.remove! } end def retrieve_versions_from_cache!(cache_name) versions.each { |name, v| v.retrieve_from_cache!(cache_name) } end def retrieve_versions_from_store!(identifier) versions.each { |name, v| v.retrieve_from_store!(identifier) } end end # Versions end # Uploader end # CarrierWave carrierwave-0.10.0/lib/carrierwave/utilities.rb000066400000000000000000000002241230347170300215200ustar00rootroot00000000000000# encoding: utf-8 require 'carrierwave/utilities/uri' require 'carrierwave/utilities/deprecation' module CarrierWave module Utilities end end carrierwave-0.10.0/lib/carrierwave/utilities/000077500000000000000000000000001230347170300211755ustar00rootroot00000000000000carrierwave-0.10.0/lib/carrierwave/utilities/deprecation.rb000066400000000000000000000007351230347170300240240ustar00rootroot00000000000000# encoding: utf-8 require 'active_support/deprecation' module CarrierWave module Utilities module Deprecation def self.new version = '0.11.0', message = 'Carrierwave' if ActiveSupport::VERSION::MAJOR < 4 ActiveSupport::Deprecation.warn("#{message} (will be removed from version #{version})") else ActiveSupport::Deprecation.new(version, message) end end end # Deprecation end # Utilities end # CarrierWave carrierwave-0.10.0/lib/carrierwave/utilities/uri.rb000066400000000000000000000010061230347170300223160ustar00rootroot00000000000000# encoding: utf-8 module CarrierWave module Utilities module Uri private def encode_path(path) # based on Ruby < 2.0's URI.encode safe_string = URI::REGEXP::PATTERN::UNRESERVED + '\/' unsafe = Regexp.new("[^#{safe_string}]", false) path.to_s.gsub(unsafe) do us = $& tmp = '' us.each_byte do |uc| tmp << sprintf('%%%02X', uc) end tmp end end end # Uri end # Utilities end # CarrierWave carrierwave-0.10.0/lib/carrierwave/validations/000077500000000000000000000000001230347170300214775ustar00rootroot00000000000000carrierwave-0.10.0/lib/carrierwave/validations/active_model.rb000066400000000000000000000047341230347170300244670ustar00rootroot00000000000000# encoding: utf-8 require 'active_model/validator' require 'active_support/concern' module CarrierWave # == Active Model Presence Validator module Validations module ActiveModel extend ActiveSupport::Concern class ProcessingValidator < ::ActiveModel::EachValidator def validate_each(record, attribute, value) if e = record.send("#{attribute}_processing_error") message = (e.message == e.class.to_s) ? :carrierwave_processing_error : e.message record.errors.add(attribute, message) end end end class IntegrityValidator < ::ActiveModel::EachValidator def validate_each(record, attribute, value) if e = record.send("#{attribute}_integrity_error") message = (e.message == e.class.to_s) ? :carrierwave_integrity_error : e.message record.errors.add(attribute, message) end end end class DownloadValidator < ::ActiveModel::EachValidator def validate_each(record, attribute, value) if e = record.send("#{attribute}_download_error") message = (e.message == e.class.to_s) ? :carrierwave_download_error : e.message record.errors.add(attribute, message) end end end module HelperMethods ## # Makes the record invalid if the file couldn't be uploaded due to an integrity error # # Accepts the usual parameters for validations in Rails (:if, :unless, etc...) # def validates_integrity_of(*attr_names) validates_with IntegrityValidator, _merge_attributes(attr_names) end ## # Makes the record invalid if the file couldn't be processed (assuming the process failed # with a CarrierWave::ProcessingError) # # Accepts the usual parameters for validations in Rails (:if, :unless, etc...) # def validates_processing_of(*attr_names) validates_with ProcessingValidator, _merge_attributes(attr_names) end # ## # Makes the record invalid if the remote file couldn't be downloaded # # Accepts the usual parameters for validations in Rails (:if, :unless, etc...) # def validates_download_of(*attr_names) validates_with DownloadValidator, _merge_attributes(attr_names) end end included do extend HelperMethods include HelperMethods end end end end carrierwave-0.10.0/lib/carrierwave/version.rb000066400000000000000000000000541230347170300211730ustar00rootroot00000000000000module CarrierWave VERSION = "0.10.0" end carrierwave-0.10.0/lib/generators/000077500000000000000000000000001230347170300170215ustar00rootroot00000000000000carrierwave-0.10.0/lib/generators/templates/000077500000000000000000000000001230347170300210175ustar00rootroot00000000000000carrierwave-0.10.0/lib/generators/templates/uploader.rb000066400000000000000000000027621230347170300231660ustar00rootroot00000000000000# encoding: utf-8 class <%= class_name %>Uploader < CarrierWave::Uploader::Base # Include RMagick or MiniMagick support: # include CarrierWave::RMagick # include CarrierWave::MiniMagick # Choose what kind of storage to use for this uploader: storage :file # storage :fog # Override the directory where uploaded files will be stored. # This is a sensible default for uploaders that are meant to be mounted: def store_dir "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" end # Provide a default URL as a default if there hasn't been a file uploaded: # def default_url # # For Rails 3.1+ asset pipeline compatibility: # # ActionController::Base.helpers.asset_path("fallback/" + [version_name, "default.png"].compact.join('_')) # # "/images/fallback/" + [version_name, "default.png"].compact.join('_') # end # Process files as they are uploaded: # process :scale => [200, 300] # # def scale(width, height) # # do something # end # Create different versions of your uploaded files: # version :thumb do # process :resize_to_fit => [50, 50] # end # Add a white list of extensions which are allowed to be uploaded. # For images you might use something like this: # def extension_white_list # %w(jpg jpeg gif png) # end # Override the filename of the uploaded files: # Avoid using model.id or version_name here, see uploader/store.rb for details. # def filename # "something.jpg" if original_filename # end end carrierwave-0.10.0/lib/generators/uploader_generator.rb000066400000000000000000000003641230347170300232320ustar00rootroot00000000000000class UploaderGenerator < Rails::Generators::NamedBase source_root File.expand_path("../templates", __FILE__) def create_uploader_file template "uploader.rb", File.join('app/uploaders', class_path, "#{file_name}_uploader.rb") end endcarrierwave-0.10.0/script/000077500000000000000000000000001230347170300154065ustar00rootroot00000000000000carrierwave-0.10.0/script/console000077500000000000000000000006741230347170300170050ustar00rootroot00000000000000#!/usr/bin/env ruby # File: script/console irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb' libs = " -r irb/completion" # Perhaps use a console_lib to store any extra methods I may want available in the cosole # libs << " -r #{File.dirname(__FILE__) + '/../lib/console_lib/console_logger.rb'}" libs << " -r #{File.dirname(__FILE__) + '/../lib/carrierwave.rb'}" puts "Loading carrierwave gem" exec "#{irb} #{libs} --simple-prompt"carrierwave-0.10.0/script/destroy000077500000000000000000000006031230347170300170240ustar00rootroot00000000000000#!/usr/bin/env ruby APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..')) begin require 'rubigen' rescue LoadError require 'rubygems' require 'rubigen' end require 'rubigen/scripts/destroy' ARGV.shift if ['--help', '-h'].include?(ARGV[0]) RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit] RubiGen::Scripts::Destroy.new.run(ARGV) carrierwave-0.10.0/script/generate000077500000000000000000000006051230347170300171270ustar00rootroot00000000000000#!/usr/bin/env ruby APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..')) begin require 'rubigen' rescue LoadError require 'rubygems' require 'rubigen' end require 'rubigen/scripts/generate' ARGV.shift if ['--help', '-h'].include?(ARGV[0]) RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit] RubiGen::Scripts::Generate.new.run(ARGV) carrierwave-0.10.0/spec/000077500000000000000000000000001230347170300150345ustar00rootroot00000000000000carrierwave-0.10.0/spec/compatibility/000077500000000000000000000000001230347170300177055ustar00rootroot00000000000000carrierwave-0.10.0/spec/compatibility/paperclip_spec.rb000066400000000000000000000077551230347170300232410ustar00rootroot00000000000000# encoding: utf-8 require 'spec_helper' require 'carrierwave/orm/activerecord' module Rails; end unless defined?(Rails) describe CarrierWave::Compatibility::Paperclip do before do Rails.stub(:root).and_return('/rails/root') Rails.stub(:env).and_return('test') @uploader_class = Class.new(CarrierWave::Uploader::Base) do include CarrierWave::Compatibility::Paperclip version :thumb version :list end @model = mock('a model') @model.stub!(:id).and_return(23) @model.stub!(:ook).and_return('eek') @model.stub!(:money).and_return('monkey.png') @uploader = @uploader_class.new(@model, :monkey) end after do FileUtils.rm_rf(public_path) end describe '#store_path' do it "should mimics paperclip default" do @uploader.store_path("monkey.png").should == "/rails/root/public/system/monkeys/23/original/monkey.png" end it "should interpolate the root path" do @uploader.stub!(:paperclip_path).and_return(":rails_root/foo/bar") @uploader.store_path("monkey.png").should == Rails.root + "/foo/bar" end it "should interpolate the attachment" do @uploader.stub!(:paperclip_path).and_return("/foo/:attachment/bar") @uploader.store_path("monkey.png").should == "/foo/monkeys/bar" end it "should interpolate the id" do @uploader.stub!(:paperclip_path).and_return("/foo/:id/bar") @uploader.store_path("monkey.png").should == "/foo/23/bar" end it "should interpolate the id partition" do @uploader.stub!(:paperclip_path).and_return("/foo/:id_partition/bar") @uploader.store_path("monkey.png").should == "/foo/000/000/023/bar" end it "should interpolate the basename" do @uploader.stub!(:paperclip_path).and_return("/foo/:basename/bar") @uploader.store_path("monkey.png").should == "/foo/monkey/bar" end it "should interpolate the extension" do @uploader.stub!(:paperclip_path).and_return("/foo/:extension/bar") @uploader.store_path("monkey.png").should == "/foo/png/bar" end end describe '.interpolate' do before do @uploader_class.interpolate :ook do |custom, style| custom.model.ook end @uploader_class.interpolate :aak do |model, style| style end end it 'should allow you to add custom interpolations' do @uploader.stub!(:paperclip_path).and_return("/foo/:id/:ook") @uploader.store_path("monkey.png").should == '/foo/23/eek' end it 'mimics paperclips arguments' do @uploader.stub!(:paperclip_path).and_return("/foo/:aak") @uploader.store_path("monkey.png").should == '/foo/original' end context 'when multiple uploaders include the compatibility module' do before do @uploader_class_other = Class.new(CarrierWave::Uploader::Base) do include CarrierWave::Compatibility::Paperclip version :thumb version :list end @uploader = @uploader_class_other.new(@model, :monkey) end it 'should not share custom interpolations' do @uploader.stub!(:paperclip_path).and_return("/foo/:id/:ook") @uploader.store_path('monkey.jpg').should == '/foo/23/:ook' end end context 'when there are multiple versions' do before do @complex_uploader_class = Class.new(CarrierWave::Uploader::Base) do include CarrierWave::Compatibility::Paperclip interpolate :ook do |model, style| 'eek' end version :thumb version :list def paperclip_path "#{public_path}/foo/:ook/:id/:style" end end @uploader = @complex_uploader_class.new(@model, :monkey) end it 'should interpolate for all versions correctly' do @file = File.open(file_path('test.jpg')) @uploader.store!(@file) @uploader.thumb.path.should == "#{public_path}/foo/eek/23/thumb" @uploader.list.path.should == "#{public_path}/foo/eek/23/list" end end end end carrierwave-0.10.0/spec/fixtures/000077500000000000000000000000001230347170300167055ustar00rootroot00000000000000carrierwave-0.10.0/spec/fixtures/Uppercase.jpg000066400000000000000000000000151230347170300213320ustar00rootroot00000000000000this is stuffcarrierwave-0.10.0/spec/fixtures/bork.ttxt000066400000000000000000000007151230347170300205720ustar00rootroot00000000000000bork bork bork Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.carrierwave-0.10.0/spec/fixtures/bork.txt000066400000000000000000000007151230347170300204060ustar00rootroot00000000000000bork bork bork Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.carrierwave-0.10.0/spec/fixtures/bork.txtt000066400000000000000000000007151230347170300205720ustar00rootroot00000000000000bork bork bork Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.carrierwave-0.10.0/spec/fixtures/case.JPG000066400000000000000000000000151230347170300201560ustar00rootroot00000000000000this is stuffcarrierwave-0.10.0/spec/fixtures/landscape.jpg000066400000000000000000005113261230347170300213510ustar00rootroot00000000000000JFIFHiExifII*  (12i\PanasonicDMC-FZ5HHACD Systems Digital Imaging2005:07:18 13:17:36#"'P0220   |\71801006TK   ( 2005:07:15 11:58:302005:07:15 11:58:30d ( Panasonic !"#$%&0100'()7*+,-./012DVEP?DB?AF`GbGdG0fGNGrGtGzG|G~G@GuBGLGGt\GvGxG@RGVG<TGlGpG2nGXGOGGGGGGGGGGGF T?STFFFFFFFFFFFGGFFDD?AEEEDE"JAYYF'FFFFFPXFYJELENEFYYY32YY?WBDD\DDD`DnD<^D;_DDDD"DDDDpDCDD:DD0D\8Dx2D`:Dp4DDpLDNDDDDE EiENEEEEEEE.EtEfEEERDTDVDXDDDDD]XXTXX6X8Xd:XEEEE޶EE!,qYzkuZC@Vq9:u](.+/mv+m7WV5Zm6j\z}IIZ>r\b_SB3|F`8|ov>5!PerCe,un\PRSTFCCVy&7"/N$'-Q t@8".N#(-X94:8"/N$'-cl>9!/M$',kE9"/N$',it1 :".N$ ',P4/ 9"/N%'-I5 9!.N% (-]d;:!.N#  '-T1 :!.M$S '-x\' :!.M$_r (-R :".N$d ',it :"/N$ ',}dH9!.M#'-$ 4<9!.M#1',:] 9".M$ ',Wh@n:!.N$ (- :".N$  (- u9"/N$_  ',C9"/N$'-@:!.M$ ',5I*:!.M$@~ ',WBCZ) lK@ @PTaT) :T 0PP HpgTphTp$hT 0lKh lKh lS020506080909R980100 .ExifII*1J2fizACD Systems Digital Imaging2005:07:18 13:17:360220718xR980100 x!        }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ? WuA)(#%'I8Vq}910Mu9I줺KicX a#2cOmvڔ1B F%rvp#;m ( Yrƥ%-mzԉ:(i ݸ Au냀O"ѫ1qUn->2""fpΠ 1ד8X2ΗZnbY#=۱ͅpYG^~l=൲^t {TRy-cDF ag/wvӡysU{SQƟy#xkT:0[UKu &% d2D g%)%0frXXyiHUf@pF7|}Jck|CkqzO;:FHd7ZFƻۃ^>.'dHx繑D-mA 7U˷BYaow%䛓F_wrTNs5~E7kOvAyĭ&l=A8\徏 `g#9\99 9MjkȜu3U~u|a#ޞcPe*6qKn4채 $E;wdn5kk`*yV Miphi  d7o9P[i qx폿\BAˌ@}=I#eMkUt#oTue׿PzR]^yś}hTBw"ݞ!0i6˷EYFPygFAtb{K>{H3@# ;]XsO#(㪹uq yz8o,3EO]:o~a{lO<0ddvIx7-uЭ pftGQs m&mb9$34J͕21b1>zܐKn5f94trȮO1s 7T\v"RláXZxREլK =_L5F0h3\ UvYVV1 u VpL 3ƒNQe lJQktJqGehP}$A L ? mO<'9$_g}l:]6ĖȓMz̋kf-cq&N0^y~itOs61x`R2TF2B1F=:VjdIېAq)VpJ k@fDXT8ܯӀ=s XQBţä[NOrWrrFt{W3>9!$՘*I'aH; 0x ssGRJ7rrvs:ko 4Ј$f6{MTV[ vSE)<%RV.dV tne"kF4bϴAaw{M>b{_6F"y~d8:y^+U+{ZI~l!;t6qHŊEnbH rLۃ5\Hiouqs,":dDz; 2d[#q,N_pPr\qӨ25 ;G`oʻwl<|a.z>(]Yh0Iy"a5L&.AG0xaⵏn{|LNy~'xD+!wigv!d1w=2؃_Qx_^QLCj>G4-8MwJ9-Dn,pnTT;Kko)jI&hA. p2@$fk*ڕZۗXig؎H;|q.1?2g;\O"Ρ+r)gԧeg (򂤐$#kۋۻy%}WC"K3 I gcT:.]fp뼕'8r0htM fWV3'!xa kyHITF@c9<;fH-CrN1ZT+Cc7MtsMwvZ 儍I Q*㜊ѭ8/.,IUGYpY>2?.L| K-f<+nXrX ;ℂ7EcC;b= `1=qV=\mqbb6DFg+_ls9 -^8u9"5A!<̎vq|7rCխ!b$nŸh!Px)'+毌]ꟴdTv2x Fw,9?|3FǕ,jD̞jN0zg銩<~&xf^h%꥕G#(ZI |_'!kV~=BmFkEE,!b~`83.< ˩jm/ ә&o$".`o-qˡiշt1mG}slVݯ]*Pヷ k 6&sd18ڡzg`V|3Mi;/"W6q+&I-j{yGfix$,&0YXJAmq+Xϡd9.,$ޘ؉GY GUyp^-H~RrɵB  s]|'rٮ짇Rk@+wua$BNw">v 7/˸df:CxSTn0߇1S۠#yZH0v{{<(m/%{yamd#98ăP&EK KKxL fU 7'i#g<ё9+C (تy'd䓷*Vtu3xZGIkwֺ] 3)O20eIԏoq5ky/9 $:lWكg*ax"$'o YC# guw|٩:2eEx4FFx2iVCm߁a\uy8~0ծ<*#էbۤrR8b @/XtS]-S\57)f}nlԼgyq-u+DTbp @&_jR,jW'y۴a*"w+ig /]3@QnH~[F0b2ˌrH7\qxjSi) vE:'pl<6MO0>x:.X@>c54JnBu$G㖧ϣk%' GV>PI,@8VYPo i{<778+c8'׊U89>ҵk]Q.,Q%FX " @aVLJ;6[d2ֲyLW`%@fA<I+ ;;|"]=Nkyo"^0$c .G'3M;Ik̙uT 1 %5XHy.t?ܦj7f lp 's=狗T!$;{[TF(3tq<>Q|3y7nPqnʣkX\BWc;6Y1\wjйhڇ%;k:DJ#Gci%9;Uy 50Os+JwV%fnK89\NCM.\C2<  Kdw'8|q,rj72yyQ0FYl=ưVw8_a;ngŋ eZ+y&ǚDA'UQɯ;Lڶamemd[DJg9Ag-cdc)&ξ-+jGifrE4n7oJ[ם nkhdyTJ7kSzc$7->ԯ(\(IrHGY {+hIaEf8&sJ 4X$p#G brZgoG :Ioqm%DmWȉ(Y *uZ3-?@d8ȎjXybe-*GDy`xSѮ'Юa#"> ),Nױgmtρɫ yaQ F[Slq-#ܣS./~#hC L*sq#\^7L<[{KZD[ b@*P 5冧 [rqIeNr %+]KcȏRppӚ"[G]"(sUկ.<`77dݤ8H m+| &$Yzz"eIi2 "Yr't5].ord&mkV(˓.HT8*u07Ҿ.)&}%[M4%i83O񙶾8<<_w7sz7);VQX{"?'NM;8_:_(F0ߣ {@JcR,[#c]RKdc@v3P=) QQ[P ;^?z ܂c=zyUM>epgke&QUm;Th̸~֛BE˧4 [~,kw:֋THlr:\Qlkcoi[ ke¬dd A'?t Xҡb6R~ҠH ޜTTfIuzl:~g2wm mL UY# 2} @݅գz *ɉY0na=y=ThJt'awε;PfN9{)/[T&im!D-p108beq" Bvj7iOsٟI%Zumۼl`7E;dRCc;Tx[O7,t٥dC*yNl*zwW&kkedn.$l!h%@q3,HɸV,>:+[ZhdF&dH̛ q8 kZ]vkzLZ0K3yg.m )(D=yuHJ)fۘ$VIhe.xݏ\dn*htmǝnxDG$/n3,˧RjJm0K[]Vp#ҾGX7WDִLI*̪ƶ#W$lmRI&RT^$A-zLl7Ip$&L!`1rv^+o/tI~$+Lbv L9 exeȭ4IJZEXǧp]$GV +) 1FN[3qGK]r::M4 9Tzs9EI~ֆS}|NJ`N}2`uE _kFK IQp $3M6F-fV-$A߻R >dͧxO"̪@R v={uvːז:懨{ /K2K SA,~69%o{YK2_s"cO\*0w%ȡmW_=LWþ׼Vںfw2wC*F nx2Fg?0dO Kkm!8l#nJ++!I+dCWMn>'¨i-;̫ǝU س 7rxα/ٶXd4 C #q*c oK2C-5 [\`3vIz#rIԨng T!H$s+iN `w/(hf&In n]p Iw D[-VK'HxmI1ퟣ`pZɮ[V(f7$(w;qq×I5Qq}Yfm_\ZJC&!~f qRkN=ݭ6}h,J\ uo"nn%w6ZHYhCcvvZ,0"4$5IJDXwsԖdK_pio7a~WeUs F'97;ڟ -7Wٖ` I}(J7"u6[Iu{- (8ȝNO^:n{[wLO1p\|&⠟Ln䓯?216oQ};MpZB$MX3kSk [ޠ o("e3.mD9g.6ESn7VF[.Hpq!2FpjʛHu)%nwb@gRq&Z69WB{D fԎG )$|`ih0d.SPkh w33~6p2AR|O#-߱v0V@uZ)3l N̶K}3Pԯ%k;coܩ u1cW*27-/z3{[~ś[/ Zr<Γ[U lNA O;mWɫ۽l[[eBG< k-BԴKV#\~L5I*)@"7%^9oY/"$ϓB`ANvD5F!         !1A"Qa2q#BR3$br4CS %&5Ds !1AQa"q2B#3Rb ?QTeŸKИQ:Oaȷ7c0FpOA9 O-[pqO+;R-i51$!ETlBO{㗣ŨJi26pD(kl=cB\ͯ!#ddɑ7Q+R mVijOU#JB$m6BT?"P7z,TK H6o`Mfr!^i^ZChpzl87:iX:t|%%[pRB꽬MUzy0Wr0.5/>b_dPIE 巔I(YEԡO~6? uj=bniH=V>F8&=VÈu7 x.I )%%e<']~8!.^\N߹q!YyRYqH1R4}!I&ۓ{npB؛̔6t1)*ko 44r\ z!D M~Zܤ.غiwrRnֿ1)~1 Bj$A)lJxI,.mUh$p/9Kx$^u B*k;K )r@j ?8ñj+vc y.8ڍ{[yH,Ǡ0mN4Ľ+*)]bywrY\Ue"<ž8I 'Ȗ:jJ.sS~[H&l6QqI}Ĕt<4-HRu*k_)a(D4Y HCc @  3/^h6vξ[V)$JEm ͝ H7ٖK*!*eRIF$O>2ZtST]qM%6I H}5v=iJD9u _F֖ IUlw)~ aUL"Bu-IYZedsliJm-Ћ;˹J$jvz_#1 *un$$Xf2 aփ8%[)"#b7;b1ܠO %I@ebbjOKMm64Ԕ+Q ; c}3x?K"I.Nj,ӡbw[ T)uZT mSj<İ@}QBJZD\wV8 sA{8Մ:ۀڅ촏X< 1 B0Hʓ$.m(\ƕ%J@}Ga]<),>8v5փFĢ+M.2RN-Xe5\Kq%mº *dGmnVHRGڅE,ʜKb[j$Iif'O)KC\b[ 'Z tOH!IR{lu,Piea(tgce?%6; !C2.AmZԮHRkzmof%xLořu6[PIc ^5||SY0J԰1"{bF]0ʆrOᄈ*?8ʦNoʓֈhN)aUKu)DKYءI͔CEYAK-a!i?)6;\\{O\id.f,ɕEf2kuXiNiHuč()گ̵Lv[LFiiqVTgJqoI%m fuk*Qs+$~W-oo|g!%6Z ;*;` (ɮGߊD-۫$!vP>xL:n{ cjK@[H}ȇ2Ϗ"<xQ_ 8 -t Yn9[Sj*7wh*=hnmYze>mBQNȊ(yMڸ M4Rix( Ra$rU|q?KP_MS1e*8[MH)yƷپ:ѩD&44S6Hؐ\qj#Kf6 YGזW35bZjS],,$؇ڮt\Xw:3N_\:g݄^'QQ[Ȳka`kƦ٘5R\#ǪV.:@Z [BZSnShC*J$ Q_ǃ4P 2NP-N0zB4Q?(R9CY=, >ssMKLɌF`ˤ>#NMi(x^yae)Ixh<. =]#0^M!IN\VJC=jP ];Öj1IjBkA5C_@7Hm,<`83SS'x/C+J_oԆ҃ os|}Rt̜R_S"C{~q0u 52UFf3KSw '-m:cehq:u8ҒM2MCM5c?3lFǮB Spj!%堭/;?N܋zM.PUOjdk wHǽjMkD}B7@Etls\^|>#+QR%I%QK~3.Kꡉe.a${[+`!>QDvI2BpEX\ |GHS3lD9JRWPq7hہm)IiTX0)5 dM-imkN/ΛL+M@,#oaoeEo(&s-*U"[l|[S^0z_%?pPaO<_ϋUP=@ZpiJRMy(whinLE/`l@w$oJ墽йv2@q]AiRHUF{LR}m>˄ŎUsҷ]0]3eDKloԃmGjjDbUUhi(*$4 V┬gͅ='ebjZf D?09qf -AT2>q-][\T |p'5 bRPGy!.+ր,;ߵB!j-)}T򛵑6qW;*r bԸAp% P}* EGJjf,`))\j_`-v-%b4g*tړnjiZlTy'ꑞ,L0Ҵ$G}Rgg!7!ʠޭ`ameEO>V` @$Mo-@c3T+zLWi*Ib;?0Y܄Еԥ 7*T)sCɜc~I e|Jbw :tDR_5lE|UbOSkj6:c`Ѳ̧T}eÎߖ[҆[ O%fĀ/'QH%j,t'V OL#H[UjXD D&D%‹8._JN6%d0랷ZA Tt}'Q؟ 670JZJFI-HصMH kTgGGJBK uWԟ? 9M˒]5PhtlA 8%Pg3P0Я/ 6Jt&־ @GêpGq1NjJ!M:nu b3o>Eqn>%Q"%Ų'?J{ocƸܵF) ̨Pd:oK#._胩zzM_-V%9U!tښW-+%PHj$Fyģ1zFORI@1=@*G .Ky2Jul(- n6<2ijR[CcJVmWHbGcsH/F}X%ji_=o~ؑt*4nLa*8wB uqP5mumԓGAԤ-j ?f̲[[RvhϿ ,Ϝ@4]Y9`D>"*-)z\n #p;-LR BXBaFo>^Rp1!'ZD{6bCuPU9NZw#:C]oc<31KAuhY$JnKs @6Rڈhuĺ{iz1dUկnqZ|ֶBCl߱ ;q|jUQ?>l$ZjhwPiIKa6*7؍06d4RӪ#M| |F3mV6军o? xS/F )Aiңq6ĬJI<)-)Z@>,ILJCSӌb#1t44#%$"XlojC1F*^C΅P6"@"t{bi5GdOvԅ(Xj*/Kq Сk{O8z)-`6qL:ЖP6SnG;Bm!AZGt-Mh J 7'4[W,5l)s.mN.q|vAy~ ڎڦHJVZA{2VM=7)<ř%ޠUӵ6lajĈZltzA#KiH;H$@i*Vmԁ=> kT^~"R^jZwmq5}yrЗ331'yPm@%'nN*e2Z2f;JVR뷸EwX S-;=&3=F*[{r+!JԝW'u\c| PFr]64T]*OSc#Ζ?Z)mOGvi:J\b'U CkI nzxIoa|6˜;L1S\mBOiBHU^'"+hqT5dFq%%K Q_œzsd>PҀE m{|b}*ڷE,gLNZ%.C$)J H$so|U\4~e4p6BVZFֿ‚I@Wq#JR` #`l<к[љ,JNBvQ78: @P.fY}I[H+!O~""l\kVI;'I?׿.Mm/ƶnDcFU8:9s% TWO)U6 2Q%}\oI Xu: $ܧcDzq,$0t<o ܋`1z@JJՠ'W6w~#7SHp*(0 TY҄8R;i;;cqAA})A )POL4 |h7'*c]cN)i}fQV6UYK P:q2}R*lt W4Ad#*8{J$]Ϋ(c`/al}vBSoQUQjyǙ XI;*Ýul/E0fJGceَ?=uLk,%͔IH߁%TkΓsPVæMJeZDXm`OZNOּۋJ]}JYDwǶ"cTXSHCeˋFN\"#,2t$ln%L%F&Tlَ mBO}Ć9昻lr lsW0/A&n\xV{(«*PC[{/tA$I8ڶsUKzʁhH++1k "R5( 4ɷ#|kR# vc6m`>qنG̚=Rd nWDbZC)mĶޢ;}cPimTm9nsIK6P”`|ueF#˛8s5TH5*4ws %=P7ߜME9$*i@X~EzEX49MZ# (yipA|z"SQ$mPA$?S15j{&{oȤ~+]!թ(J@J{n?z<"rԶ!ӈ)i*m|r&<(uTK}:5-Խ *= ]E\[W+J~t k~VK;K5I`q v[HmJk\w&XNl]dpȗQjCSMeYWI`{}\K*Ww4tm0XJ:~҄R 9I̺ĪKNt7p\YDX{rud!G`!"m;0f$у;w8Ե8=$/8BPۋ^4*>KHtZYF 7 "V84tWn>i ?uo%zns eI3ӅXcMZ'7&ǿ}NYli%W)hjrI8 Ҽ'3 ʆ&(SP̲H=7Ho{|c )8J1CěہX$]aLFT0yJlISJ $hUw)|v#ɖ a`Lvʪ(Pa)J~EJ&И8TԵ}s1UA!Q)tT6:%B׿8⢚R]qرPl齃h~|Π̍Կb+j-]FEʆq~rfϕC%,]IQ H[n;qzDekFR !I$i*ICo _) QfSX~%BSZg/vvgn\`l,lňVijߌ.[ђ즐mޖp<^1Sfsd!T؎Ӳ+Rqqyũc\X0UwF*t-nYVV,T@ }bJܚ倲RTw[܌Xv܊=Ekna[GAmh工D@P4{}[fXU 9)FwuĻHQm)q7#|{9g=D"Rd́Ip%,4YJY<#Mm:f7s37*[#G 'Jݰ;_mOOhijEKlHUI:3%T̐V**BٚRU뿠iNڀ?ccj[ H#W~8 ֬7(S MZd$,\'u .JodWuУx 5};i9?!^t4;}[ʟD3?T*aK2mB PUq2Kg4RiGD..5͈"4GPr=ߩ>z{3p_I܄WM8'ljPDʿᲰ~bC!BսAJOmlN8$Ty,LnsbRzqQ[$b^CeDO;G;u<,+rԖ|Cձɺ7J G~ZDdL:VRרq7$$nE®jy~n$u/6#ʁKi+<[|M -4&=(5jk覄Ai,έ7:EA"=̱5,?׭ѭ!t 驫5N,҅*may6؂Sc{nʼnϦ=`,|4"p{^4NWNWz+0`yBTHR7#w;lWڣs͡* Fz]I&mĘۧ Z‚zҺVz$@)%6b `HBHklXP`G$o.Cr[n< lwҒlڵNw'HDc.BCnzm:ޟ.ǁ |޿0Uv]is .GtIGTiUH|C:JRXS~H Nߛ4 x+-yҤët$-,2nl]V$EUb-!KzF{ *ahU1fb] zP۟s(N6߬@JMQ7؟H؜j8x/)1$9e,R19E{TjX dy@6b;.T+5yIydwXm(H{ z D70}7⍴S-MF,*l(\Eʍ3HZͲWZ-,SKnÀ^Q*/jxXa5Qpf;LȬ!LEiٰjS6B@l2*h闟yL%GZ5m3'pc緦G?T`ʐ,lI8.䉴E5QX=56G .8%j󩧉fkŹ2SQ!HQQV퇜Om28\)nB-QsκŚA>Y2jES̖e${3>a;Rr3oȊ8R\ ¹$$9Cowlٛ2T'5ܖȌoұV$&8fM/nU̙uPHC]-_ʜZ7 IǝA/ ,bdʥ66>ZViBHiNrf܀3WUquO0D:\'[-IQŝH@N -F^dd-UPM0Wr(Z:n8`7ʎԕ&]);GÚ + 5-&BqN{\ĸnkql?JjGrō(H@m77AN=mB?+VuHJ֝uom;3L UYjuU Pdm{e:oI' Ru! #XˤTUߝyk4Jb ŬH)Ń.`IM!Np;_^KbHb\J^{Rš|cc42:-Hgݿ-|)o}'LР6EӮƎQFRu l| J+)Yp ZHP_J(&q 2U/\ubO }CSЦPI+X֢H8% l_ҥ-a Jw$A|)A9ʎ?Em"5qS$KOIeiқ[!6pR\DmJRKQڔ{pUG*$0 1l q֝iz> -ez ]3OJ-ꕱ(scܤ_lIJ =mCER-Pm*p$lH;DS5*[(SҒlq{q%=U\)EXgEԻSUKtiD(k")URusnl0xG%#4y]M,6зX$W}d4Q$LFltk ܒO8R1= V6 iZ[dIR@kqͱ 3DT]ǜ1M N0ڤHv$ԏm, D(ocEATՇjhK7CI&n{T@c -{C3E=?5H-](lOŴ,s 9A UB@I""[IXcnl}8q tDKVm $c=#\djʒwFCJQQOǤ `SsKK oo7FPrNd4g#H 7h· SVm͒6RJsNB ҠJm1ʚlKR} 7%6(}R+jQDvF-zcJ!md6۸;IrS{ tHej5^z43SRQ[Y75e@fy*Qh4K muZžIX=>U!c`BMuIfy5ᩗiC(PN-{Hsɞ3 B ]rE-h!ԽOCsN:E?'3M[,͙ Kq&)ζu8}֠m {q#T"ťKZ e2VE{l=7x 2K9S*9JNiS EJmei, ʒ6;؁W>=X6T*Hm‰I4P8OҝZ~n?l1ЩẗVDxC(B[p)Zlx:~n\|`2FJ_kj&Gv*S)O€"PТA [u:[XL԰l>Ucc|y؜bU9߬[d2jhvk5au[.[hlɲxNثLLۗ0(Կ9W1PSRTqPR).^}#9qٯ4jR{\,G%䰍d!$^+> .lj4yM; nNJ JPzMO{cǝrQe6q`v򟙢FpCYޗNBmN\4rdVG14eڕ=j}Kh%@N) vǷ=-;3,MOĖU ElUǎ‚vj,ISj:u~6k5:,9Jye$(-J6=belQ&C(߄bJ.Xb]VL.Lv;7Y>wIרo/t%u;*;ߐBSsga;T ֧fI͎Jn\JrWq6TI;+kqї)im%I.l?RǛ$3O=A2W\ W"=&M$bQ)Z6̳~*+ SxBеzZH{%=n"U`&)*3&::-b{j7UUlW ą9-Rda2A"|{4UZ=T"$ir\,qsVǏn% tQͿ1\{d)FK_!۵H!^{IMLhZ.>"sk/l|6c:ډScS)JlLx#SO$,(mo`âѩhKk]P7RFa,i%O ^LpGQu, NogKQnDlTʿJPO?s P`(B -GqN_XAwF/%Rx%Eh45({^q>mjmuE}Yĺ l;m#+.rvb2#8RZvp l0AQ'yvLVdM@Z%iOѤOOMP$p1.ZI2-Fz2(7V&ֽx[xŨiéٹrEVi*N:l(&$w=J\!>ФhaBN,K6eԭ;FX^3Q" I1ed6Seru_kqk\X=oVK YҥluOm]wM_#e`ڻ!1(>UqH(t D6IOW=78QR%zl4&<5$RVHM-l9 JmZQZ,@7G3e:z>`%mKH7<[~R,joud6t!is{(mF*C=HR%V_ԷHP *3MZمPFyC\*,Ξ._^R\\MjXRZR):Fsuyo>0UW\idm:BB6ja|e-)T 'bmr=?8T8TjHu}$%GN7_9]uyHh_eR Aod{pN&#*Wu0"-!'H쐑s`mVj5Dkj̝u ]fVRF"#RQutol1Ꜹ_ ڄfI%ݰR p14dAtܪedKVo}F׷`R=DnC:rUITRXU~v|yS ewsnBBi9v_!6RVÌ)~3,BɴXӌjo8BJPl&$-7W< FA%y>_FD*$7Q/+yVUOlS*ڭ",FG@#pZ):TM O 1vqG<R2L뷉De: b׋zj eYɪSR_-%)ai}Cb%5dǥz c5)hTZ9mܑfl4מv"3cRtJ!$!$J]v'){8#&ikAĢK1,)GV$܂F_|?H^ULjƏ )M-Rӱy,5c"')@//SZu LLI,-rSP#ro{1g2ҘO2mAi\p=cp_*Mm9n,:bWTJIUӱaA4K) ;/h ua:1A ^0dTJ|t#IѪu:Bkmi/)W\2\y VϘ;7$@aFP^4"z̆ syg9c;~Ge( ֠K۾|@3Ֆ% qc0uX_enKH%C}bF'hIa3~s18>2yƨ*D _ۦ f =:^t,omir/IgejC % X;E2܈<ۨS)Q bp65,OIthT[qJJTMX-ũHC9:TN:w"8ېMnK=) 3RYqkY;\mӡՊjԤ6.Dԧ 7!I#q+A0GQa}K/ P)-8LJQmIJlAW㽯~d,UjU(BSB4kk~;{QKT;6ē>a M:)Zp6=HrcLy)ImHPIH3(N;L+/$[_X e~N9v9Tѥҵ3YABrH kq| 1 b*qW>)m-%%JJ)^g/MBb!Bc:ERN턏h ֖9(WA!A~Pi•:ܣR0[0#JMJ ,&EUi&:|ƮMH6p-#-A@'k⢖O WCX6"kru6_Zں6m#X,}Gr= Oye-A_no[?v!ZBPTYOv,w0FS/>&@!!gI`o|T&/YTmI*W;lDC4#Qb$ƫ-aw@>oa1"dRԸWҺmF:@͵y$DŽKEN=HI!@scX3 :Ts#b2SݶY:@PMh:jk=UI(V[YRouj_y5jrἨKKZSd,@?l%7ɢfJɊQ1ӽ VnwR"?z7I,~.T s|'Qtz`o;c͔6RE ybBSJ7}2$qQfK?I6:juՒh5HxL@,k:NiMgr#. ,]Nu[U;8 Io+=J4F zPqiHPn ?ʢKe>JFi,wő$J%,5qŴ+Kn/7#~v]IjeC:Ӆ '} klw}j[}&RTwLh25) Ck{6ƕBF(fRf ˇKLp;`0. =bz9,3jsU4Kx()յԥ+HWs-ShNњf46HxPP{z;zˍI@ 2x%hmH+jZd%ȭ R W$I#qm D5^9ΧQ q\z W*NH 8t tAE`G|ZqA>&.tjmŖ=%.@ؐjTYio%(_s5I⭢ԸΠwv !vVoH&}&- ]I^C6">\r끔-"$(w.!benj!l6qjGu|fhRDˋuK׭[p bJ1ܷ..ۂQmC},-6?yJʬVaPӭAY)L>V$$H7osُί OL)$^BJR jX"~SrK.JKm-fVX,_t$ nM2d?tM!naZ/~ Ⱦ䵵IyxR%}bTzrZmpq5d\[ BW*TrE9a ,Qa9حG-B]t6,qm5Qe=QI:d:҉\(MƟ@sF(-_̰![k%f[ȓ"zҤPҠ $ Xs{>Kvd) /1 JOIpRv+k(ɟ5.\CD#)&.8u4T-{'~qvdTdz@9jP O&(65CBɌ%СDrnU1PǣIy @BQDrsrn  wDAj`&V]lMKHۛLXi6S2u#qurwctg;Mdf,/Q k,,fOcAHJ!Han9%𒕧I<^־C.l]2r#Z,O0e,k&dh7nJY<*WnȨ8=FS(q/Rx"pjb6;MԈݐC.0QFFx&K2s+^&kۋV.;#Nr,cS:҅BsYHm` J r ,R"‰Ì!)6Xkdʩ뿔6ǿ 0Lgic2YeiC bQ?7QV:Ck!&X|l 38L}(yalе[u7n>N^C߉8Ǫ. {}CҔ'aʼI#9,i.imQ}@H=.,FC8EA}n]cKs$-zPSoQ$.9ﰷl]5M*$j|($zPJ)UIcsGqa2,3*Y[}Unnv=ԉ5ĥpC.8`}II&}g-fq˜nD[MTPS}E>;ƕ+(*,!5~p[}ZE|8ITZ))uTk[:o݉qtۮp)HMͮn.qmsdҰz((![ RK% ._4۴j"Vj /RRl3OXotUc!izB JQI \6 jسmqPeqiRm ܭna./IĊtx\RPHQ'ӧ{ߍ2k~$~ qO"a-3iUv;"c~)Ld( `>qf1 ]x *7{ō9燊h/l?N܌GnOOm*OȟRcTR˲K,!"'n7 14Ji+Zl%EkILb$IJ2;>Vh8BoUoJ[}2[z ܃sn-onky6G3,nťTnS-p>qE+BrLȌʚhJ{d`-E5GOdnWRhot,n!SR$8R BUtU%7E$e 2CT6m8 6\FUb잨A!.oX1'/) “&QSi짂Pq) >Ck.i)qR[ u %$0YT'=z.yYNMYtyH,~&ZVulwPďⓑ=)IHI*SJ6#a=6҇2p\bӕiQw Pc'ԇiy+[0]7,m73dϋ*!^'ΒO|eʰ  U1ǨUcjKqTE 8E:NժQ)V u`s2^fCJ)R,߷azMxԭ.Hc Wx{9 %K= CdXw{2P5;:2 }b(O2^mE: mv!HQUo4A-2Su=imh]6܃ǹ=$%-h96Gy*D[b{+eNQ}#FP>\ 2yTmVs&LKiQ u`H$~D^N+͆#CSsHOq_ ƽ>WRWC2hnY_})_ v^Yn^wSMYXOߏBw*e_0ÚI!HQö._kK_:EPI-{Ic}U)c~gOJжifR)B.k ps~US&2ܒY>lB{ X_ǓH*[Y,f mu6U(=R DKPٝRңcnw'i!Ùrj<)SF= {Xq sa^Hxҟb{aK9i7]nE= ZBe!*JQ\>›P"vZ4CcAR[ǙJJ+œJHBFÕsm̮[vKuۅ.Z CA1jmdL!@mjAҵs_scVJ aOx8t&`5_PC #;-h} 6 *ir)2)IiXؤw@-'޴VP:W%EV5`;VT(jJR Җ)W{cY5H<ȧGUfGuk o*3Sj~)(HZ ab٬X%ުP$N2%i^Cmk~ F;kjiܧgbKM-D&7;nMjW<"HfBn@ #%]1ϬH0¯/)v`ǝ>3ȁ B[)Yhzy7N\a}QZTyQAn+H:R? Jlohq5&R6<䖘t΅ $}$$ X6ZTSmJ7XBt [MbWbM)Jj5[U'q|+;&6L$QWUI씓?%Eı Wv;$P*&-9KuR }opZC-uF@;{\ [HT b+mSSqAKL(ROX!N~<*+uC RrŒHf@ENd!UT 2 o/zJ{l8^iV2[aqJRH',E8=ӝ2RE* ЅP6@MwUTN)]6Hv7{_>_;=)1t*$J 8q_6%1i*:&% m\)-z\$5%`v3S%X PU`|{z$g - uNu=Mmn{ YXyܛ1= (fޞw }_Q[3:A ) $%"\khڌFOJ<eIud b7")yO%)nSI bl-̀54ߌ-@diNOKun4:@+֠#{ 8bH&-Z$FSߔMАRؘ|Y@@F4eHè:o^u{]H;nq&ZY*tR".! @P&7öؤfYʹW6@2…Χ"J!(j; hTW662m5Ym+Nn{n1/G`Jq2UFz*Rh/lWY⣠uonHc@3[Y,iI} voeuNSKKU,?kcR؛f/:z)S@ mqYJZLny}#~L2{C,ݸ*> jffU T:XR/hkI6*/[!e!G9а%n3n46uVǥP3ROre8aQ:6Z87Y/43T$?N˔i:TVGr oB8s $jJ $:LY x;2CMŗl:kJvݰFΎ!^K⒓ \!Ҁ'%q1tayWi? B}KNv_鵷I}rc ǎlI S-Ie" 3.lXWLij@ZcR%$wY(Sbf|-.-]J(=< n3OŦӴa  $RQB.nL-u!ltV[n }$o,Ǧ<)1L޽קanv+\RiR< "RTA%?Q,; RHthS_RVp;\cl~CT>ׄ6!mK@ c*̈́E=mGy ,-EHR@6yUG˾^ fba8#JTܨ6ŘfZ%3 =j7}%gQJ4mG7erP,@rbk*)0ߘqަu@ @Q3C N\&AV CؒlJIԠq9Xn8RqS ScmvHjSsY ɎlӲ89AQ](~f6$#)tRce%$v^L72Zy4~X NʬY*=gg'‡ǦkL -h,;G{/EYM1Pr)ԓCLNq> *R;bt5!RAPÎXGqߜn`c/~[*Kns >VoފAt< R"`#*Y%WDo\QDlA77bIm4%)G+q\RJ@H U{ᢟ1֖zK15 SC6<+|L:4-JNK7 \ ɺn.Ž r몈+U)i! v#-LOFt\FXΌƑ! XT.G܏.;/eMӌĉmل ;MMX ARY5Nrk2]BAw܀+F=Z'3q᧙/{nBvuֲU[ٺm#^AB܋c E߅ 0ǸN>4ID!O'**j7MX=U&i ['їSK 1\OE?'弟z,3bީq䤋 ^@H78}>%W<2_̵5r\p3&RJt2$(G½ GW9QUu<`Tٜ9K4'[ >\iD ND\RE;tRRFc*')ռ.A%)>mZJQӺU )P[}RSc6*rV) B <=xUZJZ\E#̽ ZtM,/`t8qOS+oHOxhIVRI;.Xك'=1#D%%d ~P&2^m[m [K5_|!6N:4C۩($}^6 T""#u)1 #IFnn@cgok8`,2ťMeJ ^ǵ㚄WSEnNn[Q[uܟn1XIPᰧiy!d Px JI}|g$TS )&̈́HZR7qQ߄z%x:ZecHܛ^~;I%PakT S+UX=:p@W|YVOz;fCd$!)R6#7DTQrRJGr,6bVrKI*c)*'dOci%1!iK* swtCrTЩoZ3eXo71aTR!PCM4N4wp0"M<)]UI)I|GtI%LDe8Wi6(o}u\}ks,t%{jݮWɹ's1izSL@H6 {mG ( N #ә-KR䮛ml.'(RgE-ף Qm2q6_bnN՞ tRS˓6RZTJo*ЕSSZO6 Wm"#3hx$nLJ]MP%\MPJ6>~MW2U)r$L`:u/C! &qmlIC( L 1>0SCmCvRԐk[{v|*orZg?s$6e0B؅5zG#ـUgMFd،!oa){)C}!qІBPQ#H$v IJԅGt= BTGk|*6gikq4 ӸI: 1"=B|r&Z (Ye_l\hUVfF.j\7H]pPHԑ*~FUt^}~/nBt- :\eR`bċ\FJYHix7;d]!EϊܪZI*)پǬvAsp"j_ʫMn \+AE߁#K}_̆3 %Kr7MZ \B˕iiL7TdZ60Áf[$~䳙Rc8%eo+^ҝ(FLYQikv QMڻ{;aX6j/\H Uz2Ru6BT}+=I;$n T'H!wO7\^lz*vx)#CNhQ.cTU'J\JV^!"4  ҰAi@}?ϓŏĝ$ebiNqiHeR,zm6$C]&%EqƢ\}cc/N|:d!&Q%V[1$*S*l%J$jQ\py탳A9ENCI`ҍRIx%6(UĒE65jR-[<7u@鶐~@ pU(ReEkp+ԄXRAؑ`NY]+3—~j,`46_6dmO dpBE"[ `{K"7Uƚ]ZS6LZ< rM*=a u~M$R5 [)މay[m:ShJW.] ,V6&86+pӑPJ d!2ZNےt}+!  c&51O,T& p>PO1e9lPv*RД ~T:x|%qRlssێ)5a,.@▅J-#/V' $uu.-Mm.-2 nҔstŭޥ">`1QO^2Cko`=JW|6 Ďfrd}צI[Nk_-&R3BېSvOM?BomݱB2Ѿ ]볣RS0-UAm#}ZVjRGCMWD9ďQ #5O(6GgM^Hn=Bn![6BBIF$JIM)$~3A2Bo=\JlO7?!o}ZerCYO՞yNhi}j 2&.!T,V:p?q=b•Grb>Jt 6 \e|]0(sԿqT5jI?aE?FfPӟzkndxCn@܍AH1a}Fs8j'RdgO. p,oBM q(դʡQaTMn]JS59!kHRc)˷a!JXKMy nF KNE>[,x.k Q; CT*;! X|RͱZP2‹h$'m(F_mj̍E9H_! lB"IN~C=ew߾73ƀ:Qs-7IQu@Uwa9+f\.<\i ^p3٩mT;K-C6 rY;]f;{jŔ'^'b\%COkm>c9q}(D=%HQVqmlznT6bd֜ג?0_}źv]@}OJ@aj k$aw@R6gW#/1VY Hi vPlNE*|ʛ-n{m$97䟽*o *f`H~"!RVcA)oV?/eru>\5)M+PI:Ok}"v@Q I)*1b!ml)붽6waZf^y=.KT}A&EJ,2ivGoVyc2G"9W.,]C0DTuu$n| (iH&:X:%[n pXzɸYU>?X_ B6Du reBR 7EVđWBFTR0A=7)@OQw+ N:+h=!GNrL|(B~c,(RMԝJuQf*UQykKl+l 4o*aY@l~P="]>kHʍʖ 'b:nmn-izy,5uEnE8S:yKUV+%@ b8zL.̆,.VI#{q5'%!<֥H[LOyҰ4(;}tD*|a}07Qd/'5oQltRO&ەDI^UPyOܴ\nF|,9kҴxHR`;>Y1`Je sR0)OYҫ%=7{~?-}Vޔ۲ g"M'HܒM`ȪJ" #uΡmcEWU«|S>e `))yH$wf5fC!-_E.Zߌ"ii]ֻ-o>Rf4CLwx\ oBK[[D5$?cAժ˕ ꖄ%bjn;"X.Maw*%IDU$[zp[Ttʓ*#8*N , ?Rke]%#8Z`=eCMTyt${?l!*YeP( I;ziH?8*xmVq+f;IIPQa{9g9QI|H#c>G*$p{|Y@v l 15Tˆ#)$]ԉy+ҡǜH[Ԛr9v42H`(Q]./k"NNuJQ{X_-XkV*f2;1PVX%io$`*vMV$Dv+2Mi6Xc$LZ# m*j.;$-@SiA$'e鸰 ݠRLh8QQÚ֕qr:fVE\YԻ F\Ti4~[*Pa*]NKoBQ -HN#Q l+ C􄢨].*]H+ϣdnUo6rpX2_PGHV$Eoa 'ЌDf~wIKUzRڕiZX&&'~1H3R%3*qZ,B} 'UR; 9 M=%yLʻ0\a qK (mDD_q4!3@K-%եQ R oq~H66_cJQqJZTl/&a/yt-H]Ȱh5JCKcU]A׫`t܀1Rj)rKН.ȐɲBHrseHe;WsY.D)͒-nwܑHB:*JPvPTubT};$/9+̪7M7/552eJ Xңkav;唤+Z jVOss҄Ncn6 -y]-lƊtcS).M>t!Rbq/%RT . nQQ/Rݓrd4Q_INh"]@[ Qc`V&irZBݎB-$])Kzt`O}p-&S|)r!:[eVߜHaktN[2R\`zv<$%6OZ0|8=>Cf+_c1Vy!rYINopt< XE"5APը YiUU9qJАH6ܕN>e77L֩a/>H&Ͼ=_'Yc.,*3Bad;*jQ$\X dS;z{}9HzxU Ead7JA7dZ/.ː$B4!-{9aRJ~6Btl1Dsn9X 6>aN Gb()v O6QΉ3btLYN0֦Uo.2RPժd _~pωH;8 LQQH8*x[j>'fj@m(9-ޮN߿ypbͨ P&# B6m hB4N,7ig)5K/JmOaA1lǙ;)!N#wB<"1em^*cJ .ZzNdkʐIyJ7A'bk{ĵOVu,hiu wWF{ C#E=XHijt#)hP۹@wT٭oK[RSv(;#'8;r S6rm> fdT$,JGnF{0ө3TVe|/Mk%zoa*Ǎ?W~TJܪLc%q[!2YRIR~, l%6b |4AMFsߑŐjX##cEH"MN(SjVMֵ\X?rOlSjS&JW-RR6!P$3.<tyOYn@bī%V()ho[N&¤!DgSRW\k\[r-;ݝQI笶X\RM"i3R[6Ƕ(ِy(L$ۑ҄})ES|${ x(,y<֘OlW"4L:-/+'x&IK3'T% G}$kIn/x1pdNS)ajSzv UNq](JutN;!@Pjs7Ru̬U){KrDʔR-{EʼnMIZ^7MwrG #MKEV;RNy5''SOb2mKIQ*#}c|#y5Nm`4#AI#R{'b-|O(Ă7jMQbI$$o=~,I! 8GaV%'+6H|`W; o]+L"NkIXrY( m{"GU3eM(FQw{wT(5WV\m*i:ь_KDߩZ߂O{rN8F],/\%Q`6p5MbNqXvSo~d֑ZdYEJH"׸ ۭ]rez]BIkRa[_~6Ûֆ22*F I\?<ĺԷU'IHl}t5\P-|JZ:%\Hbvcd ڏLo& FHm˺H* RbI`{TyҦ܄BnrAQ6UWOӍJoj"%qŢjB(koſ ͪcsTiN(۟U[k WX%G U3 KTHm ٭/: -9Ԉ4:4&!*B1]ZI$$|zdlxJ#~$y}P\6Cٔۉe Tzgǹ{ʌUS MSEd8o'9_NZk#=Mr}`΄OÎVTTo~6y)+!)q6AIW#NM BA73q֌b da--R{m{X)!u'LVDwr:ȸiHܓ6%j(Q|J4e.ܤ)ʘmڔ_,9<͐D8c0 M$ p%cc{j \0ʨ.93@/$Jl3򫐩mւ:A-PZ@&.Mm30myoÙV`O Q6l $EGB)0B7'ͭc⾫|kc\_3e%-ϾtHa '%#\=#- )?2qm;4$Ն6xQ>WewWssNFiI 伴KmJt{bZ(eNS҃E3Xoa[mbqP5;*>g)1s"2,ILñB-6-nVAM; :ifWXTҗUUp A :~Ib}N܏B,fz9V41N8-!KӪ #m1ߚ*56*'dK '=@՘;6jICZQlCj[x BV9,}Xb(ԷB_0%sR"-KKE6!G{0u6"rSHSuo(2`%wd8RNo1_Pad\1%n3θ=J8(^LxJUHZdtP7EVrxP33ړY2TƢ@N0._K]LpO Lٖrs.hbOZ!HSK@=5\% !GN@N9A̳< s̽`-p~2T]U;|nqGK4M.وZFi鄦%DX 9:?r%b|r5fHPR"J K+PR]X<}`VkWUm)mMA$mĝ*]$c5.ku12DpkT\KE9V] t!Du`U*~1eo |Ps-KH#(ucLv "6IroP f&#S}H!D}JǗodǾL]GC_AEbkJӪ6J n3SUGYdRJ7)$o LWE!Lhq&G1 i I+ pA\]?jJƻ2RSn$.}>{AeYaXRl!$ܭW96)EX rCe4l%H7>5 #$7U_:3T#yêrv; GdV\qOCqF +BʈmHd Y:/BȕXWƝ 3︴6A&: AORqμuuņ[R͓G_պw`'ԪSX}&eԷYZet谷OѪ)2-8^WP a@܁ no^__Flc?PU:Vy-eΓ`/}@{}j~UALf݌YLEUR=CoQ6ǡr( DQl@nlC?\S+@JEv  <|IiAKj)?7:o0JjQ`Jmƕėb!H@.]|#ؿQ&;0Heд BI%?'~N#|6Jv".\CZG;Kr#qe]`􍍸Gؿ.Q-ߔ)&U.:IiJyŀ= 罷8?-hRA:OʒH[lgXuUS= 5M:a:)MA( Rp}ϾxkDљ̓%zk@|OƝN,?AVhֈ( F$7%\j!gy[ȱ?%Ҙ2ݪ뤍:K !):czf Ykv8eM:ۺ h4PSQ[ G_ϱ鹪C*yH|Z pGk|bXKU= Šp#0RV~1Lz+g6;Yu1e)9m g/ԪtIB0[bQi"6 Cs۸H"}7&me$`"tZELYHel9C[k6 <0_qu ZRSX""KqBp 9EK#6LjL.]6) o~Mb ^-Ҷ /Ҵ_Q#oaFܥstaKKeV.X$_l=3)eu$hաV tzq^;`@t*D:W*]np},8rSeh.BZFe)$[M.(v8!W^{n T|LxIp0mF^i^q5Xz"[)mRĐ_LA%Ƨi֥f=FyPCGH*tJQQQ,1/Ҙj%D6\Qh(ؕw'T7lLh [I!wۿ~BrLӃmBJV~(q`ڕ%)ZϠ$a"2 ɐذ!itI\q : ˆ+aϾ9M]KOtuvj.% Ѣ;mӤk ~zgçjIl8֔ dW썜sa˙:gS˷h!dj%$쥎I(m]qozؑɗ+yV4l!nPk A$OGϐϕmΚ!HjKqIKuiSKBaPb'Eu,lq~Nt[@9;W[#[h*JiҤ=J JʴDiSi6RϠAXcBjO)b.u|Aε PhkQ[Kk*JV,l-s&- H:^,X {S<{OS1ql70cqhGJCI.½ak%QBRv]RP,KQ06-NT r!RjHET$;*N{JYX\2"(teID=YđΥn~p&tC3@3>ITO}[6+xOZU^oM|=) QP}nieA*UŐJMXpv܋03 ]T1IhAL{H7{|-i֧R$5]VJoU0ZI#@K=RD Ϳc2('|g-a"[l*PJ/=5<:%bU5I.KDA I8__/*W]JnR/42F§ӄT;9kJoEp@o%&' ;ritNk eVk폌"K(A2# uވhZn-kqeJ;wSh.{±ϨE3s$K)(҉ BSi^=4;iQ C6Su&P?{`V'~0R\WDLHj>Bol0V~LtϥyIWQiJp,o\ZA[ģ_2ٍU~LJIni+-ʕkqnq5fzGƢU aJJMH{.67Ń&eDeZ"׈5zLj!tA$9 MYrMXJ# B5CpҦrb;y;se~B֥Sb펡Ъǥ."aĀo*Ͱ-e#hV]mx%8cR)R\MҰ.-u_R&ZLD-7BEodm߻0ZUwU6Sj e gUkX;2ikuuZq]YRXܪ X_oj65"Y)Fk&d^qT/~ǟ"3Lcm# $?hYUߞl˙R5~=~1O>˛ ܞڭx *~J>?8$OTnގw;UslGq~?"*g:njcr"Uj hIlmv{aԺuyMʏl6Tl=B=~QIbD/]E1bNQma󏖐7Œ륥6CAd@w83I( R^q6UuA;TTHԆ[SJmVGz@Dje ZYeDf,T.ʶw2,Z]+-$~PK}4C0FJ ۉPP#sq>){R!kAK%Gc ?6jT]3*饠d⽸LEHҫِ>0237=&ۖWJt=23w)Leh-/qsX^*qr CTdBtwrH 7bl^@OA?VJX|5*rb~NKyTY[MRqH@S1Sw:j)yfBhkWZ__ZRӢnp}m+DmR:%z -; |L ܫLZxiaZV[ d I k ߔd}#IDMg"4T&]fBvIp̆eAJ`Y#p}1\ayDmuHaAHN3Jj-EU60 iUfUIm.sIp-J(bkd!A%k ullxĕmWc9#5T'E}s)>YYjþ 60rsh R[H*I@66M\`ޖg86|7ꤱ.yVԤuѫ* {mf4XGAS;zo}2ДC1+3 UؕRt_]Rc(<0,$ma$w?J2b6Y4 .4厴}EV;kbe¦Dqm8XlwQs  ePG\ %"T *hK0\J:PG ~>q%;ivz R[wCh+>cI&!Qu]K4òi[SRVW7!$=3r$ri(/uY(JM 7okᦴjQG~Y3XdIH\</r-{p79:{/&E=,-ŵ hCi BkVS6 &J\}:{'Q߉BjMėF'Z 7"681[zܚЈ+2U$V 6ܒ, kxIg~5+,(uAR[:6rG)F#68-BFU@nl;RAf.!;˴gpm8fۢBW!j$+H X,OPQaMKdaҺ>**s0YlMc~Lɧ_qB$!(ܤm5컙Mbj.Ծ@>Jmn}6gOiw#1ˤ-|fuHңB@SapHqdhKiq,0e9)KQZգ{cU8'rtמ!U[9F=D>dG@zV{='EZ@CwJ!+u {×"P35,|y!StpIXaJ'~9u4력Rvnm5^'wfM6/%aJRx:إ61qJGrJn(mBw$-22ɱn;E.EA VuvRR@:,Epp2\jk'S)A'_I@ IŘrZaGhŰ9*}:GeGn2ԠF[7Ӷ&znCRj\Ȃ8) dGԄ  & 3'HF锪y]\9 vqA!*n ؆)A*E4vEP Q5Ԉtܹbl|$BҒu}&8*mj D`Pӫ^߸I{\5p[N$!p߽>ZB)Nf’`uR}Ka׶u=JUfZM|§Sn )J|"9%=|=A BLxPPqBO I FHbfcl rgCx!A'R?ަfdVAv]pǀ0kT9Ua̕`$&.Guj*H.o?MgC dbRЀT'8qj,6c>ʉM=nǽoCPTY3Rc)*xA7C>/-QrVV\-@@ti+& TMnKufeRndKK()aBҡɷ8+"5R& Nyj%"}ԝ.?|%4j%z.2d.v;,W\Mmr)y3I̢&jHwz'*`j8- 5J\fZˏ=IVlQiB[akQ()'kw)5ה])B Ro[~_*S4v,% MI䨟ތ1#M؜Q?3Cu:A pV,M%(E QR]@6᤬%${l}$(Y'ied KaˏZNr|UmLiĎXѭnCkmH@s&%Tvƥ6RG;o|;+1U#SOP!Qܕ^p0HAv3侟P"Z%.@$烰ERQJTݖ͚MzvԯҞIR޼C$=z}&Dǵ6Axmw*7;iYZhy$ ÁbA^Ük@:@B0RHn](?AN{mXΪƸ!ıM)Κq)D, 31Eo}4޾X뮟 r:bR%KcƄcp7|zzRUR]>JoLĠ*mE Z" a),uaMy8[.t8Y %-Ke-ͩ+ lllߛqҠ6>&cu|'SknRT ġJ^H \iIf3SէZTnV)!Iv3pO aI̎0 `uزI  kO4U͉8(1xW]WYϘR&ݎs& 5̇ZK ΣN$qn7kX$Uʇ!$O64HÁ`Yc̠Î&jD*\Oַ*'}6N[3Euj!לqHi%z6ۓmX@4 #S88DI&`Zַ Ioka6Ŋ,NTl"f Gy2- n)oN, czFl`~iU]V]zrJcRDZ{9dy(B R q!9O̠ SI)Pk{!Q\yO`(&Ys1K~L(u, xԗʛ@sT.eVTxlHKi  Q~pZL1`EM ;}>`( 5P]p-ǒK a+τIyJB$vU7&EIi%&v76xsoqJ'<ΔZܤ|x,Bk6iW:kzRքAn}#s1Xϑ2ӑJO-}Yip zeUh!@#U܄sm_KQN#,jSϞ at҂M+kaqp/ ҩԈrJf;l FW8Vd>%Jxđr&,ErC tYJApnŏ=ۢCR<&KeĄ.ݖԔ=9C.:I>JR"yh2Mzm۫09O} mz!o \pN72ݨQN(&C` GlJuRKiJn ْCSrFd imGJvԿӷljr5r/Q-ܨe*q:miVJ7{a 6R=@@UNY250hИMSGWU JԂ.4Yb<3 BPьi.ar+mSߨ,8͡˶%I~io!Íڛ%CB/ʶ?a=vtG"Q .ԕ{㪳YZsDG,y)!N$I'dv媾 &wdZQrhYtEŸBeudqTMk| MWsfϭ6˫"̲HJz祕qt513Pr-au(YO~315&Yz;j%9A֋mN$_|ovC}9 . /~w4Fxp]#QPJ%X'l$05SBOMqh)uR9?=|T*\lۃmԇ,]dW^olȧryjm*%Ϲ#qd\T͈7(ҲըPUHv\~.>vȵcnpŖiNud %d-cum98/IJjSف-dG*j[`l\}nqV0\_PВW="A`ԙ򐐥tV Mͮ~ TU 1p>/Ͷ G}1 d8JTIMT~32.EBHP^ j5VYf,w<.; t6~H0f2Jҫm*N_l 4CJۆR|JihIJ}DrMތ KX/9:iħZ ʼn a' 4yH*D. .5~nӰ;N}):]Il|Kums$2:zKX7R|_ӧ_mI[!FBM_q'QgEW'@c:!.0QMq${sŭ%4K2ߩ*p &=;* *1jR@{J.5B߽b 9z\c-<]Ҥ7;\wb[{IqbQdX,j5/9:3iTUW78FLI>5QO˙=g_- $BIgVlxֻb2iHDQdU.45*[$/= n {%'e;-:[nT: A$*nOU69IɿJN(nL RAf/u2t[(N̶\!M(J_֢=64M&zIPx0s-ҥT^bJ%]f] eMeŭ)%+0 Ӕ׺:Q YZòz,r(T̴MRf-2%-k`wxp*LՙMM!ʒ-Ͼ a!7.*=a㻋cL%N[Ó\[&KH”@pיƩND"Ad]A#nl||Iaon5uNRfi˭aD5&rpN*N^NMnHCn Fg%I&=4@Vʨ:s/3h4,,2!Xz`y#/mQ&n9|::e7BV׵MC ڨxeo{Qet59aų)/J॥r~I KyC˾6v=)~1zeTv&S!UN:9 A%לooB(l)k^qsk\2PF

2sYqUYtY.8AmjԒO8hW! \A"Qq*j;o%'Io7-Z nK$Հ`i<W+NebNŘkA<1)sr/>RM*2_e)qvl ?onh]B2)':*-(YD5 O'65Iag򂓩}IĹ,1\E4=+!D$-\;}lBo$H#m G}o =xf I}Vڳ-X\$&l?Qstќ|FjyIPp+>RZkLfYaƢFBr}@ۭ6q]5D{P}v[SVtUu"o}LJ]jN*Ae͒Pi'U>x?3;|$yRZxĨy`X<ÎljؓOPש3ꨧƊILt ʛo.L4X?V YNGHQ b p!`۞׷8F*lKy`T} R?Ax2zF`}#ٵّe0RMY kZ4 Un9| AK%կ:b8dՌ'ӈ +:4$\v'4Ip7_1VIV)Ө7-RH81hHPw I _lW#3`l47kd7a`a6)a'lqH.j}(v'RPBe)gS?HlZ5zRSjNPGWe\H!H_{0@XqPeZLs DMKںRqŷ*c7&N,8[.bܔM?=_BwqPRMvi3Bu{Y# ?T̺{IOE IZҡ7$ į_ 51duz'jCEaRXJEB7O;|Fv,PHb2ZHd{m0̷<(.5-RҢmO+jmk(ĒgQIayZm-܂6U UHy PB`ۇmAwUNL7g31DŽLdN:JVN|5i*jSD !>l6'inwOQ`L3BU?.TءAZw|y\% )Mz:I*@rxDi9_ujcTu:{$zd`_`}G,g'fP@QwӋmۋX&|^^T&:X髥9m|:<6z~bKqmmu$Un/am2d6U*9i\1XkEPޔG: * jLZv۩H$Iێp:yj['I^؏l `OiS$$)LWd(&sBZYNFqÊXUAp [HR6{3e)?}1`vmg})#Nsc|$tkiVRUi-χ<*VE{{v`k*_؊ M {-}^BϖRuzߵ M4pj_Q Eӿbx,9 d]+2rp#M9 \ (P;m|U>O= =EP6=ZǣŔr z-zLKj1cZ^(u2ͥJ 4.դ'GO[K:t:IWOrCh:.`>e) 1fVW 6n,TcێLW첕.n4߿8N-f'(%w}Ŷ(n aJnTVy{WTT5Ϧö ԦJf>6+JԠ~ňB)+ImZiD @``AS24 )]ܣ< ƏW2db~Q_CMMPh#M,$ss}0ofyLrdTy^\C &{a30涤x)Wg@톨 >(e%%-G{^U[+rHz:]J C>yÖ,s>Q\S,R$2!)G12ð3{ HS4 6_=cL',֝r?BlUDhX^Gi7 &@:LJ.\m<}5+翜K @W}3%ڝrd%C6\rk){1uڧU]%ZmZЂm= 8OVuۧwv %b\D#PRBo$ 䋋X=Mm +[q *.\ַ|Te_WP3bڦm8ݒT#SgB1r_Mm"XzO[|N]w46j"ǖm=(B:Wm gTcu%]bv,=q2yx8@Zci%s?8:7"-d$aZ[Ro|az[ ?R{}/R+.&OGJ[q }ս9צ<f%N'ZW#{oW bcYC~fwU/gfNvJQh>)?NhmE߂}sY9vӮ(RLLQ'}q0 #swz]*# HJݗ/c/AnQ" *:u )!NH$^~p_ UU3NZ#Ya7j, <&Q-,)).) 6B3[Tѫ D2JMB< -Vl-rW1ȁW.FKKn[ct`$\d.<[&moyYtL͑iэ>+TR ,>Pc8[ѯf)Si.mj)[cBo[hux6EA?Y,Dxp~G6Tn"WKJ& T,IHU<Z+IR 7 i 3^'TSQ2j=Whݱ[mzViO0ӓZHE@F-`m Uٶk3V݊M6oSVFDo7jL[UWqXEAl#jyeSqzR,[@ qb?+i9Fd;NxHg\Ui69hlzjyuZm'2azR. U;mSd tJ,2u\:7R$ʍT5.*X +k4]rd"AsWH]u $E{_&.OyE$[?Є@ oNRaSIUO1cK8ta; * g:ϛW1GIh?uRR.GF\Pu PobR6RYFiJ%6-טRPSsb{`VNϠƝ)Ԃ4d`.7W0xڧR2\Q%G~i6,'YXyn|l/{R R:Psc*I `إJBCJu7}jsROیC4چCX\w!)>+ǙTN\gMGVjNMM!1K&A+)%? ݮqQY:Ff>.3"Lx킞!$\ny²O?7KU/qQ1BR@T@6HfU:L.%B @} v}獐Tɘki)Fu]5]*mlutr)#=j~Iއ}b OW b? ȞI@܏ߵD.wZmK$U~+#LnWRh2六Dh%*'k}eHB~~l@6(ǃTU:^1ڑ2[:@)ol"V`ʢc)YjBu,N^K*! %_8bS+/ Ǿ!rM:WE< UIvŴ JFaJ$u6}EkpF㵱c3պQƨ)cJ#vl Co%2<.j"\ZT{ вu]ĥUKdz)ejaSvSkmۋmšD6$m?69O%(tQA% 'u Ǿb!$}y~$( P/e`-{d%#+%LȌDH6c{}'ȚO1l-Ǚ.$d.bCܑ{tܾLܶJKl8OL?ӌx4;sf,wkXyvbv**Q!-VܗzAg ]I;bYf% h}O)iMtcraoQ*=QK\k)y8*KФ7`{OLCD0:j`WNO:l6v>O< #&.B-NZCzZ%*F9" j7Z>(}cwld%I|=Up}p>OKˉ D[1f8"^{{DxKy{8)!IQSn7r`=Do%$RGE(7+6 ܋ -FIo05eqYFQ7郹_'ezH窲k[)PJ { Ϲ#%Z]\Cmƈ$:=`}ޡ8FV]B4!58Aq\_9U,W*(>ܦܯķl!a(W8szoLit}% PQ E!H1z3|iY*7667#eʣ5S(&J _<4 (gـҢhqn@ Q&“5*SO eg[PRm*q$ %DrSN 5Ц8E <`MFfbR9J^o|=@Xf3AyUE> S+2[V҉*[O;1JR*¥5)S!i+QVY|v|gr/+H0]At%%(OXZeHbSO7T%#II6*&p9¢e[.ɀۭift$ ֡p4Ƣ1 ̕Ym%jZ-yw7.v=XH$LKͪNEJ,)Ql1üSzTźZPۤV@+6m{W4y"ΏuH}ajHkSkQR]̲;$'φ Y*%|l 1ă3lɘ21iPBϜI@vmg$Bb*qd1w4Z6ܛ@ X-2] >lbOʉ{|aVsSu6J&6Qjõo|))ffjyGYJMh X.Ì^iyS䩵LhP"6 X~oam\ʵ^^1Rx ^wbHejL  1Tް~%)XJSsImB yS2?5spFmp 5\= {1@6*Rm4eX0a1IŴqc 7&PpD unc[Yq J $`>~pjef;]9)w템JEźTʔ̗qKi! 6 o*ˬ͠Cr%NI7 M"矌JVt31\yZuBxj'm;1[66TdR OғbDF0936hqIkԯKiیfsI\JOUntGM wǩfY1 OP[톘1jTd䶖Ԡ5mua.[4ň`~$P Y( }lB_Ueonֽ:.;GD1h2 B:iR@v;MSR[C^}:#|9"\ThJ'VS)UaZj!'pFa c5 FRrSiJZAz ЀmƢp/I"ןVIm;%"\I sMGˍLCn([r+JrsmY)ԣrIN FZgy=4ڂW…'S)k8M,O)Rfiڊ\d\}0>ܕʐ$0CͰY)s<֤]i1 :5 1TvOm?> ]>p}9Ǩym^RS+ZTڒ[wlN ~i*K%BnSa…oY3+2Hsi9SԹn7`}{ہ)(eO\-0lScEp!zE eim=-54j-:УXXUNpm4b Kon !l6|==znC Mmĵ!VP@ݏOQR2bmopwWq1b'ꇲ 2&)X_3ЫUQbMZҷ&oۛcUa|oԽ:SNSYe]- &L_i6Tlc 춪q&>'ayil:GB@o7A7MD㜙mqCwOoܟApG%>IO BtSQ:PĎ_7`&2Ҥ&2֐MܒpOjU2Bi:u3dg[,KPP.]D6 Hخ&N)YYs~Wƕ2Ө*)9"K ;`Y(+zP}L_sFv2ݙ1(SvR 渉s 3QtIFv%Ũf63NK[M8ɵnɹ .j54* Ժ"^L}ꔤQb)n!8)R/kJ{rӯ F3ҐB$9ϥ T;8Jf:u1O-jya+rC-=Kq[p \%WaΊǡҠS I k pl6KzDǥITEyפ-$%XpIno큹2dT3]HTR7J;m3O"K;9^!/m*ZlK\695L[c/QIz VzS'Ldg &8WDG/#tye em%Ŭ=D'H˒ka䈑X8WQk\|(<:vW5#&39PX}On'eŀs}˹VKq,hP%uVO`4 Fv/hsf^]Su8jDP+Yeg){~qR= -!lTHE$w'`21n(Qw35g?ťf7:Ҵ` !)=';f~⳥Z̄W]XsGc'Y6s2yMmA $X?;T*3&D HZQQH/uA`J X!bVF _k$[=ʩu\#A)@~'hUFQfXe8[(]1 ܝYZ Qp%)]ӭ s$ +h 1d`)up#IxjQBR &cxͯ+S>ReinXv$&SJLPS~d㏕2XH*[s MaNak)P:l;sxCM *2r3*RZJنQo¶KmI~NregTG$a 6ON;$a"WD,PJϥXmdLB]mצyZ:m:V ӅG8T.Bf8lT~#-AY?([+o,)z)0nIP .jH6Y v)wO#s|~zxLvڄח1GP%G_7OFP(2EC+lN,#{nq, 8*B-hY/6H>/bvT&eu -FTpDBJ6:SLWfQi#z\%ME:K`eq!'tTwj+VYp+{zy~0`3H+k(M@C :lHblWc~=Zlˮ{/}3AT١ĸ%Mn!7Hu 1is3w!)-Cep%[Xm{;|;6tU~ ):_*NR=%-n:R<ꕩT@l; 訬G8DfL3CVZ (hvPcf4y!(c8#a=RA;͟(x]UU6c¨bM^oU$blۜ鴧I>fC  KQcb6S MEѷeW뙋:3Or|3Ci@?Zo{i4*L Y!d ~P;[ lك&gDTs$]RTOP^N{Kju*,A]%?rNЃL|R58Y*4]VA<Ն-[5u wG|SE!3.U$BDĤ(u{^ h+QWND.iI̺77?3#k3GXȱR*.=]@:Q;skq<9ʳn]1mz.l!mkl&IǚI#J i$jQ6pIs"U)sqb:@@IVG{"cR~^}ْ설rM% np[ԪM0Df5{J`$\ qሐE"*o.C4m  ܜuA>fəPu-iI6V5@9/YDFv6 YTm5-W$e(YIo[d,lMGsLj3f,8JygS`Z<U"0܈\X7՞mudJS4MnuNUZ񞌕]}WPl v|!s;uACćbC$>wn%ט%45 uN!j$vZ 2Qw*LU hP2UʼnO7xHʣs 5/\CRC)bJx<ܪװ"&[ya5P1Jmʵ\fs!T8PХBEHn~+ )qaWkSgM%[lNmrB[lęqԅY)JJ {Qy2\\s$țҮ[\wAm!V;Aminb:,v40RR$ 7D;lkT~KJ0)< HsqnI^al"R"jQ"#(͗asM z OU*ԙ}ت}R!6i &<rx~ S)TAu%eN< QsoGdJE͘Mj?jUf!MKR_yJ ~LR22Lw;>N{b|HnLE혝G..h'p-G 5GT]4M?DIbrߨI{S1 l\=oʓLu \NX\ 5ق $f`,+_N̮ȏ] Knm]7S&*٦:m聡:;O8]$O 4m';;S!*YRnoeiC.-@rp2P/nJB֚M`1ӧt C?E qto'Dʱʐɬ:NnSq6oqWRR56 ۍc md8V.Q;b 3D~ҁ:EqT'Q\cCS5?W[/[t3Z A' kOQ(ZXERRG${\73odoFapS5-HINR-{Vz!9FTwxO}Yqݯǿ^rUtNzMF@aM,qN-[ "-+5C~-[moj ;ut{b! E!?H* ZB$ťaAu&{lqs.S (^EiZ%ECNsi"PF}9kKIZ#JGYOcn[tU*1% Z]ouM0k1,Tׇ5%Cp6F [ jRVmi`RiH6&J &C1D;'k$ZQPOSrin ,Fo̙ѣ>V6 2Qa 2n[RaE+$ |Pi:sw?a̾jO$hCk-mر*A[n{\B[r㲮,{'q.nU:I q UlZaNVـ*C?JP!#6N.N\?9A\@1›$4A6c>q>/S PI?YQ7,tum6lf^M¹Qj&_L"UtS) i] )iO&:IFS6! q 5@1\& JS_ɦf*|TUU/!Zr@|BDNQ<&"cϨ Jz1?ه[+D9 JRP2fivE iJ ulݕ Qޖ9NcR:nUQws}n?gXO>V`VEɹ=*EՖPmtąE*bMCYʔQW) Y, zEZ|cR)u|єڮTc)2_]HHk8Ɓf;! jAO&]TGT9L,%Opq|nNCt&)eN#}~آ&X|?I˴D.j%jq^}bH8'wQQkuBTu8~H67|_8R}y1VD(̑Ԕ>`3+9&3dl!..S[_| ;фm'>Nm%+Sl+ֶs0jXK2K@b|"I'$vƄ< t! XG' 29>EHD )+ЖқD+l.N4ALhëA5CiHHUbdIE8B!){p/ T ~/p MI0Pp@9$a*Vw7f4#H)\Y]Pt*wYm*uJ J$kO\KT2iK BE֤nl67'%U/f) ;0zjtH*ԐnO{_^՗TeԡȲ*;u~D lIK,QƐXJ ǿ WslW Ն+e )tNLj֤͌I[ZfQN*DN(TޗMdyGUKEJ}|`LlEYQz%+ KF`iSJ.7PV1HBMW\@/~Ɖļ"{1:mO˵ h,tq5E>STj+u4{o$"<:뚴Q:@"/uCK#nwcTU*>Kn?Jtq .HYviDclw!Oβ\qLQ2Zou.v)"آm3<ҌV֤FrUZ `=e ^]_|b$$7/3ɦШ)R-R,6C-/j_s#:J/ظaUI\iz],KK;/+\Aid716PRu{@ `#nh:U\ʷ:d;m:%Q67i26cd2%Vp-J%i*'䛎`S8[]QM2&Ф"ŀwy.[UzTRZd'JPصF? 3ЛS~TT[-,!R; %Md IؒpXiqdƭd.:%MF& H;ܑak6]"{`BfCSPpc.g:%Nq,ak̒r{~k7U r\E[M~B\Km;r,7F\~>guZtiߤĀOC7i;2mAd).U$8OU;!ZըnN1mJ>i:*0Oa8QʺFLڎѶHrUQ!J$ v9[݈˟Y.HD', Rm.ZDiv$F@R]q 8)gWI٣P2|wY{R$IrvM1ѩ*ueM*ZRtuxHة:3\nBp,U^ɽ(T³u-yF@!gKmD72wA,5L6ִw*v$ o  7.֧LUuRA:eVt M^&68Sj(Re[9c2 e'QIm 4xS C6mƔV+)Pw5[ [Cq](a%WYP1i[y2Bu 7 {qMej#ˋj"-A* nSm楖* g[};)J n|W(-):RH$wGݗ{ % ?k}fB/#;7JEwl<ʒT6c `$t&t%*YwlHYg3"h$]ե7XiƷwg2J$7[Fu=pmӾ6y-i^o|oMSQԡJA>O~x"MW[\vJD#df"!7*I2'>aKRH6<2:Ky$ c|K(wՐTr̥BQJ'@5PČ*5-2ۉi 7锍{wb,MJyT#QBdPv@:nmqW#~u)LXFNU%[qŊ4p4B`Nouj|vZK֑uhYm )Z;8MDŽdkw,xϠT4v7r+zvx -%fc17-l0l5sse] #ej FTUB˗)&* pSk}lL!Taiٌ8}1a-)J c}>ؕ2!5`hٴ4JB#rTҭZH&װߍr۩lC〡_MxxgGA>>+4 s;Rh7EvڪpԉU ½MtBo5YfN/Vـqcc$:BPA{bI|mMnRvw8n$c?3i}B:l&8TP6biG##BTk,ML* t_9RʘZ a4O - ̚(ڃdP܉((PJ'Q\G՝(SU*4z4\J_^c-(FUf١[vTVZ@w ѣիb2):)R&JgzR9Ena3^m9KVQ+2gG}fΦظۨv֯A;'>f4-ICm b@=ΩhioytPyuR$_au{[^e)}-Y) pM=꫍]!n Ċ)gv|Rhh%@<%#KPj0cmB I݀- ^j$ :^|.̻o)G]5ԍ쁸$&ֿ'serSTb%i8 {[0kŶK52i7fMy+F ({(eYFl\#"bh\@M.ḓ,*(>TlH P@6x4}P e.[V$H&rl*^q!SA_l^ ؛5<+v!ʕ}!-ia!:b\)k჈^)4&\o&np}3ulEr =ɨ<)V[ߌ#(\ہ0RcJt$X#m-8ADɅVێMMqnz1DmWI SY$@#3 0rR{:5)zw$+8|` RPrJei3ԚKpG]o8Pg^I!o%bR\ KhP E5fFIi4PO؍s%ZVREM܍Ô8 9uGݪ;QK*obl>,+2I&s2।HZPXmPZ+jiar5$8?BOn܎PC#CbH ZooK*lq= w.& N@ܩF˴Tb=PnPaY1Ύ6Os|bTJ|*%v0qm?I'un ϙJ|"!rE{`=O]o0-$~8dڱ[ϮMU(BP겊Pt;-l#-zsbT4O26RV]O[a%!t~JRO̷1S)O9vһ Xoql] * 7>0u:VYcՊxsJ:Vp 9^I^Juaafv6bA} dz>.{ Q.=" 6N};!'$m磐Щ '>(luj~[ /? b\S@K?(iտ ԗ7fr%6,KBH;dP̸ҁq%nY8j3+ѩTgOOZT.)Ytw킳JCL>Xt=UFȌR5詁L-CPo#sUJrURp sŰT'o ܸU;)zH؞w]gR7Cnt[R=U@u9X .t8tm߶SaDZCSyg~h`omP!J;جlm.Uԟ W\kH$ܡ{0v(0Tȫ:8qx}*{1_SU]>Iٶ$W6 8 F>6H4uJaH*[HP.'`tsShY5yJm丢G{musf45PMO^XTum?}cb %+]uiwoqZ@"V@Ԓ,t؝ =ӫUEBcֿ|9AOS6 ɯ-Ǜa'F4N95<>`JdlZP F "K)o8FzcRP[G,\O;|@m$w;!!K+*Rml\Ƴ(BzRUSMI=Wٹ4 PFSnneCnn)tM\RM?gkn\ Wi-%$snI8ṹ(J/%*>F˩m.nfnb߷ø;,*LOan٥4-`xf܎1;2"V\blߘ||4P/έΣ t i4({l70 of.*ԉ^49Hib/RgLnJomF#l͆&Qy1L/[q)md Xl.pбL вqN5%~SL"NOaccd j]R).<lkWqVebsMtB2SļA$=Hii3J*vSʒʹƚZzd zaf!CIЄ-jFR k`6dMvl%HH)6¥%)u! $/Y?cy;)-7\jfWSӦ-k$ c u?QB>@ Uol)=!/c${nanQiAlH)Y-H$|uw$j`SYU2Gkȶ|qJPÍ]$zF IGVRvGWe @2mt~f'Qڀw#tǃ0P0pv߁Ҫ.-KR.trmLuM'5 lR}6?c^WG<2JR6!$%M[Z7dl' `ٛ+:Lə_&OǦRp\ iValeœL5mxҒr>ۍNؖX2?X%KqjsJQFZY#ҋ u[JJ,7U}J ʬ:MYOƦSeZi(ثB^.dEud:"?BOlxm) (<*j(4HoR TG; tn梅6I% QJBƒN׸1Ng$w'V"!N0鞅‰'~OC4ms߆>ㅕ)ᷥg9|zY.;J_uMC|425J1%oc테ʶtv$J;I`6"{mrb!uXbG1TkSI(TV}:9R q%鵖$MVEȿ\P䓼 DYS sB QDt2H> )؀8E'*дxCHlA.kmh%sov˓B!}n>i+: ܋nhOe* 駹H{@YVy$-O^R-*QS?kOLȬȔۊaHSR)CmCxJmdd]r&ҟKZ\t r;lXBf.WE=c50ҋARmZvNr@vaYis)^X06Rĉ!ΰH B͉gPe6óШ-',Uw3+//[N(Uʷqd)o.ҝj*lݠɣk8oo{McUM/ ›yzV ؂v|hYʕWx칱6$Lʓ-Ēze \ o+T4. W.ΤStǫ ˰P;cb˾(Pr-=J@P-vj#WZɜox 6e͘-َ64RJKZ۝#9:쪚+K=E8wI|{ӷ4s> "os q0ifNRK!G#:VG9qS Jnu+~Mm`?Ԙ׬"JfosJo0K-< ,%(@ 4 Tjy(@To* q[%i)&{l\\6fs U(HzPejCttXH0`da2{ކq'龟s$oYj޻*)j*C0앤P !)@#q®Mq[-P$ {0458*kܪӇ>+>i]HPQ{IMf}1&HaҨCQI m7$~JV}3;x)ՖpRu W~7VëM,!gW#lz8=PiD- QrCQ8݊}DOk`FDtc1KmHcqaظL:RS]^/r{ $2$Ǝ!IJ^ xwnb-*}~cWAd˕ţQPzKD(TqTjUPQXI+o;Sǒ%ܷ]\Cnj+?!_hW 0sS!1V6ޓo` Ш%R!e[DIWГ[n0)e ZVw ǰ̑\hI gUqseh (Ի| 4 u\|93$1]n6EaqÜ1f,Kg(6;7ǰa wQf+7驏"(Rө!Mi#F?O;Uڥ Q ",r(J:ScX)ؘ,7sY)&"GKEE%J:d#˹[!wr Ed77bҩ)Mqbtni< Ň8!Ù'Q0Re[qm>H0x-NQXa C1,^t$6tyj6Qjl(YUbPA#n%L}J%7 HSgBɾޢmcy6Gm1*[J}N϶B6播?U N*="p%$!:%)ҝ' A{u \H&(Fm6'4\D,9,o00t[@$| BQ׬Lz%Ԫ,8a:RJWܟk R3ht >\Tc (MV7|`Lɯ=05RGқ8uaў!;}Ɵ5GI;yC/rh-m'~q1R_OYҕ)JRkBS|Llʘ.liӘYyp6mqG$gfH]* @#w$/Zn((^LyZIMʗ`Rl1$TswM"K1jmk ?Xʞt(|Wu)\wgf:~BgFQ-rPFX%+~fc4|t>Uc32UZKkM_s7fџèdf-&,jzCQ )\ {X\p*IUgۛ H7USwir [oH@ ߜ"gM5NDZv7 \X"߶:e:T!%hjԻF|5t4PeMZmKI!n u P4Ѳ_] ~KPBYH#6"$J_)JyN܂8"t[Vn5X@oٝ%IROWUB ڂ, `\Jd܎?)I 8:~h+iP{qVGTrn &td@]딺SM%mlN^'Lu++%SRCQl[C@qBO-t0Z v7?ּD)3V6[ҖBM olzVLj2\iEuܢKRu[k7On{Ⳓ#Ǧ?MGOM7ks=c%x\T_պl%k$8GVaf*}i_zNHc/I*U,焵~v^!jx,e6W@EQDYTIW )JJSQ\2q!/Iik&n,;6#U)*RnI`OAgߎ86CzCJJ tg&v1r0Lm23_~dJe*r?,-a#HJIP:sn,qVm3kIL1Fnڒ )@qmUVdL/!*"'Ғmۿ 9uLȵO&'H`9( (Y'R?k, UYPzEWmD0o%چlvCdI>H'S% ~ R>7dh{lE2c#1<҄È@ $)BN+*5BE1e%l { NX3_^1,˹+$9;5iuGV 2=G qqnMtr]=ҞH Ql[M-L,3fjjH*V1[L[]iڍ9،=E-V$ll{pyEItBE^YGU7]gkcHyz-%QC{\mm86BӾV9oIKhRP{!'VArz-())T5MʙI9^MDRS,4돟xҟHYϵZT*j ix)wmgW:g"e-Ci!ߛRN[Y[_jaOet`vMFQ;oC(O2xS&*|^i,Sr9 pZ,@hRiII}6&ɵ;nw68WP͝Yr/8-x|H͔з}]j Hr-jfb/Iqjy@٤!(HM$p/.Kq|Ja?3Ix톝OX3T+6; ̯K-#|U뉂Zi.Nx7wRPʞX8mSmk)o,CiSZD% K>'!:cUX*-S%saLR%Dif%6S= ap 2PTsҺe* RaN"#dYS)]܄;ڍQ̩NY+T⩅:}Ht n9+U3֢8v+l5j H`'AAs izmROl@q^]xL͡JRt Q1[V:LƠOO![ҖiWCҀwԢ/>Bt `TDU!W'V}%7۾f AQ> fZIu _ƄR tsMjlRAcy+,x.EA] Y67Cd fLCg9;*ԫ9zEM.i? YNlSM\8ˋ2OBʭ+9yahVu+ajw4vb͵O3k0TZ-" quX_ KU|6ޓG|,->eQSr$79jEzK.5ҥ(k7 FFm]1&SjQR/w&a koKfF0˨Mng|j:Tp <_LCl" 2]-Gq{v?HEoDJ`XkizJv]- JGRF\lFؽJq3Q=$6l}`cb1ʻQXaTOTҖR['ZI'JqAҪ0è/MN NqlL׎UyjzCLQiMԥ)+Z֩m%E]Q@SH$YD|P|{\UKo-u<SpO6f 'sa# 8 oq D5ujlu$.IWmsr3Gn;/b"WttD*~km|:[KYeE,S=bHJ\1 >R}e"/<حH \2@qDm ZDTL)C(;}e\iNޭ 6ZIJ\XVW0DX%6a~:@Z&,Vlo~;RJیN58Գ<{ ` Գ"Pnc ;{UIDSYYHFn$qкM7B g5 3Zsp wG$@{v0S-NpI+p,7>G|OѹVb_obmBA #JHJ9 Juo'se;$e)_q(ӭJTI.n{}ʎSy5,Ozk:+ m{ǰqQif'=k,mꦄ+~i@qK;Wň(H2*5vTƙVr{2zYS,Ƣӣ2y@wHaM6@&*z76ADV}О xU](Lv癩q0/hje6X-Ip$oۑsΘb ͭVB>~õ܍FOl ^ΏMTN-$ (v#qͭ}VX-aEacn}!VP9!aۭ\u~c1hoL*%pqꩠٶBYm@K X{UiZJh 𛍶g^Ojh*eEBi;~,@k УƍҴޡ-n'q ܟQ oy/#[82@6;n0S=Cj (hlȿ)H=~& n*HUjjQYbT,lthkI$o*W3]U5rR! ﰵ;!I ]aKa U>,,Ex5#¿ EEI&JEKWhf/bSV! Vpv/_v{96Xϡ)2}1j1 h!;$~lZP2lvC8 o+s(4`n Sz;!:PvhhU&9-kC ,{ؔP]91˭ŧQ؟!(=vm Dbԇc8ڦLznuкnTklO}j@ݸǷw$Jn9/}Iܝ| K:cCdK nnwrhs2*JRY-[v>j rI˗{ vuJzԤ%vГoD$P6ne190UZ#kcH K sy A$~IQbȿd:ݥk%Q|y. 8V\Bmad6*'6bΪ:@%pHYs:TE b!u In`Hðb,1[ Udi1J6.TG'Rlef_ 2W!emdGiJW#BPtkA-e<.|%'Ԥ%;}ms2fn}Du*C(HaYjqA=m3f [X#$yzOWP% C }I.,ہ{c=Q[]BzTQoPMK8MKHdo?)O.'3+r2J^V^&/!)696I(2NROL{bJS9'쑅4z3UYTDnM7FH.$54C+YO'{m`o;fw|Au1iթLhP R#ap땞FǫןSf@WP8;FBk6fmb3+M&:aA>7NL[)bGkR$Xroks%2~b=T/MҩdvgJ+l!Λ zG9R4z+vC҅ QSW⤭ՅpC HQ[@=V\KPˎ? Fܐ*jMNJJgq*IKzJoWF@ซ2mօXlV[HJB=:lSؑPl:B1&l0upObJ#&K]hTPí 'Z*/r~ TNF~mI'JXh*Umc%+X'Gۜaf96_ƕ'Qkuj SK#GwA]7 čȱN:o8Ӆiodv; `;lE^QX qe^Cu**dz{%VK %rIӧ-%RP; )A7df6BG'}.mD:Miw*xޓy$*eqO:Ue *MqF ÌP$R`*q{E%@YYHî.z< 10v7_%_uok8'B T(mj^ZJ}{ m4U.@Hqp Jo|I")cV;-Rݓ=%Ү5 3eZnNACfu$ޕzu2k10(ujj?(1)XHNTIZb|J@}橐 :47iNno5fY3RuQ[PCsaco=2˴Q&a[eQmEN;MS.nsnu!lƥFh6v֕kJ?QS\WFu/d!Bڋ;LuJK!SI- ]J>Àv#nVR"1(HH8"8cz%.j JlR_cw"/Sh.CTAQk:#2bR\U5JR7i 6 R ƎRB&>d,lUa{Db͘Ϊ F\Xq𹺶{=qcԹUwfDR}_Gm M:sSxʙ2ֻr-oR`;ھgJRۈ$:!A'k}7ܟ ]jUO8=)YNTl-a3+ɣm KAN(($ |nɒCml\N{cDɟոl?j^OSJOPu;/>EFB2)JvO7=DV8 YnO*7:fUu|3}áa>U&)o1%$RI,N&S:1.IS>sH}V|Kv5lu2)qz nw9X'7yKM;0 TJ-*bVnnGp0c l* ~ R䠇)IbE /Jb-f E$~⅔w<۫Jh-GRn1EuŅ>9o@&eN9b9TNF>}ʳ6%Uۨ\y~J&ڧQMqfD($ïʈ v1.DIh%467"uyxI"(S^tX+r:Ca$wH8Y]riz}1S)D>jRO}7]A-zPUQ'7Ry"؉m8R-MQTu:T#/))>U1j{L"q-W!5ڜuTbȬB `l!Ȱ;.M˹u3RmO'|W2ɹ^ ^\s/-I^-&VA?lU~}.*>HZ J8#Mr{<`0$*\Xyȟ6ékˆHnN Rr7~]Q,\6$ >[(Lb,seL2.ГQ)._}qs\U*lW5*3r} ԥ*|ՓFȧ6x앬m{'oȸv2Q)[_e)\_e4*szlHVW4 ier\,x*ڭkI˽PMB@ R&iƅy2IqXXn}aE)(H??+%Z,$GߛbB*Op68[eStDZߜZrX?8u4ªT(zR.Oj9zf(1RI?>JaxS&Xn֢!~ JE3ѰjʗUjO0b"BTkXi> `6FYuidI1GE6ls+)n[GQG{#Z:_aQ:TT*$d 1u9YJJGo73ˑc+) i VR|m_permDq fUThT)ȢDp) qCe)\mb깙4oZ@E|aذQl;N+kmԤ%WlB6lI="a%jI[|\cGMJ <6xXm֔΋{ߌOz/x#gGLYI(IYQya)X)n{a|yAvNNgxN -I%!V!=?M Z 4QSR&!% פQHԻ.o|||Ucw)۬Y;.̕v܎ G8.e,>k`W+9rTRS`&U#2huJv6|Wmn U`nvl0]MP˨%IIQ;kµ}-o%q!ZaQJP4=F]Cu-MAܪI Gp[KNHBq;,5sDŽ7^\$J}HbitdelPzdX}>#U]HD|:T5:ړ]Ms۹ ̑ՀU2RV.J6ѵ"c-g8"]U+Ҡ 9lkJEn &_zkQ@KyoɺI57U;_䚂8 ěJk;Qj*S%6M*έ )1ԠKW uYJ4& 8+Q^j2:CeTJ{aL |ztܸN*RN)CWieV!Nj8hi!k,K_2\J( 4bv%KiSrt`6P|"3ތQHn;_5'%)*` O)L8 z\K FCCQă';_CT.Z͠k)F_m\ZCg$m [c3wӬ@-SVzNorx7 •CfŔ*BԯR2EuT*2iP68)˰RVՅlc}'؟o.fڵ{Ě}L,S$4't.O;}P% x\XS>d:ZaN ! c9,`Ld*;aֶ7_*5\ؒ97ãSd׭Ց:'i.eQTW#҆l4I'XQUm(Eʒ.{I/0aʛa$6#^guFf4z}m§ҽ:N^ @5#j$[o^t;X\ p{؁N73_:1۶ak +I=0(QkjUJm-8d*Q \8eIq*tÁ o6 ň\T:O/BV쑋Ȩ lNt2Jw<&8a(6=_Zԭ CI~AX*ʍ 4Z@6`H\1̍LLCnao@6xm-Sw8. !lqA&ʞʔ[p=¥xP,ez` Ot!I'Z#lEάjQ#p[e,)R5.M10MdrAJSkyFҴ(ęRPnYkQ%آԤ -{O}d8{^;q"ՠ,o}6d j{{ZzJ Zm=cV )UHbSsʂTMt;j}h:;Ⓨ-a U  E @>1h۱ME6,DYf}A ',K $6 r\,0*a; Nn(3b>G^ҲR@]&'"'%IrB*JpmHYXon1/:AU^xR*< $!P\m{Mq< %FC}EȱatTim6_y-ˊBh)N\y[j,\~?8ESTv8.nmV@VgUޜ1p*t-)/ZEۛ -22Y%ƾ"*&6TQ#J=M۱ E904 7qwܞv|a>_Ob%U(엂*6ґnT=Bn/$Ma :(]7b*U<՟jT+R)0<5B)ĄzPS\}rjf-S1!-Ɉ\HE7wcnJn(\ۿ;JH۽=e_X6bpZU%PX.䳨bVIW'#1txCڅZZVq+*PaKz2**J7|[>R̅*B@ ýh 7RCg0),@mLD[mKWIhP$ ub, i:'\Z*տH(S6]jR䷦罇{Ζ%,V)s Q+8#VqQl˱٦ Zj=&}PܹJֹ- Au ؝0ۘFS@t@WE 8vR +.F\gUjT_5Jr!ZR=Dw7QNq؞ÎʛdBT$?;Ȃ3㊑(% !5h% %hBK %j[u~8bkLV2 I'j6fNO2᥽:\oI o猲-g07 XVTm0 W ڬáTu(:n}a󦨒'm[adnPr$c4K65 a0S1jiGWУ1a*r;Is$q-B;̲!~{,)^U斣):HV8֡VBV)'mQiVmBУɸ2:P6 ``ua6D%5#R68L[IJ@ !Eah m~F6j"KhIAQu!$a{}1FP_hi$6N[U6k/+ȸULdmiT>{`'ժ+Kc%¸S` pwUpxoofhD̐i}mL;w2U .pQKtJJHG;qh} FU?VLe:kRP&H ~N,r4ZNd&MBRH;( j{AQԢC:e6`̕zdzN:]|p8R ~YR1ArKq-UhۜH5|~~0Q7DVt<%)) 6/J=fATrcFY!A[p8TZ\wQW%GLi0e\Sq] X#mJP)K-'$d}GIبh@s)*S.fyK"tWҔ4l 83Nϓ&ѐYuY*%oV[j]]e]osR^.V6OyQrP{ZږJui m.1>$[uN@K/?,2=ɥW9T؁nG}syG>|tDiIr.] ~[օ-}a3,@DU$rڡ !+BN$~=*Uv'BTIr3(BtY<6& ms$#OXtW8q z@Ww.Ə)J$S#Z%:E fad(q'so?Y."*=eS#R_Q('Ҿv&DT\)v;oopiE%g^,1GZ3pH "`em;sSf"Ty })IAZƯL*z#sl &6i;QGfאQLUFz0I>Hp>01K?=®uRQ'mLeT2Ei';M?gƎyu'vHG?7gHnV;a*|TN.׿ÓhQ="hpWlKFiqJeV}Z爅Pm'RCl $Y:~N#&^r)4 DXoK{X _BuX-tOLY$b bʂ\*JHV_ȐJʊ~ k&Wer2KumH*ƈJw1eJ:X1$cGJC-x jQJ:U9# &K+]ֳus 5Jn\k+p(\d6'kr{AV`sDX Q)(Km99ֳk=ZfeHdou@8o RϤٗjGqaφjq ^P"#PSA @Isߎ~[ ٲk(6Jʼ uCA6uCCy܌XSP"!-wyZH*Rm79G_e vH+uR]hw Sk~˦)FTSJe*SEoM@ބ4L6g6G VAKXRF^"( KDwGeGY׫|(m-=5~aJRRAC]Cb-p8 Ѫt%&s\ZuZur/qfEL+~*Mg%SR:%eJ|r)ʯR>|y HuKn0 Pl/sNmI4NI$X1s7gQʱϔ2Bxt B\āa0k͕9@d")P %nnoǧN3m;O) -F<ն㼕nAW* ʿUlJ\@np@Bʇ1!1zCRtlMM- 6*; v:Vnrpv41FR5mlCٞ^ɬXX#c~>+}ƥm Ċ|  rP_ HH> ijS `ږ$Oq1ɒ[p=)?R,  'ˤ'W 0ҚoN ͷQfVu, 1!m+O'rZֵ.P&)EtݒPM*:V@Ai^0l8t}иz؎lm}] B#Qnj%Y6ߔO)kizE 80XQM[iܔ:pSc UDJB!Bz)nb,Ƅ17? "YB1 MԭE#Ba| L]AM%iP>kӲd01Y6%%B38!,@a 54@6Y7 u}#a'K)ʋ%\YVSHO[w4ob#Mys2I>ѦHԢ֫z@ 7̿kuje0;\mžDHӫBnޢ@ƪE NEÔԹ%EBzw){rLX&|U -).PJHl[2;dTPH _-)dr^\;(E-վzE,CM HUb}5/M& asFo4Rh!ʝ!҇)/{B6+ӧ!R^Zm?q9^GMcm'ȉXPzYS4Xupxcg*eN 6VR>qQBWi,yAJ],76re>&LTy/kی/#~b P֫y"Cm;{|~0C+MBt ',\4ď" =jZ|W Vep6=)?7$5)J~-1ץ(Ρ}bsj*bFb׫5[N[w:`!6/MJdRnYuQRb 5IRԥHۤv,290dŚZu BҠF3af𕕂fYQnm@X۾e;ʗnf^ p`Nn&XKڀ J^l-OuQ[Au;JR.ToaaYТӳ47%z]hz{Aƞ!,SGUW<>r^TV)(pg>"3Ê#f:<ܶT\Ukۿan|Frdž=rdGIoR$8P;! qEË Km*?]RKhHJSґ~s)8ژ6ad)k"ڇl]$\%*.^$mv<@XTԳ3lYDqtxY}GEݰ`N03ΥU.@% zq#Skl>0vހ[ZJN<\r[d6(=gH]&>k[H t[+*U.tZ0hM>4uWL-ՁnD広82ci:rF N#Hm5k$i:&EsZ\ժf/V6ta@j9RcRe+=N)Csr}'93](GgnJ "C|xc+%V7bVj|/Y1BHߧ.LSu5Jdv;n;%kS}Q78U>#3%EiBeZ./w>[G@o)*< @?z +[E 4/=K Oi>DL̮MBLVO(u jA(u*ߧ · Փ*Vj,a[4qmcCXsy͸u?qo؜F')]V8hpS;ҡWĩmN܀RVZG3'jSk/WS#3Bjƞ6O>ś`z&1+2@rcȿb`ƊQ IQ~rΗ\!FZ!R BoeӣP"M uhh* ~M|0 q N>#ĩ hpC( q Ý=UzQ^tHJTm!jT=E7UT|zRV+FVөP JA(W1%)mDBRKcR}3dH`aBPYcfj2Hed)`v<c]yfڣKa`lyLTWqJeV{s' 059aNT}3 sg H5 ըd])){UrM Um[w,`a7íJWaަԗI?BđZpA*QF*+QNC3'UO,RcJMm*VײEqFҚq*)P6 eԧ $$6Y3I|a.Z;MQw t$liuu;8ޫkOq;NVEqa]š[;7Ysh@TZyԴa$}ev .K7-eJ-m--錴8ZT`Zpi%#7Kޒ,X-[+*Jk [HǏE+{EeCپWRϙ`Ek*:jњ+BA!G ұ5ܥInS)fEuNI$)!HVr.7DiJzo12{<$iՖZ*cv:SJTNlw9Hu$Ȣԣ٢r3R\RPءI6{`'Pxƚf 7NB:c>`)[Wm)a0FiԌ०DT #BӺV.l,{CD-O ҚHt,=9&ͼE'k;G$*ʣ +tI%VRܑ{J4xvS*@L[IY[r=8Zt"}2lRB}+ 3z61RDVUg kmł ?Ϸ|$ОdMLmN4 m -"Do!SfDz%0z*BۨonF`Υ2W+kR:nrGYNܘ \xKpB[HYt W|3ˑw* \E<: Roe$\*,s;Bm'ȇ~gIm }P)f :JEVvkv:: JG~UbqbOvOfY]:aۅÎ֦sev .@ERUևR[pMTiQs&ei~y)TȖ܌wRII'bl/`'($6 u'q}KRBKa!GIŌ*U 5'ʇG.CRSb0 !)Ojcm=Smy"%fYj Ӆ>1td8<'R6s"S"jتX5hs?SjY)I((Rkշ&/S톃į^$#s醡%kïj JTvq6*Rij,} ^ΟPs-̝ )i렒=Zײm Xa*;JTesnA̎2nK]MMH:{q3̼F6Q$Mq3BTF(yE[͚"J1r[dQ˯"׏>yJ5Qe,!~-HW-"4,ZUX?no[UrHIpBUe'J n$n/2H\b :"0Kt5d\X`ǧW)QvZ\ e)~_k6EӗOC̰)]: &OunmC QP7:n~%L-jC@'JoM'l3Ҁ36iQx! [V*=BsrQ@q\BUr4qAK rbT(KrI+Oo}1^ ҺTηnn`Gqَ^5+SQhKHУFڿL̯Ӵ[^]hiAJU6}D(<#c M?,=_,KO7.*B*m%Byi=]=$\lyJ.b]=K1ΥʂP|rw*;`KAR*n\a/^S $I 1&bx{CsMGħ-4ƶq#ö_Y]mH~il;_ފر.vn02[WْM␐PIv|9Qş\j0!\lz@EcK.7RBRWfWdzёKQZnnR9ST JV]66=m(lw ;SBJMj Ok>"ѪSmt㺛!%WRB-Z> oH@LJFVk"Ee :vVd7HNொʁiY%?EIh H,Y6ZP69,¥T1AD-z&^TISKB:PMJ.L=_"7]reJBNH **< Vw2Nfs{T\Q5w[GIr>XUs `R)5([*LYou)!Z j`aQy:vQu1ThVT6"֧:W@#s㌣8S#S)ը齛Gε>^pMVmB9)L:G)J=IъjBM[_hk;L5ĹIgpb5_0[Ji۟n8 VL=)Å&H; OZ9u ~*:дJTŎ;#k&^(xg]wbKB@, N+#$8$yhBߘMܡ$hZm_ț"w:6Hn3k}7` Fn?5JmV{_0ͫ1-U5UaeH@lRBThBj*J)1HTŤny$e?#1'۾ tZU |ئ_m;]xBQRM*emByySŜۙҧcTߦu:nXwHCo~5*ʛΛ[ss S_^N#6Zl'0);e %|v:T,?U8;+hZ^wA?|eU#Rq % ab5qs?teR%Il(\\7 s-ZܭH:k@`Ht:—ot&Pv%8R$ozN:MqF+6S r2)%T 6A|ՙJל]9}kx'QҎ86NIl` OYG-PWkƟ"SLE/wBwئm )j YҞb;HHzE7l})~/Ĺ_ٳT+Jq]ee 4"u  KhAqK+@ɵs.Đ)ji-Jc=fK V97VIγjκVҵFZlMk(qDP }`*A-AyYtSAܲޘͼG,VMelx]PNtҞ^F |P̭gԽM}%^D;f7#0+(*QACCoHx]nv2\0jhͰIdnI#q20$jTNBuiq:Bl ᾯ(qqTEdO|xIRk)H$?%OnSle ̂Fg)f6d U\,T%,? Pp﬒G~֩gS΁ KC`է|5cqj`Ɯ~ 1JFnr$m$IQHack)jU+LtM"#"[l*t)&RekDjBBc6 ll/VS9p4[d^ mq9B zЁ*>}aj`kB`>S7<>$DH͆ҋ&ĔܕkOCPѐǕ- R2#&_MZq\%Dsm/k`h.^SDjJ"y攗W_mk]G9D9F@^9}@s营,iԕϤ Vm}bSMEi2X iǧq fԳTGT)(;9PjrGMM{EjkU< !A7S~l#0{ X9Tjm4LI%ڳb䴅֝dE}ѐԤB2PʎVAA7ZI'p,6bgq~*i6ǎa4[[l*KުI ŷ唳| Flɍka ! 䄏I-Qix}M3JMAHRHkolZʞ/72܇RRK)eIznmlח{LlcI)U(/ %Ѡ{tU'\2 F{63jyFuCKBը$pՔf '抬Ŷ|"#"9sJ,lzi%wRእeRrF=BcNWB E ~اZ꡼vÝN~:ClJ}PVT HUV0jEKȇ*&rJΗUZmc{a1.0Rh3+zD0җIJu4˟2\ " 3ď:96ZM(;Kos>0dzU:܉Oc4AJ6%j\cOlug(fSsǘZ%kPZlpYد][`V}yu猵\#3Gq3)i(ojR,IFgP-NtWECS i*'܍8 ds**񎳙3&vނtӲo҉2 !!@vq̙3>,I*+?8Q T1,,4S!w=IٞDi* ̇i?"w!}&gJ3XsY-,W|1h\oWQSIuu [zЄЅ)V8ďaވ |"cяTɮV6(Pv튵GE"a`jLOG*6RyÆ?Oq,Èu5U"Ҥ-*DRL6T -lb#KN2LJQ$؞EV3W_ =LD3-RY-YR R?)%|-Ԑmr9B'94Vlg4nM= Զ~8X;ŁdZK;#kOK퍊ҝD<166667HI*|ByQҤueI )$.;&喢G9(}s }!lq`!HռWS(yoLi-%.Z x٧8TqMt8K'ߌ!WB Yқ]b*TpՖ?V }76) P]N k_6)(uz:v$skn,4Gm7It,1 9,SDUy麓nQb_VZ:R )FlM+*ʠ6]2Ber H ꧔Ө \dp !*]Z߿96ifO}ى85jBn#kHR0d ]um5,e?)@(m) )&䟌y%-||$mM٨3;*&poxiRmm%b l+^&F”Ҷ*HQ؂E8Ar%:>I?As(4)h*]`l@ 9;OE~,-m,)I.!r[WUNmb(܍YsŨtRq%KfaT([po;Di9RRClΡqQ!%C}lY>J %jmIkЩt#Km953qiBЇmF@ +SJdES4/s}J s+ȓPALIt%MIN]?WA#>%.3RiS%q`C^Z-ah;SA$\a_lCwx5deBbc-I7Q6:E]u3*M?"-806dp|Y]+}_B1VL.#71q҄gcw t}|yYۇjQW^lPI辩6V Csj ĔeZJ@)[m-$y1ۜZʋ: ؅=+$r{b% mG}2G4:.**GIzFjH8+PR(JQ.l 9ԺSle\RV&Pz{j[6&bJ'o.~"hУReؔDUͯs (6?ܤ)yfEV<7BPRK|AI6?q*SL\p!  8bQ ɹ?P(U|g/D.!Xq$ (7:m>SbereRlrTA"7^>'* 1"&1%"F8.W+o;=P瘜 ?5S晑dOy@0OI I ww PkϜ5OR5 j (}X 9?g,%<} /)C6/6՟@hZmj+h$f[rM;F9V\N^ B@thS 9S"(ʳR4 &9+ʁ\;܋O)jVUilYEIJȱ?o|iG,md_3!G&A/VR_Z E6m*xY~:"}EHr{IxWm@! #cD+8ˀ={;J}l~V4  n&DnKX!*_$n _<͙_! 'ț6qsi!a.4R@Î@7Q*XGC Џ?2؀2dž+yS)D̍q!IC^JtcbuⷅY(YI Jh:!N Bt_CߞGP5ǿ^RT-* lj̞@+AMʈD7앭u\,(Dds{bOX6;mq2iU,Y2lQiZH6)Wqc5Vec6׭) +0J}HfChYu6Woc:e5Sn+VJq- goH,e(Xzoj(b 뺯-e1Ze3CwRy?IRQbfbrWDV)w4>at"R{ ȭZw Sn R=h%|l fʘԴ+դ*Ji xsقf[f4VWQ|$p)Nuq#OiގhzM#*rOYS<ʮ5Mś- ZD('Ԕqpqª䘐*[C~UcKa[n?%>̺ T)٥XZ=6ξ5"ޛ85 1%9Î0X6{ 3@ ÆZ(@$KZTɰ'olj/ ڎ~Z"%5-ϨlI*:[} _p~]F/˙F5VS!a%,TڒsSneJT!qեhWanR<߬ ɎI:+eNzn67ʕlېu+Ӏ@*W#V&Ɏ׽أ l[aPВ=.QpBg/?Z"N*]V#Wc z aPL-`Zz`;e v]sl jlld u2FT̩zVoFQ?`qReS%"O2R@YJ?Qd*:Œ`Ǣ-?P$k  Z큔꼸,4U Z-:I]${ZY[B0iΪ`Jq*bGo|7?8ɞ,.Zi!>z}A=n .Ѹj)Vyl>jH:Cd{byKTUHVQ EVH*#oVKNA=}4t%)5H&)/8h%ΰA\%W_{ "'IsN6mK"Y ~77 T |4Z{4UMU|wjQlި%R Eun/4$jpE ԞÐ0;E@?/OeQ^ғ`,wI g5Fyy?w)eQ!A>۞qN d^mK QbG\vT?8ʔ;hP|yߨy TiNGKjS2l &f {{` d6ZD2Fz/!jJT Q:ձ'2fES.iԉ2B]kiJU8uߡ*iyCFhqi1'H}im#Y:RA"a,SE3N ]fԅ]KSŀ(FsG`/bE,QgR3bٕ$ [_Xj[mf1 Z$'O't 01օ+fLK%*?SDTCC-D-d.iRlj> yy "%6%EJ:S|Vr_DibZASM{l&k9zDSj3N:TT^'Sj W۝~ERپ=@0e_mK ªHiOHRmQ cX$&l̯PUhyS#KhHPHBnI=#q# qt^ScemMDR JQ<`Veu'R@9UYԢcj6'O1ޏ%s{moTȏA554T56_h>rdAZ&Km%o 'hklqb,*P_r&eK+"ҙJB5[bSlf4Rdiu6t)SaMu%%[ w RU:EdxDHJ0hrӉrCb_U -i!Z Z$4ԣ6iWbmbM޵@(2y~7;s6ə *\S6y=Z)lBǕ|'?q?:T~Ѡ'՘JkZeZ7mr9,l|?a)_gϯx #28MLMa,amɑ!.(iQlm[_ +3D#挵ttE4Y+$ P،!öMCῗXsm9O6LR[[jM-T0ynjUL1@ WM쟞1 O E6CFm7gH˧Ajlz̶P-{\smӶ%01.˵W&I߾92^Nnj9J7êB~Gr3%%8 q$`x==h_̀8Ŵ pui°i8a 2u:[HUJZlw eq؋p+g$gHzhtmCi GҐ?Rq0ʺ@+B/Bvj Ip.'GFjv>_ɔV܂Z5H]yԴzOa+dFGQqs"jB 0乭J2$ *=i^SUzcglY}kQ;}3%Ow|oG$1e-AJJ][(^U,ށ5ŒkZΤ$󽽰?8Ҫ5jiq%!NJ@*?O5,#/ҫfۮ2yl RBO;L=T170_)ٚl\aKX&CRRw;(z@WX*UT6Л/|yM}a23dpF!3;r:j@ (h.AX*ؘ]O)q_K-ok%CUY ~"O3K~L[rS.XJu>{,o؍LGKu6u_c] TDDOפJRů3zLFRSMybRWff/2I ;pF7%GNgBߥh J{{bV  W*ATYjd#Ro;0Bĸ/tOFm\CҒvKgn!v6Q<{lT*UbQ}!Le!@jqd[ǔ=G2tNS i XXڰ" kbz ,\y*SY&[/`篼w(1tQ)5ApJB\wE&ak[]/<ִ$qDìfFfXdc-V)/M@kZ (ˮ< _!it 6I#ΤfFbU!e -_ԓiQm'a.}k&-.j-?tbTmk vc}^fFbs.VU2>\+JSH6jQdw^St t ; )0*rN&)%lFup5%{{v Qz{q"7.; o45-ͦ;"+"@KnTS*Q FByioRTZ'rO|"Xt#eTZyIReɋ4vJߓ*1(9Ȏҙz+KJ@ґV5(:Wv Eu1iR"bX+. 4ʐ^PĊE.ҧg.c u-@Hv%7k9s xʩ{B`WyG '<~0jioiRJB/eTv6 6D@x.L6ҟm׌t ֓“b@l4"/Sg&hjsY.,[x!I:lwO8 ?9ɁSwZ)+Q*}+NElN; RB}8F]D0Y8%z $%CTKER=mcJun`;3*ѿ'S;Q~Ou*P HRJ[I(: g#=N)2%1%ҒT$I#$lw?9HZݢL:S3M.Nl< `HKWcNY;_s$Ո"ԥ_˴]O Ӿ mszxDRᄚSe% (g:;sntZRm%tGoaנ|6OizAƖM5ڢ.lgѲ VHH$ގ`n\FDBmJ֥}7&pH'kB;u_|JȠ,rv.ӱęF廊ؑј1P)ifӹDkM=vr1f^O}A-T8s R:zmEJ*RN9Ĝ;]uM1cP ԢJGorpnJoمK-|dVhLH8o:NjFI'8S.QVVcQ\83A!*QYkVV;}ܣh"j:5NxuK07HҶb}k*&ZT+YB!:ϥu6 դd,ˏ;8C33S#UhCPJ0Y"ͦ29M̕Y.L*J۸o ;jAh*2A̕*uKn 4@JZYt8=!)Ҁ߾d%dJ#ITf%Y)vOeqq$mC6ٔt⾦ \Δ3!=NJEj9<3ΫTiS&严ӽ@@J~E}>1(HIK%R}|QܯZs&\Xe8I) י|GQמJc4,ta@aOh\6NGu|UC~]/T>L 4:Ĉ$-P=[~F4,[L3lX?*ǧ]m@%B ӪpBjS4V=Q|TEo/e>#^T~MKDC(9Ģ(1ފ¼} WNUY$mCoh&_oGj1v WT.ԒCͅ$@<`-şBjȉ7eXC I-X<A՗kUw5f_;,f\C6kP~$:RC{qo2U325>mĩ:Բ\!E*(@;[ˑ@*j>5i5ٝ|U֗/Î#ɔQ(j=rp"p}u |gZƗW'J\ҫ;Gi1oFK0mG܄u E ε$:7|7TzY˝D-$eVM~ط>׉+0k,+(e$6q7?lZΙEF)i-"AXI~cH!)T1B=$h}RFF["@ʔV Rn"[2\I BJƓ:/,Jje_ u撢% *$Y?|5&u?6 V`MTzY!ŭO7VB~5|*4T]?%\zJ\zFV%G]R#gu&5BJ ZlJwl4RƊ7iʳ*2a/xY f=4uueHԭ(>yr6q!/(-jbӸ^P(9ϡĦĩ@4*2zDszJmk \wc5Cyoɓ-mra8 &oQHiN6VӢZA;ac95 u%A6a~[)ʌ4%wW;E%E5Vtmt"8j0ԧJS䔄F+QYOiqHJV(%E#f0H#J'1Dna mUŅlk0@n.%*߿ ]&[Śa^3#eвӦ+ZۛoW9Ux GTo~ǜ?!n=aj(aDْZ\t,Y]U[Juɹ l^P۱2cԶYw,$-ܫ6|,QӤ5j6Z0Qym*]^-aRqBl%,Xs j  byC59EilE)P ?JN䋛j7-\jyL&JT[ '8XUP>pt ݛ93&. G2=4Z)4,/ytejE59̒ݵN! l odX~mFYvJ2`Ccuٰ6' 3BG.Jx0RTvZBtr}$o8Pzye ԆӗvQ &S2ԕK߭aʏVZaZIʤA6Rkۿ؅;xy ?.LLVc Pl\ { JT5:L٭;-N)4^_P[)JwU{ 6څbyI^ćW>ڃ RJPn%Cw)ա97%ÅE`HQRTu%cqmE{ P[b.1zШ2*24#PbeT-VR{{Kme԰!) wŵssfӘ 3 bPem:4NMÝ=Mz, [hSg2MImB/\xPADo/(;dy^ptt]ma8&Q-B! _7:OqoyN}xHd ~Z*!5gLW$EyN~$P~1|h\$!ZO;lR_ڎ/k=ҭ`g)"mף))Ia{U%eu\TJeQCYiDب)rٹ?ͧq$|S0iZx KF̔)6S@ L}&F_jA+t +bfnJTDp]fC Kai)ZM?LeeSDRV܇[KN)jOEHNm)7Nk a JTaUf8/)H!i QU(ղ>VBGMR鉜޵&ԯQ؜T3,Wzל-v-w;1VTϙ6\2 iBFmJ Xi)PH:I;mղL!5h DIҟH8/t6Rե$j]w3vݏ|6)u(7FeAdHPRiQԐ$Za!xJꋒYBaPu}փ~$4)'F{>>W%܃&(9og~O疗P^u!Q[MeӢ:]!%*%e=;(•z>C,T /$>nBMͯ6(}S>Q4!*H}mDxJ)ILQ趠88%GHӏU:Q:}QaTi7 !uGe(G˽x7d :%vsqb$O-vOc9eu*m\-\mkk;Xh6 hl7z<_먾6R.풦醒SqJЕ뫃N8ʣ"Zɷ苃,S)QlKQ۝dx~" +5*imm!JH矶h;$Ǖ ܅::ۻ2cpT,(*R+TZǙp3-Mj/: )*"@roc9,~%TܚcyIq#r P3MZJR2%9}D :4ܺ&D讒 N}68mDzHhs}ebF1Њb,Pt *)M$ʢJI}MeN8r W1etS-вǑ 2,> Ф[77'xjQ/G>"*S> ]] }Yw+Oi%MwЅ[z|]1D_ |*Թf @Q&:Bslf 嚵}:-qWX!$p- ާ߯w9w&f/ҝ(@ISHd܋^Ì+g6ߛ].,v](%VJ.8>f UI M #*Zlʝ:KS喇PhBRSpyMZ2 ;Gk[a鹝2?! )h]7$@qzHS4RIݐTk8Z@5,Pof1b8tplߒVoGO%W4vXϜ4k/:nFl-)85FB 3g˒fyKj=&5E 6f#ĝQ.BcZc@Iͱ6K(ĬG[ɹeYk8,)mm$^xDi B˔#͎Im7^%Cn?n.jŶ6y]D.;/uE}ƣs˲3C3(qيIyKyz$BUr,,,n+*CyF}kt⒲^b,/Ͼ ȋ˳ 2藅t fP %ck ߠ'AKOuO䴠I*|Df spC)Xp]kݰGT '+EպE yKm({]IrqJ0C=$P*ڠHRf%5V+bHHnp 7%[ 1,+R@$m`;=#&s`3TQcɾA.8{LE5{ _ZTHAP~qecfA*iqթ~}"d1ViS0J ~0xkV\H3&Bژzj&ÒQv&)`uؐc`'2ce{#b#é*" >pe8(rKlﶫl n ;aNPBZv@d7ʏ*TBu܀pc FkeIOF1Y֔6B@_&곬 yn'5F8~pHBBVfj9fX&|6t(imMBͶl)MR7ƪw''M&6SrV܇ vX{`6beIVHs:N*A]!CEq,0\6YvZoQ!LY@!n\MsuosXFe'"IOR\e/4)UJ}5tLYy^l9ҲWnUkn>%G&' <#}!#O:Z]m )6$H"׹ v#::H:}>}$R j; 98)jd\c.ZrKʙ[(SI;#NU#1F&cPJVA((ے kVu3AO,5AT-j)QK-zrZde)!Nq7 YD& YbGqmUߤ#r.s,iZ;:`Q F`DO]mm2R 7 3I$! 4 jQ7B.ƴ6$ߵZکc2k7 7EoY=:^5&6O%;&!.45TTPۜgYQ_L@Kh\bUBy +rW!7AeiJHK^H\@ Bݡ$"č D[(#Ia_#(=bwHBja%GB q{4$YKTFJjXl=\µJEO YTkYhv _@tY \&"Ⴏ2.\Od8ĒPQ=`8I B;#O~F"Z4 6# ʔH%`8SCI??*9wP*Jb4WB>RMS7`[bwӿ/}@*A?ہL2&m58n;<v5Iɑ*tBy$!'X[oP q ];?˘.FJyڐHJ,noQPҥr{M2;1T֧P,m6c1|'`q1SM]Mmi[U%pҷMI(ܓk g7) q&`yj8Zd꾂E@VnIRn֥$ ]i4/Q*m֖M;*I&@o; pi2C~RHꩵ'orM8kX4?)}9e_ EIB7#۶*1 %ߔ A z" '4-HG)[N$XCbIj ub= ![{e2%|4|ƅr\$n7=ɡ??u$Ï51LnQµuN)KHIJnT0ߕ^KPPҤHnN(>P Me} wJ5?W֫n-lP&e*htcu$*?n؉]"_%{-r$ E[:E&3AMIK5vJpqԹSS5V)Y Pͷ$b_c^JD>FKg*Y+Ke%>HRAtDfd?ODQ[9! <,\ @#wb|K׻:pvZqk)XSIlp z;b,X*Cŧ#PiN>\mػ&'7tT]rr';E65"pFvv4#\C`oAJb2ff؞ТSB9 toSě=A778 M̆C^a*j7 # ~=:*RkIʎV!}ҒO9✊kɐ\<ӈy R@IUհl!BXc>T3Su.xҗiS*W}e%2D*Ka7@"׾="}^-O(LRI04b>w }]#g! aɨSRe&9[ŸS l,HI5T&{2%gtg Jp:;{(?U@}kmM)2J![p(/`lvg磻N~CCϼJmC8'5Z%/UaR֭(Qk-8@eEp:Pv/(X;E!\ :ґo u<,ĥk!CHd |9 5=:һUL؎ JWǮ]]l8iˤ7CM=:.-]k7'O'ߌn5yLMyRT;.+i`%:z-MAJQLKB)łm` ː;XDd4z$\mސer5}srűғ>$J(]% \ `Kԕ)钺na hUZH: 6(mz--*JAӵwBYeWzzJ%.:AnuQ7SNbuJԳn@iG)˱>pDzJ5A}zQNpn%G/c j`ܙj[rHh #PBS`1䠯{S՘#D)Lˌөmd5 Dx FHfY13OB.ZSK '.{jÃlg!@έ"ވ5hlHKo<իW&lPXIsN iHu @7MI'fakbʣLCQ2^je %)*䀒vߌn~hħLUT7;` RS~p~rzv_ \ʍa:GV.J_r\g/S ehDy<ֺI QQ;a`7y¡rSI}%Nxsa 1..!ǓRBAzW!@k*W.Zқ#ήxO! ՞'7XBG`RX&Ns?dj-E^=\rT un)-GJt49ہ|]WQUgA{"#u|lH/}Ķ6 e_Q].{m5*bb!L+jS h**U`=P,*\VJϭPCsGyY@$H}BRޔ.6ȩ}󙌐ǽu$ݩCcͥ0 L<a+rS̀΅ U5($ŷ9k茶mW}&a_L"#q-#Jqs}U|/3ǔm06qHR(G:lG[JjfWzY#q#V\\Rۅo*Z\)+t<Uƥ;]|# >|w$Ш$n>$Z˴T SPi@8҈BnA bEEڭBdl)kmN cv Rh^Vf]dgeD!1(ϥIN~-*3!'Zĕ tBRnIPY"ٱ(cmˍUؐ:8zm܃mfdgḀc҇sh:j[/[r[!mX ) A6OsEԹ9UhqZ=ZSʉ6<㲵V\Y)-鹓6HT[^: J;/ףEI"$jR\NڝI^|mj7|`IGbcWT[&t 3YdESIKNW]^,~ " i,aJqje6#ӰQ;qHjߌP]V1aj'U/˂u$[܀?|wHT-̭VZE)V8Vf]DJn渵*e HZ_bI퇗*/[q:IѤq|P!{AFյnby.P\SHOd$j#r_r[,? @޽k[?Wo`7#|Y)B43J*j+COA^)եbBy 4##!͑*"kShyN$-(6;rƯ¦]|_-4)8$')WurY4MWֵ3]Q7);0+צUiRdMժ2^ p[7$ Q |o1)-R^_ue$<,-`y8 h>ru| ,48Nr$o).WWT0ʦe.vJ@Oᾐ)@&ɰ۟iQRB\IS]r= Iu2e:6xXѲ i4rmGbO$?d@`+O{6E~'ё #J>`u)zxӛSNvy"DRogT EK8 {m[0S4[cԢHYfUï L7Syd)䬕)ZFanؓKԢ`w2*D9GHT'f!a_]{ ZRw7`E e"YEH\b)SA ru \t4^6wg #ild腊dyÍRR'-P=A/y6G{s|IzF?>rRXG$uFz-u%Ed\2 mzlzj ӷkTSO^]20]Or 66jn5b %`]).'6 }RPshn56#д؀ϸ'|Y+~gvhu 4ߚH!mPJl\ l@89Bji-R[O@^2l9'A5`|QETvRӏ4I%VV~*5ʜvtF!$JP < D'A<Yloo>(* ;EIKqqZ I( 6Ý|yHiǣ!ޚBo{ۋ|D^3|sw*T\b׊#HS@-bp1Pa^q_@,HaPd>ps TY4L*z\p7 (W3FEd WQ *P.vE͌S|~px5 s.ȆdSԾ5u$?H;n9#2y\uRzxL1c<Ϝ%t1ZGJK(% .'OIq-?{3&gqy2Ii@H |r1z?tAI;QG1l>m R@7:!Ne&+E KckS*0qm>1jg7PmIRTw7PM_qԚYݣP`Lw_ҐI>7؛zM}}ědHU^`Ҋs$L } ߬lT6 \sQ;ˏаHQqm AD 6#U*5)Ʀ IVқ C 3Ur>ӽ6D6@Q( Iq-*A$d\rB]R\T):BuҖR_xS*Z8Ģ pB 9E's1q{"o&@^JD橆LnL#1{ Ӡ̗.*\ Xr2Q ^6ێ0k1W14%J}r[%.+Z7 7k?<[T-%(-Րw;%##kዖ*J"ATՈbU+Ay4OQZQ+6:[q) +Rpuz,3aT@cH; Ș|Q 1j-ԗ>^4/ `GVn":$O[5v"QUXPRNSn>eȤ7JU&FW`&I54:XAKRUi<_`lh}D[bvlnI@FJD\Jckj:jthKV|0WI<~UzsaIzY\&RZ}k)u /86Y2=V;,RJ@ IoGvE~ !d8p{JqV1 9*a #JaQ+ҝGpMQ&V\f.)W 䋋='$kh9RCqЕjr6n|+E 4G)D%*Q7N:Y6+ax>0MGVKb £;"<҉ :yQ# 59sPYiO9%Q!EJ 8xRq>5;-&CEfu%ODm;UF*)/qN>B7E\_K) i5NPT̕2SCwŇ"HɁ5LKoNU%Rll;oڜZs9]2(rTs>klSS+Xuqplwsܔf6T b]I'RwQ68 Tʪl7#Ѫuu9PI\RN^۹+4ڔJӨd8䄇V$D |++@|rc?)Mfi"RE٘[PK(I>ʾ_hEmRCEۏ-MIJbjޮ)aPmE<ʬ>< i($O[|9^l^N'? L&%` rN&=F2N{$H1[Rɯꂷ?1`nx킔Vn܎\ 6@*Z6.ȩG[}2:3tVܛ{`eM RY};8݈ If `ߖ&@]Xr*\Vwi_QaT@I<1Cb4@S9!Ervq{oa~{'MVaӔDSB!RWM ]eDW{RߤL%:mɊkRz;lԢ8&ƀ#2 $5J_UTV,%伂XNLRc""XVlT?H|0K0V:zs%ǥN*e[y0N!ڶ>M8/HndĈX-V"Sr6HXYaRizd`ڒ%N@ znmǨs/ͦ&A@HA*Ki't_k,M͍C Dc`T5#Ik(H0EP/%V+ &#P7'5]MѩRBT/`?R3mhoe>RӈT6UQr 'UZ\]bSWj[/q%o&X/{q{yJY7||QW#f8/j ~ָˮ#ԦD%q R ҤpmF1])C?2U$֫k<ÉRq+aۊ@)8: 6sJvă}8ɫmUL亃]>qQQfBnV^+JVEi$)$Fk޶>͉߽\*~A]F ]9i %G{/b-7ܕA\<̦ZRz^5*[a%MBx-|Id9pHHoI}ŀ:QRؘ.*nQg*D8 ]6 np"dXr~@)[{'m ZRB}>CkЀ!<ÑH]i("^×#N"X\5SkPF.:{{m~6t'ʨn*kPկCH_! Z[ː.@*9R-&]EJ_#% F\umJEssKUtGbCXІmŋ&߂okcU1rb[~fT= H6$X-t{qz^/]Ǒkv}ԪbǪ===[ )E't>M}_=ZFEp* ikSwVW$NË[ Օo= +*O*Fr9NԚje0kI&֭]Z'P2WfZ!-K+l%-IJA* ~B\F Ͽ8^Lʊ(涄[a }EBsר30AUm4G :Ӥ }c!X2Z|'> 9~T4-J֤(8čHщ)0d?Ju(lmb^(De׍FdE (1yfsq Z=67;TYu<̆*,Fy $,=j\z{pq6Gzhvr.2wMRjHeNJK{qP͏ `"Y DF#`M֤$%X@;'ќdbߌBrDzt Q.-Y(Jo\`PYF25GmJ(( F2tAR%B[Ak-VNn\d.g`f_A8qeŒjF;R:w*;q*5LXQV9 WsmՌl~^sb yTN]iUʚЇr[M$*;-`kՂ͓5g&Hj{j- 8S`* !E{3zgBS6ZGjB֎pT)"XOߎ0%gq6+#Jn9 k/[@<#GD2VFPFUAM9LJ۱&+4m{slSTPfkJ1hRM5֥: IҐ.Nci)eRSn)#k 0j@ 5_WL~\nz{Md,* H$2Jf` wۚFH7 ai4/o9N@}UJPn&T oE/{4;^龯Ir=hYRZA*l'c<)R`Vs#-SnU >j0(rm\#ԤHQåp[T,aIl6*nJ@*:o-_*EB*}@ Lt2 YF8.]>b*lU3:Wv \zXJA)  9?zw;CZK Vs:C}?QI*)m$9StbJakxJ<;o 2*3Ky}: o]}?={s<//=Zīϣ Kz8B E:Rd\hMLH[Y1k]޹T^CVhGɰZLB Ey֕.!IR= ?P2eQO7֨$& uM!*C:[6 )ѳ_w1*:+T|:+qWOK,\B-ZZFIlOS&M2MhJ!8; w@Zu(~^ ΅=e_tIZ4-F5%nK6[:KQsZ$=JJ Hq!5HʴʂN6rݚ !zZU\ \`}xvLIQsB@z?=T2*ȋPJZYBNB3YcR2Fa\\9S"WP%i&-*:ֳr~O|z#Po] *NpT*=M-ԫ'eBTJ-'DZگpJP}RfFtJ2:JOF,Xr.BKmJG} +#-د7CU *C-@\k-[*DM\O:%%2XX@7_{|a|z_fLu-枴ԭa+(Wyi1P;”x)>IGUo~"!͚I;;=3a'WDo6qɷh 9{Cc UB!TfQiD[ ʥ1 wMK+SdF qah4g3RYZ5J)J茗BWl+U}ŪUBJ[oE&Ct8]IJ#{C準݊lxtEx&>"M.Feas M4ʂԗ Tܝ0/Uq(x?59;IlΘFr1A=zf^!߫J[BB}H@>{0[cڮ'i9-D]PKZ:IH$5RmރF|Ӕ- Eʅ*,a#ˈ Jȓ뒧EnK"W%)*ZvJ 鸸D˹"bayH 2mrH n~-T*IVuJqVRE鰾0}{E?'wf*-fV"uLPi2+m ELR(4_x+0YmaHԠrmc]u&e'"rkhY/E2Ia*CeZn8N@U!eވ >G.m}]|%*`\Jr3UIjm<΅X_77zj3RPK\, TZADj\Е[bʸ|D#6֐bzAAQ!K,JuR:EVo~.p@E?;uFXKHnEM;}%iV Hjty K"[hٔ,%8Eۋ`^^b%RrgZc(O梥mca*Î?hY1E'n.GNꆶ#Wl1e;!.ǀKIE%'co` X7Y:JVsRۛ|{Y~bTruk.H*8;̲!y0hA@Iԅs*Q>w-686=ϰċE]IZ؈I@̉uR %{nNVo":pPjB将q(w܎hY2h7iqmF=J 2>K03!Z]&βN)bx%0JRiH(P'6=ì.h47rr׆Ն(6n\Y\mJR~GDuH+qV4IDs۰Ŭ@ӟ F/x5ӘMҋDE0dz}pEl ]i++z2dJd-qB]:Oۏn|Hc\@Y(pLdѧȋPb3GLKD)*}eJ^H_8qCZA[MYeWbӈ#*jZPRfJIjSj+!Knnoc[t\R GqDZPW||e4/xi]lSuV769VYNJǚY]#+{X95hyn}߸/JF\IxGvZ95Nei 22r v1k mim@Nm˾݈5'A*ٯF5?OJT6܃{w8NF\9󈌦LKW$[RTok}S:m;A@tw*zuKD JuJ8BmH=Hقj҇ziOY#N{X{N1/<<Ϥi'j]W3Dzf^@T44˨PٴAiK(\{`}I鎳%ל+&:֤IvXNaPPJ]P]N/ ;B0 7 Qݎ}n5eYQ3FX@2ޤ6kJv)1j] r#37Of M>қ\Zc?N*84*p}b:S %K6U$}eʌP^7'۟2Hse DY;QWSb0ɨJJX23ɱ:HQ:HX\g<甯S+5RB}eS@ԭEKzRBcY,*!O"wEhSC~,ANbɍUF^5%`jrrJ٦ ^}VԲTKjéF$0*_/ˍ]u}AB]kQ[,AaQ﬒“J>csV=7R%6[C>pK_!{4U1-.KSǣ<m@RxH T|W$Ss]SJEO6>ڗ$|V$QyR왊_S_H)`I=*4|']TtKmb\7{`/Uu3snԔ^s\EɰVy_o5J̔VpJQŬF(/dzdPt#񉹾CD*{ R oq'@ywX'l6 P+^"IԷ5 \~"$J4!kjMW6*\h,Y{DU Lo}O;otкGOu:&pPDS &)* v  VZP{|ٽ~M'jH~V"QCj a*\2tC:܏l#(-LXAɌaPGC:^F ,+LV*X"|sO i5PPԄ8Sk۞pY253ۋjuzQM-]o&M狰ue"+ =yS(k}Cl;{[carrierwave-0.10.0/spec/fixtures/multi_page.pdf000066400000000000000000001536341230347170300215420ustar00rootroot00000000000000%PDF-1.5 % 3 0 obj << /Length 4 0 R /Filter /FlateDecode >> stream xWɎ8 +2@C\r `r9@iL/et[=Oekg{}ǫS+C^x۶/דӹ-/}C@sK5\aw>}u$gOs(GaQmްg x/OO>XlK*v3G"/&v&]$m{YҘ8qӸ|5RpPlN !4 h@zTAd , %O!:d N0=gVi<#lb]+|gb;ۛKC_t]XJNLZ'[p.nU$q NEM +u 03,Pj l;'Y)12 όqȆY:)^#) $HϮ?OL Á=pdYp4NQ댌.zQ- k j9AUڝYP ]bA{&ٟ=&b3O~CCn74A8pDfVmVԗPZI+weWU)^o]L{~62jO8<ә4*igd@_).jlФȳ z(mUgQ 1f >n«!WfL[YBt)gʸbo۔tUF)SN6,ґ+)_u =Scn> /b2 << /BM /Normal >> /a0 << /CA 1 /ca 1 >> >> /Pattern << /p6 6 0 R >> /Font << /f-0-0 5 0 R /f-1-0 7 0 R >> >> endobj 8 0 obj << /Type /Page /Parent 1 0 R /MediaBox [ 0 0 612 792 ] /Contents 3 0 R /Group << /Type /Group /S /Transparency /CS /DeviceRGB >> /Resources 2 0 R >> endobj 9 0 obj << /Length 10 0 R /Filter /FlateDecode /Type /XObject /Subtype /Image /Width 129 /Height 151 /ColorSpace /DeviceRGB /Interpolate true /BitsPerComponent 8 >> stream xOk-u?AG1'< !3fI@Z׃0ݾILHOy"&L~}t-=RJUks؎m-g__z}}_?7'vy+14m#׿~Q7HēT Jư' Iġ5?(.LTL٤7)ZT>DGla/%"JiI~[`)}%&]5OA2#RCaYDU7P;|]r7y堼c04x?Ea'Eg:Q VG:#PDøPƜwԯYxЩQ C!LU0?Ԇ='5)_84 l m+^O?k}ID)^:,%DY?3ɂcujn<^uHJ`ziWb%@HJIj,j*ⱨ^ 4>xɴ?{JJ .]/t6 s7O_?[BwyWŚFȲqP! -[+uf4KduxG1Bq$"ĥ_$37lC׌,-gi 2`ЫhR8W3U„{UH`VΙX#|Y:RC7vU9EPpR3 9_?LmG|ݕRṔUQ '_0,"mRZd"G1T JE.voSPX<N"UPZɏ_Db(C݀#i(c.;% ) ksJ1IR(ˈM!6ʢKಣ2Q" "8GLP殇Rڢc/" YhpP3;h% ^wSu= ͉?Ne.ҧ* MwMt(-Pt04.8!/erWE2fOȤzP&,]YyE:.3@ (M.kIt8iTY˲mn͟i+pA ,g3V>2Gڹ{,Z~GF%%0~N͑Xp:P>BJP$enzI P '>鶌LD(\J?_#! Cc+QOswﴈ~wDن> Y(2 ӼF__4\}$Cп0?Y9 ԩE(v-DW| 4v}݅3'/4q2C"WMG@RJJ23B? ퟋӝ)(n=/[s6հpZtE>??ںZS& 4u+wa)xVCT[d )(@t֨Y`p*rrU +LNlaC怏?15d/%p xfgY^f8̘[2oftR7"#96J/Q : C-T4u#5gWZ/)u)F.H"4K /js#]c~˄73"v+r|%!pP,0 k|^>BPa\<  8p> |kp^r_i8 ] @7~-'OˁWfB#RP R\izek!#tc Ҝ9e* x.SJf{j(:ʟIb,;]pXM^6h/H](E*s0.ǀ A;߼Mk ;zU)NpyvĈTW>W=ݏ\Y q>3N ^pM,f:o׍*s33}Q#q>^rMZ'Ne~-5Ҫ\#aYͧ;ζY4z0ITu JfⲨ c`)DTbHDVU<-vr-Y{R@hL{baOH[6rQLZmQ]0-+wĽ?2T5e)>怟٘EZ> o8$5wu5QZRp6{'!}`Y0Gp NuC|> v,ˌe i>?LcS1>ڹ}h;!ǮT'J0Ă QꚔL épqHkENq o؋O":s}HD(>̀ߠSZP*Md!+͜K ~EaW8T,wvIV` <`PB`:'a#,)Dl˝: <6z2ag9y'{jU"SfHtSVsIe{3D=*6V 喝tS6ǖYM^J1E3P5'Zfh.:Fy;" |n-@= /3t#ԇa߅~ {)%K}ln9)MW{~~;:VqٴG>o΂ 9Ge0PJaaB셂TFũJ!W 8 7'y? o|@l A򨵼hKAכ.䮉<, ^ො6MW$J 6=˧Q_~.b#jȑcYp 7&vJ\9l;lkTɅݝvz͏÷a\ xmn?T@^jRХ@d9szy%W_:eţZ(ׂ3^bdLu:i9 C裤ݪdϬcS0='K]mCuT".m X11Ef+aOlv7ZF-쉋#i.sU{v7N?ҼsABY}eps֤|5E QӣNr$"W'CD;E4|TA7B'd:@ `nɑ!cLV\oAlQ||Fh9pf̂6-̍W,B l["YeJp3a^F"QH[wNiڶ?>Ͳ@qw0HȽXD&w[zgo>C]- աo|>eQ|lGJǃ{tۃ%5^}2}O%,4m:ֱ|d[7 )*GG6?]˻`6Wxqm= ^m[Av. endstream endobj 10 0 obj 6607 endobj 6 0 obj << /Length 11 0 R /PatternType 1 /BBox [0 0 129 151] /XStep 2152 /YStep 2152 /TilingType 1 /PaintType 1 /Matrix [ 0.75 0 0 0.75 142.3875 581.75 ] /Resources << /XObject << /x9 9 0 R >> >> >> stream q 129 0 0 151 0 0 cm /x9 Do Q endstream endobj 11 0 obj 32 endobj 13 0 obj << /Length 14 0 R /Filter /FlateDecode >> stream xXKk0We^z+,PzpvׅB M?TfyBȖ439?0]\z{z6ҳvoel'+N Zݬ`Y~ q ./0` (,wMN&B xwM *;ާ~PC"Hw烲ߋC$L Z| EF;#gL)-n\v-b|,u VO@.7kkU ;c'@aք0(VbV`“H.(2( R@9qîE#U1U8b94qWCa=hɹy^e+>LH˕O+.ͬ w&¶lџƗ*>_}ȇGV}eWj!$8WZE.Uc^`mnB|nM_wgOϞ7G<_Щ41poÒ'1h9a57ZWO)w+kأ#Z/@@63z:Or$9н GV7sP̯EFfĚ16 Cx(,1'FjڏMC~o<ޤPLxKD7,8uctR >> endstream endobj 14 0 obj 926 endobj 12 0 obj << /ExtGState << /a0 << /CA 1 /ca 1 >> >> /Font << /f-1-0 7 0 R /f-0-0 5 0 R >> >> endobj 15 0 obj << /Type /Page /Parent 1 0 R /MediaBox [ 0 0 612 792 ] /Contents 13 0 R /Group << /Type /Group /S /Transparency /CS /DeviceRGB >> /Resources 12 0 R >> endobj 17 0 obj << /Length 18 0 R /Filter /FlateDecode >> stream xY͊$7 )]럲]Caa!lrX?UnyXd=ݳ3!4dfgY%YU*u Q]rF[we5~5G3chOe|V>!^)A?f_/f mR[C{!'^bKq'jɏ 0fh *H('Yx igDlC' lOE@WR+h#B H])BxD7oㇽ>o.?7oÃ1d1'l3gh1@76D'"8 ԇ9#l߬ i^ַ1X[p (~޹!'? ePXO b%u`(wh,_]e}fG ^1mBwV n(3p»",\ɺb;,'+V qE@ZFBl,y vGVNI¼ چBP2E;!.Y=3r_=2O}Z˧˶Es0^ ̘zc@ȿdϫZt5Te2".@G݉:${8 ^ l9>`q0L7ť7xg0Ѓ 81aKB2;tNJ;!ľ$SZ\DjQc`ɔV֐$d֔A\h8t;gV{cU;inzϘP6acÝ]8p_0z G=Gܼa1yw{c~cOwIzOצV)XHBf1FK(6;"̕(^" .^ gd,н`#+ Q @$pFEmܸX];J7[inT& HD^inc[YnZ wh^)/]*O J,P">_cL.lKE hRtlq8RdW2jOn2dļ:qYĂ3C@S+%8B+Jlqpن O@L!ӏRMf?TTC/>UAr $:q*:,]9U߿2kkc4"({t'],j!8Zc:= %^'з%g)lvlj'rg !Y׷tEpV U"ŜHdZYooT$TdAT-?W]0HQ3>'}HdM,8?AVOO"e68_UFd&;}#R Zr.myR)2c_-5ޝÈ"ܥ4`E-2ҮE >!$రO`s9&փў;w7V t_Ћ endstream endobj 18 0 obj 1505 endobj 16 0 obj << /ExtGState << /a0 << /CA 1 /ca 1 >> >> /Font << /f-0-0 5 0 R /f-1-0 7 0 R /f-2-0 19 0 R >> >> endobj 20 0 obj << /Type /Page /Parent 1 0 R /MediaBox [ 0 0 612 792 ] /Contents 17 0 R /Group << /Type /Group /S /Transparency /CS /DeviceRGB >> /Resources 16 0 R >> endobj 21 0 obj << /Length 22 0 R /Filter /FlateDecode /Length1 24144 >> stream x |E0^U}\=921BBB$ $ 7 &[ %窀',C]Y]QvqT<(x@fz鼪W Q+bPpʼI Q?*?MY(/̘ws "g]6-E9me7#4 ys}2r*8X~^q[?>X.Ί-ѯӿs\ZlĬ#Y&%J&cm12~odF ϫ6O@rNR&H(ܠ***` +E{ eG2\挸2{dO$WB'LWbc煊?G*;Ι-e=r6gYԈQsÙђ^J8;쾤p:v2`$P,*g:49g+e]7s#əy|l~CY3f1v kn3q+!cl6csnlb/T7"E>fDH^'4h+~y!9kx/;TTTVV S:NHL5m֨Mobň*܈c@^Nasq:Um+|v=ᆾ ~^7ur6וM;"mKxޮ4(}N eFin3/MӉ RB-p.zrwO_?0O?HK{go)~\t)ȁMsɱ8|V( ,>VT N<8\͠G%YyR/.u@5ÕXbslX't[I W/#n^jYAElBJ8h@wN|mx5S6v&#ݹ||08'qg%L3E$fjz\Cnv@?:sǰΞLI`w$f~; ',6W`}~Y%,yJkrߧATjBR@F "'"˻]u^6 owV{g[D_;dsi2~@rEP8b# GBu)BO_p[{bMb7.;|gM=cc%;&ճg1O2+o=u?abr?(1zPI LJ&KDX-U7 Va"!(l l½vO|IsBfP4))b'^s\ 6()3bիkmQN{Lو7v?,Ce^Kn?jk'MFъ-LV Y$`10fG_I00dak6Έe,SnHaJ#L0МTSbNyQņ>cx}TأaːB5m֠g\$}:q ,VJR.y6EJ-YE)t'Tfa@73CɼMS[Oli߃G|B_sPu{Euva~|+X:l[%~jTHE Z6JR\:"KUڒ:+%%9 ,a$ #Y"b[8{=G,Al=g,;\B jҩ D6[K X ĵAivJ jtv*Tzh14P O gf36t=׾p]j<4wɋ,^"wM$O8~ػNqɯ33d"bC+Ɠ1(h 2&M5ޱ~{?ݟkq1EhbDzjHamo<09|`;c)&N0#@sa1"rʌjn2Y"KapFH 5ڈ4ʆuj|TqNS~Eu*B%)q 0{ڱ,M|qgߺt'6^zj f6G $77M7$d z%Ǧ0SfFK2f04:P50{4S/4dϹj1DHe*WE{L 61NMs-bŴBYYli`[YuG>ÃHѠB`yEgHV&m{:xn x܁j`87 (z CQ ZPpT>iVP" IBsZ3~Yڊʎj&텔d]YYц bG4I9kmS?vo+?ke_KrTU;'J6婳\eU7w-;ϲQB܏<;I(6QՙkǬ8K:Ё2͡"GTqbp&'a=tUm S`)p뼢-Ai[k`2>eGd^d^D5&@`&5HN͕ELwn|Vѵ7yOگW[BswA#GoZO h !М'gJ7:Fa+ sB{aFAYo789Y<[\y򟳝 &0Sb\Q&(Ptf#xc:pCFw)+*7ɭ2+/"Rd ?` _Pk DE(5ܼf?5{['ln~v[Բ;1㆑#7&~*q鮡]O{7S U?Poe3m*k\=g/e rd,!J!] b 7 li<[7[?m6Q5AMNY^՚u(xZޡ*=?G4eM#zvV=C_]uOtrt&w=}ܛ}?QE*& > &6G1U\q$gX_w]F[6!+z+;<(MG Ieak!)T0Y(e<0 Q Wa`t.1/:j؃ ]xL.jV,UaLCЋnmp2(3lЋdK2c(dP3nhˋZڠn:RTu>KRj5e^UuҒ>Rۯw޽Wq9o|ûCf:ߜӪ "W ,'Q Jþ UoyÄ-_L"qV@rHBwK`> F*z7_ B7dY*aj;(Ϲs])|Q3n",RaLS'\FՉLĉ8pqo`z(qJDc la>EpPnspnen62УssPׇc\F߉G^QTk:X߄q񙳸9[~GmH-[m:oa9u98ć#qY~!b_[}u(cs/m EH;2:˾_bXfv}ayeNv!K.g[緘LzlȈq۬ֈEIo2#:٦VEmB>GzT5YUj cT]Ebp&ʴb T5/Ǟ&xYR .yJW3 Nr[9ǥtj%^1qr rW3ΨTTj5q u>%½{-29ؑj̜ZnfBp"+HRk_|O~Ns%(}֞/`ČJNw)zdKȜKտ~fCR6GX09j򂞩<3jd p[3[ y p V.ɱ2aRlM櫓x Gyg8{5bxZ8f{G;w0Fʓy/RCblyN5 IMXk, *D2Ti5Գ|P/`qHg_ ??v,s $II#o_m'`k5NdڸL25lEh> +W|Ox*/hjJ4,(bXhiZUY(Č B[FO퇠nA35f5GBqf)e|*Z[^F%}#6qbJC8X5nI@u~ !h@@FWP LIYlV^^(qd2SW?FճdcY(?y@UyuxVoD?d*!Q :0"uEDf${e6SXՍzI8)B+w:t JNEsJ'ѹS 3 Y*:^VN9vx &8Üu Y# J~l0 5̀!bLR7R ]#3}EODwHB oTrQԜkP/0{AA:4\gR68곋%4 tF!Wz(Gmwd8Q]6eKnŬ7٦:Ι̊ήb2Ӊ&eAo{NO#AV!#vwvoOٺ.ri+WIIqz] vQy?2 g0=6])]l`0[m@g&<Sww=JMjUNM隅̒ aL''v&[o^uMod N[u`ʇgq'jg@qcpڠKOQ=$hj+ (a5$K~5קX1̐4c`I&-,v\쿍#*CPd.?.ìLT0L[ۀwRCxj7!0]`-OX<*3<#XItɈڡiqoHp7ϷPZj\fZ#7=da0}eҤd3MfH^8dbV z%Iw:Q(" \&QGYYLV+p+9ׯ[THJqcJ~9zY2ᗁdO0XQ4$lhy,H:B<|$i/>:zmCv˟x?;LI~}ĽȋVM)6tNg|8./ fZkSquxeyuskg.#DqIhi 6`8_K *vijG*&0ǽߞwd>e_{8?xg[OSI\{)0$4w{)>f.en<'7S *wyRqR6s}6(;F3`r Tn=ɾh v@c=]Ǿʫ)cixK7rT։Z;>@v,If U/٢1A[yZàZvrR,Dn_hl m}.2@ k=3ttjM蠝+*bGO݃ _3|ۨ<ׂ/IV,%|T`IE4M7jhi3am9`''.?w\qp;dԢ/5aWM}9H|\NHS i5έ;+mX*Ϭ"6Ba3%zKЧ=]à)d:Q]ܣh>Zv!v+oe7H+8A#YZQ6 =nս{cc1GܬB'P!'c;٬,#y ےVuFXĐp9vd_}@!tޱ@ GA~,t6R!RAqg9z)f^x' yՕUp-,{sJK]W>-[; m.Uu$9[6Mxyƛ9ࣽ 4cU. .8[WUל(Rx (j<'H#x "Ζnŋ[Ng|xb1L|F| +bo)h84RI$p"I0$.<ɀ 1Lk>|$d12&c3.BmBf?I7EǮu9 JC9Hϵѫ0f'ƌŨD(A* ktLC,7FYSe-"q?IљsP!ۘcʶFŵCv]~/0}@03$p, ؑ, V&cVbqG Fhl2dS)Mf ezjì@H]T 2ЁS`+ cU֌W7i{E6q?7ks$oD) A}JE<Վ LA0_}Mxv XTMbn!ajg {9&;@IWo~:v9yvd5Q>c}2.춖|4E%s9eV1 I<-ڱKuv%}v87NN{/| `3AgQ&ZK{{d4/d$cHş"%^C ]! 2C9PFMYʍA36)N"ZRb9q8Ju@*Lm/,l$U}w:[uw{Mt,=3|֖͏8N٦' 47шWL'y uhhg{{Qhp˳,f6XOO'c̜hoZibLھFw}gɤ[|:cu>6e3tw.ٳN 8Ezz{a kFk4 M\x!hkJ9ff+b*vg|Ӊ~}vWNXӫg߃8_:3"&vmΙ[]Ól/ped7$W,҄t$9N}Uj$8J2# Vt/FAHL\#E2T"_8R/It2K%-E72qt#k;qt"zR#zI-,FߢK肜/#Nv!r\TYTSubYň$$I抜dɩ/0CRDݫ{U)HTt:j:5eUe" _lHӹHv&`3YBӝ_Ob*ȇ>#1Cj2=_it׸7bk_eY{Xxw,__q)$S`2P?"?t}21a`yP-2:E2aŤL&ִ?ve2"+{7[ߺOv^\%J"b;&x_0;Ox嵷}-U `їj>\ w?(&AO5bzsaKVU|JW?%#ĵ:<6#< &JyzOko㐟zQ&Lr- n>HV RpoUo[I&d%ax4vxZ%>Ԝv1Q)ȅ{(Z+au)R<~O{}sg)Gn;9?zov=zju.c-538 Ն<޼ˌrUfۦ:6>cʫ FVaSp؅rvu8fdCa uˑ4.;G^z8 ft?;Mͬ RTC 5,5Ruz8e $&VUF, ]q _8YƥAP3BkDl"4lň($H؁7j3m#B,v -U Ŋ+ץL&@&]Q٭a32*AZѦWueez5ZA-`DsH1c`jڞ(a]Lb0]s4| K~ځ*}=Ǎ^ N a=>25H\[#+0 @p0DڜW # ^ߋߚgr~v7Ңh`( Q6˔pL++d퉽+t&ԇxUоX MӒޭan8q伴Ǖ?xθ~c'~fw|j'sΏJk'oyvqvcJSǿo xۏ^'޵+%SBܳ SQ3j<26dnBG#soo߹qƜ^]},._pcO>V[ z1!qov6ŻfE~o;t+>JO?xzzFNt.Rmjcm7GystSssMef~:aAui.Cg9`pH.Yls JpvmUS<*3;jU 9om]Ҷ췶;/߱C[׽8qO"y:H㉇?gZKqs#ܬb8Sz'Ojf4XX08^l C VȦFS1f i|ϥ•jHfO[h]\!P,fNS Otfv?:RU8bVfLl>1^*$I74ޚr=R|jxUw޾9ƛ쭧lPn23s[}99ls?pkl垡5|c|x:313#|gvCo_/9pt0SyMr `8J`,5"ک eHZ_27vWkVļ9_׊+*LOS:|)ص5ſz v:ObD/}[oד/&oK;eb3ſw챍Ti۲]BՒi96RnPn:F%*z !Hv^sqeY[KӶ(PH[߼,ɽoKXo=DОwriLtu+4y@az\O/_F(nHHt٥EWB́esQd!j q3ӥJ?Ѳ/RK` S[֥vq#ֶ% $5q蚸+=S^yUt,ւbW}ѓڰͤOЈ7bߧ]/y̰[6]tݟ ͐'_1bWjW&dRA<ڠ6_z0%uHfD&fdAV9@jv/xxNe,BE(WBQ; =ߡu>N4 v6xmnC_h \<C#|t7\PtZ[u{%FϠ_]-ljw7# O 8Zh!zidqrFA c`0t!14%vSx2=< nh)ԆсNc=w>48n`xvW|ItJTj:e2 N0~\!r$߆9h+?XɼLZXFF`k8 f!PM>\ME?1I)84ŒDel7 |;~FM{);vwah~7}G | E1d43d1marw_%K,LE#V)tr})o6|7~o;p;< ቗H&aL~G#'8E%?3N&1%LṠQc6ñ'$๐{m^ryp7/?Օq%'L%ړQ.U'1A]-ypf&ٸ/LƏg,1OswRBZ8n$H3L#] #0:ؙY d+g)`c4s 6!+c'$1# Py ip/FH$n:0⟘$bpR3tCGCx*;݃r%z"u2@v] gCq#(=-F'Y}̼?I csLh-jNB˸:xb8aςt[!WTiȁ~0q  Q89R쯨C: ΈAꀪx#1 MH>I@7%C@K.;nGMh;^-Pn 9 Lv#d4y ؎`?/wm`CQercMHGd4p=s%Ɂx3hddhfr.E34Mbj~je*.-).*YУ{X^nNv4  v9ൂmf2:YҶT1`<gFÓb5M T mxImOzNjz'V[~: ;uP*\wjaZyV6@9 ծU8n V. n['&wGeuP; vcg_|7@TUtq&R=ij|Ⱥ*o(T-?L Op)uAqA{Lp}tWpw ;4):.L0Uq-\-]e6Tf #m Ѵג 54.#M[oZ4ƥp `j<hԲPǣ?S0.Wz|mhèe{jۖns tAo0jVҺRͨtD@ . ԛ&z SzC7c*>fdV\дA)8Q ? p緿#)\%5hRbMGo\?J۰a`88pCӆIް%G7,nB8wy7fnڜ}:UDi'?ΣˮJ8N%Q & eWH)OSy^C-h:ٖ'P24CmOGk _ 0 CkCǡW:hCp=Cq.nYơ'POC8L!ל́yh~@z q0~Ӻac)ЍxoAF:x|sYb7_ 4Y⋀Vx,CE>i B Z Y6# ^BnM|e=p#?;ڀP.'wBy6hvX zX ZP Ka+*w*gwޡ/[qPUTƁ5ԟ"8I P 0`^Gk8 p^kQg}Ejdwiٞs IӆFtT>ld*Vֳ8Uݽ*OHa+eC~ƁNRL!`me(@>]2=Y-p`p# n3 $I=@#ҹh.o8 O|VM['g8cP 0` a -JBB>R9 4 ҲZ!#]D7]8 ;~ tc (@aoFAM x( wQ+fq`!H 9&@ Smr-?k H=AoW g-r?ɫm꧃v(+v'H3À=*j&la65`DApHZ˟EOHPƂ4_%HD}8IDPIUPIt($:u6h0J4֎$䉗sp Xt3`fĂ3 c}[^`Q5h=[QI: ކ[W z#nVnVĽXmi­qNڂ[5[pk$6H˪lO?W_׷c FC@!`ÐHjg*t f:4ܓW:^^8W@SHN0@_z CL&-5A`"Jxm84?=]z]K+phC ŧĔM>lZOJbذGO?O"M(&bs:sF?=g!? TPG Zi^| |h~6ҫ~ | Pw0^mw}}w^!B͡h@P;u4<f+|s|ZôTÍ-p|j s_wc"ի^/PCy0\а_l-YZY!hf,_VԌkG,Dž㸥Ռ⥱0↺SqՁjJ5w{izr,tUZV)^!rFu3ㅴ̨O}|u~/gWLߪ-}GO$qZV'WAO=ȧuaeܯ]zy˝ړk_>'?{Oi~hSz@S%3])i54yLOOO Wwi Oj7jSAVGSTUgЈ<Ϋ*?n6ެ>kPh.̓JJg!GQzp{|@MP}F}Bۼ َtԭ_~ x6i!亭O{oO7)PmGE[#W_ jbTk:Nj#&7&^ ~nA&J+u:]5;PYN+\!H$ UP0ifF9H r!L ګl.Haf]S / BYp(]@-Ű`Q endstream endobj 22 0 obj 17997 endobj 23 0 obj << /Length 24 0 R /Filter /FlateDecode >> stream x]M0@ c`#EKP0&D!oH{v?)jaxnKa̬3V.͜[~]8Sߛ׶x]yMmcKa}nҴ?|iGTb endstream endobj 24 0 obj 487 endobj 25 0 obj << /Type /FontDescriptor /FontName /HMAHXQ+ArialMT /FontFamily (Arial) /Flags 4 /FontBBox [ -664 -324 2028 1037 ] /ItalicAngle 0 /Ascent 905 /Descent -211 /CapHeight 1037 /StemV 80 /StemH 80 /FontFile2 21 0 R >> endobj 26 0 obj << /Type /Font /Subtype /CIDFontType2 /BaseFont /HMAHXQ+ArialMT /CIDSystemInfo << /Registry (Adobe) /Ordering (Identity) /Supplement 0 >> /FontDescriptor 25 0 R /W [0 [ 750 722 556 833 556 277 556 722 277 777 222 556 556 333 556 556 666 556 556 500 277 777 556 556 666 500 222 556 500 666 943 666 722 610 722 610 777 277 722 277 556 277 277 500 277 722 556 500 500 556 556 556 333 556 556 556 556 354 190 556 556 556 666 666 ]] >> endobj 5 0 obj << /Type /Font /Subtype /Type0 /BaseFont /HMAHXQ+ArialMT /Encoding /Identity-H /DescendantFonts [ 26 0 R] /ToUnicode 23 0 R >> endobj 27 0 obj << /Length 28 0 R /Filter /FlateDecode /Length1 9256 >> stream xzy|SW~9j"fkIeYdK7][wll ,m 8Ö&C& iI:% M)!3̄>kڦ df:3!ӗ ;W24Ӿ?޵ro! :hdܺk|w!w{g< q!?ݶkGL!m-BB~MOO|L NwB;޵gM"5ЏwxB"wh ]qYz⟇ U-)dȈ\iBNw)Ly]In6MStòڽlOW0Yw)\²Kz65X`,2#5f5Z>z.mGZʜ:_QJ,݁>ZrQ2+Mw;(o$Wq)\<,3ɘ6NWV)($4W hlMOqqxMsl S#8ev:|b"u: ~ÖLWLTYE\NS&Eb9A 4SYr0@y>6l\ vcQMW30g`l&DLe265tu"QЊ +*Kqٍ~MY^wM j7宬lUTd VM)Vڤ&\к=;)ܓ"tٞrGx<\TO=^oc833γJJ n}7 u_ 6L b|^eg>2gFBme^?ӵU/ڢy?3dž{?ފ{-FW<^ȴ{nD]=v}XS&ǫr:{Μj֛{M΀"/p*J*\ķ~^u#%>u0۽+%g[euGW7ԠӚxMeefMWurY`.,Kre./PM4I9n+J&Sȶ(} O4 1?8en/).p՗J7|~GWxe?T{!ZHM\`!,V:f:uP 5YqYѬ(Ut\IX;%%"[nBRM.%ҸoeyqI1kZ2|m+%'fgFk#~սؖqVc'L]Tְ1wkw? ˙HC؃.(>BbRÝwBy(%^z\C kB^Vob͇q_,#[jm߫B{uct]~Pҩ->MjwdlMqି&Oۻiogps1skc[s>{SArNhDџ ѦVhMQJCIk 5`຃3($ QDkݴm!;MV(ZUTJ-K 8uD /[-eo]ДJ-x/V OO)l0@)sv&injJ-^zyM#+[ґ1AejEEDߑ͍X[ez9nc[>ں5^S[$0$PP)YM*yschu1Қ>:~}xpǸ8hFϲ7TꂊnQaȡrxC P__δVM+VbZ#VZmVAҏi=zJ" ܯP F, Q<6a@1/kq@og%7%ӧvuekMvmBbNX`HU:=x'!M !G F}:eu)Ca(Ά#8lБ%@r,9|.ͤt=D`L_JfEG\\MPIw$wBpCߤ/ͭ:F͝pNb\ mrCP5p0^?"=[Qz ؤ=YWQ|iݞ |y:}nۄ:f_ZѶѠ4 ^ o>K4pwBC{zwB|"MWY|.Fvv[)>MmkrWvlaU}~5Ayd*GE#4jd2i=Տ\2?_q/C[rwERb(j6fw)\VTDkuCm@*c59amùjv)|WHc“|ն{I/;:BnMpK$.1.ggmOZ#-LmԞ-8戴0*c{mڱ~usg{dw[cmJB6)5;ލfĝ= *@G_ɖgM1c` 3/rHΙZ p6 PBxJh}_=?4ʡeYQ;=rݿΝҎ/pߞ7oӊ *Sk,XlT2Z bdN &iO+{'.JFS#R Do&[~xqjf &X\=7B_509;*v6m%Y c;Q'kUY&@X@Vd*2 ؅e;=)vH*C/s?:S84=VjQ Eqɭ8~&/FF.7 .N?oO%G5>9)|bCy' 61ȅZ٪mn{=WUA8Q\U]SRڄS$x>&j`I=L[CTϧ:t}W#ʾ}'r:[fxH,ɹq)[. BE* Wa "6K &i*\7UD*:^Fk2_6Sf) R q?GkUj?=3XbgKFgf`êl˟]~32'<5g}Ü}N==Vj@%W!#|8VP~Eo}8F+D"$FT Q*h+CrRX[ D ]p+G 5h&4@'ZN;Ϭ"@/j"(~͵h ͣ2z?r]A@op Vpw-{^^t~3AUmQo.La=ʵTT9̻=t|hSГ^G\ Yx789)WnыBɷ+k: (J/"ځG 'Cz{p(Z[M>ocY稭ZULɠQeX$y4QmK:ó0]]B3_Hg=&cLsÌwdaFXflEZc1f 1 xPڏ1sksm#0`138m g"O/!Xo@dG-Z@ZjfvWcAU)$*"Dfp(L;S̄.!q;::a\}m 2%m/`&7%28] 2r{Ƅ2Ԁ䓙Z&ɪo+qo1ƅ̍wsY1"wa!# +0FXX0 ̺LdFya#{3C1N>Ԥ3ɓc_7E:@&m"sx(he: &=(cji;XLt|bq`jB:.)i]Z  QjjmL< XD3$icLوz8-?=)4\zBSK+L]EVޯCg)@(u{yG`܇^m{kx;D“O?6ѯo:F>B>hMp3pOF#cĢo^w=S~Wȫ-qMpfWŗ%.ig_D1 ",3`oJ-?E+^-a}פѡk^ װ}ϻl҇4͎*5W@l]JpJ (\bPy19di N GQ9R* ")g!mtdVa>_{/Wj 7f0yC23(acbǒG}uVfÉL"ٛK!XT;wṱ9v4ʙϡ\ q$\hnd׌J endstream endobj 28 0 obj 6608 endobj 29 0 obj << /Length 30 0 R /Filter /FlateDecode >> stream x]j0>EâU B^<}@!߾1_B ?盙|Τ3ze黛eO+Q$$%渒-VHr\)i2Z֌ /6[ ^eAE:2P*PP֠GtF$@4 D)b~#H9gP> endobj 32 0 obj << /Type /Font /Subtype /CIDFontType2 /BaseFont /FPFKOE+DroidSerif /CIDSystemInfo << /Registry (Adobe) /Ordering (Identity) /Supplement 0 >> /FontDescriptor 31 0 R /W [0 [ 500 458 634 352 613 287 288 861 286 535 562 492 310 645 577 310 538 564 944 451 471 559 259 369 559 559 559 559 604 937 ]] >> endobj 7 0 obj << /Type /Font /Subtype /Type0 /BaseFont /FPFKOE+DroidSerif /Encoding /Identity-H /DescendantFonts [ 32 0 R] /ToUnicode 29 0 R >> endobj 33 0 obj << /Length 34 0 R /Filter /FlateDecode /Length1 18012 >> stream x| |յ}&gL2dM6 $@XA#a'j%,7pA\P2 &5Kkh3ŢTEJf~k&{=,Z|K~\ 4[7/jo ֏H;Wo6b p+^!} ey ҂m+eozʟp#>lbՒE@*j~Om$ x\Qv0_MV/VI(IS@h%>A $F\ a>U;}l>YibF3>n"qIO ^A۠P +jFH7RBMqKDH˺N,l2&A >vܟQ: M7&zNMZS Vrt86Q@M*++"P0p71{f\ jS*zCG tX죿_L[\߲ҠŁg'?'p2SrBmڷv5).-e5O]{{'^ͯoė4.6F gZb%[iOLF?=:E%ԍVՓ[üܺI[1-b,/ 9\׋ d{FoIo{oW}Wٷ'ڗx侇}`>2XAO>QJAG R$YEH69Kka.,t5\8"vwRPxaD :xYMvkz}P$iCR&Mp!P"M~Aޠ][-VyvOxO@s?.?#F_$ $U>A5o S]Iό%b?oƘTht"*zblG;# 'Qav~ RЕBd4EzQ^hyuV;w9: i$FOq¬UY,_L;#u }6};uRlu.%˄Y KӺQ+N1l2Bc0~n"6鸓XVuaga6ux\2AlxR5j0^@[f@5LAQAH~G\Ob{wXQ~Ѥ!nKjk ]|hH8Zȵ7O!W\iL_lAR`}J=d\bkqkeޫzZ^ɗ$Zj/L|mjJb?0!"o645߅ꍸMQ.+i: Wx:Z)4I'Pܙug7SW^W;fprmj3}aHŌ/-}}Dbt>zu 7XY84zĉ?'^}<>s~ɳ11/DXJSqQ8P)H,Y&82 :h&iG]J@Lk7ٯXu&C1|{;i'FTY;S\& }HZCWլH!r^MyCn5pxSQW 1TUf1c3$ΛP$>LUT1,ui47fVWZۓXa0Գ)S"@v(ĶʈZ4 $MΝM-f'U,ԍfH:C_mamT\ B3_uUT P"Pa'Ɯo4 ;ٳ o:r-%P,%B ny,DڃY}TN?n1ߴ"T:~;t ӧW倸6/!炓6fO"UU>ԹY_OM#FR?wû:+%L"H$3grRV\R3mfID17hLQtd4zGIŇMz#jݸ?zrRO?s&Gۯ:GT>83f07vL/zjYuˍ>p`:wbn?szR-:Ehh—څMB/6hR0TEATND͏=ZݶF wp&ƒ` H_F[d-!,xvy܋ 'Jsh8 HنQT x|ZN03YtgJ9 84>ޛ6w} edϴ)Ss`"W? .wnӳ3t({=Lψ#ifNYАEr<ܙU9grhNS&[G :d4g2gfrP 3Gӯ<?37h þ1VQȄTJ|9wWO(m{fFHBHy|$3 ]>{۽zyT8Zd5:6ciB Ĝ=N0 1)Xa*un **+Ȳdohs7屶ǎ̿W.jY<\ZyNUkEfӸn{61pMeT5MM nmej0,X<:k72F+Mǵ{_u33giOiiii&WTߡ[U.n޴Elgt1x+=3V[XnPݬ M(S( Av܉CsJ?X %G,,  C詶'jGMP*aU8&j\2M,[[%J#/~B(3wGxU^$xވDrs@8g~8\_cjt2^co5֩qh+M&1f+ M]ƫju 2[ݛ3Ea1Мc2hQ0 zJnp:r6nQ |~v}e0AzX lߥzŘ8$yqKg%Ͽ.]-j_FNհMj%q6I{Ӿ삫LߒgG[NuB'zKjgN9x?O&DꞌGfNv^`YiF,W*1 9-..= dd N wK%!"{ipums 9rUQlM345'eK9`_]ȑD`.CHrµ WwlXpnr׽(_JJAgp7'Uw{Ժ 2){엳-0OB9awՑ\gFT2= %D!-o'$ဘ֎"0I ?{C\+"Q@PEp ;w=j^U6SoֲuU/#H^QR=//gY'οm7Nu˧5o1e~x@qIJ²Sd-ZK3mo[xvM+i,5:TA55\/ 9!vO5/ϫ-J{JVf?qwvݔ5Ľ{KI{efv'C pyb]ci/G52 V 2́ޥh%(tgՈkdԈŢ#& 0UVSjY{{̿qhۊ *Y'W?G$~_~H](vZ6)<+ytV\RGn:̩!&?;юG|x4c$vpZli6dI+]x9vCo >&]Y:X[ PLW\\igqnvQI6g+h7\cUXOzl!IESnvY5UA-&,R[T7%PQ^k Bi?|usgXP>n%!A{ei-J83J8k*9WIonKzG7Y|2}}Lj:K7Kyd? p%,t2N梦-;?~8Z|r:2PW^lhp45ԏ:OtzCpڋ z<2U-uhNUMHWB@yM!":r Q<^JJq{-spnXDtu5g!aϨ=-a]"K6pBd #K}( 4FM!qs{=ggF/o?z)H~^d:w\,^2[,`crdZ-ʂv YamRL`f ${8@ḧ1TCR1b EB "šC8SqJBr{1b|S6^t/dpȚ0&H8M wN5@s.>Bb⍄yhΒG.z$/3q"34kqYu+'_̝7oKvF‡NygҦ6xܳɨU_OU\!Ү+нtQ5f?in9h.ܯg:zdGFXeM*"+wX6rsg,={'5?鏻F~A:ΗS&(]8LIo+}>y"HWrgsYӑpJWTUȭ(^++Zr ̝Ge{-}ukF7bREu5vy+Oƍ}R'ⓣ! gm[ wFR|MŰB-c0i;~7?ꇔ7shEĚr֜4 t^C~\wz_M.{G{%9$=2ƮyX3?{|# .^ebثo0__z<қ㣌ahrW{siIg& O( M\4??2KLf0`ITa!oa~˞Bq ʦJ>]E Yc 4fj5&YKꨎ)H,ۤ 3>$st435-Ng.,P 3cZBcA}8 #Z(:egϴu^ s_k,;i}J -5vmX<6q>ز99/kf5tYn*[_p$\0hE 5ZKi5?ѱ"T̾j|0<ܾる"k߲_)ıknjqa5uKO|x|cճ"Sӭ&̿ΗaHny/'OWb<ꞑ5:Q+ ,%]|T5$(GGG|jTA6%TYY T͛BWhI Hc\U0T)""*aafPyb}5T u2[.Ԭ~ʙVyI&Q$$<D ɜeC<m.8ՉQ4zpHyew EʤD|O [w钳8`g#wAh[}y8v/hER]t.kxdYsO`'RGs±Kp|WݣNJ'7_%¡3=7:&:Ի 0)H,Ya,G^%<#DtV;r٣͆"k:=z'L_D n]."0^dhy r S\yk(1(ހ5k3!%5:s!Z$٭lWn={V_.NVUbeGV R\B9jgsINh~Ȑ?=(*Y7JN6+ DP}yb@29$2"VagFϣuQ5jz03X il;('+7x 2 ԃlfDBY@ٔHg} qT)=C'1@-8oqfaNb;`L%<rgp$%'U83O<U02p[p}`6q"x_W8 B='_H.Klɏ!Qx9?9r9{Kwc zMǑ|C<o+i '?K@AΩ>#d:}''&?a^gA;~ʼnqO%Zr ^|84\J#3ppoWtSrL98+$4F{jp]b#98 '4 _Q=]JpC[ w(:xo-xi!U 17B01>9#5ʓ.k`Q|oTa4 *l:vҝqKn{'o ER̞ĝ_&I>|eLjBr:yx|bϟ@Kqd+B!*Af G]E wһp{~H?_sUr7› ?/EհOΔ  s}“‹IV\*vJK7j~3Z0 HL(k#(pDxN.xp54'\O;.r?y<+5P t]Dz+=C >=FGpN.Rn*װ݈=ͽ˝>Fpל|_ ?#h w31C,/DRjI$g>Y,6d>Sw/F֏sS7.!:TW`H9i00Cޤ~xvz>G&ZK8 } \N“d'ג* ~Gr#&_wDH~ڍCm-(7zmx6ytB|m8?3J$Ԥ'CU"g$KWp 2'h 4#"H?,E]uI5])pN0#ΫB~KqJh!}Дlj1N׮~ 0s쫅ڢB_c{! fbֆ@QvlߏF_cMWilo鴓i OâK1g8'@(h 8's b\n㢥Y ~0F&/ ,A`RVQ`:LLa|l5pp3,n.5-1,a!*>:u\OǪ==[|YY364з p4zS5Fn!}l%lU- 4|190)vOO f_{<`r<.=]ԐgW=E}fK}FXAo8\ZRYy96Tog5UlY5,F4D RŖt=DŽ\s5Fqˢ15"sg˱p8VPDD{s+ NZ㋑~?XXT1z籞=(To̎ƕco1h5yjs}cXV.h:P^rUZ1>DU8Jb)<%̀9'Ffq}?A9"6 n0[_ \&l\Zǟ x˅@('K?<5؉nE'i-{ k20 rP6qYqw9Nob\S@X6T/s7݆Mm{= %(2uv\zpzpkzpkzp= {!N1w tra;X8rpP- rn΅0?#闍lf5MEseuG0ɯwV=K)w3θG9S{G .KDʁXL}eܡ NoH=ڏ( gOKQї >l:ua})A{@?q-cgX(ϥ/ {9_Cy.CƾZ/L :^7)"O!Uk)O'1_ƃl y3z-}Sԋ6BGU!}AnW\UJRJrKJp\_ʷWojMқ1E`NX(B7jNTKu&36bڄufm`cVmY zTHщHѩRt"E'Rt"EJѩޅQ#E;R#EJюHю*o;R-Hт-HѢR E R EJт-HѢR(H J  R(* R(*E R E R%HQ%HQR E R>!)|*)|HC JC RT 3RŒfŒf0#Y0ӅQ #0R #ŰJ1H1*0R #0]I"Q$9EHrI$G([:f#M11!B!RiThcHCRTRĐ"1"1HыHѫR"E/R"EJѫ n&FH+&*_p#SЧŸ W5PT!>O!qoށ*`&Vaڍi?1IjmLcJ %7I3~yI/ K$wEa8,R_}:5zU ܮ0RԳЈb}Y@. R/ j:Ta,%>8{ SU045mpzJ9J0t=*Le0bmߪd=0&?&t{2H dO+`d<{.*A0Dl<[/bnywIx*}7-x-o/H9ngǽmVܛ v#I{' Ȏ{kBlEL r8/I+Ow{ $+22_z=x}1ErzV4[q!1n{RIv{K늎{zyyg{r={M>yۂȍ{/PګxCa_N=0F/D0W5@,JtR.],M&H)[ʒ2%ƪ1kFhD 0 lFEYΫe3MQQ J4A,ks&h^}3'0@ I$fm湓b)9;VnI-r[[ct$ɚnJg}n5}qtk4 .Uu:DKMS?^/fv6i=B23N R546 R#Agv!hU4f#ADLCC}2Hx~OkT<0cƆ>O8˅pPb/T>ʰHkN,_}׋(E^_>Kb?䎡TCP8-,-qe¤kK§=и S{Vb݋}]c7AKV2hY+!1+}tĺK }R־e R1!_WZ<=UWOYwUƪSԱ;ܷi`w*:-p{?:azpߵ1dј>0)fĺY3edzc]Ⱦ.36[|`H_.7s2Q)e uj~OWWZu4 4*ٿ$!mζq'ˍ! $:6+ ee/ji+Q*d:|&x/ٹ,~Y_\2OêT,EX؞h{UonoQooFfJ{8X^{X\NpGk?Y}?aصcO]>~ Ir3ukDRIY C endstream endobj 34 0 obj 13008 endobj 35 0 obj << /Length 36 0 R /Filter /FlateDecode >> stream x]n0E|"W#!*ݰCMRb,C}JEJ;c!=wϝWY]hehe9Eh.)|I$͗mYi0'M.I==$ir.7kh",i[iǽH*'bih>v}_f#Yb"'͕&W˚_mBFm ܗgYDjT +A%2:g>"'@2P.A=f(=t"NF4T p__8E?r £Wcj?ArU1sOF__HĜ~~lE|~UX7q_a ;۽+|~7 endstream endobj 36 0 obj 378 endobj 37 0 obj << /Type /FontDescriptor /FontName /MBZEBN+Arial-BoldMT /FontFamily (Arial Bold) /Flags 4 /FontBBox [ -627 -376 2033 1047 ] /ItalicAngle 0 /Ascent 905 /Descent -211 /CapHeight 1047 /StemV 80 /StemH 80 /FontFile2 33 0 R >> endobj 38 0 obj << /Type /Font /Subtype /CIDFontType2 /BaseFont /MBZEBN+Arial-BoldMT /CIDSystemInfo << /Registry (Adobe) /Ordering (Identity) /Supplement 0 >> /FontDescriptor 37 0 R /W [0 [ 750 666 610 777 556 389 610 277 610 556 333 610 610 777 610 277 277 556 556 722 556 610 333 277 333 333 610 889 333 736 610 333 666 277 722 556 ]] >> endobj 19 0 obj << /Type /Font /Subtype /Type0 /BaseFont /MBZEBN+Arial-BoldMT /Encoding /Identity-H /DescendantFonts [ 38 0 R] /ToUnicode 35 0 R >> endobj 1 0 obj << /Type /Pages /Kids [ 8 0 R 15 0 R 20 0 R ] /Count 3 >> endobj 39 0 obj << /Creator (cairo 1.9.5 (http://cairographics.org)) /Producer (cairo 1.9.5 (http://cairographics.org)) >> endobj 40 0 obj << /Type /Catalog /Pages 1 0 R >> endobj xref 0 41 0000000000 65535 f 0000054031 00000 n 0000001172 00000 n 0000000015 00000 n 0000001149 00000 n 0000031500 00000 n 0000008429 00000 n 0000039454 00000 n 0000001384 00000 n 0000001584 00000 n 0000008405 00000 n 0000008709 00000 n 0000009759 00000 n 0000008731 00000 n 0000009736 00000 n 0000009888 00000 n 0000011699 00000 n 0000010091 00000 n 0000011675 00000 n 0000053868 00000 n 0000011848 00000 n 0000012051 00000 n 0000030145 00000 n 0000030170 00000 n 0000030736 00000 n 0000030759 00000 n 0000031020 00000 n 0000031657 00000 n 0000038361 00000 n 0000038385 00000 n 0000038814 00000 n 0000038837 00000 n 0000039107 00000 n 0000039614 00000 n 0000052719 00000 n 0000052744 00000 n 0000053201 00000 n 0000053224 00000 n 0000053495 00000 n 0000054110 00000 n 0000054236 00000 n trailer << /Size 41 /Root 40 0 R /Info 39 0 R >> startxref 54289 %%EOF carrierwave-0.10.0/spec/fixtures/new.jpeg000066400000000000000000000000151230347170300203410ustar00rootroot00000000000000this is stuffcarrierwave-0.10.0/spec/fixtures/new.txt000066400000000000000000000007151230347170300202420ustar00rootroot00000000000000bork bork bork Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.carrierwave-0.10.0/spec/fixtures/old.jpeg000066400000000000000000000000151230347170300203260ustar00rootroot00000000000000this is stuffcarrierwave-0.10.0/spec/fixtures/old.txt000066400000000000000000000007151230347170300202270ustar00rootroot00000000000000bork bork bork Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.carrierwave-0.10.0/spec/fixtures/portrait.jpg000066400000000000000000001245631230347170300212660ustar00rootroot00000000000000JFIFHHPhotoshop 3.08BIM8BIMHH8BIM x8BIM8BIM 8BIM 7http://www.digimarc.com/cgi-bin/ci.pl?4+267342+0+1999+18BIM' 8BIMH/fflff/ff2Z5-8BIMp8BIM@@8BIM8BIM MpeJFIFHH&File written by Adobe Photoshop 5.0Adobed            pM"?   3!1AQa"q2B#$Rb34rC%Scs5&DTdE£t6UeuF'Vfv7GWgw5!1AQaq"2B#R3$brCScs4%&5DTdEU6teuFVfv'7GWgw ?ۘ+k}|}G7ڬb_k/ 7;j;_@MB4<2uhC_t~P '}=}K?Ҙmq} cH}%X̋z9eX2}W:8#\7È9ϲ{k5#t(;EqM+7s__{?Ӣcm8UB800~WiְXΌGOǢZFx}^fWW˛G^u;}-S6o_cvSSgQgct?) ӺYoAY;cٻz5T/S7be`tP>Wz7}oo^H쏲mi(Gk4z$=O IYP/l?yOwM{1OP SBziCjS]\CGzL9N|LTϳfb-2 lc3K?sRV+iUmiL7}/QvK*!l33XuWY`ki=~u"O+nCk.?nso#t^@kcc"Y] Gw5~BAz~[269:]YsnI5<}fNuUӆ4H8Y鱭YU̬Z[#]vvU]9x֏s۪K^/_~}^5f{8x' ce?Qu8}c] O?Sc  >LclIv>ziffS!u.~6ͦ6Y-/gqwgXtLN,mbpfm*H}|ǻbʲ2Z {}o.͟ OoNn%6`_޳:?5r}KjW4ՁT[(˜T; hIRnt$v>eͱ+ }sg{~W*mB~}ba3&#hwE܈_O˽ M ٬rѲ=Z mhֲ~~baSU]A{}Q\9w8G[Z˦K`x#74~ Uhcb@nߤg58[~A͞ç _fCd}9Jנ}UeY]n?6Yosͣ[V7"8~4wr>Y[pu ;H>\> )ޣVV7wCkvQ)ޞ;́ckw]dnZg齟eUsA/Ԉk}zkG˟Z¶Vَ4Apޮ5O׻u=6~S􊤈[8k'XiZR k=v~{߲M 1G308j.ׁ =G۳ʿRu7e`kPU]Lg~G_21rs՚sUޏj}LaŎQsLpL;vvjxֱ0y(}atp 6bG}z9q .ݹ6][{C'ݶ>ZUpk2;}K,8 iVYV+n;,q&~57vҷv{_;;F5 >;]D; @:n#;C􋉷)VD8AW'[N,s[c}:wkZl,͕zk*C'a$Ke?e7qU8{ph[w[ڻc[VDXm@-g?{;ޡS+cmw-nN魯'b)Y71ƅ*{lsQ[C:okiвRW.pǿ>ɍ8Z6ߠeQ"ͧ~詌Uciu==/ܷ2vT}&ZAx2Omֲ]oCZٕa99A6_\$T6 h,ÓS^Kti73/۽dAA3*ؗ?/yp/,;@qkI+wXcCvbu.yUPe7=污齵,cH^S;,dzf"^CVˬ oۘ-k; jsC,}^z~\]YK`kr.qD{6K~Vb>6KEzEV~/dїCoN2KZ-"˚ 嬪^EUQ_N.Ѱlwc(.%X=ct6pphs7;[Ƽ\krŕ[mksnhmU5\l{~}t qȚ] !e"\@i ^什BĦp -q{XY}=c_3i\2P4l9@Ú1NzELRY`gnu| &/8AKT]gqz !-8COZfr~ -;HUcq~ +:IXgw'7HYj{+=Oat 2FZn  % : O d y  ' = T j " 9 Q i  * C \ u & @ Z t .Id %A^z &Ca~1Om&Ed#Cc'Ij4Vx&IlAe@e Ek*Qw;c*R{Gp@j>i  A l !!H!u!!!"'"U"""# #8#f###$$M$|$$% %8%h%%%&'&W&&&''I'z''( (?(q(())8)k))**5*h**++6+i++,,9,n,,- -A-v--..L.../$/Z///050l0011J1112*2c223 3F3334+4e4455M555676r667$7`7788P8899B999:6:t::;-;k;;<' >`>>?!?a??@#@d@@A)AjAAB0BrBBC:C}CDDGDDEEUEEF"FgFFG5G{GHHKHHIIcIIJ7J}JK KSKKL*LrLMMJMMN%NnNOOIOOP'PqPQQPQQR1R|RSS_SSTBTTU(UuUVV\VVWDWWX/X}XYYiYZZVZZ[E[[\5\\]']x]^^l^__a_``W``aOaabIbbcCccd@dde=eef=ffg=ggh?hhiCiijHjjkOkklWlmm`mnnknooxop+ppq:qqrKrss]sttptu(uuv>vvwVwxxnxy*yyzFz{{c{|!||}A}~~b~#G k͂0WGrׇ;iΉ3dʋ0cʍ1fΏ6n֑?zM _ɖ4 uL$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)Km&File written by Adobe Photoshop 5.0Adobed         Q  s!1AQa"q2B#R3b$r%C4Scs5D'6Tdt& EFVU(eufv7GWgw8HXhx)9IYiy*:JZjzm!1AQa"q2#BRbr3$4CS%cs5DT &6E'dtU7()󄔤euFVfvGWgw8HXhx9IYiy*:JZjz ?0nmTU_@`ؼծGpdm=|5!_^B q`&c|GFŊ&Uh^ܺK˒!#o0qLO5_UxQ0< q-_mBIIB ms_fص+/[EC:~` AyivDFrP9W95]ynb}lE-$Jjr TɘdNHVG*zW!21,gXc8d\Z[HаV6|Kq,<ϣ_zuUaf 7rr ;%* v\p1CPw-p탅eeL~gm<;qghyG q{ז/s3cF qT=n3M=(4-ʝ6z#%+yJ򮊄 :Ӿ8}da^j-`*1 $={ u'W`7_rfr͗e`LY ?QROTi:]?ɋ$e66+-Cޚ[ceic<Ѝ~\.[{"nMt]ւ@'qa܉j9"F=2~żCbW~^ /ㅍiL:u2 `_8`G6n,Z\#е)SC}C7|˨s%`ra^4SC2yn۠_ S <'o/0HfVo2f0UP=asC.dZřk2VjZ>aiފLTq\%{=.Ѳ$WHcCžIl V[v]Fq$l N\'/0ĎIA‰ʲ0lErAm^#􏋯~,O} 6x% _GA`C&[ֲ FYm9̞]s q1[Nc%$P7*?Ezj/qP27y ^iȒ+?91)=I"Yv4Q!)?q1Pdj3Og,[QEY’N35;wa"e/;2_e"ON(#F`ZIC<h5EdUx|VW1Ԃ#bBIj{Q$EަV7Tlw8s v%z 2֟Pg&{ `{L__rTO*!El(C*Kޟ E N,*U  6:ᢋ ^eTb'}).Nܾ׶gm!ONG*ZVޭ"ъe~2t{rld&h`^LCu|XiBGT4;d$n> G @-#)5vmW3`P?0X58˒,G&apYcVJ1f!iO0M! γy1p_Uy4ѩܽIEfeޓMZviv6ηP})$E q "v&:/{HM VW"FCLՈjkcdGE  A! K( Љ)ޖZ٘(IAOue,à5{۱ޜe↴.h9pU^M2$vhW6*v9ڬqzq d\Ä1m^1\)sF=$i[-iB;Ƥfb+w e+ >zώU)sBՉ )_QOJAa#mn5Kqj]es M}0ے|{ti/U;ug,b€9 <1DDap[Tpҿ^fKfpwSu3Í%ڢKt}q-a:mʀ(5TLjr"XȦךbusYh-dP H&f!}]D~r&XdPo.Fق'l4/~Q ?~G~UJI~΋pxF;#tɚ-D ,|EtlY䖍'=cͺa &)6A'Q|1?5uZIhI#:8+Y>^[NhdS+7ZfMRvn-^ŇOɧPXmƛ*jP7ޔ=3!~A`ޛT~rDǎSq6|D-u ;PT 0͖w1FHc+O]$U1 e󕝽FZNV_O!_$~f˫k 04ZMQor[iM>ܛĿQk|ǡ\iZs)}GqeL^í!UN\H )+fJIjS4l:yiU2r"C8 ~?oG$//Jaӡę nO ia ~*|m@NLXLjzuXk[h<7u IT~p q428Ecz#fC& 嵜hx1Pr+,k$ @Ės_~iJpiu5딊U}>ND9=H>uevj׏8ōW۴|k#R2-;M5Zy":G獩,:7ѠH \%3Һi?eȦ(' ' Ey1QB? ia2U֏,|*bGQƿb\ _$nBkZbjIZܷjnUc< /\O/LrBi UGCqu~.^|F8</Ljl0,hU_a zD*MPp8K:%T}oPƲ}&Ek[#oZvk,K|6V AF_# [[I5UPГQUYy|*Mk&]5nYscuݦe&?$0qsJT^B8oI㿏fKU# 2##s^رbcӢToAJd8["DIE>ҵ9( FP )Z lCS#~dP|U1di:]lYXW;P9ᅐc\w?pwL]K W`Olη3&F 851zh%,g4B~/8\+ !ibG\Y>5Zk 

)$HT965ɢYjS9!u#e+t1iZuHAUeڍʸSiڤV95g$Ym(,-~-pܰO.^KꟷF|OQ6ے4NEYՔV#m"tzQ\8IT~Sk' a[#i粩*>S+$s,#NYf0~~.pa8H*C¤;oO'&Z[𲕩c+rNRI = I8ԩOZb GۿO L$I<Gxc6VV.ԑ3)1 ݂jhT !'a ܀#)da.nP@zBG*5zlQQUY#W 'Y DR/2[赹BRI*>oOS#Q+Jf?&944Wj_˷ Dd:Sa/deMˍ>01YEwc qXMeIU /ߵ9\lcUZTX~slEHN$ӓ^]$=mݿ{"|YFF e!H[iLդw&yr%@AfT}P Q"8]ij,ib).<ͥiHEo<41jQUJ")ĔK5P"5Y5CE~~y8'2`Ւ1)VU{g*Nd-'ώ!y2uKcItSʥ_~/euW!$Pi=#!aZ5}Ȱ{uj0;-VIZL=VU^r[3IM4YEj[yD:J8*jECO8ÚE-bw|$!k:j*>: 2d"yW'}Yn <Pخ~0c)`!֭2o/‡׮}Pi;25٦4MhIiUEq3ȭrҖdqSN-],4b0Q{90sz.wiZ9T._G"z7Â,3D#?6|`OR d#j y'_\SG-P%~aŭ,Ѯ-_If'[\!-ܬpJ?xy젧? 7Tq$$$%{hZbPtM<4I οV^9џnfHE$d2W*y[k%]kV(_vo#SUGFhɓž7h~U򗑴{@ѥ;<1 NJxO2GLA uFR6 ƊI#c+˭SSV:Ok1 CUj>h(iJib3өܟDIE.rPXjP5*%{lac8 9wȇ!V^MEiX9 ѹud@[YC?K/E9bh [Qx =XG^UZ?k ǵ+Y#+uRx8䩮ў[6-+"#E`90!$Tڅr^JF6e* .g|EK&-~Gy( jkoR[7,se P:-zN,rKmo+!<.0a>m ok}-Ũ܍xv.oQŦ9 6P?iȆĠ5|}{rci#ӭ8Y?k⃊rKmCεk-Ŕ!#"]~Řt@ 8") Ɩռw<3˨(L*y7ːio-jT1󳻈4r\ W/rcʵiEH hMGg6mbCإjM|#SkG$e=rT%7El+DowVFz_0_(J v??9)q ;I5=idi6_6eS9ED3b1reA뜾#-wooH~*>dT$y4k 1E3GƅkQ yS(ݚ&>[fbkIU %EmL1-.n$zlB6JɾJҖ-x$<~C^DZe44Ĝa-Ie֖eh@)ݩOP~ٵF{(>*m~@18j[[=5weݿ C/=/Rm:8{^,ǐC,zq\ֲE*Def?ͷÒk({y];5*N1LUoIH6~#r ֯FG5iKtN-OXwUm#O{sż}d. _+3<ۧc'=lUͬbd1( ?+~i_;ja\ A4 F<*!m_RM9'mۭ(k!.ROUo]{S,% Z3n<#%hvڻkP)NZ`v)צE_4njvss1q' Ш;ٜ\'Pٵ7Voߴ|2~~Kht,P#|DNәIXZ 5myߝ!uHZB4jD|)j-:ՕƐ2EG6Cݞ@nt%.cNtqd _/\]FWҋ!ep@e$ᷞWLX#wA_٥NF87PKtc>^&|\xt,ThwSܲ1ܪdtC}9O&VcJuEC)x^d.fpQI,וH? 複2ƴ;F~d I4IBEc v-IRhzC}XLJlz7ɧ%S?NEaz,I@IPf,ZY:@?oȤڑ"F-=ach-)i'Ӗ^H6[ʥ&0,n2"F%7԰ftxl;&}:ԬWOdY=ELur:%E=:\O6@MWT"K7en?)(>ܸcM1хృvWh37?LϘra18 qˬOsfȵwOUڡROrqرk_(]iGטcHLʜe&°6TMMOX!eel9[ ZƷyJROOcrw& >:p+q ӑ2ݟz7506/X]![yG+XEoz-02 qxdkgMfQk@f{+&NF50䇏5yXkֆb9P^'w;dao,O^GzNKo=kP7ܾj?dv9G3Xt\Gt OHK]hTӪ~,6d6Hf'~=ZBYul. XN|raS¹!xC0:Ҕbx}0ȩʐI uqC[(?2.MqĦ"1jl.$M5.m}L~BM6_lmJu<ѬK_MnfS1,kM`P 79iq6supX@ c3m̺\(b8#^z(志$C7٨mW4kJXH'NA~|?",2MF@t*9Fa^g' C'ҼqonK +FL#jp]C%x-"45DDfs<<)~oV[S+#r_H'93OҊơw<ҿmAsF$ U8,>+9c!e49>.?p;87D9D&#ɫẄ́jv-n1y+BcK;;`8h 0 *s2b =}xKg5zF庁ZKou6Zu^ i!Yԁ^Mq_LEڻu|I _o'gHFK dn fco#ⷻR'v@iU4ɑVʹH&jr_9;cI.d,m 0,v* ֌]9ԭ["-д`[y^d J>y WV򎾄Qօ(>8Wb6S\RUhC[S.mԥ8嗋4F\!bm젂Zu-6b_y9G?Q[jcnv91=_5 ;pc_~}-]37e}\CrCq-RNs:#HaP,/X hF!fWND V%[sKOSݪS#AwHn]yy%YnxΤzqUzaNklmXzWYV+D #1|s j+ijc CWAY_w^Fo%*m%e4mR) pg *\YO/) $s/yt:bE#( *?fYl2Ơ~ZzPSJՅw2e`>j(Xu+g9"փbufmOO ALGKAd'08+oit|%ؒ8fkyVfk P *Boqd,pʄ܆e<2b{;ѰKPM4MKU"ZVu:e%o-] 6X4[rPkXn?J\[iڭVU?/EjI8k*\\(#d<1&~ <@5lcYoU ==?f(*S] [qZIp>7}|+#HDZneQaH\ZzuRy||?o~X\lxnUGxX%X~l^mm2Kԡ1YjKhN՚5? 22_kp3$J8[JӮb0p#U[)_ۢpo;gnk, !dF`9rnJh$5sijǜI+R(>˗27`c^X̚\7^R@"q_H||19ci |nR2hE;4`sf 7_ol /A^+RIڿ?0pHcDT{@@4a4a @W?,R]V,4FD!Sz|XW`/+@ȿk#hZz"JQ#ɡsZʧIwfE%wA Hc &IF!&JcPp2¾`&T^El6#[冎v3G;82PB>M,դx1wKVy@iqc:no{l4@݈eD&G$Zj6Sz!q=G+&xȺDPsӈ$f> _;𸹦"E?v*/w͚[YUfڼI5ZF$>ƍ*q r?k|3[? HNMHxp.×"sa!E|,_6+H-y'bXr>gŗok%Zju3qGV^nG/eF?d#O̦:=5K2҈^=:NJ-yijj##^,nQ2͖-miaj-//Gu}s823&ЧZ՛Wir4̈Ar c*Ecӣ|!$ v2߻קľ$O/Gd@RRn[nX73]C }iYݜFnU 1YUޡo +zWt5 {LEvEd-BKȦ2~ލQ4R::F5y- XN^ph,YHf2)zkMFqaNg+ݜFʰEgcjd~qMk5 <\.'0@*ɿ Ci]cN\IK["# TV\ l_@4Lg:A(oW~`UDq%ŕ) 4@꼪hk^O~p9Z]1B0PWnMa2/僌ҴqnyҮjmk7Hyd-tTi~a_7C!%˧Gm=QHxKx4U@={'P392d)=@a~/"=s%UeUfF4?xwT[S)ׂxbLk~dFȿp& FIQ/c4|ع{iyr"LSҵ}KȞ\G4ҡ||Y~n#aDEbbyr>OW%j1qc olI*~?/EB9ER ڤs?Y!䈊+J|Otv(jmB;QZvjmuHL(~eI$c_A[qHvq'v)8!ki[OD18 H}4ty!L9ioaX>nAfi1Gm'tPsu.Y+b 9 g<]s>QDĬW|.Lu)^VmڠB_PL^IyPm [[kLEv,L"k+R&o*YKfGvsM]N2JXD<,Qɿg8NHb)ݗNБs܆ d·Eoß m3;50s S0ò%r_ُ3SBcNE MB?lc!n@goh9mPZJd_@T*4JDCJJ|ڢ5krUnNM1V害lG.yRdh"(vo|ifr[N1F8gɀ4bۘ(x>''0ٍjڢ=gX~9⣭RLՓ+ڷ  c,50JSON6v_~,eoZ^Ǩҡ&{PʌlvfQ5KnfCmjő0rCOpl)G{h%yv˹"jRHHc$ӳf Z{0fRBb* n 8ؿ6~^Ueyw V`ҵ Ku⣛7c52Vc-|N]c''( Jl-ZͺŒ2}]cTFQ_kc)y3]'JݣV$uA_'ƹxZ5.UN~"(m ]٪HI˵+HQ[bdDdݜZ54+$1,-.kOWAY{FghE$)[k|?T7bjĎ=(W~nyJl1$ᖀQSaZ R*CGyRɓ,(8 k7 lC1;'bߤb_:E?8m:U@^eI_$[p]Ef{[*2LŨ>/aɘ.@Q].Jk֞>ٍUY\EܺC/ VfC XJFWbŘA6IRfG^;wOPI9s$G_olK7e(љ է^cur$B.- %/Xa%ٴ~daXӐaZ~.mwNlF̖yO!ޢ^dV%I [( Yd i_[F}i^Yt"Ns?/9tE]+Mۡ>(PB|}G3~%H+PcyPO/1yupHK`yvw05Af $l 1tot.+&f䷟Bյɒ5 n+^t'3pxиPJ\M*:tR(;PWmdp$@ +S|P*Mh*@98bWrIXE?P8^doXL̆OCE {m[sח. Ş9'=oI` )SklRuӓ~2Fji?0-[5UI9f?pH/@TҙW$ڇ#H9'nʏ6wlv7 $8ք,'fʗ{i\ aFJ ,u?>6~x"Eᷔãޣܭq$0D27f@EfxƟƿ &N5at+3[{,Dy<snFo|' 7Ǥū35 耊M"|!2LR&{i{{!1m±F q3$#/mHmtQn-&U57-[6Qm1 l9٪G§ ſpd[vYPudPѱ"T`9'(MH摃@Ezxq˭b164۾ҥ.jt'$X bIw?a\5RI+=2OyTJ9HOH.WdL,TrAiVKdPBP7'|6Ҵ㑯 B$*@_I"ޟQedVy/?ô຅ޕLfvl7e~TxB10 k2tMNcCX$D28azON]I.HXw WEa;3hOY] llӒ;MsU]phOnzPVvK%syf'(`\AR#0Vi"X=(؊SҥBt(R{].TJ,9 ՒJ\Kp,̳iZ [w,]e'qپaV1906f*kI0g4hm#oX4KJNbK;UI[]S#B$wی=0X 4YY9[.eӘ?7aiʁN#4Iats':UڪYWpNo˟&]8\&A iz6VƔ) P'/[*~[Ԅ;eg˦D>o:MmlEXCuHOaG<15Uu9 Jsr򉿺_SLn52H6i*ni#Z+CVB;Sn6sDy+i>IZ mOJעx :B J'Au~P>7S2Q s+C x -FFj"X/ ɇ+0Ry13Yeq| .] X}(g|T6JV5bN8U#ZE2.AA!Gc5>[&kerMro6 zWRK-*vvcaV朾LܰRʗz:$w'ՑG7s~}gm/UKpWz^OҖȘX!9YZ[ H#5&]?#d"㐏_c:xm2AwOi>_-G#SZwfWk0%xQ[jffW ᣺cZ? $W2[ǧ&;y1i#THOR NWtmƍ"rc"#E;_yӭ87"(^'&/@-c/laEuw;A$syy8x'_UUKc!KX) V6*~x@O5ޫuq!IRgnR#]U$oic3Uݝzm\ߛ-R`LPT+?1 l3FwkwsO\Ť% #Eo*~ǩ"cp&>;?Zr:T3 Lr@_af5;1RrOo /ڽёa؀O%z35%jZI;J@ A|T$8dp %ǩG(;=yh2B6oc$N(_٧ q6 rgrb&1@AOo U%I,w] *Xw^/-((BByc,4(w'tŌB uFY"4~X!zICp46JԔ]_gFДI&Cn̄r&$;%/lj=EqUoNXs5 y^K-ܕn AC!cᘳ;7_5[ZLf-s"C5&d!/8ᘤed7_yh"w7st"ċG^_e& W"< Qi+FsyV4eaя'.ӊuZSӡ1O@9mA`vu)̪ˏԚ%&F_&YzG^8iacO~J9PܓCS?f.3ڜr="+iDNU(drTeD+_^]7bc麚SSfDGpe1"b"PN-jKk. uSx0wڟ<*z^˧mjy,8dAg Yቔ B)'A'mk4+^>E"|] |==ʤEǭJ#ԒU $Jn oƭap,&UNrIR xm2Q3O׷SGij*( ƀ=xr( Zu3% 72|\jvv?gH IŽdՅ{b򏗪FfT%>>=>}5B`k^0„*>E.T"$๗UN~YrzDb呋i`ָ}CTgA P4-C`zß~c/J<cAyn2_~kiH & GsD9N \܈o뗼upo갌_셙>'Ǎd.<#YdFq< V//nm$6۶1sgyMFx+ԫqHdUgT,aY(Ղ3$a2<|>95ag݇Q.w2@\W6a~ W"+*E+F*~eQn7{HĞU閷q`C ıp& '!ǑV k|i?׻<q\If̔0oQQi)QIpD?kdcbh24;OYuwkmI'c^gYZGUo)2$g)P^IZqH|YdG& 3sߵ\Ȍgcɬa2i5.Xޣ68`AQA95T#)8'r[oE-ྲྀ*V;DMA@(}$ %Ed*>X-ܐ%AP1//^lN,Us+:`(ƙx 4) ۾JnB6(!P~}/-vֱibYY~;8 kY^GT%I_fXKoc2V|01(uy) t?h(,ud㪻<\zȢ! tX+rU#D3mO75P~,J2 A:yc`y/_~8=4ޱci//щ/%D.t2ʈ̑49h>, 1!Z2rn?m9b1隔j#`+W`q? $Ȍm:]yR ^Sj W##Ep^_{c˨Ú+O9>FrXLf ;/绾n [^!nwhտyS9r^T4_-"}Ji%aC̉8doK'EEHЖj6u5/VT*zYy_)Y`!-3W Ub]="D4%y|ʧPnQ߻oK!$O"wFo~gr 'QgmqjqRydozB`Vˇ;TO{YQsю!y~qLTpV|Lc'~ةS]+rXdcCUUvop9/J'ޓ._Yy~V^XSEe4 (LY1̧WJa<4Ԟ;f//?k\yW;͙b6S,]iz律:ّLzpAI/Nj+W9|?dĵ\Q CM3'S.G.@,hY$Cn@P+j eHR?7'{ah(mJܲ]Оd$É&崎'㺍JF1:ypXX!Qȫ>m^\Y֣no"J#LDYmԷD dK3I{= i.eIdg↣`~ɼ=vq\GF'.]z'vX@"JExFi$%le>/BfQ^kY-,MJW!w?RJ1cۗ̚}{ztTHY?SebR,`Rd^P4E$ Hͮb+x)gr`gn !r˅o,ntQ79hFX.x*H?n8?ȵ+۝-f,Q)#E,u~O2$5E'GO*IfK~DD2a𲏇nrԖã[H z$.VqML睰L_IcM?hrd_=4 gc Dեf@$(%)Nu,RC Ү#4,O(Zmȯ[OK(dRm4ĭ5 Q/Hٹ|*ˑbQqQ%Հ`즵e/< Oe`&(dCǷ/阠>J>s5GuIz7_k283[]D!nLnHCɜGWXTWv{6~mQM[;MjUyUUHGŸ˘:틙дٵ 5sưZ/Š^}ٹ͛l–2N`!{][UKw _6?py=o Bto1騀 ȏ!=~_a4RGrbg+*eؐ#YQ2u,eX[y&][N1E.3Kzdr-)]WD>!fSlH.atHx ^ $zۥK<\H\z鼲O_#,xqY]SPm;GV6j93qF3@qe0buyZP"{ۥ3~eU9fǵ˓OG|2bhriW}j {h Q?yTz'ųL|޳ E, }\ DLVVO6e@2"D mB߅K i!G>%7V?]!GmawٰoLT/#xG6ΥwfuьrB$ ȢLPƂk_GRA#[XMwWBj2qIF w-*;YfR}zM9WZP=X)siۈ[{bXI$Udlc,/VG ONe%*40I"ٿo%bքu;.Y1]Bfw݈`Ǐj?3ȏ!IDc,;:]~,(zQ `XC/x?k))^4,F1$poB+ʮ 8'c'l:#=q5ļ*>rKx?@?A_ >뷫̕&%_vle[j72x(KIĻ u!Q?ȌL SW:v0\G;ķ,x]1F}Q>e>m&1 o.4HܯqȆ>9>|Mq;"t7Q[۔[I% q|H?oX 1yV[3A")GÏ[lM!_B'BͰa[g" !Yr\/ڬ67|RAȬMgӃkZKFՙ<oS!Ӈ׋$c@j * r}/pI1M0[UBX1n[1p^%OMJC˱Pje~mvf<7,R9"K*>f,c] dk' !1R?.gAЧ(OjbgBKsDj,??c̒9n %`#?kg=ÙgZ]Ub=4 1+us[#5&_kSȗV"(_#{-CTfKXƜ}qWd 7og (׸LV֊UD2'<~7y@Rݾ*PH,Hߥ5ӵ/GqV>/K>'ȈpǎJ'-\LSr#E\_$1qT;"ƾ p",<ݰC,KLp'f-~+tEE]򬤟Jĥmwtˣͧ@V^gko&1 TVM=2M4d;7~?l 3χQPXWV[JHlg$gn1rYAseii؂u8Ԭ#~D.b[h<_7\~!&-ռr]!IeВEOV2Dy?|#LVZמnbȆvC5¦Xq2u.l#ܓHLjE_Iՙ}5Ow_oF73 yk]O!$?ga ),w~}hpk%QWٕ_)wq19ok- NS` +7Pfg!ZM?R~7xo`zOkGcq*["(U_T!S m0{lhrGk: U֣ cj(f9 8d>,re6v6m"יOp>YMM*^h%O`YB:>oQc1aw?%YQ3+{cݜ~}*#8 )*MZ`H}_^1&&I|1DU+YEBCvcG)? ޻fMրmL[j0jᇍ|q%$?5䶰O7OƠ/8 وӼ%dw6}DD߬'T_g,<4U?9=~r*p[N z*nkq3mGM-o=VTڑc~^7:s0v>Wwq}_OPEeerL6~1Ͱ]v+k9O'X"Lb>rg^k9mn (Di?4^l>eeWF[RRԃ$Cɘ5I?&B $zJfy֢)EExQ9Dm[/..}=Zn% -\Vvdo|b"Z F-rMeQ({BT14XD*<DQ)Jӯ K-&HP ޛwV67ZkYߖ]Gb@hPJhhk9 [,%]3K,$R:-x֜ۿe9يElg>L}gPfz*Jk}s&%Սn"COSSTdsF>c|Xua=d1B$a¿2/GK҉݌³jD^zf<"'sbm+k 45V؈"IąuvVF_?w =S,E4HVnܻqn ×<>!Lj7&n+-R}YU_߰g!-,'LRHvSANo?.dG8!d^5iY~+VO7v[T\1;UбmJ/o%Q l6 8/dgUCZ -zvv7_yTeaA4D'b9"*ǒ(~4^YP. J2r*y:^7$Ƞe_,`ՠYTP;1)V 46G<"@y6 vCw>u:Vrs2ϓfE&,ΟeB*8|r ⱒ8K!o@To$!c'ビ+co -g2 جF$"F~_dLWmձZM ,Df)c!^~Z,(!WV^%չ|#IYc*I#G(K Id' Xqƿ߷zy,qd7Eqpx٢?oۗ6o0K>XiQb 4 ^G2WԘ /)h mE3+v{V,,$|2G}x3co/ԇInKm$eco_ gezWM 0bQFfߓQWU)Lږ#REӡu,*/eItQH zƀ*7'/yvGaLcX"nOwUeW)c˱Ff?./!c1PLj}+pdp7Hl?{5-@<@WxqL1ahVhl˸LpFᜲ J1'2a/oҖf*ݒ>H"NKO4xY pܔ*UݔoߧI#(egmwLŎy[JA Vx/qB)K?ڱYƻs`UɰI;%6Q[z 9Ud/FnqYۃvP xRO ,Mi,޹iQTfZ>G7\M󜓴  Atrz5SĐjhq^,l]X - PǨ\#Ӈem2C̢ {(OJ$ޝV%L'/w1IqQ))k5̉+-=CM ?SDc$ʭMܲI: A PR4Pb>F.Ld["0983v:I%o?xd-"0& i>g/qݢifuH @R[_#3kmwR3*q~w2^ ֜d2IŻh`Oو 䓯?ݏ̳,YFQ?iN*xφ `o)GHn金ՙKr/(`y1O06pu7&D~+B;7,J@^-SǕV|!^fB>R.3L$Pg.[[S'_Hԩ7Yl0DO:+k沉1=IeH_\*B>,.ip}9(NrqJʼ4OKjjGñf8W{iRA՘~?8[ !:?€w8Hc$Sݓz~Fܻte$(3NF)eMi^)hA~?+\+Hkկd* *1E2Bsdew#MVG:$'n9yJk4#tҺZ]L/$!JUAeG,-fe ;G 0.ʼAC_oybr[LК'%rf4̎e 0D;^<˹jCovXj[GdH_UrBi9c$r=R9"Oc1kjPȅ˯bkfQz[1'+l(Q9͹'knW?!MؓA& l !?nF^z?S>A|-yv"lO/[x(>O? O #'0ٗc7ks2l%&OvDG*7R)?9Q㝧Ix$ڿƹwV2ֿHAa_'5k?׏`ȱpcarrierwave-0.10.0/spec/fixtures/sponsored.doc000066400000000000000000000000111230347170300214000ustar00rootroot00000000000000Hi there carrierwave-0.10.0/spec/fixtures/test+.jpg000066400000000000000000000000151230347170300204350ustar00rootroot00000000000000this is stuffcarrierwave-0.10.0/spec/fixtures/test.jpeg000066400000000000000000000000151230347170300205270ustar00rootroot00000000000000this is stuffcarrierwave-0.10.0/spec/fixtures/test.jpg000066400000000000000000000000151230347170300203620ustar00rootroot00000000000000this is stuffcarrierwave-0.10.0/spec/fog_credentials.rb000066400000000000000000000023561230347170300205170ustar00rootroot00000000000000unless defined?(FOG_CREDENTIALS) credentials = [] if Fog.mocking? # Local and Rackspace don't have fog mock support yet mappings = { 'AWS' => [:aws_access_key_id, :aws_secret_access_key], 'Google' => [:google_storage_access_key_id, :google_storage_secret_access_key], # 'Local' => [:local_root], # 'Rackspace' => [:rackspace_api_key, :rackspace_username] } for provider, keys in mappings data = {:provider => provider} for key in keys data[key] = key.to_s end credentials << data end FOG_CREDENTIALS = credentials else Fog.credential = :carrierwave mappings = { 'AWS' => [:aws_access_key_id, :aws_secret_access_key], 'Google' => [:google_storage_access_key_id, :google_storage_secret_access_key], 'Local' => [:local_root], 'Rackspace' => [:rackspace_api_key, :rackspace_username] } for provider, keys in mappings unless (creds = Fog.credentials.reject {|key, value| ![*keys].include?(key)}).empty? data = {:provider => provider} for key in keys data[key] = creds[key] end credentials << data end end FOG_CREDENTIALS = credentials end end carrierwave-0.10.0/spec/generators/000077500000000000000000000000001230347170300172055ustar00rootroot00000000000000carrierwave-0.10.0/spec/generators/uploader_generator_spec.rb000066400000000000000000000011751230347170300244310ustar00rootroot00000000000000require 'spec_helper' require 'generators/uploader_generator' describe UploaderGenerator, :type => :generator do destination File.expand_path("../../tmp", __FILE__) before :each do prepare_destination end it "should properly create uploader file" do run_generator %w(Avatar) assert_file 'app/uploaders/avatar_uploader.rb', /class AvatarUploader < CarrierWave::Uploader::Base/ end it "should properly create namespaced uploader file" do run_generator %w(MyModule::Avatar) assert_file 'app/uploaders/my_module/avatar_uploader.rb', /class MyModule::AvatarUploader < CarrierWave::Uploader::Base/ end endcarrierwave-0.10.0/spec/mount_spec.rb000066400000000000000000000602661230347170300175470ustar00rootroot00000000000000# encoding: utf-8 require 'spec_helper' describe CarrierWave::Mount do after do FileUtils.rm_rf(public_path) end describe '.mount_uploader' do before do @class = Class.new @class.send(:extend, CarrierWave::Mount) @uploader = Class.new(CarrierWave::Uploader::Base) @class.mount_uploader(:image, @uploader) @instance = @class.new end it "should maintain the ability to super" do @class.class_eval do def image_uploader super end def image=(val) super end end @instance.image = stub_file('test.jpg') @instance.image.should be_an_instance_of(@uploader) end it "should inherit uploaders to subclasses" do @subclass = Class.new(@class) @subclass_instance = @subclass.new @subclass_instance.image = stub_file('test.jpg') @subclass_instance.image.should be_an_instance_of(@uploader) end it "should allow marshalling uploaders and versions" do Object.const_set("MyClass#{@class.object_id}".gsub('-', '_'), @class) Object.const_set("Uploader#{@uploader.object_id}".gsub('-', '_'), @uploader) @uploader.class_eval do def rotate end end @uploader.version :thumb do process :rotate end @instance.image = stub_file('test.jpg') lambda { Marshal.dump @instance.image }.should_not raise_error end describe "expected behavior with subclassed uploaders" do before do @class = Class.new @class.send(:extend, CarrierWave::Mount) @uploader1 = Class.new(CarrierWave::Uploader::Base) @uploader1.process :rotate @uploader1.version :thumb do process :compress end @uploader2 = Class.new(@uploader1) @uploader2.process :shrink @uploader2.version :secret do process :encrypt end @class.mount_uploader(:image1, @uploader1) @class.mount_uploader(:image2, @uploader2) @instance = @class.new end it "should inherit defined versions" do @instance.image1.should respond_to(:thumb) @instance.image2.should respond_to(:thumb) end it "should not inherit versions defined in subclasses" do @instance.image1.should_not respond_to(:secret) @instance.image2.should respond_to(:secret) end it "should inherit defined processors properly" do @uploader1.processors.should == [[:rotate, [], nil]] @uploader2.processors.should == [[:rotate, [], nil], [:shrink, [], nil]] @uploader1.versions[:thumb][:uploader].processors.should == [[:compress, [], nil]] @uploader2.versions[:thumb][:uploader].processors.should == [[:compress, [], nil]] @uploader2.versions[:secret][:uploader].processors.should == [[:encrypt, [], nil]] end end describe '#image' do it "should return a blank uploader when nothing has been assigned" do @instance.should_receive(:read_uploader).with(:image).twice.and_return(nil) @instance.image.should be_an_instance_of(@uploader) @instance.image.should be_blank end it "should return a blank uploader when an empty string has been assigned" do @instance.should_receive(:read_uploader).with(:image).twice.and_return('') @instance.image.should be_an_instance_of(@uploader) @instance.image.should be_blank end it "should retrieve a file from the storage if a value is stored in the database" do @instance.should_receive(:read_uploader).with(:image).at_least(:once).and_return('test.jpg') @instance.image.should be_an_instance_of(@uploader) end it "should set the path to the store dir" do @instance.should_receive(:read_uploader).with(:image).at_least(:once).and_return('test.jpg') @instance.image.current_path.should == public_path('uploads/test.jpg') end end describe '#image=' do it "should cache a file" do @instance.image = stub_file('test.jpg') @instance.image.should be_an_instance_of(@uploader) end it "should copy a file into into the cache directory" do @instance.image = stub_file('test.jpg') @instance.image.current_path.should =~ /^#{public_path('uploads/tmp')}/ end it "should do nothing when nil is assigned" do @instance.should_not_receive(:write_uploader) @instance.image = nil end it "should do nothing when an empty string is assigned" do @instance.should_not_receive(:write_uploader) @instance.image = stub_file('test.jpg') end it "should fail silently if the image fails a white list integrity check" do @uploader.class_eval do def extension_white_list %w(txt) end end @instance.image = stub_file('test.jpg') @instance.image.should be_blank end it "should fail silently if the image fails a black list integrity check" do @uploader.class_eval do def extension_black_list %w(jpg) end end @instance.image = stub_file('test.jpg') @instance.image.should be_blank end it "should fail silently if the image fails to be processed" do @uploader.class_eval do process :monkey def monkey raise CarrierWave::ProcessingError, "Ohh noez!" end end @instance.image = stub_file('test.jpg') end end describe '#image?' do it "should be false when nothing has been assigned" do @instance.should_receive(:read_uploader).with(:image).and_return(nil) @instance.image?.should be_false end it "should be false when an empty string has been assigned" do @instance.should_receive(:read_uploader).with(:image).and_return('') @instance.image?.should be_false end it "should be true when a file has been cached" do @instance.image = stub_file('test.jpg') @instance.image?.should be_true end end describe '#image_url' do it "should return nil when nothing has been assigned" do @instance.should_receive(:read_uploader).with(:image).and_return(nil) @instance.image_url.should be_nil end it "should return nil when an empty string has been assigned" do @instance.should_receive(:read_uploader).with(:image).and_return('') @instance.image_url.should be_nil end it "should get the url from a retrieved file" do @instance.should_receive(:read_uploader).at_least(:once).with(:image).and_return('test.jpg') @instance.image_url.should == '/uploads/test.jpg' end it "should get the url from a cached file" do @instance.image = stub_file('test.jpg') @instance.image_url.should =~ %r{uploads/tmp/[\d\-]+/test.jpg} end it "should get the url from a cached file's version" do @uploader.version(:thumb) @instance.image = stub_file('test.jpg') @instance.image_url(:thumb).should =~ %r{uploads/tmp/[\d\-]+/thumb_test.jpg} end end describe '#image_cache' do before do @instance.stub!(:write_uploader) @instance.stub!(:read_uploader).and_return(nil) end it "should return nil when nothing has been assigned" do @instance.image_cache.should be_nil end it "should be nil when a file has been stored" do @instance.image = stub_file('test.jpg') @instance.image.store! @instance.image_cache.should be_nil end it "should be the cache name when a file has been cached" do @instance.image = stub_file('test.jpg') @instance.image_cache.should =~ %r(^[\d]+\-[\d]+\-[\d]{4}/test\.jpg$) end end describe '#image_cache=' do before do @instance.stub!(:write_uploader) @instance.stub!(:read_uploader).and_return(nil) CarrierWave::SanitizedFile.new(file_path('test.jpg')).copy_to(public_path('uploads/tmp/1369894322-123-1234/test.jpg')) end it "should do nothing when nil is assigned" do @instance.image_cache = nil @instance.image.should be_blank end it "should do nothing when an empty string is assigned" do @instance.image_cache = '' @instance.image.should be_blank end it "retrieve from cache when a cache name is assigned" do @instance.image_cache = '1369894322-123-1234/test.jpg' @instance.image.current_path.should == public_path('uploads/tmp/1369894322-123-1234/test.jpg') end it "should not write over a previously assigned file" do @instance.image = stub_file('test.jpg') @instance.image_cache = '1369894322-123-1234/monkey.jpg' @instance.image.current_path.should =~ /test.jpg$/ end end describe 'with ShamRack' do before do sham_rack_app = ShamRack.at('www.example.com').stub sham_rack_app.register_resource('/test.jpg', File.read(file_path('test.jpg')), 'image/jpg') end after do ShamRack.unmount_all end describe '#remote_image_url' do it "should return nil" do @instance.remote_image_url.should be_nil end it "should return previously cached URL" do @instance.remote_image_url = 'http://www.example.com/test.jpg' @instance.remote_image_url.should == 'http://www.example.com/test.jpg' end end describe '#remote_image_url=' do it "should do nothing when nil is assigned" do @instance.remote_image_url = nil @instance.image.should be_blank end it "should do nothing when an empty string is assigned" do @instance.remote_image_url = '' @instance.image.should be_blank end it "retrieve from cache when a cache name is assigned" do @instance.remote_image_url = 'http://www.example.com/test.jpg' @instance.image.current_path.should =~ /test.jpg$/ end it "should write over a previously assigned file" do @instance.image = stub_file('portrait.jpg') @instance.remote_image_url = 'http://www.example.com/test.jpg' @instance.image.current_path.should =~ /test.jpg$/ end end end describe '#store_image!' do before do @instance.stub!(:write_uploader) @instance.stub!(:read_uploader).and_return(nil) end it "should do nothing when no file has been uploaded" do @instance.store_image! @instance.image.should be_blank end it "store an assigned file" do @instance.image = stub_file('test.jpg') @instance.store_image! @instance.image.current_path.should == public_path('uploads/test.jpg') end it "should remove an uploaded file when remove_image? returns true" do @instance.image = stub_file('test.jpg') path = @instance.image.current_path @instance.remove_image = true @instance.store_image! @instance.image.should be_blank File.exist?(path).should be_false end end describe '#remove_image!' do before do @instance.stub!(:write_uploader) @instance.stub!(:read_uploader).and_return(nil) end it "should do nothing when no file has been uploaded" do @instance.remove_image! @instance.image.should be_blank end it "should remove an uploaded file" do @instance.image = stub_file('test.jpg') path = @instance.image.current_path @instance.remove_image! @instance.image.should be_blank File.exist?(path).should be_false end end describe '#remove_image' do it "should store a value" do @instance.remove_image = true @instance.remove_image.should be_true end end describe '#remove_image?' do it "should be true when the value is truthy" do @instance.remove_image = true @instance.remove_image?.should be_true end it "should be false when the value is falsey" do @instance.remove_image = false @instance.remove_image?.should be_false end it "should be false when the value is ''" do @instance.remove_image = '' @instance.remove_image?.should be_false end it "should be false when the value is '0'" do @instance.remove_image = '0' @instance.remove_image?.should be_false end it "should be false when the value is 'false'" do @instance.remove_image = 'false' @instance.remove_image?.should be_false end end describe '#image_integrity_error' do it "should be nil by default" do @instance.image_integrity_error.should be_nil end it "should be nil after a file is cached" do @instance.image = stub_file('test.jpg') @instance.image_integrity_error.should be_nil end describe "when an integrity check fails" do before do @uploader.class_eval do def extension_white_list %w(txt) end end end it "should be an error instance if file was cached" do @instance.image = stub_file('test.jpg') e = @instance.image_integrity_error e.should be_an_instance_of(CarrierWave::IntegrityError) e.message.lines.grep(/^You are not allowed to upload/).should be_true end it "should be an error instance if file was downloaded" do sham_rack_app = ShamRack.at('www.example.com').stub sham_rack_app.register_resource('/test.jpg', File.read(file_path('test.jpg')), 'image/jpg') @instance.remote_image_url = "http://www.example.com/test.jpg" e = @instance.image_integrity_error e.should be_an_instance_of(CarrierWave::IntegrityError) e.message.lines.grep(/^You are not allowed to upload/).should be_true end it "should be an error instance when image file is assigned and remote_image_url is blank" do @instance.image = stub_file('test.jpg') @instance.remote_image_url = "" e = @instance.image_integrity_error e.should be_an_instance_of(CarrierWave::IntegrityError) e.message.lines.grep(/^You are not allowed to upload/).should be_true end end end describe '#image_processing_error' do it "should be nil by default" do @instance.image_processing_error.should be_nil end it "should be nil after a file is cached" do @instance.image = stub_file('test.jpg') @instance.image_processing_error.should be_nil end describe "when an processing error occurs" do before do @uploader.class_eval do process :monkey def monkey raise CarrierWave::ProcessingError, "Ohh noez!" end end end it "should be an error instance if file was cached" do @instance.image = stub_file('test.jpg') @instance.image_processing_error.should be_an_instance_of(CarrierWave::ProcessingError) end it "should be an error instance if file was downloaded" do sham_rack_app = ShamRack.at('www.example.com').stub sham_rack_app.register_resource('/test.jpg', File.read(file_path('test.jpg')), 'image/jpg') @instance.remote_image_url = "http://www.example.com/test.jpg" @instance.image_processing_error.should be_an_instance_of(CarrierWave::ProcessingError) end end end describe '#image_download_error' do before do sham_rack_app = ShamRack.at('www.example.com').stub sham_rack_app.register_resource('/test.jpg', File.read(file_path('test.jpg')), 'image/jpg') end it "should be nil by default" do @instance.image_download_error.should be_nil end it "should be nil if file download was successful" do @instance.remote_image_url = "http://www.example.com/test.jpg" @instance.image_download_error.should be_nil end it "should be an error instance if file could not be found" do @instance.remote_image_url = "http://www.example.com/missing.jpg" @instance.image_download_error.should be_an_instance_of(CarrierWave::DownloadError) end end describe '#image_download_error' do before do sham_rack_app = ShamRack.at('www.example.com').stub sham_rack_app.register_resource('/test.jpg', File.read(file_path('test.jpg')), 'image/jpg') end it "should be nil by default" do @instance.image_download_error.should be_nil end it "should be nil if file download was successful" do @instance.remote_image_url = "http://www.example.com/test.jpg" @instance.image_download_error.should be_nil end it "should be an error instance if file could not be found" do @instance.remote_image_url = "http://www.example.com/missing.jpg" @instance.image_download_error.should be_an_instance_of(CarrierWave::DownloadError) end end describe '#write_image_identifier' do it "should write to the column" do @instance.should_receive(:write_uploader).with(:image, "test.jpg") @instance.image = stub_file('test.jpg') @instance.write_image_identifier end it "should remove from the column when remove_image is true" do @instance.image = stub_file('test.jpg') @instance.store_image! @instance.remove_image = true @instance.should_receive(:write_uploader).with(:image, nil) @instance.write_image_identifier end end describe '#image_identifier' do it "should return the identifier from the mounted column" do @instance.should_receive(:read_uploader).with(:image).and_return("test.jpg") @instance.image_identifier.should == 'test.jpg' end end end describe '#mount_uploader without an uploader' do before do @class = Class.new @class.send(:extend, CarrierWave::Mount) @class.mount_uploader(:image) @instance = @class.new end describe '#image' do before do @instance.stub!(:read_uploader).and_return('test.jpg') end it "should return an instance of a subclass of CarrierWave::Uploader::Base" do @instance.image.should be_a(CarrierWave::Uploader::Base) end it "should set the path to the store dir" do @instance.image.current_path.should == public_path('uploads/test.jpg') end end end describe '#mount_uploader with a block' do describe 'and no uploader given' do before do @class = Class.new @class.send(:extend, CarrierWave::Mount) @class.mount_uploader(:image) do def monkey 'blah' end end @instance = @class.new end it "should return an instance of a subclass of CarrierWave::Uploader::Base" do @instance.image.should be_a(CarrierWave::Uploader::Base) end it "should apply any custom modifications" do @instance.image.monkey.should == "blah" end end describe 'and an uploader given' do before do @class = Class.new @class.send(:extend, CarrierWave::Mount) @uploader = Class.new(CarrierWave::Uploader::Base) @uploader.version :thumb do version :mini version :maxi end @class.mount_uploader(:image, @uploader) do def fish 'blub' end end @instance = @class.new end it "should return an instance of the uploader specified" do @instance.image.should be_a_kind_of(@uploader) end it "should apply any custom modifications to the instance" do @instance.image.fish.should == "blub" end it "should apply any custom modifications to all defined versions" do @instance.image.thumb.fish.should == "blub" @instance.image.thumb.mini.fish.should == "blub" @instance.image.thumb.maxi.fish.should == "blub" end it "should not apply any custom modifications to the uploader class" do @uploader.new.should_not respond_to(:fish) end end end describe '#mount_uploader with :ignore_integrity_errors => false' do before do @class = Class.new @class.send(:extend, CarrierWave::Mount) @uploader = Class.new(CarrierWave::Uploader::Base) @class.mount_uploader(:image, @uploader, :ignore_integrity_errors => false) @instance = @class.new @uploader.class_eval do def extension_white_list %w(txt) end end end it "should raise an error if the image fails an integrity check when cached" do running { @instance.image = stub_file('test.jpg') }.should raise_error(CarrierWave::IntegrityError) end it "should raise an error if the image fails an integrity check when downloaded" do sham_rack_app = ShamRack.at('www.example.com').stub sham_rack_app.register_resource('/test.jpg', File.read(file_path('test.jpg')), 'image/jpg') running { @instance.remote_image_url = "http://www.example.com/test.jpg" }.should raise_error(CarrierWave::IntegrityError) end end describe '#mount_uploader with :ignore_processing_errors => false' do before do @class = Class.new @class.send(:extend, CarrierWave::Mount) @uploader = Class.new(CarrierWave::Uploader::Base) @class.mount_uploader(:image, @uploader, :ignore_processing_errors => false) @instance = @class.new @uploader.class_eval do process :monkey def monkey raise CarrierWave::ProcessingError, "Ohh noez!" end end end it "should raise an error if the image fails to be processed when cached" do running { @instance.image = stub_file('test.jpg') }.should raise_error(CarrierWave::ProcessingError) end it "should raise an error if the image fails to be processed when downloaded" do sham_rack_app = ShamRack.at('www.example.com').stub sham_rack_app.register_resource('/test.jpg', File.read(file_path('test.jpg')), 'image/jpg') running { @instance.remote_image_url = "http://www.example.com/test.jpg" }.should raise_error(CarrierWave::ProcessingError) end end describe '#mount_uploader with :ignore_download_errors => false' do before do @class = Class.new @class.send(:extend, CarrierWave::Mount) @uploader = Class.new(CarrierWave::Uploader::Base) @class.mount_uploader(:image, @uploader, :ignore_download_errors => false) @instance = @class.new end it "should raise an error if the image fails to be processed" do @uploader.class_eval do def download! uri raise CarrierWave::DownloadError end end running { @instance.remote_image_url = "http://www.example.com/test.jpg" }.should raise_error(CarrierWave::DownloadError) end end describe '#mount_uploader with :mount_on => :monkey' do before do @class = Class.new @class.send(:extend, CarrierWave::Mount) @uploader = Class.new(CarrierWave::Uploader::Base) @class.mount_uploader(:image, @uploader, :mount_on => :monkey) @instance = @class.new end describe '#image' do it "should retrieve a file from the storage if a value is stored in the database" do @instance.should_receive(:read_uploader).at_least(:once).with(:monkey).and_return('test.jpg') @instance.image.should be_an_instance_of(@uploader) @instance.image.current_path.should == public_path('uploads/test.jpg') end end describe '#write_image_identifier' do it "should write to the given column" do @instance.should_receive(:write_uploader).with(:monkey, "test.jpg") @instance.image = stub_file('test.jpg') @instance.write_image_identifier end it "should remove from the given column when remove_image is true" do @instance.image = stub_file('test.jpg') @instance.store_image! @instance.remove_image = true @instance.should_receive(:write_uploader).with(:monkey, nil) @instance.write_image_identifier end end end end carrierwave-0.10.0/spec/orm/000077500000000000000000000000001230347170300156315ustar00rootroot00000000000000carrierwave-0.10.0/spec/orm/activerecord_spec.rb000066400000000000000000000650101230347170300216440ustar00rootroot00000000000000# encoding: utf-8 require 'spec_helper' require 'support/activerecord' class TestMigration < ActiveRecord::Migration def self.up create_table :events, :force => true do |t| t.column :image, :string t.column :textfile, :string t.column :foo, :string end end def self.down drop_table :events end end class Event < ActiveRecord::Base; end # setup a basic AR class for testing $arclass = 0 describe CarrierWave::ActiveRecord do describe '#mount_uploader' do before(:all) { TestMigration.up } after(:all) { TestMigration.down } after { Event.delete_all } before do # Rails 4 defaults to no root in JSON, join the party ActiveRecord::Base.include_root_in_json = false # My god, what a horrible, horrible solution, but AR validations don't work # unless the class has a name. This is the best I could come up with :S $arclass += 1 @class = Class.new(Event) # AR validations don't work unless the class has a name, and # anonymous classes can be named by assigning them to a constant Object.const_set("Event#{$arclass}", @class) @class.table_name = "events" @uploader = Class.new(CarrierWave::Uploader::Base) @class.mount_uploader(:image, @uploader) @event = @class.new end describe '#image' do it "should return blank uploader when nothing has been assigned" do expect(@event.image).to be_blank end it "should return blank uploader when an empty string has been assigned" do @event[:image] = '' @event.save! @event.reload expect(@event.image).to be_blank end it "should retrieve a file from the storage if a value is stored in the database" do @event[:image] = 'test.jpeg' @event.save! @event.reload expect(@event.image).to be_an_instance_of(@uploader) end it "should set the path to the store dir" do @event[:image] = 'test.jpeg' @event.save! @event.reload expect(@event.image.current_path).to eq public_path('uploads/test.jpeg') end it "should return valid JSON when to_json is called when image is nil" do expect(@event[:image]).to be_nil hash = JSON.parse(@event.to_json) expect(hash.keys).to include("image") expect(hash["image"].keys).to include("url") expect(hash["image"]["url"]).to be_nil end it "should return valid JSON when to_json is called when image is present" do @event[:image] = 'test.jpeg' @event.save! @event.reload expect(JSON.parse(@event.to_json)["image"]).to eq({"url" => "/uploads/test.jpeg"}) end it "should return valid JSON when to_json is called on a collection containing uploader from a model" do @event[:image] = 'test.jpeg' @event.save! @event.reload expect(JSON.parse({:data => @event.image}.to_json)).to eq({"data"=>{"image"=>{"url"=>"/uploads/test.jpeg"}}}) end it "should return valid XML when to_xml is called when image is nil" do expect(@event[:image]).to be_nil hash = Hash.from_xml(@event.to_xml)["event#{$arclass}"] expect(hash.keys).to include("image") expect(hash["image"].keys).to include("url") expect(hash["image"]["url"]).to be_nil end it "should return valid XML when to_xml is called when image is present" do @event[:image] = 'test.jpeg' @event.save! @event.reload expect(Hash.from_xml(@event.to_xml)["event#{$arclass}"]["image"]).to eq({"url" => "/uploads/test.jpeg"}) end it "should respect options[:only] when passed to as_json for the serializable hash" do @event[:image] = 'test.jpeg' @event.save! @event.reload expect(@event.as_json(:only => [:foo])).to eq({"foo" => nil}) end it "should respect options[:except] when passed to as_json for the serializable hash" do @event[:image] = 'test.jpeg' @event.save! @event.reload expect(@event.as_json(:except => [:id, :image, :foo])).to eq({"textfile" => nil}) end it "should respect both options[:only] and options[:except] when passed to as_json for the serializable hash" do @event[:image] = 'test.jpeg' @event.save! @event.reload expect(@event.as_json(:only => [:foo], :except => [:id])).to eq({"foo" => nil}) end it "should respect options[:only] when passed to to_xml for the serializable hash" do @event[:image] = 'test.jpeg' @event.save! @event.reload expect(Hash.from_xml(@event.to_xml(:only => [:foo]))["event#{$arclass}"]["image"]).to be_nil end it "should respect options[:except] when passed to to_xml for the serializable hash" do @event[:image] = 'test.jpeg' @event.save! @event.reload expect(Hash.from_xml(@event.to_xml(:except => [:image]))["event#{$arclass}"]["image"]).to be_nil end it "should respect both options[:only] and options[:except] when passed to to_xml for the serializable hash" do @event[:image] = 'test.jpeg' @event.save! @event.reload expect(Hash.from_xml(@event.to_xml(:only => [:foo], :except => [:id]))["event#{$arclass}"]["image"]).to be_nil end end describe '#image=' do it "should cache a file" do @event.image = stub_file('test.jpeg') expect(@event.image).to be_an_instance_of(@uploader) end it "should write nothing to the database, to prevent overriden filenames to fail because of unassigned attributes" do expect(@event[:image]).to be_nil end it "should copy a file into into the cache directory" do @event.image = stub_file('test.jpeg') expect(@event.image.current_path).to match(%r(^#{public_path('uploads/tmp')})) end it "should do nothing when nil is assigned" do @event.image = nil expect(@event.image).to be_blank end it "should do nothing when an empty string is assigned" do @event.image = '' expect(@event.image).to be_blank end context 'when validating white list integrity' do before do @uploader.class_eval do def extension_white_list %w(txt) end end end it "should use I18n for integrity error messages" do # Localize the error message to Dutch change_locale_and_store_translations(:nl, :errors => { :messages => { :extension_white_list_error => "Het opladen van %{extension} bestanden is niet toe gestaan. Geaccepteerde types: %{allowed_types}" } }) do # Assigning image triggers check_whitelist! and thus should be inside change_locale_and_store_translations @event.image = stub_file('test.jpg') expect(@event).to_not be_valid @event.valid? expect(@event.errors[:image]).to eq (['Het opladen van "jpg" bestanden is niet toe gestaan. Geaccepteerde types: txt']) end end end context 'when validating black list integrity' do before do @uploader.class_eval do def extension_black_list %w(jpg) end end end it "should use I18n for integrity error messages" do # Localize the error message to Dutch change_locale_and_store_translations(:nl, :errors => { :messages => { :extension_black_list_error => "You are not allowed to upload %{extension} files, prohibited types: %{prohibited_types}" } }) do # Assigning image triggers check_blacklist! and thus should be inside change_locale_and_store_translations @event.image = stub_file('test.jpg') expect(@event).to_not be_valid @event.valid? expect(@event.errors[:image]).to eq(['You are not allowed to upload "jpg" files, prohibited types: jpg']) end end end context 'when validating processing' do before do @uploader.class_eval do process :monkey def monkey raise CarrierWave::ProcessingError end end @event.image = stub_file('test.jpg') end it "should make the record invalid when a processing error occurs" do expect(@event).to_not be_valid end it "should use I18n for processing errors without messages" do @event.valid? expect(@event.errors[:image]).to eq(['failed to be processed']) change_locale_and_store_translations(:pt, :activerecord => { :errors => { :messages => { :carrierwave_processing_error => 'falha ao processar imagem.' } } }) do expect(@event).to_not be_valid expect(@event.errors[:image]).to eq(['falha ao processar imagem.']) end end end context 'when validating processing' do before do @uploader.class_eval do process :monkey def monkey raise CarrierWave::ProcessingError, "Ohh noez!" end end @event.image = stub_file('test.jpg') end it "should make the record invalid when a processing error occurs" do expect(@event).to_not be_valid end it "should use the error's messages for processing errors with messages" do @event.valid? expect(@event.errors[:image]).to eq(['Ohh noez!']) change_locale_and_store_translations(:pt, :activerecord => { :errors => { :messages => { :carrierwave_processing_error => 'falha ao processar imagem.' } } }) do expect(@event).to_not be_valid expect(@event.errors[:image]).to eq(['Ohh noez!']) end end end end describe '#save' do it "should do nothing when no file has been assigned" do expect(@event.save).to be_true expect(@event.image).to be_blank end it "should copy the file to the upload directory when a file has been assigned" do @event.image = stub_file('test.jpeg') expect(@event.save).to be_true expect(@event.image).to be_an_instance_of(@uploader) expect(@event.image.current_path).to eq public_path('uploads/test.jpeg') end it "should do nothing when a validation fails" do @class.validate { |r| r.errors.add :textfile, "FAIL!" } @event.image = stub_file('test.jpeg') expect(@event.save).to be_false expect(@event.image).to be_an_instance_of(@uploader) expect(@event.image.current_path).to match(/^#{public_path('uploads/tmp')}/) end it "should assign the filename to the database" do @event.image = stub_file('test.jpeg') expect(@event.save).to be_true @event.reload expect(@event[:image]).to eq('test.jpeg') expect(@event.image_identifier).to eq('test.jpeg') end it "should preserve the image when nothing is assigned" do @event.image = stub_file('test.jpeg') expect(@event.save).to be_true @event = @class.find(@event.id) @event.foo = "bar" expect(@event.save).to be_true expect(@event[:image]).to eq('test.jpeg') expect(@event.image_identifier).to eq('test.jpeg') end it "should remove the image if remove_image? returns true" do @event.image = stub_file('test.jpeg') @event.save! @event.remove_image = true @event.save! @event.reload expect(@event.image).to be_blank expect(@event[:image]).to eq(nil) expect(@event.image_identifier).to eq(nil) end it "should mark image as changed when saving a new image" do expect(@event.image_changed?).to be_false @event.image = stub_file("test.jpeg") expect(@event.image_changed?).to be_true @event.save @event.reload expect(@event.image_changed?).to be_false @event.image = stub_file("test.jpg") expect(@event.image_changed?).to be_true expect(@event.changed_for_autosave?).to be_true end end describe "remove_image!" do before do @event.image = stub_file('test.jpeg') @event.save! @event.remove_image! end it "should clear the serialization column" do expect(@event.attributes['image']).to be_blank end it "should return to false after being saved" do @event.save! @event.remove_image.should == false @event.remove_image?.should == false end end describe "#remote_image_url=" do # FIXME ideally image_changed? and remote_image_url_changed? would return true it "should mark image as changed when setting remote_image_url" do expect(@event.image_changed?).to be_false @event.remote_image_url = 'http://www.example.com/test.jpg' expect(@event.image_changed?).to be_true @event.save @event.reload expect(@event.image_changed?).to be_false end context 'when validating download' do before do @uploader.class_eval do def download! file raise CarrierWave::DownloadError end end @event.remote_image_url = 'http://www.example.com/missing.jpg' end it "should make the record invalid when a download error occurs" do expect(@event).to_not be_valid end it "should use I18n for download errors without messages" do @event.valid? expect(@event.errors[:image]).to eq(['could not be downloaded']) change_locale_and_store_translations(:pt, :activerecord => { :errors => { :messages => { :carrierwave_download_error => 'não pode ser descarregado' } } }) do expect(@event).to_not be_valid expect(@event.errors[:image]).to eq(['não pode ser descarregado']) end end end end describe "#serializable_hash" do it "should include the image with url" do @event.image = stub_file("test.jpg") @event.serializable_hash["image"].should have_key("url") end it "should include the other columns" do ["id", "foo"].each do |key| expect(@event.serializable_hash).to have_key(key) end end it "should take an option to exclude the image column" do expect(@event.serializable_hash(:except => :image)).to_not have_key("image") end it "should take an option to only include the image column" do expect(@event.serializable_hash(:only => :image)).to have_key("image") end context "with multiple uploaders" do before do @uploader1 = Class.new(CarrierWave::Uploader::Base) @class.mount_uploader(:textfile, @uploader1) @event = @class.new @event.image = stub_file('old.jpeg') @event.textfile = stub_file('old.txt') end it "serializes the correct values" do expect(@event.serializable_hash["image"]["url"]).to match(/old\.jpeg$/) expect(@event.serializable_hash["textfile"]["url"]).to match(/old\.txt$/) end it "should have JSON for each uploader" do parsed = JSON.parse(@event.to_json) expect(parsed["image"]["url"]).to match(/old\.jpeg$/) expect(parsed["textfile"]["url"]).to match(/old\.txt$/) end end end describe '#destroy' do it "should not raise an error with a custom filename" do @uploader.class_eval do def filename "page.jpeg" end end @event.image = stub_file('test.jpeg') expect(@event.save).to be_true expect { @event.destroy }.to_not raise_error end it "should do nothing when no file has been assigned" do expect(@event.save).to be_true @event.destroy end it "should remove the file from the filesystem" do @event.image = stub_file('test.jpeg') expect(@event.save).to be_true expect(@event.image).to be_an_instance_of(@uploader) expect(@event.image.current_path).to eq public_path('uploads/test.jpeg') @event.destroy expect(File.exist?(public_path('uploads/test.jpeg'))).to be_false end end describe 'with overriddent filename' do describe '#save' do before do @uploader.class_eval do def filename model.name + File.extname(super) end end @event.stub!(:name).and_return('jonas') end it "should copy the file to the upload directory when a file has been assigned" do @event.image = stub_file('test.jpeg') expect(@event.save).to be_true expect(@event.image).to be_an_instance_of(@uploader) expect(@event.image.current_path).to eq(public_path('uploads/jonas.jpeg')) end it "should assign an overridden filename to the database" do @event.image = stub_file('test.jpeg') expect(@event.save).to be_true @event.reload expect(@event[:image]).to eq('jonas.jpeg') end end end describe 'with validates_presence_of' do before do @class.validates_presence_of :image @event.stub!(:name).and_return('jonas') end it "should be valid if a file has been cached" do @event.image = stub_file('test.jpeg') expect(@event).to be_valid end it "should not be valid if a file has not been cached" do expect(@event).to_not be_valid end end describe 'with validates_size_of' do before do @class.validates_size_of :image, :maximum => 40 @event.stub!(:name).and_return('jonas') end it "should be valid if a file has been cached that matches the size criteria" do @event.image = stub_file('test.jpeg') expect(@event).to be_valid end it "should not be valid if a file has been cached that does not match the size criteria" do @event.image = stub_file('bork.txt') expect(@event).to_not be_valid end end end describe '#mount_uploader with mount_on' do before(:all) { TestMigration.up } after(:all) { TestMigration.down } after { Event.delete_all } before do @class = Class.new(Event) @class.table_name = "events" @uploader = Class.new(CarrierWave::Uploader::Base) @class.mount_uploader(:avatar, @uploader, :mount_on => :image) @event = @class.new end describe '#avatar=' do it "should cache a file" do @event.avatar = stub_file('test.jpeg') @event.save @event.reload expect(@event.avatar).to be_an_instance_of(@uploader) expect(@event.image).to eq('test.jpeg') end end end describe '#mount_uploader removing old files' do before(:all) { TestMigration.up } after(:all) { TestMigration.down } before do @class = Class.new(Event) @class.table_name = "events" @uploader = Class.new(CarrierWave::Uploader::Base) @class.mount_uploader(:image, @uploader) @event = @class.new @event.image = stub_file('old.jpeg') expect(@event.save).to be_true expect(File.exists?(public_path('uploads/old.jpeg'))).to be_true end after do FileUtils.rm_rf(file_path("uploads")) end describe 'normally' do it "should remove old file if old file had a different path" do @event.image = stub_file('new.jpeg') expect(@event.save).to be_true expect(File.exists?(public_path('uploads/new.jpeg'))).to be_true expect(File.exists?(public_path('uploads/old.jpeg'))).to be_false end it "should not remove old file if old file had a different path but config is false" do @uploader.stub!(:remove_previously_stored_files_after_update).and_return(false) @event.image = stub_file('new.jpeg') expect(@event.save).to be_true expect(File.exists?(public_path('uploads/new.jpeg'))).to be_true expect(File.exists?(public_path('uploads/old.jpeg'))).to be_true end it "should not remove file if old file had the same path" do @event.image = stub_file('old.jpeg') expect(@event.save).to be_true expect(File.exists?(public_path('uploads/old.jpeg'))).to be_true end it "should not remove file if validations fail on save" do @class.validate { |r| r.errors.add :textfile, "FAIL!" } @event.image = stub_file('new.jpeg') expect(@event.save).to be_false expect(File.exists?(public_path('uploads/old.jpeg'))).to be_true end end describe 'with an overriden filename' do before do @uploader.class_eval do def filename model.foo + File.extname(super) end end @event.image = stub_file('old.jpeg') @event.foo = 'test' expect(@event.save).to be_true expect(File.exists?(public_path('uploads/test.jpeg'))).to be_true expect(@event.image.read).to eq('this is stuff') end it "should not remove file if old file had the same dynamic path" do @event.image = stub_file('test.jpeg') expect(@event.save).to be_true expect(File.exists?(public_path('uploads/test.jpeg'))).to be_true end it "should remove old file if old file had a different dynamic path" do @event.foo = "new" @event.image = stub_file('test.jpeg') expect(@event.save).to be_true expect(File.exists?(public_path('uploads/new.jpeg'))).to be_true expect(File.exists?(public_path('uploads/test.jpeg'))).to be_false end end end describe '#mount_uploader removing old files with versions' do before(:all) { TestMigration.up } after(:all) { TestMigration.down } before do @class = Class.new(Event) @class.table_name = "events" @uploader = Class.new(CarrierWave::Uploader::Base) @uploader.version :thumb @class.mount_uploader(:image, @uploader) @event = @class.new @event.image = stub_file('old.jpeg') expect(@event.save).to be_true expect(File.exists?(public_path('uploads/old.jpeg'))).to be_true expect(File.exists?(public_path('uploads/thumb_old.jpeg'))).to be_true end after do FileUtils.rm_rf(file_path("uploads")) end it "should remove old file if old file had a different path" do @event.image = stub_file('new.jpeg') expect(@event.save).to be_true expect(File.exists?(public_path('uploads/new.jpeg'))).to be_true expect(File.exists?(public_path('uploads/thumb_new.jpeg'))).to be_true expect(File.exists?(public_path('uploads/old.jpeg'))).to be_false expect(File.exists?(public_path('uploads/thumb_old.jpeg'))).to be_false end it "should not remove file if old file had the same path" do @event.image = stub_file('old.jpeg') expect(@event.save).to be_true expect(File.exists?(public_path('uploads/old.jpeg'))).to be_true expect(File.exists?(public_path('uploads/thumb_old.jpeg'))).to be_true end end describe '#mount_uploader removing old files with multiple uploaders' do before(:all) { TestMigration.up } after(:all) { TestMigration.down } before do @class = Class.new(Event) @class.table_name = "events" @uploader = Class.new(CarrierWave::Uploader::Base) @class.mount_uploader(:image, @uploader) @uploader1 = Class.new(CarrierWave::Uploader::Base) @class.mount_uploader(:textfile, @uploader1) @event = @class.new @event.image = stub_file('old.jpeg') @event.textfile = stub_file('old.txt') expect(@event.save).to be_true expect(File.exists?(public_path('uploads/old.jpeg'))).to be_true expect(File.exists?(public_path('uploads/old.txt'))).to be_true end after do FileUtils.rm_rf(file_path("uploads")) end it "should remove old file1 and file2 if old file1 and file2 had a different paths" do @event.image = stub_file('new.jpeg') @event.textfile = stub_file('new.txt') expect(@event.save).to be_true expect(File.exists?(public_path('uploads/new.jpeg'))).to be_true expect(File.exists?(public_path('uploads/old.jpeg'))).to be_false expect(File.exists?(public_path('uploads/new.txt'))).to be_true expect(File.exists?(public_path('uploads/old.txt'))).to be_false end it "should remove old file1 but not file2 if old file1 had a different path but old file2 has the same path" do @event.image = stub_file('new.jpeg') @event.textfile = stub_file('old.txt') expect(@event.save).to be_true expect(File.exists?(public_path('uploads/new.jpeg'))).to be_true expect(File.exists?(public_path('uploads/old.jpeg'))).to be_false expect(File.exists?(public_path('uploads/old.txt'))).to be_true end it "should not remove file1 or file2 if file1 and file2 have the same paths" do @event.image = stub_file('old.jpeg') @event.textfile = stub_file('old.txt') expect(@event.save).to be_true expect(File.exists?(public_path('uploads/old.jpeg'))).to be_true expect(File.exists?(public_path('uploads/old.txt'))).to be_true end end describe '#mount_uploader removing old files with with mount_on' do before(:all) { TestMigration.up } after(:all) { TestMigration.down } before do @class = Class.new(Event) @class.table_name = "events" @uploader = Class.new(CarrierWave::Uploader::Base) @class.mount_uploader(:avatar, @uploader, :mount_on => :image) @event = @class.new @event.avatar = stub_file('old.jpeg') expect(@event.save).to be_true expect(File.exists?(public_path('uploads/old.jpeg'))).to be_true end after do FileUtils.rm_rf(file_path("uploads")) end it "should remove old file if old file had a different path" do @event.avatar = stub_file('new.jpeg') expect(@event.save).to be_true expect(File.exists?(public_path('uploads/new.jpeg'))).to be_true expect(File.exists?(public_path('uploads/old.jpeg'))).to be_false end it "should not remove file if old file had the same path" do @event.avatar = stub_file('old.jpeg') expect(@event.save).to be_true expect(File.exists?(public_path('uploads/old.jpeg'))).to be_true end end end carrierwave-0.10.0/spec/processing/000077500000000000000000000000001230347170300172105ustar00rootroot00000000000000carrierwave-0.10.0/spec/processing/mime_types_spec.rb000066400000000000000000000044641230347170300227320ustar00rootroot00000000000000# encoding: utf-8 require 'spec_helper' describe CarrierWave::MimeTypes do before do @klass = Class.new do attr_accessor :content_type include CarrierWave::MimeTypes end @instance = @klass.new FileUtils.cp(file_path('landscape.jpg'), file_path('landscape_copy.jpg')) @instance.stub(:original_filename).and_return file_path('landscape_copy.jpg') @instance.stub(:file).and_return CarrierWave::SanitizedFile.new(file_path('landscape_copy.jpg')) @file = @instance.file end after do FileUtils.rm(file_path('landscape_copy.jpg')) end describe '#set_content_type' do it "does not set content_type if already set" do @instance.file.content_type = 'image/jpeg' @instance.file.should_not_receive(:content_type=) @instance.set_content_type end it "set content_type if content_type is nil" do pending 'This spec is deprecated because Proxy now read content type itself.' @instance.file.content_type = nil @instance.file.should_receive(:content_type=).with('image/jpeg') @instance.set_content_type end it "set content_type if content_type is empty" do @instance.file.content_type = '' @instance.file.should_receive(:content_type=).with('image/jpeg') @instance.set_content_type end %w[ application/octet-stream binary/octet-stream ].each do |type| it "sets content_type if content_type is generic (#{type})" do @instance.file.content_type = type @instance.file.should_receive(:content_type=).with('image/jpeg') @instance.set_content_type end end it "sets content_type if override is true" do @instance.file.content_type = 'image/jpeg' @instance.file.should_receive(:content_type=).with('image/jpeg') @instance.set_content_type(true) end end describe "test errors" do context "invalid mime type" do before do @instance.file.content_type = nil # TODO: somehow force a ::MIME::InvalidContentType error when set_content_type is called. end it "should raise a MIME::InvalidContentType error" do # lambda {@instance.set_content_type}.should raise_exception(::MIME::InvalidContentType, /^Failed to process file with MIME::Types, maybe not valid content-type\? Original Error:/) end end end end carrierwave-0.10.0/spec/processing/mini_magick_spec.rb000066400000000000000000000134731230347170300230260ustar00rootroot00000000000000# encoding: utf-8 require 'spec_helper' describe CarrierWave::MiniMagick do before do @klass = Class.new do include CarrierWave::MiniMagick end @instance = @klass.new FileUtils.cp(file_path('landscape.jpg'), file_path('landscape_copy.jpg')) @instance.stub(:current_path).and_return(file_path('landscape_copy.jpg')) @instance.stub(:cached?).and_return true end after do FileUtils.rm(file_path('landscape_copy.jpg')) end describe "#convert" do it "should convert from one format to another" do @instance.convert('png') img = ::MiniMagick::Image.open(@instance.current_path) img['format'].should =~ /PNG/ end end describe '#resize_to_fill' do it "should resize the image to exactly the given dimensions and maintain file type" do @instance.resize_to_fill(200, 200) @instance.should have_dimensions(200, 200) ::MiniMagick::Image.open(@instance.current_path)['format'].should =~ /JPEG/ end it "should resize the image to exactly the given dimensions and maintain updated file type" do @instance.convert('png') @instance.resize_to_fill(200, 200) @instance.should have_dimensions(200, 200) ::MiniMagick::Image.open(@instance.current_path)['format'].should =~ /PNG/ end it "should scale up the image if it smaller than the given dimensions" do @instance.resize_to_fill(1000, 1000) @instance.should have_dimensions(1000, 1000) end end describe '#resize_and_pad' do it "should resize the image to exactly the given dimensions and maintain file type" do @instance.resize_and_pad(200, 200) @instance.should have_dimensions(200, 200) ::MiniMagick::Image.open(@instance.current_path)['format'].should =~ /JPEG/ end it "should resize the image to exactly the given dimensions and maintain updated file type" do @instance.convert('png') @instance.resize_and_pad(200, 200) @instance.should have_dimensions(200, 200) ::MiniMagick::Image.open(@instance.current_path)['format'].should =~ /PNG/ end it "should scale up the image if it smaller than the given dimensions" do @instance.resize_and_pad(1000, 1000) @instance.should have_dimensions(1000, 1000) end it "should pad with white" do @instance.resize_and_pad(200, 200) color = color_of_pixel(@instance.current_path, 0, 0) color.should include('#FFFFFF') color.should_not include('#FFFFFF00') end it "should pad with transparent" do @instance.convert('png') @instance.resize_and_pad(200, 200, :transparent) color = color_of_pixel(@instance.current_path, 0, 0) color.should include('#FFFFFF00') end it "should not pad with transparent" do @instance.resize_and_pad(200, 200, :transparent) @instance.convert('png') color = color_of_pixel(@instance.current_path, 0, 0) color.should include('#FFFFFF') color.should_not include('#FFFFFF00') end end describe '#resize_to_fit' do it "should resize the image to fit within the given dimensions and maintain file type" do @instance.resize_to_fit(200, 200) @instance.should have_dimensions(200, 150) ::MiniMagick::Image.open(@instance.current_path)['format'].should =~ /JPEG/ end it "should resize the image to fit within the given dimensions and maintain updated file type" do @instance.convert('png') @instance.resize_to_fit(200, 200) @instance.should have_dimensions(200, 150) ::MiniMagick::Image.open(@instance.current_path)['format'].should =~ /PNG/ end it "should scale up the image if it smaller than the given dimensions" do @instance.resize_to_fit(1000, 1000) @instance.should have_dimensions(1000, 750) end end describe '#resize_to_limit' do it "should resize the image to fit within the given dimensions and maintain file type" do @instance.resize_to_limit(200, 200) @instance.should have_dimensions(200, 150) ::MiniMagick::Image.open(@instance.current_path)['format'].should =~ /JPEG/ end it "should resize the image to fit within the given dimensions and maintain updated file type" do @instance.convert('png') @instance.resize_to_limit(200, 200) @instance.should have_dimensions(200, 150) ::MiniMagick::Image.open(@instance.current_path)['format'].should =~ /PNG/ end it "should not scale up the image if it smaller than the given dimensions" do @instance.resize_to_limit(1000, 1000) @instance.should have_dimensions(640, 480) end end describe "test errors" do context "invalid image file" do before do File.open(@instance.current_path, 'w') do |f| f.puts "bogus" end end it "should fail to process a non image file" do lambda {@instance.resize_to_limit(200, 200)}.should raise_exception(CarrierWave::ProcessingError, /^Failed to manipulate with MiniMagick, maybe it is not an image\? Original Error:/) end it "should use I18n" do change_locale_and_store_translations(:nl, :errors => { :messages => { :mini_magick_processing_error => "Kon bestand niet met MiniMagick bewerken, misschien is het geen beeld bestand? MiniMagick foutmelding: %{e}" } }) do lambda {@instance.resize_to_limit(200, 200)}.should raise_exception(CarrierWave::ProcessingError, /^Kon bestand niet met MiniMagick bewerken, misschien is het geen beeld bestand\? MiniMagick foutmelding:/) end end it "should not suppress errors when translation is unavailable" do change_locale_and_store_translations(:foo, {}) do lambda do @instance.resize_to_limit(200, 200) end.should raise_exception( CarrierWave::ProcessingError, /Not a JPEG/ ) end end end end end carrierwave-0.10.0/spec/processing/rmagick_spec.rb000066400000000000000000000177121230347170300221740ustar00rootroot00000000000000# encoding: utf-8 require 'spec_helper' describe CarrierWave::RMagick do before do @klass = Class.new do include CarrierWave::RMagick end @instance = @klass.new FileUtils.cp(file_path('landscape.jpg'), file_path('landscape_copy.jpg')) @instance.stub(:current_path).and_return(file_path('landscape_copy.jpg')) @instance.stub(:cached?).and_return true end after do FileUtils.rm(file_path('landscape_copy.jpg')) end describe '#convert' do it "should convert the image to the given format" do @instance.convert(:png) ::Magick::Image.read(@instance.current_path).first.format.should == 'PNG' end end describe '#resize_to_fill' do it "should resize the image to exactly the given dimensions and maintain file type" do @instance.resize_to_fill(200, 200) @instance.should have_dimensions(200, 200) ::Magick::Image.read(@instance.current_path).first.format.should == 'JPEG' end it "should resize the image to exactly the given dimensions and maintain updated file type" do @instance.convert('png') @instance.resize_to_fill(200, 200) @instance.should have_dimensions(200, 200) ::Magick::Image.read(@instance.current_path).first.format.should == 'PNG' end it "should scale up the image if it smaller than the given dimensions" do @instance.resize_to_fill(1000, 1000) @instance.should have_dimensions(1000, 1000) end end describe '#resize_and_pad' do it "should resize the image to exactly the given dimensions and maintain file type" do @instance.resize_and_pad(200, 200) @instance.should have_dimensions(200, 200) ::Magick::Image.read(@instance.current_path).first.format.should == 'JPEG' end it "should resize the image to exactly the given dimensions and maintain updated file type" do @instance.convert('png') @instance.resize_and_pad(200, 200) @instance.should have_dimensions(200, 200) ::Magick::Image.read(@instance.current_path).first.format.should == 'PNG' end it "should pad with white" do @instance.resize_and_pad(200, 200) color = color_of_pixel(@instance.current_path, 0, 0) color.should include('#FFFFFF') color.should_not include('#FFFFFF00') end it "should pad with transparent" do @instance.convert('png') @instance.resize_and_pad(200, 200, :transparent) color = color_of_pixel(@instance.current_path, 0, 0) color.should include('#FFFFFF00') end it "should not pad with transparent" do @instance.resize_and_pad(200, 200, :transparent) @instance.convert('png') color = color_of_pixel(@instance.current_path, 0, 0) color.should include('#FFFFFF') color.should_not include('#FFFFFF00') end it "should pad with given color" do @instance.resize_and_pad(200, 200, '#888') color = color_of_pixel(@instance.current_path, 0, 0) color.should include('#888888') end it "should scale up the image if it smaller than the given dimensions" do @instance.resize_and_pad(1000, 1000) @instance.should have_dimensions(1000, 1000) end end describe '#resize_to_fit' do it "should resize the image to fit within the given dimensions and maintain file type" do @instance.resize_to_fit(200, 200) @instance.should have_dimensions(200, 150) ::Magick::Image.read(@instance.current_path).first.format.should == 'JPEG' end it "should resize the image to fit within the given dimensions and maintain updated file type" do @instance.convert('png') @instance.resize_to_fit(200, 200) @instance.should have_dimensions(200, 150) ::Magick::Image.read(@instance.current_path).first.format.should == 'PNG' end it "should scale up the image if it smaller than the given dimensions" do @instance.resize_to_fit(1000, 1000) @instance.should have_dimensions(1000, 750) end end describe '#resize_to_limit' do it "should resize the image to fit within the given dimensions and maintain file type" do @instance.resize_to_limit(200, 200) @instance.should have_dimensions(200, 150) ::Magick::Image.read(@instance.current_path).first.format.should == 'JPEG' end it "should resize the image to fit within the given dimensions and maintain updated file type" do @instance.convert('png') @instance.resize_to_limit(200, 200) @instance.should have_dimensions(200, 150) ::Magick::Image.read(@instance.current_path).first.format.should == 'PNG' end it "should not scale up the image if it smaller than the given dimensions" do @instance.resize_to_limit(1000, 1000) @instance.should have_dimensions(640, 480) end end describe '#resize_to_geometry_string' do it "should resize the image to comply with `200x200^` Geometry String spec and maintain file type" do @instance.resize_to_geometry_string('200x200^') @instance.should have_dimensions(267, 200) ::Magick::Image.read(@instance.current_path).first.format.should == 'JPEG' end it "should resize the image to comply with `200x200^` Geometry String spec and maintain updated file type" do @instance.convert('png') @instance.resize_to_geometry_string('200x200^') @instance.should have_dimensions(267, 200) ::Magick::Image.read(@instance.current_path).first.format.should == 'PNG' end it "should resize the image to have 125% larger dimensions" do @instance.resize_to_geometry_string('125%') @instance.should have_dimensions(800, 600) end it "should resize the image to have a given height" do @instance.resize_to_geometry_string('x256') @instance.should have_height(256) end it "should resize the image to have a given width" do @instance.resize_to_geometry_string('256x') @instance.should have_width(256) end end describe "#manipulate!" do it 'should support passing write options to RMagick' do image = ::Magick::Image.read(file_path('landscape_copy.jpg')) ::Magick::Image.stub(:read => image) ::Magick::Image::Info.any_instance.should_receive(:quality=).with(50) ::Magick::Image::Info.any_instance.should_receive(:depth=).with(8) @instance.manipulate! do |image, index, options| options[:write] = { :quality => 50, :depth => 8 } image end end it 'should support passing read options to RMagick' do ::Magick::Image::Info.any_instance.should_receive(:density=).with(10) ::Magick::Image::Info.any_instance.should_receive(:size=).with("200x200") @instance.manipulate! :read => { :density => 10, :size => %{"200x200"} } end end describe "test errors" do context "invalid image file" do before do File.open(@instance.current_path, 'w') do |f| f.puts "bogus" end end it "should fail to process a non image file" do lambda {@instance.resize_to_limit(200, 200)}.should raise_exception(CarrierWave::ProcessingError, /^Failed to manipulate with rmagick, maybe it is not an image\? Original Error:/) end it "should use I18n" do change_locale_and_store_translations(:nl, :errors => { :messages => { :rmagick_processing_error => "Kon bestand niet met rmagick bewerken, misschien is het geen beeld bestand? rmagick foutmelding: %{e}" } }) do lambda {@instance.resize_to_limit(200, 200)}.should raise_exception(CarrierWave::ProcessingError, /^Kon bestand niet met rmagick bewerken, misschien is het geen beeld bestand\? rmagick foutmelding:/) end end it "should not suppress errors when translation is unavailable" do change_locale_and_store_translations(:foo, {}) do lambda do @instance.resize_to_limit(200, 200) end.should raise_exception( CarrierWave::ProcessingError, /Not a JPEG/ ) end end end end end carrierwave-0.10.0/spec/sanitized_file_spec.rb000066400000000000000000000554211230347170300213730ustar00rootroot00000000000000# encoding: utf-8 require 'spec_helper' require 'mime/types' describe CarrierWave::SanitizedFile do before do FileUtils.cp(file_path('test.jpg'), file_path('llama.jpg')) end after(:all) do if File.exists?(file_path('llama.jpg')) FileUtils.rm(file_path('llama.jpg')) end FileUtils.rm_rf(public_path) end describe '#empty?' do it "should be empty for nil" do @sanitized_file = CarrierWave::SanitizedFile.new(nil) @sanitized_file.should be_empty end it "should be empty for an empty string" do @sanitized_file = CarrierWave::SanitizedFile.new("") @sanitized_file.should be_empty end it "should be empty for an empty StringIO" do @sanitized_file = CarrierWave::SanitizedFile.new(StringIO.new("")) @sanitized_file.should be_empty end end describe '#original_filename' do it "should default to the original_filename" do file = mock('file', :original_filename => 'llama.jpg') sanitized_file = CarrierWave::SanitizedFile.new(file) sanitized_file.original_filename.should == "llama.jpg" end it "should defer to the base name of the path if original_filename is unavailable" do file = mock('file', :path => '/path/to/test.jpg') sanitized_file = CarrierWave::SanitizedFile.new(file) sanitized_file.original_filename.should == "test.jpg" end it "should be nil otherwise" do file = mock('file') sanitized_file = CarrierWave::SanitizedFile.new(file) sanitized_file.original_filename.should be_nil end end describe '#basename' do it "should return the basename for complicated extensions" do @sanitized_file = CarrierWave::SanitizedFile.new(file_path('complex.filename.tar.gz')) @sanitized_file.basename.should == "complex.filename" end it "should be the filename if the file has no extension" do @sanitized_file = CarrierWave::SanitizedFile.new(file_path('complex')) @sanitized_file.basename.should == "complex" end end describe '#extension' do %w[gz bz2 z lz xz].each do |ext| it "should return the extension for complicated extensions (tar.#{ext})" do @sanitized_file = CarrierWave::SanitizedFile.new(file_path("complex.filename.tar.#{ext}")) @sanitized_file.extension.should == "tar.#{ext}" end end it "should return the extension for real-world user file names" do @sanitized_file = CarrierWave::SanitizedFile.new(file_path('Photo on 2009-12-01 at 11.12.jpg')) @sanitized_file.extension.should == "jpg" end it "should return the extension for basic filenames" do @sanitized_file = CarrierWave::SanitizedFile.new(file_path('something.png')) @sanitized_file.extension.should == "png" end it "should be an empty string if the file has no extension" do @sanitized_file = CarrierWave::SanitizedFile.new(file_path('complex')) @sanitized_file.extension.should == "" end end describe '#filename' do before do @sanitized_file = CarrierWave::SanitizedFile.new(nil) end it "should default to the original filename if it is valid" do @sanitized_file.should_receive(:original_filename).at_least(:once).and_return("llama.jpg") @sanitized_file.filename.should == "llama.jpg" end it "should remove illegal characters from a filename" do @sanitized_file.should_receive(:original_filename).at_least(:once).and_return("test-s,%&m#st?.jpg") @sanitized_file.filename.should == "test-s___m_st_.jpg" end it "should remove slashes from the filename" do @sanitized_file.should_receive(:original_filename).at_least(:once).and_return("../../very_tricky/foo.bar") @sanitized_file.filename.should_not =~ /[\\\/]/ end it "should remove illegal characters if there is no extension" do @sanitized_file.should_receive(:original_filename).at_least(:once).and_return('`*foo') @sanitized_file.filename.should == "__foo" end it "should remove the path prefix on Windows" do @sanitized_file.should_receive(:original_filename).at_least(:once).and_return('c:\temp\foo.txt') @sanitized_file.filename.should == "foo.txt" end it "should make sure the *nix directory thingies can't be used as filenames" do @sanitized_file.should_receive(:original_filename).at_least(:once).and_return(".") @sanitized_file.filename.should == "_." end it "should maintain uppercase filenames" do @sanitized_file.should_receive(:original_filename).at_least(:once).and_return("DSC4056.JPG") @sanitized_file.filename.should == "DSC4056.JPG" end end describe '#filename with an overridden sanitize_regexp' do before do @sanitized_file = CarrierWave::SanitizedFile.new(nil) @sanitized_file.stub(:sanitize_regexp).and_return(/[^a-zA-Z\.\-\+_]/) end it "should default to the original filename if it is valid" do @sanitized_file.should_receive(:original_filename).at_least(:once).and_return("llama.jpg") @sanitized_file.filename.should == "llama.jpg" end it "should remove illegal characters from a filename" do @sanitized_file.should_receive(:original_filename).at_least(:once).and_return("123.jpg") @sanitized_file.filename.should == "___.jpg" end end describe '#some unicode filenames with an overridden sanitize_regexp' do before do @sanitized_file = CarrierWave::SanitizedFile.new(nil) regexp = RUBY_VERSION >= '1.9' ? Regexp.new('[^[:word:]\.\-\+]') : /[^éôёЁа-яА-Яa-zA-Zà-üÀ-Ü0-9\.\-\+_]/u @sanitized_file.stub(:sanitize_regexp).and_return(regexp) end it "should default to the original filename if it is valid" do @sanitized_file.should_receive(:original_filename).at_least(:once).and_return("тестовый.jpg") @sanitized_file.filename.should == "тестовый.jpg" end it "should downcase characters properly" do @sanitized_file.should_receive(:original_filename).at_least(:once).and_return("ТестоВый Ёжик.jpg") @sanitized_file.filename.should == "ТестоВый_Ёжик.jpg" end it "should remove illegal characters from a filename" do @sanitized_file.should_receive(:original_filename).at_least(:once).and_return("⟲«Du côté des chars lourds»_123.doc") @sanitized_file.filename.should == "__Du_côté_des_chars_lourds__123.doc" end end describe "#content_type" do it "preserves file's content_type" do @sanitized_file = CarrierWave::SanitizedFile.new(:content_type => 'image/png') @sanitized_file.content_type.should == 'image/png' end it "should handle Mime::Type object" do @file = File.open(file_path('sponsored.doc')) @file.stub!(:content_type).and_return(MIME::Type.new('application/msword')) @sanitized_file = CarrierWave::SanitizedFile.new(@file) @sanitized_file.stub!(:file).and_return(@file) lambda { @sanitized_file.content_type }.should_not raise_error @sanitized_file.content_type.should == 'application/msword' end it 'should read content type from path if missing' do @sanitized_file = CarrierWave::SanitizedFile.new('llama.jpg') @sanitized_file.content_type.should == 'image/jpeg' end end describe "#content_type=" do it "sets content_type" do @sanitized_file = CarrierWave::SanitizedFile.new(:content_type => 'image/png') @sanitized_file.content_type = 'text/html' @sanitized_file.content_type.should == 'text/html' end end shared_examples_for "all valid sanitized files" do describe '#empty?' do it "should not be empty" do @sanitized_file.should_not be_empty end end describe '#original_filename' do it "should return the original filename" do @sanitized_file.original_filename.should == "llama.jpg" end end describe '#filename' do it "should return the filename" do @sanitized_file.filename.should == "llama.jpg" end end describe '#basename' do it "should return the basename" do @sanitized_file.basename.should == "llama" end end describe '#extension' do it "should return the extension" do @sanitized_file.extension.should == "jpg" end end describe "#read" do it "should return the contents of the file" do @sanitized_file.read.should == "this is stuff" end end describe "#size" do it "should return the size of the file" do @sanitized_file.size.should == 13 end end describe '#move_to' do after do FileUtils.rm_f(file_path('gurr.png')) end it "should be moved to the correct location" do @sanitized_file.move_to(file_path('gurr.png')) File.exists?( file_path('gurr.png') ).should be_true end it "should have changed its path when moved" do @sanitized_file.move_to(file_path('gurr.png')) @sanitized_file.path.should == file_path('gurr.png') end it "should have changed its filename when moved" do @sanitized_file.move_to(file_path('gurr.png')) @sanitized_file.filename.should == 'gurr.png' end it "should have changed its basename when moved" do @sanitized_file.move_to(file_path('gurr.png')) @sanitized_file.basename.should == 'gurr' end it "should have changed its extension when moved" do @sanitized_file.move_to(file_path('gurr.png')) @sanitized_file.extension.should == 'png' end it "should set the right permissions" do @sanitized_file.move_to(file_path('gurr.png'), 0755) @sanitized_file.should have_permissions(0755) end it "should set the right directory permissions" do @sanitized_file.move_to(file_path('new_dir','gurr.png'), nil, 0775) @sanitized_file.should have_directory_permissions(0775) FileUtils.rm_rf(file_path('new_dir')) end it "should return itself" do @sanitized_file.move_to(file_path('gurr.png')).should == @sanitized_file end end describe '#copy_to' do after do FileUtils.rm_f(file_path('gurr.png')) end it "should be copied to the correct location" do @sanitized_file.copy_to(file_path('gurr.png')) File.exists?( file_path('gurr.png') ).should be_true file_path('gurr.png').should be_identical_to(file_path('llama.jpg')) end it "should not have changed its path when copied" do running { @sanitized_file.copy_to(file_path('gurr.png')) }.should_not change(@sanitized_file, :path) end it "should not have changed its filename when copied" do running { @sanitized_file.copy_to(file_path('gurr.png')) }.should_not change(@sanitized_file, :filename) end it "should return an object of the same class when copied" do new_file = @sanitized_file.copy_to(file_path('gurr.png')) new_file.should be_an_instance_of(@sanitized_file.class) end it "should adjust the path of the object that is returned when copied" do new_file = @sanitized_file.copy_to(file_path('gurr.png')) new_file.path.should == file_path('gurr.png') end it "should adjust the filename of the object that is returned when copied" do new_file = @sanitized_file.copy_to(file_path('gurr.png')) new_file.filename.should == 'gurr.png' end it "should adjust the basename of the object that is returned when copied" do new_file = @sanitized_file.copy_to(file_path('gurr.png')) new_file.basename.should == 'gurr' end it "should adjust the extension of the object that is returned when copied" do new_file = @sanitized_file.copy_to(file_path('gurr.png')) new_file.extension.should == 'png' end it "should set the right permissions" do new_file = @sanitized_file.copy_to(file_path('gurr.png'), 0755) new_file.should have_permissions(0755) end it "should set the right directory permissions" do new_file = @sanitized_file.copy_to(file_path('new_dir', 'gurr.png'), nil, 0755) new_file.should have_directory_permissions(0755) FileUtils.rm_rf(file_path('new_dir')) end it "should preserve the file's content type" do new_file = @sanitized_file.copy_to(file_path('gurr.png')) new_file.content_type.should == @sanitized_file.content_type end end end shared_examples_for "all valid sanitized files that are stored on disk" do describe '#move_to' do it "should not raise an error when moved to its own location" do running { @sanitized_file.move_to(@sanitized_file.path) }.should_not raise_error end it "should remove the original file" do original_path = @sanitized_file.path @sanitized_file.move_to(public_path('blah.txt')) File.exist?(original_path).should be_false end end describe '#copy_to' do it "should return a new instance when copied to its own location" do running { new_file = @sanitized_file.copy_to(@sanitized_file.path) new_file.should be_an_instance_of(@sanitized_file.class) }.should_not raise_error end it "should not remove the original file" do new_file = @sanitized_file.copy_to(public_path('blah.txt')) File.exist?(@sanitized_file.path).should be_true File.exist?(new_file.path).should be_true end end describe '#exists?' do it "should be true" do @sanitized_file.exists?.should be_true end end describe '#delete' do it "should remove it from the filesystem" do File.exists?(@sanitized_file.path).should be_true @sanitized_file.delete File.exists?(@sanitized_file.path).should be_false end end describe '#to_file' do it "should return a File object" do @sanitized_file.to_file.should be_a(File) end it "should have the same path as the SanitizedFile" do @sanitized_file.to_file.path.should == @sanitized_file.path end it "should have the same contents as the SantizedFile" do @sanitized_file.to_file.read.should == @sanitized_file.read end end end shared_examples_for "all valid sanitized files that are read from an IO object" do describe '#read' do it "should have an open IO object" do @sanitized_file.instance_variable_get(:@file).closed?.should be_false end it "should close the IO object after reading" do @sanitized_file.read @sanitized_file.instance_variable_get(:@file).closed?.should be_true end end end describe "with a valid Hash" do before do @hash = { "tempfile" => stub_merb_tempfile('llama.jpg'), "filename" => "llama.jpg", "content_type" => 'image/jpeg' } @sanitized_file = CarrierWave::SanitizedFile.new(@hash) end it_should_behave_like "all valid sanitized files" it_should_behave_like "all valid sanitized files that are stored on disk" it_should_behave_like "all valid sanitized files that are read from an IO object" describe '#path' do it "should return the path of the tempfile" do @sanitized_file.path.should_not be_nil @sanitized_file.path.should == @hash["tempfile"].path end end describe '#is_path?' do it "should be false" do @sanitized_file.is_path?.should be_false end end end describe "with a valid Tempfile" do before do @tempfile = stub_tempfile('llama.jpg', 'image/jpeg') @sanitized_file = CarrierWave::SanitizedFile.new(@tempfile) end it_should_behave_like "all valid sanitized files" it_should_behave_like "all valid sanitized files that are stored on disk" it_should_behave_like "all valid sanitized files that are read from an IO object" describe '#is_path?' do it "should be false" do @sanitized_file.is_path?.should be_false end end describe '#path' do it "should return the path of the tempfile" do @sanitized_file.path.should_not be_nil @sanitized_file.path.should == @tempfile.path end end end describe "with a valid StringIO" do before do @sanitized_file = CarrierWave::SanitizedFile.new(stub_stringio('llama.jpg', 'image/jpeg')) end it_should_behave_like "all valid sanitized files" it_should_behave_like "all valid sanitized files that are read from an IO object" describe '#exists?' do it "should be false" do @sanitized_file.exists?.should be_false end end describe '#is_path?' do it "should be false" do @sanitized_file.is_path?.should be_false end end describe '#path' do it "should be nil" do @sanitized_file.path.should be_nil end end describe '#delete' do it "should not raise an error" do running { @sanitized_file.delete }.should_not raise_error end end describe '#to_file' do it "should be nil" do @sanitized_file.to_file.should be_nil end end end describe "with a valid File object" do before do FileUtils.cp(file_path('test.jpg'), file_path('llama.jpg')) @sanitized_file = CarrierWave::SanitizedFile.new(stub_file('llama.jpg', 'image/jpeg')) @sanitized_file.should_not be_empty end it_should_behave_like "all valid sanitized files" it_should_behave_like "all valid sanitized files that are stored on disk" it_should_behave_like "all valid sanitized files that are read from an IO object" describe '#is_path?' do it "should be false" do @sanitized_file.is_path?.should be_false end end describe '#path' do it "should return the path of the file" do @sanitized_file.path.should_not be_nil @sanitized_file.path.should == file_path('llama.jpg') end end end describe "with a valid File object and an empty file" do before do FileUtils.cp(file_path('test.jpg'), file_path('llama.jpg')) FileUtils.rm file_path('llama.jpg') FileUtils.touch file_path('llama.jpg') @sanitized_file = CarrierWave::SanitizedFile.new(stub_file('llama.jpg', 'image/jpeg')) @sanitized_file.should_not be_empty end it_should_behave_like "all valid sanitized files that are stored on disk" it_should_behave_like "all valid sanitized files that are read from an IO object" describe '#is_path?' do it "should be false" do @sanitized_file.is_path?.should be_false end end describe '#path' do it "should return the path of the file" do @sanitized_file.path.should_not be_nil @sanitized_file.path.should == file_path('llama.jpg') end end end describe "with a valid path" do before do FileUtils.cp(file_path('test.jpg'), file_path('llama.jpg')) @sanitized_file = CarrierWave::SanitizedFile.new(file_path('llama.jpg')) @sanitized_file.should_not be_empty end it_should_behave_like "all valid sanitized files" it_should_behave_like "all valid sanitized files that are stored on disk" describe '#is_path?' do it "should be true" do @sanitized_file.is_path?.should be_true end end describe '#path' do it "should return the path of the file" do @sanitized_file.path.should_not be_nil @sanitized_file.path.should == file_path('llama.jpg') end end end describe "with a valid Pathname" do before do FileUtils.copy_file(file_path('test.jpg'), file_path('llama.jpg')) @sanitized_file = CarrierWave::SanitizedFile.new(Pathname.new(file_path('llama.jpg'))) @sanitized_file.should_not be_empty end it_should_behave_like "all valid sanitized files" it_should_behave_like "all valid sanitized files that are stored on disk" describe '#is_path?' do it "should be true" do @sanitized_file.is_path?.should be_true end end describe '#path' do it "should return the path of the file" do @sanitized_file.path.should_not be_nil @sanitized_file.path.should == file_path('llama.jpg') end end end describe "that is empty" do before do @empty = CarrierWave::SanitizedFile.new(nil) end describe '#empty?' do it "should be true" do @empty.should be_empty end end describe '#exists?' do it "should be false" do @empty.exists?.should be_false end end describe '#is_path?' do it "should be false" do @empty.is_path?.should be_false end end describe '#size' do it "should be zero" do @empty.size.should be_zero end end describe '#path' do it "should be nil" do @empty.path.should be_nil end end describe '#original_filename' do it "should be nil" do @empty.original_filename.should be_nil end end describe '#filename' do it "should be nil" do @empty.filename.should be_nil end end describe '#basename' do it "should be nil" do @empty.basename.should be_nil end end describe '#extension' do it "should be nil" do @empty.extension.should be_nil end end describe '#delete' do it "should not raise an error" do running { @empty.delete }.should_not raise_error end end describe '#to_file' do it "should be nil" do @empty.to_file.should be_nil end end end describe "that is an empty string" do before do @empty = CarrierWave::SanitizedFile.new("") end describe '#empty?' do it "should be true" do @empty.should be_empty end end describe '#exists?' do it "should be false" do @empty.exists?.should be_false end end describe '#is_path?' do it "should be false" do @empty.is_path?.should be_false end end describe '#size' do it "should be zero" do @empty.size.should be_zero end end describe '#path' do it "should be nil" do @empty.path.should be_nil end end describe '#original_filename' do it "should be nil" do @empty.original_filename.should be_nil end end describe '#filename' do it "should be nil" do @empty.filename.should be_nil end end describe '#basename' do it "should be nil" do @empty.basename.should be_nil end end describe '#extension' do it "should be nil" do @empty.extension.should be_nil end end describe '#delete' do it "should not raise an error" do running { @empty.delete }.should_not raise_error end end describe '#to_file' do it "should be nil" do @empty.to_file.should be_nil end end end end carrierwave-0.10.0/spec/spec_helper.rb000066400000000000000000000063131230347170300176550ustar00rootroot00000000000000# encoding: utf-8 require 'rubygems' require 'bundler/setup' require 'tempfile' require 'time' require 'logger' require 'carrierwave' require 'timecop' require 'open-uri' require 'sham_rack' require 'mini_magick' require 'generator_spec' require 'mysql2' require 'fog' require 'storage/fog_helper' unless ENV['REMOTE'] == 'true' Fog.mock! end require 'fog_credentials' # after Fog.mock! CARRIERWAVE_DIRECTORY = "carrierwave#{Time.now.to_i}" unless defined?(CARRIERWAVE_DIRECTORY) alias :running :lambda def file_path( *paths ) File.expand_path(File.join(File.dirname(__FILE__), 'fixtures', *paths)) end def public_path( *paths ) File.expand_path(File.join(File.dirname(__FILE__), 'public', *paths)) end CarrierWave.root = public_path I18n.load_path << File.expand_path(File.join(File.dirname(__FILE__), "..", "lib", "carrierwave", "locale", 'en.yml')) module CarrierWave module Test module MockStorage def mock_storage(kind) storage = mock("storage for #{kind} uploader") storage.stub!(:setup!) storage end end module MockFiles def stub_merb_tempfile(filename) raise "#{path} file does not exist" unless File.exist?(file_path(filename)) t = Tempfile.new(filename) FileUtils.copy_file(file_path(filename), t.path) return t end def stub_tempfile(filename, mime_type=nil, fake_name=nil) raise "#{path} file does not exist" unless File.exist?(file_path(filename)) t = Tempfile.new(filename) FileUtils.copy_file(file_path(filename), t.path) # This is stupid, but for some reason rspec won't play nice... eval <<-EOF def t.original_filename; '#{fake_name || filename}'; end def t.content_type; '#{mime_type}'; end def t.local_path; path; end EOF return t end def stub_stringio(filename, mime_type=nil, fake_name=nil) if filename t = StringIO.new( IO.read( file_path( filename ) ) ) else t = StringIO.new end t.stub!(:local_path => "", :original_filename => filename || fake_name, :content_type => mime_type) return t end def stub_file(filename, mime_type=nil, fake_name=nil) f = File.open(file_path(filename)) return f end end module I18nHelpers def change_locale_and_store_translations(locale, translations, &block) current_locale = I18n.locale begin I18n.backend.store_translations locale, translations I18n.locale = locale yield ensure I18n.reload! I18n.locale = current_locale end end end module ManipulationHelpers def color_of_pixel(path, x, y) image = ::MiniMagick::Image.open(path) color = image.run_command("convert", "#{image.path}[1x1+#{x}+#{y}]", "-depth", "8", "txt:").split("\n")[1] end end end end RSpec.configure do |config| config.include CarrierWave::Test::Matchers config.include CarrierWave::Test::MockFiles config.include CarrierWave::Test::MockStorage config.include CarrierWave::Test::I18nHelpers config.include CarrierWave::Test::ManipulationHelpers end carrierwave-0.10.0/spec/storage/000077500000000000000000000000001230347170300165005ustar00rootroot00000000000000carrierwave-0.10.0/spec/storage/fog_helper.rb000066400000000000000000000252531230347170300211460ustar00rootroot00000000000000def fog_tests(fog_credentials) describe CarrierWave::Storage::Fog do describe fog_credentials[:provider] do shared_examples_for "#{fog_credentials[:provider]} storage" do before do CarrierWave.configure do |config| config.reset_config config.fog_attributes = {} config.fog_credentials = fog_credentials config.fog_directory = CARRIERWAVE_DIRECTORY config.fog_public = true config.fog_use_ssl_for_aws = true end eval <<-RUBY class FogSpec#{fog_credentials[:provider]}Uploader < CarrierWave::Uploader::Base storage :fog end RUBY @provider = fog_credentials[:provider] # @uploader = FogSpecUploader.new @uploader = eval("FogSpec#{@provider}Uploader") @uploader.stub!(:store_path).and_return('uploads/test.jpg') @storage = CarrierWave::Storage::Fog.new(@uploader) @directory = @storage.connection.directories.get(CARRIERWAVE_DIRECTORY) || @storage.connection.directories.create(:key => CARRIERWAVE_DIRECTORY, :public => true) end describe '#cache_stored_file!' do it "should cache_stored_file! after store!" do uploader = @uploader.new uploader.store!(@file) lambda { uploader.cache_stored_file! }.should_not raise_error end end describe '#store!' do let(:store_path) { 'uploads/test+.jpg' } before do @uploader.stub!(:store_path).and_return(store_path) @fog_file = @storage.store!(@file) end it "should upload the file" do @directory.files.get(store_path).body.should == 'this is stuff' end it "should have a path" do @fog_file.path.should == store_path end it "should have a content_type" do @fog_file.content_type.should == 'image/jpeg' @directory.files.get(store_path).content_type.should == 'image/jpeg' end it "should have an extension" do @fog_file.extension.should == "jpg" end context "without asset_host" do it "should have a public_url" do unless fog_credentials[:provider] == 'Local' @fog_file.public_url.should_not be_nil end end it "should have a url" do unless fog_credentials[:provider] == 'Local' @fog_file.url.should_not be_nil end end it "should use a subdomain URL for AWS if the directory is a valid subdomain" do if @provider == 'AWS' @uploader.stub(:fog_directory).and_return('assets.site.com') @fog_file.public_url.should include('https://assets.site.com.s3.amazonaws.com') end end it "should not use a subdomain URL for AWS if the directory is not a valid subdomain" do if @provider == 'AWS' @uploader.stub(:fog_directory).and_return('SiteAssets') @fog_file.public_url.should include('https://s3.amazonaws.com/SiteAssets') end end it "should use https as a default protocol" do if @provider == 'AWS' @fog_file.public_url.should start_with 'https://' end end it "should use https as a default protocol" do if @provider == 'AWS' @uploader.stub(:fog_use_ssl_for_aws).and_return(false) @fog_file.public_url.should start_with 'http://' end end end context "with asset_host" do before { @uploader.stub(:asset_host).and_return(asset_host) } context "when a asset_host is a proc" do let(:asset_host) { proc { "http://foo.bar" } } describe "args passed to proc" do let(:asset_host) { proc { |storage| storage.should be_instance_of ::CarrierWave::Storage::Fog::File } } it "should be the uploader" do @fog_file.public_url end end it "should have a asset_host rooted public_url" do @fog_file.public_url.should == 'http://foo.bar/uploads/test%2B.jpg' end it "should have a asset_host rooted url" do @fog_file.url.should == 'http://foo.bar/uploads/test%2B.jpg' end it "should always have the same asset_host rooted url" do @fog_file.url.should == 'http://foo.bar/uploads/test%2B.jpg' @fog_file.url.should == 'http://foo.bar/uploads/test%2B.jpg' end it 'should retrieve file name' do @fog_file.filename.should == 'test+.jpg' end end context "when a string" do let(:asset_host) { "http://foo.bar" } it "should have a asset_host rooted public_url" do @fog_file.public_url.should == 'http://foo.bar/uploads/test%2B.jpg' end it "should have a asset_host rooted url" do @fog_file.url.should == 'http://foo.bar/uploads/test%2B.jpg' end it "should always have the same asset_host rooted url" do @fog_file.url.should == 'http://foo.bar/uploads/test%2B.jpg' @fog_file.url.should == 'http://foo.bar/uploads/test%2B.jpg' end end end context "without extension" do let(:store_path) { 'uploads/test' } it "should have no extension" do @fog_file.extension.should be_nil end end it "should return filesize" do @fog_file.size.should == 13 end it "should be deletable" do @fog_file.delete @directory.files.head(store_path).should == nil end end describe '#retrieve!' do before do @directory.files.create(:key => 'uploads/test.jpg', :body => 'A test, 1234', :public => true) @uploader.stub!(:store_path).with('test.jpg').and_return('uploads/test.jpg') @fog_file = @storage.retrieve!('test.jpg') end it "should retrieve the file contents" do @fog_file.read.chomp.should == "A test, 1234" end it "should have a path" do @fog_file.path.should == 'uploads/test.jpg' end it "should have a public url" do unless fog_credentials[:provider] == 'Local' @fog_file.public_url.should_not be_nil end end it "should return filesize" do @fog_file.size.should == 12 end it "should be deletable" do @fog_file.delete @directory.files.head('uploads/test.jpg').should == nil end end describe 'fog_public' do context "true" do before do directory_key = "#{CARRIERWAVE_DIRECTORY}public" @directory = @storage.connection.directories.create(:key => directory_key, :public => true) @uploader.stub!(:fog_directory).and_return(directory_key) @uploader.stub!(:store_path).and_return('uploads/public.txt') @fog_file = @storage.store!(@file) end after do @directory.files.new(:key => 'uploads/public.txt').destroy @directory.destroy end it "should be available at public URL" do unless Fog.mocking? || fog_credentials[:provider] == 'Local' open(@fog_file.public_url).read.should == 'this is stuff' end end end context "false" do before do directory_key = "#{CARRIERWAVE_DIRECTORY}private" @directory = @storage.connection.directories.create(:key => directory_key, :public => true) @uploader.stub!(:fog_directory).and_return(directory_key) @uploader.stub!(:fog_public).and_return(false) @uploader.stub!(:store_path).and_return('uploads/private.txt') @fog_file = @storage.store!(@file) end after do @directory.files.new(:key => 'uploads/private.txt').destroy @directory.destroy end it "should have an authenticated_url" do if ['AWS', 'Rackspace', 'Google', 'OpenStack'].include?(@provider) @fog_file.authenticated_url.should_not be_nil end end it 'should generate correct filename' do @fog_file.filename.should == 'private.txt' end it "should handle query params" do if @provider == 'AWS' && !Fog.mocking? headers = Excon.get(@fog_file.url(:query => {"response-content-disposition" => "attachment"})).headers headers["Content-Disposition"].should == "attachment" end end end end end end describe "with a valid Hash" do before do @file = CarrierWave::SanitizedFile.new( :tempfile => stub_merb_tempfile('test.jpg'), :filename => 'test.jpg', :content_type => 'image/jpeg' ) end it_should_behave_like "#{fog_credentials[:provider]} storage" end describe "with a valid Tempfile" do before do @file = CarrierWave::SanitizedFile.new(stub_tempfile('test.jpg', 'image/jpeg')) end it_should_behave_like "#{fog_credentials[:provider]} storage" end describe "with a valid StringIO" do before do @file = CarrierWave::SanitizedFile.new(stub_stringio('test.jpg', 'image/jpeg')) end it_should_behave_like "#{fog_credentials[:provider]} storage" end describe "with a valid File object" do before do @file = CarrierWave::SanitizedFile.new(stub_file('test.jpg', 'image/jpeg')) @file.should_not be_empty end it_should_behave_like "#{fog_credentials[:provider]} storage" end describe "with a valid path" do before do @file = CarrierWave::SanitizedFile.new(file_path('test.jpg')) @file.should_not be_empty end it_should_behave_like "#{fog_credentials[:provider]} storage" end describe "with a valid Pathname" do before do @file = CarrierWave::SanitizedFile.new(Pathname.new(file_path('test.jpg'))) @file.should_not be_empty end it_should_behave_like "#{fog_credentials[:provider]} storage" end end end carrierwave-0.10.0/spec/storage/fog_spec.rb000066400000000000000000000001501230347170300206060ustar00rootroot00000000000000# encoding: utf-8 require 'spec_helper' for credential in FOG_CREDENTIALS fog_tests(credential) end carrierwave-0.10.0/spec/support/000077500000000000000000000000001230347170300165505ustar00rootroot00000000000000carrierwave-0.10.0/spec/support/activerecord.rb000066400000000000000000000011111230347170300215410ustar00rootroot00000000000000require 'mysql2' require 'active_record' require 'carrierwave/orm/activerecord' # Change this if MySQL is unavailable dbconfig = { :adapter => 'mysql2', :database => 'carrierwave_test', :username => 'root', :encoding => 'utf8' } database = dbconfig.delete(:database) ActiveRecord::Base.establish_connection(dbconfig) begin ActiveRecord::Base.connection.create_database database rescue ActiveRecord::StatementInvalid => e # database already exists end ActiveRecord::Base.establish_connection(dbconfig.merge(:database => database)) ActiveRecord::Migration.verbose = false carrierwave-0.10.0/spec/uploader/000077500000000000000000000000001230347170300166475ustar00rootroot00000000000000carrierwave-0.10.0/spec/uploader/cache_spec.rb000066400000000000000000000246761230347170300212700ustar00rootroot00000000000000# encoding: utf-8 require 'spec_helper' describe CarrierWave::Uploader do before do FileUtils.rm_rf(public_path) @uploader_class = Class.new(CarrierWave::Uploader::Base) @uploader = @uploader_class.new end after do FileUtils.rm_rf(public_path) end describe '.clean_cached_files!' do before do five_days_ago_int = 1369894322 three_days_ago_int = 1370067122 yesterday_int = 1370239922 now_int = 1369894322 @cache_dir = File.expand_path(@uploader_class.cache_dir, CarrierWave.root) FileUtils.mkdir_p File.expand_path("#{five_days_ago_int}-234-2213", @cache_dir) FileUtils.mkdir_p File.expand_path("#{three_days_ago_int}-234-2213", @cache_dir) FileUtils.mkdir_p File.expand_path("#{yesterday_int}-234-2213", @cache_dir) end after { FileUtils.rm_rf(@cache_dir) } it "should clear all files older than, by defaul, 24 hours in the default cache directory" do Timecop.freeze(Time.at(1370261522)) do @uploader_class.clean_cached_files! end Dir.glob("#{@cache_dir}/*").size.should == 1 end it "should permit to set since how many seconds delete the cached files" do Timecop.freeze(Time.at(1370261522)) do @uploader_class.clean_cached_files!(60*60*24*4) end Dir.glob("#{@cache_dir}/*").size.should == 2 end it "should be aliased on the CarrierWave module" do Timecop.freeze(Time.at(1370261522)) do CarrierWave.clean_cached_files! end Dir.glob("#{@cache_dir}/*").size.should == 1 end end describe '#cache_dir' do it "should default to the config option" do @uploader.cache_dir.should == 'uploads/tmp' end end describe '#cache!' do before do CarrierWave.stub!(:generate_cache_id).and_return('1369894322-345-2255') end it "should cache a file" do @uploader.cache!(File.open(file_path('test.jpg'))) @uploader.file.should be_an_instance_of(CarrierWave::SanitizedFile) end it "should be cached" do @uploader.cache!(File.open(file_path('test.jpg'))) @uploader.should be_cached end it "should store the cache name" do @uploader.cache!(File.open(file_path('test.jpg'))) @uploader.cache_name.should == '1369894322-345-2255/test.jpg' end it "should set the filename to the file's sanitized filename" do @uploader.cache!(File.open(file_path('test.jpg'))) @uploader.filename.should == 'test.jpg' end it "should move it to the tmp dir" do @uploader.cache!(File.open(file_path('test.jpg'))) @uploader.file.path.should == public_path('uploads/tmp/1369894322-345-2255/test.jpg') @uploader.file.exists?.should be_true end it "should set the url" do @uploader.cache!(File.open(file_path('test.jpg'))) @uploader.url.should == '/uploads/tmp/1369894322-345-2255/test.jpg' end it "should raise an error when trying to cache a string" do running { @uploader.cache!(file_path('test.jpg')) }.should raise_error(CarrierWave::FormNotMultipart) end it "should raise an error when trying to cache a pathname" do running { @uploader.cache!(Pathname.new(file_path('test.jpg'))) }.should raise_error(CarrierWave::FormNotMultipart) end it "should do nothing when trying to cache an empty file" do @uploader.cache!(nil) end it "should set permissions if options are given" do @uploader_class.permissions = 0777 @uploader.cache!(File.open(file_path('test.jpg'))) @uploader.should have_permissions(0777) end it "should set directory permissions if options are given" do @uploader_class.directory_permissions = 0777 @uploader.cache!(File.open(file_path('test.jpg'))) @uploader.should have_directory_permissions(0777) end describe "with ensuring multipart form deactivated" do before do CarrierWave.configure do |config| config.ensure_multipart_form = false end end it "should not raise an error when trying to cache a string" do running { @uploader.cache!(file_path('test.jpg')) }.should_not raise_error(CarrierWave::FormNotMultipart) end it "should raise an error when trying to cache a pathname and " do running { @uploader.cache!(Pathname.new(file_path('test.jpg'))) }.should_not raise_error(CarrierWave::FormNotMultipart) end end describe "with the move_to_cache option" do before do ## make a copy file = file_path('test.jpg') tmpfile = file_path("test_move.jpeg") FileUtils.rm_f(tmpfile) FileUtils.cp(file, File.join(File.dirname(file), "test_move.jpeg")) @tmpfile = File.open(tmpfile) ## stub CarrierWave.stub!(:generate_cache_id).and_return('1369894322-345-2255') @cached_path = public_path('uploads/tmp/1369894322-345-2255/test_move.jpeg') @uploader_class.permissions = 0777 @uploader_class.directory_permissions = 0777 end after do FileUtils.rm_f(@tmpfile.path) end context "set to true" do before do @uploader_class.move_to_cache = true end it "should move it from the upload dir to the tmp dir" do original_path = @tmpfile.path @uploader.cache!(@tmpfile) @uploader.file.path.should == @cached_path File.exist?(@cached_path).should be_true File.exist?(original_path).should be_false end it "should use move_to() during cache!()" do CarrierWave::SanitizedFile.any_instance.should_receive(:move_to).with(@cached_path, 0777, 0777) @uploader.cache!(@tmpfile) end it "should not use copy_to() during cache!()" do CarrierWave::SanitizedFile.any_instance.should_not_receive(:copy_to) @uploader.cache!(@tmpfile) end end context "set to false" do before do @uploader_class.move_to_cache = false end it "should copy it from the upload dir to the tmp dir" do original_path = @tmpfile.path @uploader.cache!(@tmpfile) @uploader.file.path.should == @cached_path File.exist?(@cached_path).should be_true File.exist?(original_path).should be_true end it "should use copy_to() during cache!()" do CarrierWave::SanitizedFile.any_instance.should_receive(:copy_to).with(@cached_path, 0777, 0777) @uploader.cache!(@tmpfile) end it "should not use move_to() during cache!()" do CarrierWave::SanitizedFile.any_instance.should_not_receive(:move_to) @uploader.cache!(@tmpfile) end end end end describe '#retrieve_from_cache!' do it "should cache a file" do @uploader.retrieve_from_cache!('1369894322-345-2255/test.jpeg') @uploader.file.should be_an_instance_of(CarrierWave::SanitizedFile) end it "should be cached" do @uploader.retrieve_from_cache!('1369894322-345-2255/test.jpeg') @uploader.should be_cached end it "should set the path to the tmp dir" do @uploader.retrieve_from_cache!('1369894322-345-2255/test.jpeg') @uploader.current_path.should == public_path('uploads/tmp/1369894322-345-2255/test.jpeg') end it "should overwrite a file that has already been cached" do @uploader.retrieve_from_cache!('1369894322-345-2255/test.jpeg') @uploader.retrieve_from_cache!('1369894322-345-2255/bork.txt') @uploader.current_path.should == public_path('uploads/tmp/1369894322-345-2255/bork.txt') end it "should store the cache_name" do @uploader.retrieve_from_cache!('1369894322-345-2255/test.jpeg') @uploader.cache_name.should == '1369894322-345-2255/test.jpeg' end it "should store the filename" do @uploader.retrieve_from_cache!('1369894322-345-2255/test.jpeg') @uploader.filename.should == 'test.jpeg' end it "should set the url" do @uploader.retrieve_from_cache!('1369894322-345-2255/test.jpeg') @uploader.url.should == '/uploads/tmp/1369894322-345-2255/test.jpeg' end it "should raise an error when the cache_id has an invalid format" do running { @uploader.retrieve_from_cache!('12345/test.jpeg') }.should raise_error(CarrierWave::InvalidParameter) @uploader.file.should be_nil @uploader.filename.should be_nil @uploader.cache_name.should be_nil end it "should raise an error when the original_filename contains invalid characters" do running { @uploader.retrieve_from_cache!('1369894322-345-2255/te/st.jpeg') }.should raise_error(CarrierWave::InvalidParameter) running { @uploader.retrieve_from_cache!('1369894322-345-2255/te??%st.jpeg') }.should raise_error(CarrierWave::InvalidParameter) @uploader.file.should be_nil @uploader.filename.should be_nil @uploader.cache_name.should be_nil end end describe 'with an overridden, reversing, filename' do before do @uploader_class.class_eval do def filename super.reverse unless super.blank? end end end describe '#cache!' do before do CarrierWave.stub!(:generate_cache_id).and_return('1369894322-345-2255') end it "should set the filename to the file's reversed filename" do @uploader.cache!(File.open(file_path('test.jpg'))) @uploader.filename.should == "gpj.tset" end it "should move it to the tmp dir with the filename unreversed" do @uploader.cache!(File.open(file_path('test.jpg'))) @uploader.current_path.should == public_path('uploads/tmp/1369894322-345-2255/test.jpg') @uploader.file.exists?.should be_true end end describe '#retrieve_from_cache!' do it "should set the path to the tmp dir" do @uploader.retrieve_from_cache!('1369894322-345-2255/test.jpg') @uploader.current_path.should == public_path('uploads/tmp/1369894322-345-2255/test.jpg') end it "should set the filename to the reversed name of the file" do @uploader.retrieve_from_cache!('1369894322-345-2255/test.jpg') @uploader.filename.should == "gpj.tset" end end end describe '.generate_cache_id' do it 'should generate dir name bsed on UTC time' do Timecop.travel(Time.at(1369896000)) do CarrierWave.generate_cache_id.should match(/\A1369896000-\d+-\d+\Z/) end end end end carrierwave-0.10.0/spec/uploader/callback_spec.rb000066400000000000000000000014771230347170300217530ustar00rootroot00000000000000# encoding: utf-8 require 'spec_helper' describe CarrierWave::Uploader do it "should keep callbacks on different classes isolated" do @uploader_class_1 = Class.new(CarrierWave::Uploader::Base) # First Uploader only has default before-callback @uploader_class_1._before_callbacks[:cache].should == [:check_whitelist!, :check_blacklist!] @uploader_class_2 = Class.new(CarrierWave::Uploader::Base) @uploader_class_2.before :cache, :before_cache_callback # Second Uploader defined with another callback @uploader_class_2._before_callbacks[:cache].should == [:check_whitelist!, :check_blacklist!, :before_cache_callback] # Make sure the first Uploader doesn't inherit the same callback @uploader_class_1._before_callbacks[:cache].should == [:check_whitelist!, :check_blacklist!] end end carrierwave-0.10.0/spec/uploader/configuration_spec.rb000066400000000000000000000070751230347170300230660ustar00rootroot00000000000000# encoding: utf-8 require 'spec_helper' describe CarrierWave do before do @uploader_class = Class.new(CarrierWave::Uploader::Base) end describe '.configure' do it "should proxy to Uploader configuration" do CarrierWave::Uploader::Base.add_config :test_config CarrierWave.configure do |config| config.test_config = "foo" end CarrierWave::Uploader::Base.test_config.should == 'foo' end end end describe CarrierWave::Uploader::Base do before do @uploader_class = Class.new(CarrierWave::Uploader::Base) end describe '.configure' do it "should set a configuration parameter" do @uploader_class.add_config :foo_bar @uploader_class.configure do |config| config.foo_bar = "monkey" end @uploader_class.foo_bar.should == 'monkey' end end describe ".storage" do it "should set the storage if an argument is given" do storage = mock('some kind of storage') @uploader_class.storage storage @uploader_class.storage.should == storage end it "should default to file" do @uploader_class.storage.should == CarrierWave::Storage::File end it "should set the storage from the configured shortcuts if a symbol is given" do @uploader_class.storage :file @uploader_class.storage.should == CarrierWave::Storage::File end it "should remember the storage when inherited" do @uploader_class.storage :fog subclass = Class.new(@uploader_class) subclass.storage.should == CarrierWave::Storage::Fog end it "should be changeable when inherited" do @uploader_class.storage :fog subclass = Class.new(@uploader_class) subclass.storage.should == CarrierWave::Storage::Fog subclass.storage :file subclass.storage.should == CarrierWave::Storage::File end end describe '.add_config' do it "should add a class level accessor" do @uploader_class.add_config :foo_bar @uploader_class.foo_bar = 'foo' @uploader_class.foo_bar.should == 'foo' end ['foo', :foo, 45, ['foo', :bar]].each do |val| it "should be inheritable for a #{val.class}" do @uploader_class.add_config :foo_bar @child_class = Class.new(@uploader_class) @uploader_class.foo_bar = val @uploader_class.foo_bar.should == val @child_class.foo_bar.should == val @child_class.foo_bar = "bar" @child_class.foo_bar.should == "bar" @uploader_class.foo_bar.should == val end end it "should add an instance level accessor" do @uploader_class.add_config :foo_bar @uploader_class.foo_bar = 'foo' @uploader_class.new.foo_bar.should == 'foo' end it "should add a convenient in-class setter" do @uploader_class.add_config :foo_bar @uploader_class.foo_bar "monkey" @uploader_class.foo_bar.should == "monkey" end describe "assigning a proc to a config attribute" do before(:each) do @uploader_class.add_config :hoobatz @uploader_class.hoobatz = this_proc end context "when the proc accepts no arguments" do let(:this_proc) { proc { "a return value" } } it "calls the proc without arguments" do @uploader_class.new.hoobatz.should == "a return value" end end context "when the proc accepts one argument" do let(:this_proc) { proc { |arg1| arg1.should be_an_instance_of(@uploader_class) } } it "calls the proc with an instance of the uploader" do @uploader_class.new.hoobatz end end end end end carrierwave-0.10.0/spec/uploader/default_url_spec.rb000066400000000000000000000041311230347170300225130ustar00rootroot00000000000000# encoding: utf-8 require 'spec_helper' describe CarrierWave::Uploader do before do @uploader_class = Class.new(CarrierWave::Uploader::Base) @uploader = @uploader_class.new end after do FileUtils.rm_rf(public_path) end describe 'with a default url' do before do @uploader_class.class_eval do version :thumb def default_url ["http://someurl.example.com", version_name].compact.join('/') end end @uploader = @uploader_class.new end describe '#blank?' do it "should be true by default" do @uploader.should be_blank end end describe '#current_path' do it "should return nil" do @uploader.current_path.should be_nil end end describe '#url' do it "should return the default url" do @uploader.url.should == 'http://someurl.example.com' end it "should return the default url with version when given" do @uploader.url(:thumb).should == 'http://someurl.example.com/thumb' end end describe '#cache!' do before do CarrierWave.stub!(:generate_cache_id).and_return('1369894322-345-2255') end it "should cache a file" do @uploader.cache!(File.open(file_path('test.jpg'))) @uploader.file.should be_an_instance_of(CarrierWave::SanitizedFile) end it "should be cached" do @uploader.cache!(File.open(file_path('test.jpg'))) @uploader.should be_cached end it "should no longer be blank" do @uploader.cache!(File.open(file_path('test.jpg'))) @uploader.should_not be_blank end it "should set the current_path" do @uploader.cache!(File.open(file_path('test.jpg'))) @uploader.current_path.should == public_path('uploads/tmp/1369894322-345-2255/test.jpg') end it "should set the url" do @uploader.cache!(File.open(file_path('test.jpg'))) @uploader.url.should_not == 'http://someurl.example.com' @uploader.url.should == '/uploads/tmp/1369894322-345-2255/test.jpg' end end end end carrierwave-0.10.0/spec/uploader/download_spec.rb000066400000000000000000000163151230347170300220230ustar00rootroot00000000000000# encoding: utf-8 require 'spec_helper' describe CarrierWave::Uploader::Download do before do @uploader_class = Class.new(CarrierWave::Uploader::Base) @uploader = @uploader_class.new end after do FileUtils.rm_rf(public_path) end describe '#download!' do before do CarrierWave.stub!(:generate_cache_id).and_return('1369894322-345-2255') sham_rack_app = ShamRack.at('www.example.com').stub sham_rack_app.register_resource('/test.jpg', File.read(file_path('test.jpg')), 'image/jpg') sham_rack_app.register_resource('/test-with-no-extension/test', File.read(file_path('test.jpg')), 'image/jpeg') sham_rack_app.register_resource('/test%20with%20spaces/test.jpg', File.read(file_path('test.jpg')), 'image/jpg') sham_rack_app.handle do |request| if request.path_info == '/content-disposition' ["200 OK", {'Content-Type'=>'image/jpg', 'Content-Disposition'=>'filename="another_test.jpg"'}, [File.read(file_path('test.jpg'))]] end end ShamRack.at("www.redirect.com") do |env| [301, {'Content-Type'=>'text/html', 'Location'=>"http://www.example.com/test.jpg"}, ['Redirecting']] end end after do ShamRack.unmount_all end it "should cache a file" do @uploader.download!('http://www.example.com/test.jpg') @uploader.file.should be_an_instance_of(CarrierWave::SanitizedFile) end it "should be cached" do @uploader.download!('http://www.example.com/test.jpg') @uploader.should be_cached end it "should store the cache name" do @uploader.download!('http://www.example.com/test.jpg') @uploader.cache_name.should == '1369894322-345-2255/test.jpg' end it "should set the filename to the file's sanitized filename" do @uploader.download!('http://www.example.com/test.jpg') @uploader.filename.should == 'test.jpg' end it "should move it to the tmp dir" do @uploader.download!('http://www.example.com/test.jpg') @uploader.file.path.should == public_path('uploads/tmp/1369894322-345-2255/test.jpg') @uploader.file.exists?.should be_true end it "should set the url" do @uploader.download!('http://www.example.com/test.jpg') @uploader.url.should == '/uploads/tmp/1369894322-345-2255/test.jpg' end it "should set permissions if options are given" do @uploader_class.permissions = 0777 @uploader.download!('http://www.example.com/test.jpg') @uploader.should have_permissions(0777) end it "should set directory permissions if options are given" do @uploader_class.directory_permissions = 0777 @uploader.download!('http://www.example.com/test.jpg') @uploader.should have_directory_permissions(0777) end it "should raise an error when trying to download a local file" do running { @uploader.download!('/etc/passwd') }.should raise_error(CarrierWave::DownloadError) end it "should raise an error when trying to download a missing file" do running { @uploader.download!('http://www.example.com/missing.jpg') }.should raise_error(CarrierWave::DownloadError) end it "should accept spaces in the url" do @uploader.download!('http://www.example.com/test with spaces/test.jpg') @uploader.url.should == '/uploads/tmp/1369894322-345-2255/test.jpg' end it "should follow redirects" do @uploader.download!('http://www.redirect.com/') @uploader.url.should == '/uploads/tmp/1369894322-345-2255/test.jpg' end it "should read content-disposition headers" do @uploader.download!('http://www.example.com/content-disposition') @uploader.url.should == '/uploads/tmp/1369894322-345-2255/another_test.jpg' end it 'should set file extension based on content-type if missing' do @uploader.download!('http://www.example.com/test-with-no-extension/test') @uploader.url.should == '/uploads/tmp/1369894322-345-2255/test.jpeg' end it 'should not obscure original exception message' do expect { @uploader.download!('http://www.example.com/missing.jpg') }.to raise_error(CarrierWave::DownloadError, /could not download file: 404/) end describe '#download! with an extension_white_list' do before do @uploader_class.class_eval do def extension_white_list %w(txt) end end end it "should follow redirects but still respect the extension_white_list" do running { @uploader.download!('http://www.redirect.com/') }.should raise_error(CarrierWave::IntegrityError) end it "should read content-disposition header but still respect the extension_white_list" do running { @uploader.download!('http://www.example.com/content-disposition') }.should raise_error(CarrierWave::IntegrityError) end end describe '#download! with an extension_black_list' do before do @uploader_class.class_eval do def extension_black_list %w(jpg) end end end it "should follow redirects but still respect the extension_black_list" do running { @uploader.download!('http://www.redirect.com/') }.should raise_error(CarrierWave::IntegrityError) end it "should read content-disposition header but still respect the extension_black_list" do running { @uploader.download!('http://www.example.com/content-disposition') }.should raise_error(CarrierWave::IntegrityError) end end end describe '#download! with an overridden process_uri method' do before do @uploader_class.class_eval do def process_uri(uri) raise CarrierWave::DownloadError end end end it "should allow overriding the process_uri method" do running { @uploader.download!('http://www.example.com/test.jpg') }.should raise_error(CarrierWave::DownloadError) end end describe '#process_uri' do it "should parse but not escape already escaped uris" do uri = 'http://example.com/%5B.jpg' processed = @uploader.process_uri(uri) processed.class.should == URI::HTTP processed.to_s.should == uri end it "should parse but not escape uris with query-string-only characters not needing escaping" do uri = 'http://example.com/?foo[]=bar' processed = @uploader.process_uri(uri) processed.class.should == URI::HTTP processed.to_s.should == uri end it "should escape and parse unescaped uris" do uri = 'http://example.com/ %[].jpg' processed = @uploader.process_uri(uri) processed.class.should == URI::HTTP processed.to_s.should == 'http://example.com/%20%25%5B%5D.jpg' end it "should escape and parse brackets in uri paths without harming the query string" do uri = 'http://example.com/].jpg?test[]' processed = @uploader.process_uri(uri) processed.class.should == URI::HTTP processed.to_s.should == 'http://example.com/%5D.jpg?test[]' end it "should throw an exception on bad uris" do uri = '~http:' expect { @uploader.process_uri(uri) }.to raise_error(CarrierWave::DownloadError) end end end carrierwave-0.10.0/spec/uploader/extension_blacklist_spec.rb000066400000000000000000000054601230347170300242570ustar00rootroot00000000000000# encoding: utf-8 require 'spec_helper' describe CarrierWave::Uploader do before do @uploader_class = Class.new(CarrierWave::Uploader::Base) @uploader = @uploader_class.new end after do FileUtils.rm_rf(public_path) end describe '#cache!' do before do CarrierWave.stub!(:generate_cache_id).and_return('1369894322-345-2255') end it "should not raise an integrity error if there is no black list" do @uploader.stub!(:extension_black_list).and_return(nil) running { @uploader.cache!(File.open(file_path('test.jpg'))) }.should_not raise_error(CarrierWave::IntegrityError) end it "should raise an integrity error if there is a black list and the file is on it" do @uploader.stub!(:extension_black_list).and_return(%w(jpg gif png)) running { @uploader.cache!(File.open(file_path('test.jpg'))) }.should raise_error(CarrierWave::IntegrityError) end it "should not raise an integrity error if there is a black list and the file is not on it" do @uploader.stub!(:extension_black_list).and_return(%w(txt doc xls)) running { @uploader.cache!(File.open(file_path('test.jpg'))) }.should_not raise_error(CarrierWave::IntegrityError) end it "should not raise an integrity error if there is a black list and the file is not on it, using start of string matcher" do @uploader.stub!(:extension_black_list).and_return(%w(txt)) running { @uploader.cache!(File.open(file_path('bork.ttxt'))) }.should_not raise_error(CarrierWave::IntegrityError) end it "should not raise an integrity error if there is a black list and the file is not on it, using end of string matcher" do @uploader.stub!(:extension_black_list).and_return(%w(txt)) running { @uploader.cache!(File.open(file_path('bork.txtt'))) }.should_not raise_error(CarrierWave::IntegrityError) end it "should compare black list in a case insensitive manner when capitalized extension provided" do @uploader.stub!(:extension_black_list).and_return(%w(jpg gif png)) running { @uploader.cache!(File.open(file_path('case.JPG'))) }.should raise_error(CarrierWave::IntegrityError) end it "should compare black list in a case insensitive manner when lowercase extension provided" do @uploader.stub!(:extension_black_list).and_return(%w(JPG GIF PNG)) running { @uploader.cache!(File.open(file_path('test.jpg'))) }.should raise_error(CarrierWave::IntegrityError) end it "should accept and check regular expressions" do @uploader.stub!(:extension_black_list).and_return([/jpe?g/, 'gif', 'png']) running { @uploader.cache!(File.open(file_path('test.jpeg'))) }.should raise_error(CarrierWave::IntegrityError) end end end carrierwave-0.10.0/spec/uploader/extension_whitelist_spec.rb000066400000000000000000000054551230347170300243270ustar00rootroot00000000000000# encoding: utf-8 require 'spec_helper' describe CarrierWave::Uploader do before do @uploader_class = Class.new(CarrierWave::Uploader::Base) @uploader = @uploader_class.new end after do FileUtils.rm_rf(public_path) end describe '#cache!' do before do CarrierWave.stub!(:generate_cache_id).and_return('1369894322-345-2255') end it "should not raise an integrity error if there is no white list" do @uploader.stub!(:extension_white_list).and_return(nil) running { @uploader.cache!(File.open(file_path('test.jpg'))) }.should_not raise_error(CarrierWave::IntegrityError) end it "should not raise an integrity error if there is a white list and the file is on it" do @uploader.stub!(:extension_white_list).and_return(%w(jpg gif png)) running { @uploader.cache!(File.open(file_path('test.jpg'))) }.should_not raise_error(CarrierWave::IntegrityError) end it "should raise an integrity error if there is a white list and the file is not on it" do @uploader.stub!(:extension_white_list).and_return(%w(txt doc xls)) running { @uploader.cache!(File.open(file_path('test.jpg'))) }.should raise_error(CarrierWave::IntegrityError) end it "should raise an integrity error if there is a white list and the file is not on it, using start of string matcher" do @uploader.stub!(:extension_white_list).and_return(%w(txt)) running { @uploader.cache!(File.open(file_path('bork.ttxt'))) }.should raise_error(CarrierWave::IntegrityError) end it "should raise an integrity error if there is a white list and the file is not on it, using end of string matcher" do @uploader.stub!(:extension_white_list).and_return(%w(txt)) running { @uploader.cache!(File.open(file_path('bork.txtt'))) }.should raise_error(CarrierWave::IntegrityError) end it "should compare white list in a case insensitive manner when capitalized extension provided" do @uploader.stub!(:extension_white_list).and_return(%w(jpg gif png)) running { @uploader.cache!(File.open(file_path('case.JPG'))) }.should_not raise_error(CarrierWave::IntegrityError) end it "should compare white list in a case insensitive manner when lowercase extension provided" do @uploader.stub!(:extension_white_list).and_return(%w(JPG GIF PNG)) running { @uploader.cache!(File.open(file_path('test.jpg'))) }.should_not raise_error(CarrierWave::IntegrityError) end it "should accept and check regular expressions" do @uploader.stub!(:extension_white_list).and_return([/jpe?g/, 'gif', 'png']) running { @uploader.cache!(File.open(file_path('test.jpeg'))) }.should_not raise_error(CarrierWave::IntegrityError) end end end carrierwave-0.10.0/spec/uploader/mountable_spec.rb000066400000000000000000000013271230347170300221770ustar00rootroot00000000000000# encoding: utf-8 require 'spec_helper' describe CarrierWave::Uploader do before do @uploader_class = Class.new(CarrierWave::Uploader::Base) @uploader = @uploader_class.new end after do FileUtils.rm_rf(public_path) end describe '#model' do it "should be remembered from initialization" do model = mock('a model object') @uploader = @uploader_class.new(model) @uploader.model.should == model end end describe '#mounted_as' do it "should be remembered from initialization" do model = mock('a model object') @uploader = @uploader_class.new(model, :llama) @uploader.model.should == model @uploader.mounted_as.should == :llama end end end carrierwave-0.10.0/spec/uploader/overrides_spec.rb000066400000000000000000000051251230347170300222130ustar00rootroot00000000000000# encoding: utf-8 require 'spec_helper' describe CarrierWave::Uploader do before do @uploader_class = Class.new(CarrierWave::Uploader::Base) @uploader_class.configure do |config| config.fog_credentials = { :provider => 'AWS', # required :aws_access_key_id => 'XXXX', # required :aws_secret_access_key => 'YYYY', # required :region => 'us-east-1' # optional, defaults to 'us-east-1' } config.fog_directory = "defaultbucket" end @uploader = @uploader_class.new @uploader_overridden = @uploader_class.new @uploader_overridden.fog_credentials = { :provider => 'AWS', # required :aws_access_key_id => 'ZZZZ', # required :aws_secret_access_key => 'AAAA', # required :region => 'us-east-2' # optional, defaults to 'us-east-1' } @uploader_overridden.fog_public = false end describe 'fog_credentials' do it 'should reflect the standard value if no override done' do @uploader.fog_credentials.should be_a(Hash) @uploader.fog_credentials[:provider].should be_eql('AWS') @uploader.fog_credentials[:aws_access_key_id].should be_eql('XXXX') @uploader.fog_credentials[:aws_secret_access_key].should be_eql('YYYY') @uploader.fog_credentials[:region].should be_eql('us-east-1') end it 'should reflect the new values in uploader class with override' do @uploader_overridden.fog_credentials.should be_a(Hash) @uploader_overridden.fog_credentials[:provider].should be_eql('AWS') @uploader_overridden.fog_credentials[:aws_access_key_id].should be_eql('ZZZZ') @uploader_overridden.fog_credentials[:aws_secret_access_key].should be_eql('AAAA') @uploader_overridden.fog_credentials[:region].should be_eql('us-east-2') end end describe 'fog_directory' do it 'should reflect the standard value if no override done' do @uploader.fog_directory.should be_eql('defaultbucket') end it 'should reflect the standard value in overridden object because property is not overridden' do @uploader_overridden.fog_directory.should be_eql('defaultbucket') end end describe 'fog_public' do it 'should reflect the standard value if no override done' do @uploader.fog_public.should be_eql(true) end it 'should reflect the standard value in overridden object because property is not overridden' do @uploader_overridden.fog_public.should be_eql(false) end end endcarrierwave-0.10.0/spec/uploader/paths_spec.rb000066400000000000000000000010511230347170300213220ustar00rootroot00000000000000# encoding: utf-8 require 'spec_helper' describe CarrierWave::Uploader do before do @root = CarrierWave.root CarrierWave.root = nil @uploader_class = Class.new(CarrierWave::Uploader::Base) @uploader = @uploader_class.new end after do FileUtils.rm_rf(public_path) CarrierWave.root = @root end describe '#root' do it "should default to the current value of CarrierWave.root" do @uploader.root.should be_nil CarrierWave.root = public_path @uploader.root.should == public_path end end end carrierwave-0.10.0/spec/uploader/processing_spec.rb000066400000000000000000000122441230347170300223650ustar00rootroot00000000000000# encoding: utf-8 require 'spec_helper' describe CarrierWave::Uploader do before do @uploader_class = Class.new(CarrierWave::Uploader::Base) @uploader = @uploader_class.new end after do FileUtils.rm_rf(public_path) end describe '.process' do it "should add a single processor when a symbol is given" do @uploader_class.process :sepiatone @uploader.should_receive(:sepiatone) @uploader.process! end it "should add multiple processors when an array of symbols is given" do @uploader_class.process :sepiatone, :desaturate, :invert @uploader.should_receive(:sepiatone) @uploader.should_receive(:desaturate) @uploader.should_receive(:invert) @uploader.process! end it "should add a single processor with an argument when a hash is given" do @uploader_class.process :format => 'png' @uploader.should_receive(:format).with('png') @uploader.process! end it "should add a single processor with several argument when a hash is given" do @uploader_class.process :resize => [200, 300] @uploader.should_receive(:resize).with(200, 300) @uploader.process! end it "should add multiple processors when an hash with multiple keys is given" do @uploader_class.process :resize => [200, 300], :format => 'png' @uploader.should_receive(:resize).with(200, 300) @uploader.should_receive(:format).with('png') @uploader.process! end it "should call the processor if the condition method returns true" do @uploader_class.process :resize => [200, 300], :if => :true? @uploader_class.process :fancy, :if => :true? @uploader.should_receive(:true?).with("test.jpg").twice.and_return(true) @uploader.should_receive(:resize).with(200, 300) @uploader.should_receive(:fancy).with() @uploader.process!("test.jpg") end it "should not call the processor if the condition method returns false" do @uploader_class.process :resize => [200, 300], :if => :false? @uploader_class.process :fancy, :if => :false? @uploader.should_receive(:false?).with("test.jpg").twice.and_return(false) @uploader.should_not_receive(:resize) @uploader.should_not_receive(:fancy) @uploader.process!("test.jpg") end it "should call the processor if the condition block returns true" do @uploader_class.process :resize => [200, 300], :if => lambda{|record, args| record.true?(args[:file])} @uploader_class.process :fancy, :if => :true? @uploader.should_receive(:true?).with("test.jpg").twice.and_return(true) @uploader.should_receive(:resize).with(200, 300) @uploader.should_receive(:fancy).with() @uploader.process!("test.jpg") end it "should not call the processor if the condition block returns false" do @uploader_class.process :resize => [200, 300], :if => lambda{|record, args| record.false?(args[:file])} @uploader_class.process :fancy, :if => :false? @uploader.should_receive(:false?).with("test.jpg").twice.and_return(false) @uploader.should_not_receive(:resize) @uploader.should_not_receive(:fancy) @uploader.process!("test.jpg") end context "when using RMagick" do before do def @uploader.cover manipulate! { |frame, index| frame if index.zero? } end @uploader_class.send :include, CarrierWave::RMagick end after do @uploader.instance_eval { undef cover } end context "with a multi-page PDF" do before do @uploader.cache! File.open(file_path("multi_page.pdf")) end it "should successfully process" do @uploader_class.process :convert => 'jpg' @uploader.process! end it "should support page specific transformations" do @uploader_class.process :cover @uploader.process! end end context "with a simple image" do before do @uploader.cache! File.open(file_path("portrait.jpg")) end it "should still allow page specific transformations" do @uploader_class.process :cover @uploader.process! end end end context "with 'enable_processing' set to false" do it "should not do any processing" do @uploader_class.enable_processing = false @uploader_class.process :sepiatone, :desaturate, :invert @uploader.should_not_receive(:sepiatone) @uploader.should_not_receive(:desaturate) @uploader.should_not_receive(:invert) @uploader.process! end end end describe '#cache!' do before do CarrierWave.stub!(:generate_cache_id).and_return('1369894322-345-2255') end it "should trigger a process!" do @uploader.should_receive(:process!) @uploader.cache!(File.open(file_path('test.jpg'))) end end describe '#recreate_versions!' do before do CarrierWave.stub!(:generate_cache_id).and_return('1369894322-345-2255') end it "should trigger a process!" do @uploader.store!(File.open(file_path('test.jpg'))) @uploader.should_receive(:process!) @uploader.recreate_versions! end end end carrierwave-0.10.0/spec/uploader/proxy_spec.rb000066400000000000000000000033331230347170300213710ustar00rootroot00000000000000# encoding: utf-8 require 'spec_helper' describe CarrierWave::Uploader do before do @uploader_class = Class.new(CarrierWave::Uploader::Base) @uploader = @uploader_class.new end after do FileUtils.rm_rf(public_path) end describe '#blank?' do it "should be true when nothing has been done" do @uploader.should be_blank end it "should not be true when the file is empty" do @uploader.retrieve_from_cache!('1369894322-345-2255/test.jpeg') @uploader.should be_blank end it "should not be true when a file has been cached" do @uploader.cache!(File.open(file_path('test.jpg'))) @uploader.should_not be_blank end end describe '#read' do it "should be nil by default" do @uploader.read.should be_nil end it "should read the contents of a cached file" do @uploader.cache!(File.open(file_path('test.jpg'))) @uploader.read.should == "this is stuff" end end describe '#size' do it "should be zero by default" do @uploader.size.should == 0 end it "should get the size of a cached file" do @uploader.cache!(File.open(file_path('test.jpg'))) @uploader.size.should == 13 end end describe '#content_type' do it "should be nil when nothing has been done" do @uploader.content_type.should be_nil end it "should get the content type when the file has been cached" do @uploader.cache!(File.open(file_path('test.jpg'))) @uploader.content_type.should == 'image/jpeg' end it "should get the content type when the file is empty" do @uploader.retrieve_from_cache!('1369894322-345-2255/test.jpeg') @uploader.content_type.should == 'image/jpeg' end end end carrierwave-0.10.0/spec/uploader/remove_spec.rb000066400000000000000000000032051230347170300215030ustar00rootroot00000000000000# encoding: utf-8 require 'spec_helper' describe CarrierWave::Uploader do before do @uploader_class = Class.new(CarrierWave::Uploader::Base) @uploader = @uploader_class.new end after do FileUtils.rm_rf(public_path) end describe '#remove!' do before do @file = File.open(file_path('test.jpg')) @stored_file = mock('a stored file') @stored_file.stub!(:path).and_return('/path/to/somewhere') @stored_file.stub!(:url).and_return('http://www.example.com') @stored_file.stub!(:identifier).and_return('this-is-me') @stored_file.stub!(:delete) @storage = mock('a storage engine') @storage.stub!(:store!).and_return(@stored_file) @uploader_class.storage.stub!(:new).and_return(@storage) @uploader.store!(@file) end it "should reset the current path" do @uploader.remove! @uploader.current_path.should be_nil end it "should not be cached" do @uploader.remove! @uploader.should_not be_cached end it "should reset the url" do @uploader.cache!(@file) @uploader.remove! @uploader.url.should be_nil end it "should reset the identifier" do @uploader.remove! @uploader.identifier.should be_nil end it "should delete the file" do @stored_file.should_receive(:delete) @uploader.remove! end it "should reset the cache_name" do @uploader.cache!(@file) @uploader.remove! @uploader.cache_name.should be_nil end it "should do nothing when trying to remove an empty file" do running { @uploader.remove! }.should_not raise_error end end end carrierwave-0.10.0/spec/uploader/store_spec.rb000066400000000000000000000265141230347170300213520ustar00rootroot00000000000000# encoding: utf-8 require 'spec_helper' describe CarrierWave::Uploader do before do @uploader_class = Class.new(CarrierWave::Uploader::Base) @uploader = @uploader_class.new end after do FileUtils.rm_rf(public_path) end describe '#store_dir' do it "should default to the config option" do @uploader.store_dir.should == 'uploads' end end describe '#filename' do it "should default to nil" do @uploader.filename.should be_nil end end describe '#store!' do before do @file = File.open(file_path('test.jpg')) @stored_file = mock('a stored file') @stored_file.stub!(:path).and_return('/path/to/somewhere') @stored_file.stub!(:url).and_return('http://www.example.com') @storage = mock('a storage engine') @storage.stub!(:store!).and_return(@stored_file) @storage.stub!(:identifier).and_return('this-is-me') @uploader_class.storage.stub!(:new).with(@uploader).and_return(@storage) end it "should set the current path" do @uploader.store!(@file) @uploader.current_path.should == '/path/to/somewhere' end it "should not be cached" do @uploader.store!(@file) @uploader.should_not be_cached end it "should set the url" do @uploader.store!(@file) @uploader.url.should == 'http://www.example.com' end it "should set the identifier" do @uploader.store!(@file) @uploader.identifier.should == 'this-is-me' end it "should, if a file is given as argument, cache that file" do @uploader.should_receive(:cache!).with(@file) @uploader.store!(@file) end it "should use a previously cached file if no argument is given" do @uploader.cache!(File.open(file_path('test.jpg'))) @uploader.should_not_receive(:cache!) @uploader.store! end it "should instruct the storage engine to store the file" do @uploader.cache!(@file) @storage.should_receive(:store!).with(@uploader.file).and_return(:monkey) @uploader.store! end it "should reset the cache_name" do @uploader.cache!(@file) @uploader.store! @uploader.cache_name.should be_nil end it "should cache the result given by the storage engine" do @uploader.store!(@file) @uploader.file.should == @stored_file end it "should delete the old file" do @uploader.cache!(@file) @uploader.file.should_receive(:delete).and_return(true) @uploader.store! end context "with the delete_tmp_file_after_storage option set to false" do before do @uploader_class.delete_tmp_file_after_storage = false end it "should not delete the old file" do @uploader.cache!(@file) @uploader.file.should_not_receive(:delete) @uploader.store! end it "should not delete the old cache_id" do @uploader.cache!(@file) cache_path = @uploader.send(:cache_path) # WARNING: violating private cache_id_dir = File.dirname(cache_path) cache_parent_dir = File.split(cache_id_dir).first File.should be_directory(cache_parent_dir) File.should be_directory(cache_id_dir) @uploader.store! File.should be_directory(cache_parent_dir) File.should be_directory(cache_id_dir) end end it "should delete the old cache_id" do @uploader.cache!(@file) cache_path = @uploader.send(:cache_path) # WARNING: violating private cache_id_dir = File.dirname(cache_path) cache_parent_dir = File.split(cache_id_dir).first File.should be_directory(cache_parent_dir) File.should be_directory(cache_id_dir) @uploader.store! File.should be_directory(cache_parent_dir) File.should_not be_directory(cache_id_dir) end context "when the old cache_id directory is not empty" do before do @uploader.cache!(@file) cache_path = @uploader.send(:cache_path) # WARNING: violating private @cache_id_dir = File.dirname(cache_path) @existing_file = File.join(@cache_id_dir, "exsting_file.txt") File.open(@existing_file, "wb"){|f| f << "I exist"} end it "should not delete the old cache_id" do @uploader.store! File.should be_directory(@cache_id_dir) end it "should not delete other existing files in old cache_id dir" do @uploader.store! File.should exist @existing_file end end it "should do nothing when trying to store an empty file" do @uploader.store!(nil) end it "should not re-store a retrieved file" do @stored_file = mock('a stored file') @storage.stub!(:retrieve!).and_return(@stored_file) @uploader_class.storage.should_not_receive(:store!) @uploader.retrieve_from_store!('monkey.txt') @uploader.store! end end describe '#retrieve_from_store!' do before do @stored_file = mock('a stored file') @stored_file.stub!(:path).and_return('/path/to/somewhere') @stored_file.stub!(:url).and_return('http://www.example.com') @storage = mock('a storage engine') @storage.stub!(:retrieve!).and_return(@stored_file) @storage.stub!(:identifier).and_return('this-is-me') @uploader_class.storage.stub!(:new).with(@uploader).and_return(@storage) end it "should set the current path" do @uploader.retrieve_from_store!('monkey.txt') @uploader.current_path.should == '/path/to/somewhere' end it "should not be cached" do @uploader.retrieve_from_store!('monkey.txt') @uploader.should_not be_cached end it "should set the url" do @uploader.retrieve_from_store!('monkey.txt') @uploader.url.should == 'http://www.example.com' end it "should set the identifier" do @uploader.retrieve_from_store!('monkey.txt') @uploader.identifier.should == 'this-is-me' end it "should instruct the storage engine to retrieve the file and store the result" do @storage.should_receive(:retrieve!).with('monkey.txt').and_return(@stored_file) @uploader.retrieve_from_store!('monkey.txt') @uploader.file.should == @stored_file end it "should overwrite a file that has already been cached" do @uploader.retrieve_from_cache!('1369894322-345-2255/test.jpeg') @uploader.retrieve_from_store!('bork.txt') @uploader.file.should == @stored_file end end describe 'with an overridden filename' do before do @uploader_class.class_eval do def filename; "foo.jpg"; end end end it "should create new files if there is a file" do @file = File.open(file_path('test.jpg')) @uploader.store!(@file) @path = ::File.expand_path(@uploader.store_path, @uploader.root) File.exist?(@path).should be_true end it "should not create new files if there is no file" do @uploader.store!(nil) @path = ::File.expand_path(@uploader.store_path, @uploader.root) File.exist?(@path).should be_false end end describe 'without a store dir' do before do @uploader_class.class_eval do def store_dir; nil; end end end it "should create a new file with a valid path and url" do @file = File.open(file_path('test.jpg')) @uploader.store!(@file) @path = ::File.expand_path(@uploader.store_path, @uploader.root) File.exist?(@path).should be_true @uploader.url.should == '/test.jpg' end end describe 'with an overridden, reversing, filename' do before do @uploader_class.class_eval do def filename super.reverse unless super.blank? end end end describe '#store!' do before do @file = File.open(file_path('test.jpg')) @stored_file = mock('a stored file') @stored_file.stub!(:path).and_return('/path/to/somewhere') @stored_file.stub!(:url).and_return('http://www.example.com') @storage = mock('a storage engine') @storage.stub!(:store!).and_return(@stored_file) @uploader_class.storage.stub!(:new).with(@uploader).and_return(@storage) end it "should set the current path" do @uploader.store!(@file) @uploader.current_path.should == '/path/to/somewhere' end it "should set the url" do @uploader.store!(@file) @uploader.url.should == 'http://www.example.com' end it "should, if a file is given as argument, reverse the filename" do @uploader.store!(@file) @uploader.filename.should == 'gpj.tset' end end describe '#retrieve_from_store!' do before do @stored_file = mock('a stored file') @stored_file.stub!(:path).and_return('/path/to/somewhere') @stored_file.stub!(:url).and_return('http://www.example.com') @storage = mock('a storage engine') @storage.stub!(:retrieve!).and_return(@stored_file) @uploader_class.storage.stub!(:new).with(@uploader).and_return(@storage) end it "should set the current path" do @uploader.retrieve_from_store!('monkey.txt') @uploader.current_path.should == '/path/to/somewhere' end it "should set the url" do @uploader.retrieve_from_store!('monkey.txt') @uploader.url.should == 'http://www.example.com' end it "should pass the identifier to the storage engine" do @storage.should_receive(:retrieve!).with('monkey.txt').and_return(@stored_file) @uploader.retrieve_from_store!('monkey.txt') @uploader.file.should == @stored_file end it "should not set the filename" do @uploader.retrieve_from_store!('monkey.txt') @uploader.filename.should be_nil end end end describe "#store! with the move_to_store option" do before do @file = File.open(file_path('test.jpg')) @uploader_class.permissions = 0777 @uploader_class.directory_permissions = 0777 CarrierWave.stub!(:generate_cache_id).and_return('1369894322-345-2255') end context "set to true" do before do @uploader_class.move_to_store = true end it "should move it from the tmp dir to the store dir" do @uploader.cache!(@file) @cached_path = @uploader.file.path @stored_path = ::File.expand_path(@uploader.store_path, @uploader.root) @cached_path.should == public_path('uploads/tmp/1369894322-345-2255/test.jpg') File.exists?(@cached_path).should be_true File.exists?(@stored_path).should be_false @uploader.store! File.exists?(@cached_path).should be_false File.exists?(@stored_path).should be_true end it "should use move_to() during store!()" do @uploader.cache!(@file) @stored_path = ::File.expand_path(@uploader.store_path, @uploader.root) @uploader.file.should_receive(:move_to).with(@stored_path, 0777, 0777) @uploader.file.should_not_receive(:copy_to) @uploader.store! end end context "set to false" do before do @uploader_class.move_to_store = false end it "should use copy_to() during store!()" do @uploader.cache!(@file) @stored_path = ::File.expand_path(@uploader.store_path, @uploader.root) @uploader.file.should_receive(:copy_to).with(@stored_path, 0777, 0777) @uploader.file.should_not_receive(:move_to) @uploader.store! end end end end carrierwave-0.10.0/spec/uploader/url_spec.rb000066400000000000000000000201141230347170300210060ustar00rootroot00000000000000# encoding: utf-8 require 'spec_helper' require 'active_support/json' describe CarrierWave::Uploader do before do class MyCoolUploader < CarrierWave::Uploader::Base; end @uploader = MyCoolUploader.new end after do FileUtils.rm_rf(public_path) Object.send(:remove_const, "MyCoolUploader") if defined?(::MyCoolUploader) end describe '#url' do before do CarrierWave.stub!(:generate_cache_id).and_return('1369894322-345-2255') end it "should default to nil" do @uploader.url.should be_nil end it "should raise ArgumentError when version doesn't exist" do lambda { @uploader.url(:thumb) }.should raise_error(ArgumentError) end it "should not raise exception when hash specified as argument" do lambda { @uploader.url({}) }.should_not raise_error end it "should not raise ArgumentError when storage's File#url method doesn't get params" do module StorageX; class File; def url; true; end; end; end @uploader.stub!(:file).and_return(StorageX::File.new) lambda { @uploader.url }.should_not raise_error end it "should not raise ArgumentError when versions version exists" do MyCoolUploader.version(:thumb) lambda { @uploader.url(:thumb) }.should_not raise_error(ArgumentError) end it "should get the directory relative to public, prepending a slash" do @uploader.cache!(File.open(file_path('test.jpg'))) @uploader.url.should == '/uploads/tmp/1369894322-345-2255/test.jpg' end it "should get the directory relative to public for a specific version" do MyCoolUploader.version(:thumb) @uploader.cache!(File.open(file_path('test.jpg'))) @uploader.url(:thumb).should == '/uploads/tmp/1369894322-345-2255/thumb_test.jpg' end it "should get the directory relative to public for a nested version" do MyCoolUploader.version(:thumb) do version(:mini) end @uploader.cache!(File.open(file_path('test.jpg'))) @uploader.url(:thumb, :mini).should == '/uploads/tmp/1369894322-345-2255/thumb_mini_test.jpg' end it "should prepend the config option 'asset_host', if set and a string" do MyCoolUploader.version(:thumb) @uploader.class.configure do |config| config.asset_host = "http://foo.bar" end @uploader.cache!(File.open(file_path('test.jpg'))) @uploader.url(:thumb).should == 'http://foo.bar/uploads/tmp/1369894322-345-2255/thumb_test.jpg' end it "should prepend the result of the config option 'asset_host', if set and a proc" do MyCoolUploader.version(:thumb) @uploader.class.configure do |config| config.asset_host = proc { "http://foo.bar" } end @uploader.cache!(File.open(file_path('test.jpg'))) @uploader.url(:thumb).should == 'http://foo.bar/uploads/tmp/1369894322-345-2255/thumb_test.jpg' end it "should prepend the config option 'base_path', if set and 'asset_host' is not set" do MyCoolUploader.version(:thumb) @uploader.class.configure do |config| config.base_path = "/base_path" config.asset_host = nil end @uploader.cache!(File.open(file_path('test.jpg'))) @uploader.url(:thumb).should == '/base_path/uploads/tmp/1369894322-345-2255/thumb_test.jpg' end it "should return file#url if available" do @uploader.cache!(File.open(file_path('test.jpg'))) @uploader.file.stub!(:url).and_return('http://www.example.com/someurl.jpg') @uploader.url.should == 'http://www.example.com/someurl.jpg' end it "should get the directory relative to public, if file#url is blank" do @uploader.cache!(File.open(file_path('test.jpg'))) @uploader.file.stub!(:url).and_return('') @uploader.url.should == '/uploads/tmp/1369894322-345-2255/test.jpg' end it "should uri encode the path of a file without an asset host" do @uploader.cache!(File.open(file_path('test+.jpg'))) @uploader.url.should == '/uploads/tmp/1369894322-345-2255/test%2B.jpg' end it "should uri encode the path of a file with a string asset host" do MyCoolUploader.version(:thumb) @uploader.class.configure do |config| config.asset_host = "http://foo.bar" end @uploader.cache!(File.open(file_path('test+.jpg'))) @uploader.url(:thumb).should == 'http://foo.bar/uploads/tmp/1369894322-345-2255/thumb_test%2B.jpg' end it "should uri encode the path of a file with a proc asset host" do MyCoolUploader.version(:thumb) @uploader.class.configure do |config| config.asset_host = proc { "http://foo.bar" } end @uploader.cache!(File.open(file_path('test+.jpg'))) @uploader.url(:thumb).should == 'http://foo.bar/uploads/tmp/1369894322-345-2255/thumb_test%2B.jpg' end it "shouldn't double-encode the path of an available file#url" do url = 'http://www.example.com/directory%2Bname/another%2Bdirectory/some%2Burl.jpg' @uploader.cache!(File.open(file_path('test.jpg'))) @uploader.file.stub!(:url).and_return(url) @uploader.url.should == url end end describe '#to_json' do before do CarrierWave.stub!(:generate_cache_id).and_return('1369894322-345-2255') end it "should return a hash with a nil URL" do MyCoolUploader.version(:thumb) hash = JSON.parse(@uploader.to_json) hash.keys.should hash.keys.should include("uploader") hash["uploader"].keys.should include("url") hash["uploader"].keys.should include("thumb") hash["uploader"]["url"].should be_nil hash["uploader"]["thumb"].keys.should include("url") hash["uploader"]["thumb"]["url"].should be_nil end it "should return a hash including a cached URL" do @uploader.cache!(File.open(file_path("test.jpg"))) JSON.parse(@uploader.to_json).should == {"uploader" => {"url" => "/uploads/tmp/1369894322-345-2255/test.jpg"}} end it "should return a hash including a cached URL of a version" do MyCoolUploader.version(:thumb) @uploader.cache!(File.open(file_path("test.jpg"))) hash = JSON.parse(@uploader.to_json)["uploader"] hash.keys.should include "thumb" hash["thumb"].should == {"url" => "/uploads/tmp/1369894322-345-2255/thumb_test.jpg"} end it "should allow an options parameter to be passed in" do lambda { @uploader.to_json({:some => 'options'}) }.should_not raise_error end end describe '#to_xml' do before do CarrierWave.stub!(:generate_cache_id).and_return('1369894322-345-2255') end it "should return a hash with a blank URL" do Hash.from_xml(@uploader.to_xml).should == {"uploader" => {"url" => nil}} end it "should return a hash including a cached URL" do @uploader.cache!(File.open(file_path("test.jpg"))) Hash.from_xml(@uploader.to_xml).should == {"uploader" => {"url" => "/uploads/tmp/1369894322-345-2255/test.jpg"}} end it "should return a hash including a cached URL of a version" do MyCoolUploader.version(:thumb) @uploader.cache!(File.open(file_path("test.jpg"))) Hash.from_xml(@uploader.to_xml)["uploader"]["thumb"].should == {"url" => "/uploads/tmp/1369894322-345-2255/thumb_test.jpg"} end it "should return a hash including an array with a cached URL" do @uploader.cache!(File.open(file_path("test.jpg"))) hash = Hash.from_xml([@uploader].to_xml) hash.should have_value([{"url"=>"/uploads/tmp/1369894322-345-2255/test.jpg"}]) end end describe '#to_s' do before do CarrierWave.stub!(:generate_cache_id).and_return('1369894322-345-2255') end it "should default to empty space" do @uploader.to_s.should == '' end it "should get the directory relative to public, prepending a slash" do @uploader.cache!(File.open(file_path('test.jpg'))) @uploader.to_s.should == '/uploads/tmp/1369894322-345-2255/test.jpg' end it "should return file#url if available" do @uploader.cache!(File.open(file_path('test.jpg'))) @uploader.file.stub!(:url).and_return('http://www.example.com/someurl.jpg') @uploader.to_s.should == 'http://www.example.com/someurl.jpg' end end end carrierwave-0.10.0/spec/uploader/versions_spec.rb000066400000000000000000000503401230347170300220600ustar00rootroot00000000000000# encoding: utf-8 require 'spec_helper' describe CarrierWave::Uploader do before do @uploader_class = Class.new(CarrierWave::Uploader::Base) @uploader = @uploader_class.new end after do FileUtils.rm_rf(public_path) end describe '.version' do it "should add it to .versions" do @uploader_class.version :thumb @uploader_class.versions[:thumb].should be_a(Hash) @uploader_class.versions[:thumb][:uploader].should be_a(Class) @uploader_class.versions[:thumb][:uploader].ancestors.should include(@uploader_class) end it "should only assign versions to parent" do @uploader_class.version :large @uploader_class.version :thumb do version :mini do version :micro end end @uploader_class.versions.should have(2).versions @uploader_class.versions.should include :large @uploader_class.versions.should include :thumb @uploader.large.versions.should be_empty @uploader.thumb.versions.keys.should == [:mini] @uploader.thumb.mini.versions.keys.should == [:micro] @uploader.thumb.mini.micro.versions.should be_empty end it "should add an accessor which returns the version" do @uploader_class.version :thumb @uploader.thumb.should be_a(@uploader_class) end it "should add it to #versions which returns the version" do @uploader_class.version :thumb @uploader.versions[:thumb].should be_a(@uploader_class) end it "should set the version name" do @uploader_class.version :thumb @uploader.version_name.should == nil @uploader.thumb.version_name.should == :thumb end it "should set the version names on the class" do @uploader_class.version :thumb @uploader.class.version_names.should == [] @uploader.thumb.class.version_names.should == [:thumb] end it "should remember mount options" do model = mock('a model') @uploader_class.version :thumb @uploader = @uploader_class.new(model, :gazelle) @uploader.thumb.model.should == model @uploader.thumb.mounted_as.should == :gazelle end it "should apply any overrides given in a block" do @uploader_class.version :thumb do def store_dir public_path('monkey/apache') end end @uploader.store_dir.should == 'uploads' @uploader.thumb.store_dir.should == public_path('monkey/apache') end it "should not initially have a value for enable processing" do thumb = (@uploader_class.version :thumb)[:uploader] thumb.instance_variable_get('@enable_processing').should be_nil end it "should return the enable processing value of the parent" do @uploader_class.enable_processing = false thumb = (@uploader_class.version :thumb)[:uploader] thumb.enable_processing.should be_false end it "should return its own value for enable processing if set" do @uploader_class.enable_processing = false thumb = (@uploader_class.version :thumb)[:uploader] thumb.enable_processing = true thumb.enable_processing.should be_true end it "should reopen the same class when called multiple times" do @uploader_class.version :thumb do def self.monkey "monkey" end end @uploader_class.version :thumb do def self.llama "llama" end end @uploader_class.version(:thumb)[:uploader].monkey.should == "monkey" @uploader_class.version(:thumb)[:uploader].llama.should == "llama" end it "should accept option :from_version" do @uploader_class.version :small_thumb, :from_version => :thumb @uploader_class.version(:small_thumb)[:options][:from_version].should == :thumb end describe 'with nested versions' do before do @uploader_class.version :thumb do version :mini version :micro end end it "should add an array of version names" do @uploader.class.version_names.should == [] @uploader.thumb.class.version_names.should == [:thumb] @uploader.thumb.mini.class.version_names.should == [:thumb, :mini] @uploader.thumb.micro.class.version_names.should == [:thumb, :micro] end it "should set the version name for the instances" do @uploader.version_name.should be_nil @uploader.thumb.version_name.should == :thumb @uploader.thumb.mini.version_name.should == :thumb_mini @uploader.thumb.micro.version_name.should == :thumb_micro end it "should process nested versions" do @uploader_class.class_eval { include CarrierWave::MiniMagick version :rotated do process :rotate version :boxed do process :resize_to_fit => [200, 200] end end def rotate manipulate! do |img| img.rotate "90" img end end } @uploader.cache! File.open(file_path('portrait.jpg')) @uploader.should have_dimensions(233, 337) @uploader.rotated.should have_dimensions(337, 233) @uploader.rotated.boxed.should have_dimensions(200, 138) end end end describe 'with a version' do before do @uploader_class.version(:thumb) end describe '#cache!' do before do CarrierWave.stub!(:generate_cache_id).and_return('1369894322-345-2255') end it "should set store_path with versions" do CarrierWave.should_receive(:generate_cache_id).once @uploader.cache!(File.open(file_path('test.jpg'))) @uploader.store_path.should == 'uploads/test.jpg' @uploader.thumb.store_path.should == 'uploads/thumb_test.jpg' @uploader.thumb.store_path('kebab.png').should == 'uploads/thumb_kebab.png' end it "should move it to the tmp dir with the filename prefixed" do CarrierWave.should_receive(:generate_cache_id).once @uploader.cache!(File.open(file_path('test.jpg'))) @uploader.current_path.should == public_path('uploads/tmp/1369894322-345-2255/test.jpg') @uploader.thumb.current_path.should == public_path('uploads/tmp/1369894322-345-2255/thumb_test.jpg') @uploader.file.exists?.should be_true @uploader.thumb.file.exists?.should be_true end it "should cache the files based on the parent" do CarrierWave.should_receive(:generate_cache_id).once @uploader.cache!(File.open(file_path('bork.txt'))) File.read(public_path(@uploader.to_s)).should == File.read(public_path(@uploader.thumb.to_s)) end end describe "version with move_to_cache set" do before do FileUtils.cp(file_path('test.jpg'), file_path('test_copy.jpg')) CarrierWave.stub!(:generate_cache_id).and_return('1369894322-345-2255') @uploader_class.send(:define_method, :move_to_cache) do true end end after do FileUtils.mv(file_path('test_copy.jpg'), file_path('test.jpg')) end it "should copy the parent file when creating the version" do @uploader_class.version(:thumb) @uploader.cache!(File.open(file_path('test.jpg'))) @uploader.current_path.should == public_path('uploads/tmp/1369894322-345-2255/test.jpg') @uploader.thumb.current_path.should == public_path('uploads/tmp/1369894322-345-2255/thumb_test.jpg') @uploader.file.exists?.should be_true @uploader.thumb.file.exists?.should be_true end it "should allow overriding move_to_cache on versions" do @uploader_class.version(:thumb) do def move_to_cache true end end @uploader.cache!(File.open(file_path('test.jpg'))) @uploader.current_path.should == public_path('uploads/tmp/1369894322-345-2255/test.jpg') @uploader.thumb.current_path.should == public_path('uploads/tmp/1369894322-345-2255/thumb_test.jpg') @uploader.file.exists?.should be_false @uploader.thumb.file.exists?.should be_true end end describe '#retrieve_from_cache!' do it "should set the path to the tmp dir" do @uploader.retrieve_from_cache!('1369894322-345-2255/test.jpg') @uploader.current_path.should == public_path('uploads/tmp/1369894322-345-2255/test.jpg') @uploader.thumb.current_path.should == public_path('uploads/tmp/1369894322-345-2255/thumb_test.jpg') end it "should set store_path with versions" do @uploader.retrieve_from_cache!('1369894322-345-2255/test.jpg') @uploader.store_path.should == 'uploads/test.jpg' @uploader.thumb.store_path.should == 'uploads/thumb_test.jpg' @uploader.thumb.store_path('kebab.png').should == 'uploads/thumb_kebab.png' end end describe '#store!' do before do @uploader_class.storage = mock_storage('base') @uploader_class.version(:thumb)[:uploader].storage = mock_storage('thumb') @uploader_class.version(:preview)[:uploader].storage = mock_storage('preview') @file = File.open(file_path('test.jpg')) @base_stored_file = mock('a stored file') @base_stored_file.stub!(:path).and_return('/path/to/somewhere') @base_stored_file.stub!(:url).and_return('http://www.example.com') @thumb_stored_file = mock('a thumb version of a stored file') @thumb_stored_file.stub!(:path).and_return('/path/to/somewhere/thumb') @thumb_stored_file.stub!(:url).and_return('http://www.example.com/thumb') @preview_stored_file = mock('a preview version of a stored file') @preview_stored_file.stub!(:path).and_return('/path/to/somewhere/preview') @preview_stored_file.stub!(:url).and_return('http://www.example.com/preview') @storage = mock('a storage engine') @storage.stub!(:store!).and_return(@base_stored_file) @thumb_storage = mock('a storage engine for thumbnails') @thumb_storage.stub!(:store!).and_return(@thumb_stored_file) @preview_storage = mock('a storage engine for previews') @preview_storage.stub!(:store!).and_return(@preview_stored_file) @uploader_class.storage.stub!(:new).with(@uploader).and_return(@storage) @uploader_class.version(:thumb)[:uploader].storage.stub!(:new).and_return(@thumb_storage) @uploader_class.version(:preview)[:uploader].storage.stub!(:new).and_return(@preview_storage) end it "should set the current path for the version" do @uploader.store!(@file) @uploader.current_path.should == '/path/to/somewhere' @uploader.thumb.current_path.should == '/path/to/somewhere/thumb' end it "should set the url" do @uploader.store!(@file) @uploader.url.should == 'http://www.example.com' @uploader.thumb.url.should == 'http://www.example.com/thumb' end it "should, if a file is given as argument, set the store_path" do @uploader.store!(@file) @uploader.store_path.should == 'uploads/test.jpg' @uploader.thumb.store_path.should == 'uploads/thumb_test.jpg' @uploader.thumb.store_path('kebab.png').should == 'uploads/thumb_kebab.png' end it "should instruct the storage engine to store the file and its version" do @uploader.cache!(@file) @storage.should_receive(:store!).with(@uploader.file).and_return(:monkey) @thumb_storage.should_receive(:store!).with(@uploader.thumb.file).and_return(:gorilla) @uploader.store! end it "should process conditional versions if the condition method returns true" do @uploader_class.version(:preview)[:options][:if] = :true? @uploader.should_receive(:true?).at_least(:once).and_return(true) @uploader.store!(@file) @uploader.thumb.should be_present @uploader.preview.should be_present end it "should not process conditional versions if the condition method returns false" do @uploader_class.version(:preview)[:options][:if] = :false? @uploader.should_receive(:false?).at_least(:once).and_return(false) @uploader.store!(@file) @uploader.thumb.should be_present @uploader.preview.should be_blank end it "should process conditional version if the condition block returns true" do @uploader_class.version(:preview)[:options][:if] = lambda{|record, args| record.true?(args[:file])} @uploader.should_receive(:true?).at_least(:once).and_return(true) @uploader.store!(@file) @uploader.thumb.should be_present @uploader.preview.should be_present end it "should not process conditional versions if the condition block returns false" do @uploader_class.version(:preview)[:options][:if] = lambda{|record, args| record.false?(args[:file])} @uploader.should_receive(:false?).at_least(:once).and_return(false) @uploader.store!(@file) @uploader.thumb.should be_present @uploader.preview.should be_blank end it "should not cache file twice when store! called with a file" do @uploader_class.process :banana @uploader.thumb.class.process :banana @uploader.should_receive(:banana).at_least(:once).at_most(:once).and_return(true) @uploader.thumb.should_receive(:banana).at_least(:once).at_most(:once).and_return(true) @uploader.store!(@file) @uploader.store_path.should == 'uploads/test.jpg' @uploader.thumb.store_path.should == 'uploads/thumb_test.jpg' end end describe '#recreate_versions!' do before do @file = File.open(file_path('test.jpg')) end it "should overwrite all stored versions with the contents of the original file" do @uploader.store!(@file) File.open(@uploader.path, 'w') { |f| f.write "Contents changed" } File.read(@uploader.thumb.path).should_not == "Contents changed" @uploader.recreate_versions! File.read(@uploader.thumb.path).should == "Contents changed" end it "should recreate all versions if any are missing" do @uploader.store!(@file) File.exists?(@uploader.thumb.path).should == true FileUtils.rm(@uploader.thumb.path) File.exists?(@uploader.thumb.path).should == false @uploader.recreate_versions! File.exists?(@uploader.thumb.path).should == true end it "should recreate only specified versions if passed as args" do @uploader_class.version(:mini) @uploader_class.version(:maxi) @uploader.store!(@file) File.exists?(@uploader.thumb.path).should == true File.exists?(@uploader.mini.path).should == true File.exists?(@uploader.maxi.path).should == true FileUtils.rm(@uploader.thumb.path) File.exists?(@uploader.thumb.path).should == false FileUtils.rm(@uploader.mini.path) File.exists?(@uploader.mini.path).should == false FileUtils.rm(@uploader.maxi.path) File.exists?(@uploader.maxi.path).should == false @uploader.recreate_versions!(:thumb, :maxi) File.exists?(@uploader.thumb.path).should == true File.exists?(@uploader.maxi.path).should == true File.exists?(@uploader.mini.path).should == false end it "should not create version if proc returns false" do @uploader_class.version(:mini, :if => Proc.new { |*args| false } ) @uploader.store!(@file) @uploader.mini.path.should be_nil @uploader.recreate_versions!(:mini) @uploader.mini.path.should be_nil end it "should not change the case of versions" do @file = File.open(file_path('Uppercase.jpg')) @uploader.store!(@file) @uploader.thumb.path.should == public_path('uploads/thumb_Uppercase.jpg') @uploader.recreate_versions! @uploader.thumb.path.should == public_path('uploads/thumb_Uppercase.jpg') end end describe '#remove!' do before do @uploader_class.storage = mock_storage('base') @uploader_class.version(:thumb)[:uploader].storage = mock_storage('thumb') @file = File.open(file_path('test.jpg')) @base_stored_file = mock('a stored file') @thumb_stored_file = mock('a thumb version of a stored file') @storage = mock('a storage engine') @storage.stub!(:store!).and_return(@base_stored_file) @thumb_storage = mock('a storage engine for thumbnails') @thumb_storage.stub!(:store!).and_return(@thumb_stored_file) @uploader_class.storage.stub!(:new).with(@uploader).and_return(@storage) @uploader_class.version(:thumb)[:uploader].storage.stub!(:new).with(@uploader.thumb).and_return(@thumb_storage) @base_stored_file.stub!(:delete) @thumb_stored_file.stub!(:delete) @uploader.store!(@file) end it "should reset the current path for the version" do @uploader.remove! @uploader.current_path.should be_nil @uploader.thumb.current_path.should be_nil end it "should reset the url" do @uploader.remove! @uploader.url.should be_nil @uploader.thumb.url.should be_nil end it "should delete all the files" do @base_stored_file.should_receive(:delete) @thumb_stored_file.should_receive(:delete) @uploader.remove! end end describe '#retrieve_from_store!' do before do @uploader_class.storage = mock_storage('base') @uploader_class.version(:thumb)[:uploader].storage = mock_storage('thumb') @file = File.open(file_path('test.jpg')) @base_stored_file = mock('a stored file') @base_stored_file.stub!(:path).and_return('/path/to/somewhere') @base_stored_file.stub!(:url).and_return('http://www.example.com') @thumb_stored_file = mock('a thumb version of a stored file') @thumb_stored_file.stub!(:path).and_return('/path/to/somewhere/thumb') @thumb_stored_file.stub!(:url).and_return('http://www.example.com/thumb') @storage = mock('a storage engine') @storage.stub!(:retrieve!).and_return(@base_stored_file) @thumb_storage = mock('a storage engine for thumbnails') @thumb_storage.stub!(:retrieve!).and_return(@thumb_stored_file) @uploader_class.storage.stub!(:new).with(@uploader).and_return(@storage) @uploader_class.version(:thumb)[:uploader].storage.stub!(:new).with(@uploader.thumb).and_return(@thumb_storage) end it "should set the current path" do @uploader.retrieve_from_store!('monkey.txt') @uploader.current_path.should == '/path/to/somewhere' @uploader.thumb.current_path.should == '/path/to/somewhere/thumb' end it "should set the url" do @uploader.retrieve_from_store!('monkey.txt') @uploader.url.should == 'http://www.example.com' @uploader.thumb.url.should == 'http://www.example.com/thumb' end it "should pass the identifier to the storage engine" do @storage.should_receive(:retrieve!).with('monkey.txt').and_return(@base_stored_file) @thumb_storage.should_receive(:retrieve!).with('monkey.txt').and_return(@thumb_stored_file) @uploader.retrieve_from_store!('monkey.txt') @uploader.file.should == @base_stored_file @uploader.thumb.file.should == @thumb_stored_file end it "should not set the filename" do @uploader.retrieve_from_store!('monkey.txt') @uploader.filename.should be_nil end end end describe 'with a version with option :from_version' do before do @uploader_class.class_eval do def upcase content = File.read(current_path) File.open(current_path, 'w') { |f| f.write content.upcase } end end @uploader_class.version(:thumb) do process :upcase end @uploader_class.version(:small_thumb, :from_version => :thumb) end describe '#cache!' do before do CarrierWave.stub!(:generate_cache_id).and_return('1369894322-345-2255') end it "should cache the files based on the version" do @uploader.cache!(File.open(file_path('bork.txt'))) File.read(public_path(@uploader.to_s)).should_not == File.read(public_path(@uploader.thumb.to_s)) File.read(public_path(@uploader.thumb.to_s)).should == File.read(public_path(@uploader.small_thumb.to_s)) end end end end