Remove old GUI
authorMichael Spang <mspang@csclub.uwaterloo.ca>
Wed, 5 Dec 2007 02:44:16 +0000 (21:44 -0500)
committerMichael Spang <mspang@csclub.uwaterloo.ca>
Wed, 5 Dec 2007 02:44:16 +0000 (21:44 -0500)
bin/ceo-old [deleted file]
pylib/csc/apps/legacy/__init__.py [deleted file]
pylib/csc/apps/legacy/helpers.py [deleted file]
pylib/csc/apps/legacy/main.py [deleted file]

diff --git a/bin/ceo-old b/bin/ceo-old
deleted file mode 100755 (executable)
index 8550f79..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-#!/usr/bin/python2.4 --
-"""CEO SUID Python Wrapper Script"""
-import os, sys
-
-safe_environment = ['LOGNAME', 'USERNAME', 'USER', 'HOME', 'TERM', 'LANG'
-    'LC_ALL', 'LC_COLLATE', 'LC_CTYPE', 'LC_MESSAGES', 'LC_MONETARY',
-    'LC_NUMERIC', 'LC_TIME', 'UID', 'GID', 'SSH_CONNECTION', 'SSH_AUTH_SOCK',
-    'SSH_CLIENT']
-
-for key in os.environ.keys():
-    if key not in safe_environment:
-        del os.environ[key]
-
-os.environ['LESSSECURE'] = '1'
-os.environ['PATH'] = '/usr/sbin:/usr/bin:/sbin:/bin'
-
-for pathent in sys.path[:]:
-    if not pathent.find('/usr') == 0:
-        sys.path.remove(pathent)
-
-euid = os.geteuid()
-egid = os.getegid()
-try:
-    os.setreuid(euid, euid)
-    os.setregid(egid, egid)
-except OSError, e:
-    print str(e)
-    sys.exit(1)
-
-import csc.apps.legacy.main
-csc.apps.legacy.main.run()
diff --git a/pylib/csc/apps/legacy/__init__.py b/pylib/csc/apps/legacy/__init__.py
deleted file mode 100644 (file)
index 0bc0b65..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-"""
-Legacy User Interface
-
-This module contains the legacy CEO user interface and related modules.
-
-    main    - all of the main UI logic
-    helpers - user interface library routines
-"""
diff --git a/pylib/csc/apps/legacy/helpers.py b/pylib/csc/apps/legacy/helpers.py
deleted file mode 100644 (file)
index a18a193..0000000
+++ /dev/null
@@ -1,411 +0,0 @@
-"""
-Helpers for legacy User Interface
-
-This module contains numerous functions that are designed to immitate
-the look and behavior of the previous CEO. Included is code for various
-curses-based UI widgets that were provided by Perl 5's Curses and
-Curses::Widgets libraries.
-
-Though attempts have been made to keep the UI [bug-]compatible with
-the previous system, some compromises have been made. For example,
-the input and textboxes draw 'OK' and 'Cancel' buttons where the old
-CEO had them, but they are fake. That is, the buttons in the old
-CEO were selectable but non-operational, but in the new CEO they are
-not even selectable.
-"""
-import curses.ascii
-
-# key constants not defined in CURSES
-KEY_RETURN = ord('\n')
-KEY_ESCAPE = 27
-KEY_EOT = 4
-
-
-def center(parent_dim, child_dim):
-    """Helper for centering a length in a larget length."""
-    return (parent_dim-child_dim)/2
-
-
-def read_input(wnd, offy, offx, width, maxlen, echo=True):
-    """
-    Read user input within a confined region of a window.
-
-    Basic line-editing is supported:
-        LEFT, RIGHT, HOME, and END move around.
-        BACKSPACE and DEL remove characters.
-        INSERT switches between insert and overwrite mode.
-        ESC and C-d abort input.
-        RETURN completes input.
-    
-    Parameters:
-        wnd    - parent window for region
-        offy   - the vertical offset for the beginning of the input region
-        offx   - the horizontal offset for the beginning of the input region
-        width  - the width of the region
-        maxlen - greatest number of characters to read (0 for no limit)
-        echo   - boolean: whether to display typed characters
-    
-    Returns: the string, or None when the user aborts.
-    """
-
-    # turn on cursor
-    try:
-        curses.curs_set(1)
-    except curses.error:
-        pass
-
-    # set keypad mode to allow UP, DOWN, etc
-    wnd.keypad(1)
-
-    # the input string
-    inputbuf = ""
-
-    # offset of cursor in input
-    # i.e. the next operation is applied at input[inputoff]
-    inputoff = 0
-
-    # display offset (for scrolling)
-    # i.e. the first character in the region is input[displayoff]
-    displayoff = 0
-
-    # insert mode (True) or overwrite mode (False)
-    insert = True
-
-    while True:
-
-        # echo mode, display the string
-        if echo:
-            # discard characters before displayoff, 
-            # as the window may be scrolled to the right
-            substring = inputbuf[displayoff:]
-    
-            # pad the string with zeroes to overwrite stale characters
-            substring = substring + " " * (width - len(substring))
-    
-            # display the substring
-            wnd.addnstr(offy, offx, substring, width)
-    
-            # await input
-            key = wnd.getch(offy, offx + inputoff - displayoff)
-
-        # not echo mode, don't display the string
-        else:
-            # await input at arbitrary location
-            key = wnd.getch(offy, offx)
-
-        # enter returns input
-        if key == KEY_RETURN:
-            return inputbuf
-
-        # escape aborts input
-        elif key == KEY_ESCAPE:
-            return None
-
-        # EOT (C-d) aborts if there is no input
-        elif key == KEY_EOT:
-            if len(inputbuf) == 0:
-                return None
-
-        # backspace removes the previous character
-        elif key == curses.KEY_BACKSPACE:
-            if inputoff > 0:
-
-                # remove the character immediately before the input offset
-                inputbuf = inputbuf[0:inputoff-1] + inputbuf[inputoff:]
-                inputoff -= 1
-
-                # move either the cursor or entire line of text left
-                if displayoff > 0:
-                    displayoff -= 1
-
-        # delete removes the current character
-        elif key == curses.KEY_DC:
-            if inputoff < len(input):
-                
-                # remove the character at the input offset
-                inputbuf = inputbuf[0:inputoff] + inputbuf[inputoff+1:]
-
-        # left moves the cursor one character left
-        elif key == curses.KEY_LEFT:
-            if inputoff > 0:
-
-                # move the cursor to the left
-                inputoff -= 1
-
-                # scroll left if necessary
-                if inputoff < displayoff:
-                    displayoff -= 1
-
-        # right moves the cursor one character right
-        elif key == curses.KEY_RIGHT:
-            if inputoff < len(inputbuf):
-                
-                # move the cursor to the right
-                inputoff += 1
-
-                # scroll right if necessary
-                if displayoff - inputoff == width:
-                    displayoff += 1
-
-        # home moves the cursor to the first character
-        elif key == curses.KEY_HOME:
-            inputoff = 0
-            displayoff = 0
-
-        # end moves the cursor past the last character
-        elif key == curses.KEY_END:
-            inputoff = len(inputbuf)
-            displayoff = len(inputbuf) - width + 1
-
-        # insert toggles insert/overwrite mode
-        elif key == curses.KEY_IC:
-            insert = not insert
-
-        # other (printable) characters are added to the input string
-        elif curses.ascii.isprint(key):
-            if len(inputbuf) < maxlen or maxlen == 0:
-
-                # insert mode: insert before current offset
-                if insert:
-                    inputbuf = inputbuf[0:inputoff] + chr(key) + inputbuf[inputoff:]
-    
-                # overwrite mode: replace current offset
-                else:
-                    inputbuf = inputbuf[0:inputoff] + chr(key) + inputbuf[inputoff+1:]
-    
-                # increment the input offset
-                inputoff += 1
-    
-                # scroll right if necessary
-                if inputoff - displayoff == width:
-                    displayoff += 1
-
-
-def inputbox(wnd, prompt, field_width, echo=True):
-    """Display a window for user input."""
-
-    wnd_height, wnd_width = wnd.getmaxyx()
-    height, width = 12, field_width + 7
-
-    # draw a window for the dialog
-    childy, childx = center(wnd_height-1, height)+1, center(wnd_width, width)
-    child_wnd = wnd.subwin(height, width, childy, childx)
-    child_wnd.clear()
-    child_wnd.border()
-
-    # draw another window for the text box
-    texty, textx = center(height-1, 3)+1, center(width-1, width-5)+1
-    textheight, textwidth = 3, width-5
-    text_wnd = child_wnd.derwin(textheight, textwidth, texty, textx)
-    text_wnd.clear()
-    text_wnd.border()
-    
-    # draw the prompt
-    prompty, promptx = 2, 3
-    child_wnd.addnstr(prompty, promptx, prompt, width-2)
-
-    # draw the fake buttons
-    fakey, fakex = 9, width - 19
-    child_wnd.addstr(fakey, fakex, "< OK > < Cancel >")
-    child_wnd.addch(fakey, fakex+2, "O", curses.A_UNDERLINE)
-    child_wnd.addch(fakey, fakex+9, "C", curses.A_UNDERLINE)
-
-    # update the screen
-    child_wnd.noutrefresh()
-    text_wnd.noutrefresh()
-    curses.doupdate()
-
-    # read an input string within the field region of text_wnd
-    inputy, inputx, inputwidth = 1, 1, textwidth - 2
-    inputbuf = read_input(text_wnd, inputy, inputx, inputwidth, 0, echo)
-    
-    # erase the window
-    child_wnd.erase()
-    child_wnd.refresh()
-
-    return inputbuf
-
-
-def line_wrap(line, width):
-    """Wrap a string to a certain width (returns a list of strings)."""
-
-    wrapped_lines = []
-    tokens = line.split(" ")
-    tokens.reverse()
-    tmp = tokens.pop()
-    if len(tmp) > width:
-        wrapped_lines.append(tmp[0:width])
-        tmp = tmp[width:]
-    while len(tokens) > 0:
-        token = tokens.pop()
-        if len(tmp) + len(token) + 1 <= width:
-            tmp += " " + token
-        elif len(token) > width:
-            tmp += " " + token[0:width-len(tmp)-1]
-            tokens.push(token[width-len(tmp)-1:])
-        else:
-            wrapped_lines.append(tmp)
-            tmp = token
-    wrapped_lines.append(tmp)
-    return wrapped_lines
-
-
-def msgbox(wnd, msg, title="Message"):
-    """Display a message in a window."""
-
-    # split message into a list of lines
-    lines = msg.split("\n")
-    
-    # determine the dimensions of the method
-    message_height = len(lines)
-    message_width = 0
-    for line in lines:
-        if len(line) > message_width:
-            message_width = len(line)
-
-    # ensure the window fits the title
-    if len(title) > message_width:
-        message_width = len(title)
-
-    # maximum message width
-    parent_height, parent_width = wnd.getmaxyx()
-    max_message_width = parent_width - 8
-
-    # line-wrap if necessary
-    if message_width > max_message_width:
-        newlines = []
-        for line in lines:
-            for newline in line_wrap(line, max_message_width):
-                newlines.append(newline)
-        lines = newlines
-        message_width = max_message_width
-        message_height = len(lines)
-
-    # random padding that perl's curses adds
-    pad_width = 2
-
-    # create the outer window
-    outer_height, outer_width = message_height + 8, message_width + pad_width + 6
-    outer_y, outer_x = center(parent_height+1, outer_height)-1, center(parent_width, outer_width)
-    outer_wnd = wnd.derwin(outer_height, outer_width, outer_y, outer_x)
-    outer_wnd.erase()
-    outer_wnd.border()
-
-    # create the inner window
-    inner_height, inner_width = message_height + 2, message_width + pad_width + 2
-    inner_y, inner_x = 2, center(outer_width, inner_width)
-    inner_wnd = outer_wnd.derwin(inner_height, inner_width, inner_y, inner_x)
-    inner_wnd.border()
-
-    # display the title
-    outer_wnd.addstr(0, 1, " " + title + " ", curses.A_REVERSE | curses.A_BOLD)
-    
-    # display the message
-    for i in xrange(len(lines)):
-        inner_wnd.addnstr(i+1, 1, lines[i], message_width)
-
-        # draw a solid block at the end of the first few lines
-        if i < len(lines) - 1:
-            inner_wnd.addch(i+1, inner_width-1, ' ', curses.A_REVERSE)
-
-    # display the fake OK button
-    fakey, fakex = outer_height - 3, outer_width - 8
-    outer_wnd.addstr(fakey, fakex, "< OK >", curses.A_REVERSE)
-    outer_wnd.addch(fakey, fakex+2, "O", curses.A_UNDERLINE | curses.A_REVERSE)
-
-    # update display
-    outer_wnd.noutrefresh()
-    inner_wnd.noutrefresh()
-    curses.doupdate()
-
-    # read a RETURN or ESC before returning
-    curses.curs_set(0)
-    outer_wnd.keypad(1)
-    while True:
-        key = outer_wnd.getch(0, 0)
-        if key == KEY_RETURN or key == KEY_ESCAPE:
-            break
-
-    # clear the window
-    outer_wnd.erase()
-    outer_wnd.refresh()
-    
-
-def menu(wnd, offy, offx, width, options, _acquire_wnd=None):
-    """
-    Draw a menu and wait for a selection.
-
-    Parameters:
-        wnd          - parent window
-        offy         - vertical offset for menu region
-        offx         - horizontal offset for menu region
-        width        - width of menu region
-        options      - a list of selections
-        _acquire_wnd - hack to support resize: must be a function callback
-                       that returns new values for wnd, offy, offx, height,
-                       width. Unused if None.
-
-    Returns: index into options that was selected
-    """
-
-    # the currently selected option
-    selected = 0
-
-    while True:
-        # disable cursor
-        curses.curs_set(0)
-
-        # hack to support resize: recreate the
-        # parent window every iteration
-        if _acquire_wnd:
-            wnd, offy, offx, height, width = _acquire_wnd()
-
-        # keypad mode so getch() works with up, down
-        wnd.keypad(1)
-
-        # display the menu
-        for i in xrange(len(options)):
-            text, callback = options[i]
-            text = text + " " * (width - len(text))
-
-            # the selected option is displayed in reverse video
-            if i == selected:
-                wnd.addstr(i+offy, offx, text, curses.A_REVERSE)
-            else:
-                wnd.addstr(i+offy, offx, text)
-                    # display the member
-
-        wnd.refresh()
-        
-        # read one keypress
-        keypress = wnd.getch()
-
-        # UP moves to the previous option
-        if (keypress == curses.KEY_UP or keypress == ord('k')) and selected > 0:
-            selected = (selected - 1)
-
-        # DOWN moves to the next option
-        elif (keypress == curses.KEY_DOWN or keypress == ord('j')) and selected < len(options) - 1:
-            selected = (selected + 1)
-
-        # RETURN runs the callback for the selected option
-        elif keypress == KEY_RETURN:
-            text, callback = options[selected]
-
-            # highlight the selected option
-            text = text + " " * (width - len(text))
-            wnd.addstr(selected+offy, offx, text, curses.A_REVERSE | curses.A_BOLD)
-            wnd.refresh()
-
-            # execute the selected option
-            if callback(wnd): # success
-                break
-
-
-def reset():
-    """Reset the terminal and clear the screen."""
-
-    reset = curses.tigetstr('rs1')
-    if not reset: reset = '\x1bc'
-    curses.putp(reset)
-
diff --git a/pylib/csc/apps/legacy/main.py b/pylib/csc/apps/legacy/main.py
deleted file mode 100644 (file)
index 98df1f7..0000000
+++ /dev/null
@@ -1,531 +0,0 @@
-"""
-CEO-like Frontend
-
-This frontend aims to be compatible in both look and function with the
-curses UI of CEO.
-
-Some small improvements have been made, such as not echoing passwords and
-aborting when nothing is typed into the first input box during an operation.
-
-This frontend is poorly documented, deprecated, and undoubtedly full of bugs.
-"""
-import curses.ascii, re, os
-from helpers import menu, inputbox, msgbox, reset
-from csc.adm import accounts, members, terms
-from csc.common.excep import InvalidArgument
-
-# color of the ceo border
-BORDER_COLOR = curses.COLOR_RED
-
-
-def read_uid(wnd):
-    """Read a username."""
-
-    prompt = 'Username:'
-    return inputbox(wnd, prompt, 36)
-
-def read_member(wnd):
-    """Looks up a member."""
-
-    # connect the members module to its backend if necessary
-    if not members.connected(): members.connect()
-
-    uid = read_uid(wnd)
-    if not uid or uid.lower() == 'exit':
-        return
-
-    member = members.get(uid)
-    if not member:
-        msgbox(wnd, "Invalid username: %s" % uid)
-        return
-
-    # display user
-    display_member_details(wnd, member)
-
-    return member
-
-
-def action_library(wnd):
-    """Display a link to the library."""
-    msgbox(wnd, "Please visit library.csclub.uwaterloo.ca")
-
-def action_new_member(wnd):
-    """Interactively add a new member."""
-
-    userid, program = '', ''
-
-    msgbox(wnd, "Membership is $2.00 CDN. Please ensure\n"
-                "the money is desposited in the safe\n"
-                "before continuing.")
-
-    # read the name
-    prompt = "New member's full name: "
-    realname = inputbox(wnd, prompt, 30)
-
-    # abort if no name is entered
-    if not realname or realname.lower() == 'exit':
-        return False
-
-    # read the program of study
-    prompt = "New member's program of study:"
-    program = inputbox(wnd, prompt, 30)
-
-    # abort if exit is entered
-    if program is None or program.lower() == 'exit':
-        return False
-
-    # read user id
-    prompt = "New member's UWdir username:"
-    while userid == '':
-        userid = inputbox(wnd, prompt, 30)
-
-    # user abort
-    if userid is None or userid.lower() == 'exit':
-        return False
-
-    # connect the members module to its backend if necessary
-    if not members.connected(): members.connect()
-
-    # attempt to create the member
-    try:
-        members.new(userid, realname, program)
-
-        msgbox(wnd, "Success! Your username is %s.  You are now registered\n"
-                    % userid + "for the " + terms.current() + " term.")
-
-    except members.InvalidRealName:
-        msgbox(wnd, 'Invalid real name: "%s"' % realname)
-        return False
-    except InvalidArgument, e:
-        if e.argname == 'uid' and e.explanation == 'duplicate uid':
-            msgbox(wnd, 'A member with this user ID exists.')
-            return False
-        else:
-            raise
-
-
-def action_term_register(wnd):
-    """Interactively register a member for a term."""
-
-    term = ''
-
-    member = read_member(wnd)
-    if not member:
-        return False
-    uid = member['uid'][0]
-
-    # verify member
-    prompt = "Is this the correct member?"
-    answer = None
-    while answer != "yes" and answer != "y" and answer != "n" and answer != "no" and answer != "exit":
-        answer = inputbox(wnd, prompt, 28) 
-
-    # user abort
-    if answer == "exit":
-        return False
-
-    # read the term
-    prompt = "Which term to register for ([wsf]20nn):"
-    while not re.match('^[wsf][0-9]{4}$', term) and not term == 'exit':
-        term = inputbox(wnd, prompt, 41) 
-
-    # abort when exit is entered
-    if term.lower() == 'exit':
-        return False
-
-    # already registered?
-    if members.registered(uid, term):
-        msgbox(wnd, "You are already registered for term " + term)
-        return False
-
-    try:
-
-        # attempt to register
-        members.register(uid, term)
-        
-        # display success message
-        msgbox(wnd, "You are now registered for term " + term)
-
-    except members.InvalidTerm:
-        msgbox(wnd, "Term is not valid: %s" % term)
-
-    return False
-
-
-def action_term_register_multiple(wnd):
-    """Interactively register a member for multiple terms."""
-
-    base, num = '', None
-
-    member = read_member(wnd)
-    if not member:
-        return False
-    uid = member['uid'][0]
-
-    # verify member
-    prompt = "Is this the correct member?"
-    answer = None
-    while answer != "yes" and answer != "y" and answer != "n" and answer != "no" and answer != "exit":
-        answer = inputbox(wnd, prompt, 28) 
-
-    # user abort
-    if answer == "exit":
-        return False
-
-    # read the base
-    prompt = "Which term to start registering ([fws]20nn):"
-    while not re.match('^[wsf][0-9]{4}$', base) and not base == 'exit':
-        base = inputbox(wnd, prompt, 41) 
-
-    # abort when exit is entered
-    if base.lower() == 'exit':
-        return False
-
-    # read number of terms
-    prompt = 'How many terms?'
-    while not num or not re.match('^[0-9]*$', num):
-        num = inputbox(wnd, prompt, 36)
-    num = int(num)
-
-    # any terms in the range?
-    if num < 1:
-        msgbox(wnd, "No terms to register.")
-        return False
-
-    # compile a list to register
-    term_list = terms.interval(base, num)
-
-    # already registered?
-    for term in term_list:
-        if members.registered(uid, term):
-            msgbox(wnd, "You are already registered for term " + term)
-            return False
-
-    try:
-
-        # attempt to register all terms
-        members.register(uid, term_list)
-        
-        # display success message [sic]
-        msgbox(wnd, "Your are now registered for terms: " + ", ".join(term_list))
-
-    except members.InvalidTerm:
-        msgbox(wnd, "Invalid term entered.")
-
-    return False
-
-
-def input_password(wnd):
-
-    # password input loop
-    password = "password"
-    check = "check"
-    while password != check:
-    
-        # read password
-        prompt = "User password:"
-        password = None
-        while not password:
-            password = inputbox(wnd, prompt, 18, False) 
-
-        # read another password
-        prompt = "Enter the password again:"
-        check = None
-        while not check:
-            check = inputbox(wnd, prompt, 27, False) 
-
-    return password
-
-
-def action_create_account(wnd):
-    """Interactively create an account for a member."""
-    
-    member = read_member(wnd)
-    if not member:
-        return False
-
-    # member already has an account?
-    if not accounts.connected(): accounts.connect()
-    if 'posixAccount' in member['objectClass']:
-        msgbox(wnd, "Account already exists.")
-        return False
-
-    # verify member
-    prompt = "Is this the correct member?"
-    answer = None
-    while answer != "yes" and answer != "y" and answer != "n" and answer != "no" and answer != "exit":
-        answer = inputbox(wnd, prompt, 28) 
-
-    # user abort
-    if answer == "exit":
-        return False
-
-    # incorrect member; abort
-    if answer == "no" or answer == "n":
-        msgbox(wnd, "I suggest searching for the member by userid or name from the main menu.")
-        return False
-
-    msgbox(wnd, "Ensure the member has signed the machine\n"
-                "usage policy. Accounts of users who have\n"
-                "not signed will be suspended if discovered.")
-
-    # read password
-    password = input_password(wnd)
-
-    # create the UNIX account
-    try:
-        if not accounts.connected(): accounts.connect()
-        accounts.create_member(member['uid'][0], password, member['cn'][0])
-    except accounts.NameConflict, e:
-        msgbox(wnd, str(e))
-        return False
-    except accounts.NoAvailableIDs, e:
-        msgbox(wnd, str(e))
-        return False
-    except accounts.InvalidArgument, e:
-        msgbox(wnd, str(e))
-        return False
-    except accounts.LDAPException, e:
-        msgbox(wnd, "Error creating LDAP entry - Contact the Systems Administrator: %s" % e)
-        return False
-    except accounts.KrbException, e:
-        msgbox(wnd, "Error creating Kerberos principal - Contact the Systems Administrator: %s" % e)
-        return False
-
-    # success
-    msgbox(wnd, "Please run 'addhomedir " + member['uid'][0] + "'.")
-    msgbox(wnd, "Success! Your account has been added")
-
-    return False
-
-
-def display_member_details(wnd, member):
-    """Display member attributes in a message box."""
-
-    # clone and sort term_list
-    if 'term' in member:
-        term_list = list(member['term'])
-    else:
-        term_list = []
-    term_list.sort( terms.compare )
-
-    # labels for data
-    id_label, name_label = "ID:", "Name:"
-    program_label, terms_label = "Program:", "Terms:"
-
-    if 'program' in member:
-        program = member['program'][0]
-    else:
-        program = None
-
-    # format it all into a massive string
-    message =  "%8s %-20s %10s %-10s\n" % (name_label, member['cn'][0], id_label, member['uid'][0]) + \
-               "%8s %-20s\n" % (program_label, program )
-
-    message += "%s %s" % (terms_label, " ".join(term_list))
-
-    # display the string in a message box
-    msgbox(wnd, message)
-    
-
-def action_display_member(wnd):
-    """Interactively display a member."""
-    
-    if not members.connected(): members.connect()
-    member = read_member(wnd)
-    return False
-
-
-def page(text):
-    """Send a text buffer to an external pager for display."""
-    
-    try:
-        pager = '/usr/bin/less'
-        pipe = os.popen(pager, 'w')
-        pipe.write(text)
-        pipe.close() 
-    except IOError:
-        # broken pipe (user didn't read the whole list)
-        pass
-    
-
-def format_members(member_list):
-    """Format a member list into a string."""
-
-    # clone and sort member_list
-    member_list = list(member_list)
-    member_list.sort( lambda x, y: cmp(x['uid'], y['uid']) )
-
-    buf = ''
-    
-    for member in member_list:
-        if 'uid' in member:
-            uid = member['uid'][0]
-        else:
-            uid = None
-        if 'program' in member:
-            program = member['program'][0]
-        else:
-            program = None
-        attrs = ( uid, member['cn'][0], program )
-        buf += "%10s %30s\n%41s\n\n" % attrs
-
-    return buf
-
-
-def action_list_term(wnd):
-    """Interactively list members registered in a term."""
-
-    term = None
-
-    # read the term
-    prompt = "Which term to list members for ([fws]20nn): "
-    while term is None or (not term == '' and not re.match('^[wsf][0-9]{4}$', term) and not term == 'exit'):
-        term = inputbox(wnd, prompt, 41) 
-
-    # abort when exit is entered
-    if not term or term.lower() == 'exit':
-        return False
-
-    # connect the members module to its backends if necessary
-    if not members.connected(): members.connect()
-    
-    # retrieve a list of members for term
-    member_list = members.list_term(term)
-
-    # format the data into a mess of text
-    buf = format_members(member_list.values())
-
-    # display the mass of text with a pager
-    page( buf )
-
-    return False
-
-
-def action_list_name(wnd):
-    """Interactively search for members by name."""
-    
-    name = None
-
-    # read the name
-    prompt = "Enter the member's name: "
-    name = inputbox(wnd, prompt, 41) 
-
-    # abort when exit is entered
-    if not name or name.lower() == 'exit':
-        return False
-
-    # connect the members module to its backends if necessary
-    if not members.connected(): members.connect()
-    
-    # retrieve a list of members with similar names
-    member_list = members.list_name(name)
-
-    # format the data into a mess of text
-    buf = format_members(member_list.values())
-
-    # display the mass of text with a pager
-    page( buf )
-
-    return False
-
-
-def null_callback(wnd):
-    """Callback for unimplemented menu options."""
-    return False
-
-
-def exit_callback(wnd):
-    """Callback for the exit option."""
-    return True
-
-
-# the top level ceo menu
-top_menu = [
-    ( "New member", action_new_member ),
-    ( "Register for a term", action_term_register ),
-    ( "Register for multiple terms", action_term_register_multiple ),
-    ( "Display a member", action_display_member ),
-    ( "List members registered in a term", action_list_term ),
-    ( "Search for a member by name", action_list_name ),
-    ( "Create an account", action_create_account ),
-    ( "Library functions", action_library ),
-    ( "Exit", exit_callback ),
-]
-
-
-def acquire_ceo_wnd(screen=None):
-    """Create the top level ceo window."""
-    
-    # hack to get a reference to the entire screen
-    # even when the caller doesn't (shouldn't) have one
-    if screen is None:
-        screen = globals()['screen']
-    else:
-        globals()['screen'] = screen
-
-    # if the screen changes size, a mess may be left
-    screen.erase()
-
-    # for some reason, the legacy ceo system
-    # excluded the top line from its window
-    height, width = screen.getmaxyx()
-    ceo_wnd = screen.subwin(height-1, width, 1, 0)
-
-    # draw the border around the ceo window
-    curses.init_pair(1, BORDER_COLOR, -1)
-    color_attr = curses.color_pair(1) | curses.A_BOLD
-    ceo_wnd.attron(color_attr)
-    ceo_wnd.border()
-    ceo_wnd.attroff(color_attr)
-
-    # return window and dimensions of inner area
-    return ceo_wnd, 1, 1, height-2, width-2
-
-
-def ceo_main_curses(screen):
-    """Wrapped main for curses."""
-    
-    curses.use_default_colors()
-
-    # workaround for SSH sessions on virtual consoles (reset terminal)
-    reset()
-
-    # create ceo window
-    ceo_wnd, menu_y, menu_x, menu_height, menu_width = acquire_ceo_wnd(screen)
-
-    try:
-        # display the top level menu
-        menu(ceo_wnd, menu_y, menu_x, menu_width, top_menu, acquire_ceo_wnd)
-    finally:
-        members.disconnect()
-        accounts.disconnect()
-
-
-def run():
-    """Main function for legacy UI."""
-
-    # workaround for xterm-color (bad terminfo? - curs_set(0) fails)
-    if "TERM" in os.environ and os.environ['TERM'] == "xterm-color":
-        os.environ['TERM'] = "xterm"
-
-    # wrap the entire program using curses.wrapper
-    # so that the terminal is restored to a sane state
-    # when the program exits
-    try:
-        curses.wrapper(ceo_main_curses)
-    except KeyboardInterrupt:
-        pass
-    except curses.error:
-        print "Is your screen too small?"
-        raise
-    except:
-        reset()
-        raise
-
-    # clean up screen before exit
-    reset()
-
-if __name__ == '__main__':
-    run()
-