httpimagestore 1.5.0 → 1.6.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.
@@ -62,7 +62,7 @@
62
62
  <orderEntry type="library" scope="PROVIDED" name="ruby-ip (v0.9.1, RVM: ruby-1.9.3-p429) [gem]" level="application" />
63
63
  <orderEntry type="library" scope="PROVIDED" name="sdl4r (v0.9.11, RVM: ruby-1.9.3-p429) [gem]" level="application" />
64
64
  <orderEntry type="library" scope="PROVIDED" name="unicorn (v4.6.3, RVM: ruby-1.9.3-p429) [gem]" level="application" />
65
- <orderEntry type="library" scope="PROVIDED" name="unicorn-cuba-base (v1.1.1, RVM: ruby-1.9.3-p429) [gem]" level="application" />
65
+ <orderEntry type="library" scope="PROVIDED" name="unicorn-cuba-base (v1.1.2, RVM: ruby-1.9.3-p429) [gem]" level="application" />
66
66
  <orderEntry type="library" scope="PROVIDED" name="uuidtools (v2.1.4, RVM: ruby-1.9.3-p429) [gem]" level="application" />
67
67
  </component>
68
68
  </module>
data/Gemfile.lock CHANGED
@@ -39,14 +39,14 @@ GEM
39
39
  nokogiri (~> 1.5.2)
40
40
  oauth2
41
41
  hashie (2.0.5)
42
- highline (1.6.19)
42
+ highline (1.6.20)
43
43
  httpauth (0.2.0)
44
44
  httpclient (2.3.4.1)
45
45
  httpthumbnailer-client (1.1.1)
46
46
  cli (~> 1.3)
47
47
  httpclient (>= 2.3)
48
48
  multipart-parser (~> 0.1.1)
49
- jeweler (1.8.7)
49
+ jeweler (1.8.8)
50
50
  builder
51
51
  bundler (~> 1.0)
52
52
  git (>= 1.2.5)
@@ -55,13 +55,13 @@ GEM
55
55
  nokogiri (= 1.5.10)
56
56
  rake
57
57
  rdoc
58
- json (1.8.0)
58
+ json (1.8.1)
59
59
  jwt (0.1.8)
60
60
  multi_json (>= 1.5)
61
61
  kgio (2.8.1)
62
62
  mime-types (1.25)
63
63
  msgpack (0.5.5)
64
- multi_json (1.8.0)
64
+ multi_json (1.8.2)
65
65
  multi_test (0.0.2)
66
66
  multi_xml (0.5.5)
67
67
  multipart-parser (0.1.1)
