A Python library for parsing customizable WordPress-style shortcodes.
This Python library parses customizable WordPress-style shortcodes with space-separated positional and keyword arguments:
[% tag arg1 key=arg2 %]
Arguments containing spaces can be enclosed in quotes:
[% tag "arg 1" key="arg 2" %]
You can use double or single quotes:
[% tag 'arg 1' key='arg 2' %]
Quoted arguments can contain =
symbols:
[% tag "foo=bar" key="baz=bam" %]
Shortcodes can be atomic, as above, or can enclose a block of content between opening and closing tags:
[% tag %] ... [% endtag %]
Block-scoped shortcodes can be nested to any depth. Innermost shortcodes are processed first:
[% tag %] ... content with [% more %] shortcodes ... [% endtag %]
Shortcode syntax is customizable:
<tag arg="foo"> ... </tag>
Install from the Python package index using pip
:
$ pip install shortcodes
Alternatively, you can incorporate the single shortcodes.py
file directly into your application. The library is entirely self-contained and its code has been placed in the public domain.
Note that this library requires Python 3.6 or later.
Create a new shortcode by registering a handler function for its tag using the @register
decorator:
import shortcodes @shortcodes.register("tag") def handler(pargs, kwargs, context): ... return replacement_text
The handler function should accept three arguments:
pargs
: a list of the shortcode's positional arguments.
kwargs
: a dictionary of the shortcode's keyword arguments.
context
: an optional arbitrary context object supplied by the caller.
Positional and keyword arguments are passed as strings. The function itself should return a string which will replace the shortcode in the parsed text.
If you specify a closing tag in the @register
decorator the new shortcode will have block scope:
@shortcodes.register("tag", "endtag") def handler(pargs, kwargs, context, content): ... return replacement_text
The handler function for a block-scoped shortcode should accept a fourth argument, a string containing the block's content. (Note that any shortcodes in this content will already have been processed when the handler is called.)
Handlers registered using the @register
decorator are available globally. If you need to avoid global state in your application you can register handlers on an individual Parser
instance instead:
parser = shortcodes.Parser() parser.register(handler, tag)
Supplying an endtag
argument gives the new shortcode block scope:
parser.register(handler, tag, endtag)
By default, every new Parser
instance inherits the set of globally-registered shortcodes at the moment of instantiation. You can start a new Parser
off with a clean slate by setting the inherit_globals
flag to False
:
parser = shortcodes.Parser(inherit_globals=False)
To parse an input string containing shortcodes, create a Parser
instance and run the string through its parse()
method:
parser = shortcodes.Parser() output = parser.parse(text, context=None)
A single Parser
instance can process an unlimited number of input strings. The parse()
method's optional context
argument accepts an arbitrary object to pass on to the registered handler functions.
By default, if the parser finds a shortcode with an unregistered tag, it will raise a ShortcodeSyntaxError
exception. You can choose to ignore shortcodes with unregistered tags using the ignore_unknown
flag:
parser = shortcodes.Parser(ignore_unknown=True)
Ignored shortcodes are treated as raw text.
The Parser
object's constructor accepts a number of optional arguments which you can use to customize the syntax of your shortcodes:
parser = shortcodes.Parser(start="[%", end="%]", esc="\\\\")
The escape character (a backslash by default) allows you to escape shortcodes in your text, i.e. the escaped shortcode \\[% foo %]
will be rendered as the literal text [% foo %]
.
The following exception types may be raised by the library:
ShortcodeError
Base class for all exceptions raised by the library.
ShortcodeSyntaxError
Raised if the parser detects invalid shortcode syntax.
ShortcodeRenderingError
Raised if a shortcode handler throws an exception. Note that the
ShortcodeRenderingError
instance wraps the original exception which can be
accessed via its __cause__
attribute.
Let's make a very simple shortcode to mark a block of text as a HTML code sample. We'll use the word code
as our tag.
Our shortcode will accept a single argument — the name of the programming language — and will have block scope as it needs to enclose a block of content. We'll choose the word endcode
as our closing tag.
import shortcodes import html @shortcodes.register("code", "endcode") def handler(pargs, kwargs, context, content): lang = pargs[0] code = html.escape(content) return '<pre class="%s">%s</pre>' % (lang, code)
We're done. Now we can try it with some input text:
[% code python %] def hello_world(): print('hello, world') [% endcode %]
If we create a Parser
object and run the input above through its parse()
method it will give us back the following output:
<pre class="python"> def hello_world(): print('hello, world') </pre>
This work has been placed in the public domain.