# -*- coding: iso-8859-1 -*-
"""
    MoinMoin - Inline RSS generator

    Copyright (c) 2003 by Conectiva, Inc.
    Copyright (c) 2000-2002 by Jürgen Hermann <jh@web.de>
    All rights reserved, see COPYING for details.

    Written by Gustavo Niemeyer <niemeyer@conectiva.com>, based on
    code from RecentChanges.

    This macro allows one to use a moin page as an RSS feed. To
    define an RSS feed, enclose the items you want to provide in the
    comment tags "##irss start" and "##irss stop", like this:
    
    ----

    This won't be considered

    ##irss start

    == Newest item ==
    This will be the newest RSS item.

    == Older item ==
    This is another RSS item.

    ##irss stop

    This won't be considered as well.

    ----

    Then, you can link to that feed in from any moin page using
    [[IRSS([PageName])]]. If linking from the same page, the pagename
    parameter is optional.

    You can also have more than one feed in the same page. To do that,
    use "##irss start yourfeedname", and link to it with
    [[IRSS([PageName], yourfeedname)]] (you can use an empty first
    parameter, if the feed is in the same page). Using the feedname in
    the stop tag, like "##irss stop yourfeedname", is optional and necessary
    only if you want to have one feed "contained" inside another one.

    The following tags can also be used between the start and stop tags
    to configure the feed.

    ##irss topic <rss topic>

        This will set the "title" of the whole RSS feed. Only the first
        topic found is considered. The default topic is the page name.

    ##irss descr <rss description>

        This will set the "description" of the whole RSS feed. Only the
        first description found is considered. The default description
        is "News from <pagename>".

    ##irss reverse 

        The default behavior is to have the newer items at the top of
        the list. If used, this tag will reverse this behavior.

    ##irss link <url>

        This tag should be used just after a Moin title, and will
        make the RSS item title link to that URL. The default behavior
        is to link the RSS item title to the Moin title in the page
        where the feed is defined.
"""

import re, string, sys, StringIO, new, sha
import pprint
from MoinMoin import config, util, wikiutil, wikimacro
from MoinMoin.Page import Page
from MoinMoin.wikixml.util import RssGenerator
from MoinMoin.parser.wiki import Parser
from MoinMoin.formatter.text_html import Formatter

