Go to the first, previous, next, last section, table of contents.


Action Templates

Action Templates are declarative data constructs that reduce the amount of work to create new Agroups actions. They are considered the extensibility feature of Agroups. All Agroups predefined and user defined actions are implemented with Action Templates. Hopefully the real power of action templates is realized in user defined templates. This chapter describes how a user can create new action templates and hence add to the user collection of general actions.

To create new action templates you should know something about Elisp. It is possible to know a little about Elisp and learn as you go. It will be helpful if you actually try the examples in this chapter to see the effect and get some facility with both the form of templates and some Elisp.

In some of the sections that follow, for precision we use dot notation to describe various forms. So as not to cause confusion for the new Lisp user we just remind that the dotted list (x . (a b c)) is equivalent to the list (x a b c).

Simple Action Template Example

In this section we show an extremely simple and probably useless action template just to spark interest and get us started. This will be an action template that has one slot: some text to display as a message in the minibuffer. Here is this simple template that creates a new action called simple-message

(agroups-add-action-template
 '((action simple-message)
   (id "Simple Message")
   (keys "sm")
   (afun (message text))
   (slots (text "Text of message"))))

The function agroups-add-action-template is used to create new actions and is given a single argument being a template. The template is a declarative data structure represented as a list of properties. In this simple example the properties are: action, id, key, afun and slots. We will go through these one by one to see what they mean in the next section. But first to get a more overall picture of what this does, evaluate the above agroups-add-action-template. After evaluating this template see if it has been added by entering

KA KO v A

and note that is a capital A. You should be able to see this registered in the user action collection in the Agroups buffer

User defined actions
  Simple Message (keys: s m)

At this point for all practical purposes this now looks like any of the given actions in the Predefined Action collection. Furthermore we should now be able to create an entry of this action in the current group. So lets do this with

KA KO A s m

and again note that this is a capital A. When Agroups prompts for the "Text of message" slot enter

Text of message
    ---- Edit or enter below and type C-cC-c when done ----
Hello World

but don't put a RET after the text "Hello World". Then type C-cC-c to enter the slot value and give it whatever id and keys you want. Now execute the action and you should see

Hello World

in the minibuffer. You are now a creator of new Agroups actions!

Simple Action Template Properties

As promised in the previous section Simple Action Template Example we will now go through each of the properties in the template we created. Just to recall our simple action template

(agroups-add-action-template
 '((action simple-message)
   (id "Simple Message")
   (keys "sm")
   (afun (message text))
   (slots (text "Text of message"))))

Actually the only property that is required is action and all others are optional and serve to make more and more complex actions. For example you could create an action with nothing more than

