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,67 @@
|
|
1
|
+
module Assets
|
2
|
+
|
3
|
+
# Rule evaluator
|
4
|
+
class Evaluator
|
5
|
+
include Adamantium, Concord.new(:rule)
|
6
|
+
|
7
|
+
# Return asset
|
8
|
+
#
|
9
|
+
# @return [Asset]
|
10
|
+
#
|
11
|
+
# @api private
|
12
|
+
#
|
13
|
+
def asset
|
14
|
+
rule = self.rule
|
15
|
+
Asset.new(
|
16
|
+
:name => rule.name,
|
17
|
+
:mime => rule.mime,
|
18
|
+
:created_at => Time.now,
|
19
|
+
:body => body,
|
20
|
+
:size => size,
|
21
|
+
:sha1 => sha1
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Return sha1 hexdigest of body
|
26
|
+
#
|
27
|
+
# @return [String]
|
28
|
+
#
|
29
|
+
# @api private
|
30
|
+
#
|
31
|
+
def sha1
|
32
|
+
Digest::SHA1.hexdigest(body)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Return body
|
36
|
+
#
|
37
|
+
# @return [String]
|
38
|
+
#
|
39
|
+
# @api private
|
40
|
+
#
|
41
|
+
def body
|
42
|
+
rule.body
|
43
|
+
end
|
44
|
+
memoize :body
|
45
|
+
|
46
|
+
# Return size in bytes
|
47
|
+
#
|
48
|
+
# @return [Fixnum]
|
49
|
+
#
|
50
|
+
# @api private
|
51
|
+
#
|
52
|
+
def size
|
53
|
+
body.bytesize
|
54
|
+
end
|
55
|
+
|
56
|
+
# Return mime
|
57
|
+
#
|
58
|
+
# @return [Mime]
|
59
|
+
#
|
60
|
+
# @api private
|
61
|
+
#
|
62
|
+
def mime
|
63
|
+
rule.mime
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Assets
|
2
|
+
# Asset request handler
|
3
|
+
class Handler
|
4
|
+
include Adamantium, Concord.new(:environment, :prefix)
|
5
|
+
|
6
|
+
# Instantiate object
|
7
|
+
#
|
8
|
+
# @param [Environment] environment
|
9
|
+
# @param [String] prefix
|
10
|
+
#
|
11
|
+
# @return [undefined]
|
12
|
+
#
|
13
|
+
# @api private
|
14
|
+
#
|
15
|
+
def self.new(environment, prefix = '')
|
16
|
+
super(environment, Regexp.compile(%r(\A#{Regexp.escape(prefix)})))
|
17
|
+
end
|
18
|
+
|
19
|
+
# Call handler
|
20
|
+
#
|
21
|
+
# @param [Application] application
|
22
|
+
# @param [Request] request
|
23
|
+
#
|
24
|
+
# @return [Response]
|
25
|
+
#
|
26
|
+
# @api private
|
27
|
+
#
|
28
|
+
def call(_application, request)
|
29
|
+
name = request.path_info.gsub(prefix, '')
|
30
|
+
asset = environment.get(name)
|
31
|
+
if asset
|
32
|
+
Responder.run(request, asset)
|
33
|
+
else
|
34
|
+
Responder::NOT_FOUND
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
data/lib/assets/mime.rb
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
module Assets
|
2
|
+
class Mime
|
3
|
+
include Adamantium, Concord.new(:extname, :content_type)
|
4
|
+
|
5
|
+
REGISTRY = {}
|
6
|
+
|
7
|
+
# Return extname
|
8
|
+
#
|
9
|
+
# @return [String]
|
10
|
+
#
|
11
|
+
# @api private
|
12
|
+
#
|
13
|
+
attr_reader :extname
|
14
|
+
|
15
|
+
# Return content type
|
16
|
+
#
|
17
|
+
# @return [String]
|
18
|
+
#
|
19
|
+
# @api private
|
20
|
+
#
|
21
|
+
attr_reader :content_type
|
22
|
+
|
23
|
+
# Return mime for extname
|
24
|
+
#
|
25
|
+
# @param [String] extname
|
26
|
+
#
|
27
|
+
# @return [Mime]
|
28
|
+
#
|
29
|
+
# @api private
|
30
|
+
#
|
31
|
+
def self.extname(extname)
|
32
|
+
REGISTRY.fetch(extname)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Return mime name
|
36
|
+
#
|
37
|
+
# @param [String] name
|
38
|
+
#
|
39
|
+
# @return [Mime]
|
40
|
+
#
|
41
|
+
# @api private
|
42
|
+
#
|
43
|
+
def self.from_name(name)
|
44
|
+
extname(::File.extname(name))
|
45
|
+
end
|
46
|
+
|
47
|
+
# Instantiate object
|
48
|
+
#
|
49
|
+
# @return [Mime]
|
50
|
+
#
|
51
|
+
# @api private
|
52
|
+
#
|
53
|
+
def self.new(*)
|
54
|
+
instance = super
|
55
|
+
REGISTRY[instance.extname]=instance
|
56
|
+
instance
|
57
|
+
end
|
58
|
+
|
59
|
+
private_class_method :new
|
60
|
+
|
61
|
+
JPG = new('.jpg', 'image/jpg' )
|
62
|
+
ICO = new('.ico', 'image/vnd.microsoft.icon' )
|
63
|
+
PNG = new('.png', 'image/png' )
|
64
|
+
GIF = new('.gif', 'image/gif' )
|
65
|
+
SVG = new('.svg', 'image/svg' )
|
66
|
+
PDF = new('.pdf', 'application/pdf' )
|
67
|
+
RUBY = new('.rb', 'application/ruby' )
|
68
|
+
TXT = new('.txt', 'text/plain; charset=UTF-8' )
|
69
|
+
CSS = new('.css', 'text/css; charset=UTF-8' )
|
70
|
+
JAVASCRIPT = new('.js', 'application/javascript; charset=UTF-8' )
|
71
|
+
COFFEESCRIPT = new('.coffee', 'application/coffeescript; charset=UTF-8' )
|
72
|
+
SASS = new('.sass', 'text/plain; charset=UTF-8' )
|
73
|
+
HTML = new('.html', 'text/html; charset=UTF-8' )
|
74
|
+
|
75
|
+
IMAGES = [
|
76
|
+
JPG, ICO, PNG, SVG, GIF
|
77
|
+
].freeze
|
78
|
+
|
79
|
+
REGISTRY.freeze
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
module Assets
|
2
|
+
class Package
|
3
|
+
include Concord::Public.new(:repository, :contents)
|
4
|
+
|
5
|
+
# Return rules for mime type
|
6
|
+
#
|
7
|
+
# @return [Enumerable<Rule>]
|
8
|
+
#
|
9
|
+
# @api private
|
10
|
+
#
|
11
|
+
def mime(mime)
|
12
|
+
contents.fetch(mime, [])
|
13
|
+
end
|
14
|
+
|
15
|
+
# Return image rules
|
16
|
+
#
|
17
|
+
# @return [Enumerable<Rule>]
|
18
|
+
#
|
19
|
+
# @api private
|
20
|
+
#
|
21
|
+
def images
|
22
|
+
Mime::IMAGES.each_with_object([]) do |mime, images|
|
23
|
+
images.concat(mime(mime))
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class Builder
|
28
|
+
|
29
|
+
# Run builder
|
30
|
+
#
|
31
|
+
# @param [String] directory
|
32
|
+
#
|
33
|
+
# @return [Package]
|
34
|
+
#
|
35
|
+
# @api private
|
36
|
+
#
|
37
|
+
def self.run(directory)
|
38
|
+
repository = Repository::Directory.new(directory)
|
39
|
+
builder = new(repository)
|
40
|
+
yield builder
|
41
|
+
Package.new(repository, builder.contents)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Initialize object
|
45
|
+
#
|
46
|
+
# @param [Repository] repository
|
47
|
+
#
|
48
|
+
# @return [undefined]
|
49
|
+
#
|
50
|
+
# @api private
|
51
|
+
#
|
52
|
+
def initialize(repository)
|
53
|
+
@repository = repository
|
54
|
+
@contents = Hash.new { |hash, key| hash[key] = [] }
|
55
|
+
end
|
56
|
+
|
57
|
+
# Return contents
|
58
|
+
#
|
59
|
+
# @return [Hash]
|
60
|
+
#
|
61
|
+
# @api private
|
62
|
+
#
|
63
|
+
attr_reader :contents
|
64
|
+
|
65
|
+
# Return repository
|
66
|
+
#
|
67
|
+
# @return [Repository]
|
68
|
+
#
|
69
|
+
# @api private
|
70
|
+
#
|
71
|
+
attr_reader :repository
|
72
|
+
|
73
|
+
# Add compile
|
74
|
+
#
|
75
|
+
# @param [String] name
|
76
|
+
#
|
77
|
+
# @return [self]
|
78
|
+
#
|
79
|
+
# @api private
|
80
|
+
#
|
81
|
+
def compile(name)
|
82
|
+
append(repository.compile(name))
|
83
|
+
end
|
84
|
+
|
85
|
+
# Add static file
|
86
|
+
#
|
87
|
+
# @param [String] name
|
88
|
+
#
|
89
|
+
# @return [self]
|
90
|
+
#
|
91
|
+
# @api private
|
92
|
+
#
|
93
|
+
def file(name)
|
94
|
+
append(repository.file(name))
|
95
|
+
end
|
96
|
+
|
97
|
+
# Perform glob
|
98
|
+
#
|
99
|
+
# @param [String] pattern
|
100
|
+
#
|
101
|
+
# @return [Enumerable<String>]
|
102
|
+
#
|
103
|
+
# @api private
|
104
|
+
#
|
105
|
+
def glob(pattern)
|
106
|
+
repository.glob(pattern)
|
107
|
+
end
|
108
|
+
|
109
|
+
# Append rule
|
110
|
+
#
|
111
|
+
# @param [Rule] rule
|
112
|
+
#
|
113
|
+
# @return [self]
|
114
|
+
#
|
115
|
+
# @api private
|
116
|
+
#
|
117
|
+
def append(rule)
|
118
|
+
@contents[rule.mime] << rule
|
119
|
+
self
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Assets
|
2
|
+
|
3
|
+
# Abstract base class for asset repositories
|
4
|
+
class Repository
|
5
|
+
|
6
|
+
# Physical directory repository
|
7
|
+
class Directory
|
8
|
+
include Concord.new(:root)
|
9
|
+
|
10
|
+
# Build a file rule
|
11
|
+
#
|
12
|
+
# @param [#to_s] name
|
13
|
+
#
|
14
|
+
# @return [Rule::File]
|
15
|
+
#
|
16
|
+
# @api private
|
17
|
+
#
|
18
|
+
def file(name)
|
19
|
+
Rule::File.new(name.to_s, path(name))
|
20
|
+
end
|
21
|
+
|
22
|
+
# Return names matching pattern
|
23
|
+
#
|
24
|
+
# @param [String] pattern
|
25
|
+
#
|
26
|
+
# @return [Enumerable<String>]
|
27
|
+
#
|
28
|
+
# @api private
|
29
|
+
#
|
30
|
+
def glob(pattern)
|
31
|
+
root = self.root
|
32
|
+
Pathname.glob(root.join(pattern)).map do |match|
|
33
|
+
match.relative_path_from(root).to_s
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Return path for name
|
38
|
+
#
|
39
|
+
# @param [String] name
|
40
|
+
#
|
41
|
+
# @return [Pathname]
|
42
|
+
#
|
43
|
+
# @api private
|
44
|
+
#
|
45
|
+
def path(name)
|
46
|
+
root.join(name)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Build a compile rule
|
50
|
+
#
|
51
|
+
# @return [Rule::Compile::Sass]
|
52
|
+
#
|
53
|
+
# @api private
|
54
|
+
#
|
55
|
+
def compile(name)
|
56
|
+
Rule::Compile.build(file(name))
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
module Assets
|
2
|
+
# Abstract base class for asset responders
|
3
|
+
class Responder
|
4
|
+
include AbstractType, Concord.new(:asset)
|
5
|
+
|
6
|
+
HEADERS = IceNine.deep_freeze('Cache-Control' => 'max-age=120, must-revalidate')
|
7
|
+
NOT_FOUND = Response.build(
|
8
|
+
Response::Status::NOT_FOUND,
|
9
|
+
HEADERS.merge('Content-Type' => Assets::Mime::TXT.content_type),
|
10
|
+
'Not Found'
|
11
|
+
)
|
12
|
+
|
13
|
+
# Call responder
|
14
|
+
#
|
15
|
+
# @param [Object] asset
|
16
|
+
#
|
17
|
+
# @return [Response]
|
18
|
+
#
|
19
|
+
# @api private
|
20
|
+
#
|
21
|
+
def self.call(asset)
|
22
|
+
new(asset).response
|
23
|
+
end
|
24
|
+
|
25
|
+
# Run responder
|
26
|
+
#
|
27
|
+
# @param [Request] request
|
28
|
+
# @param [Asset] asset
|
29
|
+
#
|
30
|
+
# @return [Response] response
|
31
|
+
#
|
32
|
+
# @api private
|
33
|
+
#
|
34
|
+
def self.run(request, asset)
|
35
|
+
timestamp = request.if_modified_since
|
36
|
+
|
37
|
+
responder =
|
38
|
+
if timestamp && asset.fresh_at?(timestamp)
|
39
|
+
NotModified
|
40
|
+
else
|
41
|
+
New
|
42
|
+
end
|
43
|
+
|
44
|
+
responder.call(asset)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Return response
|
48
|
+
#
|
49
|
+
# @return [Response]
|
50
|
+
#
|
51
|
+
# @api private
|
52
|
+
#
|
53
|
+
def response
|
54
|
+
Response.new(status, headers, body)
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
# Return status code
|
60
|
+
#
|
61
|
+
# @return [Fixnum]
|
62
|
+
#
|
63
|
+
# @api private
|
64
|
+
#
|
65
|
+
def status
|
66
|
+
self.class::STATUS
|
67
|
+
end
|
68
|
+
|
69
|
+
# Return headers
|
70
|
+
#
|
71
|
+
# @return [Hash]
|
72
|
+
#
|
73
|
+
# @api private
|
74
|
+
#
|
75
|
+
def headers
|
76
|
+
HEADERS.merge('Last-Modified' => asset.created_at.httpdate)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Return content type header value
|
80
|
+
#
|
81
|
+
# @return [String]
|
82
|
+
#
|
83
|
+
# @api private
|
84
|
+
#
|
85
|
+
def content_type
|
86
|
+
asset.mime.content_type
|
87
|
+
end
|
88
|
+
|
89
|
+
# Not modified responder
|
90
|
+
class NotModified < self
|
91
|
+
STATUS = Response::Status::NOT_MODIFIED
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
# Return body
|
96
|
+
#
|
97
|
+
# @return [nil]
|
98
|
+
#
|
99
|
+
# @api private
|
100
|
+
#
|
101
|
+
def body; end
|
102
|
+
|
103
|
+
end
|
104
|
+
|
105
|
+
# New asset responder
|
106
|
+
class New < self
|
107
|
+
STATUS = Response::Status::OK
|
108
|
+
|
109
|
+
private
|
110
|
+
|
111
|
+
# Return headers
|
112
|
+
#
|
113
|
+
# @return [Hash]
|
114
|
+
#
|
115
|
+
# @api private
|
116
|
+
#
|
117
|
+
def headers
|
118
|
+
super.merge('Content-Type' => content_type)
|
119
|
+
end
|
120
|
+
|
121
|
+
# Return body
|
122
|
+
#
|
123
|
+
# @return [String]
|
124
|
+
#
|
125
|
+
# @api private
|
126
|
+
#
|
127
|
+
def body
|
128
|
+
asset.body
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Assets
|
2
|
+
class Rule
|
3
|
+
class Compile
|
4
|
+
# Abstract base class for compilers that compile to css
|
5
|
+
class Css < self
|
6
|
+
|
7
|
+
MIME = Mime::CSS
|
8
|
+
|
9
|
+
# Compiler for sass
|
10
|
+
class Sass < self
|
11
|
+
handle(Mime::SASS)
|
12
|
+
|
13
|
+
# Return body
|
14
|
+
#
|
15
|
+
# @return [String]
|
16
|
+
#
|
17
|
+
# @api private
|
18
|
+
#
|
19
|
+
def body
|
20
|
+
::Sass.compile(input.body, :syntax => :sass)
|
21
|
+
end
|
22
|
+
|
23
|
+
end # Sass
|
24
|
+
|
25
|
+
end # Css
|
26
|
+
end # Compile
|
27
|
+
end # Rule
|
28
|
+
end # Assets
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Assets
|
2
|
+
class Rule
|
3
|
+
class Compile
|
4
|
+
class Javascript
|
5
|
+
|
6
|
+
# Compiler for sass
|
7
|
+
class Coffescript < self
|
8
|
+
handle(Mime::COFFEESCRIPT)
|
9
|
+
|
10
|
+
# Return body
|
11
|
+
#
|
12
|
+
# @return [String]
|
13
|
+
#
|
14
|
+
# @api private
|
15
|
+
#
|
16
|
+
def body
|
17
|
+
::CoffeeScript.compile(input.body)
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module Assets
|
2
|
+
class Rule
|
3
|
+
class Compile < self
|
4
|
+
include Concord.new(:input)
|
5
|
+
|
6
|
+
# Return name
|
7
|
+
#
|
8
|
+
# @return [String]
|
9
|
+
#
|
10
|
+
# @api private
|
11
|
+
#
|
12
|
+
def name
|
13
|
+
input.name
|
14
|
+
end
|
15
|
+
|
16
|
+
# Return updated at
|
17
|
+
#
|
18
|
+
# @return [Time]
|
19
|
+
#
|
20
|
+
# @api private
|
21
|
+
#
|
22
|
+
def updated_at
|
23
|
+
input.updated_at
|
24
|
+
end
|
25
|
+
|
26
|
+
# Return target name
|
27
|
+
#
|
28
|
+
# @return [String]
|
29
|
+
#
|
30
|
+
# @api private
|
31
|
+
#
|
32
|
+
def target_name
|
33
|
+
regexp = %r(#{Regexp.escape(input.extname)}\z)
|
34
|
+
name.gsub(regexp, mime.extname)
|
35
|
+
end
|
36
|
+
memoize :target_name
|
37
|
+
|
38
|
+
# Return registry
|
39
|
+
#
|
40
|
+
# @return [Hash]
|
41
|
+
#
|
42
|
+
# @api private
|
43
|
+
#
|
44
|
+
def self.registry
|
45
|
+
@registry ||= {}
|
46
|
+
end
|
47
|
+
|
48
|
+
# protected_class_method :registry
|
49
|
+
singleton_class.send(:protected, :registry)
|
50
|
+
|
51
|
+
# Register handler
|
52
|
+
#
|
53
|
+
# @param [Mime] mime
|
54
|
+
#
|
55
|
+
# @return [undefined]
|
56
|
+
#
|
57
|
+
# @api private
|
58
|
+
#
|
59
|
+
def self.handle(mime)
|
60
|
+
Compile.registry[mime]=self
|
61
|
+
end
|
62
|
+
private_class_method :handle
|
63
|
+
|
64
|
+
# Build compiler rule
|
65
|
+
#
|
66
|
+
# @param [Rule::File] rule
|
67
|
+
#
|
68
|
+
# @return [Rule::Compile]
|
69
|
+
#
|
70
|
+
# @api private
|
71
|
+
#
|
72
|
+
def self.build(rule)
|
73
|
+
builder = Compile.registry.fetch(rule.mime)
|
74
|
+
compiler = builder.new(rule)
|
75
|
+
Rename.new(compiler.target_name, compiler)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Return mime type
|
79
|
+
#
|
80
|
+
# @return [Mime]
|
81
|
+
#
|
82
|
+
# @api private
|
83
|
+
#
|
84
|
+
def mime
|
85
|
+
self.class.mime
|
86
|
+
end
|
87
|
+
|
88
|
+
# Return mime type
|
89
|
+
#
|
90
|
+
# @return [Mime]
|
91
|
+
#
|
92
|
+
# @api private
|
93
|
+
#
|
94
|
+
def self.mime
|
95
|
+
self::MIME
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|