Skip to content

Commit

Permalink
Merge pull request #120 from rubychan/sass-scanner
Browse files Browse the repository at this point in the history
Sass scanner
  • Loading branch information
korny committed Jun 9, 2013
2 parents 880aa33 + 36c2d89 commit bdfffae
Show file tree
Hide file tree
Showing 5 changed files with 225 additions and 17 deletions.
1 change: 1 addition & 0 deletions Changes.textile
Expand Up @@ -4,6 +4,7 @@ p=. _This files lists all changes in the CodeRay library since the 0.9.8 release

h2. Changes in 1.1

* New scanner: Sass [#93]
* Diff scanner: Highlight inline changes in multi-line changes [#99]
* Remove double-click toggle handler from HTML table output
* Fixes to CSS scanner (floats, pseudoclasses)
Expand Down
1 change: 1 addition & 0 deletions lib/coderay/helpers/file_type.rb
Expand Up @@ -117,6 +117,7 @@ def shebang filename
'rpdf' => :ruby,
'ru' => :ruby,
'rxml' => :ruby,
'sass' => :sass,
# 'sch' => :scheme,
'sql' => :sql,
# 'ss' => :scheme,
Expand Down
31 changes: 14 additions & 17 deletions lib/coderay/scanners/css.rb
Expand Up @@ -15,44 +15,41 @@ class CSS < Scanner

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

HexColor = /#(?:#{Hex}{6}|#{Hex}{3})/
Color = /#{HexColor}/

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

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

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

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

Id = /##{Name}/
Id = /(?!#{HexColor}\b(?!-))##{Name}/
Class = /\.#{Name}/
PseudoClass = /::?#{Name}/
PseudoClass = /::?#{Ident}/
AttributeSelector = /\[[^\]]*\]?/
end

protected

def setup
@state = :initial
@value_expected = nil
@value_expected = false
end

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

elsif match = scan(/#{RE::Color}/o)
elsif match = scan(/#{RE::HexColor}/o)
encoder.text_token match, :color

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

elsif match = scan(/ [+>:;,.=()\/] /x)
elsif match = scan(/ [+>~:;,.=()\/] /x)
if match == ':'
value_expected = true
elsif match == ';'
Expand Down
207 changes: 207 additions & 0 deletions lib/coderay/scanners/sass.rb
@@ -0,0 +1,207 @@
module CodeRay
module Scanners

# A scanner for Sass.
class Sass < CSS

register_for :sass
file_extension 'sass'

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)/

STRING_CONTENT_PATTERN = {
"'" => /(?:[^\n\'\#]+|\\\n|#{RE::Escape}|#(?!\{))+/,
'"' => /(?:[^\n\"\#]+|\\\n|#{RE::Escape}|#(?!\{))+/,
}

protected

def setup
@state = :initial
end

def scan_tokens encoder, options
states = Array(options[:state] || @state)
string_delimiter = nil

until eos?

if match = scan(/\s+/)
encoder.text_token match, :space
value_expected = false if match.index(/\n/)

elsif states.last == :sass_inline && (match = scan(/\}/))
encoder.text_token match, :inline_delimiter
encoder.end_group :inline
states.pop

elsif case states.last
when :initial, :media, :sass_inline
if match = scan(/(?>#{RE::Ident})(?!\()/ox)
encoder.text_token match, value_expected ? :value : (check(/.*:/) ? :key : :type)
next
elsif !value_expected && (match = scan(/\*/))
encoder.text_token match, :type
next
elsif match = scan(RE::Class)
encoder.text_token match, :class
next
elsif match = scan(RE::Id)
encoder.text_token match, :constant
next
elsif match = scan(RE::PseudoClass)
encoder.text_token match, :pseudo_class
next
elsif match = scan(RE::AttributeSelector)
# TODO: Improve highlighting inside of attribute selectors.
encoder.text_token match[0,1], :operator
encoder.text_token match[1..-2], :attribute_name if match.size > 2
encoder.text_token match[-1,1], :operator if match[-1] == ?]
next
elsif match = scan(/(\=|@mixin +)#{RE::Ident}/o)
encoder.text_token match, :function
next
elsif match = scan(/@media/)
encoder.text_token match, :directive
# states.push :media_before_name
next
end

when :block
if match = scan(/(?>#{RE::Ident})(?!\()/ox)
if value_expected
encoder.text_token match, :value
else
encoder.text_token match, :key
end
next
end

when :string
if match = scan(STRING_CONTENT_PATTERN[string_delimiter])
encoder.text_token match, :content
elsif match = scan(/['"]/)
encoder.text_token match, :delimiter
encoder.end_group :string
string_delimiter = nil
states.pop
elsif match = scan(/#\{/)
encoder.begin_group :inline
encoder.text_token match, :inline_delimiter
states.push :sass_inline
elsif match = scan(/ \\ | $ /x)
encoder.end_group state
encoder.text_token match, :error unless match.empty?
states.pop
else
raise_inspect "else case #{string_delimiter} reached; %p not handled." % peek(1), encoder
end

else
#:nocov:
raise_inspect 'Unknown state', encoder
#:nocov:

end

elsif match = scan(/\$#{RE::Ident}/o)
encoder.text_token match, :variable
next

elsif match = scan(/&/)
encoder.text_token match, :local_variable

elsif match = scan(/\+#{RE::Ident}/o)
encoder.text_token match, :include
value_expected = true

elsif match = scan(/\/\*(?:.*?\*\/|.*)|\/\/.*/)
encoder.text_token match, :comment

elsif match = scan(/#\{/)
encoder.begin_group :inline
encoder.text_token match, :inline_delimiter
states.push :sass_inline

elsif match = scan(/\{/)
value_expected = false
encoder.text_token match, :operator
states.push :block

elsif match = scan(/\}/)
value_expected = false
encoder.text_token match, :operator
if states.last == :block || states.last == :media
states.pop
end

elsif match = scan(/['"]/)
encoder.begin_group :string
string_delimiter = match
encoder.text_token match, :delimiter
if states.include? :sass_inline
content = scan_until(/(?=#{string_delimiter}|\}|\z)/)
encoder.text_token content, :content unless content.empty?
encoder.text_token string_delimiter, :delimiter if scan(/#{string_delimiter}/)
encoder.end_group :string
else
states.push :string
end

elsif match = scan(/#{SASS_FUNCTION}/o)
encoder.text_token match, :predefined

elsif match = scan(/#{RE::Function}/o)
encoder.begin_group :function
start = match[/^[-\w]+\(/]
encoder.text_token start, :delimiter
if match[-1] == ?)
encoder.text_token match[start.size..-2], :content
encoder.text_token ')', :delimiter
else
encoder.text_token match[start.size..-1], :content
end
encoder.end_group :function

elsif match = scan(/(?: #{RE::Dimension} | #{RE::Percentage} | #{RE::Num} )/ox)
encoder.text_token match, :float

elsif match = scan(/#{RE::HexColor}/o)
encoder.text_token match, :color

elsif match = scan(/! *(?:important|optional)/)
encoder.text_token match, :important

elsif match = scan(/(?:rgb|hsl)a?\([^()\n]*\)?/)
encoder.text_token match, :color

elsif match = scan(/@else if\b|#{RE::AtKeyword}/)
encoder.text_token match, :directive
value_expected = true

elsif match = scan(/ == | != | [-+*\/>~:;,.=()] /x)
if match == ':'
value_expected = true
elsif match == ';'
value_expected = false
end
encoder.text_token match, :operator

else
encoder.text_token getch, :error

end

end

if options[:keep_state]
@state = states
end

encoder
end

end

end
end
2 changes: 2 additions & 0 deletions lib/coderay/styles/alpha.rb
Expand Up @@ -78,6 +78,7 @@ class Alpha < Style
.exception { color:#C00; font-weight:bold }
.float { color:#60E }
.function { color:#06B; font-weight:bold }
.function .delimiter { color:#024; font-weight:bold }
.global-variable { color:#d70 }
.hex { color:#02b }
.imaginary { color:#f00 }
Expand All @@ -86,6 +87,7 @@ class Alpha < Style
.inline-delimiter { font-weight: bold; color: #666 }
.instance-variable { color:#33B }
.integer { color:#00D }
.important { color:#D00 }
.key .char { color: #60f }
.key .delimiter { color: #404 }
.key { color: #606 }
Expand Down

0 comments on commit bdfffae

Please sign in to comment.