(agroups-add-action-template
 '((action not-much)))

Of course if you have the time to experiment you will find that the action is well named, ie. what it does is not-much!

The action property makes the action distinct from any other and is needed behind the scenes to indicate an action of this type. This action symbol will appear in the Agroups save file when the simple-message action entry was created. If you were to look you would see that it appears as u-simple-message instead of simple-message. Agroups given actions are guaranteed to never begin with "u-" which stands for User defined. Agroups adds this prefix to every user defined template action and does the book keeping to maintain this so that the user never has to worry about creating an action that clashes with any predefined actions.

The id property is a short one line identifying description of the action. We say "short one line" since this will appear in the user defined actions list that you saw above. We say "identifying" because the id is the string that completion will work on if the user chooses to use completion instead of the accelerator keys you specify. And the keys property is the accelerator keys you specify.

The afun property is the action function that gets applied to the slot variables. So in the simple-message action that we defined we had

(afun (message text))

The value of the afun property is more or less what you would expect with an ordinary Elisp function call on variables. In this example the function message is applied to the variable text. We say "more or less what you would expect" because instead of just allowing variables as arguments you can have any kind of list structure or structures where the slot variables get substituted on entry creation. For example if the message function allowed a list of text strings instead of just a text string, we could have specified

(afun (message (text)))

Note that the list (text) is not quoted. No part of any nested list structures have to be quoted since nothing in such lists get evaluated. Just the variables are substituted for values.

The substitution of the variable values brings us to the slots property. The whole purpose of the slots property is to provide a list of variables that will be bound to the values determined by a specific entry instance of the action template. So our simple-message action template we have

(slots (text "Text of message"))

and this defined the variable text that on action entry creation gets a value that is bound to the variable text in the above afun message form. In our further example of a user creating a simple-message entry the text slot variable got bound to the string value "Hello World".

That completes the simple understanding of action templates. The rest of the capabilities of action templates are achieved through more of these action templates properties and slot properties. They expand on those additional properties. But first, in the next section we give a formal but brief description of the full syntax the top level action template form and in subsequent sections we give the full syntax and semantics of each template property form.

Form of Action Templates

We saw in the previous section that the function

agroups-add-action-template

takes an action template as an argument and adds that in the form of an action to the user defined collection of actions. This template argument itself is quoted so that the template can be passed as a variable. Even though we quote the top level template list structure, none of the nested lists or symbols need to be quoted. An exception would be if the template designer explicitly wants a quote for some reason.

The action template itself is simply a list of properties of the form

(<property 1> <property 2> ... <property n>)

Each property is a list of the form

(<property name>  <value 1> <value 2> ... <value n>)

where <property name> is a Template Property symbol listed below and the <value n> are the specific values that make this template distinct from other templates. When we use just the <property name> to refer to the property in this document it is meant to connote the entire property above with property name and values.

The only required property is the action property all others are optional and have defaults. The following table lists all template properties and their defaults on the right:

  Template Property             Default
  ---------------------------------------------------------
* action              No default, required
* id                  An id would be constructed from action
* keys                No keys, user would have to use completion
* afun                No afun, action entry will not execute
* slots               No data for template, all entries the same
  pfun                No preprocessing before template expansion
  exists              No existing condition check
  default-slot        Default slot when user hits RET is first slot

Although the only property required is the action property all good action templates should have the properties marked with * above. The syntax and semantics of each of these is described in the following sections.

action Template Property

The action template property is a list of the form

(action <action>)

Where <action> should be a symbol (or list see below) unique from other user action symbols unless the enclosing user template is intended to replace another existing template with the same action symbol. In the context of the action template we refer to the action symbol as simply "the action". The action is what distinguishes one action from another in the collection of all Agroups actions.

When the template is added to the system, Agroups changes this symbol to u-<action>. The reason for this is so that the user does to not have to worry about clashes with Agroups given actions. Agroups given action symbols are guaranteed not to begin with "u-".

It is also possible for <action> to be a list of action symbols and in this case all rules above apply to each symbol in this list. For example the Agroups given "File or dired" action is specified in its template as

(action (file dired))

When such an action template is added with a list of actions it must have a pfun property to set the action, See section pfun Template Property. Usually this is done by the pfun function based on the current environment and most likely the current buffer. For example, the Agroups given "File or dired" action sets the action of the user created entry based on whether the buffer is a dired or a buffer associated with a file. If no pfun property is specified or a pfun function does not set the action then Agroups will just assume the first symbol of the list is the action.

id Template Property

The id template property is a list of the form

(id <descriptive string>)

where <descriptive string> is a short string designed to fit on one line describing the action. There should be no line terminators in this string. If it is more than one line Agroups will just take the first line of the string without the terminators. It should be descriptive but relatively short since it appears in action lists and edit buffers. This is also the string that will be used by the Agroups user when using completion on an action id.

afun Template Property

The term "afun" means action function. The action function is the function that brings about the final result of executing an action entry. The afun template property has the form

(afun <function spec>)

Where <function spec> is what you would normally expect with a function call. So the form of <function spec> is

(<function> . <s-expression list>)

where <function> is any function symbol or lambda. Anywhere in the <s-expression list> a slots property slot symbol may appear which results in that symbol being substituted for the slot values when the action is expended into an entry. There is no evaluation either before or after the slot variable substitutions. Then only the substituted s-expressions in the <s-expression list> along with the action symbol are placed in the user's newly created entry instance. When that entry instance is executed the <function> is looked up based on the action in the entry instance and applied to the instance s-expressions.

The no evaluation before or after clause above indicates that nothing in the <s-expression list> needs to be quoted. However there are cases where the template designer might want to include quotes. An example is where the <function> is eval and the <s-expression list> is just a single s-expression containing a necessarily quoted entity.

To illustrate implications of all of the above we give two action templates that would have the same action entry execution effect but would create different entries in the Agroups save file. Then we make several specific points about these action templates that follow from the above general description.

The action template

(agroups-add-action-template
 '((action compose-message)
   (afun (message "%s %s" first second))
   (slots
    (first "First part of message")
    (second "Second part of message"))))

