-
Notifications
You must be signed in to change notification settings - Fork 4
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
Conversation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Чтобы создать 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`. После этого попробуйте запустить приложение в терминале.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
No description provided.