I've got this working to my satisfaction.
[/update]
This is a structure question, rather than about s specific piece of code. Just looking for some guidance from all you Lua gurus...
I'm working on a Lua script to interface with Todo.txt (yes, I'm aware that it's been done).
Right now, I have the skin read the todo.txt file, process the strings into a table of task objects, and set Text field of string meters in the skin using that information. It's been educational to implement, but the overall idea is pretty straightforward.
My question is regarding file IO in the other direction, so to speak. If I want to alter a task or mark it as complete from the skin, how should the script handle that?
My thoughts so far go roughly like this -
1) I can have script methods write straight to the todo.txt/done.txt files, then just re-read the file contents (perhaps even just refresh the skin) to update the tasks displayed in the skin. I have to do this already on initialization to build the task table, so I would just have to re-call those methods after writing changes...
2) I could have the changes occur in the script first - alter the tasks inside the table of tasks, so that I can update the tasks displayed in the skin without having to do any file IO. Then periodically "flush" changes to the actual todo.txt file - for instance, have a "destructor" function called by OnCloseAction to write the altered task list to todo.txt when the skin is closed, and/or on refresh.
#1 is quick and dirty, but it is so, so simple and it does have the advantage that your todo.txt file would always be up to date.
#2 seems neater and might enable better response times, since the file doesn't have to be re-read when you make a change. But there's a big lag effect and it's more complicated.
The middle ground might be to change table -> update skin from table -> write to file (but don't re-read the file). But at that point, since you already have the file open to write changes, how much more overhead is incurred by re-reading it and just refreshing the table from that? (#1)
Thanks in advance for any feedback.
I'm working on a Lua script to interface with Todo.txt (yes, I'm aware that it's been done).
Right now, I have the skin read the todo.txt file, process the strings into a table of task objects, and set Text field of string meters in the skin using that information. It's been educational to implement, but the overall idea is pretty straightforward.
My question is regarding file IO in the other direction, so to speak. If I want to alter a task or mark it as complete from the skin, how should the script handle that?
My thoughts so far go roughly like this -
1) I can have script methods write straight to the todo.txt/done.txt files, then just re-read the file contents (perhaps even just refresh the skin) to update the tasks displayed in the skin. I have to do this already on initialization to build the task table, so I would just have to re-call those methods after writing changes...
2) I could have the changes occur in the script first - alter the tasks inside the table of tasks, so that I can update the tasks displayed in the skin without having to do any file IO. Then periodically "flush" changes to the actual todo.txt file - for instance, have a "destructor" function called by OnCloseAction to write the altered task list to todo.txt when the skin is closed, and/or on refresh.
#1 is quick and dirty, but it is so, so simple and it does have the advantage that your todo.txt file would always be up to date.
#2 seems neater and might enable better response times, since the file doesn't have to be re-read when you make a change. But there's a big lag effect and it's more complicated.
The middle ground might be to change table -> update skin from table -> write to file (but don't re-read the file). But at that point, since you already have the file open to write changes, how much more overhead is incurred by re-reading it and just refreshing the table from that? (#1)
Thanks in advance for any feedback.
Oh, and if you want to have a look at the Spaghetti so far:
Code: Select all
dbg = true
function Initialize()
path = SELF:GetOption('TodoPath','.\todo.txt')
dateSwitch = tonumber(SELF:GetOption('UseDate', '0')) -- unimplemented
lineMeters = {}
priMeters = {}
taskMeters= {}
for i=1, 10, 1 do
table.insert(lineMeters, SKIN:GetMeter('line' .. i))
table.insert(priMeters, SKIN:GetMeter('pri' .. i))
table.insert(taskMeters, SKIN:GetMeter('task' .. i))
end
rawLines = readLinesFromFile(path)
rawLines = removeBlankLines(rawLines)
TodoList = TaskList:createListFromLines(rawLines)
-- testing filtering
-- temp = TodoList:searchFilter("")
TodoList:sortByPri()
populateSkin(TodoList)
end
function Update()
--...
end
-- read lines from todo.txt into a simple table
function readLinesFromFile(fp)
local temp = {}
for line in io.lines(fp) do
table.insert(temp, line)
end
return temp
end
-- removes blank lines from table of lines
function removeBlankLines(lineTable)
for i,v in ipairs(lineTable) do
if v == "" then
table.remove(lineTable, i)
end
end
return lineTable
end
-- sets text and shows meters
function populateSkin(atable)
for i,v in ipairs(atable) do
if i > #taskMeters then
break
end
-- TODO make line numbers optional
SKIN:Bang('!SetOption', lineMeters[i]:GetName(), 'Text', v.lineNo)
lineMeters[i]:Show()
if v.priority ~= nil then
SKIN:Bang('!SetOption', priMeters[i]:GetName(), 'Text', v.priority)
priMeters[i]:Show()
end
SKIN:Bang('!SetOption', taskMeters[i]:GetName(), 'Text', v.taskString)
taskMeters[i]:Show()
end
end
-- does not support timestamping. We're working on it.
function addTask(taskStr)
io.output(io.open(path, "a"))
io.write('\n' .. taskStr)
io.flush()
io.close()
SKIN:Bang('!Refresh')
end
-- not implemented at all. Will probably be painful.
function doTask(taskNum)
end
--[=[-------------------------------------------------------------------------------
Attempt at a "TaskList" class.
TaskList objects hold Task objects and have methods to build/process lists.
Then the skin/script interface bits can be used to display a given TaskList.
! Currently list is sorted in place; should a copy be returned instead?
! TODO filter by project and/or context
---------------------------------------------------------------------------------]=]
-- TaskList namespace/prototype (empty)
TaskList = {}
-- TaskList constructor
function TaskList:new (lst)
lst = lst or {}
setmetatable(lst, self)
self.__index = self
return lst
end
-- should create a new TaskList of Tasks from a Table of single-line strings
function TaskList:createListFromLines(strings)
newList = TaskList:new ()
for i,v in ipairs(strings) do
table.insert(newList, Task:newTask(v, i)) -- uses posiiton in table as line number
end
return newList
end
-- sorts self by Priority then alpahbetically
function TaskList:sortByPri()
table.sort(self, function (te1, te2)
if (te1.priority ~= nil and te2.priority ~= nil) then
if (te1.priority < te2.priority) then
return true
else
return false
end
elseif (te1.priority ~= nil and te2.priority == nil) then
return true
elseif (te1.priority == nil and te2.priority ~= nil) then
return false
else
if (te1.taskString < te2.taskString) then
return true
else
return false
end
end
end)
end
-- sorts self by Age
function TaskList:sortByAge()
table.sort(self, function (te1, te2)
if te1.startDate == nil and te2.startDate == nil then
if te1.taskString < te2.taskString then
return true
else
return false
end
elseif te1.startDate < te2.startDate then
return true
else
return false
end
end)
end
-- sorts self by line number
function TaskList:sortByLine()
table.sort(self, function (te1, te2)
if te1.lineNo < te2.lineNo then
return true
else
return false
end
end)
end
-- returns a table containing tasks filtered by a search term
function TaskList:searchFilter(term)
local temp = {}
for i,v in ipairs(self) do
if string.find(v.taskString, term) then
table.insert(temp, v)
end
end
return temp
end
-- add a new task to the TaskList
-- does NOT add the task to the file
-- not sure how to handle line numbers.
function TaskList:addTask(taskStr)
table.insert(self, Task:newTask(taskStr, #self+1))
end
-- removes a task from the tasklist by line number
-- does NOT mark the task as complete or remove it from the todo.txt file
function TaskList:removeTask(lineNum)
for index, task in ipairs(self) do
if task.lineNo == lineNum then
table.remove(self, index)
break
end
end
end
-- end TaskList class
--[=[ ------------------------------------------------------------------------------
Attempt at a "Task" class to better organize this mess.
Passed compliler, but so far no idea if it works.
Has an object constructor to make new Task objects use the defined Task as a prototype and metatable
Has a method to take a string and line number and transform it into a new task object
Would like to add more methods as necessary,
one idea would return the Lifetime of a completed task (completeDate - startDate) ?
---------------------------------------------------------------------------------]=]
-- define namespace for "Task" class (with defaults)
Task = {taskString="", lineNo=nil, priority=nil, completed=false, completeDate=nil, startDate=nil, context={}, project={}}
-- constructor function
function Task:new (tsk)
tsk = tsk or {} -- creates a new table/object if not given one
setmetatable(tsk, self) -- uses itself as metatable?
self.__index = self
return tsk
end
-- creates a new task from a single line string
function Task:newTask (str, lineNum)
local t = Task:new() -- create a new task object
t.taskString = str
t.lineNo = lineNum
-- eat first four characters
tempStr = string.sub(t.taskString, 1, 4)
-- search first four chars for complete or priority
if string.sub(tempStr, 1, 2) == 'x ' then
t.completed = true
t.taskString = string.sub(t.taskString, 3)
else
t.priority = string.match(tempStr, "%(%u%) ")
if t.priority then
t.taskString = string.sub(t.taskString, 5)
end
end
-- search task string for dates
datePat = "%d%d%d%d%-%d%d%-%d%d"
if t.completed then
tempStr = string.sub(t.taskString, 1, 22) -- completed task may have two dates
t.completeDate, t.startDate = string.match(tempStr, "(" .. datePat .. ") (" .. datePat .. ") ")
else
tempStr = string.sub(t.taskString, 1, 11) -- incomplete may only have one (start date)
t.startDate = string.match(tempStr, datePat .. " ")
end
-- strip dates from task text
if startDate then
t.taskString = string.sub(t.taskString, 12)
end
if completeDate then
t.taskString = string.sub(t.taskString, 12)
end
-- search for context(s) !bugged! word boundaries?
for match in string.gmatch(t.taskString, "@(%w+)") do
table.insert(t.context, match)
end
-- search for project(s) !bugged! word boundaries?
for match in string.gmatch(t.taskString, "%+(%w+)") do
table.insert(t.project, match)
end
return t -- return the newly created and filled Task object
end
-- marks this task as complete
-- does NOT remove the task from the file or TaskList
function Task:markComplete()
self.completed = true
self.completeDate = os.date("%Y-%m-%d")
end
-- changes priority of this task to the letter passed as argument,
-- or deprioritizes the task if passed nil/false
-- does NOT update todo.txt file
function Task:changePriority(newPri)
if not newPri then
self.priority = nil
elseif not string.find(newPri, "%a") then
error("Bad argument for new task priority - must be a letter.", 2)
else
self.priority = "(" .. string.upper(newPri) .. ")"
end
end
-- end Task class