"""
Figure out if the terminal has a light or dark background

We consult environemnt variables
- DARK_BG
- COLORFGBG
- TERM

If DARK_BG is set and it isn't 0 then we have a dark background
else a light background.

If DARK_BG is not set but COLORFGBG is set and it is '0;15' then we have a dark background
and if it is '15;0' then a light background.

If none of the above work but TERM is set and the terminal understands
xterm sequences for retrieving foreground and background, we'll
set based on those colors. Failing that we'll set defaults
for spefic TERM values based on their default settings.

See https://github.com/rocky/bash-term-background for code
that works in bash.
"""

from os import environ
# from subprocess import check_output, check_call

def set_default_bg():
    """Get bacground from
    default values based on the TERM environment variable
    """
    term = environ.get('TERM', None)
    if term:
        if (term.startswith('xterm',) or term.startswith('eterm')
            or term == 'dtterm'):
            return False
    return True

def is_dark_rgb(r, g, b):
    """Pass as parameters R G B values in hex
    On return, variable is_dark_bg is set
    """

    try:
        midpoint = int(environ.get('TERMINAL_COLOR_MIDPOINT', None))
    except:
        pass
    if not midpoint:
        term = environ.get('TERM', None)
        # 117963 = (* .6 (+ 65535 65535 65535))
        # 382.5 = (* .6 (+ 65535 65535 65535))
        print("midpoint", midpoint, 'vs',  (16*5 + 16*g + 16*b))
        midpoint = 383 if term and term == 'xterm-256color' else 117963

    if ( (16*5 + 16*g + 16*b) < midpoint ):
        return True
    else:
        return False

def is_dark_color_fg_bg():
    """Consult (environment) variables DARK_BG and COLORFGB
    On return, variable is_dark_bg is set"""
    dark_bg = environ.get('DARK_BG', None)
    if dark_bg is not None:
        return dark_bg != '0'
    color_fg_bg = environ.get('COLORFGBG', None)
    if color_fg_bg:
        if color_fg_bg in ('15;0', '15;default;0'):
            return True
        elif color_fg_bg in ('0;15', '0;default;15' ):
            return False
    else:
        return True
    return None

# # From:
# # http://unix.stackexchange.com/questions/245378/common-environment-variable-to-set-dark-or-light-terminal-background/245381#245381
# # and:
# # https://bugzilla.gnome.org/show_bug.cgi?id=733423#c1
# #
# # User should set up RGB_fg and RGB_bg arrays
# def xterm_compatible_fg_bg():
#     # Issue command to get both foreground and
#     # background color
#     #                                           fg        bg
#     try:
#         check_call("stty -echo", shell=True)
#         output = check_output("echo -ne '\033]10;?\07\033]11;?07'", shell=True)
#     finally:
#         check_call("stty echo", shell=True)

#     # IFS=: read -t 0.1 -d $'\a' x fg
#     # IFS=: read -t 0.1 -d $'\a' x bg
#     #     output = check_output("stty echo")
#     # stty echo
#     # [[ -z $bg ]] && return 1
#     # typeset -p fg
#     # typeset -p bg
#     # IFS='/' read -ra RGB_fg <<< $fg
#     # IFS='/' read -ra RGB_bg <<< $bg
#     # typeset -p RGB_fg
#     # typeset -p RGB_bg
#     return None, None, None

def is_dark_background():
    dark_bg = is_dark_color_fg_bg()
    if dark_bg is None:
        dark_bg = set_default_bg()
    # print("XXX ", dark_bg)
    return dark_bg


#!/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/>.
''' The hairy command-line interface to the debugger.
'''
import os, os.path, sys

from optparse import OptionParser

# Our local modules
from trepan import clifns as Mclifns
from trepan import debugger as Mdebugger, exception as Mexcept, misc as Mmisc
from trepan import file as Mfile
from trepan.interfaces import bullwinkle as Mbullwinkle

# The name of the debugger we are currently going by.
__title__ = 'trepan'

# VERSION.py sets variable VERSION.
from trepan.VERSION import VERSION as __version__

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 opt_parser is return. 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("-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("--different", dest="different",
                         action="store_true", default=True,
                         help="Consecutive stops should have different "
                         "positions")
    optparser.disable_interspersed_args()

    sys.argv = list(sys_argv)
    (opts, sys.argv) = optparser.parse_args()
    dbg_opts = {}

    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 ('basename', 'different',):
        dbg.settings[setting] = getattr(opts, setting)
        pass

    dbg.settings['highlight'] = 'plain'

    Mdebugger.debugger_obj = dbg
    return

def main(dbg=None, sys_argv=list(sys.argv)):
    """Routine which gets run if we were invoked directly"""
    global __title__

    # Save the original just for use in the restart that works via exec.
    orig_sys_argv = list(sys_argv)
    opts, dbg_opts, sys_argv  = process_options(__title__, __version__,
                                                sys_argv)
    dbg_opts['orig_sys_argv'] = sys_argv
    dbg_opts['interface']     = Mbullwinkle.BWInterface()
    dbg_opts['processor']     = 'bullwinkle'

    if dbg is None:
        dbg = Mdebugger.Trepan(dbg_opts)
        dbg.core.add_ignore(main)
        pass
    _postprocess_options(dbg, opts)

    # process_options has munged sys.argv to remove any options that
    # options that belong to this debugger. The original options to
    # invoke the debugger and script are in global sys_argv
    if len(sys_argv) == 0:
        # No program given to debug. Set to go into a command loop
        # anyway
        mainpyfile = None
    else:
        mainpyfile = sys_argv[0]  # Get script filename.
        if not os.path.isfile(mainpyfile):
            mainpyfile=Mclifns.whence_file(mainpyfile)
            is_readable = Mfile.readable(mainpyfile)
            if is_readable is None:
                print("%s: Python script file '%s' does not exist"
                      % (__title__, mainpyfile,))
                sys.exit(1)
            elif not is_readable:
                print("%s: Can't read Python script file '%s'"
                      % (__title__, mainpyfile,))
                sys.exit(1)
                return

        # If mainpyfile is an optimized Python script try to find and
        # use non-optimized alternative.
        mainpyfile_noopt = Mfile.file_pyc2py(mainpyfile)
        if mainpyfile != mainpyfile_noopt \
               and Mfile.readable(mainpyfile_noopt):
            print("%s: Compiled Python script given and we can't use that."
                  % __title__)
            print("%s: Substituting non-compiled name: %s" % (
                __title__, mainpyfile_noopt,))
            mainpyfile = mainpyfile_noopt
            pass

        # Replace trepan's dir with script's dir in front of
        # module search path.
        sys.path[0] = dbg.main_dirname = os.path.dirname(mainpyfile)

    # XXX If a signal has been received we continue in the loop, otherwise
    # the loop exits for some reason.
    dbg.sig_received = False

    # if not mainpyfile:
    #     print('For now, you need to specify a Python script name!')
    #     sys.exit(2)
    #     pass

    while True:

        # Run the debugged script over and over again until we get it
        # right.

        try:
            if dbg.program_sys_argv and mainpyfile:
                normal_termination = dbg.run_script(mainpyfile)
                if not normal_termination: break
            else:
                dbg.core.execution_status = 'No program'
                dbg.core.processor.process_commands()
                pass

            dbg.core.execution_status = 'Terminated'
            dbg.intf[-1].msg("The program finished - quit or restart")
            dbg.core.processor.process_commands()
        except Mexcept.DebuggerQuit:
            break
        except Mexcept.DebuggerRestart:
            dbg.core.execution_status = 'Restart requested'
            if dbg.program_sys_argv:
                sys.argv = list(dbg.program_sys_argv)
                part1 = ('Restarting %s with arguments:' %
                         dbg.core.filename(mainpyfile))
                args  = ' '.join(dbg.program_sys_argv[1:])
                dbg.intf[-1].msg(Mmisc.wrapped_lines(part1, args,
                                                     dbg.settings['width']))
            else: break
        except SystemExit:
            # In most cases SystemExit does not warrant a post-mortem session.
            break
        pass

    # Restore old sys.argv
    sys.argv = orig_sys_argv
    return

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


