dassets 0.0.1 → 0.1.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. data/README.md +56 -2
  2. data/bin/dassets +7 -0
  3. data/dassets.gemspec +9 -3
  4. data/lib/dassets/asset_file.rb +65 -0
  5. data/lib/dassets/cli.rb +109 -0
  6. data/lib/dassets/digests_file.rb +70 -0
  7. data/lib/dassets/root_path.rb +12 -0
  8. data/lib/dassets/runner/cache_command.rb +46 -0
  9. data/lib/dassets/runner/digest_command.rb +65 -0
  10. data/lib/dassets/runner.rb +42 -0
  11. data/lib/dassets/server/request.rb +48 -0
  12. data/lib/dassets/server/response.rb +36 -0
  13. data/lib/dassets/server.rb +37 -0
  14. data/lib/dassets/version.rb +1 -1
  15. data/lib/dassets.rb +32 -2
  16. data/test/helper.rb +3 -1
  17. data/test/support/app/assets/.digests +4 -0
  18. data/test/support/app/assets/public/file1.txt +1 -0
  19. data/test/support/app/assets/public/file2.txt +1 -0
  20. data/test/support/app/assets/public/grumpy_cat.jpg +0 -0
  21. data/test/support/app/assets/public/nested/file3.txt +0 -0
  22. data/test/support/app.rb +10 -0
  23. data/test/support/app_public/.gitkeep +0 -0
  24. data/test/support/config/assets.rb +7 -0
  25. data/test/support/example.digests +3 -0
  26. data/test/support/public/file1-daa05c683a4913b268653f7a7e36a5b4.txt +1 -0
  27. data/test/support/public/file2-9bbe1047cffbb590f59e0e5aeff46ae4.txt +1 -0
  28. data/test/support/public/grumpy_cat-b0d1f399a916f7a25c4c0f693c619013.jpg +0 -0
  29. data/test/support/public/nested/file3-d41d8cd98f00b204e9800998ecf8427e.txt +0 -0
  30. data/test/system/rack_tests.rb +78 -0
  31. data/test/unit/asset_file_tests.rb +76 -0
  32. data/test/unit/config_tests.rb +27 -0
  33. data/test/unit/dassets_tests.rb +49 -0
  34. data/test/unit/digests_file_tests.rb +90 -0
  35. data/test/unit/runner/cache_command_tests.rb +62 -0
  36. data/test/unit/runner/digest_command_tests.rb +83 -0
  37. data/test/unit/runner_tests.rb +29 -0
  38. data/test/unit/server/request_tests.rb +76 -0
  39. data/test/unit/server/response_tests.rb +70 -0
  40. data/test/unit/server_tests.rb +17 -0
  41. metadata +130 -10
data/README.md CHANGED
@@ -1,10 +1,64 @@
1
1
  # Dassets
2
2
 
3
- Digest and serve asset files.
3
+ Digest and serve HTML asset files.
4
4
 
5
5
  ## Usage
6
6
 
