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,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
@@ -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,12 @@
1
+ module Assets
2
+ class Rule
3
+ class Compile
4
+ # Base class for compilers that compile to javascript
5
+ class Javascript < self
6
+
7
+ MIME = Mime::JAVASCRIPT
8
+
9
+ end
10
+ end
11
+ end
12
+ 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