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.
@@ -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