Chris@0: module CodeRay Chris@0: Chris@0: # This module holds the Encoder class and its subclasses. Chris@0: # For example, the HTML encoder is named CodeRay::Encoders::HTML Chris@0: # can be found in coderay/encoders/html. Chris@0: # Chris@0: # Encoders also provides methods and constants for the register Chris@0: # mechanism and the [] method that returns the Encoder class Chris@0: # belonging to the given format. Chris@0: module Encoders Chris@0: extend PluginHost Chris@0: plugin_path File.dirname(__FILE__), 'encoders' Chris@0: Chris@0: # = Encoder Chris@0: # Chris@0: # The Encoder base class. Together with Scanner and Chris@0: # Tokens, it forms the highlighting triad. Chris@0: # Chris@0: # Encoder instances take a Tokens object and do something with it. Chris@0: # Chris@0: # The most common Encoder is surely the HTML encoder Chris@0: # (CodeRay::Encoders::HTML). It highlights the code in a colorful Chris@0: # html page. Chris@0: # If you want the highlighted code in a div or a span instead, Chris@0: # use its subclasses Div and Span. Chris@0: class Encoder Chris@0: extend Plugin Chris@0: plugin_host Encoders Chris@0: Chris@0: attr_reader :token_stream Chris@0: Chris@0: class << self Chris@0: Chris@0: # Returns if the Encoder can be used in streaming mode. Chris@0: def streamable? Chris@0: is_a? Streamable Chris@0: end Chris@0: Chris@0: # If FILE_EXTENSION isn't defined, this method returns the Chris@0: # downcase class name instead. Chris@0: def const_missing sym Chris@0: if sym == :FILE_EXTENSION Chris@0: plugin_id Chris@0: else Chris@0: super Chris@0: end Chris@0: end Chris@0: Chris@0: end Chris@0: Chris@0: # Subclasses are to store their default options in this constant. Chris@0: DEFAULT_OPTIONS = { :stream => false } Chris@0: Chris@0: # The options you gave the Encoder at creating. Chris@0: attr_accessor :options Chris@0: Chris@0: # Creates a new Encoder. Chris@0: # +options+ is saved and used for all encode operations, as long Chris@0: # as you don't overwrite it there by passing additional options. Chris@0: # Chris@0: # Encoder objects provide three encode methods: Chris@0: # - encode simply takes a +code+ string and a +lang+ Chris@0: # - encode_tokens expects a +tokens+ object instead Chris@0: # - encode_stream is like encode, but uses streaming mode. Chris@0: # Chris@0: # Each method has an optional +options+ parameter. These are Chris@0: # added to the options you passed at creation. Chris@0: def initialize options = {} Chris@0: @options = self.class::DEFAULT_OPTIONS.merge options Chris@0: raise "I am only the basic Encoder class. I can't encode "\ Chris@0: "anything. :( Use my subclasses." if self.class == Encoder Chris@0: end Chris@0: Chris@0: # Encode a Tokens object. Chris@0: def encode_tokens tokens, options = {} Chris@0: options = @options.merge options Chris@0: setup options Chris@0: compile tokens, options Chris@0: finish options Chris@0: end Chris@0: Chris@0: # Encode the given +code+ after tokenizing it using the Scanner Chris@0: # for +lang+. Chris@0: def encode code, lang, options = {} Chris@0: options = @options.merge options Chris@0: scanner_options = CodeRay.get_scanner_options(options) Chris@0: tokens = CodeRay.scan code, lang, scanner_options Chris@0: encode_tokens tokens, options Chris@0: end Chris@0: Chris@0: # You can use highlight instead of encode, if that seems Chris@0: # more clear to you. Chris@0: alias highlight encode Chris@0: Chris@0: # Encode the given +code+ using the Scanner for +lang+ in Chris@0: # streaming mode. Chris@0: def encode_stream code, lang, options = {} Chris@0: raise NotStreamableError, self unless kind_of? Streamable Chris@0: options = @options.merge options Chris@0: setup options Chris@0: scanner_options = CodeRay.get_scanner_options options Chris@0: @token_stream = Chris@0: CodeRay.scan_stream code, lang, scanner_options, &self Chris@0: finish options Chris@0: end Chris@0: Chris@0: # Behave like a proc. The token method is converted to a proc. Chris@0: def to_proc Chris@0: method(:token).to_proc Chris@0: end Chris@0: Chris@0: # Return the default file extension for outputs of this encoder. Chris@0: def file_extension Chris@0: self.class::FILE_EXTENSION Chris@0: end Chris@0: Chris@0: protected Chris@0: Chris@0: # Called with merged options before encoding starts. Chris@0: # Sets @out to an empty string. Chris@0: # Chris@0: # See the HTML Encoder for an example of option caching. Chris@0: def setup options Chris@0: @out = '' Chris@0: end Chris@0: Chris@0: # Called with +content+ and +kind+ of the currently scanned token. Chris@0: # For simple scanners, it's enougth to implement this method. Chris@0: # Chris@0: # By default, it calls text_token or block_token, depending on Chris@0: # whether +content+ is a String. Chris@0: def token content, kind Chris@0: encoded_token = Chris@0: if content.is_a? ::String Chris@0: text_token content, kind Chris@0: elsif content.is_a? ::Symbol Chris@0: block_token content, kind Chris@0: else Chris@0: raise 'Unknown token content type: %p' % [content] Chris@0: end Chris@0: append_encoded_token_to_output encoded_token Chris@0: end Chris@0: Chris@0: def append_encoded_token_to_output encoded_token Chris@0: @out << encoded_token if encoded_token && defined?(@out) && @out Chris@0: end Chris@0: Chris@0: # Called for each text token ([text, kind]), where text is a String. Chris@0: def text_token text, kind Chris@0: end Chris@0: Chris@0: # Called for each block (non-text) token ([action, kind]), Chris@0: # where +action+ is a Symbol. Chris@0: # Chris@0: # Calls open_token, close_token, begin_line, and end_line according to Chris@0: # the value of +action+. Chris@0: def block_token action, kind Chris@0: case action Chris@0: when :open Chris@0: open_token kind Chris@0: when :close Chris@0: close_token kind Chris@0: when :begin_line Chris@0: begin_line kind Chris@0: when :end_line Chris@0: end_line kind Chris@0: else Chris@0: raise 'unknown block action: %p' % action Chris@0: end Chris@0: end Chris@0: Chris@0: # Called for each block token at the start of the block ([:open, kind]). Chris@0: def open_token kind Chris@0: end Chris@0: Chris@0: # Called for each block token end of the block ([:close, kind]). Chris@0: def close_token kind Chris@0: end Chris@0: Chris@0: # Called for each line token block at the start of the line ([:begin_line, kind]). Chris@0: def begin_line kind Chris@0: end Chris@0: Chris@0: # Called for each line token block at the end of the line ([:end_line, kind]). Chris@0: def end_line kind Chris@0: end Chris@0: Chris@0: # Called with merged options after encoding starts. Chris@0: # The return value is the result of encoding, typically @out. Chris@0: def finish options Chris@0: @out Chris@0: end Chris@0: Chris@0: # Do the encoding. Chris@0: # Chris@0: # The already created +tokens+ object must be used; it can be a Chris@0: # TokenStream or a Tokens object. Chris@0: if RUBY_VERSION >= '1.9' Chris@0: def compile tokens, options Chris@0: for text, kind in tokens Chris@0: token text, kind Chris@0: end Chris@0: end Chris@0: else Chris@0: def compile tokens, options Chris@0: tokens.each(&self) Chris@0: end Chris@0: end Chris@0: Chris@0: end Chris@0: Chris@0: end Chris@0: end