Обработка файлов из командной строки

Для работы с Markdown и HTML через Node.js используются библиотеки Remark и Rehype. Remark отвечает за разбор и трансформацию Markdown, а Rehype — за работу с HTML.

npm install remark remark-cli rehype rehype-stringify unified
  • remark — основной пакет для работы с Markdown.
  • remark-cli — позволяет использовать Remark из командной строки.
  • rehype — библиотека для обработки HTML.
  • rehype-stringify — сериализация дерева HTML обратно в строку.
  • unified — ядро для построения конвейеров обработки текста.

Создание конвейера обработки файлов

Обработка файлов из командной строки строится вокруг потока unified. Конвейер может включать несколько этапов: чтение файла, разбор Markdown, трансформация AST, конвертация в HTML и запись результата.

Пример базового конвейера для конвертации Markdown в HTML:

import fs from 'fs';
import { unified } from 'unified';
import remarkParse from 'remark-parse';
import remarkRehype from 'remark-rehype';
import rehypeStringify from 'rehype-stringify';

const filePath = process.argv[2];

const fileContent = fs.readFileSync(filePath, 'utf-8');

const processed = unified()
  .use(remarkParse)        // Разбор Markdown
  .use(remarkRehype)       // Преобразование в HTML AST
  .use(rehypeStringify)    // Сериализация в HTML
  .processSync(fileContent);

console.log(String(processed));

В этом примере:

  • process.argv[2] — путь к файлу, передаваемому через командную строку.
  • remarkParse преобразует текст Markdown в AST (Abstract Syntax Tree).
  • remarkRehype переводит Markdown AST в HTML AST.
  • rehypeStringify превращает HTML AST обратно в текст HTML.

Работа с несколькими файлами

Для обработки сразу нескольких файлов удобно использовать цикл и встроенные возможности Node.js:

import path from 'path';

const files = process.argv.slice(2);

files.forEach(file => {
  const content = fs.readFileSync(file, 'utf-8');

  const result = unified()
    .use(remarkParse)
    .use(remarkRehype)
    .use(rehypeStringify)
    .processSync(content);

  const outputFile = path.basename(file, '.md') + '.html';
  fs.writeFileSync(outputFile, String(result));
  console.log(`Файл ${outputFile} создан`);
});
  • process.argv.slice(2) позволяет получать все переданные файлы.
  • path.basename используется для генерации имени выходного файла.
  • fs.writeFileSync записывает результат на диск.

Применение плагинов Remark и Rehype

Remark и Rehype поддерживают плагины для расширения функциональности:

  • remark-toc — автоматическая генерация оглавления.
  • remark-autolink-headings — добавление ссылок к заголовкам.
  • rehype-highlight — подсветка синтаксиса кода в HTML.

Пример использования плагина для подсветки кода:

import rehypeHighlight from 'rehype-highlight';

const result = unified()
  .use(remarkParse)
  .use(remarkRehype)
  .use(rehypeHighlight)
  .use(rehypeStringify)
  .processSync(fileContent);

Плагины могут принимать настройки, позволяя контролировать стиль вывода и детали трансформации.

Обработка ошибок и асинхронные операции

При работе с файловой системой и конвейером Remark/Rehype важно учитывать асинхронность и обработку ошибок:

import { readFile, writeFile } from 'fs/promises';

async function processFile(file) {
  try {
    const content = await readFile(file, 'utf-8');
    const result = await unified()
      .use(remarkParse)
      .use(remarkRehype)
      .use(rehypeStringify)
      .process(content);

    const outputFile = file.replace(/\.md$/, '.html');
    await writeFile(outputFile, String(result));
    console.log(`Обработан файл: ${outputFile}`);
  } catch (error) {
    console.error(`Ошибка обработки файла ${file}:`, error.message);
  }
}

const files = process.argv.slice(2);
files.forEach(processFile);
  • fs/promises позволяет работать с файлами через async/await.
  • Ошибки на любом этапе конвейера обрабатываются блоком try/catch.

Поддержка потоковой обработки

Remark и Rehype могут работать не только с полным текстом, но и со стримами, что полезно для больших файлов или потоковой обработки данных:

import { createReadStream, createWriteStream } from 'fs';
import { pipeline } from 'stream';
import { promisify } from 'util';

const pipe = promisify(pipeline);

await pipe(
  createReadStream('input.md'),
  unified()
    .use(remarkParse)
    .use(remarkRehype)
    .use(rehypeStringify)
    .stream(),
  createWriteStream('output.html')
);
  • Такой подход снижает потребление памяти.
  • Можно интегрировать с другими потоками данных, например, сетевыми.

Настройка CLI с помощью Remark CLI

Remark CLI позволяет запускать обработку файлов без написания отдельного скрипта:

npx remark input.md --output output.html --use remark-rehype --use rehype-stringify
  • Флаги --use подключают нужные плагины.
  • CLI поддерживает пакетные операции на нескольких файлах.

Работа с AST для расширенных преобразований

Remark/Rehype создают AST, с которым можно работать напрямую:

import { visit } from 'unist-util-visit';

const tree = unified().use(remarkParse).parse(fileContent);

visit(tree, 'heading', node => {
  node.children.push({
    type: 'text',
    value: ' ?'
  });
});

const html = unified()
  .use(remarkRehype)
  .use(rehypeStringify)
  .stringify(tree);
  • visit позволяет обходить все узлы AST.
  • Можно изменять заголовки, ссылки, абзацы, добавлять атрибуты.

Практическая структура проекта для CLI

Рекомендуемая структура проекта:

project/
├─ src/
│  ├─ cli.js
│  ├─ processor.js
├─ package.json
├─ README.md
  • cli.js — обработка аргументов командной строки.
  • processor.js — создание конвейеров Remark/Rehype.
  • Такой подход упрощает расширение функционала и подключение плагинов.

Эта организация позволяет гибко обрабатывать файлы Markdown, интегрировать плагины и поддерживать как синхронные, так и асинхронные сценарии работы с командной строкой.