would have the same action entry execution effect as

(defun myfunction (first second) (message "%s %s" (car first) second))

(agroups-add-action-template
 '((action compose-message)
   (afun (myfunction (first) second))
   (slots 
    (first "First part of message")
    (second "Second part of message"))))

Lets say for the sake of illustration that the user creates two entries from these action templates and in both cases enters the date "Hello" for the first slot and "World" for the second slot. Then both of those entries when executed will result in the message "Hello World" being displayed in the minibuffer. It might help to actually try adding these two templates and then two corresponding action entries to see what gets generated in the action entries themselves that you can see in the save file with "KA KO v s". Now for the specific points:

[point 1: afun s-expressions can be any s-expression]

In the second version of the compose-message action the afun spec

(afun (myfunction (first) second))

is kind of silly because all that the myfunction does is take the car of the first argument which appears in the entry as the list ("Hello"). The following would make more sense in this case

(afun (myfunction1 first second))

but the more awkward myfunction version helps visualize the idea of substituting the slot variable first in the <s-expression list> form.

[point 2: The afun function is any function symbol or lambda]

The first version of the compose-message action used a standard existing Elisp function message and the second version uses a user defined function myfunction. Also note that we can use a lamda instead of myfunction

(agroups-add-action-template
 '((action compose-message)
   (afun ((lambda (first second) (message "%s %s" (car first) second))
           (first) second))
   (slots 
    (first "First part of message")
    (second "Second part of message"))))

[point 3: no evaluation either before or after the slot variable substitutions]

In the version

(afun (myfunction (first) second))

the resulting user entry appears as

(... compose-message ("Hello") "World")

which shows that neither (first) nor ("Hello") was evaluated, just the slot variable first was substituted with the value "Hello" and the variable second was substituted with the value "World".

