dassets 0.14.2 → 0.15.1

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.
Files changed (43) hide show
  1. checksums.yaml +7 -7
  2. data/Gemfile +5 -1
  3. data/README.md +15 -17
  4. data/dassets.gemspec +14 -9
  5. data/lib/dassets.rb +51 -13
  6. data/lib/dassets/asset_file.rb +27 -24
  7. data/lib/dassets/cache.rb +27 -33
  8. data/lib/dassets/config.rb +55 -50
  9. data/lib/dassets/engine.rb +11 -23
  10. data/lib/dassets/file_store.rb +27 -27
  11. data/lib/dassets/server.rb +27 -27
  12. data/lib/dassets/server/request.rb +44 -41
  13. data/lib/dassets/server/response.rb +101 -80
  14. data/lib/dassets/source.rb +15 -9
  15. data/lib/dassets/source_file.rb +103 -82
  16. data/lib/dassets/source_proxy.rb +36 -20
  17. data/lib/dassets/version.rb +3 -1
  18. data/test/helper.rb +31 -25
  19. data/test/support/app.rb +5 -5
  20. data/test/support/empty/{.gitkeep → .keep} +0 -0
  21. data/test/support/factory.rb +3 -2
  22. data/test/support/{public/nested/file3-d41d8cd98f00b204e9800998ecf8427e.txt → linked_source_files/linked_file.txt} +0 -0
  23. data/test/support/source_files/linked +1 -0
  24. data/test/support/source_files/linked_file2.txt +1 -0
  25. data/test/system/rack_tests.rb +65 -61
  26. data/test/unit/asset_file_tests.rb +69 -61
  27. data/test/unit/cache_tests.rb +15 -34
  28. data/test/unit/config_tests.rb +59 -52
  29. data/test/unit/dassets_tests.rb +31 -24
  30. data/test/unit/engine_tests.rb +9 -43
  31. data/test/unit/file_store_tests.rb +44 -31
  32. data/test/unit/server/request_tests.rb +57 -59
  33. data/test/unit/server/response_tests.rb +82 -82
  34. data/test/unit/server_tests.rb +5 -9
  35. data/test/unit/source_file_tests.rb +80 -73
  36. data/test/unit/source_proxy_tests.rb +84 -90
  37. data/test/unit/source_tests.rb +66 -50
  38. data/tmp/.gitkeep +0 -0
  39. metadata +92 -72
  40. data/.gitignore +0 -19
  41. data/test/support/public/file2-9bbe1047cffbb590f59e0e5aeff46ae4.txt +0 -1
  42. data/test/support/public/grumpy_cat-b0d1f399a916f7a25c4c0f693c619013.jpg +0 -0
  43. data/test/support/public/nested/a-thing.txt-7413d18f2eba9c695a880aff67fde135.no-use +0 -4
@@ -1,31 +1,19 @@
1
- module Dassets
1
+ # frozen_string_literal: true
2
2
 
3
- class Engine
3
+ module Dassets; end
4
4
 
5
- attr_reader :opts
6
-
7
- def initialize(opts=nil)
8
- @opts = opts || {}
9
- end
10
-
11
- def ext(input_ext)
12
- raise NotImplementedError
13
- end
14
-
15
- def compile(input)
16
- raise NotImplementedError
17
- end
5
+ class Dassets::Engine
6
+ attr_reader :opts
18
7
 
8
+ def initialize(opts = nil)
9
+ @opts = opts || {}
19
10
  end
20
11
 
21
- class NullEngine < Engine
22
- def ext(input_ext)
23
- input_ext
24
- end
25
-
26
- def compile(input)
27
- input
28
- end
12
+ def ext(input_ext)
13
+ raise NotImplementedError
29
14
  end
30
15
 
16
+ def compile(input)
17
+ raise NotImplementedError
18
+ end
31
19
  end
@@ -1,38 +1,38 @@
1
- require 'thread'
1
+ # frozen_string_literal: true
2
2
 
