Skip to content

Commit 5127b07

Browse files
authored
Merge pull request #450 from control-toolbox/unify-ansi-display
Unify ANSI display formatting in Core module
2 parents d2eb7f5 + 3045680 commit 5127b07

7 files changed

Lines changed: 258 additions & 107 deletions

File tree

src/CTBase.jl

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,14 @@ import DocStringExtensions: TYPEDEF, TYPEDSIGNATURES
1212
# MODULAR ORGANIZATION
1313
# ============================================================================ #
1414

15-
# Exceptions module - enhanced error handling system (must load first)
16-
include(joinpath(@__DIR__, "Exceptions", "Exceptions.jl"))
17-
using .Exceptions
18-
19-
# Core module - fundamental types and utilities
15+
# Core module - fundamental types and utilities (must load first)
2016
include(joinpath(@__DIR__, "Core", "Core.jl"))
2117
using .Core
2218

19+
# Exceptions module - enhanced error handling system
20+
include(joinpath(@__DIR__, "Exceptions", "Exceptions.jl"))
21+
using .Exceptions
22+
2323
# Unicode module - Unicode character utilities
2424
include(joinpath(@__DIR__, "Unicode", "Unicode.jl"))
2525
using .Unicode

src/Core/Core.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ module Core
1111
import DocStringExtensions: TYPEDEF, TYPEDSIGNATURES
1212

1313
include("types.jl")
14+
include("tags.jl")
1415
include("default.jl")
1516

1617
# Private utilities
@@ -19,6 +20,7 @@ include("macros.jl")
1920

2021
# Public utilities
2122
include("matrix_utils.jl")
23+
include("display.jl")
2224

2325
# Export public API
2426
export ctNumber, matrix2vec, to_out_of_place, @ensure

src/Core/display.jl

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
"""
2+
_apply_ansi(s, code, io::IO)
3+
4+
Apply ANSI escape codes to a string if color is enabled in the IO context.
5+
6+
# Arguments
7+
- `s::AbstractString`: The string to style.
8+
- `code::String`: The ANSI code (e.g., `"2"` for dim, `"1"` for bold, `"1;31"` for red).
9+
- `io::IO`: Output stream to check for color support.
10+
11+
# Returns
12+
- `String`: The string wrapped in ANSI escape codes if `get(io, :color, false)` is true,
13+
otherwise the plain string.
14+
"""
15+
_apply_ansi(s, code, io::IO) = get(io, :color, false) ? "\033[$(code)m$(s)\033[0m" : s
16+
17+
"""
18+
$(TYPEDSIGNATURES)
19+
20+
Apply dimmed (faint) ANSI styling to a string.
21+
22+
# Arguments
23+
- `s::AbstractString`: The string to style.
24+
- `io::IO`: Output stream to check for color support.
25+
26+
# Returns
27+
- `String`: The string wrapped in dim ANSI escape codes if color is enabled.
28+
"""
29+
_dim(s, io::IO) = _apply_ansi(s, "2", io)
30+
31+
"""
32+
$(TYPEDSIGNATURES)
33+
34+
Apply bold ANSI styling to a string.
35+
36+
# Arguments
37+
- `s::AbstractString`: The string to style.
38+
- `io::IO`: Output stream to check for color support.
39+
40+
# Returns
41+
- `String`: The string wrapped in bold ANSI escape codes if color is enabled.
42+
"""
43+
_bold(s, io::IO) = _apply_ansi(s, "1", io)
44+
45+
"""
46+
$(TYPEDSIGNATURES)
47+
48+
Apply red ANSI styling to a string.
49+
50+
# Arguments
51+
- `s::AbstractString`: The string to style.
52+
- `io::IO`: Output stream to check for color support.
53+
54+
# Returns
55+
- `String`: The string wrapped in red ANSI escape codes if color is enabled.
56+
"""
57+
_red(s, io::IO) = _apply_ansi(s, "1;31", io)
58+
59+
"""
60+
$(TYPEDSIGNATURES)
61+
62+
Apply yellow ANSI styling to a string.
63+
64+
# Arguments
65+
- `s::AbstractString`: The string to style.
66+
- `io::IO`: Output stream to check for color support.
67+
68+
# Returns
69+
- `String`: The string wrapped in yellow ANSI escape codes if color is enabled.
70+
"""
71+
_yellow(s, io::IO) = _apply_ansi(s, "33", io)
72+
73+
"""
74+
$(TYPEDSIGNATURES)
75+
76+
Apply green ANSI styling to a string.
77+
78+
# Arguments
79+
- `s::AbstractString`: The string to style.
80+
- `io::IO`: Output stream to check for color support.
81+
82+
# Returns
83+
- `String`: The string wrapped in green ANSI escape codes if color is enabled.
84+
"""
85+
_green(s, io::IO) = _apply_ansi(s, "32", io)
86+
87+
"""
88+
get_format_codes(io::IO) -> NamedTuple
89+
90+
Get ANSI formatting codes based on terminal color support.
91+
92+
Returns a NamedTuple with formatting codes for consistent display across all show() methods.
93+
94+
# Fields
95+
- `bold`: Bold text
96+
- `reset`: Reset all formatting
97+
- `name`: Bold blue for names (options, types, etc.)
98+
- `type`: Cyan for types
99+
- `value`: Green for values
100+
- `keyword`: Yellow for keywords/aliases
101+
- `count`: Magenta for counts
102+
- `label`: Gray for labels/descriptions
103+
104+
# Example
105+
```julia
106+
fmt = get_format_codes(io)
107+
print(io, fmt.name, "option_name", fmt.reset, "::", fmt.type, "Int", fmt.reset)
108+
```
109+
110+
# Notes
111+
- Automatically detects color support via `get(io, :color, false)`
112+
- Returns empty strings for all codes if colors are not supported
113+
- Ensures consistent color scheme across the entire package
114+
"""
115+
function get_format_codes(io::IO)
116+
supports_color = get(io, :color, false)
117+
118+
return (
119+
# Text formatting
120+
bold=supports_color ? "\033[1m" : "",
121+
reset=supports_color ? "\033[0m" : "",
122+
123+
# Colors for different semantic elements
124+
name=supports_color ? "\033[1m\033[34m" : "", # Bold blue for names
125+
type=supports_color ? "\033[36m" : "", # Cyan for types
126+
value=supports_color ? "\033[32m" : "", # Green for values
127+
keyword=supports_color ? "\033[33m" : "", # Yellow for keywords/aliases
128+
count=supports_color ? "\033[35m" : "", # Magenta for counts
129+
label=supports_color ? "\033[90m" : "", # Gray for labels
130+
)
131+
end

