lacquer 0.4.0 → 0.6.6

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 (47) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -20
  3. data/.travis.yml +3 -0
  4. data/Gemfile +2 -7
  5. data/README.md +260 -0
  6. data/Rakefile +11 -33
  7. data/init.rb +1 -1
  8. data/lacquer.gemspec +19 -69
  9. data/lib/generators/lacquer/install_generator.rb +5 -1
  10. data/lib/generators/lacquer/templates/initializer.rb +15 -3
  11. data/lib/generators/lacquer/templates/{varnish.sample.vcl → varnish.vcl.erb} +27 -27
  12. data/lib/generators/lacquer/templates/varnishd.yml +31 -0
  13. data/lib/lacquer/cache_control.rb +75 -0
  14. data/lib/lacquer/cache_utils.rb +14 -12
  15. data/lib/lacquer/capistrano/v2/hooks.rb +21 -0
  16. data/lib/lacquer/capistrano/v3/tasks/lacquer.rake +24 -0
  17. data/lib/lacquer/capistrano.rb +7 -0
  18. data/lib/lacquer/configuration.rb +12 -0
  19. data/lib/lacquer/delayed_job_job.rb +2 -2
  20. data/lib/lacquer/railtie.rb +9 -0
  21. data/lib/lacquer/recipes.rb +62 -0
  22. data/lib/lacquer/resque_job.rb +2 -2
  23. data/lib/lacquer/sidekiq_worker.rb +11 -0
  24. data/lib/lacquer/tasks.rb +48 -0
  25. data/lib/lacquer/varnish.rb +35 -4
  26. data/lib/lacquer/varnishd.rb +152 -0
  27. data/lib/lacquer/version.rb +3 -0
  28. data/lib/lacquer.rb +9 -0
  29. data/rails/init.rb +1 -1
  30. data/recipes/lacquer.rb +1 -0
  31. data/spec/config/generate.vcl.erb +15 -0
  32. data/spec/config/varnish.vcl +15 -0
  33. data/spec/config/varnishd.yml +12 -0
  34. data/spec/lacquer/cache_control_spec.rb +74 -0
  35. data/spec/lacquer/cache_utils_spec.rb +20 -8
  36. data/spec/lacquer/delayed_job_job_spec.rb +4 -2
  37. data/spec/lacquer/resque_job_spec.rb +4 -2
  38. data/spec/lacquer/sidekiq_worker_spec.rb +15 -0
  39. data/spec/lacquer/varnish_spec.rb +62 -18
  40. data/spec/lacquer/varnishd_spec.rb +122 -0
  41. data/spec/spec_helper.rb +13 -1
  42. data/tasks/lacquer.rake +1 -0
  43. metadata +135 -66
  44. data/.bundle/config +0 -2
  45. data/Gemfile.lock +0 -39
  46. data/README.rdoc +0 -83
  47. data/VERSION +0 -1