3
- module Dassets
3
+ require "thread"
4
4
 
5
- class FileStore
6
- attr_reader :root
5
+ module Dassets; end
7
6
 
8
- def initialize(root)
9
- @root = root
10
- @save_mutex = ::Mutex.new
11
- end
12
-
13
- def save(url, &block)
14
- @save_mutex.synchronize do
15
- store_path(url).tap do |path|
16
- FileUtils.mkdir_p(File.dirname(path))
17
- File.open(path, "w"){ |f| f.write(block.call) }
18
- end
19
- end
20
- end
7
+ class Dassets::FileStore
8
+ attr_reader :root
21
9
 
22
- def store_path(url)
23
- File.join(@root, url)
24
- end
25
-
26
- class NullStore < FileStore
27
- def initialize
28
- super('')
29
- end
10
+ def initialize(root)
11
+ @root = root
12
+ @save_mutex = ::Mutex.new
13
+ end
30
14
 
31
- def save(url, &block)
32
- store_path(url) # no-op, just return the store path like the base does
15
+ def save(url_path, &block)
16
+ @save_mutex.synchronize do
17
+ store_path(url_path).tap do |path|
18
+ FileUtils.mkdir_p(File.dirname(path))
19
+ File.open(path, "w"){ |f| f.write(block.call) }
33
20
  end
34
21
  end
22
+ end
23
+
24
+ def store_path(url_path)
25
+ File.join(@root, url_path)
26
+ end
27
+ end
35
28
 
29
+ class Dassets::NullFileStore < Dassets::FileStore
30
+ def initialize
31
+ super("")
36
32
  end
37
33
 
34
+ def save(url_path)
35
+ # No-op, just return the store path like the base does.
36
+ store_path(url_path)
37
+ end
38
38
  end
@@ -1,37 +1,37 @@
1
- require 'dassets/server/request'
2
- require 'dassets/server/response'
1
+ # frozen_string_literal: true
2
+
3
+ require "dassets/server/request"
4
+ require "dassets/server/response"
3
5
 
4
6
  # Rack middleware for serving Dassets asset files
5
7
 
6
- module Dassets
7
- class Server
8
+ module Dassets; end
8
9
 
9
- def initialize(app)
10
- @app = app
11
- end
10
+ class Dassets::Server
11
+ def initialize(app)
12
+ @app = app
13
+ end
12
14
 
13
- # The Rack call interface. The receiver acts as a prototype and runs
14
- # each request in a clone object unless the +rack.run_once+ variable is
15
- # set in the environment. Ripped from:
16
- # http://github.com/rtomayko/rack-cache/blob/master/lib/rack/cache/context.rb
17
- def call(env)
18
- if env['rack.run_once']
19
- call! env
20
- else
21
- clone.call! env
22
- end
15
+ # The Rack call interface. The receiver acts as a prototype and runs
16
+ # each request in a clone object unless the +rack.run_once+ variable is
17
+ # set in the environment. Ripped from:
18
+ # http://github.com/rtomayko/rack-cache/blob/master/lib/rack/cache/context.rb
19
+ def call(env)
20
+ if env["rack.run_once"]
21
+ call! env
22
+ else
23
+ clone.call! env
23
24
  end
25
+ end
24
26
 
25
- # The real Rack call interface.
26
- # if an asset file is being requested, this is an endpoint - otherwise, call
27
- # on up to the app as normal
28
- def call!(env)
29
- if (request = Request.new(env)).for_asset_file?
30
- Response.new(env, request.asset_file).to_rack
31
- else
32
- @app.call(env)
33
- end
27
+ # The real Rack call interface.
28
+ # if an asset file is being requested, this is an endpoint - otherwise, call
29
+ # on up to the app as normal
30
+ def call!(env)
31
+ if (request = Request.new(env)).for_asset_file?
32
+ Response.new(env, request.asset_file).to_rack
33
+ else
34
+ @app.call(env)
34
35
  end
