#!/usr/bin/env python

# This script reads the api documentation and generates python statements
# that set the __doc__ strings of all the documented ReQL functions.
#
# These statements should be placed in drivers/python/rethinkdb/docs.py

import json
import sys
import re
from textwrap import fill
from HTMLParser import HTMLParser

# The json documentation generated from docs/rql/src/*.yaml
docs_file = "../../docs/rql/py_docs.json"

# Load the json documentation
docs = json.load(open(docs_file))

# The class for all reql objects
query = 'rethinkdb.ast.RqlQuery.'

# The python class associated with each type
parents = {
    None: '',
    'r': 'rethinkdb.',
    'sequence': query,
    'query': query,
    'stream': query,
    'singleSelection': query,
    'array': query,
    'number': query,
    'bool': query,
    'value': query,
    'string': query,
    'time': query,
    'any': query,
    'connection': 'rethinkdb.net.Connection.',
    'cursor': 'rethinkdb.net.Cursor.',
    'db': 'rethinkdb.ast.DB.',
    'table': 'rethinkdb.ast.Table.',
}

# The real python names for names used in the docs
tags = {
    '[] (get_field)': [(query, '__getitem__')],
    '[] (nth)': [(query, 'nth')],
    '[] (slice)': [(query, 'slice')],
    '+': [(query, '__add__'), ('rethinkdb.', 'add')],
    '-': [(query, '__sub__'), ('rethinkdb.', 'sub')],
    '*': [(query, '__mul__'), ('rethinkdb.', 'mul')],
    '/': [(query, '__div__'), ('rethinkdb.', 'div')],
    '%': [(query, '__mod__'), ('rethinkdb.', 'mod')],
    '&': [(query, '__and__'), ('rethinkdb.', 'all')],
    '|': [(query, '__or__'), ('rethinkdb.', 'any')],
    '==': [(query, '__eq__')],
    '!=': [(query, '__ne__')],
    '<': [(query, '__lt__')],
    '>': [(query, '__gt__')],
    '<=': [(query, '__le__')],
    '>=': [(query, '__ge__')],
    '~': [(query, '__invert__'), (query, 'not_'), ('rethinkdb.', 'not_')],
    'r': [('', 'rethinkdb')],
    'repl': [('rethinkdb.net.Connection.', 'repl')],
    'count': lambda parent: not parent == 'rethinkdb.' and [(query, 'count')] or []
}

# Whether the given namespaces have methods
has_methods = { 'rethinkdb.': False, '': False }

format_tag = {
    'a': lambda text, href: text + " ("  + href + ")",
    'br': lambda _: "\n",
    'code': lambda text: "`" + text + "`",
    'p': lambda text: "\n" + text + "\n",
    'h1': lambda text: "\n" + text + "\n",
    'ul': lambda text: "\n" + text + "\n",
    'li': lambda text: fill(text, initial_indent = " * ", subsequent_indent = "   "),
    'strong': lambda text: "*" + text + "*",
    'em': lambda text: text,
}

nowrap_tags = ['ul', 'br']

class DocFormatter(HTMLParser):
    def __init__(self, where):
        self.where = where
        self.out = ""
        self.stack = [("", {}, "")]
        HTMLParser.__init__(self)

    def handle_starttag(self, tag, attrs):
        if tag in nowrap_tags and len(self.stack) == 1 and self.stack[0][0] == '':
            (_, _, data) = self.stack.pop()
            self.out = self.out + fill(data)
        self.stack.append((tag, dict(attrs), ""))

    def handle_data(self, data):
        (tag, attrs, old) = self.stack.pop()
        self.stack.append((tag, attrs, old + data))

    def handle_endtag(self, tag):
        (opentag, attrs, data) = self.stack.pop()
        if opentag != tag:
            raise Exception("%s: tag mismatch: %s and %s" % (self.where, opentag, tag))
        formatter = format_tag.get(tag, False)
        if not formatter:
            raise Exception("%s: could not format tag: %s" % (self.where, tag))
        formatted = formatter(data, **attrs)
        if len(self.stack) == 0:
            self.out = self.out + formatted
            self.stack = [("", {}, "")]
        else:
            (parent, pattrs, pdata) = self.stack.pop()
            self.stack.append((parent, pattrs, pdata + formatted))

    def get(self):
        (tag, _, data) = self.stack.pop()
        if len(self.stack) != 0 or tag != "":
            raise Exception("%s: tag not closed: %s" % (self.where, tag))
        else:
            return self.out + fill(data)

# Convert the html formatting into plain text
def doc_format(where, doc):
    doc_formatter = DocFormatter(where)
    doc_formatter.feed(doc)
    return doc_formatter.get()

def example_format(example):
    return '\n>>> ' + re.sub("\n(.)", "\n... \\1", example)

# Start generating the docs.py file
print '# Generated by gendocs.py'
print
print 'import rethinkdb'

# For each section of the documentation
for key in docs:
    command = docs[key]

    where = command['name']

    # Format the description and examples
    doc = doc_format(where, command['description'])
    for example in command['examples']:
        doc = doc + '\n\n' + \
              doc_format(where,example['description']) + example_format(example['code'])

    # Find all the python names (and the associated modules) for the command
    tag = command['name']
    names = tags.get(tag, [(parents[command['io'][0][0]], tag)])
    if type(names) == type(lambda x: x):
        names = names(parent)

    # Print out the statements that sets each __doc__ string
    for parent, name in names:
        if has_methods.get(parent, True):
            func = '.__func__'
        else:
            func = ''
        print parent + name + func + '.__doc__' + ' = ' + repr(doc)
