15. User Interfaces

Parsing Program Arguments

#-----------------------------
# Parsing program arguments
# -- getopt way (All Python versions)

#-----------------------------
# Preamble

import sys
import getopt

# getopt() explicitly receives arguments for it to process.
# No magic. Explicit is better than implicit.

# PERL: @ARGV
argv = sys.argv[1:]

# Note that sys.argv[0] is the script name, and need to be
# stripped.

#-----------------------------
# Short options

# PERL: getopt("vDo");
# Polluting the caller's namespace is evil. Don't do that.

# PERL: getopt("vDo:", \%opts);
opts, rest = getopt.getopt(argv, "vDo:")

# If you want switches to take arguments, you must say so.
# Unlike PERL, which silently performs its magic, switches
# specified without trailing colons are considered boolean
# flags by default.

# PERL: getopt("vDo", \%opts);
opts, rest = getopt.getopt(argv, "v:D:o:")

# PERL: getopts("vDo:", \%opts);
# getopt/getopts distinction is not present in Python 'getopt'
# module.

#-----------------------------
# getopt() return values, compared to PERL

# getopt() returns two values. The first is a list of
# (option, value) pair. (Not a dictionary, i.e. Python hash.)
# The second is the list of arguments left unprocessed.

# Example
# >>> argv = "-v ARG1 -D ARG2 -o ARG3".split()
# >>> opts, rest = getopt.getopt(argv, "v:D:o:")
# >>> print opts
# [('-v', 'ARG1'), ('-D', 'ARG2'), ('-o', 'ARG3')]

#-----------------------------
# Long options

# getopt() handles long options too. Pass a list of option
# names as the third argument. If an option takes an argument,
# append an equal sign.

opts, rest = getopt.getopt(argv, "", [
    "verbose", "Debug", "output="])

#-----------------------------
# Switch clustering

# getopt() does switch clustering just fine.

# Example
# >>> argv1 = '-r -f /tmp/testdir'.split()
# >>> argv2 = '-rf /tmp/testdir'.split()
# >>> print getopt.getopt(argv1, 'rf')
# ([('-r', ''), ('-f', '')], ['/tmp/testdir'])
# >>> print getopt.getopt(argv2, 'rf')
# ([('-r', ''), ('-f', '')], ['/tmp/testdir'])

#-----------------------------
# @@INCOMPLETE@@

# TODO: Complete this section using 'getopt'. Show how to
# use the parsed result.

# http://www.python.org/doc/current/lib/module-getopt.html
# Python library reference has a "typical usage" demo.

# TODO: Introduce 'optparse', a very powerful command line
# option parsing module. New in 2.3.

Testing Whether a Program Is Running Interactively

##------------------
import sys

def is_interactive_python():
    try:
        ps = sys.ps1
    except:
        return False
    return True
##------------------
import sys
def is_interactive():
    # only False if stdin is redirected like "-t" in perl.
    return sys.stdin.isatty()

# Or take advantage of Python's Higher Order Functions:
is_interactive = sys.stdin.isatty
##------------------
import posix
def is_interactive_posix():
    tty = open("/dev/tty")
    tpgrp = posix.tcgetpgrp(tty.fileno())
    pgrp = posix.getpgrp()
    tty.close()
    return (tpgrp == pgrp)

# test with:
#  python 15.2.py
#  echo "dummy" | python 15.2.py | cat
print "is python shell:", is_interactive_python()
print "is a tty:", is_interactive()
print "has no tty:", is_interactive_posix()

if is_interactive():
    while True:
        try:
            ln = raw_input("Prompt:")
        except:
            break
        print "you typed:", ln

Clearing the Screen


# Python has no Term::Cap module.
# One could use the curses, but this was not ported to windows,
# use console.

# just run clear
import os
os.system("clear")
# cache output
clear = os.popen("clear").read()
print clear
# or to avoid print's newline
sys.stdout.write(clear)

Determining Terminal or Window Size

# Determining Terminal or Window Size

# eiter use ioctl
import struct, fcntl, termios, sys

s = struct.pack("HHHH", 0, 0, 0, 0)
hchar, wchar = struct.unpack("HHHH", fcntl.ioctl(sys.stdout.fileno(),
                                 termios.TIOCGWINSZ, s))[:2]
# or curses
import curses
(hchar,wchar) = curses.getmaxyx()

