# -*- coding: utf-8 -*-
#   Copyright (C) 2009, 2013-2014, 2016 Rocky Bernstein <rocky@gnu.org>
#
#   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 3 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, see <http://www.gnu.org/licenses/>.
"""Signal handlers."""
# TODO:
#  - Doublecheck handle_pass and other routines.
#  - can remove signal handler altogether when
#         ignore=True, print=False, pass=True
#
#
import signal

def YN(b):
    """Return 'Yes' for True and 'No' for False, and ?? for anything
    else."""
    if type(b) != bool:
        return "??"
    if b:
        return "Yes"
    return "No"

def lookup_signame(num):
    """Find the corresponding signal name for 'num'. Return None
    if 'num' is invalid."""
    signames = signal.__dict__
    num = abs(num)
    for signame in list(signames.keys()):
        if signame.startswith('SIG') and signames[signame] == num:
            return signame
        pass
    # Something went wrong. Should have returned above
    return None

def lookup_signum(name):
    """Find the corresponding signal number for 'name'. Return None
    if 'name' is invalid."""
    uname = name.upper()
    if (uname.startswith('SIG') and hasattr(signal, uname)):
        return getattr(signal, uname)
    else:
        uname = "SIG"+uname
        if hasattr(signal, uname):
            return getattr(signal, uname)
        return None
    return  # Not reached

def canonic_signame(name_num):
    """Return a signal name for a signal name or signal
    number.  Return None is name_num is an int but not a valid signal
    number and False if name_num is a not number. If name_num is a
    signal name or signal number, the canonic if name is returned."""
    signum = lookup_signum(name_num)
    if signum is None:
        # Maybe signame is a number?
        try:
            num = int(name_num)
            signame = lookup_signame(num)
            if signame is None:
                return None
        except:
            return False
        return signame

    signame = name_num.upper()
    if not signame.startswith('SIG'): return 'SIG'+signame
    return signame

fatal_signals = ['SIGKILL', 'SIGSTOP']

# I copied these from GDB source code.
signal_description = {
  "SIGHUP"    : "Hangup",
  "SIGINT"    : "Interrupt",
  "SIGQUIT"   : "Quit",
  "SIGILL"    : "Illegal instruction",
  "SIGTRAP"   : "Trace/breakpoint trap",
  "SIGABRT"   : "Aborted",
  "SIGEMT"    : "Emulation trap",
  "SIGFPE"    : "Arithmetic exception",
  "SIGKILL"   : "Killed",
  "SIGBUS"    : "Bus error",
  "SIGSEGV"   : "Segmentation fault",
  "SIGSYS"    : "Bad system call",
  "SIGPIPE"   : "Broken pipe",
  "SIGALRM"   : "Alarm clock",
  "SIGTERM"   : "Terminated",
  "SIGURG"    : "Urgent I/O condition",
  "SIGSTOP"   : "Stopped (signal)",
  "SIGTSTP"   : "Stopped (user)",
  "SIGCONT"   : "Continued",
  "SIGCHLD"   : "Child status changed",
  "SIGTTIN"   : "Stopped (tty input)",
  "SIGTTOU"   : "Stopped (tty output)",
  "SIGIO"     : "I/O possible",
  "SIGXCPU"   : "CPU time limit exceeded",
  "SIGXFSZ"   : "File size limit exceeded",
  "SIGVTALRM" : "Virtual timer expired",
  "SIGPROF"   : "Profiling timer expired",
  "SIGWINCH"  : "Window size changed",
  "SIGLOST"   : "Resource lost",
  "SIGUSR1"   : "User-defined signal 1",
  "SIGUSR2"   : "User-defined signal 2",
  "SIGPWR"    : "Power fail/restart",
  "SIGPOLL"   : "Pollable event occurred",
  "SIGWIND"   : "SIGWIND",
  "SIGPHONE"  : "SIGPHONE",
  "SIGWAITING": "Process's LWPs are blocked",
  "SIGLWP"    : "Signal LWP",
  "SIGDANGER" : "Swap space dangerously low",
  "SIGGRANT"  : "Monitor mode granted",
  "SIGRETRACT": "Need to relinquish monitor mode",
  "SIGMSG"    : "Monitor mode data available",
  "SIGSOUND"  : "Sound completed",
  "SIGSAK"    : "Secure attention"
  }

class SignalManager:
    """Manages Signal Handling information for the debugger

    - Do we print/not print when signal is caught
    - Do we pass/not pass the signal to the program
    - Do we stop/not stop when signal is caught

    Parameter dbgr is a Debugger object. ignore is a list of
    signals to ignore. If you want no signals, use [] as None uses the
    default set. Parameter default_print specifies whether or not we
    print receiving a signals that is not ignored.

    All the methods which change these attributes return None on error, or
    True/False if we have set the action (pass/print/stop) for a signal
    handler.
    """
    def __init__(self, dbgr, ignore_list=None, default_print=True):
        self.dbgr    = dbgr
        # dbgr.core.add_ignore(SigHandler.handle)
        self.sigs    = {}

        # List of signals. Dunno why signal doesn't provide.
        self.siglist = []

        # Ignore signal handling initially for these known signals.
        if ignore_list is None:
            ignore_list = ['SIGALRM',    'SIGCHLD',  'SIGURG',
                           'SIGIO',      'SIGCLD',
                           'SIGVTALRM'   'SIGPROF',  'SIGWINCH',  'SIGPOLL',
                           'SIGWAITING', 'SIGLWP',   'SIGCANCEL', 'SIGTRAP',
                           'SIGTERM',    'SIGQUIT',  'SIGILL',
                           # Wierd stuff from 3.3
                           'SIG_SETMASK', 'ITIMER_PROF', 'ITIMER_VIRTUAL',
                           'ITIMER_REAL', 'ITIMER_PROF',
                           'SIG_BLOCK', 'SIG_UNBLOCK'
                           ]  # NOQA
        self.ignore_list = ignore_list
        self._orig_set_signal  = signal.signal
        signal.signal = self.set_signal_replacement

        self.info_fmt='%-14s%-4s\t%-4s\t%-5s\t%-4s\t%s'
        self.header  = self.info_fmt % ('Signal', 'Stop', 'Print',
                                        'Stack', 'Pass',
                                        'Description')

        if default_print: default_print = self.dbgr.intf[-1].msg

        for signame in list(signal.__dict__.keys()):
            # Look for a signal name on this os.
            if signame.startswith('SIG') and '_' not in signame:
                self.siglist.append(signame)
                self.initialize_handler(signame)
            pass
        self.action('SIGINT stop print nostack nopass')
        return

    def initialize_handler(self, signame):
        if signame in fatal_signals: return False
        signum = lookup_signum(signame)
        if signum is None: return False

        try:
            old_handler = signal.getsignal(signum)
        except ValueError:
            # On some OS's (Redhat 8), SIGNUM's are listed (like
            # SIGRTMAX) that getsignal can't handle.
            old_handler = None
            if signame in self.sigs:
                self.sigs.pop(signame)
                pass

        if signame in self.ignore_list:
            self.sigs[signame] = SigHandler(self.dbgr, signame, signum,
                                            old_handler,
                                            None, False,
                                            print_stack=False,
                                            pass_along=True)
        else:
            self.sigs[signame] = SigHandler(self.dbgr, signame, signum,
                                            old_handler,
                                            self.dbgr.intf[-1].msg,
                                            True,
                                            print_stack=False,
                                            pass_along=False)
            pass
        return True

    def set_signal_replacement(self, signum, handle):
        """A replacement for signal.signal which chains the signal behind
        the debugger's handler"""
        signame = lookup_signame(signum)
        if signame is None:
            self.dbgr.intf[-1].errmsg(("%s is not a signal number"
                                       " I know about.")  % signum)
            return False
        # Since the intent is to set a handler, we should pass this
        # signal on to the handler
        self.sigs[signame].pass_along = True
        if self.check_and_adjust_sighandler(signame, self.sigs):
            self.sigs[signame].old_handler = handle
            return True
        return False

    def check_and_adjust_sighandler(self, signame, sigs):
        """
        Check to see if a single signal handler that we are interested
        in has changed or has not been set initially. On return
        self.sigs[signame] should have our signal handler. True is
        returned if the same or adjusted, False or None if error or
        not found.
        """

        signum = lookup_signum(signame)

        try:
            old_handler = signal.getsignal(signum)
        except ValueError:
            # On some OS's (Redhat 8), SIGNUM's are listed (like
            # SIGRTMAX) that getsignal can't handle.
            if signame in self.sigs:
                sigs.pop(signame)
                pass
            return None
        if old_handler != self.sigs[signame].handle:
            if old_handler not in [signal.SIG_IGN, signal.SIG_DFL]:
                # save the program's signal handler
                sigs[signame].old_handler = old_handler
                pass
            # set/restore _our_ signal handler
            try:
                # signal.signal(signum, self.sigs[signame].handle)
                self._orig_set_signal(signum, self.sigs[signame].handle)
            except ValueError:
                # Probably not in main thread
                return False
            except KeyError:
                # May be weird keys from 3.3
                return False
            pass
        return True

    def check_and_adjust_sighandlers(self):
        """Check to see if any of the signal handlers we are interested in have
        changed or is not initially set. Change any that are not right. """
        for signame in list(self.sigs.keys()):
            if not self.check_and_adjust_sighandler(signame, self.sigs):
                break
            pass
        return

    def is_name_or_number(self, name_num):
        signame = canonic_signame(name_num)
        if signame is None:
            self.dbgr.intf[-1].errmsg(("%s is not a signal number" +
                                       " I know about.")  % name_num)
            return False
        elif not signame:
            self.dbgr.intf[-1].errmsg(("%s is not a signal name I " +
                                       "know about.") % name_num)
            return False
        return signame

    def print_info_signal_entry(self, signame):
        """Print status for a single signal name (signame)"""
        if signame in signal_description:
            description=signal_description[signame]
        else:
            description=""
            pass
        if signame not in list(self.sigs.keys()):
            # Fake up an entry as though signame were in sigs.
            self.dbgr.intf[-1].msg(self.info_fmt
                                   % (signame, 'No', 'No', 'No', 'Yes',
                                      description))
            return

        sig_obj = self.sigs[signame]
        self.dbgr.intf[-1].msg(self.info_fmt %
                               (signame,
                                YN(sig_obj.b_stop),
                                YN(sig_obj.print_method is not None),
                                YN(sig_obj.print_stack),
                                YN(sig_obj.pass_along),
                                description))
        return

    def info_signal(self, args):
        """Print information about a signal"""
        if len(args) == 0: return None
        signame = args[0]
        if signame in ['handle', 'signal']:
            # This has come from dbgr's info command
            if len(args) == 1:
                # Show all signal handlers
                self.dbgr.core.processor.section(self.header)
                for signame in self.siglist:
                    self.print_info_signal_entry(signame)
                return True
            else:
                signame = args[1]
                pass
            pass

        signame = self.is_name_or_number(signame)
        self.dbgr.core.processor.section(self.header)
        self.print_info_signal_entry(signame)
        return True

    def action(self, arg):
        """Delegate the actions specified in 'arg' to another
        method.
        """
        if not arg:
            self.info_signal(['handle'])
            return True
        args = arg.split()
        signame = args[0]
        signame = self.is_name_or_number(args[0])
        if not signame: return

        if len(args) == 1:
            self.info_signal([signame])
            return True
        # We can display information about 'fatal' signals, but not
        # change their actions.
        if signame in fatal_signals:
            return None

        if signame not in list(self.sigs.keys()):
            if not self.initialize_handler(signame): return None
            pass

        # multiple commands might be specified, i.e. 'nopass nostop'
        for attr in args[1:]:
            if attr.startswith('no'):
                on = False
                attr = attr[2:]
            else:
                on = True
            if 'stop'.startswith(attr):
                self.handle_stop(signame, on)
            elif 'print'.startswith(attr) and len(attr) >= 2:
                self.handle_print(signame, on)
            elif 'pass'.startswith(attr):
                self.handle_pass(signame, on)
            elif 'ignore'.startswith(attr):
                self.handle_ignore(signame, on)
            elif 'stack'.startswith(attr):
                self.handle_print_stack(signame, on)
            else:
                self.dbgr.intf[-1].errmsg('Invalid arguments')
                pass
            pass
        return self.check_and_adjust_sighandler(signame, self.sigs)

    def handle_print_stack(self, signame, print_stack):
        """Set whether we stop or not when this signal is caught.
        If 'set_stop' is True your program will stop when this signal
        happens."""
        self.sigs[signame].print_stack = print_stack
        return print_stack

    def handle_stop(self, signame, set_stop):
        """Set whether we stop or not when this signal is caught.
        If 'set_stop' is True your program will stop when this signal
        happens."""
        if set_stop:
            self.sigs[signame].b_stop       = True
            # stop keyword implies print AND nopass
            self.sigs[signame].print_method = self.dbgr.intf[-1].msg
            self.sigs[signame].pass_along   = False
        else:
            self.sigs[signame].b_stop       = False
            pass
        return set_stop

    def handle_pass(self, signame, set_pass):
        """Set whether we pass this signal to the program (or not)
        when this signal is caught. If set_pass is True, Dbgr should allow
        your program to see this signal.
        """
        self.sigs[signame].pass_along = set_pass
        if set_pass:
            # Pass implies nostop
            self.sigs[signame].b_stop = False
            pass
        return set_pass

    def handle_ignore(self, signame, set_ignore):
        """'pass' and 'noignore' are synonyms. 'nopass and 'ignore' are
        synonyms."""
        self.handle_pass(signame, not set_ignore)
        return set_ignore

    def handle_print(self, signame, set_print):
        """Set whether we print or not when this signal is caught."""
        if set_print:
            self.sigs[signame].print_method = self.dbgr.intf[-1].msg
        else:
            self.sigs[signame].print_method = None
            pass
        return set_print
    pass

