haml-edge 2.3.209 → 2.3.210

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. data/.yardopts +2 -0
  2. data/EDGE_GEM_VERSION +1 -1
  3. data/Rakefile +24 -2
  4. data/VERSION +1 -1
  5. data/lib/haml/exec.rb +11 -4
  6. data/lib/haml/filters.rb +3 -0
  7. data/lib/haml/helpers/action_view_extensions.rb +4 -2
  8. data/lib/haml/helpers/action_view_mods.rb +6 -4
  9. data/lib/haml/helpers.rb +2 -10
  10. data/lib/haml/html.rb +0 -1
  11. data/lib/haml/precompiler.rb +37 -30
  12. data/lib/haml/railtie.rb +6 -2
  13. data/lib/haml/root.rb +4 -0
  14. data/lib/haml/template.rb +2 -0
  15. data/lib/haml/util/subset_map.rb +101 -0
  16. data/lib/haml/util.rb +74 -0
  17. data/lib/haml.rb +5 -2
  18. data/lib/sass/engine.rb +36 -31
  19. data/lib/sass/files.rb +1 -1
  20. data/lib/sass/plugin/staleness_checker.rb +9 -9
  21. data/lib/sass/plugin.rb +21 -0
  22. data/lib/sass/script/color.rb +4 -3
  23. data/lib/sass/script/css_lexer.rb +11 -1
  24. data/lib/sass/script/css_parser.rb +4 -1
  25. data/lib/sass/script/funcall.rb +9 -0
  26. data/lib/sass/script/interpolation.rb +21 -0
  27. data/lib/sass/script/lexer.rb +30 -13
  28. data/lib/sass/script/node.rb +1 -1
  29. data/lib/sass/script/number.rb +4 -5
  30. data/lib/sass/script/parser.rb +13 -14
  31. data/lib/sass/script/string.rb +8 -2
  32. data/lib/sass/script/string_interpolation.rb +27 -4
  33. data/lib/sass/script.rb +1 -2
  34. data/lib/sass/scss/css_parser.rb +5 -3
  35. data/lib/sass/scss/parser.rb +146 -64
  36. data/lib/sass/scss/rx.rb +9 -1
  37. data/lib/sass/scss/sass_parser.rb +11 -0
  38. data/lib/sass/scss/script_lexer.rb +2 -0
  39. data/lib/sass/scss/static_parser.rb +48 -0
  40. data/lib/sass/scss.rb +3 -0
  41. data/lib/sass/selector/abstract_sequence.rb +40 -0
  42. data/lib/sass/selector/comma_sequence.rb +80 -0
  43. data/lib/sass/selector/sequence.rb +194 -0
  44. data/lib/sass/selector/simple.rb +107 -0
  45. data/lib/sass/selector/simple_sequence.rb +161 -0
  46. data/lib/sass/selector.rb +353 -0
  47. data/lib/sass/tree/comment_node.rb +1 -0
  48. data/lib/sass/tree/debug_node.rb +1 -0
  49. data/lib/sass/tree/directive_node.rb +1 -0
  50. data/lib/sass/tree/extend_node.rb +60 -0
  51. data/lib/sass/tree/for_node.rb +1 -0
  52. data/lib/sass/tree/if_node.rb +2 -0
  53. data/lib/sass/tree/import_node.rb +2 -0
  54. data/lib/sass/tree/mixin_def_node.rb +1 -0
  55. data/lib/sass/tree/mixin_node.rb +21 -5
  56. data/lib/sass/tree/node.rb +59 -12
  57. data/lib/sass/tree/prop_node.rb +20 -21
  58. data/lib/sass/tree/root_node.rb +8 -17
  59. data/lib/sass/tree/rule_node.rb +49 -100
  60. data/lib/sass/tree/variable_node.rb +1 -0
  61. data/lib/sass/tree/warn_node.rb +1 -0
  62. data/lib/sass/tree/while_node.rb +1 -0
  63. data/lib/sass.rb +1 -0
  64. data/test/haml/engine_test.rb +185 -3
  65. data/test/haml/helper_test.rb +25 -2
  66. data/test/haml/template_test.rb +2 -2
  67. data/test/haml/templates/helpers.haml +13 -0
  68. data/test/haml/util/subset_map_test.rb +91 -0
  69. data/test/haml/util_test.rb +25 -0
  70. data/test/sass/conversion_test.rb +23 -3
  71. data/test/sass/engine_test.rb +50 -7
  72. data/test/sass/extend_test.rb +1045 -0
  73. data/test/sass/results/complex.css +0 -1
  74. data/test/sass/results/script.css +1 -1
  75. data/test/sass/script_conversion_test.rb +16 -0
  76. data/test/sass/script_test.rb +37 -4
  77. data/test/sass/scss/css_test.rb +17 -3
  78. data/test/sass/scss/rx_test.rb +1 -1
  79. data/test/sass/scss/scss_test.rb +30 -0
  80. data/test/sass/templates/complex.sass +0 -2
  81. data/test/test_helper.rb +5 -0
  82. metadata +17 -3
