przn 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +496 -66
- data/default_theme.yml +98 -18
- data/exe/przn +8 -2
- data/lib/przn/audience_link.rb +3 -3
- data/lib/przn/code_highlighter.rb +165 -0
- data/lib/przn/controller.rb +506 -14
- data/lib/przn/echoes_client.rb +20 -0
- data/lib/przn/file_watcher.rb +69 -0
- data/lib/przn/image_util.rb +65 -2
- data/lib/przn/kitty_text.rb +2 -1
- data/lib/przn/mermaid_renderer.rb +88 -0
- data/lib/przn/parser.rb +417 -35
- data/lib/przn/prawn_pdf_exporter.rb +133 -15
- data/lib/przn/presentation.rb +43 -0
- data/lib/przn/presenter_renderer.rb +22 -8
- data/lib/przn/renderer.rb +2411 -305
- data/lib/przn/screenshot_pdf_exporter.rb +173 -5
- data/lib/przn/slide.rb +4 -2
- data/lib/przn/terminal.rb +10 -0
- data/lib/przn/theme.rb +85 -10
- data/lib/przn/version.rb +1 -1
- data/lib/przn.rb +11 -5
- data/sample/ruby-logo-LICENSE +15 -0
- data/sample/ruby.jpg +0 -0
- data/sample/ruby.png +0 -0
- data/sample/sample.md +285 -28
- data/sample/shapes.md +55 -0
- metadata +91 -77
- data/sample/doge.jpg +0 -0
- data/sample/doge.png +0 -0
data/default_theme.yml
CHANGED
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
# or pass `--theme path/to/your.yml` to override that.
|
|
6
6
|
# All keys are optional — unspecified values fall back to these defaults.
|
|
7
7
|
|
|
8
|
-
font:
|
|
9
|
-
family: #
|
|
10
|
-
size:
|
|
11
|
-
color: #
|
|
8
|
+
font: # body text typography (paragraphs, lists, h2–h6, code, tables, …)
|
|
9
|
+
family: # font family (terminal: OSC 66 f=, requires Echoes)
|
|
10
|
+
size: # OSC 66 scale: numeric (1–7) or named (xx-small … xxxx-large); default 2 (small)
|
|
11
|
+
color: # named ANSI or 6-digit hex; falls back to the terminal's default fg when unset
|
|
12
12
|
|
|
13
13
|
title: # h1 typography (slide titles); each attribute is independent of `font`
|
|
14
14
|
family: # font family (terminal: OSC 66 f=, requires Echoes)
|
|
@@ -18,26 +18,106 @@ title: # h1 typography (slide titles); each attribute is indepe
|
|
|
18
18
|
bullet: # unordered-list marker; also h2–h6 heading prefix
|
|
19
19
|
text: "・" # the glyph
|
|
20
20
|
size: # OSC 66 scale (1–7) for the bullet; default = body text's scale
|
|
21
|
+
color: # named ANSI or 6-digit hex; falls back to body text color (font.color) when unset
|
|
22
|
+
|
|
23
|
+
code: # fenced code block (and inline `<code>`) typography
|
|
24
|
+
family: # font family (terminal: OSC 66 f=, requires Echoes); falls back to font.family
|
|
25
|
+
size: # OSC 66 scale: numeric (1–7) or named (xx-small … xxxx-large); default = body text's scale
|
|
26
|
+
color: # fg for all code text — fenced blocks and inline `<code>`; named ANSI or 6-digit hex; falls back to body fg
|
|
27
|
+
bg: # block background (fenced only); named ANSI or 6-digit hex; default = dim gray (ANSI 256-color 236)
|
|
28
|
+
highlight: # Rouge theme name for syntax highlighting (e.g. monokai, gruvbox, github.dark, base16.solarized.dark);
|
|
29
|
+
# unset = the built-in ANSI palette (picks up the terminal's colour theme — works on light or dark)
|
|
21
30
|
|
|
22
31
|
# Default slide background, applied when a slide has no <bg .../> directive.
|
|
23
|
-
#
|
|
24
|
-
#
|
|
32
|
+
# `color` / `from` / `to` / `angle` use Echoes' OSC 7772 (no-op on other
|
|
33
|
+
# terminals). `image` uses the Kitty Graphics Protocol (works on Kitty,
|
|
34
|
+
# Echoes, Wezterm, Ghostty — PNG only) and wins over `color` / gradient
|
|
35
|
+
# when both are set. All unset = no background override.
|
|
25
36
|
background:
|
|
37
|
+
image: # background image path, relative to the deck; PNG only
|
|
26
38
|
color: # solid color, e.g. "#1a1a2e"
|
|
27
39
|
from: # gradient endpoint, e.g. "#1a1a2e"
|
|
28
40
|
to: # gradient endpoint, e.g. "#16213e"
|
|
29
41
|
angle: # gradient angle in degrees (0 = left→right, 90 = bottom→top)
|
|
30
42
|
type: # gradient type; defaults to "linear"
|
|
31
43
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
#
|
|
40
|
-
# the
|
|
41
|
-
#
|
|
42
|
-
#
|
|
43
|
-
#
|
|
44
|
+
# Bottom-of-screen slide counter. Without `duration:`, przn shows a
|
|
45
|
+
# plain " N / M " counter at the bottom-right. Set `duration:` to opt
|
|
46
|
+
# into the 🐇 / 🐢 runner bar (a nod to the Rabbit presentation tool):
|
|
47
|
+
# slide-progress (🐇) and elapsed-vs-goal (🐢) anchored between the
|
|
48
|
+
# left (current) and right (total) numbers.
|
|
49
|
+
#
|
|
50
|
+
# `rabbit:` / `turtle:` are the raw escape sequences emitted at the
|
|
51
|
+
# runner positions. The defaults are Echoes-private OSC 7772 multicell
|
|
52
|
+
# frames with `flip=h` so the emojis face right (towards the goal at the
|
|
53
|
+
# right edge); on terminals that don't speak OSC 7772 (plain kitty,
|
|
54
|
+
# wezterm, …) the runner emojis don't render. To stay portable, override
|
|
55
|
+
# with a plain glyph or a standard OSC 66 multicell — at the cost of the
|
|
56
|
+
# flip. The renderer writes the value verbatim; whatever lands here goes
|
|
57
|
+
# straight to the terminal.
|
|
58
|
+
counter:
|
|
59
|
+
color: # named ANSI or 6-digit hex; default = dim
|
|
60
|
+
duration: # "30m", "1h30m", "1800s", or plain integer seconds; opt in to the runner bar
|
|
61
|
+
rabbit: "\e]7772;multicell;s=2:flip=h;🐇\a"
|
|
62
|
+
turtle: "\e]7772;multicell;s=2:flip=h;🐢\a"
|
|
63
|
+
|
|
64
|
+
# Slide-to-slide fade transition. Opt-in per slide via the h1 IAL —
|
|
65
|
+
# `# Title {transition=fade}`. The slide carrying the attribute fades
|
|
66
|
+
# out when leaving it and fades in when arriving at it; the two halves
|
|
67
|
+
# are independent (slide A → B can fade-out from A and snap into B if
|
|
68
|
+
# only A opts in). `duration:` controls the total ms of one half. The
|
|
69
|
+
# fade only paints text — Kitty-graphics images and shapes stay opaque
|
|
70
|
+
# in v1. Echoes-only (uses OSC 7772 cell-alpha); other terminals snap.
|
|
71
|
+
transition:
|
|
72
|
+
duration: 200 # ms per fade half — full A→B fade is twice this when both sides opt in
|
|
73
|
+
|
|
74
|
+
# Fragment animations (`<at fragment="fade-up">`, etc.). `duration:` is
|
|
75
|
+
# the per-fragment animation length in ms — drives the lerp from the
|
|
76
|
+
# fragment's initial offset position + alpha 0 to its declared position
|
|
77
|
+
# + alpha 1. Same OSC 7772 cell-alpha plumbing as `<action opacity>`;
|
|
78
|
+
# the x/y move uses the existing action interpolation so non-Echoes
|
|
79
|
+
# terminals still see the slide-in even when the fade portion snaps.
|
|
80
|
+
fragment:
|
|
81
|
+
duration: 300 # ms per fragment animation
|
|
82
|
+
|
|
83
|
+
# Slide layouts. Each layout names an ordered list of slots; a slot is a
|
|
84
|
+
# rectangular region with x / y / width / height (1-based cells, or N%
|
|
85
|
+
# of the terminal width/height). A slide picks a layout via an h1 IAL,
|
|
86
|
+
# e.g. `# Title {layout=two-column}`. The `title` slot, when present,
|
|
87
|
+
# is auto-filled from the h1; other slots fill in declaration order,
|
|
88
|
+
# advanced by a `<slot/>` (or `<slot name="..."/>`) marker.
|
|
89
|
+
#
|
|
90
|
+
# Two layouts have special semantics:
|
|
91
|
+
# `default` — theme-wide fallback for slides without an
|
|
92
|
+
# `{layout=...}` IAL. Shipped identical to
|
|
93
|
+
# `title-content`: centered title band across the top,
|
|
94
|
+
# content below. Override it in your own theme to give
|
|
95
|
+
# every plain slide a different layout.
|
|
96
|
+
# `cover` — automatic for slide 0 when no IAL is set, roughly
|
|
97
|
+
# emulating Keynote's "Title" slide: big centered
|
|
98
|
+
# heading near the middle, smaller subtitle near the
|
|
99
|
+
# bottom. Override / delete from your own theme to opt
|
|
100
|
+
# out — explicit `{layout=...}` on slide 0 still wins.
|
|
101
|
+
# Other named layouts are opt-in via `# Title {layout=name}`.
|
|
102
|
+
layouts:
|
|
103
|
+
default:
|
|
104
|
+
- {name: title, x: center, y: 3, width: 90%, height: 6}
|
|
105
|
+
- {name: content, x: 5, y: 10, width: 90%, height: 80%}
|
|
106
|
+
cover:
|
|
107
|
+
- {name: title, x: center, y: 35%, width: 100%, height: 25%, size: xxx-large}
|
|
108
|
+
- {name: subtitle, x: center, y: 80%, width: 100%, height: 15%}
|
|
109
|
+
title-only:
|
|
110
|
+
- {name: title, x: center, y: 40%, width: 90%, height: 20%}
|
|
111
|
+
takahashi:
|
|
112
|
+
- {name: title, x: center, y: center, width: 100%, height: 100%, size: xxxx-large}
|
|
113
|
+
title-content:
|
|
114
|
+
- {name: title, x: center, y: 3, width: 90%, height: 6}
|
|
115
|
+
- {name: content, x: 5, y: 10, width: 90%, height: 80%}
|
|
116
|
+
two-column:
|
|
117
|
+
- {name: title, x: center, y: 3, width: 90%, height: 6}
|
|
118
|
+
- {name: left, x: 5, y: 10, width: 45%, height: 80%}
|
|
119
|
+
- {name: right, x: 50%, y: 10, width: 45%, height: 80%}
|
|
120
|
+
photo-caption:
|
|
121
|
+
- {name: title, x: center, y: 3, width: 90%, height: 6}
|
|
122
|
+
- {name: image, x: 5, y: 10, width: 60%, height: 70%}
|
|
123
|
+
- {name: caption, x: 65%, y: 10, width: 30%, height: 70%}
|
data/exe/przn
CHANGED
|
@@ -17,11 +17,16 @@ OptionParser.new do |opts|
|
|
|
17
17
|
end
|
|
18
18
|
}
|
|
19
19
|
opts.on('-o', '--output FILE', 'Output file path for export') { |v| options[:output] = v }
|
|
20
|
+
opts.on('--pdf-quality LEVEL',
|
|
21
|
+
'PDF size/quality preset for --export pdf: lossless|low|medium|high|max (default: medium)') { |v|
|
|
22
|
+
options[:pdf_quality] = v
|
|
23
|
+
}
|
|
20
24
|
opts.on('--theme FILE', 'Theme file (YAML)') { |v| options[:theme] = v }
|
|
21
25
|
opts.on('--generate-theme', 'Generate theme.yml in current directory') { options[:generate_theme] = true }
|
|
22
26
|
opts.on('--present', 'Open the deck in presenter mode (auto-spawns an audience window on the secondary display)') { options[:present] = true }
|
|
23
27
|
opts.on('--audience', 'Run as the audience receiver (spawned by --present; expects --socket)') { options[:audience] = true }
|
|
24
28
|
opts.on('--socket PATH', 'Unix socket path used by --present/--audience to coordinate') { |v| options[:socket] = v }
|
|
29
|
+
opts.on('--no-watch', 'Disable hot reload (default on; auto-reloads the deck and theme.yml when they change on disk)') { options[:watch] = false }
|
|
25
30
|
end.parse!
|
|
26
31
|
|
|
27
32
|
if options[:generate_theme]
|
|
@@ -56,7 +61,7 @@ when 'pdf'
|
|
|
56
61
|
require_relative '../lib/przn/screenshot_pdf_exporter'
|
|
57
62
|
|
|
58
63
|
output = options[:output] || File.basename(file, File.extname(file)) + '.pdf'
|
|
59
|
-
Przn.export_pdf(file, output, theme: theme)
|
|
64
|
+
Przn.export_pdf(file, output, theme: theme, quality: options[:pdf_quality])
|
|
60
65
|
when 'prawn'
|
|
61
66
|
require_relative '../lib/przn/prawn_pdf_exporter'
|
|
62
67
|
|
|
@@ -72,6 +77,7 @@ else
|
|
|
72
77
|
elsif options[:present]
|
|
73
78
|
Przn.present(file, theme: theme, theme_path: options[:theme]).run
|
|
74
79
|
else
|
|
75
|
-
Przn.start(file, theme: theme, start_at: start_at
|
|
80
|
+
Przn.start(file, theme: theme, theme_path: options[:theme], start_at: start_at,
|
|
81
|
+
watch: options.fetch(:watch, true)).run
|
|
76
82
|
end
|
|
77
83
|
end
|
data/lib/przn/audience_link.rb
CHANGED
|
@@ -21,7 +21,7 @@ module Przn
|
|
|
21
21
|
File.unlink(path) if File.exist?(path)
|
|
22
22
|
server = UNIXServer.new(path)
|
|
23
23
|
client = server.accept
|
|
24
|
-
|
|
24
|
+
send_message(client, {type: "ready"})
|
|
25
25
|
while (line = client.gets)
|
|
26
26
|
msg = JSON.parse(line.chomp, symbolize_names: true)
|
|
27
27
|
break if msg[:type] == "quit"
|
|
@@ -36,13 +36,13 @@ module Przn
|
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
# Presenter-side: connect to an audience socket at `path` and return a
|
|
39
|
-
#
|
|
39
|
+
# UNIXSocket to hand to `send_message` and `#close`. Caller drives the
|
|
40
40
|
# protocol from the controller.
|
|
41
41
|
def connect(path)
|
|
42
42
|
UNIXSocket.new(path)
|
|
43
43
|
end
|
|
44
44
|
|
|
45
|
-
def
|
|
45
|
+
def send_message(io, msg)
|
|
46
46
|
io.puts(JSON.generate(msg))
|
|
47
47
|
rescue Errno::EPIPE, IOError
|
|
48
48
|
# Other side hung up — caller decides whether to keep going.
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Przn
|
|
4
|
+
# Tokenizer wrapper around Rouge for fenced code blocks. Maps Rouge's
|
|
5
|
+
# token tree to either a hand-tuned ANSI-name palette (the default —
|
|
6
|
+
# picks up the user's terminal colour theme so code reads naturally
|
|
7
|
+
# against light or dark backgrounds), or to a named Rouge theme like
|
|
8
|
+
# `monokai` / `gruvbox` / `github.dark` whose styles return absolute
|
|
9
|
+
# `#RRGGBB` colours. The choice rides on `theme.code.highlight` in
|
|
10
|
+
# theme.yml; unset keeps the ANSI palette.
|
|
11
|
+
module CodeHighlighter
|
|
12
|
+
# Rouge is required lazily so a `require 'przn'` doesn't pull in
|
|
13
|
+
# the full 200-language lexer tree at startup for decks that don't
|
|
14
|
+
# use fenced code blocks at all.
|
|
15
|
+
@loaded = false
|
|
16
|
+
|
|
17
|
+
# Memoized tokenization keyed by `[code, language]`. Warmed in the
|
|
18
|
+
# background by `Renderer#preload` so that by the time the user
|
|
19
|
+
# navigates to a slide, its fenced blocks are already tokenized —
|
|
20
|
+
# the first visit is now as fast as the second. Cache entries are
|
|
21
|
+
# small (a few KB per block) and never invalidated; the keys
|
|
22
|
+
# change automatically when slide content changes.
|
|
23
|
+
@cache = {}
|
|
24
|
+
@cache_mutex = Mutex.new
|
|
25
|
+
|
|
26
|
+
# Memoised Rouge::Theme instances keyed by name. `Rouge::Theme.find`
|
|
27
|
+
# is cheap but `.new` allocates per call; one instance per theme is
|
|
28
|
+
# plenty since `style_for` is pure. Returns nil for unknown names —
|
|
29
|
+
# caller falls through to TOKEN_COLORS, matching the same
|
|
30
|
+
# silent-fallback philosophy as an unknown lexer.
|
|
31
|
+
@rouge_theme_cache = {}
|
|
32
|
+
@rouge_theme_mutex = Mutex.new
|
|
33
|
+
|
|
34
|
+
# Mapping from Rouge token qualnames to ANSI color names (resolved
|
|
35
|
+
# through Parser::NAMED_COLORS by the renderer's `color_code`).
|
|
36
|
+
# Lookups walk up the dotted hierarchy — `Literal.String.Single`
|
|
37
|
+
# falls back to `Literal.String`, then `Literal`, before giving up.
|
|
38
|
+
TOKEN_COLORS = {
|
|
39
|
+
'Keyword' => 'cyan',
|
|
40
|
+
'Keyword.Constant' => 'magenta',
|
|
41
|
+
'Keyword.Declaration' => 'cyan',
|
|
42
|
+
'Keyword.Namespace' => 'cyan',
|
|
43
|
+
'Keyword.Pseudo' => 'cyan',
|
|
44
|
+
'Keyword.Reserved' => 'cyan',
|
|
45
|
+
'Keyword.Type' => 'yellow',
|
|
46
|
+
'Name.Builtin' => 'cyan',
|
|
47
|
+
'Name.Builtin.Pseudo' => 'magenta',
|
|
48
|
+
'Name.Class' => 'yellow',
|
|
49
|
+
'Name.Constant' => 'yellow',
|
|
50
|
+
'Name.Decorator' => 'magenta',
|
|
51
|
+
'Name.Exception' => 'yellow',
|
|
52
|
+
'Name.Function' => 'green',
|
|
53
|
+
'Name.Function.Magic' => 'magenta',
|
|
54
|
+
'Name.Namespace' => 'yellow',
|
|
55
|
+
'Name.Tag' => 'cyan',
|
|
56
|
+
'Name.Variable.Class' => 'yellow',
|
|
57
|
+
'Name.Variable.Global' => 'red',
|
|
58
|
+
'Name.Variable.Instance' => 'red',
|
|
59
|
+
'Literal.String' => 'green',
|
|
60
|
+
'Literal.String.Doc' => 'green',
|
|
61
|
+
'Literal.String.Symbol' => 'yellow',
|
|
62
|
+
'Literal.String.Regex' => 'magenta',
|
|
63
|
+
'Literal.String.Escape' => 'magenta',
|
|
64
|
+
'Literal.String.Interpol' => 'yellow',
|
|
65
|
+
'Literal.Number' => 'magenta',
|
|
66
|
+
'Comment' => 'bright_black',
|
|
67
|
+
'Operator' => 'red',
|
|
68
|
+
'Operator.Word' => 'cyan',
|
|
69
|
+
# Diff / patch hunks, coloured the way `git diff` does: removed
|
|
70
|
+
# lines red, added lines green, file headers stand out.
|
|
71
|
+
'Generic.Deleted' => 'red',
|
|
72
|
+
'Generic.Inserted' => 'green',
|
|
73
|
+
'Generic.Heading' => 'yellow',
|
|
74
|
+
'Generic.Subheading' => 'cyan'
|
|
75
|
+
}.freeze
|
|
76
|
+
|
|
77
|
+
module_function
|
|
78
|
+
|
|
79
|
+
# Tokenize `code` with the Rouge lexer for `language` and return an
|
|
80
|
+
# array of `[color_or_nil, value_string]` token pairs in source
|
|
81
|
+
# order. `nil` color means "use the default foreground". Returns
|
|
82
|
+
# nil if Rouge isn't available, the language is unknown, or
|
|
83
|
+
# `language` is blank — in any of those cases the renderer falls
|
|
84
|
+
# back to its plain-text path.
|
|
85
|
+
#
|
|
86
|
+
# `theme:` picks the colour mapping. `nil` (the default) walks the
|
|
87
|
+
# built-in ANSI-name TOKEN_COLORS table. A string like `"monokai"` /
|
|
88
|
+
# `"gruvbox"` / `"github.dark"` resolves to a Rouge theme and uses
|
|
89
|
+
# its per-token `#RRGGBB` styles instead. Unknown theme names
|
|
90
|
+
# silently fall through to TOKEN_COLORS.
|
|
91
|
+
def highlight(code, language, theme: nil)
|
|
92
|
+
return nil if code.nil? || language.nil? || language.empty?
|
|
93
|
+
lang = language.to_s.downcase
|
|
94
|
+
# Theme rides in the cache key so two themes can coexist for the
|
|
95
|
+
# same `[code, lang]` without trampling each other.
|
|
96
|
+
key = [code, lang, theme]
|
|
97
|
+
cached = @cache_mutex.synchronize { @cache[key] }
|
|
98
|
+
return cached if cached
|
|
99
|
+
|
|
100
|
+
load_rouge
|
|
101
|
+
return nil unless @loaded
|
|
102
|
+
lexer = Rouge::Lexer.find(lang)
|
|
103
|
+
return nil unless lexer
|
|
104
|
+
|
|
105
|
+
rouge_theme = theme && resolve_rouge_theme(theme)
|
|
106
|
+
|
|
107
|
+
tokens = []
|
|
108
|
+
lexer.lex(code) { |tok, val| tokens << [color_for(tok, rouge_theme), val] }
|
|
109
|
+
@cache_mutex.synchronize { @cache[key] = tokens }
|
|
110
|
+
tokens
|
|
111
|
+
rescue StandardError
|
|
112
|
+
# A lexer crash shouldn't kill the slide — fall back to plain.
|
|
113
|
+
nil
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Resolve a Rouge theme name to a memoised theme instance.
|
|
117
|
+
# `Rouge::Theme.find` returns the theme CLASS (or nil); we `.new`
|
|
118
|
+
# it once and reuse. Anything Rouge doesn't recognise (typos,
|
|
119
|
+
# retired names, garbage) returns nil and the caller falls back
|
|
120
|
+
# to TOKEN_COLORS.
|
|
121
|
+
def resolve_rouge_theme(name)
|
|
122
|
+
cached = @rouge_theme_mutex.synchronize { @rouge_theme_cache[name] }
|
|
123
|
+
return cached if cached
|
|
124
|
+
return nil if @rouge_theme_mutex.synchronize { @rouge_theme_cache.key?(name) }
|
|
125
|
+
|
|
126
|
+
klass = Rouge::Theme.find(name.to_s)
|
|
127
|
+
instance = klass && klass.new
|
|
128
|
+
@rouge_theme_mutex.synchronize { @rouge_theme_cache[name] = instance }
|
|
129
|
+
instance
|
|
130
|
+
rescue StandardError
|
|
131
|
+
nil
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Resolve a token's colour. With a Rouge theme, ask the theme for
|
|
135
|
+
# its `style_for(token).fg` — a `#RRGGBB` string or nil. Without
|
|
136
|
+
# one, walk the qualname up the dotted hierarchy through
|
|
137
|
+
# TOKEN_COLORS: `Literal.String.Single` → `Literal.String` →
|
|
138
|
+
# `Literal` → nil. nil means "use the default fg."
|
|
139
|
+
def color_for(token, rouge_theme = nil)
|
|
140
|
+
if rouge_theme
|
|
141
|
+
style = rouge_theme.style_for(token)
|
|
142
|
+
return style && style.fg
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
name = token.qualname
|
|
146
|
+
while name && !name.empty?
|
|
147
|
+
return TOKEN_COLORS[name] if TOKEN_COLORS.key?(name)
|
|
148
|
+
i = name.rindex('.')
|
|
149
|
+
return nil unless i
|
|
150
|
+
name = name[0, i]
|
|
151
|
+
end
|
|
152
|
+
nil
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def load_rouge
|
|
156
|
+
return if @loaded
|
|
157
|
+
begin
|
|
158
|
+
require 'rouge'
|
|
159
|
+
@loaded = true
|
|
160
|
+
rescue LoadError
|
|
161
|
+
@loaded = false
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|