annotate vendor/gems/coderay-1.0.0/lib/coderay/encoders/html.rb @ 1169:492ff72268e3 bug_521

Close obsolete branch bug_521
author Chris Cannam
date Thu, 18 Oct 2012 10:42:48 +0100
parents cbb26bc654de
children
rev   line source
Chris@909 1 require 'set'
Chris@909 2
Chris@909 3 module CodeRay
Chris@909 4 module Encoders
Chris@909 5
Chris@909 6 # = HTML Encoder
Chris@909 7 #
Chris@909 8 # This is CodeRay's most important highlighter:
Chris@909 9 # It provides save, fast XHTML generation and CSS support.
Chris@909 10 #
Chris@909 11 # == Usage
Chris@909 12 #
Chris@909 13 # require 'coderay'
Chris@909 14 # puts CodeRay.scan('Some /code/', :ruby).html #-> a HTML page
Chris@909 15 # puts CodeRay.scan('Some /code/', :ruby).html(:wrap => :span)
Chris@909 16 # #-> <span class="CodeRay"><span class="co">Some</span> /code/</span>
Chris@909 17 # puts CodeRay.scan('Some /code/', :ruby).span #-> the same
Chris@909 18 #
Chris@909 19 # puts CodeRay.scan('Some code', :ruby).html(
Chris@909 20 # :wrap => nil,
Chris@909 21 # :line_numbers => :inline,
Chris@909 22 # :css => :style
Chris@909 23 # )
Chris@909 24 #
Chris@909 25 # == Options
Chris@909 26 #
Chris@909 27 # === :tab_width
Chris@909 28 # Convert \t characters to +n+ spaces (a number.)
Chris@909 29 #
Chris@909 30 # Default: 8
Chris@909 31 #
Chris@909 32 # === :css
Chris@909 33 # How to include the styles; can be :class or :style.
Chris@909 34 #
Chris@909 35 # Default: :class
Chris@909 36 #
Chris@909 37 # === :wrap
Chris@909 38 # Wrap in :page, :div, :span or nil.
Chris@909 39 #
Chris@909 40 # You can also use Encoders::Div and Encoders::Span.
Chris@909 41 #
Chris@909 42 # Default: nil
Chris@909 43 #
Chris@909 44 # === :title
Chris@909 45 #
Chris@909 46 # The title of the HTML page (works only when :wrap is set to :page.)
Chris@909 47 #
Chris@909 48 # Default: 'CodeRay output'
Chris@909 49 #
Chris@909 50 # === :line_numbers
Chris@909 51 # Include line numbers in :table, :inline, or nil (no line numbers)
Chris@909 52 #
Chris@909 53 # Default: nil
Chris@909 54 #
Chris@909 55 # === :line_number_anchors
Chris@909 56 # Adds anchors and links to the line numbers. Can be false (off), true (on),
Chris@909 57 # or a prefix string that will be prepended to the anchor name.
Chris@909 58 #
Chris@909 59 # The prefix must consist only of letters, digits, and underscores.
Chris@909 60 #
Chris@909 61 # Default: true, default prefix name: "line"
Chris@909 62 #
Chris@909 63 # === :line_number_start
Chris@909 64 # Where to start with line number counting.
Chris@909 65 #
Chris@909 66 # Default: 1
Chris@909 67 #
Chris@909 68 # === :bold_every
Chris@909 69 # Make every +n+-th number appear bold.
Chris@909 70 #
Chris@909 71 # Default: 10
Chris@909 72 #
Chris@909 73 # === :highlight_lines
Chris@909 74 #
Chris@909 75 # Highlights certain line numbers.
Chris@909 76 # Can be any Enumerable, typically just an Array or Range, of numbers.
Chris@909 77 #
Chris@909 78 # Bolding is deactivated when :highlight_lines is set. It only makes sense
Chris@909 79 # in combination with :line_numbers.
Chris@909 80 #
Chris@909 81 # Default: nil
Chris@909 82 #
Chris@909 83 # === :hint
Chris@909 84 # Include some information into the output using the title attribute.
Chris@909 85 # Can be :info (show token kind on mouse-over), :info_long (with full path)
Chris@909 86 # or :debug (via inspect).
Chris@909 87 #
Chris@909 88 # Default: false
Chris@909 89 class HTML < Encoder
Chris@909 90
Chris@909 91 register_for :html
Chris@909 92
Chris@909 93 FILE_EXTENSION = 'snippet.html'
Chris@909 94
Chris@909 95 DEFAULT_OPTIONS = {
Chris@909 96 :tab_width => 8,
Chris@909 97
Chris@909 98 :css => :class,
Chris@909 99 :style => :alpha,
Chris@909 100 :wrap => nil,
Chris@909 101 :title => 'CodeRay output',
Chris@909 102
Chris@909 103 :line_numbers => nil,
Chris@909 104 :line_number_anchors => 'n',
Chris@909 105 :line_number_start => 1,
Chris@909 106 :bold_every => 10,
Chris@909 107 :highlight_lines => nil,
Chris@909 108
Chris@909 109 :hint => false,
Chris@909 110 }
Chris@909 111
Chris@909 112 autoload :Output, 'coderay/encoders/html/output'
Chris@909 113 autoload :CSS, 'coderay/encoders/html/css'
Chris@909 114 autoload :Numbering, 'coderay/encoders/html/numbering'
Chris@909 115
Chris@909 116 attr_reader :css
Chris@909 117
Chris@909 118 protected
Chris@909 119
Chris@909 120 HTML_ESCAPE = { #:nodoc:
Chris@909 121 '&' => '&amp;',
Chris@909 122 '"' => '&quot;',
Chris@909 123 '>' => '&gt;',
Chris@909 124 '<' => '&lt;',
Chris@909 125 }
Chris@909 126
Chris@909 127 # This was to prevent illegal HTML.
Chris@909 128 # Strange chars should still be avoided in codes.
Chris@909 129 evil_chars = Array(0x00...0x20) - [?\n, ?\t, ?\s]
Chris@909 130 evil_chars.each { |i| HTML_ESCAPE[i.chr] = ' ' }
Chris@909 131 #ansi_chars = Array(0x7f..0xff)
Chris@909 132 #ansi_chars.each { |i| HTML_ESCAPE[i.chr] = '&#%d;' % i }
Chris@909 133 # \x9 (\t) and \xA (\n) not included
Chris@909 134 #HTML_ESCAPE_PATTERN = /[\t&"><\0-\x8\xB-\x1f\x7f-\xff]/
Chris@909 135 HTML_ESCAPE_PATTERN = /[\t"&><\0-\x8\xB-\x1f]/
Chris@909 136
Chris@909 137 TOKEN_KIND_TO_INFO = Hash.new do |h, kind|
Chris@909 138 h[kind] = kind.to_s.gsub(/_/, ' ').gsub(/\b\w/) { $&.capitalize }
Chris@909 139 end
Chris@909 140
Chris@909 141 TRANSPARENT_TOKEN_KINDS = Set[
Chris@909 142 :delimiter, :modifier, :content, :escape, :inline_delimiter,
Chris@909 143 ]
Chris@909 144
Chris@909 145 # Generate a hint about the given +kinds+ in a +hint+ style.
Chris@909 146 #
Chris@909 147 # +hint+ may be :info, :info_long or :debug.
Chris@909 148 def self.token_path_to_hint hint, kinds
Chris@909 149 kinds = Array kinds
Chris@909 150 title =
Chris@909 151 case hint
Chris@909 152 when :info
Chris@909 153 kinds = kinds[1..-1] if TRANSPARENT_TOKEN_KINDS.include? kinds.first
Chris@909 154 TOKEN_KIND_TO_INFO[kinds.first]
Chris@909 155 when :info_long
Chris@909 156 kinds.reverse.map { |kind| TOKEN_KIND_TO_INFO[kind] }.join('/')
Chris@909 157 when :debug
Chris@909 158 kinds.inspect
Chris@909 159 end
Chris@909 160 title ? " title=\"#{title}\"" : ''
Chris@909 161 end
Chris@909 162
Chris@909 163 def setup options
Chris@909 164 super
Chris@909 165
Chris@909 166 if options[:wrap] || options[:line_numbers]
Chris@909 167 @real_out = @out
Chris@909 168 @out = ''
Chris@909 169 end
Chris@909 170
Chris@909 171 @HTML_ESCAPE = HTML_ESCAPE.dup
Chris@909 172 @HTML_ESCAPE["\t"] = ' ' * options[:tab_width]
Chris@909 173
Chris@909 174 @opened = []
Chris@909 175 @last_opened = nil
Chris@909 176 @css = CSS.new options[:style]
Chris@909 177
Chris@909 178 hint = options[:hint]
Chris@909 179 if hint && ![:debug, :info, :info_long].include?(hint)
Chris@909 180 raise ArgumentError, "Unknown value %p for :hint; \
Chris@909 181 expected :info, :info_long, :debug, false, or nil." % hint
Chris@909 182 end
Chris@909 183
Chris@909 184 css_classes = TokenKinds
Chris@909 185 case options[:css]
Chris@909 186 when :class
Chris@909 187 @span_for_kind = Hash.new do |h, k|
Chris@909 188 if k.is_a? ::Symbol
Chris@909 189 kind = k_dup = k
Chris@909 190 else
Chris@909 191 kind = k.first
Chris@909 192 k_dup = k.dup
Chris@909 193 end
Chris@909 194 if kind != :space && (hint || css_class = css_classes[kind])
Chris@909 195 title = HTML.token_path_to_hint hint, k if hint
Chris@909 196 css_class ||= css_classes[kind]
Chris@909 197 h[k_dup] = "<span#{title}#{" class=\"#{css_class}\"" if css_class}>"
Chris@909 198 else
Chris@909 199 h[k_dup] = nil
Chris@909 200 end
Chris@909 201 end
Chris@909 202 when :style
Chris@909 203 @span_for_kind = Hash.new do |h, k|
Chris@909 204 kind = k.is_a?(Symbol) ? k : k.first
Chris@909 205 h[k.is_a?(Symbol) ? k : k.dup] =
Chris@909 206 if kind != :space && (hint || css_classes[kind])
Chris@909 207 title = HTML.token_path_to_hint hint, k if hint
Chris@909 208 style = @css.get_style Array(k).map { |c| css_classes[c] }
Chris@909 209 "<span#{title}#{" style=\"#{style}\"" if style}>"
Chris@909 210 end
Chris@909 211 end
Chris@909 212 else
Chris@909 213 raise ArgumentError, "Unknown value %p for :css." % options[:css]
Chris@909 214 end
Chris@909 215
Chris@909 216 @set_last_opened = options[:hint] || options[:css] == :style
Chris@909 217 end
Chris@909 218
Chris@909 219 def finish options
Chris@909 220 unless @opened.empty?
Chris@909 221 warn '%d tokens still open: %p' % [@opened.size, @opened] if $CODERAY_DEBUG
Chris@909 222 @out << '</span>' while @opened.pop
Chris@909 223 @last_opened = nil
Chris@909 224 end
Chris@909 225
Chris@909 226 @out.extend Output
Chris@909 227 @out.css = @css
Chris@909 228 if options[:line_numbers]
Chris@909 229 Numbering.number! @out, options[:line_numbers], options
Chris@909 230 end
Chris@909 231 @out.wrap! options[:wrap]
Chris@909 232 @out.apply_title! options[:title]
Chris@909 233
Chris@909 234 if defined?(@real_out) && @real_out
Chris@909 235 @real_out << @out
Chris@909 236 @out = @real_out
Chris@909 237 end
Chris@909 238
Chris@909 239 super
Chris@909 240 end
Chris@909 241
Chris@909 242 public
Chris@909 243
Chris@909 244 def text_token text, kind
Chris@909 245 if text =~ /#{HTML_ESCAPE_PATTERN}/o
Chris@909 246 text = text.gsub(/#{HTML_ESCAPE_PATTERN}/o) { |m| @HTML_ESCAPE[m] }
Chris@909 247 end
Chris@909 248 if style = @span_for_kind[@last_opened ? [kind, *@opened] : kind]
Chris@909 249 @out << style << text << '</span>'
Chris@909 250 else
Chris@909 251 @out << text
Chris@909 252 end
Chris@909 253 end
Chris@909 254
Chris@909 255 # token groups, eg. strings
Chris@909 256 def begin_group kind
Chris@909 257 @out << (@span_for_kind[@last_opened ? [kind, *@opened] : kind] || '<span>')
Chris@909 258 @opened << kind
Chris@909 259 @last_opened = kind if @set_last_opened
Chris@909 260 end
Chris@909 261
Chris@909 262 def end_group kind
Chris@909 263 if $CODERAY_DEBUG && (@opened.empty? || @opened.last != kind)
Chris@909 264 warn 'Malformed token stream: Trying to close a token (%p) ' \
Chris@909 265 'that is not open. Open are: %p.' % [kind, @opened[1..-1]]
Chris@909 266 end
Chris@909 267 if @opened.pop
Chris@909 268 @out << '</span>'
Chris@909 269 @last_opened = @opened.last if @last_opened
Chris@909 270 end
Chris@909 271 end
Chris@909 272
Chris@909 273 # whole lines to be highlighted, eg. a deleted line in a diff
Chris@909 274 def begin_line kind
Chris@909 275 if style = @span_for_kind[@last_opened ? [kind, *@opened] : kind]
Chris@909 276 if style['class="']
Chris@909 277 @out << style.sub('class="', 'class="line ')
Chris@909 278 else
Chris@909 279 @out << style.sub('>', ' class="line">')
Chris@909 280 end
Chris@909 281 else
Chris@909 282 @out << '<span class="line">'
Chris@909 283 end
Chris@909 284 @opened << kind
Chris@909 285 @last_opened = kind if @options[:css] == :style
Chris@909 286 end
Chris@909 287
Chris@909 288 def end_line kind
Chris@909 289 if $CODERAY_DEBUG && (@opened.empty? || @opened.last != kind)
Chris@909 290 warn 'Malformed token stream: Trying to close a line (%p) ' \
Chris@909 291 'that is not open. Open are: %p.' % [kind, @opened[1..-1]]
Chris@909 292 end
Chris@909 293 if @opened.pop
Chris@909 294 @out << '</span>'
Chris@909 295 @last_opened = @opened.last if @last_opened
Chris@909 296 end
Chris@909 297 end
Chris@909 298
Chris@909 299 end
Chris@909 300
Chris@909 301 end
Chris@909 302 end