data/lib/sass/engine.rb CHANGED
@@ -9,12 +9,14 @@ require 'sass/tree/directive_node'
9
9
  require 'sass/tree/variable_node'
10
10
  require 'sass/tree/mixin_def_node'
11
11
  require 'sass/tree/mixin_node'
12
+ require 'sass/tree/extend_node'
12
13
  require 'sass/tree/if_node'
13
14
  require 'sass/tree/while_node'
14
15
  require 'sass/tree/for_node'
15
16
  require 'sass/tree/debug_node'
16
17
  require 'sass/tree/warn_node'
17
18
  require 'sass/tree/import_node'
19
+ require 'sass/selector'
18
20
  require 'sass/environment'
19
21
  require 'sass/script'
20
22
  require 'sass/scss'
@@ -79,60 +81,49 @@ module Sass
79
81
  end
80
82
 
81
83
  # The character that begins a CSS property.
82
- # @private
83
84
  PROPERTY_CHAR = ?:
84
85
 
85
86
  # The character that designates that
86
87
  # a property should be assigned to a SassScript expression.
87
- # @private
88
88
  SCRIPT_CHAR = ?=
89
89
 
90
90
  # The character that designates the beginning of a comment,
91
91
  # either Sass or CSS.
92
- # @private
93
92
  COMMENT_CHAR = ?/
94
93
 
95
94
  # The character that follows the general COMMENT_CHAR and designates a Sass comment,
96
95
  # which is not output as a CSS comment.
97
- # @private
98
96
  SASS_COMMENT_CHAR = ?/
99
97
 
100
98
  # The character that follows the general COMMENT_CHAR and designates a CSS comment,
101
99
  # which is embedded in the CSS document.
102
- # @private
103
100
  CSS_COMMENT_CHAR = ?*
104
101
 
105
102
  # The character used to denote a compiler directive.
106
- # @private
107
103
  DIRECTIVE_CHAR = ?@
108
104
 
109
105
  # Designates a non-parsed rule.
110
- # @private
111
106
  ESCAPE_CHAR = ?\\
112
107
 
113
108
  # Designates block as mixin definition rather than CSS rules to output
114
- # @private
115
109
  MIXIN_DEFINITION_CHAR = ?=
116
110
 
117
111
  # Includes named mixin declared using MIXIN_DEFINITION_CHAR
118
- # @private
119
112
  MIXIN_INCLUDE_CHAR = ?+
120
113
 
121
114
  # The regex that matches properties of the form `name: prop`.
