#!/usr/bin/env python
#
# Copyright (C) 2003 Stefan Seefeld
# All rights reserved.
# Licensed to the public under the terms of the GNU LGPL (>= 2),
# see the file COPYING for details.
#

from Synopsis import config
from Synopsis.getoptions import get_options
from Synopsis import IR
from Synopsis.Processor import Processor, Composite, Error
from Synopsis.Processors import *

import sys, os, os.path, getopt

def error(msg):
    """Write an error message and exit."""
    sys.stderr.write('Error: %s\n'%msg)
    sys.exit(-1)

def __import__(name, verbose):
    """Import a named processor. Instead of returning the module, this
    reimplementation returns the processor itself. Exits on error."""

    from Synopsis.import_processor import import_processor
    try:
        return import_processor(name)

    except ImportError, msg:
        if verbose:
            print 'Unable to import %s'%name
            print 'Reason :', msg
        else:
            print 'Error : no processor \'%s\''%'.'.join(name.split('.')[-2:])
        sys.exit(-1)

def usage():
    """Print a little usage text"""

    print 'Usage : %s [options] <input files>'%'synopsis'
    print """
List of options:
  -h, --help                  Display usage summary.
  -V, --version               Display version information.
  -v  --verbose               Operate verbosely.
  -d  --debug                 Operate in debug mode.
  -P  --profile               Profile execution.
  -o <file>, --output=<file>  Write output to <file>.
  -p <lang>, --parser=<lang>  Select a parser for <lang>.
  -Wp,<arg>[,<arg>...]        Send <args> to the parser.
  -t [<markup>]
    --translate[=<markup>]    Translate comments to doc-strings, processing it as
                                <markup> (typical values are 'javadoc' or 'rst').
  --cfilter=<filter>          Specify a comment filter.
  --cprocessor=<processor>    Specify a comment processor.
  -Wc,<arg>[,<arg>...]        Send <args> to the comment translator.
  -l                          Run the linker.
  -Wl,<arg>[,<arg>...]        Send <args> to the linker.
  --linker=<processor>        Link, and invoke <processor>.
  -f <type>,
    --formatter=<type>        Select a formatter for <type>.
  -Wf,<arg>[,<arg>...]        Send <args> to the formatter.
  -I <path>                   Add <path> to list of include paths.
  -D <macro>                  Add <macro> to list of predefined macros.
  -s <directory>,
    --sxr=<directory>         Specify sxr directory. If given, process source cross-references.
  --probe                     Probe the specified processor.
"""
   
