Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Модуль 1 «Введение в Node.js. CLI» #1

Merged
merged 15 commits into from
Jan 17, 2024
Merged

Conversation

AntonovIgor
Copy link
Contributor

No description provided.

Чтобы создать CLI-интерфейс в нашем приложении необходимо спланировать детали реализации и архитектуру. Определимся с командами. Функциональность CLI-приложения реализуется за счёт команд. Все команды перечислены в техническом задании к проекту.

А что такое команда? Команда выполняет определённое действие и команд несколько. Можно оформить в виде одного модуля, но поддерживать его станет сложно. Да и непонятно чего придерживаться при разработке дополнительных команд: какую структуру соблюдать, как передавать параметры и так далее.

Чтобы ответить и учесть эти вопросы, спроектируем интерфейс команды. У каждой команды обязательно должно быть имя и возможность выполниться.

Интерфейс `Command` определяет:

* Метод `getName`. Он вернёт имя команды. Например, `--help`, `--generate`. Он пригодится при реализации менеджера команд, чтобы отличить одну команду от другой.
* Метод `execute`. Метод для выполнения команды. У каждой команды должен быть реализован одноимённый метод.
Создадим новый модуль `version.command.ts` и опишем класс, который имплементирует ранее созданный интерфейс (`Command`).

Команда `--version` должна считывать из `package.json` версию приложения. То есть значение свойства `version`.

Для чтения файла воспользуемся встроенным модулем `readFileSync`. Импортируем из модуля `node:fs` функцию `readFileSync` и прочитаем с её помощью файл `package.json`. Не забываем указывать кодировку.

Перед чтением файла воспользуемся функцией `resolve` из встроенного модуля `node:path`. Эта функция преобразует относительный путь в абсолютный.
Создадим ещё одну команду — `--help`. Она возвращает информацию о доступных командах. Эта команда используется в качестве команды по умолчанию. То есть когда пользователь ничего не ввёл.
Мы реализовали две команды, теперь научимся ими управлять. Для этого
потребуется отдельный менеджер. Реализуем его в виде класса приложения `CLIApplication`.

Он будет решать несколько задач: регистрировать список команд
(ранее созданные классы), разбирать пользовательский ввод и запускать
нужную команду.

Начнём с регистрации списка команд. Создадим метод `registerCommands`
и опишем в нём функцию для регистрации команд.

На вход метод будет принимать массив с командами. Внутри предусмотрим проверку: убедимся, что невозможно зарегистрировать две команды с одинаковым именем.
Научимся разбирать пользовательский ввод. Например, если пользователь вводит `--help` необходимо разобрать эту строку в более удобную структуру, чтобы потом было проще искать одну из доступных команд. Особенно это
актуально, когда команда будет получать параметры.

Создадим новый класс `CommandParser`. В нём реализуем статический метод `parse`.
Команды регистрируются, пользовательский ввод парсится (разбирается). Остаётся научить приложение исполнять команды.

Создадим отдельный публичный метод `processCommand`. Аргументом он получает пользовательский ввод и задействует ранее созданные методы.

Обратите внимание, мы предусмотрели команду по умолчанию. Ей становится `--help`.
Соберём всё вместе: создадим отдельную точку входа для CLI-приложения. В ней опишем единственную функцию `bootstrap` в которой выполним инициализацию приложения.

Попробуйте протестировать приложение с помощью сценария `ts`. Например: `npm run ts ./src/main.cli.ts -- --version`. Обратите внимание на два дополнительных дефиса. Мы передаём параметры для `main.cli.ts`, а не для `npm`.
Попробуем реализовать более сложную команду — `--import`. Эта команда должна  импортировать тестовые данные из tsv-файла.

tsv-файл — это обычный текстовый файл. Значения в нём разделены символами табуляции. Чтобы разобрать такую строку на отдельные составляющие, необходимо разрезать её по символу `\t`.
Подготовим основу для команды `--import`. Опишем новый класс и сделаем заготовку для метода `execute`.

WIP: Линтер ругается на неиспользуемые переменные.
Перед тем, как написать код для считывания данных, подумаем о структурах и типах. Мы считываем данные определённой «формы». Подготовим описание типов.

Создадим директорию `shared/types` и опишем в ней общие типы. Для удобства каждый тип опишем в отдельном модуле. Нам потребуется несколько типов:

- Объявление (`Offer`);
- Категория (`Category`);
- Пользователь (`User`);
- Тип объявления (`OfferType`).

При описании типов важно опираться на техническое задание, чтобы ничего не пропустить.
Займёмся импортом данных из файлов. Мы без проблем можем написать этот код внутри команды `ImportCommand`, но вместо этого создадим отдельный класс.

Почему: код импорта может меняться и усложняться. Поэтому лучше разместить его отдельно. К тому же команда импорта может начать работать с файлами разных форматов, не только TSV.

Определим минимальный интерфейс. Класс, отвечающий за чтение данных должен уметь их читать. Определим метод `read`.
Создадим класс `TSVFileReader`. В нём реализуем чтение и разбор tsv-файла.

Воспользуемся той же практикой — синхронной функцией `readFileSync`. Она прочитает файл целиком.

Такой подход хорош, когда файлы небольшие, но с большими файлами мы разберёмся позже.

Мы считываем файл целиком и разбиваем его на строки. В этом нам поможет управляющий символ `\n`.

Затем каждую строку разбиваем на отдельные части.
Вернёмся к команде `ImportCommand` и добавим код для чтения из файла. В методе `execute` добавим инициализацию класса `TSVFileReader`.

Результат временно выведем в консоль.
Чтобы команда `--import` заработала, зарегистрируем её в `CLIApplication`, а затем попробуем протестировать:

```
npm run ts ./src/main.cli.ts -- --import <path-to-mock>
```
Остался последний штрих. Команды работают, но для запуска приходится использовать `ts-node` или `node`. С консольными приложениями всё обычно происходит не так.

Мы их запускаем в терминале. Чтобы наше приложение удалось запустить в терминале, нам необходимо прописать `sheband`.

sheband — последовательность из символов `#` и `!`. После этого символа можно определить путь к приложению, которое умеет исполнять данный скрипт.

Укажем в качестве такого приложения путь к Node.js.

Попробуем собрать проект (`npm run build`) и запустить: `./dist/main.cli.js --help`. Ничего не выходит. Терминал выдаст ошибку
«permission denied».

Чтобы исправить ошибку — необходимо сделать файл запускаемым. Для этого
добавим файлу дополнительный атрибут с помощью команды `chmod`. Выполните:
`chmod u+x ./dist/main.cli.js`. После этого попробуйте запустить приложение
в терминале.
@AntonovIgor AntonovIgor self-assigned this Jan 10, 2024
@AntonovIgor AntonovIgor merged commit 832d417 into main Jan 17, 2024
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant