Customizing pudding

Pudding is built highly customizable and modular. You can customize any part to get the behaviour and output you want.

Most likely you want a custom writer class to get a different output format. But you can also write your own statements, functions or even compiler.

On this page there is a overview of how pudding works internally and an example of how to create a custom function. Of course pudding can do much more, but this would be too much to write down :)

How pudding works

1. Compiling the syntax

First of all, you need the compiled syntax for the processor.

from pudding.compiler import Compiler

syntax = Compiler().compile_file(syntax_file)
# or
syntax = Compiler().compile(syntax_string)

If no arguments are given the compiler uses the default tokens. You can limit or extend the allowed tokens by giving a list of tokens when initializing the compiler class. See Compiler in the API Reference.

2. Creating the context

The processor also needs a context object containing a reader class with the input and writer class for the wanted output format.

from pudding.processor.context import Context
from pudding.reader import Reader
from pudding.writer import Xml

reader = Reader(content)
writer = Xml(output_file, encoding=encoding)
context = Context(reader, writer)

Both classes Reader and Writer can be customized by inheriting the base class and giving them to the context at this point.

Tip

View the Writer module in the API Reference first and decide which class suits you best as a base class.

3. Converting and writing the output

Now we can combine everything and initialize the processor class with the context and syntax.

from pudding.processor.processor import Processor

writer = Processor(context, syntax).convert()
writer.write_output()

Running .convert() starts the conversion of the input. If you do not use a writer that writes directly into files like SliXml, call .write_output() to write to a file. Otherwise you can use .generate_output() to receive the converted input as a string.

Adding a simple custom function

Here is an example of a custom function:

import re
import requests

from pudding.datatypes import String
from pudding.processor import PAction
from pudding.processor.context import Context
from pudding.tokens.functions.function import Function

class SendRequest(Function):
    """Class for `send` function.

    Send a GET request to the provided url.
    """
    min_args = 1
    max_args = 1

    match_re = re.compile(r"(custom\.send_request)\((.*)\)$")
    value_types = (String,)

    def execute(self, context: Context) -> PAction:
        """Send a GET request.

        :param context: Current context object.
        :returns: Returns PAction.CONTINUE for processor class.
        """
        url = context.replace_string_vars(self.get_string(0))
        print(requests.get(url))
        return PAction.CONTINUE

Naming your function

Function names should follow the existing scheme, e.g. custom.function(). Otherwise custom functions with no arguments will prevent grammars with the same name from being called. It should also not be overriding any other function names.

Class attributes

  • min_args: Integer defining the minimum amount of arguments.

  • max_args: Integer defining the maximum amount of arguments.

  • match_re: Compiled regular expression matching the function name and arguments. It must contain two groups where the first group matches the function name and the second matching the values including any delimiters (commas, spaces, etc.).

  • value_types: Tuple of datatypes in order of the arguments. Available are Regex, String and Varname. See Value types

Execute function

This function is called when the processor calls your function. It takes the context object and returns a PAction (processor action).

To get mandatory string values use the get_string method. It raises a TypeError if the value at the given index is not a string. Otherwise you need to use get_values.

Using the context object you can, for example, replace the variables in the string with matched values.

Finally return a PAction, telling the processor what to do next. Unless you want a specific behaviour, I recommend keeping this to PAction.CONTINUE.

Adding it to the compiler

As described above you need give all occuring tokens to the compiler.

from pudding.compiler import Compiler
from pudding.compiler.util import DEFAULT_TOKENS

tokens = DEFAULT_TOKENS + (SendRequest,)
compiler = Compiler(tokens)
...

From here you can follow the steps 2 and 3 to convert your content.