#+TITLE: Org Edna
#+AUTHOR: Ian Dunn
#+EMAIL: dunni@gnu.org
#+DATE: {{{modification-time}}}

#+STARTUP: overview
#+STARTUP: indent
#+TODO: FIXME | FIXED
#+OPTIONS: toc:2 num:nil timestamp:nil \n:nil |:t ':t email:t
#+OPTIONS: *:t <:t d:nil todo:nil pri:nil tags:not-in-toc

#+TEXINFO_DIR_CATEGORY: Emacs
#+TEXINFO_DIR_TITLE: Org Edna: (edna)
#+TEXINFO_DIR_DESC: Extensible Dependencies 'N' Actions for Org Mode tasks

* Copying
Copyright (C) 2017 Ian Dunn

#+BEGIN_QUOTE
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
#+END_QUOTE
* Introduction
:PROPERTIES:
:CUSTOM_ID: introduction
:DESCRIPTION: A Brief Introduction to Edna
:END:

Extensible Dependencies 'N' Actions (EDNA) for Org Mode tasks

Edna provides an extensible means of specifying conditions which must be
fulfilled before a task can be completed and actions to take once it is.

Org Edna runs when either the BLOCKER or TRIGGER properties are set on a
headline, and when it is changing from a TODO state to a DONE state.

For brevity, we use TODO state to indicate any state in ~org-not-done-keywords~,
and DONE state to indicate any state in ~org-done-keywords~.

** Installation and Setup
:PROPERTIES:
:DESCRIPTION: How to install Edna
:END:

*Requirements*

| Emacs |  25.1 |
| seq   |  2.19 |
| org   | 9.0.5 |

Right now, the only way to install Edna is by cloning the bazaar repo:

#+BEGIN_SRC shell
bzr branch https://bzr.savannah.gnu.org/r/org-edna-el/ org-edna
make -C org-edna compile autoloads
#+END_SRC

After that, add the following to your init file (typically .emacs):

