#!/usr/bin/env python
#
#  $Id: synopsis,v 1.6 2003/01/20 06:43:02 chalky Exp $
#
#  This file is a part of Synopsis.
#  Copyright (C) 2000, 2001 Stefan Seefeld
#
#  Synopsis 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., 59 Temple Place - Suite 330, Boston, MA
#  02111-1307, USA.

import sys, getopt, os, os.path, string, stat, re
import cPickle

# Hack, I can't be stuffed finding places to change it :)
pickle = cPickle

from Synopsis.Core import Type, AST, Util
import Synopsis.Config

def version(): print "synopsis version 0.4.1"
cmdname = os.path.basename(sys.argv[0])
def usage():
    print "\nUsage:", cmdname, "[flags] files"
    print """
The supported flags are:

  -p,   --parser <parser>              Select a parser
  -Wp,<arg>[,<arg>...]                 Send args to the parser
  -Wl,<arg>[,<arg>...]                 Send args to the linker
  -f,   --formatter <formatter>        Select a formatter
  -Wf,<arg>[,<arg>...]                 Send args to the formatter
  -c,   --config <filename>            provide custom configuration through Config class in <filename>
  -Wc,<arg>[,<arg>...]                 Send args to the config module
  -I<path>                             Specify include path
  -D<macro>                            Specify macro for the parser
  -o <filename>                        Write output to <filename>
  -v,   --verbose                      Print debugging info
  -V,   --version                      Print version info then exit
  -h,   --help                         Print this usage message and exit
  -l    --list                         List all available modules and exit"""

help = 0
parser_cmd = ""
parser_args = []
linker_cmd = "Linker"
linker_args = []
formatter_cmd = ""
formatter_args = []
config_file = ""
config_args = {}
output = ""
verbose = 0
project_file = ""

def importParser(name):
    try:
        parser = Util._import("Synopsis.Parser." + name)
    except ImportError:
        sys.stderr.write(cmdname + ": Could not import parser `" + name + "'\n")
        sys.exit(1)
    return parser

def importLinker(name):
    try:
        linker = Util._import("Synopsis.Linker." + name)
    except ImportError:
        sys.stderr.write(cmdname + ": Could not import linker `" + name + "'\n")
        sys.exit(1)
    return linker

def importFormatter(name):
    try:
        formatter = Util._import("Synopsis.Formatter." + name)
    except ImportError:
        sys.stderr.write(cmdname + ": Could not import formatter `" + name + "'\n")
        sys.exit(1)
    return formatter

def listModules():
    """Lists all Parsers, Linkers and Formatters"""
    try:
	from Synopsis.Parser import Python
	parser = Python
    except ImportError:
	print "Warning: Unable to load Python parser so docstrings will not be printed."
	parser = None
    print "Synopsis: searching for installed modules..."
    listModulesFrom('Parser', parser, '^# THIS-IS-A-PARSER')
    listModulesFrom('Linker', parser, '^# THIS-IS-A-LINKER')
    listModulesFrom('Formatter', parser, '^# THIS-IS-A-FORMATTER')

def listModulesFrom(type, parser, needed_string):
    """Finds and lists modules of the given type. parser is passed to
    showModuleParser if it is not None"""
    re_needed = re.compile(needed_string, re.M)
    module = Util._import("Synopsis."+type)
    moddir = module.__path__[0]
    print "%ss:"%type
    files = os.listdir(moddir)
    for module in files:
	file = os.path.join(moddir, module)
	st = os.stat(file)
	if stat.S_ISDIR(st[stat.ST_MODE]):
	    file = os.path.join(file,'__init__.py')
	elif stat.S_ISREG(st[stat.ST_MODE]):
	    if file[-3:] != '.py': continue
	    if module == '__init__.py': continue
	    module = module[:-3]
	else:
	    continue
	try:
	    f = open(file, 'rt')
	    if re_needed.search(f.read()):
		f.seek(0)
		print " ",module
		if parser: showModuleSynopsis(f, parser)
	    f.close()
	except IOError:
	    # silently ignore as not-a-module
	    pass

def showModuleSynopsis(file, parser):
    """Shows the doc string for the given file object using parser"""
    doc = parser.get_synopsis(file)
    if not doc: return
    indent = "    "
    # Strip """'s
    if doc[:3] == '"""' and doc[-3:] == '"""':
	doc = doc[3:-3]
    # Strip surrounding whitespace
    doc = string.strip(doc)
    print indent+string.join(string.split(doc,'\n'),'\n'+indent)