src/Core/tags.jl

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
"""
2+
AbstractTag
3+
4+
Abstract type for tag dispatch pattern used to handle extension-dependent implementations.
5+
6+
This type is used for multiple dispatch in validation functions and other contexts
7+
where behavior depends on loaded extensions (e.g., Enzyme, Zygote, CUDA).
8+
9+
# Example
10+
```julia
11+
struct MyTag <: AbstractTag end
12+
13+
function validate_backend(tag::MyTag, backend::Symbol)
14+
# Tag-specific validation logic
15+
end
16+
```
17+
18+
See also: Extension-based validation patterns in extension modules
19+
"""
20+
abstract type AbstractTag end

src/Exceptions/Exceptions.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ The Exceptions module is organized into thematic files:
3737
module Exceptions
3838

3939
import DocStringExtensions: TYPEDEF, TYPEDSIGNATURES
40+
using ..Core: Core
4041

4142
# Type definitions
4243
include("types.jl")

src/Exceptions/display.jl

Lines changed: 13 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -1,92 +1,3 @@
1-
# Custom display functions for user-friendly error messages
2-
3-
# ANSI formatting primitives
4-
"""
5-
_apply_ansi(s, code, io::IO)
6-
7-
Apply ANSI escape codes to a string if color is enabled in the IO context.
8-
9-
# Arguments
10-
- `s::AbstractString`: The string to style.
11-
- `code::String`: The ANSI code (e.g., "2" for dim, "1" for bold, "1;31" for red).
12-
- `io::IO`: Output stream to check for color support.
13-
14-
# Returns
15-
- `String`: The string wrapped in ANSI escape codes if `get(io, :color, false)` is true,
16-
otherwise the plain string.
17-
"""
18-
_apply_ansi(s, code, io::IO) = get(io, :color, false) ? "\033[$(code)m$(s)\033[0m" : s
19-
20-
"""
21-
$(TYPEDSIGNATURES)
22-
23-
Apply dimmed (faint) ANSI styling to a string.
24-
25-
# Arguments
26-
- `s::AbstractString`: The string to style.
27-
- `io::IO`: Output stream to check for color support.
28-
29-
# Returns
30-
- `String`: The string wrapped in dim ANSI escape codes if color is enabled.
31-
"""
32-
_dim(s, io::IO) = _apply_ansi(s, "2", io)
33-
34-
"""
35-
$(TYPEDSIGNATURES)
36-
37-
Apply bold ANSI styling to a string.
38-
39-
# Arguments
40-
- `s::AbstractString`: The string to style.
41-
- `io::IO`: Output stream to check for color support.
42-
43-
# Returns
44-
- `String`: The string wrapped in bold ANSI escape codes if color is enabled.
45-
"""
46-
_bold(s, io::IO) = _apply_ansi(s, "1", io)
47-
48-
"""
49-
$(TYPEDSIGNATURES)
50-
51-
Apply red ANSI styling to a string.
52-
53-
# Arguments
54-
- `s::AbstractString`: The string to style.
55-
- `io::IO`: Output stream to check for color support.
56-
57-
# Returns
58-
- `String`: The string wrapped in red ANSI escape codes if color is enabled.
59-
"""
60-
_red(s, io::IO) = _apply_ansi(s, "1;31", io)
61-
62-
"""
63-
$(TYPEDSIGNATURES)
64-
65-
Apply yellow ANSI styling to a string.
66-
67-
# Arguments
68-
- `s::AbstractString`: The string to style.
69-
- `io::IO`: Output stream to check for color support.
70-
71-
# Returns
72-
- `String`: The string wrapped in yellow ANSI escape codes if color is enabled.
73-
"""
74-
_yellow(s, io::IO) = _apply_ansi(s, "33", io)
75-
76-
"""
77-
$(TYPEDSIGNATURES)
78-
79-
Apply green ANSI styling to a string.
80-
81-
# Arguments
82-
- `s::AbstractString`: The string to style.
83-
- `io::IO`: Output stream to check for color support.
84-
85-
# Returns
86-
- `String`: The string wrapped in green ANSI escape codes if color is enabled.
87-
"""
88-
_green(s, io::IO) = _apply_ansi(s, "32", io)
89-
901
"""
912
extract_user_frames(st::Vector)
923
@@ -360,16 +271,16 @@ function _print_pipe_field(io, label::String, value, max_len::Int, color::Symbol
360271
# Multi-line case: AmbiguousDescription.candidates
361272
for (i, v) in enumerate(value)
362273
if i == 1
363-
print(io, _dim("", io), " ", _bold(rpad(label, max_len), io), " ")
274+
print(io, Core._dim("", io), " ", Core._bold(rpad(label, max_len), io), " ")
364275
_print_colored(io, string(v), color)
365276
println(io)
366277
else
367-
println(io, _dim("", io), " ", " "^max_len, " ", string(v))
278+
println(io, Core._dim("", io), " ", " "^max_len, " ", string(v))
368279
end
369280
end
370281
else
371282
# Single value
372-
print(io, _dim("", io), " ", _bold(rpad(label, max_len), io), " ")
283+
print(io, Core._dim("", io), " ", Core._bold(rpad(label, max_len), io), " ")
373284
_print_colored(io, string(value), color)
374285
println(io)
375286
end
@@ -390,9 +301,9 @@ Print colored text based on a color symbol.
390301
"""
391302
function _print_colored(io, text, color::Symbol)
392303
if color == :yellow
393-
print(io, _yellow(text, io))
304+
print(io, Core._yellow(text, io))
394305
elseif color == :green
395-
print(io, _green(text, io))
306+
print(io, Core._green(text, io))
396307
else
397308
print(io, text)
398309
end
@@ -413,22 +324,22 @@ function _format_user_friendly_error(io::IO, e::CTException)
413324
frame = isempty(user_frames) ? nothing : user_frames[1]
414325

