mbj-assets 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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,3 @@
1
+ body
2
+ #hello
3
+ display: none
@@ -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
@@ -0,0 +1,3 @@
1
+ require 'assets'
2
+ require 'devtools'
3
+ Devtools.init_spec_helper