#!/usr/bin/env python
#******************************************************************************\
#* Copyright (C) 2003-2004 Martin Blais <blais@furius.ca>
#*
#* 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 2 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, write to the Free Software
#* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#*
#*****************************************************************************/

"""python-genpage [<options>] <file>

Generate a textual help file for a Python script.  The format is
reStructuredText and is meant to be converted by docutils to HTML.

"""

__moredoc__ = """We assume the following in the script to be processed:

- the first line of the docstring is a single usage line;
- the script can be loaded as a module without running anything, that is, it has
  a main() program and unless invoked as the main program it does not run it;
- the script behaves as would be expected with optparse when invoked with
  --help;

The following global scope variables have special meaning:

- ``__doc__``: main description docstring;

- ``__author__``: the author of the program;

- ``__version__``: the version string of the program (can be any format);

- ``__copyright__``: the licensing terms;

- ``__title__``: the title to use, if different from the basename of the
  program;

- ``__moredoc__``: additional description string for the doc generation only
  (i.e. not used upon --help invocation);

- ``__depends__``: list of dependencies that indicates the requirements to
  be able to run this script;

- ``__bugs__``: URI (URL or email) to report bugs to;

All of the variables are optional and the script still succeeds without them..
"""

__version__ = "Revision: 1.7 "
__author__ = "Martin Blais <blais@furius.ca>"
__depends__ = ['Python-2.3']
__copyright__ = """Copyright (C) 2003-2004 Martin Blais <blais@furius.ca>.
This code is distributed under the terms of the GNU General Public License."""

import sys, os, logging
from os.path import *
import re
import commands
import imp, compiler
from compiler.visitor import ASTVisitor
from compiler.ast import *


emptyre = re.compile('^\s*$')

def split_docstring(doc):
    """Split docstring in 1st line, abstract (first paragraph) and description
    (all but first line)."""
    doclines = doc.splitlines()
    usage = doclines[0]
    desclines = doclines[1:]

    # throw away empty lines at the beginning.
    for i in xrange(len(desclines)):
        if not emptyre.match(desclines[i]):
            break
    # accumulate abstract
    abslines = []
    for i in xrange(i, len(desclines)):
        l = desclines[i]
        if emptyre.match(l):
            break
        abslines.append(l)

    desc = os.linesep.join(desclines)
    return usage, abslines, desc


def parse_old(fn):
    """Parse the given file and return an dictionary which has all the desired
    attributes on it."""
    g = {}
    g['__name__'] = 'script' # do not invoke it as __main__
    g['__file__'] = fn
    execfile(fn, g)
    return g




class ModuleAttributeVisitor(object):
    "AST visitor that extracts the attributes that live at module level."

    def __init__(self):
        self.attributes = {}
        
    def visitModule(self, node):
        self.attributes['doc'] = node.doc

    def visitAssign(self, node):
        if (isinstance(node.nodes[0], AssName) and
            isinstance(node.expr, Const)):

            aname = node.nodes[0].name
            if not re.match('__(.*)__$', aname):
                return
            self.attributes[aname] = node.expr.value

    def visitFunction(self, node):
        pass # ignore children.


def parse(fn):
    mod = compiler.parseFile(fn)

    vis = ModuleAttributeVisitor()
    compiler.walk(mod, vis)

    return vis.attributes



def main():
    import optparse
    parser = optparse.OptionParser(__doc__.strip(), version=__version__)

    parser.add_option('-o', '--output', action='store',
                      help="Save output to given file.")
    parser.add_option('-l', '--link', action='store',
                      help="Add the given link to the program itself.")
    parser.add_option('-A', '--abstract-only', action='store_true',
                      help="Only output abstract.")

    global opts
    opts, args = parser.parse_args()

    if not args:
        raise SystemExit("Error: no files to process.")

    if opts.output:
        try:
            outf = open(opts.output, 'w')
        except IOError, e:
            raise SystemExit("Error: cannot open output file (%s)" % str(e))
    else:
        outf = sys.stdout

    for fn in args:
        g = parse(fn)

        if opts.abstract_only:
            output_abstract(fn, g, outf)
        else:
            output(fn, g, outf)

def output(fn, va, oss):
    "Perform output to ReST using the given context variables."

    class Dummy: pass
    v = Dummy()

    # Set important variables.
    for i in ['doc', 'moredoc', 'title',
              'author', 'version', 'copyright', 'depends', 'bugs']:
        setattr(v, i, va.get(i, None))

    # Set title from filename.
    if not v.title:
        v.title = basename(fn)

    # Split usage string.
    doclines = v.doc.splitlines()
    v.usage, v.abstract, v.desc = split_docstring(v.doc)

    # Extract options from dynamic help.
    o = commands.getoutput('%s --help' % fn)
    mre = re.compile('^options:$', re.M)
    mo = mre.search(o)
    if mo:
        v.options = o[ mo.end(): ]
    else:
        v.options = None

    #
    # Perform output.
    #
    savedout = sys.stdout
    sys.stdout = oss

    print '=' * len(v.title)
    print v.title
    print '=' * len(v.title)
    print

    if v.author:
        print ':Author:', v.author
    if v.version:
        print ':Version:', v.version
    if v.abstract:
        print ':Abstract:\n'
        for l in v.abstract:
            print '  ', l
        print

    print '\n.. contents:: Table of Contents'

    if v.desc:
        print """
Description
===========
"""
        print v.desc

    if v.moredoc:
        print """
"""
        print v.moredoc

    if v.usage:
        print """
Usage
=====

::
"""
        print '  ', v.usage

    if v.options:
        print """
Options
=======

::
"""
        print '  ', v.options

    print """
Please Donate!
==============

.. important::

   This computer program or library is provided for free.  I am aware that some
   of the programs that I provide for free allow people to get their work done
   faster or better.  If you are using this program for benefit, **especially if
   you are using it within a commercial environment and it saves you time or
   work**, please consider making a donation to my company's PayPal account by
   clicking on the link below.

   .. raw:: html

       <form action="https://www.paypal.com/cgi-bin/webscr" method="post">
       <input type="hidden" name="cmd" value="_s-xclick">
       <input type="image" src="https://www.paypal.com/en_US/i/btn/x-click-but04.gif" border="0" name="submit" alt="Make payments with PayPal - it's fast, free and secure!">
       <img alt="" border="0" src="https://www.paypal.com/en_US/i/scr/pixel.gif" width="1" height="1">
       <input type="hidden" name="encrypted" value="-----BEGIN PKCS7-----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-----END PKCS7-----
       ">
       </form>

"""

    if opts.link:
        print """
Download
========

`Download program here <%s>`_.
""" % opts.link

    if v.bugs:
        print """
`Reporting bugs <%s>`_.
""" % v.bugs

    if v.depends:
        print """
Requirements
============
"""
        for d in v.depends:
            print '- %s' % d

    if v.copyright:
        print """
Copyright
=========
"""
        print v.copyright

    sys.stdout = savedout




def output_abstract(fn, va, oss):
    """
    Perform output using the given context variables.
    """

    class Dummy: pass
    v = Dummy()

    # Set important variables.
    for i in ['doc']:
        setattr(v, i, va.get(i, None))

    # Split usage string.
    doclines = v.doc.splitlines()
    v.usage, v.abstract, v.desc = split_docstring(v.doc)

    #
    # Perform output.
    #
    savedout = sys.stdout
    sys.stdout = oss

    if v.abstract:
        for l in v.abstract:
            print l
    sys.stdout = savedout



if __name__ == '__main__':
    main()