7
- TODO: Write code samples and usage instructions here
7
+ You have some css, js, images, etc files. You want to update, deploy, and serve them in an efficient way. Dassets can help.
8
+
9
+ ### Setup
10
+
11
+ ```ruby
12
+ # in config/dassets.rb
13
+ require 'dassets'
14
+
15
+ Dassets.configure do |c|
16
+
17
+ # tell Dassets what the root path of your app is
18
+ c.root_path '/path/to/app/root'
19
+
20
+ # it works best to *not* keep the asset files in your public dir
21
+ c.files_path '/path/to/not/public' # default: '{root_path}/app/assets/public'
22
+
23
+ # you can choose the file to write the digests to, if you want
24
+ c.digests_file_path '/path/to/.digests' # default: '{files_path}/app/assets/.digests'
25
+
26
+ end
27
+ ```
28
+
29
+ ### Digest
30
+
31
+ ```
32
+ $ dassets digest # rebuild the .digests for all asset files, OR
33
+ $ dassets digest /path/to/asset/file # update the digest for just one file
34
+ ```
35
+
36
+ Use the CLI to build your digests file. Protip: use guard to auto rebuild digests every time you edit an asset file. TODO: link to some guard tools or docs.
37
+
38
+ ### Link To
39
+
40
+ ```rb
41
+ Dassets.init
42
+ Dassets['css/site.css'].href # => "/css/site-123abc.css"
43
+ Dassets['img/logos/main.jpg'].href # => "/img/logos/main-a1b2c3.jpg"
44
+ ```
45
+
46
+ ### Serve
47
+
48
+ In development, use the Dassets middleware to serve your digested asset files:
49
+
50
+ ```ruby
51
+ # `app` is a rack application
52
+ require 'dassets/server'
53
+ app.use Dassets::Server
54
+ ```
55
+
56
+ In production, use the CLI to cache your digested asset files to the public dir:
57
+
58
+ ```
59
+ # call the CLI in your deploy scripts or whatever
60
+ $ dassets cache /path/to/public/dir
61
+ ```
8
62
 
9
63
  ## Installation
10
64
 
data/bin/dassets ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Copyright (c) 2013-Present Kelly Redding and Collin Redding
4
+ #
5
+
6
+ require 'dassets/cli'
7
+ Dassets::CLI.run *ARGV
data/dassets.gemspec CHANGED
@@ -8,8 +8,8 @@ Gem::Specification.new do |gem|
8
8
  gem.version = Dassets::VERSION
9
9
  gem.authors = ["Kelly Redding", "Collin Redding"]
10
10
  gem.email = ["kelly@kellyredding.com", "collin.redding@me.com"]
11
- gem.description = %q{Digest and serve asset files}
12
- gem.summary = %q{Digested Assets}
11
+ gem.description = %q{Digest and serve HTML asset files}
12
+ gem.summary = %q{Digested asset files}
13
13
  gem.homepage = "http://github.com/redding/dassets"
14
14
 
15
15
  gem.files = `git ls-files`.split($/)
@@ -17,6 +17,12 @@ Gem::Specification.new do |gem|
17
17
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
18
  gem.require_paths = ["lib"]
19
19
 
20
- gem.add_development_dependency("assert")
20
+ gem.add_development_dependency("assert", ["~> 2.0"])
21
+ gem.add_development_dependency('assert-rack-test', ["~> 1.0"])
22
+ gem.add_development_dependency("sinatra", ["~> 1.4"])
23
+
24
+
25
+ gem.add_dependency('ns-options', ["~> 1.1"])
26
+ gem.add_dependency("rack", ["~> 1.0"])
21
27
 
22
28
  end