class SigHandler:
    """Store information about what we do when we handle a signal,

    - Do we print/not print when signal is caught
    - Do we pass/not pass the signal to the program
    - Do we stop/not stop when signal is caught

    Parameters:
       signame : name of signal (e.g. SIGUSR1 or USR1)
       print_method routine to use for "print"
       stop routine to call to invoke debugger when stopping
       pass_along: True is signal is to be passed to user's handler
    """
    def __init__(self, dbgr, signame, signum, old_handler,
                 print_method, b_stop,
                 print_stack=False, pass_along=True):

        self.dbgr         = dbgr
        self.old_handler  = old_handler
        self.pass_along   = pass_along
        self.print_method = print_method
        self.print_stack  = print_stack
        self.signame      = signame
        self.signum       = signum
        self.b_stop       = b_stop
        return

    def handle(self, signum, frame):
        """This method is called when a signal is received."""
        if self.print_method:
            self.print_method('\nProgram received signal %s.'
                              % self.signame)
        if self.print_stack:
            import traceback
            strings = traceback.format_stack(frame)
            for s in strings:
                if s[-1] == '\n': s = s[0:-1]
                self.print_method(s)
                pass
            pass
        if self.b_stop:
            core = self.dbgr.core
            old_trace_hook_suspend = core.trace_hook_suspend
            core.trace_hook_suspend = True
            core.stop_reason = ('intercepting signal %s (%d)' %
                                (self.signame, signum))
            core.processor.event_processor(frame, 'signal', signum)
            core.trace_hook_suspend = old_trace_hook_suspend
            pass
        if self.pass_along:
            # pass the signal to the program
            if self.old_handler:
                self.old_handler(signum, frame)
                pass
            pass
        return
    pass

# When invoked as main program, do some basic tests of a couple of functions
if __name__=='__main__':
    import trepan.inout
    import trepan.processor.command
    import trepan.interfaces
    for b in (True, False,):
        print('YN of %s is %s' % (repr(b), YN(b)))
        pass
    for signum in range(signal.NSIG):
        signame = lookup_signame(signum)
        if signame is not None:
            if not signame.startswith('SIG'): continue
            print(signame, signum, lookup_signum(signame))
            assert(signum == lookup_signum(signame))
            # Try without the SIG prefix
            assert(signum == lookup_signum(signame[3:]))
            pass
        pass

    for i in (15, -15, 300):
        print('lookup_signame(%d): %s' % (i, lookup_signame(i)))
        pass

    for i in ('term', 'TERM', 'NotThere'):
        print('lookup_signum(%s): %s' % (i, repr(lookup_signum(i))))
        pass

    for i in ('15', '-15', 'term', 'sigterm', 'TERM', '300', 'bogus'):
        print('canonic_signame(%s): %s' % (i, canonic_signame(i)))
        pass

    from trepan import debugger as Mdebugger
    dbgr = Mdebugger.Trepan()
    h = SignalManager(dbgr)
    h.info_signal(["TRAP"])
    # Set to known value
    h.action('SIGUSR1')
    h.action('usr1 print pass stop')
    h.info_signal(['USR1'])
    # noprint implies no stop
    h.action('SIGUSR1 noprint')
    h.info_signal(['USR1'])
    h.action('foo nostop')
    # stop keyword implies print
    h.action('SIGUSR1 stop')
    h.info_signal(['SIGUSR1'])
    h.action('SIGUSR1 noprint')
    h.info_signal(['SIGUSR1'])
    h.action('SIGUSR1 nopass stack')
    h.info_signal(['SIGUSR1'])
    pass


# -*- coding: utf-8 -*-
#   Copyright (C) 2008-2009, 2013 Rocky Bernstein <rocky@gnu.org>
#
#   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 3 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, see <http://www.gnu.org/licenses/>.
import os, linecache
import os.path as osp

# FIXME: do a better job of this. Live parsing?
def is_ok_line_for_breakpoint(filename, lineno, errmsg_fn):
    """Check whether specified line seems to be executable.

    Return `lineno` if it is, 0 if not (e.g. a docstring, comment, blank
    line or EOF). Warning: testing is not comprehensive.
    """
    line = linecache.getline(filename, lineno)
    if not line:
        errmsg_fn('End of file')
        return False
    line = line.strip()
    # Don't allow setting breakpoint at a blank line
    if (not line or (line[0] == '#') or
         (line[:3] == '"""') or line[:3] == "'''"):
        errmsg_fn('Blank or comment')
        return False
    return True

def file2module(filename):
    """Given a file name, extract the most likely module name. """
    basename = osp.basename(filename)
    if '.' in basename:
        pos = basename.rfind('.')
        return basename[:pos]
    else:
        return basename
    return None

def search_file(filename, directories, cdir):
    """Return a full pathname for filename if we can find one. path
    is a list of directories to prepend to filename. If no file is
    found we'll return None"""

    for trydir in directories:

        # Handle $cwd and $cdir
        if trydir =='$cwd': trydir='.'
        elif trydir == '$cdir': trydir = cdir

        tryfile = osp.realpath(osp.join(trydir, filename))
        if osp.isfile(tryfile):
            return tryfile
    return None

def whence_file(py_script, dirnames=None):
    """Do a shell-like path lookup for py_script and return the results.
    If we can't find anything return py_script"""
    if py_script.find(os.sep) != -1:
        # Don't search since this name has path separator components
        return py_script
    if dirnames is None:
        dirnames = os.environ['PATH'].split(os.pathsep)
    for dirname in dirnames:
        py_script_try = osp.join(dirname, py_script)
        if osp.exists(py_script_try):
            return py_script_try
    # Failure
    return py_script

def path_expanduser_abs(filename):
    return os.path.abspath(os.path.expanduser(filename))

# Demo
if __name__=='__main__':
    import sys
    print(file2module(sys.argv[0]), sys.argv[0])
    ok = is_ok_line_for_breakpoint(__file__, 1, sys.stdout.write)
    print("\nCan stop at line 1? ", ok)
    ok = is_ok_line_for_breakpoint(__file__, 2, sys.stdout.write)
    print("\nCan stop at line 2? ", ok)
    print(path_expanduser_abs("./.trepan3krc"))
    print(path_expanduser_abs("~/.trepan3krc"))


# -*- coding: utf-8 -*-
#  Copyright (C) 2007-2010, 2015 Rocky Bernstein

#  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 3 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, see <http://www.gnu.org/licenses/>.

import inspect, types

