# -*- coding: iso-8859-1 -*-
"""
    MoinMoin - "Literate Programming" action

    Return merged Literate code file or zip

    @version: 0.7
    @copyright: 2007 by Oleg Kobchenko
    @web: http://moinmoin.wikiwikiweb.de/OlegKobchenko
    @license: GNU GPL, see COPYING for details.
"""

import re, os, mimetypes
from textwrap import dedent
from MoinMoin import config, wikiutil, user
from MoinMoin.util import filesys, MoinMoinNoFooter
from MoinMoin.Page import Page
from MoinMoin.action.AttachFile import _addLogEntry

action_name = "Literate Programming"

comments = {'ijs':'NB.','c':'//','cpp':'//','bat':'rem','py':'#','pl':'#'}

class ActionError(Exception): pass

myRequest = ""

def execute(pagename, request):
    global myRequest
    myRequest = request
    try:
        if not request.page.exists():
            raise ActionError('This page is already deleted or was never created!')

        if request.form.has_key('target'):
            target = request.form['target'][0]
            if target == '.':
                sendall(request, pagename)
            else:
                sendone(request, target)
        else:
            sendzip(request, pagename)

        raise MoinMoinNoFooter

    except ActionError, e:
        return request.page.send_page(request, msg=e.args[0])

def getComment(target):
    global comments
    ext = target.rsplit(".", 1)
    if len(ext) < 2: return ""
    ext = ext[1]
    if not comments.has_key(ext): return ""
    return comments[ext]

def sendzip(request, pagename):
    import shutil

    # get directory, and possibly create it
    fname = "%s.zip" % wikiutil.taintfilename(pagename)
    if not fname:
        raise ActionError('Invalid filename "%s"!' % wikiutil.escape(fname))

    page = Page(request, pagename)
    attach_dir = page.getPagePath("attachments", check_create=1)
    current_file = page.getPagePath('current', check_create=0, isfile=1)
    fpath = encode(request, os.path.join(attach_dir, fname))
    cpath = encode(request, current_file)

    if (not os.path.exists(fpath)) or stale(fpath, cpath):
        makezip(request, pagename, fpath, fname)

    ContentFile(request, fname)

    shutil.copyfileobj(open(fpath, 'rb'), request, 8192)

def stale(fpath, cpath):
    from os import stat

    ftime = stat(fpath).st_mtime
    ctime = stat(cpath).st_mtime
    if ftime < ctime - 10:
        os.remove(fpath)
        return 1
    return 0

def makezip(request, pagename, fpath, fname):
    import zipfile
    from datetime import datetime

    if not request.user.may.write(pagename):
        raise ActionError('Write permissions required to create Literate attachment')

    body = request.page.get_raw_body().expandtabs()
    targets = uniqueTargets(body)

    zf = zipfile.ZipFile(fpath, "w", zipfile.ZIP_DEFLATED)

    text = "Literate Programmimg from %s\n\n%s\n\n" % (location(request), "\n".join(targets))
    zf.writestr("literate.txt", encode(request, text))

    timestamp = wikiutil.version2timestamp(request.page.mtime_usecs())
    date_time = datetime.fromtimestamp(timestamp).timetuple()[:6]

    for target in targets:
        text = expanded(body, target)
        zi = zipfile.ZipInfo(filename=encode(request, target), date_time=date_time)
        zi.compress_type = zipfile.ZIP_DEFLATED
        zf.writestr(zi, encode(request, text))

    zf.close()
    os.chmod(fpath, 0666 & config.umask)
    _addLogEntry(request, 'ATTNEW', pagename, fname)

def sendall(request, pagename):
    ContentFile(request, wikiutil.taintfilename(pagename)+".txt")

    body = request.page.get_raw_body().expandtabs()
    targets = uniqueTargets(body)

    text = "Literate programmimg from %s\n\n%s\n\n" % (
        location(request), "\n".join(targets))
    request.write(encode(request, text))

    for target in targets:
        text = expanded(body, target)
        comment = getComment(target)
        if len(comment):
            text = "\n%s %s %s ===\n\n%s\n" % (comment,
                ''.rjust(60-len(target)).replace(' ', '='), target, text)
        request.write(encode(request, text))

def sendone(request, target):
    ContentFile(request, wikiutil.taintfilename(target))
    text = source(request, target)
    request.write(encode(request, text))

def uniqueTargets(body):
    targets = []
    for m in re.finditer(r'''#!literate.*?name=['"]([^'"]+\.[^'"]*)['"]''', body):
        m.group(1) not in targets and targets.append(m.group(1))
    if len(targets) == 0:
        raise ActionError('Not a literate page')
    return targets

root = ""

def source(request, target):
    global root
    root = target
    body = request.page.get_raw_body().expandtabs()
    text = expanded(body, target)
    comment = getComment(target)
    if len(comment) > 0:
        text = '%s literate program from %s#%s\n\n%s' % (
            comment, location(request), target, text)
    return text

def expanded(body, name):
    return expand(body, fragment(body, name))

def expand(body, frag):
    recurse = 0
    for m in re.finditer(u"(?m)^[ ]*«([^»]+)»\n?", frag):
        offset = m.group().find(u"«")
        text = fragment(body, m.group(1))
        text = len(text) and indent(dedent(text), offset) or ""
        frag = frag.replace(m.group(), text)
        recurse = len(text) and 1 or recurse
    return recurse and expand(body, frag) or frag

def fragment(body, name):
    return "".join(fragments(body, name))

def fragments(body, name):
    global myRequest
    global root
    r = []
    for m in re.finditer(r'''(?s)\{\{\{#!literate.*?(name=['"]%s['"][^\n\r]*)\r?\n(.*?)\}\}\}''' % name, body):
        litName, litCaption, litFor = parseArgs(myRequest, m.group(1))
        if (len(litFor) == 0 or litFor == root):
            r.append(m.group(2))
    if 0 and name == 'test':
        raise Exception("len=%d" % len(r))
    return r

def parseArgs(request, args):
    litName, litCaption, litFor = "", "", ""
    attrs, msg = wikiutil.parseAttributes(request, args)
    if not msg:
        try:
            litName = str(attrs.get('name','"untitled"')[1:-1])
        except ValueError:
            pass
        try:
            litCaption = str(attrs.get('caption','""')[1:-1])
        except ValueError:
            pass
        try:
            litFor = str(attrs.get('for','""')[1:-1])
        except ValueError:
            pass
    return litName, litCaption, litFor

def indent(text, offset):
    lst = text.splitlines()
    return "\n".join([t.rjust(offset+len(t)) for t in lst])+"\n"

def location(request):
    return 'http://%s%s/%s' % (
        request.http_host, request.getScriptname(), pagename(request))

def pagename(request):
    return wikiutil.quoteWikinameURL(request.page.page_name)

def ContentFile(request, filename):
    type, enc = mimetypes.guess_type(filename)
    ContentType(request, type or "application/octet-stream", filename)

def ContentType(request, type, filename):
    headers = ['Content-Type: %s; charset=%s' % (type,config.charset)]
    if filename:
        headers.append('Content-Disposition: inline; filename="%s"' % encode(request, filename))
    request.http_headers(headers)

def encode(request, text):
    return request.page.encodeTextMimeType(text).encode(config.charset)
