hanami-assets 0.0.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +23 -0
  3. data/LICENSE.md +22 -0
  4. data/README.md +426 -9
  5. data/bin/hanami-assets +22 -0
  6. data/hanami-assets.gemspec +26 -12
  7. data/lib/hanami/assets.rb +153 -2
  8. data/lib/hanami/assets/bundler.rb +173 -0
  9. data/lib/hanami/assets/cache.rb +58 -0
  10. data/lib/hanami/assets/compiler.rb +212 -0
  11. data/lib/hanami/assets/compressors/abstract.rb +119 -0
  12. data/lib/hanami/assets/compressors/builtin_javascript.rb +36 -0
  13. data/lib/hanami/assets/compressors/builtin_stylesheet.rb +57 -0
  14. data/lib/hanami/assets/compressors/closure_javascript.rb +25 -0
  15. data/lib/hanami/assets/compressors/javascript.rb +77 -0
  16. data/lib/hanami/assets/compressors/jsmin.rb +283 -0
  17. data/lib/hanami/assets/compressors/null_compressor.rb +19 -0
  18. data/lib/hanami/assets/compressors/sass_stylesheet.rb +38 -0
  19. data/lib/hanami/assets/compressors/stylesheet.rb +77 -0
  20. data/lib/hanami/assets/compressors/uglifier_javascript.rb +25 -0
  21. data/lib/hanami/assets/compressors/yui_javascript.rb +25 -0
  22. data/lib/hanami/assets/compressors/yui_stylesheet.rb +25 -0
  23. data/lib/hanami/assets/config/global_sources.rb +50 -0
  24. data/lib/hanami/assets/config/manifest.rb +112 -0
  25. data/lib/hanami/assets/config/sources.rb +77 -0
  26. data/lib/hanami/assets/configuration.rb +539 -0
  27. data/lib/hanami/assets/helpers.rb +733 -0
  28. data/lib/hanami/assets/precompiler.rb +67 -0
  29. data/lib/hanami/assets/version.rb +4 -1
  30. metadata +189 -17
  31. data/.gitignore +0 -9
  32. data/Gemfile +0 -4
  33. data/Rakefile +0 -2
  34. data/bin/console +0 -14
  35. data/bin/setup +0 -8
