annotate vendor/gems/coderay-0.9.7/lib/coderay/encoders/html.rb @ 855:7294e8db2515 bug_162

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