Chris@909: module CodeRay Chris@909: Chris@909: # This module holds the Encoder class and its subclasses. Chris@909: # For example, the HTML encoder is named CodeRay::Encoders::HTML Chris@909: # can be found in coderay/encoders/html. Chris@909: # Chris@909: # Encoders also provides methods and constants for the register Chris@909: # mechanism and the [] method that returns the Encoder class Chris@909: # belonging to the given format. Chris@909: module Encoders Chris@909: Chris@909: extend PluginHost Chris@909: plugin_path File.dirname(__FILE__), 'encoders' Chris@909: Chris@909: # = Encoder Chris@909: # Chris@909: # The Encoder base class. Together with Scanner and Chris@909: # Tokens, it forms the highlighting triad. Chris@909: # Chris@909: # Encoder instances take a Tokens object and do something with it. Chris@909: # Chris@909: # The most common Encoder is surely the HTML encoder Chris@909: # (CodeRay::Encoders::HTML). It highlights the code in a colorful Chris@909: # html page. Chris@909: # If you want the highlighted code in a div or a span instead, Chris@909: # use its subclasses Div and Span. Chris@909: class Encoder Chris@909: extend Plugin Chris@909: plugin_host Encoders Chris@909: Chris@909: class << self Chris@909: Chris@909: # If FILE_EXTENSION isn't defined, this method returns the Chris@909: # downcase class name instead. Chris@909: def const_missing sym Chris@909: if sym == :FILE_EXTENSION Chris@909: (defined?(@plugin_id) && @plugin_id || name[/\w+$/].downcase).to_s Chris@909: else Chris@909: super Chris@909: end Chris@909: end Chris@909: Chris@909: # The default file extension for output file of this encoder class. Chris@909: def file_extension Chris@909: self::FILE_EXTENSION Chris@909: end Chris@909: Chris@909: end Chris@909: Chris@909: # Subclasses are to store their default options in this constant. Chris@909: DEFAULT_OPTIONS = { } Chris@909: Chris@909: # The options you gave the Encoder at creating. Chris@909: attr_accessor :options, :scanner Chris@909: Chris@909: # Creates a new Encoder. Chris@909: # +options+ is saved and used for all encode operations, as long Chris@909: # as you don't overwrite it there by passing additional options. Chris@909: # Chris@909: # Encoder objects provide three encode methods: Chris@909: # - encode simply takes a +code+ string and a +lang+ Chris@909: # - encode_tokens expects a +tokens+ object instead Chris@909: # Chris@909: # Each method has an optional +options+ parameter. These are Chris@909: # added to the options you passed at creation. Chris@909: def initialize options = {} Chris@909: @options = self.class::DEFAULT_OPTIONS.merge options Chris@909: @@CODERAY_TOKEN_INTERFACE_DEPRECATION_WARNING_GIVEN = false Chris@909: end Chris@909: Chris@909: # Encode a Tokens object. Chris@909: def encode_tokens tokens, options = {} Chris@909: options = @options.merge options Chris@909: @scanner = tokens.scanner if tokens.respond_to? :scanner Chris@909: setup options Chris@909: compile tokens, options Chris@909: finish options Chris@909: end Chris@909: Chris@909: # Encode the given +code+ using the Scanner for +lang+. Chris@909: def encode code, lang, options = {} Chris@909: options = @options.merge options Chris@909: @scanner = Scanners[lang].new code, CodeRay.get_scanner_options(options).update(:tokens => self) Chris@909: setup options Chris@909: @scanner.tokenize Chris@909: finish options Chris@909: end Chris@909: Chris@909: # You can use highlight instead of encode, if that seems Chris@909: # more clear to you. Chris@909: alias highlight encode Chris@909: Chris@909: # The default file extension for this encoder. Chris@909: def file_extension Chris@909: self.class.file_extension Chris@909: end Chris@909: Chris@909: def << token Chris@909: unless @@CODERAY_TOKEN_INTERFACE_DEPRECATION_WARNING_GIVEN Chris@909: warn 'Using old Tokens#<< interface.' Chris@909: @@CODERAY_TOKEN_INTERFACE_DEPRECATION_WARNING_GIVEN = true Chris@909: end Chris@909: self.token(*token) Chris@909: end Chris@909: Chris@909: # Called with +content+ and +kind+ of the currently scanned token. Chris@909: # For simple scanners, it's enougth to implement this method. Chris@909: # Chris@909: # By default, it calls text_token, begin_group, end_group, begin_line, Chris@909: # or end_line, depending on the +content+. Chris@909: def token content, kind Chris@909: case content Chris@909: when String Chris@909: text_token content, kind Chris@909: when :begin_group Chris@909: begin_group kind Chris@909: when :end_group Chris@909: end_group kind Chris@909: when :begin_line Chris@909: begin_line kind Chris@909: when :end_line Chris@909: end_line kind Chris@909: else Chris@909: raise ArgumentError, 'Unknown token content type: %p, kind = %p' % [content, kind] Chris@909: end Chris@909: end Chris@909: Chris@909: # Called for each text token ([text, kind]), where text is a String. Chris@909: def text_token text, kind Chris@909: @out << text Chris@909: end Chris@909: Chris@909: # Starts a token group with the given +kind+. Chris@909: def begin_group kind Chris@909: end Chris@909: Chris@909: # Ends a token group with the given +kind+. Chris@909: def end_group kind Chris@909: end Chris@909: Chris@909: # Starts a new line token group with the given +kind+. Chris@909: def begin_line kind Chris@909: end Chris@909: Chris@909: # Ends a new line token group with the given +kind+. Chris@909: def end_line kind Chris@909: end Chris@909: Chris@909: protected Chris@909: Chris@909: # Called with merged options before encoding starts. Chris@909: # Sets @out to an empty string. Chris@909: # Chris@909: # See the HTML Encoder for an example of option caching. Chris@909: def setup options Chris@909: @out = get_output(options) Chris@909: end Chris@909: Chris@909: def get_output options Chris@909: options[:out] || '' Chris@909: end Chris@909: Chris@909: # Append data.to_s to the output. Returns the argument. Chris@909: def output data Chris@909: @out << data.to_s Chris@909: data Chris@909: end Chris@909: Chris@909: # Called with merged options after encoding starts. Chris@909: # The return value is the result of encoding, typically @out. Chris@909: def finish options Chris@909: @out Chris@909: end Chris@909: Chris@909: # Do the encoding. Chris@909: # Chris@909: # The already created +tokens+ object must be used; it must be a Chris@909: # Tokens object. Chris@909: def compile tokens, options = {} Chris@909: content = nil Chris@909: for item in tokens Chris@909: if item.is_a? Array Chris@909: raise ArgumentError, 'Two-element array tokens are no longer supported.' Chris@909: end Chris@909: if content Chris@909: token content, item Chris@909: content = nil Chris@909: else Chris@909: content = item Chris@909: end Chris@909: end Chris@909: raise 'odd number list for Tokens' if content Chris@909: end Chris@909: Chris@909: alias tokens compile Chris@909: public :tokens Chris@909: Chris@909: end Chris@909: Chris@909: end Chris@909: end