122
- # @private
123
115
  PROPERTY_NEW_MATCHER = /^[^\s:"\[]+\s*[=:](\s|$)/
124
116
 
125
117
  # The regex that matches and extracts data from
126
118
  # properties of the form `name: prop`.
127
- # @private
128
119
  PROPERTY_NEW = /^([^\s=:"]+)\s*(=|:)(?:\s+|$)(.*)/
129
120
 
130
121
  # The regex that matches and extracts data from
131
122
  # properties of the form `:name prop`.
132
- # @private
133
123
  PROPERTY_OLD = /^:([^\s=:"]+)\s*(=?)(?:\s+|$)(.*)/
134
124
 
135
125
  # The default options for Sass::Engine.
126
+ # @api public
136
127
  DEFAULT_OPTIONS = {
137
128
  :style => :nested,
138
129
  :load_paths => ['.'],
@@ -382,7 +373,10 @@ WARNING
382
373
  # if we're using the new property syntax
383
374
  Tree::RuleNode.new(parse_interp(line.text))
384
375
  else
385
- parse_property(line, PROPERTY_OLD)
376
+ name, eq, value = line.text.scan(PROPERTY_OLD)[0]
377
+ raise SyntaxError.new("Invalid property: \"#{line.text}\".",
378
+ :line => @line) if name.nil? || value.nil?
379
+ parse_property(name, parse_interp(name), eq, value, :old, line)
386
380
  end
387
381
  when ?!, ?$
388
382
  parse_variable(line)
@@ -401,20 +395,29 @@ WARNING
401
395
  parse_mixin_include(line, root)
402
396
  end
403
397
  else
404
- if line.text =~ PROPERTY_NEW_MATCHER
405
- parse_property(line, PROPERTY_NEW)
406
- else
407
- Tree::RuleNode.new(parse_interp(line.text))
408
- end
398
+ parse_property_or_rule(line)
409
399
  end
410
400
  end
411
401
 
412
- def parse_property(line, property_regx)
413
- name, eq, value = line.text.scan(property_regx)[0]
402
+ def parse_property_or_rule(line)
403
+ scanner = StringScanner.new(line.text)
404
+ hack_char = scanner.scan(/[:\*\.]|\#(?!\{)/)
405
+ parser = Sass::SCSS::SassParser.new(scanner, @line)
414
406
 
415
- raise SyntaxError.new("Invalid property: \"#{line.text}\".",
416
- :line => @line) if name.nil? || value.nil?
407
+ unless res = parser.parse_interp_ident
408
+ return Tree::RuleNode.new(parse_interp(line.text))
409
+ end
410
+ res.unshift(hack_char) if hack_char
411
+
412
+ name = line.text[0...scanner.pos]
413
+ if scanner.scan(/\s*([:=])(?:\s|$)/)
414
+ parse_property(name, res, scanner[1], scanner.rest, :new, line)
415
+ else
416
+ Tree::RuleNode.new(res + parse_interp(scanner.rest))
417
+ end
418
+ end
417
419
 
420
+ def parse_property(name, parsed_name, eq, value, prop, line)
418
421
  if value.strip.empty?
419
422
  expr = Sass::Script::String.new("")
420
423
  else
@@ -427,9 +430,7 @@ WARNING
427
430
  @line, line.offset + 1, @options[:filename])
428
431
  end
429
432
  end
430
- Tree::PropNode.new(
431
- parse_interp(name), expr,
432
- property_regx == PROPERTY_OLD ? :old : :new)
433
+ Tree::PropNode.new(parse_interp(name), expr, prop)
433
434
  end
434
435
 
435
436
  def parse_variable(line)
@@ -500,6 +501,12 @@ WARNING
500
501
  :line => @line + 1) unless line.children.empty?
501
502
  offset = line.offset + line.text.index(value).to_i
502
503
  Tree::DebugNode.new(parse_script(value, :offset => offset))
504
+ elsif directive == "extend"
505
+ raise SyntaxError.new("Invalid extend directive '@extend': expected expression.") unless value
506
+ raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath extend directives.",
507
+ :line => @line + 1) unless line.children.empty?
508
+ offset = line.offset + line.text.index(value).to_i
509
+ Tree::ExtendNode.new(parse_interp(value, offset))
503
510
  elsif directive == "warn"
504
511
  raise SyntaxError.new("Invalid warn directive '@warn': expected expression.") unless value
505
512
  raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath warn directives.",
@@ -552,7 +559,6 @@ WARNING
552
559
  nil
553
560
  end
554
561
 
555
- # @private
556
562
  MIXIN_DEF_RE = /^(?:=|@mixin)\s*(#{Sass::SCSS::RX::IDENT})(.*)$/
557
563
  def parse_mixin_definition(line)
558
564
  name, arg_string = line.text.scan(MIXIN_DEF_RE).first
@@ -565,7 +571,6 @@ WARNING
565
571
  Tree::MixinDefNode.new(name, args)
566
572
  end
567
573
 
568
- # @private
569
574
  MIXIN_INCLUDE_RE = /^(?:\+|@include)\s*(#{Sass::SCSS::RX::IDENT})(.*)$/
570
575
  def parse_mixin_include(line, root)
571
576
  name, arg_string = line.text.scan(MIXIN_INCLUDE_RE).first
@@ -605,15 +610,15 @@ WARNING
605
610
  end
606
611
  end
607
612
 
608
- def parse_interp(text)
609
- self.class.parse_interp(text, @line, :filename => @filename)
613
+ def parse_interp(text, offset = 0)
614
+ self.class.parse_interp(text, @line, offset, :filename => @filename)
610
615
  end
611
616
 
612
617
  # It's important that this have strings (at least)
613
618
  # at the beginning, the end, and between each Script::Node.
614
619
  #
615
620
  # @private
616
- def self.parse_interp(text, line, options)
621
+ def self.parse_interp(text, line, offset, options)
617
622
  res = []
618
623
  rest = Haml::Shared.handle_interpolation text do |scan|
619
624
  escapes = scan[2].size
@@ -623,7 +628,7 @@ WARNING
623
628
  else
624
629
  res << "\\" * [0, escapes - 1].max
625
630
  res << Script::Parser.new(
626
- scan, line, scan.pos - scan.matched_size, options).
631
+ scan, line, offset + scan.pos - scan.matched_size, options).
627
632
  parse_interpolated
628
633
  end
629
634
  end
data/lib/sass/files.rb CHANGED
@@ -20,7 +20,7 @@ module Sass
20
20
  options = Sass::Engine::DEFAULT_OPTIONS.merge(options)
21
21
  text = File.read(filename)
22
22
 
23
- if options[:cache]
23
+ if options[:cache] || options[:read_cache]
24
24
  compiled_filename = sassc_filename(filename, options)
25
25
  sha = Digest::SHA1.hexdigest(text)
26
26
 
@@ -22,6 +22,7 @@ module Sass
22
22
  # *WARNING*: It is important not to retain the instance for too long,
23
23
  # as its instance-level caches are never explicitly expired.
24
24
  class StalenessChecker
25
+ DELETED = 1.0/0.0 # positive Infinity
25
26
  @dependencies_cache = {}
26
27
 
27
28
  class << self
@@ -47,15 +48,9 @@ module Sass
47
48
  # @param template_file [String] The location of the Sass or SCSS template
48
49
  # that is compiled to `css_file`.
49
50
  def stylesheet_needs_update?(css_file, template_file)
50
- template_file = File.expand_path(template_file)
51
+ template_file, css_mtime = File.expand_path(template_file), mtime(css_file)
51
52
 
52
- unless File.exists?(css_file) && File.exists?(template_file)
53
- @dependencies.delete(template_file)
54
- true
55
- else
56
- css_mtime = mtime(css_file)
57
- mtime(template_file) > css_mtime || dependencies_stale?(template_file, css_mtime)
58
- end
53
+ css_mtime == DELETED || dependency_updated?(css_mtime).call(template_file)
59
54
  end
60
55
 
61
56
  # Returns whether or not a given CSS file is out of date
@@ -87,7 +82,12 @@ module Sass
87
82
  end
88
83
 
89
84
  def mtime(filename)
90
- @mtimes[filename] ||= File.mtime(filename)
85
+ @mtimes[filename] ||= begin
86
+ File.mtime(filename).to_i
87
+ rescue Errno::ENOENT
88
+ @dependencies.delete(filename)
89
+ DELETED
90
+ end
91
91
  end
92
92
 
93
93
  def dependencies(filename)
data/lib/sass/plugin.rb CHANGED
@@ -231,6 +231,27 @@ module Sass
231
231
  end
232
232
  end
233
233
 
234
+ # Updates all stylesheets, even those that aren't out-of-date.
235
+ # Ignores the cache.
236
+ #
237
+ # @param individual_files [Array<(String, String)>]
238
+ # A list of files to check for updates
239
+ # **in addition to those specified by the
240
+ # {file:SASS_REFERENCE.md#template_location-option `:template_location` option}.**
241
+ # The first string in each pair is the location of the Sass/SCSS file,
242
+ # the second is the location of the CSS file that it should be compiled to.
243
+ # @see #update_stylesheets
244
+ def force_update_stylesheets(individual_files = [])
245
+ old_options = options
246
+ self.options = options.dup
247
+ options[:never_update] = false
248
+ options[:always_update] = true
249
+ options[:cache] = false
250
+ update_stylesheets(individual_files)
251
+ ensure
252
+ self.options = old_options
253
+ end
254
+
234
255
  # Watches the template directory (or directories)
235
256
  # and updates the CSS files whenever the related Sass/SCSS files change.
236
257
  # `watch` never returns.
@@ -19,7 +19,6 @@ module Sass::Script
19
19
  class << self; include Haml::Util; end
20
20
 
21
21
  # A hash from color names to `[red, green, blue]` value arrays.
22
- # @private
23
22
  HTML4_COLORS = map_vals({
24
23
  'black' => 0x000000,
25
24
  'silver' => 0xc0c0c0,
@@ -39,7 +38,6 @@ module Sass::Script
39
38
  'aqua' => 0x00ffff
40
39
  }) {|color| (0..2).map {|n| color >> (n << 3) & 0xff}.reverse}
41
40
  # A hash from `[red, green, blue]` value arrays to color names.
42
- # @private
43
41
  HTML4_COLORS_REVERSE = map_hash(HTML4_COLORS) {|k, v| [v, k]}
44
42
 
45
43
  # Constructs an RGB or HSL color object,
@@ -108,7 +106,10 @@ module Sass::Script
108
106
  end
109
107
 
110
108
  [:saturation, :lightness].each do |k|
111
- next if @attrs[k].nil? || (0..100).include?(@attrs[k])
109
+ next if @attrs[k].nil?
110
+ @attrs[k] = 0 if @attrs[k] < 0.00001 && @attrs[k] > -0.00001
111
+ @attrs[k] = 100 if @attrs[k] - 100 < 0.00001 && @attrs[k] - 100 > -0.00001
112
+ next if (0..100).include?(@attrs[k])
112
113
  raise Sass::SyntaxError.new("#{k.to_s.capitalize} must be between 0 and 100")
113
114
  end
114
115
 
@@ -1,11 +1,21 @@
1
1
  module Sass
2
2
  module Script
3
+ # This is a subclass of {Lexer} for use in parsing plain CSS properties.
4
+ #
5
+ # @see Sass::SCSS::CssParser
3
6
  class CssLexer < Lexer
7
+ private
8
+
4
9
  def token
5
10
  important || super
6
11
  end
7
12
 
8
- def string(*args)
13
+ def string(re, *args)
14
+ if re == :uri
15
+ return unless uri = scan(URI)
16
+ return [:string, Script::String.new(uri)]
17
+ end
18
+
9
19
  return unless scan(STRING)
10
20
  [:string, Script::String.new((@scanner[1] || @scanner[2]).gsub(/\\(['"])/, '\1'), :string)]
11
21
  end
@@ -3,6 +3,9 @@ require 'sass/script/css_lexer'
3
3
 
4
4
  module Sass
5
5
  module Script
6
+ # This is a subclass of {Parser} for use in parsing plain CSS properties.
7
+ #
8
+ # @see Sass::SCSS::CssParser
6
9
  class CssParser < Parser
7
10
  private
8
11
 
@@ -21,7 +24,7 @@ module Sass
21
24
  # Short-circuit all the SassScript-only productions
22
25
  alias_method :interpolation, :concat
23
26
  alias_method :or_expr, :div
24
- alias_method :unary_div, :funcall
27
+ alias_method :unary_div, :ident
25
28
  alias_method :paren, :string
26
29
  end
27
30
  end
@@ -17,6 +17,15 @@ module Sass
17
17
  # @return [Array<Script::Node>]
18
18
  attr_reader :args
19
19
 
20
+ # Don't set the context for child nodes if this is `url()`,
21
+ # since `url()` allows quoted strings.
22
+ #
23
+ # @param context [Symbol]
24
+ # @see Node#context=
25
+ def context=(context)
26
+ super unless @name == "url"
27
+ end
28
+
20
29
  # @param name [String] See \{#name}
21
30
  # @param name [Array<Script::Node>] See \{#args}
22
31
  def initialize(name, args)
@@ -1,5 +1,15 @@
1
1
  module Sass::Script
2
+ # A SassScript object representing `#{}` interpolation outside a string.
3
+ #
4
+ # @see StringInterpolation
2
5
  class Interpolation < Node
6
+ # Interpolation in a property is of the form `before #{mid} after`.
7
+ #
8
+ # @param before [Node] The SassScript before the interpolation
9
+ # @param mid [Node] The SassScript within the interpolation
10
+ # @param after [Node] The SassScript after the interpolation
11
+ # @param wb [Boolean] Whether there was whitespace between `before` and `#{`
12
+ # @param wa [Boolean] Whether there was whitespace between `}` and `after`
3
13
  def initialize(before, mid, after, wb, wa)
4
14
  @before = before
5
15
  @mid = mid
@@ -8,10 +18,12 @@ module Sass::Script
8
18
  @whitespace_after = wa
9
19
  end
10
20
 
21
+ # @return [String] A human-readable s-expression representation of the interpolation
11
22
  def inspect
12
23
  "(interpolation #{@before.inspect} #{@mid.inspect} #{@after.inspect})"
13
24
  end
14
25
 
26
+ # @see Node#to_sass
15
27
  def to_sass(opts = {})
16
28
  res = ""
17
29
  res << @before.to_sass(opts) if @before
@@ -22,12 +34,21 @@ module Sass::Script
22
34
  res
23
35
  end
24
36
 
37
+ # Returns the three components of the interpolation, `before`, `mid`, and `after`.
38
+ #
39
+ # @return [Array<Node>]
40
+ # @see #initialize
41
+ # @see Node#children
25
42
  def children
26
43
  [@before, @mid, @after].compact
27
44
  end
28
45
 
29
46
  protected
30
47
 
48
+ # Evaluates the interpolation.
49
+ #
50
+ # @param environment [Sass::Environment] The environment in which to evaluate the SassScript
51
+ # @return [Sass::Script::String] The SassScript string that is the value of the interpolation
31
52
  def _perform(environment)
32
53
  res = ""
33
54
  res << @before.perform(environment).to_s if @before
@@ -40,7 +40,6 @@ module Sass
40
40
  attr_reader :offset
41
41
 
42
42
  # A hash from operator strings to the corresponding token types.
43
- # @private
44
43
  OPERATORS = {
45
44
  '+' => :plus,
46
45
  '-' => :minus,
@@ -67,10 +66,8 @@ module Sass
67
66
  '{' => :lcurly,
68
67
  }
69
68
 
70
- # @private
71
69
  OPERATORS_REVERSE = Haml::Util.map_hash(OPERATORS) {|k, v| [v, k]}
72
70
 
73
- # @private
74
71
  TOKEN_NAMES = Haml::Util.map_hash(OPERATORS_REVERSE) {|k, v| [k, v.inspect]}.merge({
75
72
  :const => "variable (e.g. $foo)",
76
73
  :ident => "identifier (e.g. middle)",
@@ -79,22 +76,19 @@ module Sass
79
76
 
80
77
  # A list of operator strings ordered with longer names first
81
78
  # so that `>` and `<` don't clobber `>=` and `<=`.
82
- # @private
83
79
  OP_NAMES = OPERATORS.keys.sort_by {|o| -o.size}
84
80
 
85
81
  # A sub-list of {OP_NAMES} that only includes operators
86
82
  # with identifier names.
87
- # @private
88
83
  IDENT_OP_NAMES = OP_NAMES.select {|k, v| k =~ /^\w+/}
89
84
 
90
85
  # A hash of regular expressions that are used for tokenizing.
91
- # @private
92
86
  REGULAR_EXPRESSIONS = {
93
87
  :whitespace => /\s+/,
94
88
  :comment => COMMENT,
95
89
  :single_line_comment => SINGLE_LINE_COMMENT,
96
90
  :variable => /([!\$])(#{IDENT})/,
97
- :ident => IDENT,
91
+ :ident => /(#{IDENT})(\()?/,
98
92
  :number => /(-)?(?:(\d*\.\d+)|(\d+))([a-zA-Z%]+)?/,
99
93
  :color => HEXCOLOR,
100
94
  :bool => /(true|false)\b/,
@@ -120,6 +114,8 @@ module Sass
120
114
  [:single, false] => string_re("'", "'"),
121
115
  [:double, true] => string_re('', '"'),
122
116
  [:single, true] => string_re('', "'"),
117
+ [:uri, false] => /url\(#{W}(#{URLCHAR}*?)(#{W}\)|(?=#\{))/,
118
+ [:uri, true] => /(#{URLCHAR}*?)(#{W}\)|(?=#\{))/,
123
119
  }
124
120
 
125
121
  # @param str [String, StringScanner] The source text to lex
@@ -179,11 +175,24 @@ module Sass
179
175
  @scanner.eos? && @tok.nil?
180
176
  end
181
177
 
178
+ # Raise an error to the effect that `name` was expected in the input stream
179
+ # and wasn't found.
180
+ #
181
+ # This calls \{#unpeek!} to rewind the scanner to immediately after
182
+ # the last returned token.
183
+ #
184
+ # @param name [String] The name of the entity that was expected but not found
185
+ # @raise [Sass::SyntaxError]
182
186
  def expected!(name)
183
187
  unpeek!
184
188
  Sass::SCSS::Parser.expected(@scanner, name, @line)
185
189
  end
186
190
 
191
+ # Records all non-comment text the lexer consumes within the block
192
+ # and returns it as a string.
193
+ #
194
+ # @yield A block in which text is recorded
195
+ # @return [String]
187
196
  def str
188
197
  old_pos = @tok ? @tok.pos : @scanner.pos
189
198
  yield
@@ -216,8 +225,8 @@ module Sass
216
225
  end
217
226
 
218
227
  variable || string(:double, false) || string(:single, false) || number ||
219
- color || bool || raw(URI) || raw(UNICODERANGE) || special_fun ||
220
- ident_op || ident || op
228
+ color || bool || string(:uri, false) || raw(UNICODERANGE) ||
229
+ special_fun || ident_op || ident || op
221
230
  end
222
231
 
223
232
  def variable
@@ -236,14 +245,20 @@ module Sass
236
245
  end
237
246
 
238
247
  def ident
239
- return unless s = scan(REGULAR_EXPRESSIONS[:ident])
240
- [:ident, s]
248
+ return unless scan(REGULAR_EXPRESSIONS[:ident])
249
+ [@scanner[2] ? :funcall : :ident, @scanner[1]]
241
250
  end
242
251
 
243
252
  def string(re, open)
244
253
  return unless scan(STRING_REGULAR_EXPRESSIONS[[re, open]])
245
254
  @interpolation_stack << re if @scanner[2].empty? # Started an interpolated section
246
- [:string, Script::String.new(@scanner[1].gsub(/\\(['"]|\#\{)/, '\1'), :string)]
255
+ str =
256
+ if re == :uri
257
+ Script::String.new("#{'url(' unless open}#{@scanner[1]}#{')' unless @scanner[2].empty?}")
258
+ else
259
+ Script::String.new(@scanner[1].gsub(/\\(['"]|\#\{)/, '\1'), :string)
260
+ end
261
+ [:string, str]
247
262
  end
248
263
 
249
264
  def number
@@ -269,11 +284,13 @@ module Sass
269
284
  return unless str1 = scan(/(calc|expression|progid:[a-z\.]*)\(/i)
270
285
  str2, _ = Haml::Shared.balance(@scanner, ?(, ?), 1)
271
286
  c = str2.count("\n")
287
+ old_line = @line
288
+ old_offset = @offset
272
289
  @line += c
273
290
  @offset = (c == 0 ? @offset + str2.size : str2[/\n(.*)/, 1].size)
274
291
  [:special_fun,
275
292
  Haml::Util.merge_adjacent_strings(
276
- [str1] + Sass::Engine.parse_interp(str2, @line, @options)),
293
+ [str1] + Sass::Engine.parse_interp(str2, old_line, old_offset, @options)),
277
294
  str1.size + str2.size]
278
295
  end
279
296
 
@@ -35,7 +35,7 @@ module Sass::Script
35
35
  children.each {|c| c.options = options}
36
36
  end
37
37
 
38
- # Sets the options hash for this node,
38
+ # Sets the context for this node,
39
39
  # as well as for all child nodes.
40
40
  #
41
41
  # @param context [Symbol]
@@ -30,6 +30,7 @@ module Sass::Script
30
30
  # The precision with which numbers will be printed to CSS files.
31
31
  # For example, if this is `1000.0`,
32
32
  # `3.1415926` will be printed as `3.142`.
33
+ # @api public
33
34
  PRECISION = 1000.0
34
35
 
35
36
  # @param value [Numeric] The value of the number
@@ -115,7 +116,7 @@ module Sass::Script
115
116
  # @raise [NoMethodError] if `other` is an invalid type
116
117
  def times(other)
117
118
  if other.is_a? Number
118
- self.operate(other, :*)
119
+ operate(other, :*)
119
120
  elsif other.is_a? Color
120
121
  other.times(self)
121
122
  else
@@ -303,7 +304,7 @@ module Sass::Script
303
304
  # @return [Boolean] Whether or not this number can be compared with the other.
304
305
  def comparable_to?(other)
305
306
  begin
306
- self.operate(other, :+)
307
+ operate(other, :+)
307
308
  true
308
309
  rescue Sass::UnitConversionError
309
310
  false
@@ -323,7 +324,7 @@ module Sass::Script
323
324
  rv
324
325
  end
325
326
 
326
- protected
327
+ private
327
328
 
328
329
  def operate(other, operation)
329
330
  this = self
@@ -381,9 +382,7 @@ module Sass::Script
381
382
  end
382
383
 
383
384
  # A hash of unit names to their index in the conversion table
384
- # @private
385
385
  CONVERTABLE_UNITS = {"in" => 0, "cm" => 1, "pc" => 2, "mm" => 3, "pt" => 4}
386
- # @private
387
386
  CONVERSION_TABLE = [[ 1, 2.54, 6, 25.4, 72 ], # in
388
387
  [ nil, 1, 2.36220473, 10, 28.3464567], # cm
389
388
  [ nil, nil, 1, 4.23333333, 12 ], # pc
@@ -120,7 +120,6 @@ module Sass
120
120
  new(*args).parse
121
121
  end
122
122
 
123
- # @private
124
123
  PRECEDENCE = [
125
124
  :comma, :single_eq, :concat, :or, :and,
126
125
  [:eq, :neq],
@@ -214,24 +213,24 @@ RUBY
214
213
  unary :plus, :unary_minus
215
214
  unary :minus, :unary_div
216
215
  unary :div, :unary_not # For strings, so /foo/bar works
217
- unary :not, :funcall
216
+ unary :not, :ident
218
217
 
219
- def funcall
220
- return raw unless @lexer.peek && @lexer.peek.type == :ident
218
+ def ident
219
+ return funcall unless @lexer.peek && @lexer.peek.type == :ident
221
220
  return if @stop_at && @stop_at.include?(@lexer.peek.value)
222
221
 
223
222
  name = @lexer.next
224
- # An identifier without arguments is just a string
225
- unless try_tok(:lparen)
226
- if color = Color::HTML4_COLORS[name.value]
227
- return node(Color.new(color))
228
- end
229
- node(Script::String.new(name.value, :identifier))
230
- else
231
- args = fn_arglist || []
232
- assert_tok(:rparen)
233
- node(Script::Funcall.new(name.value, args))
223
+ if color = Color::HTML4_COLORS[name.value]
224
+ return node(Color.new(color))
234
225
  end
226
+ node(Script::String.new(name.value, :identifier))
227
+ end
228
+
229
+ def funcall
230
+ return raw unless tok = try_tok(:funcall)
231
+ args = fn_arglist || []
232
+ assert_tok(:rparen)
233
+ node(Script::Funcall.new(tok.value, args))
235
234
  end
236
235
 
237
236
  def defn_arglist!(must_have_default)
@@ -15,6 +15,10 @@ module Sass::Script
15
15
  # @return [Symbol] `:string` or `:identifier`
16
16
  attr_reader :type
17
17
 
18
+ # In addition to setting the \{#context} of the string,
19
+ # this sets the string to be an identifier if the context is `:equals`.
20
+ #
21
+ # @see Node#context=
18
22
  def context=(context)
19
23
  super
20
24
  @type = :identifier if context == :equals
@@ -29,6 +33,7 @@ module Sass::Script
29
33
  @type = type
30
34
  end
31
35
 
36
+ # @see Literal#plus
32
37
  def plus(other)
33
38
  other_str = other.is_a?(Sass::Script::String) ? other.value : other.to_s
34
39
  Sass::Script::String.new(self.value + other_str, self.type)
@@ -47,12 +52,13 @@ module Sass::Script
47
52
  def to_sass(opts = {})
48
53
  type = opts[:type] || self.type
49
54
  if type == :identifier
50
- if context == :equals && Sass::SCSS::RX.escape_ident(self.value).include?(?\\)
55
+ if context == :equals && self.value !~ Sass::SCSS::RX::URI &&
56
+ Sass::SCSS::RX.escape_ident(self.value).include?(?\\)
51
57
  return "unquote(#{Sass::Script::String.new(self.value, :string).to_sass})"
52
58
  elsif context == :equals && self.value.size == 0
53
59
  return %q{""}
54
60
  end
55
- return self.value
61
+ return self.value.gsub("\n", " ")
56
62
  end
57
63
 
58
64
  return "\"#{value.gsub('"', "\\\"")}\"" if opts[:quote] == %q{"}