Directives

Directive

class Directive(line[, mo])

The base class of all directives. Directives can be distinguished by the different tasks, they handle, these Task are generally:

Parameters:
  • line – the line number the directive was found
  • mo – a match object of an regular expression
class Directive(object):
    #Attributes
    <<Directive.expression>>
    <<Directive.priority>>
    <<Directive.line>>

    #Methods
    <<Directive.__init__>>
    <<Directive.collect_block>>
    <<Directive.process>>
    <<Directive.match>>
    <<Directive.__repr__>>
expression

A regular expression defining the directive.

expression = ""
priority

An integer process priority. Directives with a lower priority will be processed earlier.

priority = 10
line

A integer defining the original line number of the directive.

line = None
__init__(line[, mo])

The constructor

def __init__(self, line, mo=None):
    self.line = line
collect_block(document, index)

This method is called by Document. If the directive is defining a text block. It retrieves the text lines of the block from the document and return them.

Parameters:
  • document (Document) – the document calling the function.
  • index (integer) – the line index of the directive.
Returns:

If the directive collects a block the return value is a tuple (directive name, block of lines), or None otherwise.

def collect_block(self, document, index):
    return None
process(document, block, index)

This method is called by Document. The directive should do whatever it is supposed to do.

Parameters:
  • document (Document) – the document calling the function.
  • block – The line block the directive is in.
  • index (integer) – the line index of the directive within the block.
def process(self, document, block, index):
    pass
match(lines)

This method is called by Document. It gives the directive the chance to find and manipulate other directives.

Parameters:lines (list) – a list of all document lines.
def match(self, lines):
    pass
__repr__()

returns a textual representation of the directive.

def __repr__(self):
    return "<%s at %i>" % (self.__class__.__name__, self.line)

NameDirective

class NameDirective(line, mo)

The base class for directives with a name argument. It inherits Directive.

Parameters:
  • line – the line number the directive was found
  • mo – a match object of an regular expression or a string defining the name.
name

A string defining the argument of the directive.

class NameDirective(Directive):
    def __init__(self, line, mo):
        super(NameDirective, self).__init__(line, mo)
        if isinstance(mo, str):
            self.name = mo
        else:
            self.name = mo.group(1)


    def __repr__(self):
        return "<%s(%s) %i>" % (self.__class__.__name__,
                                self.name, self.line)

Start

class Start

This class represents a @start directive. It inherits NameDirective.

The @start directive defines the beginning of a text block. It is called with an argument defining the name of the text block. There are two special text blocks:

  • () The empty one defining the main text block
  • (__macro__) defining a text block for implementing macros.

There are several possibilities to end a text block.

  1. The end of the file
  2. A line with a smaller indentation as the @start directive.
  3. Another start directive with same indentation.
  4. An unnamed end (@) directive with the same indentation as the @start directive.
  5. A named end directive closing this block or an outer block.

Text blocks defined by @start can be nested.

class Start(NameDirective):
    #Attributes
    <<Start.has_named_end>>
    <<Start.inherited attributes>>

    #Methods
    <<Start._find_matching_end>>
    <<Start.collect_block>>
    <<Start.process>>
has_named_end

A boolean value, signalizing if the directive is ended by a named end directive.

has_named_end = False

<<Start.inherited attributes>>

expression = re.compile(r"@start\((.*)\)")
priority = 5
collect_block(document, index)

See Directive.collect_block(). The returned lines are unindented to column 0.

def collect_block(self, document, index):
    end = self._find_matching_end(document.lines[index:])
    block = document.lines[index+1:index+end]

    reduce_block = list(filter(bool, block))
    if not reduce_block:
        document.add_error(self.line, "Empty '%s' block" % self.name)
        return None

    #unindent the block, empty lines may not count (filter(bool, block))
    indent_getter = operator.attrgetter("indent")
    min_indent = min(list(map(indent_getter, reduce_block)))
    block = [ l.clone().change_indent(-min_indent) for l in block ]
    return self.name, block
process(document, block, index)

See Directive.process(). Removes all lines of the text block from the containing block.

def process(self, document, block, index):
    end = self._find_matching_end(block[index:])
    del block[index:index+end]
_find_matching_end(block)

Finds the matching end for the text block.

