import nis, re, UserList

class UserDataEntry:
    '''
    UserDataEntry - provide a simple interface for user data
    (userid, (full) name, mailname). For convenience, there is a
    method lastname() which trys to guess the lastname from name
    by using the last whitespace separated part.
    '''
    
    def __init__( self, userid='', name='', mailname='' ):
        'Usage: data = UserDataEntry( userid, name, mailname )'
        self.userid = userid.strip()
        self.name = name.strip()
        self.mailname = mailname.strip()

    def __str__( self ):
        return ' '.join( [
          self.userid.ljust(8),
          self.name.ljust(30),
          self.mailname ] )

    def lastname( self ):
        '''
        Guess a last name by using the last word of the name field.
        If there is only whitespace return an empty string.
        Usage: data.lastname()
        '''
        parts = self.name.split()
        if parts:
            return parts[-1]
        else:
            return ''


class UserDataList( UserList.UserList ):
    '''
    UserDataList - provide operations for handling of user data
    from NIS and a sendmail user database (userdb)

    Usage:

    user_list = user_data_list.UserDataList( source='NIS' )
    user_list.filter_by( 'lastname', 'Schwarzer' )
    user_list.update_mailnames( '/path/to/userdb' )
    # synonymous:
    # user_list.userdb = '/path/to/userdb'
    # user_list.update_mailnames()
    user_list.sort_by( 'userid' )
    print user_list
    '''
    
    def __init__( self, source=None ):
        '''
        Inititialize the UserDataEntry's, possibly from NIS
        Usage: data = UserDataList( source='NIS' )
        '''        
        UserList.UserList.__init__( self )
        # turn data into _list, a private attribute (by convention)
        self._list = self.data
        del self.data
        self.userdb = ''
        if source == 'NIS':
            self._init_from_NIS()

    def _init_from_NIS( self ):
        'Initialize object from NIS password data'
        passwd_lines = nis.cat( 'passwd.byname' ).values()
        for line in passwd_lines:
            line_parts = line.split( ':' )
            try:
                userid, name = line_parts[0], line_parts[4]
                self._list.append( UserDataEntry(userid, name) )
            except IndexError:
                # ignore invalidly formed lines
                pass

    def __str__( self ):
        'Return a list of passwd entries, one per line'
        formatted_entries = [ str(entry) for entry in self._list ]
        return '\n'.join( formatted_entries )

    def _sort_function( self, fieldname ):
        'Return a sort function appropriate for the specified field'
        sort_function_dict = {
          'userid'  : lambda a, b: cmp( a.userid, b.userid ),
          'name'    : lambda a, b: cmp( a.name, b.name ),
          'lastname': lambda a, b: cmp( a.lastname(), b.lastname() ),
          'mailname': lambda a, b: cmp( a.mailname, b.mailname )
        }
        if sort_function_dict.has_key( fieldname ):
            return sort_function_dict[fieldname]
        else:
            raise( ValueError('unsupported sort field "%s"' % fieldname) )

    def sort_by( self, fieldname ):
        '''
        Sort entry list by the specified fieldname
        Usage: data.sort_by( 'lastname' )
        '''
        self._list.sort( self._sort_function(fieldname) )

    def _filter_function( self, fieldname ):
        '''
        Return a filter function appropriate for the specified field
        A match occurs for fieldname
        userid   if the userid   EQUALS   the value,
        name     if the name     CONTAINS the value,
        lastname if the lastname CONTAINS the value,
        mailname if the mailname CONTAINS the value,
        nomail   if the mailname EQUALS   '', the value is ignored
        '''
        filter_function_dict = {
          'userid'  : lambda entry, value: entry.userid == value,
          'name'    : lambda entry, value: entry.name.find(value) != -1,
          'lastname': lambda entry, value: entry.lastname().find(value) != -1,
          'mailname': lambda entry, value: entry.mailname.find(value) != -1,
          'nomail'  : lambda entry, value='': entry.mailname == ''
        }
        if filter_function_dict.has_key( fieldname ):
            return filter_function_dict[fieldname]
        else:
            raise( ValueError('unsupported filter field "%s"' % fieldname) )

    def filter_by( self, fieldname, value ):
        '''
        Extract all entries where the fieldname matches the given value.
        A match occurs for fieldname
        userid   if the userid   EQUALS   the value,
        name     if the name     CONTAINS the value,
        lastname if the lastname CONTAINS the value,
        mailname if the mailname CONTAINS the value,
        nomail   if the mailname EQUALS   '', the value is ignored
        
        Usage: data.filter_by( 'name', 'Schwarzer' )
        '''
        self._list = [ entry
          for entry in self._list
          if self._filter_function(fieldname)(entry, value) ]
    
    def update_mailnames( self, filename=None ):
        '''
        Update mailnames in passwd entries by using the userdb file
        Usage: data.update_mailnames( userdb )  or
               data.userdb= userdb; data.update_mailnames()
        '''
        # get the userdb file contents,
        #  raising an implicit exception on IO error
        if filename is None:
            file = open( self.userdb, 'r' )
        else:
            file = open( filename, 'r' )
        contents = file.readlines()
        file.close()
        # generate a dictionary with key userid -> value mailname
        mailname_dict = {}
        for line in contents:
            pattern = re.compile( r'^(\w+):mailname\s+(\S+)' )
            matchobj = pattern.search( line )
            if matchobj:
                userid, mailname = matchobj.groups()
                mailname_dict[userid] = mailname
        # iterate over the UserDataList contents (entries) and update them
        for entry in self._list:
            if mailname_dict.has_key( entry.userid ):
                entry.mailname = mailname_dict[entry.userid]