def print_dict(s, obj, title):
    if hasattr(obj, "__dict__"):
        obj = obj.__dict__
        pass
    if isinstance(obj, dict):
        s += "\n%s:\n" % title
        keys = list(obj.keys())
        keys.sort()
        for key in keys:
            s+="  %s:\t%s\n" % (repr(key), obj[key])
            pass
        pass
    return s

def print_argspec(obj, obj_name):
    '''A slightly decorated version of inspect.format_argspec'''
    try:
        return obj_name + inspect.formatargspec(*inspect.getargspec(obj))
    except:
        return None
    return  # Not reached

def print_obj(arg, frame, format=None, short=False):
    """Return a string representation of an object """
    try:
        if not frame:
            # ?? Should we have set up a dummy globals
            # to have persistence?
            obj = eval(arg, None, None)
        else:
            obj = eval(arg, frame.f_globals, frame.f_locals)
            pass
    except:
        return 'No symbol "' + arg + '" in current context.'
    # format and print
    what = arg
    if format:
        what = format + ' ' + arg
        obj = printf(obj, format)
    s = '%s = %s' % (what, obj)
    if not short:
        s += '\ntype = %s' % type(obj)
        if callable(obj):
            argspec = print_argspec(obj, arg)
            if argspec:
                s += ':\n\t'
                if inspect.isclass(obj):
                    s += 'Class constructor information:\n\t'
                    obj = obj.__init__
                elif isinstance(obj, types.InstanceType):
                    obj = obj.__call__
                    pass
                s+= argspec
            pass

        # Try to list the members of a class.
        # Not sure if this is correct or the
        # best way to do.
        s = print_dict(s, obj, "object variables")
        if hasattr(obj, "__class__"):
            s = print_dict(s, obj.__class__, "class variables")
            pass
    return s

pconvert = {'c': chr, 'x': hex, 'o': oct, 'f': float, 's': str}
twos = ('0000', '0001', '0010', '0011', '0100', '0101', '0110', '0111',
        '1000', '1001', '1010', '1011', '1100', '1101', '1110', '1111')

def printf(val, fmt):
    global pconvert, twos
    if not fmt:
        fmt = ' '  # not 't' nor in pconvert
    # Strip leading '/'
    if fmt[0] == '/':
        fmt = fmt[1:]
    f = fmt[0]
    if f in pconvert.keys():
        try:
            return pconvert[f](val)
        except:
            return str(val)
    # binary (t is from 'twos')
    if f == 't':
        try:
            res = ''
            while val:
                res = twos[val & 0xf] + res
                val = val >> 4
            return res
        except:
            return str(val)
    return str(val)

if __name__ == '__main__':
    print(print_dict('', globals(), 'my globals'))
    print('-' * 40)
    print(print_obj('print_obj', None))
    print('-' * 30)
    print(print_obj('Exception', None))
    print('-' * 30)
    print(print_argspec('Exception', None))

    class Foo:
        def __init__(self, bar=None): pass
        pass
    print(print_obj('Foo.__init__', None))
    print('-' * 30)
    print(print_argspec(Foo.__init__, '__init__'))
    assert printf(31, "/o") == '037'
    assert printf(31, "/t") == '00011111'
    assert printf(33, "/c") == '!'
    assert printf(33, "/x") == '0x21'


# -*- coding: utf-8 -*-
#   Copyright (C) 2008-2010, 2013-2015 Rocky Bernstein <rocky@gnu.org>
#
#   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 3 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, see <http://www.gnu.org/licenses/>.

def option_set(options, value, default_options):
    if not options or value not in options:
        return default_options.get(value)
    else:
        return options.get(value)
    return None  # Not reached

def bool2YN(b):
    if b: return 'Y'
    else: return 'N'

def wrapped_lines(msg_part1, msg_part2, width):
    if len(msg_part1) + len(msg_part2) + 1 > width:
        return msg_part1 + "\n\t" + msg_part2
    else:
        return msg_part1 + " " + msg_part2
    return  # Not reached

import os
from glob import glob

def pyfiles(callername, level=2):
    "All python files caller's dir without the path and trailing .py"
    d = os.path.dirname(callername)
    # Get the name of our directory.
    # A glob pattern that will get all *.py files but not __init__.py
    glob(os.path.join(d, '[a-zA-Z]*.py'))
    py_files = glob(os.path.join(d, '[a-zA-Z]*.py'))
    return [ os.path.basename(filename[0:-3]) for filename in py_files ]

# Demo it
if __name__=='__main__':
    TEST_OPTS = {'a': True, 'b': 5, 'c': None}
    get_option = lambda key: option_set(opts, key, TEST_OPTS)
    opts={'d': 6, 'a': False}
    for opt in ['a', 'b', 'c', 'd']:
        print(opt, get_option(opt))
        pass
    for b in [True, False]:
        print(bool2YN(b))
    pass

    print(wrapped_lines('hi', 'there', 80))
    print(wrapped_lines('hi', 'there', 5))
    print(pyfiles(__file__))
    pass


# -*- coding: utf-8 -*-
#   Copyright (C) 2008-2010, 2013-2016 Rocky Bernstein <rocky@gnu.org>
#
#   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 3 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, see <http://www.gnu.org/licenses/>.
import inspect, linecache, sys, traceback
import pyficache
from reprlib import Repr

from trepan import vprocessor as Mprocessor
from trepan import exception as Mexcept, misc as Mmisc
from trepan.lib import bytecode as Mbytecode, display as Mdisplay
from trepan.lib import thred as Mthread
from trepan.bwprocessor import location as Mlocation, msg as Mmsg

def get_stack(f, t, botframe, proc_obj=None):
    """Return a stack of frames which the debugger will use for in
    showing backtraces and in frame switching. As such various frame
    that are really around may be excluded unless we are debugging the
    sebugger. Also we will add traceback frame on top if that
    exists."""
    exclude_frame = lambda f: False
    if proc_obj:
        settings = proc_obj.debugger.settings
        if not settings['dbg_trepan']:
            exclude_frame = lambda f: \
                proc_obj.core.ignore_filter.is_included(f)
            pass
        pass
    stack = []
    if t and t.tb_frame is f:
        t = t.tb_next
    while f is not None:
        if exclude_frame(f): break  # See commented alternative below
        stack.append((f, f.f_lineno))
        # bdb has:
        # if f is botframe: break
        f = f.f_back
        pass
    stack.reverse()
    i = max(0, len(stack) - 1)
    while t is not None:
        stack.append((t.tb_frame, t.tb_lineno))
        t = t.tb_next
        pass
    return stack, i

def run_hooks(obj, hooks, *args):
    """Run each function in `hooks' with args"""
    for hook in hooks:
        if hook(obj, *args): return True
        pass
    return False

def resolve_name(obj, command_name):
    if command_name not in obj.commands:
        return None
    return command_name

# Default settings for command processor method call
DEFAULT_PROC_OPTS = {
    # A list of debugger initialization files to read on first command
    # loop entry.  Often this something like [~/.trepanrc] which the
    # front-end sets.
    'initfile_list' : []
}

