Статьи

Приложения командной строки для остальных

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

IDE и графические клиенты становятся не более чем неуклюжими и «О, как медленно?» Они могут быть! Командная строка быстро становится почетным знаком для нас, ботаников. Вы можете просто перебирать кучу файлов, и любые невежественные свидетели думают, что вы подключены к Матрице или что-то в этом роде.

Для разработчиков Ruby наше первое знакомство с языком было, скорее всего, терминальной средой. Я все еще заглядываю в irb, если мне нужно проверить синтаксис, идиомы или даже просто использовать всю мощь Ruby в качестве простого калькулятора.

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

Однако, если вы делаете что-то исключительно для гиков, командная строка — это то место, где я хочу жить. Мы принимаем как должное общие инструменты, такие как git , Rails Generators и Rake. Редко мы учитываем время и усилия, затраченные на создание таких сложных наборов командной строки.

Давая CLI немного любви

Получение правильного интерфейса жизненно важно. Ключевыми компонентами интерфейса командной строки являются команды (duh!), Флаги (или опции) и, конечно, аргументы. Мы немного пососем яйца и объясним это на примере.

команды

Наиболее очевидной является сама команда. Если мы говорим git, pull , push и commit — все это команды. Мы говорим git-приложению, что хотим выполнить какое-то конкретное действие.

Флаги или Варианты

На самом базовом уровне эти флаги сообщают команде, что она должна выполняться не по умолчанию . Например, команда * nix mkdir может принимать флаг -p или --parents . Указано здесь как в длинном, так и в коротком форматах. Они делают то же самое, только одно более четко для чтения.

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

Глядя на только что приведенные примеры, для родительского флага mkdir я всегда использую краткую форму -p , но всякий раз, когда я ищу много verbose вывода, я всегда выбираю длинный формат. Нет особой причины, поскольку я сказал, что они абсолютно одинаковы, только один формат имеет тенденцию чувствовать себя «более правильным» в зависимости от обстоятельств.

Стоит отметить, что приложение и команда могут принимать независимые параметры. Например, я буду использовать git, так как почти все, кто читает эту статью, будут использовать ее или, по крайней мере, использовали. Сам Git может принимать флаг -p или --paginate который будет --paginate любой результат выполнения в less . Также команда commit принимает -a и -m или даже комбинирует -am для размещения всех измененных файлов и фиксации с последующим сообщением. Мы можем объединить все это в git -p commit -am 'A well formed commit message' . Четко показывает флаг приложения и параметры команды для git suite, хотя это действительно глупый пример. Не используйте -p для коммитов.

аргументы

Третий и последний компонент нашего CLI — это, конечно, аргументы, которые мы хотим передать рассматриваемой команде. Хорошим примером этого будет ls /some/directory , путь к каталогу которого является аргументами, с которыми будет работать команда.

Рубиновая тонированная CLI

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

 touch test && chmod +x test && echo "#!/usr/bin/env ruby" >> test 

Когда мы выполняем ./test , ничего не происходит. Не стесняйтесь заполнить его предложениями о проверке, чтобы проверить, все ли в порядке.

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

Теперь для наивного прохода в приложении командной строки мы будем использовать ARGV Руби. test файл, который мы создали ранее, реализует следующее, чтобы начать работу.

 ARGV.each { |arg| puts arg } 

Теперь, выполнив ./test hello 'hiya you' goodbye :

 hello hiya you goodbye 

Я делаю это только для того, чтобы показать, что аргументы разбиты и назначены в массив ARGV , а также эффект использования кавычек.

Прежде чем мы пойдем дальше, вы начинаете бояться разбора ARGV для флагов, команд, опций команд и аргументов? Я конечно

К счастью, Ruby поставляется с довольно изящной библиотекой, которая устраняет любые головные боли при ходьбе / разборе. Это, конечно, стандартная библиотека OptionParser .

