This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
module CodeRay
|
||||
module Encoders
|
||||
|
||||
map \
|
||||
:loc => :lines_of_code,
|
||||
:plain => :text,
|
||||
:plaintext => :text,
|
||||
:remove_comments => :comment_filter,
|
||||
:stats => :statistic,
|
||||
:term => :terminal,
|
||||
:tty => :terminal,
|
||||
:yml => :yaml
|
||||
|
||||
# No default because Tokens#nonsense should raise NoMethodError.
|
||||
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,25 @@
|
||||
module CodeRay
|
||||
module Encoders
|
||||
|
||||
load :token_kind_filter
|
||||
|
||||
# A simple Filter that removes all tokens of the :comment kind.
|
||||
#
|
||||
# Alias: +remove_comments+
|
||||
#
|
||||
# Usage:
|
||||
# CodeRay.scan('print # foo', :ruby).comment_filter.text
|
||||
# #-> "print "
|
||||
#
|
||||
# See also: TokenKindFilter, LinesOfCode
|
||||
class CommentFilter < TokenKindFilter
|
||||
|
||||
register_for :comment_filter
|
||||
|
||||
DEFAULT_OPTIONS = superclass::DEFAULT_OPTIONS.merge \
|
||||
:exclude => [:comment, :docstring]
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,39 @@
|
||||
module CodeRay
|
||||
module Encoders
|
||||
|
||||
# Returns the number of tokens.
|
||||
#
|
||||
# Text and block tokens are counted.
|
||||
class Count < Encoder
|
||||
|
||||
register_for :count
|
||||
|
||||
protected
|
||||
|
||||
def setup options
|
||||
super
|
||||
|
||||
@count = 0
|
||||
end
|
||||
|
||||
def finish options
|
||||
output @count
|
||||
end
|
||||
|
||||
public
|
||||
|
||||
def text_token text, kind
|
||||
@count += 1
|
||||
end
|
||||
|
||||
def begin_group kind
|
||||
@count += 1
|
||||
end
|
||||
alias end_group begin_group
|
||||
alias begin_line begin_group
|
||||
alias end_line begin_group
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,49 @@
|
||||
module CodeRay
|
||||
module Encoders
|
||||
|
||||
# = Debug Encoder
|
||||
#
|
||||
# Fast encoder producing simple debug output.
|
||||
#
|
||||
# It is readable and diff-able and is used for testing.
|
||||
#
|
||||
# You cannot fully restore the tokens information from the
|
||||
# output, because consecutive :space tokens are merged.
|
||||
#
|
||||
# See also: Scanners::Debug
|
||||
class Debug < Encoder
|
||||
|
||||
register_for :debug
|
||||
|
||||
FILE_EXTENSION = 'raydebug'
|
||||
|
||||
def text_token text, kind
|
||||
if kind == :space
|
||||
@out << text
|
||||
else
|
||||
text = text.gsub('\\', '\\\\\\\\') if text.index('\\')
|
||||
text = text.gsub(')', '\\\\)') if text.index(')')
|
||||
@out << "#{kind}(#{text})"
|
||||
end
|
||||
end
|
||||
|
||||
def begin_group kind
|
||||
@out << "#{kind}<"
|
||||
end
|
||||
|
||||
def end_group kind
|
||||
@out << '>'
|
||||
end
|
||||
|
||||
def begin_line kind
|
||||
@out << "#{kind}["
|
||||
end
|
||||
|
||||
def end_line kind
|
||||
@out << ']'
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,63 @@
|
||||
module CodeRay
|
||||
module Encoders
|
||||
|
||||
load :lint
|
||||
|
||||
# = Debug Lint Encoder
|
||||
#
|
||||
# Debug encoder with additional checks for:
|
||||
#
|
||||
# - empty tokens
|
||||
# - incorrect nesting
|
||||
#
|
||||
# It will raise an InvalidTokenStream exception when any of the above occurs.
|
||||
#
|
||||
# See also: Encoders::Debug
|
||||
class DebugLint < Debug
|
||||
|
||||
register_for :debug_lint
|
||||
|
||||
def text_token text, kind
|
||||
raise Lint::EmptyToken, 'empty token for %p' % [kind] if text.empty?
|
||||
raise Lint::UnknownTokenKind, 'unknown token kind %p (text was %p)' % [kind, text] unless TokenKinds.has_key? kind
|
||||
super
|
||||
end
|
||||
|
||||
def begin_group kind
|
||||
@opened << kind
|
||||
super
|
||||
end
|
||||
|
||||
def end_group kind
|
||||
raise Lint::IncorrectTokenGroupNesting, 'We are inside %s, not %p (end_group)' % [@opened.reverse.map(&:inspect).join(' < '), kind] if @opened.last != kind
|
||||
@opened.pop
|
||||
super
|
||||
end
|
||||
|
||||
def begin_line kind
|
||||
@opened << kind
|
||||
super
|
||||
end
|
||||
|
||||
def end_line kind
|
||||
raise Lint::IncorrectTokenGroupNesting, 'We are inside %s, not %p (end_line)' % [@opened.reverse.map(&:inspect).join(' < '), kind] if @opened.last != kind
|
||||
@opened.pop
|
||||
super
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def setup options
|
||||
super
|
||||
@opened = []
|
||||
end
|
||||
|
||||
def finish options
|
||||
raise 'Some tokens still open at end of token stream: %p' % [@opened] unless @opened.empty?
|
||||
super
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,23 @@
|
||||
module CodeRay
|
||||
module Encoders
|
||||
|
||||
load :html
|
||||
|
||||
# Wraps HTML output into a DIV element, using inline styles by default.
|
||||
#
|
||||
# See Encoders::HTML for available options.
|
||||
class Div < HTML
|
||||
|
||||
FILE_EXTENSION = 'div.html'
|
||||
|
||||
register_for :div
|
||||
|
||||
DEFAULT_OPTIONS = HTML::DEFAULT_OPTIONS.merge \
|
||||
:css => :style,
|
||||
:wrap => :div,
|
||||
:line_numbers => false
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,190 @@
|
||||
module CodeRay
|
||||
module Encoders
|
||||
|
||||
# = Encoder
|
||||
#
|
||||
# The Encoder base class. Together with Scanner and
|
||||
# Tokens, it forms the highlighting triad.
|
||||
#
|
||||
# Encoder instances take a Tokens object and do something with it.
|
||||
#
|
||||
# The most common Encoder is surely the HTML encoder
|
||||
# (CodeRay::Encoders::HTML). It highlights the code in a colorful
|
||||
# html page.
|
||||
# If you want the highlighted code in a div or a span instead,
|
||||
# use its subclasses Div and Span.
|
||||
class Encoder
|
||||
extend Plugin
|
||||
plugin_host Encoders
|
||||
|
||||
class << self
|
||||
|
||||
# If FILE_EXTENSION isn't defined, this method returns the
|
||||
# downcase class name instead.
|
||||
def const_missing sym
|
||||
if sym == :FILE_EXTENSION
|
||||
(defined?(@plugin_id) && @plugin_id || name[/\w+$/].downcase).to_s
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
# The default file extension for output file of this encoder class.
|
||||
def file_extension
|
||||
self::FILE_EXTENSION
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# Subclasses are to store their default options in this constant.
|
||||
DEFAULT_OPTIONS = { }
|
||||
|
||||
# The options you gave the Encoder at creating.
|
||||
attr_accessor :options, :scanner
|
||||
|
||||
# Creates a new Encoder.
|
||||
# +options+ is saved and used for all encode operations, as long
|
||||
# as you don't overwrite it there by passing additional options.
|
||||
#
|
||||
# Encoder objects provide three encode methods:
|
||||
# - encode simply takes a +code+ string and a +lang+
|
||||
# - encode_tokens expects a +tokens+ object instead
|
||||
#
|
||||
# Each method has an optional +options+ parameter. These are
|
||||
# added to the options you passed at creation.
|
||||
def initialize options = {}
|
||||
@options = self.class::DEFAULT_OPTIONS.merge options
|
||||
@@CODERAY_TOKEN_INTERFACE_DEPRECATION_WARNING_GIVEN = false
|
||||
end
|
||||
|
||||
# Encode a Tokens object.
|
||||
def encode_tokens tokens, options = {}
|
||||
options = @options.merge options
|
||||
@scanner = tokens.scanner if tokens.respond_to? :scanner
|
||||
setup options
|
||||
compile tokens, options
|
||||
finish options
|
||||
end
|
||||
|
||||
# Encode the given +code+ using the Scanner for +lang+.
|
||||
def encode code, lang, options = {}
|
||||
options = @options.merge options
|
||||
@scanner = Scanners[lang].new code, CodeRay.get_scanner_options(options).update(:tokens => self)
|
||||
setup options
|
||||
@scanner.tokenize
|
||||
finish options
|
||||
end
|
||||
|
||||
# You can use highlight instead of encode, if that seems
|
||||
# more clear to you.
|
||||
alias highlight encode
|
||||
|
||||
# The default file extension for this encoder.
|
||||
def file_extension
|
||||
self.class.file_extension
|
||||
end
|
||||
|
||||
def << token
|
||||
unless @@CODERAY_TOKEN_INTERFACE_DEPRECATION_WARNING_GIVEN
|
||||
warn 'Using old Tokens#<< interface.'
|
||||
@@CODERAY_TOKEN_INTERFACE_DEPRECATION_WARNING_GIVEN = true
|
||||
end
|
||||
self.token(*token)
|
||||
end
|
||||
|
||||
# Called with +content+ and +kind+ of the currently scanned token.
|
||||
# For simple scanners, it's enougth to implement this method.
|
||||
#
|
||||
# By default, it calls text_token, begin_group, end_group, begin_line,
|
||||
# or end_line, depending on the +content+.
|
||||
def token content, kind
|
||||
case content
|
||||
when String
|
||||
text_token content, kind
|
||||
when :begin_group
|
||||
begin_group kind
|
||||
when :end_group
|
||||
end_group kind
|
||||
when :begin_line
|
||||
begin_line kind
|
||||
when :end_line
|
||||
end_line kind
|
||||
else
|
||||
raise ArgumentError, 'Unknown token content type: %p, kind = %p' % [content, kind]
|
||||
end
|
||||
end
|
||||
|
||||
# Called for each text token ([text, kind]), where text is a String.
|
||||
def text_token text, kind
|
||||
@out << text
|
||||
end
|
||||
|
||||
# Starts a token group with the given +kind+.
|
||||
def begin_group kind
|
||||
end
|
||||
|
||||
# Ends a token group with the given +kind+.
|
||||
def end_group kind
|
||||
end
|
||||
|
||||
# Starts a new line token group with the given +kind+.
|
||||
def begin_line kind
|
||||
end
|
||||
|
||||
# Ends a new line token group with the given +kind+.
|
||||
def end_line kind
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# Called with merged options before encoding starts.
|
||||
# Sets @out to an empty string.
|
||||
#
|
||||
# See the HTML Encoder for an example of option caching.
|
||||
def setup options
|
||||
@out = get_output(options)
|
||||
end
|
||||
|
||||
def get_output options
|
||||
options[:out] || ''.dup
|
||||
end
|
||||
|
||||
# Append data.to_s to the output. Returns the argument.
|
||||
def output data
|
||||
@out << data.to_s
|
||||
data
|
||||
end
|
||||
|
||||
# Called with merged options after encoding starts.
|
||||
# The return value is the result of encoding, typically @out.
|
||||
def finish options
|
||||
@out
|
||||
end
|
||||
|
||||
# Do the encoding.
|
||||
#
|
||||
# The already created +tokens+ object must be used; it must be a
|
||||
# Tokens object.
|
||||
def compile tokens, options = {}
|
||||
content = nil
|
||||
for item in tokens
|
||||
if item.is_a? Array
|
||||
raise ArgumentError, 'Two-element array tokens are no longer supported.'
|
||||
end
|
||||
if content
|
||||
token content, item
|
||||
content = nil
|
||||
else
|
||||
content = item
|
||||
end
|
||||
end
|
||||
raise 'odd number list for Tokens' if content
|
||||
end
|
||||
|
||||
alias tokens compile
|
||||
public :tokens
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,58 @@
|
||||
module CodeRay
|
||||
module Encoders
|
||||
|
||||
# A Filter encoder has another Tokens instance as output.
|
||||
# It can be subclass to select, remove, or modify tokens in the stream.
|
||||
#
|
||||
# Subclasses of Filter are called "Filters" and can be chained.
|
||||
#
|
||||
# == Options
|
||||
#
|
||||
# === :tokens
|
||||
#
|
||||
# The Tokens object which will receive the output.
|
||||
#
|
||||
# Default: Tokens.new
|
||||
#
|
||||
# See also: TokenKindFilter
|
||||
class Filter < Encoder
|
||||
|
||||
register_for :filter
|
||||
|
||||
protected
|
||||
def setup options
|
||||
super
|
||||
|
||||
@tokens = options[:tokens] || Tokens.new
|
||||
end
|
||||
|
||||
def finish options
|
||||
output @tokens
|
||||
end
|
||||
|
||||
public
|
||||
|
||||
def text_token text, kind # :nodoc:
|
||||
@tokens.text_token text, kind
|
||||
end
|
||||
|
||||
def begin_group kind # :nodoc:
|
||||
@tokens.begin_group kind
|
||||
end
|
||||
|
||||
def begin_line kind # :nodoc:
|
||||
@tokens.begin_line kind
|
||||
end
|
||||
|
||||
def end_group kind # :nodoc:
|
||||
@tokens.end_group kind
|
||||
end
|
||||
|
||||
def end_line kind # :nodoc:
|
||||
@tokens.end_line kind
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,333 @@
|
||||
require 'set'
|
||||
|
||||
module CodeRay
|
||||
module Encoders
|
||||
|
||||
# = HTML Encoder
|
||||
#
|
||||
# This is CodeRay's most important highlighter:
|
||||
# It provides save, fast XHTML generation and CSS support.
|
||||
#
|
||||
# == Usage
|
||||
#
|
||||
# require 'coderay'
|
||||
# puts CodeRay.scan('Some /code/', :ruby).html #-> a HTML page
|
||||
# puts CodeRay.scan('Some /code/', :ruby).html(:wrap => :span)
|
||||
# #-> <span class="CodeRay"><span class="co">Some</span> /code/</span>
|
||||
# puts CodeRay.scan('Some /code/', :ruby).span #-> the same
|
||||
#
|
||||
# puts CodeRay.scan('Some code', :ruby).html(
|
||||
# :wrap => nil,
|
||||
# :line_numbers => :inline,
|
||||
# :css => :style
|
||||
# )
|
||||
#
|
||||
# == Options
|
||||
#
|
||||
# === :tab_width
|
||||
# Convert \t characters to +n+ spaces (a number or false.)
|
||||
# false will keep tab characters untouched.
|
||||
#
|
||||
# Default: 8
|
||||
#
|
||||
# === :css
|
||||
# How to include the styles; can be :class or :style.
|
||||
#
|
||||
# Default: :class
|
||||
#
|
||||
# === :wrap
|
||||
# Wrap in :page, :div, :span or nil.
|
||||
#
|
||||
# You can also use Encoders::Div and Encoders::Span.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# Default: false
|
||||
#
|
||||
# === :line_numbers
|
||||
# Include line numbers in :table, :inline, or nil (no line numbers)
|
||||
#
|
||||
# Default: nil
|
||||
#
|
||||
# === :line_number_anchors
|
||||
# Adds anchors and links to the line numbers. Can be false (off), true (on),
|
||||
# or a prefix string that will be prepended to the anchor name.
|
||||
#
|
||||
# The prefix must consist only of letters, digits, and underscores.
|
||||
#
|
||||
# Default: true, default prefix name: "line"
|
||||
#
|
||||
# === :line_number_start
|
||||
# Where to start with line number counting.
|
||||
#
|
||||
# Default: 1
|
||||
#
|
||||
# === :bold_every
|
||||
# Make every +n+-th number appear bold.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# Default: nil
|
||||
#
|
||||
# === :hint
|
||||
# Include some information into the output using the title attribute.
|
||||
# Can be :info (show token kind on mouse-over), :info_long (with full path)
|
||||
# or :debug (via inspect).
|
||||
#
|
||||
# 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
|
||||
{
|
||||
'&' => '&',
|
||||
'"' => '"',
|
||||
'>' => '>',
|
||||
'<' => '<',
|
||||
# "\t" => will be set to ' ' * options[:tab_width] during setup
|
||||
}.tap do |hash|
|
||||
# Escape ASCII control codes except \x9 == \t and \xA == \n.
|
||||
(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.
|
||||
def self.token_path_to_hint hint, kinds
|
||||
kinds = Array kinds
|
||||
title =
|
||||
case hint
|
||||
when :info
|
||||
kinds = kinds[1..-1] if TRANSPARENT_TOKEN_KINDS.include? kinds.first
|
||||
TOKEN_KIND_TO_INFO[kinds.first]
|
||||
when :info_long
|
||||
kinds.reverse.map { |kind| TOKEN_KIND_TO_INFO[kind] }.join('/')
|
||||
when :debug
|
||||
kinds.inspect
|
||||
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 << '</span>' while @opened.pop
|
||||
@last_opened = nil
|
||||
end
|
||||
|
||||
if @out.respond_to? :to_str
|
||||
@out.extend Output
|
||||
@out.css = @css
|
||||
if options[:line_numbers]
|
||||
Numbering.number! @out, options[:line_numbers], options
|
||||
end
|
||||
@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 << '</span>'
|
||||
else
|
||||
@out << text
|
||||
end
|
||||
end
|
||||
|
||||
# token groups, eg. strings
|
||||
def begin_group kind
|
||||
@out << (@span_for_kinds[@last_opened ? [kind, *@opened] : kind] || '<span>')
|
||||
@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]
|
||||
if style['class="']
|
||||
@out << style.sub('class="', 'class="line ')
|
||||
else
|
||||
@out << style.sub('>', ' class="line">')
|
||||
end
|
||||
else
|
||||
@out << '<span class="line">'
|
||||
end
|
||||
@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
|
||||
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)
|
||||
"<span#{title}#{" style=\"#{style}\"" if style}>"
|
||||
else
|
||||
"<span#{title}#{" class=\"#{css_class}\"" if css_class}>"
|
||||
end
|
||||
end
|
||||
end.tap do |span|
|
||||
h.clear if h.size >= 100
|
||||
h[kinds] = span
|
||||
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|
|
||||
reopen << (@span_for_kinds[index > 0 ? [kind, *@opened[0...index]] : kind] || '<span>')
|
||||
end
|
||||
text.gsub("\n", "#{'</span>' * @opened.size}#{'</span>' if style}\n#{reopen}#{style}")
|
||||
end
|
||||
|
||||
def close_span
|
||||
if @opened.pop
|
||||
@out << '</span>'
|
||||
@last_opened = @opened.last if @last_opened
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,65 @@
|
||||
module CodeRay
|
||||
module Encoders
|
||||
|
||||
class HTML
|
||||
class CSS # :nodoc:
|
||||
|
||||
attr :stylesheet
|
||||
|
||||
def CSS.load_stylesheet style = nil
|
||||
CodeRay::Styles[style]
|
||||
end
|
||||
|
||||
def initialize style = :default
|
||||
@styles = Hash.new
|
||||
style = CSS.load_stylesheet style
|
||||
@stylesheet = [
|
||||
style::CSS_MAIN_STYLES,
|
||||
style::TOKEN_COLORS.gsub(/^(?!$)/, '.CodeRay ')
|
||||
].join("\n")
|
||||
parse style::TOKEN_COLORS
|
||||
end
|
||||
|
||||
def get_style_for_css_classes css_classes
|
||||
cl = @styles[css_classes.first]
|
||||
return '' unless cl
|
||||
style = ''
|
||||
1.upto css_classes.size do |offset|
|
||||
break if style = cl[css_classes[offset .. -1]]
|
||||
end
|
||||
# warn 'Style not found: %p' % [styles] if style.empty?
|
||||
return style
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
CSS_CLASS_PATTERN = /
|
||||
( # $1 = selectors
|
||||
(?:
|
||||
(?: \s* \. [-\w]+ )+
|
||||
\s* ,?
|
||||
)+
|
||||
)
|
||||
\s* \{ \s*
|
||||
( [^\}]+ )? # $2 = style
|
||||
\s* \} \s*
|
||||
|
|
||||
( [^\n]+ ) # $3 = error
|
||||
/mx
|
||||
def parse stylesheet
|
||||
stylesheet.scan CSS_CLASS_PATTERN do |selectors, style, error|
|
||||
raise "CSS parse error: '#{error.inspect}' not recognized" if error
|
||||
for selector in selectors.split(',')
|
||||
classes = selector.scan(/[-\w]+/)
|
||||
cl = classes.pop
|
||||
@styles[cl] ||= Hash.new
|
||||
@styles[cl][classes] = style.to_s.strip.delete(' ').chomp(';')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,108 @@
|
||||
module CodeRay
|
||||
module Encoders
|
||||
|
||||
class HTML
|
||||
|
||||
module Numbering # :nodoc:
|
||||
|
||||
def self.number! output, mode = :table, options = {}
|
||||
return self unless mode
|
||||
|
||||
options = DEFAULT_OPTIONS.merge options
|
||||
|
||||
start = options[:line_number_start]
|
||||
unless start.is_a? Integer
|
||||
raise ArgumentError, "Invalid value %p for :line_number_start; Integer expected." % start
|
||||
end
|
||||
|
||||
anchor_prefix = options[:line_number_anchors]
|
||||
anchor_prefix = 'line' if anchor_prefix == true
|
||||
anchor_prefix = anchor_prefix.to_s[/[\w-]+/] if anchor_prefix
|
||||
anchoring =
|
||||
if anchor_prefix
|
||||
proc do |line|
|
||||
line = line.to_s
|
||||
anchor = anchor_prefix + line
|
||||
"<a href=\"##{anchor}\" name=\"#{anchor}\">#{line}</a>"
|
||||
end
|
||||
else
|
||||
:to_s.to_proc
|
||||
end
|
||||
|
||||
bold_every = options[:bold_every]
|
||||
highlight_lines = options[:highlight_lines]
|
||||
bolding =
|
||||
if bold_every == false && highlight_lines == nil
|
||||
anchoring
|
||||
elsif highlight_lines.is_a? Enumerable
|
||||
highlight_lines = highlight_lines.to_set
|
||||
proc do |line|
|
||||
if highlight_lines.include? line
|
||||
"<strong class=\"highlighted\">#{anchoring[line]}</strong>" # highlighted line numbers in bold
|
||||
else
|
||||
anchoring[line]
|
||||
end
|
||||
end
|
||||
elsif bold_every.is_a? Integer
|
||||
raise ArgumentError, ":bolding can't be 0." if bold_every == 0
|
||||
proc do |line|
|
||||
if line % bold_every == 0
|
||||
"<strong>#{anchoring[line]}</strong>" # every bold_every-th number in bold
|
||||
else
|
||||
anchoring[line]
|
||||
end
|
||||
end
|
||||
else
|
||||
raise ArgumentError, 'Invalid value %p for :bolding; false or Integer expected.' % bold_every
|
||||
end
|
||||
|
||||
if position_of_last_newline = output.rindex(RUBY_VERSION >= '1.9' ? /\n/ : ?\n)
|
||||
after_last_newline = output[position_of_last_newline + 1 .. -1]
|
||||
ends_with_newline = after_last_newline[/\A(?:<\/span>)*\z/]
|
||||
|
||||
if ends_with_newline
|
||||
line_count = output.count("\n")
|
||||
else
|
||||
line_count = output.count("\n") + 1
|
||||
end
|
||||
else
|
||||
line_count = 1
|
||||
end
|
||||
|
||||
case mode
|
||||
when :inline
|
||||
max_width = (start + line_count).to_s.size
|
||||
line_number = start
|
||||
output.gsub!(/^.*$\n?/) do |line|
|
||||
line_number_text = bolding.call line_number
|
||||
indent = ' ' * (max_width - line_number.to_s.size)
|
||||
line_number += 1
|
||||
"<span class=\"line-numbers\">#{indent}#{line_number_text}</span>#{line}"
|
||||
end
|
||||
|
||||
when :table
|
||||
line_numbers = (start ... start + line_count).map(&bolding).join("\n")
|
||||
line_numbers << "\n"
|
||||
line_numbers_table_template = Output::TABLE.apply('LINE_NUMBERS', line_numbers)
|
||||
|
||||
output.gsub!(/<\/div>\n/, '</div>')
|
||||
output.wrap_in! line_numbers_table_template
|
||||
output.wrapped_in = :div
|
||||
|
||||
when :list
|
||||
raise NotImplementedError, 'The :list option is no longer available. Use :table.'
|
||||
|
||||
else
|
||||
raise ArgumentError, 'Unknown value %p for mode: expected one of %p' %
|
||||
[mode, [:table, :inline]]
|
||||
end
|
||||
|
||||
output
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,164 @@
|
||||
module CodeRay
|
||||
module Encoders
|
||||
|
||||
class HTML
|
||||
|
||||
# This module is included in the output String of the HTML Encoder.
|
||||
#
|
||||
# It provides methods like wrap, div, page etc.
|
||||
#
|
||||
# Remember to use #clone instead of #dup to keep the modules the object was
|
||||
# extended with.
|
||||
#
|
||||
# TODO: Rewrite this without monkey patching.
|
||||
module Output
|
||||
|
||||
attr_accessor :css
|
||||
|
||||
class << self
|
||||
|
||||
# Raises an exception if an object that doesn't respond to to_str is extended by Output,
|
||||
# to prevent users from misuse. Use Module#remove_method to disable.
|
||||
def extended o # :nodoc:
|
||||
warn "The Output module is intended to extend instances of String, not #{o.class}." unless o.respond_to? :to_str
|
||||
end
|
||||
|
||||
def make_stylesheet css, in_tag = false # :nodoc:
|
||||
sheet = css.stylesheet
|
||||
sheet = <<-'CSS' if in_tag
|
||||
<style type="text/css">
|
||||
#{sheet}
|
||||
</style>
|
||||
CSS
|
||||
sheet
|
||||
end
|
||||
|
||||
def page_template_for_css css # :nodoc:
|
||||
sheet = make_stylesheet css
|
||||
PAGE.apply 'CSS', sheet
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def wrapped_in? element
|
||||
wrapped_in == element
|
||||
end
|
||||
|
||||
def wrapped_in
|
||||
@wrapped_in ||= nil
|
||||
end
|
||||
attr_writer :wrapped_in
|
||||
|
||||
def wrap_in! template
|
||||
Template.wrap! self, template, 'CONTENT'
|
||||
self
|
||||
end
|
||||
|
||||
def apply_title! title
|
||||
self.sub!(/(<title>)(<\/title>)/) { $1 + title + $2 }
|
||||
self
|
||||
end
|
||||
|
||||
def wrap! element, *args
|
||||
return self if not element or element == wrapped_in
|
||||
case element
|
||||
when :div
|
||||
raise "Can't wrap %p in %p" % [wrapped_in, element] unless wrapped_in? nil
|
||||
wrap_in! DIV
|
||||
when :span
|
||||
raise "Can't wrap %p in %p" % [wrapped_in, element] unless wrapped_in? nil
|
||||
wrap_in! SPAN
|
||||
when :page
|
||||
wrap! :div if wrapped_in? nil
|
||||
raise "Can't wrap %p in %p" % [wrapped_in, element] unless wrapped_in? :div
|
||||
wrap_in! Output.page_template_for_css(@css)
|
||||
if args.first.is_a?(Hash) && title = args.first[:title]
|
||||
apply_title! title
|
||||
end
|
||||
self
|
||||
else
|
||||
raise "Unknown value %p for :wrap" % element
|
||||
end
|
||||
@wrapped_in = element
|
||||
self
|
||||
end
|
||||
|
||||
def stylesheet in_tag = false
|
||||
Output.make_stylesheet @css, in_tag
|
||||
end
|
||||
|
||||
#-- don't include the templates in docu
|
||||
|
||||
class Template < String # :nodoc:
|
||||
|
||||
def self.wrap! str, template, target
|
||||
target = Regexp.new(Regexp.escape("<%#{target}%>"))
|
||||
if template =~ target
|
||||
str[0,0] = $`
|
||||
str << $'
|
||||
else
|
||||
raise "Template target <%%%p%%> not found" % target
|
||||
end
|
||||
end
|
||||
|
||||
def apply target, replacement
|
||||
target = Regexp.new(Regexp.escape("<%#{target}%>"))
|
||||
if self =~ target
|
||||
Template.new($` + replacement + $')
|
||||
else
|
||||
raise "Template target <%%%p%%> not found" % target
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
SPAN = Template.new '<span class="CodeRay"><%CONTENT%></span>'
|
||||
|
||||
DIV = Template.new <<-DIV
|
||||
<div class="CodeRay">
|
||||
<div class="code"><pre><%CONTENT%></pre></div>
|
||||
</div>
|
||||
DIV
|
||||
|
||||
TABLE = Template.new <<-TABLE
|
||||
<table class="CodeRay"><tr>
|
||||
<td class="line-numbers"><pre><%LINE_NUMBERS%></pre></td>
|
||||
<td class="code"><pre><%CONTENT%></pre></td>
|
||||
</tr></table>
|
||||
TABLE
|
||||
|
||||
PAGE = Template.new <<-PAGE
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<title></title>
|
||||
<style type="text/css">
|
||||
.CodeRay .line-numbers a {
|
||||
text-decoration: inherit;
|
||||
color: inherit;
|
||||
}
|
||||
body {
|
||||
background-color: white;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
<%CSS%>
|
||||
.CodeRay {
|
||||
border: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<%CONTENT%>
|
||||
</body>
|
||||
</html>
|
||||
PAGE
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,83 @@
|
||||
module CodeRay
|
||||
module Encoders
|
||||
|
||||
# A simple JSON Encoder.
|
||||
#
|
||||
# Example:
|
||||
# CodeRay.scan('puts "Hello world!"', :ruby).json
|
||||
# yields
|
||||
# [
|
||||
# {"type"=>"text", "text"=>"puts", "kind"=>"ident"},
|
||||
# {"type"=>"text", "text"=>" ", "kind"=>"space"},
|
||||
# {"type"=>"block", "action"=>"open", "kind"=>"string"},
|
||||
# {"type"=>"text", "text"=>"\"", "kind"=>"delimiter"},
|
||||
# {"type"=>"text", "text"=>"Hello world!", "kind"=>"content"},
|
||||
# {"type"=>"text", "text"=>"\"", "kind"=>"delimiter"},
|
||||
# {"type"=>"block", "action"=>"close", "kind"=>"string"},
|
||||
# ]
|
||||
class JSON < Encoder
|
||||
|
||||
begin
|
||||
require 'json'
|
||||
rescue LoadError
|
||||
begin
|
||||
require 'rubygems' unless defined? Gem
|
||||
gem 'json'
|
||||
require 'json'
|
||||
rescue LoadError
|
||||
$stderr.puts "The JSON encoder needs the JSON library.\n" \
|
||||
"Please gem install json."
|
||||
raise
|
||||
end
|
||||
end
|
||||
|
||||
register_for :json
|
||||
FILE_EXTENSION = 'json'
|
||||
|
||||
protected
|
||||
def setup options
|
||||
super
|
||||
|
||||
@first = true
|
||||
@out << '['
|
||||
end
|
||||
|
||||
def finish options
|
||||
@out << ']'
|
||||
end
|
||||
|
||||
def append data
|
||||
if @first
|
||||
@first = false
|
||||
else
|
||||
@out << ','
|
||||
end
|
||||
|
||||
@out << data.to_json
|
||||
end
|
||||
|
||||
public
|
||||
def text_token text, kind
|
||||
append :type => 'text', :text => text, :kind => kind
|
||||
end
|
||||
|
||||
def begin_group kind
|
||||
append :type => 'block', :action => 'open', :kind => kind
|
||||
end
|
||||
|
||||
def end_group kind
|
||||
append :type => 'block', :action => 'close', :kind => kind
|
||||
end
|
||||
|
||||
def begin_line kind
|
||||
append :type => 'block', :action => 'begin_line', :kind => kind
|
||||
end
|
||||
|
||||
def end_line kind
|
||||
append :type => 'block', :action => 'end_line', :kind => kind
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,45 @@
|
||||
module CodeRay
|
||||
module Encoders
|
||||
|
||||
# Counts the LoC (Lines of Code). Returns an Integer >= 0.
|
||||
#
|
||||
# Alias: +loc+
|
||||
#
|
||||
# Everything that is not comment, markup, doctype/shebang, or an empty line,
|
||||
# is considered to be code.
|
||||
#
|
||||
# For example,
|
||||
# * HTML files not containing JavaScript have 0 LoC
|
||||
# * in a Java class without comments, LoC is the number of non-empty lines
|
||||
#
|
||||
# A Scanner class should define the token kinds that are not code in the
|
||||
# KINDS_NOT_LOC constant, which defaults to [:comment, :doctype].
|
||||
class LinesOfCode < TokenKindFilter
|
||||
|
||||
register_for :lines_of_code
|
||||
|
||||
NON_EMPTY_LINE = /^\s*\S.*$/
|
||||
|
||||
protected
|
||||
|
||||
def setup options
|
||||
if scanner
|
||||
kinds_not_loc = scanner.class::KINDS_NOT_LOC
|
||||
else
|
||||
warn "Tokens have no associated scanner, counting all nonempty lines." if $VERBOSE
|
||||
kinds_not_loc = CodeRay::Scanners::Scanner::KINDS_NOT_LOC
|
||||
end
|
||||
|
||||
options[:exclude] = kinds_not_loc
|
||||
|
||||
super options
|
||||
end
|
||||
|
||||
def finish options
|
||||
output @tokens.text.scan(NON_EMPTY_LINE).size
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,59 @@
|
||||
module CodeRay
|
||||
module Encoders
|
||||
|
||||
# = Lint Encoder
|
||||
#
|
||||
# Checks for:
|
||||
#
|
||||
# - empty tokens
|
||||
# - incorrect nesting
|
||||
#
|
||||
# It will raise an InvalidTokenStream exception when any of the above occurs.
|
||||
#
|
||||
# See also: Encoders::DebugLint
|
||||
class Lint < Debug
|
||||
|
||||
register_for :lint
|
||||
|
||||
InvalidTokenStream = Class.new StandardError
|
||||
EmptyToken = Class.new InvalidTokenStream
|
||||
UnknownTokenKind = Class.new InvalidTokenStream
|
||||
IncorrectTokenGroupNesting = Class.new InvalidTokenStream
|
||||
|
||||
def text_token text, kind
|
||||
raise EmptyToken, 'empty token for %p' % [kind] if text.empty?
|
||||
raise UnknownTokenKind, 'unknown token kind %p (text was %p)' % [kind, text] unless TokenKinds.has_key? kind
|
||||
end
|
||||
|
||||
def begin_group kind
|
||||
@opened << kind
|
||||
end
|
||||
|
||||
def end_group kind
|
||||
raise IncorrectTokenGroupNesting, 'We are inside %s, not %p (end_group)' % [@opened.reverse.map(&:inspect).join(' < '), kind] if @opened.last != kind
|
||||
@opened.pop
|
||||
end
|
||||
|
||||
def begin_line kind
|
||||
@opened << kind
|
||||
end
|
||||
|
||||
def end_line kind
|
||||
raise IncorrectTokenGroupNesting, 'We are inside %s, not %p (end_line)' % [@opened.reverse.map(&:inspect).join(' < '), kind] if @opened.last != kind
|
||||
@opened.pop
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def setup options
|
||||
@opened = []
|
||||
end
|
||||
|
||||
def finish options
|
||||
raise 'Some tokens still open at end of token stream: %p' % [@opened] unless @opened.empty?
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,18 @@
|
||||
module CodeRay
|
||||
module Encoders
|
||||
|
||||
# = Null Encoder
|
||||
#
|
||||
# Does nothing and returns an empty string.
|
||||
class Null < Encoder
|
||||
|
||||
register_for :null
|
||||
|
||||
def text_token text, kind
|
||||
# do nothing
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,24 @@
|
||||
module CodeRay
|
||||
module Encoders
|
||||
|
||||
load :html
|
||||
|
||||
# Wraps the output into a HTML page, using CSS classes and
|
||||
# line numbers in the table format by default.
|
||||
#
|
||||
# See Encoders::HTML for available options.
|
||||
class Page < HTML
|
||||
|
||||
FILE_EXTENSION = 'html'
|
||||
|
||||
register_for :page
|
||||
|
||||
DEFAULT_OPTIONS = HTML::DEFAULT_OPTIONS.merge \
|
||||
:css => :class,
|
||||
:wrap => :page,
|
||||
:line_numbers => :table
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,23 @@
|
||||
module CodeRay
|
||||
module Encoders
|
||||
|
||||
load :html
|
||||
|
||||
# Wraps HTML output into a SPAN element, using inline styles by default.
|
||||
#
|
||||
# See Encoders::HTML for available options.
|
||||
class Span < HTML
|
||||
|
||||
FILE_EXTENSION = 'span.html'
|
||||
|
||||
register_for :span
|
||||
|
||||
DEFAULT_OPTIONS = HTML::DEFAULT_OPTIONS.merge \
|
||||
:css => :style,
|
||||
:wrap => :span,
|
||||
:line_numbers => false
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,95 @@
|
||||
module CodeRay
|
||||
module Encoders
|
||||
|
||||
# Makes a statistic for the given tokens.
|
||||
#
|
||||
# Alias: +stats+
|
||||
class Statistic < Encoder
|
||||
|
||||
register_for :statistic
|
||||
|
||||
attr_reader :type_stats, :real_token_count # :nodoc:
|
||||
|
||||
TypeStats = Struct.new :count, :size # :nodoc:
|
||||
|
||||
protected
|
||||
|
||||
def setup options
|
||||
super
|
||||
|
||||
@type_stats = Hash.new { |h, k| h[k] = TypeStats.new 0, 0 }
|
||||
@real_token_count = 0
|
||||
end
|
||||
|
||||
STATS = <<-STATS # :nodoc:
|
||||
|
||||
Code Statistics
|
||||
|
||||
Tokens %8d
|
||||
Non-Whitespace %8d
|
||||
Bytes Total %8d
|
||||
|
||||
Token Types (%d):
|
||||
type count ratio size (average)
|
||||
-------------------------------------------------------------
|
||||
%s
|
||||
STATS
|
||||
|
||||
TOKEN_TYPES_ROW = <<-TKR # :nodoc:
|
||||
%-20s %8d %6.2f %% %5.1f
|
||||
TKR
|
||||
|
||||
def finish options
|
||||
all = @type_stats['TOTAL']
|
||||
all_count, all_size = all.count, all.size
|
||||
@type_stats.each do |type, stat|
|
||||
stat.size /= stat.count.to_f
|
||||
end
|
||||
types_stats = @type_stats.sort_by { |k, v| [-v.count, k.to_s] }.map do |k, v|
|
||||
TOKEN_TYPES_ROW % [k, v.count, 100.0 * v.count / all_count, v.size]
|
||||
end.join
|
||||
@out << STATS % [
|
||||
all_count, @real_token_count, all_size,
|
||||
@type_stats.delete_if { |k, v| k.is_a? String }.size,
|
||||
types_stats
|
||||
]
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
public
|
||||
|
||||
def text_token text, kind
|
||||
@real_token_count += 1 unless kind == :space
|
||||
@type_stats[kind].count += 1
|
||||
@type_stats[kind].size += text.size
|
||||
@type_stats['TOTAL'].size += text.size
|
||||
@type_stats['TOTAL'].count += 1
|
||||
end
|
||||
|
||||
def begin_group kind
|
||||
block_token ':begin_group', kind
|
||||
end
|
||||
|
||||
def end_group kind
|
||||
block_token ':end_group', kind
|
||||
end
|
||||
|
||||
def begin_line kind
|
||||
block_token ':begin_line', kind
|
||||
end
|
||||
|
||||
def end_line kind
|
||||
block_token ':end_line', kind
|
||||
end
|
||||
|
||||
def block_token action, kind
|
||||
@type_stats['TOTAL'].count += 1
|
||||
@type_stats[action].count += 1
|
||||
@type_stats[kind].count += 1
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,195 @@
|
||||
module CodeRay
|
||||
module Encoders
|
||||
|
||||
# Outputs code highlighted for a color terminal.
|
||||
#
|
||||
# Note: This encoder is in beta. It currently doesn't use the Styles.
|
||||
#
|
||||
# Alias: +term+
|
||||
#
|
||||
# == Authors & License
|
||||
#
|
||||
# By Rob Aldred (http://robaldred.co.uk)
|
||||
#
|
||||
# Based on idea by Nathan Weizenbaum (http://nex-3.com)
|
||||
#
|
||||
# MIT License (http://www.opensource.org/licenses/mit-license.php)
|
||||
class Terminal < Encoder
|
||||
|
||||
register_for :terminal
|
||||
|
||||
TOKEN_COLORS = {
|
||||
:debug => "\e[1;37;44m",
|
||||
|
||||
:annotation => "\e[34m",
|
||||
:attribute_name => "\e[35m",
|
||||
:attribute_value => "\e[31m",
|
||||
:binary => {
|
||||
:self => "\e[31m",
|
||||
:char => "\e[1;31m",
|
||||
:delimiter => "\e[1;31m",
|
||||
},
|
||||
:char => {
|
||||
:self => "\e[35m",
|
||||
:delimiter => "\e[1;35m"
|
||||
},
|
||||
:class => "\e[1;35;4m",
|
||||
:class_variable => "\e[36m",
|
||||
:color => "\e[32m",
|
||||
:comment => {
|
||||
:self => "\e[1;30m",
|
||||
:char => "\e[37m",
|
||||
:delimiter => "\e[37m",
|
||||
},
|
||||
:constant => "\e[1;34;4m",
|
||||
:decorator => "\e[35m",
|
||||
:definition => "\e[1;33m",
|
||||
:directive => "\e[33m",
|
||||
:docstring => "\e[31m",
|
||||
:doctype => "\e[1;34m",
|
||||
:done => "\e[1;30;2m",
|
||||
:entity => "\e[31m",
|
||||
:error => "\e[1;37;41m",
|
||||
:exception => "\e[1;31m",
|
||||
:float => "\e[1;35m",
|
||||
:function => "\e[1;34m",
|
||||
:global_variable => "\e[1;32m",
|
||||
:hex => "\e[1;36m",
|
||||
:id => "\e[1;34m",
|
||||
:include => "\e[31m",
|
||||
:integer => "\e[1;34m",
|
||||
:imaginary => "\e[1;34m",
|
||||
:important => "\e[1;31m",
|
||||
:key => {
|
||||
:self => "\e[35m",
|
||||
:char => "\e[1;35m",
|
||||
:delimiter => "\e[1;35m",
|
||||
},
|
||||
:keyword => "\e[32m",
|
||||
:label => "\e[1;33m",
|
||||
:local_variable => "\e[33m",
|
||||
:namespace => "\e[1;35m",
|
||||
:octal => "\e[1;34m",
|
||||
:predefined => "\e[36m",
|
||||
:predefined_constant => "\e[1;36m",
|
||||
:predefined_type => "\e[1;32m",
|
||||
:preprocessor => "\e[1;36m",
|
||||
:pseudo_class => "\e[1;34m",
|
||||
:regexp => {
|
||||
:self => "\e[35m",
|
||||
:delimiter => "\e[1;35m",
|
||||
:modifier => "\e[35m",
|
||||
:char => "\e[1;35m",
|
||||
},
|
||||
:reserved => "\e[32m",
|
||||
:shell => {
|
||||
:self => "\e[33m",
|
||||
:char => "\e[1;33m",
|
||||
:delimiter => "\e[1;33m",
|
||||
:escape => "\e[1;33m",
|
||||
},
|
||||
:string => {
|
||||
:self => "\e[31m",
|
||||
:modifier => "\e[1;31m",
|
||||
:char => "\e[1;35m",
|
||||
:delimiter => "\e[1;31m",
|
||||
:escape => "\e[1;31m",
|
||||
},
|
||||
:symbol => {
|
||||
:self => "\e[33m",
|
||||
:delimiter => "\e[1;33m",
|
||||
},
|
||||
:tag => "\e[32m",
|
||||
:type => "\e[1;34m",
|
||||
:value => "\e[36m",
|
||||
:variable => "\e[34m",
|
||||
|
||||
:insert => {
|
||||
:self => "\e[42m",
|
||||
:insert => "\e[1;32;42m",
|
||||
:eyecatcher => "\e[102m",
|
||||
},
|
||||
:delete => {
|
||||
:self => "\e[41m",
|
||||
:delete => "\e[1;31;41m",
|
||||
:eyecatcher => "\e[101m",
|
||||
},
|
||||
:change => {
|
||||
:self => "\e[44m",
|
||||
:change => "\e[37;44m",
|
||||
},
|
||||
:head => {
|
||||
:self => "\e[45m",
|
||||
:filename => "\e[37;45m"
|
||||
},
|
||||
}
|
||||
|
||||
TOKEN_COLORS[:keyword] = TOKEN_COLORS[:reserved]
|
||||
TOKEN_COLORS[:method] = TOKEN_COLORS[:function]
|
||||
TOKEN_COLORS[:escape] = TOKEN_COLORS[:delimiter]
|
||||
|
||||
protected
|
||||
|
||||
def setup(options)
|
||||
super
|
||||
@opened = []
|
||||
@color_scopes = [TOKEN_COLORS]
|
||||
end
|
||||
|
||||
public
|
||||
|
||||
def text_token text, kind
|
||||
if color = @color_scopes.last[kind]
|
||||
color = color[:self] if color.is_a? Hash
|
||||
|
||||
@out << color
|
||||
@out << (text.index("\n") ? text.gsub("\n", "\e[0m\n" + color) : text)
|
||||
@out << "\e[0m"
|
||||
if outer_color = @color_scopes.last[:self]
|
||||
@out << outer_color
|
||||
end
|
||||
else
|
||||
@out << text
|
||||
end
|
||||
end
|
||||
|
||||
def begin_group kind
|
||||
@opened << kind
|
||||
@out << open_token(kind)
|
||||
end
|
||||
alias begin_line begin_group
|
||||
|
||||
def end_group kind
|
||||
if @opened.pop
|
||||
@color_scopes.pop
|
||||
@out << "\e[0m"
|
||||
if outer_color = @color_scopes.last[:self]
|
||||
@out << outer_color
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def end_line kind
|
||||
@out << (@line_filler ||= "\t" * 100)
|
||||
end_group kind
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def open_token kind
|
||||
if color = @color_scopes.last[kind]
|
||||
if color.is_a? Hash
|
||||
@color_scopes << color
|
||||
color[:self]
|
||||
else
|
||||
@color_scopes << @color_scopes.last
|
||||
color
|
||||
end
|
||||
else
|
||||
@color_scopes << @color_scopes.last
|
||||
''
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,46 @@
|
||||
module CodeRay
|
||||
module Encoders
|
||||
|
||||
# Concats the tokens into a single string, resulting in the original
|
||||
# code string if no tokens were removed.
|
||||
#
|
||||
# Alias: +plain+, +plaintext+
|
||||
#
|
||||
# == Options
|
||||
#
|
||||
# === :separator
|
||||
# A separator string to join the tokens.
|
||||
#
|
||||
# Default: empty String
|
||||
class Text < Encoder
|
||||
|
||||
register_for :text
|
||||
|
||||
FILE_EXTENSION = 'txt'
|
||||
|
||||
DEFAULT_OPTIONS = {
|
||||
:separator => nil
|
||||
}
|
||||
|
||||
def text_token text, kind
|
||||
super
|
||||
|
||||
if @first
|
||||
@first = false
|
||||
else
|
||||
@out << @sep
|
||||
end if @sep
|
||||
end
|
||||
|
||||
protected
|
||||
def setup options
|
||||
super
|
||||
|
||||
@first = true
|
||||
@sep = options[:separator]
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,111 @@
|
||||
module CodeRay
|
||||
module Encoders
|
||||
|
||||
load :filter
|
||||
|
||||
# A Filter that selects tokens based on their token kind.
|
||||
#
|
||||
# == Options
|
||||
#
|
||||
# === :exclude
|
||||
#
|
||||
# One or many symbols (in an Array) which shall be excluded.
|
||||
#
|
||||
# Default: []
|
||||
#
|
||||
# === :include
|
||||
#
|
||||
# One or many symbols (in an array) which shall be included.
|
||||
#
|
||||
# Default: :all, which means all tokens are included.
|
||||
#
|
||||
# Exclusion wins over inclusion.
|
||||
#
|
||||
# See also: CommentFilter
|
||||
class TokenKindFilter < Filter
|
||||
|
||||
register_for :token_kind_filter
|
||||
|
||||
DEFAULT_OPTIONS = {
|
||||
:exclude => [],
|
||||
:include => :all
|
||||
}
|
||||
|
||||
protected
|
||||
def setup options
|
||||
super
|
||||
|
||||
@group_excluded = false
|
||||
@exclude = options[:exclude]
|
||||
@exclude = Array(@exclude) unless @exclude == :all
|
||||
@include = options[:include]
|
||||
@include = Array(@include) unless @include == :all
|
||||
end
|
||||
|
||||
def include_text_token? text, kind
|
||||
include_group? kind
|
||||
end
|
||||
|
||||
def include_group? kind
|
||||
(@include == :all || @include.include?(kind)) &&
|
||||
!(@exclude == :all || @exclude.include?(kind))
|
||||
end
|
||||
|
||||
public
|
||||
|
||||
# Add the token to the output stream if +kind+ matches the conditions.
|
||||
def text_token text, kind
|
||||
super if !@group_excluded && include_text_token?(text, kind)
|
||||
end
|
||||
|
||||
# Add the token group to the output stream if +kind+ matches the
|
||||
# conditions.
|
||||
#
|
||||
# If it does not, all tokens inside the group are excluded from the
|
||||
# stream, even if their kinds match.
|
||||
def begin_group kind
|
||||
if @group_excluded
|
||||
@group_excluded += 1
|
||||
elsif include_group? kind
|
||||
super
|
||||
else
|
||||
@group_excluded = 1
|
||||
end
|
||||
end
|
||||
|
||||
# See +begin_group+.
|
||||
def begin_line kind
|
||||
if @group_excluded
|
||||
@group_excluded += 1
|
||||
elsif include_group? kind
|
||||
super
|
||||
else
|
||||
@group_excluded = 1
|
||||
end
|
||||
end
|
||||
|
||||
# Take care of re-enabling the delegation of tokens to the output stream
|
||||
# if an exluded group has ended.
|
||||
def end_group kind
|
||||
if @group_excluded
|
||||
@group_excluded -= 1
|
||||
@group_excluded = false if @group_excluded.zero?
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
# See +end_group+.
|
||||
def end_line kind
|
||||
if @group_excluded
|
||||
@group_excluded -= 1
|
||||
@group_excluded = false if @group_excluded.zero?
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,72 @@
|
||||
module CodeRay
|
||||
module Encoders
|
||||
|
||||
# = XML Encoder
|
||||
#
|
||||
# Uses REXML. Very slow.
|
||||
class XML < Encoder
|
||||
|
||||
register_for :xml
|
||||
|
||||
FILE_EXTENSION = 'xml'
|
||||
|
||||
autoload :REXML, 'rexml/document'
|
||||
|
||||
DEFAULT_OPTIONS = {
|
||||
:tab_width => 8,
|
||||
:pretty => -1,
|
||||
:transitive => false,
|
||||
}
|
||||
|
||||
protected
|
||||
def setup options
|
||||
super
|
||||
|
||||
@doc = REXML::Document.new
|
||||
@doc << REXML::XMLDecl.new
|
||||
@tab_width = options[:tab_width]
|
||||
@root = @node = @doc.add_element('coderay-tokens')
|
||||
end
|
||||
|
||||
def finish options
|
||||
@doc.write @out, options[:pretty], options[:transitive], true
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
public
|
||||
def text_token text, kind
|
||||
if kind == :space
|
||||
token = @node
|
||||
else
|
||||
token = @node.add_element kind.to_s
|
||||
end
|
||||
text.scan(/(\x20+)|(\t+)|(\n)|[^\x20\t\n]+/) do |space, tab, nl|
|
||||
case
|
||||
when space
|
||||
token << REXML::Text.new(space, true)
|
||||
when tab
|
||||
token << REXML::Text.new(tab, true)
|
||||
when nl
|
||||
token << REXML::Text.new(nl, true)
|
||||
else
|
||||
token << REXML::Text.new($&)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def begin_group kind
|
||||
@node = @node.add_element kind.to_s
|
||||
end
|
||||
|
||||
def end_group kind
|
||||
if @node == @root
|
||||
raise 'no token to close!'
|
||||
end
|
||||
@node = @node.parent
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,50 @@
|
||||
autoload :YAML, 'yaml'
|
||||
|
||||
module CodeRay
|
||||
module Encoders
|
||||
|
||||
# = YAML Encoder
|
||||
#
|
||||
# Slow.
|
||||
class YAML < Encoder
|
||||
|
||||
register_for :yaml
|
||||
|
||||
FILE_EXTENSION = 'yaml'
|
||||
|
||||
protected
|
||||
def setup options
|
||||
super
|
||||
|
||||
@data = []
|
||||
end
|
||||
|
||||
def finish options
|
||||
output ::YAML.dump(@data)
|
||||
end
|
||||
|
||||
public
|
||||
def text_token text, kind
|
||||
@data << [text, kind]
|
||||
end
|
||||
|
||||
def begin_group kind
|
||||
@data << [:begin_group, kind]
|
||||
end
|
||||
|
||||
def end_group kind
|
||||
@data << [:end_group, kind]
|
||||
end
|
||||
|
||||
def begin_line kind
|
||||
@data << [:begin_line, kind]
|
||||
end
|
||||
|
||||
def end_line kind
|
||||
@data << [:end_line, kind]
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user