Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 123 additions & 0 deletions bin/ccsay
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
#!/usr/bin/env node
import { textToAsciiArt } from "./fonts";
const c = (n) => `\x1b[${n}m`;
const COLOR_MAP = {
black: c(30),
red: c(31),
green: c(32),
yellow: c(33),
blue: c(34),
magenta: c(35),
cyan: c(36),
white: c(37),
orange: "\x1b[38;5;208m",
purple: "\x1b[38;5;129m",
pink: "\x1b[38;5;205m",
gray: c(90),
grey: c(90),
};
function parseColor(colorInput) {
return COLOR_MAP[colorInput.toLowerCase()] || COLOR_MAP.orange || c(38);
}
function showHelp() {
const helpText = `ccsay - Display text in colorful ASCII art

Usage:
ccsay [options] [text]
echo "text" | ccsay [options]

Options:
--help, -h Show this help message
--color, -c Set text color (default: orange)
Available colors: black, red, green, yellow, blue, magenta,
cyan, white, orange, purple, pink, gray, grey

Examples:
ccsay "Hello World" Display "Hello World" in ASCII art
ccsay -c red "ERROR" Display "ERROR" in red
ccsay "Line 1\\nLine 2" Multi-line text with newlines
echo "Piped text" | ccsay Read from stdin
ccsay --color blue "BUILDING..." Display in blue color

Version:
0.0.2

For more information, visit: https://github.com/watany-dev/ccsay`;
console.log(helpText);
}
export function main() {
const args = process.argv.slice(2);
// Check for help flags first
if (args.includes("--help") || args.includes("-h")) {
showHelp();
return;
}
let color = COLOR_MAP.orange;
const textArgs = [];
// Parse arguments for color option
for (let i = 0; i < args.length; i++) {
const arg = args[i];
if (arg === "-c" || arg === "--color") {
// Get next argument as color value
if (i + 1 < args.length) {
color = parseColor(args[i + 1] || "");
i++; // Skip the color value in next iteration
}
}
else {
textArgs.push(arg || "");
}
}
// Rejoin args, looking for separated \n sequences
let rawText;
if (textArgs.length === 0) {
// Check if stdin has data (piped input)
if (!process.stdin.isTTY) {
// Read from stdin
let stdinData = "";
process.stdin.setEncoding("utf8");
process.stdin.on("data", (chunk) => {
stdinData += chunk;
});
process.stdin.on("end", () => {
const text = stdinData.trim();
const asciiArt = textToAsciiArt(text);
console.log(`${color}${asciiArt}\x1b[0m`);
});
return;
}
rawText = "CLAUDE\nCODE";
}
else {
const parts = [];
for (let i = 0; i < textArgs.length; i++) {
const currentArg = textArgs[i];
if (!currentArg)
continue;
if (i > 0 && currentArg.startsWith("n")) {
const prevArg = textArgs[i - 1];
if (prevArg?.endsWith("\\")) {
// Remove the trailing \ from previous part and add \n
if (parts.length > 0) {
const lastPart = parts[parts.length - 1];
if (lastPart) {
parts[parts.length - 1] = lastPart.slice(0, -1);
}
}
parts.push(`\\n${currentArg.slice(1)}`);
continue;
}
}
parts.push(currentArg);
}
rawText = parts.join(" ");
}
// Handle literal \n in command line arguments
const text = rawText.replace(/\\n/g, "\n");
const asciiArt = textToAsciiArt(text);
console.log(`${color}${asciiArt}\x1b[0m`);
}
// Run when this file is executed directly
if (import.meta.url === `file://${process.argv[1]}`) {
main();
}
85 changes: 85 additions & 0 deletions bin/fonts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
const f = (s) => s;
export const BLOCK_FONT = {
A: f([" █████╗ ", "██╔══██╗", "███████║", "██╔══██║", "██║ ██║", "╚═╝ ╚═╝"]),
B: f(["██████╗ ", "██╔══██╗", "██████╔╝", "██╔══██╗", "██████╔╝", "╚═════╝ "]),
C: f([" ██████╗", "██╔════╝", "██║ ", "██║ ", "╚██████╗", " ╚═════╝"]),
D: f(["██████╗ ", "██╔══██╗", "██║ ██║", "██║ ██║", "██████╔╝", "╚═════╝ "]),
E: f(["███████╗", "██╔════╝", "█████╗ ", "██╔══╝ ", "███████╗", "╚══════╝"]),
F: f(["███████╗", "██╔════╝", "█████╗ ", "██╔══╝ ", "██║ ", "╚═╝ "]),
G: f([" ██████╗ ", "██╔════╝ ", "██║ ███╗", "██║ ██║", "╚██████╔╝", " ╚═════╝ "]),
H: f(["██╗ ██╗", "██║ ██║", "███████║", "██╔══██║", "██║ ██║", "╚═╝ ╚═╝"]),
I: f(["██╗", "██║", "██║", "██║", "██║", "╚═╝"]),
J: f([" ██╗", " ██║", " ██║", "██ ██║", "╚█████╔╝", " ╚════╝ "]),
K: f(["██╗ ██╗", "██║ ██╔╝", "█████╔╝ ", "██╔═██╗ ", "██║ ██╗", "╚═╝ ╚═╝"]),
L: f(["██╗ ", "██║ ", "██║ ", "██║ ", "███████╗", "╚══════╝"]),
M: f(["███╗ ███╗", "████╗ ████║", "██╔████╔██║", "██║╚██╔╝██║", "██║ ╚═╝ ██║", "╚═╝ ╚═╝"]),
N: f(["███╗ ██╗", "████╗ ██║", "██╔██╗ ██║", "██║╚██╗██║", "██║ ╚████║", "╚═╝ ╚═══╝"]),
O: f([" ██████╗ ", "██╔═══██╗", "██║ ██║", "██║ ██║", "╚██████╔╝", " ╚═════╝ "]),
P: f(["██████╗ ", "██╔══██╗", "██████╔╝", "██╔═══╝ ", "██║ ", "╚═╝ "]),
Q: f([" ██████╗ ", "██╔═══██╗", "██║ ██║", "██║▄▄ ██║", "╚██████╔╝", " ╚══▀▀═╝ "]),
R: f(["██████╗ ", "██╔══██╗", "██████╔╝", "██╔══██╗", "██║ ██║", "╚═╝ ╚═╝"]),
S: f(["███████╗", "██╔════╝", "███████╗", "╚════██║", "███████║", "╚══════╝"]),
T: f(["████████╗", "╚══██╔══╝", " ██║ ", " ██║ ", " ██║ ", " ╚═╝ "]),
U: f(["██╗ ██╗", "██║ ██║", "██║ ██║", "██║ ██║", "╚██████╔╝", " ╚═════╝ "]),
V: f(["██╗ ██╗", "██║ ██║", "██║ ██║", "╚██╗ ██╔╝", " ╚████╔╝ ", " ╚═══╝ "]),
W: f(["██╗ ██╗", "██║ ██║", "██║ █╗ ██║", "██║███╗██║", "╚███╔███╔╝", " ╚══╝╚══╝ "]),
X: f(["██╗ ██╗", "╚██╗██╔╝", " ╚███╔╝ ", " ██╔██╗ ", "██╔╝ ██╗", "╚═╝ ╚═╝"]),
Y: f(["██╗ ██╗", "╚██╗ ██╔╝", " ╚████╔╝ ", " ╚██╔╝ ", " ██║ ", " ╚═╝ "]),
Z: f(["███████╗", "╚══███╔╝", " ███╔╝ ", " ███╔╝ ", "███████╗", "╚══════╝"]),
" ": f([" ", " ", " ", " ", " ", " "]),
"!": f(["██╗", "██║", "██║", "╚═╝", "██╗", "╚═╝"]),
"?": f(["██████╗ ", "╚════██╗", " ▄███╔╝", " ▀▀══╝ ", " ██╗ ", " ╚═╝ "]),
".": f([" ", " ", " ", " ", "██╗", "╚═╝"]),
",": f([" ", " ", " ", " ", "▄█╗", "╚═╝"]),
"-": f([" ", " ", "█████╗", "╚════╝", " ", " "]),
"0": f([" ██████╗ ", "██╔═████╗", "██║██╔██║", "████╔╝██║", "╚██████╔╝", " ╚═════╝ "]),
"1": f([" ██╗", "███║", "╚██║", " ██║", " ██║", " ╚═╝"]),
"2": f(["██████╗ ", "╚════██╗", " █████╔╝", "██╔═══╝ ", "███████╗", "╚══════╝"]),
"3": f(["██████╗ ", "╚════██╗", " █████╔╝", " ╚═══██╗", "██████╔╝", "╚═════╝ "]),
"4": f(["██╗ ██╗", "██║ ██║", "███████║", "╚════██║", " ██║", " ╚═╝"]),
"5": f(["███████╗", "██╔════╝", "███████╗", "╚════██║", "███████║", "╚══════╝"]),
"6": f([" ██████╗ ", "██╔════╝ ", "███████╗ ", "██╔═══██╗", "╚██████╔╝", " ╚═════╝ "]),
"7": f(["███████╗", "╚════██║", " ██╔╝", " ██╔╝ ", " ██║ ", " ╚═╝ "]),
"8": f([" █████╗ ", "██╔══██╗", "╚█████╔╝", "██╔══██╗", "╚█████╔╝", " ╚════╝ "]),
"9": f([" █████╗ ", "██╔══██╗", "╚██████║", " ╚═══██║", " █████╔╝", " ╚════╝ "]),
};
export function textToAsciiArt(text) {
if (!text)
return "\n\n\n\n\n";
const textLines = text.split("\n");
const spaceChar = BLOCK_FONT[" "];
const allAsciiLines = [];
for (let lineIndex = 0; lineIndex < textLines.length; lineIndex++) {
const textLine = textLines[lineIndex];
if (!textLine) {
for (let i = 0; i < 6; i++)
allAsciiLines.push([]);
}
else {
const upperText = textLine.toUpperCase();
const lines = [[], [], [], [], [], []];
const charArts = [];
for (let charIndex = 0; charIndex < upperText.length; charIndex++) {
const char = upperText[charIndex];
const charArt = BLOCK_FONT[char || " "];
charArts[charIndex] = charArt || spaceChar || ["", "", "", "", "", ""];
}
for (let i = 0; i < 6; i++) {
for (let charIndex = 0; charIndex < charArts.length; charIndex++) {
const charArt = charArts[charIndex];
if (charArt) {
const artLine = charArt[i];
if (artLine)
lines[i]?.push(artLine);
}
}
const line = lines[i];
if (line)
allAsciiLines.push(line);
}
}
if (lineIndex < textLines.length - 1)
allAsciiLines.push([]);
}
return allAsciiLines.map((line) => line.join("")).join("\n");
}
123 changes: 123 additions & 0 deletions bin/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
#!/usr/bin/env node
import { textToAsciiArt } from "./fonts";
const c = (n) => `\x1b[${n}m`;
const COLOR_MAP = {
black: c(30),
red: c(31),
green: c(32),
yellow: c(33),
blue: c(34),
magenta: c(35),
cyan: c(36),
white: c(37),
orange: "\x1b[38;5;208m",
purple: "\x1b[38;5;129m",
pink: "\x1b[38;5;205m",
gray: c(90),
grey: c(90),
};
function parseColor(colorInput) {
return COLOR_MAP[colorInput.toLowerCase()] || COLOR_MAP.orange || c(38);
}
function showHelp() {
const helpText = `ccsay - Display text in colorful ASCII art

Usage:
ccsay [options] [text]
echo "text" | ccsay [options]

Options:
--help, -h Show this help message
--color, -c Set text color (default: orange)
Available colors: black, red, green, yellow, blue, magenta,
cyan, white, orange, purple, pink, gray, grey

Examples:
ccsay "Hello World" Display "Hello World" in ASCII art
ccsay -c red "ERROR" Display "ERROR" in red
ccsay "Line 1\\nLine 2" Multi-line text with newlines
echo "Piped text" | ccsay Read from stdin
ccsay --color blue "BUILDING..." Display in blue color

Version:
0.0.2

For more information, visit: https://github.com/watany-dev/ccsay`;
console.log(helpText);
}
export function main() {
const args = process.argv.slice(2);
// Check for help flags first
if (args.includes("--help") || args.includes("-h")) {
showHelp();
return;
}
let color = COLOR_MAP.orange;
const textArgs = [];
// Parse arguments for color option
for (let i = 0; i < args.length; i++) {
const arg = args[i];
if (arg === "-c" || arg === "--color") {
// Get next argument as color value
if (i + 1 < args.length) {
color = parseColor(args[i + 1] || "");
i++; // Skip the color value in next iteration
}
}
else {
textArgs.push(arg || "");
}
}
// Rejoin args, looking for separated \n sequences
let rawText;
if (textArgs.length === 0) {
// Check if stdin has data (piped input)
if (!process.stdin.isTTY) {
// Read from stdin
let stdinData = "";
process.stdin.setEncoding("utf8");
process.stdin.on("data", (chunk) => {
stdinData += chunk;
});
process.stdin.on("end", () => {
const text = stdinData.trim();
const asciiArt = textToAsciiArt(text);
console.log(`${color}${asciiArt}\x1b[0m`);
});
return;
}
rawText = "CLAUDE\nCODE";
}
else {
const parts = [];
for (let i = 0; i < textArgs.length; i++) {
const currentArg = textArgs[i];
if (!currentArg)
continue;
if (i > 0 && currentArg.startsWith("n")) {
const prevArg = textArgs[i - 1];
if (prevArg?.endsWith("\\")) {
// Remove the trailing \ from previous part and add \n
if (parts.length > 0) {
const lastPart = parts[parts.length - 1];
if (lastPart) {
parts[parts.length - 1] = lastPart.slice(0, -1);
}
}
parts.push(`\\n${currentArg.slice(1)}`);
continue;
}
}
parts.push(currentArg);
}
rawText = parts.join(" ");
}
// Handle literal \n in command line arguments
const text = rawText.replace(/\\n/g, "\n");
const asciiArt = textToAsciiArt(text);
console.log(`${color}${asciiArt}\x1b[0m`);
}
// Run when this file is executed directly
if (import.meta.url === `file://${process.argv[1]}`) {
main();
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"type": "module",
"scripts": {
"dev": "bun run src/index.ts",
"build": "bun build src/index.ts --compile --outfile bin/ccsay",
"build": "npx tsc && cp bin/index.js bin/ccsay && chmod +x bin/ccsay",
"lint": "bunx @biomejs/biome check .",
"lint:fix": "bunx @biomejs/biome check --write .",
"format": "bunx @biomejs/biome format --write .",
Expand Down
13 changes: 9 additions & 4 deletions src/fonts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,19 @@ export function textToAsciiArt(text: string): string {
const charArts: string[][] = [];
for (let charIndex = 0; charIndex < upperText.length; charIndex++) {
const char = upperText[charIndex];
charArts[charIndex] = BLOCK_FONT[char] || spaceChar;
const charArt = BLOCK_FONT[char || " "];
charArts[charIndex] = charArt || spaceChar || ["", "", "", "", "", ""];
}
for (let i = 0; i < 6; i++) {
for (let charIndex = 0; charIndex < charArts.length; charIndex++) {
const artLine = charArts[charIndex][i];
if (artLine) lines[i].push(artLine);
const charArt = charArts[charIndex];
if (charArt) {
const artLine = charArt[i];
if (artLine) lines[i]?.push(artLine);
}
}
allAsciiLines.push(lines[i]);
const line = lines[i];
if (line) allAsciiLines.push(line);
}
}
if (lineIndex < textLines.length - 1) allAsciiLines.push([]);
Expand Down
6 changes: 3 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env bun
#!/usr/bin/env node
import { textToAsciiArt } from "./fonts";

const c = (n: number) => `\x1b[${n}m`;
Expand All @@ -19,7 +19,7 @@ const COLOR_MAP: Record<string, string> = {
};

function parseColor(colorInput: string): string {
return COLOR_MAP[colorInput.toLowerCase()] || COLOR_MAP.orange;
return COLOR_MAP[colorInput.toLowerCase()] || COLOR_MAP.orange || c(38);
}

function showHelp(): void {
Expand Down Expand Up @@ -128,6 +128,6 @@ export function main() {
}

// Run when this file is executed directly
if (import.meta.main) {
if (import.meta.url === `file://${process.argv[1]}`) {
main();
}
Loading
Loading