@@ -0,0 +1,74 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '../../spec_helper')
2
+ require 'lacquer/cache_control'
3
+
4
+ describe Lacquer::CacheControl do
5
+ describe "#register" do
6
+ it "persists cache settings for url" do
7
+ cache_control = described_class.new
8
+ cache_control.register :class_section, :url => "^/sv/class_sections/%s.*$", :args => "[0-9]+"
9
+ cache_control.store.first[:group].should == :class_section
10
+ cache_control.store.first[:url].should == "^/sv/class_sections/%s.*$"
11
+ cache_control.store.first[:args].should == ["[0-9]+"]
12
+ end
13
+ end
14
+
15
+ describe "#urls_for" do
16
+ it "returns urls to expire for object" do
17
+ cache_control = described_class.new
18
+ cache_control.register :class_section, :url => "^/sv/class_sections/%s.*$", :args => "[0-9]+"
19
+ cache_control.urls_for(:class_section, double("ClassSection", :to_param => 1)).should == ["^/sv/class_sections/1.*$"]
20
+ end
21
+ end
22
+
23
+ context "vcl" do
24
+ it "returns all urls as vcl conditions" do
25
+ cache_control = described_class.new
26
+ cache_control.register :class_section, :url => "^/sv/class_sections/%s.*$", :args => "[0-9]+"
27
+ cache_control.register :class_section, :url => "^/sv/info_screens/%s.*$", :args => "[0-9]+"
28
+
29
+ conditions = cache_control.to_vcl_conditions
30
+ conditions.should include("req.url ~ \"^/sv/class_sections/[0-9]+.*$\"")
31
+ conditions.should include("||")
32
+ conditions.should include("req.url ~ \"^/sv/info_screens/[0-9]+.*$\"")
33
+ end
34
+
35
+ it "returns vcl for pass urls" do
36
+ cache_control = described_class.new
37
+ cache_control.register :pass, :url => "^/admin"
38
+ pass_urls = cache_control.to_vcl_pass_urls
39
+ pass_urls.should include('if(req.url ~ "^/admin")')
40
+ pass_urls.should include('return(pass)')
41
+ end
42
+
43
+ it "returns vcl for pipe urls" do
44
+ cache_control = described_class.new
45
+ cache_control.register :pipe, :url => "*.mp4$"
46
+ pass_urls = cache_control.to_vcl_pipe_urls
47
+ pass_urls.should include('if(req.url ~ "*.mp4$")')
48
+ pass_urls.should include('return(pipe)')
49
+ end
50
+
51
+ it "returns vcl for override ttl on beresp" do
52
+ cache_control = described_class.new
53
+ cache_control.register :class_section, :url => "^/sv/competitions$", :expires_in => "7d"
54
+ override_ttl = cache_control.to_vcl_override_ttl_urls
55
+ override_ttl.should include('if(req.url ~ "^/sv/competitions$")')
56
+ override_ttl.should include('unset beresp.http.Set-Cookie')
57
+ override_ttl.should include('return(deliver)')
58
+ end
59
+
60
+ it "group by expires in" do
61
+ cache_control = described_class.new
62
+ cache_control.register :class_section, :url => "^/sv/competitions$", :expires_in => "1d"
63
+ cache_control.register :class_section, :url => "^/sv/competitions/%s$", :args => "[0-9]+", :expires_in => "2d"
64
+ cache_control.register :class_section, :url => "^/sv/competitions/%s/info_screen$", :args => "[0-9]+"
65
+
66
+ override_ttl = cache_control.to_vcl_override_ttl_urls
67
+ override_ttl.should include('if(req.url ~ "^/sv/competitions$")')
68
+ override_ttl.should include('set beresp.ttl = 1d')
69
+ override_ttl.should include('if(req.url ~ "^/sv/competitions/[0-9]+$")')
70
+ override_ttl.should include('set beresp.ttl = 2d')
71
+ override_ttl.should_not include('info_screen')
72
+ end
73
+ end
74
+ end
@@ -1,4 +1,7 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '../../spec_helper')
2
+ require 'lacquer/delayed_job_job'
3
+ require 'lacquer/resque_job'
4
+ require 'lacquer/sidekiq_worker'
2
5
 
3
6
  describe "Lacquer" do
4
7
  before(:each) do
@@ -7,8 +10,8 @@ describe "Lacquer" do
7
10
 
8
11
  describe "talking to varnish" do
9
12
  before(:each) do
10
- @varnish_stub = mock('varnish')
11
- Lacquer::Varnish.stub!(:new).and_return(@varnish_stub)
13
+ @varnish_stub = double('varnish')
14
+ Lacquer::Varnish.stub(:new).and_return(@varnish_stub)
12
15
  end
13
16
 
14
17
  describe "when backend is :none" do
@@ -17,7 +20,7 @@ describe "Lacquer" do
17
20
  end
18
21
 
19
22
  it "sends commands to varnish instantly" do
20
- @varnish_stub.should_receive(:purge).twice
23
+ @varnish_stub.should_receive(:purge).with('/', '/blog/posts').once
21
24
  @controller.clear_cache_for('/', '/blog/posts')
22
25
  end
23
26
 
@@ -30,8 +33,8 @@ describe "Lacquer" do
30
33
  describe "when backend is :delayed_job" do
31
34
  it "sends commands to a delayed_job queue" do
32
35
  Lacquer.configuration.job_backend = :delayed_job
