Существует 2 ключевых подхода по организации взаимодействия с пользователями при разработке приложения с командным интерфейсом:

  • Интерактивное взаимодействие.
  • Выполнение команды по аргументам приложения.

Передача данных в приложение через аргументы имеет следующие преимущества:

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

Интерактивное взаимодействие тоже имеет свои сферы применения:

  • Интерактивное взаимодействие зачастую проще для неподготовленного пользователя, так как не требует от пользователя сначала найти все опции, а затем ввести необходимые данные.
  • В интерактивном режиме приложение может предоставлять контекстные подсказки, т.е. подсказки на основании действий пользователя, делая их более информативными.
  • В интерактивном режиме пользователь может ввести большое количество информации, причём её проверка будет осуществляться на этапе ввода, а не на этапе использования, уберегая пользователя от повторного ввода большого объёма данных. При ошибке будет достаточно только повторить последний ввод.
  • Трудно играть в режиме обработки переданных аргументов.

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

Работа в интерактивном режиме

При работе в интерактивном режиме у вас всегда есть 2 потока информации, с которыми необходимо работать: поток стандартного ввода, поток стандартного вывода. Помимо них есть ещё стандартный поток вывода сообщений об ошибке. На последний поток зачастую выводят расширенные сообщения об источнике проблемы и другую отладочную информацию.

Вывод на стандартный поток

Для вывода информации на стандартный поток вывода Ruby предоставляет следующие методы:

  • Kernel#puts - позволяет вывести строку текста на стандартный поток вывода. Строка завершается символом перевода на новую строку.
  • Kernel#print - позволяет вывести строку текста на стандартный поток вывода, после строки не печатается символ перехода на новую строку. Подходит для формирования приветствий.
  • Kernel#printf - позволяет вывести информацию согласно согласно форматированной строке. Может выводить информацию как на стандартный поток вывода, так и на произвольный поток вывода. Описание формата приведено в документации на метод Kernel#format
  • Kernel#p - позволяет вывести информацию о переданных объектах в формате, показывающем структуру объекта с точки зрения зыка программирования Ruby. Очень удобно использовать метод для отладки приложения.
  • Kernel#pp - позволяет вывести информацию о внутреннем состоянии переданных объектов, как и метод Kernel#p, однако значительно лучше справляется с выводом информации о сложных структурах данных.
  • Kernel#putc - выводит на стандартный поток вывода один символ. В качестве параметра передаётся код данного символа.

Все указанные методы, за исключением pp, также доступны и для потоков вывода, которые унаследованы от класса IO. Например, IO#puts - позволяет вывести строку на любом потоке вывода информации. В частности, вам доступна глобальная переменная $stdout, которая связана со стандартным потоком вывода информации.

Ввод со стандартного потока

Для получения информации со стандартного потока ввода Ruby предоставляет следующие методы:

  • Kernel#gets - считывает одну строку со стандартного ввода. Считываение строки начинается, когда пользователь нажимает клавишу на клавиатуре. Когда поток заканчивается возвращает `nil`. Вы можеет послать сигнал завершения потока, нажав сочетание клавиш `Ctrl+D`. *Внимание*. Метод начинает работать по-другому, если приложению в качестве аргументов были переданы пути к файлам.
  • Kernel#readline - работает также как и gets за исключением того, что при окончании потока ввода выбрасывает исключение EOFError
  • Kernel#readlines - считывает все линии, которые были переданы на стандартный поток ввода, в массив. Считывается продолжается до окончания потока ввода.

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

Объект $stdin

В переменную $stdin записана ссылка на объект, взаимодействующий со стандартным потоком ввода. Эта ссылка в отличие от предыдущих методов не изменяет своего поведения. В неё записан объект класса IO. Рассмотрим его методы по получению информации.

  • IO#gets - получить строку целиком с потока ввода.
  • IO#getc - получить 1 символ с потока ввода в виде строки. Когда поток заканчивается, метод возвращает nil.
  • IO#getbyte - получить 1 символ с потока ввода в виде кода числа.
  • IO#read - считать определённое количество символов, по умолчанию все символы с потока ввода.
  • IO#readline - считать 1 строку, при окончании потока выбрасывает исключение EOFError.
  • IO#readlines - считать строки с потока ввода и поместить их в массив.

Обработка строк

Преобразование к числам

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

  • Kernel#Integer - преобразует переданный ему аргумент в целое число. В качестве аргумента может выступать не только строка, но также и вещественное число.
  • Kernel#Float - преобразует переданный аргумент в вещественное число.

