rugments 1.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (103) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +52 -0
  3. data/README.md +195 -0
  4. data/bin/rugmentize +6 -0
  5. data/lib/rugments/cli.rb +357 -0
  6. data/lib/rugments/formatter.rb +29 -0
  7. data/lib/rugments/formatters/html.rb +142 -0
  8. data/lib/rugments/formatters/null.rb +17 -0
  9. data/lib/rugments/formatters/terminal256.rb +174 -0
  10. data/lib/rugments/lexer.rb +431 -0
  11. data/lib/rugments/lexers/apache/keywords.yml +453 -0
  12. data/lib/rugments/lexers/apache.rb +67 -0
  13. data/lib/rugments/lexers/apple_script.rb +366 -0
  14. data/lib/rugments/lexers/c.rb +210 -0
  15. data/lib/rugments/lexers/clojure.rb +109 -0
  16. data/lib/rugments/lexers/coffeescript.rb +172 -0
  17. data/lib/rugments/lexers/common_lisp.rb +343 -0
  18. data/lib/rugments/lexers/conf.rb +22 -0
  19. data/lib/rugments/lexers/cpp.rb +63 -0
  20. data/lib/rugments/lexers/csharp.rb +85 -0
  21. data/lib/rugments/lexers/css.rb +269 -0
  22. data/lib/rugments/lexers/dart.rb +102 -0
  23. data/lib/rugments/lexers/diff.rb +39 -0
  24. data/lib/rugments/lexers/elixir.rb +105 -0
  25. data/lib/rugments/lexers/erb.rb +54 -0
  26. data/lib/rugments/lexers/erlang.rb +116 -0
  27. data/lib/rugments/lexers/factor.rb +300 -0
  28. data/lib/rugments/lexers/gherkin/keywords.rb +13 -0
  29. data/lib/rugments/lexers/gherkin.rb +135 -0
  30. data/lib/rugments/lexers/go.rb +176 -0
  31. data/lib/rugments/lexers/groovy.rb +102 -0
  32. data/lib/rugments/lexers/haml.rb +226 -0
  33. data/lib/rugments/lexers/handlebars.rb +77 -0
  34. data/lib/rugments/lexers/haskell.rb +181 -0
  35. data/lib/rugments/lexers/html.rb +92 -0
  36. data/lib/rugments/lexers/http.rb +78 -0
  37. data/lib/rugments/lexers/ini.rb +55 -0
  38. data/lib/rugments/lexers/io.rb +66 -0
  39. data/lib/rugments/lexers/java.rb +74 -0
  40. data/lib/rugments/lexers/javascript.rb +258 -0
  41. data/lib/rugments/lexers/literate_coffeescript.rb +31 -0
  42. data/lib/rugments/lexers/literate_haskell.rb +34 -0
  43. data/lib/rugments/lexers/llvm.rb +82 -0
  44. data/lib/rugments/lexers/lua/builtins.rb +21 -0
  45. data/lib/rugments/lexers/lua.rb +120 -0
  46. data/lib/rugments/lexers/make.rb +114 -0
  47. data/lib/rugments/lexers/markdown.rb +151 -0
  48. data/lib/rugments/lexers/matlab/builtins.rb +10 -0
  49. data/lib/rugments/lexers/matlab.rb +70 -0
  50. data/lib/rugments/lexers/moonscript.rb +108 -0
  51. data/lib/rugments/lexers/nginx.rb +69 -0
  52. data/lib/rugments/lexers/nim.rb +149 -0
  53. data/lib/rugments/lexers/objective_c.rb +188 -0
  54. data/lib/rugments/lexers/ocaml.rb +109 -0
  55. data/lib/rugments/lexers/perl.rb +195 -0
  56. data/lib/rugments/lexers/php/builtins.rb +192 -0
  57. data/lib/rugments/lexers/php.rb +162 -0
  58. data/lib/rugments/lexers/plain_text.rb +23 -0
  59. data/lib/rugments/lexers/prolog.rb +62 -0
  60. data/lib/rugments/lexers/properties.rb +53 -0
  61. data/lib/rugments/lexers/puppet.rb +126 -0
  62. data/lib/rugments/lexers/python.rb +225 -0
  63. data/lib/rugments/lexers/qml.rb +70 -0
  64. data/lib/rugments/lexers/r.rb +55 -0
  65. data/lib/rugments/lexers/racket.rb +540 -0
  66. data/lib/rugments/lexers/ruby.rb +413 -0
  67. data/lib/rugments/lexers/rust.rb +188 -0
  68. data/lib/rugments/lexers/sass/common.rb +172 -0
  69. data/lib/rugments/lexers/sass.rb +72 -0
  70. data/lib/rugments/lexers/scala.rb +140 -0
  71. data/lib/rugments/lexers/scheme.rb +109 -0
  72. data/lib/rugments/lexers/scss.rb +32 -0
  73. data/lib/rugments/lexers/sed.rb +167 -0
  74. data/lib/rugments/lexers/shell.rb +150 -0
  75. data/lib/rugments/lexers/slim.rb +222 -0
  76. data/lib/rugments/lexers/smalltalk.rb +114 -0
  77. data/lib/rugments/lexers/sml.rb +345 -0
  78. data/lib/rugments/lexers/sql.rb +138 -0
  79. data/lib/rugments/lexers/swift.rb +153 -0
  80. data/lib/rugments/lexers/tcl.rb +189 -0
  81. data/lib/rugments/lexers/tex.rb +70 -0
  82. data/lib/rugments/lexers/toml.rb +68 -0
  83. data/lib/rugments/lexers/vb.rb +162 -0
  84. data/lib/rugments/lexers/viml/keywords.rb +11 -0
  85. data/lib/rugments/lexers/viml.rb +99 -0
  86. data/lib/rugments/lexers/xml.rb +57 -0
  87. data/lib/rugments/lexers/yaml.rb +362 -0
  88. data/lib/rugments/plugins/redcarpet.rb +28 -0
  89. data/lib/rugments/regex_lexer.rb +432 -0
  90. data/lib/rugments/template_lexer.rb +23 -0
  91. data/lib/rugments/text_analyzer.rb +46 -0
  92. data/lib/rugments/theme.rb +202 -0
  93. data/lib/rugments/themes/base16.rb +128 -0
  94. data/lib/rugments/themes/colorful.rb +65 -0
  95. data/lib/rugments/themes/github.rb +69 -0
  96. data/lib/rugments/themes/monokai.rb +88 -0
  97. data/lib/rugments/themes/monokai_sublime.rb +89 -0
  98. data/lib/rugments/themes/thankful_eyes.rb +69 -0
  99. data/lib/rugments/token.rb +180 -0
  100. data/lib/rugments/util.rb +99 -0
  101. data/lib/rugments/version.rb +3 -0
  102. data/lib/rugments.rb +33 -0
  103. metadata +149 -0