def make_processor(argv):
    """Parse command line options and translate them to
    processor commands and arguments."""
    parser = None
    parser_opts = {}
    translator_opts = {}
    cfilters = {'ss': Comments.SSFilter,
                'sss': Comments.SSSFilter,
                'ssd': Comments.SSDFilter,
                'c': Comments.CFilter,
                'qt': Comments.QtFilter,
                'java': Comments.JavaFilter}
    cprocessors = {'previous': Comments.Previous,
                   'grouper': Comments.Grouper}
    linker = None
    linker_opts = {'processors':[]}
    formatter = None
    formatter_opts = {}
    options = {}
    help = False
    probe = False

    opts, args = getopt.getopt(argv,
                               'o:p:t:lf:s:I:D:W:jvhVdP',
                               ['output=',
                                'parser=', 'translate=', 'cfilter=', 'cprocessor=',
                                'linker=', 'formatter=', 'sxr=',
                                'version', 'help', 'verbose', 'debug', 'profile',
                                'probe'])
    
    for o, a in opts:
        if o in ['-V', '--version']:
            print 'synopsis version %s (revision %s)'%(config.version, config.revision)
            sys.exit(0)
         
        if o in ['-v', '--verbose']: options['verbose'] = True
        elif o in ['-d', '--debug']: options['debug'] = True
        elif o in ['-P', '--profile']: options['profile'] = True
        elif o in ['-o', '--output']: options['output'] = a

        elif o in ['-p', '--parser']:
            if parser:
                error('Multiple parsers specified.')
            parser = __import__('Synopsis.Parsers.%s.Parser'%a,
                                'verbose' in options)

        elif o in ['-t', '--translate']:
            if 'markup' in translator_opts:
                error('Multiple translators specified.')
            translator_opts['markup'] = a or ''

        elif o == '--cfilter':
            if a not in cfilters:
                error('%s is not a known comment filter.\n'
                      '\t possible values are : %s'
                      %(a, ', '.join([f for f in cfilters])))
            translator_opts['filter'] = cfilters[a]()

        elif o == '--cprocessor':
            if a not in cprocessors:
                error('%s is not a known comment processor.\n'
                      '\t possible values are : %s'
                      %(a, ', '.join([p for p in cprocessors])))
            if not translator_opts.get('processor'): translator_opts['processor'] = []
            translator_opts['processor'].append(cprocessors[a]())

        elif o == '-l':
            if not linker:
                linker = __import__('Synopsis.Processors.Linker',
                                    'verbose' in options)

        elif o == '--linker':
            if not linker:
                linker = __import__('Synopsis.Processors.Linker',
                                    'verbose' in options)
            linker_opts['processors'].extend([__import__('Synopsis.Processors.%s'%x,
                                                         'verbose' in options)()
                                              for x in a.split(',')])

        elif o in ['-f', '--formatter']:
            if formatter:
                error('Multiple formatters specified.')
            formatter = __import__('Synopsis.Formatters.%s.Formatter'%a,
                                   'verbose' in options)

        elif o == '-I':
            if not parser_opts.get('cppflags'): parser_opts['cppflags'] = []
            parser_opts['cppflags'].append('-I%s'%a)

        elif o == '-D':
            if not parser_opts.get('cppflags'): parser_opts['cppflags'] = []
            parser_opts['cppflags'].append('-D%s'%a)

        elif o == '-W':
            if a[0] == "p":
                for o,a in get_options(a[2:].split(','), expect_non_options = False):
                    parser_opts[o.replace('-', '_')] = a
            elif a[0] == "l":
                for o,a in get_options(a[2:].split(','), expect_non_options = False):
                    linker_opts[o.replace('-', '_')] = a
            elif a[0] == "f":
                for o,a in get_options(a[2:].split(','), expect_non_options = False):
                    formatter_opts[o.replace('-', '_')] = a

        elif o in ['-s', '--sxr']:
            parser_opts['sxr_prefix'] = a
            linker_opts['sxr_prefix'] = a
            formatter_opts['sxr_prefix'] = a

        elif o in ['-h', '--help']:
            help = True
        elif o == '--probe':
            probe = True

    if help:
        for p in parser, linker, formatter:
            if not p: continue
            processor = p()
            print "Parameters for processor '%s':"%p.__module__
            parameters = processor.get_parameters()
            tab = max(map(lambda x:len(x), parameters.keys()))
            for p in parameters:
                print "   %-*s     %s"%(tab, p, parameters[p].doc)
        if not (parser or linker or formatter):
            usage()
        sys.exit(0)

    # quick hack: if the parser is Cpp, rename the 'cppflags'
    # options to just 'flags'
    if parser and parser.__module__ == 'Synopsis.Parsers.Cpp':
        if parser_opts.has_key('cppflags'):
            parser_opts['flags'] = parser_opts['cppflags']
            del parser_opts['cppflags']

    if probe:
	if parser and parser.__module__ == 'Synopsis.Parsers.Cpp':
            cpp = parser(**parser_opts)
            info = cpp.probe()
            if not info:
                print 'Error: no compiler found'
            else:
                print 'Compiler:', info.compiler
                print 'Flags:', ', '.join(info.flags)
                print 'Language:', info.language
                print 'Header search path:\n  %s\n'%'\n  '.join(info.include_paths)
	        macros = [k + (v and '=%s'%v or '') for (k,v) in info.macros]

                print 'Macro definitions:\n  %s\n'%'\n  '.join(macros)
	else:
            print 'Only the Cpp processor supports probing at this time.'
	sys.exit(0)

    if not args:
        usage()
	sys.exit(0)

    if translator_opts.has_key('processor'):
	processor = translator_opts['processor']

    options['input'] = args

    #now instantiate the processor
    processors = []
    if parser:
        processors.append(parser(**parser_opts))
    if translator_opts:
        processors.append(Comments.Translator(**translator_opts))
    if linker:
        processors.append(linker(**linker_opts))
    if formatter:
        processors.append(formatter(**formatter_opts))

    return Composite(*processors, **options)

def main():
    processor = make_processor(sys.argv[1:])
    processor.process(IR.IR())

if __name__ == '__main__':

    try:
        main()
    except getopt.GetoptError, e:
        error(str(e))
    except Error, e:
        error(str(e))
    except KeyboardInterrupt, e:
        print 'KeyboardInterrupt'
    except IOError, e:
        error(str(e))