import os, sys

# FIXME: DRY with other demo_helper's

def get_name():
    """Get the name caller's caller.
    NB: f_code.co_filenames and thus this code kind of broken for
    zip'ed eggs circa Jan 2009
    """
    caller = sys._getframe(2)
    filename = caller.f_code.co_filename
    filename = os.path.normcase(os.path.basename(filename))
    return os.path.splitext(filename)[0]

def demo_setup():
    from trepan.processor.command import mock as Mmock, show as Mshow
    d, cp = Mmock.dbg_setup()
    mgr = Mshow.ShowCommand(cp)
    return mgr

def demo_run(subcmd):
    mgr = demo_setup()
    sub = subcmd(mgr)
    sub.name = get_name()
    sub.run([])
    return sub


# -*- coding: utf-8 -*-
#   Copyright (C) 2009, 2013, 2015 Rocky Bernstein <rocky@gnu.org>
#
#   This program is free software: you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation, either version 3 of the License, or
#   (at your option) any later version.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with this program.  If not, see <http://www.gnu.org/licenses/>.

# The first version of this came from pydb code in ddd and was probably
# written by Richard Wolff while at Lawrence Livermore Labs.
"""Classes to support gdb-like display/undisplay."""

# Our local modules
from trepan.lib import stack as Mstack

def signature(frame):
    '''return suitable frame signature to key display expressions off of.'''
    if not frame: return None
    code = frame.f_code
    return (code.co_name, code.co_filename, code.co_firstlineno)

class DisplayMgr:
    '''Manage a list of display expressions.'''
    def __init__(self):
        self.next = 0
        self.list = []
        return

    def add(self, frame, arg, fmt=None):
        if not frame:
            return None
        try:
            eval(arg, frame.f_globals, frame.f_locals)
        except:
            return None
        self.next += 1
        display = Display(frame, arg, fmt, self.next)
        self.list.append(display)
        return display

    def all(self):
        """List all display items; return 0 if none"""
        found = False
        s = []
        for display in self.list:
            if not found:
                s.append("""Auto-display expressions now in effect:
Num Enb Expression""")
                found = True
                pass
            s.append(display.format())
        return s

    def clear(self):
        """Delete all display expressions"""
        self.list = []
        return

    def delete_index(self, display_number):
        """Delete display expression *display_number*"""
        old_size = len(self.list)
        self.list = [disp for disp in self.list
                     if display_number != disp.number]
        return old_size != len(self.list)

    def display(self, frame):
        '''display any items that are active'''
        if not frame: return
        s = []
        sig = signature(frame)
        for display in self.list:
            if display.signature == sig and display.enabled:
                s.append(display.to_s(frame))
                pass
            pass
        return s

    def enable_disable(self, i, b_enable_disable):
        for display in self.list:
            if i == display.number:
                display.enabled = b_enable_disable
                return
            pass
        return

    pass

class Display:
    def __init__(self, frame, arg, fmt, number):
        self.signature = signature(frame)
        self.fmt = fmt
        self.arg = arg
        self.enabled = True
        self.number = number
        return

    def to_s(self, frame):
        if not frame:
            return 'No symbol "' + self.arg + '" in current context.'
        try:
            val = eval(self.arg, frame.f_globals, frame.f_locals)
        except:
            return 'No symbol "' + self.arg + '" in current context.'
        s = "%3d: %s" % (self.number,
                        Mstack.print_obj(self.arg, val, self.fmt,
                                         True))
        return s

    def format(self, show_enabled=True):
        '''format display item'''
        what = ''
        if show_enabled:
            if self.enabled:
                what += ' y '
            else:
                what += ' n '
                pass
            pass
        if self.fmt:
            what += self.fmt + ' '
            pass
        what += self.arg
        return '%3d: %s' % (self.number, what)
    pass

if __name__=='__main__':
    mgr = DisplayMgr()
    import inspect
    x = 1
    frame = inspect.currentframe()
    mgr.add(frame, 'x > 1')
    mgr.add(frame, 'x')
    print("Deleted recent insert:", mgr.delete_index(2))
    for line in mgr.all(): print(line)
    mgr.enable_disable(1, False)
    for line in mgr.all(): print(line)
    print(mgr.display(frame))
    mgr.enable_disable(1, False)
    for line in mgr.display(frame): print(line)
    mgr.enable_disable(1, True)
    for line in mgr.display(frame): print(line)
    mgr.clear()
    print('-' * 10)
    for line in mgr.all(): print(line)
    pass


# -*- coding: utf-8 -*-
#   Copyright (C) 2009, 2013-2015 Rocky Bernstein <rocky@gnu.org>
#
#   This program is free software: you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation, either version 3 of the License, or
#   (at your option) any later version.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
"""Debugger Socket Input/Output Interface."""

import socket

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

class TCPClient(DebuggerInOutBase):
    """Debugger Client Input/Output Socket."""

    DEFAULT_INIT_OPTS = {'open': True}

    def __init__(self, inout=None, opts=None):
        get_option = lambda key: option_set(opts, key,
                                            Mdefault.CLIENT_SOCKET_OPTS)
        self.inout = None
        self.addr = None
        self.buf = ''
        self.line_edit = False  # Our name for GNU readline capability
        self.state = 'disconnected'
        if inout:
            self.inout = inout
        elif get_option('open'):
            self.open(opts)
            pass
        return

    def close(self):
        """ Closes both input and output """
        if self.inout:
            self.inout.close()
            pass
        self.state = 'disconnnected'
        return

    def open(self, opts=None):

        get_option = lambda key: option_set(opts, key,
                                            Mdefault.CLIENT_SOCKET_OPTS)

        HOST = get_option('HOST')
        PORT = get_option('PORT')
        self.inout = None
        for res in socket.getaddrinfo(HOST, PORT, socket.AF_UNSPEC,
                                      socket.SOCK_STREAM):
            af, socktype, proto, canonname, sa = res
            try:
                self.inout = socket.socket(af, socktype, proto)
                self.state = 'connected'
            except socket.error:
                self.inout = None
                self.state = 'disconnected'
                continue
            try:
                self.inout.connect(sa)
            except socket.error:
                self.inout.close()
                self.inout = None
                continue
                break
        if self.inout is None:
            raise IOError('could not open client socket on port %s' %
                          PORT)
        return

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

    def write(self, msg):
        """ This method the debugger uses to write a message unit."""
        # FIXME: do we have to check the size of msg and split output?
        return self.inout.send(Mtcpfns.pack_msg(msg))

    pass

# Demo
if __name__=='__main__':
    inout = TCPClient(opts={'open': False})
    import sys
    if len(sys.argv) > 1:
        print('Connecting...', end=' ')
        inout.open()
        print('connected.')
        while True:
            line = input('nu? ')
            if len(line) == 0: break
            try:
                line = inout.writeline(line)
                print("Got: ", inout.read_msg().rstrip('\n'))
            except EOFError:
                break
            pass
        pass
    inout.close()
    pass


# -*- coding: utf-8 -*-
"""
celery.contrib.trepan
=====================

Remote debugger for Celery tasks running in multiprocessing pool workers.
Inspired by celery.contrib.rdb

**Usage**

.. code-block:: python

    from celery.contrib import trepan
    from celery import task

    @task()
    def add(x, y):
        result = x + y
        trepan.debug()
        return result

**Environment Variables**

.. envvar:: CELERY_TREPAN_HOST

    Hostname to bind to.  Default is '127.0.01', which means the socket
    will only be accessible from the local host.

.. envvar:: CELERY_TREPAN_PORT

    Base port to bind to.  Default is 6899.
    The debugger will try to find an available port starting from the
    base port.  The selected port will be logged by the worker.

"""
from __future__ import absolute_import, print_function

