Skip to content
Brett Terpstra edited this page Oct 21, 2021 · 19 revisions

A plugin is a ruby file located in a plugins directory. The directory is ~/.config/doing/plugins by default, but you can add a plugins_path key to ~/.doingrc to point doing to wherever you want to keep your plugins.

Include some meta at the top of the plugin. It's optional, but helpful.

# frozen_string_literal: true
#
# title: Export plugin example
# description: Speak the most recent entry (macOS)
# author: Brett Terpstra
# url: https://brettterpstra.com

As well as any info a user would need to use/configure it

# Example
#
# doing show -o sayit
#
# ## Configuration
#
# Change what the plugin says by generating a template with
# `doing template --type say`, saving it to a file, and
# putting the path to that file in `export_templates->say` in
# .doingrc.
#
# export_templates:
#   say: /path/to/template.txt
#
# Use a different voice by adding a `say_voice` key to your
# .doingrc. Use `say -v ?` to see available voices.
#
# say_voice: Zarvox

Use the example plugin as a skeleton

module Doing
  ##
  ## @brief      Plugin class
  ##
  class SayExport
    include Doing::Util

    def self.settings
    end

    def self.template(trigger) # Optional
    end

    def self.render(wwid, items, variables: {})
    end

    Doing::Plugins.register 'say', :export, self
  end
end

Add settings

#-------------------------------------------------------
## Plugin Settings. A plugin must have a self.settings
## method that returns a hash with plugin settings.
##
## trigger:   (required) Regular expression to match
## FORMAT when used with `--output FORMAT`. Registered
## name of plugin must be able to match the trigger, but
## alternatives can be included
##
## templates: (optional) Array of templates this plugin
## can export (plugin must have :template method)
##
##   Each template is a hash containing:
##               - name: display name for template
##               - trigger: regular expression for
##                 `template --type FORMAT`
##
##   If a template is included, a config key will
##   automatically be added for the user to override
##   The config key will be available at:
##
##       wwid.config['export_templates'][PLUGIN_NAME]
##
## config:    (optional) A Hash which will be
## added to the main configuration in the plugins section.
## Options defined here are included when config file is
## created or updated with `config --update`. Use this to
## add new configuration keys, not to override existing
## ones.
##
##   The configuration keys will be available at:
##
##      wwid.config['plugins'][PLUGIN_NAME][KEY]
##
## @brief      Method to return plugin settings (required)
##
## @return     Hash of settings for this plugin
##
def self.settings
  {
    trigger: 'say(?:it)?',
    templates: [
      { name: 'say', trigger: 'say(?:it)?' }
    ],
    config: {
      'say_voice' => 'Fiona'
    }
  }
end

If your plugin allows a user-configured template, include a template method

#-------------------------------------------------------
## Output a template. Only required if template(s) are
## included in settings. The method should return a
## string (not output it to the STDOUT).
##
## @brief      Method to return template (optional)
##
## @param      trigger  The trigger passed to the
##                      template function. When this
##                      method defines multiple
##                      templates, the trigger can be
##                      used to determine which one is
##                      output.
##
## @return     (String) template contents
##
def self.template(trigger)
  return unless trigger =~ /^say(it)?$/

  'On %date, you were %title, recorded in section %section%took'
end

Lastly, provide a render method that accepts a WWID object, an array of items, and additional options

##
## @brief      Render data received from an output
##             command
##
## @param      wwid       The wwid object with config
##                        and public methods
## @param      items      An array of items to be output
##                        { <Date>date, <String>title,
##                        <String>section, <Array>note }
## @param      variables  Additional variables including
##                        flags passed to command
##                        (variables[:options])
##
## @return     (String) Rendered output
##
def self.render(wwid, items, variables: {})
  return if items.nil? || items.empty?

  # the :options key includes the flags passed to the
  # command that called the plugin use `puts
  # variables.inspect` to see properties and methods
  # when run
  opt = variables[:options]

  # This plugin just grabs the last item in the `items`
  # list (which could be the oldest or newest, depending
  # on the sort order of the command that called the
  # plugin). Most of the time you'll want to use :each
  # or :map to generate output.
  i = items[-1]

  # Format the item. Items are a hash with 3 keys: date,
  # title, and section (parent section) Start time is in
  # item.date. The wwid object has some methods for
  # calculation and formatting, including
  # wwid.item.end_date to convert the @done
  # timestamp to an end date.
  interval = wwid.get_interval(i, formatted: true) if wwid.i.end_date && opt[:times]
  if interval
    d, h, m = interval.split(/:/)
    took = ' and it took'
    took += " #{d.to_i} days" if d.to_i.positive?
    took += " #{h.to_i} hours" if h.to_i.positive?
    took += " #{m.to_i} minutes" if m.to_i.positive?
  end
  date = i.date.strftime('%A %B %e at %I:%M%p')
  title = i.title.gsub(/@/, 'hashtag ')
  tpl = template('say')
  if wwid.config['export_templates'].key?('say')
    cfg_tpl = wwid.config['export_templates']['say']
    tpl = cfg_tpl unless cfg_tpl.nil? || cfg_tpl.empty?
  end
  output = tpl.dup
  output.gsub!(/%date/, date)
  output.gsub!(/%title/, title)
  output.gsub!(/%section/, i.section)
  output.gsub!(/%took/, took || '')

  # Debugging output
  # warn "Saying: #{output}"

  # To provide results on the command line after the
  # command runs, add to the wwid.results array. Results
  # are provided on STDERR unless doing is run with
  # `--stdout`
  wwid.results.push([['Spoke the last entry. Did you hear it?', 0], 0])

  # This export runs a command for fun, most plugins won't
  `say -v #{wwid.config['say_voice']} "#{output}"`

  # Return the result (don't output to terminal with puts or print)
  output
end

Now you're ready to register the plugin.

# Register the plugin with doing.
# Doing::Plugins.register 'NAME', TYPE, Class
#
# Name should be lowercase, no spaces
#
# TYPE is :import or :export
#
# Class is the plugin class (e.g. Doing::SayExport), or
# self if called within the class
Doing::Plugins.register 'say', :export, self

Place the plugin in the plugins directory (defined in ~/.doingrc under 'plugin_path', default ~/.config/doing/plugins). Now you can test it. If it's an export plugin, you should be able to use the name/trigger you registerd as an argument to doing show -o NAME.

To enable verbose output and traces for debugging, run GLI_DEBUG=true doing show -o NAME.

Clone this wiki locally