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