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

    Так сложилось, что раньше я имел небольшой опыт программирования на языке 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]
    Версия с нормальной подстветкой в моем блоге

    Similar posts

    Ads
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More

    Comments 7

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

        Ну на самом деле хорошо, что пост хоть чем-то кому-то пригодился.)
          0
          Вот, можно тут посмотреть — github.com/mshakhan/rtm. Правда, там тоже еще всё сыро, но уже работает.
            0
            Точку в конце урла надо убрать.
        +3
        За код в картинках, надо расстреливать!
          0
          Что-то не работает, вот что выдает:
          ~/Temp/todo $ ruby ./rtodo.rb
          System files was created
          > Welcome to R-todo v0.1, type 'help' to get info
          help

          ^C./rtodo.rb:9:in `gets': Interrupt
          from ./rtodo.rb:9
            0
            Вместо load лучше использовать require.

            Вместо eval(«system.#{method}») — system.send(method)
            eval(«system.#{method}(string_user_input)») — system.send(method, string_user_input)

            system.keywords.each… — этому циклу стоит находиться в классе System (кстати, цикл лучше заменить на вызов find). А keywords лучше сделать константным хешем:

            class System
            KEYWORDS = { 'add' => 'task_add', 'remove' => 'task_remove', 'list' => 'task_list'}

            #…
            end

            Для чтения файла целиком существует метод File#readlines.

            … Дальше было лень всматриваться в код :)

            Only users with full accounts can post comments. Log in, please.