mbj-assets 0.0.3
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 -0
- data/.gitignore +5 -0
- data/.rspec +1 -0
- data/.travis.yml +10 -0
- data/Gemfile +6 -0
- data/Gemfile.devtools +60 -0
- data/Guardfile +18 -0
- data/Rakefile +2 -0
- data/TODO +2 -0
- data/assets.gemspec +28 -0
- data/config/flay.yml +3 -0
- data/config/flog.yml +2 -0
- data/config/mutant.yml +3 -0
- data/config/reek.yml +93 -0
- data/config/roodi.yml +26 -0
- data/config/yardstick.yml +2 -0
- data/lib/assets/asset.rb +24 -0
- data/lib/assets/builder.rb +88 -0
- data/lib/assets/environment/cache.rb +113 -0
- data/lib/assets/environment/dynamic.rb +56 -0
- data/lib/assets/environment/static.rb +37 -0
- data/lib/assets/environment.rb +34 -0
- data/lib/assets/evaluator.rb +67 -0
- data/lib/assets/handler.rb +39 -0
- data/lib/assets/mime.rb +81 -0
- data/lib/assets/package.rb +124 -0
- data/lib/assets/repository.rb +61 -0
- data/lib/assets/responder.rb +133 -0
- data/lib/assets/rule/compile/css.rb +28 -0
- data/lib/assets/rule/compile/javascript/coffescript.rb +24 -0
- data/lib/assets/rule/compile/javascript.rb +12 -0
- data/lib/assets/rule/compile.rb +99 -0
- data/lib/assets/rule/concat.rb +97 -0
- data/lib/assets/rule/file.rb +67 -0
- data/lib/assets/rule/rename.rb +57 -0
- data/lib/assets/rule.rb +69 -0
- data/lib/assets.rb +37 -0
- data/spec/assets/application.coffee +1 -0
- data/spec/assets/application.sass +3 -0
- data/spec/assets/fonts.css +1 -0
- data/spec/assets/test.jpg +0 -0
- data/spec/integration/assets/spike_spec.rb +156 -0
- data/spec/spec_helper.rb +3 -0
- metadata +247 -0
@@ -0,0 +1,97 @@
|
|
1
|
+
module Assets
|
2
|
+
class Rule
|
3
|
+
# Rule that concatenates assets from other rules
|
4
|
+
class Concat < self
|
5
|
+
include Concord.new(:name, :mime, :rules)
|
6
|
+
|
7
|
+
# Return name
|
8
|
+
#
|
9
|
+
# @return [String]
|
10
|
+
#
|
11
|
+
# @api private
|
12
|
+
#
|
13
|
+
attr_reader :name
|
14
|
+
|
15
|
+
# Return mime
|
16
|
+
#
|
17
|
+
# @return [Mime]
|
18
|
+
#
|
19
|
+
# @api private
|
20
|
+
#
|
21
|
+
attr_reader :mime
|
22
|
+
|
23
|
+
# Instantiate object
|
24
|
+
#
|
25
|
+
# @param [String] name
|
26
|
+
# @param [Enumerable<Rule>] rules
|
27
|
+
#
|
28
|
+
# @api private
|
29
|
+
#
|
30
|
+
def self.new(name, rules)
|
31
|
+
mime = detect_mime(rules)
|
32
|
+
super(name, mime, rules)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Build concat rules
|
36
|
+
#
|
37
|
+
# @param [String] name
|
38
|
+
#
|
39
|
+
# @return [Rule::Concat]
|
40
|
+
#
|
41
|
+
# @api private
|
42
|
+
#
|
43
|
+
def self.build(name, *rules)
|
44
|
+
new(name, rules)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Return body
|
48
|
+
#
|
49
|
+
# @return [String]
|
50
|
+
#
|
51
|
+
# @api private
|
52
|
+
#
|
53
|
+
def body
|
54
|
+
rules.map(&:body).join
|
55
|
+
end
|
56
|
+
|
57
|
+
# Return updated_at
|
58
|
+
#
|
59
|
+
# @return [Time]
|
60
|
+
#
|
61
|
+
# @api private
|
62
|
+
#
|
63
|
+
def updated_at
|
64
|
+
rules = self.rules
|
65
|
+
time = rules.first.updated_at
|
66
|
+
rules.each do |rule|
|
67
|
+
updated_at = rule.updated_at
|
68
|
+
if time < updated_at
|
69
|
+
time = updated_at
|
70
|
+
end
|
71
|
+
end
|
72
|
+
time
|
73
|
+
end
|
74
|
+
|
75
|
+
# Detect mime type
|
76
|
+
#
|
77
|
+
# @param [Rules] rules
|
78
|
+
#
|
79
|
+
# @return [Mime]
|
80
|
+
#
|
81
|
+
# @api private
|
82
|
+
#
|
83
|
+
def self.detect_mime(rules)
|
84
|
+
raise "No mime type for empty rules" if rules.empty?
|
85
|
+
|
86
|
+
mime = rules.first.mime
|
87
|
+
|
88
|
+
unless rules.all? { |rule| rule.mime == mime }
|
89
|
+
raise 'Rules do not share mime type!'
|
90
|
+
end
|
91
|
+
|
92
|
+
mime
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Assets
|
2
|
+
class Rule
|
3
|
+
class File < self
|
4
|
+
include Concord.new(:name, :path)
|
5
|
+
|
6
|
+
# Return path
|
7
|
+
#
|
8
|
+
# TODO: Remove this
|
9
|
+
#
|
10
|
+
# @return [String]
|
11
|
+
#
|
12
|
+
# @api private
|
13
|
+
#
|
14
|
+
attr_reader :path
|
15
|
+
|
16
|
+
# Return name
|
17
|
+
#
|
18
|
+
# @return [String]
|
19
|
+
#
|
20
|
+
# @api private
|
21
|
+
#
|
22
|
+
attr_reader :name
|
23
|
+
|
24
|
+
# Return extname
|
25
|
+
#
|
26
|
+
# @return [String]
|
27
|
+
#
|
28
|
+
# @api private
|
29
|
+
#
|
30
|
+
def extname
|
31
|
+
::File.extname(path)
|
32
|
+
end
|
33
|
+
memoize :extname
|
34
|
+
|
35
|
+
# Return body of asset
|
36
|
+
#
|
37
|
+
# @return [String]
|
38
|
+
#
|
39
|
+
# @api private
|
40
|
+
#
|
41
|
+
def body
|
42
|
+
::File.read(path)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Return mime type
|
46
|
+
#
|
47
|
+
# @return [Mime]
|
48
|
+
#
|
49
|
+
# @api private
|
50
|
+
#
|
51
|
+
def mime
|
52
|
+
Mime.extname(extname)
|
53
|
+
end
|
54
|
+
memoize :mime
|
55
|
+
|
56
|
+
# Return modification time
|
57
|
+
#
|
58
|
+
# @return [Time]
|
59
|
+
#
|
60
|
+
# @api private
|
61
|
+
#
|
62
|
+
def updated_at
|
63
|
+
::File.mtime(path)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Assets
|
2
|
+
class Rule
|
3
|
+
|
4
|
+
# Rule to rename other rule
|
5
|
+
class Rename < self
|
6
|
+
|
7
|
+
# Initialize object
|
8
|
+
#
|
9
|
+
# @param [String] name
|
10
|
+
# the new logical name
|
11
|
+
#
|
12
|
+
# @param [Rule] rule
|
13
|
+
# the rule to rename
|
14
|
+
#
|
15
|
+
include Concord.new(:name, :rule)
|
16
|
+
|
17
|
+
# Return name
|
18
|
+
#
|
19
|
+
# @return [String]
|
20
|
+
#
|
21
|
+
# @api private
|
22
|
+
#
|
23
|
+
attr_reader :name
|
24
|
+
|
25
|
+
# Return body
|
26
|
+
#
|
27
|
+
# @return [String]
|
28
|
+
#
|
29
|
+
# @api private
|
30
|
+
#
|
31
|
+
def body
|
32
|
+
rule.body
|
33
|
+
end
|
34
|
+
|
35
|
+
# Return mime
|
36
|
+
#
|
37
|
+
# @return [Mime]
|
38
|
+
#
|
39
|
+
# @api rpivate
|
40
|
+
#
|
41
|
+
def mime
|
42
|
+
rule.mime
|
43
|
+
end
|
44
|
+
|
45
|
+
# Return updated at
|
46
|
+
#
|
47
|
+
# @return [Time]
|
48
|
+
#
|
49
|
+
# @api private
|
50
|
+
#
|
51
|
+
def updated_at
|
52
|
+
rule.updated_at
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/lib/assets/rule.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
module Assets
|
2
|
+
|
3
|
+
# Abstract base class for rules that generate assets
|
4
|
+
class Rule
|
5
|
+
include Adamantium, AbstractType
|
6
|
+
|
7
|
+
# Return asset
|
8
|
+
#
|
9
|
+
# @return [Asset]
|
10
|
+
#
|
11
|
+
# @api private
|
12
|
+
#
|
13
|
+
def asset
|
14
|
+
Evaluator.new(self).asset
|
15
|
+
end
|
16
|
+
|
17
|
+
# Return body
|
18
|
+
#
|
19
|
+
# @return [String]
|
20
|
+
#
|
21
|
+
# @api private
|
22
|
+
#
|
23
|
+
abstract_method :body
|
24
|
+
|
25
|
+
# Return mime
|
26
|
+
#
|
27
|
+
# @return [Mime]
|
28
|
+
#
|
29
|
+
# @api private
|
30
|
+
#
|
31
|
+
abstract_method :mime
|
32
|
+
|
33
|
+
# Return updated at
|
34
|
+
#
|
35
|
+
# @return [Time]
|
36
|
+
#
|
37
|
+
# @api private
|
38
|
+
#
|
39
|
+
abstract_method :updated_at
|
40
|
+
|
41
|
+
# Return renamed asset
|
42
|
+
#
|
43
|
+
# @param [String] name
|
44
|
+
#
|
45
|
+
# @return [Rule::Rename]
|
46
|
+
#
|
47
|
+
# @api private
|
48
|
+
#
|
49
|
+
def rename(name)
|
50
|
+
Rename.new(name, self)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Test if asset is fresh at specific time
|
54
|
+
#
|
55
|
+
# @param [Time] time
|
56
|
+
#
|
57
|
+
# @return [true]
|
58
|
+
# if asset is fresh at time
|
59
|
+
#
|
60
|
+
# @return [false]
|
61
|
+
#
|
62
|
+
# @api private
|
63
|
+
#
|
64
|
+
def fresh_at?(time)
|
65
|
+
time >= updated_at
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
data/lib/assets.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'time'
|
2
|
+
require 'abstract_type'
|
3
|
+
require 'descendants_tracker'
|
4
|
+
require 'adamantium'
|
5
|
+
require 'concord'
|
6
|
+
require 'equalizer'
|
7
|
+
require 'ice_nine'
|
8
|
+
require 'anima'
|
9
|
+
require 'request'
|
10
|
+
require 'response'
|
11
|
+
require 'sass'
|
12
|
+
require 'coffee-script'
|
13
|
+
|
14
|
+
# Library namespace
|
15
|
+
module Assets
|
16
|
+
end
|
17
|
+
|
18
|
+
require 'assets/asset'
|
19
|
+
require 'assets/repository'
|
20
|
+
require 'assets/mime'
|
21
|
+
require 'assets/evaluator'
|
22
|
+
require 'assets/rule'
|
23
|
+
require 'assets/rule/concat'
|
24
|
+
require 'assets/rule/file'
|
25
|
+
require 'assets/rule/compile'
|
26
|
+
require 'assets/rule/compile/css'
|
27
|
+
require 'assets/rule/compile/javascript'
|
28
|
+
require 'assets/rule/compile/javascript/coffescript'
|
29
|
+
require 'assets/rule/rename'
|
30
|
+
require 'assets/environment'
|
31
|
+
require 'assets/environment/static'
|
32
|
+
require 'assets/environment/dynamic'
|
33
|
+
require 'assets/environment/cache'
|
34
|
+
require 'assets/responder'
|
35
|
+
require 'assets/handler'
|
36
|
+
require 'assets/package'
|
37
|
+
require 'assets/builder'
|
@@ -0,0 +1 @@
|
|
1
|
+
alert "Hello World"
|
@@ -0,0 +1 @@
|
|
1
|
+
// Example body of fonts.css
|
Binary file
|
@@ -0,0 +1,156 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Assets, 'and spiking around' do
|
4
|
+
|
5
|
+
let(:root) do
|
6
|
+
Pathname.new(__FILE__).parent.parent.parent.join('assets')
|
7
|
+
end
|
8
|
+
|
9
|
+
let(:repository) do
|
10
|
+
Assets::Repository::Directory.new(root)
|
11
|
+
end
|
12
|
+
|
13
|
+
let(:fonts) do
|
14
|
+
repository.file('fonts.css')
|
15
|
+
end
|
16
|
+
|
17
|
+
let(:application) do
|
18
|
+
repository.compile('application.sass')
|
19
|
+
end
|
20
|
+
|
21
|
+
let(:stylesheet) do
|
22
|
+
Assets::Rule::Concat.build('application.css', fonts, application)
|
23
|
+
end
|
24
|
+
|
25
|
+
let(:images) do
|
26
|
+
repository.glob('*.jpg').map do |name|
|
27
|
+
repository.file(name)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
let(:rules) do
|
32
|
+
rules = []
|
33
|
+
rules.concat(images)
|
34
|
+
rules << repository.compile('application.coffee')
|
35
|
+
rules << stylesheet
|
36
|
+
end
|
37
|
+
|
38
|
+
let(:environment) do
|
39
|
+
Assets::Environment::Cache.build(rules)
|
40
|
+
end
|
41
|
+
|
42
|
+
let(:server) do
|
43
|
+
Assets::Handler.new(environment, '/assets/')
|
44
|
+
end
|
45
|
+
|
46
|
+
let(:request) do
|
47
|
+
Request::Rack.new({'PATH_INFO' => path_info}.merge(extra_hash))
|
48
|
+
end
|
49
|
+
|
50
|
+
subject do
|
51
|
+
server.call(application, request)
|
52
|
+
end
|
53
|
+
|
54
|
+
def strip_indentation(text)
|
55
|
+
lines = text.split("\n")
|
56
|
+
match = /\A[ ]*/.match(lines.first)
|
57
|
+
length = match[0].length
|
58
|
+
lines.map { |line| line[(length)..-1] }.join("\n") << "\n"
|
59
|
+
end
|
60
|
+
|
61
|
+
let(:expected_body) do
|
62
|
+
strip_indentation(<<-CSS)
|
63
|
+
// Example body of fonts.css
|
64
|
+
body #hello {
|
65
|
+
display: none; }
|
66
|
+
CSS
|
67
|
+
end
|
68
|
+
|
69
|
+
context 'with development environment' do
|
70
|
+
|
71
|
+
let(:asset) { environment.get(name) }
|
72
|
+
let(:path_info) { "/assets/#{name}" }
|
73
|
+
|
74
|
+
context 'accessing image' do
|
75
|
+
let(:name) { 'test.jpg' }
|
76
|
+
let(:extra_hash) { {} }
|
77
|
+
let(:expected_body) { File.read('spec/assets/test.jpg') }
|
78
|
+
|
79
|
+
its(:content_type) { should eql('image/jpg') }
|
80
|
+
its(:last_modified) { should eql(Time.httpdate(asset.created_at.httpdate)) }
|
81
|
+
its(:cache_control) { should eql('max-age=120, must-revalidate') }
|
82
|
+
its(:status) { should be(Response::Status::OK) }
|
83
|
+
its(:body) { should eql(expected_body) }
|
84
|
+
end
|
85
|
+
|
86
|
+
context 'compiling coffescript' do
|
87
|
+
let(:name) { 'application.js' }
|
88
|
+
let(:extra_hash) { {} }
|
89
|
+
let(:expected_body) do
|
90
|
+
strip_indentation(<<-JAVASCRIPT)
|
91
|
+
(function() {
|
92
|
+
alert("Hello World");
|
93
|
+
|
94
|
+
}).call(this);
|
95
|
+
JAVASCRIPT
|
96
|
+
end
|
97
|
+
|
98
|
+
its(:content_type) { should eql('application/javascript; charset=UTF-8') }
|
99
|
+
its(:last_modified) { should eql(Time.httpdate(asset.created_at.httpdate)) }
|
100
|
+
its(:cache_control) { should eql('max-age=120, must-revalidate') }
|
101
|
+
its(:status) { should be(Response::Status::OK) }
|
102
|
+
its(:body) { should eql(expected_body) }
|
103
|
+
end
|
104
|
+
|
105
|
+
context 'with unknown asset' do
|
106
|
+
let(:name) { 'not_found.txt' }
|
107
|
+
let(:path_info) { '/assets/not_found.txt' }
|
108
|
+
let(:extra_hash) { {} }
|
109
|
+
its(:body) { should eql('Not Found') }
|
110
|
+
its(:cache_control) { should eql('max-age=120, must-revalidate') }
|
111
|
+
its(:status) { should be(Response::Status::NOT_FOUND) }
|
112
|
+
end
|
113
|
+
|
114
|
+
context 'with known conditional get' do
|
115
|
+
|
116
|
+
|
117
|
+
let(:extra_hash) { {'HTTP_IF_MODIFIED_SINCE' => lastmod_time.httpdate } }
|
118
|
+
let(:name) { 'application.css' }
|
119
|
+
|
120
|
+
context 'with modified_since < asset.created_at' do
|
121
|
+
|
122
|
+
let(:lastmod_time) { asset.created_at - 1000 }
|
123
|
+
its(:status) { should be(Response::Status::OK) }
|
124
|
+
|
125
|
+
its(:body) { should eql(expected_body) }
|
126
|
+
its(:content_type) { should eql('text/css; charset=UTF-8') }
|
127
|
+
its(:last_modified) { should eql(Time.httpdate(asset.created_at.httpdate)) }
|
128
|
+
its(:cache_control) { should eql('max-age=120, must-revalidate') }
|
129
|
+
|
130
|
+
end
|
131
|
+
|
132
|
+
context 'with modified_since > asset.created_at' do
|
133
|
+
|
134
|
+
let(:lastmod_time) { asset.created_at + 1000 }
|
135
|
+
its(:status) { should be(Response::Status::NOT_MODIFIED) }
|
136
|
+
|
137
|
+
its(:body) { should be(nil) }
|
138
|
+
its(:content_type) { should be(nil) }
|
139
|
+
its(:last_modified) { should eql(Time.httpdate(stylesheet.asset.created_at.httpdate)) }
|
140
|
+
its(:cache_control) { should eql('max-age=120, must-revalidate') }
|
141
|
+
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
context 'with known asset and unconditional get' do
|
146
|
+
let(:path_info) { '/assets/application.css' }
|
147
|
+
let(:extra_hash) { {} }
|
148
|
+
its(:content_type) { should eql('text/css; charset=UTF-8') }
|
149
|
+
its(:last_modified) { should eql(Time.httpdate(stylesheet.asset.created_at.httpdate)) }
|
150
|
+
its(:cache_control) { should eql('max-age=120, must-revalidate') }
|
151
|
+
its(:status) { should be(Response::Status::OK) }
|
152
|
+
its(:body) { should eql(expected_body) }
|
153
|
+
end
|
154
|
+
|
155
|
+
end
|
156
|
+
end
|
data/spec/spec_helper.rb
ADDED