condenser 1.2 → 1.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/lib/condenser/asset.rb +19 -16
  3. data/lib/condenser/build_cache.rb +1 -1
  4. data/lib/condenser/context.rb +9 -25
  5. data/lib/condenser/helpers/parse_helpers.rb +1 -1
  6. data/lib/condenser/manifest.rb +3 -1
  7. data/lib/condenser/pipeline.rb +8 -3
  8. data/lib/condenser/processors/babel_processor.rb +1 -1
  9. data/lib/condenser/processors/css_media_combiner_processor.rb +81 -0
  10. data/lib/condenser/processors/js_analyzer.rb +0 -2
  11. data/lib/condenser/processors/purgecss_processor.rb +6 -4
  12. data/lib/condenser/processors/rollup_processor.rb +37 -35
  13. data/lib/condenser/resolve.rb +1 -3
  14. data/lib/condenser/templating_engine/ejs.rb +1 -1
  15. data/lib/condenser/transformers/dart_sass_transformer.rb +285 -0
  16. data/lib/condenser/transformers/jst_transformer.rb +67 -17
  17. data/lib/condenser/transformers/sass/functions.rb +133 -0
  18. data/lib/condenser/transformers/sass/importer.rb +48 -0
  19. data/lib/condenser/transformers/sass.rb +4 -0
  20. data/lib/condenser/transformers/sass_transformer.rb +124 -281
  21. data/lib/condenser/transformers/svg_transformer/base.rb +26 -0
  22. data/lib/condenser/transformers/svg_transformer/tag.rb +54 -0
  23. data/lib/condenser/transformers/svg_transformer/template.rb +151 -0
  24. data/lib/condenser/transformers/svg_transformer/template_error.rb +2 -0
  25. data/lib/condenser/transformers/svg_transformer/value.rb +13 -0
  26. data/lib/condenser/transformers/svg_transformer/var_generator.rb +10 -0
  27. data/lib/condenser/transformers/svg_transformer.rb +19 -0
  28. data/lib/condenser/version.rb +1 -1
  29. data/lib/condenser.rb +17 -5
  30. data/test/cache_test.rb +46 -2
  31. data/test/dependency_test.rb +2 -2
  32. data/test/manifest_test.rb +34 -0
  33. data/test/minifiers/terser_minifier_test.rb +0 -1
  34. data/test/minifiers/uglify_minifier_test.rb +0 -1
  35. data/test/postprocessors/css_media_combiner_test.rb +107 -0
  36. data/test/postprocessors/purgecss_test.rb +62 -0
  37. data/test/preprocessor/babel_test.rb +693 -299
  38. data/test/preprocessor/js_analyzer_test.rb +0 -2
  39. data/test/processors/rollup_test.rb +50 -20
  40. data/test/resolve_test.rb +8 -9
  41. data/test/server_test.rb +6 -1
  42. data/test/templates/ejs_test.rb +2 -11
  43. data/test/templates/erb_test.rb +0 -5
  44. data/test/test_helper.rb +3 -1
  45. data/test/transformers/dart_scss_test.rb +139 -0
  46. data/test/transformers/jst_test.rb +165 -21
  47. data/test/transformers/scss_test.rb +14 -0
  48. data/test/transformers/svg_test.rb +40 -0
  49. metadata +23 -6
  50. data/lib/condenser/transformers/sass_transformer/importer.rb +0 -50
@@ -1,309 +1,152 @@
1
- class Condenser
2
- # Transformer engine class for the SASS/SCSS compiler. Depends on the `sass`
3
- # gem.
4
- #
5
- # For more infomation see:
6
- #
7
- # https://github.com/sass/sass
8
- # https://github.com/rails/sass-rails
1
+ # Transformer engine class for the SASS/SCSS compiler. Depends on the `sass`
2
+ # gem.
3
+ #
4
+ # For more infomation see:
5
+ #
6
+ # https://github.com/sass/sass
7
+ # https://github.com/rails/sass-rails
8
+ #
9
+ class Condenser::SassTransformer
10
+
11
+ attr_accessor :options
12
+
13
+ # Internal: Defines default sass syntax to use. Exposed so the ScssProcessor
14
+ # may override it.
15
+ def self.syntax
16
+ :sass
17
+ end
18
+
19
+ def self.setup(environment)
20
+ require "sassc" unless defined?(::SassC::Engine)
21
+ end
22
+
23
+ # Public: Return singleton instance with default options.
9
24
  #