[point 4: only the substituted s-expressions in the <s-expression list> along with the action symbol are placed in the user's newly created entry instance]

Generally speaking it is better to use an <s-expression list> that is as least complicated as possible. So myfunction1 function <s-expression list> is better than the myfunction function <s-expression list> which is better than the message <s-expression list>. You can see this by the lists appearing in each user entry instance for each function version:

message version:  (... compose-message "%s %s" "Hello" "World")
myfunction version:  (... compose-message ("Hello") "World")
myfunction1 version:  (... compose-message "Hello" "World")

The myfunction1 version not only has the least amount of data in the user's entry collection but it is easier to change or add to the effects of the action later without having the user to recreate his entries derived from the action. In fact the message version is horrible because the "%s %s" string would occur in every user's entry needlessly.

[point 5 <function> is looked up based on the action in the entry instance and applied to the instance s-expressions]

You can see from the entry instances listed in point 4 that nowhere does the actual action function appear an the entry instance. However note that the s-expression lists are quite different. This means that without the user having to change his entry instances of your action you can change the function but you can't change the topology of the s-expression lists. This is yet another reason in support point 4 that the less complex list of s-expressions is better.

slots Template Property

The slots template property is the heart of the template since the variety of values of the slots will allow one general action template to provide an infinite variety of specific action entry instances to the Agroups user.

The form of the slots property is a list of the form

(slots . <slots list>)

Where <slots list> is a list of zero or more individual slot forms. You can actually specify no slots but that would not be a very useful template since every action entry generated from such a template of the same action would do the same thing. The section in this document on the afun property describes how slots are actually used when an action entry is executed, See section afun Template Property.

The subsections below expand on the individual slot form. The Form of a Template Slot subsection describes the general syntax and semantics of a slot form and subsequent subsections describe the specific slot properties.

Form of a Template Slot

An individual template slot is a list of the form

(<slot symbol> <slot id> . <slot properties>)

The <slot symbol> is a symbol that should appear in the afun form, See section afun Template Property. A specific value will be substituted for the <slot symbol> in the afun form when the template is expanded to create an entry.

The <slot id> is either a short but descriptive single string or if more description is necessary a list of strings. This string or strings should not have any kind of line terminators. They will automatically be interpreted as one line or multiple lines. The <slot id> will appear to the user when creating or editing an action entry slot.

The <slot properties> is a list of zero or more slot properties that we will describe in subsequent subsections. If a slot form specifies zero slot properties then Agroups defaults will be chosen. The following table lists all slot properties with their defaults on the right:

Slot Property              Default
-------------------------------------------------------------
type            Slot data type will be of type string
select          Slot data will not be selected by user but entered
sfun            No slot data function, user will be asked for data
editor          Default Agroups slot editor
printer         Default Agroups slot data printer based on type

In the following subsections that have specific descriptions of these slot properties each property applies to the enclosing slot form that we defined at the beginning of this section. In particular it applies to how the slot data is acquired and manipulated.

type Template Slot Property

The type template slot property will indicate the Elisp data type of the expected value of the slot data. The type template slot property has the form

(type <Elisp type symbol or t>)

Where <Elisp type symbol or t> is an unquoted symbol. Elisp data types are permissible such as string, number ... etc. with the addition of the Agroups special type t which means any Elisp data type. The default if no type slot property is specified is string.

The type property is used by the default Agroups editor and printer so that it knows how to edit or present the data. Also the afun function is guaranteed to receive the type of data that is specified for a slot or string if not specified.

select Template Slot Property

The select template slot property allows the template designer to prompt the user for some number of fixed values with descriptions when creating or editing the slot. When the user chooses a value it becomes the slot value. It has the form

(select . <list of select choices>)

where <list of select choices> is a list select choices of the form

(<choice description> <value>)

The <choice description> is a string or list of strings exactly like the <slot id> above and the same rules apply. The value must be the type specified by the type slot property or if type property is not specified a string. Here is an example

(agroups-add-action-template
 '((action select-message)
   (afun (message text))
   (slots
    (text "Text of message"
     (select ("Message says Hello" "Hello")
             ("Message says Goodbye" "Goodbye"))))))

Note that no type is specified for the text slot. Since the default is the type string the select data "Hello" and "Goodbye" are not in error since they are strings. If instead we had the symbols hello and goodbye as the select values in the above

(select ("Message says Hello" hello)
        ("Message says Goodbye" goodbye))

Then when expanding that template Agroups would give us, the template designers, an error

Error: Template slot: text is not of type: string, it has value: hello

We could fix this by adding a (type symbol) property but then the user would get an error when trying to execute an entry generated from our template since the function message takes a string and not a symbol and it would then complain. So here is a similar example that uses symbols as values but uses eval as an action function with some code that uses the selected select symbol

