El Mon, Mar 02, 2009 at 06:07:33PM +0100, David Maus ens deleit? amb les
seg?ents paraules:
> > If you tell me how to implement the missing pieces or just do the patch,
> > I'll be
> > very grateful :)
>
> You could use the LuaFileSystem library[1] for testing and parsing the
> directory tree. The example on LuaFileSystem's homepage is actually a
> function that iterates over a given directory.
Right. I already found lua-fs (or something similar), but didn't want to add an
external dependency.
> > PS: I haven't looked for it, but I'm sure I can do the same with emacs
> > itself,
> > give a path where all org files are located, instead of a list which I
> > always
> > forget to update.
>
> Do you mean something like:
>
>
> (setq org-directory "~/Org/")
> (setq org-agenda-files (list org-directory))
Niiiice. I'm still adapting to the whole new infrastructure. I just came from
vim :)
> If you set up Emacs/Orgmode correctly for all of your agenda files you
> could probably use a custom agenda command[2] that outputs items that
> match a certain criteria (i.e.: current, soon etc. items) and gather
> the list of items by exporting these items using Orgmodes
> agenda-export function[3].
Ok. I have something working right now.
--- ~/.emacs.d/init.el ---
(setq org-agenda-files (list "~/plans/" "~/plans/Projects/"))
--- ~/.emacs.d/init.el ---
Note the I have to add both directories, as it doesn't seem to recursively
search for .org files. Hopefully, the number of directories is not going to
change so often :)
Some other notes:
* Emacs already grabs the necessary files, so no need for the files argument in
register.
* In order to get the output the command (at least for me) _must_ provide the
root emacs configuration file (in my case, I have hardcoded
"~/.emacs.d/init.el", but its stored in a separate variable an could be
overrided from 'register').
* I _must_ use "emacs -batch -l configfile -eval '....'" to get the data, as
emacsclient has no '-batch' option.
* The types of notifications has changed from the past/today/soon/future into
something that org-agenda returns by default ("emacs -batch -eval
'(org-batch-agenda-csv "a")'").
We could use filters and other arguments, but this would require multiple runs
of emacs, which is slow (as it seems we can't use emacsclient) and blocks
awesome while updating.
That would be, instead of the "a" argument, something like:
- past
"DEADLINE<\"<today>\"|SCHEDULED<\"<today>\"|TIMESTAMP<\"<today>\""
- today
"DEADLINE<\"<today>\"|SCHEDULED<\"<today>\"|TIMESTAMP<\"<today>\""
- soon / future
"DEADLINE>=\"<today>\"|SCHEDULED>=\"<today>\"|TIMESTAMP>=\"<today>\""
org-agenda-ndays <number of days for soon> org-agenda-start-day "<date to start
as soon>"
Note that this last one also fools some of the fields of the CSV, like
'type' or 'extra'
* I use a very hackish code to align the text, so I'd like if I can do something
more sophisticated with pango markup.
The problem is that text is formed by two columns of variable width, and text,
by default, is not monospaced (with the <tt> markup, text loses its boldness,
and <b> does not give it back)
* I think CLOSED entries still show up... should investigate on org-agenda to
see if they can be omitted
Well, I've attached a patch to the git source with my changes.
Waiting for your comments :)
Lluis
--
"And it's much the same thing with knowledge, for whenever you learn
something new, the whole world becomes that much richer."
-- The Princess of Pure Reason, as told by Norton Juster in The Phantom
Tollbooth
diff --git a/org-awesome.lua b/org-awesome.lua
index 15a266d..286b49a 100644
--- a/org-awesome.lua
+++ b/org-awesome.lua
@@ -16,92 +16,119 @@ local print = print
-- Module org
module("org")
+local config = "~/.emacs.d/init.el"
+local emacs = '/usr/bin/emacs -batch -l %s --eval \'(org-batch-agenda-csv "a")\' 2>/dev/null'
local agenda = { }
-local count = { }
+local count = {
+ -- Categories, depending on the org-agenda 'type' field
+ -- past : 'past-scheduled'
+ -- today: 'deadline' or 'scheduled'
+ -- soon : 'upcoming-deadline'
+ -- other: 'timestamp'
+ -- Anything else in the form "ANYTHINGELSE: <somedate>"
+}
local options = { }
-local files = { }
local widget
-local function basename(file)
- if options.show_basename then
- return string.gsub(file, "(.*/)(.*)", " %2:") or file .. " :"
- else
- return ""
- end
-end
-
+-- Parse emacs' org-agenda output
local function parse_agenda()
- -- Compute delays
- local today = os.time{year=os.date("%Y"), month=os.date("%m"), day=os.date("%d")}
- local soon = today+24*3600*options.delay_soon
- local future = today+24*3600*options.delay_future
-
- agenda = { }
- count = {
- past = 0,
- today = 0,
- soon = 0,
- future = 0
- }
-
- for i = 1, #files do
- local fd = io.open(files[i], "r")
- if not fd then
- print("Could not open '" .. files[i] .. "'")
- break
- end
-
- -- Parse org file
- for l in fd:lines() do
- local scheduled = string.find(l, "SCHEDULED:")
- local closed = string.find(l, "CLOSED:")
- local deadline = string.find(l, "DEADLINE:")
-
- if (scheduled and not closed) or (deadline and not closed) then
- local b, e, y, m, d = string.find(l, "(%d%d%d%d)-(%d%d)-(%d%d)")
-
- -- If date found
- if b and maybe_task then
- local t = os.time{year=y, month=m, day=d}
- local flag
-
- -- Determine proper flag
- if t < today then
- flag = options.color_past
- count.past = count.past + 1
- elseif t == today then
- flag = options.color_today
- count.today = count.today + 1
- elseif t <= soon then
- flag = options.color_soon
- count.soon = count.soon + 1
- elseif t <= future then
- flag = options.color_future
- count.future = count.future + 1
- end
-
- if flag then
- local task = string.gsub(maybe_task, "*", "")
- local item = {
- task = task,
- file = files[i],
- deadline = deadline and not scheduled,
- output = "<span color='" .. flag .. "'>" .. os.date("%d %b:", t) .. basename(files[i]) .. task .. "</span>",
- when = t,
- flag = flag
- }
- table.insert(agenda, item)
- end
+ emacs_cmd = emacs:format(config)
+ local fd = io.popen(emacs_cmd, "r")
+ if not fd then
+ print("Could not get emac's output (from '"..emacs_cmd.."')")
+ return
+ end
+
+ agenda = { }
+ count = {
+ past = 0,
+ today = 0,
+ soon = 0,
+ other = 0
+ }
+
+ for l in fd:lines() do
+ local ll = l..","
+ local item = { }
+ -- Parse CSV into a table
+ -- Note that no commas will appear except for value separators
+ for t in ll:gmatch("([^,]*),") do
+ table.insert(item, t)
+ --[[
+ From apropos org-batch-agenda-csv
+ 1 category
+ 2 head
+ 3 type
+ 4 todo
+ 5 tags
+ 6 date
+ 7 time
+ 8 extra
+ 9 priority-l
+ 10 priority-n
+ 11 agenda-day
+ --]]
+ end
+
+ local entry = {
+ type = nil,
+ prio = item[10]
+ }
+ local color = nil
+ -- The whole tabbing makes it appear nice but is a hack that might only work for me
+ local tab = "\t"
+
+ if item[3] == "past-scheduled" then
+ count.past = count.past + 1
+ entry.type = 1
+ color = options.color_past
+ elseif item[3] == "deadline" then
+ count.today = count.today + 1
+ entry.type = 2
+ if item[8] ~= "Deadline:" then
+ tab = "\t\t"
end
- end
- maybe_task = l
- end
- fd:close()
- end
-
- -- Sort by date
- table.sort(agenda, function (a, b) return (a.when < b.when) end)
- return 0
+ color = options.color_today
+ elseif item[3] == "scheduled" then
+ count.today = count.today + 1
+ entry.type = 2
+ color = options.color_today
+ elseif item[3] == "upcoming-deadline" then
+ count.soon = count.soon + 1
+ tab = "\t\t"
+ entry.type = 3
+ color = options.color_soon
+ elseif item[3] == "timestamp" then
+ count.other = count.other + 1
+ entry.type = 4
+ tab = "\t\t\t"
+ color = options.color_future
+ else
+ print(l)
+ end
+
+ if entry.type ~= nil then
+ local output = "<i>"..item[8].."</i>"..tab..item[1].."\t"..item[2]
+ entry.output = "<span color='"..color.."'>"..output.."</span>"
+ table.insert(agenda, entry)
+ end
+ end
+
+ fd:close()
+
+ -- Sort first by 'type', then by 'priority-n', else maintain org-agenda order
+ -- TODO: Is it already correctly ordered by org-agenda?
+ table.sort(agenda, function(a,b)
+ if a.type < b.type then
+ return true
+ elseif a.type == b.type and a.prio > b.prio then
+ return true
+ else
+ return false
+ end
+ end)
+
+ return 0
end
local function colorize_format(flag, count)
@@ -110,13 +137,18 @@ end
--- Register org to a widget
-- @param w The widget to register, must be a textbox
--- @param f A table containing the paths to the org files to parse
--- @param opt Options, this is an optional table which can have the following keys: format: textbox format ("$past/$today/$soon/$future" by default), format_colorize: a boolean to colorize the texbox or not (true, by default), width: naughty's width (400 by default), position: naughty position, see naughty documentation for available positions ("top_right" by default), timeout and hover_timeout: see naughty documentation, color_{past,today,soon,future}, delay_{soon,future}: in days (by default soon is 3 days, future is 7 days), show_basename: a boolean to show or not the org file basename in naughty when multiple files are used
-function register(w, f, opt)
+-- @param opt Options, this is an optional table which can have the following keys:
+-- format: textbox format ("$past/$today/$soon/$other" by default)
+-- format_colorize: a boolean to colorize the texbox or not (true, by default)
+-- width: naughty's width (400 by default)
+-- position: naughty position
+-- see naughty documentation for available positions ("top_right" by default)
+-- timeout and hover_timeout: see naughty documentation
+-- color_{past,today,soon,other}
+function register(w, opt)
widget = w
- files = f
- options.format = opt and opt.format or "$past/$today/$soon/$future"
+ options.format = opt and opt.format or "$past/$today/$soon/$other"
options.width = opt and opt.width or 400
options.timeout = opt and opt.timeout or 0
options.hover_timeout = opt and opt.hover_timeout or 0
@@ -125,18 +157,11 @@ function register(w, f, opt)
options.color_today = opt and opt.color_today or "#DED200"
options.color_soon = opt and opt.color_soon or "#00D225"
options.color_future = opt and opt.color_future or "#00921A"
- options.delay_soon = opt and opt.delay_soon or 3
- options.delay_future = opt and opt.delay_future or 7
if opt and opt.format_colorize ~= nil then
options.format_colorize = opt.format_colorize
else
options.format_colorize = true
end
- if opt and opt.show_basename ~= nil then
- options.show_basename = opt.show_basename
- else
- options.show_basename = false
- end
update()
end
@@ -152,26 +177,29 @@ function update()
format = string.gsub(format, "$past", colorize_format(options.color_past, count.past))
format = string.gsub(format, "$today", colorize_format(options.color_today, count.today))
format = string.gsub(format, "$soon", colorize_format(options.color_soon, count.soon))
- format = string.gsub(format, "$future", colorize_format(options.color_future, count.future))
+ format = string.gsub(format, "$other", colorize_format(options.color_future, count.other))
widget.text = format
end
--- Open naughty and display agenda
function naughty_open()
- if #agenda > 0 then
- local output = { }
- for i = 1, #agenda do
- table.insert(output, agenda[i].output)
- end
-
- n = naughty.notify({
- text = table.concat(output, "\n"),
- position = options.position,
- timeout = options.timeout, hover_timeout = options.hover_timeout,
- width = options.width, screen = capi.mouse.screen
- })
- end
+ if #agenda > 0 then
+ local output = { }
+
+ for i = 1, #agenda do
+ table.insert(output, agenda[i].output)
+ end
+
+ n = naughty.notify({
+ text = table.concat(output, "\n"),
+ position = options.position,
+ timeout = options.timeout,
+ hover_timeout = options.hover_timeout,
+ width = options.width,
+ screen = capi.mouse.screen
+ })
+ end
end
--- Close naughty
@@ -180,3 +208,4 @@ function naughty_close()
naughty.destroy(n)
end
end
+