def execute(pagename, request):

    page = Page(request, pagename)

    # Send a null page to make the page set its internal data (ugh!)
    out = StringIO.StringIO()
    backup = sys.stdout, request.write
    sys.stdout, request.write = out, out.write
    page.send_page(request, content_only=1)
    sys.stdout, request.write = backup

    # get params
    items_limit = 100
    try:
        max_items = int(request.form['items'].value)
        max_items = min(max_items, items_limit) # not more than `items_limit`
    except (KeyError, ValueError):
        # not more than 15 items in a RSS file by default
        max_items = 15

    # prepare output
    out = StringIO.StringIO()
    handler = RssGenerator(out)

    # get data
    interwiki = request.getBaseURL()
    if interwiki[-1] != "/": interwiki = interwiki + "/"

    logo = re.search(r'src="([^"]*)"', request.cfg.logo_string)
    if logo: logo = request.getQualifiedURL(logo.group(1))

    title_re = re.compile(r"^(={1,5}) (?P<title>.*) \1$")
    start_re = re.compile(r"^##irss\s+start(?:\s+(?P<name>\S+))?\s*$")
    stop_re = re.compile(r"^##irss stop(?:\s+(?P<name>\S+))?\s*$")
    topic_re = re.compile(r"^##irss\s+topic\s+(?P<topic>.*)$")
    descr_re = re.compile(r"^##irss\s+descr\s+(?P<descr>.*)$")
    reverse_re = re.compile(r"^##irss\s+reverse\s*$")
    link_re = re.compile(r"^##irss\s+link\s+(?P<link>.*)$")
    feeding = 0
    counter = 0
    rssdata = []
    rsstopic = None
    rssdescr = None
    rssreverse = 0
    class RssData:
        def __init__(self, title):
            self.title = title
            self.link = None
            self.lines = []
    if not request.user.may.read(pagename):
        rssdata.append(RssData("You are not allowed to view that page."))
    else:
        rssname = None
        titles = {}
        body = IncludeParser(request, page).parse(page.get_raw_body())
        for line in body.splitlines():
            m = start_re.match(line)
            if m:
                if feeding:
                    continue
                name = m.group("name")
                try:
                    if name != request.form["name"].value: continue
                except KeyError:
                    if name: continue
                rssname = name
                feeding = 1
                continue

            m = title_re.match(line)
            if m:
                if counter >= max_items:
                    break
                # Based on parser/wiki.py
                item_title = m.group("title")
                try:
                    titles[item_title] += 1
                    unique_id = '-%d' % titles[item.title]
                except KeyError:
                    titles[item_title] = 1
                    unique_id = ''
                if not feeding:
                    continue
                item = RssData(item_title)
                rssdata.append(item)
                item.link = "%s%s#head-%s%s" % \
                            (interwiki, wikiutil.quoteWikinameURL(pagename),
                             sha.new(item.title).hexdigest(), unique_id)
                counter += 1
                continue

            if not feeding:
                continue

            m = stop_re.match(line)
            if m:
                name = m.group("name")
                if name and name != rssname:
                    continue
                feeding = 0
                continue

            m = topic_re.match(line)
            if m:
                if not rsstopic:
                    rsstopic = m.group("topic").strip()
                continue

            m = descr_re.match(line)
            if m:
                if not rssdescr:
                    rssdescr = m.group("descr").strip()
                continue

            m = link_re.match(line)
            if m:
                if rssdata:
                    rssdata[-1].link = m.group("link")
                continue

            m = reverse_re.match(line)
            if m:
                rssreverse = 1
                continue

            if rssdata and not line.startswith("##"):
                rssdata[-1].lines.append(line)

    if not rsstopic:
        rsstopic = pagename
    if not rssdescr:
        rssdescr = "News from " + pagename
    if rssreverse:
        rssdata.reverse()

    # start SAX stream
    handler.startDocument()

    # emit channel description
    handler.startNode('channel', {
        (handler.xmlns['rdf'], 'about'): request.getBaseURL(),
    })
    handler.simpleNode('title', rsstopic)
    handler.simpleNode('link', interwiki +
                               wikiutil.quoteWikinameURL(pagename))
    handler.simpleNode('description', rssdescr)
    if logo:
        handler.simpleNode('image', None, {
            (handler.xmlns['rdf'], 'resource'): logo,
        })
    if request.cfg.interwikiname:
        handler.simpleNode(('wiki', 'interwiki'), request.cfg.interwikiname)

    handler.endNode('channel')

    # emit logo data
    if logo:
        handler.startNode('image', attr={
            (handler.xmlns['rdf'], 'about'): logo,
        })
        handler.simpleNode('title', rsstopic)
        handler.simpleNode('link', interwiki + 
                                   wikiutil.quoteWikinameURL(pagename))
        handler.simpleNode('url', logo)
        handler.endNode('image')

    # emit items
    for item in rssdata:
        handler.startNode('item')
        handler.simpleNode('title', item.title)
        handler.simpleNode('link', item.link)

        #handler.simpleNode(('dc', 'date'), util.W3CDate(item.time))

        desc_text = "\n".join(item.lines)
        if desc_text:
            parsed = StringIO.StringIO()
            request_write = request.write
            request.write = parsed.write
            Parser(desc_text, request).format(page.formatter)
            request.write = request_write
            handler.simpleNode('description', parsed.getvalue())
        handler.endNode('item')

    # end SAX stream
    handler.endDocument()

    # send the generated XML document
    request.http_headers(["Content-Type: text/xml; charset=%s" % config.charset]
                         + request.nocache)
    sys.stderr.write(out.getvalue().encode('utf-8'))
    sys.stderr.write("whatever works\n")
    request.write(out.getvalue())
    request.finish()
    request.no_closing_html_code = 1

class IncludeParser:
    def __init__(self, request, page):
        self.parser = Parser("", request)
        # Format the empty string, making it set its internal data (ugh!).
        out = StringIO.StringIO()
        backup = sys.stdout, request.write
        sys.stdout, request.write = out, out.write
        self.parser.format(page.formatter)
        sys.stdout, request.write = backup
        self.include_re = re.compile("\[\[Include(?:\(.*?\))?\]\]")
        self.macro = wikimacro.Macro(self.parser)

        # This is really deep and cool black magic. Basically, it creates
        # a local copy of the function macro.Include.execute that behaves
        # exactly like the original one, but with a different "Page"
        # global. This allows us to follow changes in the Include macro
        # without much trouble.
        from MoinMoin.macro.Include import execute
        func_globals = {}
        func_globals.update(execute.func_globals)
        class IncludePage(Page):
            incparser = self
            def send_page(self, request, msg=None, **keywords):
                request.write(self.incparser._parse(self.get_raw_body()))
        func_globals["Page"] = IncludePage
        self.execute = new.function(execute.func_code, func_globals,
                                    execute.func_name,
                                    execute.func_defaults)

    def _macro_repl(self, match):
        word = match.group(0)
        macro_name = word[2:-2]
        args = None
        if string.count(macro_name, "("):
            macro_name, args = string.split(macro_name, '(', 1)
            args = args[:-1]
        return self.execute(self.macro, args)

    def _parse(self, body):
        return self.include_re.sub(self._macro_repl, body)

    def parse(self, body):
        # Turn on some special settings, like disabling the "edit" link.
        #item = self.macro.form["action"]
        #pprint.pprint(self.macro.form, sys.stderr)
        #item_value = item.value
        #item.value = "print"
        body = self._parse(body)
        #item.value = item_value
        return body

# vim:ts=4:sw=4:et
