Pull to refresh

Пишем простой консольный To-do менеджер на Ruby

Reading time 5 min
Views 1.1K
Так сложилось, что раньше я имел небольшой опыт программирования на языке Java и PHP. Поэтому я с легкостью перешел на язык Ruby. Конечно мне пришлось часто обращаться к литературе, а так же замечательной утилите ri (или более удобному аналогу fxri).
Особенности языка ruby пришлись мне по душе. (такое я по-правде говоря не мог сказать после перехода с java на php)

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

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

Чтобы запустить данную утилиту понадобится всего лишь написать в консоли:
$ ruby ./rtodo.rb

Программа состоит из трёх файлов:
system.rb — файл содержащий реализацию работы класса System
rtodo.rb — главный исполняемый файл
list.dat — хранилище наших задач

Инструкция по работе (в примерах):
add Complete script for my web-page (добавляем задачу)
list (выводим список всех задач — у каждой задачи свой номер)
remove 1 (удаляем задачу под номером 1)

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


rtodo.rb

image

System.rb

image

Начнем изучение с главного файла — rtodo.rb



Загружаем файл, содержащий реализацию класса System
load 'system.rb'
класс System отвечает за работу с задачами (добавить/удалить/показать)

создаем объект класса System
system = System.new

приветствуем нашего юзера
puts "> Welcome to R-todo v0.1, type 'help' to get info"

инициализируем строковую переменную, именно здесь будет хранится то, что будет вводить юзер
user_input = ""

используя конструкцию while мы обрабатываем всё, что вводит юзер, до тех пор пока юзер не введет 'quit', это будет означать, что программа должна немедленно завершиться

while user_input != "quit"
заносим в переменную то, что ввел наш юзер в консоле
user_input = gets.chomp
конвертируем строку, которую ввёл юзер в массив. Каждое слово — отдельный элемент массива
formatted_user_input = user_input.split
далее мы будем проходиться по каждому элементу хеш-массива keywords. Вы наверное удивлены. Описание этой переменной вы найдете в файле system.rb — пока простой знайте, что этот хеш-массив содержит в себе конструкции вида 'ключевое слово' => 'вызываемая функия'
system.keywords.each do |keyword,method|
если первое слово в строке, которую ввёл юзер соответствует какому-нибудь ключевому слово, тогда…
if formatted_user_input[0] == keyword
инициализируем массив, в котором будет хранится строка, которую ввел юзер. Только без первого слова, т.к. оно является ключом, и служит исключительно для работы программы
buffer = []
это строка заполняет массив словами
1.upto(formatted_user_input.index(formatted_user_input.last)) { |index| buffer.push(formatted_user_input.at(index))}
а затем объединяет слова массива в единую строку, используя пробел в качестве разделителя
string_user_input = buffer.join(" ")
на эту нижеприведённую строку стоит обратить особое внимание.
Помимо синтаксического сахара здесь встречается опасный метод eval, более подробно о нём вы можете прочесть в любой книге по php/ruby. Дело в том, что метод task_list не предусматривает аргументов, поэтому если юзер ввел команду list то аргумент функции eval будет одного вида, а если юзер ввел какую-то другую команду — то аргумент будет вполне себе стандартным для нашего класса System.
keyword == "list" ? eval("system.#{method}") : eval("system.#{method}(string_user_input)")
end
end
end
puts "> R-todo was aborted by user"


Переходим к файлу System.rb


Хочу напомнить вам, этот файл непосредственно отвечает за работу с списком задач.

class System
переменные объекта
attr_reader :keywords, :buffer
метод-конструктор, этот метод всегда будет вызываться при создании объекта класса System
def initialize
И так, для начала инициализируем хэш-массив переменной объекта @keywords
в этом массиве будут содержаться ключевые слова и названия методов
проще говоря, когда юзер введет 'add' будет вызван метод 'task_add'
@keywords = { 'add' => 'task_add', 'remove' => 'task_remove', 'list' => 'task_list'}
создаем переменную-массив. Сюда мы будем помещать все текущие (существующую задачи)
@buffer = []
Объясню более подробно.
В буффере хранятся все задачи юзера. Как только юзер удаляют какую либо задачу — она сначала удаляется из буфера, затем весь этот буфер записывается в файл. Иными словами — мы каждый раз заново пишем содержимое нашего файла. Для данной программы это допустимо, но в приложениях, работающих с огромными файлама — такой метод может сказаться на производительности.

файл list.dat содержит список задач

если файл, содержащий список задач существует
if File.exist?('list.dat')
инициализируем переменную list_file, содержащую наш файл list.dat
и переписываем данные из файла в наш буфер
list_file = File.new('list.dat','r')
list_file.each_line do |line|
@buffer << line
end

если этого файла не существует, то…
else
создаем новый файл
list_file = File.new("list.dat","w+")
puts "System files was created"
end

закрываем файл (сохраняем)
list_file.close
end


метод добавляет новое задание, в качестве аргумента — текст задачи
def task_add(task)
помещаем текст задачи в буфер
@buffer << task
puts "> task added to list (#{task})"
task_write_to_file вызываем метод записывающий весь буфер в файл
task_list вызываем метод показывающий текущий список задач (обновленный)
end

метод удаляет задачу, в качестве аргумента выступает номер задачи
def task_remove(number)
если неверно указан аргумент
if number.to_i > (@buffer.index(@buffer.last))
puts "> Please write a correct task index"

если верно
else
puts "> Task #{number} removed"

удаляем задачу из буфера
@buffer.delete_at(number.to_i)
вызываем метод записывающий весь буфер в файл
task_write_to_file
end

вызываем метод показывающий текущий список задач (обновленный)
task_list
end


метод показывающий текущий список задач
def task_list
puts "********* #{@buffer.index(@buffer.last) + 1} item's *********\n"

проходимся по каждому элементу буфера
@buffer.each do |task|
puts "\t #{@buffer.index(task)} - #{task}"
end
puts "\n********* #{@buffer.index(@buffer.last) + 1} item's *********"
end

метод записывающий весь буфер в файл
def task_write_to_file
открываем файл, если в качестве второго аргумента указать 'w' то каждый раз файл будет писаться заново, в отличии от альтернативного аргумента 'a'
file = File.new('list.dat', 'w')
проходимся по каждому элементу буфера
@buffer.each do |task|
записываем текущий элемент буфера в файл
file.puts task
end

закрываем файл (сохраняем)
file.close
end
end


Хочу обратить ваше внимание на то, что это очень сырая программа. В том числе и в плане безопасности. Я не стал уделять много внимания проверке того, что вводит юзер. (а между тем, эта самая важная часть) Если данный материал придется вам по душе — то я неприменно продолжу наращивать функционал.

Скачать архив с программой [Zip, 1.2 kb]
Версия с нормальной подстветкой в моем блоге
Tags:
Hubs:
+3
Comments 7
Comments Comments 7

Articles