# graph contents of values
import struct, fcntl, termios, sys
width = struct.unpack("HHHH", fcntl.ioctl(sys.stdout.fileno(),
                                 termios.TIOCGWINSZ, 
                                 struct.pack("HHHH", 0, 0, 0, 0)))[1]
if width<10:
    print "You must have at least 10 characters"
    raise SystemExit

max_value = 0                    
for v in values:
    max_value = max(max_value,v)
    
ratio = (width-10)/max_value   # chars per unit
for v in values:
    print "%8.1f %s" % (v, "*"*(v*ratio))

Changing Text Color


# there seems to be no standard ansi module
# and BLINK does not blink here.
RED = '\033[31m'
RESET = '\033[0;0m'
BLINK = '\033[05m'
NOBLINK = '\033[25m'

print RED+"DANGER, Will Robinson!"+RESET
print "This is just normal text"
print "Will ``"+BLINK+"Do you hurt yet?"+NOBLINK+"'' and back"

Reading from the Keyboard


# Show ASCII values for keypresses

# _Getch is from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/134892
class _Getch:
    """Gets a single character from standard input.  Doesn't echo to screen."""
    def __init__(self):
        try:
            self.impl = _GetchWindows()
        except ImportError:
            self.impl = _GetchUnix()

    def __call__(self):
        return self.impl()


class _GetchUnix:
    def __init__(self):
        import tty, sys

    def __call__(self):
        import sys, tty, termios
        fd = sys.stdin.fileno()
        old_settings = termios.tcgetattr(fd)
        try:
            tty.setraw(sys.stdin.fileno())
            ch = sys.stdin.read(1)
        finally:
            termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
        return ch


class _GetchWindows:
    def __init__(self):
        import msvcrt

    def __call__(self):
        import msvcrt
        return msvcrt.getch()


getch = _Getch()

print "Press keys to see their ASCII values.  Use Ctrl-C to quit.\n"
try:
    while True:
        char = ord(getch())
        if char == 3:
            break
        print " Decimal: %3d   Octal: %3o   Hex: x%02x" % (char, char, char)
except KeyboardError:
    pass
#----------------------------------------

Ringing the Terminal Bell

print "\aWake up!\n";
#----------------------------------------
# @@INCOMPLETE@@

Using POSIX termios

# @@INCOMPLETE@@
# @@INCOMPLETE@@

Checking for Waiting Input

# On Windows
import msvcrt
if msvcrt.kbhit():
    c = msvcrt.getch

# See http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/134892
# @@INCOMPLETE@@

Reading Passwords

#----------------------------------------
import getpass
import pwd
import crypt
password = getpass.getpass('Enter your password: ')
username = getpass.getuser()
encrypted = pwd.getpwnam(username).pw_passwd
if not encrypted or encrypted == 'x':
    # If using shadow passwords, this will be empty or 'x'
    print "Cannot verify password"
elif crypt.crypt(password, encrypted) != encrypted:
    print "You are not", username
else:
    print "Welcome,", username
#----------------------------------------

Editing Input


# simply importing readline gives line edit capabilities to raw_
import readline
readline.add_history("fake line")
line = raw_input()

# download the following standalone program
#!/usr/bin/python
# vbsh - very bad shell

import os
import readline

while True:
    try:
        cmd = raw_input('$ ')
    except EOFError:
        break
    status = os.system(cmd)
    exit_value = status >> 8
    signal_num = status & 127
    dumped_core = status & 128 and "(core dumped)" or ""
    print "Program terminated with status %d from signal %d%s\n" % (
            exit_value, signal_num, dumped_core)



readline.add_history("some line!")
readline.remove_history_item(position)
line = readline.get_history_item(index)

# an interactive python shell would be
import code, readline
code.InteractiveConsole().interact("code.InteractiveConsole")

Managing the Screen

# @@INCOMPLETE@@
# @@INCOMPLETE@@

Controlling Another Program with Expect

#----------------------------------------
# This entry uses pexpect, a pure Python Expect-like module.
# http://pexpect.sourceforge.net/

# for more information, check pexpect's documentation and example.

import pexpect

#----------------------------------------
# spawn program
try:
    command = pexpect.spawn("program to run")
except pexpect.ExceptionPexpect:
    # couldn't spawn program
    pass

#----------------------------------------
# you can pass any filelike object to setlog
# passing None will stop logging