data/README.md CHANGED
@@ -17,6 +17,10 @@ It is using [HTTP Thumbnailer](https://github.com/jpastuszek/httpthumbnailer) as
17
17
 
18
18
  ## Changelog
19
19
 
20
+ ### 1.6.0
21
+ * `output_store_path` and `output_store_url` `path` argument support
22
+ * encoding resulting file storage URL
23
+
20
24
  ### 1.5.0
21
25
  * `output_ok` and `output_text` support
22
26
  * named captures regexp matcher support
@@ -447,6 +451,10 @@ Arguments:
447
451
 
448
452
  1. image names - names of images to output storage path for in order
449
453
 
454
+ Options:
455
+
456
+ * `path` - name of predefined path that will be used to generate output path; `#{path}` variable and all derivative will be replaced with given image storage path; if not specified the actual storage path will be provided
457
+
450
458
  Example:
451
459
 
452
460
  ```sdl
@@ -498,6 +506,10 @@ Arguments:
498
506
 
499
507
  1. image names - names of images
500
508
 
509
+ Options:
510
+
511
+ * `path` - name of predefined path that will be used to generate output URL path part; `#{path}` variable and all derivative will be replaced with given image storage path; if not specified the original URL will be provided
512
+
501
513
  Example:
502
514
 
503
515
  ```sdl
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.5.0
1
+ 1.6.0
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "httpimagestore"
8
- s.version = "1.5.0"
8
+ s.version = "1.6.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Jakub Pastuszek"]
12
- s.date = "2013-10-22"
12
+ s.date = "2013-11-04"
13
13
  s.description = "Thumbnails images using httpthumbnailer and stored data on HTTP server (S3)"
14
14
  s.email = "jpastuszek@gmail.com"
15
15
  s.executables = ["httpimagestore"]
@@ -1,6 +1,7 @@
1
1
  require 'httpimagestore/configuration/path'
2
2
  require 'httpimagestore/configuration/handler'
3
3
  require 'pathname'
4
+ require 'uri'
4
5
 
5
6
  module Configuration
6
7
  class FileStorageOutsideOfRootDirError < ConfigurationError
@@ -18,7 +19,7 @@ module Configuration
18
19
  class FileSourceStoreBase < SourceStoreBase
19
20
  extend Stats
20
21
  def_stats(
21
- :total_file_store,
22
+ :total_file_store,
22
23
  :total_file_store_bytes,
23
24
  :total_file_source,
24
25
  :total_file_source_bytes
@@ -31,11 +32,11 @@ module Configuration
31
32
  matcher = InclusionMatcher.new(image_name, if_image_name_on)
32
33
 
33
34
  self.new(
34
- configuration.global,
35
- image_name,
35
+ configuration.global,
36
+ image_name,
36
37
  matcher,
37
- root_dir,
38
- path_spec
38
+ root_dir,
39
+ path_spec
39
40
  )
40
41
  end
41
42
 
@@ -80,7 +81,7 @@ module Configuration
80
81
  FileSourceStoreBase.stats.incr_total_file_source_bytes(data.bytesize)
81
82
 
82
83
  image = Image.new(data)
83
- image.source_url = "file://#{rendered_path}"
84
+ image.source_url = "file://#{URI.encode(rendered_path.to_s)}"
84
85
  image
85
86
  rescue Errno::ENOENT
86
87
  raise NoSuchFileError.new(image_name, rendered_path)
@@ -89,7 +90,7 @@ module Configuration
89
90
  end
90
91
  end
91
92
  Handler::register_node_parser FileSource
92
-
93
+
93
94
  class FileStore < FileSourceStoreBase
94
95
  include ClassLogging
95
96
 
@@ -105,7 +106,7 @@ module Configuration
105
106
  get_named_image_for_storage(request_state) do |image_name, image, rendered_path|
106
107
  storage_path = storage_path(rendered_path)
107
108
 
108
- image.store_url = "file://#{rendered_path.to_s}"
109
+ image.store_url = "file://#{URI.encode(rendered_path.to_s)}"
109
110
 
110
111
  log.info "storing '#{image_name}' in file '#{storage_path}' (#{image.data.length} bytes)"
111
112
  storage_path.open('wb'){|io| io.write image.data}
@@ -1,5 +1,6 @@
1
1
  require 'httpimagestore/configuration/handler'
2
2
  require 'httpimagestore/ruby_string_template'
3
+ require 'uri'
3
4
 
4
5
  module Configuration
5
6
  class StorePathNotSetForImage < ConfigurationError
@@ -62,29 +63,59 @@ module Configuration
62
63
  Handler::register_node_parser OutputText
63
64
 
64
65
  class OutputMultiBase
65
- class ImageName < String
66
+ class OutputSpec
66
67
  include ConditionalInclusion
68
+ attr_reader :image_name
69
+ attr_reader :path_spec
67
70
 
68
- def initialize(name, matcher)
69
- super name
71
+ def initialize(global, image_name, path_spec, matcher)
72
+ @global = global
73
+ @image_name = image_name
74
+ @path_spec = path_spec
70
75
  inclusion_matcher matcher
71
76
  end
77
+
78
+ def store_path(request_state)
79
+ store_path = request_state.images[@image_name].store_path or raise StorePathNotSetForImage.new(@image_name)
80
+ return store_path unless @path_spec
81
+ rendered_path(store_path, request_state)
82
+ end
83
+
84
+ def store_url(request_state)
85
+ store_url = request_state.images[@image_name].store_url or raise StoreURLNotSetForImage.new(@image_name)
86
+ return store_url unless @path_spec
87
+ uri = URI(store_url)
88
+ uri.path = '/' + URI.encode(rendered_path(URI.decode(uri.path), request_state))
89
+ uri
90
+ end
91
+
92
+ private
93
+
94
+ def rendered_path(store_path, request_state)
95
+ path = @global.paths[@path_spec]
96
+ locals = {
97
+ path: store_path,
98
+ image_name: @image_name
99
+ }
100
+ Pathname.new(path.render(request_state.with_locals(locals))).cleanpath.to_s
101
+ end
72
102
  end
73
103
 
74
104
  def self.parse(configuration, node)
75
105
  nodes = node.values.empty? ? node.children : [node]
76
- names = nodes.map do |node|
106
+ output_specs = nodes.map do |node|
77
107
  image_name = node.grab_values('image name').first
78
- matcher = InclusionMatcher.new(image_name, node.grab_attributes('if-image-name-on').first)
79
- ImageName.new(image_name, matcher)
108
+ path_spec, if_image_name_on = *node.grab_attributes('path', 'if-image-name-on')
109
+ matcher = InclusionMatcher.new(image_name, if_image_name_on)
110
+ OutputSpec.new(configuration.global, image_name, path_spec, matcher)
80
111
  end
81
112
 
82
113
  configuration.output and raise StatementCollisionError.new(node, 'output')
83
- configuration.output = self.new(names)
114
+ configuration.output = self.new(output_specs)
84
115
  end
85
116
 
86
- def initialize(names)
87
- @names = names
117
+ def initialize(output_specs)
118
+ @output_specs = output_specs
88
119
  end
89
120
  end
90
121
  Handler::register_node_parser OutputOK
@@ -133,10 +164,10 @@ module Configuration
133
164
  end
134
165
 
135
166
  def realize(request_state)
136
- paths = @names.select do |name|
137
- name.included?(request_state)
138
- end.map do |name|
139
- request_state.images[name].store_path or raise StorePathNotSetForImage.new(name)
167
+ paths = @output_specs.select do |output_spec|
168
+ output_spec.included?(request_state)
169
+ end.map do |output_spec|
170
+ output_spec.store_path(request_state)
140
171
  end
141
172
 
142
173
  request_state.output do
@@ -152,10 +183,10 @@ module Configuration
152
183
  end
153
184
 
154
185
  def realize(request_state)
155
- urls = @names.select do |name|
156
- name.included?(request_state)
157
- end.map do |name|
158
- request_state.images[name].store_url or raise StoreURLNotSetForImage.new(name)
186
+ urls = @output_specs.select do |output_spec|
187
+ output_spec.included?(request_state)
188
+ end.map do |output_spec|
189
+ output_spec.store_url(request_state)
159
190
  end
160
191
 
161
192
  request_state.output do
@@ -171,13 +171,27 @@ describe Configuration do
171
171
  Pathname.new("/tmp/test.out2")
172
172
  end
173
173
 
174
+ let :test_file do
175
+ Pathname.new('/tmp/abc/test.out')
176
+ end
177
+
178
+ let :space_test_file do
179
+ Pathname.new('/tmp/abc/t e s t.out')
180
+ end
181
+
174
182
  before :each do
183
+ test_file.dirname.mkdir
184
+ test_file.open('w'){|io| io.write('abc')}
185
+ space_test_file.open('w'){|io| io.write('abc')}
175
186
  in_file.open('w'){|io| io.write('abc')}
176
187
  out_file.unlink if out_file.exist?
177
188
  out2_file.unlink if out2_file.exist?
178
189
  end
179
190
 
180
191
  after :each do
192
+ test_file.exist? and test_file.unlink
193
+ space_test_file.exist? and space_test_file.unlink
194
+ test_file.dirname.exist? and test_file.dirname.rmdir
181
195
  out_file.unlink if out_file.exist?
182
196
  out2_file.unlink if out2_file.exist?
183
197
  in_file.unlink
@@ -284,6 +298,62 @@ describe Configuration do
284
298
  end
285
299
  end
286
300
 
301
+ describe 'custom formatting' do
302
+ it 'should provide formatted file store path' do
303
+ subject = Configuration.read(<<-'EOF')
304
+ path "out" "abc/test.out"
305
+ path "formatted" "hello/#{dirname}/world/#{basename}-xyz.#{extension}"
306
+
307
+ post "single" {
308
+ store_file "input" root="/tmp" path="out"
309
+
310
+ output_store_path "input" path="formatted"
311
+ }
312
+ EOF
313
+
314
+ subject.handlers[0].sources[0].realize(state)
315
+ subject.handlers[0].stores[0].realize(state)
316
+ subject.handlers[0].output.realize(state)
317
+
318
+ env.instance_eval &state.output_callback
319
+ env.res['Content-Type'].should == 'text/plain'
320
+ env.res.data.should == "hello/abc/world/test-xyz.out\r\n"
321
+ end
322
+
323
+ it 'should provide formatted file store path for each path' do
324
+ subject = Configuration.read(<<-'EOF')
325
+ path "in" "test.in"
326
+ path "out" "abc/test.out"
327
+ path "out2" "test.out2"
328
+
329
+ path "formatted" "hello/#{dirname}/world/#{basename}-xyz.#{extension}"
330
+ path "formatted2" "#{image_digest}.#{extension}"
331
+
332
+ post "single" {
333
+ source_file "original" root="/tmp" path="in"
334
+
335
+ store_file "input" root="/tmp" path="out"
336
+ store_file "original" root="/tmp" path="out2"
337
+
338
+ output_store_path {
339
+ "input" path="formatted"
340
+ "original" path="formatted2"
341
+ }
342
+ }
343
+ EOF
344
+
345
+ subject.handlers[0].sources[0].realize(state)
346
+ subject.handlers[0].sources[1].realize(state)
347
+ subject.handlers[0].stores[0].realize(state)
348
+ subject.handlers[0].stores[1].realize(state)
349
+ subject.handlers[0].output.realize(state)
350
+
351
+ env.instance_eval &state.output_callback
352
+ env.res['Content-Type'].should == 'text/plain'
353
+ env.res.data.should == "hello/abc/world/test-xyz.out\r\nba7816bf8f01cfea.out2\r\n"
354
+ end
355
+ end
356
+
287
357
  describe 'error handling' do
288
358
  it 'should raise StorePathNotSetForImage for output of not stored image' do
289
359
  subject = Configuration.read(<<-EOF)
@@ -402,6 +472,56 @@ describe Configuration do
402
472
  end
403
473
  end
404
474
 
475
+ describe 'custom formatting' do
476
+ it 'should provide formatted file store URL' do
477
+ subject = Configuration.read(<<-'EOF')
478
+ path "out" "abc/test.out"
479
+
480
+ path "formatted" "hello/#{dirname}/world/#{basename}-xyz.#{extension}"
481
+
482
+ post "single" {
483
+ store_file "input" root="/tmp" path="out"
484
+
485
+ output_store_url "input" path="formatted"
486
+ }
487
+ EOF
488
+
489
+ subject.handlers[0].sources[0].realize(state)
490
+ subject.handlers[0].stores[0].realize(state)
491
+ subject.handlers[0].output.realize(state)
492
+
493
+ env.instance_eval &state.output_callback
494
+ env.res['Content-Type'].should == 'text/uri-list'
495
+ env.res.data.should == "file://abc/hello/world/test-xyz.out\r\n"
496
+ end
497
+ end
498
+
499
+ describe 'URL encoding' do
500
+ it 'should provide properly encoded file store URL' do
501
+ subject = Configuration.read(<<-'EOF')
502
+ path "out" "abc/t e s t.out"
503
+ path "formatted" "hello/#{dirname}/world/#{basename}-xyz.#{extension}"
504
+
505
+ post "single" {
506
+ store_file "input" root="/tmp" path="out"
507
+
508
+ output_store_url {
509
+ "input"
510
+ "input" path="formatted"
511
+ }
512
+ }
513
+ EOF
514
+
515
+ subject.handlers[0].sources[0].realize(state)
516
+ subject.handlers[0].stores[0].realize(state)
517
+ subject.handlers[0].output.realize(state)
518
+
519
+ env.instance_eval &state.output_callback
520
+ env.res['Content-Type'].should == 'text/uri-list'
521
+ env.res.data.should == "file://abc/t%20e%20s%20t.out\r\nfile://abc/hello/world/t%20e%20s%20t-xyz.out\r\n"
522
+ end
523
+ end
524
+
405
525
  describe 'error handling' do
406
526
  it 'should raise StoreURLNotSetForImage for output of not stored image' do
407
527
  subject = Configuration.read(<<-EOF)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: httpimagestore
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.0
4
+ version: 1.6.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-10-22 00:00:00.000000000 Z
12
+ date: 2013-11-04 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: unicorn-cuba-base
@@ -324,7 +324,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
324
324
  version: '0'
325
325
  segments:
326
326
  - 0
327
- hash: 1314462780475505822
327
+ hash: 3462476370179380761
328
328
  required_rubygems_version: !ruby/object:Gem::Requirement
329
329
  none: false
330
330
  requirements: