haml 3.0.0.rc.3 → 3.0.0.rc.4

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of haml might be problematic. Click here for more details.

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
- 3.0.0.rc.3
1
+ 3.0.0.rc.4
@@ -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
@@ -169,6 +169,22 @@ module ActionView
169
169
  alias_method :form_for_without_haml, :form_for
170
170
  alias_method :form_for, :form_for_with_haml
171
171
  end
172
+
173
+ module CacheHelper
174
+ # This is a workaround for a Rails 3 bug
175
+ # that's present at least through beta 3.
176
+ # Their fragment_for assumes that the block
177
+ # will return its contents as a string,
178
+ # which is not always the case.
179
+ # Luckily, it only makes this assumption if caching is disabled,
180
+ # so we only override that case.
181
+ def fragment_for_with_haml(*args, &block)
182
+ return fragment_for_without_haml(*args, &block) if controller.perform_caching
183
+ capture(&block)
184
+ end
185
+ alias_method :fragment_for_without_haml, :fragment_for
186
+ alias_method :fragment_for, :fragment_for_with_haml
187
+ end
172
188
  else
173
189
  module FormTagHelper
174
190
  def form_tag_with_haml(url_for_options = {}, options = {}, *parameters_for_url, &proc)
@@ -5,5 +5,6 @@
5
5
 
6
6
  # Rails 3.0.0.beta.2+
7
7
  if defined?(ActiveSupport) && Haml::Util.has?(:public_method, ActiveSupport, :on_load)
8
+ require 'haml/template/options'
8
9
  ActiveSupport.on_load(:action_view) {Haml.init_rails(binding)}
9
10
  end
@@ -1,3 +1,4 @@
1
+ require 'haml/template/options'
1
2
  require 'haml/engine'
2
3
  require 'haml/helpers/action_view_mods'
3
4
  require 'haml/helpers/action_view_extensions'
@@ -5,15 +6,6 @@ require 'haml/helpers/action_view_extensions'
5
6
  module Haml
6
7
  # The class that keeps track of the global options for Haml within Rails.
7
8
  module Template
8
- extend self
9
-
10
- @options = {}
11
- # The options hash for Haml when used within Rails.
12
- # See {file:HAML_REFERENCE.md#haml_options the Haml options documentation}.
13
- #
14
- # @return [{Symbol => Object}]
15
- attr_accessor :options
16
-
17
9
  # Enables integration with the Rails 2.2.5+ XSS protection,
18
10
  # if it's available and enabled.
19
11
  #
@@ -0,0 +1,16 @@
1
+ # We keep options in its own self-contained file
2
+ # so that we can load it independently in Rails 3,
3
+ # where the full template stuff is lazy-loaded.
4
+
5
+ module Haml
6
+ module Template
7
+ extend self
8
+
9
+ @options = {}
10
+ # The options hash for Haml when used within Rails.
11
+ # See {file:HAML_REFERENCE.md#haml_options the Haml options documentation}.
12
+ #
13
+ # @return [{Symbol => Object}]
14
+ attr_accessor :options
15
+ end
16
+ end
@@ -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