User Tools

Site Tools


tel: command-line tool to look up MLZ phone numbers.

#!/usr/bin/env python3

'''
Command line tool to look up MLZ phone numbers. 
'''

import argparse, collections, json, lxml.etree, os, pathlib
import re, sys, urllib.request, yaml

def ordered_load(stream, Loader=yaml.Loader,
                 object_pairs_hook=collections.OrderedDict):
    '''
    Overwrite YAML loader to keep dictionary sorted.
    '''
    class OrderedLoader(Loader):
        pass
    def construct_mapping(loader, node):
        loader.flatten_mapping(node)
        return object_pairs_hook(loader.construct_pairs(node))
    OrderedLoader.add_constructor(
        yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG,
        construct_mapping)
    return yaml.load(stream, OrderedLoader)

def ordered_dump(data, stream=None, Dumper=yaml.Dumper, **kwds):
    '''
    Overwrite YAML dumper to keep dictionary sorted.
    '''
    class OrderedDumper(Dumper):
        pass
    def odict_representer(dumper, data):
        return dumper.represent_mapping(
            yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG,
            data.items())
    OrderedDumper.add_representer(collections.OrderedDict, odict_representer)
    return yaml.dump(data, stream, OrderedDumper, **kwds)

def parse_phone( short, long ):
    '''
    Parse phone numbers, which may come as HTML tables.
    '''
    if not ( type(long)==str and re.search( r'<table', long ) ):
        try:
            return int(short)
        except:
            return short
    ret = [ short ]
    long = re.sub( r'&nbsp;', '', long )
    root = lxml.etree.XML( long )
    table = root[0]
    for row in iter(table):
        if len(row)>=2:
            ret.append( "%s %s" % ( row[0].text, row[1].text ) )
        elif len(row)==1:
            ret.append( "((%s))" % row[0].text )
    return ", ".join(ret)

def purge_str( txt ):
    '''
    Remove <br> and similar from list entries.
    '''
    txt = re.sub( r'<br\/?>', ' ', txt )
    txt = txt.rstrip().lstrip()
    return txt

def download_phonelist( backupfile, args ):
    '''
    Download the phone list, parse it, store it, and return it.
    '''
    if args.f:
        h = open('search.php')
        t = h.read()
    else:
        h = urllib.request.urlopen(
            'http://mlz-garching.de/files/addons/telefonverzeichnis/search.php' )
        t = h.read().decode(encoding='UTF-8')
    din = json.loads(t)
    dout = []
    for e in din:
        eout = collections.OrderedDict()
        eout['name'] = e['5']
        eout['forename'] = e['4'] or ''
        try:
            eout['department'] = purge_str( e['abteilung'] )
        except:
            eout['department'] = ''
        txt = e['funktion'] or ''
        try:
            txt += ' '+e['funktionen']
        except: pass
        txt = re.sub( r'er/in', 'er', txt )
        eout['functions'] = purge_str( txt )
        eout['phone'] = parse_phone( e['erstenummer'], e['telefonnummern'] )
        eout['email'] = e['email']
        dout.append(eout)
    with backupfile.open('w') as f:
        ordered_dump( dout, f, default_flow_style=False, allow_unicode=True )
    return dout

def find_phonelist( backupfile ):
    '''
    Returns the previously downloaded and parsed phone list.
    '''
    try:
        f = backupfile.open()
    except:
        return None
    return ordered_load( f )

def find_backupdir():
    '''
    Returns path of directory to hold the downloaded phone list.
    '''
    home = os.getenv( 'HOME' )
    if not home:
        raise Exception( 'Cannot retrieve $HOME' )
    path = pathlib.Path( home )
    if not path.is_dir():
        raise Exception( 'No home directory %s' % path )
    path1 = path / 'addr'
    if path1.is_dir():
        path1 = path1 / 'auto'
        if not path1.is_dir():
            path1.mkdir()
        return path1
    path1 = path / '.tel'
    if not path1.is_dir():
        path1.mkdir()
    return path1

def lookup( phonelist, name, args ):
    '''
    Lookup name in phonelist and print result.
    '''
    out = []
    for i in range(len(phonelist)):
        for k in [ 'name', 'forename', 'department', 'functions' ]:
            n, e = name, phonelist[i][k]
            if args.i:
                n, e = n.lower(), e.lower()
            if re.search( n, e  ):
                out.append( phonelist[i] )
                break
    print( ordered_dump( out, default_flow_style=False, allow_unicode=True ) )

if __name__ == '__main__':

    parser = argparse.ArgumentParser( description='Lookup MLZ phone number' )
    parser.add_argument('name', nargs='?')
    parser.add_argument('-f', action='store_true',
                        help='no download; read from file (for debugging)')
    parser.add_argument('-i', action='store_true',
                        help='ignore case')
    parser.add_argument('-u', action='store_true',
                        help='update downloaded phone list')
    args = parser.parse_args()

    backupdir = find_backupdir()
    backupfile = backupdir / 'mlz.yaml'
    phonelist = find_phonelist( backupfile )
    if not phonelist or args.u:
        phonelist_old = phonelist
        phonelist = download_phonelist( backupfile, args )
        if not phonelist_old:
            print( 'Downloaded phone list with %d entries' % len(phonelist) )
        else:
            print( 'Downloaded phone list: %d-> %d entries' % 
                   ( len(phonelist_old), len(phonelist) ) )

    name = args.name
    if not name:
        exit(0)
    lookup( phonelist, name, args )