Простой терминал на основе задач-отслеживание Луа/календарь


Я изучаю Lua и написал эту маленькую календарь/время-отслеживание сценария. Это позволяет создать задачи с крайним сроком, распечатать их, и пометить их как делается. Задание представляет собой таблицу со следующими полями:

  • name: строку
  • due: срок, сама таблица с количество полей day, month и year
  • comment: строку
  • done: логический о том, была ли она завершена или нет

Скрипт работает путем хранить все задачи в таблице по имени tasks. Он автоматически сохраняет его в файл, расположенный в $HOME/.luacal или luacal если $HOME не определено.

Файл данных является основным настраиваемый текстовый формат. Она начинается с [ в одиночестве на линии, которая обозначает начало выполнения задачи, то в каждой строке является собственность. Их порядок не имеет значения, и заканчивается ]сама по себе линия тоже. Вот простой пример такого файла, с двумя задачами:

[
name=Hello
due=22/3/2018
comment=Task done
done=true
]
[
name=Testing saving tasks
due=23/3/2018
comment=Just want to see if auto-saving works fine
done=false
]

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

Вот код

#!/usr/bin/env lua

-- scope : simple calendar with lua to help me manage my time

-- prints `s` and returns user input
local function ask(s)
    io.write(s)
    return io.read()
end

-- s: string to parse as a date
--    if one element is not provided,
--    it is filled with today's
local function parse_date(s)
    local date = {}

    for v in s:gmatch('%d+') do
        table.insert(date, v)
    end

    local day = tonumber(date[1] or os.date('%d'))
    local month = tonumber(date[2] or os.date('%m'))
    local year = tonumber(date[3] or os.date('%Y'))

    return { day = day, month = month, year = year }
end

-- d: table to be converted as a string
--    with format dd/mm/yyyy
local function dump_date(d)
    return d.day .. '/' .. d.month .. '/' .. d.year
end
-- t: tasks table
-- printdone: whether to display completed tasks,
--            which are hidden by default
local function print_tasks(t,printdone)
    for _, v in pairs(t) do
        if not v.done  or printdone then
            print('`' .. v.name .. '`:')
            print('\tdue:     ' .. dump_date(v.due))
            print('\tcomment: ' .. v.comment)
            if v.done then
                print('\ttask is done')
            end
        end
    end
end

-- f: file to save the tasks to
-- t: tasks table
local function save_tasks(f,t)
    -- save the current output file and
    -- set the default to the given one
    local saved_output = io.output()
    io.output(f)

    for _,v in pairs(t) do
        io.write('[\n')
        io.write('name=' .. v.name .. '\n')
        io.write('due=' .. dump_date(v.due) .. '\n')
        io.write('comment=' .. v.comment .. '\n')
        io.write('done=' .. tostring(v.done) .. '\n')
        io.write(']\n')
    end

    -- restore previous output file
    io.output(saved_output)
end

-- f: file to read from
-- t: tasks table to be filled
local function load_tasks(f,t)
    local ctask = {}
    for line in f:lines() do
        if line:match('%[') then
            ctask = {}
        elseif line:match('%]') then
            table.insert(t, ctask)
        else
            if line:match('name*') then
                ctask.name = line:match('name=([%a%s]+)')
            elseif line:match('due*') then
                ctask.due = parse_date(line:match('due=([%d%/]+)'))
            elseif line:match('comment*') then
                ctask.comment = line:match('comment=(.+)')
            elseif line:match('done*') then
                ctask.done = line:match('done=(.+)') == 'true'
            end
        end
    end
end


-- actual code

local running = true
local tasks = {}

print('LuaCal - Lua Calendar')

-- load tasks if file exists
local loadpath = (os.getenv('HOME') .. '/.luacal') or 'luacal'
local loadfile = io.open(loadpath, 'r')
if loadfile then
    load_tasks(loadfile, tasks)
    loadfile:close()
end

-- main loop
while running do
    io.write('luacal> ')
    local command = io.read()

    -- add a new task
    if command == 'add' then
        -- query user for the new task
        table.insert(tasks, {
            name = ask('name: '),
            due = parse_date(ask('due: ')),
            comment = ask('comment: '),
            done = false
        })

        -- save everything to disk
        local savepath = (os.getenv('HOME') .. '/.luacal') or 'luacal'
        local savefile = io.open(savepath, 'w')
        save_tasks(savefile, tasks)
        savefile:close()

    -- mark one task as done
    elseif command =='done' then
        local name = ask('name: ')
        local found = false

        for _,v in pairs(tasks) do
            if v.name == name then
                v.done = true
                found = true
            end
        end

        if not found then
            print('cannot find task `' .. name .. '`')
        end

        -- save everything to disk
        local savepath = (os.getenv('HOME') .. '/.luacal') or 'luacal'
        local savefile = io.open(savepath, 'w')
        save_tasks(savefile, tasks)
        savefile:close()

    -- print all active tasks
    elseif command == 'print' then
        print_tasks(tasks, false)

    -- also print completed tasks
    elseif command == 'printall' then
        print_tasks(tasks, true)

    -- quite on demand or <eof>
    elseif command == 'exit' or not command then
        print('bye!')
        running = false

    else
        print(string.format('unknown command `%s`, command', command))

    end
