@ 260802.2
current
#!/usr/bin/env python2

import sys

import pywingo

if len(sys.argv) != 2 or sys.argv[1] not in ('top', 'bot', 'left', 'right'):
    print >> sys.stderr, 'Usage: growto (top | bot | left | right)'
    sys.exit(1)

def edges(client):
    '''
    Returns a dictionary of edges for the given client.
    '''
    eleft, etop = W.GetClientX(client), W.GetClientY(client)
    w, h = W.GetClientWidth(client), W.GetClientHeight(client)
    eright, ebot = eleft + w, etop + h
    return {
        'left': eleft, 'top': etop,
        'right': eright, 'bot': ebot,
    }

def flatten(lst):
    '''
    Flattens a list: [[a]] -> [a].
    '''
    flattened = []
    for sublst in lst:
        for x in sublst:
            flattened.append(x)
    return flattened

def newpos(vertical, order, default, include):
    '''
    Finds a new edge for the client from a list of edges corresponding
    to all relevant surfaces.

    `vertical` should be True if the client is moving/growing/shrinking in
    a vertical direction (top/bot). Should be False otherwise.

    `order` should either be `min` or `max`, and is used to retrieve the
    next edge to move/grow/shrink to from a list of candidate edges.

    `default` is the edge of last resort if no other edges were found.
    It is also used as a min/max depending on `order`.
    (e.g., the head boundary.)

    `include` is the comparison criterion for edges. It should return
    True if the given edge is a possible choice.
    (e.g., all edges less than the active client's left edge when
           growing in the left direction.)
    '''
    def extract(edges):
        if vertical:
            return [edges['top'], edges['bot']]
        else:
            return [edges['left'], edges['right']]
    candidates = filter(include, flatten(map(extract, alledges)))
    return order(default, order(candidates) if candidates else default)

# Initialization. Connect and grab the edges/geometry of the active client.
direction = sys.argv[1]
W = pywingo.Wingo()

win = W.GetActive()
if not W.MatchClientMapped(win):
    print >> sys.stderr, 'Window %d is not visible' % win
    sys.exit(1)
wine = edges(win)
wine_width = wine['right'] - wine['left']
wine_height = wine['bot'] - wine['top']

# Find the geometry of the active head and compute the edges for all
# clients on the head.
wrk = W.GetClientWorkspace(win)
head = W.WorkspaceHead(wrk)
headw, headh = W.GetHeadWidth(head), W.GetHeadHeight(head)
clients = filter(lambda c: W.MatchClientMapped(c), W.GetClientList(wrk))
alledges = []
for c in clients:
    if c == win:
        continue
    alledges.append(edges(c))

if direction == 'top':
    newtop = newpos(True, max, 0, lambda e: e < wine['top'])

    W.MoveRelative(win, wine['left'], newtop)
    W.Resize(win, wine_width, wine_height + wine['top'] - newtop)
elif direction == 'left':
    newleft = newpos(False, max, 0, lambda e: e < wine['left'])

    W.MoveRelative(win, newleft, wine['top'])
    W.Resize(win, wine_width + wine['left'] - newleft, wine_height)
elif direction == 'bot':
    newbot = newpos(True, min, headh, lambda e: e > wine['bot'])

    W.Resize(win, wine_width, newbot - wine['top'])
elif direction == 'right':
    newright = newpos(False, min, headw, lambda e: e > wine['right'])

    W.Resize(win, newright - wine['left'], wine_height)
else:
    assert False, 'bug'