Skip to content

Commit bdfffae

Browse files
committed
Merge pull request #120 from rubychan/sass-scanner
Sass scanner
2 parents 880aa33 + 36c2d89 commit bdfffae

File tree

5 files changed

+225
-17
lines changed

5 files changed

+225
-17
lines changed

Changes.textile

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ p=. _This files lists all changes in the CodeRay library since the 0.9.8 release
44

55
h2. Changes in 1.1
66

7+
* New scanner: Sass [#93]
78
* Diff scanner: Highlight inline changes in multi-line changes [#99]
89
* Remove double-click toggle handler from HTML table output
910
* Fixes to CSS scanner (floats, pseudoclasses)

lib/coderay/helpers/file_type.rb

+1
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ def shebang filename
117117
'rpdf' => :ruby,
118118
'ru' => :ruby,
119119
'rxml' => :ruby,
120+
'sass' => :sass,
120121
# 'sch' => :scheme,
121122
'sql' => :sql,
122123
# 'ss' => :scheme,

lib/coderay/scanners/css.rb

+14-17
Original file line numberDiff line numberDiff line change
@@ -15,44 +15,41 @@ class CSS < Scanner
1515

1616
module RE # :nodoc:
1717
Hex = /[0-9a-fA-F]/
18-
Unicode = /\\#{Hex}{1,6}(?:\r\n|\s)?/ # differs from standard because it allows uppercase hex too
19-
Escape = /#{Unicode}|\\[^\r\n\f0-9a-fA-F]/
20-
NMChar = /[-_a-zA-Z0-9]|#{Escape}/
21-
NMStart = /[_a-zA-Z]|#{Escape}/
22-
NL = /\r\n|\r|\n|\f/
23-
String1 = /"(?:[^\n\r\f\\"]|\\#{NL}|#{Escape})*"?/ # TODO: buggy regexp
24-
String2 = /'(?:[^\n\r\f\\']|\\#{NL}|#{Escape})*'?/ # TODO: buggy regexp
18+
Unicode = /\\#{Hex}{1,6}\b/ # differs from standard because it allows uppercase hex too
19+
Escape = /#{Unicode}|\\[^\n0-9a-fA-F]/
20+
NMChar = /[-_a-zA-Z0-9]/
21+
NMStart = /[_a-zA-Z]/
22+
String1 = /"(?:[^\n\\"]+|\\\n|#{Escape})*"?/ # TODO: buggy regexp
23+
String2 = /'(?:[^\n\\']+|\\\n|#{Escape})*'?/ # TODO: buggy regexp
2524
String = /#{String1}|#{String2}/
2625

2726
HexColor = /#(?:#{Hex}{6}|#{Hex}{3})/
28-
Color = /#{HexColor}/
2927

30-
Num = /-?(?:[0-9]+(?!\.\d)|[0-9]*\.[0-9]+)/
28+
Num = /-?(?:[0-9]*\.[0-9]+|[0-9]+)/
3129
Name = /#{NMChar}+/
3230
Ident = /-?#{NMStart}#{NMChar}*/
3331
AtKeyword = /@#{Ident}/
3432
Percentage = /#{Num}%/
3533

3634
reldimensions = %w[em ex px]
3735
absdimensions = %w[in cm mm pt pc]
38-
Unit = Regexp.union(*(reldimensions + absdimensions + %w[s]))
36+
Unit = Regexp.union(*(reldimensions + absdimensions + %w[s dpi dppx deg]))
3937

4038
Dimension = /#{Num}#{Unit}/
4139

42-
Comment = %r! /\* (?: .*? \*/ | .* ) !mx
43-
Function = /(?:url|alpha|attr|counters?)\((?:[^)\n\r\f]|\\\))*\)?/
40+
Function = /(?:url|alpha|attr|counters?)\((?:[^)\n]|\\\))*\)?/
4441

45-
Id = /##{Name}/
42+
Id = /(?!#{HexColor}\b(?!-))##{Name}/
4643
Class = /\.#{Name}/
47-
PseudoClass = /::?#{Name}/
44+
PseudoClass = /::?#{Ident}/
4845
AttributeSelector = /\[[^\]]*\]?/
4946
end
5047

5148
protected
5249

5350
def setup
5451
@state = :initial
55-
@value_expected = nil
52+
@value_expected = false
5653
end
5754

