Здесь, в Infinite Red, мы проводим много видеоконференций! Почему? Потому что все живут и работают в разных местах! Мы совершенно удаленная компания. Мы используем услугу видеоконференцсвязи Zoom, чтобы оставаться на связи с коллегами и клиентами.

С Zoom у вас могут быть постоянные места для онлайн-встреч, называемые комнатами, каждая со своим постоянным URL-адресом. Эти URL-адреса запомнить непросто, поэтому мы создали инструмент браузера для подключения к этим комнатам. В инструменте браузера каждой комнате дается псевдоним (например, «Клен»), и когда вы нажимаете на псевдоним, вы подключаетесь к соответствующей комнате.

Это прекрасно работает: когда мы хотим с кем-нибудь пообщаться в видеочате, мы говорим: Давай встретимся в Maple, и они открывают инструмент браузера, нажимают на Maple и пуф! Вы вместе на встрече. Но некоторым людям (например, мне) нравится делать все из командной строки; это быстрее! Поэтому я решил создать CLI (интерфейс командной строки), чтобы перенести функциональность псевдонимов инструмента браузера в командную строку. Я использовал Gluegun для создания этого инструмента, который я назвал Leaf CLI. (Кстати, мы также использовали Gluegun для создания Ignite!) В этой статье я объясню, как мне это удалось.

Настройка

Для начала я создал новый проект npm с npm init. Затем я добавил Gluegun с npm i gluegun --save. Затем в моем index.js я требую, создаю экземпляр и запускаю Gluegun:

module.exports = async function () {
  const { build } = require(‘gluegun’)
  const runtime = build()
    .brand(‘leaf_plugin’)
    .loadDefault(`${__dirname}/plugins/leaf_plugin`)
    .createRuntime()
  const result = await runtime.run()
}

Вы могли заметить, что здесь используется более старая версия EcmaScript. Это потому, что Node не поддерживает последнюю передовую версию ES2016. Вместо import мы используем require, а вместо export default мы используемmodule.exports = <the exported async function>.

Метод build в Gluegun создает для нас среду выполнения. В нем есть множество методов, которые вы используете для настройки своего интерфейса командной строки. Здесь я использовал loadDefault для загрузки моего плагина: leaf_plugin. Я мог бы назвать это leaf_cli, чтобы оно соответствовало названию проекта npm, но меня сбивает с толку, когда у всех есть общие имена, и plugin в любом случае более точен, поскольку сам по себе не является рабочим интерфейсом командной строки.

loadDefault делает leaf_plugin и все его команды доступными в CLI. Если вы включаете несколько плагинов, вы можете загрузить не более одного с помощью
loadDefault, а остальные должны быть загружены с помощью load. loadDefault сообщает вашему интерфейсу командной строки предположить, что вы хотите использовать этот плагин, если не указан другой. Также есть loadAll и некоторые параметры, которые вы можете использовать для управления загружаемыми плагинами, но я не буду здесь останавливаться на них.

Плагины

Настроив CLI и включив мой плагин, пришло время добавить несколько команд в мой плагин. Команды помещаются в ./commands каталог папки плагина. В данном случае это ./plugins/leaf_plugin/commands/. В структуре каталогов плагинов leaf_cli это даст мне команды invite, join, list, reserve и setName. nameConfig.js и roomIds.js - служебные файлы.

.
└── plugins
    └── leaf_plugin
        ├── checkRoom.js
        ├── commands
        │   ├── invite.js
        │   ├── join.js
        │   ├── list.js
        │   ├── reserve.js
        │   ├── rooms.js
        │   └── setName.js
        ├── nameConfig.js
        └── roomIds.js

Для каждого дополнительного плагина вы добавляете каталог (имя которого совпадает с именем плагина), содержащий другой каталог с именем commands, в который вы помещаете свои команды. Обратите внимание, что если вы создаете интерфейс командной строки с одним плагином, вам не нужно включать plugins каталоги с названиями (например ,leaf_plugin). Вместо этого вы можете поместить commands в корень вашего проекта. Я мог бы сделать это с помощью Leaf CLI, но я хотел проиллюстрировать более общий подход.

