#! /usr/bin/env python
"""
:Authors: David Goodger, Ueli Schlaepfer
:Contact: goodger@users.sourceforge.net
:Revision: $Revision: 1.13 $
:Date: $Date: 2002/07/31 01:46:28 $
:Copyright: This module has been placed in the public domain.

Transforms needed by most or all documents:

- `Decorations`: Generate a document's header & footer.
- `Messages`: Placement of system messages stored in
  `nodes.document.messages`.
- `TestMessages`: Like `Messages`, used on test runs.
- `FinalReferences`: Resolve remaining references.
- `Pending`: Execute pending transforms (abstract base class;
  `FirstReaderPending`, `LastReaderPending`, `FirstWriterPending`, and
  `LastWriterPending` are its concrete subclasses).
"""

__docformat__ = 'reStructuredText'

import re
import sys
import time
from docutils import nodes, utils
from docutils.transforms import TransformError, Transform


class Decorations(Transform):

    """
    Populate a document's decoration element (header, footer).
    """

    def transform(self):
        header = self.generate_header()
        footer = self.generate_footer()
        if header or footer:
            decoration = nodes.decoration()
            decoration += header
            decoration += footer
            document = self.document
            index = document.first_child_not_matching_class(
                nodes.PreDecorative)
            if index is None:
                document += decoration
            else:
                document[index:index] = [decoration]

    def generate_header(self):
        return None

    def generate_footer(self):
        # @@@ Text is hard-coded for now.
        # Should be made dynamic (language-dependent).
        options = self.document.options
        if options.generator or options.datestamp or options.source_link \
               or options.source_url:
            text = []
            if options.source_link and options._source or options.source_url:
                if options.source_url:
                    source = options.source_url
                else:
                    source = utils.relative_uri(options._destination,
                                                options._source)
                text.extend([
                    nodes.reference('', 'View document source',
                                    refuri=source),
                    nodes.Text('. ')])
            if options.datestamp:
                datestamp = time.strftime(options.datestamp, time.gmtime())
                text.append(nodes.Text('Generated on: ' + datestamp + '. '))
            if options.generator:
                text.extend([
                    nodes.Text('Generated by '),
                    nodes.reference('', 'Docutils', refuri=
                                    'http://docutils.sourceforge.net/'),
                    nodes.Text(' from '),
                    nodes.reference('', 'reStructuredText', refuri='http://'
                                    'docutils.sourceforge.net/rst.html'),
                    nodes.Text(' source. ')])
            footer = nodes.footer()
            footer += nodes.paragraph('', '', *text)
            return footer
        else:
            return None


class Messages(Transform):

    """
    Place any system messages generated after parsing into a dedicated section
    of the document.
    """

    def transform(self):
        unfiltered = self.document.messages.get_children()
        threshold = self.document.reporter['writer'].report_level
        messages = []
        for msg in unfiltered:
            if msg['level'] >= threshold:
                messages.append(msg)
        if len(messages) > 0:
            section = nodes.section(CLASS='system-messages')
            # @@@ get this from the language module?
            section += nodes.title('', 'Docutils System Messages')
            section += messages
            self.document.messages[:] = []
            self.document += section


class TestMessages(Transform):

    """
    Append all system messages to the end of the document.
    """

    def transform(self):
        self.document += self.document.messages.get_children()


class FinalChecks(Transform):

    """
    Perform last-minute checks.

    - Check for dangling references (incl. footnote & citation).
    """

    def transform(self):
        visitor = FinalCheckVisitor(self.document)
        self.document.walk(visitor)


class FinalCheckVisitor(nodes.SparseNodeVisitor):

    def unknown_visit(self, node):
        pass

    def visit_reference(self, node):
        if node.resolved or not node.hasattr('refname'):
            return
        refname = node['refname']
        id = self.document.nameids.get(refname)
        if id is None:
            msg = self.document.reporter.error(
                  'Unknown target name: "%s".' % (node['refname']))
            self.document.messages += msg
            msgid = self.document.set_id(msg)
            prb = nodes.problematic(
                  node.rawsource, node.rawsource, refid=msgid)
            prbid = self.document.set_id(prb)
            msg.add_backref(prbid)
            node.parent.replace(node, prb)
        else:
            del node['refname']
            node['refid'] = id
            self.document.ids[id].referenced = 1
            node.resolved = 1

    visit_footnote_reference = visit_citation_reference = visit_reference


class Pending(Transform):

    """
    Base class for the execution of pending transforms.

    `nodes.pending` element objects each contain a "stage" attribute; the
    stage of the pending element must match the `stage` of this transform.
    """

    stage = None
    """The stage of processing applicable to this transform; match with
    `nodes.pending.stage`.  Possible values include 'first reader',
    'last reader', 'first writer', and 'last writer'.  Overriden in
    subclasses (below)."""

    def transform(self):
        for pending in self.document.pending:
            if pending.stage == self.stage:
                pending.transform(self.document, self.component,
                                  pending).transform()


class FirstReaderPending(Pending):

    stage = 'first reader'


class LastReaderPending(Pending):

    stage = 'last reader'


class FirstWriterPending(Pending):

    stage = 'first writer'


class LastWriterPending(Pending):

    stage = 'last writer'


test_transforms = (TestMessages,)
"""Universal transforms to apply to the raw document when testing."""

first_reader_transforms = (FirstReaderPending,)
"""Universal transforms to apply before any other Reader transforms."""

last_reader_transforms = (LastReaderPending, Decorations)
"""Universal transforms to apply after all other Reader transforms."""

first_writer_transforms = (FirstWriterPending,)
"""Universal transforms to apply before any other Writer transforms."""

last_writer_transforms = (LastWriterPending, FinalChecks, Messages)
"""Universal transforms to apply after all other Writer transforms."""