35
-
36
36
  end
37
37
  end
@@ -1,56 +1,59 @@
1
- require 'rack'
1
+ # frozen_string_literal: true
2
2
 
3
- module Dassets; end
4
- class Dassets::Server
5
-
6
- class Request < Rack::Request
3
+ require "rack"
7
4
 
8
- # The HTTP request method. This is the standard implementation of this
9
- # method but is respecified here due to libraries that attempt to modify
10
- # the behavior to respect POST tunnel method specifiers. We always want
11
- # the real request method.
12
- def request_method; @env['REQUEST_METHOD']; end
5
+ module Dassets; end
6
+ class Dassets::Server; end
7
+
8
+ class Dassets::Server::Request < Rack::Request
9
+ # The HTTP request method. This is the standard implementation of this
10
+ # method but is respecified here due to libraries that attempt to modify
11
+ # the behavior to respect POST tunnel method specifiers. We always want
12
+ # the real request method.
13
+ def request_method
14
+ @env["REQUEST_METHOD"]
15
+ end
13
16
 
14
- def path_info
15
- @env['PATH_INFO'].sub(dassets_base_url, '')
16
- end
17
+ def path_info
18
+ @env["PATH_INFO"].sub(dassets_base_url, "")
19
+ end
17
20
 
18
- def dassets_base_url
19
- Dassets.config.base_url.to_s
20
- end
21
+ def dassets_base_url
22
+ Dassets.config.base_url.to_s
23
+ end
21
24
 
22
- # Determine if the request is for an asset file
23
- # This will be called on every request so speed is an issue
24
- # - first check if the request is a GET or HEAD (fast)
25
- # - then check if for a digested asset resource (kinda fast)
26
- # - then check if source exists for the digested asset (slower)
27
- def for_asset_file?
28
- !!((get? || head?) && for_digested_asset? && asset_file.exists?)
29
- end
25
+ # Determine if the request is for an asset file
26
+ # This will be called on every request so speed is an issue
27
+ # - first check if the request is a GET or HEAD (fast)
28
+ # - then check if for a digested asset resource (kinda fast)
29
+ # - then check if source exists for the digested asset (slower)
30
+ def for_asset_file?
31
+ !!((get? || head?) && for_digested_asset? && asset_file.exists?)
32
+ end
30
33
 
31
- def asset_path
32
- @asset_path ||= path_digest_match.captures.select{ |m| !m.empty? }.join
33
- end
34
+ def asset_path
35
+ @asset_path ||= path_digest_match.captures.select{ |m| !m.empty? }.join
36
+ end
34
37
 
35
- def asset_file
36
- @asset_file ||= Dassets[asset_path]
37
- end
38
+ def asset_file
39
+ @asset_file ||= Dassets.asset_file(asset_path)
40
+ end
38
41
 
39
- private
42
+ private
40
43
 
41
- def for_digested_asset?
42
- !path_digest_match.captures.empty?
43
- end
44
+ def for_digested_asset?
45
+ !path_digest_match.captures.empty?
46
+ end
44
47
 
45
- def path_digest_match
46
- @path_digest_match ||= begin
47
- path_info.match(/\/(.+)-[a-f0-9]{32}(\..+|)$/i) || NullDigestMatch.new
48
- end
48
+ def path_digest_match
49
+ @path_digest_match ||= begin
50
+ path_info.match(%r{/(.+)-[a-f0-9]{32}(\..+|)$}i) || NullDigestMatch.new
49
51
  end
52
+ end
50
53
 
51
- class NullDigestMatch
52
- def captures; []; end
54
+ class NullDigestMatch
55
+ def captures
56
+ []
53
57
  end
54
-
55
58
  end
56
59
  end
@@ -1,108 +1,129 @@
1
- require 'rack/response'
2
- require 'rack/utils'
3
- require 'rack/mime'
1
+ # frozen_string_literal: true
2
+
3
+ require "rack/response"
4
+ require "rack/utils"
5
+ require "rack/mime"
4
6
 