#+BEGIN_SRC emacs-lisp
(add-to-list 'load-path "/full/path/to/org-edna/")
(load "/path/to/org-edna/org-edna-autoloads.el")
(org-edna-load)
#+END_SRC

If you ever want to disable Edna, run ~org-edna-unload~.

** Basic Operation
:PROPERTIES:
:CUSTOM_ID: operation
:DESCRIPTION: How to use Edna
:END:

Let's start with an example: Say you want to do laundry, but once you've put
your clothes in the washer, you forget about it.  Even with a tool like
org-notify or appt, Org won't know when to remind you.  If you've got them
scheduled for an hour after the other, maybe you forgot one time, or ran a
little late.  Now Org will remind you too early.

Edna can handle this for you like so:

#+BEGIN_SRC org
,* TODO Put clothes in washer
  SCHEDULED: <2017-04-08 Sat 09:00>
  :PROPERTIES:
  :TRIGGER: next-sibling scheduled!("++1h")
  :END:
,* TODO Put clothes in dryer
  :PROPERTIES:
  :TRIGGER: next-sibling scheduled!("++1h")
  :BLOCKER:  previous-sibling
  :END:
,* TODO Fold laundry
  :PROPERTIES:
  :TRIGGER: next-sibling scheduled!("++1h")
  :BLOCKER:  previous-sibling
  :END:
,* TODO Put clothes away
  :PROPERTIES:
  :TRIGGER: next-sibling scheduled!("++1h")
  :BLOCKER:  previous-sibling
  :END:
#+END_SRC

After you've put your clothes in the washer and mark the task DONE, Edna will
schedule the following task for one hour after you set the first headline as
done.

Another example might be a checklist that you've done so many times that you do
part of it on autopilot:

#+BEGIN_SRC org
,* TODO Address all TODOs in code
,* TODO Commit Code to Repository
#+END_SRC

The last thing anyone wants is to find out that some part of the code on which
they've been working for days has a surprise waiting for them.  Once again, Edna
can help:

#+BEGIN_SRC org
,* TODO Address all TODOs in code
  :PROPERTIES:
  :BLOCKER: file("main.cpp") file("code.cpp") re-search?("TODO")
  :END:
,* TODO Commit Code to Repository
#+END_SRC

** Blockers
:PROPERTIES:
:CUSTOM_ID: blockers
:DESCRIPTION: Blocking a TODO Item
:END:

A blocker indicates conditions which must be met in order for a headline to be
marked as DONE.  Typically, this will be a list of headlines that must be marked
as DONE.

** Triggers
:PROPERTIES:
:CUSTOM_ID: triggers
:DESCRIPTION: Triggering actions after completing a task
:END:

A trigger is an action to take when a headline is set to done.  For example,
scheduling another task, marking another task as TODO, or renaming a file.

** Syntax
:PROPERTIES:
:CUSTOM_ID: syntax
:DESCRIPTION: Basic explanation of Edna's syntax
:END:
#+cindex: syntax

Edna has its own language for commands, the basic form of which is KEYWORD(ARG1 ARG2 ...)

KEYWORD can be any valid lisp symbol, such as key-word, KEY_WORD!, or keyword?.

Each argument can be one of the following:

- A symbol, such as arg or org-mode
- A quoted string, such as "hello" or "My name is Edna"
- A number, such as 0.5, +1e3, or -5
- A UUID, such as c5e30c76-879a-494d-9281-3a4b559c1a3c

Each argument takes specific datatypes as input, so be sure to read the entry
before using it.

The parentheses can be omitted for commands with no arguments.
* Basic Features
:PROPERTIES:
:CUSTOM_ID: basic
:DESCRIPTION: Finders and Actions
:END:

The most basic features of Edna are *finders* and *actions*.

** Finders
:PROPERTIES:
:DESCRIPTION: How to find targets
:CUSTOM_ID: finders
:END:
A finder specifies locations from which to test conditions or perform actions.
These locations are referred to as "targets".

More than one finder may be used.  In this case, the targets are merged
together, removing any duplicates.

*** ancestors
:PROPERTIES:
:DESCRIPTION: Find a list of ancestors
:CUSTOM_ID: ancestors
:END:

Syntax: ancestors

The ~ancestors~ finder returns a list of the current headline's ancestors.

For example:

#+BEGIN_SRC org
,* TODO Heading 1
,** TODO Heading 2
,** TODO Heading 3
,*** TODO Heading 4
,**** TODO Heading 5
     :PROPERTIES:
     :BLOCKER:  ancestors
     :END:
#+END_SRC

In the above example, "Heading 5" will be blocked until "Heading 1", "Heading
3", and "Heading 4" are marked "DONE", while "Heading 2" is ignored.

*** chain-find
:PROPERTIES:
:CUSTOM_ID: chain-find
:DESCRIPTION: Find a target as org-depend does
:END:

Syntax: chain-find(OPTION OPTION...)

Identical to the chain argument in org-depend, chain-find selects its single
target using the following method:

1. Creates a list of possible targets
2. Filters the targets from Step 1
3. Sorts the targets from Step 2

After this is finished, chain-find selects the first target in the list and
returns it.

One option from each of the following three categories may be used; if more than
one is specified, the last will be used.

*Selection*

- from-top:     Select siblings of the current headline, starting at the top
- from-bottom:  As above, but from the bottom
- from-current: Selects siblings, starting from the headline (wraps)
- no-wrap:      As above, but without wrapping

*Filtering*

- todo-only:          Select only targets with TODO state set that isn't a DONE keyword
- todo-and-done-only: Select all targets with a TODO state set

*Sorting*

- priority-up:   Sort by priority, highest first
- priority-down: Same, but lowest first
- effort-up:     Sort by effort, highest first
- effort-down:   Sort by effort, lowest first

*** children
:PROPERTIES:
:CUSTOM_ID: children
:DESCRIPTION: Find all immediate children
:END:

Syntax: children

The ~children~ finder returns a list of the *immediate* children of the current
headline.

In order to get all levels of children of the current headline, use the
[[#descendants][descendants]] keyword instead.

*** descendants
:PROPERTIES:
:CUSTOM_ID: descendants
:DESCRIPTION: Find all descendants
:END:

Syntax: descendants

The ~descendants~ finder returns a list of all descendants of the current
headline.

#+BEGIN_SRC org
,* TODO Heading 1
   :PROPERTIES:
   :BLOCKER:  descendants
   :END:
,** TODO Heading 2
,*** TODO Heading 3
,**** TODO Heading 4
,***** TODO Heading 5
#+END_SRC

In the above example, "Heading 1" will block until Headings 2, 3, 4, and 5 are
DONE.

*** file
:PROPERTIES:
:CUSTOM_ID: file
:DESCRIPTION: Find a file by name
:END:

- Syntax: file("FILE")

The ~file~ finder finds a single file, specified as a string.  The returned target
will be the minimum point in the file.

Note that with the default condition, ~file~ won't work.  See [[#conditions][conditions]] for how
to set a different condition.  For example:

#+BEGIN_SRC org
,* TODO Test
  :PROPERTIES:
  :BLOCKER:  file("~/myfile.org") headings?
  :END:
#+END_SRC

Here, "Test" will block until myfile.org is clear of headlines.

*** first-child
:PROPERTIES:
:CUSTOM_ID: first-child
:DESCRIPTION: Find the first child of a headline
:END:

- Syntax: first-child

The ~first-child~ finder returns the first child of a headline, if any.

*** ids
:PROPERTIES:
:DESCRIPTION: Find a list of headlines with given IDs
:CUSTOM_ID: ids
:END:

- Syntax: id(ID1 ID2 ...)

The ~ids~ finder will search for headlines with given IDs, using ~org-id~.  Any
number of UUIDs may be specified.  For example:

#+BEGIN_SRC org
,* TODO Test
  :PROPERTIES:
  :BLOCKER:  ids(62209a9a-c63b-45ef-b8a8-12e47a9ceed9 6dbd7921-a25c-4e20-b035-365677e00f30)
  :END:
#+END_SRC

Here, "Test" will block until the headline with ID
62209a9a-c63b-45ef-b8a8-12e47a9ceed9 and the headline with ID
6dbd7921-a25c-4e20-b035-365677e00f30 are set to "DONE".

Note that UUIDs need not be quoted; Edna will handle that for you.

*** match
:PROPERTIES:
:CUSTOM_ID: match
:DESCRIPTION: Good old tag matching
:END:

- Syntax: match("MATCH-STRING" SCOPE SKIP)

The ~match~ keyword will take any arguments that ~org-map-entries~ usually takes.
In fact, the arguments to ~match~ are passed straight into ~org-map-entries~.

#+BEGIN_SRC org
,* TODO Test
  :PROPERTIES:
  :BLOCKER:  match("test&mine" agenda)
  :END:
#+END_SRC

"Test" will block until all entries tagged "test" and "mine" in the agenda files
are marked DONE.

See the documentation for ~org-map-entries~ for a full explanation of the first
argument.

*** next-sibling
:PROPERTIES:
:CUSTOM_ID: next-sibling
:DESCRIPTION: Find the next sibling
:END:

- Syntax: next-sibling

The ~next-sibling~ keyword returns the next sibling of the current heading, if
any.

*** olp
:PROPERTIES:
:CUSTOM_ID: olp
:DESCRIPTION: Find a headline by its outline path
:END:

- Syntax: olp("FILE" "OLP")

Finds the heading given by OLP in FILE.  Both arguments are strings.

#+BEGIN_SRC org
,* TODO Test
  :PROPERTIES:
  :BLOCKER:  olp("test.org" "path/to/heading")
  :END:
#+END_SRC

"Test" will block if the heading "path/to/heading" in "test.org" is not DONE.

*** org-file
:PROPERTIES:
:CUSTOM_ID: org-file
:DESCRIPTION: Find a file in org-directory
:END:

- Syntax: org-file("FILE")

A special form of ~file~, ~org-file~ will find FILE in ~org-directory~.

#+BEGIN_SRC org
,* TODO Test
  :PROPERTIES:
  :BLOCKER:  org-file("test.org")
  :END:
#+END_SRC

Note that the file still requires an extension.

*** parent
:PROPERTIES:
:CUSTOM_ID: parent
:DESCRIPTION: Find a parent
:END:

- Syntax: parent

Returns the parent of the current headline, if any.

*** previous-sibling
:PROPERTIES:
:CUSTOM_ID: previous-sibling
:DESCRIPTION: Find the previous sibling
:END:

- Syntax: previous-sibling

Returns the previous sibling of the current headline on the same level.

*** rest-of-siblings
:PROPERTIES:
:CUSTOM_ID: rest-of-siblings
:DESCRIPTION: Find the remaining siblings
:END:

- Syntax: rest-of-siblings

Starting from the headline following the current one, all same-level siblings
are returned.

*** self
:PROPERTIES:
:CUSTOM_ID: self
:END:

- Syntax: self

Returns the current headline.

*** siblings
:PROPERTIES:
:CUSTOM_ID: siblings
:DESCRIPTION: Find all the siblings
:END:

- Syntax: siblings

Returns all siblings of the source heading as targets.

*** siblings-wrap
:PROPERTIES:
:CUSTOM_ID: siblings-wrap
:DESCRIPTION: Find the siblings, but wrap around
:END:

- Syntax: siblings-wrap

Finds the siblings on the same level as the current headline, wrapping when it
reaches the end.

** Actions
:PROPERTIES:
:DESCRIPTION: Next steps
:END:
Once Edna has collected its targets for a trigger, it will perform actions on
them.

Actions must always end with '!'.

*** Scheduled/Deadline
:PROPERTIES:
:CUSTOM_ID: planning
:END:

- Syntax: scheduled!(OPTIONS)
- Syntax: deadline!(OPTIONS)

There are several forms that the planning keywords can take:

- PLANNING("WKDY[ TIME]")

  Sets PLANNING to the following weekday WKDY at TIME.  If TIME is not
  specified, only a date will be added to the target.

  WKDY is a weekday or weekday abbreviation (see ~org-read-date~)

  TIME is a time string HH:MM, etc.

- PLANNING(rm|remove)

  Remove PLANNING from all targets.  The argument to this form may be either a
  string or a symbol.

- PLANNING(copy|cp)

  Copy PLANNING info verbatim from the current headline to all targets.  The
  argument to this form may be either a string or a symbol.

- PLANNING("[+|-][+|-]NTHING")

  Increment(+) or decrement(-) source (double) or current (single) PLANNING by N
  THINGs

  N is an integer

  THING is one of y (years), m (months), d (days), h (hours), or M (minutes)

Examples:

scheduled!("Mon 09:00") -> Set SCHEDULED to the following Monday at 9:00
*** TODO State

- Syntax: todo!(NEW-STATE)

Sets the TODO state of the target headline to NEW-STATE.

NEW-STATE may either be a string or a symbol denoting the new TODO state.

*** Archive
:PROPERTIES:
:CUSTOM_ID: archive!
:END:

- Syntax: archive!

Archives all targets with confirmation.

Confirmation is controlled with ~org-edna-prompt-for-archive~.  If this option is
nil, Edna will not ask before archiving the target.

*** Chain Property

- Syntax: chain!("PROPERTY")

Copies PROPERTY from the source entry to all targets.

*** Clocking

- Syntax: clock-in!
- Syntax: clock-out!

Clocks into or out of all targets.

~clock-in!~ has no special handling of targets, so be careful when specifying
multiple targets.

*** Property

- Syntax: set-property!("PROPERTY","VALUE")

Sets the property PROPERTY on all targets to VALUE.

- Syntax: delete-property!("PROPERTY")

Deletes the property PROPERTY from all targets.

*** Priority

- Syntax: set-priority!(PRIORITY)

Sets the priority of all targets to PRIORITY.  PRIORITY is processed as follows:

- If PRIORITY is a string, the first character is used as the priority
- Any other value is passed into ~org-priority~ verbatim, so it can be 'up, 'down, or an integer

*** Tag

- Syntax: tag!("TAG-SPEC")

Tags all targets with TAG-SPEC, which is any valid tag specification,
e.g. tag1:tag2

*** Effort

- Syntax: set-effort!(VALUE)

Sets the effort of all targets according to VALUE:

- If VALUE is a string, then the effort is set to VALUE
- If VALUE is an integer, then set the value to the VALUE'th allowed effort property
- If VALUE is the symbol 'increment, increment effort

* Advanced Features
:PROPERTIES:
:CUSTOM_ID: advanced
:END:
** Conditions
:PROPERTIES:
:CUSTOM_ID: conditions
:END:

Edna gives you he option to specify *blocking conditions*.  Each condition is checked
for each of the specified targets; if one of the conditions returns true for
that target, then that headline is blocked.

*** done
:PROPERTIES:
:CUSTOM_ID: done
:END:

- Syntax: done?

Blocks the current headline if any target is DONE.

*** headings
:PROPERTIES:
:CUSTOM_ID: headings
:END:

- Syntax: headings?

Blocks the current headline if any target belongs to a file that has an Org heading.

#+BEGIN_EXAMPLE
org-file("refile.org") headings?
#+END_EXAMPLE

The above example blocks if refile.org has any headings.

*** todo-state
:PROPERTIES:
:CUSTOM_ID: todo-state
:END:

- Syntax: todo-state?(STATE)

Blocks if any target has a headline with TODO state set to STATE.

STATE may be a string or a symbol.

*** variable-set
:PROPERTIES:
:CUSTOM_ID: variable-set
:END:

- Syntax: variable-set?(VARIABLE,VALUE)

Blocks the current headline if VARIABLE is set to VALUE.

VARIABLE should be a symbol, and VALUE is any valid lisp expression

#+BEGIN_EXAMPLE
self variable-set?(test-variable,12)
#+END_EXAMPLE

*** has-property
:PROPERTIES:
:CUSTOM_ID: has-property
:END:

- Syntax: has-property?("PROPERTY","VALUE")

Tests each target for the property PROPERTY, and blocks if it's set to VALUE.

*** re-search
:PROPERTIES:
:CUSTOM_ID: re-search
:DESCRIPTION: Search for a regular expression
:END:

- Syntax: re-search?("REGEXP")

Blocks the current headline if the regular expression REGEXP is present in any
of the targets.

The targets are expected to be files, although this will work with other targets
as well.

*** Negating Conditions
:PROPERTIES:
:CUSTOM_ID: negate
:END:
Any condition can be negated using '!'.

#+BEGIN_EXAMPLE
match("test") !has-property?("PROP","1")
#+END_EXAMPLE

The above example will cause the current headline to block if any headline
tagged "test" does *not* have the property PROP set to "1".
** Consideration
Special keyword that's only valid for blockers.

This keyword can allow specifying only a portion of tasks to consider:

1. consider(PERCENT)
2. consider(NUMBER)
3. consider(all) (Default)

(1) tells the blocker to only consider some portion of the targets.  If at least
PERCENT of them are in a DONE state, allow the task to be set to DONE.  PERCENT
must be a decimal.

(2) tells the blocker to only consider NUMBER of the targets.

(3) tells the blocker to consider all following targets.

A consideration must be specified before the targets to which it applies:

#+BEGIN_EXAMPLE
consider(0.5) siblings consider(all) match("find_me")
#+END_EXAMPLE

The above code will allow task completion if at least half the siblings are
complete, and all tasks tagged "find_me" are complete.

#+BEGIN_SRC emacs-lisp
consider(1) ids(ID1 ID2 ID3) consider(2) ids(ID3 ID4 ID5 ID6)
#+END_SRC

The above code will allow task completion if at least one of ID1, ID2, and ID3
are complete, and at least two of ID3, ID4, ID5, and ID6 are complete.

If no consideration is given, ALL is assumed.
* Extending Edna
:PROPERTIES:
:DESCRIPTION: What else can it do?
:END:

Extending Edna is (relatively) simple.

During operation, Edna searches for functions of the form org-edna-TYPE/KEYWORD.

** Naming Conventions
:PROPERTIES:
:DESCRIPTION: How to name new functions
:END:

In order to distinguish between actions, finders, and conditions, we add '?' to
conditions and '!' to actions.  This is taken from the practice in Guile and
Scheme to suffix destructive functions with '!' and predicates with '?'.

Thus, one can have an action that files a target, and a finder that finds a
file.

** Finders
:PROPERTIES:
:DESCRIPTION: Making a new finder
:END:

Finders have the form org-edna-finder/KEYWORD, like so:

#+BEGIN_SRC emacs-lisp
(defun org-edna-finder/test-finder ()
  (list (point-marker)))
#+END_SRC

All finders must return a list of markers, one for each target found, or nil if
no targets were found.

** Actions
:PROPERTIES:
:DESCRIPTION: Making a new action
:END:

Actions have the form org-edna-action/KEYWORD!:

#+BEGIN_SRC emacs-lisp
(defun org-edna-action/test-action! (last-entry arg1 arg2)
  )
#+END_SRC

Each action has at least one argument: ~last-entry~.  This is a marker for the
current entry (not to be confused with the current target).

The rest of the arguments are the arguments specified in the form.

** Conditions
:PROPERTIES:
:DESCRIPTION: Making a new condition
:END:

#+BEGIN_SRC emacs-lisp
(defun org-edna-condition/test-cond? (neg))
#+END_SRC

All conditions have at least one argument, "NEG".  If NEG is non-nil, the
condition should be negated.

Most conditions have the following form:

#+BEGIN_SRC emacs-lisp
(defun org-edna-condition/test-condition? (neg)
  (let ((condition (my-test-for-condition)))
    (when (org-xor condition neg)
      (string-for-blocking-entry-here))))
#+END_SRC

For conditions, we return true if condition is true and neg is false, or if
condition is false and neg is true:

| cond | neg | res |
|------+-----+-----|
| t    | t   | f   |
| t    | f   | t   |
| f    | t   | t   |
| f    | f   | f   |

This is an XOR table, so we pass CONDITION and NEG into ~org-xor~ to get our
result.

A condition must return a string if the current entry should be blocked.
* Contributing
:PROPERTIES:
:DESCRIPTION: I wanna help!
:END:

We are all happy for any help you may provide.

First, check out the source code on Savannah: https://savannah.nongnu.org/projects/org-edna-el/

#+BEGIN_SRC shell
bzr branch https://bzr.savannah.gnu.org/r/org-edna-el/ org-edna
#+END_SRC

You'll also want a copy of the most recent Org Mode source:

#+BEGIN_SRC shell
git clone git://orgmode.org/org-mode.git
#+END_SRC

*Bugs*

There are two ways to submit bug reports:

1. Using the bug tracker at Savannah
2. Sending an email using ~org-edna-submit-bug-report~

When submitting a bug report, be sure to include the Edna form that caused the
bug, with as much context as possible.

*Development*

Submitting patches along with a bug report is the easiest way.

There are a few rules to follow:

- Verify that any new Edna keywords follow the appropriate naming conventions
- Any new keywords should be documented
- Run 'make check' to verify that your mods don't break anything
- Avoid additional or altered dependencies if at all possible
  - Exception: New versions of Org mode are allowed
