##############################################################################
#
# Copyright (c) 2004 Zope Foundation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""STX Configuration Documentation Renderer

Usage: stxdocs.py [options]
Options:
    -h / --help
        Print this message and exit.

    -f <path>
        Specifies the root ZCML meta directives file, relative to the current
        location. All included files will be considered as well

    -o <dir>
        Specifies a directory, relative to the current location in which the
        documentation is stored. Note that this tool will create
        sub-directories with files in them. 
"""
# XXX This should be ripped out of the package.
import sys, os, getopt
import zope.configuration
from zope.schema import getFieldsInOrder 
from zope.configuration import config, xmlconfig
from zope.configuration.docutils import wrap, makeDocStructures

def usage(code, msg=''): #pragma NO COVER
    # Python 2.1 required
    print >> sys.stderr, __doc__
    if msg:
        print >> sys.stderr, msg
    sys.exit(code)

def _directiveDocs(name, schema, handler, info, indent_offset=0): #pragma NO COVER
    """Generate the documentation for one directive."""

    # Write out the name of the directive
    text  = ' '*indent_offset
    text +=  '%s\n\n' %name

    # Specify the file and location it has been declared
    if isinstance(info, xmlconfig.ParserInfo):
        # We do not want to specify the whole path; starting at the 'zope'
        # package is enough.
        base_dir = os.path.dirname(os.path.dirname(
            zope.configuration.__file__))[:-4]
        file = info.file.replace(base_dir, '')

        info_text = 'File %s, lines %i - %i.' %(file, info.line, info.eline)
        text += wrap(info_text, 78, indent_offset+2)

    elif isinstance(info, (str, unicode)) and info:
        text += wrap(info, 78, indent_offset+2)

    # Insert Handler information
    if handler is not None:
        handler_path = handler.__module__ + '.' + handler.__name__
        text += wrap('Handler: %s' %handler_path, 78, indent_offset+2)

    # Use the schema documentation string as main documentation text for the
    # directive.
    text += wrap(schema.getDoc(), 78, indent_offset+2)
    text += ' '*indent_offset + '  Attributes\n\n'

    # Create directive attribute documentation
    for name, field in getFieldsInOrder(schema):
        name = name.strip('_')
        if field.required:
            opt = 'required'
        else:
            opt = 'optional, default=%s' %repr(field.default)
        text += ' '*indent_offset
        text += '    %s -- %s (%s)\n\n' %(name, field.__class__.__name__, opt)

        text += wrap(field.title, 78, indent_offset+6)
        text += wrap(field.description, 78, indent_offset+6)

    return text

def _subDirectiveDocs(subdirs, namespace, name): #pragma NO COVER
    """Appends a list of sub-directives and their full specification."""
    if subdirs.has_key((namespace, name)):
        text = '\n  Subdirectives\n\n'
        sub_dirs = []
        # Simply walk through all sub-directives here.
        subs = subdirs[(namespace, name)]
        for sd_ns, sd_name, sd_schema, sd_handler, sd_info in subs:
            sub_dirs.append(_directiveDocs(
                sd_name, sd_schema, sd_handler, sd_info, 4))

        return text + '\n\n'.join(sub_dirs)
    return ''

def makedocs(target_dir, zcml_file): #pragma NO COVER
    """Generate the documentation tree.

    All we need for this is a starting ZCML file and a directory in which to
    put the documentation.
    """
    context = xmlconfig.file(zcml_file, execute=False)
    namespaces, subdirs = makeDocStructures(context)

    for namespace, directives in namespaces.items():
        ns_dir = os.path.join(target_dir, namespace.split('/')[-1])
        # Create a directory for the namespace, if necessary
        if not os.path.exists(ns_dir):
            os.mkdir(ns_dir)

        # Create a file for each directive
        for name, (schema, handler, info) in directives.items():
            dir_file = os.path.join(ns_dir, name+'.stx')
            text = _directiveDocs(name, schema, handler, info)
            text += _subDirectiveDocs(subdirs, namespace, name)
            open(dir_file, 'w').write(text)

def _makeabs(path): #pragma NO COVER
    """Make an absolute path from the possibly relative path."""
    if not path == os.path.abspath(path):
        cwd = os.getcwd()
        # This is for symlinks.
        if os.environ.has_key('PWD'):
            cwd = os.environ['PWD']
        path = os.path.normpath(os.path.join(cwd, path))    
    return path

def main(argv=sys.argv): #pragma NO COVER
    try:
        opts, args = getopt.getopt(
            sys.argv[1:],
            'h:f:o:',
            ['help'])
    except getopt.error, msg:
        usage(1, msg)

    zcml_file = None
    output_dir = None
    for opt, arg in opts:
        if opt in ('-h', '--help'):
            usage(0)
        elif opt in ('-o', ):
            output_dir = arg
        elif opt in ('-f', ):
            zcml_file = _makeabs(arg)
            if not os.path.exists(zcml_file):
                usage(1, 'The specified zcml file does not exist.')

    if zcml_file is None or output_dir is None:
        usage(0, "Both, the '-f' and '-o' option are required")

    # Generate the docs
    makedocs(output_dir, zcml_file)

if __name__ == '__main__': #pragma NO COVER
    main()