class BWProcessor(Mprocessor.Processor):

    def __init__(self, core_obj, opts=None):
        Mprocessor.Processor.__init__(self, core_obj)

        self.response = {'errs': [], 'msg': []}
        self.continue_running = False   # True if we should leave command loop

        self.cmd_instances    = self._populate_commands()

        # command name before alias or macro resolution
        self.cmd_name         = ''

        # Current command getting run
        self.current_command  = ''

        self.debug_nest       = 1
        self.display_mgr      = Mdisplay.DisplayMgr()
        self.intf             = core_obj.debugger.intf
        self.last_command     = None    # Initially a no-op
        self.precmd_hooks     = []

        # If not:
        # self.location         = lambda : print_location(self)

        self.preloop_hooks    = []
        self.postcmd_hooks    = []

        self._populate_cmd_lists()

        # Stop only if line/file is different from last time
        self.different_line = None

        # These values updated on entry. Set initial values.
        self.curframe       = None
        self.event          = None
        self.event_arg      = None
        self.frame          = None
        self.list_lineno    = 0

        # Create a custom safe Repr instance and increase its maxstring.
        # The default of 30 truncates error messages too easily.
        self._repr             = Repr()
        self._repr.maxstring   = 100
        self._repr.maxother    = 60
        self._repr.maxset      = 10
        self._repr.maxfrozen   = 10
        self._repr.array       = 10
        self._saferepr         = self._repr.repr
        self.stack             = []
        self.thread_name       = None
        self.frame_thread_name = None
        return

    def add_preloop_hook(self, hook, position=-1, nodups = True):
        if hook in self.preloop_hooks: return False
        self.preloop_hooks.insert(position, hook)
        return True

    def adjust_frame(self, pos, absolute_pos):
        """Adjust stack frame by pos positions. If absolute_pos then
        pos is an absolute number. Otherwise it is a relative number.

        A negative number indexes from the other end."""
        if not self.curframe:
            Mmsg.errmsg(self, "No stack.")
            return

        # Below we remove any negativity. At the end, pos will be
        # the new value of self.curindex.
        if absolute_pos:
            if pos >= 0:
                pos = len(self.stack)-pos-1
            else:
                pos = -pos-1
        else:
            pos += self.curindex

        if pos < 0:
            Mmsg.errmsg(self,
                        "Adjusting would put us beyond the oldest frame.")
            return
        elif pos >= len(self.stack):
            Mmsg.errmsg(self,
                        "Adjusting would put us beyond the newest frame.")
            return

        self.curindex = pos
        self.curframe = self.stack[self.curindex][0]
        self.print_location()
        self.list_lineno = None
        return

    # To be overridden in derived debuggers
    def defaultFile(self):
        """Produce a reasonable default."""
        filename = self.curframe.f_code.co_filename
        # Consider using is_exec_stmt(). I just don't understand
        # the conditions under which the below test is true.
        if filename == '<string>' and self.debugger.mainpyfile:
            filename = self.debugger.mainpyfile
            pass
        return filename

    def event_processor(self, frame, event, event_arg, prompt='Trepan'):
        'command event processor: reading a commands do something with them.'
        self.frame     = frame
        self.event     = event
        self.event_arg = event_arg

        filename = frame.f_code.co_filename
        lineno   = frame.f_lineno
        line     = linecache.getline(filename, lineno, frame.f_globals)
        if not line:
            opts = {'output': 'plain',
                    'reload_on_change': self.settings('reload'),
                    'strip_nl': False}
            line = pyficache.getline(filename, lineno, opts)
        self.current_source_text = line
        if self.settings('skip') is not None:
            if Mbytecode.is_def_stmt(line, frame):
                return True
            if Mbytecode.is_class_def(line, frame):
                return True
            pass
        self.thread_name = Mthread.current_thread_name()
        self.frame_thread_name = self.thread_name
        self.process_commands()
        return True

    def forget(self):
        """ Remove memory of state variables set in the command processor """
        self.stack       = []
        self.curindex    = 0
        self.curframe    = None
        self.thread_name = None
        self.frame_thread_name = None
        return

    def eval(self, arg):
        """Eval string arg in the current frame context."""
        try:
            return eval(arg, self.curframe.f_globals,
                        self.curframe.f_locals)
        except:
            t, v = sys.exc_info()[:2]
            if isinstance(t, str):
                exc_type_name = t
                pass
            else: exc_type_name = t.__name__
            Mmsg.errmsg(self, str("%s: %s" % (exc_type_name, arg)))
            raise
        return None  # Not reached

    def exec_line(self, line):
        if self.curframe:
            local_vars = self.curframe.f_locals
            global_vars = self.curframe.f_globals
        else:
            local_vars = None
            # FIXME: should probably have place where the
            # user can store variables inside the debug session.
            # The setup for this should be elsewhere. Possibly
            # in interaction.
            global_vars = None
        try:
            code = compile(line + '\n', '"%s"' % line, 'single')
            exec(code, global_vars, local_vars)
        except:
            t, v = sys.exc_info()[:2]
            if isinstance(t, bytes):
                exc_type_name = t
            else: exc_type_name = t.__name__
            Mmsg.errmsg(self, '%s: %s' % (str(exc_type_name), str(v)))
            pass
        return

    def ok_for_running(self, cmd_obj, name, cmd_hash):
        '''We separate some of the common debugger command checks here:
        whether it makes sense to run the command in this execution state,
        if the command has the right number of arguments and so on.
        '''
        if hasattr(cmd_obj, 'execution_set'):
            if not (self.core.execution_status in cmd_obj.execution_set):
                part1 = ("Command '%s' is not available for execution "
                         "status:" % name)
                Mmsg.errmsg(self,
                            Mmisc.
                            wrapped_lines(part1,
                                            self.core.execution_status,
                                            self.debugger.settings['width']))
                return False
            pass
        if self.frame is None and cmd_obj.need_stack:
            self.intf[-1].errmsg("Command '%s' needs an execution stack."
                                 % name)
            return False
        return True

    def process_commands(self):
        """Handle debugger commands."""
        if self.core.execution_status != 'No program':
            self.setup()
            Mlocation.print_location(self, self.event)
            pass
        leave_loop = run_hooks(self, self.preloop_hooks)
        self.continue_running = False

        while not leave_loop:
            try:
                run_hooks(self, self.precmd_hooks)
                # bdb had a True return to leave loop.
                # A more straight-forward way is to set
                # instance variable self.continue_running.
                leave_loop = self.process_command()
                if leave_loop or self.continue_running: break
            except EOFError:
                # If we have stacked interfaces, pop to the next
                # one.  If this is the last one however, we'll
                # just stick with that.  FIXME: Possibly we should
                # check to see if we are interactive.  and not
                # leave if that's the case. Is this the right
                # thing?  investigate and fix.
                if len(self.debugger.intf) > 1:
                    del self.debugger.intf[-1]
                    self.last_command = ''
                else:
                    if self.debugger.intf[-1].output:
                        self.debugger.intf[-1].output.writeline('Leaving')
                        raise Mexcept.DebuggerQuit
                        pass
                    break
                pass
            pass
        return run_hooks(self, self.postcmd_hooks)

    def process_command(self):
        # process command
        self.response = {'errs': [], 'msg': []}
        cmd_hash = self.intf[-1].read_command()

        # FIXME: put this into a routine
        if not isinstance(cmd_hash, dict):
            Mmsg.errmsg(self, "invalid input, expecting a hash: %s" % cmd_hash,
                        {'set_name': True})
            self.intf[-1].msg(self.response)
            return False
        if 'command' not in cmd_hash:
            Mmsg.errmsg(self,
                        "invalid input, expecting a 'command' key: %s" %
                        cmd_hash,
                        {'set_name': True})
            self.intf[-1].msg(self.response)
            return False

        self.cmd_name = cmd_hash['command']
        cmd_name = resolve_name(self, self.cmd_name)
        if cmd_name:
            cmd_obj = self.commands[cmd_name]
            if self.ok_for_running(cmd_obj, cmd_name, cmd_hash):
                try:
                    self.response['name'] = cmd_name
                    result = cmd_obj.run(cmd_hash)
                    self.intf[-1].msg(self.response)
                    if result: return result
                except (Mexcept.DebuggerQuit,
                        Mexcept.DebuggerRestart, SystemExit):
                    # Let these exceptions propagate through
                    raise
                except:
                    Mmsg.errmsg(self, "INTERNAL ERROR: " +
                                traceback.format_exc())
                    pass
                pass
            else:
                self.undefined_cmd(cmd_name)
                pass
            pass
        return False

    def remove_preloop_hook(self, hook):
        try:
            position = self.preloop_hooks.index(hook)
        except ValueError:
            return False
        del self.preloop_hooks[position]
        return True

    def setup(self):
        """Initialization done before entering the debugger-command
        loop. In particular we set up the call stack used for local
        variable lookup and frame/up/down commands.

        We return True if we should NOT enter the debugger-command
        loop."""
        self.forget()
        if self.settings('dbg_trepan'):
            self.frame = inspect.currentframe()
            pass
        if self.event in ['exception', 'c_exception']:
            exc_type, exc_value, exc_traceback = self.event_arg
        else:
            _, _, exc_traceback = (None, None, None,)  # NOQA
            pass
        if self.frame or exc_traceback:
            self.stack, self.curindex = \
                get_stack(self.frame, exc_traceback, None, self)
            self.curframe = self.stack[self.curindex][0]
            self.thread_name = Mthread.current_thread_name()

        else:
            self.stack = self.curframe = \
                self.botframe = None
            pass
        if self.curframe:
            self.list_lineno = \
                max(1, inspect.getlineno(self.curframe))
        else:
            self.list_lineno = None
            pass
        # if self.execRcLines()==1: return True
        return False

    def undefined_cmd(self, cmd):
        """Error message when a command doesn't exist"""
        Mmsg.errmsg(self, 'Undefined command: "%s". Try "help".' % cmd)
        return

    def _populate_commands(self):
        """ Create an instance of each of the debugger
        commands. Commands are found by importing files in the
        directory 'command'. Some files are excluded via an array set
        in __init__.  For each of the remaining files, we import them
        and scan for class names inside those files and for each class
        name, we will create an instance of that class. The set of
        DebuggerCommand class instances form set of possible debugger
        commands."""
        cmd_instances = []
        from trepan.bwprocessor import command as Mcommand
        eval_cmd_template = 'command_mod.%s(self)'
        for mod_name in Mcommand.__modules__:
            import_name = "command." + mod_name
            try:
                command_mod = getattr(__import__(import_name), mod_name)
            except:
                print('Error importing %s: %s' % (mod_name, sys.exc_info()[0]))
                continue

            classnames = [ tup[0] for tup in
                           inspect.getmembers(command_mod, inspect.isclass)
                           if ('DebuggerCommand' != tup[0] and
                               tup[0].endswith('Command')) ]
            for classname in classnames:
                eval_cmd = eval_cmd_template % classname
                try:
                    instance = eval(eval_cmd)
                    cmd_instances.append(instance)
                except:
                    print('Error loading %s from %s: %s' %
                          (classname, mod_name, sys.exc_info()[0]))
                    pass
                pass
            pass
        return cmd_instances

    def _populate_cmd_lists(self):
        """ Populate self.commands"""
        self.commands = {}
        for cmd_instance in self.cmd_instances:
            cmd_name = cmd_instance.name
            self.commands[cmd_name] = cmd_instance
            pass
        return

    pass

# Demo it
if __name__=='__main__':
    from trepan.interfaces import bullwinkle as Mbullwinkle

    class Debugger:
        def __init__(self):
            self.intf = [Mbullwinkle.BWInterface()]
            self.settings = {'dbg_trepan': True, 'reload': False}
        pass

    class MockCore:
        def filename(self, fn): return fn

        def canonic_filename(self, frame): return frame.f_code.co_filename

        def __init__(self):
            self.debugger = Debugger()
            return
        pass
    core = MockCore()
    bwproc = BWProcessor(core)
    print('commands:')
    commands = list(bwproc.commands.keys())
    commands.sort()
    print(commands)
    print(resolve_name(bwproc, 'quit'))
    # print('-' * 10)
    # print_source_line(sys.stdout.write, 100, 'source_line_test.py')
    # print('-' * 10)
    bwproc.frame = sys._getframe()
    bwproc.setup()
    # print()
    # print('-' * 10)
    Mlocation.print_location(bwproc)

    # print 'Removing non-existing quit hook: ', bwproc.remove_preloop_hook(fn)
    # bwproc.add_preloop_hook(fn)
    # print bwproc.preloop_hooks
    # print 'Removed existing quit hook: ', bwproc.remove_preloop_hook(fn)
    pass


# -*- coding: utf-8 -*-
#   Copyright (C) 2015 Rocky Bernstein <rocky@gnu.org>
''' Location routines'''

import pyficache, linecache, tempfile
from trepan.lib import stack as Mstack