@@ -0,0 +1,432 @@
1
+ module Rugments
2
+ # @abstract
3
+ # A stateful lexer that uses sets of regular expressions to
4
+ # tokenize a string. Most lexers are instances of RegexLexer.
5
+ class RegexLexer < Lexer
6
+ # A rule is a tuple of a regular expression to test, and a callback
7
+ # to perform if the test succeeds.
8
+ #
9
+ # @see StateDSL#rule
10
+ class Rule
11
+ attr_reader :callback
12
+ attr_reader :re
13
+ attr_reader :beginning_of_line
14
+ def initialize(re, callback)
15
+ @re = re
16
+ @callback = callback
17
+ @beginning_of_line = re.source[0] == '^'
18
+ end
19
+
20
+ def inspect
21
+ "#<Rule #{@re.inspect}>"
22
+ end
23
+ end
24
+
25
+ # a State is a named set of rules that can be tested for or
26
+ # mixed in.
27
+ #
28
+ # @see RegexLexer.state
29
+ class State
30
+ attr_reader :name, :rules
31
+ def initialize(name, rules)
32
+ @name = name
33
+ @rules = rules
34
+ end
35
+
36
+ def inspect
37
+ "#<#{self.class.name} #{@name.inspect}>"
38
+ end
39
+ end
40
+
41
+ class StateDSL
42
+ attr_reader :rules
43
+ def initialize(name, &defn)
44
+ @name = name
45
+ @defn = defn
46
+ @rules = []
47
+ end
48
+
49
+ def to_state(lexer_class)
50
+ load!
51
+ rules = @rules.map do |rule|
52
+ rule.is_a?(String) ? lexer_class.get_state(rule) : rule
53
+ end
54
+ State.new(@name, rules)
55
+ end
56
+
57
+ def prepended(&defn)
58
+ parent_defn = @defn
59
+ StateDSL.new(@name) do
60
+ instance_eval(&defn)
61
+ instance_eval(&parent_defn)
62
+ end
63
+ end
64
+
65
+ def appended(&defn)
66
+ parent_defn = @defn
67
+ StateDSL.new(@name) do
68
+ instance_eval(&parent_defn)
69
+ instance_eval(&defn)
70
+ end
71
+ end
72
+
73
+ protected
74
+
75
+ # Define a new rule for this state.
76
+ #
77
+ # @overload rule(re, token, next_state=nil)
78
+ # @overload rule(re, &callback)
79
+ #
80
+ # @param [Regexp] re
81
+ # a regular expression for this rule to test.
82
+ # @param [String] tok
83
+ # the token type to yield if `re` matches.
84
+ # @param [#to_s] next_state
85
+ # (optional) a state to push onto the stack if `re` matches.
86
+ # If `next_state` is `:pop!`, the state stack will be popped
87
+ # instead.
88
+ # @param [Proc] callback
89
+ # a block that will be evaluated in the context of the lexer
90
+ # if `re` matches. This block has access to a number of lexer
91
+ # methods, including {RegexLexer#push}, {RegexLexer#pop!},
92
+ # {RegexLexer#token}, and {RegexLexer#delegate}. The first
93
+ # argument can be used to access the match groups.
94
+ def rule(re, tok = nil, next_state = nil, &callback)
95
+ if tok.nil? && callback.nil?
96
+ fail 'please pass `rule` a token to yield or a callback'
97
+ end
98
+
99
+ callback ||= case next_state
100
+ when :pop!
101
+ proc do |stream|
102
+ puts " yielding #{tok.qualname}, #{stream[0].inspect}" if @debug
103
+ @output_stream.call(tok, stream[0])
104
+ puts " popping stack: #{1}" if @debug
105
+ @stack.pop || fail('empty stack!')
106
+ end
107
+ when :push
108
+ proc do |stream|
109
+ puts " yielding #{tok.qualname}, #{stream[0].inspect}" if @debug
110
+ @output_stream.call(tok, stream[0])
111
+ puts " pushing #{@stack.last.name}" if @debug
112
+ @stack.push(@stack.last)
113
+ end
114
+ when Symbol
115
+ proc do |stream|
116
+ puts " yielding #{tok.qualname}, #{stream[0].inspect}" if @debug
117
+ @output_stream.call(tok, stream[0])
118
+ state = @states[next_state] || self.class.get_state(next_state)
119
+ puts " pushing #{state.name}" if @debug
120
+ @stack.push(state)
121
+ end
122
+ when nil
123
+ proc do |stream|
124
+ puts " yielding #{tok.qualname}, #{stream[0].inspect}" if @debug
125
+ @output_stream.call(tok, stream[0])
126
+ end
127
+ else
128
+ fail "invalid next state: #{next_state.inspect}"
129
+ end
130
+
131
+ rules << Rule.new(re, callback)
132
+ end
133
+
134
+ # Mix in the rules from another state into this state. The rules
135
+ # from the mixed-in state will be tried in order before moving on
136
+ # to the rest of the rules in this state.
137
+ def mixin(state)
138
+ rules << state.to_s
139
+ end
140
+
141
+ private
142
+
143
+ def load!
144
+ return if @loaded
145
+ @loaded = true
146
+ instance_eval(&@defn)
147
+ end
148
+ end
149
+
150
+ # The states hash for this lexer.
151
+ # @see state
152
+ def self.states
153
+ @states ||= {}
154
+ end
155
+
156
+ def self.state_definitions
157
+ @state_definitions ||= InheritableHash.new(superclass.state_definitions)
158
+ end
159
+ @state_definitions = {}
160
+
161
+ def self.replace_state(name, new_defn)
162
+ states[name] = nil
163
+ state_definitions[name] = new_defn
164
+ end
165
+
166
+ # The routines to run at the beginning of a fresh lex.
167
+ # @see start
168
+ def self.start_procs
169
+ @start_procs ||= InheritableList.new(superclass.start_procs)
170
+ end
171
+ @start_procs = []
172
+
173
+ # Specify an action to be run every fresh lex.
174
+ #
175
+ # @example
176
+ # start { puts "I'm lexing a new string!" }
177
+ def self.start(&b)
178
+ start_procs << b
179
+ end
180
+
181
+ # Define a new state for this lexer with the given name.
182
+ # The block will be evaluated in the context of a {StateDSL}.
183
+ def self.state(name, &b)
184
+ name = name.to_s
185
+ state_definitions[name] = StateDSL.new(name, &b)
186
+ end
187
+
188
+ def self.prepend(name, &b)
189
+ name = name.to_s
190
+ dsl = state_definitions[name] or fail "no such state #{name.inspect}"
191
+ replace_state(name, dsl.prepended(&b))
192
+ end
193
+
194
+ def self.append(_state, &b)
195
+ name = name.to_s
196
+ dsl = state_definitions[name] or fail "no such state #{name.inspect}"
197
+ replace_state(name, dsl.appended(&b))
198
+ end
199
+
200
+ # @private
201
+ def self.get_state(name)
202
+ return name if name.is_a? State
203
+
204
+ states[name.to_sym] ||= begin
205
+ defn = state_definitions[name.to_s] or fail "unknown state: #{name.inspect}"
206
+ defn.to_state(self)
207
+ end
208
+ end
209
+
210
+ # @private
211
+ def get_state(state_name)
212
+ self.class.get_state(state_name)
213
+ end
214
+
215
+ # The state stack. This is initially the single state `[:root]`.
216
+ # It is an error for this stack to be empty.
217
+ # @see #state
218
+ def stack
219
+ @stack ||= [get_state(:root)]
220
+ end
221
+
222
+ # The current state - i.e. one on top of the state stack.
223
+ #
224
+ # NB: if the state stack is empty, this will throw an error rather
225
+ # than returning nil.
226
+ def state
227
+ stack.last || fail('empty stack!')
228
+ end
229
+
230
+ # reset this lexer to its initial state. This runs all of the
231
+ # start_procs.
232
+ def reset!
233
+ @stack = nil
234
+ @current_stream = nil
235
+
236
+ self.class.start_procs.each do |pr|
237
+ instance_eval(&pr)
238
+ end
239
+ end
240
+
241
+ # This implements the lexer protocol, by yielding [token, value] pairs.
242
+ #
243
+ # The process for lexing works as follows, until the stream is empty:
244
+ #
245
+ # 1. We look at the state on top of the stack (which by default is
246
+ # `[:root]`).
247
+ # 2. Each rule in that state is tried until one is successful. If one
248
+ # is found, that rule's callback is evaluated - which may yield
249
+ # tokens and manipulate the state stack. Otherwise, one character
250
+ # is consumed with an `'Error'` token, and we continue at (1.)
251
+ #
252
+ # @see #step #step (where (2.) is implemented)
253
+ def stream_tokens(str, &b)
254
+ stream = StringScanner.new(str)
255
+
256
+ @current_stream = stream
257
+ @output_stream = b
258
+ @states = self.class.states
259
+ @null_steps = 0
260
+
261
+ until stream.eos?
262
+ if @debug
263
+ puts "lexer: #{self.class.tag}"
264
+ puts "stack: #{stack.map(&:name).inspect}"
265
+ puts "stream: #{stream.peek(20).inspect}"
266
+ end
267
+
268
+ success = step(state, stream)
269
+
270
+ unless success
271
+ puts ' no match, yielding Error' if @debug
272
+ b.call(Token::Tokens::Error, stream.getch)
273
+ end
274
+ end
275
+ end
276
+
277
+ # The number of successive scans permitted without consuming
278
+ # the input stream. If this is exceeded, the match fails.
279
+ MAX_NULL_SCANS = 5
280
+
281
+ # Runs one step of the lex. Rules in the current state are tried
282
+ # until one matches, at which point its callback is called.
283
+ #
284
+ # @return true if a rule was tried successfully
285
+ # @return false otherwise.
286
+ def step(state, stream)
287
+ state.rules.each do |rule|
288
+ if rule.is_a?(State)
289
+ puts " entering mixin #{rule.name}" if @debug
290
+ return true if step(rule, stream)
291
+ puts " exiting mixin #{rule.name}" if @debug
292
+ else
293
+ puts " trying #{rule.inspect}" if @debug
294
+
295
+ # XXX HACK XXX
296
+ # StringScanner's implementation of ^ is b0rken.
297
+ # see http://bugs.ruby-lang.org/issues/7092
298
+ # TODO: this doesn't cover cases like /(a|^b)/, but it's
299
+ # the most common, for now...
300
+ next if rule.beginning_of_line && !stream.beginning_of_line?
301
+
302
+ if size = stream.skip(rule.re)
303
+ puts " got #{stream[0].inspect}" if @debug
304
+
305
+ instance_exec(stream, &rule.callback)
306
+
307
+ if size.zero?
308
+ @null_steps += 1
309
+ if @null_steps > MAX_NULL_SCANS
310
+ puts ' too many scans without consuming the string!' if @debug
311
+ return false
312
+ end
313
+ else
314
+ @null_steps = 0
315
+ end
316
+
317
+ return true
318
+ end
319
+ end
320
+ end
321
+
322
+ false
323
+ end
324
+
325
+ # Yield a token.
326
+ #
327
+ # @param tok
328
+ # the token type
329
+ # @param val
330
+ # (optional) the string value to yield. If absent, this defaults
331
+ # to the entire last match.
332
+ def token(tok, val = @current_stream[0])
333
+ yield_token(tok, val)
334
+ end
335
+
336
+ # Yield tokens corresponding to the matched groups of the current
337
+ # match.
338
+ def groups(*tokens)
339
+ tokens.each_with_index do |tok, i|
340
+ yield_token(tok, @current_stream[i + 1])
341
+ end
342
+ end
343
+
344
+ # Delegate the lex to another lexer. The #lex method will be called
345
+ # with `:continue` set to true, so that #reset! will not be called.
346
+ # In this way, a single lexer can be repeatedly delegated to while
347
+ # maintaining its own internal state stack.
348
+ #
349
+ # @param [#lex] lexer
350
+ # The lexer or lexer class to delegate to
351
+ # @param [String] text
352
+ # The text to delegate. This defaults to the last matched string.
353
+ def delegate(lexer, text = nil)
354
+ puts " delegating to #{lexer.inspect}" if @debug
355
+ text ||= @current_stream[0]
356
+
357
+ lexer.lex(text, continue: true) do |tok, val|
358
+ puts " delegated token: #{tok.inspect}, #{val.inspect}" if @debug
359
+ yield_token(tok, val)
360
+ end
361
+ end
362
+
363
+ def recurse(text = nil)
364
+ delegate(self.class, text)
365
+ end
366
+
367
+ # Push a state onto the stack. If no state name is given and you've
368
+ # passed a block, a state will be dynamically created using the
369
+ # {StateDSL}.
370
+ def push(state_name = nil, &b)
371
+ push_state = if state_name
372
+ get_state(state_name)
373
+ elsif block_given?
374
+ StateDSL.new(b.inspect, &b).to_state(self.class)
375
+ else
376
+ # use the top of the stack by default
377
+ state
378
+ end
379
+
380
+ puts " pushing #{push_state.name}" if @debug
381
+ stack.push(push_state)
382
+ end
383
+
384
+ # Pop the state stack. If a number is passed in, it will be popped
385
+ # that number of times.
386
+ def pop!(times = 1)
387
+ fail 'empty stack!' if stack.empty?
388
+
389
+ puts " popping stack: #{times}" if @debug
390
+
391
+ stack.pop(times)
392
+
393
+ nil
394
+ end
395
+
396
+ # replace the head of the stack with the given state
397
+ def goto(state_name)
398
+ fail 'empty stack!' if stack.empty?
399
+
400
+ puts " going to state #{state_name} " if @debug
401
+ stack[-1] = get_state(state_name)
402
+ end
403
+
404
+ # reset the stack back to `[:root]`.
405
+ def reset_stack
406
+ puts ' resetting stack' if @debug
407
+ stack.clear
408
+ stack.push get_state(:root)
409
+ end
410
+
411
+ # Check if `state_name` is in the state stack.
412
+ def in_state?(state_name)
413
+ state_name = state_name.to_s
414
+ stack.any? do |state|
415
+ state.name == state_name.to_s
416
+ end
417
+ end
418
+
419
+ # Check if `state_name` is the state on top of the state stack.
420
+ def state?(state_name)
421
+ state_name.to_s == state.name
422
+ end
423
+
424
+ private
425
+
426
+ def yield_token(tok, val)
427
+ return if val.nil? || val.empty?
428
+ puts " yielding #{tok.qualname}, #{val.inspect}" if @debug
429
+ @output_stream.yield(tok, val)
430
+ end
431
+ end
432
+ end
@@ -0,0 +1,23 @@
1
+ module Rugments
2
+ # @abstract
3
+ # A TemplateLexer is one that accepts a :parent option, to specify
4
+ # which language is being templated. The lexer class can specify its
5
+ # own default for the parent lexer, which is otherwise defaulted to
6
+ # HTML.
7
+ class TemplateLexer < RegexLexer
8
+ # the parent lexer - the one being templated.
9
+ def parent
10
+ return @parent if instance_variable_defined? :@parent
11
+ @parent = option(:parent) || 'html'
12
+ if @parent.is_a? ::String
13
+ lexer_class = Lexer.find(@parent)
14
+ @parent = lexer_class.new(options)
15
+ end
16
+ end
17
+
18
+ start { parent.reset! }
19
+ end
20
+ end
21
+
22
+ lib_path = File.expand_path(File.dirname(__FILE__))
23
+ Dir.glob(File.join(lib_path, 'lexers/*.rb')) { |f| require_relative f }
@@ -0,0 +1,46 @@
1
+ module Rugments
2
+ class TextAnalyzer < String
3
+ # Find a shebang. Returns nil if no shebang is present.
4
+ def shebang
5
+ return @shebang if instance_variable_defined? :@shebang
6
+
7
+ self =~ /\A\s*#!(.*)$/
8
+ @shebang = $1
9
+ end
10
+
11
+ # Check if the given shebang is present.
12
+ #
13
+ # This normalizes things so that `text.shebang?('bash')` will detect
14
+ # `#!/bash`, '#!/bin/bash', '#!/usr/bin/env bash', and '#!/bin/bash -x'
15
+ def shebang?(match)
16
+ match = /\b#{match}(\s|$)/
17
+ match === shebang
18
+ end
19
+
20
+ # Return the contents of the doctype tag if present, nil otherwise.
21
+ def doctype
22
+ return @doctype if instance_variable_defined? :@doctype
23
+
24
+ self =~ %r(\A\s*
25
+ (?:<\?.*?\?>\s*)? # possible <?xml...?> tag
26
+ <!DOCTYPE\s+(.+?)>
27
+ )xm
28
+ @doctype = $1
29
+ end
30
+
31
+ # Check if the doctype matches a given regexp or string
32
+ def doctype?(type=//)
33
+ type === doctype
34
+ end
35
+
36
+ # Return true if the result of lexing with the given lexer contains no
37
+ # error tokens.
38
+ def lexes_cleanly?(lexer)
39
+ lexer.lex(self) do |(tok, _)|
40
+ return false if tok.name == 'Error'
41
+ end
42
+
43
+ true
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,202 @@
1
+ module Rugments
2
+ class Theme
3
+ include Token::Tokens
4
+
5
+ class Style < Hash
6
+ def initialize(theme, hsh = {})
7
+ super()
8
+ @theme = theme
9
+ merge!(hsh)
10
+ end
11
+
12
+ [:fg, :bg].each do |mode|
13
+ define_method mode do
14
+ return self[mode] unless @theme
15
+ @theme.palette(self[mode]) if self[mode]
16
+ end
17
+ end
18
+
19
+ def render(selector, &b)
20
+ return enum_for(:render, selector).to_a.join("\n") unless b
21
+
22
+ return if empty?
23
+
24
+ yield "#{selector} {"
25
+ rendered_rules.each do |rule|
26
+ yield " #{rule};"
27
+ end
28
+ yield '}'
29
+ end
30
+
31
+ def rendered_rules(&b)
32
+ return enum_for(:rendered_rules) unless b
33
+ yield "color: #{fg}" if fg
34
+ yield "background-color: #{bg}" if bg
35
+ yield 'font-weight: bold' if self[:bold]
36
+ yield 'font-style: italic' if self[:italic]
37
+ yield 'text-decoration: underline' if self[:underline]
38
+
39
+ (self[:rules] || []).each(&b)
40
+ end
41
+ end
42
+
43
+ def styles
44
+ @styles ||= self.class.styles.dup
45
+ end
46
+
47
+ @palette = {}
48
+ def self.palette(arg = {})
49
+ @palette ||= InheritableHash.new(superclass.palette)
50
+
51
+ if arg.is_a? Hash
52
+ @palette.merge! arg
53
+ @palette
54
+ else
55
+ case arg
56
+ when /#[0-9a-f]+/i
57
+ arg
58
+ else
59
+ @palette[arg] || fail("not in palette: #{arg.inspect}")
60
+ end
61
+ end
62
+ end
63
+
64
+ @styles = {}
65
+ def self.styles
66
+ @styles ||= InheritableHash.new(superclass.styles)
67
+ end
68
+
69
+ def self.render(opts = {}, &b)
70
+ new(opts).render(&b)
71
+ end
72
+
73
+ class << self
74
+ def style(*tokens)
75
+ style = tokens.last.is_a?(Hash) ? tokens.pop : {}
76
+
77
+ style = Style.new(self, style)
78
+
79
+ tokens.each do |tok|
80
+ styles[tok] = style
81
+ end
82
+ end
83
+
84
+ def get_own_style(token)
85
+ token.token_chain.each do |anc|
86
+ return styles[anc] if styles[anc]
87
+ end
88
+
89
+ nil
90
+ end
91
+
92
+ def get_style(token)
93
+ get_own_style(token) || base_style
94
+ end
95
+
96
+ def base_style
97
+ styles[Token::Tokens::Text]
98
+ end
99
+
100
+ def name(n = nil)
101
+ return @name if n.nil?
102
+
103
+ @name = n.to_s
104
+ Theme.registry[@name] = self
105
+ end
106
+
107
+ def find(n)
108
+ registry[n.to_s]
109
+ end
110
+
111
+ def registry
112
+ @registry ||= {}
113
+ end
114
+ end
115
+ end
116
+
117
+ module HasModes
118
+ def mode(arg = :absent)
119
+ return @mode if arg == :absent
120
+
121
+ @modes ||= {}
122
+ @modes[arg] ||= get_mode(arg)
123
+ end
124
+
125
+ def get_mode(mode)
126
+ return self if self.mode == mode
127
+
128
+ new_name = "#{name}.#{mode}"
129
+ Class.new(self) { name(new_name); mode!(mode) }
130
+ end
131
+
132
+ def mode!(arg)
133
+ @mode = arg
134
+ send("make_#{arg}!")
135
+ end
136
+ end
137
+
138
+ class CSSTheme < Theme
139
+ def initialize(opts = {})
140
+ @scope = opts[:scope] || '.highlight'
141
+ end
142
+
143
+ def render(&b)
144
+ return enum_for(:render).to_a.join("\n") unless b
145
+
146
+ # shared styles for tableized line numbers
147
+ yield "#{@scope} table td { padding: 5px; }"
148
+ yield "#{@scope} table pre { margin: 0; }"
149
+
150
+ styles.each do |tok, style|
151
+ style.render(css_selector(tok), &b)
152
+ end
153
+ end
154
+
155
+ def render_base(selector, &b)
156
+ self.class.base_style.render(selector, &b)
157
+ end
158
+
159
+ def style_for(tok)
160
+ self.class.get_style(tok)
161
+ end
162
+
163
+ private
164
+
165
+ def css_selector(token)
166
+ inflate_token(token).map do |tok|
167
+ fail "unknown token: #{tok.inspect}" if tok.shortname.nil?
168
+
169
+ single_css_selector(tok)
170
+ end.join(', ')
171
+ end
172
+
173
+ def single_css_selector(token)
174
+ return @scope if token == Text
175
+
176
+ "#{@scope} .#{token.shortname}"
177
+ end
178
+
179
+ # yield all of the tokens that should be styled the same
180
+ # as the given token. Essentially this recursively all of
181
+ # the subtokens, except those which are more specifically
182
+ # styled.
183
+ def inflate_token(tok, &b)
184
+ return enum_for(:inflate_token, tok) unless block_given?
185
+
186
+ yield tok
187
+ tok.sub_tokens.each do |(_, st)|
188
+ next if styles[st]
189
+
190
+ inflate_token(st, &b)
191
+ end
192
+ end
193
+ end
194
+ end
195
+
196
+
197
+ require_relative 'themes/thankful_eyes'
198
+ require_relative 'themes/colorful'
199
+ require_relative 'themes/base16'
200
+ require_relative 'themes/github'
201
+ require_relative 'themes/monokai'
202
+ require_relative 'themes/monokai_sublime'