#
##
##  This file is part of pyFormex 1.0.5  (Sat Feb 16 10:40:32 CET 2019)
##  pyFormex is a tool for generating, manipulating and transforming 3D
##  geometrical models by sequences of mathematical operations.
##  Home page: http://pyformex.org
##  Project page:  http://savannah.nongnu.org/projects/pyformex/
##  Copyright 2004-2018 (C) Benedict Verhegghe (benedict.verhegghe@ugent.be)
##  Distributed under the GNU General Public License version 3 or later.
##
##  This program is free software: you can redistribute it and/or modify
##  it under the terms of the GNU General Public License as published by
##  the Free Software Foundation, either version 3 of the License, or
##  (at your option) any later version.
##
##  This program is distributed in the hope that it will be useful,
##  but WITHOUT ANY WARRANTY; without even the implied warranty of
##  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
##  GNU General Public License for more details.
##
##  You should have received a copy of the GNU General Public License
##  along with this program.  If not, see http://www.gnu.org/licenses/.
##

"""Extract info from a Python module and shipout in sphinx .rst format.

This script automatically extracts class & function docstrings and argument
list from a module and ships out the information in a format that can be
used by the Sphinx document preprocessor.

It includes parts from the examples in the Python library reference manual
in the section on using the parser module. Refer to the manual for a thorough
discussion of the operation of this code.

Usage:  py2rst.py PYTHONFILE [> outputfile.tex]
"""
from __future__ import absolute_import, division, print_function
import sys

_debug = False

from pyformex.utils import underlineHeader

import inspect



############# Output formatting ##########################


def debug(s):
    sys.__stderr__.write(s)


def indent(s,n):
    """Indent all lines of a multinline string with n blanks."""
    return '\n'.join([ ' '*n + si for si in s.split('\n') ])


def split_doc(docstring):
    try:
        s = docstring.split('\n')
        shortdoc = s[0]
        if len(s) > 2:
            longdoc = '\n'.join(s[2:])
        else:
            longdoc = ''
        return shortdoc.strip('"'),longdoc.strip('"')
    except:
        return '',''


def sanitize(s):
    """Sanitize a string for LaTeX."""
    for c in '#&%':
        s = s.replace('\\'+c,c)
        s = s.replace(c,'\\'+c)
    ## for c in '{}':
    ##     s = s.replace('\\'+c,c)
    ##     s = s.replace(c,'\\'+c)
    return s



out = ''

def ship_start():
    global out
    out = ''

def ship(s):
    global out
    out += s
    out += '\n'


def debug(s):
    if _debug:
        ship('.. DEBUG:'+str(s))

def ship_module(name,docstring):
    shortdoc,longdoc = split_doc(docstring)
    ship("""..  -*- rst -*-
.. pyformex reference manual --- %s
.. CREATED WITH pyformex --docmodule: DO NOT EDIT

.. include:: <isonum.txt>
.. include:: ../defines.inc
.. include:: ../links.inc

.. _sec:ref-%s:

:mod:`%s` --- %s
%s

.. automodule:: %s
   :synopsis: %s""" % (name,name,name,shortdoc,'='*(12+len(name)+len(shortdoc)),name,shortdoc))

def ship_end():
    ship("""

.. moduleauthor:: pyFormex project (http://pyformex.org)

.. End
""")


def ship_functions(members=[]):
    if members:
        ship("""   :members: %s""" % (','.join(members)))

def ship_class(name,members=[],special=[],exclude=[]):
    ship("""
   .. autoclass:: %s
      :members: %s""" % (name,','.join(members)))
    if special:
        ship("      :special-members: %s" % ','.join(special))
    if exclude:
        ship("      :exclude-members: %s" % ','.join(exclude))

def ship_attribute(name):
    ship("   .. autodata:: %s" % name)

def ship_section_init(secname,modname):
    ship(indent('\n'+underlineHeader("%s defined in module %s" % (secname,modname),'~')+'\n',3))


############# Info selection ##########################


def filter_local(name,fullname):
    """Filter definitions to include in doc

    We only include names defined in the module itself, except
    for the gui.draw module, where we also include the defines from
    openg.draw
    """
    return name.__module__ == fullname or (fullname == 'pyformex.gui.draw' and name.__module__ == 'pyformex.opengl.draw')