Данные методы ожидают, что им будет передан корректный аргумент. Если в качестве аргумента передаются некорректные данные, тогда данные методы будут выбрасывать исключение о неправильном формате данных ArgumentError. Таким образом можно организовать надёжную проверку данных, однако мы с вами пока что не умеем обрабатывать исключительные ситуации, поэтому посмотрим на другие методы, предоставляемые классом String.

  • String#to_i - преобразовать текущую строку к целому числу и вернуть данное число. Данный метод постарается найти целое число в начале строки и преобразовать его к числу. Если число не было найдено, то метод вернёт число 0 в качестве результата.
  • String#to_f - преобразовать текущую строку к вещественному числу и вернуть получившееся число. Метод страдает от тех же недостатков, что и предыдущий.

Таким образом последние методы нельзя использовать для надёжной проверки ввода, однако они достаточно просты и их можно использовать в тех ситуациях, когда вы уверены в том, что строки действительно содержат необходимые данные и их обработка не приведёт к неожиданным последствиям.

Отбрасывание лишнего у строк

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

  • String#strip - метод удаляет все пустые символы в начале и в конце строки, включая переносы строк. В результате текущая строка не изменяется, но а создаётся новая строка с нужным содержимым. Не забудьте сохранить ссылку на новую строку. У этого метода также есть 2 специализированные версии:
    • String#lstrip - удалить все пробелы с начала строки.
    • String#rstrip - удалить все пробелы с конца строки.
  • String#chomp - метод удаляет перенос каретки в конце строки. Этот метод следует использовать, когда вас моут интересовать пробелы в начале и в конце строки, но вы хотите избавиться от «паразитного» переноса, который возникает при использовании метода Kernel#gets.

Практикум

Приложение-повторятель

Реализуйте приложение, которое будет повторять предложения, введённые пользователем. Приложение должно выполнять следующие действия:

  1. Сразу после запуска приложение выводит информацию о тех действиях, которые приложение позволяет выполнять, о тех данных, которые ожидаются от пользователя.
  2. Перед вводом пользователя обязательно показывать приглашение для ввода данных. Приглашение может быть простым, например > , или более информативным, например введите строку> . Приглашение должно показываться на строке, в которой пользователь должен вводить данные.
  3. Считайте строку, которую ввёл пользователь и выведете её на экран с помощью методов puts, print, printf, p
  4. Предложите пользователю ещё раз ввести данные.
  5. Завершите считывание данных, когда пользователь введёт строку “stop, please”.

Простейший калькулятор, умеющий складывать

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

  1. Приложение должно показывать справку об использовании, а также приглашение для ввода данных.
  2. Когда пользователь ввёл строку, приложение должно считать её, преобразовать к вещественному числу, прибавить к текущей сумме и вывести:
    • Значение считанного числа.
    • Значение суммы на настоящий момент.
  3. Данные действия должны происходить в бесконечном цикле.
  4. Бесконечный цикл считывания данных должен закончиться, если
    • Пользователь ввёл строку “over”.
    • Пользователь завершил поток ввода данных, введя сочетание клавиш Ctrl+D. В этом случае приложение должно завершаться корректно, не должно быть сообщений от интерпретатора Ruby.
  5. По окончании работы приложение должно вывести результат сложения чисел.

Ответьте на следующие вопросы:

  • Что происходит с потоком ввода, когда пользователь вводит сочетание клавиш Ctrl+D?
  • Что происходит, если попытаться сложить строку и число?

Реализация корректного ввода положительных вещественных чисел

Реализуйте приложение, которое будет контролировать, что пользователь ввёл целое положительное число.

  1. Приложение должно показывать справку об использовании, а также приглашение для ввода данных.
  2. Приложение должно считывать строку, введённую пользователем, преобразовывать её к целому числу.
    • Если пользователь ввёл отрицательное число или 0, то сообщать пользователю об ошибочном вводе.
    • Если пользователь ввёл положительное число, то сообщать ему о правильном вводе и выводить введённое число.
    • Дополнительно в последнем случае проверять, что введённая строка не содержит других символов кроме введённого числа.
  3. Бесконечный цикл заканчивается, если
    • Пользователь ввёл число “99.999”.
    • Пользователь завершил поток ввода данных, введя сочетание клавиш Ctrl+D.
  4. По окончании работы приложение должно сообщать о количестве успешных и ошибочных вводов данных со стороны пользователя.

Решение задачи №3 из пункта 1.2 задачника

Реализуйте задачу №3 из пункта 1.2 задачника. При этом выполните следующие условия:

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