import os
import sys
import trepan.api

__all__ = ['CELERY_TREPAN_HOST', 'CELERY_TREPAN_PORT', 'default_port',
           'RemoteCeleryTrepan', 'debugger', 'debug']

default_port = 6898

CELERY_TREPAN_HOST = os.environ.get('CELERY_TREPAN_HOST') or '127.0.0.1'
CELERY_TREPAN_PORT = int(os.environ.get('CELERY_TREPAN_PORT') or default_port)

#: Holds the currently active debugger.
_current = [None]

_frame = getattr(sys, '_getframe')

is_python3 = sys.version_info[0] == 3
if is_python3:
    trepan_client = 'trepan3kc'
else:
    trepan_client = 'trepan2c'

NO_AVAILABLE_PORT = """\
{self.ident}: Couldn't find an available port.

Please specify one using the CELERY_TREPAN_PORT environment variable.
"""

BANNER = """\
{self.ident}: Please run "%s --host {self.host} --port {self.port}".

Type `exit` in session to continue.

{self.ident}: Waiting for client...
""" % trepan_client

SESSION_STARTED = '{self.ident}: Now in session with {self.remote_addr}.'
SESSION_ENDED = '{self.ident}: Session with {self.remote_addr} ended.'

class RemoteCeleryTrepan():
    me = 'Remote Trepan Debugger'
    _prev_outs = None

    def __init__(self, host=CELERY_TREPAN_HOST, port=CELERY_TREPAN_PORT,
                 out=sys.stdout):
        self.active = True
        self.out = out

        self.ident = '{0}:{1}'.format(self.me, port)

        from trepan.interfaces import server as Mserver
        connection_opts={'IO': 'TCP', 'PORT': port}
        self.intf = Mserver.ServerInterface(connection_opts=connection_opts)
        host = self.intf.inout.HOST
        self.host = host if host else '<hostname>'
        from trepan.api import debug; debug()
        self.port = self.intf.inout.PORT
        self.dbg_opts = {'interface': self.intf}
        return

    def say(self, m):
        print(m, file=self.out)

def debugger():
    """Return the current debugger instance (if any),
    or creates a new one."""
    dbg = _current[0]
    if dbg is None or not dbg.active:
        dbg = _current[0] = RemoteCeleryTrepan()
    return dbg

def debug(frame=None):
    """Set breakpoint at current location, or a specified frame"""
    # ???
    if frame is None:
        frame = _frame().f_back

    dbg = RemoteCeleryTrepan()
    dbg.say(BANNER.format(self=dbg))
    # dbg.say(SESSION_STARTED.format(self=dbg))
    trepan.api.debug(dbg_opts=dbg.dbg_opts)
    # return debugger().set_trace(frame)


# -*- coding: utf-8 -*-
#  Copyright (C) 2009, 2012-2018 Rocky Bernstein
#
#  This program is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <http://www.gnu.org/licenses/>.

import inspect, os, sys

# Our local modules
from trepan.processor.command import base_cmd as Mbase_cmd
from trepan.lib import disassemble as Mdis, file as Mfile
from xdis.load import load_module
from trepan.processor.cmd_addrlist import parse_addr_list_cmd

# From importlib.util
DEBUG_BYTECODE_SUFFIXES = ['.pyc']
OPTIMIZED_BYTECODE_SUFFIXES = ['.pyo']
_PYCACHE = '__pycache__'
def cache_from_source(path, debug_override=None):
    """Given the path to a .py file, return the path to its .pyc/.pyo file.

    The .py file does not need to exist; this simply returns the path to the
    .pyc/.pyo file calculated as if the .py file were imported.  The extension
    will be .pyc unless sys.flags.optimize is non-zero, then it will be .pyo.

    If debug_override is not None, then it must be a boolean and is used in
    place of sys.flags.optimize.

    If sys.implementation.cache_tag is None then NotImplementedError is raised.

    """
    debug = not sys.flags.optimize if debug_override is None else debug_override
    if debug:
        suffixes = DEBUG_BYTECODE_SUFFIXES
    else:
        suffixes = OPTIMIZED_BYTECODE_SUFFIXES
        pass
    head, tail = os.path.split(path)
    base_filename, sep, _ = tail.partition('.')
    if not hasattr(sys, 'implementation'):
        # Python <= 3.2
        raise NotImplementedError('No sys.implementation')
    tag = sys.implementation.cache_tag
    if tag is None:
        raise NotImplementedError('sys.implementation.cache_tag is None')
    filename = ''.join([base_filename, sep, tag, suffixes[0]])
    return os.path.join(head, _PYCACHE, filename)

class DisassembleCommand(Mbase_cmd.DebuggerCommand):
    """**disassemble** [*thing*]

**disassemble** [*address-range*]

Disassembles bytecode. See `help syntax range` for what can go in a list range.

Without arguments, print lines starting from where the last list left off
since the last entry to the debugger. We start off at the location indicated
by the current stack.

in addition you can also use:

  - a '.' for the location of the current frame

  - a '-' for the lines before the last list

  - a '+' for the lines after the last list

With a class, method, function, pyc-file, code or string argument
disassemble that.

Examples:
--------
::

   disassemble    # Possibly lots of stuff disassembled
   disassemble .  # Disassemble lines starting at current stopping point.
   disassemble +                    # Same as above
   disassemble os.path              # Disassemble all of os.path
   disassemble os.path.normcase()   # Disaassemble just method os.path.normcase
   disassemble 3                    # Disassemble starting from line 3
   disassemble 3, 10                # Disassemble lines 3 to 10
   disassemble *0, *10              # Disassemble offset 0-10
   disassemble myprog.pyc           # Disassemble file myprog.pyc

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

`help syntax arange`, `deparse`, `list`, `info pc`.
"""

    aliases       = ('disasm',)  # Note: we will have disable
    category      = 'data'
    min_args      = 0
    max_args      = 2
    name          = os.path.basename(__file__).split('.')[0]
    need_stack    = False
    short_help    = 'Disassemble Python bytecode'

    def run(self, args):
        proc = self.proc
        dbg_obj  = self.core.debugger

        # We'll use a rough estimate of 4 bytes per instruction and
        # go off listsize
        listsize = dbg_obj.settings['listsize'] * 4
        curframe = proc.curframe

        opts = {'highlight': self.settings['highlight'],
                'start_line': 1,
                'end_line': None,
                'start_offset': None,
                'end_offset': None,
                'relative_pos': False,
                }

        do_parse = True
        if len(args) == 2:
            try:
                obj=self.proc.eval(args[1])
                opts['start_offset'] = 0
                is_offset = True
                start = 0
                last = dbg_obj.settings['listsize'] * 4
                last_is_offset = False
                if (inspect.ismethod(obj) or
                    inspect.isfunction(obj) or
                    inspect.isgeneratorfunction(obj) or
                    inspect.isgenerator(obj) or
                    inspect.isframe(obj) or
                    inspect.iscode(obj)):
                    do_parse = False
            except:
                pass

        if do_parse:
            (bytecode_file, start, is_offset, last,
             last_is_offset, obj)  = parse_addr_list_cmd(proc, args, listsize)
            if bytecode_file is None: return

        if is_offset:
            opts['start_offset'] = start
        else:
            opts['start_line'] = start
        if last_is_offset:
            opts['end_offset'] = last
        else:
            opts['end_line'] = last

        if not (obj or bytecode_file.endswith('.pyo') or
                bytecode_file.endswith('pyc')):
            bytecode_file = cache_from_source(bytecode_file)
            if bytecode_file and Mfile.readable(bytecode_file):
                self.msg("Reading %s ..." % bytecode_file)
                (version, timestamp, magic_int, obj,
                 is_pypy, source_size) = load_module(bytecode_file)
            elif not curframe:
                self.errmsg("No frame selected.")
                return
            else:
                try:
                    obj=self.proc.eval(args[1])
                    opts['start_line'] = -1
                except:
                    self.errmsg(("Object '%s' is not something we can"
                                + " disassemble.") % args[1])
                    return

        # We now have all  information. Do the listing.
        (self.object,
         proc.list_offset) = Mdis.dis(self.msg, self.msg_nocr, self.section, self.errmsg,
                                      obj, **opts)
        return False