def format_location(proc_obj):
    """Show where we are. GUI's and front-end interfaces often
    use this to update displays. So it is helpful to make sure
    we give at least some place that's located in a file.
    """
    i_stack = proc_obj.curindex
    if i_stack is None or proc_obj.stack is None:
        return False
    location = {}
    core_obj = proc_obj.core
    dbgr_obj = proc_obj.debugger

    # Evaluation routines like "exec" don't show useful location
    # info. In these cases, we will use the position before that in
    # the stack.  Hence the looping below which in practices loops
    # once and sometimes twice.
    while i_stack >= 0:
        frame_lineno = proc_obj.stack[i_stack]
        i_stack -= 1
        frame, lineno = frame_lineno

        filename = Mstack.frame2file(core_obj, frame)

        location['filename'] = filename
        location['fn_name']  = frame.f_code.co_name
        location['lineno']   = lineno

        if '<string>' == filename and dbgr_obj.eval_string:
            filename = pyficache.unmap_file(filename)
            if '<string>' == filename:
                fd = tempfile.NamedTemporaryFile(suffix='.py',
                                                 prefix='eval_string',
                                                 delete=False)
                fd.write(bytes(dbgr_obj.eval_string, 'UTF-8'))
                fd.close()
                pyficache.remap_file(fd.name, '<string>')
                filename = fd.name
                pass
            pass

        opts = {
            'reload_on_change' : proc_obj.settings('reload'),
            'output'           : 'plain'
            }
        line = pyficache.getline(filename, lineno, opts)
        if not line:
            line = linecache.getline(filename, lineno,
                                     proc_obj.curframe.f_globals)
            pass

        if line and len(line.strip()) != 0:
            location['text'] = line
            pass
        if '<string>' != filename: break
        pass

    return location

def print_location(proc_obj, event=None):
    response = proc_obj.response
    response['name'] = 'status'
    response['location'] = format_location(proc_obj)
    if event:
        response['event'] = event
        if event in ['return', 'exception']:
            val = proc_obj._saferepr(proc_obj.event_arg)
            event['arg'] = val
            pass
        pass
    proc_obj.intf[-1].msg(response)
    return

# Demo it
if __name__=='__main__':
    class MockDebugger:
        def __init__(self):
            self.eval_string = None
        pass

    class MockProcessor:
        def __init__(self, core_obj):
            self.curindex = 0
            self.stack = []
            self.core = core_obj
            self.debugger = MockDebugger()
            self.opts = {'highlight': 'plain', 'reload': False}
            pass

        def settings(self, key):
            return self.opts[key]
        pass

    class MockCore:
        def filename(self, fn): return fn

        def canonic_filename(self, frame): return frame.f_code.co_filename
        pass

    core = MockCore()
    cmdproc = MockProcessor(core)

    import sys
    cmdproc.curframe = cmdproc.frame = sys._getframe()
    cmdproc.stack.append((sys._getframe(), 10))

    import pprint
    pp = pprint.PrettyPrinter()
    pp.pprint(format_location(cmdproc))

    def test(cmdproc, pp):
        cmdproc.stack[0:0] = [(sys._getframe(1), 1)]
        pp.pprint(format_location(cmdproc))
        pass
    eval('test(cmdproc, pp)')
    cmdproc.debugger.eval_string = 'Fooled you!'
    eval('test(cmdproc, pp)')
    pass


# -*- coding: utf-8 -*-
#   Copyright (C) 2010, 2013, 2015, 2018 Rocky Bernstein <rocky@gnu.org>
#
#   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 3 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, see <http://www.gnu.org/licenses/>.
"""A base class for a debugger interface."""

import sys

NotImplementedMessage = "This method must be overriden in a subclass"

__all__ = ['TrepanInterface']

class TrepanInterface:
    """
A debugger interface handles the communication or interaction with between
the program and the outside portion which could be
    - a user,
    - a front-end that talks to a user, or
    - another interface in another process or computer
    """

    def __init__(self, inp=None, out=None):
        self.input  = inp or sys.stdin
        self.output = out or sys.stdout
        self.interactive = False
        return

    def close(self):
        """ Closes all input and/or output """
        raise NotImplementedError(NotImplementedMessage)
        return

    def confirm(self, prompt, default):
        """ Called when a dangerous action is about to be done to make sure
        it's okay. `prompt' is printed; user response is returned."""
        raise NotImplementedError(NotImplementedMessage)

    def errmsg(self, str, prefix="** "):
        """Common routine for reporting debugger error messages.
           """
        raise NotImplementedError(NotImplementedMessage)

    def finalize(self, last_wishes=None):
        raise NotImplementedError(NotImplementedMessage)

    def msg(self, msg):
        """ used to write to a debugger that is connected to this
        server; `str' written will have a newline added to it
        """
        if hasattr(self.output, 'writeline'):
            self.output.writeline(msg)
        elif hasattr(self.output, 'writelines'):
            self.output.writelines(msg + "\n")
            pass
        return

    def msg_nocr(self, msg):
        """ used to write to a debugger that is connected to this
        server; `str' written will not have a newline added to it
        """
        self.output.write(msg)
        return

    def read_command(self, prompt):
        raise NotImplementedError(NotImplementedMessage)

    def readline(self, prompt, add_to_history=True):
        raise NotImplementedError(NotImplementedMessage)

    pass


# -*- coding: utf-8 -*-
#   Copyright (C) 2008-2009, 2013, 2015, 2017 Rocky Bernstein <rocky@gnu.org>
#
#   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 3 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, see <http://www.gnu.org/licenses/>.

# Our local modules
from trepan.processor.command import base_subcmd as Mbase_subcmd
from trepan import misc as Mmisc

class InfoProgram(Mbase_subcmd.DebuggerSubcommand):
    """**info program**

Execution status of the program. Listed are:

* Program name

* Instruction PC

* Reason the program is stopped.

See also:
---------

`info line`, `info args`, `info frame`, `info pc`"""

    min_abbrev = 2  # Need at least info pr
    max_args   = 0
    need_stack = True
    short_help = 'Execution status of the program'

    def run(self, args):
        """Execution status of the program."""
        mainfile = self.core.filename(None)
        if self.core.is_running():
            if mainfile:
                part1 = "Python program '%s' is stopped" % mainfile
            else:
                part1 = 'Program is stopped'
                pass
            if self.proc.event:
                msg = 'via a %s event.' % self.proc.event
            else:
                msg = '.'
            self.msg(Mmisc.wrapped_lines(part1, msg,
                                         self.settings['width']))
            if self.proc.curframe:
                self.msg("PC offset is %d." % self.proc.curframe.f_lasti)

            if self.proc.event == 'return':
                val = self.proc.event_arg
                part1 = 'Return value is'
                self.msg(Mmisc.wrapped_lines(part1, self.proc._saferepr(val),
                                             self.settings['width']))
                pass
            elif self.proc.event == 'exception':
                exc_type, exc_value, exc_tb = self.proc.event_arg
                self.msg('Exception type: %s' %
                         self.proc._saferepr(exc_type))
                if exc_value:
                    self.msg('Exception value: %s' %
                             self.proc._saferepr(exc_value))
                    pass
                pass
            self.msg('It stopped %s.' % self.core.stop_reason)
            if self.proc.event in ['signal', 'exception', 'c_exception']:
                self.msg('Note: we are stopped *after* running the '
                         'line shown.')
                pass
        else:
            if mainfile:
                part1 = "Python program '%s'" % mainfile
                msg   = "is not currently running. "
                self.msg(Mmisc.wrapped_lines(part1, msg,
                                             self.settings['width']))
            else:
                self.msg('No Python program is currently running.')
                pass
            self.msg(self.core.execution_status)
            pass
        return False
    pass

if __name__ == '__main__':
    from trepan.processor.command import mock, info as Minfo
    d, cp = mock.dbg_setup()
    i = Minfo.InfoCommand(cp)
    sub = InfoProgram(i)
    sub.run([])


# -*- coding: utf-8 -*-
#  Copyright (C) 2009-2010, 2012-2013, 2015 Rocky Bernstein
#
#  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 3 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, see <http://www.gnu.org/licenses/>.
"""A base class for debugger commands.

This file is the one module in this directory that isn't a real command
and commands.py needs to take care to avoid instantiating this class
and storing it as a list of known debugger commands.
"""

NotImplementedMessage = "This method must be overriden in a subclass"

import columnize
from pygments.console import colorize

from trepan.lib import format as Mformat

__all__ = ['DebuggerCommand']

class DebuggerCommand:
    """Base Class for Debugger commands. We pull in some helper
    functions for command from module cmdfns."""

    category = 'misc'

    def __init__(self, proc):
        """proc contains the command processor object that this
        command is invoked through.  A debugger field gives access to
        the stack frame and I/O."""
        self.proc = proc

        # Convenience class access. We don't expect that either core
        # or debugger will change over the course of the program
        # execution like errmsg(), msg(), and msg_nocr() might. (See
        # the note below on these latter 3 methods.)
        #
        self.core     = proc.core
        self.debugger = proc.debugger
        self.settings = self.debugger.settings
        return

    aliases = ()
    name    = 'YourCommandName'

    def columnize_commands(self, commands):
        """List commands arranged in an aligned columns"""
        commands.sort()
        width = self.debugger.settings['width']
        return columnize.columnize(commands, displaywidth=width,
                                   lineprefix='    ')

    def confirm(self, msg, default=False):
        """ Convenience short-hand for self.debugger.intf[-1].confirm """
        return self.debugger.intf[-1].confirm(msg, default)

    # Note for errmsg, msg, and msg_nocr we don't want to simply make
    # an assignment of method names like self.msg = self.debugger.intf.msg,
    # because we want to allow the interface (intf) to change
    # dynamically. That is, the value of self.debugger may change
    # in the course of the program and if we made such an method assignemnt
    # we wouldn't pick up that change in our self.msg
    def errmsg(self, msg, opts={}):
        """ Convenience short-hand for self.debugger.intf[-1].errmsg """
        try:
            return(self.debugger.intf[-1].errmsg(msg))
        except EOFError:
            # FIXME: what do we do here?
            pass
        return None

    def msg(self, msg, opts={}):
        """ Convenience short-hand for self.debugger.intf[-1].msg """
        try:
            return(self.debugger.intf[-1].msg(msg))
        except EOFError:
            # FIXME: what do we do here?
            pass
        return None

    def msg_nocr(self, msg, opts={}):
        """ Convenience short-hand for self.debugger.intf[-1].msg_nocr """
        try:
            return(self.debugger.intf[-1].msg_nocr(msg))
        except EOFError:
            # FIXME: what do we do here?
            pass
        return None

    def rst_msg(self, text, opts={}):
        """Convert ReStructuredText and run through msg()"""
        text = Mformat.rst_text(text,
                                'plain' == self.debugger.settings['highlight'],
                                self.debugger.settings['width'])
        return self.msg(text)

    def run(self, args):
        """ The method that implements the debugger command.
        Help on the command comes from the docstring of this method.
        """
        raise NotImplementedError(NotImplementedMessage)

    pass

    def section(self, message, opts={}):
        if 'plain' != self.settings['highlight']:
            message = colorize('bold', message)
        else:
            message += "\n" + '-' * len(message)
            pass
        self.msg(message)

