#!/usr/bin/env python
# Copyright (c) 2003-2006 ActiveState Software Inc.
#
# The MIT License
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files
# (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify,
# merge, publish, distribute, sublicense, and/or sell copies of the
# Software, and to permit persons to whom the Software is furnished
# to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
#
# Authors:
#    Shane Caraveo <ShaneC@ActiveState.com>
#    Trent Mick <TrentM@ActiveState.com>

"""
    pydbgp [dbgp_args_below] script.py [script_args]
    
    -d hostname:port  to debug a script
    -e script.py      preload and execute script before debugging
    -k ide_key        a IDE key used with proxies
    -m modules        comma deliminated list of modules to ignore
                      during debug session
    -i interactive    start debugger in interactive mode
                      if this is used in combination with a script, then
                      interactive mode will be entered when the script has
                      completed debugging
    -n                run without debugging.  This will start the debugger
                      if there is an exception.  It can also be used in
                      combination with -i to start the interactive shell when
                      the script has finished running.
    -p                perform code profiling
    -r                Do not redirect stdin to the IDE
    -l log_level      Logging levels from the logging module:
                        CRITICAL
                        ERROR
                        WARN
                        INFO
                        DEBUG
    -f logFile        Full path to write log messages to

"""
    
__version__ = (1, 1, 0)
__revision__ = "$Revision: #1 $ $Change: 118727 $"


import sys
import os

# Alternate environment variable for specifying dbgp location.  This
# allows python -E to work even if dbgp is not installed under
# site-packages
if "PYDBGP_PATH" in os.environ:
    sys.path.insert(0, os.environ['PYDBGP_PATH'])

import getopt
import socket
import types
import logging

is_v2 = sys.version_info[0] == 2
if is_v2:
    pythonlib = "pythonlib"
    UnicodeType = str
else:
    pythonlib = "python3lib"
    UnicodeType = str

def _get_dbgp_client_pythonlib_path():
    """Find the DBGP Python client library in the common install
    configuration. Returns None if it could not be found.
    """
    from os.path import dirname, join, abspath, exists
    try:
        this_dir = dirname(abspath(__file__))
    except NameError:
        this_dir = dirname(abspath(sys.argv[0]))
    candidate_paths = [
        dirname(this_dir), # Komodo source tree layout
        join(dirname(this_dir), pythonlib),
    ]
    for candidate_path in candidate_paths:
        landmark = join(candidate_path, "dbgp", "__init__.py")
        if exists(landmark):
            return candidate_path

_p = (not hasattr(sys, "frozen") and _get_dbgp_client_pythonlib_path() or None)
if _p: sys.path.insert(0, _p)
try:
    import dbgp.client
    from dbgp.client import log, h_main
    from dbgp.common import *
finally:
    if _p: del sys.path[0]



class IOStream:
    def __init__(self, origStream, encoding):
        self.__dict__['_origStream'] = origStream
        self.__dict__['_encoding'] = encoding
        self._encodeOnOutput = (sys.version_info[0] == 2)
    
    def write(self, s):
        global DBGPHideChildren
        origDBGPHideChildren = DBGPHideChildren
        DBGPHideChildren = DBGPDebugDebugger != DBGP_STOPPABLE_ALWAYS
        try:
            if self._encodeOnOutput and type(s)==UnicodeType:
                try:
                    s = s.encode(self._encoding)
                except:
                    pass
            self._origStream.write(s)
        finally:
            DBGPHideChildren = origDBGPHideChildren

    def writelines(self, lines):
        text = ''.join(lines)
        self.write(text)
        
    def __getattr__(self, attr):
        if attr in self.__dict__:
            return getattr(self,attr)
        return getattr(self._origStream, attr)

def _fixencoding():
    """If we're not run from a tty, force stdout to an encoding defined
    in LANG or to mbcs.  This is required to make python properly output
    unicode output, otherwise it just spits out an exception."""
    # based on logic found in Py_Initialize in pythonrun.c
    import locale
    codeset = locale.getdefaultlocale()[1]
    if codeset:
        try:
            import codecs
            secret_decoder_ring = codecs.lookup(codeset)
        except LookupError:
            if sys.platform.startswith('win'):
                codeset = 'mbcs'
            else:
                codeset = 'UTF-8'
        if not hasattr(sys.stdout, "isatty") or not sys.stdout.isatty():
            sys.stdout = IOStream(sys.stdout, codeset)
        if not hasattr(sys.stderr, "isatty") or not sys.stderr.isatty():
            sys.stderr = IOStream(sys.stdout, codeset)