415326
type_name = string(nameof(typeof(e)))
416-
print(io, _red(type_name, io))
327+
print(io, Core._red(type_name, io))
417328

418329
if !isnothing(frame)
419330
func_name = string(frame.func)
420331
file_name = basename(string(frame.file))
421332
line_num = frame.line
422-
print(io, " ", _dim("", io), " ", _bold(func_name, io), " ")
423-
print(io, _yellow("$(file_name):$(line_num)", io))
333+
print(io, " ", Core._dim("", io), " ", Core._bold(func_name, io), " ")
334+
print(io, Core._yellow("$(file_name):$(line_num)", io))
424335
end
425336
println(io)
426337

427338
# Blank pipe separator
428-
println(io, _dim("", io))
339+
println(io, Core._dim("", io))
429340

430341
# Message
431-
println(io, _dim("", io), " ", _bold(e.msg, io))
342+
println(io, Core._dim("", io), " ", Core._bold(e.msg, io))
432343

433344
# Build field pairs
434345
primary_pairs = _build_primary_pairs(e)
@@ -440,7 +351,7 @@ function _format_user_friendly_error(io::IO, e::CTException)
440351
max_len = maximum(length(p[1]) for p in all_pairs)
441352

442353
# Blank pipe separator
443-
println(io, _dim("", io))
354+
println(io, Core._dim("", io))
444355

445356
# Primary fields
446357
for p in primary_pairs
@@ -449,7 +360,7 @@ function _format_user_friendly_error(io::IO, e::CTException)
449360

450361
# Separator between primary and secondary
451362
if !isempty(primary_pairs) && !isempty(secondary_pairs)
452-
println(io, _dim("", io))
363+
println(io, Core._dim("", io))
453364
end
454365

455366
# Secondary fields
@@ -459,7 +370,7 @@ function _format_user_friendly_error(io::IO, e::CTException)
459370
end
460371

461372
# Closing visual
462-
return println(io, _dim("└─", io))
373+
return println(io, Core._dim("└─", io))
463374
end
464375

465376
"""

0 commit comments

Comments
 (0)