# -*- coding: utf-8 -*-
#   Modification of Python's Lib/dis.py
# FIXME: see if we can use more of Lib/dis in Python3
'''Disassembly Routines'''

import inspect, sys, types
from dis import distb, findlabels, findlinestarts

from xdis import IS_PYPY, PYTHON_VERSION
from xdis.main import get_opcode
from xdis.bytecode import get_instructions_bytes, Bytecode

from trepan.lib import format as Mformat
format_token = Mformat.format_token

_have_code = (types.MethodType, types.FunctionType, types.CodeType, type)

def _try_compile(source, name):
    """Attempts to compile the given source, first as an expression and
       then as a statement if the first approach fails.

       Utility function to accept strings in functions that otherwise
       expect code objects
    """
    try:
        c = compile(source, name, 'eval')
    except SyntaxError:
        c = compile(source, name, 'exec')
    return c

# Modified from dis. Changed output to use msg, msg_nocr, section, and
# pygments.  Added first_line and last_line parameters

def dis(msg, msg_nocr, section, errmsg, x=None, start_line=-1, end_line=None,
        relative_pos = False, highlight='light', start_offset=0, end_offset=None,
        include_header=False):
    """Disassemble classes, methods, functions, or code.

    With no argument, disassemble the last traceback.

    """
    lasti = -1
    if x is None:
        distb()
        return None, None
    if start_offset is None:
        start_offset = 0
    mess = ''
    if start_line > 1:
        mess += "from line %d " % start_line
    elif start_offset > 1:
        mess = "from offset %d " % start_offset
    if end_line:
        mess += "to line %d" % end_line
    elif end_offset:
        mess += "to offset %d" % end_offset

    sectioned = False

    # Try to dogpaddle to the code object for the type setting x
    if hasattr(types, 'InstanceType') and isinstance(x, types.InstanceType):
        x = x.__class__
    if inspect.ismethod(x):
        section("Disassembly of %s: %s" % (x, mess))
        sectioned = True
        x = x.im_func
    elif inspect.isfunction(x) or inspect.isgeneratorfunction(x):
        section("Disassembly of %s: %s" % (x, mess))
        x = x.func_code
        sectioned = True
    elif inspect.isgenerator(x):
        section("Disassembly of %s: %s" % (x, mess))
        frame = x.gi_frame
        lasti = frame.f_last_i
        x = x.gi_code
        sectioned = True
    elif inspect.isframe(x):
        section("Disassembly of %s: %s" % (x, mess))
        sectioned = True
        if hasattr(x, 'f_lasti'):
            lasti = x.f_lasti
            if lasti == -1: lasti = 0
            pass
        opc = get_opcode(PYTHON_VERSION, IS_PYPY)
        x = x.f_code
        if include_header:
            header_lines = Bytecode(x, opc).info().split("\n")
            header = '\n'.join([format_token(Mformat.Comment, h) for h in header_lines])
            msg(header)
        pass
    elif inspect.iscode(x):
        pass

    if hasattr(x, '__dict__'):  # Class or module
        items = sorted(x.__dict__.items())
        for name, x1 in items:
            if isinstance(x1, _have_code):
                if not sectioned:
                    section("Disassembly of %s: " % x)
                try:
                    dis(msg, msg_nocr, section, errmsg, x1,
                        start_line=start_line, end_line=end_line,
                        relative_pos = relative_pos)
                    msg("")
                except TypeError:
                    _, msg, _ = sys.exc_info()
                    errmsg("Sorry:", msg)
                    pass
                pass
            pass
        pass
    elif hasattr(x, 'co_code'):  # Code object
        if not sectioned:
            section("Disassembly of %s: " % x)
        return disassemble(msg, msg_nocr, section, x, lasti=lasti,
                           start_line=start_line, end_line=end_line,
                           relative_pos = relative_pos,
                           highlight = highlight,
                           start_offset = start_offset,
                           end_offset = end_offset)
    elif isinstance(x, str):    # Source code
        return disassemble_string(msg, msg_nocr, x,)
    else:
        errmsg("Don't know how to disassemble %s objects." %
               type(x).__name__)
    return None, None

def disassemble(msg, msg_nocr, section, co, lasti=-1, start_line=-1,
                end_line=None, relative_pos=False, highlight='light',
                start_offset=0, end_offset=None):
    """Disassemble a code object."""
    return disassemble_bytes(msg, msg_nocr, co.co_code, lasti, co.co_firstlineno,
                             start_line, end_line, relative_pos,
                        co.co_varnames, co.co_names, co.co_consts,
                        co.co_cellvars, co.co_freevars,
                        dict(findlinestarts(co)), highlight,
                        start_offset=start_offset, end_offset=end_offset)

def disassemble_string(msg, msg_nocr, source):
    """Compile the source string, then disassemble the code object."""
    return disassemble_bytes(msg, msg_nocr, _try_compile(source, '<dis>'))

opc = get_opcode(PYTHON_VERSION, IS_PYPY)

def disassemble_bytes(orig_msg, orig_msg_nocr, code, lasti=-1, cur_line=0,
                      start_line=-1, end_line=None, relative_pos=False,
                      varnames=(), names=(), constants=(), cells=(),
                      freevars=(), linestarts={}, highlight='light',
                      start_offset=0, end_offset=None):
    """Disassemble byte string of code. If end_line is negative
    it counts the number of statement linestarts to use."""
    statement_count = 10000
    if end_line is None:
        end_line = 10000
    elif relative_pos:
        end_line += start_line -1
        pass

    labels = findlabels(code)

    null_print = lambda x: None
    if start_line > cur_line:
        msg_nocr = null_print
        msg = null_print
    else:
        msg_nocr = orig_msg_nocr
        msg = orig_msg

    for instr in get_instructions_bytes(code, opc, varnames, names,
                                        constants, cells, linestarts):
        offset = instr.offset
        if end_offset and offset > end_offset:
            break

        if instr.starts_line:
            if offset:
                msg("")

            cur_line = instr.starts_line
            if (start_line and ((start_line > cur_line) or
                                start_offset and start_offset > offset)) :
                msg_nocr = null_print
                msg = null_print
            else:
                statement_count -= 1
                msg_nocr = orig_msg_nocr
                msg = orig_msg
                pass
            if ((cur_line > end_line) or
                (end_offset and offset > end_offset)):
                break
            msg_nocr(format_token(Mformat.LineNumber,
                                  "%4d" % cur_line,
                                  highlight=highlight))
        else:
            if start_offset and offset and start_offset <= offset:
                msg_nocr = orig_msg_nocr
                msg = orig_msg
                pass
            msg_nocr('    ')

        if offset == lasti: msg_nocr(format_token(Mformat.Arrow, '-->',
                                                  highlight=highlight))
        else: msg_nocr('   ')
        if offset in labels: msg_nocr(format_token(Mformat.Arrow, '>>',
                                                   highlight=highlight))
        else: msg_nocr('  ')
        msg_nocr(repr(offset).rjust(4))
        msg_nocr(' ')
        msg_nocr(format_token(Mformat.Opcode,
                              instr.opname.ljust(20),
                              highlight=highlight))
        msg_nocr(repr(instr.arg).ljust(10))
        msg_nocr(' ')
        # Show argval?
        msg(format_token(Mformat.Name,
                         instr.argrepr.ljust(20),
                         highlight=highlight))
        pass

    return code, offset

# Demo it
if __name__ == '__main__':
    def msg(msg_str):
        print(msg_str)
        return

    def msg_nocr(msg_str):
        sys.stdout.write(msg_str)
        return

    def errmsg(msg_str):
        msg('*** ' + msg_str)
        return

    def section(msg_str):
        msg('=== ' + msg_str + ' ===')
        return
    curframe = inspect.currentframe()
    # dis(msg, msg_nocr, errmsg, section, curframe,
    #     start_line=10, end_line=40, highlight='dark')
    # print('-' * 40)
    # does nothing because start_offset is too high:
    # dis(msg, msg_nocr, errmsg, section, curframe,
    #     start_offset=10, end_offset=20, highlight='dark')
    print('-' * 40)
    dis(msg, msg_nocr, section, errmsg, disassemble)
    # print('-' * 40)
    # magic, moddate, modtime, co = pyc2code(sys.modules['types'].__file__)
    # disassemble(msg, msg_nocr, section, co, -1, 1, 70)
    pass


# -*- coding: utf-8 -*-
#   Copyright (C) 2008-2010, 2013, 2015, 2017-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/>.
""" Functions for working with Python frames"""
import inspect, linecache, os, re, xdis

from trepan.lib import bytecode as Mbytecode, printing as Mprint
from trepan.lib import format as Mformat
from trepan.lib.deparse import deparse_offset
from trepan.lib import pp as Mpp
from trepan.processor.cmdfns import deparse_fn
from xdis.main import get_opcode
from xdis import PYTHON_VERSION, IS_PYPY

format_token = Mformat.format_token

_with_local_varname = re.compile(r'_\[[0-9+]\]')

def count_frames(frame, count_start=0):
    "Return a count of the number of frames"
    count = -count_start
    while frame:
        count += 1
        frame = frame.f_back
    return count

import reprlib as Mrepr

_re_pseudo_file = re.compile(r'^<.+>')

def deparse_source_from_code(code):
    source_text = ''
    try:
        source_lines = deparse_fn(code).split("\n")
        source_text = ''
        for i, source_text in enumerate(source_lines):
            if len(source_text) > 0:
                break
        if len(source_lines) > 1:
            source_text += '...'
        source_text = '"%s"' % source_text
    except:
        pass
    return source_text

