Skip to content

Commit 3bf9692

Browse files
colorzize: module improvement
1 parent 7a3f0c5 commit 3bf9692

1 file changed

Lines changed: 121 additions & 55 deletions

File tree

src/colorize.ts

Lines changed: 121 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,115 +1,181 @@
1+
// Terminal configuration
2+
const TERMINAL_WIDTH = 80
3+
const DEFAULT_INDENT = 28
4+
const ANSI_REGEX = /\x1b\[[0-9;]*m/g
5+
6+
// Check if colors should be disabled
7+
const SUPPORTS_COLOR =
8+
process.env.NO_COLOR === undefined &&
9+
process.stdout.isTTY &&
10+
process.env.TERM !== 'dumb'
11+
12+
// ANSI color codes
113
const GREEN = '\x1b[32m' // For short options (-h)
214
const CYAN = '\x1b[36m' // For long options (--help)
315
const YELLOW = '\x1b[33m' // Could be used for parameter values
416
const RESET = '\x1b[0m' // Reset to default color
517

18+
/**
19+
* Helper function to wrap text to a specified width
20+
* @param text - The text to wrap
21+
* @param maxLength - Maximum line length
22+
* @returns Array of wrapped lines
23+
*/
24+
function wrapText(text: string, maxLength: number): string[] {
25+
const words = text.split(' ')
26+
const lines: string[] = []
27+
let currentLine = ''
28+
29+
for (const word of words) {
30+
const testLine = currentLine + word + ' '
31+
if (testLine.length > maxLength && currentLine.length > 0) {
32+
lines.push(currentLine.trim())
33+
currentLine = word + ' '
34+
} else {
35+
currentLine = testLine
36+
}
37+
}
38+
39+
if (currentLine.trim().length > 0) {
40+
lines.push(currentLine.trim())
41+
}
42+
43+
return lines
44+
}
45+
46+
/**
47+
* Helper function to strip ANSI codes from a string
48+
* @param text - Text with ANSI codes
49+
* @returns Text without ANSI codes
50+
*/
51+
function stripAnsi(text: string): string {
52+
return text.replace(ANSI_REGEX, '')
53+
}
54+
55+
/**
56+
* Helper function to apply color only if terminal supports it
57+
* @param color - ANSI color code
58+
* @param text - Text to colorize
59+
* @returns Colorized text or plain text
60+
*/
61+
function colorize(color: string, text: string): string {
62+
return SUPPORTS_COLOR ? `${color}${text}${RESET}` : text
63+
}
64+
65+
/**
66+
* Colorize text in green (for short options like -h)
67+
* @param text - The text to colorize
68+
* @returns Colorized text
69+
*/
670
export function short(text: string): string {
7-
return `${GREEN}${text}${RESET}`
71+
return colorize(GREEN, text)
872
}
973

74+
/**
75+
* Colorize text in cyan (for long options like --help)
76+
* @param text - The text to colorize
77+
* @returns Colorized text
78+
*/
1079
export function long(text: string): string {
11-
return `${CYAN}${text}${RESET}`
80+
return colorize(CYAN, text)
1281
}
1382

83+
/**
84+
* Format text as a heading (yellow, bold, underlined)
85+
* @param text - The text to format
86+
* @returns Formatted heading text
87+
*/
1488
export function heading(text: string): string {
15-
return underline(bold(`${YELLOW}${text}${RESET}`))
89+
const colored = colorize(YELLOW, text)
90+
return underline(bold(colored))
1691
}
1792

93+
/**
94+
* Make text bold
95+
* @param text - The text to make bold
96+
* @returns Bold text
97+
*/
1898
export function bold(text: string): string {
19-
return `\x1b[1m${text}${RESET}`
99+
return SUPPORTS_COLOR ? `\x1b[1m${text}${RESET}` : text
20100
}
21101

102+
/**
103+
* Make text italic
104+
* @param text - The text to make italic
105+
* @returns Italic text
106+
*/
22107
export function italic(text: string): string {
23-
return `\x1b[3m${text}${RESET}`
108+
return SUPPORTS_COLOR ? `\x1b[3m${text}${RESET}` : text
24109
}
25110

111+
/**
112+
* Underline text
113+
* @param text - The text to underline
114+
* @returns Underlined text
115+
*/
26116
export function underline(text: string): string {
27-
return `\x1b[4m${text}${RESET}`
117+
return SUPPORTS_COLOR ? `\x1b[4m${text}${RESET}` : text
28118
}
29119

120+
/**
121+
* Print a formatted command with description
122+
* @param short_command - Short command option (e.g., '-h') or null
123+
* @param long_command - Long command option (e.g., '--help')
124+
* @param description - Description text
125+
* @param indentation - Column width for command alignment (default: 28)
126+
*/
30127
export function command(
31128
short_command: string | null,
32129
long_command: string,
33130
description: string,
34-
indentation: number = 28
35-
): undefined {
131+
indentation: number = DEFAULT_INDENT
132+
): void {
36133
// Format the command portion
37134
let commandPart = ''
38135

39136
if (short_command) {
40137
// Handle newline character if present in short_command
41-
if (short_command.includes('\n')) {
42-
commandPart =
43-
short(short_command.replace('\n', '')) + ' ' + bold(long(long_command))
44-
} else {
45-
commandPart = short(short_command) + ' ' + bold(long(long_command))
46-
}
138+
const cleanShort = short_command.replace('\n', '')
139+
commandPart = short(cleanShort) + ' ' + bold(long(long_command))
47140
} else {
48141
commandPart = bold(long(long_command))
49142
}
50143

51144
// Get the visible length of the command part (without ANSI codes)
52-
const commandPartLength = commandPart.replace(/\x1b\[\d+m/g, '').length
145+
const commandPartLength = stripAnsi(commandPart).length
53146

54147
// Calculate indentation padding
55148
const padding = Math.max(0, indentation - commandPartLength)
56149

57150
// Calculate available space for description after indentation
58-
const maxLineLength = 80 // Standard terminal width
59-
const wrapLength = maxLineLength - indentation
151+
const wrapLength = TERMINAL_WIDTH - indentation
60152

61153
// Word wrap the description
62-
const words = description.split(' ')
63-
let lines = []
64-
let currentLine = ''
65-
66-
for (const word of words) {
67-
if ((currentLine + word).length > wrapLength && currentLine.length > 0) {
68-
lines.push(currentLine.trim())
69-
currentLine = word + ' '
70-
} else {
71-
currentLine += word + ' '
72-
}
73-
}
74-
75-
if (currentLine.trim().length > 0) {
76-
lines.push(currentLine.trim())
77-
}
154+
const lines = wrapText(description, wrapLength)
78155

79156
// Output first line with proper indentation
80-
console.log(commandPart + ' '.repeat(padding) + lines[0])
157+
console.log(commandPart + ' '.repeat(padding) + (lines[0] || ''))
81158

82159
// Output additional lines with consistent indentation
83160
for (let i = 1; i < lines.length; i++) {
84161
console.log(' '.repeat(indentation) + lines[i])
85162
}
86163
}
87164

165+
/**
166+
* Print formatted info text with optional indentation
167+
* @param text - The text to display
168+
* @param indentation - Number of spaces to indent (default: 0)
169+
*/
88170
export function info(text: string, indentation: number = 0): void {
89-
// Calculate maximum line length
90-
const maxLineLength = 80
91-
const wrapLength = maxLineLength - indentation
171+
// Calculate available space for text after indentation
172+
const wrapLength = TERMINAL_WIDTH - indentation
92173

93174
// Word wrap the text
94-
const words = text.split(' ')
95-
let lines = []
96-
let currentLine = ''
97-
98-
for (const word of words) {
99-
if ((currentLine + word).length > wrapLength && currentLine.length > 0) {
100-
lines.push(currentLine.trim())
101-
currentLine = word + ' '
102-
} else {
103-
currentLine += word + ' '
104-
}
105-
}
106-
107-
if (currentLine.trim().length > 0) {
108-
lines.push(currentLine.trim())
109-
}
175+
const lines = wrapText(text, wrapLength)
110176

111177
// Print each line with indentation and styling
112-
for (let i = 0; i < lines.length; i++) {
113-
console.log(' '.repeat(indentation) + italic(lines[i]))
178+
for (const line of lines) {
179+
console.log(' '.repeat(indentation) + italic(line))
114180
}
115181
}

0 commit comments

Comments
 (0)