def main(argv):
    logLevel = logging.WARN
    configureLogging(log, logLevel)
    _fixencoding()
    try:
        optlist, args = getopt.getopt(argv[1:], 'hVd:e:k:l:f:m:inpr',
            ['help', 'version', 'debug_port',
             'key', 'log_level', 'log_file', 'preload', 'modules',
             'interactive', 'nodebug', 'nostdin'])
    except getopt.GetoptError:
        msg = sys.exec_info()[0]
        sys.stderr.write("pydbgp: error: %s\n" % str(msg))
        sys.stderr.write("See 'pydbgp --help'.\n")
        return 1

    import locale
    codeset = locale.getdefaultlocale()[1]
    idekey = getenv('USER', getenv('USERNAME', ''))
    if is_v2:
        try:
            if codeset:
                idekey = idekey.decode(codeset)
            else:
                idekey = idekey.decode()
        except (UnicodeDecodeError, LookupError):
            log.warn("unable to decode idekey %r"%idekey)
            pass # nothing we can do if defaultlocale is wrong
    host = '127.0.0.1'
    port = 9000
    preloadScript = None
    ignoreModules = []
    profiling = 0
    interactive = 0
    nodebug = 0
    redirect = 1
    logFile = None
    for opt, optarg in optlist:
        if optarg and is_v2:
            try:
                if codeset:
                    optarg = optarg.decode(codeset)
                else:
                    optarg = optarg.decode()
            except (UnicodeDecodeError, LookupError):
                log.warn("unable to decode argument %s = %r"%(opt,optarg))
                pass # nothing we can do if defaultlocale is wrong
        if opt in ('-h', '--help'):
            sys.stdout.write(__doc__)
            return 0
        elif opt in ('-V', '--version'):
            import re
            kw = re.findall('\$(\w+):\s(.*?)\s\$', __revision__)
            sys.stderr.write("pydbgp Version %s %s %s %s %s\n"\
                             % ('.'.join([str(i) for i in __version__]),
                                kw[0][0], kw[0][1], kw[1][0], kw[1][1]))
            return 0
        elif opt in ('-d', '--debug_port'):
            if optarg.find(':') >= 0:
                host, port = optarg.split(':')
                port = int(port)
            else:
                host = '127.0.0.1'
                port = int(optarg)
        elif opt in ('-k', '--key'):
            idekey = optarg
        elif opt in ('-n', '--nodebug'):
            nodebug = 1
        elif opt in ('-l', '--log_level'):
            level_names = dict([ (logging.getLevelName(lvl), lvl) for lvl in
                                 range(logging.NOTSET, logging.CRITICAL+1, 10) ])
            # Add the levels that have multiple names.
            level_names['WARN'] = logging.WARNING
            level_names['FATAL'] = logging.FATAL
            try:
                logLevel = level_names[optarg]
            except KeyError:
                sys.stderr.write("pydbgp: error: Invalid log level\n")
                sys.stderr.write("See 'pydbgp --help'.\n")
                return 1
        elif opt in ('-f', '--log_file'):
            logFile = optarg
        elif opt in ('-e', '--preload'):
            preloadScript = optarg
        elif opt in ('-m', '--modules'):
            ignoreModules = optarg.split(',')
        elif opt in ('-p', '--profile', '--profiling'):
            profiling = 1
        elif opt in ('-i', '--interactive'):
            interactive = 1
        elif opt in ('-r', '--nostdin'):
            redirect = 0

    if not port:
        sys.stderr.write("pydbgp: error: IDE Port not provided\n")
        sys.stderr.write("See 'pydbgp --help'.\n")
        return 1
    
    if interactive:
        if not args:
            args = ['interactive']
            if sys.path[0] != '' and os.getcwd() not in sys.path:
                sys.path.insert(0, os.getcwd())

    if not args:
        sys.stderr.write("pydbgp: error: scriptname not provided\n")
        sys.stderr.write("See 'pydbgp --help'.\n")
        return 1
    
    # handle ~ paths
    if not interactive:
        args[0] = os.path.expanduser(args[0])
        args[0] = os.path.realpath(args[0])
        if not os.path.exists(args[0]):
            sys.stderr.write("pydbgp: error: scriptname %s does not exist\n" % (args[0],))
            sys.stderr.write("See 'pydbgp --help'.\n")
            return 1
        
    if nodebug:
        dbgp.client.runWithoutDebug(args, interactive, host, port, idekey, logLevel)
    elif profiling:
        dbgp.client.runWithProfiling(args, host, port, idekey, logLevel)
    else:
        if logFile:
            log.addHandler(logging.FileHandler(logFile))
            # Does not remove the existing default stderr handler.
        log.setLevel(logLevel)
        dbgp.client.set_thread_support(dbgp.client.backendCmd.debug_threads)
        client = dbgp.client.backendCmd(idekey, preloadScript, ignoreModules, module=h_main())
        client.stdin_enabled = redirect
        try:
            client.connect(host, port, '__main__', args)
        except socket.error:
            return 1
        if interactive and args[0] == 'interactive':
            cprt = 'Type "copyright", "credits" or "license" for more information.'
            sys.stdout.write("Python %s on %s\n%s\n" %
                       (sys.version, sys.platform, cprt))
            # wait until exit
            client.runInteractive()
        else:
            client.runMain(args, interactive)
    return 0

if __name__ == "__main__":
    sys.exit( main(sys.argv) )


