dynamic_resize 0.0.1.beta
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +1 -0
- data/.gitignore +22 -0
- data/.rspec +1 -0
- data/.travis.yml +7 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +22 -0
- data/README.md +133 -0
- data/Rakefile +1 -0
- data/certs/georgedrummond.pem +21 -0
- data/dynamic_resize.gemspec +31 -0
- data/lib/dynamic_resize.rb +32 -0
- data/lib/dynamic_resize/configuration.rb +5 -0
- data/lib/dynamic_resize/helpers.rb +7 -0
- data/lib/dynamic_resize/middleware/rack.rb +26 -0
- data/lib/dynamic_resize/middleware/resize.rb +51 -0
- data/lib/dynamic_resize/providers/base.rb +21 -0
- data/lib/dynamic_resize/providers/embedly.rb +15 -0
- data/lib/dynamic_resize/providers/imgix.rb +36 -0
- data/lib/dynamic_resize/providers/rack.rb +22 -0
- data/lib/dynamic_resize/railtie.rb +7 -0
- data/lib/dynamic_resize/version.rb +3 -0
- data/lib/generators/dynamic_resize/install_generator.rb +28 -0
- data/lib/generators/dynamic_resize/templates/README +15 -0
- data/lib/generators/dynamic_resize/templates/initializer.rb +22 -0
- data/spec/configuration_spec.rb +21 -0
- data/spec/dynamic_resize_spec.rb +5 -0
- data/spec/helpers_spec.rb +10 -0
- data/spec/middleware/rack_spec.rb +5 -0
- data/spec/middleware/resize_spec.rb +54 -0
- data/spec/providers/base_spec.rb +47 -0
- data/spec/providers/embedly_spec.rb +13 -0
- data/spec/providers/imgix_spec.rb +28 -0
- data/spec/providers/rack_spec.rb +5 -0
- data/spec/spec_helper.rb +6 -0
- metadata +193 -0
- metadata.gz.sig +1 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: ce0e9c3cdec38e7ebd188576fc87e5a8a4a72950
|
4
|
+
data.tar.gz: 1d9979454b08eef224cb0e9c9abf3a7da72c25e1
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: cdb364a3c2bf7ce080eb72a8108b87e7c87ae69cd22aa0beed0fea417baa0348ac71488c280af449d65eef64e367f99812e798e33e25bdb4fddf0c82a9c42702
|
7
|
+
data.tar.gz: fe8c9f3ad8add58cbfe190ed1ee1e018f730c1e5311dcdc2b284c697350cb22814bd315b0e99898de7e9fbfa0aa8f59648c10b9bb4e7307ad33f582af8bb2854
|
checksums.yaml.gz.sig
ADDED
Binary file
|
data.tar.gz.sig
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
S�W��~����LJ�]��Bf�.Ñ��Ԛ�"ݬ��y��Y�۩�Q����qozo�1m���(�{=eꝤ�"�>,���BR!��3 ���i�n�p����F�Oi�7;V��
|
data/.gitignore
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
*.bundle
|
19
|
+
*.so
|
20
|
+
*.o
|
21
|
+
*.a
|
22
|
+
mkmf.log
|
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 George Drummond
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
# Dynamic Resize
|
2
|
+
|
3
|
+
[![Build Status](https://travis-ci.org/georgedrummond/dynamic_resize.svg?branch=master)](https://travis-ci.org/georgedrummond/dynamic_resize) [![Gem Version](https://badge.fury.io/rb/dynamic_resize.svg)](http://badge.fury.io/rb/dynamic_resize) [![Coverage Status](https://coveralls.io/repos/georgedrummond/dynamic_resize/badge.png?branch=master)](https://coveralls.io/r/georgedrummond/dynamic_resize?branch=master)
|
4
|
+
[![MIT](http://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/georgedrummond/flymo/tree/master/LICENSE.txt)
|
5
|
+
|
6
|
+
Handling image uploads in a rails app is simple with gems such as [carrierwave](https://github.com/carrierwaveuploader/carrierwave) or [paperclip](https://github.com/thoughtbot/paperclip) but **generating new image sizes from already existing uploads is hard**. This problem gets exponentially more difficult when you're scaling to thousands or millions of images.
|
7
|
+
|
8
|
+
One solution to this problem is cropping your required image sizes at run time. This also allows for faster iterations in design since you aren't constrained to pre determined dimensions.
|
9
|
+
|
10
|
+
DynamicResize tries to make solving the problem of "On the fly image resizing" easier and more standardised.
|
11
|
+
|
12
|
+
## Installation
|
13
|
+
|
14
|
+
Add dynamic_resize to your Gemfile and run bundler
|
15
|
+
|
16
|
+
```ruby
|
17
|
+
source 'https://rubygems.org'
|
18
|
+
|
19
|
+
gem 'dynamic_resize'
|
20
|
+
```
|
21
|
+
|
22
|
+
For Rails apps you will want to run the generator to install the preconfigured initializer
|
23
|
+
|
24
|
+
```
|
25
|
+
bundle exec rake generate dynamic_resize:install
|
26
|
+
```
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
# config/initializers/dynamic_resize.rb
|
30
|
+
|
31
|
+
DynamicResize.configure do |config|
|
32
|
+
# This is the recommended provider for work on your
|
33
|
+
# local development machine.
|
34
|
+
#
|
35
|
+
# When deploying to a live environment it is recommended
|
36
|
+
# to user one of the production providers listed in the
|
37
|
+
# gems Readme.md https://github.com/georgedrummond/dynamic_resize
|
38
|
+
#
|
39
|
+
if Rails.env.development?
|
40
|
+
config.provider = DynamicResize::Providers::Rack.new(
|
41
|
+
secret_token: '5f001229e7fa7235906311a745fc6147'
|
42
|
+
)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Define image sizes so you dont need to repeat yourself and
|
46
|
+
# type the same dimensions in multiple templates.
|
47
|
+
#
|
48
|
+
# config.sizes = {
|
49
|
+
# large: [300, 500],
|
50
|
+
# medium: [400, 400]
|
51
|
+
# }
|
52
|
+
end
|
53
|
+
```
|
54
|
+
|
55
|
+
## Using it in views
|
56
|
+
|
57
|
+
```erb
|
58
|
+
<%= image_tag dynamic_resize('/users/georgedrummond.jpg', width: 120, height: 120) %>
|
59
|
+
```
|
60
|
+
|
61
|
+
## Providers
|
62
|
+
|
63
|
+
There are numerous opensource and paid for services that you could use as your backend, and I'll happily accept pull requests for any you'd like to see added to the gem.
|
64
|
+
|
65
|
+
### Rack (Development)
|
66
|
+
|
67
|
+
The rack middleware is only recommended for development on your local machine since you will probably find it *painfully* slow on pages with multiple large images. **Do not use this in production**.
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
# config/initializers/dynamic_resize.rb
|
71
|
+
|
72
|
+
DynamicResize.configure do |config|
|
73
|
+
config.provider = DynamicResize::Providers::Rack.new(
|
74
|
+
secure_token: 'your-secret-token'
|
75
|
+
)
|
76
|
+
end
|
77
|
+
```
|
78
|
+
|
79
|
+
### Imgix
|
80
|
+
|
81
|
+
```ruby
|
82
|
+
# config/initializers/dynamic_resize.rb
|
83
|
+
|
84
|
+
DynamicResize.configure do |config|
|
85
|
+
config.provider = DynamicResize::Providers::Imgix.new(
|
86
|
+
secure_token: 'your-secret-token',
|
87
|
+
subdomain: 'bucket-name' # Default subdomain
|
88
|
+
)
|
89
|
+
end
|
90
|
+
```
|
91
|
+
|
92
|
+
```erb
|
93
|
+
<%= image_tag dynamic_resize('/image.jpg', size: :thumb, subdomain: 'uploads') %>
|
94
|
+
```
|
95
|
+
|
96
|
+
### Embedly
|
97
|
+
|
98
|
+
### Writing custom providers
|
99
|
+
|
100
|
+
## Contributing
|
101
|
+
|
102
|
+
1. Fork it
|
103
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
104
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
105
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
106
|
+
5. Create new Pull Request
|
107
|
+
|
108
|
+
## License
|
109
|
+
|
110
|
+
```
|
111
|
+
Copyright (c) 2014 George Drummond
|
112
|
+
|
113
|
+
MIT License
|
114
|
+
|
115
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
116
|
+
a copy of this software and associated documentation files (the
|
117
|
+
"Software"), to deal in the Software without restriction, including
|
118
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
119
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
120
|
+
permit persons to whom the Software is furnished to do so, subject to
|
121
|
+
the following conditions:
|
122
|
+
|
123
|
+
The above copyright notice and this permission notice shall be
|
124
|
+
included in all copies or substantial portions of the Software.
|
125
|
+
|
126
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
127
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
128
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
129
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
130
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
131
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
132
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
133
|
+
```
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
@@ -0,0 +1,21 @@
|
|
1
|
+
-----BEGIN CERTIFICATE-----
|
2
|
+
MIIDijCCAnKgAwIBAgIBATANBgkqhkiG9w0BAQUFADBFMRcwFQYDVQQDDA5nZW9y
|
3
|
+
Z2VkcnVtbW9uZDEVMBMGCgmSJomT8ixkARkWBWdtYWlsMRMwEQYKCZImiZPyLGQB
|
4
|
+
GRYDY29tMB4XDTE0MTEyNzEwNDM0OVoXDTE1MTEyNzEwNDM0OVowRTEXMBUGA1UE
|
5
|
+
AwwOZ2VvcmdlZHJ1bW1vbmQxFTATBgoJkiaJk/IsZAEZFgVnbWFpbDETMBEGCgmS
|
6
|
+
JomT8ixkARkWA2NvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf4
|
7
|
+
54aq/9FhoZz6Xy6Y5edbJyd1lFfT++s8Z2brs8AYPUVGiDL4lVqY8q4rxhRQwN6M
|
8
|
+
3OdbW7EkNdbB45aWrNKgjXogeQ/ZH7fyRjFxZi9ulBDDGVEm5vPDFrKL+dn22cmk
|
9
|
+
IItuTIowFG8zcT2A3zLocCQBtnJngw50qKQviMAEuA4lmAuKOVrBKW7JWYEv+sWF
|
10
|
+
1tGY1e+JUX3d50gvTd0KEYKTFQumK3klFmNl05dVuI5RbIligeiJUW9vOJ5h9C4c
|
11
|
+
cH/BEjVozZcJttRVoVWrtTHFq+006eEJacJEK3RheZ4YeoDGX2g/B3IpHdOhmxqF
|
12
|
+
WRdknsQNncMWr1W75DECAwEAAaOBhDCBgTAJBgNVHRMEAjAAMAsGA1UdDwQEAwIE
|
13
|
+
sDAdBgNVHQ4EFgQUs7cwGEFHp8VE5yVGOK3YN9B+Ot8wIwYDVR0RBBwwGoEYZ2Vv
|
14
|
+
cmdlZHJ1bW1vbmRAZ21haWwuY29tMCMGA1UdEgQcMBqBGGdlb3JnZWRydW1tb25k
|
15
|
+
QGdtYWlsLmNvbTANBgkqhkiG9w0BAQUFAAOCAQEAK61f2LJ+0uyNq9N9/aG4ME1j
|
16
|
+
HSnfHDDD/7pcy0KiU+z0gz/UlZ1e/8r8cE7gkx4wBQ5ALcF/HPbs9SA99VJ8rnLh
|
17
|
+
DhkFgvfnUAPKeTDzoQhg/oqeVO4wh69PQu2MH3LVhKKsbM+QyUDtuR3SRRI5cet8
|
18
|
+
MPvI9QvMZAjL4kHmsYeJntqoSY7LiWhSYmUbQdVbkC8OHvH45631CdPTReMXkXje
|
19
|
+
tEzoR6ivkO7KK14wyAEoOzjyZYc49igcmAZEE53JOiIxc2dUEVtemglD+mrwsGfv
|
20
|
+
y5mMd5rxjHeTsQstgLRy/FBiEDKUB/Yf3nr7HY5RVnjjwd0zCDk2OFMjtU3Oig==
|
21
|
+
-----END CERTIFICATE-----
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'dynamic_resize/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'dynamic_resize'
|
8
|
+
spec.version = DynamicResize::VERSION
|
9
|
+
spec.authors = ["George Drummond"]
|
10
|
+
spec.email = ["georgedrummond@gmail.com"]
|
11
|
+
spec.summary = %q{Write a short summary. Required.}
|
12
|
+
spec.description = %q{Write a longer description. Optional.}
|
13
|
+
spec.homepage = 'https://github.com/georgedrummond/dynamic_resize'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ['lib']
|
20
|
+
|
21
|
+
spec.cert_chain = ['certs/georgedrummond.pem']
|
22
|
+
spec.signing_key = File.expand_path("~/.ssh/gem-private_key.pem")
|
23
|
+
|
24
|
+
spec.add_dependency 'mini_magick'
|
25
|
+
|
26
|
+
spec.add_development_dependency 'bundler', '~> 1.6'
|
27
|
+
spec.add_development_dependency 'rake'
|
28
|
+
spec.add_development_dependency 'rspec'
|
29
|
+
spec.add_development_dependency 'rspec-its'
|
30
|
+
spec.add_development_dependency 'simplecov'
|
31
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module DynamicResize
|
2
|
+
autoload :Configuration, 'dynamic_resize/configuration'
|
3
|
+
autoload :Helpers, 'dynamic_resize/helpers'
|
4
|
+
|
5
|
+
module Middleware
|
6
|
+
autoload :Rack, 'dynamic_resize/middleware/rack'
|
7
|
+
autoload :Resize, 'dynamic_resize/middleware/resize'
|
8
|
+
end
|
9
|
+
|
10
|
+
module Providers
|
11
|
+
autoload :Base, 'dynamic_resize/providers/base'
|
12
|
+
autoload :Embedly, 'dynamic_resize/providers/embedly'
|
13
|
+
autoload :Halfshell, 'dynamic_resize/providers/halfshell'
|
14
|
+
autoload :Imgix, 'dynamic_resize/providers/imgix'
|
15
|
+
autoload :Rack, 'dynamic_resize/providers/rack'
|
16
|
+
end
|
17
|
+
|
18
|
+
class << self
|
19
|
+
attr_accessor :configuration
|
20
|
+
|
21
|
+
def configure
|
22
|
+
self.configuration ||= Configuration.new
|
23
|
+
yield(configuration)
|
24
|
+
end
|
25
|
+
|
26
|
+
def for_image(image, opts)
|
27
|
+
DynamicResize.configuration.provider.resize(image, opts)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
require 'dynamic_resize/railtie' if defined?(Rails)
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module DynamicResize
|
2
|
+
module Middleware
|
3
|
+
class Rack
|
4
|
+
def initialize(app, options={})
|
5
|
+
@app = app
|
6
|
+
end
|
7
|
+
|
8
|
+
def call(env)
|
9
|
+
if env['PATH_INFO'] =~ /^\/dynamic_resize\//
|
10
|
+
request_signature = File.basename(env['PATH_INFO'])
|
11
|
+
secure_token = DynamicResize.configuration.provider.secure_token
|
12
|
+
resize_middleware = DynamicResize::Middleware::Resize.from_url(env['ORIGINAL_FULLPATH'], secure_token: secure_token)
|
13
|
+
|
14
|
+
if resize_middleware.valid_signature?(request_signature)
|
15
|
+
resize_middleware.process!
|
16
|
+
['200', {'Content-Type' => 'image/jpeg'}, File.open(resize_middleware.file_path)]
|
17
|
+
else
|
18
|
+
['400', {'Content-Type' => 'text/plain'}, ['Image signature could not be verified :(']]
|
19
|
+
end
|
20
|
+
else
|
21
|
+
@app.call(env)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'mini_magick'
|
2
|
+
|
3
|
+
module DynamicResize
|
4
|
+
module Middleware
|
5
|
+
class Resize
|
6
|
+
attr_accessor :width, :height, :image, :secure_token
|
7
|
+
|
8
|
+
def self.from_url(url, opts={})
|
9
|
+
query = URI(url).query
|
10
|
+
params = CGI.parse(query)
|
11
|
+
params.map { |key, value| params[key] = value.first }
|
12
|
+
|
13
|
+
new(
|
14
|
+
width: params['w'].to_i,
|
15
|
+
height: params['h'].to_i,
|
16
|
+
image: params['url'],
|
17
|
+
secure_token: opts[:secure_token]
|
18
|
+
)
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize(opts={})
|
22
|
+
@width = opts[:width]
|
23
|
+
@height = opts[:height]
|
24
|
+
@image = opts[:image]
|
25
|
+
@secure_token = opts[:secure_token]
|
26
|
+
end
|
27
|
+
|
28
|
+
def signature
|
29
|
+
Digest::MD5.hexdigest [@image, @width, @height, @secure_token].join('::')
|
30
|
+
end
|
31
|
+
|
32
|
+
def valid_signature?(test_signature)
|
33
|
+
signature == test_signature
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_url
|
37
|
+
"/dynamic_resize/#{signature}?w=#{width}&h=#{height}&url=#{CGI.escape image}"
|
38
|
+
end
|
39
|
+
|
40
|
+
def file_path
|
41
|
+
"public/dynamic_resize/#{signature}"
|
42
|
+
end
|
43
|
+
|
44
|
+
def process!
|
45
|
+
image = MiniMagick::Image.open(@image)
|
46
|
+
image.resize [@height, @width].join('x')
|
47
|
+
image.write file_path
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module DynamicResize
|
2
|
+
module Providers
|
3
|
+
class Base
|
4
|
+
attr_accessor :width, :height, :image
|
5
|
+
|
6
|
+
def resize(image, opts={})
|
7
|
+
if opts[:size]
|
8
|
+
size = DynamicResize.configuration.sizes[opts[:size]]
|
9
|
+
@width = size[0]
|
10
|
+
@height = size[1]
|
11
|
+
end
|
12
|
+
|
13
|
+
@image = image
|
14
|
+
@width = opts[:width] if opts[:width]
|
15
|
+
@height = opts[:height] if opts[:height]
|
16
|
+
|
17
|
+
generate_image_url
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module DynamicResize
|
2
|
+
module Providers
|
3
|
+
class Embedly < Base
|
4
|
+
attr_accessor :api_key
|
5
|
+
|
6
|
+
def initialize(opts={})
|
7
|
+
@api_key = opts[:api_key]
|
8
|
+
end
|
9
|
+
|
10
|
+
def generate_image_url
|
11
|
+
"https://i.embed.ly/1/image/resize?url=#{image}&key=#{api_key}&height=#{height}&width=#{width}"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module DynamicResize
|
2
|
+
module Providers
|
3
|
+
class Imgix < Base
|
4
|
+
attr_accessor :subdomain
|
5
|
+
|
6
|
+
def initialize(opts={})
|
7
|
+
@subdomain = opts[:subdomain]
|
8
|
+
@secure_token = opts[:secure_token]
|
9
|
+
end
|
10
|
+
|
11
|
+
def generate_image_url
|
12
|
+
url = [base_url, @image, '?', query]
|
13
|
+
url << "&s=#{request_signature}" if @secure_token
|
14
|
+
url.join
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def base_url
|
20
|
+
"https://#{@subdomain}.imgix.net"
|
21
|
+
end
|
22
|
+
|
23
|
+
def query
|
24
|
+
q = []
|
25
|
+
q << "w=#{@width}" if @width
|
26
|
+
q << "h=#{@height}" if @height
|
27
|
+
q.join('&')
|
28
|
+
end
|
29
|
+
|
30
|
+
def request_signature
|
31
|
+
signature_attributes = [@secure_token, @image, '?', query]
|
32
|
+
Digest::MD5.hexdigest(signature_attributes.join)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module DynamicResize
|
2
|
+
module Providers
|
3
|
+
class Rack < Base
|
4
|
+
attr_accessor :secure_token
|
5
|
+
|
6
|
+
def initialize(opts={})
|
7
|
+
@secure_token = opts[:secure_token]
|
8
|
+
end
|
9
|
+
|
10
|
+
def generate_image_url
|
11
|
+
resize = DynamicResize::Middleware::Resize.new(
|
12
|
+
width: @width,
|
13
|
+
height: @height,
|
14
|
+
image: @image,
|
15
|
+
secure_token: @secure_token
|
16
|
+
)
|
17
|
+
|
18
|
+
resize.to_url
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module DynamicResize
|
2
|
+
module Generators
|
3
|
+
class InstallGenerator < Rails::Generators::Base
|
4
|
+
source_root File.expand_path('../templates', __FILE__)
|
5
|
+
|
6
|
+
desc 'Setup up DynamicResize in your rails project'
|
7
|
+
|
8
|
+
def install_middleware
|
9
|
+
application do
|
10
|
+
"config.middleware.use 'DynamicResize::Middleware::Rack' if Rails.env.development?"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def copy_initializer
|
15
|
+
copy_file 'initializer.rb', 'config/initializers/dynamic_resize.rb'
|
16
|
+
gsub_file 'config/initializers/dynamic_resize.rb', 'dynamic_resize.middleware_secure_token', SecureRandom.hex
|
17
|
+
end
|
18
|
+
|
19
|
+
def gitignore
|
20
|
+
append_file '.gitignore', '/public/dynamic_resize/'
|
21
|
+
end
|
22
|
+
|
23
|
+
def print_readme
|
24
|
+
readme 'README'
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
|
2
|
+
~~~~~~~~~~~~~~~~~~~~
|
3
|
+
|
4
|
+
Almost ready to go
|
5
|
+
------------------
|
6
|
+
|
7
|
+
You have now configured DynamicResize for use in your rails development environment.
|
8
|
+
|
9
|
+
You still need to setup a provider for your production and staging environments. For
|
10
|
+
guidance on how to do this please visit our Readme.md
|
11
|
+
|
12
|
+
--> https://github.com/georgedrummond/dynamic_resize <--
|
13
|
+
|
14
|
+
~~~~~~~~~~~~~~~~~~~~
|
15
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
DynamicResize.configure do |config|
|
2
|
+
# This is the recommended provider for work on your
|
3
|
+
# local development machine.
|
4
|
+
#
|
5
|
+
# When deploying to a live environment it is recommended
|
6
|
+
# to user one of the production providers listed in the
|
7
|
+
# gems Readme.md https://github.com/georgedrummond/dynamic_resize
|
8
|
+
#
|
9
|
+
if Rails.env.development?
|
10
|
+
config.provider = DynamicResize::Providers::Rack.new(
|
11
|
+
secret_token: 'dynamic_resize.middleware_secure_token'
|
12
|
+
)
|
13
|
+
end
|
14
|
+
|
15
|
+
# Define image sizes so you dont need to repeat yourself and
|
16
|
+
# type the same dimensions in multiple templates.
|
17
|
+
#
|
18
|
+
# config.sizes = {
|
19
|
+
# large: [300, 500],
|
20
|
+
# medium: [400, 400]
|
21
|
+
# }
|
22
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe DynamicResize::Configuration do
|
4
|
+
let(:provider) { 'foobar' }
|
5
|
+
let(:sizes) do
|
6
|
+
{
|
7
|
+
large: [200, 400],
|
8
|
+
thumb: [100, 100]
|
9
|
+
}
|
10
|
+
end
|
11
|
+
|
12
|
+
subject(:config) { DynamicResize::Configuration.new }
|
13
|
+
|
14
|
+
before do
|
15
|
+
config.provider = provider
|
16
|
+
config.sizes = sizes
|
17
|
+
end
|
18
|
+
|
19
|
+
its(:provider) { should eq provider }
|
20
|
+
its(:sizes) { should eq sizes }
|
21
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe DynamicResize::Middleware::Resize do
|
4
|
+
let!(:image) { 'https://example.rspec/images/1.png' }
|
5
|
+
let!(:secure_token) { '0bf9c9ef09fb73971b6c71a307d4e506' }
|
6
|
+
|
7
|
+
let!(:signature) { 'c2040fc794e5cbb27b724fa0d127699e' }
|
8
|
+
|
9
|
+
shared_examples 'resize_middleware' do
|
10
|
+
its(:width) { should eq 333 }
|
11
|
+
its(:height) { should eq 444 }
|
12
|
+
its(:image) { should eq image }
|
13
|
+
its(:secure_token) { should eq secure_token }
|
14
|
+
its(:signature) { should eq signature }
|
15
|
+
its(:to_url) { should eq "/dynamic_resize/#{signature}?w=333&h=444&url=#{CGI.escape image}" }
|
16
|
+
its(:file_path) { should eq "public/dynamic_resize/#{signature}"}
|
17
|
+
end
|
18
|
+
|
19
|
+
subject(:resizer) do
|
20
|
+
DynamicResize::Middleware::Resize.new(
|
21
|
+
width: 333,
|
22
|
+
height: 444,
|
23
|
+
image: image,
|
24
|
+
secure_token: secure_token
|
25
|
+
)
|
26
|
+
end
|
27
|
+
|
28
|
+
include_examples 'resize_middleware'
|
29
|
+
|
30
|
+
describe '#valid_token?' do
|
31
|
+
context 'true' do
|
32
|
+
it { expect(resizer.valid_signature? signature).to eq true }
|
33
|
+
end
|
34
|
+
|
35
|
+
context 'false' do
|
36
|
+
it { expect(resizer.valid_signature? 'invalid').to eq false }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe '#from_url' do
|
41
|
+
let!(:url) { '/dynamic_resize/c2040fc794e5cbb27b724fa0d127699e?w=333&h=444&url=https%3A%2F%2Fexample.rspec%2Fimages%2F1.png' }
|
42
|
+
|
43
|
+
subject do
|
44
|
+
DynamicResize::Middleware::Resize.from_url(url, secure_token: secure_token)
|
45
|
+
end
|
46
|
+
|
47
|
+
include_examples 'resize_middleware'
|
48
|
+
end
|
49
|
+
|
50
|
+
describe '#process!' do
|
51
|
+
it 'todo'
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe DynamicResize::Providers::Base do
|
4
|
+
let(:image) { '/image.png' }
|
5
|
+
|
6
|
+
before do
|
7
|
+
DynamicResize.configure do |config|
|
8
|
+
config.sizes = {
|
9
|
+
demo: [200, 500]
|
10
|
+
}
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
subject(:base_provider) { DynamicResize::Providers::Base.new }
|
15
|
+
|
16
|
+
describe '#resize' do
|
17
|
+
before { expect(base_provider).to receive(:generate_image_url).once }
|
18
|
+
|
19
|
+
context 'invalid size' do
|
20
|
+
it 'raise an exception'
|
21
|
+
end
|
22
|
+
|
23
|
+
context 'size' do
|
24
|
+
before { base_provider.resize(image, size: :demo) }
|
25
|
+
|
26
|
+
its(:image) { should eq image }
|
27
|
+
its(:height) { should eq 500 }
|
28
|
+
its(:width) { should eq 200 }
|
29
|
+
end
|
30
|
+
|
31
|
+
context 'size with override' do
|
32
|
+
before { base_provider.resize(image, size: :demo, height: 4000) }
|
33
|
+
|
34
|
+
its(:image) { should eq image }
|
35
|
+
its(:height) { should eq 4000 }
|
36
|
+
its(:width) { should eq 200 }
|
37
|
+
end
|
38
|
+
|
39
|
+
context 'height + width' do
|
40
|
+
before { base_provider.resize(image, width: 40, height: 90) }
|
41
|
+
|
42
|
+
its(:image) { should eq image }
|
43
|
+
its(:width) { should eq 40 }
|
44
|
+
its(:height) { should eq 90 }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe DynamicResize::Providers::Embedly do
|
4
|
+
let!(:api_key) { SecureRandom.hex }
|
5
|
+
|
6
|
+
subject(:embedly) { DynamicResize::Providers::Embedly.new(api_key: api_key) }
|
7
|
+
|
8
|
+
it 'resize' do
|
9
|
+
# TODO: Handle url vs path
|
10
|
+
resized = embedly.resize('/wow.png', height: 30, width: 40)
|
11
|
+
expect(resized).to eq "https://i.embed.ly/1/image/resize?url=/wow.png&key=#{api_key}&height=30&width=40"
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe DynamicResize::Providers::Imgix do
|
4
|
+
let!(:subdomain) { 'dynamic-resize' }
|
5
|
+
|
6
|
+
context 'not signed' do
|
7
|
+
subject(:imgix) { DynamicResize::Providers::Imgix.new(subdomain: subdomain) }
|
8
|
+
|
9
|
+
it 'resize' do
|
10
|
+
# TODO: Handle url vs path
|
11
|
+
resized = imgix.resize('/demo.png', height: 30, width: 40)
|
12
|
+
expect(resized).to eq "https://#{subdomain}.imgix.net/demo.png?w=40&h=30"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
context 'signed' do
|
17
|
+
subject(:imgix) { DynamicResize::Providers::Imgix.new(subdomain: subdomain, secure_token: 'abcdef') }
|
18
|
+
|
19
|
+
it 'signed' do
|
20
|
+
image = '/frog.jpg'
|
21
|
+
resized = imgix.resize(image, width: 100)
|
22
|
+
|
23
|
+
expect(resized).to eq 'https://dynamic-resize.imgix.net/frog.jpg?w=100&s=cd3bdf071108af73b15c21bdcee5e49c'
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'allow me to switch out subdomain'
|
28
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,193 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dynamic_resize
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1.beta
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- George Drummond
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain:
|
11
|
+
- |
|
12
|
+
-----BEGIN CERTIFICATE-----
|
13
|
+
MIIDijCCAnKgAwIBAgIBATANBgkqhkiG9w0BAQUFADBFMRcwFQYDVQQDDA5nZW9y
|
14
|
+
Z2VkcnVtbW9uZDEVMBMGCgmSJomT8ixkARkWBWdtYWlsMRMwEQYKCZImiZPyLGQB
|
15
|
+
GRYDY29tMB4XDTE0MTEyNzEwNDM0OVoXDTE1MTEyNzEwNDM0OVowRTEXMBUGA1UE
|
16
|
+
AwwOZ2VvcmdlZHJ1bW1vbmQxFTATBgoJkiaJk/IsZAEZFgVnbWFpbDETMBEGCgmS
|
17
|
+
JomT8ixkARkWA2NvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf4
|
18
|
+
54aq/9FhoZz6Xy6Y5edbJyd1lFfT++s8Z2brs8AYPUVGiDL4lVqY8q4rxhRQwN6M
|
19
|
+
3OdbW7EkNdbB45aWrNKgjXogeQ/ZH7fyRjFxZi9ulBDDGVEm5vPDFrKL+dn22cmk
|
20
|
+
IItuTIowFG8zcT2A3zLocCQBtnJngw50qKQviMAEuA4lmAuKOVrBKW7JWYEv+sWF
|
21
|
+
1tGY1e+JUX3d50gvTd0KEYKTFQumK3klFmNl05dVuI5RbIligeiJUW9vOJ5h9C4c
|
22
|
+
cH/BEjVozZcJttRVoVWrtTHFq+006eEJacJEK3RheZ4YeoDGX2g/B3IpHdOhmxqF
|
23
|
+
WRdknsQNncMWr1W75DECAwEAAaOBhDCBgTAJBgNVHRMEAjAAMAsGA1UdDwQEAwIE
|
24
|
+
sDAdBgNVHQ4EFgQUs7cwGEFHp8VE5yVGOK3YN9B+Ot8wIwYDVR0RBBwwGoEYZ2Vv
|
25
|
+
cmdlZHJ1bW1vbmRAZ21haWwuY29tMCMGA1UdEgQcMBqBGGdlb3JnZWRydW1tb25k
|
26
|
+
QGdtYWlsLmNvbTANBgkqhkiG9w0BAQUFAAOCAQEAK61f2LJ+0uyNq9N9/aG4ME1j
|
27
|
+
HSnfHDDD/7pcy0KiU+z0gz/UlZ1e/8r8cE7gkx4wBQ5ALcF/HPbs9SA99VJ8rnLh
|
28
|
+
DhkFgvfnUAPKeTDzoQhg/oqeVO4wh69PQu2MH3LVhKKsbM+QyUDtuR3SRRI5cet8
|
29
|
+
MPvI9QvMZAjL4kHmsYeJntqoSY7LiWhSYmUbQdVbkC8OHvH45631CdPTReMXkXje
|
30
|
+
tEzoR6ivkO7KK14wyAEoOzjyZYc49igcmAZEE53JOiIxc2dUEVtemglD+mrwsGfv
|
31
|
+
y5mMd5rxjHeTsQstgLRy/FBiEDKUB/Yf3nr7HY5RVnjjwd0zCDk2OFMjtU3Oig==
|
32
|
+
-----END CERTIFICATE-----
|
33
|
+
date: 2014-11-30 00:00:00.000000000 Z
|
34
|
+
dependencies:
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: mini_magick
|
37
|
+
requirement: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
42
|
+
type: :runtime
|
43
|
+
prerelease: false
|
44
|
+
version_requirements: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: bundler
|
51
|
+
requirement: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - "~>"
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '1.6'
|
56
|
+
type: :development
|
57
|
+
prerelease: false
|
58
|
+
version_requirements: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - "~>"
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '1.6'
|
63
|
+
- !ruby/object:Gem::Dependency
|
64
|
+
name: rake
|
65
|
+
requirement: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
- !ruby/object:Gem::Dependency
|
78
|
+
name: rspec
|
79
|
+
requirement: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0'
|
84
|
+
type: :development
|
85
|
+
prerelease: false
|
86
|
+
version_requirements: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0'
|
91
|
+
- !ruby/object:Gem::Dependency
|
92
|
+
name: rspec-its
|
93
|
+
requirement: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - ">="
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '0'
|
98
|
+
type: :development
|
99
|
+
prerelease: false
|
100
|
+
version_requirements: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - ">="
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '0'
|
105
|
+
- !ruby/object:Gem::Dependency
|
106
|
+
name: simplecov
|
107
|
+
requirement: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - ">="
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '0'
|
112
|
+
type: :development
|
113
|
+
prerelease: false
|
114
|
+
version_requirements: !ruby/object:Gem::Requirement
|
115
|
+
requirements:
|
116
|
+
- - ">="
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: '0'
|
119
|
+
description: Write a longer description. Optional.
|
120
|
+
email:
|
121
|
+
- georgedrummond@gmail.com
|
122
|
+
executables: []
|
123
|
+
extensions: []
|
124
|
+
extra_rdoc_files: []
|
125
|
+
files:
|
126
|
+
- ".gitignore"
|
127
|
+
- ".rspec"
|
128
|
+
- ".travis.yml"
|
129
|
+
- Gemfile
|
130
|
+
- LICENSE.txt
|
131
|
+
- README.md
|
132
|
+
- Rakefile
|
133
|
+
- certs/georgedrummond.pem
|
134
|
+
- dynamic_resize.gemspec
|
135
|
+
- lib/dynamic_resize.rb
|
136
|
+
- lib/dynamic_resize/configuration.rb
|
137
|
+
- lib/dynamic_resize/helpers.rb
|
138
|
+
- lib/dynamic_resize/middleware/rack.rb
|
139
|
+
- lib/dynamic_resize/middleware/resize.rb
|
140
|
+
- lib/dynamic_resize/providers/base.rb
|
141
|
+
- lib/dynamic_resize/providers/embedly.rb
|
142
|
+
- lib/dynamic_resize/providers/imgix.rb
|
143
|
+
- lib/dynamic_resize/providers/rack.rb
|
144
|
+
- lib/dynamic_resize/railtie.rb
|
145
|
+
- lib/dynamic_resize/version.rb
|
146
|
+
- lib/generators/dynamic_resize/install_generator.rb
|
147
|
+
- lib/generators/dynamic_resize/templates/README
|
148
|
+
- lib/generators/dynamic_resize/templates/initializer.rb
|
149
|
+
- spec/configuration_spec.rb
|
150
|
+
- spec/dynamic_resize_spec.rb
|
151
|
+
- spec/helpers_spec.rb
|
152
|
+
- spec/middleware/rack_spec.rb
|
153
|
+
- spec/middleware/resize_spec.rb
|
154
|
+
- spec/providers/base_spec.rb
|
155
|
+
- spec/providers/embedly_spec.rb
|
156
|
+
- spec/providers/imgix_spec.rb
|
157
|
+
- spec/providers/rack_spec.rb
|
158
|
+
- spec/spec_helper.rb
|
159
|
+
homepage: https://github.com/georgedrummond/dynamic_resize
|
160
|
+
licenses:
|
161
|
+
- MIT
|
162
|
+
metadata: {}
|
163
|
+
post_install_message:
|
164
|
+
rdoc_options: []
|
165
|
+
require_paths:
|
166
|
+
- lib
|
167
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
168
|
+
requirements:
|
169
|
+
- - ">="
|
170
|
+
- !ruby/object:Gem::Version
|
171
|
+
version: '0'
|
172
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
173
|
+
requirements:
|
174
|
+
- - ">"
|
175
|
+
- !ruby/object:Gem::Version
|
176
|
+
version: 1.3.1
|
177
|
+
requirements: []
|
178
|
+
rubyforge_project:
|
179
|
+
rubygems_version: 2.2.2
|
180
|
+
signing_key:
|
181
|
+
specification_version: 4
|
182
|
+
summary: Write a short summary. Required.
|
183
|
+
test_files:
|
184
|
+
- spec/configuration_spec.rb
|
185
|
+
- spec/dynamic_resize_spec.rb
|
186
|
+
- spec/helpers_spec.rb
|
187
|
+
- spec/middleware/rack_spec.rb
|
188
|
+
- spec/middleware/resize_spec.rb
|
189
|
+
- spec/providers/base_spec.rb
|
190
|
+
- spec/providers/embedly_spec.rb
|
191
|
+
- spec/providers/imgix_spec.rb
|
192
|
+
- spec/providers/rack_spec.rb
|
193
|
+
- spec/spec_helper.rb
|
metadata.gz.sig
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
�i�?@���r�g��k�'��S�@Q2���G}�}�dZ���(�d�5�Ԣ���\]h���~56���2�ی�'!�(��e���nҁ,�������[LJ���`�_F%�n:���%�2N�����Q�p��Av��/�J��5��bm��qס���0{���.��6Tx��`@�wE��웣k���y��&��¢:M��4za!� ?��L(|�=��n�%�:�ϼD���ɢ�}ʣ��G���-�S���f
|