Первая команда, которую я написал, была join; давай взглянем на это. Вот команда
целиком, но я тоже разбиваю ее по частям:

module.exports = async function (context) {
  const { print, http, system, parameters } = context
  const { info, warning, success } = print
  const { getAndSetName, nameFromConfig } =
    require(‘./../nameConfig’)
  const { roomIds } = require(‘./../roomIds’)
 
  room = parameters.second
  user = parameters.third
 
  if (user==null && nameFromConfig(context)==null) {
    await getAndSetName(context)
  }
 
  name = nameFromConfig(context)
  const roomId = roomIds[room]
 
  if (roomId != null) {
    const api = http.create({
      baseURL: “http://leaf.infinite.red”,
      headers: {‘Accept’: ‘text/html’},
      maxRedirects: 0
    })
 
    const request = ‘/join/’ + room + ‘/’ + name
    const response = await api.get(request)
    system.run(“open zoommtg://zoom.us/join?confno=” + roomId)
  } else {
   warning(“That’s not a valid room”)
  }
}

А теперь давайте разберемся:

const { print, http, system, parameters } = context

Сначала я извлекаю некоторые инструменты из context, которые Gluegun предоставляет для подключаемых
команд. context набита вкусностями, как сказал бы его автор Стив Келлок. Эти полезности включают в себя набор сторонних библиотек, которые предлагают широко применимые общие функции, такие как работа с файлами, взаимодействие с API, аргументы и подсказки командной строки. Мы скоро увидим, как некоторые из них используются в команде join.

room = parameters.second
user = parameters.third

Затем я беру аргументы командной строки, используя parameters, одно из свойств, предоставляемых Gluegun. Синтаксис join: leaf join <room> <user>.

const { getAndSetName, nameFromConfig } = require(‘./../nameConfig’)
// …
 
if (user==null && nameFromConfig(context)==null) {
  await getAndSetName(context)
}
 
name = nameFromConfig(context)

Здесь я использую один из файлов служебных программ, о которых упоминал ранее, чтобы получить имя пользователя. Leaf CLI сохраняет имя пользователя как конфигурацию, поэтому он проверяет аргумент командной строки
и файл конфигурации для него. Если имя пользователя не еще сохранено, функция getAndSetName(context) запросит его у пользователя. Я передаю context, потому что он содержит стороннюю библиотеку, которую я буду использовать для подсказки. Заглянем внутрь nameConfig.js, чтобы увидеть, как это работает.

// nameConfig.js
const getAndSetName = async function(context) {
  const { prompt } = context
  prompt.question(‘name’, ‘What is your name?’)
  return prompt.ask(‘name’)
    .then(function(answer){
      setName(context, answer.name)
    })
}

Как видите, я извлекаю prompt из context, а затем использую его, чтобы запросить
имя пользователя. setName делает то, что написано на жестяной коробке. Мы не пойдем дальше именно в эту кроличью нору; назад к join.js!

// ...
const api = http.create({
  baseURL: “REDACTED”,
  headers: {‘Accept’: ‘text/html’},
  maxRedirects: 0
})
 
const request = ‘/join/’ + room + ‘/’ + name
const response = await api.get(request)
system.run(“open zoommtg://zoom.us/join?confno=” + roomId)
// ...

После проверки room аргумента командной строки мы используем еще одну полезность из context, http от Gluegun, чтобы сделать http-запрос. Этот конкретный запрос обновляет версию Leaf для браузера, чтобы показать присутствие пользователя. Наконец, мы воспользуемся последним плюсом Gluegun, system, чтобы открыть приложение Zoom и присоединиться к комнате.

Вот как это выглядит в действии:

Заключение

Это охватывает основы Gluegun! Это позволяет безболезненно создавать новые интерфейсы командной строки с использованием существующих плагинов. Сообщите нам, если вы сочтете это полезным в ваших собственных интерфейсах командной строки!

О Моргане

Морган - инженер-программист, специализирующийся на веб-проектах в Infinite Red. Ей нравится использовать Rails и Phoenix. Следующие посты Моргана вы можете увидеть в Публикации Red Shift. Следить за ней можно на Medium и GitHub.