10
- class SassTransformer
11
- autoload :Importer, 'condenser/transformers/sass_transformer/importer'
25
+ # Returns SassProcessor object.
26
+ def self.instance
27
+ @instance ||= new
28
+ end
12
29
 
13
- attr_accessor :options
14
-
15
- # Internal: Defines default sass syntax to use. Exposed so the ScssProcessor
16
- # may override it.
17
- def self.syntax
18
- :sass
19
- end
30
+ def self.call(environment, input)
31
+ instance.call(environment, input)
32
+ end
20
33
 
21
- def self.setup(environment)
22
- require "sassc" unless defined?(::SassC::Engine)
23
- end
24
-
25
- # Public: Return singleton instance with default options.
26
- #
27
- # Returns SassProcessor object.
28
- def self.instance
29
- @instance ||= new
30
- end
34
+ def self.cache_key
35
+ instance.cache_key
36
+ end
31
37
 
32
- def self.call(environment, input)
33
- instance.call(environment, input)
34
- end
38
+ attr_reader :cache_key
35
39
 
36
- def self.cache_key
37
- instance.cache_key
40
+ def name
41
+ self.class.name
42
+ end
43
+
44
+ # Public: Initialize template with custom options.
45
+ #
46
+ # options - Hash
47
+ # cache_version - String custom cache version. Used to force a cache
48
+ # change after code changes are made to Sass Functions.
49
+ #
50
+ def initialize(options = {}, &block)
51
+ @options = options
52
+ @cache_version = options[:cache_version]
53
+ # @cache_key = "#{self.class.name}:#{VERSION}:#{Autoload::Sass::VERSION}:#{@cache_version}".freeze
54
+ @importer_class = options[:importer] || Condenser::Sass::Importer
55
+
56
+ @sass_config = options[:sass_config] || {}
57
+ @functions = Module.new do
58
+ include Functions
59
+ include options[:functions] if options[:functions]
60
+ class_eval(&block) if block_given?
38
61
  end
62
+ end
39
63
 
40
- attr_reader :cache_key
64
+ def call(environment, input)
65
+ context = environment.new_context_class
66
+ engine_options = merge_options({
67
+ syntax: self.class.syntax,
68
+ filename: input[:filename],
69
+ source_map_file: "#{input[:filename]}.map",
70
+ source_map_contents: true,
71
+ # cache_store: Cache.new(environment.cache),
72
+ load_paths: environment.path,
73
+ importer: @importer_class,
74
+ condenser: { context: context, environment: environment },
75
+ asset: input
76
+ })
77
+
78
+ engine = SassC::Engine.new(input[:source], engine_options)
41
79
 
42
- def name
43
- self.class.name
80
+ css = Condenser::Utils.module_include(SassC::Script::Functions, @functions) do
81
+ engine.render
44
82
  end
83
+ css.delete_suffix!("\n/*# sourceMappingURL=#{File.basename(input[:filename])}.map */")
84
+ # engine.source_map
85
+ # css = css.delete_suffix!("\n/*# sourceMappingURL= */\n")
45
86
 
