Matie - M ake A T hing; и Я интерактивного E xplanation

Командная строка - это забавная порода глупо простых, безумно глубоких и сложных с долгой историей постоянного развития. Целые книги, классы, степени и докторские диссертации были созданы для понимания командной строки и текстовых интерфейсов.

Что такое CLI

Но мы не будем об этом говорить. Мы собираемся что-то сделать и, надеюсь, получим от этого удовольствие. Создание интерфейса командной строки может быть невероятно простым, но вы можете потратить годы на его полировку и добавление, охватывая больше вариантов использования и обеспечивая его работу различными способами. Хватит об этом, давайте поговорим о том, что такое CLI, чтобы мы могли избавиться от этого.

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

По сути, командная строка имеет приглашение для ввода, например, кассир банка говорит «Чем я могу вам помочь сегодня?», а ваша команда - вы говорите банкиру «Да, я хочу снять со своего счета 350 долларов ». Этот обмен может продолжаться до тех пор, пока банкир не скажет: «Есть ли еще что-нибудь, чем я могу вам помочь?», и вы не ответите «нет». Это похоже на вызов программы exit.

CLI - это просто последовательный набор действий и входов до тех пор, пока не будет достигнут конечный выход или он не вернется в нейтральное состояние.

Хорошо, теперь, когда мы это прошли, давайте уже что-нибудь сделаем! Я обещаю, что с большинством многословных слов покончено.

Установка

Я буду использовать Node.js версии 9.4.0 и встроенный модуль readline. Вот и все. Никаких плагинов, разработчиков приложений или фреймворков. После этого я буду обсуждать, какие модули я могу порекомендовать добавить в этот небольшой проект.

Теперь давайте инициализируем пустой проект с помощью npm в терминале:

$ mkdir Magie
$ cd Magie
$ npm init

Просто примите все значения по умолчанию или заполните их как хотите. Я начну работать с index.js в качестве точки входа по умолчанию, но вы можете установить здесь все, что захотите.

Структура приложения

// index.js
[Matie Class]
    |
    +-[Add Module]
    |
    +-[Remove Module]
    |
    +-[Clear Screen]
    |
    +-[Exit Program]
    |
    +-[Promt]
    |
    +-[Output To Terminal]
    |
    +-[parseCommand]
    |
    +-[runCommand]

Это базовая структура модуля, которую мы будем использовать для создания нашего маленького интерфейса командной строки Matie. Кажется, что это не так уж и много, и многое еще упущено, но это все, чтобы перейти к основному циклу взаимодействия.

Базовый поток приложения

Это упрощенная схема цикла событий. Однако с Мати будет сложнее.

  • Распечатайте приглашение, чтобы пользователь знал, что вы готовы к вводу, и дождитесь возврата строки.
  • По возвращении обработайте строку по той схеме, по которой вы планируете команды для работы.
  • Действовать на вводе как на ошибку или как на команду, которую нужно запустить.
  • Результат вывести в терминал.
  • Завершить или вернуться к приглашению к началу.

Просто как 1, 2, 3

Мы начнем с чего-то простого, которое считывает переданные им аргументы, а затем может что-то делать на основе этих входных данных.

На самом деле это ничего не делает, кроме выхода из системы, каждый переданный аргумент показывает символ приглашения, а затем завершается, но это все еще базовое приложение CLI.

Есть несколько моментов, которые намекают на то, что мы будем делать дальше, но основной поток уже начинает складываться.

Мы обрабатываем аргументы, а затем вызываем приглашение; Здесь программа запускает цикл чтения, оценки и печати.

Затем у нас действительно будет цикл после ввода данных из пользовательской подсказки!

Readline

Затем нам нужно настроить модуль readline, чтобы помочь нам захватить строку терминала при возврате и передать ее программе. Он предоставляет интерфейс, который читает по одной строке из любого читаемого потока.

Во-первых, в конструкторе нашего класса мы устанавливаем свойство rl для хранения интерфейса readline, созданного readline.createInterface. Это вернет интерфейс для ввода и вывода, который мы передаем, который можно было бы использовать для обработки файла или потока, но это совершенно другая статья. Затем мы установим событие line для интерфейса readline.

