diff --git a/lib/coderay/helpers/file_type.rb b/lib/coderay/helpers/file_type.rb index 59bc73fc..f7a75fc1 100644 --- a/lib/coderay/helpers/file_type.rb +++ b/lib/coderay/helpers/file_type.rb @@ -1,5 +1,5 @@ module CodeRay - + # = FileType # # A simple filetype recognizer. @@ -8,18 +8,18 @@ module CodeRay # # # determine the type of the given # lang = FileType[file_name] - # + # # # return :text if the file type is unknown # lang = FileType.fetch file_name, :text - # + # # # try the shebang line, too # lang = FileType.fetch file_name, :text, true module FileType - + UnknownFileType = Class.new Exception - + class << self - + # Try to determine the file type of the file. # # +filename+ is a relative or absolute path to a file. @@ -30,7 +30,7 @@ def [] filename, read_shebang = false name = File.basename filename ext = File.extname(name).sub(/^\./, '') # from last dot, delete the leading dot ext2 = filename.to_s[/\.(.*)/, 1] # from first dot - + type = TypeFromExt[ext] || TypeFromExt[ext.downcase] || @@ -39,10 +39,10 @@ def [] filename, read_shebang = false TypeFromName[name] || TypeFromName[name.downcase] type ||= shebang(filename) if read_shebang - + type end - + # This works like Hash#fetch. # # If the filetype cannot be found, the +default+ value @@ -51,7 +51,7 @@ def fetch filename, default = nil, read_shebang = false if default && block_given? warn 'Block supersedes default value argument; use either.' end - + if type = self[filename, read_shebang] type else @@ -60,9 +60,9 @@ def fetch filename, default = nil, read_shebang = false raise UnknownFileType, 'Could not determine type of %p.' % filename end end - + protected - + def shebang filename return unless File.exist? filename File.open filename, 'r' do |f| @@ -73,9 +73,9 @@ def shebang filename end end end - + end - + TypeFromExt = { 'c' => :c, 'cfc' => :xml, @@ -125,20 +125,21 @@ def shebang filename 'xml' => :xml, 'yaml' => :yaml, 'yml' => :yaml, + 'liquid' => :liquid, } for cpp_alias in %w[cc cpp cp cxx c++ C hh hpp h++ cu] TypeFromExt[cpp_alias] = :cpp end - + TypeFromShebang = /\b(?:ruby|perl|python|sh)\b/ - + TypeFromName = { 'Capfile' => :ruby, 'Rakefile' => :ruby, 'Rantfile' => :ruby, 'Gemfile' => :ruby, } - + end - + end diff --git a/lib/coderay/scanners/liquid.rb b/lib/coderay/scanners/liquid.rb index a0255712..93a546e7 100644 --- a/lib/coderay/scanners/liquid.rb +++ b/lib/coderay/scanners/liquid.rb @@ -1,54 +1,133 @@ module CodeRay module Scanners - load :html - class Liquid < Scanner - + register_for :liquid - title 'Liquid Template' + + DIRECTIVE_KEYWORDS = "list|endlist|for|endfor|wrap|endwrap|if|endif|unless|endunless|elsif|assign|cycle|capture|end|capture|fill|iflist|endiflist|else" + + DIRECTIVE_OPERATORS = "=|==|!=|>|<|<=|>=|contains" - KINDS_NOT_LOC = HTML::KINDS_NOT_LOC + MATH = "=|==|!=|>|<|<=|>" - LIQUID_BLOCK = / - ({[{|%]) - \s + FILTER_KEYWORDS = "date|capitalize|downcase|upcase|first|last|join|sort|map|size|escape_once|escape|strip_html|strip_newlines|newline_to_br|replace_first|replace|remove_first|remove|truncate|truncatewords|prepend|append|minus|plus|times|divided_by|split|modulo" + + LIQUID_DIRECTIVE_BLOCK = / + {% (.*?) - \s - ([%|}]}) - / - - START_OF_LIQUID = /{{|{%/ + %} + /x - protected - def setup - @html_scanner = CodeRay.scanner :html, tokens: @tokens, keep_tokens: true, keep_state: true - @liquid_attribute_scanner = CodeRay.scanner :html, tokens: @tokens, keep_tokens: true, keep_stat: true + @html_scanner = CodeRay.scanner(:html, tokens: @tokens, keep_tokens: true, keep_state: false) end - - def scan_tokens(tokens, options) - until eos? - if (match = scan_until(/(?=#{START_OF_LIQUID})/o) || scan_rest) and not match.empty? - @html_scanner.tokenize match, tokens: tokens - elsif match = scan(/#{LIQUID_BLOCK}/o) - start_tag = self[1] - code = self[2] - end_tag = self[3] - - tokens.begin_group :inline - tokens.text_token start_tag, :inline_delimiter - - unless code.empty? - @liquid_attribute_scanner.tokenize code, tokens: tokens, state: :attribute + + def scan_spaces(encoder) + if match = scan(/\s+/) + encoder.text_token match, :space + end + end + + def scan_selector(encoder, options, match) + scan_spaces(encoder) + if match = scan(/in|with/) + scan_spaces(encoder) + encoder.text_token match, :type + if delimiter = scan(/:/) + encoder.text_token delimiter, :delimiter + scan_spaces(encoder) + end + if variable = scan(/(\w+)|('\S+')|("\w+")/) + encoder.text_token variable, :variable + end + scan_selector(encoder, options, match) + end + end + + def scan_directive(encoder, options, match) + encoder.text_token match, :key + state = :liquid + scan_spaces(encoder) + #This regex doesn't work and I don't know why + if match = scan(/#{DIRECTIVE_KEYWORDS}/) + encoder.text_token match, :directive + scan_spaces(encoder) + if match =~ /if/ + if match = scan(/\w+\.?\w*/) + encoder.text_token match, :variable + end + scan_spaces(encoder) + if match = scan(/#{MATH}/) + encoder.text_token match, :char + scan_spaces(encoder) + end + if match = scan(/(\w+)|('\S+')|(".+")/) + encoder.text_token match, :variable + scan_spaces(encoder) end - - tokens.text_token end_tag, :inline_delimiter unless end_tag.empty? - tokens.end_group :inline - else - raise_inspect 'else-case reached!', tokens - end + end + end + scan_selector(encoder, options, match) + scan_spaces(encoder) + if match = scan(/%}/) + encoder.text_token match, :key + state = :initial + end + end + + def scan_output_filters(encoder, options, match) + encoder.text_token match, :delimiter + scan_spaces(encoder) + if directive = scan(/#{FILTER_KEYWORDS}/) + encoder.text_token directive, :directive + end + if delimiter = scan(/:/) + encoder.text_token delimiter, :delimiter + end + scan_spaces(encoder) + if variable = scan(/(\w+)|('\S+')|(".+")/) + encoder.text_token variable, :variable + end + if next_filter = scan(/\s\|\s/) + scan_output_filters(encoder, options, next_filter) + end + end + + def scan_output(encoder, options, match) + encoder.text_token match, :key + state = :liquid + scan_spaces(encoder) + if match = scan(/(\w+)|('\S+')|("\w+")/) + encoder.text_token match, :variable + end + if match = scan(/(\s\|\s)/) + scan_output_filters(encoder, options, match) + end + scan_spaces(encoder) + if match = scan(/}}/) + encoder.text_token match, :key + end + state = :initial + end + + def scan_tokens(encoder, options) + state = :initial + + until eos? + if (match = scan_until(/(?=({{|{%))/) || scan_rest) and not match.empty? and state != :liquid + @html_scanner.tokenize(match, tokens: encoder) + state = :initial + scan_spaces(encoder) + elsif match = scan(/{%/) + scan_directive(encoder, options, match) + elsif match = scan(/{{/) + scan_output(encoder, options, match) + else + raise "Else-case reached. State: #{state.to_s}." + end end + encoder end end end