Skip to content
Brett Terpstra edited this page Jan 12, 2022 · 12 revisions

Hooks allow you to register commands and script to run when doing performs certain actions. You can register the following events:

  • post_config -- Runs after the main configuration is read, before any local .doingrc configurations
  • post_local_config -- Runs after local configurations are read and merged into the configuration object
  • post_read -- Runs after the contents of the doing file are parsed
  • post_write -- Runs after the contents of the doing file are changed. Does not run unless a modification is made

To register a hook, simply place a Ruby file in your plugins folder. This folder is located at ~/.config/doing/plugins by default, but you can specify another location in your config using the plugin_path key.

The ruby file should contain a call to register the hook. Pass a block to perform when the associated event is triggered.

The register method takes an optional priority: argument containing an integer. If multiple hooks are registered, they will be executed in order of priority, lowest to highest.

Doing::Hooks.register :post_read, priority: 20 do { |wwid| ... }

The following runs a shell script called after_doing.sh whenever a change is made to the doing file (post_write event):

# frozen_string_literal: true

Doing::Hooks.register :post_write do |filename|
  res = `/bin/bash ~/scripts/after_doing.sh`.strip
  Doing.logger.debug('Hooks:', res)
end

That's all there is to it. As an example, I use the above shell script to update my iTerm status bar and my Touch Bar (BetterTouchTool widget) with my current task whenever the file changes. Any time I add or complete an entry, my status bars update.

:post_config

The post_config hook receives the Doing::WWID object. This contains the @config hash and has access to all of the WWID methods. Changes made to the object within the block will carry through to the current doing operation.

If you modify the configuration variable, the changes will be incorporated into the current operation but will not be saved. If you want to save the modified configuration, use wwid.write_config to save it to a file. This will overwrite your current configuration with whatever you've done to the wwid.configuration object, so be careful. You can provide a filename argument to wwid.write to write to a different file than the primary config, e.g. (wwid.write_config(File.expand_path('~/configuration.yml'))).

Doing::Hooks.register :post_config do |wwid|
  wwid.config['new config key'] = 'My new value'
end

:post_local_config

The post_local_config hook runs after local .doingrc files are merged into the configuration. Keys from these files are not written out to any configurations by default, so performing any destructive file write operations in this hook is discouraged.

Doing::Hooks.register :post_config do |wwid|
  wwid.config['new config key'] = 'My new value'
end

:post_read

The post_read hook receives the current Doing::WWID object. This includes the @content object. wwid.content contains string keys for each section (named for the section title), and the values include an :items key with an array of all of the items in that section. Each item has values for date(Date), title(String), section(String), and note(Note < Array).

The items found in wwid.content have a variety of methods for testing whether they match search strings or tag filters, as well as for adding tags and modifying them. Modifications made to items by your hook will be passed on and saved when the file writes out at the end of a command. Not all commands write to disk, though; commands like show and view never save changes.

Doing::Hooks.register :post_read do |wwid|
  Doing.logger.info('Hook:', wwid.content.keys)
end

:pre_entry_add

Called before every item is added, and receives the WWID object and the new entry, which can be modified before it's added.

Doing::Hooks.register :post_entry_add do |wwid, entry|
  if entry.tags?('hooked')
    entry.tag('addhook')
  end
end

:post_entry_added

Called after adding a new entry, receives the WWID object and a copy of the added entry.

:post_entry_updated

Called after an existing entry is modified, receives the WWID object and the modified entry (which can be modified)

:post_entry_removed

Called after removing an entry, receives the WWID object and a copy of the removed entry

:pre_export

Called before any output operation, receives the WWID object, the output format, and the array of entries (which can be modified, but modifications will only affect the output, not the doing file content)

:pre_write

The pre_write hook receives the WWID object and the filename that is going to be written. Modifications made to the WWID.content object at this point will be written out to filename.

Doing::Hooks.register :pre_write do |wwid, filename|
  return unless filename == wwid.config['doing_file']
  
  wwid.content.each do |section, content|
    content[:items].each do |i|
      # If any item has @test AND @todo tags, add @hooktest
      if i.tags?(['test', 'todo'], :and)
        i.tag('hooktest')
      end
    end
  end
end

:post_write

The post_write hook receives the filename that was saved to.

If you want to see what changed, you can use WWID.new.get_diff(filename). Note that this does a line-by-line diff between the current Doing file and the most recent backup, so it can be slow and will introduce a couple of seconds wait after any command that updates the doing file. :get_diff returns an Items object (which is an array of Items, each with date, title, note, and section, plus its own ).

Doing::Hooks.register :post_write do |filename|
  `~/scripts/btt_stats.rb refresh doing`
  # This is what I use to update things like my
  # BetterTouchTool widgets and my iTerm status bar.
end