blob: d6d2ddfeace1e6d5224c30a7979cf9ff3838d1d1 [file] [log] [blame]
# -*- coding: utf-8 -*- #
module Rouge
class Theme
include Token::Tokens
class Style < Hash
def initialize(theme, hsh={})
super()
@theme = theme
merge!(hsh)
end
[:fg, :bg].each do |mode|
define_method mode do
return self[mode] unless @theme
@theme.palette(self[mode]) if self[mode]
end
end
def render(selector, &b)
return enum_for(:render, selector).to_a.join("\n") unless b
return if empty?
yield "#{selector} {"
rendered_rules.each do |rule|
yield " #{rule};"
end
yield "}"
end
def rendered_rules(&b)
return enum_for(:rendered_rules) unless b
yield "color: #{fg}" if fg
yield "background-color: #{bg}" if bg
yield "font-weight: bold" if self[:bold]
yield "font-style: italic" if self[:italic]
yield "text-decoration: underline" if self[:underline]
(self[:rules] || []).each(&b)
end
end
def styles
@styles ||= self.class.styles.dup
end
@palette = {}
def self.palette(arg={})
@palette ||= InheritableHash.new(superclass.palette)
if arg.is_a? Hash
@palette.merge! arg
@palette
else
case arg
when /#[0-9a-f]+/i
arg
else
@palette[arg] or raise "not in palette: #{arg.inspect}"
end
end
end
def palette(*a) self.class.palette(*a) end
@styles = {}
def self.styles
@styles ||= InheritableHash.new(superclass.styles)
end
def self.render(opts={}, &b)
new(opts).render(&b)
end
class << self
def style(*tokens)
style = tokens.last.is_a?(Hash) ? tokens.pop : {}
tokens.each do |tok|
styles[tok] = style
end
end
def get_own_style(token)
token.token_chain.reverse_each do |anc|
return Style.new(self, styles[anc]) if styles[anc]
end
nil
end
def get_style(token)
get_own_style(token) || base_style
end
def base_style
get_own_style(Token::Tokens::Text)
end
def name(n=nil)
return @name if n.nil?
@name = n.to_s
Theme.registry[@name] = self
end
def find(n)
registry[n.to_s]
end
def registry
@registry ||= {}
end
end
end
module HasModes
def mode(arg=:absent)
return @mode if arg == :absent
@modes ||= {}
@modes[arg] ||= get_mode(arg)
end
def get_mode(mode)
return self if self.mode == mode
new_name = "#{self.name}.#{mode}"
Class.new(self) { name(new_name); mode!(mode) }
end
def mode!(arg)
@mode = arg
send("make_#{arg}!")
end
end
class CSSTheme < Theme
def initialize(opts={})
@scope = opts[:scope] || '.highlight'
end
def render(&b)
return enum_for(:render).to_a.join("\n") unless b
# shared styles for tableized line numbers
yield "#{@scope} table td { padding: 5px; }"
yield "#{@scope} table pre { margin: 0; }"
styles.each do |tok, style|
Style.new(self, style).render(css_selector(tok), &b)
end
end
def render_base(selector, &b)
self.class.base_style.render(selector, &b)
end
def style_for(tok)
self.class.get_style(tok)
end
private
def css_selector(token)
inflate_token(token).map do |tok|
raise "unknown token: #{tok.inspect}" if tok.shortname.nil?
single_css_selector(tok)
end.join(', ')
end
def single_css_selector(token)
return @scope if token == Text
"#{@scope} .#{token.shortname}"
end
# yield all of the tokens that should be styled the same
# as the given token. Essentially this recursively all of
# the subtokens, except those which are more specifically
# styled.
def inflate_token(tok, &b)
return enum_for(:inflate_token, tok) unless block_given?
yield tok
tok.sub_tokens.each do |(_, st)|
next if styles[st]
inflate_token(st, &b)
end
end
end
end