Module: Pangea::CLI::Theme
- Defined in:
- lib/pangea/cli/theme.rb
Overview
Nord-palette ANSI theming for the pangea CLI.
Emits 24-bit color escape sequences using the Nord palette (www.nordtheme.com/) mapped to semantic roles. Falls back to plain text when:
-
Neither stdout nor stderr is a TTY (piped / captured output)
-
‘NO_COLOR` is set (no-color.org/)
-
‘PANGEA_NO_COLOR` is set
-
‘TERM` is `dumb`
Nord palette reference:
Polar Night (backgrounds, muted text):
nord0 #2E3440
nord1 #3B4252
nord2 #434C5E
nord3 #4C566A ← most-used muted foreground
Snow Storm (foregrounds):
nord4 #D8DEE9
nord5 #E5E9F0
nord6 #ECEFF4 ← brightest fg (resource addresses, headings)
Frost (info / progress / primary accent):
nord7 #8FBCBB ← headings (teal)
nord8 #88C0D0 ← primary info/progress (cyan)
nord9 #81A1C1 ← paths, secondary (blue)
nord10 #5E81AC ← backgrounds / rarely used
Aurora (semantic accents):
nord11 #BF616A ← error (red)
nord12 #D08770 ← destroy (orange)
nord13 #EBCB8B ← warning, replace, counts (yellow)
nord14 #A3BE8C ← success, create (green)
nord15 #B48EAD ← meta, import (purple)
Constant Summary collapse
- NORD =
rgb triples
{ nord0: [46, 52, 64], nord1: [59, 66, 82], nord2: [67, 76, 94], nord3: [76, 86, 106], nord4: [216, 222, 233], nord5: [229, 233, 240], nord6: [236, 239, 244], nord7: [143, 188, 187], nord8: [136, 192, 208], nord9: [129, 161, 193], nord10: [94, 129, 172], nord11: [191, 97, 106], nord12: [208, 135, 112], nord13: [235, 203, 139], nord14: [163, 190, 140], nord15: [180, 142, 173], }.freeze
- SEMANTICS =
Semantic role → palette entry.
{ # Structural label: :nord3, # "[pangea]" prefix (muted) divider: :nord3, # horizontal rules, tree glyphs heading: :nord7, # section titles resource: :nord6, # resource addresses (brightest fg) path: :nord9, # file paths namespace: :nord15, # namespaces / envs count: :nord13, # numeric counts (emphasis) # Status levels info: :nord8, # neutral info / progress success: :nord14, # success / create warning: :nord13, # warning error: :nord11, # error transient: :nord12, # transient / retry-worthy error deprecation: :nord3, # dropped deprecation summary (muted) # Change actions create: :nord14, # + (green) update: :nord8, # ~ (cyan) delete: :nord11, # - (red) replace: :nord13, # ± (yellow) read: :nord9, # > (blue) import: :nord15, # → (purple) noop: :nord3, # = (muted) }.freeze
- ROLES =
Ordered list of roles used to test output is theme-reachable.
SEMANTICS.keys.freeze
- MARKER =
The blackmatter family marker. Adopted from the blackmatter-shell starship prompt (‘[❄](bold #88C0D0)` — Nord frost cyan snowflake). Every pangea-emitted line begins with this glyph, colored by the line’s semantic level — visual coherence with the rest of the pleme-io / blackmatter-themed shell.
'❄'
Class Method Summary collapse
-
.action_glyph(action) ⇒ Object
Action glyph + color for a planned change.
-
.bold(text) ⇒ Object
Bold emphasis.
-
.color(role, text) ⇒ Object
Colorise ‘text` with the given semantic role.
-
.count(n, role: :count) ⇒ Object
Pretty-print a count with semantic color.
-
.dim(text) ⇒ Object
Dim emphasis.
-
.enabled? ⇒ Boolean
Whether ANSI is currently enabled (TTY + not disabled by env).
- .error_glyph ⇒ Object
-
.log(message, level: :info, io: $stderr) ⇒ Object
Standard pangea-prefixed log line.
-
.marker(level: :info) ⇒ Object
The marker in its level color — ‘❄` tinted by semantic role.
-
.override_enabled(value) ⇒ Object
Force-enable (for testing).
-
.progress_glyph ⇒ Object
Status glyphs for apply progress (inherit action colors via semantics).
-
.rgb_for(role) ⇒ Object
RGB tuple for a role, or nil if unknown.
-
.section(title, io: $stderr, width: 72, level: :heading) ⇒ Object
Section divider with snowflake title.
-
.structured_log(*parts, io: $stderr, marker_level: :info) ⇒ Object
Structured log with multiple highlighted fragments.
- .success_glyph ⇒ Object
Class Method Details
.action_glyph(action) ⇒ Object
Action glyph + color for a planned change. Returns a two-character colored string suitable for inline use (e.g., “ #glyph addr”).
175 176 177 178 179 180 181 182 183 184 185 186 187 |
# File 'lib/pangea/cli/theme.rb', line 175 def action_glyph(action) glyph, role = case action when 'create' then ['+', :create] when 'update' then ['~', :update] when 'delete' then ['-', :delete] when 'replace' then ['±', :replace] when 'read' then ['>', :read] when 'import' then ['→', :import] when 'no-op' then ['=', :noop] else ['?', :info] end color(role, glyph) end |
.bold(text) ⇒ Object
Bold emphasis.
122 123 124 125 |
# File 'lib/pangea/cli/theme.rb', line 122 def bold(text) return text.to_s unless enabled? "\e[1m#{text}\e[0m" end |
.color(role, text) ⇒ Object
Colorise ‘text` with the given semantic role.
114 115 116 117 118 119 |
# File 'lib/pangea/cli/theme.rb', line 114 def color(role, text) return text.to_s unless enabled? rgb = rgb_for(role) return text.to_s unless rgb "\e[38;2;#{rgb[0]};#{rgb[1]};#{rgb[2]}m#{text}\e[0m" end |
.count(n, role: :count) ⇒ Object
Pretty-print a count with semantic color. Zero counts are muted.
195 196 197 198 199 200 201 202 |
# File 'lib/pangea/cli/theme.rb', line 195 def count(n, role: :count) text = n.to_s if n.to_i.zero? color(:deprecation, text) else color(role, text) end end |
.dim(text) ⇒ Object
Dim emphasis.
128 129 130 131 |
# File 'lib/pangea/cli/theme.rb', line 128 def dim(text) return text.to_s unless enabled? "\e[2m#{text}\e[0m" end |
.enabled? ⇒ Boolean
Whether ANSI is currently enabled (TTY + not disabled by env).
98 99 100 101 |
# File 'lib/pangea/cli/theme.rb', line 98 def enabled? return @enabled unless @enabled.nil? @enabled = compute_enabled end |
.error_glyph ⇒ Object
192 |
# File 'lib/pangea/cli/theme.rb', line 192 def error_glyph = color(:error, '✗') |
.log(message, level: :info, io: $stderr) ⇒ Object
Standard pangea-prefixed log line. Emits to stderr by default. ‘level` is a semantic role (info / success / warning / error) and drives both the snowflake color and the body color.
141 142 143 |
# File 'lib/pangea/cli/theme.rb', line 141 def log(, level: :info, io: $stderr) io.puts "#{marker(level: level)} #{color(level, )}" end |
.marker(level: :info) ⇒ Object
The marker in its level color — ‘❄` tinted by semantic role.
134 135 136 |
# File 'lib/pangea/cli/theme.rb', line 134 def marker(level: :info) bold(color(level, MARKER)) end |
.override_enabled(value) ⇒ Object
Force-enable (for testing). Pass nil to restore auto-detect.
104 105 106 |
# File 'lib/pangea/cli/theme.rb', line 104 def override_enabled(value) @enabled = value end |
.progress_glyph ⇒ Object
Status glyphs for apply progress (inherit action colors via semantics).
190 |
# File 'lib/pangea/cli/theme.rb', line 190 def progress_glyph = color(:info, '➜') |
.rgb_for(role) ⇒ Object
RGB tuple for a role, or nil if unknown.
109 110 111 |
# File 'lib/pangea/cli/theme.rb', line 109 def rgb_for(role) NORD[SEMANTICS[role]] end |
.section(title, io: $stderr, width: 72, level: :heading) ⇒ Object
Section divider with snowflake title. Used to separate synth/init/ plan/apply phases when the output would otherwise run together. Shape: ‘❄ title ─────────────────────`
164 165 166 167 168 169 170 171 |
# File 'lib/pangea/cli/theme.rb', line 164 def section(title, io: $stderr, width: 72, level: :heading) label = " #{title} " head = "#{marker(level: level)}#{color(level, label)}" # account for the marker glyph + space that don't count toward Nord widths trailing_len = [width - label.length - 2, 4].max trailing = '─' * trailing_len io.puts "#{head}#{color(:divider, trailing)}" end |
.structured_log(*parts, io: $stderr, marker_level: :info) ⇒ Object
Structured log with multiple highlighted fragments. Example:
Theme.structured_log(
[:info, 'Synthesizing'],
[:path, '/path/to/template.rb'],
[:deprecation, 'in namespace'],
[:namespace, 'development'],
)
Produces: ‘❄ Synthesizing /path/to/template.rb in namespace development` with the snowflake in the info color and each token colored per its role.
156 157 158 159 |
# File 'lib/pangea/cli/theme.rb', line 156 def structured_log(*parts, io: $stderr, marker_level: :info) body = parts.map { |(role, text)| color(role, text) }.join(' ') io.puts "#{marker(level: marker_level)} #{body}" end |
.success_glyph ⇒ Object
191 |
# File 'lib/pangea/cli/theme.rb', line 191 def success_glyph = color(:success, '✔') |