dealingwith

Lua Tasks for ComputerCraft

Nov 29th, 2020 (edited)
1,172
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. --ldoc
  2. --[[
  3. % todo.txt in Lua
  4.  
  5. # The `todo.txt` text format
  6.  
  7. An active task line in a `todo.txt` file is
  8.  
  9.     (A) 2012-08-13 Some task here @context +project
  10.  
  11. The components are an optional priority (A-Z), an optional date
  12. (YYYY-MM-DD), a task name, and a list of context and project
  13. identifiers at the end of the file.
  14.  
  15. A finished task line has the form
  16.  
  17.     x 2012-08-14 2012-08-13 Some task here @context +project
  18.  
  19. That is, the line starts with `x` and the completion date, and
  20. otherwise looks pretty much the same as an active task line.
  21.  
  22. In addition to the priority, added date, done date, contexts, and projects,
  23. there can be additional data provided in key:value pairs.  The particular
  24. key-value pair meanings at present are:
  25.  
  26.  - `tic` specifies when a task stopwatch was started
  27.  - `time` specifies the total time spent on a task so far
  28.  - `repeat` specifies when a repeating task should be added to `todo`
  29.  - `queue` specifies when a task should be moved from `proj` to `todo`
  30.  - `color` specifies a color that should be used
  31.  
  32. Code for storing and processing tasks belongs to the `Task` namespace.
  33.  
  34. ---
  35.  
  36. Adopted by https://github.com/dealingwith for CC:Tweaked
  37. from https://github.com/dbindel/tlua
  38. --]]
  39.  
  40. Task = {}
  41.  
  42. --[[
  43. ## Parsing task lines
  44.  
  45. The `parse_task` function converts a task line, either finished or
  46. unfinished, into a Lua table.  The fields are `done`, `priority`,
  47. `description`, `added`, `projects`, and `contexts`.  There can be
  48. auxiliary information in `data`.
  49. --]]
  50.  
  51. function Task.parse(line)
  52.    local result = { data = {}, projects = {}, contexts = {} }
  53.    
  54.    local function lget(pattern, handler)
  55.       line = string.gsub(line, pattern,
  56.                          function(...) handler(...); return "" end)
  57.    end
  58.  
  59.    local function get_done(s)     result.done = s       end
  60.    local function get_priority(s) result.priority = s   end
  61.    local function get_date(s)     result.added = s    end
  62.    local function get_project(s) table.insert(result.projects or {}, s) end
  63.    local function get_context(s) table.insert(result.contexts or {}, s) end
  64.    local function get_value(k,v) result.data[k] = v end
  65.  
  66.    line = line .. " " -- Needed if description is empty
  67.    lget("^x%s+(%d%d%d%d%-%d%d%-%d%d)%s+", get_done)
  68.    lget("^%(([A-Z])%)%s+",                get_priority)
  69.    lget("^(%d%d%d%d%-%d%d%-%d%d)%s+",     get_date)
  70.    line = " " .. line -- Needed if description is empty
  71.    lget("%s+%+(%w+)", get_project)
  72.    lget("%s+%@(%w+)", get_context)
  73.    lget("%s+(%w+):([^%s]+)", get_value)
  74.  
  75.    line = string.gsub(line, "^%s*", "")
  76.    line = string.gsub(line, "%s*$", "")
  77.  
  78.    result.description = line
  79.    return result
  80. end
  81.  
  82. --[[
  83. ## Generating task lines
  84.  
  85. The `Task.string` function takes a task record and generates a
  86. corresponding string that can be parsed by `Task.parse`.
  87. --]]
  88.  
  89. function Task.string(task)
  90.    local result
  91.    if task.done then
  92.       result = "x " .. task.done .. " "
  93.    elseif task.priority then
  94.       result = "(" .. task.priority .. ") "
  95.    else
  96.       result = ""
  97.    end
  98.    if task.added then
  99.       result = result .. task.added .. " "
  100.    end
  101.    result = result .. task.description
  102.    for k,v in pairs(task.data) do
  103.       result = result .. " " .. k .. ":" .. v
  104.    end
  105.    for i,project in ipairs(task.projects) do
  106.       result = result .. " +" .. project
  107.    end
  108.    for i,context in ipairs(task.contexts) do
  109.       result = result .. " @" .. context
  110.    end
  111.    return result
  112. end
  113.  
  114. --[[
  115. ## Task ordering and filtering
  116.  
  117. We define the following order on tasks:
  118.  
  119. 1.  Unfinished comes before completed.
  120. 2.  Sort completed tasks by completion date (most recent first)
  121. 3.  Sort unfinished tasks by priority (unprioritized last)
  122. 4.  Sort within priority by date added (oldest first)
  123. 5.  Then sort according to original ordering (task.number)
  124.  
  125. The last criterion is used to stabilize the sort, since stability
  126. is not guaranteed by the lua `table.sort` function.
  127. --]]
  128.  
  129. function Task.compare(t1,t2)
  130.    if t1.done ~= t2.done then
  131.       if t1.done and t2.done then
  132.          return (t1.done > t2.done)
  133.       else
  134.          return t2.done
  135.       end
  136.    elseif t1.priority ~= t2.priority then
  137.       if t1.priority and t2.priority then
  138.          return (t1.priority < t2.priority)
  139.       else
  140.          return t1.priority
  141.       end
  142.    elseif t1.added ~= t2.added then
  143.       if t1.added and t2.added then
  144.          return (t1.added < t2.added)
  145.       else
  146.          return t1.added
  147.       end
  148.    else
  149.       return t1.number < t2.number
  150.    end
  151. end
  152.  
  153. --[[
  154. The `Task.sort` command sorts a task list.  Note that this does not
  155. affect the original list, other than assigning the `number` field to
  156. each of the tasks (if not previously assigned).
  157. --]]
  158.  
  159. function Task.sort(tasks)
  160.    for i = 1,#tasks do
  161.       tasks[i].number = tasks[i].number or i
  162.    end
  163.    return table.sort(tasks, Task.compare)
  164. end
  165.  
  166. --[[
  167. The `Task.make_filter` function generates a predicate that checks
  168. whether a given task matches a filter.  The basic idea is that
  169. every field in the filter should match with a field in the actual
  170. task.  Key/value pairs can be used to specify more sophisticated
  171. filtering.  For the moment, this includes:
  172.  
  173.  - from:YYYY-MM-DD  -- allow all since indicated date
  174.  - until:YYYY-MM-DD -- allow all up to indicated date
  175. --]]
  176.  
  177. function Task.make_filter(taskspec, opname)
  178.    local tfilter
  179.    if not taskspec then
  180.       return function(task) return true end
  181.    elseif type(taskspec) == "function" then
  182.       return taskspec
  183.    elseif type(taskspec) == "table" then
  184.       tfilter = taskspec
  185.    elseif type(taskspec) == "string" then
  186.       tfilter = Task.parse(taskspec)
  187.    else
  188.       error("Invalid filter specification")
  189.    end
  190.    local td = tfilter.data
  191.    
  192.    local function match_list(task, lname)
  193.       local task_names = {}
  194.       local matches = true
  195.       for i,n in ipairs(task[lname]) do task_names[n] = true end
  196.       for i,n in ipairs(tfilter[lname]) do
  197.          matches = matches and task_names[n]
  198.       end
  199.       return matches
  200.    end
  201.    
  202.    local function task_match(task)
  203.       return
  204.          ((not td.from)     or td.from     <= task.done ) and
  205.          ((not td["until"]) or td["until"] >= task.done ) and
  206.          ((not tfilter.done)     or tfilter.done     == task.done    ) and
  207.          ((not tfilter.priority) or tfilter.priority == task.priority) and
  208.          ((not tfilter.added)    or tfilter.added    == task.added   ) and
  209.          (tfilter.description == "" or
  210.           string.find(task.description, tfilter.description, 1, true)) and
  211.          match_list(task, 'projects') and
  212.          match_list(task, 'contexts')
  213.    end
  214.  
  215.    return task_match
  216. end
  217.  
  218. --[[
  219. ## Task I/O
  220.  
  221. On input, a task file is allowed to have shell-style comments and
  222. blank lines (which are ignored).  On output, it is just the formatted
  223. tasks.
  224. --]]
  225.  
  226. function Task.read_tasks(task_file)
  227.    local tasks = {}
  228.    for task_line in io.lines(task_file) do
  229.       local task = Task.parse(task_line)
  230.       table.insert(tasks, task)
  231.       task.number = #tasks
  232.    end
  233.    return tasks
  234. end
  235.  
  236. function Task.write_tasks(task_file, tasks)
  237.    local open_file = (type(task_file) == "string")
  238.    if open_file then task_file = io.open(task_file, "w+") end
  239.    for i,task in ipairs(tasks) do
  240.       task_file:write(Task.string(task) .. "\n")
  241.    end
  242.    if open_file then task_file:close() end
  243. end
  244.  
  245. --[[
  246. ## Task operations
  247.  
  248. The basic operations on a task are to `start` it or `complete` it.
  249. These mostly potentially involve setting some date fields.
  250. --]]
  251.  
  252. local today_strings = {}
  253. local function date_string(offset)
  254.    offset = offset or 0
  255.    if today_strings[offset] then
  256.       return today_strings[offset]
  257.    elseif offset == 0 then
  258.       today_strings[0] = os.date("%F", os.time())
  259.       return today_strings[0]
  260.    else
  261.       local t = os.date("*t", os.time())
  262.       t.day = t.day + offset
  263.       today_strings[offset] = os.date("%F", os.time(t))
  264.       return today_strings[offset]
  265.    end
  266. end
  267.  
  268. function Task.start(task)
  269.    task.added = task.added or date_string()
  270. end
  271.  
  272. function Task.complete(task)
  273.    task.priority = nil
  274.    task.done = task.done or date_string()
  275. end
  276.  
  277. --[[
  278. # Todo main routines
  279.  
  280. The `Task` namespace has the methods for reading, writing, and
  281. manipulating tasks and task files.  The main `Todo` class is where we
  282. actually have the logic of how we want to move things around according
  283. to user commands.
  284.  
  285. ## Creation and save functions
  286.  
  287. We use `new` to generate an object for testing; otherwise, we `load`
  288. the files at the beginning and `save` them at the end.
  289. --]]
  290.  
  291. Todo = {}
  292. Todo.__index = Todo
  293.  
  294. function Todo:new()
  295.    local result = {
  296.       todo_tasks = {},
  297.       done_tasks = {},
  298.       proj_tasks = {}
  299.    }
  300.    setmetatable(result, self)
  301.    return result
  302. end
  303.  
  304. function Todo:load(todo_file, done_file, proj_file)
  305.    local result = {
  306.       todo_file = todo_file,
  307.       done_file = done_file,
  308.       proj_file = proj_file,
  309.       todo_tasks = Task.read_tasks(todo_file),
  310.       done_tasks = Task.read_tasks(done_file),
  311.       proj_tasks = Task.read_tasks(proj_file),
  312.       done_updated = nil,
  313.       proj_updated = nil
  314.    }
  315.    local function bydesc(tbl)
  316.       for i,t in ipairs(tbl) do tbl[t.description] = t end
  317.    end
  318.    bydesc(result.todo_tasks)
  319.    bydesc(result.done_tasks)
  320.    bydesc(result.proj_tasks)
  321.    setmetatable(result, self)
  322.    return result
  323. end
  324.  
  325. function Todo:save()
  326.    Task.write_tasks(self.todo_file, self.todo_tasks)
  327.    Task.write_tasks(self.done_file, self.done_tasks)
  328.    if self.proj_updated then
  329.       Task.write_tasks(self.proj_file, self.proj_tasks)
  330.    end
  331. end
  332.  
  333. --[[
  334. ## Interpreting id strings
  335.  
  336. A number in the range of valid indices for the to-do list refers to
  337. a `todo` task.  If there is only a single active timer, we would like to
  338. use the task being timed as the default id for the `toc` and `do`
  339. commands.
  340. --]]
  341.  
  342. function Todo:get_id(id)
  343.    id = tonumber(id)
  344.    if not id then
  345.       error("Task identifier must be numeric")
  346.    elseif id < 1 or id > #(self.todo_tasks) then
  347.       error("Task identifier is out of range")
  348.    end
  349.    return id
  350. end
  351.  
  352. function Todo:get_tic_id(id)
  353.    if id then return self:get_id(id) end
  354.    local ntics = 0
  355.    local id = nil
  356.    for i,task in ipairs(self.todo_tasks) do
  357.       if task.data.tic then
  358.          ntics = ntics + 1
  359.          id = i
  360.       end
  361.    end
  362.    if ntics == 1 then return id end
  363. end
  364.  
  365. --[[
  366. ## Pretty-printing tasks
  367.  
  368. Text coloring and formatting is done by inserting ANSI escape codes.
  369. I grabbed these from the original `todo.sh` script.  Users can select
  370. colors.
  371. --]]
  372.  
  373. local color_codes = {
  374.    BLACK='\27[0;30m',
  375.    RED='\27[0;31m',
  376.    GREEN='\27[0;32m',
  377.    BROWN='\27[0;33m',
  378.    BLUE='\27[0;34m',
  379.    PURPLE='\27[0;35m',
  380.    CYAN='\27[0;36m',
  381.    LIGHT_GREY='\27[0;37m',
  382.    DARK_GREY='\27[1;30m',
  383.    LIGHT_RED='\27[1;31m',
  384.    LIGHT_GREEN='\27[1;32m',
  385.    YELLOW='\27[1;33m',
  386.    LIGHT_BLUE='\27[1;34m',
  387.    LIGHT_PURPLE='\27[1;35m',
  388.    LIGHT_CYAN='\27[1;36m',
  389.    WHITE='\27[1;37m',
  390.    DEFAULT='\27[0m'
  391. }
  392.  
  393. local function color(name)
  394.    name = string.upper(name or 'DEFAULT')
  395.    io.stdout:write(color_codes[name] or color_codes.DEFAULT)
  396. end
  397.  
  398. function Todo:print_task(task,i)
  399.    local result
  400.    local function p(s) io.stdout:write(s .. " ") end
  401.  
  402.    if i then p(string.format("%2d.", i)) end
  403.  
  404. --[[
  405.    if     task.done     then p("x " .. task.done)
  406.    elseif task.priority then p("(" .. task.priority .. ")")
  407.    else                      p("   ")
  408.    end
  409.  
  410.    color(task.data.color or (task.data.tic and "GREEN") or "DEFAULT")
  411. ]]
  412.  
  413.    p(string.format("%-30s", task.description))
  414.  
  415. --[[
  416.    color()
  417.  
  418.    for i,project in ipairs(task.projects) do p("+" .. project) end
  419.    for i,context in ipairs(task.contexts) do p("@" .. context) end
  420.  
  421.    if task.added then
  422.       color "LIGHT_GREY"
  423.       p(task.added)
  424.       color()
  425.    end
  426. ]]
  427.  
  428.    io.stdout:write("\n")
  429. end
  430.  
  431. --[[
  432. ## Global processing
  433.  
  434. The `archive`, and `stamp` commands act on all elements of
  435. the task list.
  436. --]]
  437.  
  438. function Todo:archive()
  439.    Task.sort(self.todo_tasks)
  440.    for i,task in ipairs(self.todo_tasks) do
  441.       if task.done then
  442.          table.insert(self.done_tasks, task)
  443.          self.todo_tasks[i] = nil
  444.          self.done_updated = true
  445.       end
  446.    end
  447. end
  448.  
  449. function Todo.match_date_spec(spec)
  450.    local dt = os.date("*t", os.time())
  451.    local dayname = {'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'}
  452.    local result = spec and
  453.       ((spec == "weekdays" and (dt.wday > 1 and dt.wday < 7)) or
  454.        (spec == "weekends" and (dt.wday == 1 or dt.wday == 7)) or
  455.        string.match(spec, dayname[dt.wday]) or
  456.        (string.match(spec, "%d%d%d%d%-%d%d%-%d%d") and spec <= date_string()))
  457.    return result
  458. end
  459.  
  460. function Todo:autoqueue()
  461.    for i,task in ipairs(self.proj_tasks) do
  462.       if Todo.match_date_spec(task.data["repeat"]) and
  463.          (not task.data.starting or
  464.           task.data.starting <= date_string()) and
  465.          not self.todo_tasks[task.description] and
  466.          (not self.done_tasks[task.description] or
  467.           self.done_tasks[task.description].done ~= date_string()) then
  468.            
  469.           local tnew = {
  470.              priority = task.priority,
  471.              added = date_string(),
  472.              description = task.description,
  473.              projects = task.projects,
  474.              contexts = task.contexts,
  475.              data = {}
  476.           }
  477.           table.insert(self.todo_tasks, tnew)
  478.  
  479.       elseif Todo.match_date_spec(task.data.queue) then
  480.  
  481.          task.added = task.added or date_string()
  482.          task.data = {}
  483.          table.insert(self.todo_tasks, task)
  484.          table.remove(self.proj_tasks, i)
  485.          self.proj_updated = true
  486.  
  487.       end
  488.    end
  489. end
  490.  
  491. function Todo:stamp()
  492.    for i,task in ipairs(self.todo_tasks) do
  493.       task.added = task.added or date_string()
  494.    end
  495. end
  496.  
  497. --[[
  498. ## Timers
  499.  
  500. The `tic` and `toc` commands can be used to keep track of how much
  501. time is being spent on a given task.  The `time` command can be used
  502. to report the elapsed time on a task without starting the stopwatch.
  503. --]]
  504.  
  505. local function timetosec(timestr)
  506.    if not timestr then return 0 end
  507.    local h,m,s = string.match(timestr, "(%d+)%.(%d%d)%.(%d%d)")
  508.    if h then
  509.       return s+60*(m+60*h)
  510.    else
  511.       return tonumber(timestr)
  512.    end
  513. end
  514.  
  515. local function sectotime(cumsec)
  516.    local h = math.floor(cumsec / 3600)
  517.    cumsec = cumsec-h*3600
  518.    local m = math.floor(cumsec / 60)
  519.    cumsec = cumsec-m*60
  520.    local s = cumsec
  521.    return string.format("%d.%02d.%02d", h, m, s)
  522. end
  523.  
  524. function Todo:tic(id)
  525.    local task = self.todo_tasks[self:get_id(id)]
  526.    task.data.tic = task.data.tic or os.time()
  527. end
  528.  
  529. function Todo:toc(id)
  530.    local task = self.todo_tasks[self:get_tic_id(id)]
  531.    local td = task.data
  532.    if not td.tic then
  533.       error("No timer was set!")
  534.    end
  535.    local elapsed = os.difftime(os.time(), td.tic)
  536.    td.tic = nil
  537.    td.time = sectotime(timetosec(td.time) + elapsed)
  538.    print("Elapsed:", sectotime(elapsed))
  539.    print("Total  :", td.time)
  540. end
  541.  
  542. function Todo:time(id)
  543.    local task = self.todo_tasks[self:get_tic_id(id)]
  544.    local td = task.data
  545.    local elapsed = td.tic and os.difftime(os.time(), td.tic) or 0
  546.    print("Total:", sectotime(timetosec(td.time) + elapsed))
  547. end
  548.  
  549. --[[
  550. ## Reports
  551.  
  552. The `done` function is like `list`, but for projects that are already
  553. done.  The `summary` function reports on the total time being spent on
  554. tasks matching a given filter.  The `report` function gives both a
  555. list of tasks done and a summary.  The `today` function is a report
  556. for a particular day (by default, the current day).
  557. --]]
  558.  
  559. function Todo:list(taskspec)
  560.    Task.sort(self.todo_tasks)
  561.    local filter = Task.make_filter(taskspec, "ls")
  562.    io.stdout:write("\n")
  563.    for i,task in ipairs(self.todo_tasks) do
  564.       if filter(task) then
  565.          self:print_task(task,i)
  566.       end
  567.    end
  568.    io.stdout:write("\n")
  569. end
  570.  
  571. function Todo:done(taskspec)
  572.    local filter = Task.make_filter(taskspec, "done")
  573.    io.stdout:write("\n")
  574.    for i,task in ipairs(self.done_tasks) do
  575.       if filter(task) then
  576.          self:print_task(task)
  577.       end
  578.    end
  579.    io.stdout:write("\n")
  580. end
  581.  
  582. function Todo:summary(taskspec)
  583.    local filter = Task.make_filter(taskspec, "summary")
  584.    local ttime = 0
  585.    local ptimes = {}
  586.    local ptasks = {}
  587.    for i,task in ipairs(self.done_tasks) do
  588.       if filter(task) then
  589.          for j,proj in ipairs(task.projects) do
  590.             if not ptimes[proj] then
  591.                table.insert(ptimes, proj)
  592.                ptimes[proj] = 0
  593.                ptasks[proj] = 0
  594.             end
  595.             ptimes[proj] = ptimes[proj] + timetosec(task.data.time)
  596.             ptasks[proj] = ptasks[proj] + 1
  597.          end
  598.          ttime = ttime + timetosec(task.data.time)
  599.       end
  600.    end
  601.    table.sort(ptimes)
  602.  
  603.    print(string.format("Total recorded time : %10s", sectotime(ttime)))
  604.    print("By project:")
  605.    for i,pname in ipairs(ptimes) do
  606.       print(string.format("%10s : %10s : %d tasks",
  607.                           pname, sectotime(ptimes[pname]), ptasks[pname]))
  608.    end
  609. end
  610.  
  611. function Todo:report(taskspec)
  612.    local filter = Task.make_filter(taskspec, "report")
  613.    self:done(filter)
  614.    self:summary(filter)
  615. end
  616.  
  617. function Todo:today(date)
  618.    local offset = tonumber(date)
  619.    if offset then
  620.       self:report("x " .. date_string(offset))
  621.    else
  622.       self:report("x " .. (date or date_string()))
  623.    end
  624. end
  625.  
  626. --[[
  627. ## Adding, removing, and updating tasks
  628. --]]
  629.  
  630. function Todo:add(task_string)
  631.    if not task_string then
  632.       error("Add requires a task string")
  633.    end
  634.    local task = Task.parse(task_string)
  635.    Task.start(task)
  636.    table.insert(self.todo_tasks, task)
  637.    return task
  638. end
  639.  
  640. function Todo:start(task_string)
  641.    local task = self:add(task_string)
  642.    task.data.tic = os.time()
  643. end
  644.  
  645. function Todo:delete(...)
  646.    local ids = {}
  647.    for i,id in ipairs{...} do
  648.       ids[i] = self:get_id(id)
  649.    end
  650.    table.sort(ids, function(a,b) return a > b end)
  651.    for i,id in ipairs(ids) do
  652.       table.remove(self.todo_tasks, id)
  653.    end
  654. end
  655.  
  656. function Todo:prioritize(id, priority)
  657.    id = self:get_id(id)
  658.    if not string.match(priority, "[A-Z]") then
  659.       error("Priority must be a single character, A-Z")
  660.    end
  661.    self.todo_tasks[id].priority = priority
  662. end
  663.  
  664. function Todo:finish(...)
  665.    for i,id in ipairs{...} do
  666.       local task = self.todo_tasks[self:get_id(id)]
  667.       Task.complete(task)
  668.       if task.data.tic then
  669.          self:toc(id)
  670.       end
  671.    end
  672. end
  673.  
  674. function Todo:did(task_string)
  675.    self:add(task_string)
  676.    self:finish(#self.todo_tasks)
  677. end
  678.  
  679. --[[
  680. ## The help
  681. --]]
  682.  
  683. local help_string = [[
  684. Commands:
  685.  
  686.    ls [filter]      -- List all tasks (optionally matching filter)
  687.    done [filter]    -- Report completed tasks by filter
  688.    summary [filter] -- Print total time records by filter
  689.    report [filter]  -- List tasks and summary by filter
  690.    today [date]     -- Report for a day (default: current day)
  691.  
  692.    add task     -- Add a new task record
  693.    do id        -- Finish indicated task
  694.    did task     -- Add a new record for a completed task
  695.    del id       -- Delete indicated task (by number)
  696.    pri id level -- Prioritize indicated task
  697.  
  698.    start task   -- Add a new task record and start timer
  699.    tic id       -- Start stopwatch on indicated task or project tag
  700.    toc [id]     -- Stop stopwatch on indicated task or project tag
  701.    time [id]    -- Report time spent n indicated task or project tag
  702.  
  703.    arch         -- Archive any completed tasks to done.txt
  704.    stamp        -- Mark any undated entries as added today
  705.    help         -- This function
  706. ]]
  707.  
  708. function Todo:help()
  709.    print(help_string)
  710. end
  711.  
  712. --[[
  713. # The main event
  714. --]]
  715.  
  716. local todo_tasks = {
  717.  
  718.    ls      = Todo.list,
  719.    done    = Todo.done,
  720.    summary = Todo.summary,
  721.    report  = Todo.report,
  722.    today   = Todo.today,
  723.  
  724.    add = Todo.add,
  725.    did = Todo.did,
  726.    del = Todo.delete,
  727.    pri = Todo.prioritize,
  728.    ["do"] = Todo.finish,
  729.  
  730.    start = Todo.start,
  731.    tic   = Todo.tic,
  732.    toc   = Todo.toc,
  733.    time  = Todo.time,
  734.  
  735.    arch  = Todo.archive,
  736.    stamp = Todo.stamp,
  737.    
  738.    help = Todo.help
  739. }
  740.  
  741. function Todo:run(id, ...)
  742.    if not id then
  743.       error("Must specify a task")
  744.    elseif not todo_tasks[id] then
  745.       error("Invalid task: " .. id)
  746.    else
  747.       todo_tasks[id](self, ...)
  748.    end
  749.    Task.sort(self.todo_tasks)
  750. end
  751.  
  752. local function main(...)
  753.    local todo = Todo:load("todo.txt",
  754.                           "done.txt",
  755.                           "proj.txt")
  756.    todo:autoqueue()
  757.    todo:run(...)
  758.    todo:archive()
  759.    todo:save()
  760. end
  761.  
  762. if not test_mode then main(...) end
RAW Paste Data