46
- # Public: Initialize template with custom options.
47
- #
48
- # options - Hash
49
- # cache_version - String custom cache version. Used to force a cache
50
- # change after code changes are made to Sass Functions.
51
- #
52
- def initialize(options = {}, &block)
53
- @options = options
54
- @cache_version = options[:cache_version]
55
- # @cache_key = "#{self.class.name}:#{VERSION}:#{Autoload::Sass::VERSION}:#{@cache_version}".freeze
56
- @importer_class = options[:importer] || Condenser::SassTransformer::Importer
57
-
58
- @sass_config = options[:sass_config] || {}
59
- @functions = Module.new do
60
- include Functions
61
- include options[:functions] if options[:functions]
62
- class_eval(&block) if block_given?
63
- end
64
- # puts @functions.method(:asset_path).source_location
65
- end
87
+ input[:source] = css
88
+ # input[:map] = map.to_json({})
89
+ input[:linked_assets] += context.links
90
+ input[:process_dependencies] += context.dependencies
91
+ end
66
92
 
67
- def call(environment, input)
68
- # context = input[:environment].context_class.new(input)
69
- engine_options = merge_options({
70
- syntax: self.class.syntax,
71
- filename: input[:filename],
72
- source_map_file: "#{input[:filename]}.map",
73
- source_map_contents: true,
74
- # cache_store: Cache.new(environment.cache),
75
- load_paths: environment.path,
76
- importer: @importer_class,
77
- condenser: {
78
- context: environment.new_context_class,
79
- environment: environment
80
- },
81
- asset: input
82
- })
93
+ private
83
94
 
84
- engine = SassC::Engine.new(input[:source], engine_options)
85
-
86
- css = Utils.module_include(SassC::Script::Functions, @functions) do
87
- engine.render
88
- end
89
- css.delete_suffix!("\n/*# sourceMappingURL=#{File.basename(input[:filename])}.map */")
90
- # engine.source_map
91
- # css = css.delete_suffix!("\n/*# sourceMappingURL= */\n")
95
+ # Public: Build the cache store to be used by the Sass engine.
96
+ #
97
+ # input - the input hash.
98
+ # version - the cache version.
99
+ #
100
+ # Override this method if you need to use a different cache than the
101
+ # Condenser cache.
102
+ def build_cache_store(input, version)
103
+ CacheStore.new(input[:cache], version)
104
+ end
92
105
 
106
+ def merge_options(options)
107
+ defaults = @sass_config.dup
93
108
 
94
- input[:source] = css
95
- # input[:map] = map.to_json({})
109
+ if load_paths = defaults.delete(:load_paths)
110
+ options[:load_paths] += load_paths
96
111
  end
97
112
 
98
- private
99
-
100
- # Public: Build the cache store to be used by the Sass engine.
101
- #
102
- # input - the input hash.
103
- # version - the cache version.
104
- #
105
- # Override this method if you need to use a different cache than the
106
- # Condenser cache.
107
- def build_cache_store(input, version)
108
- CacheStore.new(input[:cache], version)
109
- end
113
+ options.merge!(defaults)
114
+ options
115
+ end
110
116
 
111
- def merge_options(options)
112
- defaults = @sass_config.dup
117
+ # Functions injected into Sass context during Condenser evaluation.
118
+ module Functions
119
+ include Condenser::Sass::Functions
113
120
 
114
- if load_paths = defaults.delete(:load_paths)
115
- options[:load_paths] += load_paths
116
- end
121
+ # Returns a Sass::Script::String.
122
+ def asset_path(path, options = {})
123
+ condenser_context.link_asset(path.value)
117
124
 
118
- options.merge!(defaults)
119
- options
125
+ path = condenser_context.asset_path(path.value, options)
126
+ query = "?#{query}" if query
127
+ fragment = "##{fragment}" if fragment
128
+ SassC::Script::Value::String.new("#{path}#{query}#{fragment}", :string)
120
129
  end
121
130
 
