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.
- checksums.yaml +7 -7
- data/Gemfile +5 -1
- data/README.md +15 -17
- data/dassets.gemspec +14 -9
- data/lib/dassets.rb +51 -13
- data/lib/dassets/asset_file.rb +27 -24
- data/lib/dassets/cache.rb +27 -33
- data/lib/dassets/config.rb +55 -50
- data/lib/dassets/engine.rb +11 -23
- data/lib/dassets/file_store.rb +27 -27
- data/lib/dassets/server.rb +27 -27
- data/lib/dassets/server/request.rb +44 -41
- data/lib/dassets/server/response.rb +101 -80
- data/lib/dassets/source.rb +15 -9
- data/lib/dassets/source_file.rb +103 -82
- data/lib/dassets/source_proxy.rb +36 -20
- data/lib/dassets/version.rb +3 -1
- data/test/helper.rb +31 -25
- data/test/support/app.rb +5 -5
- data/test/support/empty/{.gitkeep → .keep} +0 -0
- data/test/support/factory.rb +3 -2
- data/test/support/{public/nested/file3-d41d8cd98f00b204e9800998ecf8427e.txt → linked_source_files/linked_file.txt} +0 -0
- data/test/support/source_files/linked +1 -0
- data/test/support/source_files/linked_file2.txt +1 -0
- data/test/system/rack_tests.rb +65 -61
- data/test/unit/asset_file_tests.rb +69 -61
- data/test/unit/cache_tests.rb +15 -34
- data/test/unit/config_tests.rb +59 -52
- data/test/unit/dassets_tests.rb +31 -24
- data/test/unit/engine_tests.rb +9 -43
- data/test/unit/file_store_tests.rb +44 -31
- data/test/unit/server/request_tests.rb +57 -59
- data/test/unit/server/response_tests.rb +82 -82
- data/test/unit/server_tests.rb +5 -9
- data/test/unit/source_file_tests.rb +80 -73
- data/test/unit/source_proxy_tests.rb +84 -90
- data/test/unit/source_tests.rb +66 -50
- data/tmp/.gitkeep +0 -0
- metadata +92 -72
- data/.gitignore +0 -19
- data/test/support/public/file2-9bbe1047cffbb590f59e0e5aeff46ae4.txt +0 -1
- data/test/support/public/grumpy_cat-b0d1f399a916f7a25c4c0f693c619013.jpg +0 -0
- data/test/support/public/nested/a-thing.txt-7413d18f2eba9c695a880aff67fde135.no-use +0 -4
data/lib/dassets/source.rb
CHANGED
@@ -1,25 +1,27 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dassets/engine"
|
2
4
|
|
3
5
|
module Dassets; end
|
4
|
-
class Dassets::Source
|
5
6
|
|
7
|
+
class Dassets::Source
|
6
8
|
attr_reader :path, :engines, :response_headers
|
7
9
|
|
8
10
|
def initialize(path)
|
9
11
|
@path = path.to_s
|
10
12
|
@filter = proc{ |paths| paths }
|
11
|
-
@engines = Hash.new{ |
|
12
|
-
@response_headers =
|
13
|
+
@engines = Hash.new{ |hash, key| hash[key] = [] }
|
14
|
+
@response_headers = {}
|
13
15
|
end
|
14
16
|
|
15
17
|
def filter(&block)
|
16
18
|
block.nil? ? @filter : @filter = block
|
17
19
|
end
|
18
20
|
|
19
|
-
def engine(input_ext, engine_class, registered_opts=nil)
|
20
|
-
default_opts = {
|
21
|
+
def engine(input_ext, engine_class, registered_opts = nil)
|
22
|
+
default_opts = { "source_path" => @path }
|
21
23
|
engine_opts = default_opts.merge(registered_opts || {})
|
22
|
-
@engines[input_ext.to_s]
|
24
|
+
@engines[input_ext.to_s] << engine_class.new(engine_opts)
|
23
25
|
end
|
24
26
|
|
25
27
|
def files
|
@@ -28,12 +30,16 @@ class Dassets::Source
|
|
28
30
|
|
29
31
|
private
|
30
32
|
|
33
|
+
# Use "**{,/*/**}/*" to glob following symlinks and returning immediate-child
|
34
|
+
# matches. See https://stackoverflow.com/a/2724048.
|
31
35
|
def glob_files
|
32
|
-
Dir
|
36
|
+
Dir
|
37
|
+
.glob(File.join(@path, "**{,/*/**}/*"))
|
38
|
+
.uniq
|
39
|
+
.reject{ |path| !File.file?(path) }
|
33
40
|
end
|
34
41
|
|
35
42
|
def apply_filter(files)
|
36
43
|
@filter.call(files)
|
37
44
|
end
|
38
|
-
|
39
45
|
end
|
data/lib/dassets/source_file.rb
CHANGED
@@ -1,114 +1,135 @@
|
|
1
|
-
|
2
|
-
require 'dassets'
|
3
|
-
require 'dassets/asset_file'
|
4
|
-
require 'dassets/source_proxy'
|
1
|
+
# frozen_string_literal: true
|
5
2
|
|
6
|
-
|
3
|
+
require "fileutils"
|
4
|
+
require "dassets"
|
5
|
+
require "dassets/asset_file"
|
6
|
+
require "dassets/source_proxy"
|
7
7
|
|
8
|
-
|
8
|
+
module Dassets; end
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
10
|
+
class Dassets::SourceFile
|
11
|
+
def self.find_by_digest_path(path, **options)
|
12
|
+
Dassets.source_files[path] || Dassets::NullSourceFile.new(path, **options)
|
13
|
+
end
|
13
14
|
|
14
|
-
|
15
|
+
attr_reader :file_path
|
15
16
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
17
|
+
def initialize(file_path)
|
18
|
+
@file_path = file_path.to_s
|
19
|
+
@ext_list = File.basename(@file_path).split(".").reverse
|
20
|
+
end
|
20
21
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
22
|
+
# Get the last matching one (in the case two sources with the same path are
|
23
|
+
# configured) since we select the last matching source file (from the last
|
24
|
+
# configured source) in `find_by_digest_path` above.
|
25
|
+
def source
|
26
|
+
@source ||=
|
27
|
+
Dassets.config.sources.select{ |source|
|
26
28
|
@file_path =~ /^#{slash_path(source.path)}/
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
def asset_file
|
31
|
-
@asset_file ||= Dassets::AssetFile.new(self.digest_path)
|
32
|
-
end
|
29
|
+
}.last
|
30
|
+
end
|
33
31
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
digest_ext_list << self.source.engines[ext].ext(ext)
|
38
|
-
end.reject(&:empty?).reverse.join('.')
|
32
|
+
def asset_file
|
33
|
+
@asset_file ||= Dassets::AssetFile.new(digest_path)
|
34
|
+
end
|
39
35
|
|
40
|
-
|
36
|
+
def digest_path
|
37
|
+
@digest_path ||=
|
38
|
+
begin
|
39
|
+
digest_basename =
|
40
|
+
@ext_list
|
41
|
+
.reduce([]){ |digest_ext_list, ext|
|
42
|
+
digest_ext_list <<
|
43
|
+
source.engines[ext].reduce(ext)do |ext_acc, engine|
|
44
|
+
engine.ext(ext_acc)
|
45
|
+
end
|
46
|
+
}
|
47
|
+
.reject(&:empty?)
|
48
|
+
.reverse
|
49
|
+
.join(".")
|
50
|
+
|
51
|
+
File.join(
|
52
|
+
[digest_dirname(@file_path), digest_basename].reject(&:empty?),
|
53
|
+
)
|
41
54
|
end
|
42
|
-
|
55
|
+
end
|
43
56
|
|
44
|
-
|
45
|
-
|
46
|
-
|
57
|
+
def compiled
|
58
|
+
@ext_list.reduce(read_file(@file_path)) do |file_acc, ext|
|
59
|
+
source.engines[ext].reduce(file_acc) do |ext_acc, engine|
|
60
|
+
engine.compile(ext_acc)
|
47
61
|
end
|
48
62
|
end
|
63
|
+
end
|
49
64
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
def mtime
|
55
|
-
File.mtime(@file_path)
|
56
|
-
end
|
57
|
-
|
58
|
-
def response_headers
|
59
|
-
self.source.nil? ? Hash.new : self.source.response_headers
|
60
|
-
end
|
61
|
-
|
62
|
-
def ==(other_source_file)
|
63
|
-
self.file_path == other_source_file.file_path
|
64
|
-
end
|
65
|
+
def exists?
|
66
|
+
File.file?(@file_path)
|
67
|
+
end
|
65
68
|
|
66
|
-
|
69
|
+
def mtime
|
70
|
+
File.mtime(@file_path)
|
71
|
+
end
|
67
72
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
end
|
73
|
+
def response_headers
|
74
|
+
source.nil? ? {} : source.response_headers
|
75
|
+
end
|
72
76
|
|
73
|
-
|
74
|
-
|
77
|
+
def ==(other)
|
78
|
+
if other.is_a?(self.class)
|
79
|
+
file_path == other.file_path
|
80
|
+
else
|
81
|
+
super
|
75
82
|
end
|
83
|
+
end
|
76
84
|
|
77
|
-
|
78
|
-
File.send(File.respond_to?(:binread) ? :binread : :read, path.to_s)
|
79
|
-
end
|
85
|
+
private
|
80
86
|
|
87
|
+
# remove the source path from the dirname (if it exists)
|
88
|
+
def digest_dirname(file_path)
|
89
|
+
slash_path(File.dirname(file_path)).sub(slash_path(source.path), "")
|
81
90
|
end
|
82
91
|
|
83
|
-
|
84
|
-
|
92
|
+
def slash_path(path)
|
93
|
+
File.join(path, "")
|
94
|
+
end
|
85
95
|
|
86
|
-
|
96
|
+
def read_file(path)
|
97
|
+
File.send(File.respond_to?(:binread) ? :binread : :read, path.to_s)
|
98
|
+
end
|
99
|
+
end
|
87
100
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
101
|
+
# A null source file is used to represent source that either doesn't exist
|
102
|
+
# or source that is a proxy (ie a combination)
|
103
|
+
class Dassets::NullSourceFile < Dassets::SourceFile
|
104
|
+
def initialize(digest_path, **options)
|
105
|
+
@file_path = ""
|
106
|
+
@ext_list = []
|
107
|
+
@digest_path = digest_path
|
108
|
+
@source_proxy =
|
109
|
+
if Dassets.config.combination?(@digest_path)
|
110
|
+
Dassets::SourceProxy.new(@digest_path, **options)
|
93
111
|
else
|
94
|
-
NullSourceProxy.new
|
112
|
+
Dassets::NullSourceProxy.new
|
95
113
|
end
|
96
|
-
|
97
|
-
|
98
|
-
def compiled; @source_proxy.content; end
|
99
|
-
def exists?; @source_proxy.exists?; end
|
100
|
-
def mtime; @source_proxy.mtime; end
|
114
|
+
end
|
101
115
|
|
102
|
-
|
103
|
-
|
104
|
-
|
116
|
+
def compiled
|
117
|
+
@source_proxy.content
|
118
|
+
end
|
105
119
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
def mtime; nil; end
|
110
|
-
end
|
120
|
+
def exists?
|
121
|
+
@source_proxy.exists?
|
122
|
+
end
|
111
123
|
|
124
|
+
def mtime
|
125
|
+
@source_proxy.mtime
|
112
126
|
end
|
113
127
|
|
128
|
+
def ==(other)
|
129
|
+
if other.is_a?(self.class)
|
130
|
+
file_path == other.file_path
|
131
|
+
else
|
132
|
+
super
|
133
|
+
end
|
134
|
+
end
|
114
135
|
end
|
data/lib/dassets/source_proxy.rb
CHANGED
@@ -1,42 +1,45 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "digest/md5"
|
4
|
+
require "dassets/cache"
|
5
|
+
require "dassets/source_file"
|
4
6
|
|
5
7
|
module Dassets; end
|
6
|
-
class Dassets::SourceProxy
|
7
8
|
|
9
|
+
class Dassets::SourceProxy
|
8
10
|
attr_reader :digest_path, :content_cache, :fingerprint_cache
|
9
11
|
attr_reader :source_files
|
10
12
|
|
11
|
-
def initialize(digest_path, options
|
12
|
-
options ||= {}
|
13
|
+
def initialize(digest_path, **options)
|
13
14
|
@digest_path = digest_path
|
14
|
-
@content_cache = options[:content_cache] || Dassets::
|
15
|
-
@fingerprint_cache = options[:fingerprint_cache] || Dassets::
|
16
|
-
@source_files =
|
17
|
-
|
18
|
-
|
19
|
-
|
15
|
+
@content_cache = options[:content_cache] || Dassets::NoCache.new
|
16
|
+
@fingerprint_cache = options[:fingerprint_cache] || Dassets::NoCache.new
|
17
|
+
@source_files =
|
18
|
+
get_source_files(
|
19
|
+
@digest_path,
|
20
|
+
content_cache: @content_cache,
|
21
|
+
fingerprint_cache: @fingerprint_cache,
|
22
|
+
)
|
20
23
|
end
|
21
24
|
|
22
25
|
def key
|
23
|
-
"#{
|
26
|
+
"#{digest_path} -- #{mtime}"
|
24
27
|
end
|
25
28
|
|
26
29
|
def content
|
27
|
-
@content_cache[
|
30
|
+
@content_cache[key] ||= source_content
|
28
31
|
end
|
29
32
|
|
30
33
|
def fingerprint
|
31
|
-
@fingerprint_cache[
|
34
|
+
@fingerprint_cache[key] ||= source_fingerprint
|
32
35
|
end
|
33
36
|
|
34
37
|
def mtime
|
35
|
-
@source_files.map
|
38
|
+
@source_files.map(&:mtime).compact.max
|
36
39
|
end
|
37
40
|
|
38
41
|
def response_headers
|
39
|
-
@source_files.inject(
|
42
|
+
@source_files.inject({}){ |hash, f| hash.merge!(f.response_headers) }
|
40
43
|
end
|
41
44
|
|
42
45
|
def exists?
|
@@ -46,17 +49,30 @@ class Dassets::SourceProxy
|
|
46
49
|
private
|
47
50
|
|
48
51
|
def source_content
|
49
|
-
@source_files.map
|
52
|
+
@source_files.map(&:compiled).join("\n")
|
50
53
|
end
|
51
54
|
|
52
55
|
def source_fingerprint
|
53
56
|
Digest::MD5.new.hexdigest(source_content)
|
54
57
|
end
|
55
58
|
|
56
|
-
def get_source_files(digest_path, options)
|
59
|
+
def get_source_files(digest_path, **options)
|
57
60
|
Dassets.config.combinations[digest_path.to_s].map do |source_digest_path|
|
58
|
-
Dassets::SourceFile.find_by_digest_path(source_digest_path, options)
|
61
|
+
Dassets::SourceFile.find_by_digest_path(source_digest_path, **options)
|
59
62
|
end
|
60
63
|
end
|
64
|
+
end
|
65
|
+
|
66
|
+
class Dassets::NullSourceProxy
|
67
|
+
def content
|
68
|
+
nil
|
69
|
+
end
|
70
|
+
|
71
|
+
def exists?
|
72
|
+
false
|
73
|
+
end
|
61
74
|
|
75
|
+
def mtime
|
76
|
+
nil
|
77
|
+
end
|
62
78
|
end
|
data/lib/dassets/version.rb
CHANGED
data/test/helper.rb
CHANGED
@@ -1,43 +1,49 @@
|
|
1
|
-
#
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This file is automatically required when you run `assert`
|
4
|
+
# put any test helpers here.
|
3
5
|
|
4
6
|
# add the root dir to the load path
|
5
7
|
$LOAD_PATH.unshift(File.expand_path("../..", __FILE__))
|
6
8
|
|
7
9
|
# require pry for debugging (`binding.pry`)
|
8
|
-
require
|
10
|
+
require "pry"
|
9
11
|
|
10
|
-
require
|
12
|
+
require "test/support/factory"
|
11
13
|
|
12
|
-
|
14
|
+
require "pathname"
|
15
|
+
TEST_SUPPORT_PATH = Pathname.new(File.expand_path("../support", __FILE__))
|
13
16
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
alias_method :sample, :choice
|
18
|
-
end
|
19
|
-
end
|
17
|
+
ENV["DASSETS_TEST_MODE"] = "yes"
|
18
|
+
|
19
|
+
require "dassets"
|
20
20
|
|
21
|
-
|
22
|
-
|
21
|
+
@dumb_engine =
|
22
|
+
Class.new(Dassets::Engine) do
|
23
|
+
def ext(_in_ext)
|
24
|
+
""
|
25
|
+
end
|
23
26
|
|
24
|
-
|
27
|
+
def compile(input)
|
28
|
+
"#{input}\nDUMB"
|
29
|
+
end
|
30
|
+
end
|
25
31
|
|
26
|
-
|
32
|
+
@useless_engine =
|
33
|
+
Class.new(Dassets::Engine) do
|
34
|
+
def ext(_in_ext)
|
35
|
+
"no-use"
|
36
|
+
end
|
27
37
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
end
|
32
|
-
@useless_engine = Class.new(Dassets::Engine) do
|
33
|
-
def ext(in_ext); 'no-use'; end
|
34
|
-
def compile(input); "#{input}\nUSELESS"; end
|
35
|
-
end
|
38
|
+
def compile(input)
|
39
|
+
"#{input}\nUSELESS"
|
40
|
+
end
|
41
|
+
end
|
36
42
|
|
37
43
|
Dassets.configure do |c|
|
38
44
|
c.source TEST_SUPPORT_PATH.join("app/assets") do |s|
|
39
|
-
s.engine
|
40
|
-
s.engine
|
45
|
+
s.engine "dumb", @dumb_engine
|
46
|
+
s.engine "useless", @useless_engine
|
41
47
|
s.response_headers[Factory.string] = Factory.string
|
42
48
|
end
|
43
49
|
end
|