-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbanner.go
More file actions
165 lines (144 loc) · 4.64 KB
/
Copy pathbanner.go
File metadata and controls
165 lines (144 loc) · 4.64 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
// Package gofiglet renders ASCII art text using figlet fonts (.flf).
// It supports ANSI colors, true color (RGB), and per-character coloring.
// Built-in fonts are embedded and available without external file paths.
package gofiglet
import (
"fmt"
"image/color"
"os"
"strings"
)
// Banner holds configuration for rendering a multi-segment ASCII banner.
// Each entry in Title is rendered with the corresponding color from Colors.
type Banner struct {
// Title holds the banner's text segments. Segments are concatenated
// with no separator before rendering; each segment is colored
// independently via Colors.
Title []string
// Colors holds one color per Title segment, applied cyclically by
// index (segment i gets Colors[i % len(Colors)]). NewCmdBanner
// requires len(Colors) == len(Title).
Colors []color.Color
// FontName is the figlet font to render with, by name.
FontName string
// FontPath, if set, is an on-disk directory to load additional fonts
// from (in addition to the embedded builtin fonts) before rendering.
FontPath string
// TopPadding is the number of leading newlines to add before the
// rendered output. It does not affect kerning or layout.
TopPadding int
// BottomPadding is the number of trailing newlines to add after the
// rendered output.
BottomPadding int
}
// BannerOptions configures a Banner via the functional options pattern.
type BannerOptions func(b *Banner)
// WithColors sets the color palette for each Title segment.
func WithColors(colors []color.Color) BannerOptions {
return func(b *Banner) {
b.Colors = colors
}
}
// WithFont sets the figlet font name to use for rendering.
func WithFont(f string) BannerOptions {
return func(b *Banner) {
b.FontName = f
}
}
// WithLocalFont sets the font name and loads additional fonts from a local directory.
func WithLocalFont(f string, p string) BannerOptions {
return func(b *Banner) {
b.FontName = f
b.FontPath = p
}
}
// WithPadding sets the number of newlines to add before (top) and after
// (bottom) the rendered output. Pass 0 to disable padding on either side.
func WithPadding(top, bottom int) BannerOptions {
return func(b *Banner) {
b.TopPadding = top
b.BottomPadding = bottom
}
}
// WithZeroPadding is a convenience wrapper around WithPadding(0, 0) that
// disables both top and bottom padding.
func WithZeroPadding() BannerOptions {
return WithPadding(0, 0)
}
// NewCmdBanner creates a Banner with sensible defaults for CLI tool banners.
// Title entries represent command and subcommand names (e.g., ["cmd", "sub"]).
// Colors must match the number of Title entries.
func NewCmdBanner(title []string, options ...BannerOptions) (*Banner, error) {
b := &Banner{
Title: title,
Colors: []color.Color{ColorCyan, TrueColorPink206},
FontName: "smallsmursh",
TopPadding: 1,
}
for _, o := range options {
o(b)
}
if len(b.Colors) != len(b.Title) {
return nil, fmt.Errorf(
"banner has %d title entries but %d colors provided; counts must match",
len(b.Title), len(b.Colors),
)
}
if b.FontPath != "" {
if err := verifyExists(b.FontPath); err != nil {
return nil, err
}
}
return b, nil
}
// CmdBanner renders b as a colored ASCII art string. It returns an error
// if b.FontPath is set but fails to load, or if rendering fails (e.g.
// b.FontName cannot be found, or a Title segment contains a non-ASCII
// character).
func CmdBanner(b *Banner) (string, error) {
ascii := NewASCIIRender()
if b.FontPath != "" {
if err := ascii.LoadFont(b.FontPath); err != nil {
return "", err
}
}
figletOptions := NewRenderOptions()
figletOptions.FontName = b.FontName
bannerTitle := strings.Join(b.Title, "")
figletColors := make([]color.Color, 0, len(bannerTitle))
for i, entry := range b.Title {
color := b.Colors[i%len(b.Colors)]
for range entry {
figletColors = append(figletColors, color)
}
}
figletOptions.FontColor = figletColors
var renderedString strings.Builder
for i := 0; i < b.TopPadding; i++ {
renderedString.WriteByte('\n')
}
asciiString, err := ascii.RenderOpts(bannerTitle, figletOptions)
if err != nil {
return "", err
}
renderedString.WriteString(asciiString)
for i := 0; i < b.BottomPadding; i++ {
renderedString.WriteByte('\n')
}
return renderedString.String(), nil
}
// PrintCmdBanner renders and prints b to stdout. It returns an error if
// rendering fails; see CmdBanner.
func PrintCmdBanner(b *Banner) (int, error) {
s, err := CmdBanner(b)
if err != nil {
return 0, err
}
return fmt.Print(s)
}
// verifyExists returns an error if filename does not exist on disk (e.g.
// os.ErrNotExist), or nil if it does.
func verifyExists(filename string) error {
_, err := os.Stat(filename)
return err
}