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
+

Reply via email to