@@ -0,0 +1,77 @@
1
+ require 'hanami/assets/compressors/abstract'
2
+
3
+ module Hanami
4
+ module Assets
5
+ module Compressors
6
+ # Base class for JavaScript compressors
7
+ #
8
+ # @since 0.1.0
9
+ # @api private
10
+ class Javascript < Abstract
11
+ # Factory for Javascript compressors.
12
+ #
13
+ # It loads a compressor for the given name.
14
+ #
15
+ # @param engine_name [Symbol,String,NilClass,#compress] the name of the
16
+ # engine to load or an instance of an engine
17
+ #
18
+ # @return [Hanami::Assets::Compressors::Abstract] returns a concrete
19
+ # implementation of a compressor
20
+ #
21
+ # @raise [Hanami::Assets::Compressors::UnknownCompressorError] when the
22
+ # given name refers to an unknown compressor engine
23
+ #
24
+ # @since 0.1.0
25
+ # @api private
26
+ #
27
+ # @see Hanami::Assets::Compressors::Abstract#for
28
+ # @see Hanami::Assets::Configuration#javascript_compressor
29
+ #
30
+ # @example Basic Usage
31
+ # require 'hanami/assets'
32
+ # require 'hanami/assets/compressors/javascript'
33
+ #
34
+ # Hanami::Assets::Compressors::Javascript.for(:closure)
35
+ # # => #<Hanami::Assets::Compressors::ClosureJavascript:0x007fa32a32e108 ...>
36
+ #
37
+ # @example Null Compressor
38
+ # require 'hanami/assets'
39
+ # require 'hanami/assets/compressors/javascript'
40
+ #
41
+ # Hanami::Assets::Compressors::Javascript.for(nil)
42
+ # # => #<Hanami::Assets::Compressors::NullCompressor:0x007fa32a314258>
43
+ #
44
+ # @example Custom Compressor
45
+ # require 'hanami/assets'
46
+ # require 'hanami/assets/compressors/javascript'
47
+ #
48
+ # class CustomJavascriptCompressor
49
+ # def compress(filename)
50
+ # # ...
51
+ # end
52
+ # end
53
+ #
54
+ # Hanami::Assets::Compressors::Javascript.for(CustomJavascriptCompressor.new)
55
+ # # => #<CustomJavascriptCompressor:0x007fa32a2cdf10>
56
+ #
57
+ # @example Third Party Compressor
58
+ # require 'hanami/assets'
59
+ # require 'hanami/assets/compressors/javascript'
60
+ # require 'hanami/foo/compressor' # third party gem
61
+ #
62
+ # Hanami::Assets::Compressors::Javascript.for(:foo)
63
+ # # => #<Hanami::Assets::Compressors::FooJavascript:0x007fa3dd9ed968>
64
+ #
65
+ # @example Unknown Engine
66
+ # require 'hanami/assets'
67
+ # require 'hanami/assets/compressors/javascript'
68
+ #
69
+ # Hanami::Assets::Compressors::Javascript.for(:wat)
70
+ # # => Hanami::Assets::Compressors::UnknownCompressorError: Unknown Javascript compressor: :wat
71
+ def self.for(engine_name)
72
+ super
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,283 @@
1
+ #--
2
+ # jsmin.rb - Ruby implementation of Douglas Crockford's JSMin.
3
+ #
4
+ # This port of jsmin.c was made by Ryan Grove (@rgrove) as work for <tt>jsmin</tt> gem.
5
+ # Copyright (c) 2008-2012 Ryan Grove
6
+ #
7
+ # This is a port of jsmin.c, and is distributed under the same terms, which are
8
+ # as follows:
9
+ #
10
+ # Copyright (c) 2002 Douglas Crockford (www.crockford.com)
11
+ #
12
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
13
+ # of this software and associated documentation files (the "Software"), to deal
14
+ # in the Software without restriction, including without limitation the rights
15
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
+ # copies of the Software, and to permit persons to whom the Software is
17
+ # furnished to do so, subject to the following conditions:
18
+ #
19
+ # The above copyright notice and this permission notice shall be included in all
20
+ # copies or substantial portions of the Software.
21
+ #
22
+ # The Software shall be used for Good, not Evil.
23
+ #
24
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30
+ # SOFTWARE.
31
+ #++
32
+
33
+ require 'strscan'
34
+
35
+ # = JSMin
36
+ #
37
+ # Ruby implementation of Douglas Crockford's JavaScript minifier, JSMin.
38
+ #
39
+ # *Author*:: Ryan Grove (mailto:ryan@wonko.com)
40
+ # *Version*:: 1.0.1 (2008-11-10)
41
+ # *Copyright*:: Copyright (c) 2008 Ryan Grove. All rights reserved.
42
+ # *Website*:: http://github.com/rgrove/jsmin
43
+ #
44
+ # == Example
45
+ #
46
+ # require 'rubygems'
47
+ # require 'jsmin'
48
+ #
49
+ # File.open('example.js', 'r') {|file| puts JSMin.minify(file) }
50
+ #
51
+ module JSMin
52
+ CHR_APOS = "'".freeze
53
+ CHR_ASTERISK = '*'.freeze
54
+ CHR_BACKSLASH = '\\'.freeze
55
+ CHR_CR = "\r".freeze
56
+ CHR_FRONTSLASH = '/'.freeze
57
+ CHR_LF = "\n".freeze
58
+ CHR_QUOTE = '"'.freeze
59
+ CHR_SPACE = ' '.freeze
60
+
61
+ ORD_LF = ?\n
62
+ ORD_SPACE = ?\
63
+ ORD_TILDE = ?~
64
+
65
+ class ParseError < RuntimeError
66
+ attr_accessor :source, :line
67
+ def initialize(err, source, line)
68
+ @source = source,
69
+ @line = line
70
+ super "JSMin Parse Error: #{err} at line #{line} of #{source}"
71
+ end
72
+ end
73
+
74
+ class << self
75
+ def raise(err)
76
+ super ParseError.new(err, @source, @line)
77
+ end
78
+
79
+ # Reads JavaScript from _input_ (which can be a String or an IO object) and
80
+ # returns a String containing minified JS.
81
+ def minify(input)
82
+ @js = StringScanner.new(input.is_a?(IO) ? input.read : input.to_s)
83
+ @source = input.is_a?(IO) ? input.inspect : input.to_s[0..100]
84
+ @line = 1
85
+
86
+ @a = "\n"
87
+ @b = nil
88
+ @lookahead = nil
89
+ @output = ''
90
+
91
+ action_get
92
+
93
+ while !@a.nil? do
94
+ case @a
95
+ when CHR_SPACE
96
+ if alphanum?(@b)
97
+ action_output
98
+ else
99
+ action_copy
100
+ end
101
+
102
+ when CHR_LF
103
+ if @b == CHR_SPACE
104
+ action_get
105
+ elsif @b =~ /[{\[\(+-]/
106
+ action_output
107
+ else
108
+ if alphanum?(@b)
109
+ action_output
110
+ else
111
+ action_copy
112
+ end
113
+ end
114
+
115
+ else
116
+ if @b == CHR_SPACE
117
+ if alphanum?(@a)
118
+ action_output
119
+ else
120
+ action_get
121
+ end
122
+ elsif @b == CHR_LF
123
+ if @a =~ /[}\]\)\\"+-]/
124
+ action_output
125
+ else
126
+ if alphanum?(@a)
127
+ action_output
128
+ else
129
+ action_get
130
+ end
131
+ end
132
+ else
133
+ action_output
134
+ end
135
+ end
136
+ end
137
+
138
+ @output
139
+ end
140
+
141
+ private
142
+
143
+ # Corresponds to action(1) in jsmin.c.
144
+ def action_output
145
+ @output << @a
146
+ action_copy
147
+ end
148
+
149
+ # Corresponds to action(2) in jsmin.c.
150
+ def action_copy
151
+ @a = @b
152
+
153
+ if @a == CHR_APOS || @a == CHR_QUOTE
154
+ loop do
155
+ @output << @a
156
+ @a = get
157
+
158
+ break if @a == @b
159
+
160
+ if @a[0] <= ORD_LF
161
+ raise "unterminated string literal: #{@a.inspect}"
162
+ end
163
+
164
+ if @a == CHR_BACKSLASH
165
+ @output << @a
166
+ @a = get
167
+
168
+ if @a[0] <= ORD_LF
169
+ raise "unterminated string literal: #{@a.inspect}"
170
+ end
171
+ end
172
+ end
173
+ end
174
+
175
+ action_get
176
+ end
177
+
178
+ # Corresponds to action(3) in jsmin.c.
179
+ def action_get
180
+ @b = nextchar
181
+
182
+ if @b == CHR_FRONTSLASH && (@a == CHR_LF || @a =~ /[\(,=:\[!&|?{};]/)
183
+ @output << @a
184
+ @output << @b
185
+
186
+ loop do
187
+ @a = get
188
+
189
+ # Inside a regex [...] set, which MAY contain a '/' itself.
190
+ # Example:
191
+ # mootools Form.Validator near line 460:
192
+ # return Form.Validator.getValidator('IsEmpty').test(element) || (/^(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]\.?){0,63}[a-z0-9!#$%&'*+/=?^_`{|}~-]@(?:(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)*[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\])$/i).test(element.get('value'));
193
+ if @a == '['
194
+ loop do
195
+ @output << @a
196
+ @a = get
197
+ case @a
198
+ when ']' then break
199
+ when CHR_BACKSLASH then
200
+ @output << @a
201
+ @a = get
202
+ when @a[0] <= ORD_LF
203
+ raise "JSMin parse error: unterminated regular expression " +
204
+ "literal: #{@a}"
205
+ end
206
+ end
207
+ elsif @a == CHR_FRONTSLASH
208
+ break
209
+ elsif @a == CHR_BACKSLASH
210
+ @output << @a
211
+ @a = get
212
+ elsif @a[0] <= ORD_LF
213
+ raise "unterminated regular expression : #{@a.inspect}"
214
+ end
215
+
216
+ @output << @a
217
+ end
218
+
219
+ @b = nextchar
220
+ end
221
+ end
222
+
223
+ # Returns true if +c+ is a letter, digit, underscore, dollar sign,
224
+ # backslash, or non-ASCII character.
225
+ def alphanum?(c)
226
+ c.is_a?(String) && !c.empty? && (c[0] > ORD_TILDE || c =~ /[0-9a-z_$\\]/i)
227
+ end
228
+
229
+ # Returns the next character from the input. If the character is a control
230
+ # character, it will be translated to a space or linefeed.
231
+ def get
232
+ if @lookahead
233
+ c = @lookahead
234
+ @lookahead = nil
235
+ else
236
+ c = @js.getch
237
+ if c == CHR_LF || c == CHR_CR
238
+ @line += 1
239
+ return CHR_LF
240
+ end
241
+ return ' ' unless c.nil? || c[0] >= ORD_SPACE
242
+ end
243
+ c
244
+ end
245
+
246
+ # Gets the next character, excluding comments.
247
+ def nextchar
248
+ c = get
249
+ return c unless c == CHR_FRONTSLASH
250
+
251
+ case peek
252
+ when CHR_FRONTSLASH
253
+ loop do
254
+ c = get
255
+ return c if c[0] <= ORD_LF
256
+ end
257
+
258
+ when CHR_ASTERISK
259
+ get
260
+ loop do
261
+ case get
262
+ when CHR_ASTERISK
263
+ if peek == CHR_FRONTSLASH
264
+ get
265
+ return ' '
266
+ end
267
+
268
+ when nil
269
+ raise 'unterminated comment'
270
+ end
271
+ end
272
+
273
+ else
274
+ return c
275
+ end
276
+ end
277
+
278
+ # Gets the next character without getting it.
279
+ def peek
280
+ @lookahead = get
281
+ end
282
+ end
283
+ end
@@ -0,0 +1,19 @@
1
+ require 'hanami/assets/compressors/abstract'
2
+
3
+ module Hanami
4
+ module Assets
5
+ module Compressors
6
+ # No-op, it returns the asset contents without to compress them.
7
+ #
8
+ # @since 0.1.0
9
+ # @api private
10
+ class NullCompressor < Abstract
11
+ # @since 0.1.0
12
+ # @api private
13
+ def compress(filename)
14
+ read(filename)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,38 @@
1
+ require 'hanami/assets/compressors/stylesheet'
2
+ require 'sass'
3
+
4
+ module Hanami
5
+ module Assets
6
+ module Compressors
7
+ # Sass compressor for stylesheet
8
+ #
9
+ # It depends on <tt>sass</tt> gem.
10
+ #
11
+ # @since 0.1.0
12
+ # @api private
13
+ #
14
+ # @see http://sass-lang.com
15
+ # @see https://rubygems.org/gems/sass
16
+ class SassStylesheet < Stylesheet
17
+ # @since 0.1.0
18
+ # @api private
19
+ #
20
+ # FIXME This is the same logic that we have for Hanami::Assets::Compiler
21
+ SASS_CACHE_LOCATION = Pathname(Hanami.respond_to?(:root) ?
22
+ Hanami.root : Dir.pwd).join('tmp', 'sass-cache')
23
+ # @since 0.1.0
24
+ # @api private
25
+ def initialize
26
+ @compressor = Sass::Engine
27
+ end
28
+
29
+ # @since 0.1.0
30
+ # @api private
31
+ def compress(filename)
32
+ compressor.new(read(filename), filename: filename, syntax: :scss,
33
+ style: :compressed, cache_location: SASS_CACHE_LOCATION).render
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,77 @@
1
+ require 'hanami/assets/compressors/abstract'
2
+
3
+ module Hanami
4
+ module Assets
5
+ module Compressors
6
+ # Base class for stylesheet compressors
7
+ #
8
+ # @since 0.1.0
9
+ # @api private
10
+ class Stylesheet < Abstract
11
+ # Factory for Stylesheet compressors.
12
+ #
13
+ # It loads a compressor for the given name.
14
+ #
15
+ # @param engine_name [Symbol,String,NilClass,#compress] the name of the
16
+ # engine to load or an instance of an engine
17
+ #
18
+ # @return [Hanami::Assets::Compressors::Abstract] returns a concrete
19
+ # implementation of a compressor
20
+ #
21
+ # @raise [Hanami::Assets::Compressors::UnknownCompressorError] when the
22
+ # given name refers to an unknown compressor engine
23
+ #
24
+ # @since 0.1.0
25
+ # @api private
26
+ #
27
+ # @see Hanami::Assets::Compressors::Abstract#for
28
+ # @see Hanami::Assets::Configuration#stylesheet_compressor
29
+ #
30
+ # @example Basic Usage
31
+ # require 'hanami/assets'
32
+ # require 'hanami/assets/compressors/stylesheet'
33
+ #
34
+ # Hanami::Assets::Compressors::Stylesheet.for(:sass)
35
+ # # => #<Hanami::Assets::Compressors::SassStylesheet:0x007f8674cc4a50 ...>
36
+ #
37
+ # @example Null Compressor
38
+ # require 'hanami/assets'
39
+ # require 'hanami/assets/compressors/stylesheet'
40
+ #
41
+ # Hanami::Assets::Compressors::Stylesheet.for(nil)
42
+ # # => #<Hanami::Assets::Compressors::NullCompressor:0x007fa32a314258>
43
+ #
44
+ # @example Custom Compressor
45
+ # require 'hanami/assets'
46
+ # require 'hanami/assets/compressors/stylesheet'
47
+ #
48
+ # class CustomStylesheetCompressor
49
+ # def compress(filename)
50
+ # # ...
51
+ # end
52
+ # end
53
+ #
54
+ # Hanami::Assets::Compressors::Stylesheet.for(CustomStylesheetCompressor.new)
55
+ # # => #<CustomStylesheetCompressor:0x007fa32a2cdf10>
56
+ #
57
+ # @example Third Party Compressor
58
+ # require 'hanami/assets'
59
+ # require 'hanami/assets/compressors/stylesheet'
60
+ # require 'hanami/foo/compressor' # third party gem
61
+ #
62
+ # Hanami::Assets::Compressors::Stylesheet.for(:foo)
63
+ # # => #<Hanami::Assets::Compressors::FooStylesheet:0x007fa3dd9ed968>
64
+ #
65
+ # @example Unknown Engine
66
+ # require 'hanami/assets'
67
+ # require 'hanami/assets/compressors/stylesheet'
68
+ #
69
+ # Hanami::Assets::Compressors::Stylesheet.for(:wat)
70
+ # # => Hanami::Assets::Compressors::UnknownCompressorError: Unknown Stylesheet compressor: :wat
71
+ def self.for(engine_name)
72
+ super
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end