Plugins for the web with Perl, Catalyst and DBIx::Class
June 7, 2011 § Leave a Comment
One of the reasons why Catalyst and DBIx::Class are recommendable over their counterparts on CPAN is that the possess an organic architecture, which is why they have grown to be a lot more mature as a platform. Occasionally, I see a few feature requests in mailing lists and after pondering a bit, I notice that the feature has already been implemented “unintentionally”, plugins are one such case.
Recently, Geovanny Junior, from http://eutsiv.com.br, asked in the São Paulo Perl Mongers mailing list, if there was a way to add plugins to his Catalyst/DBIx::Class application, including his database schema. He wanted a class structure like this:
- MyApp
- central application class
- MyApp::Schema
- DBIx::Class Schema
- MyApp::Schema::Result
- core table namespace
- MyApp::Plugin
- general plugin namespace
- MyApp::Plugin::Schema::Result
- table plugin namespace
- MyApp::Plugin::Schema::ResultSet
- resultset plugin namespace
The answer is that, inspite of both Catalyst and DBIx::Class not having been designed with this specific plugin structure in mind, this is very easy to implement combining Catalyst, DBIx::Class and Moose.
With DBIx::Class, you only have to invoke the loadnamespaces method again, with the additional plugin namespace, in order to load aditional tables.
MyApp::Schema->load_namespaces() # load standard tables MyApp::Schema->load_namespaces( result_namespace => '+MyApp::Plugin::Result', resultset_namespace => '+MyApp::Plugin::ResultSet' ); # load plugin tables
When you load the plugins, the core schema uses them as aditional tables. DBIx::Class doesn’t care whether they’re plugins or not, so you can work with them as regular table classes. To deploy the enhanced schema, run the deploy method on a connected schema object:
MyApp::Schema->connect(etc...)->deploy;
I Particularly think it’s a bad idea to create plugins that modify tables in your standard platform as it will lead to maintainance hell. There’s a very good chance that table, field and index names from different plugins will collide. The ideal architecture for a pluggable system is to isolate the core from the plugins so that additions are the least intrusive as possible. That will lower the chance of propagating errors and collisions across the system. In my opinion, the least intrusive approach for this case is to create new tables and join them with the core tables, instead of altering the existing tables to add new fields. Fortunately, this kind of thing is easy to implement, with the loadnamespaces approach.
Catalyst uses Module::Pluggable for loading it’s components. For the case of plugins that don’t involve the database, you can pass aditional configuration to Module::Pluggable via the setupcomponents configuration option:
MyApp->config( setup_components => { search_path => [qw(MyApp::Plugin)], except => [qw(MyApp::Plugin::Schema)] } );
Notice the use of “except” to avoid loading schema plugins, since those will already have been loaded when you load the schema.
To illustrate the concept, let’s create a few classes:
package MyApp::Schema::Result::User; use warnings; use strict; use base 'DBIx::Class'; __PACKAGE__->load_components(qw(Core)); __PACKAGE__->table("user"); __PACKAGE__->add_columns(qw(id name age added));
This is a simple proof-of-concept core table.
Now, a controller that’s also part of the application core.
package MyApp::Controller::Root; use Moose; sub base :Chained('/') PathPart('') Args(0) { my($self, $c) = @_; $c->stash->{user} = $c->model->first; }
Also very simple, it just loads the first record from the default model, which can be configured to be one of the database tables.
After that, we create a plugin table (which is actually a regular table in a different namespace).
package MyApp::Plugin::Schema::Result::User::USA; use warnings; use strict; use base 'DBIx::Class'; __PACKAGE__->load_components(qw(Core)); __PACKAGE__->table("user_usa"); __PACKAGE__->add_columns(qw(user_id zip_code)); __PACKAGE__->belongs_to( user => User => {'foreign.id' => 'self.user_id'}, { proxy => [qw/name age added/] } ); sub country {q{USA}}
And yet another table, with a different country.
package MyApp::Plugin::Schema::Result::User::Brazil; use warnings; use strict; use base 'DBIx::Class'; __PACKAGE__->load_components(qw(Core)); __PACKAGE__->table("user_brazil"); __PACKAGE__->add_columns(qw(user_id CEP)); __PACKAGE__->belongs_to( user => User => {'foreign.id' => 'self.user_id'}, { proxy => [qw/name age added/] } ); sub country {q{Brazil}}
After that, a plugin for adding internationalization into the controller.
package MyApp::Plugin::Controller::International; use Moose; use MyApp::Controller::Root; package MyApp::Controller::Root; use Moose; __PACKAGE__->meta->make_mutable; before base => sub { my($self, $c) = @_; my $country = $c->req->header('X-Geolocation') || 'Brazil'; $c->stash->{current_model_instance} = $c->model('DB::User::' . $country); $c->stash->{template} = $country . '.tt' }; __PACKAGE__->meta->make_immutable;
The code above lives entirely in the same file, first a declaration of the plugin package, to keep Catalyst from complaining that the package isn’t defined. We then import the module we’re going to alter, this guarantees the correct load order in the catalyst setup. After that, we switch the scope to the package we want to plug in to, this is a cheap way of gaining all the Moose syntax sugar.
By default, Catalyst components are imutable Moose classes. Imutability means that the class won’t be altered by the meta-object protocol, and the Moose backend can optimize away. For adding a method modifier, we need to temporarily disable imutability via the make_mutable method on the meta-class. After that, we apply the method modifier, which is what injects the plugin behaviour into the core app. In this case, it’ll multiplex between the USA and Brazil tables and the USA.tt/Brazil.tt according to what it sees in the request header.
Now we need to teach each template to use the specific data from the plugin classes.
# Brazil.tt Olá, o primeiro usuário brasileiro registrado é [% user.name %], portador do CEP [% user.CEP %] # USA.tt Hello, the first registered american user is [% user.name %], his zip code is [% user.zip_code %]
The plugins can be configured normally through the regular catalyst configuration files, just like the core app components.
GTD with Org Mode, Part III
June 2, 2011 § Leave a Comment
Processing
Processing tasks, in my case, means looking at any incoming email messages and other things I have captured throughout the day. I process my inboxes twice a day, after breakfast and after lunch. Before establishing the routine of checking at a given point of the day, I found I was looking at my mailboxes all the time, and that diverts focus and wastes time.
The Agenda
The best way to process items is by using the agenda. Tasks can be displayed in several dimensions, and while I used the default agenda settings for quite a long time, I found that using a single agenda view which displays all the dimensions I need at once is a lot more practical. I use what org mode calls a “block agenda”, which are just several agendas bundled into one.
(setq org-agenda-custom-commands
'(("w" "Work Agenda"
;; inbox
((tags-todo "-TODO=\"INBOX\"+#inbox"
((org-agenda-overriding-header "Inbox")))
;; deadlines
(tags-todo "+DEADLINE<=\"<today>\""
((org-agenda-overriding-header "Late Deadlines")
(org-agenda-tags-todo-honor-ignore-options t)
(org-agenda-todo-ignore-scheduled t)
(org-agenda-todo-ignore-deadlines nil)))
;; deadlines
(tags-todo "+SCHEDULED<=\"<today>\""
((org-agenda-overriding-header "Late Schedule")
(org-agenda-tags-todo-honor-ignore-options t)
(org-agenda-todo-ignore-scheduled nil)
(org-agenda-todo-ignore-deadlines t)))
;; waiting
(tags-todo "+#waiting"
((org-agenda-overriding-header "Waiting")
(org-agenda-tags-todo-honor-ignore-options t)
(org-agenda-todo-ignore-scheduled t)
(org-agenda-todo-ignore-deadlines t)))
;; today's schedule
(agenda "")
;; started tasks
(tags-todo "+TODO=\"STARTED\"-#hold"
((org-agenda-overriding-header "STARTED Actions")
(org-agenda-tags-todo-honor-ignore-options t)
(org-agenda-todo-ignore-scheduled nil)
(org-agenda-todo-ignore-deadlines nil)))
;; next tasks
(tags-todo "+TODO=\"NEXT\"-#hold"
((org-agenda-overriding-header "NEXT Actions")
(org-agenda-tags-todo-honor-ignore-options t)
(org-agenda-todo-ignore-scheduled t)
(org-agenda-todo-ignore-deadlines t)))
;; projects
(tags-todo "-#waiting-TODO=\"INBOX\""
((org-agenda-skip-function 'bh/skip-non-projects)
(org-agenda-overriding-header
"Projects (< to restrict by project)")))
;; backlog
(tags-todo "+TODO=\"TODO\"-#hold-#inbox"
((org-agenda-overriding-header "Action Backlog")
(org-agenda-tags-todo-honor-ignore-options t)
(org-agenda-todo-ignore-scheduled t)
(org-agenda-todo-ignore-deadlines t))))
((org-agenda-filter-preset '("-#hold"))))
;; stuck projects revision agenda view
("#" "Stuck Projects"
;; stuck projects
((tags-todo "-#hold"
((org-agenda-skip-function 'bh/skip-non-stuck-projects)
(org-agenda-overriding-header "Stuck Projects")))
;; action backlog
(tags-todo "-#hold"
((org-agenda-overriding-header "Action Backlog")))))
;; candidate tasks for archiving
("A" "Tasks to be Archived" tags "-#hold"
((org-agenda-overriding-header "Tasks to Archive")
(org-agenda-skip-function 'bh/skip-non-archivable-tasks)))
;; held items for revision
("r" "Review Items" tags-todo "+#hold"
((org-agenda-todo-ignore-with-date nil)
(org-agenda-todo-ignore-scheduled nil)
(org-agenda-todo-ignore-deadlines nil)))))
The above setting configures all my agenda views, listed in order of priority.
- Work Agenda
This is a block agenda, a composition of several agendas, containing everything I need for my day-to-day workflow. It is designed to be processed from top to bottom throughout the day.- The Inbox
Incoming tasks are the top priority, these aren’t meant to be executed immediately, just processed for being done at some point. The goal is to always keep recently captured items on top, to remind me to process them at some point. I have also configured reminders throughout the day, to make sure I don’t forget to process my inbox tasks. Also, processing the inbox is an easy way to immediately shorten the list of tasks in the agenda, it’s a good feeling to see the items disapear from the agenda so quicklly, even if it’s temporary.
- The Waiting List
These are unscheduled tasks that are waiting on some resource (a person, a piece of equipment that is about to arrive, etc.) in order to get done. This comes second on the agenda list so that I start the day knowing what people I need to get in touch with. This list only displays unscheduled tasks.
- The Calendar
This is where tasks with scheduled dates and deadlines show up. My day is driven by what shows up in here. This piece of the agenda can be set to span a day, a week, a month or a year. It defaults to displaying the current day.(setq org-agenda-ndays 1)
- Started Actions
A list of actions I have already started but haven’t finished yet. This comes right after any appointments to ensure that I finish things I have started. This article has only been finished because there’s a task in this section that’s constantly nagging me about it. Started tasks are set to a special keyword which is activated by clocking in on an item (more on clocking later on).. The relevant elisp has been stolen from Bernt Hansen’s excellent org mode guide.(defun bh/clock-in-to-started (kw) "Switch task from TODO or NEXT to STARTED when clocking in. Skips capture tasks and tasks with subtasks" (if (and (member (org-get-todo-state) (list "TODO" "NEXT")) (not (and (boundp 'org-capture-mode) org-capture-mode)) (not (bh/is-project-p-with-open-subtasks))) "STARTED")) ;; Change task state to STARTED when clocking in (setq org-clock-in-switch-to-state 'bh/clock-in-to-started)
- Next Actions
A list of things that I should start doing once all the scheduled and started actions have been concluded. This creates movement in all the projects I have committed to.(global-set-key "\C-ca" 'org-agenda)
- Projects
A list of all the projects I’m currently engaged in. This is also stolen from Bernt Hansen’s setup. A project is defined by any task containing subtasks.(defun bh/is-project-p () "Any task with a todo keyword subtask" (let ((has-subtask) (subtree-end (save-excursion (org-end-of-subtree t)))) (save-excursion (forward-line 1) (while (and (not has-subtask) (< (point) subtree-end) (re-search-forward "^\*+ " subtree-end t)) (when (member (org-get-todo-state) org-todo-keywords-1) (setq has-subtask t)))) has-subtask)) (defun bh/is-project-p-with-open-subtasks () "Any task with a todo keyword subtask" (let ((has-subtask) (subtree-end (save-excursion (org-end-of-subtree t)))) (save-excursion (forward-line 1) (while (and (not has-subtask) (< (point) subtree-end) (re-search-forward "^\*+ " subtree-end t)) (when (and (member (org-get-todo-state) org-todo-keywords-1) (not (member (org-get-todo-state) org-done-keywords))) (setq has-subtask t)))) has-subtask))
These assertions are then used in the agenda definition to skip task trees accordingly via the following functions:
(defun bh/skip-non-projects () "Skip trees that are not projects" (let* ((subtree-end (save-excursion (org-end-of-subtree t)))) (if (bh/is-project-p) nil subtree-end))) (defun bh/skip-projects () "Skip trees that are projects" (let* ((subtree-end (save-excursion (org-end-of-subtree t)))) (if (bh/is-project-p) subtree-end nil)))
- Action Backlog
A list of all the currently captured actions, minus all the tasks in the previous agenda views. This allows me to quickly navigate to any given active task without having to leave the agenda. It is also good for mass editing of tasks properties.
- The Inbox
Stuck Projects
A stuck project is a project that has no way to progress. In practical terms, that means projects that don’t have NEXT or STARTED actions in their subtrees. In this agenda I skip all headlines that don’t match that criteria via a custom function also stolen from Bernt Hansen.
(defun bh/skip-non-stuck-projects () "Skip trees that are not stuck projects" (let* ((subtree-end (save-excursion (org-end-of-subtree t))) (has-next (save-excursion (forward-line 1) (and (< (point) subtree-end) (re-search-forward "^\\*+ \\(NEXT\\|STARTED\\) " subtree-end t))))) (if (and (bh/is-project-p) (not has-next)) nil ; a stuck project, has subtasks but no next task subtree-end)))
Archive Candidates
An agenda that only displays tasks for archiving. I look at this agenda once per month and archive all the headlines that show up here.
(defun bh/skip-non-archivable-tasks () "Skip trees that are not available for archiving" (let* ((subtree-end (save-excursion (org-end-of-subtree t))) (a-month-ago (* 60 60 24 31)) (last-month (format-time-string "%Y-%m-" (time-subtract (current-time) (seconds-to-time a-month-ago)))) (this-month (format-time-string "%Y-%m-" (current-time))) (subtree-is-current (save-excursion (forward-line 1) (and (< (point) subtree-end) (re-search-forward (concat last-month "\\|" this-month) subtree-end t))))) (if subtree-is-current subtree-end ; Has a date in this month or last month, skip it nil)))
Agenda Tweaks
- Org Files
I load all my org files into the agenda, with the exception of a special flag that I activate when exporting the agenda in batch mode. More on exporting later on.(if (not (boundp 'ec/org-agenda-export)) (setq org-agenda-files '("~/var/lib/org")))
- Sorting
Sorting order in the agenda is very important, given I process tasks in order, from top to bottom. For the daily schedule, the sorting priority is for headlines that have specific times. These headlines must absolutely be done in the time they were scheduled for so it makes sense to have then show up first in the calendar. Sorting by priority and habits gets the I have defined as the most important, as long;; Agenda sorting functions (setq org-agenda-cmp-user-defined 'bh/agenda-sort) ;; Sorting order for tasks on the agenda (setq org-agenda-sorting-strategy (quote ((agenda time-up priority-down habit-up user-defined-up effort-up category-up) (todo priority-down category-up) (tags priority-down category-up))))
This is the custom sorting function, a variation on Bernt Hansen’s version. I give priority to deadlines first, then schedules, late dates first, pending dates last.
(defun bh/agenda-sort (a b) "Sorting strategy for agenda items. Late deadlines first, then scheduled, then non-late deadlines" (let (result num-a num-b) (cond ; time specific items are already sorted first by org-agenda-sorting-strategy ; late deadlines ((bh/agenda-sort-test-num 'bh/is-late-deadline '< a b)) ; deadlines for today ((bh/agenda-sort-test 'bh/is-due-deadline a b)) ; pending deadlines ((bh/agenda-sort-test-num 'bh/is-pending-deadline '< a b)) ; late scheduled items ((bh/agenda-sort-test-num 'bh/is-scheduled-late '> a b)) ; scheduled items for today ((bh/agenda-sort-test 'bh/is-scheduled-today a b)) ; non-deadline and non-scheduled items ((bh/agenda-sort-test 'bh/is-not-scheduled-or-deadline a b)) ; finally default to unsorted (t (setq result nil)) ) result)) (defmacro bh/agenda-sort-test (fn a b) "Test for agenda sort" `(cond ; if both match leave them unsorted ((and (apply ,fn (list ,a)) (apply ,fn (list ,b))) (setq result nil)) ; if a matches put a first ((apply ,fn (list ,a)) ; if b also matches leave unsorted (if (apply ,fn (list ,b)) (setq result nil) (setq result -1))) ; otherwise if b matches put b first ((apply ,fn (list ,b)) (setq result 1)) ; if none match leave them unsorted (t nil))) (defmacro bh/agenda-sort-test-num (fn compfn a b) `(cond ((apply ,fn (list ,a)) (setq num-a (string-to-number (match-string 1 ,a))) (if (apply ,fn (list ,b)) (progn (setq num-b (string-to-number (match-string 1 ,b))) (setq result (if (apply ,compfn (list num-a num-b)) -1 1))) (setq result -1))) ((apply ,fn (list ,b)) (setq result 1)) (t nil))) (defun bh/is-not-scheduled-or-deadline (date-str) (and (not (bh/is-deadline date-str)) (not (bh/is-scheduled date-str)))) (defun bh/is-due-deadline (date-str) (string-match "Deadline:" date-str)) (defun bh/is-late-deadline (date-str) (string-match "In *\\(-.*\\)d\.:" date-str)) (defun bh/is-pending-deadline (date-str) (string-match "In \\([^-]*\\)d\.:" date-str)) (defun bh/is-deadline (date-str) (or (bh/is-due-deadline date-str) (bh/is-late-deadline date-str) (bh/is-pending-deadline date-str))) (defun bh/is-scheduled (date-str) (or (bh/is-scheduled-today date-str) (bh/is-scheduled-late date-str))) (defun bh/is-scheduled-today (date-str) (string-match "Scheduled:" date-str)) (defun bh/is-scheduled-late (date-str) (string-match "Sched\.\\(.*\\)x:" date-str))
- Tweaks
Don’t hide empty dates from the agenda display, to help unclutter things.(setq org-timeline-show-empty-dates nil)
Don’t allow todos to be marked as done if there are pending subtasks.
(setq org-enforce-todo-dependencies t)
Dim tasks that have pending dependencies.
(setq org-agenda-dim-blocked-tasks t)
In log mode, show entries that have been clocked during the day.
(setq org-agenda-log-mode-items (quote (clock)))
Include the diary file in the calendar.
(setq org-agenda-include-diary t)
Show repeating timestamps in future dates.
(setq org-agenda-repeating-timestamp-show-all t)
Show all agenda dates even if they are empty.
(setq org-agenda-show-all-dates t)
Start the agenda on monday.
(setq org-agenda-start-on-weekday nil)
Enable display of the time grid.
(setq org-agenda-use-time-grid t)
Display tags farther right in the agenda.
(setq org-agenda-tags-column -102)
Priorities
I have a special file that bootstraps the entire GTD process, called gtd.org. This is where I maintain all the recurrent tasks that keep the GTD workflow going, these tasks are set to the top priority, otherwise they get lost in the ocean of captured tasks and never get done and I end up either abandoning the process or having to remember to dig out the task from the list, which goes against the entire philosophy of GTD. Currently, this is my only use for explicit priorities. The rest of the priorities are set by org-mode automatically, based on how late a given task is.
Habits
Tasks like the ones defined in gtd.org are so important that simply viewing whether the task is scheduled or not isn’t enough. Setting a task as a habit allows the agenda to display additional information about a recurring task in the form of a small graph which tells you how consistent you have been with that task. It also adjusts the priority of a task automatically, based on the consistency, the longer a habit has gone undone, the higher the priority. This allows habits to eventually ripple up the priority list and eventually get done.
** TODO [#A] Read mail - 1st pass SCHEDULED: <2011-05-21 Sat 06:20 ++1d> :PROPERTIES: :STYLE: habit :Effort: 0:10 :CLOCK_MODELINE_TOTAL: today :END: ** TODO [#A] Process inbox - 1st pass SCHEDULED: <2011-05-21 Sat 06:30 ++1d> :PROPERTIES: :STYLE: habit :Effort: 0:10 :CLOCK_MODELINE_TOTAL: today :END: ** TODO [#A] Read mail - 2nd pass SCHEDULED: <2011-05-21 Sat 14:00 ++1d> :PROPERTIES: :STYLE: habit :Effort: 0:10 :CLOCK_MODELINE_TOTAL: today :END: ** TODO [#A] Process inbox - 2nd pass SCHEDULED: <2011-05-21 Sat 14:10 ++1d> :PROPERTIES: :STYLE: habit :Effort: 0:10 :CLOCK_MODELINE_TOTAL: today :END:
I have defined all four tasks as habits, this is enabled by the org-habit module which we loaded earlier. To define a task as a habit, it needs to have a repeating SCHEDULED or DEADLINE property and have the STYLE property set to “habit”, as demonstrated above.
A habit appears in the agenda like this:

An asterisk means the task was done on that day and an exclamation mark identifies the current day. Under my color theme, red means that the task is late, orange indicates the last day of tolerance for getting that task done, and green means you met the scheduled for that task.
The graph also reflects the repetition specification for the task, you can read about specifying recurring dates in the org manual.
The default settings only span 2 weeks. I prefer to cover 3 weeks before and 1 week ahead so that I get a complete quasi-monthly view including the past consistency and the future scheduling of a given habit.
(setq org-habit-following-days 7) (setq org-habit-preceding-days 21)
Estimation
The gtd.org file also has a habit for estimating tasks, which has to be done every day.
** TODO [#A] Estimate effort for all of today's tasks SCHEDULED: <2011-06-03 Fri 06:40 ++1d> :PROPERTIES: :Effort: 0:10 :LAST_REPEAT: [2011-06-02 Thu 06:49] :STYLE: habit :END:
When working this habit, I go through the headlines in the inbox and apply estimates to each task by pressing ‘e’. Predefined estimate times can be set globally.
(setq org-global-properties
'(("Effort_ALL". "0 0:10 0:30 1:00 2:00 3:00 4:00")))
They can also be set locally via the org file headers
#+PROPERTY: Effort_ALL 0 0:10 0:30 1:00 2:00 3:00 4:00 5:00 6:00 7:00 8:00
Being precise is not important at this particular phase, an aproximation will suffice. These estimates are going to be used later on when you get to the point of selecting what task to do based on your available time.
Tagging
Tags are used for implementing what GTD defines as “context”. Pressing “:” over a headline will set the tags for it.
Refiling
The heavy lifting of the processing phase consists of moving headlines from the inbox to their appropriate locations in the different org files. Refiling is done via C-w.
My particular configuration allows me to refile into any org file under up to 5 headlines. By default, org mode doesn’t allow you to explicitly select the target file, it deduces the file from the headline you’ve selected. I prefer to be able to select the file specifically.
; allow refiling into up to 5 levels of the headline trees in all org files (setq org-refile-targets (quote ((org-agenda-files :maxlevel . 5) (nil :maxlevel . 5)))) ; Targets start with the file name - allows creating level 1 tasks (setq org-refile-use-outline-path (quote file)) ; Targets complete in steps so we start with filename ; TAB shows the next level of targets etc (setq org-outline-path-complete-in-steps t)
I use several different org files because it gives me better customization support, given I can use the file headers for most of the relevant settings and create exports from the files.
Bulk Actions
Actions can be performed on several headlines at a time. Pressing “m” over a task will select that task for processing. Marking is prefix-compliant, which means you can type “C-u 10 m” to mark the next 10 headlines in the agenda, starting from point.
After marking headlines, pressing “B” will display options of actions to be performed on the marked tasks, including estimation, scheduling, tagging and refiling.
GTD with Org Mode, Part II
May 20, 2011 § 1 Comment
As the post title suggests, My daily work process is based on David Allen’s GTD methodology. As recommended in his book, I have adapted a few things that I think will enhance my productivity, based on my personal characteristics and thought process. It is recommendable that you read the book and take some time to adapt the process to your own lifestyle.
The Five Stages of Mastering Workflow
These are a few simple recommendations extracted from the GTD book, which I have implemented in org mode. The basis of the system is to try and relieve your mind from having to remember things, so that you can focus on doing things instead. The process consists of five core activities:
- Collect
- Process
- Organize
- Review
- Do
The only thing I have to remember to do is to collect things to do and notes throughout the day and follow the schedule I have established within a file called gtd.org.
Collecting
The recommendation is that everything you are considering to do at some point should be captured somewhere then fed to the GTD process. The rationale is that if you need to stop and consider if something needs doing or not, at the point where the issue arises, you will end up not making the ideal judgement on that task. So you should just get in the habit of taking notes of everything, then you can process those notes subsequently. Knowing that everything you capture will be processed and dealt with at some point leaves you more focused on the task at hand. You can just quickly take a note and go back to what you were doing before, knowing that at some point, you will deal with the note you just took.
Taking notes in org is a process called “capturing” within the org jargon. You can specify templates for several different types of notes. Over time, your brain will associate “capturing” with “doing”, and you’ll always take notes of everything you want to do.
Org directory
This is where org looks for files when you use relative paths to specify files. I keep a linux root-like structure in my home dir, and I treat my org files as if they were a like a simple database or log system, so files go into ~/var/lib/org by default.
(setq org-directory (expand-file-name "~/var/lib/org"))
Headline Types
I use 5 types of headlines to control task state and help me keep track of progress in general.
- TODO
- tasks that have been captured and haven’t been dealt with yet
- NEXT
- tasks that have been flagged for attention once everything else is cleared
- STARTED
- tasks in course
- DONE
- tasks that have been concluded
- CANCELLED
- tasks that were scheduled previously but can’t be concluded for one or another reason
- INBOX
- A special marker indicating that this headline is actually a container for incoming tasks, this is just here so that the inbox headings don’t show up as a task in the agenda.
(setq org-todo-keywords
(quote ((sequence
"TODO(t)"
"NEXT(n)"
"STARTED(s)"
"|" "DONE(d!/!)" "CANCELLED(c@/!)")
(sequence "INBOX"))))
The letters between parentheses are the keybindings you need to use after changing the headline state. The vertical bar is special, meaning that states listed from here on in the current sequence are final states.
Inboxes
Whenever you capture something, org-mode will place the entry(ies) you create in this file. The trick is to make the capture process as quick and painless as possible and capture absolutely everything throughout your day. It’s very important that nothing gets in the way of capturing, otherwise you’ll start avoiding it, which means you’ll also avoid doing things. Just capture everything without caring much about what it is and then process it later. Whenever I capture, the headline gets stored in my default notes file and I can process it later and possibly move it somewhere else.
(setq org-default-notes-file (concat org-directory "/todo.org"))
Org mode has a sprintf-like notation for specifying how the headline is supposed to be formatted and what information is supposed to go in it when you capture. I use a single template for everything, from notes to tasks and reminders. The workflow keywords have different meanings for these based on what the headline actually is. If it’s a note, the headline task means “file this note under the proper headline and tags”. If it’s an email it can mean “reply to this email”. TODO Headlines basically mean “deal with this bit of information”. Your mileage may vary.
I use C-c r as the key binding for capturing notes, this is legacy from the days of remember.el, I got used to it and decided to keep it. You can change this to whatever you see fit. Capture creates a headline from the template and places it under whatever file and headline you configure.
(setq org-capture-templates
(quote
(("t" "todo" entry (file+headline "todo.org" "inbox")
"* TODO %?%a\n %U\n"
:clock-in t
:clock-resume t))))
The characters in the template mean the following:
| %? | after filling in the template, place the cursor here |
| %a | invoke org-store-link and insert the link here |
| %U | insert an inactive timestamp here |
That specific template is configured to interrupt the current task clock, clock the time we’ve spent capturing and resume the clock on the interrupted task once we’re done capturing.
Org mode automatically adjusts the amount of * to the target heading you’re capturing to. If my inbox heading were a level 3 heading, it would add 3 extra stars to the captured headline, to make it a sibling of inbox.
(global-set-key (kbd "C-c r") 'org-capture)
Org mode can create links pointing to several different locations. I won’t go into all the details of the linking syntax, and you won’t need to know it all because invoking org-capture, preferably through a key binding, will create an org headline with a link created by org-store-link for you automatically, and place it in your inbox.
You can also invoke org-store-link manually then use org-insert-link to insert the link into an org file. Mostly, links are just a file name and some text between brackets. Org uses the text line under the cursor for searching inside the file, much like you would do with grep, except that when you follow the link, you get a buffer with the file opened in it and the cursor placed over the line where the first search matched.
(global-set-key "\C-cl" 'org-store-link)
I maintain a global inbox for everything, during the processing phase, I either move the headlines into their definitive locations in my org files or I send it to a specific project’s inbox if I know it belongs to another project and I’m still skeptic about what to with it. This also lets me account the time I spend processing that specific project’s tasks under the right heading.
You can also teach org how to open your links, my setup uses all the defaults, except for the gnus setting, which I set to gnus-no-server, so that following links uses the local message cache and doesn’t connect to the imap server, which takes forever.
(setq org-link-frame-setup
'((vm . vm-visit-folder-other-window)
(gnus . gnus-no-server)
(file . find-file-other-window)
(wl . wl)))
Links to mail messages have special syntax and logic that allows org to invoke Gnus and open the equivalent emails correctly. As usual, capturing inside a mail buffer will do the right thing.
IRC
(setq org-irc-link-to-logs t)
By default, if you capture in an erc buffer, it’ll create a link to the channel you captured in. This setting changes this behaviour to saving a log file and link to that instead.The text under the cursor when you captured will be used for searching, when you access the link later on. This is very useful because I use IRC for everything, I even do IM in it via bitlbee, so most of the meetings and conversations happen in an erc buffer throughout the day. With org-capture, I can quickly annotate the meeting while it happens, and/or review it later. This is a lot better than taking several separate notes because you get the full transcript of the chat to give you context. It’s also very useful for when you say “I’ll do X tomorrow” to someone in a quick chat. These types of promises made inside IM windows tend to be forgotten more often than not, I got into the habit of only confirming if I’m going to do something after I’ve captured a reminder to do it. This sole feature has justified moving to ERC, which ships with emacs.
Live/Phone Meetings
For capturing annotations during meetings outside of IRC, I use org-timer. Start it with C-c C-x 0, move your cursor into the heading where you want to annotate and use C-c C-x – to start a list, pressing M-RET (alt + return) will insert additional items into the list and record the times on each new item. And you get a list like this one:
- 0:00:01 :: started meeting - 0:01:02 :: John reveals his new product proposal: the frobnicator - 0:01:35 :: frobnicators are supposed to be good organizational devices - 0:01:52 :: it's a variation on a taser - 0:02:08 :: electrocutes you whenever you slip a deadline
GTD with Org Mode, Part I
May 16, 2011 § Leave a Comment
Org Mode is, as it’s name suggests, a text-editing mode for emacs that helps you deal with organizational tasks for virtually anything. The main philosophy is to use plain-text to keep the data portable, this allows you to edit your org files anywhere.
Introduction And Overview
This post describes my personal org mode configuration and the reasoning behind each choice. I won’t be explaining any of the basic org-mode syntax, the org manual already has an excelent tutorial on that. If you aren’t familiar with the basic syntax of org-mode I suggest you at least read the “Document Structure” section of the org mode manual before proceeding.
How to read this article
Emacs configuration files are prone to be enormous, and subject to a lot of change over time, so I decided to keep this as a permanent page on the blog so anyone can check the current state of my entire configuration and I’ll post a new entry every time something changes. If you’re looking for a step-to-step guide of how everything evolved into this page, look at the posts under the orgmode tag of the blog, ordered chronologically.
Installing
Org Mode has a very active contributor community, so development happens very fast and new features are being added every day, so I keep a close eye on the repository and mailing list. Generally, I hold off on implementing fancy things on my own until I get feedback from the ML, because odds are someone somewhere is trying to do something similar. My install is based on the bleeding edge version from git, which is very easy to set up:
git clone git://orgmode.org/org-mode.git
Org Babel
Babel is a feature that has to be mentioned before we start the actual tour of org mode, because it’s the feature that actually bootstraps the setup. Through this feature, you can evaluate code from several languages by using the following syntax:
#+begin_src emacs-lisp :results output (message "%s" "hello world") #+end_src : hello world
Results can be produced in several different ways, depending on what language you’re evaluating.
I use org babel to keep my emacs file organized in several code blocks with structured comments to go along with them. This makes it easier to keep track of the rationale. In fact, this post is actually just a formatted export of my emacs startup org file, created by org-mode itself. You can download the raw org-mode file that originated this post from https://github.com/edenc/dotemacs/blob/master/emacs.org.
To use an org file as your emacs startup file, you need the following elisp snippet in your ~/.emacs file, to bootstrap the load process:
(setq dotfiles-dir
(file-name-directory
(or (buffer-file-name) load-file-name)))
(let* ((org-dir (expand-file-name
"lisp" (expand-file-name
"org" (expand-file-name
"src" dotfiles-dir))))
(org-contrib-dir (expand-file-name
"lisp" (expand-file-name
"contrib" (expand-file-name
".." org-dir))))
(load-path (append (list org-dir org-contrib-dir)
(or load-path nil))))
(require 'org-install)
(require 'ob-tangle))
(mapc #'org-babel-load-file
(directory-files
(expand-file-name "~/etc/emacs")
t "\\.org$"))
The above snippet loads the babel source blocks of all the .org files in the ~/etc/emacs directory, you should edit that to your preference. After setting up the bootstrap code in .emacs, you can mimic my setup with:
git clone git@github.com:edenc/dotemacs.git ~/etc/emacs
Activating
Org mode’s architecture is modular, so you get to pick what features to use by loading the equivalent modules.
These are the modules I use:
(setq org-modules
'(org-bbdb
org-gnus
org-info
org-jsinfo
org-irc
org-w3m
org-id
org-habit))
(require 'org-install)
To enable org mode automatically for .org and .orgarchive files:
(add-to-list 'auto-mode-alist
'("\\.\\(org\\|org_archive\\)$" . org-mode))
Hello World
May 11, 2011 § Leave a Comment
Hi, I’m back to blogging, check back in a few days and I’ll have cool new exciting non-buzzy non-trendy things here for you to read.