UNPKG

eyo

Version:

CLI for restoring the letter «ё» (yo) in russian texts

254 lines (220 loc) 6.2 kB
'use strict'; const chalk = require('chalk'); const charset = require('charset'); const glob = require('glob'); const fs = require('fs'); const iconv = require('iconv-lite'); const isutf8 = require('isutf8'); const program = require('commander'); const fetch = require('node-fetch'); const Eyo = require('eyo-kernel'); const exitCodes = require('./exit-codes'); const { diffColor, okSymbol, errorSymbol, warningSymbol, } = require('./symbols'); const safeEyo = new Eyo(); safeEyo.dictionary.loadSafeSync(); const notSafeEyo = new Eyo(); notSafeEyo.dictionary.loadNotSafeSync(); 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 (item.count > 1) { info.push('count: ' + item.count); } const text = (i + 1) + '. ' + newBefore.join('') + ' → ' + newAfter.join('') + (info.length ? ' (' + info.join(', ') + ')' : ''); if (isError) { console.error(text); } else { console.warn(text); } } function printErrorItem(item, i) { printItem(item, i, true); } function printWarningItem(item, i) { printItem(item, i, false); } /** * Это ссылка? * * @param {string} path * @returns {boolean} */ function isUrl(path) { return path.search(/^https?:/) > -1; } /** * Развернуть glob-аргументы. * * @param {string[]} args * @returns {string[]} */ 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 */ function processText(text, resource) { if (program.lint) { lintText(text, resource); } else { if (program.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 */ function lintText(text, resource) { const safeReplacement = safeEyo.lint(text, program.sort); let notSafeReplacement = []; if (!program.onlySafe) { notSafeReplacement = notSafeEyo.lint(text, program.sort); } if (safeReplacement.length) { console.error(chalk.red(errorSymbol) + ' ' + resource); } else if (notSafeReplacement.length) { console.warn(chalk.yellow(warningSymbol) + ' ' + resource); } else if (!program.onlySafe) { console.log(chalk.green(okSymbol) + ' ' + resource); } if (safeReplacement.length) { console.error(chalk.red('Safe replacements:')); 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.forEach(printWarningItem); console.warn(chalk.yellow('---')); } } /** * Ёфицировать файл. * * @param {string} file * * @returns {Promise} */ function processFile(file) { return new Promise((resolve, reject) => { if (isFile(file)) { fs.readFile(file, (error, buffer) => { if (error) { reject(error); return; } if (isutf8(buffer)) { processText(buffer.toString('utf8'), file); } else { 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} */ function isFile(file) { return fs.existsSync(file) && fs.statSync(file).isFile(); } /** * Ёфицировать страницу. * * @param {string} url */ async function processUrl(url) { try { const response = await fetch(url); const buffer = await response.buffer(); if (response.status !== 200) { console.log(chalk.red(`${url}: returns status code is ${response.status}.`)); process.exitCode = exitCodes.ERROR_LOADING; return; } if (isutf8(buffer)) { processText(buffer.toString('utf8'), url); } else { const encoding = charset(response.headers.get('content-type'), buffer); if (iconv.encodingExists(encoding)) { processText(iconv.decode(buffer, encoding), url); } else { console.error(encoding + ': is unknown charset.'); process.exitCode = exitCodes.UNKNOWN_CHARSET; } } } catch(error) { console.log(chalk.red(error)); process.exitCode = exitCodes.ERROR_LOADING; } } module.exports = { expandGlobArgs, isUrl, processFile, processText, processUrl, };