def parseArgs(args):
    global parser_cmd, parser_args, linker_cmd, linker_args, formatter_cmd
    global formatter_args, config_file, config_args, output, verbose, help
    global project_file
    try:
        opts,files = Util.getopt_spec(args, "p:f:P:W:I:D:o:c:Vvhl", ["parser=", "formatter=", "config=", "version", "help", "list"])
    except getopt.error, e:
        sys.stderr.write("Error in arguments: " + str(e) + "\n")
        sys.stderr.write("Use `" + cmdname + " -h' for help\n")
        sys.exit(1)

    for opt in opts:
        o,a = opt

        if o == "-p" or o == "--parser":
            parser_cmd = a
	    config_args['parser'] = a

        elif o == "-f" or o == "--formatter":
            formatter_cmd = a
	    config_args['formatter'] = a
	
	elif o == "-P":
	    project_file = a

        elif o == "-W":
            if a[0] == "p":
                parser_args.extend(string.split(a[2:], ","))
            elif a[0] == "l":
                linker_args.extend(string.split(a[2:], ","))
            elif a[0] == "f":
                formatter_args.extend(string.split(a[2:], ","))
            elif a[0] == "c":
                args = string.split(a[2:], ",")
                #for i in range(0, (len(args)+1)/2): config_args[args[2*i]] = args[2*i+1]
		for arg in args:
		    key_value = string.split(arg, '=', 1)
		    if len(key_value) == 1:
			config_args[key_value[0]] = None
		    else:
			key, value = key_value
			config_args[key] = value
            else:
                sys.stderr.write("Error in arguments: option " + o + " not recognized\n")
                sys.stderr.write("Use " + cmdname + " -h for help\n")
                sys.exit(1)

        elif o == "-I":
            parser_args.append("-I" + a)
        elif o == "-D":
            parser_args.append("-D" + a)
        elif o == "-o":
            output = a
        elif o == "-c" or o == "--config":
            config_file = a
        elif o == "-v" or o == "--verbose":
            verbose = 1
            parser_args.append("-v")
            linker_args.append("-v")
            formatter_args.append("-v")
        elif o == "-V" or o == "--version":
            version()
            sys.exit(0)
        elif o == "-h" or o == "--help":
            help = 1
        elif o == "-l" or o == "--list":
            listModules()
            sys.exit(0)

    return files

def do_project_file(filename):
    from Synopsis.Core import Project, Executor
    proj = Project.Project()
    proj.load(filename)

    creator = Executor.ExecutorCreator(proj)
    formatter = proj.default_formatter()
    if config_args.has_key('formatter'):
        name = config_args['formatter']
        formatter = proj.actions().get_action(name)
        if not formatter:
            print "Fatal error: Specified formatter '%s' not found in project."%name
            sys.exit(1)
    ex = creator.create(formatter)
    ex.get_output(ex.get_output_names()[0][0])


def main(argv=None):
    global parser_cmd, parser_args, linker_cmd, linker_args, formatter_cmd, formatter_args, help

    if argv is None: argv = sys.argv

    #
    # parse command line arguments
    #
    files = parseArgs(argv[1:])
    if help:
        usage()
        if parser_cmd:
            parser = importParser(parser_cmd)
            if hasattr(parser, "usage"):
                print "\nArguments for parser `" + parser_cmd + "':\n"
                parser.usage()
        if linker_cmd:
            linker = importLinker(linker_cmd)
            if hasattr(linker, "usage"):
                print "\nArguments for linker `" + linker_cmd + "':\n"
                linker.usage()
        if formatter_cmd:
            formatter = importFormatter(formatter_cmd)
            if hasattr(formatter, "usage"):
                print "\nArguments for formatter `" + formatter_cmd + "':\n"
                formatter.usage()
        sys.exit(0)

    # Check for project file
    if project_file: return do_project_file(project_file)

    config = None
    if config_file:
        config = Util._import(config_file)
        config = config.Config(config_args)
    else:
	config = Synopsis.Config.Base(config_args)

    if len(files) == 0:
        sys.stderr.write(cmdname + ": No files specified\n")
        sys.exit(1)

    if parser_cmd != "": parser = importParser(parser_cmd)
    elif config and config.parser: parser = importParser(config.parser.name)
    #
    # real program start
    #
    ast = AST.AST()

    #
    # read files, either from pickled data base or from parser
    #
    for file in files:
        if file != "-" and not os.path.isfile(file):
            sys.stderr.write(cmdname + ": `" + file + "' does not exist\n")
            sys.exit(1)

        if parser_cmd == "" and not (config and config.parser):
            if verbose: print "Synopsis: loading", file

	    try:
		loaded_ast = AST.load(file)
	    except:
		print "Unable to load '%s'"
		print sys.exc_info()[1]
		sys.exit(1)

        else:
            if verbose: print "Synopsis: parsing", file
            loaded_ast = parser.parse(file, [], parser_args, config and config.parser)

	# The loaded or parsed AST is now in loaded_ast --> merge into 'ast'
	ast.merge(loaded_ast)

    #
    # resolve symbols here...
    if verbose: print "Synopsis: linking..."
    if config and config.linker: linker = importLinker(config.linker.name)
    else: linker = importLinker(linker_cmd)
    linker.resolve(linker_args, ast, config and config.linker)

    #
    # generate output, either with pickler or with a formatter
    #
    formatter = None
    if formatter_cmd: formatter = importFormatter(formatter_cmd)
    elif config and config.formatter: formatter = importFormatter(config.formatter.name)
    else:
        if verbose: print "Synopsis: writing file..."
        if output == "":
            sys.stderr.write(cmdname + ": no output file specified\n")
            sys.exit(1)
	dir = os.path.split(output)[0]
	if dir and not os.path.exists(dir):
	    if verbose: print "Warning: Creating directory",dir
	    os.makedirs(dir, 0755)
	AST.save(output, ast)
    if formatter:
        if verbose: print "Synopsis: formatting..."
        if output != "": formatter_args.extend(["-o",output])
        formatter.format(formatter_args, ast, config and config.formatter)

if __name__ == '__main__':
        main()

