haml-edge 2.3.240 → 2.3.241

Sign up to get free protection for your applications and to get access to all the features.
data/EDGE_GEM_VERSION CHANGED
@@ -1 +1 @@
1
- 2.3.240
1
+ 2.3.241
data/Rakefile CHANGED
@@ -269,6 +269,7 @@ OPTS
269
269
  list.exclude('lib/haml/helpers/xss_mods.rb')
270
270
  list.exclude('lib/sass/plugin/merb.rb')
271
271
  list.exclude('lib/sass/plugin/rails.rb')
272
+ list.exclude('lib/sass/less.rb')
272
273
  end.to_a
273
274
  t.options << '--incremental' if Rake.application.top_level_tasks.include?('redoc')
274
275
  t.options += FileList.new(scope('yard/*.rb')).to_a.map {|f| ['-e', f]}.flatten
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.3.240
1
+ 2.3.241
data/lib/haml/exec.rb CHANGED
@@ -155,6 +155,17 @@ module Haml
155
155
  flag = 'wb' if @options[:unix_newlines] && flag == 'w'
156
156
  File.open(filename, flag)
157
157
  end
158
+
159
+ def handle_load_error(err)
160
+ dep = err.message.scan(/^no such file to load -- (.*)/)[0]
161
+ raise err if @options[:trace] || dep.nil? || dep.empty?
162
+ $stderr.puts <<MESSAGE
163
+ Required dependency #{dep} not found!
164
+ Run "gem install #{dep}" to get it.
165
+ Use --trace for backtrace.
166
+ MESSAGE
167
+ exit 1
168
+ end
158
169
  end
159
170
 
160
171
  # An abstrac class that encapsulates the code
@@ -558,14 +569,7 @@ END
558
569
  raise "#{e.is_a?(::Haml::SyntaxError) ? "Syntax error" : "Error"} on line " +
559
570
  "#{get_line e}: #{e.message}"
560
571
  rescue LoadError => err
561
- dep = err.message.scan(/^no such file to load -- (.*)/)[0]
562
- raise err if @options[:trace] || dep.nil? || dep.empty?
563
- $stderr.puts <<MESSAGE
564
- Required dependency #{dep} not found!
565
- Run "gem install #{dep}" to get it.
566
- Use --trace for backtrace.
567
- MESSAGE
568
- exit 1
572
+ handle_load_error(err)
569
573
  end
570
574
  end
571
575
 
@@ -595,14 +599,15 @@ Options:
595
599
  END
596
600
 
597
601
  opts.on('-F', '--from FORMAT',
598
- 'The format to convert from. Can be css, scss, sass, or sass2.',
602
+ 'The format to convert from. Can be css, scss, sass, less, or sass2.',
599
603
  'sass2 is the same as sass, but updates more old syntax to new.',
600
604
  'By default, this is inferred from the input filename.',
601
605
  'If there is none, defaults to css.') do |name|
602
606
  @options[:from] = name.downcase.to_sym
603
- unless [:css, :scss, :sass, :sass2].include?(@options[:from])
607
+ unless [:css, :scss, :sass, :less, :sass2].include?(@options[:from])
604
608
  raise "Unknown format for sass-convert --from: #{name}"
605
609
  end
610
+ try_less_note if @options[:from] == :less
606
611
  end
607
612
 