# stop logging
command.setlog(None)

# log to stdout
import sys
command.setlog(sys.stdout)

# log to specific file
fp = file("pexpect.log", "w")
command.setlog(fp)

#----------------------------------------
# expecting simple string
command.expect("ftp>")

# expecting regular expression
# actually, string is always treated as regular expression

# so it's the same thing
command.expect("Name.*:")

# you can do it this way, too
import re
regex = re.compile("Name.*:")
command.expect(regex)

#----------------------------------------
# expecting with timeout
try:
    command.expect("Password:", 10)
except pexpect.TIMEOUT:
    # timed out
    pass

# setting default timeout
command.timeout = 10

# since we set default timeout, following does same as above
try:
    command.expect("Password:")
except pexpect.TIMEOUT:
    # timed out
    pass

#----------------------------------------
# what? do you *really* want to wait forever?

#----------------------------------------
# sending line: normal way
command.sendline("get spam_and_ham")

# you can also treat it as file
print>>command, "get spam_and_ham"

#----------------------------------------
# finalization

# close connection with child process
# (that is, freeing file descriptor)
command.close()

# kill child process
import signal
command.kill(signal.SIGKILL)

#----------------------------------------
# expecting multiple choices
which = command.expect(["invalid", "success", "error", "boom"])

# return value is index of matched choice
# 0: invalid
# 1: success
# 2: error
# 3: boom

#----------------------------------------
# avoiding exception handling
choices = ["invalid", "success", "error", "boom"]
choices.append(pexpect.TIMEOUT)
choices.append(pexpect.EOF)

which = command.expect(choices)

# if TIMEOUT or EOF occurs, appropriate index is returned
# (instead of raising exception)
# 4: TIMEOUT
# 5: EOF

Creating Menus with Tk

from Tkinter import *

def print_callback():
    print "print_callback"

main = Tk()

menubar = Menu(main)
main.config(menu=menubar)

file_menu = Menu(menubar)
menubar.add_cascade(label="File", underline=1, menu=file_menu)
file_menu.add_command(label="Print", command=print_callback)

main.mainloop()

# using a class
from Tkinter import *

class Application(Tk):
    def print_callback(self):
        print "print_callback"
    def debug_callback(self):
        print "debug:", self.debug.get()
        print "debug level:", self.debug_level.get()

    def createWidgets(self):
        menubar = Menu(self)
        self.config(menu=menubar)
        file_menu = Menu(menubar)
        menubar.add_cascade(label="File",      
                    underline=1, menu=file_menu)
        file_menu.add_command(label="Print",
                command=self.print_callback)
        file_menu.add_command(label="Quit Immediately",
                command=sys.exit)
        # 
        options_menu = Menu(menubar)
        menubar.add_cascade(label="Options",
                underline=0, menu=options_menu)
        options_menu.add_checkbutton(
                label="Create Debugging File",
                variable=self.debug,
                command=self.debug_callback,
                onvalue=1, offvalue=0)
        options_menu.add_separator()
        options_menu.add_radiobutton(
                label = "Level 1",
                variable = self.debug_level,
                value = 1
                )
        options_menu.add_radiobutton(
                label = "Level 2",
                variable = self.debug_level,
                value = 2
                )
        options_menu.add_radiobutton(
                label = "Level 3",
                variable = self.debug_level,
                value = 3
                )

    def __init__(self, master=None):
        Tk.__init__(self, master)
        # bound variables must be IntVar, StrVar, ...
        self.debug = IntVar()
        self.debug.set(0)
        self.debug_level = IntVar()
        self.debug_level.set(1)
        self.createWidgets()

app = Application()
app.mainloop()

Creating Dialog Boxes with Tk

# @@INCOMPLETE@@
# @@INCOMPLETE@@

Responding to Tk Resize Events

# @@INCOMPLETE@@
# @@INCOMPLETE@@

Removing the DOS Shell Window with Windows Perl/Tk

# Start Python scripts without the annoying DOS window on win32
# Use extension ".pyw" on files - eg: "foo.pyw" instead of "foo.py"
# Or run programs using "pythonw.exe" rather than "python.exe" 

Program: Small termcap program

# @@INCOMPLETE@@
# @@INCOMPLETE@@

Program: tkshufflepod

# @@INCOMPLETE@@
# @@INCOMPLETE@@