def format_stack_entry(dbg_obj, frame_lineno, lprefix=': ',
                       include_location=True, color='plain'):
    """Format and return a stack entry gdb-style.
    Note: lprefix is not used. It is kept for compatibility.
    """
    frame, lineno = frame_lineno
    filename = frame2file(dbg_obj.core, frame)

    s = ''
    if frame.f_code.co_name:
        funcname = frame.f_code.co_name
    else:
        funcname = "<lambda>"
        pass
    s = format_token(Mformat.Function, funcname, highlight=color)

    args, varargs, varkw, local_vars = inspect.getargvalues(frame)
    if '<module>' == funcname and ([], None, None,) == (args, varargs, varkw,):
        is_module = True
        if is_exec_stmt(frame):
            fn_name = format_token(Mformat.Function, 'exec', highlight=color)
            source_text = deparse_source_from_code(frame.f_code)
            s += ' %s(%s)' % (format_token(Mformat.Function, fn_name,
                                           highlight=color), source_text)
        else:
            fn_name = get_call_function_name(frame)
            if fn_name:
                source_text = deparse_source_from_code(frame.f_code)
                if fn_name: s += ' %s(%s)' % (format_token(Mformat.Function, fn_name,
                                                           highlight=color), source_text)
            pass
    else:
        is_module = False
        parms=inspect.formatargvalues(args, varargs, varkw, local_vars)
        maxargstrsize = dbg_obj.settings['maxargstrsize']
        if len(parms) >= maxargstrsize:
            parms = "%s...)" % parms[0:maxargstrsize]
            pass
        s += parms
        pass

    # Note: ddd can't handle wrapped stack entries (yet).
    # The 35 is hoaky though. FIXME.
    if len(s) >= 35: s += "\n    "

    if '__return__' in frame.f_locals:
        rv = frame.f_locals['__return__']
        s += '->'
        s += format_token(Mformat.Return, Mrepr.repr(rv),
                          highlight=color)
        pass

    if include_location:
        is_pseudo_file = _re_pseudo_file.match(filename)
        add_quotes_around_file = not is_pseudo_file
        if is_module:
            if filename == '<string>':
                s += ' in exec'
            elif not is_exec_stmt(frame) and not is_pseudo_file:
                s += ' file'
        elif s == '?()':
            if is_exec_stmt(frame):
                s = 'in exec'
                # exec_str = get_exec_string(frame.f_back)
                # if exec_str != None:
                #     filename = exec_str
                #     add_quotes_around_file = False
                #     pass
                # pass
            elif not is_pseudo_file:
                s = 'in file'
                pass
            pass
        elif not is_pseudo_file:
            s += ' called from file'
            pass

        if add_quotes_around_file: filename = "'%s'" % filename
        s += " %s at line %s" % (
            format_token(Mformat.Filename, filename,
                         highlight=color),
            format_token(Mformat.LineNumber, "%r" % lineno,
                         highlight=color)
            )
    return s

def frame2file(core_obj, frame, canonic=True):
    if canonic:
        return core_obj.filename(core_obj.canonic_filename(frame))
    else:
        return core_obj.filename(frame.f_code.co_filename)

def frame2filesize(frame):
    if '__cached__' in frame.f_globals:
        bc_path = frame.f_globals['__cached__']
    else:
        bc_path = None
    path = frame.f_globals['__file__']
    fs_size = os.stat(path).st_size
    if bc_path:
        (version, timestamp, magic_int, co, is_pypy,
         bc_source_size) =  xdis.load_module(bc_path, fast_load=True, get_code=False)
        return fs_size, bc_source_size
    elif os.path.exists(path):
        return fs_size, None
    else:
        return None, None

def check_path_with_frame(frame, path):
    my_size = os.stat(path).st_size
    fs_size, bc_size = frame2filesize(frame)
    if bc_size and bc_size != my_size:
        return False, "bytecode and local files mismatch"
    if fs_size and fs_size != my_size:
        return False, "debugger file and local files mismatch"
    return True, None

def is_exec_stmt(frame):
    """Return True if we are looking at an exec statement"""
    return hasattr(frame, 'f_back') and get_call_function_name(frame) == 'exec'

import dis

opc = get_opcode(PYTHON_VERSION, IS_PYPY)

def get_call_function_name(frame):
    """If f_back is looking at a call function, return
    the name for it. Otherwise return None"""
    f_back = frame.f_back
    if not f_back: return None
    if 'CALL_FUNCTION' != Mbytecode.op_at_frame(f_back): return None

    co         = f_back.f_code
    code       = co.co_code
    # labels     = dis.findlabels(code)
    linestarts = dict(dis.findlinestarts(co))
    offset     = f_back.f_lasti
    while offset >= 0:
        if offset in linestarts:
            op = code[offset]
            offset += 1
            arg = code[offset]
            # FIXME: put this code in xdis
            extended_arg = 0
            while True:
                if PYTHON_VERSION >= 3.6:
                    if op == opc.EXTENDED_ARG:
                        extended_arg += (arg << 8)
                        continue
                    arg = code[offset] + extended_arg
                    # FIXME: Python 3.6.0a1 is 2, for 3.6.a3 we have 1
                else:
                    if op == opc.EXTENDED_ARG:
                        extended_arg += (arg << 256)
                        continue
                    arg = code[offset] + code[offset+1]*256 + extended_arg
                break

            return co.co_names[arg]
        offset -= 1
        pass
    return None

def print_stack_entry(proc_obj, i_stack, color='plain', opts={}):
    frame_lineno = proc_obj.stack[len(proc_obj.stack)-i_stack-1]
    frame, lineno = frame_lineno
    intf = proc_obj.intf[-1]
    if frame is proc_obj.curframe:
        intf.msg_nocr(format_token(Mformat.Arrow, '->',
                                   highlight=color))
    else:
        intf.msg_nocr('##')
    intf.msg("%d %s" %
             (i_stack, format_stack_entry(proc_obj.debugger, frame_lineno,
                                          color=color)))
    if opts.get('source', False):
        filename = frame2file(proc_obj.core, frame)
        line = linecache.getline(filename, lineno, frame.f_globals)
        intf.msg(line)
        pass

    if opts.get('deparse', False):
        name = frame.f_code.co_name
        if frame.f_lasti == -1:
            last_i = 0
        else:
            last_i = frame.f_lasti
        deparsed, nodeInfo = deparse_offset(frame.f_code, name, last_i, None)
        if name == '<module>':
            name == 'module'
        if nodeInfo:
            extractInfo = deparsed.extract_node_info(nodeInfo)
            intf.msg(extractInfo.selectedLine)
            intf.msg(extractInfo.markerLine)
        pass
    if opts.get('full', False):
        names = list(frame.f_locals.keys())
        for name in sorted(names):
            if _with_local_varname.match(name):
                val = frame.f_locals[name]
            else:
                val = proc_obj.getval(name, frame.f_locals)
                pass
            width = opts.get('width', 80)
            Mpp.pp(val, width, intf.msg_nocr, intf.msg,
                   prefix='%s =' % name)
            pass

        deparsed, nodeInfo = deparse_offset(frame.f_code, name, frame.f_lasti, None)
        if name == '<module>':
            name == 'module'
        if nodeInfo:
            extractInfo = deparsed.extract_node_info(nodeInfo)
            intf.msg(extractInfo.selectedLine)
            intf.msg(extractInfo.markerLine)
        pass

def print_stack_trace(proc_obj, count=None, color='plain', opts={}):
    "Print count entries of the stack trace"
    if count is None:
        n=len(proc_obj.stack)
    else:
        n=min(len(proc_obj.stack), count)
    try:
        for i in range(n):
            print_stack_entry(proc_obj, i, color=color, opts=opts)
    except KeyboardInterrupt:
        pass
    return

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

def eval_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?
            val = eval(arg, None, None)
        else:
            val = eval(arg, frame.f_globals, frame.f_locals)
            pass
    except:
        return 'No symbol "' + arg + '" in current context.'

    return print_obj(arg, val, format, short)

def print_obj(arg, val, format=None, short=False):
    """Return a string representation of an object """
    what = arg
    if format:
        what = format + ' ' + arg
        val = Mprint.printf(val, format)
        pass
    s = '%s = %s' % (what, val)
    if not short:
        s += '\n  type = %s' % type(val)
        # Try to list the members of a class.
        # Not sure if this is correct or the
        # best way to do.
        s = print_dict(s, val, "object variables")
        if hasattr(val, "__class__"):
            s = print_dict(s, val.__class__, "class variables")
            pass
        pass
    return s

# Demo stuff above
if __name__=='__main__':
    class MockDebuggerCore:

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

        def filename(self, name):
            return name
        pass

    class MockDebugger:
        def __init__(self):
            self.core = MockDebuggerCore()
            self.settings = {
                'maxargstrsize': 80
                }
            pass
        pass

    frame = inspect.currentframe()
    m = MockDebugger()
    print(format_stack_entry(m, (frame, 10,)))
    print(format_stack_entry(m, (frame, 10,), color='dark'))
    print("frame count: %d" % count_frames(frame))
    print("frame count: %d" % count_frames(frame.f_back))
    print("frame count: %d" % count_frames(frame, 1))
    print("def statement: x=5?: %s" %
          repr(Mbytecode.is_def_stmt('x=5', frame)))
    # Not a "def" statement because frame is wrong spot
    print(Mbytecode.is_def_stmt('def foo():', frame))

    def sqr(x): x * x

    def fn(x):
        frame = inspect.currentframe()
        print(get_call_function_name(frame))
        return
    print('=' * 30)
    eval('fn(5)')
    print('=' * 30)
    print(print_obj('fn', fn))
    print('=' * 30)
    print(print_obj('len', len))
    print('=' * 30)
    print(print_obj('MockDebugger', MockDebugger))
    pass


# -*- coding: utf-8 -*-
#   Copyright (C) 2006-2010, 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/>.
"""Handles gdb-like subcommand processing."""