end

Код функциональной и у меня не удалось заставить его рухнуть с странно входного до сих пор, я очень доволен. Теперь очевидно, так как я не очень опытный с Lua там могут быть вопиюще неправильные вещи в Там, который является, почему я решил разместить его здесь.

Мои две основные проблемы :

  • Бедный таблицы использование: Я родом из C фона и я всегда оказываюсь в замешательстве, когда мне нужно создать таблицы в языках сценариев, я не знаю, если есть способ, чтобы предопределить субструктур (по аналогии, структуры в структуру в C), так что все функции работают на них общей базы, или если я просто идти вперед и объявить их на лету, как я сделал здесь. Скажем, например, Я добавить поле в due таблицы в задаче, скажем timeсама таблица с полями hour и minute. Теперь, если time не определен, он будет nil и это нормально, но потом некоторые другие функции могут попытаться получить доступ к time.hour и будут жаловаться attempt to index field 'hour' (a nil value). Я могла бы много не пропустит-проверка условия, но это будет просто сделать код уродливым. Есть ли способ, чтобы создать таблицу со всеми подтаблиц уже создан, так что я не получаю ошибки индексирования? Поле не присутствует простоnil и это не проблема, поскольку я могу справиться с ней foo = some_table.subtable.nil_field or 'some_default_string'. В этом случае some_table.subtable уже в таблице, даже если он пустой, чего я хочу.
  • Ввод/файл парсинга: я следовал официальной документации match способ и получил его на работу относительно легко, я просто не могу понять, так ли я его правильно или нет, или даже whethere есть лучший способ сделать это. Есть куча формате JSON пакет доступен через luarocks, но я уже очень хорошо знакомы с питона json пакет так что бы не было много упражнений, чтобы использовать его здесь.


234
4
задан 22 марта 2018 в 08:03 Источник Поделиться
Комментарии
1 ответ

В главном цикле while, у вас есть

io.write('luacal> ')
local command = io.read()

для которого уже есть отдельная функция:

local command = ask('luacal> ')


Каждый раз, когда вы называете save_tasksвы сначала создаете файл тип объекта, передавая его вокруг и затем обработка закрытия буфера. Почему просто не указать путь вместо самого файла? Пусть функция беспокоиться об открытии/закрытии буфера.


Определить путь к файлу, как глобальное. Так что один файл будет манипулировать во время жизни программы (даже если состояние процесса ОКР волшебным образом меняется).


Почти все функции, определенные внутри luacal> приглашение принять tasks. Положить, что в качестве первого аргумента (почему будет позже)


Вы грубо хранить всю таблицу в файл. Взгляните на некоторые из пользователей внесли свой код для таблицы-мариновать в Lua. Вы можете использовать его так, чтобы ваш load_tasks будут заменены простым loadfile() метод.

Вы также можете попробовать создать базу данных SQLite на основе БД, но это до вас!


Следующие if-else гнездо может быть заменен на один матч:

if line:match('name*') then
ctask.name = line:match('name=([%a%s]+)')
elseif line:match('due*') then
ctask.due = parse_date(line:match('due=([%d%/]+)'))
elseif line:match('comment*') then
ctask.comment = line:match('comment=(.+)')
elseif line:match('done*') then
ctask.done = line:match('done=(.+)') == 'true'
end

становится

field, value = line:match '^([^=]+)=(.+)$'
ctask[field] = value

хотя предполагается, что


  1. ты не мариновать столы, и придерживаться собственных структурах

  2. не было бы никакой вредоносной активности в файле.


За время полевого хранения, вы можете взглянуть на os.time() функция, которая принимает табличный ввод похожа на структуру собственного time поле. Хранение эпохи, ИМО, лучше как можно позже формат данных, используя os.date() в пользовательский предпочтительный формат строки (например, для. Я предпочитаю даты стандарта ISO 8601 формат).


Почему я сказал за то, что tasks в качестве первого параметра все функции команды:

local COMMANDS = {
add = function(tasks)
table.insert(tasks, {...}) -- same code from above
save_tasks(tasks) -- will pick filepath from global
end,
print = function(tasks)
print_tasks(tasks, false)
end,
printall = function(tasks)
print_tasks(tasks, true)
end,
.
.
.
...
}

и в то время как петли должны нести:

command = ask "luacal> "
if command == "exit" or not command then
running = false
break
end
if COMMANDS[command] == nil then
string.format('unknown command `%s`, command', command)
else
COMMANDS[command](tasks)
end

Таким образом, добавление других функций/команд легко.

2
ответ дан 28 марта 2018 в 11:03 Источник Поделиться