condenser 1.2 → 1.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.
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