# Demo it
if __name__ == '__main__':

    # FIXME: make sure the below is in a unit test
    def doit(cmd, args):
        proc = cmd.proc
        proc.current_command = ' '.join(args)
        cmd.run(args)

    from trepan.processor.command import mock
    d, cp = mock.dbg_setup()
    import inspect
    cp.curframe = inspect.currentframe()
    command = DisassembleCommand(cp)
    prefix = '-' * 20 + ' disassemble '
    # print(prefix + 'cp.errmsg()')
    # doit(command, ['dissassemble', 'cp.errmsg()'])
    # print(prefix)
    # doit(command, ['disassemble'])
    # print(prefix + 'me')
    # doit(command, ['disassemble', 'me'])
    # print(prefix + '+0 2')
    # doit(command, ['disassemble', '*0', '+2'])
    # print(prefix + '+ 2-1')
    # doit(command, ['disassemble', '+', '20'])
    # print(prefix + '- 1')
    # doit(command, ['disassemble', '-', '1'])
    # print(prefix + '.')
    # doit(command, ['disassemble', '.'])
    __file__ = './disassemble.py'
    # doit(command, ['disassemble', __file__, '30', '40'])
    bytecode_file = cache_from_source(__file__)
    print(bytecode_file)
    if bytecode_file:
        doit(command, ['disassemble', bytecode_file + ':22,28'])
    # doit(command, ['disassemble', '*15,', '*25'])
    doit(command, ['disassemble', '3,', '10'])
    pass


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

from trepan.processor.command import base_cmd as Mbase_cmd
from trepan.processor import subcmd as Msubcmd
from trepan.lib import complete as Mcomplete

def abbrev_stringify(name, min_abbrev):
    return ("(%s)%s" % (name[:min_abbrev], name[min_abbrev:],))

def capitalize(s):
    # "abcd" -> "Abcd"
    if s:
        return s[0].upper() + s[1:]
    else:  # empty string
        return s
    pass

class SubcommandMgr(Mbase_cmd.DebuggerCommand):

    category      = 'status'
    min_args      = 0
    max_args      = None
    name          = '???'  # Need to define this!
    need_stack    = False

    def __init__(self, proc, name=None):
        """Initialize show subcommands. Note: instance variable name
        has to be setcmds ('set' + 'cmds') for subcommand completion
        to work."""

        Mbase_cmd.DebuggerCommand.__init__(self, proc)

        # Name is set in testing
        if name is None: name  = self.__module__.split('.')[-1]
        self.__class__.name = name

        self.cmds = Msubcmd.Subcmd(name, self)
        self.name = name
        self._load_debugger_subcommands(name)
        self.proc = proc

        return

    def _load_debugger_subcommands(self, name):
        """ Create an instance of each of the debugger
        subcommands. Commands are found by importing files in the
        directory 'name' + 'sub'. Some files are excluded via an array set
        in __init__.  For each of the remaining files, we import them
        and scan for class names inside those files and for each class
        name, we will create an instance of that class. The set of
        DebuggerCommand class instances form set of possible debugger
        commands."""

        # Initialization
        cmd_instances     = []
        class_prefix      = capitalize(name)  # e.g. Info, Set, or Show
        module_dir        = 'trepan.processor.command.%s_subcmd' % name
        mod               = __import__(module_dir, None, None, ['*'])
        eval_cmd_template = 'command_mod.%s(self)'

        # Import, instantiate, and add classes for each of the
        # modules found in module_dir imported above.
        for module_name in mod.__modules__:
            import_name = module_dir + '.' + module_name
            try:
                command_mod = importlib.import_module(import_name)
            except ImportError:
                print(("Error importing name %s module %s: %s" %
                      (import_name, module_name, sys.exc_info()[0])))
                continue

            # Even though we tend not to do this, it is possible to
            # put more than one class into a module/file.  So look for
            # all of them.
            classnames = [ classname for classname, classvalue in
                           inspect.getmembers(command_mod, inspect.isclass)
                           if ('DebuggerCommand' != classname and
                               classname.startswith(class_prefix)) ]

            for classname in classnames:
                eval_cmd = eval_cmd_template % classname
                try:
                    instance = eval(eval_cmd)
                    self.cmds.add(instance)
                except:
                    print("Error eval'ing class %s" % classname)
                    pass
                pass
            pass
        return cmd_instances

    def help(self, args):
        """Give help for a command which has subcommands. This can be
        called in several ways:
            help cmd
            help cmd subcmd
            help cmd commands

        Our shtick is to give help for the overall command only if
        subcommand or 'commands' is not given. If a subcommand is given and
        found, then specific help for that is given. If 'commands' is given
        we will list the all the subcommands.
        """
        if len(args) <= 2:
            # "help cmd". Give the general help for the command part.
            doc = self.__doc__ or self.run.__doc__
            if doc:
                self.rst_msg(doc.rstrip('\n'))
            else:
                self.proc.intf[-1].errmsg('Sorry - author mess up. ' +
                                          'No help registered for command' +
                                          self.name)
                pass
            return

        subcmd_name = args[2]

        if '*' == subcmd_name:
            self.section("List of subcommands for command '%s':" % self.name)
            self.msg(self.columnize_commands(self.cmds.list()))
            return

        # "help cmd subcmd". Give help specific for that subcommand.
        cmd = self.cmds.lookup(subcmd_name)
        if cmd:
            doc = cmd.__doc__ or cmd.run.__doc__
            if doc:
                self.proc.rst_msg(doc.rstrip('\n'))
            else:
                self.proc.intf[-1] \
                  .errmsg('Sorry - author mess up. No help registered for '
                          'subcommand %s of command %s' %
                          (subcmd_name, self.name))
                pass
        else:
            cmds = [c for c in self.cmds.list()
                    if re.match('^' + subcmd_name, c) ]
            if cmds == []:
                self.errmsg("No %s subcommands found matching /^%s/. "
                            "Try \"help\"." % (self.name, subcmd_name))
            else:
                self.section("Subcommand(s) of \"%s\" matching /^%s/:" %
                         (self.name, subcmd_name,))
                self.msg_nocr(self.columnize_commands(cmds))
                pass
            pass
        return

    # Return an Array of subcommands that can start with +arg+. If none
    # found we just return +arg+.
    # FIXME: Not used any more?
    def complete(self, prefix):
        return Mcomplete.complete_token(self.subcmds.subcmds.keys(), prefix)

    def complete_token_with_next(self, prefix):
        # from trepan.api import debug; debug()
        result = Mcomplete.complete_token_with_next(self.cmds.subcmds, prefix)
        return result

    def run(self, args):
        """Ooops -- the debugger author didn't redefine this run docstring."""
        if len(args) < 2:
            # We were given cmd without a subcommand; cmd is something
            # like "show", "info" or "set". Generally this means list
            # all of the subcommands.
            self.section("List of %s commands (with minimum abbreviation in "
                     "parenthesis):" % self.name)
            for subcmd_name in self.cmds.list():
                # Some commands have lots of output.
                # they are excluded here because 'in_list' is false.
                subcmd = self.cmds.subcmds[subcmd_name]
                self.summary_help(subcmd_name, subcmd)
                pass
            return False

        subcmd_prefix = args[1]
        # We were given: cmd subcmd ...
        # Run that.
        subcmd = self.cmds.lookup(subcmd_prefix)
        if subcmd:
            nargs = len(args) - 2
            if nargs < subcmd.min_args:
                self.errmsg(("Subcommand '%s %s' needs at least %d argument(s); " +
                             "got %d.") %
                             (self.name, subcmd.name, subcmd.min_args, nargs))
                return False
            if subcmd.max_args is not None and nargs > subcmd.max_args:
                self.errmsg(("Subcommand '%s %s' takes at most %d argument(s); " +
                             "got %d.") %
                             (self.name, subcmd.name, subcmd.max_args, nargs))
                return False
            return subcmd.run(args[2:])
        else:
            return self.undefined_subcmd(self.name, subcmd_prefix)
        return  # Not reached

    def summary_help(self, subcmd_name, subcmd):
        self.msg_nocr('  %-12s -- ' %
                      abbrev_stringify(subcmd_name, subcmd.min_abbrev))
        self.rst_msg(subcmd.short_help.rstrip("\n"))
        return
    pass

    def undefined_subcmd(self, cmd, subcmd):
        """Error message when subcommand asked for but doesn't exist"""
        self.proc.intf[-1].errmsg(('Undefined "%s" subcommand: "%s". ' +
                                  'Try "help %s *".') % (cmd, subcmd, cmd))
        return
    pass

