eyo
Version:
CLI for restoring the letter «ё» (yo) in russian texts
241 lines (208 loc) • 6.12 kB
JavaScript
import fs from 'node:fs';
import chalk from 'chalk';
import { glob } from 'glob';
import { program } from 'commander';
import { Eyo, notSafeDictionary, safeDictionary } from 'eyo-kernel';
import exitCodes from './exit-codes.mjs';
import {
diffColor,
okSymbol,
errorSymbol,
warningSymbol,
} from './symbols.mjs';
const safeEyo = new Eyo();
safeEyo.dictionary.set(safeDictionary)
const notSafeEyo = new Eyo();
notSafeEyo.dictionary.set(notSafeDictionary);
export function printItem(item, i, isError) {
const before = item.before;
const after = item.after;
const newBefore = [];
const newAfter = [];
const info = [];
const pos = Array.isArray(item.position) ? item.position[0] : item.position;
// Diff by letters
for (let n = 0; n < before.length; n++) {
if (before[n] !== after[n]) {
newBefore[n] = diffColor(before[n]);
newAfter[n] = diffColor(after[n]);
} else {
newBefore[n] = before[n];
newAfter[n] = after[n];
}
}
info.push(pos.line + ':' + pos.column);
if (Array.isArray(item.position) && item.position.length > 1) {
info.push('count: ' + item.position.length);
}
const text =
(i + 1) + '. ' +
newBefore.join('') + ' → ' +
newAfter.join('') +
(info.length ? ' (' + info.join(', ') + ')' : '');
if (isError) {
console.error(text);
} else {
console.warn(text);
}
}
export function printErrorItem(item, i) {
printItem(item, i, true);
}
export function printWarningItem(item, i) {
printItem(item, i, false);
}
/**
* Это ссылка?
*
* @param {string} path
* @returns {boolean}
*/
export function isUrl(path) {
return path.search(/^https?:/) > -1;
}
/**
* Развернуть glob-аргументы.
*
* @param {string[]} args
* @returns {string[]}
*/
export function expandGlobArgs(args) {
let result = [];
for (const value of args) {
if (isUrl(value)) {
result.push(value);
} else {
const files = glob.sync(value);
if (files) {
result = result.concat(files);
}
}
}
return result;
}
/**
* Ёфицировать текст и вывести в консоль.
*
* @param {string} text
* @param {string} resource
*/
export function processText(text, resource) {
const opts = program.opts();
if (opts.lint) {
lintText(text, resource);
} else {
if (opts.inPlace) {
try {
const result = safeEyo.restore(text);
fs.writeFileSync(resource, result);
} catch(e) {
process.exitCode = exitCodes.CANT_WRITE;
console.error(e);
}
} else {
process.stdout.write(safeEyo.restore(text));
}
}
}
/**
* Проверка текста.
*
* @param {string} text
* @param {string} resource
*/
export function lintText(text, resource) {
const opts = program.opts();
const safeReplacement = safeEyo.lint(text, opts.sort);
let notSafeReplacement = [];
if (!opts.onlySafe) {
notSafeReplacement = notSafeEyo.lint(text, opts.sort);
}
if (safeReplacement.length) {
console.error(chalk.red(errorSymbol) + ' ' + resource);
} else if (notSafeReplacement.length) {
console.warn(chalk.yellow(warningSymbol) + ' ' + resource);
} else if (!opts.onlySafe) {
console.log(chalk.green(okSymbol) + ' ' + resource);
}
if (safeReplacement.length) {
console.error(chalk.red(`Safe replacements: ${safeReplacement.length}`));
safeReplacement.forEach(printErrorItem);
console.error(chalk.red('---'));
if (!process.exitCode) {
process.exitCode = exitCodes.HAS_REPLACEMENT;
}
}
if (notSafeReplacement.length) {
console.warn(chalk.yellow(`Not safe replacements: ${notSafeReplacement.length}`));
notSafeReplacement.forEach(printWarningItem);
console.warn(chalk.yellow('---'));
}
}
/**
* Ёфицировать файл.
*
* @param {string} file
*
* @returns {Promise}
*/
export function processFile(file) {
return new Promise((resolve, reject) => {
if (isFile(file)) {
fs.readFile(file, (error, buffer) => {
if (error) {
reject(error);
return;
}
const decoder = new TextDecoder('utf-8', { fatal: true });
try {
processText(decoder.decode(buffer), file);
} catch {
console.error(chalk.red(file + ': is not UTF-8.'));
process.exitCode = exitCodes.NOT_UTF8;
}
resolve();
});
} else {
console.error(chalk.red(file + ': no such file.'));
process.exitCode = exitCodes.NO_SUCH_FILE;
resolve();
}
});
}
/**
* Это файл?
*
* @param {string} file
* @returns {boolean}
*/
export function isFile(file) {
return fs.existsSync(file) && fs.statSync(file).isFile();
}
/**
* Ёфицировать страницу.
*
* @param {string} url
*/
export async function processUrl(url) {
try {
const response = await fetch(url);
const arrayBuffer = await response.arrayBuffer();
const buffer = Buffer.from(arrayBuffer);
if (response.status !== 200) {
console.log(chalk.red(`${url}: returns status code is ${response.status}.`));
process.exitCode = exitCodes.ERROR_LOADING;
return;
}
const decoder = new TextDecoder('utf-8', { fatal: true });
try {
processText(decoder.decode(buffer), url);
} catch {
console.error('Erroor: is unknown charset.');
process.exitCode = exitCodes.UNKNOWN_CHARSET;
}
} catch(error) {
console.log(chalk.red(error));
process.exitCode = exitCodes.ERROR_LOADING;
}
}