if __name__ == '__main__':
    from trepan.processor.command import mock
    d, cp = mock.dbg_setup()
    dd = DebuggerCommand(cp)
    dd.msg("hi")
    dd.errmsg("Don't do that")
    pass


# -*- coding: utf-8 -*-
#   Copyright (C) 2009, 2013-2015 Rocky Bernstein <rocky@gnu.org>
#
#   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 3 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, see <http://www.gnu.org/licenses/>.
# Post-Mortem interface

import inspect, os, sys, re, traceback

# Our local modules
from trepan import debugger as Mdebugger
from trepan.exception import DebuggerQuit, DebuggerRestart

def get_last_or_frame_exception():
    """Intended to be used going into post mortem routines.  If
    sys.last_traceback is set, we will return that and assume that
    this is what post-mortem will want. If sys.last_traceback has not
    been set, then perhaps we *about* to raise an error and are
    fielding an exception. So assume that sys.exc_info()[2]
    is where we want to look."""

    try:
        if inspect.istraceback(sys.last_traceback):
            # We do have a traceback so prefer that.
            return sys.last_type, sys.last_value, sys.last_traceback
    except AttributeError:
        pass
    return sys.exc_info()

def pm(frameno=1, dbg=None):
    """Set up post-mortem debugging using the last traceback.  But if
    there is no traceback, we'll assume that sys.exc_info() contains
    what we want and frameno is the index location of where we want
    to start.

    'dbg', is an optional trepan.Trepan object.
    """
    post_mortem(get_last_or_frame_exception(), frameno, dbg=dbg)
    return

def post_mortem_excepthook(exc_type, exc_value, exc_tb, tb_fn=None):
    if str(exc_type) == str(DebuggerQuit): return
    try:
        if str(exc_type) == str(DebuggerRestart):
            if ( exc_value and exc_value.sys_argv and
                 len(exc_value.sys_argv) > 0 ):
                print("No restart handler - trying restart via execv(%s)" %
                       repr(exc_value.sys_argv))
                os.execvp(exc_value.sys_argv[0], exc_value.sys_argv)
            else:
                print("No restart handler, no params registered")
                print("Entering post-mortem debugger...")
        else:
            if tb_fn:
                tb_fn(exc_type, exc_value, exc_tb)
            else:
                traceback.print_exception(exc_type, exc_value, exc_tb)
            print("Uncaught exception. Entering post-mortem debugger...")
            pass
        post_mortem((exc_type, exc_value, exc_tb))
        print("Post-mortem debugger finished.")
    except:
        pass
    return

def post_mortem(exc=None, frameno=1, dbg=None):
    """Enter debugger read loop after your program has crashed.

    exc is a triple like you get back from sys.exc_info.  If no exc
    parameter, is supplied, the values from sys.last_type,
    sys.last_value, sys.last_traceback are used. And if these don't
    exist either we'll assume that sys.exc_info() contains what we
    want and frameno is the index location of where we want to start.

    'frameno' specifies how many frames to ignore in the traceback.
    The default is 1, that is, we don't need to show the immediate
    call into post_mortem. If you have wrapper functions that call
    this one, you may want to increase frameno.
    """

    if dbg is None:
        # Check for a global debugger object
        if Mdebugger.debugger_obj is None:
            Mdebugger.debugger_obj = Mdebugger.Trepan()
            pass
        dbg = Mdebugger.debugger_obj
        pass
    re_bogus_file = re.compile("^<.+>$")

    if exc[0] is None:
        # frameno+1 because we are about to add one more level of call
        # in get_last_or_frame_exception
        exc = get_last_or_frame_exception()
        if exc[0] is None:
            print("Can't find traceback for post_mortem "
                  "in sys.last_traceback or sys.exec_info()")
            return
        pass
    exc_type, exc_value, exc_tb = exc
    dbg.core.execution_status = ('Terminated with unhandled exception %s'
                                 % exc_type)

    # tb has least-recent traceback entry first. We want the most-recent
    # entry. Also we'll pick out a mainpyfile name if it hasn't previously
    # been set.
    if exc_tb is not None:
        while exc_tb.tb_next is not None:
            filename = exc_tb.tb_frame.f_code.co_filename
            if (dbg.mainpyfile and 0 == len(dbg.mainpyfile)
                and not re_bogus_file.match(filename)):
                dbg.mainpyfile = filename
                pass
            exc_tb = exc_tb.tb_next
            pass
        dbg.core.processor.curframe = exc_tb.tb_frame
        pass

    if 0 == len(dbg.program_sys_argv):
        # Fake program (run command) args since we weren't called with any
        dbg.program_sys_argv = list(sys.argv[1:])
        dbg.program_sys_argv[:0] = [dbg.mainpyfile]

    # if 0 == len(dbg._sys_argv):
    #     # Fake script invocation (restart) args since we don't have any
    #     dbg._sys_argv = list(dbg.program_sys_argv)
    #     dbg._sys_argv[:0] = [__title__]

    try:
        # # FIXME: This can be called from except hook in which case we
        # # need this. Dunno why though.
        # try:
        #     _pydb_trace.set_trace(t.tb_frame)
        # except:
        #     pass

        # Possibly a bug in Python 2.5. Why f.f_lineno is
        # not always equal to t.tb_lineno, I don't know.
        f = exc_tb.tb_frame
        if f and f.f_lineno != exc_tb.tb_lineno : f = f.f_back
        dbg.core.processor.event_processor(f, 'exception', exc, 'Trepan3k:pm')
    except DebuggerRestart:
        while True:
            sys.argv = list(dbg._program_sys_argv)
            dbg.msg("Restarting %s with arguments:\n\t%s"
                  % (dbg.filename(dbg.mainpyfile),
                     " ".join(dbg._program_sys_argv[1:])))
            try:
                dbg.run_script(dbg.mainpyfile)
            except DebuggerRestart:
                pass
            pass
    except DebuggerQuit:
        pass
    return

def uncaught_exception(dbg, tb_fn=None):
    exc = sys.exc_info()
    exc_type, exc_value, exc_tb = exc
    if exc_type == DebuggerQuit: return
    if exc_type == DebuggerRestart:
        print("restart not done yet - entering post mortem debugging")
    elif exc_tb is None:
        print("You don't seem to have an exception traceback, yet.")
        return
    else:
        if tb_fn:
            tb_fn(exc_type, exc_value, exc_tb)
        else:
            traceback.print_exception(exc_type, exc_value, exc_tb)
        print("uncaught exception. entering post mortem debugging")
        pass
    dbg.core.execution_status = ('Terminated with unhandled exception %s'
                                 % exc_type)
    dbg.core.processor.event_processor(exc_tb.tb_frame, 'exception', exc,
                                       'Trepan3k:pm')
    print("Post mortem debugger finished.")
    return

if __name__=='__main__':
    if len(sys.argv) > 1:
        pm()
        pass
    pass


# -*- coding: utf-8 -*-
#   Copyright (C) 2009, 2013-2014, 2016-2017 Rocky Bernstein <rocky@gnu.org>
#
#   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 3 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, see <http://www.gnu.org/licenses/>.
"""Debugger Server Input/Output interface. """

import socket, errno

from trepan.lib import default as Mdefault
from trepan import misc as Mmisc
from trepan.inout import tcpfns as Mtcpfns
from trepan.inout.base import DebuggerInOutBase

# FIXME: Consider using Python's socketserver/SocketServer?
class TCPServer(DebuggerInOutBase):
    """Debugger Server Input/Output Socket."""

    DEFAULT_INIT_OPTS = {'open': True, 'socket': None}

    def __init__(self, inout=None, opts=None):
        get_option = lambda key: Mmisc.option_set(opts, key,
                                                  self.DEFAULT_INIT_OPTS)

        self.inout = None
        self.conn = None
        self.addr = None
        self.remote_addr = ''
        self.buf = ''    # Read buffer
        self.line_edit = False  # Our name for GNU readline capability
        self.state = 'disconnected'
        self.PORT = None
        self.HOST = None
        if inout:
            self.inout = inout
        if get_option('socket'):
            self.inout = opts['socket']
            self.inout.listen(1)
            self.state = 'listening'
        elif get_option('open'):
            self.open(opts)
            pass
        return

    def close(self):
        """ Closes both socket and server connection. """
        self.state = 'closing'
        if self.inout:
            self.inout.close()
            pass
        self.state = 'closing connection'
        if self.conn:
            self.conn.close()
        self.state = 'disconnected'
        return

    def open(self, opts=None):
        get_option = lambda key: Mmisc.option_set(opts, key,
                                                  Mdefault.SERVER_SOCKET_OPTS)

        self.HOST  = get_option('HOST')
        self.PORT  = get_option('PORT')
        self.reuse = get_option('reuse')
        self.search_limit = get_option('search_limit')
        self.inout = None

        this_port = self.PORT - 1
        for i in range(self.search_limit):
            this_port += 1
            for res in socket.getaddrinfo(self.HOST, this_port, socket.AF_UNSPEC,
                                          socket.SOCK_STREAM, 0,
                                          socket.AI_PASSIVE):
                af, socktype, proto, canonname, sa = res
                try:
                    self.inout = socket.socket(af, socktype, proto)
                except socket.error:
                    self.inout = None
                    continue
                try:
                    if get_option('reuse'):
                        # The following socket option allows the OS to reclaim
                        # The address faster on termination.
                        self.inout.setsockopt(socket.SOL_SOCKET,
                                              socket.SO_REUSEADDR, 1)

                        pass
                    self.inout.bind(sa)
                    self.inout.listen(1)
                    self.state = 'listening'
                    break
                except socket.error as exc:
                    if exc.errno in [errno.EADDRINUSE, errno.EINVAL]:
                        self.inout.close()
                        self.inout = None
                        continue
                    raise
                pass
            if self.state == 'listening':
                break
        if self.inout is None:
            raise IOError('could not open server socket after trying ports '
                          '%s..%s' % (self.PORT, this_port))
        self.PORT = this_port
        return

    def read(self):
        if len(self.buf) == 0:
            self.read_msg()
            pass
        return self.buf

    def read_msg(self):
        """Read one message unit. It's possible however that
        more than one message will be set in a receive, so we will
        have to buffer that for the next read.
        EOFError will be raised on EOF.
        """
        if self.state != 'connected':
            self.wait_for_connect()
            pass
        if self.state == 'connected':
            if 0 == len(self.buf):
                self.buf = self.conn.recv(Mtcpfns.TCP_MAX_PACKET)
                if 0 == len(self.buf):
                    self.state = 'disconnected'
                    raise EOFError
                pass
            self.buf, data = Mtcpfns.unpack_msg(self.buf)
            return data.decode('utf-8')
        else:
            raise IOError("read_msg called in state: %s." % self.state)

    def wait_for_connect(self):
        self.conn, self.addr = self.inout.accept()
        self.remote_addr = ':'.join(str(v) for v in self.addr)
        self.state = 'connected'
        return

    def write(self, msg):
        """ This method the debugger uses to write. In contrast to
        writeline, no newline is added to the end to `str'. Also
        msg doesn't have to be a string.
        """
        if self.state != 'connected':
            self.wait_for_connect()
            pass
        buffer = Mtcpfns.pack_msg(msg)
        while len(buffer) > Mtcpfns.TCP_MAX_PACKET:
            self.conn.send(buffer[:Mtcpfns.TCP_MAX_PACKET])
            buffer = buffer[Mtcpfns.TCP_MAX_PACKET:]
        return self.conn.send(buffer)