5
7
  module Dassets; end
6
- class Dassets::Server
8
+ class Dassets::Server; end
9
+
10
+ class Dassets::Server::Response
11
+ attr_reader :asset_file, :status, :headers, :body
12
+
13
+ def initialize(env, asset_file)
14
+ @asset_file = asset_file
15
+
16
+ mtime = @asset_file.mtime.to_s
17
+ @status, @headers, @body = if env["HTTP_IF_MODIFIED_SINCE"] == mtime
18
+ [
19
+ 304,
20
+ Rack::Utils::HeaderHash.new("Last-Modified" => mtime),
21
+ [],
22
+ ]
23
+ elsif !@asset_file.exists?
24
+ [
25
+ 404,
26
+ Rack::Utils::HeaderHash.new,
27
+ ["Not Found"],
28
+ ]
29
+ else
30
+ @asset_file.digest!
31
+ body = Body.new(env, @asset_file)
32
+ [
33
+ body.partial? ? 206 : 200,
34
+ Rack::Utils::HeaderHash
35
+ .new
36
+ .merge(@asset_file.response_headers).tap do |h|
37
+ h["Last-Modified"] = mtime.to_s
38
+ h["Content-Type"] = @asset_file.mime_type.to_s
39
+ h["Content-Length"] = body.size.to_s
40
+ h["Content-Range"] = body.content_range if body.partial?
41
+ end,
42
+ env["REQUEST_METHOD"] == "HEAD" ? [] : body,
43
+ ]
44
+ end
45
+ end
7
46
 
8
- class Response
47
+ def to_rack
48
+ [@status, @headers.to_hash, @body]
49
+ end
50
+
51
+ # This class borrows from the body range handling in Rack::File and adapts
52
+ # it for use with Dasset's asset files and their generic string content.
53
+ class Body
54
+ CHUNK_SIZE = (8 * 1024) # 8k
9
55
 
10
- attr_reader :asset_file, :status, :headers, :body
56
+ attr_reader :asset_file, :size, :content_range
11
57
 
12
58
  def initialize(env, asset_file)
13
59
  @asset_file = asset_file
14
-
15
- mtime = @asset_file.mtime.to_s
16
- @status, @headers, @body = if env['HTTP_IF_MODIFIED_SINCE'] == mtime
17
- [ 304, Rack::Utils::HeaderHash.new('Last-Modified' => mtime), [] ]
18
- elsif !@asset_file.exists?
19
- [ 404, Rack::Utils::HeaderHash.new, ["Not Found"] ]
20
- else
21
- @asset_file.digest!
22
- body = Body.new(env, @asset_file)
23
- [ body.partial? ? 206 : 200,
24
- Rack::Utils::HeaderHash.new.merge(@asset_file.response_headers).tap do |h|
25
- h['Last-Modified'] = mtime
26
- h['Content-Type'] = @asset_file.mime_type.to_s
27
- h['Content-Length'] = body.size.to_s
28
- h['Content-Range'] = body.content_range if body.partial?
29
- end,
30
- env["REQUEST_METHOD"] == "HEAD" ? [] : body
31
- ]
32
- end
60
+ @range, @content_range = get_range_info(env, @asset_file)
61
+ @size = range_end - range_begin + 1
33
62
  end
34
63
 
35
- def to_rack
36
- [@status, @headers.to_hash, @body]
64
+ def partial?
65
+ !@content_range.nil?
37
66
  end
38
67
 
39
- class Body
40
-
41
- # this class borrows from the body range handling in Rack::File and adapts
42
- # it for use with Dasset's asset files and their generic string content.
43
-
44
- CHUNK_SIZE = (8*1024).freeze # 8k
45
-
46
- attr_reader :asset_file, :size, :content_range
47
-
48
- def initialize(env, asset_file)
49
- @asset_file = asset_file
50
- @range, @content_range = get_range_info(env, @asset_file)
51
- @size = self.range_end - self.range_begin + 1
52
- end
53
-
54
- def partial?
55
- !@content_range.nil?
56
- end
68
+ def range_begin
69
+ @range.begin
70
+ end
57
71
 
