awecolor 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +67 -0
- package/awecolor.mjs +157 -0
- package/package.json +29 -0
package/README.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<h1>awecolor: Hex Color Visualizer</h1>
|
|
3
|
+
<p><strong>Colorize hex color codes in your terminal.</strong></p>
|
|
4
|
+
<p>Pipe text or pass files to see hex colors rendered with their actual background.</p>
|
|
5
|
+
<p>
|
|
6
|
+
<strong>English</strong> ·
|
|
7
|
+
<a href="./README_cn.md">简体中文</a>
|
|
8
|
+
</p>
|
|
9
|
+
<p>
|
|
10
|
+
<img src="https://img.shields.io/badge/version-0.1.0-7C3AED?style=flat-square" alt="Version">
|
|
11
|
+
<img src="https://img.shields.io/badge/node-%E2%89%A518-0EA5E9?style=flat-square" alt="Node">
|
|
12
|
+
</p>
|
|
13
|
+
<p>
|
|
14
|
+
<img src="https://img.shields.io/badge/status-alpha-c96a3d?style=flat-square" alt="Status">
|
|
15
|
+
<img src="https://img.shields.io/badge/install-npm-22C55E?style=flat-square" alt="npm install">
|
|
16
|
+
<img src="https://img.shields.io/badge/platform-terminal-334155?style=flat-square" alt="Platform">
|
|
17
|
+
<img src="https://img.shields.io/npm/dm/awecolor?style=flat-square" alt="npm downloads">
|
|
18
|
+
<img src="https://img.shields.io/github/stars/mugpeng/awecolor?style=flat-square" alt="GitHub stars">
|
|
19
|
+
</p>
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
> Colorize hex color codes in your terminal.
|
|
23
|
+
|
|
24
|
+
A small CLI that finds hex color codes (`#RGB`, `#RGBA`, `#RRGGBB`, `#RRGGBBAA`) in text and renders them with colored backgrounds. Useful for previewing colors in CSS, config files, or any text that contains hex values.
|
|
25
|
+
|
|
26
|
+
## Install
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm install -g awecolor
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Or run directly with `npx`:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npx awecolor styles.css
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Quick Start
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# Pipe from any command
|
|
42
|
+
cat styles.css | awecolor
|
|
43
|
+
|
|
44
|
+
# Read a file directly
|
|
45
|
+
awecolor theme.json
|
|
46
|
+
|
|
47
|
+
# List all colors with locations
|
|
48
|
+
awecolor --extract styles.css
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Commands
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
awecolor [OPTIONS] [FILE...]
|
|
55
|
+
|
|
56
|
+
awecolor styles.css # Colorize hex codes in a file
|
|
57
|
+
cat styles.css | awecolor # Colorize from stdin
|
|
58
|
+
awecolor --extract styles.css # List colors with location and context
|
|
59
|
+
awecolor --no-color styles.css # Plain text output (no ANSI)
|
|
60
|
+
awecolor --force-color styles.css # Force color even when piped
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Development
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
npm test
|
|
67
|
+
```
|
package/awecolor.mjs
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { argv, stdin, stdout, stderr, exit } from "node:process";
|
|
4
|
+
import { createRequire } from "node:module";
|
|
5
|
+
|
|
6
|
+
const require = createRequire(import.meta.url);
|
|
7
|
+
const { version: VERSION } = require("./package.json");
|
|
8
|
+
|
|
9
|
+
const HELP = `Usage: awecolor [OPTIONS] [FILE...]
|
|
10
|
+
|
|
11
|
+
Colorize hex color codes in text.
|
|
12
|
+
|
|
13
|
+
Options:
|
|
14
|
+
--extract List colors with location and context instead of colorizing.
|
|
15
|
+
--no-color Output plain text (no ANSI codes).
|
|
16
|
+
--force-color Force color output even when stdout is not a TTY.
|
|
17
|
+
-h, --help Show this message and exit.
|
|
18
|
+
-v, --version Show the version and exit.`;
|
|
19
|
+
|
|
20
|
+
const HEX_RE = /#(?:[0-9a-fA-F]{8}|[0-9a-fA-F]{6}|[0-9a-fA-F]{4}|[0-9a-fA-F]{3})\b(?![0-9a-fA-F])/g;
|
|
21
|
+
|
|
22
|
+
// --- Pure logic ---
|
|
23
|
+
|
|
24
|
+
function expandHex(hex) {
|
|
25
|
+
const h = hex.slice(1);
|
|
26
|
+
if (h.length === 8 || h.length === 6) return "#" + h.slice(0, 6);
|
|
27
|
+
if (h.length === 4) return "#" + h[0] + h[0] + h[1] + h[1] + h[2] + h[2];
|
|
28
|
+
return "#" + h[0] + h[0] + h[1] + h[1] + h[2] + h[2];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function hexToRgb(hex) {
|
|
32
|
+
const h = hex.slice(1);
|
|
33
|
+
return [parseInt(h.slice(0, 2), 16), parseInt(h.slice(2, 4), 16), parseInt(h.slice(4, 6), 16)];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function luminance(r, g, b) {
|
|
37
|
+
return 0.299 * r + 0.587 * g + 0.114 * b;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function foregroundColor(hex) {
|
|
41
|
+
const [r, g, b] = hexToRgb(hex);
|
|
42
|
+
return luminance(r, g, b) > 160 ? "#111111" : "#ffffff";
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function colorize(text, chalk) {
|
|
46
|
+
return text.replace(HEX_RE, (match) => {
|
|
47
|
+
const bg = expandHex(match);
|
|
48
|
+
const fg = foregroundColor(bg);
|
|
49
|
+
return chalk.bgHex(bg).hex(fg)(` ${match} `);
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function extractColors(text) {
|
|
54
|
+
const results = [];
|
|
55
|
+
const lines = text.split("\n");
|
|
56
|
+
const termWidth = stdout.columns || 80;
|
|
57
|
+
|
|
58
|
+
for (let i = 0; i < lines.length; i++) {
|
|
59
|
+
const line = lines[i];
|
|
60
|
+
let match;
|
|
61
|
+
HEX_RE.lastIndex = 0;
|
|
62
|
+
while ((match = HEX_RE.exec(line)) !== null) {
|
|
63
|
+
const col = match.index + 1;
|
|
64
|
+
const meta = `${match[0]} ${i + 1}:${col}`;
|
|
65
|
+
const maxContext = termWidth - meta.length - 2;
|
|
66
|
+
let context = line;
|
|
67
|
+
if (context.length > maxContext && maxContext > 10) {
|
|
68
|
+
const start = Math.max(0, match.index - Math.floor(maxContext / 3));
|
|
69
|
+
const end = Math.min(line.length, start + maxContext);
|
|
70
|
+
context = (start > 0 ? "..." : "") + line.slice(start, end) + (end < line.length ? "..." : "");
|
|
71
|
+
}
|
|
72
|
+
results.push(`${meta} ${context}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return results;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// --- CLI wiring ---
|
|
79
|
+
|
|
80
|
+
function parseArgs(args) {
|
|
81
|
+
const opts = { files: [], extract: false, noColor: false, forceColor: false };
|
|
82
|
+
let i = 0;
|
|
83
|
+
while (i < args.length) {
|
|
84
|
+
const a = args[i];
|
|
85
|
+
if (a === "--extract") { opts.extract = true; i++; }
|
|
86
|
+
else if (a === "--no-color") { opts.noColor = true; i++; }
|
|
87
|
+
else if (a === "--force-color") { opts.forceColor = true; i++; }
|
|
88
|
+
else if (a === "-h" || a === "--help") { stdout.write(HELP + "\n"); exit(0); }
|
|
89
|
+
else if (a === "-v" || a === "--version") { stdout.write(`awecolor ${VERSION}\n`); exit(0); }
|
|
90
|
+
else if (a === "--") { opts.files = args.slice(i + 1); break; }
|
|
91
|
+
else if (a.startsWith("-")) { stderr.write(`Unknown option: ${a}\nRun "awecolor --help" for usage.\n`); exit(1); }
|
|
92
|
+
else { opts.files.push(a); i++; }
|
|
93
|
+
}
|
|
94
|
+
return opts;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function isInteractive() {
|
|
98
|
+
return stdin.isTTY && stdout.isTTY;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function hasAnsiSupport() {
|
|
102
|
+
return stdout.isTTY || !!process.env.FORCE_COLOR || !!process.env.COLORTERM;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function readStdin() {
|
|
106
|
+
let data = "";
|
|
107
|
+
for await (const chunk of stdin) data += chunk;
|
|
108
|
+
return data;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function main() {
|
|
112
|
+
const args = argv.slice(2);
|
|
113
|
+
const opts = parseArgs(args);
|
|
114
|
+
|
|
115
|
+
const useColor = !opts.noColor && (opts.forceColor || hasAnsiSupport());
|
|
116
|
+
|
|
117
|
+
let Chalk;
|
|
118
|
+
if (useColor) {
|
|
119
|
+
const { Chalk: C } = await import("chalk");
|
|
120
|
+
Chalk = new C({ level: 3 });
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (opts.files.length === 0) {
|
|
124
|
+
if (isInteractive()) { stdout.write(HELP + "\n"); exit(0); }
|
|
125
|
+
const text = await readStdin();
|
|
126
|
+
if (!text) { exit(0); }
|
|
127
|
+
if (opts.extract) {
|
|
128
|
+
const results = extractColors(text);
|
|
129
|
+
stdout.write(results.join("\n") + "\n");
|
|
130
|
+
} else if (useColor) {
|
|
131
|
+
stdout.write(colorize(text, Chalk));
|
|
132
|
+
} else {
|
|
133
|
+
stdout.write(text);
|
|
134
|
+
}
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
for (const file of opts.files) {
|
|
139
|
+
let text;
|
|
140
|
+
try {
|
|
141
|
+
text = await readFile(file, "utf-8");
|
|
142
|
+
} catch (err) {
|
|
143
|
+
stderr.write(`Cannot read "${file}": ${err.code === "ENOENT" ? "file not found" : err.message}\n`);
|
|
144
|
+
exit(1);
|
|
145
|
+
}
|
|
146
|
+
if (opts.extract) {
|
|
147
|
+
const results = extractColors(text);
|
|
148
|
+
stdout.write(results.join("\n") + "\n");
|
|
149
|
+
} else if (useColor) {
|
|
150
|
+
stdout.write(colorize(text, Chalk));
|
|
151
|
+
} else {
|
|
152
|
+
stdout.write(text);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
main();
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "awecolor",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Colorize hex color codes in text",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"awecolor": "./awecolor.mjs"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"awecolor.mjs"
|
|
11
|
+
],
|
|
12
|
+
"keywords": [
|
|
13
|
+
"hex",
|
|
14
|
+
"color",
|
|
15
|
+
"cli",
|
|
16
|
+
"colorize",
|
|
17
|
+
"terminal"
|
|
18
|
+
],
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=18"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"chalk": "^5.4.1"
|
|
24
|
+
},
|
|
25
|
+
"scripts": {
|
|
26
|
+
"test": "echo '#FF0000 #00FF00 #0000FF' | node awecolor.mjs --extract | grep -q '#FF0000'"
|
|
27
|
+
},
|
|
28
|
+
"license": "MIT"
|
|
29
|
+
}
|