dassets 0.2.0 → 0.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.
- data/README.md +15 -5
- data/lib/dassets.rb +59 -12
- data/lib/dassets/asset_file.rb +15 -14
- data/lib/dassets/cmds/cache_cmd.rb +33 -0
- data/lib/dassets/cmds/digest_cmd.rb +53 -0
- data/lib/dassets/{digests_file.rb → digests.rb} +14 -23
- data/lib/dassets/engine.rb +33 -0
- data/lib/dassets/runner.rb +10 -4
- data/lib/dassets/server/response.rb +1 -1
- data/lib/dassets/source_file.rb +71 -0
- data/lib/dassets/version.rb +1 -1
- data/test/support/app/assets/.digests +1 -0
- data/test/support/app/assets/file1.txt +1 -0
- data/test/support/app/assets/file2.txt +1 -0
- data/test/support/app/assets/grumpy_cat.jpg +0 -0
- data/test/support/app/assets/nested/a-thing.txt.useless.dumb +1 -0
- data/test/support/app/assets/nested/file3.txt +0 -0
- data/test/support/app/assets/public/nested/a-thing.txt.no-use +4 -0
- data/test/support/config/assets.rb +12 -1
- data/test/support/example.digests +3 -3
- data/test/support/public/nested/a-thing.txt-7413d18f2eba9c695a880aff67fde135.no-use +4 -0
- data/test/support/source_files/_ignored.txt +0 -0
- data/test/support/source_files/nested/_nested_ignored.txt +0 -0
- data/test/support/source_files/nested/test2.txt +0 -0
- data/test/support/source_files/test1.txt +0 -0
- data/test/system/cache_cmd_run_tests.rb +27 -0
- data/test/system/digest_cmd_run_tests.rb +70 -0
- data/test/unit/asset_file_tests.rb +11 -11
- data/test/unit/cmds/cache_cmd_tests.rb +33 -0
- data/test/unit/cmds/digest_cmd_tests.rb +23 -0
- data/test/unit/config_tests.rb +52 -7
- data/test/unit/dassets_tests.rb +59 -5
- data/test/unit/digests_tests.rb +79 -0
- data/test/unit/engine_tests.rb +59 -0
- data/test/unit/server/response_tests.rb +5 -5
- data/test/unit/source_file_tests.rb +82 -0
- metadata +45 -13
- data/lib/dassets/runner/cache_command.rb +0 -46
- data/lib/dassets/runner/digest_command.rb +0 -65
- data/test/unit/digests_file_tests.rb +0 -90
- data/test/unit/runner/cache_command_tests.rb +0 -62
- data/test/unit/runner/digest_command_tests.rb +0 -83
data/README.md
CHANGED
@@ -17,11 +17,21 @@ Dassets.configure do |c|
|
|
17
17
|
# tell Dassets what the root path of your app is
|
18
18
|
c.root_path '/path/to/app/root'
|
19
19
|
|
20
|
-
#
|
21
|
-
c.
|
22
|
-
|
23
|
-
#
|
24
|
-
c.
|
20
|
+
# tell Dassets where to write the digests
|
21
|
+
c.digests_path '/path/to/.digests' # default: '{source_path}/.digests'
|
22
|
+
|
23
|
+
# tell Dassets where to look for source files and (optionally) how to filter those files
|
24
|
+
c.source_path 'lib/asset_files' # default: '{root_path}/app/assets'
|
25
|
+
c.source_filter proc{ |paths| paths.select{ |p| ... } }
|
26
|
+
# --OR--
|
27
|
+
c.sources 'lib/asset_files' do |paths|
|
28
|
+
# return the filtered source path list
|
29
|
+
paths.select{ |p| ... }
|
30
|
+
end
|
31
|
+
|
32
|
+
# tell Dassets where to write output files to
|
33
|
+
# it works best to *not* output to your public dir if using fingerprinting
|
34
|
+
c.output_path '/lib/assets_output' # default: '{source_path}/public'
|
25
35
|
|
26
36
|
end
|
27
37
|
```
|
data/lib/dassets.rb
CHANGED
@@ -1,39 +1,86 @@
|
|
1
1
|
require 'pathname'
|
2
|
+
require 'set'
|
2
3
|
require 'ns-options'
|
3
4
|
|
4
5
|
require 'dassets/version'
|
5
6
|
require 'dassets/root_path'
|
6
|
-
require 'dassets/
|
7
|
+
require 'dassets/digests'
|
8
|
+
require 'dassets/engine'
|
7
9
|
|
8
10
|
ENV['DASSETS_ASSETS_FILE'] ||= 'config/assets'
|
9
11
|
|
10
12
|
module Dassets
|
11
13
|
|
12
|
-
def self.config; Config;
|
13
|
-
def self.
|
14
|
+
def self.config; @config ||= Config.new; end
|
15
|
+
def self.sources; @sources ||= Set.new; end
|
16
|
+
def self.digests; @digests ||= NullDigests.new; end
|
14
17
|
|
15
|
-
def self.
|
16
|
-
|
17
|
-
@digests_file = DigestsFile.new(self.config.digests_file_path)
|
18
|
+
def self.configure(&block)
|
19
|
+
block.call(self.config)
|
18
20
|
end
|
19
21
|
|
20
22
|
def self.reset
|
21
|
-
@
|
23
|
+
@sources = @digests = nil
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.init
|
27
|
+
require self.config.assets_file
|
28
|
+
@sources = SourceList.new(self.config)
|
29
|
+
@digests = Digests.new(self.config.digests_path)
|
22
30
|
end
|
23
31
|
|
24
|
-
def self.digests; @digests_file || NullDigestsFile.new; end
|
25
32
|
def self.[](asset_path)
|
26
33
|
self.digests.asset_file(asset_path)
|
27
34
|
end
|
28
35
|
|
36
|
+
# Cmds
|
37
|
+
|
38
|
+
def self.digest_source_files(paths=nil)
|
39
|
+
require 'dassets/cmds/digest_cmd'
|
40
|
+
Cmds::DigestCmd.new(paths).run
|
41
|
+
end
|
42
|
+
|
29
43
|
class Config
|
30
44
|
include NsOptions::Proxy
|
31
45
|
|
32
|
-
option :
|
33
|
-
option :
|
34
|
-
option :
|
35
|
-
|
46
|
+
option :root_path, Pathname, :required => true
|
47
|
+
option :digests_path, Pathname, :required => true
|
48
|
+
option :output_path, RootPath, :required => true
|
49
|
+
|
50
|
+
option :assets_file, Pathname, :default => ENV['DASSETS_ASSETS_FILE']
|
51
|
+
option :source_path, RootPath, :default => proc{ "app/assets" }
|
52
|
+
option :source_filter, Proc, :default => proc{ |paths| paths }
|
53
|
+
|
54
|
+
attr_reader :engines
|
55
|
+
|
56
|
+
def initialize
|
57
|
+
super({
|
58
|
+
:digests_path => proc{ File.join(self.source_path, '.digests') },
|
59
|
+
:output_path => proc{ File.join(self.source_path, 'public') }
|
60
|
+
})
|
61
|
+
@engines = Hash.new{ |k,v| Dassets::NullEngine.new }
|
62
|
+
end
|
63
|
+
|
64
|
+
def source(path=nil, &filter)
|
65
|
+
self.source_path = path if path
|
66
|
+
self.source_filter = filter if filter
|
67
|
+
end
|
68
|
+
|
69
|
+
def engine(input_ext, engine_class, opts=nil)
|
70
|
+
@engines[input_ext.to_s] = engine_class.new(opts)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
module SourceList
|
75
|
+
def self.new(config)
|
76
|
+
paths = Set.new
|
77
|
+
paths += Dir.glob(File.join(config.source_path, "**/*"))
|
78
|
+
paths.reject!{ |path| !File.file?(path) }
|
79
|
+
paths.reject!{ |path| path =~ /^#{config.output_path}/ }
|
80
|
+
paths.reject!{ |path| path =~ /^#{config.digests_path}/ }
|
36
81
|
|
82
|
+
config.source_filter.call(paths).sort
|
83
|
+
end
|
37
84
|
end
|
38
85
|
|
39
86
|
end
|
data/lib/dassets/asset_file.rb
CHANGED
@@ -6,13 +6,13 @@ module Dassets; end
|
|
6
6
|
class Dassets::AssetFile
|
7
7
|
|
8
8
|
def self.from_abs_path(abs_path)
|
9
|
-
rel_path = abs_path.sub("#{Dassets.config.
|
9
|
+
rel_path = abs_path.sub("#{Dassets.config.output_path}/", '')
|
10
10
|
md5 = Digest::MD5.file(abs_path).hexdigest
|
11
11
|
self.new(rel_path, md5)
|
12
12
|
end
|
13
13
|
|
14
14
|
attr_reader :path, :md5, :dirname, :extname, :basename
|
15
|
-
attr_reader :
|
15
|
+
attr_reader :output_path, :url, :href
|
16
16
|
|
17
17
|
def initialize(rel_path, md5)
|
18
18
|
@path, @md5 = rel_path, md5
|
@@ -20,40 +20,41 @@ class Dassets::AssetFile
|
|
20
20
|
@extname = File.extname(@path)
|
21
21
|
@basename = File.basename(@path, @extname)
|
22
22
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
@
|
23
|
+
@output_path = File.join(Dassets.config.output_path, @path)
|
24
|
+
|
25
|
+
url_basename = "#{@basename}-#{@md5}#{@extname}"
|
26
|
+
@url = File.join(@dirname, url_basename).sub(/^\.\//, '').sub(/^\//, '')
|
27
|
+
@href = "/#{@url}"
|
27
28
|
end
|
28
29
|
|
29
30
|
def content
|
30
|
-
@content ||= if File.exists?(@
|
31
|
-
File.read(@
|
31
|
+
@content ||= if File.exists?(@output_path) && File.file?(@output_path)
|
32
|
+
File.read(@output_path)
|
32
33
|
end
|
33
34
|
end
|
34
35
|
|
35
36
|
def mtime
|
36
|
-
@mtime ||= if File.exists?(@
|
37
|
-
File.mtime(@
|
37
|
+
@mtime ||= if File.exists?(@output_path) && File.file?(@output_path)
|
38
|
+
File.mtime(@output_path).httpdate
|
38
39
|
end
|
39
40
|
end
|
40
41
|
|
41
42
|
# We check via File::size? whether this file provides size info via stat,
|
42
43
|
# otherwise we have to figure it out by reading the whole file into memory.
|
43
44
|
def size
|
44
|
-
@size ||= if File.exists?(@
|
45
|
-
File.size?(@
|
45
|
+
@size ||= if File.exists?(@output_path) && File.file?(@output_path)
|
46
|
+
File.size?(@output_path) || Rack::Utils.bytesize(self.content)
|
46
47
|
end
|
47
48
|
end
|
48
49
|
|
49
50
|
def mime_type
|
50
|
-
@mime_type ||= if File.exists?(@
|
51
|
+
@mime_type ||= if File.exists?(@output_path) && File.file?(@output_path)
|
51
52
|
Rack::Mime.mime_type(@extname)
|
52
53
|
end
|
53
54
|
end
|
54
55
|
|
55
56
|
def exists?
|
56
|
-
File.exists?(@
|
57
|
+
File.exists?(@output_path) && File.file?(@output_path)
|
57
58
|
end
|
58
59
|
|
59
60
|
def ==(other_asset_file)
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'dassets'
|
4
|
+
require 'dassets/digests'
|
5
|
+
|
6
|
+
module Dassets; end
|
7
|
+
class Dassets::Cmds; end
|
8
|
+
class Dassets::Cmds::CacheCmd
|
9
|
+
|
10
|
+
attr_reader :cache_root_path, :digests
|
11
|
+
|
12
|
+
def initialize(cache_root_path)
|
13
|
+
@cache_root_path = Pathname.new(cache_root_path)
|
14
|
+
@digests = Dassets::Digests.new(Dassets.config.digests_path)
|
15
|
+
end
|
16
|
+
|
17
|
+
def run(io=nil)
|
18
|
+
log io, "caching #{@digests.asset_files.count} asset file(s) to `#{@cache_root_path}` ..."
|
19
|
+
@digests.asset_files.each do |file|
|
20
|
+
cache_path = @cache_root_path.join(file.url).to_s
|
21
|
+
|
22
|
+
FileUtils.mkdir_p File.dirname(cache_path)
|
23
|
+
FileUtils.cp(file.output_path, cache_path)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def log(io, msg)
|
30
|
+
io.puts msg if io
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'dassets'
|
3
|
+
require 'dassets/source_file'
|
4
|
+
|
5
|
+
module Dassets; end
|
6
|
+
class Dassets::Cmds; end
|
7
|
+
class Dassets::Cmds::DigestCmd
|
8
|
+
|
9
|
+
attr_reader :paths
|
10
|
+
|
11
|
+
def initialize(abs_paths)
|
12
|
+
@paths = abs_paths || []
|
13
|
+
end
|
14
|
+
|
15
|
+
def run(io=nil)
|
16
|
+
files = paths
|
17
|
+
|
18
|
+
if @paths.empty?
|
19
|
+
log io, "clearing `#{Dassets.config.output_path}`"
|
20
|
+
clear_output_path(Dassets.config.output_path)
|
21
|
+
|
22
|
+
# always clear the digests in use
|
23
|
+
log io, "clearing `#{Dassets.digests.file_path}`"
|
24
|
+
clear_digests(Dassets.digests)
|
25
|
+
|
26
|
+
# always get the latest source list
|
27
|
+
files = Dassets::SourceList.new(Dassets.config)
|
28
|
+
end
|
29
|
+
|
30
|
+
log io, "digesting #{files.count} source file(s) ..."
|
31
|
+
digest_the_files(files)
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def clear_output_path(path)
|
37
|
+
Dir.glob(File.join(path, '*')).each{ |p| FileUtils.rm_r(p) } if path
|
38
|
+
end
|
39
|
+
|
40
|
+
def clear_digests(digests)
|
41
|
+
digests.clear.save! if digests
|
42
|
+
end
|
43
|
+
|
44
|
+
def digest_the_files(files)
|
45
|
+
files.map{ |f| Dassets::SourceFile.new(f).digest }
|
46
|
+
end
|
47
|
+
|
48
|
+
def log(io, msg)
|
49
|
+
io.puts msg if io
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
@@ -2,40 +2,33 @@ require 'dassets/asset_file'
|
|
2
2
|
|
3
3
|
module Dassets
|
4
4
|
|
5
|
-
class
|
5
|
+
class Digests
|
6
6
|
|
7
|
-
attr_reader :
|
7
|
+
attr_reader :file_path
|
8
8
|
|
9
9
|
def initialize(file_path)
|
10
|
-
@
|
10
|
+
@file_path, @hash = file_path, decode(file_path)
|
11
11
|
end
|
12
12
|
|
13
|
-
def [](*args);
|
14
|
-
def []=(*args);
|
15
|
-
def delete(*args); @hash.delete(*args);
|
13
|
+
def [](*args); @hash.send('[]', *args); end
|
14
|
+
def []=(*args); @hash.send('[]=', *args); end
|
15
|
+
def delete(*args); @hash.delete(*args); end
|
16
|
+
def clear(*args); @hash.clear(*args); self end
|
16
17
|
|
17
|
-
def
|
18
|
-
|
19
|
-
|
20
|
-
def values; @hash.values; end
|
21
|
-
def empty?; @hash.empty?; end
|
18
|
+
def paths
|
19
|
+
@hash.keys
|
20
|
+
end
|
22
21
|
|
23
22
|
def asset_files
|
24
|
-
|
23
|
+
self.paths.map{ |path| self.asset_file(path) }
|
25
24
|
end
|
26
25
|
|
27
26
|
def asset_file(path)
|
28
27
|
Dassets::AssetFile.new(path, @hash[path] || '')
|
29
28
|
end
|
30
29
|
|
31
|
-
def to_hash
|
32
|
-
Hash.new.tap do |to_hash|
|
33
|
-
@hash.each{ |k, v| to_hash[k] = v }
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
30
|
def save!
|
38
|
-
encode(@hash, @
|
31
|
+
encode(@hash, @file_path)
|
39
32
|
end
|
40
33
|
|
41
34
|
private
|
@@ -59,12 +52,10 @@ module Dassets
|
|
59
52
|
|
60
53
|
end
|
61
54
|
|
62
|
-
module
|
63
|
-
|
55
|
+
module NullDigests
|
64
56
|
def self.new
|
65
|
-
|
57
|
+
Digests.new('/dev/null')
|
66
58
|
end
|
67
|
-
|
68
59
|
end
|
69
60
|
|
70
61
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Dassets
|
2
|
+
|
3
|
+
class Engine
|
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
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
class NullEngine < Engine
|
22
|
+
|
23
|
+
def ext(input_ext)
|
24
|
+
input_ext
|
25
|
+
end
|
26
|
+
|
27
|
+
def compile(input)
|
28
|
+
input
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
data/lib/dassets/runner.rb
CHANGED
@@ -12,6 +12,7 @@ class Dassets::Runner
|
|
12
12
|
@opts = opts
|
13
13
|
@cmd_name = args.shift || ""
|
14
14
|
@cmd_args = args
|
15
|
+
@pwd = ENV['PWD']
|
15
16
|
end
|
16
17
|
|
17
18
|
def run
|
@@ -19,11 +20,16 @@ class Dassets::Runner
|
|
19
20
|
|
20
21
|
case @cmd_name
|
21
22
|
when 'digest'
|
22
|
-
require 'dassets/
|
23
|
-
|
23
|
+
require 'dassets/cmds/digest_cmd'
|
24
|
+
abs_paths = @cmd_args.map{ |path| File.expand_path(path, @pwd) }
|
25
|
+
Dassets::Cmds::DigestCmd.new(abs_paths).run($stdout)
|
24
26
|
when 'cache'
|
25
|
-
require 'dassets/
|
26
|
-
|
27
|
+
require 'dassets/cmds/cache_cmd'
|
28
|
+
cache_root_path = File.expand_path(@cmd_args.first, @pwd)
|
29
|
+
unless cache_root_path && File.directory?(cache_root_path)
|
30
|
+
raise CmdError, "specify an existing cache directory"
|
31
|
+
end
|
32
|
+
Dassets::Cmds::CacheCmd.new(cache_root_path).run($stdout)
|
27
33
|
when 'null'
|
28
34
|
NullCommand.new.run
|
29
35
|
else
|
@@ -14,7 +14,7 @@ class Dassets::Server
|
|
14
14
|
@status, @headers, @body = if env['HTTP_IF_MODIFIED_SINCE'] == mtime
|
15
15
|
[ 304, Rack::Utils::HeaderHash.new, [] ]
|
16
16
|
elsif !@asset_file.exists?
|
17
|
-
[ 404, Rack::Utils::HeaderHash.new, [] ]
|
17
|
+
[ 404, Rack::Utils::HeaderHash.new, ["Not Found"] ]
|
18
18
|
else
|
19
19
|
[ 200,
|
20
20
|
Rack::Utils::HeaderHash.new.tap do |h|
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'digest/md5'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'dassets'
|
4
|
+
require 'dassets/asset_file'
|
5
|
+
|
6
|
+
module Dassets
|
7
|
+
|
8
|
+
class SourceFile
|
9
|
+
|
10
|
+
attr_reader :file_path
|
11
|
+
|
12
|
+
def initialize(file_path)
|
13
|
+
@file_path = file_path
|
14
|
+
@ext_list = File.basename(@file_path).split('.').reverse
|
15
|
+
end
|
16
|
+
|
17
|
+
def exists?
|
18
|
+
File.file?(@file_path)
|
19
|
+
end
|
20
|
+
|
21
|
+
def digest
|
22
|
+
return if !self.exists?
|
23
|
+
|
24
|
+
Dassets::AssetFile.new(self.digest_path, self.fingerprint).tap do |asset_file|
|
25
|
+
FileUtils.mkdir_p(File.dirname(asset_file.output_path))
|
26
|
+
File.open(asset_file.output_path, "w"){ |f| f.write(self.compiled) }
|
27
|
+
Dassets.digests[self.digest_path] = self.fingerprint
|
28
|
+
Dassets.digests.save!
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def digest_path
|
33
|
+
@digest_path ||= begin
|
34
|
+
digest_basename = @ext_list.inject([]) do |digest_ext_list, ext|
|
35
|
+
digest_ext_list << Dassets.config.engines[ext].ext(ext)
|
36
|
+
end.reject{ |e| e.empty? }.reverse.join('.')
|
37
|
+
|
38
|
+
File.join([
|
39
|
+
digest_dirname(@file_path, Dassets.config.source_path),
|
40
|
+
digest_basename
|
41
|
+
].reject{ |p| p.empty? })
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def compiled
|
46
|
+
@compiled ||= @ext_list.inject(read_file(@file_path)) do |content, ext|
|
47
|
+
Dassets.config.engines[ext].compile(content)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def fingerprint
|
52
|
+
@fingerprint ||= Digest::MD5.new.hexdigest(self.compiled)
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def digest_dirname(file_path, source_path)
|
58
|
+
slash_path(File.dirname(file_path)).sub(slash_path(source_path), '')
|
59
|
+
end
|
60
|
+
|
61
|
+
def slash_path(path)
|
62
|
+
File.join(path, '')
|
63
|
+
end
|
64
|
+
|
65
|
+
def read_file(path)
|
66
|
+
File.send(File.respond_to?(:binread) ? :binread : :read, path.to_s)
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|