(agroups-add-action-template
 '((action select-message)
   (afun (eval (message (symbol-name 'sym))))
   (slots
    (sym "Text of message"
     (type symbol)     
     (select ("Message says Hello" hello)
             ("Message says Goodbye" goodbye))))))

Once again however this is a poor action since what it would put in the user's entry is too complex and specific. Template designer written functions are usually better for processing select values like numbers, symbols lists etc.. So in the above case

(afun (somefunction sym))

would be much better where somefunction processes the selected value of sym in some way and the user's entry for example would be simply

(... select-message hello)

which is far less complex and easier to deal with in the future than

(... select-message (message (symbol-name (quote hello))))

sfun Template Slot Property

The sfun template slot property which stands for Slot Value Function will return a value that becomes the value of a slot based on the environment at action entry creation time. This overrides any other method for getting the slot value, such as a select template slot property. However the sfun property is ignored when editing the slot value. It has the form

(sfun <slot value function>)

where <slot value function> is a function symbol or lambda. For example the following

(defun buffer-region () 
  (buffer-substring (region-beginning) (region-end)))

(agroups-add-action-template
 '((action region-message)
   (afun (message text))
   (slots (text "Text of message" (sfun buffer-region)))))

sets the text slot of a new action entry to the current buffer's region. But then when the user edits such an entry the sfun would be ignored and the existing text slot would be place in the standard Agroups edit buffer for editing.

editor Template Slot Property

The editor template slot property is used when the standard Agroups editor is not suitable for a slot and a special editor would be better. It has the form

(editor <editor function>)

where <editor function> is a function symbol or lambda representing the function that Agroups will pass as the single argument to <editor function> the value of the slot to edit. This function must return the edited value. For example the Agroups given keyboard macro action template stores the keyboard macro string in a slot called macro that uses the Standard Emacs keyboard macro editor

(macro "Keyboard macro" (editor <kbd-macro editor function>))

Agroups prefix arguments can be passed to this editor using the Agroups argument variables, See section Accessing Agroups Prefix Arguments. In fact the Agroups given keyboard macro action does this and in the above the <kbd-macro editor function> is a function that calls the Emacs keyboard macro editor and passed the Agroups prefix argument to it.

printer Template Slot Property

Normally Agroups presents the current value of a slot in the standard Agroups editor by printing the value in the Agroups buffer according to the type of the slot data. For example if the slot type is string it just prints the string verbatim, but if the type is t then it pretty prints the expected Elisp.

The printer template slot property allows this behavior to be overridden by the template designers printer. It has the form

(printer <print function>)

where <print function> is a function symbol or lambda that prints the slot data and takes as an argument the slot data.

edit-printer Template Slot Property

There are two ways that Agroups prints a template slot value in the standard Agroups editor. It prints the slot value in an Agroups buffer when displaying the whole entry object to be edited and also when editing the individual slot it places the existing slot value in the Agroups edit buffer by printing it. Usually these get printed the same way. But occasionally those two get printed in different ways. For example the Keys Macro action prints the keys in a the whole entry object view as standard Emacs keys display form. But this form is not suitable for editing the actually slot keys, the standard display form has embedded spaces for example.

The default printer or if specified for the slot the printer template slot property will be used for both ways unless the edit-printer template slot property is specified. When this property is specified it will be used for printing into the edit buffer of the individual slot where the user will actually edit it. It has the form

(edit-printer <print function>)

where <print function> is a function symbol or lambda that prints the slot data and takes as an argument the slot data.

edit-receiver Template Slot Property

When the user completes a slot edit by typing C-cC-c the Agroups edit buffer is parsed for the entered data based on the default or specified type of the slot. If the edit-receiver template slot property is specified for the slot then the parsed resulting object will be passed to the edit-receiver function. This slot property has the form

(edit-receiver <receiver function>)

where <receiver function> is the receiver function that takes one argument of the parsed object and returns the object that will be stored and associated with the slot in the user's save file. Usually this works in conjunction with the edit-printer template slot property whose function converts the stored representation back to the form that the receiver function gets before the printer function prints it.

pfun Template Property

The pfun template property specifies a preprocessing function that gets called before template slot expansion takes place. This function can set action attributes and slot values before template expansion. The common use for this is to collect information about the current environment, like the current buffer, and set that information as the entry data when an entry is created.

The pfun property has the form

(pfun <preprocessing function>)

where <preprocessing function> is a function symbol or lambda. This function is run before any slot processing is done and it can optionally set the template action and any of the slots using the functions

agroups-set-entry-attribute
agroups-set-entry-slot

The agroups-set-entry-attribute function call has the form

(agroups-set-entry-attribute <entry attribute> <value>)

where <entry attribute> is one of 'action or 'entry-id and <value> is a symbol in the case of 'action and a string in the case of 'entry-id.

Use 'action when you want to preset the action of an entry. This is almost exclusively used when the template action property specifies a list of two or more actions. For example the "Buffer associated File or dired" action has template that specifies two actions

(action (file dired))

Then in the pfun function that it specifies it sets the action depending on whether the current buffer has a file associated with it or a dired. If the template was specified with multiple actions and does not specify a pfun that sets one of these actions then Agroups will just use the first action in the list of actions when an action entry is generated.

Use 'entry-id when you want to preset the default id of an entry. Normally when the user is prompted for an entry id and just hits RET an Agroups default id is constructed from the action symbol, but if the entry-id attribute is set by the pfun then that will be used instead of the Agroups default.

The agroups-set-entry-slot function call has the form

(agroups-set-entry-slot <slot symbol> <slot value>)

where <slot symbol> is a quoted symbol matching one of the slot symbols of one of the slots of the template and <value> is an Elisp value of the type specified in that template slot. The template designer can preset a slot value using a pfun in this way or an sfun. The only difference is that it may be more convenient to use the pfun method to set a slot value in circumstances where the action is also computed by the pfun or a number of slots share the same computations.

exists Template Property

The exists template property is used to trigger something when an one or more entries exist in the current group with the template action that specifies the exists property and some existing environment condition exists. It is typically used when creating a new action entry and some entry already exists of the same action and value of one or more template slots. When a template specifies the exists property and such a condition exists the user is given the option of triggering something that the exists property specifies. If the user accepts and then there are more than one such existing entry then the user is given a choice of those entries.

The exists template property has the form

(exists <exists predicate> <exists trigger>)

The <exists predicate> is a function call form that returns non-nil if the condition exists. The form of the <exists predicate> is

(<function> . <slot symbol list>)

where <function> is any function symbol or lambda and <slot symbol list> is zero or more <slot symbol> symbols that appear in the template slots property.

The <exists trigger> for the time being has only one form

(update . <slot symbol list>)

where <slot symbol list> is a list of one or more slot symbols in the template slots property. The idea is that when the <exists predicate> returns non-nil an update of the existing slots gets triggered. In other words instead of creating a new entry the chosen existing entry slots get updated based on the specified slot's value properties.

A good example of an action using the exists property is the given Agroups "File Point" action. When that operation is executed by virtue of its template action exists property the current group is checked if any actions of point exists and if those actions have the same file as the current buffer's file then the the user chooses to update the existing entry or not. It does this with an exists property of

(exists (<predicate> file) (update point))

where <predicate> is a function that returns non-nil if its argument is the same file as the current buffer's file. The template has two slots point being the point within the file of slot file. So then what the above exists property in effect says is: If the current groups has a point action entry then ask the user if he wants to update that entry and if the user chooses then update the point slot. And since the template has for the slot point

(point "Point to go to" (type integer) (sfun point))

the sfun function is the standard Elisp function point which returns the current point in the buffer. Consequently the exiting entry's point is updated to the current point in the current buffer.

Here is a not quite so good example of using the exists template slot property but is easier to understand and try

(agroups-add-action-template
 '((action file-message)
   (afun ((lambda (file) (message "File reminder: %s" file)) file))
   (exists (buffer-file-name) (update file))
   (slots (file "File to show in message" (sfun buffer-file-name)))))

default-slot Template Property

When the Agroups editor prompts for a slot to edit it gives a default in parentheses so that if the user just hits RET it selects that slot to edit. Normally this default slot is the first slot in the slots property but the template designer can change this with the default-slot property.

The default-slot template property has the form

(default-slot <slot symbol>)

where <slot symbol> is the slot symbol whose choice will appear as the default slot when the Agroups editor asks for a slot.

Accessing Agroups Prefix Arguments

Any function or lambda specified in an action template afun, pfun or sfun or any function subsequently invoked from those can access two special variables to capture the prefix argument given when executing an action entry of this action type. The variable

agroups-arg

will contain the actual prefix argument. So, for example if the prefix argument was C-u this variable would contain (4). The variable

agroups-numeric-arg

contains the standard Emacs numeric interpretation of agroups-arg. So, for example if the prefix argument was C-u this variable would contain 4. Or to be very precise its value is always

(prefix-numeric-value agroups-arg)

Here is an example of an afun property function using agroups-arg

(agroups-add-action-template
 '((action compose-message)
   (afun ((lambda (first second) 
            (message "%s %s"
                     (if agroups-arg second first)
                     (if agroups-arg first second)))
          first second))
   (slots 
    (first "First part of message")
    (second "Second part of message"))))

So if you create an entry of this action and try it it should display a message in the minibuffer with the first slot and then the second slot and then try it with a C-u prefix argument before KA it should display the message with the second slot first and then the first slot.


Go to the first, previous, next, last section, table of contents.