Class: Guardrails::Report::Style
- Inherits:
-
Object
- Object
- Guardrails::Report::Style
- Defined in:
- lib/guardrails/report/style.rb
Overview
ANSI styling for the text audit report. Three rules:
1. Colors only when the output is a real terminal (TTY check).
Piping to a file or `tee` produces plain text.
2. `NO_COLOR=1` always wins. (https://no-color.org/ convention.)
3. Tests pass a non-TTY StringIO and get plain output by default
— no need to scrub ANSI sequences out of every expectation.
Callers don’t decide whether to colorize; they just call ‘Style.severity(:error, “raw_color”)` and the style instance quietly emits ANSI or plain text based on those rules.
Constant Summary collapse
- ANSI =
Foreground codes, kept small. Bold + dim are modifiers, not colors — combined where we want emphasis without screaming.
{ reset: "\e[0m", bold: "\e[1m", dim: "\e[2m", red: "\e[31m", yellow: "\e[33m", green: "\e[32m", cyan: "\e[36m", blue: "\e[34m", magenta: "\e[35m" }.freeze
- SEVERITY_FORMAT =
Severity → (glyph, color) tuple. Glyphs are ASCII so they render in any terminal; we don’t use emoji or box-drawing for the per-line tags. The summary box uses light box-drawing characters separately, with an ASCII fallback for terminals that mangle them (rare in 2026 but possible over SSH/PTY).
{ error: { glyph: "x", color: :red, label: "ERROR" }, warning: { glyph: "!", color: :yellow, label: "WARNING" }, suggestion: { glyph: "i", color: :cyan, label: "SUGGEST" } }.freeze
Instance Method Summary collapse
-
#box_chars ⇒ Object
Box-drawing characters for the top-of-report summary header.
-
#color? ⇒ Boolean
True when we should emit ANSI sequences.
-
#colorize(text, style) ⇒ Object
Wrap text in an ANSI color code if ‘color?`, else return unchanged.
-
#initialize(io: $stdout, force: nil, no_color: nil) ⇒ Style
constructor
A new instance of Style.
-
#location(path) ⇒ Object
File:line:col, in dim so it recedes when the content next to it is what matters.
-
#section_heading(level, title) ⇒ Object
Section heading — bolded category label with a colored severity glyph in front.
-
#severity(level, category) ⇒ Object
Tag a finding line with its severity.
-
#suggestion(text) ⇒ Object
Inline suggestion arrow.
Constructor Details
#initialize(io: $stdout, force: nil, no_color: nil) ⇒ Style
Returns a new instance of Style.
42 43 44 45 46 |
# File 'lib/guardrails/report/style.rb', line 42 def initialize(io: $stdout, force: nil, no_color: nil) @io = io @force = force @no_color = no_color end |
Instance Method Details
#box_chars ⇒ Object
Box-drawing characters for the top-of-report summary header. Falls back to ASCII (‘+ - |`) when colors are off, since both behaviors track the same TTY/NO_COLOR signal.
106 107 108 109 110 111 112 |
# File 'lib/guardrails/report/style.rb', line 106 def box_chars if color? { tl: "╭", tr: "╮", bl: "╰", br: "╯", h: "─", v: "│", t: "├", b: "┤" } else { tl: "+", tr: "+", bl: "+", br: "+", h: "-", v: "|", t: "+", b: "+" } end end |
#color? ⇒ Boolean
True when we should emit ANSI sequences. Tests pass force: true/false to bypass auto-detection.
50 51 52 53 54 55 |
# File 'lib/guardrails/report/style.rb', line 50 def color? return @force unless @force.nil? return false if no_color_env? @io.respond_to?(:tty?) && @io.tty? end |
#colorize(text, style) ⇒ Object
Wrap text in an ANSI color code if ‘color?`, else return unchanged. `style` can be a single key or array (e.g. `[:bold, :red]`) — codes concatenate.
60 61 62 63 64 65 |
# File 'lib/guardrails/report/style.rb', line 60 def colorize(text, style) return text unless color? codes = Array(style).map { |k| ANSI.fetch(k) }.join "#{codes}#{text}#{ANSI[:reset]}" end |
#location(path) ⇒ Object
File:line:col, in dim so it recedes when the content next to it is what matters. Returns plain text when colors are off.
92 93 94 |
# File 'lib/guardrails/report/style.rb', line 92 def location(path) colorize(path, :dim) end |
#section_heading(level, title) ⇒ Object
Section heading — bolded category label with a colored severity glyph in front. Used for the per-detector section intro: ‘x ERROR — raw_color (82 findings)`.
84 85 86 87 88 |
# File 'lib/guardrails/report/style.rb', line 84 def section_heading(level, title) format = SEVERITY_FORMAT.fetch(level) glyph = colorize(format[:glyph], [:bold, format[:color]]) "#{glyph} #{colorize(format[:label], [:bold, format[:color]])} #{colorize("—", :dim)} #{colorize(title, :bold)}" end |
#severity(level, category) ⇒ Object
Tag a finding line with its severity. Output shape:
[error] raw_color
[warning] helper_recommended
[suggest] pattern
Padding aligns the brackets so columns line up across the report. Colorized form bolds the bracket+label.
75 76 77 78 79 |
# File 'lib/guardrails/report/style.rb', line 75 def severity(level, category) format = SEVERITY_FORMAT.fetch(level) tag = "[#{format[:label].downcase}]".ljust(10) "#{colorize(tag, [:bold, format[:color]])} #{category}" end |
#suggestion(text) ⇒ Object
Inline suggestion arrow. Always rendered the same way so the eye learns it: ‘→ <action>`. Cyan so it stands out from the finding line without competing with the severity color.
99 100 101 |
# File 'lib/guardrails/report/style.rb', line 99 def suggestion(text) "#{colorize("→", :cyan)} #{text}" end |