33
-
34
- Delayed::Job.should_receive(:enqueue).twice
36
+ Lacquer::DelayedJobJob.should_receive(:new).with(['/', '/blog/posts'])
37
+ Delayed::Job.should_receive(:enqueue).once
35
38
  @controller.clear_cache_for('/', '/blog/posts')
36
39
  end
37
40
  end
@@ -40,7 +43,16 @@ describe "Lacquer" do
40
43
  it "sends commands to a resque queue" do
41
44
  Lacquer.configuration.job_backend = :resque
42
45
 
43
- Resque.should_receive(:enqueue).twice
46
+ Resque.should_receive(:enqueue).once
47
+ @controller.clear_cache_for('/', '/blog/posts')
48
+ end
49
+ end
50
+
51
+ describe "when backend is :sidekiq" do
52
+ it "sends commands to a sidekiq queue" do
53
+ Lacquer.configuration.job_backend = :sidekiq
54
+
55
+ Lacquer::SidekiqWorker.should_receive(:perform_async).once
44
56
  @controller.clear_cache_for('/', '/blog/posts')
45
57
  end
46
58
  end
@@ -70,8 +82,8 @@ describe "Lacquer" do
70
82
  end
71
83
 
72
84
  it "should allow purge by non-controller sweepers" do
73
- @varnish_stub = mock('varnish')
74
- Lacquer::Varnish.stub!(:new).and_return(@varnish_stub)
85
+ @varnish_stub = double('varnish')
86
+ Lacquer::Varnish.stub(:new).and_return(@varnish_stub)
75
87
 
76
88
  @sweeper = SweeperClass.new
77
89
 
@@ -3,8 +3,10 @@ require File.expand_path(File.dirname(__FILE__) + '../../spec_helper')
3
3
  describe "DelayedJobJob" do
4
4
  describe "perform" do
5
5
  it "should purge the parameter" do
6
- @varnish_mock = mock('varnish')
7
- Lacquer::Varnish.stub!(:new).and_return(@varnish_mock)
6
+ require File.expand_path('lib/lacquer/delayed_job_job')
7
+
8
+ @varnish_mock = double('varnish')
9
+ Lacquer::Varnish.stub(:new).and_return(@varnish_mock)
8
10
 
9
11
  @varnish_mock.should_receive(:purge).with('/').exactly(1).times
10
12
  Lacquer::DelayedJobJob.new('/').perform
@@ -3,8 +3,10 @@ require File.expand_path(File.dirname(__FILE__) + '../../spec_helper')
3
3
  describe "ResqueJob" do
4
4
  describe "perform" do
5
5
  it "should purge the parameter" do
6
- @varnish_mock = mock('varnish')
7
- Lacquer::Varnish.stub!(:new).and_return(@varnish_mock)
6
+ require File.expand_path('lib/lacquer/resque_job')
7
+
8
+ @varnish_mock = double('varnish')
9
+ Lacquer::Varnish.stub(:new).and_return(@varnish_mock)
8
10
 
9
11
  @varnish_mock.should_receive(:purge).with('/').exactly(1).times
10
12
  Lacquer::ResqueJob.perform('/')
@@ -0,0 +1,15 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '../../spec_helper')
2
+
3
+ describe "SidekiqWorker" do
4
+ describe "perform" do
5
+ it "should purge the parameter" do
6
+ require File.expand_path('lib/lacquer/sidekiq_worker')
7
+
8
+ @varnish_mock = double('varnish')
9
+ Lacquer::Varnish.stub(:new).and_return(@varnish_mock)
10
+
11
+ @varnish_mock.should_receive(:purge).with('/').exactly(1).times
12
+ Lacquer::SidekiqWorker.new.perform('/')
13
+ end
14
+ end
15
+ end
@@ -2,30 +2,30 @@ require File.expand_path(File.dirname(__FILE__) + '../../spec_helper')
2
2
 
3
3
  describe "Varnish" do
4
4
  before(:each) do
