UNPKG

pangu

Version:

Paranoid text spacing for good readability, to automatically insert whitespace between CJK (Chinese, Japanese, Korean) and half-width characters (alphabetical letters, numerical digits and symbols).

138 lines (121 loc) 4.69 kB
#!/usr/bin/env node import pangu from './index.js'; const usage = ` usage: pangu [-h] [-v] [-t | -f | -c] [text_or_path] pangu.js -- Paranoid text spacing for good readability, to automatically insert whitespace between CJK and half-width characters (alphabetical letters, numerical digits and symbols). positional arguments: text_or_path the text or file path to apply spacing; omit it to read stdin when input is piped optional arguments: -h, --help show this help message and exit -v, --version show program's version number and exit -t, --text specify the input value is a text -f, --file specify the input value is a file path (pass - to read from stdin) -c, --check check if text has proper spacing (exit 0 if yes, 1 if no) `.trim(); const [, , ...args] = process.argv; type Mode = '--text' | '--file' | '--check'; const modeFlags: Record<string, Mode> = { '-t': '--text', '--text': '--text', '-f': '--file', '--file': '--file', '-c': '--check', '--check': '--check', }; // An explicit - always means stdin. A missing argument only falls back to stdin when something is actually piped in, so running pangu with no arguments in a terminal still prints // the usage instead of waiting for input that never comes function wantsStdin(arg: string | undefined) { return arg === '-' || (arg === undefined && !process.stdin.isTTY); } // Reading has to be async: readFileSync(0) throws EAGAIN once a pipe carries more than a buffer or two. console.log() puts a trailing newline back, so dropping one here passes piped // input through byte for byte async function readStdin() { process.stdin.setEncoding('utf8'); const chunks: string[] = []; for await (const chunk of process.stdin) { chunks.push(String(chunk)); } return chunks.join('').replace(/\n$/, ''); } function printSpacingText(text: string | undefined) { if (typeof text === 'string') { console.log(pangu.spacingText(text)); } else { console.log(usage); process.exitCode = 1; } } // An empty string is what -f "$EMPTY_VAR" expands to, so it counts as a missing path rather than a file to open function printSpacingFile(path: string | undefined) { if (path) { // File mode only does spacing: no newline appended, the file's own EOF newlines (or lack of one) pass through untouched process.stdout.write(pangu.spacingFileSync(path)); } else { console.error('pangu: error: argument --file: expected a file path'); console.log(usage); process.exitCode = 1; } } function checkSpacing(text: string | undefined) { if (typeof text === 'string') { const hasProperSpacing = pangu.hasProperSpacing(text); if (!hasProperSpacing) { // Optionally print the corrected version to stderr for debugging console.error(`Corrected: ${pangu.spacingText(text)}`); } process.exitCode = hasProperSpacing ? 0 : 1; } else { console.log(usage); process.exitCode = 1; } } // Every exit goes through process.exitCode rather than process.exit(), because process.exit() truncates a piped stdout at one pipe buffer and pangu now streams whole files through it async function main() { // -t, -f and -c are mutually exclusive const givenModes = new Set(args.filter((arg) => arg in modeFlags).map((flag) => modeFlags[flag])); if (givenModes.size > 1) { const [first, second] = [...givenModes]; console.error(`pangu: error: argument ${second}: not allowed with argument ${first}`); console.log(usage); process.exitCode = 1; return; } if (args.length === 0) { printSpacingText(wantsStdin(undefined) ? await readStdin() : undefined); return; } switch (args[0]) { case '-h': case '--help': console.log(usage); break; case '-v': case '--version': console.log(`pangu.js ${pangu.version}`); break; case '-t': case '--text': printSpacingText(wantsStdin(args[1]) ? await readStdin() : args[1]); break; case '-f': case '--file': // An explicit - is the conventional spelling for "the file is stdin" (cf. tar -f -). A missing path is a usage error instead of a stdin fallback, so that -f with an empty path variable // reports the missing path instead of silently spacing whatever happens to be piped in if (args[1] === '-') { printSpacingText(await readStdin()); } else { printSpacingFile(args[1]); } break; case '-c': case '--check': checkSpacing(wantsStdin(args[1]) ? await readStdin() : args[1]); break; case '-': printSpacingText(await readStdin()); break; default: printSpacingText(args[0]); } } await main();