class Subcmd:
    """Gdb-like subcommand handling """
    def __init__(self, name, cmd_obj):
        self.name    = name
        self.cmd_obj = cmd_obj
        self.subcmds = {}
        self.cmdlist = []
        return

    def lookup(self, subcmd_prefix):
        """Find subcmd in self.subcmds"""
        for subcmd_name in list(self.subcmds.keys()):
            if subcmd_name.startswith(subcmd_prefix) \
               and len(subcmd_prefix) >= \
               self.subcmds[subcmd_name].__class__.min_abbrev:
                return self.subcmds[subcmd_name]
            pass
        return None

    def short_help(self, subcmd_cb, subcmd_name, label=False):
        """Show short help for a subcommand."""
        entry = self.lookup(subcmd_name)
        if entry:
            if label:
                prefix = entry.name
            else:
                prefix = ''
                pass
            if hasattr(entry, 'short_help'):
                if prefix: prefix += ' -- '
                self.cmd_obj.msg(prefix + entry.short_help)
                pass
            pass
        else:
            self.undefined_subcmd("help", subcmd_name)
            pass
        return

    def add(self, subcmd_cb):
        """Add subcmd to the available subcommands for this object.
        It will have the supplied docstring, and subcmd_cb will be called
        when we want to run the command. min_len is the minimum length
        allowed to abbreviate the command. in_list indicates with the
        show command will be run when giving a list of all sub commands
        of this object. Some commands have long output like "show commands"
        so we might not want to show that.
        """
        subcmd_name = subcmd_cb.name
        self.subcmds[subcmd_name] = subcmd_cb

        # We keep a list of subcommands to assist command completion
        self.cmdlist.append(subcmd_name)

    def run(self, subcmd_name, arg):
        """Run subcmd_name with args using obj for the environent"""
        entry=self.lookup(subcmd_name)
        if entry:
            entry['callback'](arg)
        else:
            self.cmdproc.undefined_cmd(entry.__class__.name, subcmd_name)
            pass
        return

    # Note: format of help is compatible with ddd.
    def help(self, *args):
        """help for subcommands."""

        print(args)
        subcmd_prefix = args[0]
        if not subcmd_prefix or len(subcmd_prefix) == 0:
            self.msg(self.doc)
            self.msg("""
List of %s subcommands:
""" % (self.name))
            for subcmd_name in self.list():
                self._subcmd_helper(subcmd_name, self, True, True)
            return

        entry = self.lookup(subcmd_prefix)
        if entry and hasattr(entry, 'help'):
            entry.help(args)
        else:
            self.cmd_obj.errmsg("Unknown 'help %s' subcommand %s"
                                % (self.name, subcmd_prefix))

    def list(self):
        l = list(self.subcmds.keys())
        l.sort()
        return l

    def undefined_subcmd(self, cmd, subcmd):
        """Error message when a subcommand doesn't exist"""
        self.cmd_obj.errmsg('Undefined "%s" command: "%s". Try "help".' %
                         (cmd, subcmd,))
        return
    pass

# When invoked as main program, invoke the debugger on a script
if __name__ == '__main__':

    from trepan.processor.command import mock as Mmock
    from trepan.processor.command import base_cmd as Mbase_cmd

    class TestCommand(Mbase_cmd.DebuggerCommand):
        '''Doc string for testing'''

        category = 'data'
        min_args = 0
        max_args = 5
        name = 'test'

        def __init__(self):
            self.name  = 'test'
            return

        def run(self, args): print('test command run')

    class TestTestingSubcommand:
        '''Doc string for test testing subcommand'''

        def __init__(self):
            self.name  = 'testing'
            return

        short_help = 'This is short help for test testing'
        min_abbrev = 4
        in_list    = True

        def run(self, args): print('test testing run')
        pass

    d = Mmock.MockDebugger()
    testcmd    = TestCommand()
    testcmd.debugger = d
    testcmd.proc     = d.core.processor
    testcmdMgr = Subcmd('test', testcmd)
    testsub = TestTestingSubcommand()
    testcmdMgr.add(testsub)

    for prefix in ['tes', 'test', 'testing', 'testing1']:
        x = testcmdMgr.lookup(prefix)
        if x: print(x.name)
        else: print('None')
        pass

    testcmdMgr.short_help(testcmd, 'testing')
    testcmdMgr.short_help(testcmd, 'test', True)
    testcmdMgr.short_help(testcmd, 'tes')
    print(testcmdMgr.list())
    testsub2 = TestTestingSubcommand()
    testsub2.name = 'foobar'
    testcmdMgr.add(testsub2)
    print(testcmdMgr.list())
    pass


#!/usr/bin/python3
"""
Cleans-up Sphinx-only constructs (ie from README.rst),
so that *PyPi* can format it properly.

To check for remaining errors, install ``sphinx`` and run::

        python setup.py --long-description | sed -file 'this_file.sed' | rst2html.py  --halt=warning

"""

import re

def yield_sphinx_only_markup(lines):
    """
    :param file_inp:     a `filename` or ``sys.stdin``?
    :param file_out:     a `filename` or ``sys.stdout`?`

    """
    substs = [
        ## Selected Sphinx-only Roles.
        #
        (r':abbr:`([^`]+)`',        r'\1'),
        (r':ref:`([^`]+)`',         r'`\1`_'),
        (r':term:`([^`]+)`',        r'**\1**'),
        (r':dfn:`([^`]+)`',         r'**\1**'),
        (r':(samp|guilabel|menuselection):`([^`]+)`',        r'``\2``'),

        ## Sphinx-only roles:
        #        :foo:`bar`   --> foo(``bar``)
        #        :a:foo:`bar` XXX afoo(``bar``)
        #
        #(r'(:(\w+))?:(\w+):`([^`]*)`', r'\2\3(``\4``)'),
        (r':(\w+):`([^`]*)`', r'\1(``\2``)'),

        ## Sphinx-only Directives.
        #
        (r'\.\. doctest',           r'code-block'),
        (r'\.\. plot::',            r'.. '),
        (r'\.\. seealso',           r'info'),
        (r'\.\. glossary',          r'rubric'),
        (r'\.\. figure::',          r'.. '),

        ## Other
        #
        (r'\|version\|',              r'x.x.x'),
    ]

    regex_subs = [ (re.compile(regex, re.IGNORECASE), sub) for (regex, sub) in substs ]

    def clean_line(line):
        try:
            for (regex, sub) in regex_subs:
                line = regex.sub(sub, line)
        except Exception as ex:
            print("ERROR: %s, (line(%s)"%(regex, sub))
            raise ex

        return line

    for line in lines:
        yield clean_line(line)


# -*- coding: utf-8 -*-
#   Copyright (C) 2008-2009, 2013-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/>.
'''Some singleton debugger methods that can be called without first
creating a debugger object -- these methods will create a debugger object,
if necessary, first.
'''

# The following routines could be done via something like the following
# untested code:
# def _build_standalone(methodname, docstring=None):
#     def function(arg, arg2=None, globals=None, locals=None):
#         Debugger().getattr(methodname)(arg, arg2, globals, locals)
#        return
#    function.__name__ = methodname
#    function.__doc__ = docstring
#    return function
#
# But this a bit cumbersome and perhaps overkill for the 2 or so
# functions below.  (It also doesn't work once we add the exception handling
# we see below. So for now, we'll live with the code duplication.

import sys

from trepan import debugger as Mdebugger, post_mortem as Mpost_mortem

def debugger_on_post_mortem():
    '''Call debugger on an exeception that terminates a program'''
    sys.excepthook = Mpost_mortem.post_mortem_excepthook
    return

def run_eval(expression, debug_opts=None, start_opts=None, globals_=None,
             locals_=None, tb_fn = None):

    """Evaluate the expression (given as a string) under debugger
    control starting with the statement subsequent to the place that
    this appears in your program.

    This is a wrapper to Debugger.run_eval(), so see that.

    When run_eval() returns, it returns the value of the expression.
    Otherwise this function is similar to run().
    """

    dbg = Mdebugger.Trepan(opts=debug_opts)
    try:
        return dbg.run_eval(expression, start_opts=start_opts,
                            globals_=globals_, locals_=locals_)
    except:
        dbg.core.trace_hook_suspend = True
        if start_opts and 'tb_fn' in start_opts: tb_fn = start_opts['tb_fn']
        Mpost_mortem.uncaught_exception(dbg, tb_fn)
    finally:
        dbg.core.trace_hook_suspend = False
    return

def run_call(func, debug_opts=None, start_opts=None, *args, **kwds):

    """Call the function (a function or method object, not a string)
    with the given arguments starting with the statement subsequent to
    the place that this appears in your program.

    When run_call() returns, it returns whatever the function call
    returned.  The debugger prompt appears as soon as the function is
    entered."""

    dbg = Mdebugger.Trepan(opts=debug_opts)
    try:
        return dbg.run_call(func, start_opts, *args, **kwds)
    except:
        Mpost_mortem.uncaught_exception(dbg)
        pass
    return

def run_exec(statement, debug_opts=None, start_opts=None, globals_=None,
             locals_=None):

    """Execute the statement (given as a string) under debugger
    control starting with the statement subsequent to the place that
    this run_call appears in your program.

    This is a wrapper to Debugger.run_exec(), so see that.

    The debugger prompt appears before any code is executed;
    you can set breakpoints and type 'continue', or you can step
    through the statement using 'step' or 'next'

    The optional globals_ and locals_ arguments specify the environment
    in which the code is executed; by default the dictionary of the
    module __main__ is used."""

    dbg = Mdebugger.Trepan(opts=debug_opts)
    try:
        return dbg.run_exec(statement, start_opts=start_opts,
                            globals_=globals_, locals_=locals_)
    except:
        Mpost_mortem.uncaught_exception(dbg)
        pass
    return