58
- def range_begin; @range.begin; end
59
- def range_end; @range.end; end
72
+ def range_end
73
+ @range.end
74
+ end
60
75
 
61
- def each
62
- StringIO.open(@asset_file.content, "rb") do |io|
63
- io.seek(@range.begin)
64
- remaining_len = self.size
65
- while remaining_len > 0
66
- part = io.read([CHUNK_SIZE, remaining_len].min)
67
- break if part.nil?
76
+ def each
77
+ StringIO.open(@asset_file.content, "rb") do |io|
78
+ io.seek(@range.begin)
79
+ remaining_len = size
80
+ while remaining_len > 0
81
+ part = io.read([CHUNK_SIZE, remaining_len].min)
82
+ break if part.nil?
68
83
 
69
- remaining_len -= part.length
70
- yield part
71
- end
84
+ remaining_len -= part.length
85
+ yield part
72
86
  end
73
87
  end
88
+ end
74
89
 
75
- def inspect
76
- "#<#{self.class}:#{'0x0%x' % (self.object_id << 1)} " \
77
- "digest_path=#{self.asset_file.digest_path} " \
78
- "range_begin=#{self.range_begin} range_end=#{self.range_end}>"
79
- end
90
+ def inspect
91
+ "#<#{self.class}:#{"0x0%x" % (object_id << 1)} " \
92
+ "digest_path=#{asset_file.digest_path} " \
93
+ "range_begin=#{range_begin} range_end=#{range_end}>"
94
+ end
80
95
 
81
- def ==(other_body)
82
- self.asset_file == other_body.asset_file &&
83
- self.range_begin == other_body.range_begin &&
84
- self.range_end == other_body.range_end
96
+ def ==(other)
97
+ if other.is_a?(self.class)
98
+ asset_file == other.asset_file &&
99
+ range_begin == other.range_begin &&
100
+ range_end == other.range_end
101
+ else
102
+ super
85
103
  end
104
+ end
86
105
 
87
- private
88
-
89
- def get_range_info(env, asset_file)
90
- content_size = asset_file.size
91
- # legacy rack version, just return full size
92
- return full_size_range_info(content_size) if !Rack::Utils.respond_to?(:byte_ranges)
93
- ranges = Rack::Utils.byte_ranges(env, content_size)
94
- # No ranges or multiple ranges are not supported, just return full size
95
- return full_size_range_info(content_size) if ranges.nil? || ranges.empty? || ranges.length > 1
96
- # single range
97
- [ranges[0], "bytes #{ranges[0].begin}-#{ranges[0].end}/#{content_size}"]
106
+ private
107
+
108
+ def get_range_info(env, asset_file)
109
+ content_size = asset_file.size
110
+ # legacy rack version, just return full size
111
+ unless Rack::Utils.respond_to?(:byte_ranges)
112
+ return full_size_range_info(content_size)
98
113
  end
99
114
 
100
- def full_size_range_info(content_size)
101
- [(0..content_size-1), nil]
115
+ ranges = Rack::Utils.byte_ranges(env, content_size)
116
+ # No ranges or multiple ranges are not supported, just return full size
117
+ if ranges.nil? || ranges.empty? || ranges.length > 1
118
+ return full_size_range_info(content_size)
102
119
  end
103
120
 
121
+ # single range
122
+ [ranges[0], "bytes #{ranges[0].begin}-#{ranges[0].end}/#{content_size}"]
104
123
  end
105
124
 
125
+ def full_size_range_info(content_size)
126
+ [(0..content_size - 1), nil]
127
+ end
106
128
  end
107
-
108
129
  end