Parameters:block (list) – A list of lines beginning with start
Returns:The line index of the found end.
def _find_matching_end(self, block):
    if self.has_named_end:
        # ignore all other ending conditions and directly
        # find the matching end directive
        for j, l in enumerate(block[1:]):
            j += 1
            d = l.directive
            if isinstance(d, End) and d.name == self.name:
                return j

    start_indent = block[0].indent
    for j, l in enumerate(block[1:]):
        j += 1

        lindent = l.indent
        d = l.directive

        if isinstance(d, End):
            if d.name is None and lindent == start_indent:
                #case 4: An unnamed @ directive with the same indentation
                #        as the @start directive.
                return j

            if d.start_line <= self.line:
                #case 5: A named @ directive closing this block
                #        or an outer block.
                return j

        if isinstance(d, Start) and lindent == start_indent:
            #case 3: Another @start directive with same indentation.
            return j

        if lindent < start_indent and l:
            #case 2: A line with a smaller indentation as the @start directive.
            #        (an empty line doesn't count)
            return j

    #case 1: The end of the file
    return len(block)

RStart

class RStart

This class represents a @rstart directive. It inherits Start.

The @rstart directive works like the @start directive. While @start removes it’s block completely from the containing block. @rstart replaces the lines with a <<name>> - Sentinel.

class RStart(Start):
    expression = re.compile(r"@rstart\((.*)\)")

    def process(self, document, block, index):
        end = self._find_matching_end(block[index:])
        line = block[index]
        block[index:index+end] = [ line.like("<<%s>>" % self.name) ]

CStart

class CStart

This class represents a @rstart directive. It inherits RStart.

The @cstart(name) directive is a replacement for

@rstart(name)
@code
class CStart(RStart):
    expression = re.compile(r"@cstart\((.*)\)")

    def collect_block(self, document, index):
        name_block = super(CStart, self).collect_block(document, index)

        if not name_block: return None

        name, block = name_block

        first = block[0]
        sd = [ Code(first.index) ]
        block.insert(0, first.like("@code").set(directives=sd,
                                                index=first.index-1))

        return name, block

End

class End

This class represents an end directive. It inherits NameDirective.

The end (@) directive ends a text block.

class End(NameDirective):
    expression = re.compile(r"@(\((.*)\))?\s*$", re.M)

    def __init__(self, line, mo):
        super(NameDirective, self).__init__(line, mo)
        self.start_line = self.line

        if isinstance(mo, str):
            self.name = mo
        else:
            self.name = mo.group(2)


    def match(self, lines):
        if self.name is None: return

        #find the matching start and inform it for the named end
        for l in reversed(lines[:self.line]):
            for d in l.directives:
                if isinstance(d, Start) and d.name == self.name:
                    d.has_named_end = True
                    self.start_line = d.line
                    return


    def process(self, document, block, index):
        #completely remove the directive from the containing block
        del block[index]

Include

class Include

This class represents an @include directive. It inherits NameDirective.

The @include directive inserts the contents of the text block with the same name. The lines have the same indentation as the @include directive.

The directive can have a second file argument. If given the directive inserts the text block of the specified file.

class Include(NameDirective):
    expression = re.compile(r"@include\((.+)\)")


    def process(self, document, block, index):
        #check if the name contains 2 arguments
        args = self.name.split(",")
        name = args.pop(0).strip()

        document.blocks_included.add(name)

        if args:
            #a file name is given, fetch block from that file
            fname = args[0].strip()
            subdoc = document.get_subdoc(fname)
            if subdoc:
                include = subdoc.get_compiled_block(name)
            else:
                include = None
        else:
            include = document.get_compiled_block(name)

        if not include:
            #print "error include", self.line, name
            document.add_error(self.line,
                               "Cannot find text block: %s" % name)
            return

        #replace the directive with its content
        indent = block[index].indent
        include = [ l.clone().change_indent(indent) for l in include ]
        block[index:index+1] = include

RInclude

class RInclude

This class represents an @rinclude directive. It inherits Include.

The @rinclude(text block name) directive is a is a replacement for:

.. _text block name:

**<<text block name>>**

@include(text block name)
class RInclude(Include):
    expression = re.compile(r"@rinclude\((.+)\)")

    def process(self, document, block, index):
        l = block[index]
        super(RInclude, self).process(document, block, index)

        block[index:index] = [ l.like(""),
                               l.like(".. _%s:" % self.name),
                               l.like(""),
                               l.like("**<<%s>>**" % self.name),
                               l.like("") ]

Code

class Code

This class represents an @code directive. It inherits Directive.

The @code directive starts a code block. All lines following @code will be displayed as source code.

A @code directive ends,
  • if the text block ends
  • if an @edoc occurs.

The content of the special macro __codeprefix__ is inserted before each code block. __codeprefix__ is empty by default and can be defined by a @define directive.