def debug(dbg_opts=None, start_opts=None, post_mortem=True,
          step_ignore=1, level=0):
    """
Enter the debugger.

Parameters
----------

level : how many stack frames go back. Usually it will be
the default 0. But sometimes though there may be calls in setup to the debugger
that you may want to skip.

step_ignore : how many line events to ignore after the
debug() call. 0 means don't even wait for the debug() call to finish.

param dbg_opts : is an optional "options" dictionary that gets fed
trepan.Debugger(); `start_opts' are the optional "options"
dictionary that gets fed to trepan.Debugger.core.start().

Use like this:

.. code-block:: python

    ... # Possibly some Python code
    import trepan.api # Needed only once
    ... # Possibly some more Python code
    trepan.api.debug() # You can wrap inside conditional logic too
    pass  # Stop will be here.
    # Below is code you want to use the debugger to do things.
    ....  # more Python code
    # If you get to a place in the program where you aren't going
    # want to debug any more, but want to remove debugger trace overhead:
    trepan.api.stop()

Parameter "level" specifies how many stack frames go back. Usually it will be
the default 0. But sometimes though there may be calls in setup to the debugger
that you may want to skip.

Parameter "step_ignore" specifies how many line events to ignore after the
debug() call. 0 means don't even wait for the debug() call to finish.

In situations where you want an immediate stop in the "debug" call
rather than the statement following it ("pass" above), add parameter
step_ignore=0 to debug() like this::

    import trepan.api  # Needed only once
    # ... as before
    trepan.api.debug(step_ignore=0)
    # ... as before

Module variable _debugger_obj_ from module trepan.debugger is used as
the debugger instance variable; it can be subsequently used to change
settings or alter behavior. It should be of type Debugger (found in
module trepan). If not, it will get changed to that type::

   $ python
   >>> from trepan.debugger import debugger_obj
   >>> type(debugger_obj)
   <type 'NoneType'>
   >>> import trepan.api
   >>> trepan.api.debug()
   ...
   (Trepan) c
   >>> from trepan.debugger import debugger_obj
   >>> debugger_obj
   <trepan.debugger.Debugger instance at 0x7fbcacd514d0>
   >>>

If however you want your own separate debugger instance, you can
create it from the debugger _class Debugger()_ from module
trepan.debugger::

  $ python
  >>> from trepan.debugger import Debugger
  >>> dbgr = Debugger()  # Add options as desired
  >>> dbgr
  <trepan.debugger.Debugger instance at 0x2e25320>

`dbg_opts' is an optional "options" dictionary that gets fed
trepan.Debugger(); `start_opts' are the optional "options"
dictionary that gets fed to trepan.Debugger.core.start().
"""
    if not isinstance(Mdebugger.debugger_obj, Mdebugger.Trepan):
        Mdebugger.debugger_obj = Mdebugger.Trepan(dbg_opts)
        Mdebugger.debugger_obj.core.add_ignore(debug, stop)
        pass
    core = Mdebugger.debugger_obj.core
    frame = sys._getframe(0+level)
    core.set_next(frame)
    if start_opts and 'startup-profile' in start_opts and start_opts['startup-profile']:
        dbg_initfiles = start_opts['startup-profile']
        from trepan import options
        options.add_startup_file(dbg_initfiles)
        for init_cmdfile in dbg_initfiles:
            core.processor.queue_startfile(init_cmdfile)

    if not core.is_started():
        core.start(start_opts)
        pass
    if post_mortem:
        debugger_on_post_mortem()
        pass
    if 0 == step_ignore:
        frame                   = sys._getframe(1+level)
        core.stop_reason        = 'at a debug() call'
        old_trace_hook_suspend  = core.trace_hook_suspend
        core.trace_hook_suspend = True
        core.processor.event_processor(frame, 'line', None)
        core.trace_hook_suspend = old_trace_hook_suspend
    else:
        core.step_ignore = step_ignore-1
        pass
    return

def stop(opts=None):
    if isinstance(Mdebugger.Trepan, Mdebugger.debugger_obj):
        return Mdebugger.debugger_obj.stop(opts)
    return None

# # Demo it
# if __name__=='__main__':
#     import tracer

#     def foo(n):
#         y = n
#         for i in range(n):
#             print(i)
#             pass
#         return y
#     from trepan.lib import default as Mdefault
#     settings = dict(Mdefault.DEBUGGER_SETTINGS)
#     settings.update({'trace': True, 'printset': tracer.ALL_EVENTS})
#     debug_opts={'step_ignore': -1, 'settings': settings}
#     print('Issuing: run_eval("1+2")')
#     print(run_eval('1+2', debug_opts=debug_opts))
#     print('Issuing: run_exec("x=1; y=2")')
#     run_exec('x=1; y=2', debug_opts=debug_opts)
#     print('Issuing: run_call(foo, debug_opts, None, 2)')
#     run_call(foo, debug_opts, None, 2)
#     # if len(sys.argv) > 1:
#     #     # FIXME: should this work better?
#     #     # print 'Issuing interactive: run_exec(x=1; y=2)'
#     #     # run_exec('x=1; y=2')
#     #     print 'Issuing interactive: run_call(foo)'
#     #     run_call(foo, debug_opts)
#     pass


# -*- coding: utf-8 -*-
#   Copyright (C) 2009, 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 2 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program; if not, write to the Free Software
#    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
#    02110-1301 USA.
import glob, os, re

# Our local modules
from trepan.processor.command import base_cmd as Mbase_cmd
from trepan.processor import cmdproc as Mcmdproc
from trepan.lib import complete as Mcomplete
from trepan import misc as Mmisc

categories = {
    'breakpoints' : 'Making the program stop at certain points',
    'data'        : 'Examining data',
    'files'       : 'Specifying and examining files',
    'running'     : 'Running the program',
    'status'      : 'Status inquiries',
    'support'     : 'Support facilities',
    'stack'       : 'Examining the call stack',
    'syntax'      : 'Debugger command syntax'
    }

class HelpCommand(Mbase_cmd.DebuggerCommand):
    """**help** [*command* [*subcommand*]|*expression*]

Without argument, print the list of available debugger commands.

When an argument is given, it is first checked to see if it is command
name.

If the argument is an expression or object name, you get the same
help that you would get inside a Python shell running the built-in
*help()* command.

If the environment variable *$PAGER* is defined, the file is
piped through that command.  You'll notice this only for long help
output.

Some commands like `info`, `set`, and `show` can accept an
additional subcommand to give help just about that particular
subcommand. For example `help info line` give help about the
info line command.

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

`examine` and `whatis`.
"""
    aliases       = ('?',)
    category      = 'support'
    min_args      = 0
    max_args      = None
    name          = os.path.basename(__file__).split('.')[0]
    need_stack    = False
    short_help    = 'Print commands or give help for command(s)'
    HELP_DIR      = os.path.join(os.path.dirname(__file__), 'help')
    RST_EXTENSION = '.rst'

    def complete(self, prefix):
        proc_obj = self.proc
        matches = Mcomplete.complete_token(list(categories.keys())
                                           + ['*', 'all'] +
                                           list(proc_obj.commands.keys()),
                                           prefix)
        aliases = Mcomplete.complete_token_filtered(proc_obj.aliases, prefix,
                                                    matches)
        return sorted(matches + aliases)

    def run(self, args):
        # It does not make much sense to repeat the last help
        # command. Also, given that 'help' uses PAGER, the you may
        # enter an extra CR which would rerun the (long) help command.
        self.proc.last_command=''

        if len(args) > 1:
            cmd_name = args[1]
            if cmd_name == '*':
                self.section("List of all debugger commands:")
                m = self.columnize_commands(list(self.proc.commands.keys()))
                self.msg_nocr(m)
                return
            elif cmd_name == 'aliases':
                self.show_aliases()
                return
            elif cmd_name == 'macros':
                self.show_macros()
                return
            elif cmd_name == 'syntax':
                self.show_command_syntax(args)
                return
            elif cmd_name in list(categories.keys()):
                self.show_category(cmd_name, args[2:])
                return

            command_name = Mcmdproc.resolve_name(self.proc, cmd_name)
            if command_name:
                instance = self.proc.commands[command_name]
                if hasattr(instance, 'help'):
                    return instance.help(args)
                else:
                    doc = instance.__doc__ or instance.run.__doc__
                    doc = doc.rstrip('\n')
                    self.rst_msg(doc.rstrip("\n"))
                    aliases = [key for key in self.proc.aliases
                               if command_name == self.proc.aliases[key]]
                    if len(aliases) > 0:
                        self.msg('')
                        msg = Mmisc.wrapped_lines('Aliases:',
                                                  ', '.join(aliases) + '.',
                                                  self.settings['width'])
                        self.msg(msg)
                        pass
                    pass
            else:
                cmds = [cmd for cmd in list(self.proc.commands.keys())
                        if re.match('^' + cmd_name, cmd) ]
                if cmds is None:
                    self.errmsg("No commands found matching /^%s/. "
                                "Try \"help\"." % cmd_name)
                elif len(cmds) == 1:
                    self.msg("Pattern '%s' matches command %s..." %
                             (cmd_name, cmds[0],))
                    args[1] = cmds[0]
                    self.run(args)
                else:
                    self.section("Command names matching /^%s/:" % cmd_name)
                    self.msg_nocr(self.columnize_commands(cmds))
                    pass
            return
        else:
            self.list_categories()
            pass

        return False

    def list_categories(self):
        """List the command categories and a short description of each."""
        self.section("Classes of commands:")
        cats = list(categories.keys())
        cats.sort()
        for cat in cats:  # Foo! iteritems() doesn't do sorting
            self.msg("  %-13s -- %s" % (cat, categories[cat]))
            pass
        final_msg = """
Type `help` followed by a class name for a list of commands in that class.
Type `help aliases` for a list of current aliases.
Type `help macros` for a list of current macros.
Type `help syntax *item*` for help on syntax *item*
Type `help *` for the list of all commands.
Type `help` *regexp* for the list of commands matching /^#{*regexp*}/
Type `help` followed by command name for full documentation.
"""
        for line in re.compile('\n').split(final_msg.rstrip('\n')):
            self.rst_msg(line)
            pass
        return

    def show_category(self, category, args):
        """Show short help for all commands in `category'."""
        n2cmd = self.proc.commands
        names = list(n2cmd.keys())
        if len(args) == 1 and args[0] == '*':
            self.section("Commands in class %s:" % category)
            cmds = [cmd for cmd in names if category == n2cmd[cmd].category]
            cmds.sort()
            self.msg_nocr(self.columnize_commands(cmds))
            return

        self.msg("%s.\n" % categories[category])
        self.section("List of commands:")
        names.sort()
        for name in names:  # Foo! iteritems() doesn't do sorting
            if category != n2cmd[name].category: continue
            self.msg("%-13s -- %s" % (name, n2cmd[name].short_help,))
            pass
        return

    def syntax_files(self):
        path = os.path.join(self.HELP_DIR, ("*%s" % self.RST_EXTENSION))
        files = glob.glob(path)
        return [os.path.basename(name).split('.')[0] for
                name in files]

    def show_aliases(self):
        self.section('All alias names:')
        m = self.columnize_commands(list(sorted(self.proc.aliases.keys())))
        self.msg_nocr(m)

    def show_macros(self):
        self.section('All macro names:')
        m = self.columnize_commands(list(sorted(self.proc.macros.keys())))
        self.msg_nocr(m)

    def init_syntax_summary_help(self):
        self.syntax_summary_help = {}
        self.syntax_help = {}
        for name in self.syntax_files():
            path = os.path.join(self.HELP_DIR, "%s%s" %
                                (name, self.RST_EXTENSION))
            self.syntax_help[name] = ''.join(open(path).
                                             readlines())
            self.syntax_summary_help[name] = open(path).\
                                             readline().strip()
            pass
        return

    def show_command_syntax(self, args):
        if not hasattr(self, 'syntax_summary_help'):
            self.init_syntax_summary_help()
            pass
        if len(args) == 2 or len(args) == 3 and args[2] == '*':
            self.section("List of syntax help")
            for name, help in self.syntax_summary_help.items():
                self.msg("  %-8s -- %s" % (name, help))
                pass
        else:
            for name in args[2:]:
                if name in self.syntax_files():
                    self.rst_msg(self.syntax_help[name])
                else:
                    self.errmsg("No syntax help for %s" % name)
                pass
            pass
        return
    pass