122
- # Public: Functions injected into Sass context during Condenser evaluation.
123
- #
124
- # This module may be extended to add global functionality to all Condenser
125
- # Sass environments. Though, scoping your functions to just your environment
126
- # is preferred.
127
- #
128
- # module Condenser::SassProcessor::Functions
129
- # def asset_path(path, options = {})
130
- # end
131
- # end
132
- #
133
- module Functions
134
- # Public: Generate a url for asset path.
135
- #
136
- # Default implementation is deprecated. Currently defaults to
137
- # Context#asset_path.
138
- #
139
- # Will raise NotImplementedError in the future. Users should provide their
140
- # own base implementation.
141
- #
142
- # Returns a Sass::Script::String.
143
- def asset_path(path, options = {})
144
- path = path.value
145
-
146
- path, _, query, fragment = URI.split(path)[5..8]
147
- asset = condenser_environment.find!(path)
148
- condenser_context.link_asset(path)
149
- path = condenser_context.asset_path(asset.path, options)
150
- query = "?#{query}" if query
151
- fragment = "##{fragment}" if fragment
152
- SassC::Script::Value::String.new("#{path}#{query}#{fragment}", :string)
153
- end
154
-
155
- # Public: Generate a asset url() link.
156
- #
157
- # path - Sass::Script::String URL path
158
- #
159
- # Returns a Sass::Script::String.
160
- def asset_url(path, options = {})
161
- SassC::Script::Value::String.new("url(#{asset_path(path, options).value})")
162
- end
163
-
164
- # Public: Generate url for image path.
165
- #
166
- # path - Sass::Script::String URL path
167
- #
168
- # Returns a Sass::Script::String.
169
- def image_path(path)
170
- asset_path(path, type: :image)
171
- end
172
-
173
- # Public: Generate a image url() link.
174
- #
175
- # path - Sass::Script::String URL path
176
- #
177
- # Returns a Sass::Script::String.
178
- def image_url(path)
179
- asset_url(path, type: :image)
180
- end
181
-
182
- # Public: Generate url for video path.
183
- #
184
- # path - Sass::Script::String URL path
185
- #
186
- # Returns a Sass::Script::String.
187
- def video_path(path)
188
- asset_path(path, type: :video)
189
- end
190
-
191
- # Public: Generate a video url() link.
192
- #
193
- # path - Sass::Script::String URL path
194
- #
195
- # Returns a Sass::Script::String.
196
- def video_url(path)
197
- asset_url(path, type: :video)
198
- end
199
-
200
- # Public: Generate url for audio path.
201
- #
202
- # path - Sass::Script::String URL path
203
- #
204
- # Returns a Sass::Script::String.
205
- def audio_path(path)
206
- asset_path(path, type: :audio)
207
- end
208
-
209
- # Public: Generate a audio url() link.
210
- #
211
- # path - Sass::Script::String URL path
212
- #
213
- # Returns a Sass::Script::String.
214
- def audio_url(path)
215
- asset_url(path, type: :audio)
216
- end
217
-
218
- # Public: Generate url for font path.
219
- #
220
- # path - Sass::Script::String URL path
221
- #
222
- # Returns a Sass::Script::String.
223
- def font_path(path)
224
- asset_path(path, type: :font)
225
- end
226
-
227
- # Public: Generate a font url() link.
228
- #
229
- # path - Sass::Script::String URL path
230
- #
231
- # Returns a Sass::Script::String.
232
- def font_url(path)
233
- asset_url(path, type: :font)
234
- end
235
-
236
- # Public: Generate url for javascript path.
237
- #
238
- # path - Sass::Script::String URL path
239
- #
240
- # Returns a Sass::Script::String.
241
- def javascript_path(path)
242
- asset_path(path, type: :javascript)
243
- end
244
-
245
- # Public: Generate a javascript url() link.
246
- #
247
- # path - Sass::Script::String URL path
248
- #
249
- # Returns a Sass::Script::String.
250
- def javascript_url(path)
251
- asset_url(path, type: :javascript)
252
- end
253
-
254
- # Public: Generate url for stylesheet path.
255
- #
256
- # path - Sass::Script::String URL path
257
- #
258
- # Returns a Sass::Script::String.
259
- def stylesheet_path(path)
260
- asset_path(path, type: :stylesheet)
261
- end
262
-
263
- # Public: Generate a stylesheet url() link.
264
- #
265
- # path - Sass::Script::String URL path
266
- #
267
- # Returns a Sass::Script::String.
268
- def stylesheet_url(path)
269
- asset_url(path, type: :stylesheet)
270
- end
271
-
272
- # Public: Generate a data URI for asset path.
273
- #
274
- # path - Sass::Script::String logical asset path
275
- #
276
- # Returns a Sass::Script::String.
277
- def asset_data_url(path)
278
- url = condenser_environment.asset_data_uri(path.value)
279
- Sass::Script::String.new("url(" + url + ")")
280
- end
281
-
282
- protected
283
- # Public: The Environment.
284
- #
285
- # Returns Condenser::Environment.
286
- def condenser_context
287
- options[:condenser][:context]
288
- end
289
-
290
- def condenser_environment
291
- options[:condenser][:environment]
292
- end
131
+ # Returns a Sass::Script::String.
132
+ def asset_url(path, options = {})
133
+ SassC::Script::Value::String.new("url(#{asset_path(path, options).value})")
134
+ end
293
135
 