def filter_names(info):
    return [ i for i in info if not i[0].startswith('_') ]

def filter_docstrings(info):
    return [ i for i in info if not (i[1].__doc__ is None or i[1].__doc__.startswith('_')) ]

def filter_module(info,modname):
    return [ i for i in info if i[1].__module__ == modname]

def function_key(i):
    obj = i[1]
    return obj.__code__.co_firstlineno

def property_key(i):
    obj = i[1]
    if hasattr(obj,'fget'):
        val = obj.fget.__code__.co_firstlineno
    else:
        val = 0
    #print("Property %s is at %s\n" % (i[0],val))
    return val

def class_key(i):
    obj = i[1]
    try:
        key = inspect.getsourcelines(obj)[1]
    except:
        # occasionally, inspect does not find the source code
        # return a large number then
        key = 999999
    return key


def check_declared_members(obj):
    """Check if obj has declared members

    Currently 3 members declarations are acknowledged:
    _members_
    _special_members_
    _exclude_members_
    """
    try:
        members = getattr(obj,'_members_')
    except:
        members = []
    try:
        special = getattr(obj,'_special_members_')
    except:
        special = []
    try:
        exclude = getattr(obj,'_exclude_members_')
    except:
        exclude = []
    return members, special, exclude


def do_class(name,obj):
    members, special, exclude = check_declared_members(obj)
    ship_class(name,members=members,special=special,exclude=exclude)
    return

    # # get class attributes
    # try:
    #     attrnames = getattr(obj,'_attributes_')
    # except:
    #     attrnames = []

    # # get class properties#
    # # props = inspect.getmembers(obj,inspect.isdatadescriptor)
    # # props = [ p for p in props if isinstance(p[1],property) ]
    # # props = filter_names(props)
    # # props = filter_docstrings(props)
    # # props = sorted(props,key=property_key)
    # props = []
    # # get class methods #
    # methods = inspect.getmembers(obj,inspect.ismethod)
    # methods = filter_names(methods)
    # methods = filter_docstrings(methods)
    # methods = sorted(methods,key=function_key)
    # names = [ f[0] for f in props+methods ]
    # ship_class(name,attrnames+names)


def do_list(module):
    members = inspect.getmembers(module)
    visible = [ m for m in members if not m[0].startswith('_') ]

    attributes = [ m for m in visible if not inspect.isclass(m[1]) and not inspect.isfunction(m[1]) ]
    for m in attributes:
        print(m)

    for name,obj in visible:
        print("%s docstring: %s" % (name,inspect.getdoc(obj)))


def do_module(modname,):
    """Process a module.

    Parameters:
    modname: str
        Name of the module.
    """
    if modname.startswith('pyformex.'):
        fullname = modname
    else:
        fullname = 'pyformex.'+modname
    module = __import__(fullname, fromlist=[None])

    members = inspect.getmembers(module)
    visible = [ m for m in members if not m[0].startswith('_') ]

    # Attributes #
    try:
        attrnames = getattr(module,'_attributes_')
    except:
        #attributes = [ m for m in visible if not inspect.isclass(m[1]) and not inspect.isfunction(m[1]) ]
        #attrnames = [ m[0] for m in attributes ]
        attrnames = []

    # Classes #
    classes = [ c for c in inspect.getmembers(module,inspect.isclass) if filter_local(c[1],fullname) ]
    classes = filter_names(classes)
    classes = filter_docstrings(classes)
    classes = sorted(classes,key=class_key)

    # Functions #
    functions = [ c for c in inspect.getmembers(module,inspect.isfunction) if filter_local(c[1],fullname) ]
    functions = filter_names(functions)
    functions = filter_docstrings(functions)
    functions = sorted(functions,key=function_key)
    funcnames = [ f[0] for f in functions ]

    # Shipout

    ship_start()
    ship_module(modname,module.__doc__)
    if funcnames:
        ship_functions(funcnames)
    if attrnames:
        ship_section_init('Variables',modname)
        for n in attrnames:
            ship_attribute(n)
    if classes:
        if len(classes) > 1:
            ship_section_init('Classes',modname)
        for c in classes:
            do_class(*c)
    if funcnames:
        ship_section_init('Functions',modname)
    ship_end()

    sys.__stdout__.write(out)



# End
