Chris@0
|
1 module CodeRay
|
Chris@0
|
2
|
Chris@0
|
3 # This module holds the Encoder class and its subclasses.
|
Chris@0
|
4 # For example, the HTML encoder is named CodeRay::Encoders::HTML
|
Chris@0
|
5 # can be found in coderay/encoders/html.
|
Chris@0
|
6 #
|
Chris@0
|
7 # Encoders also provides methods and constants for the register
|
Chris@0
|
8 # mechanism and the [] method that returns the Encoder class
|
Chris@0
|
9 # belonging to the given format.
|
Chris@0
|
10 module Encoders
|
Chris@0
|
11 extend PluginHost
|
Chris@0
|
12 plugin_path File.dirname(__FILE__), 'encoders'
|
Chris@0
|
13
|
Chris@0
|
14 # = Encoder
|
Chris@0
|
15 #
|
Chris@0
|
16 # The Encoder base class. Together with Scanner and
|
Chris@0
|
17 # Tokens, it forms the highlighting triad.
|
Chris@0
|
18 #
|
Chris@0
|
19 # Encoder instances take a Tokens object and do something with it.
|
Chris@0
|
20 #
|
Chris@0
|
21 # The most common Encoder is surely the HTML encoder
|
Chris@0
|
22 # (CodeRay::Encoders::HTML). It highlights the code in a colorful
|
Chris@0
|
23 # html page.
|
Chris@0
|
24 # If you want the highlighted code in a div or a span instead,
|
Chris@0
|
25 # use its subclasses Div and Span.
|
Chris@0
|
26 class Encoder
|
Chris@0
|
27 extend Plugin
|
Chris@0
|
28 plugin_host Encoders
|
Chris@0
|
29
|
Chris@0
|
30 attr_reader :token_stream
|
Chris@0
|
31
|
Chris@0
|
32 class << self
|
Chris@0
|
33
|
Chris@0
|
34 # Returns if the Encoder can be used in streaming mode.
|
Chris@0
|
35 def streamable?
|
Chris@0
|
36 is_a? Streamable
|
Chris@0
|
37 end
|
Chris@0
|
38
|
Chris@0
|
39 # If FILE_EXTENSION isn't defined, this method returns the
|
Chris@0
|
40 # downcase class name instead.
|
Chris@0
|
41 def const_missing sym
|
Chris@0
|
42 if sym == :FILE_EXTENSION
|
Chris@0
|
43 plugin_id
|
Chris@0
|
44 else
|
Chris@0
|
45 super
|
Chris@0
|
46 end
|
Chris@0
|
47 end
|
Chris@0
|
48
|
Chris@0
|
49 end
|
Chris@0
|
50
|
Chris@0
|
51 # Subclasses are to store their default options in this constant.
|
Chris@0
|
52 DEFAULT_OPTIONS = { :stream => false }
|
Chris@0
|
53
|
Chris@0
|
54 # The options you gave the Encoder at creating.
|
Chris@0
|
55 attr_accessor :options
|
Chris@0
|
56
|
Chris@0
|
57 # Creates a new Encoder.
|
Chris@0
|
58 # +options+ is saved and used for all encode operations, as long
|
Chris@0
|
59 # as you don't overwrite it there by passing additional options.
|
Chris@0
|
60 #
|
Chris@0
|
61 # Encoder objects provide three encode methods:
|
Chris@0
|
62 # - encode simply takes a +code+ string and a +lang+
|
Chris@0
|
63 # - encode_tokens expects a +tokens+ object instead
|
Chris@0
|
64 # - encode_stream is like encode, but uses streaming mode.
|
Chris@0
|
65 #
|
Chris@0
|
66 # Each method has an optional +options+ parameter. These are
|
Chris@0
|
67 # added to the options you passed at creation.
|
Chris@0
|
68 def initialize options = {}
|
Chris@0
|
69 @options = self.class::DEFAULT_OPTIONS.merge options
|
Chris@0
|
70 raise "I am only the basic Encoder class. I can't encode "\
|
Chris@0
|
71 "anything. :( Use my subclasses." if self.class == Encoder
|
Chris@0
|
72 end
|
Chris@0
|
73
|
Chris@0
|
74 # Encode a Tokens object.
|
Chris@0
|
75 def encode_tokens tokens, options = {}
|
Chris@0
|
76 options = @options.merge options
|
Chris@0
|
77 setup options
|
Chris@0
|
78 compile tokens, options
|
Chris@0
|
79 finish options
|
Chris@0
|
80 end
|
Chris@0
|
81
|
Chris@0
|
82 # Encode the given +code+ after tokenizing it using the Scanner
|
Chris@0
|
83 # for +lang+.
|
Chris@0
|
84 def encode code, lang, options = {}
|
Chris@0
|
85 options = @options.merge options
|
Chris@0
|
86 scanner_options = CodeRay.get_scanner_options(options)
|
Chris@0
|
87 tokens = CodeRay.scan code, lang, scanner_options
|
Chris@0
|
88 encode_tokens tokens, options
|
Chris@0
|
89 end
|
Chris@0
|
90
|
Chris@0
|
91 # You can use highlight instead of encode, if that seems
|
Chris@0
|
92 # more clear to you.
|
Chris@0
|
93 alias highlight encode
|
Chris@0
|
94
|
Chris@0
|
95 # Encode the given +code+ using the Scanner for +lang+ in
|
Chris@0
|
96 # streaming mode.
|
Chris@0
|
97 def encode_stream code, lang, options = {}
|
Chris@0
|
98 raise NotStreamableError, self unless kind_of? Streamable
|
Chris@0
|
99 options = @options.merge options
|
Chris@0
|
100 setup options
|
Chris@0
|
101 scanner_options = CodeRay.get_scanner_options options
|
Chris@0
|
102 @token_stream =
|
Chris@0
|
103 CodeRay.scan_stream code, lang, scanner_options, &self
|
Chris@0
|
104 finish options
|
Chris@0
|
105 end
|
Chris@0
|
106
|
Chris@0
|
107 # Behave like a proc. The token method is converted to a proc.
|
Chris@0
|
108 def to_proc
|
Chris@0
|
109 method(:token).to_proc
|
Chris@0
|
110 end
|
Chris@0
|
111
|
Chris@0
|
112 # Return the default file extension for outputs of this encoder.
|
Chris@0
|
113 def file_extension
|
Chris@0
|
114 self.class::FILE_EXTENSION
|
Chris@0
|
115 end
|
Chris@0
|
116
|
Chris@0
|
117 protected
|
Chris@0
|
118
|
Chris@0
|
119 # Called with merged options before encoding starts.
|
Chris@0
|
120 # Sets @out to an empty string.
|
Chris@0
|
121 #
|
Chris@0
|
122 # See the HTML Encoder for an example of option caching.
|
Chris@0
|
123 def setup options
|
Chris@0
|
124 @out = ''
|
Chris@0
|
125 end
|
Chris@0
|
126
|
Chris@0
|
127 # Called with +content+ and +kind+ of the currently scanned token.
|
Chris@0
|
128 # For simple scanners, it's enougth to implement this method.
|
Chris@0
|
129 #
|
Chris@0
|
130 # By default, it calls text_token or block_token, depending on
|
Chris@0
|
131 # whether +content+ is a String.
|
Chris@0
|
132 def token content, kind
|
Chris@0
|
133 encoded_token =
|
Chris@0
|
134 if content.is_a? ::String
|
Chris@0
|
135 text_token content, kind
|
Chris@0
|
136 elsif content.is_a? ::Symbol
|
Chris@0
|
137 block_token content, kind
|
Chris@0
|
138 else
|
Chris@0
|
139 raise 'Unknown token content type: %p' % [content]
|
Chris@0
|
140 end
|
Chris@0
|
141 append_encoded_token_to_output encoded_token
|
Chris@0
|
142 end
|
Chris@0
|
143
|
Chris@0
|
144 def append_encoded_token_to_output encoded_token
|
Chris@0
|
145 @out << encoded_token if encoded_token && defined?(@out) && @out
|
Chris@0
|
146 end
|
Chris@0
|
147
|
Chris@0
|
148 # Called for each text token ([text, kind]), where text is a String.
|
Chris@0
|
149 def text_token text, kind
|
Chris@0
|
150 end
|
Chris@0
|
151
|
Chris@0
|
152 # Called for each block (non-text) token ([action, kind]),
|
Chris@0
|
153 # where +action+ is a Symbol.
|
Chris@0
|
154 #
|
Chris@0
|
155 # Calls open_token, close_token, begin_line, and end_line according to
|
Chris@0
|
156 # the value of +action+.
|
Chris@0
|
157 def block_token action, kind
|
Chris@0
|
158 case action
|
Chris@0
|
159 when :open
|
Chris@0
|
160 open_token kind
|
Chris@0
|
161 when :close
|
Chris@0
|
162 close_token kind
|
Chris@0
|
163 when :begin_line
|
Chris@0
|
164 begin_line kind
|
Chris@0
|
165 when :end_line
|
Chris@0
|
166 end_line kind
|
Chris@0
|
167 else
|
Chris@0
|
168 raise 'unknown block action: %p' % action
|
Chris@0
|
169 end
|
Chris@0
|
170 end
|
Chris@0
|
171
|
Chris@0
|
172 # Called for each block token at the start of the block ([:open, kind]).
|
Chris@0
|
173 def open_token kind
|
Chris@0
|
174 end
|
Chris@0
|
175
|
Chris@0
|
176 # Called for each block token end of the block ([:close, kind]).
|
Chris@0
|
177 def close_token kind
|
Chris@0
|
178 end
|
Chris@0
|
179
|
Chris@0
|
180 # Called for each line token block at the start of the line ([:begin_line, kind]).
|
Chris@0
|
181 def begin_line kind
|
Chris@0
|
182 end
|
Chris@0
|
183
|
Chris@0
|
184 # Called for each line token block at the end of the line ([:end_line, kind]).
|
Chris@0
|
185 def end_line kind
|
Chris@0
|
186 end
|
Chris@0
|
187
|
Chris@0
|
188 # Called with merged options after encoding starts.
|
Chris@0
|
189 # The return value is the result of encoding, typically @out.
|
Chris@0
|
190 def finish options
|
Chris@0
|
191 @out
|
Chris@0
|
192 end
|
Chris@0
|
193
|
Chris@0
|
194 # Do the encoding.
|
Chris@0
|
195 #
|
Chris@0
|
196 # The already created +tokens+ object must be used; it can be a
|
Chris@0
|
197 # TokenStream or a Tokens object.
|
Chris@0
|
198 if RUBY_VERSION >= '1.9'
|
Chris@0
|
199 def compile tokens, options
|
Chris@0
|
200 for text, kind in tokens
|
Chris@0
|
201 token text, kind
|
Chris@0
|
202 end
|
Chris@0
|
203 end
|
Chris@0
|
204 else
|
Chris@0
|
205 def compile tokens, options
|
Chris@0
|
206 tokens.each(&self)
|
Chris@0
|
207 end
|
Chris@0
|
208 end
|
Chris@0
|
209
|
Chris@0
|
210 end
|
Chris@0
|
211
|
Chris@0
|
212 end
|
Chris@0
|
213 end
|