Перейти к содержимому
Skein
← Все проекты

systems · starter · 4d

CLI-утилита для текста

Собери небольшую утилиту командной строки, читающую текст из stdin или файла, трансформирующую (фильтр, подсчёт, нарезка) и компонующуюся через пайпы — путь Unix, без фреймворка.

Самый маленький настоящий CLI — текстовый фильтр читающий из stdin или файла, делающий одну вещь и компонующийся через пайпы. Собери его и поймёшь контракт Unix: stdin/stdout/stderr, коды выхода и потоковость чтобы файлу 500 МБ не нужно было 500 МБ RAM. Каждый больший CLI — просто больше флагов на том же скелете.

Результат

CLI на Node (shebang + chmod +x) читающий из stdin или аргумента-файла, с флагами --filter / --count / --slice, обработкой больших файлов потоками, корректными exit-кодами и компонуемостью через | и >.

Этапы

0/5 · 0%
  1. 01CLI который запускается

    Сделай файл запускаемый как CLI: shebang (#!/usr/bin/env node), chmod +x и одна команда читающая аргумент или stdin и пишущая в stdout. Запусти как ./tool hello и как echo hello | ./tool — оба должны работать. Exit-коды важны: 0 на успехе, не-ноль на отсутствующем вводе или плохом флаге, так что пайп вроде ./tool --bad 2>/dev/null; echo $? показывает код ошибки. Пока без библиотеки парсинга аргументов — только process.argv и process.stdin чтобы увидеть что прячет каждая библиотека.

    Критерии готовности
    • Файл с shebang, исполняем и работает как ./tool arg и как echo hi | ./tool.
    • Плохой ввод или неизвестный флаг завершается не-нулевым кодом тестируемым через echo $?.
    Самопроверка

    Покажи ./tool arg и echo | ./tool оба дают вывод и плохой флаг → не-нулевой exit. Ревьюер проверяет shebang + chmod и коды выхода.

  2. 02Stdin или файл — один код

    Пусть утилита читает либо из stdin (когда пайп), либо из аргумента-пути к файлу (когда дан), как cat, grep и sort. Правило: если аргумент-файл есть — читай его, иначе читай stdin. Обрабатывай оба единообразно как readable stream чтобы остальная утилита не заботилась откуда байты. Обрабатывай ошибки: отсутствующий файл → stderr + не-нулевой exit, не краш и не тихий пустой вывод. Докажи: cat file.txt | ./tool --count и ./tool --count file.txt дают одинаковый count.

    Критерии готовности
    • И cat file | ./tool и ./tool file дают одинаковый вывод; отсутствующий файл пишет в stderr и завершается не-нулевым кодом.
    • Ввод потребляется потоком (не readFileSync всего файла) так что большие файлы не раздувают кучу.
    Самопроверка

    Покажи пайп vs аргумент-файл одинаковый вывод и отсутствие файла → stderr + не-ноль. Ревьюер проверяет что источник — поток, не синхронное чтение всего файла.

  3. 03Флаги: filter, count, slice

    Добавь три флага покрывающих базовые паттерны Unix-текста: --filter <паттерн> оставляет только строки содержащие подстроку (как grep), --count печатает только число подходящих (или всех) строк (как wc -l), --slice <start>:<end> оставляет строки start..end (как sed -n). Парсь их руками из process.argv — без yargs/commander — чтобы обработать --help, неизвестный флаг → stderr + не-ноль и порядок флагов. Флаги компонуются: --filter foo --slice 1:5 означает 'отфильтровать затем нарезать отфильтрованный вывод'. Валидируй: --slice с плохим диапазоном (отрицательный, перевёрнутый) → stderr с не-нолём, не тихий неверный срез. Измерь: отфильтруй фикстуру 100k строк и покажи корректность и скорость.

    Критерии готовности
    • --filter оставляет подходящие строки, --count печатает число, --slice — диапазон строк; флаги компонуются по порядку, --help печатает usage.
    • Плохие диапазоны или неизвестные флаги → stderr + не-ноль; фильтрация фикстуры 100k строк корректна и потоковая, не буферизует всё.
    Самопроверка

    Покажи --filter, --count, --slice отдельно и в композиции плюс плохой флаг → stderr. Ревьюер проверяет ручной парсинг argv, --help и коды ошибок.

  4. 04Потоки для больших файлов

    Сделай обработку большого файла без удержания всего в памяти. Обрабатывай stdin/файл построчно потоком (разделение по \n, обработка хвостовой неполной строки) чтобы файл 500 МБ никогда не превышал нескольких МБ кучи. Наивный readFileSync + split даст OOM или давление GC на больших входах; поток держит память плоской. Обработай край: строка длиннее границы чанка должна быть пересобрана до фильтрации, не разрезана посередине. Докажи: сгенерируй фикстуру 10k строк и также 100 МБ фикстуру (или симулируй большим потоком), запусти утилиту и покажи ограниченность кучи (или хотя бы корректность числа строк и отсутствие краша). Объясни почему построчный буфер, а не буфер чанков — правильная гранулярность.

    Критерии готовности
    • Большие файлы обрабатываются построчно потоками с корректной обработкой строк на границах чанков; фикстура 100 МБ не даёт OOM и не ошибается в подсчёте.
    • Ты можешь объяснить почему построчный буфер (не буфер чанков) корректен и где readFileSync сломался бы.
    Самопроверка

    Покажи корректный подсчёт строк на большой фикстуре и объясни обработку границ чанков. Ревьюер проверяет что ввод — построчный поток, не чтение всего файла.

  5. 05Компануй через пайпы и редиректы

    Докажи что утилита — настоящий гражданин Unix: она компонуется с другими утилитами через пайпы и редиректы без специального кода. Продемонстрируй: cat file | ./tool --filter foo | sort | uniq -c (пайп наружу), ./tool --filter foo file > out.txt (редирект), ./tool --filter foo file | ./tool --count (пайп в себя). Вывод каждого флага — построчный текст на stdout, ошибки на stderr, код выхода сигнализирует успех — так что > и | работают ровно как для cat/grep/wc. Добавь крошечный тестовый харнесс спавнящий CLI (child_process.spawn) и проверяющий stdout/stderr/код выхода на паре случаев — твои первые тесты CLI без фреймворка.

    Критерии готовности
    • Пайпинг в sort/uniq и редирект в файл работают; пайп утилиты в себя (--filter | --count) корректен.
    • Тест на spawn проверяет stdout/stderr/код выхода минимум на 3 случаях (успех, фильтр, плохой флаг).
    Самопроверка

    Покажи пайп в sort/uniq, редирект в файл и пайп в себя плюс spawn-тесты. Ревьюер проверяет что stdout — построчный текст, stderr — ошибки, коды выхода корректны.

Стартер

fallowlone/skein-projects

projects/cli-text-tool

Открыть на GitHub ↗
  • README.md
  • src/tool.ts
  • test/tool.test.ts
Забрать только этот проект npx degit fallowlone/skein-projects/projects/cli-text-tool cli-text-tool

Реализуй заглушки, затем гоняй тесты, пока не позеленеют: bun test

Форкни репозиторий и запушь свою работу — workflow grade прогонит тесты и статические проверки на твоих раннерах.

Рубрика

Джуниор Миддл Сеньор
Контракт Unix (stdin/файл, stdout/stderr, коды выхода) Читает только stdin или только файл, не оба; ошибки идут в stdout или процесс крашится вместо не-нулевого выхода. Читает stdin при пайпе и файл когда дан, пишет результаты в stdout и ошибки в stderr, завершается не-нулевым кодом на плохом вводе. Коды выхода различают ошибку использования (2) vs файл-не-найден (1) vs успех (0), утилита pipe-safe (без лишнего форматирования ломающего sort/uniq).
Потоковость и большие файлы Читает весь файл readFileSync и сплитит; работает на маленьких фикстурах но OOM или ошибается на больших файлах или строках на границах чанков. Обрабатывает ввод построчным потоком с корректной пересборкой на границах чанков; большая фикстура считается правильно не держа всё в памяти. Может объяснить почему построчный буфер — правильная гранулярность, где буфер чанков разрезал бы строку посередине фильтра и как backpressure (pause/resume) не даёт писателю обогнать читателя.
Флаги и компонуемость Один флаг работает изолированно; комбинация флагов или пайп в другую утилиту даёт неверный вывод или игнорирует второй флаг. Флаги компонуются по порядку (--filter затем --slice) и пайпинг в sort/uniq или редирект в файл работают; --help печатает usage. Флаги парсятся вручную с корректной обработкой ошибок (неизвестный флаг → stderr + не-ноль), spawn-тест проверяет stdout/stderr/выход на нескольких случаях без фреймворка.
Эталонный разбор (спойлер)

Почему stdin ИЛИ файл: утилиты Unix — фильтры, трансформирующие поток байт независимо от источника. Принятие обоих делает утилиту компонуемой: echo hi | tool и tool file делают одно, tool file | sort | uniq -c цепочка без временных файлов.

Почему потоки: readFileSync грузит весь файл в один Buffer/String до обработки. Файлу 500 МБ нужно 500 МБ кучи плюс давление GC; построчному потоку нужна только текущая строка. Баг границы чанков — строка разрезанная между двумя колбэками чанков — классический дефект потоковости и должен обрабатываться буферизацией хвоста.

Сделай по-сеньорски

  • Добавь --regex для фильтрации JS RegExp и --invert чтобы оставлять несовпадающие строки, с экранированием спецсимволов.
  • Добавь прогресс-бар на stderr для больших файлов (обработано строк) не пачкающий stdout при пайпе.

Навыки

stdin/stdout/stderr & exit codesargument parsing (no library)stream processing for large filespipe & redirect compositiontesting a CLI with spawn

Рекомендуемый стек

nodejavascript (vanilla)

Материалы