httpthumbnailer-client 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +15 -0
- data/Gemfile +5 -5
- data/Gemfile.lock +58 -30
- data/README.md +33 -5
- data/Rakefile +5 -1
- data/VERSION +1 -1
- data/bin/httpthumbnailer-client +9 -27
- data/lib/httpthumbnailer-client.rb +9 -45
- data/lib/httpthumbnailer-client/thumbnail_spec.rb +178 -0
- data/lib/httpthumbnailer-client/uri_builder.rb +43 -0
- metadata +35 -62
- data/.rspec +0 -1
- data/features/step_definitions/httpthumbnailer-client_steps.rb +0 -0
- data/features/support/env.rb +0 -13
- data/httpthumbnailer-client.gemspec +0 -95
- data/spec/httpthumbnailer-client_spec.rb +0 -18
- data/spec/identify_spec.rb +0 -44
- data/spec/spec_helper.rb +0 -83
- data/spec/support/test-large.jpg +0 -0
- data/spec/support/test.jpg +0 -0
- data/spec/support/test.txt +0 -1
- data/spec/thumbnail_spec.rb +0 -102
- data/spec/thumbnails_spec.rb +0 -139
- data/spec/uri_builder_spec.rb +0 -16
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
MDY0YzBlNDVlNjUwN2M0MDRhY2E3Mjg1ZjEzMTY2YzFlYjdlNjE1OA==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
ZTc3ODZjY2QxNzBmYzgxZjg3ZjM1ZWZlNDEwODk3M2JlMDFlMzgwMA==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
OTA5ZDZmZGQ0MjU0NmYzZDdhZjMzMmE3MGYzMzA3Zjg3MDNjNTNjYjIyN2Mx
|
10
|
+
NWVkM2ExMGE1MDQ4ZmNhZTIzM2RjYzkyNmY4NWYzMjhkYmFmMmE2ZDc5NjE0
|
11
|
+
MGM3ZDljNjgxNjkwMTI2ZDZkZGM5ODQ2YzcyM2VhMGZmMmMxMDI=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
YmIyZDFkMDIwOTc0M2JhODBiNTNhYzAxZmNhNmM1NWIwNzg4MmJmYjE4YmMz
|
14
|
+
YWRhM2NmNjM1NTMzZTRhYWRkNjAwYTg4ZmFlMzE4ZjU4OTVlZDc4NmMxZGY0
|
15
|
+
ZWMyMTNiODhiZGViMzg1NDgzMmFhN2RlNDQ4OWYxMGM4NDQzYzE=
|
data/Gemfile
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
source "http://rubygems.org"
|
2
2
|
ruby "1.9.3"
|
3
3
|
|
4
|
-
gem "httpclient", ">= 2.3"
|
4
|
+
gem "httpclient", "~> 2.0", ">= 2.3"
|
5
5
|
gem "cli", "~> 1.3"
|
6
6
|
gem "multipart-parser", "~> 0.1.1"
|
7
7
|
|
@@ -10,11 +10,11 @@ gem "multipart-parser", "~> 0.1.1"
|
|
10
10
|
group :development do
|
11
11
|
gem "rspec", "~> 2.13"
|
12
12
|
gem "rspec-mocks", "~> 2.13"
|
13
|
-
gem
|
14
|
-
gem "jeweler", "~> 1.8.
|
13
|
+
gem 'cucumber', '~> 1.3'
|
14
|
+
gem "jeweler", "~> 1.8", ">= 1.8.8"
|
15
15
|
gem "rdoc", "~> 3.9"
|
16
16
|
gem "daemon", "~> 1"
|
17
17
|
gem "rmagick", "~> 2"
|
18
|
-
gem "httpthumbnailer", path: '../httpthumbnailer'
|
19
|
-
gem "unicorn-cuba-base", path: '../unicorn-cuba-base'
|
18
|
+
gem "httpthumbnailer", "~> 1", path: '../httpthumbnailer'
|
19
|
+
gem "unicorn-cuba-base", "~> 1", path: '../unicorn-cuba-base'
|
20
20
|
end
|
data/Gemfile.lock
CHANGED
@@ -1,53 +1,82 @@
|
|
1
1
|
PATH
|
2
2
|
remote: ../httpthumbnailer
|
3
3
|
specs:
|
4
|
-
httpthumbnailer (1.
|
4
|
+
httpthumbnailer (1.3.0)
|
5
5
|
rmagick (~> 2)
|
6
|
-
unicorn-cuba-base (~> 1.
|
6
|
+
unicorn-cuba-base (~> 1.6)
|
7
7
|
|
8
8
|
PATH
|
9
9
|
remote: ../unicorn-cuba-base
|
10
10
|
specs:
|
11
|
-
unicorn-cuba-base (1.
|
11
|
+
unicorn-cuba-base (1.6.0)
|
12
12
|
cli (~> 1.3)
|
13
13
|
cuba (~> 3.0)
|
14
|
-
facter (~> 1.6.
|
14
|
+
facter (~> 1.6, >= 1.6.18)
|
15
15
|
raindrops (~> 0.11)
|
16
16
|
ruby-ip (~> 0.9)
|
17
|
-
unicorn (>= 4.6.2)
|
17
|
+
unicorn (~> 4.6, >= 4.6.2)
|
18
18
|
|
19
19
|
GEM
|
20
20
|
remote: http://rubygems.org/
|
21
21
|
specs:
|
22
|
-
|
22
|
+
addressable (2.3.8)
|
23
|
+
builder (3.2.2)
|
23
24
|
cli (1.3.1)
|
24
|
-
cuba (3.
|
25
|
+
cuba (3.4.0)
|
25
26
|
rack
|
26
|
-
cucumber (1.
|
27
|
+
cucumber (1.3.17)
|
27
28
|
builder (>= 2.1.2)
|
28
|
-
diff-lcs (>= 1.1.
|
29
|
-
gherkin (~> 2.
|
30
|
-
|
31
|
-
|
29
|
+
diff-lcs (>= 1.1.3)
|
30
|
+
gherkin (~> 2.12)
|
31
|
+
multi_json (>= 1.7.5, < 2.0)
|
32
|
+
multi_test (>= 0.1.1)
|
32
33
|
daemon (1.1.0)
|
33
34
|
diff-lcs (1.1.3)
|
34
|
-
facter (1.6
|
35
|
-
|
36
|
-
|
37
|
-
|
35
|
+
facter (1.7.6)
|
36
|
+
faraday (0.8.9)
|
37
|
+
multipart-post (~> 1.2.0)
|
38
|
+
gherkin (2.12.2)
|
39
|
+
multi_json (~> 1.3)
|
40
|
+
git (1.2.9.1)
|
41
|
+
github_api (0.10.1)
|
42
|
+
addressable
|
43
|
+
faraday (~> 0.8.1)
|
44
|
+
hashie (>= 1.2)
|
45
|
+
multi_json (~> 1.4)
|
46
|
+
nokogiri (~> 1.5.2)
|
47
|
+
oauth2
|
48
|
+
hashie (3.4.2)
|
49
|
+
highline (1.7.2)
|
38
50
|
httpclient (2.3.3)
|
39
|
-
jeweler (1.8.
|
51
|
+
jeweler (1.8.8)
|
52
|
+
builder
|
40
53
|
bundler (~> 1.0)
|
41
54
|
git (>= 1.2.5)
|
55
|
+
github_api (= 0.10.1)
|
56
|
+
highline (>= 1.6.15)
|
57
|
+
nokogiri (= 1.5.10)
|
42
58
|
rake
|
43
59
|
rdoc
|
44
|
-
json (1.
|
45
|
-
|
60
|
+
json (1.8.3)
|
61
|
+
jwt (1.5.0)
|
62
|
+
kgio (2.9.3)
|
63
|
+
multi_json (1.10.1)
|
64
|
+
multi_test (0.1.1)
|
65
|
+
multi_xml (0.5.5)
|
46
66
|
multipart-parser (0.1.1)
|
47
|
-
|
67
|
+
multipart-post (1.2.0)
|
68
|
+
nokogiri (1.5.10)
|
69
|
+
oauth2 (1.0.0)
|
70
|
+
faraday (>= 0.8, < 0.10)
|
71
|
+
jwt (~> 1.0)
|
72
|
+
multi_json (~> 1.3)
|
73
|
+
multi_xml (~> 0.5)
|
74
|
+
rack (~> 1.2)
|
75
|
+
rack (1.6.1)
|
48
76
|
raindrops (0.13.0)
|
49
|
-
rake (10.
|
50
|
-
rdoc (3.
|
77
|
+
rake (10.4.2)
|
78
|
+
rdoc (3.12.2)
|
79
|
+
json (~> 1.4)
|
51
80
|
rmagick (2.13.2)
|
52
81
|
rspec (2.13.0)
|
53
82
|
rspec-core (~> 2.13.0)
|
@@ -57,9 +86,8 @@ GEM
|
|
57
86
|
rspec-expectations (2.13.0)
|
58
87
|
diff-lcs (>= 1.1.3, < 2.0)
|
59
88
|
rspec-mocks (2.13.1)
|
60
|
-
ruby-ip (0.9.
|
61
|
-
|
62
|
-
unicorn (4.8.2)
|
89
|
+
ruby-ip (0.9.3)
|
90
|
+
unicorn (4.9.0)
|
63
91
|
kgio (~> 2.6)
|
64
92
|
rack
|
65
93
|
raindrops (~> 0.7)
|
@@ -69,14 +97,14 @@ PLATFORMS
|
|
69
97
|
|
70
98
|
DEPENDENCIES
|
71
99
|
cli (~> 1.3)
|
72
|
-
cucumber
|
100
|
+
cucumber (~> 1.3)
|
73
101
|
daemon (~> 1)
|
74
|
-
httpclient (>= 2.3)
|
75
|
-
httpthumbnailer!
|
76
|
-
jeweler (~> 1.8.
|
102
|
+
httpclient (~> 2.0, >= 2.3)
|
103
|
+
httpthumbnailer (~> 1)!
|
104
|
+
jeweler (~> 1.8, >= 1.8.8)
|
77
105
|
multipart-parser (~> 0.1.1)
|
78
106
|
rdoc (~> 3.9)
|
79
107
|
rmagick (~> 2)
|
80
108
|
rspec (~> 2.13)
|
81
109
|
rspec-mocks (~> 2.13)
|
82
|
-
unicorn-cuba-base!
|
110
|
+
unicorn-cuba-base (~> 1)!
|
data/README.md
CHANGED
@@ -1,6 +1,13 @@
|
|
1
1
|
# HTTP Thubnailer Client
|
2
2
|
|
3
|
-
Ruby client to [httpthumbnailer](http://github.com/jpastuszek/httpthumbnailer) image
|
3
|
+
Ruby client to [httpthumbnailer](http://github.com/jpastuszek/httpthumbnailer) image thumbnailing, editing and format conversion HTTP API server.
|
4
|
+
|
5
|
+
## Changelog
|
6
|
+
|
7
|
+
### 1.3.0
|
8
|
+
* added edits support
|
9
|
+
* better spec parsing and error handling
|
10
|
+
* using single thumbnail API if only one thumbnail spec defined
|
4
11
|
|
5
12
|
## Installing
|
6
13
|
|
@@ -30,7 +37,7 @@ thumbnail.data # => 60x30 thumbnail JPEG data String
|
|
30
37
|
|
31
38
|
# generate set of thumbnails from image data (multipart API)
|
32
39
|
thumbnails = HTTPThumbnailerClient.new('http://localhost:3100').thumbnail(data) do
|
33
|
-
|
40
|
+
thumbnail 'crop', 60, 30, 'jpeg'
|
34
41
|
thumbnail 'crop', 80, 80, 'png'
|
35
42
|
thumbnail 'pad', 40, 40, 'png'
|
36
43
|
end
|
@@ -65,9 +72,28 @@ id = HTTPThumbnailerClient.new('http://localhost:3100').with_headers('XID' => '1
|
|
65
72
|
id.mime_type # => 'image/jpeg'
|
66
73
|
id.width # => 800
|
67
74
|
id.height # => 600
|
75
|
+
|
76
|
+
# generate single thumbnail from image data with additional edits
|
77
|
+
thumbnail = HTTPThumbnailerClient.new('http://localhost:3100').thumbnail(data, 'crop', 60, 30, 'jpeg') do
|
78
|
+
edit 'rotate', 90
|
79
|
+
edit 'pixelate', 0.3, 0.2, 0.4, 0.4
|
80
|
+
end
|
81
|
+
|
82
|
+
# generate set of thumbnails from image data with additional edits
|
83
|
+
thumbnails = HTTPThumbnailerClient.new('http://localhost:3100').thumbnail(data) do
|
84
|
+
thumbnail 'crop', 60, 30, 'jpeg' do
|
85
|
+
edit 'rotate', 90
|
86
|
+
end
|
87
|
+
thumbnail 'crop', 80, 80, 'png' do
|
88
|
+
edit 'rotate', 270
|
89
|
+
edit 'pixelate', 0.3, 0.2, 0.4, 0.4
|
90
|
+
end
|
91
|
+
thumbnail 'pad', 40, 40, 'png'
|
92
|
+
end
|
68
93
|
```
|
69
94
|
|
70
95
|
For more details see RSpec for [single thumbnail API](http://github.com/jpastuszek/httpthumbnailer-client/blob/master/spec/thumbnail_spec.rb) and [multipart API](http://github.com/jpastuszek/httpthumbnailer-client/blob/master/spec/thumbnails_spec.rb).
|
96
|
+
Note that when providing only one thumbnail specification using multipart API style the client will actually use single thumbnail API instead.
|
71
97
|
|
72
98
|
### CLI tool
|
73
99
|
|
@@ -85,10 +111,13 @@ cat image.jpg | httpthumbnailer-client -t crop,100,200,png > thumbnail.png
|
|
85
111
|
|
86
112
|
# generate multiple thumbnails
|
87
113
|
cat image.jpg | httpthumbnailer-client -t crop,100,200,jpeg,quality:100 -t pad,200,200,png thumbnail1.jpg thumbnail2.png
|
114
|
+
|
115
|
+
# generate thumbnail with edits
|
116
|
+
cat image.jpg | httpthumbnailer-client -t 'fit,280,280,png!pixelate,0.3,0.2,0.4,0.4!rectangle,0.04,0.8,0.92,0.17,color:blue' > thumbnail.png
|
88
117
|
```
|
89
118
|
|
90
119
|
## Contributing to HTTP Thubnailer Client
|
91
|
-
|
120
|
+
|
92
121
|
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
|
93
122
|
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
|
94
123
|
* Fork the project
|
@@ -99,6 +128,5 @@ cat image.jpg | httpthumbnailer-client -t crop,100,200,jpeg,quality:100 -t pad,2
|
|
99
128
|
|
100
129
|
## Copyright
|
101
130
|
|
102
|
-
Copyright (c) 2013 Jakub Pastuszek. See LICENSE.txt for
|
103
|
-
further details.
|
131
|
+
Copyright (c) 2013 - 2015 Jakub Pastuszek. See LICENSE.txt for further details.
|
104
132
|
|
data/Rakefile
CHANGED
@@ -18,9 +18,13 @@ Jeweler::Tasks.new do |gem|
|
|
18
18
|
gem.homepage = "http://github.com/jpastuszek/httpthumbnailer-client"
|
19
19
|
gem.license = "MIT"
|
20
20
|
gem.summary = %Q{API client for httpthumbniler server}
|
21
|
-
gem.description = %Q{
|
21
|
+
gem.description = %Q{Ruby client for HTTP API server for image thumbnailing, editing and format conversion.}
|
22
22
|
gem.email = "jpastuszek@gmail.com"
|
23
23
|
gem.authors = ["Jakub Pastuszek"]
|
24
|
+
gem.files.exclude "features/**/*"
|
25
|
+
gem.files.exclude "spec/**/*"
|
26
|
+
gem.files.exclude "*.gemspec"
|
27
|
+
gem.files.exclude ".rspec"
|
24
28
|
# dependencies defined in Gemfile
|
25
29
|
end
|
26
30
|
Jeweler::RubygemsDotOrgTasks.new
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.
|
1
|
+
1.3.0
|
data/bin/httpthumbnailer-client
CHANGED
@@ -5,20 +5,6 @@ require 'pathname'
|
|
5
5
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
6
6
|
require 'httpthumbnailer-client'
|
7
7
|
|
8
|
-
def parse_spec(spec)
|
9
|
-
method, width, height, format, *options = *spec.split(',')
|
10
|
-
fail "missing thumbnail specification argument in: #{spec}" unless method and width and height and format
|
11
|
-
|
12
|
-
opts = {}
|
13
|
-
options.each do |option|
|
14
|
-
key, value = option.split(':')
|
15
|
-
fail "missing option key or value in specifiaction: #{spec}" unless key and value
|
16
|
-
opts[key] = value
|
17
|
-
end
|
18
|
-
|
19
|
-
[method, width, height, format, opts]
|
20
|
-
end
|
21
|
-
|
22
8
|
settings = CLI.new do
|
23
9
|
description 'Client to httpthumbnailer image scaling and conversion HTTP API server'
|
24
10
|
stdin :image_data,
|
@@ -43,7 +29,9 @@ settings = CLI.new do
|
|
43
29
|
end.parse! do |settings|
|
44
30
|
STDERR.puts "Warning: there is more thumbnail specifications given than output file names; some thumbnails will not be saved" if settings.thumbnail.length > settings.output_file_name.length
|
45
31
|
fail 'only one thumbnail can be written to STDOUT' if settings.output_file_name.count('-') > 1
|
46
|
-
settings.thumbnail
|
32
|
+
settings.thumbnail.map! do |spec|
|
33
|
+
HTTPThumbnailerClient::ThumbnailSpec.from_string(spec)
|
34
|
+
end
|
47
35
|
end
|
48
36
|
|
49
37
|
thumbnailer = HTTPThumbnailerClient.new("http://#{settings.host}:#{settings.port}")
|
@@ -56,20 +44,14 @@ if settings.thumbnail.empty?
|
|
56
44
|
exit
|
57
45
|
end
|
58
46
|
|
59
|
-
thumbnails =
|
60
|
-
[thumbnailer.thumbnail(settings.stdin.read, *settings.thumbnail.first)]
|
61
|
-
else
|
62
|
-
thumbnailer.thumbnail(settings.stdin.read) do
|
63
|
-
settings.thumbnail.each do |spec|
|
64
|
-
thumbnail *spec
|
65
|
-
end
|
66
|
-
end
|
67
|
-
end
|
47
|
+
thumbnails = thumbnailer.thumbnail(settings.stdin.read, *settings.thumbnail)
|
68
48
|
|
69
|
-
thumbnails.zip(settings.output_file_name).select do |thumbnail, out_file|
|
70
|
-
out_file
|
49
|
+
thumbnails.zip(settings.output_file_name).select do |thumbnail, out_file|
|
50
|
+
out_file
|
71
51
|
end.each do |thumbnail, out_file|
|
72
|
-
if
|
52
|
+
if thumbnail.kind_of? HTTPThumbnailerClient::HTTPThumbnailerClientError
|
53
|
+
STDERR.puts "Error: thumbnailing for '#{out_file}' failed: #{thumbnail}"
|
54
|
+
elsif out_file == '-'
|
73
55
|
STDOUT.write thumbnail.data
|
74
56
|
else
|
75
57
|
File.open(out_file, 'w') do |file|
|
@@ -2,6 +2,7 @@ require 'httpclient'
|
|
2
2
|
require 'ostruct'
|
3
3
|
require 'json'
|
4
4
|
require 'multipart_parser/reader'
|
5
|
+
require 'httpthumbnailer-client/uri_builder'
|
5
6
|
|
6
7
|
class HTTPThumbnailerClient
|
7
8
|
HTTPThumbnailerClientError = Class.new(ArgumentError)
|
@@ -56,48 +57,6 @@ class HTTPThumbnailerClient
|
|
56
57
|
UnknownResponseType = Class.new HTTPThumbnailerClientError
|
57
58
|
InvalidMultipartResponseError = Class.new HTTPThumbnailerClientError
|
58
59
|
|
59
|
-
class URIBuilder
|
60
|
-
def initialize(service_uri, &block)
|
61
|
-
@specs = []
|
62
|
-
@service_uri = service_uri
|
63
|
-
instance_eval &block if block
|
64
|
-
end
|
65
|
-
|
66
|
-
def get
|
67
|
-
"#{@service_uri}/#{@specs.join('/')}"
|
68
|
-
end
|
69
|
-
|
70
|
-
def self.thumbnail(*spec)
|
71
|
-
self.new('/thumbnail').thumbnail(*spec).get
|
72
|
-
end
|
73
|
-
|
74
|
-
def self.thumbnails(&block)
|
75
|
-
self.new('/thumbnails', &block).get
|
76
|
-
end
|
77
|
-
|
78
|
-
def thumbnail(method, width, height, format = 'jpeg', options = {})
|
79
|
-
width = width.to_s
|
80
|
-
height = height.to_s
|
81
|
-
|
82
|
-
args = []
|
83
|
-
args << method.to_s
|
84
|
-
width !~ /^([0-9]+|input)$/ and raise InvalidThumbnailSpecificationError.new("bad dimension value: #{width}")
|
85
|
-
args << width
|
86
|
-
height !~ /^([0-9]+|input)$/ and raise InvalidThumbnailSpecificationError.new("bad dimension value: #{height}")
|
87
|
-
args << height
|
88
|
-
args << format.to_s
|
89
|
-
|
90
|
-
options.keys.sort{|a, b| a.to_s <=> b.to_s}.each do |key|
|
91
|
-
raise InvalidThumbnailSpecificationError.new("empty option key for value '#{options[key]}'") if key.nil? || key.to_s.empty?
|
92
|
-
raise InvalidThumbnailSpecificationError.new("missing option value for key '#{key}'") if options[key].nil? || options[key].to_s.empty?
|
93
|
-
args << "#{key}:#{options[key]}"
|
94
|
-
end
|
95
|
-
|
96
|
-
@specs << args.join(',')
|
97
|
-
self
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
60
|
class Thumbnail
|
102
61
|
def initialize(mime_type, width, height, data)
|
103
62
|
@mime_type = mime_type
|
@@ -154,10 +113,14 @@ class HTTPThumbnailerClient
|
|
154
113
|
attr_reader :keep_alive
|
155
114
|
|
156
115
|
def thumbnail(data, *spec, &block)
|
157
|
-
|
116
|
+
return_array = true
|
117
|
+
uri = if spec.empty?
|
158
118
|
URIBuilder.thumbnails(&block)
|
119
|
+
elsif spec.all?{|s| s.kind_of? ThumbnailSpec}
|
120
|
+
URIBuilder.specs(*spec)
|
159
121
|
else
|
160
|
-
|
122
|
+
return_array = false # for .thumbnail(data, 'crop', 60, 30, 'jpeg') kind of usage
|
123
|
+
URIBuilder.thumbnail(*spec, &block)
|
161
124
|
end
|
162
125
|
|
163
126
|
response = @client.request('PUT', "#{@server_url}#{uri}", nil, data, {'Content-Type' => 'image/autodetect'}.merge(@headers))
|
@@ -169,7 +132,8 @@ class HTTPThumbnailerClient
|
|
169
132
|
when 'text/plain'
|
170
133
|
raise error_for_status(response.status, response.body)
|
171
134
|
when /^image\//
|
172
|
-
Thumbnail.new(content_type, response.headers['X-Image-Width'], response.headers['X-Image-Height'], response.body)
|
135
|
+
thumb = Thumbnail.new(content_type, response.headers['X-Image-Width'], response.headers['X-Image-Height'], response.body)
|
136
|
+
return_array ? [thumb] : thumb
|
173
137
|
when /^multipart\/mixed/
|
174
138
|
parts = []
|
175
139
|
parser = MultipartParser::Reader.new(MultipartParser::Reader.extract_boundary_value(content_type))
|
@@ -0,0 +1,178 @@
|
|
1
|
+
class HTTPThumbnailerClient
|
2
|
+
#TODO: support for escaping of ! and ,
|
3
|
+
class ThumbnailSpec
|
4
|
+
class Builder
|
5
|
+
def initialize(method, width, height, format = 'jpeg', options = {}, &block)
|
6
|
+
@spec = ThumbnailSpec.new(method, width.to_s, height.to_s, format, options)
|
7
|
+
instance_eval(&block) if block
|
8
|
+
end
|
9
|
+
|
10
|
+
def edit(name, *args)
|
11
|
+
edit_options, args = *args.partition{|e| e.kind_of? Hash}
|
12
|
+
edit_options = edit_options.each.with_object({}) do |opt, hash|
|
13
|
+
hash.merge! opt
|
14
|
+
end
|
15
|
+
|
16
|
+
edit_spec ThumbnailSpec::EditSpec.new(name, args, edit_options)
|
17
|
+
self
|
18
|
+
end
|
19
|
+
|
20
|
+
def edit_spec(spec)
|
21
|
+
@spec.edits << spec
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
attr_reader :spec
|
26
|
+
|
27
|
+
def to_s
|
28
|
+
@spec.to_s
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class InvalidFormatError < ArgumentError
|
33
|
+
def for_edit(name)
|
34
|
+
exception "#{message} for edit '#{name}'"
|
35
|
+
end
|
36
|
+
|
37
|
+
def in_spec(spec)
|
38
|
+
exception "#{message} in spec '#{spec}'"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class MissingArgumentError < InvalidFormatError
|
43
|
+
def initialize(argument)
|
44
|
+
super "missing #{argument} argument"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class InvalidArgumentValueError < InvalidFormatError
|
49
|
+
def initialize(name, value, reason)
|
50
|
+
super "#{name} value '#{value}' is not #{reason}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
class MissingOptionKeyValuePairError < InvalidFormatError
|
55
|
+
def initialize(index)
|
56
|
+
super "missing key-value pair on position #{index + 1}"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class MissingOptionKeyNameError < InvalidFormatError
|
61
|
+
def initialize(value)
|
62
|
+
super "missing option key name for value '#{value}'"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
class MissingOptionKeyValueError < InvalidFormatError
|
67
|
+
def initialize(key)
|
68
|
+
super "missing option value for key '#{key}'"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
class EditSpec
|
73
|
+
attr_reader :name, :args, :options
|
74
|
+
|
75
|
+
def self.from_string(string)
|
76
|
+
args = HTTPThumbnailerClient::ThumbnailSpec.split_args(string)
|
77
|
+
args, options = HTTPThumbnailerClient::ThumbnailSpec.partition_args_options(args)
|
78
|
+
name = args.shift
|
79
|
+
|
80
|
+
begin
|
81
|
+
options = HTTPThumbnailerClient::ThumbnailSpec.parse_options(options)
|
82
|
+
rescue InvalidFormatError => error
|
83
|
+
raise error.for_edit(name)
|
84
|
+
end
|
85
|
+
new(name, args, options)
|
86
|
+
end
|
87
|
+
|
88
|
+
def initialize(name, args, options = {})
|
89
|
+
name.nil? or name.empty? and raise MissingArgumentError, 'edit name'
|
90
|
+
|
91
|
+
@name = name
|
92
|
+
@args = args
|
93
|
+
@options = options
|
94
|
+
end
|
95
|
+
|
96
|
+
def to_s
|
97
|
+
begin
|
98
|
+
[@name, *@args, *HTTPThumbnailerClient::ThumbnailSpec.options_to_s(@options)].join(',')
|
99
|
+
rescue InvalidFormatError => error
|
100
|
+
raise error.for_edit(name)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
attr_reader :method, :width, :height, :format, :options, :edits
|
106
|
+
|
107
|
+
def self.from_string(string)
|
108
|
+
edits = split_edits(string)
|
109
|
+
spec = edits.shift
|
110
|
+
args = split_args(spec)
|
111
|
+
args, options = partition_args_options(args)
|
112
|
+
method, width, height, format = *args.shift(4) # ignore extra args
|
113
|
+
|
114
|
+
options = parse_options(options)
|
115
|
+
edits = edits.map{|e| EditSpec.from_string(e)}
|
116
|
+
|
117
|
+
new(method, width, height, format, options, edits)
|
118
|
+
rescue InvalidFormatError => error
|
119
|
+
raise error.in_spec(string)
|
120
|
+
end
|
121
|
+
|
122
|
+
def initialize(method, width, height, format, options = {}, edits = [])
|
123
|
+
method.nil? or method.empty? and raise MissingArgumentError, 'method'
|
124
|
+
width.nil? or width.empty? and raise MissingArgumentError, 'width'
|
125
|
+
height.nil? or height.empty? and raise MissingArgumentError, 'height'
|
126
|
+
format.nil? or format.empty? and raise MissingArgumentError, 'format'
|
127
|
+
|
128
|
+
width !~ /^([0-9]+|input)$/ and raise InvalidArgumentValueError.new('width', width, "an integer or 'input'")
|
129
|
+
height !~ /^([0-9]+|input)$/ and raise InvalidArgumentValueError.new('height', height, "an integer or 'input'")
|
130
|
+
@method = method
|
131
|
+
@width = width
|
132
|
+
@height = height
|
133
|
+
@format = format
|
134
|
+
@options = options
|
135
|
+
@edits = edits
|
136
|
+
end
|
137
|
+
|
138
|
+
def to_s
|
139
|
+
[[@method, @width, @height, @format, *self.class.options_to_s(@options)].join(','), *@edits.map(&:to_s)].join('!')
|
140
|
+
end
|
141
|
+
|
142
|
+
def self.split_edits(string)
|
143
|
+
string.split('!')
|
144
|
+
end
|
145
|
+
|
146
|
+
def self.split_args(string)
|
147
|
+
string.split(',')
|
148
|
+
end
|
149
|
+
|
150
|
+
def self.partition_args_options(args)
|
151
|
+
options = args.drop_while{|a| not a.include?(':')}
|
152
|
+
args = args.take_while{|a| not a.include?(':')}
|
153
|
+
[args, options]
|
154
|
+
end
|
155
|
+
|
156
|
+
def self.parse_options(options)
|
157
|
+
Hash[options.map.with_index do |pair, index|
|
158
|
+
pair.empty? and raise MissingOptionKeyValuePairError, index
|
159
|
+
pair.split(':', 2)
|
160
|
+
end].tap do |map|
|
161
|
+
map.each do |key, value|
|
162
|
+
key.nil? or key.empty? and raise MissingOptionKeyNameError, value
|
163
|
+
value.nil? or value.empty? and raise MissingOptionKeyValueError, key
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def self.options_to_s(options)
|
169
|
+
options.sort_by{|k,v| k}.map do |key, value|
|
170
|
+
raise MissingOptionKeyNameError, value if key.nil? or key.to_s.empty?
|
171
|
+
raise MissingOptionKeyValueError, key if value.nil? or value.to_s.empty?
|
172
|
+
key = key.to_s.gsub('_', '-') if key.kind_of? Symbol
|
173
|
+
"#{key}:#{value}"
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|