@@ -0,0 +1,65 @@
1
+ require 'digest/md5'
2
+ require 'rack/utils'
3
+ require 'rack/mime'
4
+
5
+ module Dassets; end
6
+ class Dassets::AssetFile
7
+
8
+ def self.from_abs_path(abs_path)
9
+ rel_path = abs_path.sub("#{Dassets.config.files_path}/", '')
10
+ md5 = Digest::MD5.file(abs_path).hexdigest
11
+ self.new(rel_path, md5)
12
+ end
13
+
14
+ attr_reader :path, :md5, :dirname, :extname, :basename
15
+ attr_reader :files_path, :cache_path, :href
16
+
17
+ def initialize(rel_path, md5)
18
+ @path, @md5 = rel_path, md5
19
+ @dirname = File.dirname(@path)
20
+ @extname = File.extname(@path)
21
+ @basename = File.basename(@path, @extname)
22
+
23
+ file_name = "#{@basename}-#{@md5}#{@extname}"
24
+ @files_path = File.join(Dassets.config.files_path, @path)
25
+ @cache_path = File.join(@dirname, file_name).sub(/^\.\//, '').sub(/^\//, '')
26
+ @href = "/#{@cache_path}"
27
+ end
28
+
29
+ def content
30
+ @content ||= if File.exists?(@files_path) && File.file?(@files_path)
31
+ File.read(@files_path)
32
+ end
33
+ end
34
+
35
+ def mtime
36
+ @mtime ||= if File.exists?(@files_path) && File.file?(@files_path)
37
+ File.mtime(@files_path).httpdate
38
+ end
39
+ end
40
+
41
+ # We check via File::size? whether this file provides size info via stat,
42
+ # otherwise we have to figure it out by reading the whole file into memory.
43
+ def size
44
+ @size ||= if File.exists?(@files_path) && File.file?(@files_path)
45
+ File.size?(@files_path) || Rack::Utils.bytesize(self.content)
46
+ end
47
+ end
48
+
49
+ def mime_type
50
+ @mime_type ||= if File.exists?(@files_path) && File.file?(@files_path)
51
+ Rack::Mime.mime_type(@extname)
52
+ end
53
+ end
54
+
55
+ def exists?
56
+ File.exists?(@files_path) && File.file?(@files_path)
57
+ end
58
+
59
+ def ==(other_asset_file)
60
+ other_asset_file.kind_of?(Dassets::AssetFile) &&
61
+ self.path == other_asset_file.path &&
62
+ self.md5 == other_asset_file.md5
63
+ end
64
+
65
+ end
@@ -0,0 +1,109 @@
1
+ require 'dassets/version'
2
+ require 'dassets/runner'
3
+
4
+ module Dassets
5
+
6
+ class CLI
7
+
8
+ def self.run(*args)
9
+ self.new.run(*args)
10
+ end
11
+
12
+ def initialize
13
+ @cli = CLIRB.new
14
+ end
15
+
16
+ def run(*args)
17
+ begin
18
+ @cli.parse!(args)
19
+ Dassets::Runner.new(@cli.args, @cli.opts).run
20
+ rescue CLIRB::HelpExit
21
+ puts help
22
+ rescue CLIRB::VersionExit
23
+ puts Dassets::VERSION
24
+ rescue Dassets::Runner::UnknownCmdError => err
25
+ $stderr.puts "#{err.message}\n\n"
26
+ $stderr.puts help
27
+ exit(1)
28
+ rescue Dassets::Runner::CmdError => err
29
+ $stderr.puts "#{err.message}"
30
+ exit(1)
31
+ rescue Dassets::Runner::CmdFail => err
32
+ exit(1)
33
+ rescue CLIRB::Error => exception
34
+ $stderr.puts "#{exception.message}\n\n"
35
+ $stderr.puts help
36
+ exit(1)
37
+ rescue Exception => exception
38
+ $stderr.puts "#{exception.class}: #{exception.message}"
39
+ $stderr.puts exception.backtrace.join("\n")
40
+ exit(1)
41
+ end
42
+ exit(0)
43
+ end
44
+
45
+ def help
46
+ "Usage: dassets [options] COMMAND\n"\
47
+ "\n"\
48
+ "Options:"\
49
+ "#{@cli}"
50
+ end
51
+
52
+ end
53
+
54
+ class CLIRB # Version 1.0.0, https://github.com/redding/cli.rb
55
+ Error = Class.new(RuntimeError);
56
+ HelpExit = Class.new(RuntimeError); VersionExit = Class.new(RuntimeError)
57
+ attr_reader :argv, :args, :opts, :data
58
+
59
+ def initialize(&block)
60
+ @options = []; instance_eval(&block) if block
61
+ require 'optparse'
62
+ @data, @args, @opts = [], [], {}; @parser = OptionParser.new do |p|
63
+ p.banner = ''; @options.each do |o|
64
+ @opts[o.name] = o.value; p.on(*o.parser_args){ |v| @opts[o.name] = v }
65
+ end
66
+ p.on_tail('--version', ''){ |v| raise VersionExit, v.to_s }
67
+ p.on_tail('--help', ''){ |v| raise HelpExit, v.to_s }
68
+ end
69
+ end
70
+
71
+ def option(*args); @options << Option.new(*args); end
72
+ def parse!(argv)
73
+ @args = (argv || []).dup.tap do |args_list|
74
+ begin; @parser.parse!(args_list)
75
+ rescue OptionParser::ParseError => err; raise Error, err.message; end
76
+ end; @data = @args + [@opts]
77
+ end
78
+ def to_s; @parser.to_s; end
79
+ def inspect
80
+ "#<#{self.class}:#{'0x0%x' % (object_id << 1)} @data=#{@data.inspect}>"
81
+ end
82
+
83
+ class Option
84
+ attr_reader :name, :opt_name, :desc, :abbrev, :value, :klass, :parser_args
85
+
86
+ def initialize(name, *args)
87
+ settings, @desc = args.last.kind_of?(::Hash) ? args.pop : {}, args.pop || ''
88
+ @name, @opt_name, @abbrev = parse_name_values(name, settings[:abbrev])
89
+ @value, @klass = gvalinfo(settings[:value])
90
+ @parser_args = if [TrueClass, FalseClass, NilClass].include?(@klass)
91
+ ["-#{@abbrev}", "--[no-]#{@opt_name}", @desc]
92
+ else
93
+ ["-#{@abbrev}", "--#{@opt_name} #{@opt_name.upcase}", @klass, @desc]
94
+ end
95
+ end
96
+
97
+ private
98
+
99
+ def parse_name_values(name, custom_abbrev)
100
+ [ (processed_name = name.to_s.strip.downcase), processed_name.gsub('_', '-'),
101
+ custom_abbrev || processed_name.gsub(/[^a-z]/, '').chars.first || 'a'
102
+ ]
103
+ end
104
+ def gvalinfo(v); v.kind_of?(Class) ? [nil,gklass(v)] : [v,gklass(v.class)]; end
105
+ def gklass(k); k == Fixnum ? Integer : k; end
106
+ end
107
+ end
108
+
109
+ end
@@ -0,0 +1,70 @@
1
+ require 'dassets/asset_file'
2
+
3
+ module Dassets
4
+
5
+ class DigestsFile
6
+
7
+ attr_reader :path
8
+
9
+ def initialize(file_path)
10
+ @path, @hash = file_path, decode(file_path)
11
+ end
12
+
13
+ def [](*args); @hash.send('[]', *args); end
14
+ def []=(*args); @hash.send('[]=', *args); end
15
+ def delete(*args); @hash.delete(*args); end
16
+
17
+ def each(*args, &block); @hash.each(*args, &block); end
18
+
19
+ def keys; @hash.keys; end
20
+ def values; @hash.values; end
21
+ def empty?; @hash.empty?; end
22
+
23
+ def asset_files
24
+ @hash.map{ |path, md5| Dassets::AssetFile.new(path, md5) }
25
+ end
26
+
27
+ def asset_file(path)
28
+ Dassets::AssetFile.new(path, @hash[path] || '')
29
+ end
30
+
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
+ def save!
38
+ encode(@hash, @path)
39
+ end
40
+
41
+ private
42
+
43
+ def decode(file_path)
44
+ Hash.new.tap do |h|
45
+ if File.exists?(file_path)
46
+ File.open(file_path, 'r').each_line do |l|
47
+ path, md5 = l.split(','); path ||= ''; path.strip!; md5 ||= ''; md5.strip!
48
+ h[path] = md5 if !path.empty?
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ def encode(hash, file_path)
55
+ File.open(file_path, 'w') do |f|
56
+ hash.keys.sort.each{ |path| f.write("#{path.strip},#{hash[path].strip}\n") }
57
+ end
58
+ end
59
+
60
+ end
61
+
62
+ module NullDigestsFile
63
+
64
+ def self.new
65
+ DigestsFile.new('/dev/null')
66
+ end
67
+
68
+ end
69
+
70
+ end
@@ -0,0 +1,12 @@
1
+ # This takes a path string relative to the configured root path and tranforms
2
+ # to the full qualifed root path. The goal here is to specify path options
3
+ # with root-relative path strings.
4
+
5
+ module Dassets; end
6
+ class Dassets::RootPath < String
7
+
8
+ def initialize(path_string)
9
+ super(Dassets.config.root_path.join(path_string).to_s)
10
+ end
11
+
12
+ end
@@ -0,0 +1,46 @@
1
+ require 'pathname'
2
+ require 'fileutils'
3
+ require 'dassets/asset_file'
4
+ require 'dassets/digests_file'
5
+
6
+ module Dassets; end
7
+ class Dassets::Runner; end
8
+ class Dassets::Runner::CacheCommand
9
+
10
+ attr_reader :files_root_path, :cache_root_path, :digests_file, :asset_files
11
+
12
+ def initialize(cache_root_path)
13
+ unless cache_root_path && File.directory?(cache_root_path)
14
+ raise Dassets::Runner::CmdError, "specify an existing cache directory"
15
+ end
16
+
17
+ @files_root_path = Pathname.new(Dassets.config.files_path)
18
+ @cache_root_path = Pathname.new(cache_root_path)
19
+ @digests_file = Dassets::DigestsFile.new(Dassets.config.digests_file_path)
20
+ @asset_files = @digests_file.asset_files
21
+ end
22
+
23
+ def run(write_files=true)
24
+ begin
25
+ @asset_files.each do |file|
26
+ files_path = @files_root_path.join(file.path).to_s
27
+ cache_path = @cache_root_path.join(file.cache_path).to_s
28
+
29
+ if write_files
30
+ FileUtils.mkdir_p File.dirname(cache_path)
31
+ FileUtils.cp(files_path, cache_path)
32
+ end
33
+ end
34
+ unless ENV['DASSETS_TEST_MODE']
35
+ $stdout.puts "#{@asset_files.size} files written to #{@cache_root_path}"
36
+ end
37
+ return write_files
38
+ rescue Exception => e
39
+ unless ENV['DASSETS_TEST_MODE']
40
+ $stderr.puts e, *e.backtrace; $stderr.puts ""
41
+ end
42
+ raise Dassets::Runner::CmdFail
43
+ end
44
+ end
45
+
46
+ end
@@ -0,0 +1,65 @@
1
+ require 'set'
2
+ require 'dassets/asset_file'
3
+ require 'dassets/digests_file'
4
+
5
+ module Dassets; end
6
+ class Dassets::Runner; end
7
+ class Dassets::Runner::DigestCommand
8
+
9
+ attr_reader :asset_files, :digests_file
10
+
11
+ def initialize(file_paths)
12
+ @pwd = ENV['PWD']
13
+ @asset_files = if (file_paths || []).empty?
14
+ get_asset_files([*Dassets.config.files_path])
15
+ else
16
+ get_asset_files(file_paths)
17
+ end
18
+ @digests_file = Dassets::DigestsFile.new(Dassets.config.digests_file_path)
19
+ end
20
+
21
+ def run(save=true)
22
+ begin
23
+ digest_paths = @digests_file.keys
24
+ asset_paths = @asset_files.map{ |f| f.path }
25
+
26
+ (digest_paths - asset_paths).each{ |file| @digests_file.delete(file) }
27
+ @asset_files.each{ |f| @digests_file[f.path] = f.md5 }
28
+
29
+ @digests_file.save! if save
30
+ unless ENV['DASSETS_TEST_MODE']
31
+ $stdout.puts "digested #{@asset_files.size} assets, saved to #{@digests_file.path}"
32
+ end
33
+ return save
34
+ rescue Exception => e
35
+ unless ENV['DASSETS_TEST_MODE']
36
+ $stderr.puts e, *e.backtrace; $stderr.puts ""
37
+ end
38
+ raise Dassets::Runner::CmdFail
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ # Get all file paths fuzzy-matching the given paths. Each path must be a
45
+ # file that exists and is in the `config.files_path` tree. Return them
46
+ # as sorted AssetFile objects.
47
+ def get_asset_files(paths)
48
+ fuzzy_paths(paths).
49
+ select{ |p| is_asset_file?(p) }.
50
+ sort.
51
+ map{ |p| Dassets::AssetFile.from_abs_path(p) }
52
+ end
53
+
54
+ def fuzzy_paths(paths)
55
+ paths.inject(Set.new) do |paths, path|
56
+ p = File.expand_path(path, @pwd)
57
+ paths += Dir.glob("#{p}*") + Dir.glob("#{p}*/**/*")
58
+ end
59
+ end
60
+
61
+ def is_asset_file?(path)
62
+ File.file?(path) && path.include?("#{Dassets.config.files_path}/")
63
+ end
64
+
65
+ end
@@ -0,0 +1,42 @@
1
+ require 'dassets'
2
+
3
+ ENV['DASSETS_CONFIG_FILE'] ||= 'config/assets'
4
+
5
+ module Dassets; end
6
+ class Dassets::Runner
7
+ UnknownCmdError = Class.new(ArgumentError)
8
+ CmdError = Class.new(RuntimeError)
9
+ CmdFail = Class.new(RuntimeError)
10
+
11
+ attr_reader :cmd_name, :cmd_args, :opts
12
+
13
+ def initialize(args, opts)
14
+ @opts = opts
15
+ @cmd_name = args.shift || ""
16
+ @cmd_args = args
17
+ end
18
+
19
+ def run
20
+ require ENV['DASSETS_CONFIG_FILE']
21
+
22
+ case @cmd_name
23
+ when 'digest'
24
+ require 'dassets/runner/digest_command'
25
+ DigestCommand.new(@cmd_args).run
26
+ when 'cache'
27
+ require 'dassets/runner/cache_command'
28
+ CacheCommand.new(@cmd_args.first).run
29
+ when 'null'
30
+ NullCommand.new.run
31
+ else
32
+ raise UnknownCmdError, "unknown command `#{@cmd_name}`"
33
+ end
34
+ end
35
+
36
+ class NullCommand
37
+ def run
38
+ # if this was a real command it would do something here
39
+ end
40
+ end
41
+
42
+ end
@@ -0,0 +1,48 @@
1
+ require 'rack/request'
2
+
3
+ class Dassets::Server
4
+
5
+ class Request < Rack::Request
6
+
7
+ # The HTTP request method. This is the standard implementation of this
8
+ # method but is respecified here due to libraries that attempt to modify
9
+ # the behavior to respect POST tunnel method specifiers. We always want
10
+ # the real request method.
11
+ def request_method; @env['REQUEST_METHOD']; end
12
+ def path_info; @env['PATH_INFO']; end
13
+
14
+ # Determine if the request is for an asset file
15
+ # This will be called on every request so speed is an issue
16
+ # - first check if the request is a GET or HEAD (fast)
17
+ # - then check if for a digest resource (kinda fast)
18
+ # - then check if on a path in the digests (slower)
19
+ def for_asset_file?
20
+ !!((get? || head?) && for_digest_file? && Dassets.digests[asset_path])
21
+ end
22
+
23
+ def asset_path
24
+ @asset_path ||= path_digest_match.captures.select{ |m| !m.empty? }.join
25
+ end
26
+
27
+ def asset_file
28
+ @asset_file ||= Dassets[asset_path]
29
+ end
30
+
31
+ private
32
+
33
+ def for_digest_file?
34
+ !path_digest_match.nil?
35
+ end
36
+
37
+ def path_digest_match
38
+ @path_digest_match ||= begin
39
+ path_info.match(/\/(.+)-[a-f0-9]{32}(\..+|)$/i) || NullDigestMatch.new
40
+ end
41
+ end
42
+
43
+ class NullDigestMatch
44
+ def captures; []; end
45
+ end
46
+
47
+ end
48
+ end
@@ -0,0 +1,36 @@
1
+ require 'rack/response'
2
+ require 'rack/utils'
3
+ require 'rack/mime'
4
+
5
+ class Dassets::Server
6
+
7
+ class Response
8
+ attr_reader :asset_file, :status, :headers, :body
9
+
10
+ def initialize(env, asset_file)
11
+ @asset_file = asset_file
12
+
13
+ mtime = @asset_file.mtime.to_s
14
+ @status, @headers, @body = if env['HTTP_IF_MODIFIED_SINCE'] == mtime
15
+ [ 304, Rack::Utils::HeaderHash.new, [] ]
16
+ elsif !@asset_file.exists?
17
+ [ 404, Rack::Utils::HeaderHash.new, [] ]
18
+ else
19
+ [ 200,
20
+ Rack::Utils::HeaderHash.new.tap do |h|
21
+ h["Content-Type"] = @asset_file.mime_type.to_s
22
+ h["Content-Length"] = @asset_file.size.to_s
23
+ h["Last-Modified"] = mtime
24
+ end,
25
+ env["REQUEST_METHOD"] == "HEAD" ? [] : [ @asset_file.content ]
26
+ ]
27
+ end
28
+ end
29
+
30
+ def to_rack
31
+ [@status, @headers.to_hash, @body]
32
+ end
33
+
34
+ end
35
+
36
+ end
@@ -0,0 +1,37 @@
1
+ require 'dassets/server/request'
2
+ require 'dassets/server/response'
3
+
4
+ # Rack middleware for serving Dassets asset files
5
+
6
+ module Dassets
7
+ class Server
8
+
9
+ def initialize(app)
10
+ @app = app
11
+ end
12
+
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
23
+ end
24
+
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
34
+ end
35
+
36
+ end
37
+ end
@@ -1,3 +1,3 @@
1
1
  module Dassets
2
- VERSION = "0.0.1"
2
+ VERSION = "0.1.0"
3
3
  end
data/lib/dassets.rb CHANGED
@@ -1,5 +1,35 @@
1
- require "dassets/version"
1
+ require 'pathname'
2
+ require 'ns-options'
3
+
4
+ require 'dassets/version'
5
+ require 'dassets/root_path'
6
+ require 'dassets/digests_file'
2
7
 
3
8
  module Dassets
4
- # TODO: your code goes here...
9
+
10
+ def self.config; Config; end
11
+ def self.configure(&block); Config.define(&block); end
12
+
13
+ def self.init
14
+ @digests_file = DigestsFile.new(self.config.digests_file_path)
15
+ end
16
+
17
+ def self.reset
18
+ @digests_file = nil
19
+ end
20
+
21
+ def self.digests; @digests_file || NullDigestsFile.new; end
22
+ def self.[](asset_path)
23
+ self.digests.asset_file(asset_path)
24
+ end
25
+
26
+ class Config
27
+ include NsOptions::Proxy
28
+
29
+ option :root_path, Pathname, :required => true
30
+ option :files_path, RootPath, :default => proc{ "app/assets/public" }
31
+ option :digests_file_path, RootPath, :default => proc{ "app/assets/.digests" }
32
+
33
+ end
34
+
5
35
  end
data/test/helper.rb CHANGED
@@ -7,4 +7,6 @@ $LOAD_PATH.unshift(File.expand_path("../..", __FILE__))
7
7
  # require pry for debugging (`binding.pry`)
8
8
  require 'pry'
9
9
 
10
- # TODO: put test helpers here...
10
+ ENV['DASSETS_TEST_MODE'] = 'yes'
11
+ ENV['DASSETS_CONFIG_FILE'] = 'test/support/config/assets'
12
+ require ENV['DASSETS_CONFIG_FILE']