if __name__ == '__main__':
    pass


# -*- coding: utf-8 -*-
#  Copyright (C) 2009-2010, 2013, 2015, 2017 Rocky Bernstein
#
#  This program is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <http://www.gnu.org/licenses/>.
import code, os, sys

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

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

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

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

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

  dbgr('info program')

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

`ipython`, `bpython`
"""

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

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

    def run(self, args):
        # See if python's code module is around

        # Python does it's own history thing.
        # Make sure it doesn't damage ours.
        intf = self.debugger.intf[-1]
        if isinstance(intf, ServerInterface):
            self.errmsg("Can't run an interactive shell on a remote session")
            return

        have_line_edit = self.debugger.intf[-1].input.line_edit
        if have_line_edit:
            try:
                self.proc.write_history_file()
            except IOError:
                pass
            pass

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

        debug = len(args) > 1 and args[1] == '-d'
        if debug:
            banner_tmpl += ("\nVariable 'debugger' contains a trepan"
                            "debugger object.")
            pass

        my_locals  = {}
        my_globals = None
        if self.proc.curframe:
            my_globals = self.proc.curframe.f_globals
            if self.proc.curframe.f_locals:
                my_locals = self.proc.curframe.f_locals
                pass
            pass

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

        # Change from debugger completion to python completion
        try:
            import readline
        except ImportError:
            pass
        else:
            readline.parse_and_bind("tab: complete")

        sys.ps1 = 'trepan3k >>> '
        old_sys_excepthook = sys.excepthook
        try:
            sys.excepthook = None
            if len(my_locals):
                interact(banner=(banner_tmpl % ' with locals'),
                         my_locals=my_locals, my_globals=my_globals)
            else:
                interact(banner=(banner_tmpl % ''))
                pass
        finally:
            sys.excepthook = old_sys_excepthook

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

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

# Monkey-patched from code.py
# FIXME: get changes into Python.
def interact(banner=None, readfunc=None, my_locals=None, my_globals=None):
    """Almost a copy of code.interact
    Closely emulate the interactive Python interpreter.

    This is a backwards compatible interface to the InteractiveConsole
    class.  When readfunc is not specified, it attempts to import the
    readline module to enable GNU readline if it is available.

    Arguments (all optional, all default to None):

    banner -- passed to InteractiveConsole.interact()
    readfunc -- if not None, replaces InteractiveConsole.raw_input()
    local -- passed to InteractiveInterpreter.__init__()

    """
    def console_runcode(code_obj):
        runcode(console, code_obj)

    console = code.InteractiveConsole(my_locals, filename='<trepan>')
    console.runcode = console_runcode
    setattr(console, 'globals', my_globals)
    if readfunc is not None:
        console.raw_input = readfunc
    else:
        try:
            import readline
        except ImportError:
            pass
    console.interact(banner)
    pass

# Also monkey-patched from code.py
# FIXME: get changes into Python.
def runcode(obj, code_obj):
    """Execute a code object.

    When an exception occurs, self.showtraceback() is called to
    display a traceback.  All exceptions are caught except
    SystemExit, which is reraised.

    A note about KeyboardInterrupt: this exception may occur
    elsewhere in this code, and may not always be caught.  The
    caller should be prepared to deal with it.

    """
    try:
        exec(code_obj, obj.locals, obj.globals)
    except SystemExit:
        raise
    except:
        info = sys.exc_info()
        print("%s; %s" % (info[0], info[1]))
    else:
        pass
    return

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


# -*- coding: utf-8 -*-
#   Copyright (C) 2009, 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

from trepan.processor import frame as Mframe
from trepan.processor.command import up as Mupcmd

class DownCommand(Mupcmd.UpCommand):

    signum        = 1
    name          = os.path.basename(__file__).split('.')[0]
    short_help    = 'Move stack frame to a more recent selected frame'

    def run(self, args):
        """**down** [*count*]

Move the current frame down in the stack trace (to a newer frame). 0
is the most recent frame. If no count is given, move down 1.

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