5
- @telnet_mock = mock('Net::Telnet')
6
- Net::Telnet.stub!(:new).and_return(@telnet_mock)
7
- @telnet_mock.stub!(:close)
8
- @telnet_mock.stub!(:cmd)
9
- @telnet_mock.stub!(:puts)
10
- @telnet_mock.stub!(:waitfor)
5
+ @telnet_mock = double('Net::Telnet')
6
+ Net::Telnet.stub(:new).and_return(@telnet_mock)
7
+ @telnet_mock.stub(:close)
8
+ @telnet_mock.stub(:cmd)
9
+ @telnet_mock.stub(:puts)
10
+ @telnet_mock.stub(:waitfor)
11
11
  Lacquer.configuration.retries.should == 5
12
12
  end
13
13
 
14
14
  describe "with any command" do
15
15
  describe "when connection is unsuccessful" do
16
16
  it "should raise a Lacquer::VarnishError" do
17
- @telnet_mock.stub!(:cmd).and_raise(Timeout::Error)
18
- lambda {
17
+ @telnet_mock.stub(:cmd).and_raise(Timeout::Error)
18
+ expect {
19
19
  Lacquer::Varnish.new.purge('/')
20
- }.should raise_error(Lacquer::VarnishError)
20
+ }.to raise_error(Lacquer::VarnishError)
21
21
  end
22
22
 
23
23
  it "should retry on failure before erroring" do
24
- @telnet_mock.stub!(:cmd).and_raise(Timeout::Error)
24
+ @telnet_mock.stub(:cmd).and_raise(Timeout::Error)
25
25
  Net::Telnet.should_receive(:new).exactly(5).times
26
- lambda {
26
+ expect {
27
27
  Lacquer::Varnish.new.purge('/')
28
- }.should raise_error(Lacquer::VarnishError)
28
+ }.to raise_error(Lacquer::VarnishError)
29
29
  end
30
30
 
31
31
  it "should close the connection afterwards" do
@@ -34,23 +34,67 @@ describe "Varnish" do
34
34
  end
35
35
  end
36
36
 
37
+ describe "when using authentication" do
38
+ after(:each) do
39
+ Lacquer.configuration.varnish_servers.first[:secret] = nil
40
+ end
41
+ describe "with correct secret" do
42
+ before(:each) do
43
+ Lacquer.configuration.varnish_servers.first[:secret] = "the real secret"
44
+ end
45
+
46
+ it "should return successfully when using correct secret" do
47
+ @telnet_mock.stub(:waitfor).with("Match" => /^107/).and_yield("107 59 \nhaalpffwlcvblmdrinpnjwigwsbiiigq\n\nAuthentication required.\n\n")
48
+ @telnet_mock.stub(:cmd).with("String" => "auth a4aefcde4b0ee27268af1c9ed613e3220601276b48f9ae5914f801db6c8ef612", "Match" => /\d{3}/).and_yield('200')
49
+ @telnet_mock.stub(:cmd).with("String" => "url.purge /", "Match" => /\n\n/).and_yield('200')
50
+
51
+ expect {
52
+ Lacquer::Varnish.new.purge('/')
53
+ }.not_to raise_error
54
+ end
55
+
56
+ after(:each) do
57
+ Lacquer.configuration.varnish_servers.first[:secret] = nil
58
+ end
59
+ end
60
+
61
+ describe "with wrong secret" do
62
+ before(:each) do
63
+ Lacquer.configuration.varnish_servers.first[:secret] = "the wrong secret"
64
+ end
65
+ it "should raise Lacquer::AuthenticationError when using wrong secret" do
66
+ @telnet_mock.stub(:waitfor).with("Match" => /^107/).and_yield("107 59 \nhaalpffwlcvblmdrinpnjwigwsbiiigq\n\nAuthentication required.\n\n")
67
+ @telnet_mock.stub(:cmd).with("String" => "auth 767dc6ec9eca6e4155d20c8479d3a1a10cf88d92c3846388a830d7fd966d58f9", "Match" => /\d{3}/).and_yield('107')
68
+ @telnet_mock.stub(:cmd).with("url.purge /").and_yield('200')
69
+
70
+ expect {
71
+ Lacquer::Varnish.new.purge('/')
72
+ }.to raise_error(Lacquer::AuthenticationError)
73
+ end
74
+ after(:each) do
75
+ Lacquer.configuration.varnish_servers.first[:secret] = nil
76
+ end
77
+ end
78
+ end
79
+
37
80
  describe "when connection is unsuccessful and an error handler is set" do
38
81
  before(:each) do