В качестве примера мы собираемся эмулировать фантастическое приложение todo, todo.txt . Как следует из названия, он просто сохраняет ваши задачи в текстовом файле и позволяет вам назначать приоритеты, контекст и проекты в одной команде. Наша версия будет намного проще (с точки зрения синтаксического анализа сообщения) и будет назначать приоритет только элементу и его сообщению. Наш интерфейс будет выглядеть так:

 ./todo -p high -m 'Take out the garbage'` 

В приведенном выше заявлении мы присваиваем «высокий» приоритет элементу todo. Take out the garbage . Сначала мы рассмотрим код и объясним позже.

 #!/usr/bin/env ruby >require 'optparse' require 'ostruct' options = OpenStruct.new OptionParser.new do |opts| opts.on('-h', 'Shows this help screen') do puts opts exit end opts.on('-p', '--priority PRIORITY', 'Set the items priority') do |priority| options.priority = priority end opts.on('-m', '--message MESSAGE', 'Set the todo item') do |message| options.message = message end end.parse! puts options.priority puts options.message 

ostruct этим, во-первых, нам нужны библиотеки optparse и ostruct . OpenStruct самом деле не требуется для работы этого приложения, достаточно простого хэша, но я хотел бы использовать OpenStruct для простоты доступа и назначения для моих параметров. Затем мы создаем экземпляр OptionParser который принимает блок. В этом блоке мы определяем, какие опции будут использовать приложение и его команды.

Первый — это экран справки, на котором ничего не сказано, кроме инструкции по использованию приложения. Следующие два более интересны, так как мы просим эти параметры проанализировать содержимое, следующее за ними, как PRIORITY и MESSAGE соответственно. Эти аргументы должным образом установлены как атрибуты в нашей структуре options .

Мы наконец-то назовем parse! на нашем объекте OptionParser и плевать опции на STDOUT . Для загрузки, изучив вывод флага -h мы получим краткое описание того, что требуется для ввода приложения.

Затягивающий ввод

Метод OptionParser#on чрезвычайно гибок. Скажем, мы хотели ограничить приоритеты заданным набором от «высокого» до «низкого», мы можем исправить это, используя:

 opts.on('-p', '--priority PRIORITY', [:high, :medium, :low], 'Set the items priority') do |priority| options.priority = priority end 

Другим распространенным вариантом является использование параметров по умолчанию для сохранения некоторых нажатий клавиш. С OptionParser снова это тривиально:

 opts.on('-p', '--priority [PRIORITY]', [:high, :medium, :low], 'Set the items priority') do |priority| options.priority = priority || :medium end 

Просто заключив аргумент в фигурные скобки, мы сделали его необязательным, все, что нужно сделать пользователю, это передать флаг -p . В большинстве случаев я не нашел этот тип дефолта очень полезным. Это действительно хорошо работает только с файлами. Например, пользователь решает (нормальная работа — это не ведение журнала), он хочет записать в файл, будет ли он в местоположении по умолчанию или иным образом. Для нашего приложения todo все элементы будут иметь средний приоритет, если не указано иное.

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

 defaults = { priority: :medium } options = OpenStruct.new(defaults) 

Мне особенно нравится, что мы определяем текст справки при создании интерфейса для нашего приложения командной строки. Конечно, это не так хорошо, как в Rake’s desc но то, что у вас есть с простым старым Ruby, довольно мощно.

Всплеск цвета

Хотите добавить вывод в терминал? Обратная связь очень важна для наших пользователей, чтобы знать, что они только что набрали, чтобы эта страшная командная строка действительно сделала что-то полезное. Конечно, мы всегда можем просто puts и мы будем. Но при нормальной работе, это просто откроет текст в цвете, заданном настройками профиля пользователя. Для нашего приложения todo мы хотим, по крайней мере, вернуть сообщение об успешном завершении и цветное представление элемента в зависимости от его приоритета. Красный для высокого и так далее.

Для первого прохода мы можем рассмотреть только использование старых кодов ANSI для форматирования текста: `помещает ‘e [31m # {options.priority} e [0m”. Но давайте будем честными: к тому времени, когда вы взломаете ужасные коды ANSI, вы перестанете работать с приложением командной строки и сделаете его облачным.

К счастью, общественные драгоценности приходят на помощь с целым рядом раскрашивающих библиотек, находящихся в нашем распоряжении, все они готовы к работе по желанию. Вероятно, наиболее известным является term-ansicolor . Однако в наши дни из-за простоты синтаксиса я склоняюсь больше к colored где мы можем добиться изящного синтаксиса: puts "HIGH PRIORITY".red .

Завершение командной строки

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

Опять же, богатство драгоценных камней в рубиновом сообществе — наш спаситель. Мы уже обсуждали Rake, но мы не смотрели на «другой» инструмент командной строки, включенный в стандартные установки Rails. Thor который по синтаксису похож на Rake, но больше внимания уделяет кодированию с помощью соглашений, создавая команды в классах Ruby, которые наследуются от Thor. Аналогичный подход DSL к созданию интерфейсов командной строки заключается в использовании гем GLI . Все эти инструменты просто означают, что у CLI нет оправдания тому, что они являются приложениями второго класса.

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