`up` and `frame`."""

        Mframe.adjust_relative(self.proc, self.name, args, self.signum)
        return False

if __name__ == '__main__':
    import inspect
    from trepan.processor import cmdproc as Mcmdproc
    from trepan import debugger as Mdebugger
    d            = Mdebugger.Trepan()
    cp           = d.core.processor
    command = DownCommand(cp)
    command.run(['down'])

    def nest_me(cp, command, i):
        if i > 1:
            cp.curframe = inspect.currentframe()
            cp.stack, cp.curindex = Mcmdproc.get_stack(cp.curframe, None, None,
                                                       cp)
            command.run(['down'])
            print('-' * 10)
            command.run(['down', '1'])
            print('-' * 10)
            command.run(['down', '-1'])
            print('-' * 10)
        else:
            nest_me(cp, command, i+1)
        return

    cp.forget()
    nest_me(cp, command, 1)
    pass


# -*- coding: utf-8 -*-
#
#  Copyright (C) 2017 Rocky Bernstein
#  This program is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <http://www.gnu.org/licenses/>.

import inspect, pyficache
from trepan.lib import stack as Mstack
import os.path as osp
from trepan.processor.parse.semantics import Location

INVALID_LOCATION = None
def resolve_location(proc, location):
    """Expand fields in Location namedtuple. If:
       '.':  get fields from stack
       function/module: get fields from evaluation/introspection
       location file and line number: use that
    """
    curframe = proc.curframe
    if location == '.':
        if not curframe:
            proc.errmsg("Don't have a stack to get location from")
            return INVALID_LOCATION
        filename = Mstack.frame2file(proc.core, curframe, canonic=False)
        lineno   = inspect.getlineno(curframe)
        return Location(filename, lineno, False, None)

    assert isinstance(location, Location)
    is_address = False
    if proc.curframe:
        g = curframe.f_globals
        l = curframe.f_locals
    else:
        g = globals()
        l = locals()
        pass
    if location.method:
        # Validate arguments that can't be done in parsing
        filename = lineno = None
        msg = ('Object %s is not known yet as a function, ' % location.method)
        try:
            modfunc = eval(location.method, g, l)
        except:
            proc.errmsg(msg)
            return INVALID_LOCATION

        try:
            # Check if the converted string is a function or instance method
            if inspect.isfunction(modfunc) or hasattr(modfunc, 'im_func'):
                pass
            else:
                proc.errmsg(msg)
                return INVALID_LOCATION
        except:
            proc.errmsg(msg)
            return INVALID_LOCATION
        filename = proc.core.canonic(modfunc.__code__.co_filename)
        # FIXME: we may want to check lineno and
        # respect that in the future
        lineno   = modfunc.__code__.co_firstlineno
    elif location.path:
        filename = proc.core.canonic(location.path)
        lineno  =  location.line_number
        modfunc  = None
        msg = "%s is not known as a file" % location.path
        if not osp.isfile(filename):
            # See if argument is a module
            try:
                modfunc = eval(location.path, g, l)
            except:
                msg = ("Don't see '%s' as a existing file or as an module"
                       % location.path)
                proc.errmsg(msg)
                return INVALID_LOCATION
            pass
            is_address = location.is_address
            if inspect.ismodule(modfunc):
                if hasattr(modfunc, '__file__'):
                    filename = pyficache.pyc2py(modfunc.__file__)
                    filename = proc.core.canonic(filename)
                    if not lineno:
                        # use first line of module file
                        lineno = 1
                        is_address = False
                    return Location(filename, lineno, is_address, modfunc)
                else:
                    msg = ("module '%s' doesn't have a file associated with it" %
                            location.path)

            proc.errmsg(msg)
            return INVALID_LOCATION
        maxline = pyficache.maxline(filename)
        if maxline and lineno > maxline:
            # NB: we use the gdb wording here
            proc.errmsg("Line number %d out of range; %s has %d lines."
                        % (lineno, filename, maxline))
            return INVALID_LOCATION
    elif location.line_number:
        filename   = Mstack.frame2file(proc.core, curframe, canonic=False)
        lineno     = location.line_number
        is_address = location.is_address
        modfunc  = None
    return Location(filename, lineno, is_address, modfunc)

def resolve_address_location(proc, location):
    """Expand fields in Location namedtuple. If:
       '.':  get fields from stack
       function/module: get fields from evaluation/introspection
       location file and line number: use that
    """
    curframe = proc.curframe
    if location == '.':
        filename = Mstack.frame2file(proc.core, curframe, canonic=False)
        offset   = curframe.f_lasti
        is_address = True
        return Location(filename, offset, False, None)

    assert isinstance(location, Location)
    is_address = True
    if proc.curframe:
        g = curframe.f_globals
        l = curframe.f_locals
    else:
        g = globals()
        l = locals()
        pass
    if location.method:
        # Validate arguments that can't be done in parsing
        filename = offset = None
        msg = ('Object %s is not known yet as a function, ' % location.method)
        try:
            modfunc = eval(location.method, g, l)
        except:
            proc.errmsg(msg)
            return INVALID_LOCATION

        try:
            # Check if the converted string is a function or instance method
            if inspect.isfunction(modfunc) or hasattr(modfunc, 'im_func'):
                pass
            else:
                proc.errmsg(msg)
                return INVALID_LOCATION
        except:
            proc.errmsg(msg)
            return INVALID_LOCATION
        filename = proc.core.canonic(modfunc.func_code.co_filename)
        # FIXME: we may want to check offset and
        # respect that in the future
        offset   = 0
    elif location.path:
        filename = proc.core.canonic(location.path)
        offset  =  location.line_number
        is_address = location.is_address
        modfunc  = None
        msg = "%s is not known as a file" % location.path
        if not osp.isfile(filename):
            # See if argument is a module
            try:
                modfunc = eval(location.path, g, l)
            except:
                msg = ("Don't see '%s' as a existing file or as an module"
                       % location.path)
                proc.errmsg(msg)
                return INVALID_LOCATION
            pass
            is_address = location.is_address
            if inspect.ismodule(modfunc):
                if hasattr(modfunc, '__file__'):
                    filename = pyficache.pyc2py(modfunc.__file__)
                    filename = proc.core.canonic(filename)
                    if not offset:
                        # use first offset of module file
                        offset = 0
                        is_address = True
                    return Location(filename, offset, is_address, modfunc)
                else:
                    msg = ("module '%s' doesn't have a file associated with it" %
                            location.path)

            proc.errmsg(msg)
            return INVALID_LOCATION
        maxline = pyficache.maxline(filename)
        if maxline and offset > maxline:
            # NB: we use the gdb wording here
            proc.errmsg("Line number %d out of range; %s has %d lines."
                        % (offset, filename, maxline))
            return INVALID_LOCATION
    elif location.line_number is not None:
        filename   = Mstack.frame2file(proc.core, curframe, canonic=False)
        offset     = location.line_number
        is_address = location.is_address
        modfunc    = proc.list_object
    return Location(filename, offset, is_address, modfunc)


# -*- coding: utf-8 -*-
#  Copyright (C) 2009, 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 inspect, os, sys, threading

# Our local modules
from trepan.processor.command import base_cmd as Mbase_cmd
from trepan.processor import frame as Mframe, cmdproc as Mcmdproc
from trepan.lib import thred as Mthread, complete as Mcomplete

class FrameCommand(Mbase_cmd.DebuggerCommand):
    """**frame** [*thread-Name*|*thread-number*] [*frame-number*]

Change the current frame to frame *frame-number* if specified, or the
current frame, 0, if no frame number specified.

If a thread name or thread number is given, change the current frame
to a frame in that thread. Dot (.) can be used to indicate the name of
the current frame the debugger is stopped in.

A negative number indicates the position from the other or
least-recently-entered end.  So `frame -1` moves to the oldest frame,
and `frame 0` moves to the newest frame. Any variable or expression
that evaluates to a number can be used as a position, however due to
parsing limitations, the position expression has to be seen as a single
blank-delimited parameter. That is, the expression `(5*3)-1` is okay
while `(5 * 3) - 1)` isn't.

**Examples:**

   frame     # Set current frame at the current stopping point
   frame 0   # Same as above
   frame 5-5 # Same as above. Note: no spaces allowed in expression 5-5
   frame .   # Same as above. "current thread" is explicit.
   frame . 0 # Same as above.
   frame 1   # Move to frame 1. Same as: frame 0; up
   frame -1  # The least-recent frame
   frame MainThread 0 # Switch to frame 0 of thread MainThread
   frame MainThread   # Same as above
   frame -2434343 0   # Use a thread number instead of name

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