if __name__ == '__main__':
    from trepan.processor.command import mock
    d, cp = mock.dbg_setup()
    command = HelpCommand(cp)
    # print('-' * 20)
    # command.run(['help'])
    # print('-' * 20)
    # command.run(['help', '*'])
    # print('-' * 20)
    # command.run(['help', 'quit'])
    # print('-' * 20)
    # command.run(['help', 'stack'])
    # print('-' * 20)
    # command.run(['help', 'breakpoints'])
    # print('-' * 20)
    # command.run(['help', 'breakpoints', '*'])
    # print('-' * 20)
    # command.run(['help', 'c.*'])
    # print('-' * 20)
    command.show_command_syntax(['help', 'syntax'])
    command.show_command_syntax(['help', 'syntax', 'command'])
    pass


# -*- coding: utf-8 -*-
#   Copyright (C) 2008-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/>.
import inspect, os, re

# Our local modules
from trepan.processor.command import base_subcmd as Mbase_subcmd
from trepan.lib import file
from trepan import clifns as Mclifns, misc as Mmisc

def find_function(funcname, filename):
    cre = re.compile(r'def\s+%s\s*[(]' % re.escape(funcname))
    try:
        fp = open(filename)
    except IOError:
        return None
    # consumer of this info expects the first line to be 1
    lineno = 1
    answer = None
    while True:
        line = fp.readline()
        if line == '':
            break
        if cre.match(line):
            answer = funcname, filename, lineno
            break
        lineno = lineno + 1
        pass
    fp.close()
    return answer

class InfoLine(Mbase_subcmd.DebuggerSubcommand):
    '''**info line**

Show information about the current line

See also:
---------
`info program`, `info frame`'''

    min_abbrev = 2
    max_args = 0
    need_stack = True
    short_help = 'Show current-line information'

    def lineinfo(self, identifier):
        failed = (None, None, None)
        # Input is identifier, may be in single quotes
        idstring = identifier.split("'")
        if len(idstring) == 1:
            # not in single quotes
            ident = idstring[0].strip()
        elif len(idstring) == 3:
            # quoted
            ident = idstring[1].strip()
        else:
            return failed
        if ident == '': return failed
        parts = ident.split('.')
        # Protection for derived debuggers
        if parts[0] == 'self':
            del parts[0]
            if len(parts) == 0:
                return failed
        # Best first guess at file to look at
        fname = self.proc.defaultFile()
        if len(parts) == 1:
            item = parts[0]
        else:
            # More than one part.
            # First is module, second is method/class
            m, f = file.lookupmodule('.'.join(parts[1:]),
                                     self.debugger.mainpyfile,
                                     self.core.canonic)
            if f:
                fname = f
            item = parts[-1]
        answer = find_function(item, fname)
        return answer or failed

    def run(self, args):
        """Current line number in source file"""
        # info line identifier
        if not self.proc.curframe:
            self.errmsg("No line number information available.")
            return
        if len(args) == 3:
            # lineinfo returns (item, file, lineno) or (None,)
            answer = self.lineinfo(args[2])
            if answer[0]:
                item, filename, lineno = answer
                if not os.path.isfile(filename):
                    filename = Mclifns.search_file(filename,
                                                   self.core.search_path,
                                                   self.main_dirname)
                self.msg('Line %s of "%s" <%s>' %
                         (lineno, filename, item))
            return
        filename=self.core.canonic_filename(self.proc.curframe)
        if not os.path.isfile(filename):
            filename = Mclifns.search_file(filename, self.core.search_path,
                                           self.main_dirname)
            pass

        filename = self.core.canonic_filename(self.proc.curframe)
        msg1 = 'Line %d of \"%s\"'  % (inspect.getlineno(self.proc.curframe),
                                       self.core.filename(filename))
        msg2 = ('at instruction %d' % self.proc.curframe.f_lasti)
        if self.proc.event:
            msg2 += ', %s event' % self.proc.event
            pass
        self.msg(Mmisc.wrapped_lines(msg1, msg2, self.settings['width']))
        return False
    pass

if __name__ == '__main__':
    from trepan.processor.command import mock, info as Minfo
    from trepan import debugger as Mdebugger
    d = Mdebugger.Trepan()
    d, cp = mock.dbg_setup(d)
    i = Minfo.InfoCommand(cp)
    sub = InfoLine(i)
    sub.run([])
    cp.curframe = inspect.currentframe()
    for width in (80, 200):
        sub.settings['width'] = width
        sub.run(['file.py', 'lines'])
        pass
    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/>.
'''Things related to file/module status'''
import os, pyficache, stat, sys

def file_list():
    return list(set(pyficache.cached_files() +
                    list(pyficache.file2file_remap.keys())))

def is_compiled_py(filename):
    """
    Given a file name, return True if the suffix is pyo or pyc (an
    optimized bytecode file).
    """
    return True if filename[-4:].lower() in ('.pyc', '.pyo') else False

READABLE_MASK = (stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH)
def readable(path):
    """Test whether a path exists and is readable.  Returns None for
    broken symbolic links or a failing stat() and False if
    the file exists but does not have read permission. True is returned
    if the file is readable."""
    try:
        st = os.stat(path)
        return 0 != st.st_mode & READABLE_MASK
    except os.error:
        return None
    return True

def lookupmodule(name):
    """lookupmodule()->(module, file) translates a possibly incomplete
    file or module name into an absolute file name. None can be
    returned for either of the values positions of module or file when
    no or module or file is found.
    """
    if sys.modules.get(name):
        return (sys.modules[name], sys.modules[name].__file__)
    if os.path.isabs(name) and readable(name):
        return (None, name)
    f = os.path.join(sys.path[0], name)
    if readable(f):
        return (None, f)
    root, ext = os.path.splitext(name)
    if ext == '':
        name = name + '.py'
        pass
    if os.path.isabs(name):
        return (None, name)
    for dirname in sys.path:
        while os.path.islink(dirname):
            dirname = os.readlink(dirname)
            pass
        fullname = os.path.join(dirname, name)
        if readable(fullname):
            return (None, fullname)
        pass
    return (None, None)

def parse_position(errmsg, arg):
    """parse_position(errmsg, arg)->(fn, name, lineno)

    Parse arg as [filename|module:]lineno
    Make sure it works for C:\foo\bar.py:12
    """
    colon = arg.rfind(':')
    if colon >= 0:
        filename = arg[:colon].rstrip()
        m, f = lookupmodule(filename)
        if not f:
            errmsg("'%s' not found using sys.path" % filename)
            return (None, None, None)
        else:
            filename = pyficache.pyc2py(f)
            arg = arg[colon+1:].lstrip()
            pass
        try:
            lineno = int(arg)
        except TypeError:
            errmsg("Bad line number: %s", str(arg))
            return (None, filename, None)
        return (None, filename, lineno)
    return (None, None, None)

# Demo it
if __name__=='__main__':
    import tempfile
    print('readable("fdafsa"): %s' % readable('fdafdsa'))
    for mode, can_read in [(stat.S_IRUSR, True), (stat.S_IWUSR, False)]:
        f = tempfile.NamedTemporaryFile()
        os.chmod(f.name, mode)
        print("readable('%s'): %s" % (f.name, readable(f.name)))
        f.close()
        pass
    print("lookupmodule('os.path'): %s" % repr(lookupmodule('os.path')))
    print("lookupmodule(__file__): %s" % repr(lookupmodule(__file__)))
    print("lookupmodule('fafdsadsa'): %s" % repr(lookupmodule('fafdsafdsa')))

    pass


# -*- coding: utf-8 -*-
#   Copyright (C) 2008-2009, 2013-2014 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/>.
"""Routines related to threading. Assumes Python 2.5 or greater"""

import threading

def current_thread_name():
    return threading.currentThread().getName()

def find_debugged_frame(frame):
    """Find the first frame that is a debugged frame. We do this
    Generally we want traceback information without polluting it with
    debugger frames. We can tell these because those are frames on the
    top which don't have f_trace set. So we'll look back from the top
    to find the fist frame where f_trace is set.
    """
    f_prev = f = frame
    while f is not None and f.f_trace is None:
        f_prev = f
        f = f.f_back
        pass
    if f_prev:
        val = f_prev.f_locals.get('tracer_func_frame')
        if val == f_prev:
            if f_prev.f_back:
                f_prev = f_prev.f_back
                pass
            pass
        pass
    else:
        return frame
    return f_prev

def id2thread_name(thread_id):
    return threading.Thread.getName(threading._active[thread_id])

def map_thread_names():
    '''Invert threading._active'''
    name2id = {}
    for thread_id in list(threading._active.keys()):
        thread = threading._active[thread_id]
        name = thread.getName()
        if name not in list(name2id.keys()):
            name2id[name] = thread_id
            pass
        pass
    return name2id