class Code(Directive):
    expression = re.compile(r"@code")

    def process(self, document, block, index):
        line = block[index]

        #change the indentation the code lines
        for j in range(index+1, len(block)):
            l = block[j]

            if isinstance(l.directive, Edoc):
                break

            block[j] = l.clone().change_indent(4).set(type='c')

        #insert the rst prefix
        sd = [Subst(self.line, "__codeprefix__")]
        new_block = [
            line.like("@subst(__codeprefix__)").set(directives=sd),
            line.like("::"),
            line.like("")
            ]

        block[index:index+1] = new_block
        block.append(line.like(""))

Edoc

class Edoc

This class represents an @edoc directive. It inherits Directive.

The @edoc directive ends a previously started @code directive

class Edoc(Directive):
    expression = re.compile(r"@edoc")

    def process(self, document, block, index):
        del block[index]

If

class If

This class represents an @if directive. It inherits NameDirective.

The @if directive is used for conditional weaving. The content of an @if, @fi block is waved if the named token argument of @if, is defined in the command line by the --token option.

class If(NameDirective):
    expression = re.compile(r"@if\((.+)\)")
    priority = 4

    def process(self, document, block, index):
        for j in range(index+1, len(block)):
            d = block[j].directive
            if isinstance(d, Fi) and d.name == self.name:
                break

        else:
            document.add_error(self.line, "No fi for if %s" % self.name)
            return

        if self.name in document.tokens:
            del block[index]

        else:
            del block[index:j]

Fi

class Fi

This class represents a @fi directive. It inherits NameDirective.

The @fi ends an @if directive

class Fi(NameDirective):
    expression = re.compile(r"@fi\((.+)\)")

    def process(self, document, block, index):
        del block[index]

Ignore

class Ignore

This class represents an @ignore directive. It inherits Directive.

The @ignore directive ignores the line in the documentation output. It can be used for commentaries.

class Ignore(Directive):
    expression = re.compile("@ignore")

    def process(self, document, block, index):
        del block[index]

Define

class Define

This class represents an @define directive. It inherits NameDirective.

The @define directive defines a macro, that can be used with a @subst directive. If a substitution argument is given, the macro defines an inline substitution. Otherwise the @define has to be ended by an @enifed directive.

class Define(NameDirective):

    expression = re.compile(r"@define\((.+)\)")
    priority = 1

    def process(self, document, block, index):
        args = self.name.split(",")
        name = args.pop(0).strip()

        if args:
            #more than one argument ==> an inline substitution
            document.macros[name] = args[0].strip()
            return

        #search for the matching @enifed
        for j in range(index+1, len(block)):
            d = block[j].directive
            if isinstance(d, Enifed) and d.name == name:
                break

        else:
            document.add_error(self.line, "No enifed for define %s" % name)
            return

        document.macros[name] = [ l.clone() for l in block[index+1:j] ]

Enifed

class Enifed

This class represents an @enifed directive. It inherits NameDirective.

The @enifed directive ends a macro defined by the @define directive.

class Enifed(NameDirective):

    expression = re.compile(r"@enifed\((.+)\)")

    def process(self, document, block, index):
        del block[index]

Subst

class Subst

This class represents a @subst directive. It inherits NameDirective.

The @subst directive is replaced by the substitution, defined by a @define directive. There are two predefined macros:

__line__
Define the current line within the source code. The @subst can also handle operation with __line__ like __line__ + 2.
__file__
Defines the current source file name.
class Subst(NameDirective):
    expression = re.compile(r"@subst\((.+?)\)")
    priority = 2

    def process(self, document, block, index):
        line = block[index]

        #find the substitution
        if self.name.startswith("__line__"):
            expression = self.name.replace("__line__", str(self.line+1))
            subst = str(eval(expression))

        elif self.name not in document.macros:
            document.add_error(self.line, "No macro %s found" % self.name)
            return

        else:
            subst = document.macros[self.name]

        if isinstance(subst, str):
            #inline substitution
            l = line.clone()
            l.text = line.text.replace("@subst(%s)" % self.name, subst)
            block[index] = l
        else:
            ln = line.index
            block[index:index+1] = [ l.clone(self.line+j)\
                                         .change_indent(line.indent)\
                                         .set(index=ln+j)
                                     for j, l in enumerate(subst) ]

Indent

class Indent

This class represents an @indent directive. It inherits Directive.

The @indent directive changes the indentation of the following lines. For example a call @indent -4 dedents the following lines by 4 spaces.

class Indent(Directive):
    expression = re.compile("@indent\s+([+-]?\d+)")

    def __init__(self, line, mo):
        super(Indent, self).__init__(line, mo)
        self.indent = int(mo.group(1))


    def process(self, document, block, index):
        lines = [ l.clone().change_indent(self.indent)
                  for l in block[index+1:] ]
        block[index:] = lines