DokuWiki

It's better when it's simple

User Tools

Site Tools


plugin:command

Command Plugin

Compatible with DokuWiki

No compatibility info given!

plugin Easily create custom syntax commands.

Last updated on
2012-06-30
Provides
Syntax
Repository
Source

Tagged with command, extension

Needed for date_time, freemind, gist, template

Overview

The Command Syntax Plugin implements an extensible syntax. It allows people to extend DokuWiki syntax by writing merely one or two PHP functions, without writing a DokuWiki plugin, and without further cluttering the syntax space.

An extension of the plugin is called a command. Each command has a name and may take parameters. It may also take arbitrary content, but this content is never parsed by DokuWiki; the command is entirely responsible for interpreting any syntax embedded within the content, but it may interpret it any way it wants.

Here are examples of some (mostly) hypothetical commands:

  %dt()%  (shows the current date/time in a pre-configured format)
  %dt(8/23/05 12:55)%  (converts a date/time to a pre-configured format)
  %DT(8/23/05 12:55)%  (command name is not case-sensitive)
  %dt&cal(8/23/05)%  (converts a date/time to the pre-configured cal format)
  %scramble?hex(The password is 'FooLaBoo'.)%
  %icon?15x15(alert)%
  %include(http://randomquotes.com?type=inspirational)%
  %CALC?x=1.1&y=2&z=.85(sqrt(x*x + y*y + z*z))%
  %hits()%
  %count_days?from=2005-8-23&to=2005-12-25()%
  %notebox?right(Caution: The spider may get stuck in your nose.)%

  #dt(8/25/05)#  (put a date/time in its own <div>, not in any paragraph)
  #flash?200x150&border=green(http://flashlibrary.com/someflash.swf)#
  #discussion(on)#
  #template(calendar.php|calendar:events)#
  #template?category=health(news.php|news data)#

The Date/Time command (command name dt) is the first Command Plugin extension and is included with this plugin. The Template command (command name template) is an example of a more elaborate extension. The remaining commands are suggestive of other kinds of commands you might create. Those who create new commands should just add them to the plugin list, where people expect to find syntax extensions.

The Command Plugin also provides a way to add endless new features to the DokuWiki syntax without overcrowding the syntax space. This helps reduce both user confusion and conflicts among syntaxes. People familiar with the syntax for any one command will understand the general syntax of all the other commands.

Modification History

This is essentially the work of Spider Joe with the code hosted on github now.

  • 2005/8/25 — Created. Spider Joe
  • 2005/8/26 — Reverted back to 'substition' to permit recursion. Spider Joe
  • 2005/8/29 — Added $renderer parameter to runCommand(). Spider Joe
  • 2005/8/29 — Fixed failure to load extension for pre-cached data. Spider Joe
  • 2005/9/3 — Fixed RSS feed by changing hsc() to htmlspecialchars(). Spider Joe
  • 2012/6/30 — Moved hosting to github Martyn Eggleton

Installation

When you install the plugin you get only one demo command. This command is the Date/Time command introduced in the overview. After installing the plugin you will be able to install other commands that people have implemented. To install the plugin, download the following zip file:

To install other commands, follow the installation instructions given for the command. Every command consists of at least one file place in the extensions directory /lib/plugins/command/ext/, a PHP file whose name is that of the command, given in lowercase letters.

Syntax

Every command occurs somewhere on the page. When the command is executed, it is replaced with the result of the command. Some commands produce only external affects and so are replaced with nothing – they are simply removed. Other commands produce text or HTML that is to appear in place of the command. The replacement behavior of a command depends entirely on the implementation of that specific command.

However, when you insert the command, you have to decide what is to happen around the command. DokuWiki syntax allows you to create HTML paragraphs merely by inserting blank lines. Paragraphs are automatically generated for you. When you use a command, you have to decide whether that command is to be embedded within a paragraph or not. There are two general syntaxes for deciding this:

  Inline command (inside paragraph):   %command?parameters(content)%
  Block command (outside paragraphs):  #command?parameters(content)#

If you use an inline command somewhere in a paragraph containing other text, the result of the command will replace the command within the same paragraph. If you put the inline command on its own line, DokuWiki will wrap the result of the command in its own paragraph, putting it between <p> and </p>. We call this replacement inline embedding.

If you use a block command somewhere in a paragraph containing other text, the paragraph will split into two paragraphs, and the result of the command will be placed between the two paragraphs, not itself within a paragraph. If you use the block command on its own line, DokuWiki will replace the command without wrapping it in paragraph tags. We call this replacement block embedding.

Each command is informed of how you chose to embed it on the page, so it may produce whatever HTML code is appropriate. Some commands may only be suitable for only one of the two embedding types.

Here is the complete W3C-style BNF for the general command syntax:

  command        ::= inline_command | block_command
  inline_command ::= '%' call_string '(' inline_content ')%'
  block_command  ::= '#' call_string '(' block_content ')#'
  call_string    ::= command_name ('?' params)?
  command_name   ::= name
  params         ::= param ('&' param)*
  param          ::= value | name '=' value?
  name           ::= [a-zA-Z] [a-zA-Z0-9_]*
  value          ::= [a-zA-Z0-9_\-.]+
  inline_content ::= {any string not containing ')%'}
  block_content  ::= {any string not containing ')#'}

Syntax notes:

  1. Names are constrained to allow them to appear in PHP identifiers and to minimize user confusion over issues such as whether 'do-it' and 'do_it' are distinct commands or distinct parameter names.
  2. Command names are not case-sensitive, while parameter names are case-sensitive unless the particular command implements case-insensitivity.
  3. Parameter values are strongly constrained to minimize parsing interference with other plugin syntaxes. In particular, parameters are not a suitable means for conveying textual information to a command; text should appear as content.
  4. DokuWiki does not parse the content of a command. Any DokuWiki syntax or plugin syntax that you put in the content is handed directly to the command without translation. However, the command may implement its own content syntax.

If the BNF is too confusing, you can learn the syntax by looking at the examples in the overview. Those examples are meant to demonstrate most of the syntax.

The resulting syntax looks much like a hybrid between a URL query and a function call. This is for consistency with other DokuWiki syntaxes that use URL queries to provide parameters, such as the image syntax and the gallery plugin, and also to suggest the command-like nature of the syntax.

Command syntax is intentionally similar to the syntax of the Div/Span Shorthand plugin. Div/Span shorthand is useful for creating renderings of parsed text, provided you can use CSS to do it.

Extensions

The Command Plugin is designed to make it easy to implement custom commands. You create one file, one class in that file, and one or two functions in that class. Here is the procedure, about half of which is just decision making:

  1. Choose a command name. You should pick a name that is not already taken – or at least one you don't know to be taken.
  2. Determine the parameter set that your command will take, if it takes any parameters.
  3. Determine the form of the content that command accepts, if it uses content.
  4. Decide how the command should behave in inline embeddings and block embeddings.
  5. Create a file whose name is <command>.php, replacing <command> with the lowercase name of the command as provided in the command syntax, and put this file in the directory /lib/plugins/command/ext/.
  6. In <command>.php create a class called CommandPluginExtension_<command> and have this class extend class extend CommandPluginExtension. Again, <command> is in lowercase letters. You do not need to use PHP include() to include any other files. However, you may look at the base class CommandPluginExtension in /command/inc/extension.php.
  7. In your new class, implement the function getCachedData, overriding the stub function of this name in the base class. This function processes the command to produce whatever data can be cached. Read the description of this function in extension.php to learn how you implement it.
  8. If your function needs to do something every time the page is generated, you can override function runCommand to do that something. Otherwise your extensions will merely insert into the page the string that was returned from getCachedData.
  9. Test your class separately from DokuWiki by calling it directly. When your command is run from within a DokuWiki page, syntax and runtime errors occurring in your class may not make it to the screen or to any log file.

That's it, you're done. You don't need to worry about the syntax at all. All you have to do is implement behavior according to the indicated embedding mode (if it differs between the modes), examine the parameters and content as necessary, and perform the action and/or return replacement text.

You may prefer to simply copy /command/ext/dt.php to your new <command>.php file and modify it until it does what you need. You will certainly want to look at the documentation and code for the Date/Time command to see an example extension.

The base class CommandPluginExtension is provided below for your convenience. Refer to it for more information about overriding its functions.

extension.php

NOTICE: This is not the source for this plugin. This is just the file containing the class that programmers subclass to create a new command. See the installation for instructions on installing the plugin.

<?php
/******************************************************************************
CommandPluginExtension - Base class for a command of the Command Plugin.
 
Each command is a subclass of CommandPluginExtension.  The name of a subclass
must be of the form CommandPluginExtension_<command>, where <command> is the
name of the command as it occurs in the syntax, in lowercase letters.  Each
subclass must go in its own file, a file with name <command>.php.
 
The behavior of a command may vary according to how the command was embedded
in the text.  The Commmand Plugin provides two embedding modes: inline and
block.  When embedding inline, any replacement text that the command produces
will always appear within an HTML paragraph.  When block-embedding, the
replacement text is guaranteed to occur outside of a paragraph.  In commands
that are sensibly placed in either an HTML div or span, the command may
output a div or a span according to the embedding mode chosen.
 
The class provides two methods.  getCachedData() is a stub that each command
must override with its own implementation.  runCommand() by default causes
the command to be replaced with the string returned by getCachedData() and 
only needs to be overridden if this isn't the desired behavior.
 
The Command Plugin never instantiates this class.  Both methods are static
methods and the $this variable is not available for use.
 
NOTICE: COMMANDS THAT OUTPUT USER-PROVIDED CONTENT SHOULD BE CAREFUL TO ESCAPE
SPECIAL HTML CHARACTERS FOUND IN THAT CONTENT, unless of course, the purpose
of the command is to allow the user to embed HTML of his/her choosing.
 
See http://www.splitbrain.org/plugin:command for more information.
 
@license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
@author     Joe Lapp <http://www.spiderjoe.com>
 
Modification history:
 
8/29/05 - Added $renderer parameter to runCommand(). JTL
******************************************************************************/
 
class CommandPluginExtension
{
    /**
     * getCachedData() pre-processes a command to produce data that can be
     * cached.  DokuWiki pre-compiles its pages to extract the information
     * that does not change each time the page is generated.  This method
     * provides that information.  Usually a command that only produces
     * replacement text can produce all of that replacement text in its
     * final form during this method and then return it for caching.
     *
     * This method is also the only means by which parameters and content are
     * handed to the command, so if these values cannot be processed until
     * the page is actually generated, the method will be responsible for
     * returning them (or some aspect of them) to be cached.
     * 
     * The $params and $paramHash arguments of this method contain the
     * parameters that the user provided to the command.  $params is an array
     * that describes all of the parameters, fully characterizing exactly what
     * the user provided.  $paramHash is a convenience argument that makes it
     * easy to get to certain parameter values, but which which loses some
     * information that is only available in $params.
     *
     * This method does nothing by default and must be overridden to
     * implement the command, or at least to cache any needed arguments.
     *
     * This method is the Command Plugin analogue of the DokuWiki syntax
     * plugin's handle() method.
     *
     * @param string $embedding 'inline' or 'block'
     * @param array $params An array of the command's parameters, indexed by
     *      their order of occurrence in the parameter list.  If the parameter
     *      was an assignment (using '='), its element will be an array of the
     *      form array(name, value).  If the parameter was a simple value (no
     *      '='), its element will be a string containing that value.  If
     *      there are no parameters, $params will be an empty array.
     * @param array $paramHash An associative array indexed by parameter name
     *      (for assignment parameters) and by parameter value (for simple
     *      value parameters).  If no value was assigned to a parameter, or if
     *      the parameter is itself just a value, the value for the index key
     *      is an empty string.  For example, "?1&1&2&a&b=&c=3&d=4&d=5"
     *      produces array('1'=>'', '2'=>'', 'a'=>'', 'b'=>'', 'c'=>'3',
     *      'd'=>'5').
     * @param string $content The content that the user provided to the
     *      command.  Empty string if there was no content.
     * @param reference $errorMessage The method may set this variable to a
     *      non-empty string to report an error and provide an error message.
     *      If the method does this, this error message will be output into
     *      the text, replacing the commmand, and runCommand() is not called.
     * @return object The method may return any object that it would like
     *      cached for later use by runCommand().  The method may even return
     *      null.  If the command is using the default implementation of
     *      runCommand(), getCachedData() must return a string containing the
     *      replacement text for the command, unless the method reports an
     *      error by setting $errorMessage.  If the returned value is the
     *      replacement text, THIS METHOD SHOULD FIRST ESCAPE ANY SPECIAL
     *      HTML CHARACTERS THAT THE USER MAY HAVE PROVIDED.
     */
 
    function getCachedData($embedding, $params, $paramHash, $content,
                             &$errorMessage) // STATIC
    {
        return null;
    }
 
    /**
     * runCommand() implements the behavior that must occur each time the page
     * is generated and to return the replacement text for the command.
     *
     * The default behavior is to return the string that getCachedData()
     * returned, thus relegating the responsibility for producing the
     * replacement text to getCachedData().  This behavior is appropriate
     * when the command produces static text that can be cached in its
     * entirety.  A command should only override the default implementation
     * if some aspect of the command is dynamic.
     *
     * This method is the Command Plugin analogue of the DokuWiki syntax
     * plugin's render() method.
     *
     * @param string $embedding 'inline' or 'block'
     * @param object $cachedData This is the value that getCachedData()
     *      returned for the command, which may be null.
     * @param reference $errorMessage The method may set this variable to a
     *      non-empty string to report an error and provide an error message.
     *      If the method does this, this error message will be output into
     *      the text, serving as the replacement text for the command.
     * @param reference $renderer This is the renderer object passed by the
     *      parser to the plugin's render() method.  Some commands may need
     *      to access this object.  It is possible to output text via the
     *      renderer, but the function should return text instead.
     * @return string Replacement text for the command.  The replacement text
     *      may be either null or the empty string, which are equivalent.
     *      The return value is ignored if the method reported an error by
     *      setting $errorMessage.  If the method returns replacement text
     *      and getCachedData() has not already escaped special HTML
     *      characters, THIS METHOD SHOULD FIRST ESCAPE ANY SPECIAL HTML
     *      CHARACTERS THAT THE USER MAY HAVE PROVIDED.
     */
 
    function runCommand($embedding, $cachedData, &$renderer,
                          &$errorMessage) // STATIC
    {
        return $cachedData;
    }
}
?>

Discussion

I originally implemented this plugin using “substition”. That version supported recursion, allowing for the possibility of having a command recursively invoke the parser. However, substition does a greedy match, and I have content requiring a ”.*”. Substition was matching from the beginning of the first command on the page to the end of the last command on the page.

To fix this I changed the plugin to be a “container” not allowing any nested syntax. Only I didn't know how to use the parser to collect the data from multiple lexer tokens (i.e. DOKU_LEXER_ENTER, DOKU_LEXER_MATCHED, DOKU_LEXER_EXIT) together for processing at once. So I'm collecting that data in a static variable of handle(). And now the Command Plugin is no longer recursively callable.

Any idea how I make substition un-greedy, given that I can't include parentheses? Or how I make my container recursive? Thanks! — Spider Joe

You should be able to add the ”/U” trailing option to your regex or use “*?” instead of “*”. — Christopher Smith 2005-08-26 10:44

Thank you!!!! That did it. The installation zip file now uses 'substition' and is recursively callable, should that day ever come. If only I had your brain at the time I encountered the problem. All I had to do was insert one measly question mark. — Spider Joe


For consistency with the DokuWiki syntax that seems to be assuming dominance, should I instead use the following syntax?

  {{command?parameters>content}}

I'd make it priority 1000 or so. But then how do I distinguish inline from block?

  {{#command?parameters>content}}

… for block embedding, perhaps? Note that we'd get variations like:

  {{command>}}
  {{command>content}}
  {{command?parameters>}}

It doesn't seem to fit, does it? — Spider Joe

priority 1000 (I guess you mean getSort()) is low priority. Low numbers come first. — Christopher Smith 2005-08-30 20:03
Right. I wouldn't want to userp {{gallery>}} or {{include>}} or such. What do you think? — Spider Joe
I would suppose you switch to {{command>...}}. Not only that its getting some kind of default, but you could have the case (I had it with rss>), that a parameter is an URL containing a ”?” itself. Jan G.

Removing a command (or renaming it) corrupts all wikipages which use it (and all the history files too). The following error is displayed:

 Fatal error: Undefined class name 'commandpluginextension_somename .... 

Can you catch this error and print out some readable error message instead? — Patrick Maué 2005-10-30 18:00

plugin/command.txt · Last modified: 2013/07/11 18:03 by Aleksandr