this.rl.on('line', this.command.bind(this);

Мы просто настраиваем событие line, но есть и другие, которые мы можем охватить для полного освещения события. Вы можете прочитать Документацию для получения дополнительной информации, но для этого мы просто установим это одно событие. метод on принимает два аргумента: строку, описывающую конкретное событие, и функцию, которая должна быть вызвана для события, которому передается один аргумент, который представляет собой строку, содержащую строку .

Если бы мы запустили это сейчас, это отобразило бы нашу подсказку и позволило бы нам ввести все, что мы хотели, нажать ender, а затем просто отобразить подсказку снова. чтобы выйти из этого цикла, вам нужно нажать CTRL + C. Давай что-нибудь с этим поделать.

Убирайте с работы то, что вы вкладываете

Теперь, когда он ожидает ввода, нам нужно дать ему способ обработки нашего ввода. Сначала мы должны взглянуть на то, что это нам дает. Давайте добавим метод runCommand, который мы использовали в качестве обратного вызова для нашего события line, и поместим в него пару команд.

runCommand(cmdStr) {
    console.log(cmdStr);
    this.prompt();
}

Ему передается строка в качестве аргумента при запуске. Он выводит строку, переданную с терминала, и показывает подсказку, когда она завершена. После этого он возвращается к началу и снова ждет ввода пользователя.

Затем нам нужно вызвать действия на основе набираемых нами команд. Для этого потребуется немного предусмотрительности. Вы можете спланировать любую структуру, которую хотите, чтобы ваши команды принимали, но вам нужно иметь возможность обрабатывать строки в этой структуре. Для простоты я буду работать с командами, разделенными пробелами, где первая - это команда, а все последующие - просто аргументы, переданные в виде массива в метод-обработчик. Я знаю! Это полный рот. Давай перейдем к делу.

const cmdArr = cmdStr.split(' ');
const cmd = (cmdArr.length >= 1) ? cmdArr.splice(0, 1)[0] : null;

Нам нужен cmd, первый элемент, а затем аргументы в массиве. Для этого я разделю cmdStr пробелами на cmdArr,, а затем разделю первый элемент, проверив длину cmdArr имеет длину не менее 1. В противном случае это может быть просто NULL.

После этого мы захотим сравнить нашу переменную cmd со списком возможных команд. На данный момент этот список будет небольшим и статичным, поэтому для этого мы воспользуемся простым оператором switch.

switch(cmd) {
    case 'quit':
    case 'exit':
        this.exitProgram();
        break;
    default:
        this.output(`"${cmd}" is not a valid command`);
        break;
}

Это довольно просто, но мы можем расширить это. Однако сначала нам нужно убедиться, что мы включили эти два метода! Я просто перечислю их. Однако, чтобы закрыть программу, нам придется использовать process.

exitProgram() {
    process.exit(0);
}

Все, что мы сделали, это вызвал метод процесса с именем exit, который принимает один тип int. Int указывает статус программы при выходе. Например, вызов exit с помощью 0 завершает программу с успехом 1, а завершается с ошибкой.

output(str) {
    process.stdout.write(str + '\n');
}

В методе output мы также снова вызвали объект process. Здесь мы могли бы использовать console.log, который также использует process.stdout, однако я хотел показать, что мы просто читаем и действуем в потоках stdin и stdout. По сути, это повторяет основной вывод console.log().

С помощью readline и process мы можем выполнять множество задач. Я бы посоветовал подробнее прочитать об этих двух в документации по узлам.

Объединив все это, мы получаем нашу первую внутреннюю команду для вызова.

Вы можете очень легко расширить оператор switch другими командами и методами. Однако теперь нам нужно передать аргументы нашим методам, чтобы вы могли передавать данные из терминала этим методам.

Методические аргументы

Мы уже выделили аргументы из нашей команды и поместили их в массив, теперь все, что нам нужно сделать, это передать их методу при его вызове. Мы могли бы принять решение о какой-то обработке, определяя, соответствуют ли аргументы некоторым руководящим принципам для различных методов, но давайте оставим это простым и позволим этому конкретному методу, который мы вызываем.

Взяв то, что мы уже сделали в нашем методе runCommand, давайте изменим его так, чтобы он передавал cmdArr каждому из вызовов cmd. Мы могли бы установить некоторую переменную, чтобы они могли получить к ней доступ и ее можно было просматривать в масштабе всего класса, но на данный момент это более простое решение:

А затем это так же просто, как принять аргументы в любом из наших методов, которые требуют их. Если мы хотим быть конкретными, их можно игнорировать или даже вызывать ошибки.

Вот и все. Мы создали наш интерфейс командной строки, чтобы иметь все необходимое для расширения основных функций и взаимодействий. Однако это только часть 1 из 2. Во второй части мы подробнее поговорим об обработке аргументов и поиске ошибок. Затем мы сделаем так, чтобы программа загружала расширения автоматически и даже могла быть расширена прямо из интерфейса.

Если вам понравилось это и вы хотите узнать больше о коде или узнать больше обо мне, вы можете следить за мной и моими приключениями в Lambda School в твиттере или следить за моей следующей статьей. Вы всегда можете написать мне на почту, если у вас возникнут какие-либо вопросы.