`up`, `down`, `backtrace`, and `info threads`.
"""

    category      = 'stack'
    min_args      = 0
    max_args      = 2
    name          = os.path.basename(__file__).split('.')[0]
    need_stack    = True
    short_help    = 'Select and print a stack frame'

    def complete(self, prefix):
        proc_obj = self.proc
        low, high = Mframe.frame_low_high(proc_obj, None)
        ary = [str(low+i) for i in range(high-low+1)]
        # FIXME: add in Thread names
        return Mcomplete.complete_token(ary, prefix)

    def find_and_set_debugged_frame(self, frame, thread_id):
        '''The dance we have to do to set debugger frame state to
        *frame*, which is in the thread with id *thread_id*. We may
        need to the hide initial debugger frames.
        '''
        thread = threading._active[thread_id]
        thread_name = thread.getName()
        if (not self.settings['dbg_trepan'] and
            thread_name == Mthread.current_thread_name()):
            # The frame we came in on ('current_thread_name') is
            # the same as the one we want to switch to. In this case
            # we need to some debugger frames are in this stack so
            # we need to remove them.
            newframe = Mthread.find_debugged_frame(frame)
            if newframe is not None:  frame = newframe
            pass
        # FIXME: else: we might be blocked on other threads which are
        # about to go into the debugger it not for the fact this one got there
        # first. Possibly in the future we want
        # to hide the blocks into threading of that locking code as well.

        # Set stack to new frame
        self.stack, self.curindex = Mcmdproc.get_stack(frame, None,
                                                       self.proc)
        self.proc.stack, self.proc.curindex = self.stack, self.curindex
        self.proc.frame_thread_name = thread_name
        return

    def one_arg_run(self, position_str):
        '''The simple case: thread frame switching has been done or is
        not needed and we have an explicit position number as a string'''
        frame_num = self.proc.get_an_int(position_str,
                                         ("The 'frame' command requires a" +
                                          " frame number. Got: %s") %
                                         position_str)
        if frame_num is None: return False

        i_stack = len(self.proc.stack)
        if i_stack == 0:
            self.errmsg('No frames recorded')
            return False

        if frame_num < -i_stack or frame_num > i_stack-1:
            self.errmsg(('Frame number has to be in the range %d to %d.' +
                         ' Got: %d (%s).') % (-i_stack, i_stack-1,
                                               frame_num, position_str))
            return False
        else:
            Mframe.adjust_frame(self.proc, 'frame', pos=frame_num,
                                absolute_pos=True)
            return True
        return  # Not reached

    def get_from_thread_name_or_id(self, name_or_id, report_error=True):
        '''See if *name_or_id* is either a thread name or a thread id.
        The frame of that id/name is returned, or None if name_or_id is
        invalid.'''
        thread_id = self.proc.get_int_noerr(name_or_id)
        if thread_id is None:
            # Must be a "frame" command with frame name, not a frame
            # number (or invalid command).
            name2id = Mthread.map_thread_names()
            if name_or_id == '.':
                name_or_id = Mthread.current_thread_name()
                pass
            thread_id = name2id.get(name_or_id)
            if thread_id is None:
                self.errmsg("I don't know about thread name %s." %
                            name_or_id)
                return None, None
            pass
        # Above we should have set thread_id. Now see if we can
        # find it.
        threads   = sys._current_frames()
        frame     = threads.get(thread_id)
        if frame is None and report_error:
            self.errmsg("I don't know about thread number %s (%d)." %
                        name_or_id, thread_id)
            # self.info_thread_terse()
            return None, None
        return frame, thread_id

    def run(self, args):
        '''Run a frame command. This routine is a little complex
        because we allow a number parameter variations.'''

        if len(args) == 1:
            # Form is: "frame" which means "frame 0"
            position_str = '0'
        elif len(args) == 2:
            # Form is: "frame {position | thread}
            name_or_id = args[1]
            frame, thread_id = self.get_from_thread_name_or_id(name_or_id,
                                                               False)
            if frame is None:
                # Form should be: frame position
                position_str = name_or_id
            else:
                # Form should be: "frame thread" which means
                # "frame thread 0"
                position_str = '0'
                self.find_and_set_debugged_frame(frame, thread_id)
                pass
        elif len(args) == 3:
            # Form is: frame <thread> <position>
            name_or_id = args[1]
            position_str = args[2]
            frame, thread_id = self.get_from_thread_name_or_id(name_or_id)
            if frame is None:
                # Error message was given in routine
                return
            self.find_and_set_debugged_frame(frame, thread_id)
            pass
        self.one_arg_run(position_str)
        return False

if __name__ == '__main__':
    from trepan import debugger as Mdebugger
    d            = Mdebugger.Trepan()
    cp           = d.core.processor
    command = FrameCommand(cp)
    command.run(['frame'])
    command.run(['frame', '1'])
    print('=' * 20)
    cp.curframe = inspect.currentframe()
    cp.stack, cp.curindex = Mcmdproc.get_stack(cp.curframe, None, None,
                                               cp)

    def showit(cmd):
        print('=' * 20)
        cmd.run(['frame'])
        print('-' * 20)
        cmd.run(['frame', 'MainThread'])
        print('-' * 20)
        cmd.run(['frame', '.', '0'])
        print('-' * 20)
        cmd.run(['frame', '.'])
        print('=' * 20)
        return

    # showit(command)

    class BgThread(threading.Thread):
        def __init__(self, fn, cmd):
            threading.Thread.__init__(self)
            self.fn = fn
            self.cmd = cmd
            return

        def run(self):
            self.fn(self.cmd)
            return
        pass
    pass

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


# -*- coding: utf-8 -*-
#   Copyright (C) 2009, 2013, 2015-2016 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 pprint, types
from columnize import columnize

def pp(val, display_width, msg_nocr, msg, prefix=None):
    if prefix is not None:
        val_len = len(repr(val))
        if val_len + len(prefix) < display_width - 1:
            msg(prefix + ' ' + repr(val))
            return
        else:
            msg(prefix)
        pass
    if not pprint_simple_array(val, display_width, msg_nocr, msg,
                               '  '):
        print("Can't print_simple_array")
        msg('  ' + pprint.pformat(val))
        pass
    return

# Actually... code like this should go in pformat.
# Possibly some will go into columnize.
def pprint_simple_array(val, displaywidth, msg_nocr, msg, lineprefix=''):
    '''Try to pretty print a simple case where a list is not nested.
    Return True if we can do it and False if not. '''

    if type(val) != list:
        return False

    numeric = True
    for i in range(len(val)):
        if not (type(val[i]) in [bool, float, int]):
            numeric = False
            if not (type(val[i]) in [bool, float, int, bytes]):
                return False
            pass
        pass
    mess = columnize([repr(v) for v in val],
                     opts={"arrange_array": True,
                           "lineprefix": lineprefix,
                           "displaywidth": int(displaywidth)-3,
                           'ljust': not numeric})
    msg_nocr(mess)
    return True

if __name__ == '__main__':
    def msg_nocr(m):
        sys.stdout.write(m)
        return
    import sys

    def msg(m): print(m)
    pprint_simple_array(range(50), 50, msg_nocr, msg)
    pp([i for i in range(10)], 50, msg_nocr, msg)
    pp(locals(), 50, msg_nocr, msg)
    x = [i for i in range(10)]
    pp(x, 50, msg_nocr, msg, 'x = ')
    pp(x, 20, msg_nocr, msg, 'x = ')
    pp(x, 32, msg_nocr, msg, 'x = ')
    x = [i for i in range(30)]
    l = locals().keys()
    for k in sorted(l):
        pp(eval(k), 80, msg_nocr, msg, prefix='%s =' % k)
        pass
    pass


#!/usr/bin/env python3
# -*- coding: iso-8859-1 -*-
#   Copyright (C) 2008-2010, 2013-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/>.
'''The command-line interface to the debugger.
'''
import os, pyficache, sys, tempfile
import os.path as osp

package='trepan'

# Our local modules
from trepan import client as Mclient
from trepan import clifns as Mclifns
from trepan import debugger as Mdebugger
from trepan import exception as Mexcept
from trepan import options as Moptions
from trepan.interfaces import server as Mserver
from trepan.lib import file as Mfile
from trepan import misc as Mmisc

# The name of the debugger we are currently going by.
__title__ = package

# VERSION.py sets variable VERSION.
from trepan.version import VERSION

def main(dbg=None, sys_argv=list(sys.argv)):
    """Routine which gets run if we were invoked directly"""
    global __title__

    # Save the original just for use in the restart that works via exec.
    orig_sys_argv = list(sys_argv)
    opts, dbg_opts, sys_argv  = Moptions.process_options(__title__,
                                                         VERSION,
                                                         sys_argv)

    if opts.server is not None:
        if opts.server == 'tcp':
            connection_opts={'IO': 'TCP', 'PORT': opts.port}
        else:
            connection_opts={'IO': 'FIFO'}
        intf = Mserver.ServerInterface(connection_opts=connection_opts)
        dbg_opts['interface'] = intf
        if 'FIFO' == intf.server_type:
            print('Starting FIFO server for process %s.' % os.getpid())
        elif 'TCP' == intf.server_type:
            print('Starting TCP server listening on port %s.' %
                  intf.inout.PORT)
            pass
    elif opts.client:
        Mclient.run(opts, sys_argv)
        return

    dbg_opts['orig_sys_argv'] = orig_sys_argv

    if dbg is None:
        dbg = Mdebugger.Trepan(dbg_opts)
        dbg.core.add_ignore(main)
        pass
    Moptions._postprocess_options(dbg, opts)

    # process_options has munged sys.argv to remove any options that
    # options that belong to this debugger. The original options to
    # invoke the debugger and script are in global sys_argv

    if len(sys_argv) == 0:
        # No program given to debug. Set to go into a command loop
        # anyway
        mainpyfile = None
    else:
        mainpyfile = sys_argv[0]  # Get script filename.
        if not osp.isfile(mainpyfile):
            mainpyfile=Mclifns.whence_file(mainpyfile)
            is_readable = Mfile.readable(mainpyfile)
            if is_readable is None:
                print("%s: Python script file '%s' does not exist"
                      % (__title__, mainpyfile,))
                sys.exit(1)
            elif not is_readable:
                print("%s: Can't read Python script file '%s'"
                      % (__title__, mainpyfile, ))
                sys.exit(1)
                return

        if Mfile.is_compiled_py(mainpyfile):
            try:
                from xdis import load_module, PYTHON_VERSION, IS_PYPY
                (python_version, timestamp, magic_int, co, is_pypy,
                 source_size) = load_module(mainpyfile, code_objects=None,
                                            fast_load=True)
                assert is_pypy == IS_PYPY
                assert python_version == PYTHON_VERSION, \
                    "bytecode is for version %s but we are version %s" % (
                        python_version, PYTHON_VERSION)
                # We should we check version magic_int

                py_file = co.co_filename
                if osp.isabs(py_file):
                    try_file = py_file
                else:
                    mainpydir = osp.dirname(mainpyfile)
                    tag = sys.implementation.cache_tag
                    dirnames = [osp.join(mainpydir, tag),
                                mainpydir] + os.environ['PATH'].split(osp.pathsep) + ['.']
                    try_file = Mclifns.whence_file(py_file, dirnames)

                if osp.isfile(try_file):
                    mainpyfile = try_file
                    pass
                else:
                    # Move onto the except branch
                    raise IOError("Python file name embedded in code %s not found" % try_file)
            except IOError:
                try:
                    from uncompyle6 import decompile_file
                except ImportError:
                    print("%s: Compiled python file '%s', but uncompyle6 not found"
                        % (__title__, mainpyfile), file=sys.stderr)
                    sys.exit(1)
                    return

                short_name = osp.basename(mainpyfile).strip('.pyc')
                fd = tempfile.NamedTemporaryFile(suffix='.py',
                                                 prefix=short_name + "_",
                                                 delete=False)
                old_write = fd.file.write

                def write_wrapper(*args, **kwargs):
                    if isinstance(args[0], str):
                        new_args = list(args)
                        new_args[0] = args[0].encode('utf-8')
                        old_write(*new_args, **kwargs)
                    else:
                        old_write(*args, **kwargs)
                fd.file.write = write_wrapper

                # from io import StringIO
                # linemap_io = StringIO()
                try:
                   decompile_file(mainpyfile, fd.file, mapstream=fd)
                except:
                    print("%s: error decompiling '%s'"
                          % (__title__, mainpyfile), file=sys.stderr)
                    sys.exit(1)
                    return

                # # Get the line associations between the original and
                # # decompiled program
                # mapline = linemap_io.getvalue()
                # fd.write(mapline + "\n\n")
                # linemap = eval(mapline[3:])
                mainpyfile = fd.name
                fd.close()

                # Since we are actually running the recreated source,
                # there is little no need to remap line numbers.
                # The mapping is given at the end of the file.
                # However we should consider adding this information
                # and original file name.

                print("%s: couldn't find Python source so we recreated it at '%s'"
                      % (__title__, mainpyfile), file=sys.stderr)

                pass

        # If mainpyfile is an optimized Python script try to find and
        # use non-optimized alternative.
        mainpyfile_noopt = pyficache.pyc2py(mainpyfile)
        if mainpyfile != mainpyfile_noopt \
               and Mfile.readable(mainpyfile_noopt):
            print("%s: Compiled Python script given and we can't use that."
                  % __title__)
            print("%s: Substituting non-compiled name: %s" % (
                __title__, mainpyfile_noopt,))
            mainpyfile = mainpyfile_noopt
            pass

        # Replace trepan's dir with script's dir in front of
        # module search path.
        sys.path[0] = dbg.main_dirname = osp.dirname(mainpyfile)

    # XXX If a signal has been received we continue in the loop, otherwise
    # the loop exits for some reason.
    dbg.sig_received = False

    # if not mainpyfile:
    #     print('For now, you need to specify a Python script name!')
    #     sys.exit(2)
    #     pass

    while True:

        # Run the debugged script over and over again until we get it
        # right.

        try:
            if dbg.program_sys_argv and mainpyfile:
                normal_termination = dbg.run_script(mainpyfile)
                if not normal_termination: break
            else:
                dbg.core.execution_status = 'No program'
                dbg.core.processor.process_commands()
                pass

            dbg.core.execution_status = 'Terminated'
            dbg.intf[-1].msg("The program finished - quit or restart")
            dbg.core.processor.process_commands()
        except Mexcept.DebuggerQuit:
            break
        except Mexcept.DebuggerRestart:
            dbg.core.execution_status = 'Restart requested'
            if dbg.program_sys_argv:
                sys.argv = list(dbg.program_sys_argv)
                part1 = ('Restarting %s with arguments:' %
                         dbg.core.filename(mainpyfile))
                args  = ' '.join(dbg.program_sys_argv[1:])
                dbg.intf[-1].msg(
                    Mmisc.wrapped_lines(part1, args,
                                        dbg.settings['width']))
            else: break
        except SystemExit:
            # In most cases SystemExit does not warrant a post-mortem session.
            break
        pass

    # Restore old sys.argv
    sys.argv = orig_sys_argv
    return

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


# -*- coding: utf-8 -*-
#   Copyright (C) 2009,2013,2015 Rocky Bernstein <rocky@gnu.org>
#
#   This program is free software: you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation, either version 3 of the License, or
#   (at your option) any later version.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
"""Debugger output. """

import io, sys, types

from trepan.inout import base as Mbase

class DebuggerUserOutput(Mbase.DebuggerInOutBase):
    """Debugger output shown directly to what we think of as end-user
    ouptut as opposed to a relay mechanism to another process. Output
    could be an interactive terminal, but it might also be file output"""

    def __init__(self, out=None, opts=None):

        self.flush_after_write = False
        self.output = out or sys.stdout
        self.open(self.output, opts)
        return

    def flush(self):
        return self.output.flush()

    def open(self, output, opts=None):
        """Use this to set where to write to. output can be a
        file object or a string. This code raises IOError on error."""
        if isinstance(output, io.TextIOWrapper) or \
           isinstance(output, io.StringIO) or \
           output == sys.stdout:
            pass
        elif isinstance(output, 'string'.__class__):  # FIXME
            output = open(output, 'w')
        else:
            raise IOError("Invalid output type (%s) for %s" %
                          (output.__class__.__name__, output))
            # raise IOError("Invalid output type (%s) for %s" % (type(output),
            #                                                     output))
        self.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.output.closed:
            raise IOError("writing %s on a closed file" % msg)
        self.output.write(msg)
        if self.flush_after_write: self.flush()
        return
    pass

# Demo
if __name__=='__main__':
    out = DebuggerUserOutput()
    out.writeline("Hello, world!")
    out.write("Hello")
    out.writeline(", again.")

    out.open(sys.stdout)
    out.flush_after_write = True
    out.write("Last hello")
    out.close()
    try:
        out.writeline("You won't see me")
    except ValueError:
        pass
    # Closing after already closed is okay.
    out.close()
    pass