diff --git a/lib/coderay/encoders/html.rb b/lib/coderay/encoders/html.rb index 1b33e921..fc6678c3 100644 --- a/lib/coderay/encoders/html.rb +++ b/lib/coderay/encoders/html.rb @@ -2,7 +2,7 @@ module CodeRay module Encoders - + # = HTML Encoder # # This is CodeRay's most important highlighter: @@ -15,7 +15,7 @@ module Encoders # puts CodeRay.scan('Some /code/', :ruby).html(:wrap => :span) # #-> Some /code/ # puts CodeRay.scan('Some /code/', :ruby).span #-> the same - # + # # puts CodeRay.scan('Some code', :ruby).html( # :wrap => nil, # :line_numbers => :inline, @@ -27,7 +27,7 @@ module Encoders # === :tab_width # Convert \t characters to +n+ spaces (a number or false.) # false will keep tab characters untouched. - # + # # Default: 8 # # === :css @@ -35,6 +35,11 @@ module Encoders # # Default: :class # + # === :css_class_prefix + # Optional prefix for CSS classes when :css is set to :class. + # + # Default: nil + # # === :wrap # Wrap in :page, :div, :span or nil. # @@ -43,13 +48,13 @@ module Encoders # Default: nil # # === :title - # + # # The title of the HTML page (works only when :wrap is set to :page.) # # Default: 'CodeRay output' # # === :break_lines - # + # # Split multiline blocks at line breaks. # Forced to true if :line_numbers option is set to :inline. # @@ -79,10 +84,10 @@ module Encoders # Default: 10 # # === :highlight_lines - # + # # Highlights certain line numbers. # Can be any Enumerable, typically just an Array or Range, of numbers. - # + # # Bolding is deactivated when :highlight_lines is set. It only makes sense # in combination with :line_numbers. # @@ -95,38 +100,38 @@ module Encoders # # Default: false class HTML < Encoder - + register_for :html - + FILE_EXTENSION = 'snippet.html' - + DEFAULT_OPTIONS = { :tab_width => 8, - + :css => :class, :style => :alpha, :wrap => nil, :title => 'CodeRay output', - + :break_lines => false, - + :line_numbers => nil, :line_number_anchors => 'n', :line_number_start => 1, :bold_every => 10, :highlight_lines => nil, - + :hint => false, } - + autoload :Output, CodeRay.coderay_path('encoders', 'html', 'output') autoload :CSS, CodeRay.coderay_path('encoders', 'html', 'css') autoload :Numbering, CodeRay.coderay_path('encoders', 'html', 'numbering') - + attr_reader :css - + protected - + def self.make_html_escape_hash { '&' => '&', @@ -139,18 +144,18 @@ def self.make_html_escape_hash (Array(0x00..0x8) + Array(0xB..0x1F)).each { |invalid| hash[invalid.chr] = ' ' } end end - + HTML_ESCAPE = make_html_escape_hash HTML_ESCAPE_PATTERN = /[\t"&><\0-\x8\xB-\x1F]/ - + TOKEN_KIND_TO_INFO = Hash.new do |h, kind| h[kind] = kind.to_s.gsub(/_/, ' ').gsub(/\b\w/) { $&.capitalize } end - + TRANSPARENT_TOKEN_KINDS = Set[ :delimiter, :modifier, :content, :escape, :inline_delimiter, ] - + # Generate a hint about the given +kinds+ in a +hint+ style. # # +hint+ may be :info, :info_long or :debug. @@ -168,36 +173,36 @@ def self.token_path_to_hint hint, kinds end title ? " title=\"#{title}\"" : '' end - + def setup options super - + check_options! options - + if options[:wrap] || options[:line_numbers] @real_out = @out @out = ''.dup end - + @break_lines = (options[:break_lines] == true) - + @HTML_ESCAPE = HTML_ESCAPE.merge("\t" => options[:tab_width] ? ' ' * options[:tab_width] : "\t") - + @opened = [] @last_opened = nil @css = CSS.new options[:style] - + @span_for_kinds = make_span_for_kinds(options[:css], options[:hint]) - + @set_last_opened = options[:hint] || options[:css] == :style end - + def finish options unless @opened.empty? @out << '' while @opened.pop @last_opened = nil end - + if @out.respond_to? :to_str @out.extend Output @out.css = @css @@ -207,42 +212,42 @@ def finish options @out.wrap! options[:wrap] @out.apply_title! options[:title] end - + if defined?(@real_out) && @real_out @real_out << @out @out = @real_out end - + super end - + public - + def text_token text, kind style = @span_for_kinds[@last_opened ? [kind, *@opened] : kind] - + text = text.gsub(/#{HTML_ESCAPE_PATTERN}/o) { |m| @HTML_ESCAPE[m] } if text =~ /#{HTML_ESCAPE_PATTERN}/o text = break_lines(text, style) if @break_lines && (style || @opened.size > 0) && text.index("\n") - + if style @out << style << text << '' else @out << text end end - + # token groups, eg. strings def begin_group kind @out << (@span_for_kinds[@last_opened ? [kind, *@opened] : kind] || '') @opened << kind @last_opened = kind if @set_last_opened end - + def end_group kind check_group_nesting 'token group', kind if $CODERAY_DEBUG close_span end - + # whole lines to be highlighted, eg. a deleted line in a diff def begin_line kind if style = @span_for_kinds[@last_opened ? [kind, *@opened] : kind] @@ -257,47 +262,48 @@ def begin_line kind @opened << kind @last_opened = kind if @options[:css] == :style end - + def end_line kind check_group_nesting 'line', kind if $CODERAY_DEBUG close_span end - + protected - + def check_options! options unless [false, nil, :debug, :info, :info_long].include? options[:hint] raise ArgumentError, "Unknown value %p for :hint; expected :info, :info_long, :debug, false, or nil." % [options[:hint]] end - + unless [:class, :style].include? options[:css] raise ArgumentError, 'Unknown value %p for :css.' % [options[:css]] end - + options[:break_lines] = true if options[:line_numbers] == :inline end - + def css_class_for_kinds kinds TokenKinds[kinds.is_a?(Symbol) ? kinds : kinds.first] end - + def style_for_kinds kinds css_classes = kinds.is_a?(Array) ? kinds.map { |c| TokenKinds[c] } : [TokenKinds[kinds]] @css.get_style_for_css_classes css_classes end - + def make_span_for_kinds method, hint + css_class_prefix = options[:css_class_prefix] Hash.new do |h, kinds| begin css_class = css_class_for_kinds(kinds) title = HTML.token_path_to_hint hint, kinds if hint - + if css_class || title if method == :style style = style_for_kinds(kinds) "" else - "" + "" end end end.tap do |span| @@ -306,13 +312,13 @@ def make_span_for_kinds method, hint end end end - + def check_group_nesting name, kind if @opened.empty? || @opened.last != kind warn "Malformed token stream: Trying to close a #{name} (%p) that is not open. Open are: %p." % [kind, @opened[1..-1]] end end - + def break_lines text, style reopen = ''.dup @opened.each_with_index do |kind, index| @@ -320,7 +326,7 @@ def break_lines text, style end text.gsub("\n", "#{'' * @opened.size}#{'' if style}\n#{reopen}#{style}") end - + def close_span if @opened.pop @out << '' @@ -328,6 +334,6 @@ def close_span end end end - + end end diff --git a/lib/coderay/tokens_proxy.rb b/lib/coderay/tokens_proxy.rb index 31ff39be..55770d63 100644 --- a/lib/coderay/tokens_proxy.rb +++ b/lib/coderay/tokens_proxy.rb @@ -1,13 +1,13 @@ module CodeRay - + # The result of a scan operation is a TokensProxy, but should act like Tokens. - # + # # This proxy makes it possible to use the classic CodeRay.scan.encode API # while still providing the benefits of direct streaming. class TokensProxy - + attr_accessor :input, :lang, :options, :block - + # Create a new TokensProxy with the arguments of CodeRay.scan. def initialize input, lang, options = {}, block = nil @input = input @@ -15,7 +15,7 @@ def initialize input, lang, options = {}, block = nil @options = options @block = block end - + # Call CodeRay.encode if +encoder+ is a Symbol; # otherwise, convert the receiver to tokens and call encoder.encode_tokens. def encode encoder, options = {} @@ -25,7 +25,7 @@ def encode encoder, options = {} encoder.encode_tokens tokens, options end end - + # Tries to call encode; # delegates to tokens otherwise. def method_missing method, *args, &blk @@ -33,23 +33,23 @@ def method_missing method, *args, &blk rescue PluginHost::PluginNotFound tokens.send(method, *args, &blk) end - + # The (cached) result of the tokenized input; a Tokens instance. def tokens @tokens ||= scanner.tokenize(input) end - + # A (cached) scanner instance to use for the scan task. def scanner @scanner ||= CodeRay.scanner(lang, options, &block) end - + # Overwrite Struct#each. def each *args, &blk tokens.each(*args, &blk) self end - + end - + end diff --git a/test/unit/html.rb b/test/unit/html.rb index 00726351..451d9383 100644 --- a/test/unit/html.rb +++ b/test/unit/html.rb @@ -2,35 +2,41 @@ require 'coderay' class HtmlTest < Test::Unit::TestCase - + + def test_css_class_prefix + tokens = CodeRay.scan 'x = 1', :ruby + html = tokens.div(css_class_prefix: 'xyz-', css: :class) + assert html.include?('x = 1') + end + def test_break_lines_option snippets = {} - + snippets[:ruby] = {} - + snippets[:ruby][:in] = <<-RUBY ruby_inside = <<-RUBY_INSIDE This is tricky, isn't it? RUBY_INSIDE RUBY - + snippets[:ruby][:expected_with_option_off] = <<-HTML_OPT_INDEPENDENT_LINES_OFF ruby_inside = <<-RUBY_INSIDE This is tricky, isn't it? RUBY_INSIDE HTML_OPT_INDEPENDENT_LINES_OFF - + snippets[:ruby][:expected_with_option_on] = <<-HTML_OPT_INDEPENDENT_LINES_ON ruby_inside = <<-RUBY_INSIDE This is tricky, isn't it? RUBY_INSIDE HTML_OPT_INDEPENDENT_LINES_ON - + snippets[:java] = {} - + snippets[:java][:in] = <<-JAVA import java.lang.*; @@ -51,7 +57,7 @@ def test_break_lines_option } } JAVA - + snippets[:java][:expected_with_option_off] = <<-HTML_OPT_INDEPENDENT_LINES_OFF import java.lang.*; @@ -71,7 +77,7 @@ def test_break_lines_option } } HTML_OPT_INDEPENDENT_LINES_OFF - + snippets[:java][:expected_with_option_on] = <<-HTML_OPT_INDEPENDENT_LINES_ON import java.lang.*; @@ -91,10 +97,10 @@ def test_break_lines_option } } HTML_OPT_INDEPENDENT_LINES_ON - + for lang, code in snippets tokens = CodeRay.scan code[:in], lang - + assert_equal code[:expected_with_option_off], tokens.html assert_equal code[:expected_with_option_off], tokens.html(:break_lines => false) assert_equal code[:expected_with_option_on], tokens.html(:break_lines => true)