# Demo
if __name__=='__main__':
    inout = TCPServer(opts={'open': False})
    import sys
    if len(sys.argv) > 1:
        inout.open()
        print('Listening for connection on %s:%s' %
              (inout.HOST, inout.PORT))
        while True:
            try:
                line = inout.read_msg().rstrip('\n')
                print(line)
                inout.writeline('ack: ' + line)
            except EOFError:
                break
            pass
        pass
    inout.close()
    pass


# -*- coding: utf-8 -*-
#   Copyright (C) 2013-2015 Rocky Bernstein <rocky@gnu.org>
#
#   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 3 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, see <http://www.gnu.org/licenses/>.
"CommandProcessor completion routines"
import pyficache, re

from trepan.lib import complete as Mcomplete

def complete_token_filtered(aliases, prefix, expanded):

    """Find all starting matches in dictionary *aliases* that start
    with *prefix*, but filter out any matches already in
    *expanded*."""

    complete_ary = list(aliases.keys())
    results = [cmd for cmd in
               complete_ary if cmd.startswith(prefix)] and not (
                   cmd in aliases and expanded not in aliases[cmd])
    return sorted(results, key=lambda pair: pair[0])

def completer(self, str, state, last_token=''):
    next_blank_pos, token = Mcomplete.next_token(str, 0)
    if len(token) == 0 and not 0 == len(last_token):
        return ['', None]
    match_pairs = Mcomplete.complete_token_with_next(self.commands, token)
    match_hash = {}
    for pair in match_pairs:
        match_hash[pair[0]] = pair[1]
        pass

    alias_pairs = Mcomplete \
      .complete_token_filtered_with_next(self.aliases,
                                         token,
                                         match_hash,
                                         list(self.commands.keys()))
    match_pairs += alias_pairs

    macro_pairs = Mcomplete \
      .complete_token_filtered_with_next(self.macros,
                                         token, match_hash,
                                         self.commands.keys())
    match_pairs += macro_pairs

    if len(str) == next_blank_pos:
        if len(match_pairs) == 1 and match_pairs[0][0] == token:
            # Add space to advance completion on next tab-complete
            match_pairs[0][0] += " "
            pass
        return sorted([pair[0] for pair in match_pairs]) + [None]
    else:
        for pair in alias_pairs:
            match_hash[pair[0]] = pair[1]
            pass
        pass

    if len(match_pairs) > 1:
        # FIXME: figure out what to do here.
        # Matched multiple items in the middle of the string
        # We can't handle this so do nothing.
        return [None]
        # return match_pairs.map do |name, cmd|
        #   ["#{name} #{args[1..-1].join(' ')}"]

    # len(match_pairs) == 1
    if str[-1] == ' ' and str.rstrip().endswith(token):
        token=''
        pass
    return next_complete(str, next_blank_pos, match_pairs[0][1],
                         token) + [None]

def next_complete(str, next_blank_pos, cmd, last_token):
    next_blank_pos, token = Mcomplete.next_token(str, next_blank_pos)

    if hasattr(cmd, 'complete_token_with_next'):
        match_pairs = cmd.complete_token_with_next(token)
        if len(match_pairs) == 0:
            return [None]
        if (next_blank_pos == len(str) and 1 == len(match_pairs) and
            match_pairs[0][0] == token):
            # Add space to advance completion on next tab-complete
            match_pairs[0][0] += " "
            pass
        if next_blank_pos >= len(str):
            return sorted([pair[0] for pair in match_pairs])
        else:
            if len(match_pairs) == 1:
                return next_complete(str, next_blank_pos,  match_pairs[0][1],
                                     token)
            else:
                return sorted([pair[0] for pair in match_pairs])
                pass
            pass
        pass
    elif hasattr(cmd, 'complete'):
        matches = cmd.complete(token)
        if 0 == len(matches):
            return [None]
        return matches
    return [None]

def complete_bpnumber(self, prefix):
    return Mcomplete.complete_brkpts(self.core.bpmgr, prefix)

def complete_break_linenumber(self, prefix):
    canonic_name = self.proc.curframe.f_code.co_filename
    completions = pyficache.trace_line_numbers(canonic_name)
    return Mcomplete.complete_token([str(i) for i in completions],
                                    prefix)

def complete_identifier(cmd, prefix):
    '''Complete an arbitrary expression.'''
    if not cmd.proc.curframe: return [None]
    # Collect globals and locals.  It is usually not really sensible to also
    # complete builtins, and they clutter the namespace quite heavily, so we
    # leave them out.
    ns = cmd.proc.curframe.f_globals.copy()
    ns.update(cmd.proc.curframe.f_locals)
    if '.' in prefix:
        # Walk an attribute chain up to the last part, similar to what
        # rlcompleter does.  This will bail if any of the parts are not
        # simple attribute access, which is what we want.
        dotted = prefix.split('.')
        try:
            obj = ns[dotted[0]]
            for part in dotted[1:-1]:
                obj = getattr(obj, part)
        except (KeyError, AttributeError):
            return []
        pre_prefix = '.'.join(dotted[:-1]) + '.'
        return [pre_prefix + n for n in dir(obj) if
                n.startswith(dotted[-1])]
    else:
        # Complete a simple name.
        return Mcomplete.complete_token(ns.keys(), prefix)

def complete_id_and_builtins(cmd, prefix):
    if not cmd.proc.curframe: return [None]
    items = (list(cmd.proc.curframe.f_builtins.keys()) +
             complete_identifier(cmd, prefix))
    return Mcomplete.complete_token(items, prefix)

if __name__=='__main__':
    import inspect
    from trepan.processor import cmdproc as Mcmdproc
    from trepan.processor.command import mock as Mmock
    from trepan.processor.command import base_cmd as mBaseCmd
    d = Mmock.MockDebugger()
    cmdproc = Mcmdproc.CommandProcessor(d.core)
    cmdproc.curframe = inspect.currentframe()
    cmd = mBaseCmd.DebuggerCommand(cmdproc)
    print(complete_identifier(cmd, ''))
    print(complete_identifier(cmd, 'M'))
    print(complete_id_and_builtins(cmd, 'M'))
    pass


# -*- coding: utf-8 -*-
#   Copyright (C) 2008-2010, 2012-2013, 2015 Rocky Bernstein <rocky@gnu.org>
#
#   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 3 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, see <http://www.gnu.org/licenses/>.
NotImplementedMessage = "This method must be overriden in a subclass"

from pygments.console import colorize

__all__ = ['Processor']

class Processor:
    """A processor is the thing that handles the events that come to
    the debugger.  It has it's own I/O mechanism and a way to handle
    the events.
    """
    def __init__(self, core_obj):
        self.core = core_obj
        self.debugger = core_obj.debugger
        return

    # Note for errmsg, msg, and msg_nocr we don't want to simply make
    # an assignment of method names like self.msg = self.intf.msg,
    # because we want to allow the interface (intf) to change
    # dynamically. That is, the value of self.debugger may change
    # in the course of the program and if we made such an method assignemnt
    # we wouldn't pick up that change in our self.msg
    def errmsg(self, message, opts={}):
        """ Convenience short-hand for self.intf[-1].errmsg """
        if 'plain' != self.debugger.settings['highlight']:
            message = colorize('standout', message)
            pass
        return(self.intf[-1].errmsg(message))

    def msg(self, msg, opts={}):
        """ Convenience short-hand for self.debugger.intf[-1].msg """
        return(self.intf[-1].msg(msg))

    def msg_nocr(self, msg, opts={}):
        """ Convenience short-hand for self.debugger.intf[-1].msg_nocr """
        return(self.intf[-1].msg_nocr(msg))

    def event_processor(self, frame, event, arg):
        raise NotImplementedError(NotImplementedMessage)

    def rst_msg(self, text, opts={}):
        """Convert ReStructuredText and run through msg()"""
        from trepan.lib.format import rst_text
        text = rst_text(text,
                        'plain' == self.debugger.settings['highlight'],
                        self.debugger.settings['width'])
        return self.msg(text)

    def section(self, message, opts={}):
        if 'plain' != self.settings('highlight'):
            message = colorize('bold', message)
            pass
        return self.msg(message, opts)

    def settings(self, setting):
        return self.core.debugger.settings[setting]
    pass