# Demo this masterpiece:
if __name__ == '__main__':
    import sys
    print('=' * 10)

    def showit():
        print('Current thread: %s' % current_thread_name())
        print('All threads:')
        for thread_id, f in list(sys._current_frames().items()):
            print('  %s %s' % (id2thread_name(thread_id),
                               find_debugged_frame(f)))
            pass
        print('-' * 10)
        print('Thread->id map:')
        print(map_thread_names())
        print('=' * 10)
    showit()

    class BgThread(threading.Thread):
        def __init__(self):
            threading.Thread.__init__(self)
            return

        def run(self):
            showit()
            return
        pass

    background = BgThread()
    background.start()
    background.join()    # Wait for the background task to finish
    pass


# -*- coding: utf-8 -*-
#   Copyright (C) 2009, 2013-2014 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/>.
"""Simulate I/O using lists of strings. """

import types

from trepan.inout import base as Mbase

class StringArrayInput(Mbase.DebuggerInputBase):
    """Simulate I/O using an array of strings. Sort of like StringIO, but
    even simplier. """

    def __init__(self, inp=[], opts=None):
        self.input  = inp
        self.closed = False
        return

    def close(self):
        'Interface is for compatibility'
        self.closed = True
        return

    def open(self, inp, opts=None):
        """Use this to set where to read from.
        """
        if isinstance(inp, list):
            self.input = inp
        else:
            raise IOError("Invalid input type (%s) for %s" % (type(inp), inp))
        return

    def readline(self, use_raw=None, prompt=''):
        """Read a line of input. EOFError will be raised on EOF.

        Note that we don't support prompting"""
        if self.closed: raise ValueError
        if 0 == len(self.input):
            self.closed = True
            raise EOFError
        line = self.input[0]
        del self.input[0]
        return line
    pass