294
- # Public: Mutatable set of dependencies.
295
- #
296
- # Returns a Set.
297
- def condenser_dependencies
298
- options[:condenser][:process_dependencies]
299
- end
136
+ protected
300
137
 
138
+ def condenser_context
139
+ options[:condenser][:context]
140
+ end
141
+
142
+ def condenser_environment
143
+ options[:condenser][:environment]
301
144
  end
302
145
  end
146
+ end
303
147
 
304
- class ScssTransformer < SassTransformer
305
- def self.syntax
306
- :scss
307
- end
148
+ class Condenser::ScssTransformer < Condenser::SassTransformer
149
+ def self.syntax
150
+ :scss
308
151
  end
309
152
  end
@@ -0,0 +1,26 @@
1
+ class Condenser::SVGTransformer::Base
2
+
3
+ attr_accessor :children
4
+
5
+ def initialize(escape: nil)
6
+ @children = []
7
+ end
8
+
9
+ def to_module()
10
+ var_generator = Condenser::SVGTransformer::VarGenerator.new
11
+
12
+ <<~JS
13
+ export default function (svgAttributes) {
14
+ #{@children.last.to_js(var_generator: var_generator)}
15
+ if (svgAttributes) {
16
+ Object.keys(svgAttributes).forEach(function (key) {
17
+ __a.setAttribute(key, svgAttributes[key]);
18
+ });
19
+ }
20
+
21
+ return __a;
22
+ }
23
+ JS
24
+ end
25
+
26
+ end
@@ -0,0 +1,54 @@
1
+ class Condenser::SVGTransformer::Tag
2
+
3
+ attr_accessor :tag_name, :attrs, :children, :namespace
4
+
5
+ def initialize(name)
6
+ @tag_name = name
7
+ @attrs = []
8
+ @children = []
9
+ end
10
+
11
+ def to_s
12
+ @value
13
+ end
14
+
15
+ def inspect
16
+ "#<SVG::Tag:#{self.object_id} @tag_name=#{tag_name}>"
17
+ end
18
+
19
+ def to_js(append: nil, var_generator:, indentation: 4, namespace: nil)
20
+ namespace ||= self.namespace
21
+
22
+ output_var = var_generator.next
23
+ js = "#{' '*indentation}var #{output_var} = document.createElement"
24
+ js << if namespace
25
+ "NS(#{namespace.to_js}, #{JSON.generate(tag_name)});\n"
26
+ else
27
+ "(#{JSON.generate(tag_name)});\n"
28
+ end
29
+
30
+ @attrs.each do |attr|
31
+ if attr.is_a?(Hash)
32
+ attr.each do |k, v|
33
+ js << "#{' '*indentation}#{output_var}.setAttribute(#{JSON.generate(k)}, #{v.is_a?(String) ? v : v.to_js});\n"
34
+ end
35
+ else
36
+ js << "#{' '*indentation}#{output_var}.setAttribute(#{JSON.generate(attr)}, \"\");\n"
37
+ end
38
+ end
39
+
40
+ @children.each do |child|
41
+ js << if child.is_a?(Condenser::SVGTransformer::Tag)
42
+ child.to_js(var_generator: var_generator, indentation: indentation, append: output_var, namespace: namespace)
43
+ else
44
+ child.to_js(var_generator: var_generator, indentation: indentation, append: output_var)
45
+ end
46
+ end
47
+
48
+ js << "#{' '*indentation}#{append}.append(#{output_var});\n" if append
49
+
50
+ js
51
+ end
52
+
53
+ end
54
+
@@ -0,0 +1,151 @@
1
+ class Condenser::SVGTransformer::Template
2
+
3
+ include Condenser::ParseHelpers
4
+
5
+ attr_accessor :source
6
+
7
+ START_TAGS = ['<']
8
+ CLOSE_TAGS = ['/>', '>']
9
+ VOID_ELEMENTS = ['!DOCTYPE', '?xml']
10
+
11
+ def initialize(source)
12
+ @source = source.strip
13
+ process
14
+ end
15
+
16
+ def process
17
+ seek(0)
18
+ @tree = [Condenser::SVGTransformer::Base.new]
19
+ @stack = [:str]
20
+
21
+ while !eos?
22
+ case @stack.last
23
+ when :str
24
+ scan_until(Regexp.new("(#{START_TAGS.map{|s| Regexp.escape(s) }.join('|')}|\\z)"))
25
+ if !matched.nil? && START_TAGS.include?(matched)
26
+ @stack << :tag
27
+ end
28
+ when :tag
29
+ scan_until(Regexp.new("(\\/|[^\\s>]+)"))
30
+ if matched == '/'
31
+ @stack.pop
32
+ @stack << :close_tag
33
+ else
34
+ @tree << Condenser::SVGTransformer::Tag.new(matched)
35
+ @stack << :tag_attr_key
36
+ end
37
+ when :close_tag
38
+ scan_until(Regexp.new("([^\\s>]+)"))
39
+
40
+ el = @tree.pop
41
+ if el.tag_name != matched
42
+ raise Condenser::SVGTransformer::TemplateError.new("Expected to close #{el.tag_name.inspect} tag, instead closed #{matched.inspect}\n#{cursor}")
43
+ end
44
+ if !['!DOCTYPE', '?xml'].include?(el.tag_name)
45
+ @tree.last.children << el
46
+ scan_until(Regexp.new("(#{CLOSE_TAGS.map{|s| Regexp.escape(s) }.join('|')})"))
47
+ @stack.pop
48
+ end
49
+ when :tag_attr_key
50
+ scan_until(Regexp.new("(#{CLOSE_TAGS.map{|s| Regexp.escape(s) }.join('|')}|[^\\s=>]+)"))
51
+ if CLOSE_TAGS.include?(matched)
52
+ if matched == '/>' || VOID_ELEMENTS.include?(@tree.last.tag_name)
53
+ el = @tree.pop
54
+ @tree.last.children << el
55
+ @stack.pop
56
+ @stack.pop
57
+ else
58
+ @stack << :str
59
+ end
60
+ else
61
+ key = if matched.start_with?('"') && matched.end_with?('"')
62
+ matched[1..-2]
63
+ elsif matched.start_with?('"') && matched.end_with?('"')
64
+ matched[1..-2]
65
+ else
66
+ matched
67
+ end
68
+ @tree.last.attrs << key
69
+ @stack << :tag_attr_value_tx
70
+ end
71
+ when :tag_attr_value_tx
72
+ scan_until(Regexp.new("(#{(CLOSE_TAGS).map{|s| Regexp.escape(s) }.join('|')}|=|\\S)"))
73
+ tag_key = @tree.last.attrs.pop
74
+ if CLOSE_TAGS.include?(matched)
75
+ el = @tree.last
76
+ el.attrs << tag_key
77
+ if VOID_ELEMENTS.include?(el.tag_name)
78
+ @tree.pop
79
+ @tree.last.children << el
80
+ end
81
+ @stack.pop
82
+ @stack.pop
83
+ @stack.pop
84
+ elsif matched == '='
85
+ @stack.pop
86
+ @tree.last.attrs << tag_key
87
+ @stack << :tag_attr_value
88
+ else
89
+ @stack.pop
90
+ @tree.last.attrs << tag_key
91
+ rewind(1)
92
+ end
93
+
94
+ when :tag_attr_value
95
+ scan_until(Regexp.new("(#{CLOSE_TAGS.map{|s| Regexp.escape(s) }.join('|')}|'|\"|\\S+)"))
96
+
97
+ if matched == '"'
98
+ @stack.pop
99
+ @stack << :tag_attr_value_double_quoted
100
+ elsif matched == "'"
101
+ @stack.pop
102
+ @stack << :tag_attr_value_single_quoted
103
+ else
104
+ @stack.pop
105
+ key = @tree.last.attrs.pop
106
+ @tree.last.namespace = matched if key == 'xmlns'
107
+ @tree.last.attrs << { key => matched }
108
+ end
109
+ when :tag_attr_value_double_quoted
110
+ quoted_value = ''
111
+ scan_until(/"/)
112
+ quoted_value << pre_match if !pre_match.strip.empty?
113
+ rewind(1)
114
+
115
+ quoted_value = Condenser::SVGTransformer::Value.new(quoted_value)
116
+
117
+ key = @tree.last.attrs.pop
118
+ @tree.last.namespace = quoted_value if key == 'xmlns'
119
+ if @tree.last.attrs.last.is_a?(Hash) && !@tree.last.attrs.last.has_key?(key)
120
+ @tree.last.attrs.last[key] = quoted_value
121
+ else
122
+ @tree.last.attrs << { key => quoted_value }
123
+ end
124
+ scan_until(/\"/)
125
+ @stack.pop
126
+ when :tag_attr_value_single_quoted
127
+ quoted_value = ''
128
+ scan_until(/(')/)
129
+ quoted_value << pre_match if !pre_match.strip.empty?
130
+ rewind(1)
131
+
132
+ quoted_value = Condenser::SVGTransformer::Value.new(quoted_value)
133
+
134
+ key = @tree.last.attrs.pop
135
+ @tree.last.namespace = quoted_value if key == 'xmlns'
136
+ if @tree.last.attrs.last.is_a?(Hash) && !@tree.last.attrs.last.has_key?(key)
137
+ @tree.last.attrs.last[key] = quoted_value
138
+ else
139
+ @tree.last.attrs << { key => quoted_value }
140
+ end
141
+ scan_until(/\'/)
142
+ @stack.pop
143
+ end
144
+ end
145
+ end
146
+
147
+ def to_module
148
+ @tree.first.to_module
149
+ end
150
+
151
+ end
@@ -0,0 +1,2 @@
1
+ class Condenser::SVGTransformer::TemplateError < StandardError
2
+ end
@@ -0,0 +1,13 @@
1
+ require 'json'
2
+
3
+ class Condenser::SVGTransformer::Value
4
+
5
+ def initialize(value)
6
+ @value = value
7
+ end
8
+
9
+ def to_js
10
+ JSON.generate(@value)
11
+ end
12
+
13
+ end
@@ -0,0 +1,10 @@
1
+ class Condenser::SVGTransformer::VarGenerator
2
+ def initialize
3
+ @current = nil
4
+ end
5
+
6
+ def next
7
+ @current = @current.nil? ? '__a' : @current.next
8
+ @current
9
+ end
10
+ end