# -*- coding: utf-8 -*-
#  Copyright (C) 2015 Rocky Bernstein
#
#  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 3 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, see <http://www.gnu.org/licenses/>.
import code, os, sys

# Our local modules
from trepan.processor.command import base_cmd as Mbase_cmd

import bpython

class PythonCommand(Mbase_cmd.DebuggerCommand):
    """**bpython** [**-d**]

Run bpython as a command subshell. The *sys.ps1* prompt will be set to
`trepan2 >>> `.

If *-d* is passed, you can access debugger state via local variable *debugger*.

To issue a debugger command use function *dbgr()*. For example:

  dbgr('info program')
"""

    aliases      = ('bpython',)
    category      = 'support'
    min_args      = 0
    max_args      = 1
    name          = os.path.basename(__file__).split('.')[0]
    need_stack    = False
    short_help    = 'Run bpython as a command subshell'

    def dbgr(self, string):
        '''Invoke a debugger command from inside a python shell called inside
        the debugger.
        '''
        print('')
        self.proc.cmd_queue.append(string)
        self.proc.process_command()
        return

    def run(self, args):

        try:
            from bpython.curtsies import main as main_bpython
        except ImportError:
            self.errmsg("bpython needs to be installed to run this command")
            return

        # bpython does it's own history thing.
        # Make sure it doesn't damage ours.
        have_line_edit = self.debugger.intf[-1].input.line_edit
        if have_line_edit:
            try:
                self.proc.write_history_file()
            except IOError:
                pass
            pass

        banner_tmpl='''trepan2 python shell%s
Use dbgr(*string*) to issue debugger command: *string*'''

        debug = len(args) > 1 and args[1] == '-d'
        if debug:
            banner_tmpl += ("\nVariable 'debugger' contains a trepan" +
                            "debugger object.")
            pass
        my_locals  = {}
        # my_globals = None
        if self.proc.curframe:
            # my_globals = self.proc.curframe.f_globals
            if self.proc.curframe.f_locals:
                my_locals = self.proc.curframe.f_locals
                pass
            pass

        # Give python and the user a way to get access to the debugger.
        if debug: my_locals['debugger'] = self.debugger
        my_locals['dbgr'] = self.dbgr

        if len(my_locals):
            banner=(banner_tmpl % ' with locals')
        else:
            banner=(banner_tmpl % '')
            pass

        sys.ps1 = 'trepan2 >>> '
        print(banner)
        try:
            main_bpython([], my_locals)
        except SystemExit:
            pass

        # restore completion and our history if we can do so.
        if hasattr(self.proc.intf[-1], 'complete'):
            try:
                from readline import set_completer, parse_and_bind
                parse_and_bind("tab: complete")
                set_completer(self.proc.intf[-1].complete)
            except ImportError:
                pass
            pass

        if have_line_edit:
            self.proc.read_history_file()
            pass
        return
    pass

# if __name__ == '__main__':
#     from trepan import debugger as Mdebugger
#     d = Mdebugger.Debugger()
#     command = PythonCommand(d.core.processor)
#     command.proc.frame = sys._getframe()
#     command.proc.setup()
#     if len(sys.argv) > 1:
#         print("Type Python commands and exit to quit.")
#         print(sys.argv[1])
#         if sys.argv[1] == '-d':
#             print(command.run(['bpy', '-d']))
#         else:
#             print(command.run(['bpy']))
#             pass
#         pass
#     pass


# -*- coding: utf-8 -*-
#  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 3 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, see <http://www.gnu.org/licenses/>.
import os, threading

# Our local modules
from trepan.bwprocessor.command import base_cmd as Mbase_cmd
from trepan import exception as Mexcept

class QuitCommand(Mbase_cmd.DebuggerCommand):
    """Gently exit the debugger and debugged program.

**Input Fields:**

   { command     => 'quit',
   }

The program being debugged is exited raising a *DebuggerQuit* exception.

**Output Fields: **

   { name      => 'status',
     event     => 'terminated',
     [errmsg   => <error-message-array>]
     [msg      => <message-text array>]
  }
"""

    name          = os.path.basename(__file__).split('.')[0]
    need_stack    = False

    def nothread_quit(self, arg):
        """ quit command when there's just one thread. """

        self.debugger.core.stop()
        self.debugger.core.execution_status = 'Quit command'
        self.proc.response['event'] = 'terminated'
        self.proc.response['name']  = 'status'
        self.proc.intf[-1].msg(self.proc.response)
        raise Mexcept.DebuggerQuit

    def threaded_quit(self, arg):
        """ quit command when several threads are involved. """
        self.msg("Quit for threading not fully done yet. Try 'kill'.")
        return False

    def run(self, cmd_hash):
        threading_list = threading.enumerate()
        if (len(threading_list) == 1 and
            threading_list[0].getName() == 'MainThread'):
            # We just have a main thread so that's safe to quit
            return self.nothread_quit(cmd_hash)
        else:
            return self.threaded_quit(cmd_hash)
        pass

if __name__ == '__main__':
    from trepan.bwprocessor.command import mock
    d, cp = mock.dbg_setup()
    command = QuitCommand(cp)
    try:
        command.run(['quit'])
    except Mexcept.DebuggerQuit:
        print("A got 'quit' a exception. Ok, be that way - I'm going home.")
        pass

    class MyThread(threading.Thread):
        def run(self):
            command.run(['quit'])
            return
        pass

    t = MyThread()
    t.start()
    t.join()
    pass


# -*- coding: utf-8 -*-
#
#  Copyright (C) 2017 Rocky Bernstein
#  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 3 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, see <http://www.gnu.org/licenses/>.

from trepan.processor.parse.semantics import build_arange, Location
from trepan.processor.parse.parser import LocationError
from trepan.processor.parse.scanner import ScannerError
from trepan.processor.location import resolve_address_location

INVALID_PARSE_LIST = (None, None, None, None, None, None)
def parse_addr_list_cmd(proc, args, listsize=40):
    """Parses arguments for the "list" command and returns the tuple:
    (filename, first line number, last line number)
    or sets these to None if there was some problem."""

    text = proc.current_command[len(args[0])+1:].strip()

    if text in frozenset(('', '.', '+', '-')):
        if text == '.':
            location = resolve_address_location(proc, '.')
            return (location.path, location.line_number, True,
                    location.line_number + listsize, True,
                    location.method)

        if proc.list_offset is None:
            proc.errmsg("Don't have previous list location")
            return INVALID_PARSE_LIST

        filename = proc.list_filename
        if text == '+':
            first = max(0, proc.list_offset-1)
        elif text == '-':
            # FIXME: not quite right for offsets
            if proc.list_lineno == 1 + listsize:
                proc.errmsg("Already at start of %s." % proc.list_filename)
                return INVALID_PARSE_LIST
            first = max(1, proc.list_lineno - (2*listsize) - 1)
        elif text == '':
            # Continue from where we last left off
            first = proc.list_offset + 1
            last = first + listsize - 1
            return filename, first, True, last, True, proc.list_object
        last = first + listsize - 1
        return filename, first, True, last, True, proc.list_object
    else:
        try:
            list_range = build_arange(text)
        except LocationError as e:
            proc.errmsg("Error in parsing list range at or around:")
            proc.errmsg(e.text)
            proc.errmsg(e.text_cursor)
            return INVALID_PARSE_LIST
        except ScannerError as e:
            proc.errmsg("Lexical error in parsing list range at or around:")
            proc.errmsg(e.text)
            proc.errmsg(e.text_cursor)
            return INVALID_PARSE_LIST

        if list_range.first is None:
            # Last must have been given
            assert isinstance(list_range.last, Location)
            location = resolve_address_location(proc, list_range.last)
            if not location:
                return INVALID_PARSE_LIST
            last     = location.line_number
            if location.is_address:
                raise RuntimeError("We don't handle ending offsets")
            else:
                first    = max(1, last - listsize)
            return location.path, first, False, last, False, location.method
        elif isinstance(list_range.first, int):
            first    = list_range.first
            location = resolve_address_location(proc, list_range.last)
            if not location:
                return INVALID_PARSE_LIST
            filename = location.path
            last     = location.line_number

            if not location.is_address and last < first:
                # Treat as a count rather than an absolute location
                last = first + last
            return location.path, first, False, last, location.is_address, location.method
        else:
            # First is location. Last may be empty or a number/address
            assert isinstance(list_range.first, Location)
            location = resolve_address_location(proc, list_range.first)
            if not location:
                return INVALID_PARSE_LIST
            first    = location.line_number
            first_is_addr = location.is_address
            last_is_addr  = False
            last     = list_range.last
            if isinstance(last, str):
                # Is an offset +number
                assert last[0] in ('+', '*')
                last_is_addr = last[0] == '*'
                if last_is_addr:
                    last = int(last[1:])
                else:
                    last = first + int(last[1:])
            elif not last:
                last_is_addr = True
                last = first + listsize
            elif last < first:
                # Treat as a count rather than an absolute location
                last = first + last

            return location.path, first, first_is_addr, last, last_is_addr, location.method
        pass
    return

# Demo it
if __name__=='__main__':
    from trepan.processor.command import mock as Mmock
    from trepan.processor.cmdproc import CommandProcessor
    import sys
    d = Mmock.MockDebugger()
    cmdproc = CommandProcessor(d.core)
    # print '-' * 10
    # print_source_line(sys.stdout.write, 100, 'source_line_test.py')
    # print '-' * 10
    cmdproc.frame = sys._getframe()
    cmdproc.setup()

    def five():
        return 5
    import os
    for cmd in (
            # "disasm",
            # "disasm +",
            # "disasm -",
            "disasm *5, *10",
            "disasm *15, 10",
            "disasm five(), *10",
            # "disasm 9 , 5",
            # "disasm 7 ,",
            # "disasm '''c:\\tmp\\foo.bat''':1",
            # 'disasm """/Users/My Documents/foo.py""":2',
            # 'disasm build_range()',
            # 'disasm os:1 ,',
            ):
        args = cmd.split(' ')
        cmdproc.current_command = cmd
        print(parse_addr_list_cmd(cmdproc, args))
    pass