class StringArrayOutput(Mbase.DebuggerInOutBase):
    """Simulate I/O using an array of strings. Sort of like StringIO, but
    even simplier. """

    def __init__(self, out=[], opts=None):
        self.flush_after_write = False  # For compatibility
        self.closed = False
        self.output = out
        return

    def close(self):
        'Nothing to do here. Interface is for compatibility'
        self.closed = True
        return

    def flush(self):
        'Nothing to do here. Interface is for compatibility'
        return

    def open(self, output):
        """Use this to set where to write to. output can be a
        file object or a string. This code raises IOError on error.

        If another file was previously open upon calling this open,
        that will be stacked and will come back into use after
        a close_write().
        """
        if isinstance(output, types.Listype):
            self.output = output
        else:
            raise IOError("Invalid output type (%s) for %s" % (type(output),
                                                                 output))
        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'.
        """
        if self.closed: raise ValueError
        if [] == self.output:
            self.output = [msg]
        else:
            self.output[-1] += msg
            pass
        return

    def writeline(self, msg):
        """ used to write to a debugger that is connected to this
        server; Here, we use the null string '' as an indicator of a
        newline.
        """
        self.write(msg)
        self.output.append('')
        return

    pass

# Demo
if __name__=='__main__':
    inp= StringArrayInput(['Now is the time', 'for all good men'])
    line = inp.readline()
    print(line)
    line = inp.readline()
    print(line)
    try:
        line = inp.readline()
    except EOFError:
        print('EOF hit on read')
        pass
    out = StringArrayOutput()
    print(out.output)
#    line = io.readline("Type some more characters: ")
    out.writeline("Hello, world!")
    print(out.output)
    out.write("Hello")
    print(out.output)
    out.writeline(", again.")
    print(out.output)
#     io.open_write(sys.stdout)
    out.flush_after_write = True
    out.write("Last hello")
    out.close()
    print(out.output)
    try:
        out.writeline("You won't see me")
    except:
        pass
    # Closing after already closed is okay
    out.close()
    inp.close()
    pass


#!/usr/bin/env python
# -*- coding: iso-8859-1 -*-
#   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/>.

import os, sys, codecs

from optparse import OptionParser
from trepan import debugger as Mdebugger, api as Mapi, clifns as Mclifns
from trepan.lib import file as Mfile
from trepan.inout import output as Moutput

def default_configfile(base_filename):
    '''Return fully expanded configuration filename location for
    base_filename. python2 and  python3 debuggers share the smae
    directory: ~/.config/trepan.py
    '''
    file_dir = os.path.join(os.environ.get('HOME', '~'), '.config', 'trepanpy')
    file_dir = Mclifns.path_expanduser_abs(file_dir)

    if not os.path.isdir(file_dir):
        os.makedirs(file_dir, mode=0o755)
    return os.path.join(file_dir, base_filename)

def add_startup_file(dbg_initfiles):
    """ Read debugger startup file(s): both python code and
    debugger profile to dbg_initfiles."""

    startup_python_file = default_configfile('profile.py')

    if Mfile.readable(startup_python_file):
        with codecs.open(startup_python_file, 'r', encoding='utf8') as fp:
                exec(fp.read())

    startup_trepan_file = default_configfile('profile')
    if Mfile.readable(startup_trepan_file):
        if startup_trepan_file not in dbg_initfiles:
            dbg_initfiles.append(startup_trepan_file)
        pass
    return

def process_options(debugger_name, pkg_version, sys_argv, option_list=None):
    """Handle debugger options. Set `option_list' if you are writing
    another main program and want to extend the existing set of debugger
    options.

    The options dicionary from optparser is returned. sys_argv is
    also updated."""
    usage_str="""%prog [debugger-options] [python-script [script-options...]]

    Runs the extended python debugger"""

    # serverChoices = ('TCP','FIFO', None)

    optparser = OptionParser(usage=usage_str, option_list=option_list,
                             version="%%prog version %s" % pkg_version)

    optparser.add_option("-X", "--trace", dest="linetrace",
                         action="store_true", default=False,
                         help="Show lines before executing them. "
                         "This option also sets --batch")
    optparser.add_option("-F", "--fntrace", dest="fntrace",
                         action="store_true", default=False,
                         help="Show functions before executing them. "
                         "This option also sets --batch")
    optparser.add_option("--basename", dest="basename",
                         action="store_true", default=False,
                         help="Filenames strip off basename, "
                         "(e.g. for regression tests)"
                         )
    #     optparser.add_option("--batch", dest="noninteractive",
    #                          action="store_true", default=False,
    #                          help="Don't run interactive commands shell on "+
    #                          "stops.")
    optparser.add_option("--client", dest="client",
                         action='store_true',
                         help="Connect to an existing debugger process "
                         "started with the --server option. "
                         "See options for client.")
    optparser.add_option("-x", "--command", dest="command",
                         action="store", type='string', metavar='FILE',
                         help="Execute commands from FILE.")
    optparser.add_option("--cd", dest="cd",
                         action="store", type='string', metavar='DIR',
                         help="Change current directory to DIR.")
    optparser.add_option("--confirm", dest="confirm",
                         action="store_true", default=True,
                         help="Confirm potentially dangerous operations")
    optparser.add_option("--dbg_trepan", dest="dbg_trepan",
                         action="store_true", default=False,
                         help="Debug the debugger")
    optparser.add_option("--different", dest="different",
                         action="store_true", default=True,
                         help="Consecutive stops should have "
                         "different positions")
    #     optparser.add_option("--error", dest="errors", metavar='FILE',
    #                          action="store", type='string',
    #                          help="Write debugger's error output "
    #                          + "(stderr) to FILE")
    optparser.add_option("-e", "--exec", dest="execute", type="string",
                         help="list of debugger commands to " +
                         "execute. Separate the commands with ;;")

    optparser.add_option("-H", "--host", dest="host", default='127.0.0.1',
                         action="store", type='string', metavar='IP-OR-HOST',
                         help="connect IP or host name. "
                         "Only valid if --client option given.")

    optparser.add_option("--highlight", dest="highlight",
                         action="store", type='string',
                         metavar='{light|dark|plain}',
                         default='light',
                         help="Use syntax and terminal highlight output. "
                         "'plain' is no highlight")

    optparser.add_option("--private", dest="private",
                         action='store_true', default=False,
                         help="Don't register this as a global debugger")

    optparser.add_option("--main", dest="main",
                         action="store_true", default=True,
                         help="First stop should be in __main__"
                         )
    optparser.add_option("--no-main", dest="main",
                         action="store_false", default=True,
                         help="First stop should be in __main__"
                         )
    optparser.add_option("--post-mortem", dest="post_mortem",
                         action='store_true', default=True,
                         help=("Enter debugger on an uncaught (fatal) "
                               "exception"))

    optparser.add_option("--no-post-mortem", dest="post_mortem",
                         action='store_false', default=True,
                         help=("Don't enter debugger on an uncaught (fatal) "
                               "exception"))

    optparser.add_option("-n", "--nx", dest="noexecute",
                         action="store_true", default=False,
                         help=("Don't execute commands found in any "
                               "initialization files"))

    optparser.add_option("-o", "--output", dest="output", metavar='FILE',
                         action="store", type='string',
                         help=("Write debugger's output (stdout) "
                               "to FILE"))
    optparser.add_option("-P", "--port", dest="port", default=1027,
                         action="store", type='int',
                         help="Use TCP port number NUMBER for "
                         "out-of-process connections.")

    optparser.add_option("--server", dest="server",
                         action='store_true',
                         help="Out-of-process server connection mode")

    # optparser.add_option("--style", dest="style",
    #                      action="store", type='string',
    #                      metavar='*pygments-style*',
    #                      default=None,
    #                      help=("Pygments style; 'none' "
    #                            "uses 8-color rather than 256-color terminal"))

    optparser.add_option("--sigcheck", dest="sigcheck",
                         action="store_true", default=False,
                         help="Set to watch for signal handler changes")
    optparser.add_option("-t", "--target", dest="target",
                         help=("Specify a target to connect to. Arguments"
                               " should be of form, 'protocol address'.")),
    optparser.add_option("--from_ipython", dest='from_ipython', action='store_true',
                         default=False, help="Called from inside ipython")

    # annotate option produces annotations, used in trepan.el for a
    # better emacs integration. Annotations are similar in purpose to
    # those of GDB (see that manual for a description), although the
    # syntax is different.  they have the following format:
    #
    # ^Z^Zannotation-name
    # <arbitrary text>
    # ^Z^Z
    #
    # where ^Z is the ctrl-Z character, and "annotname" is the name of the
    # annotation. A line with only two ^Z ends the annotation (no nesting
    # allowed). See trepan.el for the usage
    optparser.add_option("--annotate", default=0, type="int",
                         help="Use annotations to work inside emacs")

    # Set up to stop on the first non-option because that's the name
    # of the script to be debugged on arguments following that are
    # that scripts options that should be left untouched.  We would
    # not want to interpret and option for the script, e.g. --help, as
    # one one of our own, e.g. --help.

    optparser.disable_interspersed_args()

    sys.argv = list(sys_argv)
    # FIXME: why does this mess up integration tests?
    # (opts, sys.argv) = optparser.parse_args(sys_argv)
    (opts, sys.argv) = optparser.parse_args()
    dbg_opts = {'from_ipython': opts.from_ipython}

    # Handle debugger startup command files: --nx (-n) and --command.
    dbg_initfiles = []
    if not opts.noexecute:
        add_startup_file(dbg_initfiles)

    # As per gdb, first we execute user initialization files and then
    # we execute any file specified via --command.
    if opts.command:
        dbg_initfiles.append(opts.command)
        pass

    dbg_opts['proc_opts'] = {'initfile_list': dbg_initfiles}

    if opts.cd:
        os.chdir(opts.cd)
        pass

    if opts.output:
        try:
            dbg_opts['output'] = Moutput.DebuggerUserOutput(opts.output)
        except IOError:
            _, xxx_todo_changeme, _ = sys.exc_info()
            (errno, strerror) = xxx_todo_changeme.args
            print("I/O in opening debugger output file %s" % opts.output)
            print("error(%s): %s" % (errno, strerror))
        except:
            print("Unexpected error in opening debugger output file %s" %
                  opts.output)
            print(sys.exc_info()[0])
            sys.exit(2)
            pass
        pass

    return opts, dbg_opts, sys.argv

def _postprocess_options(dbg, opts):
    ''' Handle options (`opts') that feed into the debugger (`dbg')'''
    # Set dbg.settings['printset']
    print_events = []
    if opts.fntrace:   print_events = ['c_call', 'c_return', 'call', 'return']
    if opts.linetrace: print_events += ['line']
    if len(print_events):
        dbg.settings['printset'] = frozenset(print_events)
        pass

    for setting in ('annotate', 'basename', 'different'):
        dbg.settings[setting] = getattr(opts, setting)
        pass

    if getattr(opts, 'highlight'):
        dbg.settings['highlight'] = opts.highlight
    else:
        dbg.settings['highlight'] = 'plain'

    # if getattr(opts, 'style') and opts.style != 'none':
    #     dbg.settings['style'] = opts.style
    # else:
    #     dbg.settings['style'] = None
    dbg.settings['style'] = None

    # Normally we want to set Mdebugger.debugger_obj so that one can
    # put trepan.debugger breakpoints in a program and not have more
    # than one debugger running. More than one debugger may confuse
    # users, e.g. set different might stop at the same line once for
    # each debugger.
    if not opts.private:
        Mdebugger.debugger_obj = dbg
        pass

    #     if opts.errors:
    #         try:
    #             dbg.stderr = open(opts.errors, 'w')
    #         except IOError, (errno, strerror):
    #             print "I/O in opening debugger output file %s" % opts.errors
    #             print "error(%s): %s" % (errno, strerror)
    #         except ValueError:
    #             print "Could not convert data to an integer."
    #         except:
    #             print "Unexpected error in opening debugger output "
    #                   "file %s" % opts.errors
    #             print sys.exc_info()[0]
    #             sys.exit(2)

    #     if opts.execute:
    #         dbg.cmdqueue = list(opts.execute.split(';;'))

    if opts.post_mortem:
        Mapi.debugger_on_post_mortem()
        pass
    return

# Demo it
if __name__=='__main__':
    import pprint

    def doit(prog, version, arg_str):
        print("options '%s'" % arg_str)
        args = arg_str.split()
        opts, dbg_opts, sys_argv = process_options('testing', version, args)
        pp.pprint(vars(opts))
        print('')
        return

    pp = pprint.PrettyPrinter(indent=4)
    doit('testing', '1.1', '')
    doit('testing', '1.2', 'foo bar')
    doit('testing', '1.3', '--server')
    doit('testing', '1.3', '--command %s bar baz' % __file__)
    doit('testing', '1.4', '--server --client')
    doit('testing', '1.5', '--style=emacs')
    doit('testing', '1.6', '--help')  # exits, so must be last
    pass


# -*- coding: utf-8 -*-
#   Copyright (C) 2009, 2013-2014, 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/>.
"""Module for client (i.e. user to communication-device) interaction.
The debugged program is at the other end of the communcation."""

# Our local modules
from trepan.interfaces import user as Muser
from trepan.inout import tcpclient as Mtcpclient, fifoclient as Mfifoclient

DEFAULT_INIT_CONNECTION_OPTS = {'IO': 'TCP'}

class ClientInterface(Muser.UserInterface):
    """Interface for a user which is attached to a debugged process
    via some sort of communication medium (e.g. socket, tty, FIFOs).
    This could be on the same computer in a different process or on
    a remote computer."""

    def __init__(self, inp=None, out=None, inout=None, user_opts={},
                 connection_opts={}):

        opts = DEFAULT_INIT_CONNECTION_OPTS.copy()
        opts.update(connection_opts)

        Muser.UserInterface.__init__(self, inp, out, user_opts)

        self.inout = None  # initialize in case assignment below fails
        if inout:
            self.inout = inout
        else:
            self.server_type = opts['IO']
            if 'FIFO' == self.server_type:
                self.inout = Mfifoclient.FIFOClient(opts=opts)
            elif 'TCP' == self.server_type:
                self.inout = Mtcpclient.TCPClient(opts=opts)
            else:
                self.errmsg("Expecting server type TCP or FIFO. Got: %s." %
                            self.server_type)
                return
            pass
        return

    def read_remote(self):
        '''Send a message back to the server (in contrast to
        the local user output channel).'''
        coded_line = self.inout.read_msg()
        if isinstance(coded_line, bytes):
            coded_line = coded_line.decode("utf-8")
        control = coded_line[0]
        remote_line = coded_line[1:]
        return (control, remote_line)

    def write_remote(self, code, msg):
        '''Send a message back to the server (in contrast to
        the local user output channel).'''
        # FIXME change into write_xxx
        return self.inout.writeline(code + msg)
    pass

# Demo
if __name__=='__main__':
    intf = ClientInterface()


# -*- coding: utf-8 -*-
#   Copyright (C) 2013, 2015, 2017-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/>.
''' Common command-parsing routines such as check command argument
counts, to parse a string for an integer, or check a string for an
on/off setting value.
'''
import os, sys, tempfile
import pyficache
from xdis import IS_PYPY

def source_tempfile_remap(prefix, text):
    fd = tempfile.NamedTemporaryFile(suffix='.py',
                                     prefix=prefix,
                                     delete=False)
    with fd:
        fd.write(bytes(text, 'UTF-8'))
        fd.close()
        pass
    return fd.name

def deparse_fn(code):
    try:
        from uncompyle6.semanitcs.linemap import (
            deparse_code_with_fragments_and_map as deparse_code)
    except ImportError:
        return None
    sys_version = sys.version[:5]
    try:
        float_version = py_str2float(sys_version)
        deparsed = deparse_code(float_version, code, is_pypy=IS_PYPY)
        return deparsed
    except:
        raise
    return None

def deparse_getline(code, filename, line_number, opts):
    # I Would like to figure out how to deparse the entire module,
    # instead doing this on a line-by-line basis.
    # But because th Python import library routines have been rewritten many times, I
    # can't figure out how to get from "<frozen importlib>" to
    # the module's code.
    # So for now, we'll have to do this on a function by function
    # bases. Fortunately pyficache has the ability to remap line
    # numbers
    deparsed = deparse_fn(code)
    text = deparsed.text.strip()
    if text:
        prefix = os.path.basename(filename) + "_"
        remapped_filename = source_tempfile_remap(prefix, text)
        lines = text.split("\n")
        first_line = code.co_firstlineno
        linemap = [(line_no, deparsed.source_linemap[line_no])
                   for line_no in
                   sorted(deparsed.source_linemap.keys())]
        print("XXXX", linemap)
        pyficache.remap_file_lines(filename, remapped_filename, linemap)
        return remapped_filename, pyficache.getline(filename, line_number, opts)
    return None, None

def get_an_int(errmsg, arg, msg_on_error, min_value=None, max_value=None):
    """Another get_int() routine, this one simpler and less stylized
    than get_int(). We eval arg return it as an integer value or
    None if there was an error in parsing this.
    """
    ret_value = None
    if arg:
        try:
            # eval() is used so we will allow arithmetic expressions,
            # variables etc.
            ret_value = int(eval(arg))
        except (SyntaxError, NameError, ValueError):
            if errmsg:
                errmsg(msg_on_error)
            else:
                errmsg('Expecting an integer, got: %s.' % str(arg))
            return None

    if min_value and ret_value < min_value:
        errmsg('Expecting integer value to be at least %d, got: %d.' %
                    (min_value, ret_value))
        return None
    elif max_value and ret_value > max_value:
        errmsg('Expecting integer value to be at most %d, got: %d.' %
               (max_value, ret_value))
        return None
    return ret_value

def get_int(errmsg, arg, default=1, cmdname=None):
    """If arg is an int, use that otherwise take default."""
    if arg:
        try:
            # eval() is used so we will allow arithmetic expressions,
            # variables etc.
            default = int(eval(arg))
        except (SyntaxError, NameError, ValueError):
            if cmdname:
                errmsg("Command '%s' expects an integer; got: %s." %
                       (cmdname, str(arg)))
            else:
                errmsg('Expecting an integer, got: %s.' % str(arg))
                pass
            raise ValueError
    return default

def get_onoff(errmsg, arg, default=None, print_error=True):
    """Return True if arg is 'on' or 1 and False arg is 'off' or 0.
    Any other value is raises ValueError."""
    if not arg:
        if default is None:
            if print_error:
                errmsg("Expecting 'on', 1, 'off', or 0. Got nothing.")
                pass
            raise ValueError
        return default
    if arg == '1' or arg == 'on': return True
    if arg == '0' or arg =='off': return False

    if print_error:
        errmsg("Expecting 'on', 1, 'off', or 0. Got: %s." % str(arg))
    raise ValueError

def get_val(curframe, errmsg, arg):
    try:
        return eval(arg, curframe.f_globals,
                    curframe.f_locals)
    except:
        t, v = sys.exc_info()[:2]
        if isinstance(t, str):
                exc_type_name = t
        else: exc_type_name = t.__name__
        errmsg(str("%s: %s" % (exc_type_name, arg)))
        raise
    return  # Not reached

def run_set_bool(obj, args):
    """set a Boolean-valued debugger setting. 'obj' is a generally a
    subcommand that has 'name' and 'debugger.settings' attributes"""
    try:
        if 0 == len(args): args = ['on']
        obj.debugger.settings[obj.name] = get_onoff(obj.errmsg, args[0])
    except ValueError:
        pass
    return

def run_set_int(obj, arg, msg_on_error, min_value=None, max_value=None):
    """set an Integer-valued debugger setting. 'obj' is a generally a
    subcommand that has 'name' and 'debugger.settings' attributes"""
    if '' == arg.strip():
        obj.errmsg("You need to supply a number.")
        return
    obj.debugger.settings[obj.name] = \
        get_an_int(obj.errmsg, arg, msg_on_error, min_value, max_value)
    return obj.debugger.settings[obj.name]

def run_show_bool(obj, what=None):
    """Generic subcommand showing a boolean-valued debugger setting.
    'obj' is generally a subcommand that has 'name' and
    'debugger.setting' attributes."""
    val = show_onoff(obj.debugger.settings[obj.name])
    if not what: what = obj.name
    return obj.msg("%s is %s." % (what, val))

def run_show_int(obj, what=None):
    """Generic subcommand integer value display"""
    val = obj.debugger.settings[obj.name]
    if not what: what = obj.name
    return obj.msg("%s is %d." % (what, val))

def show_onoff(b):
    """Return 'on' for True and 'off' for False, and ?? for anything
    else."""
    if not isinstance(b, bool):
        return "??"
    if b:
        return "on"
    return "off"

def run_show_val(obj, name):
    """Generic subcommand value display"""
    val = obj.debugger.settings[obj.name]
    obj.msg("%s is %s." % (obj.name, obj.cmd.proc._saferepr(val),))
    return False

def want_different_line(cmd, default):
    if cmd[-1] == '-':
        return False
    elif cmd[-1] == '+':
        return True
    return default

# Demo it
if __name__ == '__main__':
    def errmsg(msg):
        print("** %s" % msg)
        return

    def msg(m):
        print(m)
    print(get_int(errmsg, '1+2'))  # 3
    print(get_int(errmsg, None))  # 1
    print(get_an_int(errmsg, '6*1', '6*1 is okay'))  # 6
    print(get_an_int(errmsg, '0', '0 is too small', 1))  # errmsg
    print(get_an_int(errmsg, '5+a', '5+a is no good'))   # errmsg
    try:
        get_int(errmsg, 'pi')
    except ValueError:
        print("Good - 'pi' is not an integer")
        pass

    import inspect
    curframe = inspect.currentframe()

    print(want_different_line("s+ %s", False))
    print(want_different_line("s-", True))
    print(want_different_line("s", False))
    print(want_different_line("s", True))
    print(want_different_line("s", True))
    pass


# -*- coding: utf-8 -*-
#   Copyright (C) 2009, 2012-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/>.
'''Bytecode instruction routines'''

import dis, re
from opcode import opname, HAVE_ARGUMENT

def op_at_code_loc(code, loc):
    try:
        op = code[loc]
    except IndexError:
        return 'got IndexError'
    return opname[op]

def op_at_frame(frame, loc=None):
    code = frame.f_code.co_code
    if loc is None: loc = frame.f_lasti
    return op_at_code_loc(code, loc)

def next_opcode(code, offset):
    '''Return the next opcode and offset as a tuple. Tuple (-100,
    -1000) is returned when reaching the end.'''
    n = len(code)
    while offset < n:
        op = code[offset]
        offset += 1
        if op >= HAVE_ARGUMENT:
            offset += 2
            pass
        yield op, offset
        pass
    yield -100, -1000
    pass

def next_linestart(co, offset, count=1):
    linestarts = dict(dis.findlinestarts(co))
    code = co.co_code
    # n = len(code)
    # contains_cond_jump = False
    for op, offset in next_opcode(code, offset):
        if offset in linestarts:
            count -= 1
            if 0 == count:
                return linestarts[offset]
            pass
        pass
    return -1000

def stmt_contains_opcode(co, lineno, query_opcode):
    linestarts = dict(dis.findlinestarts(co))
    code = co.co_code
    found_start = False
    for offset, start_line in list(linestarts.items()):
        if start_line == lineno:
            found_start = True
            break
        pass
    if not found_start:
        return False
    for op, offset in next_opcode(code, offset):
        if -1000 == offset or linestarts.get(offset): return False
        opcode = opname[op]
        # debug: print opcode
        if query_opcode == opcode:
            return True
        pass
    return False

_re_def_str = r'^\s*def\s'
_re_def = re.compile(_re_def_str)

def is_def_stmt(line, frame):
    """Return True if we are looking at a def statement"""
    # Should really also check that operand of 'LOAD_CONST' is a code object
    return (line and _re_def.match(line) and op_at_frame(frame)=='LOAD_CONST'
            and stmt_contains_opcode(frame.f_code, frame.f_lineno,
                                          'MAKE_FUNCTION'))

_re_class = re.compile(r'^\s*class\s')

def is_class_def(line, frame):
    """Return True if we are looking at a class definition statement"""
    return (line and _re_class.match(line)
            and stmt_contains_opcode(frame.f_code, frame.f_lineno,
                                     'BUILD_CLASS'))

# Demo stuff above
if __name__=='__main__':
    import inspect

    def sqr(x):
        return x * x
    frame = inspect.currentframe()
    co = frame.f_code
    lineno = frame.f_lineno
    print('contains MAKE_FUNCTION %s' % stmt_contains_opcode(co, lineno-4,
                                                             'MAKE_FUNCTION'))
    print('contains MAKE_FUNCTION %s' % stmt_contains_opcode(co, lineno,
                                                             'MAKE_FUNCTION'))

    print("op at frame: %s" % op_at_frame(frame))
    print("op at frame, position 2: %s" % op_at_frame(frame, 2))
    print("def statement: x=5?: %s" % is_def_stmt('x=5', frame))
    # Not a "def" statement because frame is wrong spot
    print(is_def_stmt('def foo():', frame))

    class Foo:
        pass
    lineno = frame.f_lineno
    print('contains BUILD_CLASS %s' % stmt_contains_opcode(co, lineno-2,
                                                           'BUILD_CLASS'))
    print('contains BUILD_CLASS %s' % stmt_contains_opcode(co, lineno,
                                                           'BUILD_CLASS'))
    pass


# -*- coding: utf-8 -*-
#   Copyright (C) 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/>.
import os, threading
import ctypes  # Calm down, this has become standard library since 2.5

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

def ctype_async_raise(thread_obj, exception):
    found = False
    target_tid = 0
    for tid, tobj in threading._active.items():
        if tobj is thread_obj:
            found = True
            target_tid = tid
            break

    if not found:
        raise ValueError("Invalid thread object")

    ret = ctypes.pythonapi.PyThreadState_SetAsyncExc(target_tid, ctypes.py_object(exception))
    # ref: http://docs.python.org/c-api/init.html#PyThreadState_SetAsyncExc
    if ret == 0:
        raise Mexcept.DebuggerQuit
    elif ret > 1:
        # Huh? Why would we notify more than one threads?
        # Because we punch a hole into C level interpreter.
        # So it is better to clean up the mess.
        ctypes.pythonapi.PyThreadState_SetAsyncExc(target_tid, 0)
        raise SystemError("PyThreadState_SetAsyncExc failed")

class QuitCommand(Mbase_cmd.DebuggerCommand):
    """**quit** [**unconditionally**]

Gently terminate the debugged program.

The program being debugged is aborted via a *DebuggerQuit*
exception.

When the debugger from the outside (e.g. via a `trepan` command), the
debugged program is contained inside a try block which handles the
*DebuggerQuit* exception.  However if you called the debugger was
started in the middle of a program, there might not be such an
exception handler; the debugged program still terminates but generally
with a traceback showing that exception.

If the debugged program is threaded, we raise an exception in each of
the threads ending with our own. However this might not quit the
program.

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

See `exit` or `kill` for more forceful termination commands.

`run` and `restart` are other ways to restart the debugged program.
"""

    aliases       = ('q', 'quit!')
    category      = 'support'
    min_args      = 0
    max_args      = 0
    name          = os.path.basename(__file__).split('.')[0]
    need_stack    = False
    short_help    = 'Terminate the program - gently'

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

        self.debugger.core.stop()
        self.debugger.core.execution_status = 'Quit command'
        raise Mexcept.DebuggerQuit

    def threaded_quit(self, arg):
        """ quit command when several threads are involved. """
        threading_list = threading.enumerate()
        mythread =  threading.currentThread()
        for t in threading_list:
            if t != mythread:
                ctype_async_raise(t, Mexcept.DebuggerQuit)
                pass
            pass
        raise Mexcept.DebuggerQuit

    def run(self, args):
        confirmed = False
        if len(args) <= 1:
            if '!' != args[0][-1]:
                confirmed = self.confirm('Really quit', False)
            else:
                confirmed = True
            pass
        if confirmed:
            threading_list = threading.enumerate()
            if ((len(threading_list) == 1 or self.debugger.from_ipython) and
                threading_list[0].getName() == 'MainThread') :
                # We are in a main thread and either there is one thread or
                # we or are in ipython, so that's safe to quit.
                return self.nothread_quit(args)
            else:
                return self.threaded_quit(args)
            pass
        return

if __name__ == '__main__':
    from trepan.processor.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