5855
def scan_tokens encoder, options
@@ -158,7 +155,7 @@ def scan_tokens encoder, options
158155
elsif match = scan(/(?: #{RE::Dimension} | #{RE::Percentage} | #{RE::Num} )/ox)
159156
encoder.text_token match, :float
160157

161-
elsif match = scan(/#{RE::Color}/o)
158+
elsif match = scan(/#{RE::HexColor}/o)
162159
encoder.text_token match, :color
163160

164161
elsif match = scan(/! *important/)
@@ -170,7 +167,7 @@ def scan_tokens encoder, options
170167
elsif match = scan(RE::AtKeyword)
171168
encoder.text_token match, :directive
172169

173-
elsif match = scan(/ [+>:;,.=()\/] /x)
170+
elsif match = scan(/ [+>~:;,.=()\/] /x)
174171
if match == ':'
175172
value_expected = true
176173
elsif match == ';'

lib/coderay/scanners/sass.rb

+207
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
module CodeRay
2+
module Scanners
3+
4+
# A scanner for Sass.
5+
class Sass < CSS
6+
7+
register_for :sass
8+
file_extension 'sass'
9+
10+
SASS_FUNCTION = /(?:inline-image|linear-gradient|color-stops|mix|lighten|darken|rotate|image-url|image-width|image-height|sprite-url|sprite-path|sprite-file|sprite-map|sprite-position|sprite|unquote|join|round|ceil|floor|nth)/
11+
12+
STRING_CONTENT_PATTERN = {
13+
"'" => /(?:[^\n\'\#]+|\\\n|#{RE::Escape}|#(?!\{))+/,
14+
'"' => /(?:[^\n\"\#]+|\\\n|#{RE::Escape}|#(?!\{))+/,
15+
}
16+
17+
protected
18+
19+
def setup
20+
@state = :initial
21+
end
22+
23+
def scan_tokens encoder, options
24+
states = Array(options[:state] || @state)
25+
string_delimiter = nil
26+
27+
until eos?
28+
29+
if match = scan(/\s+/)
30+
encoder.text_token match, :space
31+
value_expected = false if match.index(/\n/)
32+
33+
elsif states.last == :sass_inline && (match = scan(/\}/))
34+
encoder.text_token match, :inline_delimiter
35+
encoder.end_group :inline
36+
states.pop
37+
38+
elsif case states.last
39+
when :initial, :media, :sass_inline
40+
if match = scan(/(?>#{RE::Ident})(?!\()/ox)
41+
encoder.text_token match, value_expected ? :value : (check(/.*:/) ? :key : :type)
42+
next
43+
elsif !value_expected && (match = scan(/\*/))
44+
encoder.text_token match, :type
45+
next
46+
elsif match = scan(RE::Class)
47+
encoder.text_token match, :class
48+
next
49+
elsif match = scan(RE::Id)
50+
encoder.text_token match, :constant
51+
next
52+
elsif match = scan(RE::PseudoClass)
53+
encoder.text_token match, :pseudo_class
54+
next
55+
elsif match = scan(RE::AttributeSelector)
56+
# TODO: Improve highlighting inside of attribute selectors.
57+
encoder.text_token match[0,1], :operator
58+
encoder.text_token match[1..-2], :attribute_name if match.size > 2
59+
encoder.text_token match[-1,1], :operator if match[-1] == ?]
60+
next
61+
elsif match = scan(/(\=|@mixin +)#{RE::Ident}/o)
62+
encoder.text_token match, :function
63+
next
64+
elsif match = scan(/@media/)
65+
encoder.text_token match, :directive
66+
# states.push :media_before_name
67+
next
68+
end
69+
70+
when :block
71+
if match = scan(/(?>#{RE::Ident})(?!\()/ox)
72+
if value_expected
73+
encoder.text_token match, :value
74+
else
75+
encoder.text_token match, :key
76+
end
77+
next
78+
end
79+
80+
when :string
81+
if match = scan(STRING_CONTENT_PATTERN[string_delimiter])
82+
encoder.text_token match, :content
83+
elsif match = scan(/['"]/)
84+
encoder.text_token match, :delimiter
85+
encoder.end_group :string
86+
string_delimiter = nil
87+
states.pop
88+
elsif match = scan(/#\{/)
89+
encoder.begin_group :inline
90+
encoder.text_token match, :inline_delimiter
91+
states.push :sass_inline
92+
elsif match = scan(/ \\ | $ /x)
93+
encoder.end_group state
94+
encoder.text_token match, :error unless match.empty?
95+
states.pop
96+
else
97+
raise_inspect "else case #{string_delimiter} reached; %p not handled." % peek(1), encoder
98+
end
99+
100+
else
101+
#:nocov:
102+
raise_inspect 'Unknown state', encoder
103+
#:nocov:
104+
105+
end
106+
107+
elsif match = scan(/\$#{RE::Ident}/o)
108+
encoder.text_token match, :variable
109+
next
110+
111+
elsif match = scan(/&/)
112+
encoder.text_token match, :local_variable
113+
114+
elsif match = scan(/\+#{RE::Ident}/o)
115+
encoder.text_token match, :include
116+
value_expected = true
117+
118+
elsif match = scan(/\/\*(?:.*?\*\/|.*)|\/\/.*/)
119+
encoder.text_token match, :comment
120+
121+
elsif match = scan(/#\{/)
122+
encoder.begin_group :inline
123+
encoder.text_token match, :inline_delimiter
124+
states.push :sass_inline
125+
126+
elsif match = scan(/\{/)
127+
value_expected = false
128+
encoder.text_token match, :operator
129+
states.push :block
130+
131+
elsif match = scan(/\}/)
132+
value_expected = false
133+
encoder.text_token match, :operator
134+
if states.last == :block || states.last == :media
135+
states.pop
136+
end
137+
138+
elsif match = scan(/['"]/)
139+
encoder.begin_group :string
140+
string_delimiter = match
141+
encoder.text_token match, :delimiter
142+
if states.include? :sass_inline
143+
content = scan_until(/(?=#{string_delimiter}|\}|\z)/)
144+
encoder.text_token content, :content unless content.empty?
145+
encoder.text_token string_delimiter, :delimiter if scan(/#{string_delimiter}/)
146+
encoder.end_group :string
147+
else
148+
states.push :string
149+
end
150+
151+
elsif match = scan(/#{SASS_FUNCTION}/o)
152+
encoder.text_token match, :predefined
153+
154+
elsif match = scan(/#{RE::Function}/o)
155+
encoder.begin_group :function
156+
start = match[/^[-\w]+\(/]
157+
encoder.text_token start, :delimiter
158+
if match[-1] == ?)
159+
encoder.text_token match[start.size..-2], :content
160+
encoder.text_token ')', :delimiter
161+
else
162+
encoder.text_token match[start.size..-1], :content
163+
end
164+
encoder.end_group :function
165+
166+
elsif match = scan(/(?: #{RE::Dimension} | #{RE::Percentage} | #{RE::Num} )/ox)
167+
encoder.text_token match, :float
168+
169+
elsif match = scan(/#{RE::HexColor}/o)
170+
encoder.text_token match, :color
171+
172+
elsif match = scan(/! *(?:important|optional)/)
173+
encoder.text_token match, :important
174+
175+
elsif match = scan(/(?:rgb|hsl)a?\([^()\n]*\)?/)
176+
encoder.text_token match, :color
177+
178+
elsif match = scan(/@else if\b|#{RE::AtKeyword}/)
179+
encoder.text_token match, :directive
180+
value_expected = true
181+
182+
elsif match = scan(/ == | != | [-+*\/>~:;,.=()] /x)
183+
if match == ':'
184+
value_expected = true
185+
elsif match == ';'
186+
value_expected = false
187+
end
188+
encoder.text_token match, :operator
189+
190+
else
191+
encoder.text_token getch, :error
192+
193+
end
194+
195+
end
196+
197+
if options[:keep_state]
198+
@state = states
199+
end
200+
201+
encoder
202+
end
203+
204+
end
205+
206+
end
207+
end

lib/coderay/styles/alpha.rb

+2
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ class Alpha < Style
7878
.exception { color:#C00; font-weight:bold }
7979
.float { color:#60E }
8080
.function { color:#06B; font-weight:bold }
81+
.function .delimiter { color:#024; font-weight:bold }
8182
.global-variable { color:#d70 }
8283
.hex { color:#02b }
8384
.imaginary { color:#f00 }
@@ -86,6 +87,7 @@ class Alpha < Style
8687
.inline-delimiter { font-weight: bold; color: #666 }
8788
.instance-variable { color:#33B }
8889
.integer { color:#00D }
90+
.important { color:#D00 }
8991
.key .char { color: #60f }
9092
.key .delimiter { color: #404 }
9193
.key { color: #606 }

0 commit comments

Comments
 (0)