608
613
  opts.on('-T', '--to FORMAT',
@@ -685,11 +690,11 @@ END
685
690
  if @options[:in_place]
686
691
  f
687
692
  elsif @options[:output]
688
- output_name = f.gsub(/\.(c|sa|sc)ss$/, ".#{@options[:to]}")
693
+ output_name = f.gsub(/\.(c|sa|sc|le)ss$/, ".#{@options[:to]}")
689
694
  output_name[0...@options[:input].size] = @options[:output]
690
695
  output_name
691
696
  else
692
- f.gsub(/\.(c|sa|sc)ss$/, ".#{@options[:to]}")
697
+ f.gsub(/\.(c|sa|sc|le)ss$/, ".#{@options[:to]}")
693
698
  end
694
699
 
695
700
  unless File.directory?(File.dirname(output))
@@ -715,6 +720,7 @@ END
715
720
  case input.path
716
721
  when /\.scss$/; :scss
717
722
  when /\.sass$/; :sass
723
+ when /\.less$/; :less
718
724
  when /\.css$/; :css
719
725
  end
720
726
  elsif @options[:in_place]
@@ -743,6 +749,11 @@ END
743
749
  if @options[:from] == :css
744
750
  require 'sass/css'
745
751
  ::Sass::CSS.new(input.read, @options[:for_tree]).render(@options[:to])
752
+ elsif @options[:from] == :less
753
+ require 'sass/less'
754
+ try_less_note
755
+ input = input.read if input.is_a?(IO) && !input.is_a?(File) # Less is dumb
756
+ Less::Engine.new(input).to_tree.to_sass_tree.send("to_#{@options[:to]}", @options[:for_tree])
746
757
  else
747
758
  if input.is_a?(File)
748
759
  ::Sass::Files.tree_for(input.path, @options[:for_engine])
@@ -758,6 +769,19 @@ END
758
769
  raise e if @options[:trace]
759
770
  file = " of #{e.sass_filename}" if e.sass_filename
760
771
  raise "Error on line #{e.sass_line}#{file}: #{e.message}\n Use --trace for backtrace"
772
+ rescue LoadError => err
773
+ handle_load_error(err)
774
+ end
775
+
776
+ @@less_note_printed = false
777
+ def try_less_note
778
+ return if @@less_note_printed
779
+ @@less_note_printed = true
780
+ warn <<NOTE
781
+ * NOTE: Sass and Less are different languages, and they work differently.
782
+ * I'll do my best to translate, but some features -- especially mixins --
783
+ * should be checked by hand.
784
+ NOTE
761
785
  end
762
786
  end
763
787
  end
data/lib/sass/less.rb ADDED
@@ -0,0 +1,363 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'less'
4
+
5
+ module Less
6
+ # This is the class that Treetop defines for parsing Less files.
7
+ # Since not everything gets parsed into the AST but is instead resolved at parse-time,
8
+ # we need to override some of it so that it can be converted into Sass.
9
+ module StyleSheet
10
+ # Selector mixins that don't have arguments.
11
+ # This depends only on the syntax at the call site;
12
+ # if it doesn't use parens, it hits this production,
13
+ # regardless of whether the mixin being called has arguments or not.
14
+ module Mixin4
15
+ def build_with_sass(env)
16
+ selectors.build(env, :mixin).each do |path|
17
+ el = path.inject(env.root) do |current, node|
18
+ current.descend(node.selector, node) or raise MixinNameError, "#{selectors.text_value} in #{env}"
19
+ end
20
+ if el.is_a?(Node::Mixin::Def)
21
+ # Calling a mixin with arguments, which gets compiled to a Sass mixin
22
+ env << Node::Mixin::Call.new(el, [], env)
23
+ else
24
+ # Calling a mixin without arguments, which gets compiled to @extend
25
+ sel = selector_str(path)
26
+ base = selector_str(selector_base(path))
27
+ if base == sel
28
+ env << Node::SassNode.new(Sass::Tree::ExtendNode.new([sel]))
29
+ else
30
+ Haml::Util.haml_warn <<WARNING
31
+ WARNING: Sass doesn't support mixing in selector sequences.
32
+ Replacing "#{sel}" with "@extend #{base}"
33
+ WARNING
34
+ env << Node::SassNode.new(Sass::Tree::CommentNode.new("// #{sel};", true))
35
+ env << Node::SassNode.new(Sass::Tree::ExtendNode.new([base]))
36
+ end
37
+ end
38
+ end
39
+ end
40
+ alias_method :build_without_sass, :build
41
+ alias_method :build, :build_with_sass
42
+
43
+ def selector_base(path)
44
+ el, i = Haml::Util.enum_with_index(path).to_a.reverse.find {|e, i| e.selector !~ /^:{1,2}$/} ||
45
+ [path.first, 0]
46
+ sel = (el.selector =~ /^:{0,2}$/ ? el.selector : "")
47
+ [Node::Element.new(el.name, sel)] + path[i+1..-1]
48
+ end
49
+
50
+ def selector_str(path)
51
+ path.map {|e| e.sass_selector_str}.join(' ').gsub(' :', ':')
52
+ end
53
+ end
54
+
55
+ # Property and variable declarations.
56
+ # We want to keep track of the line number
57
+ # so we don't space out the variables too much in the generated Sass.
58
+ module Declaration3
59
+ def build_with_sass(env)
60
+ build_without_sass(env)
61
+ env.rules.last.src_line = input.line_of(interval.first)
62
+ end
63
+ alias_method :build_without_sass, :build
64
+ alias_method :build, :build_with_sass
65
+ end
66
+
67
+ # Comma-separated selectors.
68
+ # Less breaks these into completely separate nodes.
69
+ # Since we don't want this duplication in the Sass,
70
+ # we modify the production to keep track of the original group
71
+ # so we can reconstruct it later on.
72
+ module Selectors2
73
+ def build_with_sass(env, method)
74
+ arr = build_without_sass(env, method)
75
+ return arr if method == :mixin
76
+ rarr = arr.map {|e| e.top(env)}
77
+ rarr.each {|e| e.group = rarr}
78
+ arr
79
+ end
80
+ alias_method :build_without_sass, :build
81
+ alias_method :build, :build_with_sass
82
+ end
83
+
84
+ # Attribute accessors.
85
+ # Sass just flat-out doesn't support these,
86
+ # so we print a warning to that effect and compile them to comments.
87
+ module Accessor1
88
+ def build(env)
89
+ Haml::Util.haml_warn <<WARNING
90
+ WARNING: Sass doesn't support attribute accessors.
91
+ Ignoring #{text_value}
92
+ WARNING
93
+ Node::Anonymous.new("/* #{text_value} */")
94
+ end
95
+ end
96
+
97
+ # @import statements.
98
+ # Less handles these during parse-time,
99
+ # so we want to wrap them up as a node in the tree.
100
+ # We also include the nodes, though,
101
+ # since we want to have access to the mixins
102
+ # so we can tell if they take arguments or not.
103
+ # The included nodes are hidden so they don't appear in the output.
104
+ module Import1
105
+ def build_with_sass(env)
106
+ line = input.line_of(interval.first)
107
+ import = Sass::Tree::ImportNode.new(url.value.gsub(/\.less$/, ''))
108
+ import.line = input.line_of(interval.first)
109
+ env << Node::SassNode.new(import)
110
+ old_rules = env.rules.dup
111
+ build_without_sass env
112
+ (env.rules - old_rules).each {|r| r.hide_in_sass = true}
113
+ rescue ImportError => e
114
+ raise Sass::SyntaxError.new("File to import #{url.text_value} not found or unreadable", :line => line)
115
+ end
116
+ alias_method :build_without_sass, :build
117
+ alias_method :build, :build_with_sass
118
+ end
119
+
120
+ # The IE-specific `alpha(opacity=@var)`.
121
+ # Less manually resolves the variable here at parse-time.
122
+ # We want to keep the variable around,
123
+ # so we compile this to a function.
124
+ # Less doesn't actually have an `=` operator,
125
+ # but that's okay since it's just getting compiled to Sass anyway.
126
+ module Entity::Alpha1
127
+ def build(env)
128
+ Node::Function.new("alpha",
129
+ [Node::Expression.new([
130
+ Node::Keyword.new("opacity"),
131
+ Node::Operator.new("="),
132
+ variable.build])])
133
+ end
134
+ end
135
+ end
136
+
137
+ # The Less AST classes for the document,
138
+ # including both stylesheet-level nodes and expression-level nodes.
139
+ # The main purpose of overriding these is to add `#to_sass_tree` functions
140
+ # for converting to Sass.
141
+ module Node
142
+ module Entity
143
+ attr_accessor :hide_in_sass
144
+ attr_accessor :src_line
145
+ end
146
+
147
+ class Element
148
+ attr_accessor :group
149
+
150
+ def top(env)
151
+ return self if parent.equal?(env)
152
+ return parent.top(env)
153
+ end
154
+
155
+ def to_sass_tree
156
+ if root?
157
+ root = Sass::Tree::RootNode.new("")
158
+ rules.each {|r| root << r.to_sass_tree}
159
+ return root
160
+ end
161
+ return if hide_in_sass
162
+ return if !self.equal?(group.first)
163
+
164
+ last_el = nil
165
+ sel = group.map do |el|
166
+ comma_sel = []
167
+ loop do
168
+ comma_sel << el.sass_selector_str
169
+ break unless el.rules.size == 1 && el.rules.first.is_a?(Element)
170
+ el = el.rules.first
171
+ end
172
+ last_el = el
173
+ comma_sel = comma_sel.join(' ').gsub(' :', ':')
174
+ comma_sel.gsub!(/^:/, '&:') unless parent.root?
175
+ comma_sel
176
+ end.join(', ')
177
+
178
+ rule = Sass::Tree::RuleNode.new([sel])
179
+ last_el.rules.each {|r| rule << r.to_sass_tree}
180
+ return rule
181
+ end
182
+
183
+ def sass_selector_str
184
+ case @selector
185
+ when /[+>~]/; "#{@selector} #{@name}"
186
+ else @selector + @name
187
+ end
188
+ end
189
+ end
190
+
191
+ module Mixin
192
+ class Call
193
+ def to_sass_tree
194
+ return if hide_in_sass
195
+ Sass::Tree::MixinNode.new(@mixin.name.gsub(/^\./, ''), @params.map {|v| v.to_sass_tree})
196
+ end
197
+ end
198
+
199
+ class Def
200
+ def to_sass_tree
201
+ return if hide_in_sass
202
+ mixin = Sass::Tree::MixinDefNode.new(name, @params.map do |v|
203
+ v.value.flatten!
204
+ [Sass::Script::Variable.new(v), v.value.to_sass_tree]
205
+ end)
206
+ rules.each {|r| mixin << r.to_sass_tree}
207
+ mixin
208
+ end
209
+ end
210
+ end
211
+
212
+ class SassNode
213
+ include Entity
214
+
215
+ def initialize(node)
216
+ @node = node
217
+ end
218
+
219
+ def to_sass_tree
220
+ return if hide_in_sass
221
+ @node
222
+ end
223
+ end
224
+
225
+ class Property
226
+ def to_sass_tree
227
+ return if hide_in_sass
228
+ Sass::Tree::PropNode.new([self], @value.to_sass_tree, :new)
229
+ end
230
+ end
231
+
232
+ class Expression
233
+ def to_sass_tree
234
+ if first.is_a?(Array)
235
+ val = map {|e| _to_sass_tree(e)}.inject(nil) do |e, i|
236
+ next i unless e
237
+ Sass::Script::Operation.new(e, i, :comma)
238
+ end
239
+ else
240
+ val = _to_sass_tree(self)
241
+ end
242
+ val.options = {}
243
+ val
244
+ end
245
+
246
+ private
247
+
248
+ LESS_TO_SASS_OPERATORS = {"-" => :minus, "+" => :plus, "*" => :times, "/" => :div, "=" => :single_eq}
249
+ def _to_sass_tree(arr)
250
+ return Sass::Script::UnaryOperation.new(_to_sass_tree(arr[1..-1]), :minus) if arr[0] == "-"
251
+ _to_sass_tree2(*_sass_split(arr))
252
+ end
253
+
254
+ def _to_sass_tree2(first, rest)
255
+ return first if rest.empty?
256
+ if rest[0].is_a?(Operator)
257
+ op = LESS_TO_SASS_OPERATORS[rest[0]]
258
+ if op == :times || op == :div
259
+ second, rest = _sass_split(rest[1..-1])
260
+ return _to_sass_tree2(Sass::Script::Operation.new(first, second, op), rest)
261
+ else
262
+ return Sass::Script::Operation.new(first, _to_sass_tree(rest[1..-1]), op)
263
+ end
264
+ end
265
+
266
+ Sass::Script::Operation.new(first, _to_sass_tree(rest), :concat)
267
+ end
268
+
269
+ def _sass_split(arr)
270
+ return arr[0].to_sass_tree, arr[1..-1] unless arr[0] == "("
271
+ parens = 1
272
+ i = arr[1..-1].each_with_index do |e, i|
273
+ parens += 1 if e == "("
274
+ parens -= 1 if e == ")"
275
+ break i if parens == 0
276
+ end
277
+
278
+ return _to_sass_tree(arr[1...i+1]), arr[i+2..-1]
279
+ end
280
+ end
281
+
282
+ class Color
283
+ def to_sass_tree
284
+ Sass::Script::Color.new(:red => r, :green => g, :blue => b, :alpha => a)
285
+ end
286
+ end
287
+
288
+ class Number
289
+ def to_sass_tree
290
+ Sass::Script::Number.new(self, [self.unit])
291
+ end
292
+ end
293
+
294
+ class Variable
295
+ def to_sass_tree
296
+ if @declaration
297
+ return if hide_in_sass
298
+ node = Sass::Tree::VariableNode.new(self, @value.to_sass_tree, false)
299
+ node.line = self.src_line
300
+ node
301
+ else
302
+ Sass::Script::Variable.new(self)
303
+ end
304
+ end
305
+ end
306
+
307
+ class Function
308
+ def to_sass_tree
309
+ Sass::Script::Funcall.new(self, @args.map {|a| a.to_sass_tree})
310
+ end
311
+ end
312
+
313
+ class Keyword
314
+ def to_sass_tree
315
+ Sass::Script::String.new(self)
316
+ end
317
+ end
318
+
319
+ class Anonymous
320
+ def to_sass_tree
321
+ Sass::Script::String.new(self)
322
+ end
323
+ end
324
+
325
+ class Quoted
326
+ def to_sass_tree
327
+ Sass::Script::String.new(self, true)
328
+ end
329
+ end
330
+
331
+ class FontFamily
332
+ def to_sass_tree
333
+ @family.map {|f| f.to_sass_tree}.inject(nil) do |e, f|
334
+ next f unless e
335
+ Sass::Script::Operation.new(e, f, :comma)
336
+ end
337
+ end
338
+ end
339
+ end
340
+
341
+ # The entry point to Less.
342
+ # By default Less doesn't preserve the filename of the file being parsed,
343
+ # which is unpleasant for error reporting.
344
+ # Our monkeypatch keeps it around.
345
+ class Engine
346
+ def initialize_with_sass(obj, opts = {})
347
+ initialize_without_sass(obj, opts)
348
+ @filename = obj.path if obj.is_a?(File)
349
+ end
350
+ alias_method :initialize_without_sass, :initialize
351
+ alias_method :initialize, :initialize_with_sass
352
+
353
+ def parse_with_sass
354
+ parse_without_sass
355
+ rescue Sass::SyntaxError => e
356
+ e.modify_backtrace(:filename => @filename)
357
+ raise e
358
+ end
359
+ alias_method :parse_without_sass, :parse
360
+ alias_method :parse, :parse_with_sass
361
+ alias_method :to_tree, :parse
362
+ end
363
+ end
@@ -0,0 +1,625 @@
1
+ #!/usr/bin/env ruby
2
+ require File.dirname(__FILE__) + '/../test_helper'
3
+
4
+ begin
5
+ require 'sass/less'
6
+
7
+ class LessConversionTest < Test::Unit::TestCase
8
+ def test_variable_declarations
9
+ assert_renders <<SCSS, <<LESS
10
+ $var1: 2px 3px;
11
+ $var2: $var1 + 7px;
12
+
13
+ $var3: fizz;
14
+
15
+ foo {
16
+ prop: $var1 $var2 $var3; }
17
+ SCSS
18
+ @var1: 2px 3px;
19
+ @var2: @var1 + 7px;
20
+
21
+ @var3: fizz;
22
+
23
+ foo {prop: @var1 @var2 @var3}
24
+ LESS
25
+ end
26
+
27
+ def test_nested_variable_declarations
28
+ assert_renders <<SCSS, <<LESS
29
+ .foo {
30
+ $var: 2px;
31
+ prop: $var; }
32
+ SCSS
33
+ .foo {
34
+ @var: 2px;
35
+ prop: @var; }
36
+ LESS
37
+ end
38
+
39
+ def test_import
40
+ path = File.dirname(__FILE__) + "/templates/importee.less"
41
+ resolved_path = File.dirname(__FILE__) + "/templates/importee"
42
+ assert_renders <<SCSS, <<LESS
43
+ @import "#{resolved_path}";
44
+ @import "#{resolved_path}";
45
+
46
+ @import "#{resolved_path}";
47
+ @import "#{resolved_path}";
48
+ @import "#{resolved_path}";
49
+ SCSS
50
+ @import url(#{path});
51
+ @import url("#{path}");
52
+
53
+ @import url('#{path}');
54
+ @import '#{path}';
55
+ @import "#{path}";
56
+ LESS
57
+ end
58
+
59
+ def test_mixins_found_through_import
60
+ path = File.dirname(__FILE__) + "/templates/importee.less"
61
+ resolved_path = File.dirname(__FILE__) + "/templates/importee"
62
+ assert_renders <<SCSS, <<LESS
63
+ @import "#{resolved_path}";
64
+
65
+ .baz {
66
+ @extend .foo;
67
+ @include bar; }
68
+ SCSS
69
+ @import "#{path}";
70
+
71
+ .baz {.foo; .bar;}
72
+ LESS
73
+ end
74
+
75
+ # Selectors
76
+
77
+ def test_element_selector
78
+ assert_renders <<SCSS, <<LESS
79
+ foo {
80
+ a: b; }
81
+ SCSS
82
+ foo {a: b}
83
+ LESS
84
+ end
85
+
86
+ def test_class_selector
87
+ assert_renders <<SCSS, <<LESS
88
+ .foo {
89
+ a: b; }
90
+ SCSS
91
+ .foo {a: b}
92
+ LESS
93
+ end
94
+
95
+ def test_id_selector
96
+ assert_renders <<SCSS, <<LESS
97
+ #foo {
98
+ a: b; }
99
+ SCSS
100
+ #foo {a: b}
101
+ LESS
102
+ end
103
+
104
+ def test_pseudoclass_selector
105
+ assert_renders <<SCSS, <<LESS
106
+ :foo {
107
+ a: b; }
108
+ SCSS
109
+ :foo {a: b}
110
+ LESS
111
+ end
112
+
113
+ def test_pseudoelement_selector
114
+ assert_renders <<SCSS, <<LESS
115
+ ::foo {
116
+ a: b; }
117
+ SCSS
118
+ ::foo {a: b}
119
+ LESS
120
+ end
121
+
122
+ def test_comma_selector
123
+ assert_renders <<SCSS, <<LESS
124
+ foo, .bar .baz, :bang {
125
+ a: b; }
126
+ SCSS
127
+ foo, .bar .baz, :bang {a: b}
128
+ LESS
129
+ end
130
+
131
+ def test_nested_comma_selector
132
+ assert_renders <<SCSS, <<LESS
133
+ foo bar, .baz {
134
+ .bang, &:bip bap {
135
+ a: b; } }
136
+ SCSS
137
+ foo bar, .baz {
138
+ .bang, :bip bap {a: b} }
139
+ LESS
140
+ end
141
+
142
+ def test_simple_selector_sequence
143
+ assert_renders <<SCSS, <<LESS
144
+ a.foo#bar[attr=val] {
145
+ a: b; }
146
+ SCSS
147
+ a.foo#bar[attr=val] {a: b}
148
+ LESS
149
+ end
150
+
151
+ def test_descendant_selector
152
+ assert_renders <<SCSS, <<LESS
153
+ .foo .bar {
154
+ a: b; }
155
+ SCSS
156
+ .foo .bar {a: b}
157
+ LESS
158
+ end
159
+
160
+ def test_child_selector
161
+ assert_renders <<SCSS, <<LESS
162
+ .foo > .bar {
163
+ a: b; }
164
+ SCSS
165
+ .foo > .bar {a: b}
166
+ LESS
167
+ end
168
+
169
+ def test_adjacent_selector
170
+ assert_renders <<SCSS, <<LESS
171
+ .foo + .bar {
172
+ a: b; }
173
+ SCSS
174
+ .foo + .bar {a: b}
175
+ LESS
176
+ end
177
+
178
+ def test_pseudoclass_in_sequence
179
+ assert_renders <<SCSS, <<LESS
180
+ .foo:bar {
181
+ a: b; }
182
+ SCSS
183
+ .foo:bar {a: b}
184
+ LESS
185
+ end
186
+
187
+ def test_pseudoelement_in_sequence
188
+ assert_renders <<SCSS, <<LESS
189
+ .foo::bar {
190
+ a: b; }
191
+ SCSS
192
+ .foo::bar {a: b}
193
+ LESS
194
+ end
195
+
196
+ # Properties
197
+
198
+ def test_space_separated_props
199
+ assert_renders <<SCSS, <<LESS
200
+ foo {
201
+ a: foo bar baz; }
202
+ SCSS
203
+ foo {a: foo bar baz}
204
+ LESS
205
+ end
206
+
207
+ def test_comma_separated_props
208
+ assert_renders <<SCSS, <<LESS
209
+ foo {
210
+ a: foo, bar, baz; }
211
+ SCSS
212
+ foo {a: foo, bar, baz}
213
+ LESS
214
+ end
215
+
216
+ def test_numbers
217
+ assert_renders <<SCSS, <<LESS
218
+ foo {
219
+ a: 1 2.3 -8 5px 3%; }
220
+ SCSS
221
+ foo {a: 1 2.3 -8 5px 3%}
222
+ LESS
223
+ end
224
+
225
+ def test_colors
226
+ assert_renders <<SCSS, <<LESS
227
+ foo {
228
+ a: red #abcdef blue; }
229
+ SCSS
230
+ foo {a: #f00 #abcdef blue}
231
+ LESS
232
+ end
233
+
234
+ def test_strings
235
+ assert_renders <<SCSS, <<LESS
236
+ foo {
237
+ a: "foo @var bar" "baz bang" "quote'quote" 'quote"quote'; }
238
+ SCSS
239
+ foo {a: "foo @var bar" 'baz bang' "quote'quote" 'quote"quote'}
240
+ LESS
241
+ end
242
+
243
+ def test_slash
244
+ assert_renders <<SCSS, <<LESS
245
+ foo {
246
+ a: small/8px 7em/8px;
247
+ b: 8/4;
248
+ c: (8 / 4); }
249
+ SCSS
250
+ foo {
251
+ a: small/8px 7em/8px;
252
+ b: 8/4;
253
+ c: 8 / 4; }
254
+ LESS
255
+ end
256
+
257
+ def test_url
258
+ assert_renders <<SCSS, <<LESS
259
+ foo {
260
+ a: url("http://foobar.com/fizzle.html?foo=bar&bar=baz");
261
+ b: url("http://foobar.com/fizzle.html?foo=bar&bar=baz");
262
+ c: url("http://foobar.com/fizzle.html?foo=bar&bar=baz"); }
263
+ SCSS
264
+ foo {
265
+ a: url(http://foobar.com/fizzle.html?foo=bar&bar=baz);
266
+ b: url("http://foobar.com/fizzle.html?foo=bar&bar=baz");
267
+ c: url('http://foobar.com/fizzle.html?foo=bar&bar=baz'); }
268
+ LESS
269
+ end
270
+
271
+ def test_functions
272
+ assert_renders <<SCSS, <<LESS
273
+ foo {
274
+ a: baz(12px) rgba(80, 70 120, 0.76);
275
+ b: faz(1px + 3px) bang($var, #aaaaaa * 3); }
276
+ SCSS
277
+ foo {
278
+ a: baz(12px) rgba(80, 70 120, 0.76);
279
+ b: faz(1px + 3px) bang(@var, #aaa * 3); }
280
+ LESS
281
+ end
282
+
283
+ def test_alpha_function
284
+ assert_renders <<SCSS, <<LESS
285
+ foo {
286
+ a: alpha(opacity=2px);
287
+ b: alpha(opacity = $var); }
288
+ SCSS
289
+ foo {
290
+ a: alpha(opacity=2px);
291
+ b: alpha(opacity=@var); }
292
+ LESS
293
+ end
294
+
295
+ def test_variables
296
+ assert_renders <<SCSS, <<LESS
297
+ foo {
298
+ a: $var1 $var-foo; }
299
+ SCSS
300
+ foo {a: @var1 @var-foo}
301
+ LESS
302
+ end
303
+
304
+ def test_operators
305
+ assert_renders <<SCSS, <<LESS
306
+ foo {
307
+ a: 1px + 2px;
308
+ b: #bbaa88 - #aa1122;
309
+ c: 5 * 3;
310
+ d: (8 / 4); }
311
+ SCSS
312
+ foo {
313
+ a: 1px + 2px;
314
+ b: #ba8 - #a12;
315
+ c: 5 * 3;
316
+ d: 8 / 4; }
317
+ LESS
318
+ end
319
+
320
+ def test_operator_precedence
321
+ assert_renders <<SCSS, <<LESS
322
+ foo {
323
+ a: 1 + 2 * 3 + 4;
324
+ b: 1 * 2 + 3 * 4;
325
+ c: 1 - 2 + 2 - 4;
326
+ d: 1 + 2 - 3 + 4;
327
+ e: 1 / 2 - 3 / 4;
328
+ f: 1 - 2 / 3 - 4;
329
+ g: 1 / 2 * 3 / 4; }
330
+ SCSS
331
+ foo {
332
+ a: 1 + 2 * 3 + 4;
333
+ b: 1 * 2 + 3 * 4;
334
+ c: 1 - 2 + 2 - 4;
335
+ d: 1 + 2 - 3 + 4;
336
+ e: 1 / 2 - 3 / 4;
337
+ f: 1 - 2 / 3 - 4;
338
+ g: 1 / 2 * 3 / 4; }
339
+ LESS
340
+ end
341
+
342
+ def test_operators_with_parens
343
+ assert_renders <<SCSS, <<LESS
344
+ foo {
345
+ a: 1px + 2px * 3;
346
+ b: (1px - 2px) / 3; }
347
+ SCSS
348
+ foo {
349
+ a: 1px + (2px * 3);
350
+ b: (1px - (2px)) / 3; }
351
+ LESS
352
+ end
353
+
354
+ def test_unary_minus
355
+ assert_renders <<SCSS, <<LESS
356
+ foo {
357
+ a: 1px + -3px; }
358
+ SCSS
359
+ foo {a: 1px + (- 3px)}
360
+ LESS
361
+ end
362
+
363
+ # Nested Rules
364
+
365
+ def test_single_nested_rule
366
+ assert_renders <<SCSS, <<LESS
367
+ foo bar {
368
+ a: b; }
369
+ SCSS
370
+ foo {bar {a: b}}
371
+ LESS
372
+ end
373
+
374
+ def test_single_nested_rule_with_props
375
+ assert_renders <<SCSS, <<LESS
376
+ foo {
377
+ bar {
378
+ a: b; }
379
+ c: d;
380
+ e: f; }
381
+ SCSS
382
+ foo {
383
+ bar {a: b}
384
+ c: d;
385
+ e: f; }
386
+ LESS
387
+ end
388
+
389
+ def test_two_nested_rules
390
+ assert_renders <<SCSS, <<LESS
391
+ foo {
392
+ bar {
393
+ a: b; }
394
+ baz {
395
+ c: d; } }
396
+ SCSS
397
+ foo {
398
+ bar {a: b}
399
+ baz {c: d} }
400
+ LESS
401
+ end
402
+
403
+ def test_two_nested_rules_with_props
404
+ assert_renders <<SCSS, <<LESS
405
+ foo {
406
+ bar {
407
+ a: b; }
408
+ baz {
409
+ c: d; }
410
+ e: f;
411
+ g: h; }
412
+ SCSS
413
+ foo {
414
+ bar {a: b}
415
+ baz {c: d}
416
+ e: f;
417
+ g: h; }
418
+ LESS
419
+ end
420
+
421
+ def test_nested_rules_with_combinators
422
+ assert_renders <<SCSS, <<LESS
423
+ foo {
424
+ > bar {
425
+ a: b; }
426
+ + baz {
427
+ c: d; } }
428
+ SCSS
429
+ foo {
430
+ > bar {a: b}
431
+ + baz {c: d} }
432
+ LESS
433
+ end
434
+
435
+ def test_nested_pseudo_rules
436
+ assert_renders <<SCSS, <<LESS
437
+ foo {
438
+ &:bar {
439
+ a: b; }
440
+ &::baz {
441
+ c: d; } }
442
+ SCSS
443
+ foo {
444
+ :bar {a: b}
445
+ ::baz {c: d} }
446
+ LESS
447
+ end
448
+
449
+ # Mixins
450
+
451
+ def test_class_inheritance
452
+ assert_renders <<SCSS, <<LESS
453
+ .foo {
454
+ a: b; }
455
+
456
+ .bar {
457
+ @extend .foo; }
458
+ SCSS
459
+ .foo {a: b}
460
+ .bar {.foo;}
461
+ LESS
462
+ end
463
+
464
+ def test_multiple_class_inheritance
465
+ assert_renders <<SCSS, <<LESS
466
+ .foo {
467
+ a: b; }
468
+
469
+ .bar {
470
+ c: d; }
471
+
472
+ .baz {
473
+ @extend .foo;
474
+ @extend .bar; }
475
+ SCSS
476
+ .foo {a: b}
477
+ .bar {c: d}
478
+ .baz {.foo, .bar;}
479
+ LESS
480
+ end
481
+
482
+ def test_pseudoclass_inheritance
483
+ assert_renders <<SCSS, <<LESS
484
+ :foo {
485
+ a: b; }
486
+
487
+ :bar {
488
+ @extend :foo; }
489
+ SCSS
490
+ :foo {a: b}
491
+ :bar {:foo;}
492
+ LESS
493
+ end
494
+
495
+ def test_multiple_pseudoclass_inheritance
496
+ assert_renders <<SCSS, <<LESS
497
+ :foo:bar {
498
+ a: b; }
499
+
500
+ :baz {
501
+ @extend :foo:bar; }
502
+ SCSS
503
+ :foo:bar {a: b}
504
+ :baz {:foo:bar;}
505
+ LESS
506
+ end
507
+
508
+ def test_abstract_mixin
509
+ assert_renders <<SCSS, <<LESS
510
+ @mixin foo {
511
+ a: b; }
512
+
513
+ .bar {
514
+ @include foo; }
515
+ SCSS
516
+ .foo() {a: b}
517
+ .bar {.foo;}
518
+ LESS
519
+ end
520
+
521
+ def test_mixin_with_args
522
+ assert_renders <<SCSS, <<LESS
523
+ @mixin foo($a: 2px, $b: red) {
524
+ a: $a; }
525
+
526
+ .bar {
527
+ @include foo; }
528
+ SCSS
529
+ .foo(@a: 2px, @b: #f00) {a: @a}
530
+ .bar {.foo;}
531
+ LESS
532
+
533
+ assert_renders <<SCSS, <<LESS
534
+ @mixin foo($a: 2px + 3px, $b: red) {
535
+ a: $a; }
536
+
537
+ .bar {
538
+ @include foo(5px); }
539
+ SCSS
540
+ .foo(@a: 2px + 3px, @b: #f00) {a: @a}
541
+ .bar {.foo(5px);}
542
+ LESS
543
+ end
544
+
545
+ ## Disallowed Mixins
546
+
547
+ def test_nested_mixin
548
+ assert_warning(<<WARN) {assert_renders <<SCSS, <<LESS}
549
+ WARNING: Sass doesn't support mixing in selector sequences.
550
+ Replacing ".foo .bar" with "@extend .bar"
551
+ WARN
552
+ .foo .bar {
553
+ a: b; }
554
+
555
+ .bar {
556
+ // .foo .bar;
557
+ @extend .bar; }
558
+ SCSS
559
+ .foo .bar {a: b}
560
+ .bar {.foo .bar;}
561
+ LESS
562
+ end
563
+
564
+ def test_child_selector_mixin
565
+ assert_warning(<<WARN) {assert_renders <<SCSS, <<LESS}
566
+ WARNING: Sass doesn't support mixing in selector sequences.
567
+ Replacing "> .bar" with "@extend .bar"
568
+ WARN
569
+ .foo > .bar {
570
+ a: b; }
571
+
572
+ .bar {
573
+ // > .bar;
574
+ @extend .bar; }
575
+ SCSS
576
+ .foo > .bar {a: b}
577
+ .bar {> .bar;}
578
+ LESS
579
+ end
580
+
581
+ # Accessors
582
+
583
+ def test_property_accessor
584
+ assert_warning(<<WARN) {assert_renders <<SCSS, <<LESS}
585
+ WARNING: Sass doesn't support attribute accessors.
586
+ Ignoring .magic-box['content']
587
+ WARN
588
+ .magic-box {
589
+ content: "gold"; }
590
+
591
+ .foo {
592
+ content: /* .magic-box['content'] */; }
593
+ SCSS
594
+ .magic-box {content: "gold"}
595
+ .foo {content: .magic-box['content']}
596
+ LESS
597
+ end
598
+
599
+ def test_variable_accessor
600
+ assert_warning(<<WARN) {assert_renders <<SCSS, <<LESS}
601
+ WARNING: Sass doesn't support attribute accessors.
602
+ Ignoring .magic-box[@content]
603
+ WARN
604
+ .magic-box {
605
+ $content: "gold";
606
+ content: $content; }
607
+
608
+ .foo {
609
+ content: /* .magic-box[@content] */; }
610
+ SCSS
611
+ .magic-box {@content: "gold"; content: @content}
612
+ .foo {content: .magic-box[@content]}
613
+ LESS
614
+ end
615
+
616
+ private
617
+
618
+ def assert_renders(scss, less)
619
+ assert_equal(scss, Less::Engine.new(less).to_tree.to_sass_tree.to_scss)
620
+ end
621
+ end
622
+
623
+ rescue LoadError => e
624
+ puts "\nCouldn't require less, skipping some tests."
625
+ end
@@ -0,0 +1,2 @@
1
+ .foo {a: b}
2
+ .bar () {c: d}
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: haml-edge
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.240
4
+ version: 2.3.241
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nathan Weizenbaum
@@ -69,9 +69,9 @@ files:
69
69
  - lib/haml/template.rb
70
70
  - lib/haml/shared.rb
71
71
  - lib/haml/util.rb
72
+ - lib/haml/template/options.rb
72
73
  - lib/haml/template/patch.rb
73
74
  - lib/haml/template/plugin.rb
74
- - lib/haml/template/options.rb
75
75
  - lib/haml/version.rb
76
76
  - lib/haml/html/erb.rb
77
77
  - lib/haml/root.rb
@@ -137,6 +137,7 @@ files:
137
137
  - lib/sass/selector/sequence.rb
138
138
  - lib/sass/selector/simple.rb
139
139
  - lib/sass/selector/simple_sequence.rb
140
+ - lib/sass/less.rb
140
141
  - vendor/fssm/LICENSE
141
142
  - vendor/fssm/README.markdown
142
143
  - vendor/fssm/Rakefile
@@ -312,10 +313,12 @@ files:
312
313
  - test/sass/templates/scss_importee.scss
313
314
  - test/sass/templates/warn.sass
314
315
  - test/sass/templates/warn_imported.sass
316
+ - test/sass/templates/importee.less
315
317
  - test/sass/scss/css_test.rb
316
318
  - test/sass/scss/rx_test.rb
317
319
  - test/sass/scss/scss_test.rb
318
320
  - test/sass/scss/test_helper.rb
321
+ - test/sass/less_conversion_test.rb
319
322
  - test/test_helper.rb
320
323
  - extra/haml-mode.el
321
324
  - extra/sass-mode.el
@@ -386,3 +389,4 @@ test_files:
386
389
  - test/sass/scss/css_test.rb
387
390
  - test/sass/scss/rx_test.rb
388
391
  - test/sass/scss/scss_test.rb
392
+ - test/sass/less_conversion_test.rb