39
- Lacquer.configuration.command_error_handler = mock("command_error_handler")
82
+ Lacquer.configuration.command_error_handler = double("command_error_handler")
40
83
  end
41
84
  it "should call handler on error" do
42
- @telnet_mock.stub!(:cmd).and_raise(Timeout::Error)
85
+ @telnet_mock.stub(:cmd).and_raise(Timeout::Error)
43
86
  Lacquer.configuration.command_error_handler.should_receive(:call).exactly(1).times
44
- lambda {
87
+ expect {
45
88
  Lacquer::Varnish.new.purge('/')
46
- }.should_not raise_error(Lacquer::VarnishError)
89
+ }.not_to raise_error
47
90
  end
48
91
  end
92
+
49
93
  end
50
94
 
51
95
  describe "when sending a stats command" do
52
96
  it "should return an array of stats" do
53
- @telnet_mock.stub!(:cmd).and_yield(%Q[
97
+ @telnet_mock.stub(:cmd).and_yield(%Q[
54
98
  200 2023
55
99
  6263596 Client connections accepted
56
100
  6260911 Client requests received
@@ -125,7 +169,7 @@ Closing CLI connection
125
169
 
126
170
  describe "when sending a purge command" do
127
171
  it "should return successfully" do
128
- @telnet_mock.stub!(:cmd).and_yield('200')
172
+ @telnet_mock.stub(:cmd).with("String" => "url.purge /", "Match" => /\n\n/).and_yield('200')
129
173
  Lacquer::Varnish.new.purge('/').should be(true)
130
174
  end
131
175
  end
@@ -0,0 +1,122 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '../../spec_helper')
2
+
3
+ describe "Varnishd" do
4
+ before do
5
+ spec_root = Pathname.new(__FILE__).dirname.join('..').expand_path
6
+ Lacquer::Varnishd.stub(:started_check_delay).and_return(0)
7
+ Lacquer::Varnishd.stub(:env).and_return('test')
8
+ Lacquer::Varnishd.stub(:root_path).and_return(spec_root)
9
+ end
10
+
11
+ def executes_with(regexp)
12
+ new_method = Lacquer::Varnishd.method(:new)
13
+ Lacquer::Varnishd.stub(:new).and_return do |*args|
14
+ varnishd = new_method.call(*args)
15
+ varnishd.should_receive(:execute).with(regexp)
16
+ varnishd.stub(:log)
17
+ varnishd
18
+ end
19
+ end
20
+
21
+ it "passes settings in the initailizer" do
22
+ Lacquer::Varnishd.new("listen" => ":80").listen.should == ":80"
23
+ end
24
+
25
+ it "loads settings from varnish_config" do
26
+ Lacquer::Varnishd.config.should have_key("listen")
27
+ Lacquer::Varnishd.config.should have_key("telnet")
28
+ Lacquer::Varnishd.config.should have_key("sbin_path")
29
+ Lacquer::Varnishd.config.should have_key("bin_path")
30
+ Lacquer::Varnishd.config.should have_key("storage")
31
+ Lacquer::Varnishd.config.should have_key("use_sudo")
32
+ Lacquer::Varnishd.config["params"].should have_key('overflow_max')
33
+ end
34
+
35
+ it "returns full path to varnishd" do
36
+ executes_with(%r[/opt/varnishd/sbin/varnishd])
37
+ Lacquer::Varnishd.new("sbin_path" => "/opt/varnishd/sbin").start
38
+ end
39
+
40
+ it "returns full path to varnishd using sudo" do
41
+ executes_with(%r[sudo /opt/varnishd/sbin/varnishd])
42
+ Lacquer::Varnishd.new("sbin_path" => "/opt/varnishd/sbin", "use_sudo" => true).start
43
+ end
44
+
45
+ it "returns pid file" do
46
+ executes_with(/log\/varnishd.test.pid/)
47
+ Lacquer::Varnishd.new("sbin_path" => "/opt/varnishd/sbin").start
48
+ end
49
+
50
+ it "returns pid file with custom path" do
51
+ executes_with(/pid\/varnishd.test.pid/)
52
+ Lacquer::Varnishd.new("sbin_path" => "/opt/varnishd/sbin", "pid_path" => "pid/").start
53
+ end
54
+
55
+ it "returns params as string" do
56
+ Lacquer::Varnishd.new("params" => { "max" => 2000, "add" => 2 }).params_args.should == "-p max=2000 -p add=2"
57
+ end
58
+
59
+ it "returns listen arg as string" do
60
+ Lacquer::Varnishd.new("listen" => ":80").args.should include("-a :80")
61
+ end
62
+
63
+ it "starts varnishd with args and params" do
64
+ executes_with(%r[/opt/varnishd/sbin.*-P.*log/varnishd.test.pid])
65
+ Lacquer::Varnishd.new("sbin_path" => "/opt/varnishd/sbin", "params" => { "overflow_max" => 2000 }).start
66
+ end
67
+
68
+ it "raises error if vcl_script_file is not present" do
69
+ Lacquer::Varnishd.stub(:vcl_script_filename).and_return("config/file_not_found.vcl")
70
+ expect {
71
+ Lacquer::Varnishd.new.vcl_script_path
72
+ }.to raise_error
73
+ end
74
+
75
+ it "renders vcl file when erb is present" do
76
+ Lacquer::Varnishd.stub(:vcl_script_filename).and_return("config/generate.vcl")
77
+ result = Lacquer::Varnishd.new.render_vcl
78
+ result.should include('.host = "0.0.0.0"')
79
+ result.should include('.port = "3000"')
80
+ end
81
+
82
+ describe '#reload' do
83
+ def expect_reload_cmd(attributes)
84
+ Time.stub(:now).and_return Time.parse('October 6th, 1984')
85
+ varnishadm_cmd = "#{attributes['bin_path']}/varnishadm -T #{attributes['telnet']}"
86
+ reload_id = "reload#{Time.now.usec}"
87
+ load_cmd = "#{varnishadm_cmd} vcl.load #{reload_id} config/generate.vcl"
88
+ use_cmd = "#{varnishadm_cmd} vcl.use #{reload_id}"
89
+
90
+ executes_with "#{load_cmd} && #{use_cmd}"
91
+ end
92
+
93
+ context 'given varnishd is running' do
94
+ before do
95
+ attributes = { "sbin_path" => "/opt/varnishd/sbin", "bin_path" => "/opt/bin", "telnet" => "localhost:6082" }
96
+ expect_reload_cmd attributes
97
+ @varnishd = Lacquer::Varnishd.new attributes
98
+ @varnishd.stub(:vcl_script_filename).and_return("config/generate.vcl")
99
+ @varnishd.stub(:running?).and_return true
100
+ end
101
+
102
+ it 'executes the varnishadm reload commands' do
103
+ @varnishd.should_receive :generate_vcl
104
+ end
105
+
106
+ after do
107
+ @varnishd.reload
108
+ end
109
+ end
110
+
111
+ context 'given varnishd is not running' do
112
+ before do
113
+ executes_with(%r[/opt/varnishd/sbin/varnishd])
114
+ @varnishd = Lacquer::Varnishd.new("sbin_path" => "/opt/varnishd/sbin")
115
+ end
116
+
117
+ it 'starts varnishd' do
118
+ @varnishd.reload
119
+ end
120
+ end
121
+ end
122
+ end
data/spec/spec_helper.rb CHANGED
@@ -21,6 +21,18 @@ end
21
21
 
22
22
  module Resque; end
23
23
 
24
+ module Sidekiq
25
+ module Worker
26
+ module ClassMethods
27
+ def sidekiq_options(options); end
28
+ end
29
+
30
+ def self.included(base)
31
+ base.extend(ClassMethods)
32
+ end
33
+ end
34
+ end
35
+
24
36
  Lacquer.configure do |config|
25
37
  config.enable_cache = true
26
38
  config.default_ttl = 1.week
@@ -28,6 +40,6 @@ Lacquer.configure do |config|
28
40
  config.varnish_servers << { :host => "0.0.0.0", :port => 6082 }
29
41
  end
30
42
 
31
- Rspec.configure do |c|
43
+ RSpec.configure do |c|
32
44
  c.mock_with :rspec
33
45
  end
@@ -0,0 +1 @@
1
+ load File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'lacquer', 'tasks.rb'))