Extending peml

Gentlemen, we can rebuild him. We have the technology.


Overview

There are two ways to extend peml, one is more of a customisation, while the other offers you full customisation of rendering (within the idea of the language). Customisation can be done via shortcuts, whereas serious extension can be done via new commands and hooks.

For extending in any manner, create a new php file in the Libraries folder of your peml installation. This will then be automatically included at render time. Note that the filename will be used as a prefix for your class names (more on that later).


Shortcuts

Find yourself typing something a lot? Say you have a particular love of includes. Your code may look like this.

peml

:include constants.php
:include menu.php
:include inc/functions.php
:include inc/classes.php
:doctype
html(lang:en)
  head
 

Not ugly, but not great. Let's make it a bit shorter, by adding a shortcut for the :include command. In the constructor of the pemlVersion you're using, just add the a call to pemlCore::registerShortcut() as shown, then you can type less. Note that the space between # and the filename means this is not ambiguous with id's, but be careful, shortcuts are evaluated before tags and commands, so the results could be problematic if you choose letters for your shortcut.

Shortcuts can be either 1 or 2 characters long, but 1 character shortcuts will not work if content proceeds them unless there is a space between (this will be improved upon in a future version.)

peml

# constants.php
# menu.php
# inc/functions.php
# inc/classes.php
:doctype
html(lang:en)
  head

PHP

<?php
  pemlCore::registerShortcut("#", "include");
?>

Extending functionality

While shortcuts allow easier use of existing functions, what if you want to do something new? For this example we're going to add a new filter that source highlights a line of code.

We're going to make a new command 'highlight' that only takes its current line, and highlights it using highlight_string(). In your personal library file, for our purposes "DemoCL.php", this can be done with the following code.

PHP

<?php
  class DemoCL_highlight extends pemlCommand {
    protected $inline = true;
    function inline($arg) {
      return '<span>' . highlight_string($arg, true) . '</span>';
    }
  }
?>

Now, this does a few things. Firstly its name is important. For peml to load your classes safely and automatically, you must prefix them with the name of the library file used.

The class variable declaration tells peml that this is an inline command, and so expects its input only on the same line as the command.

Finally the inline() function declaration is what will be called by peml, and returns what the output should be.


Command types

There are 3 possible command types, and 4 possible called functions.

The inline option can be mixed with one of the other two to create a command that can behave differently depending on if it is called inline or with a block attached.


Command functions

There are four automatically called functions:

For each of these functions, the return value will be added to the output, indented by the correct distance for a normal tag indent. Returning nothing is fine, it just continues to the next line (this allows the collection of multiple lines).


So, our :highlight command can now be used in any normal context, and will print out the line like so:

peml

:highlight <?php $this->has($been . "highlighted!"); ?>
<?php $this->has($been "highlighted!"); ?>

Hooks

Hooks allow you to insert your own commands into a block. A block is a defined command that does its own processing of the contents, and as such one that does not scan its inside for normal tags.

Say we want to also use our :highlight command within :php commands. At the moment, this won't work, as the :php blocks have no knowledge of our new :highlight. Also, our current highlight would be invalid inside a :php tag, as it has a wrapping span.

First, lets make a new highlight, which as we're going to insert it into the php tag, we'll call phphighlightRender.

PHP

pemlCore::registerHook("php", "highlight", "phphighlight");
class DemoCL_phphighlight extends pemlCommand {
  protected $inline = true;
  function inline($arg) {
    return 'echo \'' . str_replace("'", "\\'", (highlight_string($arg, true))) . '\';';
  }
}

Note we had to be rather careful with how we built the new line, because it's inserted directly into a php block.

The function call to registerHook is first telling peml that we want to insert into the php block, the next is the alias of the command within the block, and finally we give the actual alias to call, which is the standard command name for our class.

It should be noted that there is no setup-time checking of consistency, so order of included libraries does not matter as long as all are included by the time of rendering.

peml

:php
  echo "boo!";
  :highlight <?php $this->wontBe("executed."); ?>
p
  :highlight <?php $and . $this("still works."); ?>
boo!
<?php $this->wontBe("executed."); ?>

<?php $and $this("still works."); ?>

 

There you have it!


Blocks and containers.

Blocks are the unit that the parsing of peml is built around, so being able to control your own is integral to any real extensions. To enable block parsing, we need just need to set a block flag, as well as implement the start(), line() and finish() methods.

We're going to make a filter that appends a certain value to the beginning of every line inside it, we'll call it :prepend.

PHP

class DemoCL_prepend extends pemlCommand {
  protected $block = true;
  private $prepend_text;
  function start($arg) {
    $this->prepend_text = $arg;
  }
  function line($line) {
    return "{$this->prepend_text} $line";
  }
  function finish() {}
}

Not much new here at all. All we have to do is add a block flag for the command, and implement the relevant 3 functions.

We also add a variable to the class to store some state over from init() into the lines. Because of our options, we must implement the finish() method, but leaving it empty.

Note that because we specified a block, then our :prepend tag can be the target of command hooks, and so is inherently extensible.

peml

:prepend This
  is kinda cool. <br />
  is quite easy. <br />
  is better than normal markup. <br />
This is kinda cool.
This is quite easy.
This is better than normal markup.
 

From this I hope you see how blocks (and containers) are used, see the SCL for how the php common language constructs are coded.


More on commands

There are a couple of options in pemlCommand that haven't been touched on yet.

$this->setIndent is a property that determines the level of indentation to give each line when they are added to the content. This is needed for when you need to deviate from the standard line indentation rules. Note that the variable passed to the line() function is actually an object, that has the property indent, for finding out the current lines numerical indent.

$this->parseAttributes() is a hook into the method used by the tag parser to read attributes of the form (a:b ` c:d), and takes a string by reference. If the string starts with and contains a valid attributes block, then the attributes are returned as an associative array, and the string is modified to be the remaining part of the line. If there are no attributes, or the formatting is invalid, then false is returned, and the string is unaffected.