Index: /trunk/examples/new_rack_pbsmon.py
===================================================================
--- /trunk/examples/new_rack_pbsmon.py	(revision 293)
+++ /trunk/examples/new_rack_pbsmon.py	(revision 294)
@@ -13,7 +13,5 @@
 
 """
-Usage: pbsmon [hosts]....
-
-Specifying hostnames:
+specifying hostnames:
   To specify a range use the [] to indicate a range, a couple of examples:
 
@@ -34,8 +32,6 @@
 import re
 import types
-from optparse import OptionParser
 
 import pbs
-from PBSAdvancedParser import AdvancedParser
 from PBSQuery import PBSQuery
 from PBSQuery import PBSError
@@ -102,4 +98,142 @@
 }
 
+####
+## Rewriting the print function, so it will work with all versions of Python
+def _print(*args, **kwargs):
+    '''A wrapper function to make the functionality for the print function the same for Python2.4 and higher'''
+    ## First try if we are running in Python3 and higher
+    try:
+        Print = eval('print')
+        Print(*args, **kwargs)
+    except SyntaxError:
+        ## Then Python2.6 and Python2.7
+        try:
+            D = dict()
+            exec('from __future__ import print_function\np=print', D)
+            D['p'](*args, **kwargs)
+            del D
+        ## Finally Python2.5 or lower
+        except SyntaxError:
+            del D
+            fout    = kwargs.get('file', sys.stdout)
+            write   = fout.write
+            if args:
+                write(str(args[0]))
+                sep = kwargs.get('sep', ' ')
+                for arg in args[1:]:
+                    write(sep)
+                    write(str(a))
+                write(kwargs.get('end', '\n'))
+
+## Import argparse here, as I need the _print function
+try:
+    import argparse
+except ImportError:
+    _print('Cannot find argparse module', file=sys.stderror)
+    sys.exit(1)
+
+####
+## BEGIN functions for hostrange parsing
+def l_range(start, end):
+    '''The equivalent for the range function, but then with letters, uses the ord function'''
+    start = ord(start)
+    end   = ord(end)
+    rlist = list()
+
+    ## A ord number must be between 96 (a == 97) and 122 (z == 122)
+    if start < 96 or start > 122 and end < 96 or end > 122:
+        raise Exception('You can only use letters a to z')
+    ## If start is greater then end, then the range is invalid
+    elif start > end:
+        raise Exception('The first letter must be smaller then the second one')
+    ## Just revert the ord number to the char
+    for letter in range(start, end + 1):
+        rlist.append(chr(letter))
+    return rlist
+
+def return_range(string):
+    '''This function will return the possible values for the given ranges'''
+
+    ## First check if the first char is valid
+    if string.startswith(',') or string.startswith('-'):
+        raise Exception('Given pattern is invalid, you can\'t use , and - at the beginning')
+
+    numbers_chars        = list()
+    equal_width_length  = 0
+
+    ## First splitup the sections (divided by ,)
+    for section in string.split(','):
+        ## Within a section you can have a range, max two values
+        chars = section.split('-')
+        if len(chars) == 2:
+            ## When range is a digit, simply use the range function
+            if chars[0].isdigit() and chars[1].isdigit():
+                ## Owke, check for equal_width_length
+                if chars[0][0] == '0' or chars[1][0] == '0':
+                    if len(chars[0]) >= len(chars[1]):
+                        equal_width_length = len(chars[0])
+                    else:
+                        equal_width_length = len(chars[1])
+                ## Don't forget the +1
+                numbers_chars += range(int(chars[0]), int(chars[1])+1)
+            ## If one of the two is a digit, raise an exceptio
+            elif chars[0].isdigit() or chars[1].isdigit():
+                raise Exception('I can\'t combine integers with letters, change your range please')
+            ## Else use the l_range
+            else:
+                numbers_chars += l_range(chars[0], chars[1])
+        else:
+            ## If the value of the section is a integer value, check if it has a 0
+            if section.isdigit() and section[0] == '0':
+                if len(section) > equal_width_length:
+                    equal_width_length = len(section)
+            numbers_chars.append(section)
+
+        ## if the equal_width length is greater then 0, rebuild the list
+        ## 01, 02, 03, ... 10
+        if equal_width_length > 0:
+            tmp_list = list()
+            for number_char in numbers_chars:
+                if type(number_char) is types.IntType or number_char.isdigit():
+                    tmp_list.append('%0*d' % ( equal_width_length, int(number_char)))
+                else:
+                    tmp_list.append(number_char)
+            numbers_chars = tmp_list
+
+    return numbers_chars
+
+def product(*args, **kwargs):
+    '''Taken from the python docs, does the same as itertools.product, 
+    but this also works for py2.5'''
+    pools = map(tuple, args) * kwargs.get('repeat', 1)
+    result = [[]]
+    for pool in pools:
+        result = [x+[y] for x in result for y in pool]
+    for prod in result:
+        yield tuple(prod)
+
+def parse_args(args):
+    rlist = list()
+    for arg in args:
+        parts = re.findall(HOSTRANGE, arg)
+        if parts:
+            ## create a formatter string, sub the matched patternn with %s
+            string_format = re.sub(HOSTRANGE, '%s', arg)
+            ranges = list()
+
+            ## detect the ranges in the parts
+            for part in parts:
+                ranges.append(return_range(part))
+            
+            ## produce the hostnames
+            for combination in product(*ranges):
+                rlist.append(string_format % combination)
+        else:
+            rlist.append(arg)
+    return rlist
+
+## END functions for hostrange parsing
+####
+
 def sanitize_jobs( jobs ):
 
@@ -141,5 +275,5 @@
             p = PBSQuery( OPT_SERVERNAME )
     except PBSError, reason:
-        print 'Error: %s' % reason
+        _print('Error: %s' % reason)
         sys.exit( -1 )
 
@@ -151,5 +285,5 @@
         nodes = p.getnodes( attr )
     except PBSError, reason:
-        print 'Error: %s' % reason
+        _print('Error: %s' % reason)
         sys.exit( -1 )
 
@@ -264,6 +398,6 @@
     save_column = None
     
-    print    
-    print '  ',
+    _print()    
+    _print('  ', end=' ')
     for rack in xrange( START_RACK, racknr + 1 ):
         
@@ -279,10 +413,10 @@
                     char = save_column
                     save_column = None
-                print char,
-        else:
-            print char,
-    print    
-
-    print '  ',
+                _print(char, end=' ')
+        else:
+            _print(char, end=' ')
+    _print()    
+
+    _print('  ', end=' ')
     for rack in xrange( START_RACK, racknr + 1 ):
         
@@ -290,11 +424,11 @@
         if OPT_SKIP_EMPTY_RACKS:
             if nodes.has_key( rack ):
-                print char,
-        else:
-            print char,
-    print
+                _print(char, end=' ')
+        else:
+            _print(char, end=' ')
+    _print()
 
     for node in xrange( 1, nodenr + 1 ):
-        print '%2d' % node,
+        _print('%2d' % node, end=' ')
 
         for rack in xrange( START_RACK, racknr + 1 ):
@@ -303,9 +437,9 @@
                     continue
             try:
-                print nodes[ rack ][ node ][ 'state_char' ],
+                _print(nodes[ rack ][ node ][ 'state_char' ], end=' ')
             except KeyError:
-                print ' ',
-        print
-    print
+                _print(' ', end=' ')
+        _print()
+    _print()
 
 def print_table_summary():
@@ -319,5 +453,5 @@
             p = PBSQuery( OPT_SERVERNAME )
     except PBSError, reason:
-        print 'error: %s' % reason
+        _print('error: %s' % reason)
         sys.exit(-1)
 
@@ -327,5 +461,5 @@
         nodes = p.getnodes(attr)
     except PBSError, reason:
-        print 'error: %s' % reason
+        _print('error: %s' % reason)
         sys.exit(-1)
 
@@ -351,5 +485,5 @@
         if node.is_free():                          # can happen for single CPU jobs
             if node.has_job():
-#               print 'TD: %s' % nodename, node
+#               _print('TD: %s' % nodename, node)
                 state_char = PBS_STATES[pbs_ND_single]
                 count_states[pbs.ND_free] -=  1
@@ -363,5 +497,5 @@
                 #   count_states[pbs_ND_free_serial] +=  1 
                 
-#       print 'TD: %s %s' % (nodename, state_char)
+#       print_('TD: %s %s' % (nodename, state_char))
         dummy = nodename.split('-')
         if len( dummy ) > 1:
@@ -375,9 +509,9 @@
     n = 0
     for state in legend:
-        print '  %s  %-13s : %-5d' % (PBS_STATES[state], state, count_states[state]),
+        _print('  %s  %-13s : %-5d' % (PBS_STATES[state], state, count_states[state]), end=' ')
 
         n = n + 1
         if not (n & 1):
-            print
+            _print()
 
 def print_extended( hosts=None ):
@@ -402,52 +536,52 @@
         rows_str.append( row_str )
 
-    print
-    print row_header
-    print EXTENDED_PATTERNS[ 'line' ] % ( EXTENDED_PATTERNS[ 'line_char' ] * LENGTH_ROW )
-    print '\n'.join( rows_str )
-    print
+    _print()
+    _print(row_header)
+    _print(EXTENDED_PATTERNS[ 'line' ] % ( EXTENDED_PATTERNS[ 'line_char' ] * LENGTH_ROW ))
+    _print('\n'.join( rows_str ))
+    _print()
 
 if __name__ == '__main__':
     
-    parser = AdvancedParser(usage=__doc__)
-
-    parser.add_option( "-t", "--table", dest="table", action="store_true", help="Show an table" )
-    parser.add_option( "-l", "--list", dest="extended", action="store_true", help="Show node rows with state and jobinfo" )
-    parser.add_option( "-s", "--summary", dest="summary", action="store_true", help="Display a short summary" )
-    parser.add_option( "-a", "--all", dest="summary", action="store_true", help="Display a short summary" )
-
-    parser.add_option( "-w", "--wide", dest="wide", action="store_true", help="Wide display for node status ( only when -t is used )" )
-    parser.add_option( "-S", "--servername", dest="servername", help="Change the default servername" )
-
-    parser.set_defaults( table=PRINT_TABLE )
-    parser.set_defaults( summary=False )
-    parser.set_defaults( extended=PRINT_EXTENDED )
-    parser.set_defaults( servername=None )
-
-    ( options, args ) = parser.parse_args()
-
-    if options.servername:
-        OPT_SERVERNAME = options.servername
-
-    if options.wide:
+    parser = argparse.ArgumentParser(
+        formatter_class=argparse.RawDescriptionHelpFormatter,
+        description=__doc__,
+    )
+
+    parser.add_argument('nodes', metavar='NODES', nargs='*', type=str)
+    parser.add_argument( "-t", "--table", dest="table", action="store_true", help="Show an table", default=PRINT_TABLE )
+    parser.add_argument( "-l", "--list", dest="extended", action="store_true", help="Show node rows with state and jobinfo", default=PRINT_EXTENDED )
+    parser.add_argument( "-s", "--summary", dest="summary", action="store_true", help="Display a short summary", default=False )
+    parser.add_argument( "-a", "--all", dest="summary", action="store_true", help="Display a short summary" )
+    parser.add_argument( "-w", "--wide", dest="wide", action="store_true", help="Wide display for node status ( only when -t is used )" )
+    parser.add_argument( "-S", "--servername", dest="servername", help="Change the default servername", default=None )
+
+    args = parser.parse_args()
+    if args.nodes:
+        args.nodes = parse_args(args.nodes)
+
+    if args.servername:
+        OPT_SERVERNAME = args.servername
+
+    if args.wide:
         OPT_SKIP_EMPTY_RACKS = False
 
-    if args:
-        options.extended = True
-
-    if options.extended and PRINT_TABLE:
-        options.table = False
-
-    if options.table and PRINT_EXTENDED:
-        options.extended = False
-
-    if options.extended:
-        print_extended( args ) 
-    elif options.table:
+    if args.nodes:
+        args.extended = True
+
+    if args.extended and PRINT_TABLE:
+        args.table = False
+
+    if args.table and PRINT_EXTENDED:
+        args.extended = False
+
+    if args.extended:
+        print_extended( args.nodes ) 
+    elif args.table:
         print_table()
     else:
-        print 'Something is wrong, bye!'
+        _print('Something is wrong, bye!', file=sys.stderr)
         sys.exit( -1 )
 
-    if options.summary:
+    if args.summary:
         print_table_summary()
Index: /trunk/examples/sara_nodes.py
===================================================================
--- /trunk/examples/sara_nodes.py	(revision 293)
+++ /trunk/examples/sara_nodes.py	(revision 294)
@@ -2,554 +2,621 @@
 #
 # Author: Dennis Stam
-# Date  : 14-05-2009
-# Desc. : This program/module allows you to change
-#         the state of a node to offline or down
+# Date  : 6th of September 2012
 #
-# SVN Info:
-#       $Id: sara_nodes 4552 2010-04-29 12:15:42Z dennis $
-#       $URL: https://subtrac.sara.nl/hpcv/svn/beowulf/trunk/torque/utils/sara_nodes $
-#
-
-
-# imports of the pbs_python module
+# Tested with Python 2.5, 2.6, 2.7 (should work for 3.0, 3.1, 3.2)
+#   sara_nodes uses the module argparse
+
+## The documenation, is shown when you type --help
+HELP_DESCRIPTION = '''This program is a great example what you can achieve with the pbs_python wrapper. You can use sara_nodes to change the state of a machine to offline with a reason. Several information is stored in the nodes note attribute. Information such as; date added, date last change, the username, ticket number (handy when you wan't to reference to an issue in your tracking system) and the message.'''
+HELP_EPILOG = '''The format argument uses the Python string formatting. Fields that can be used are; nodename, state, date_add, date_edit, username, ticket and note. For example sara_nodes -f '%(nodename)s;%(state)s' '''
+
+## This variable is used to sort by basename
+SPLIT_SORT = r'r\d+n\d+'
+## This RE pattern is used for the hostrange
+HOSTRANGE = r'\[([0-9az\-,]+)\]'
+## Which states are allowed to show in the print_overview
+ALLOWED_STATES = set(['down', 'offline', 'unknown'])
+
+import pbs
 import PBSQuery
-import PBSAdvancedParser
-import pbs
-
-# python core modules
-from optparse import make_option
+import re
+import sys
+import time
+import getpass
 import types
-import sys
-import re
-import os
-import time
-import string
-
-# GINA also uses python2.3
-if sys.version_info < ( 2,4 ):
-    from set import Set as set
-
-__author__ = 'Dennis Stam'
-
-# Specify here your BATCH name pattern, this is
-# used for sorting when you are using basenames
-RE_PATTERN = '(r\d+n\d+)'
-
-# Cfegine uses : for lists, so a override is needed
-TIME_SEPARATOR = ':'
-
-class sara_nodesException( Exception ):
-
-        def __init__(self, msg='' ):
-                self.msg = msg
-                Exception.__init__( self, msg )
-
-        def __repr__(self):
-                return self.msg
-
-        def islist(self):
-                if type(self.msg) is types.ListType:
-                        return True
-
-                return False
-
-        def getlist(self):
-                return self.msg
-
-class sara_nodesCli:
-        '''
-        This class is the Command Line Interface from here we call the sara_nodes class / module
-        '''
-        option_list = [
-                make_option( '-v', '--verbose', dest='verbose', action='store_true', help='enables verbose mode' ),
-                make_option( '-n', '--dry-run', dest='dryrun', action='store_true', help='enables dry-run mode' ),
-                make_option( '-q', '--quiet', dest='quiet', action='store_true', help='enable this function to supress all feedback'),
-                make_option( '-o', '--offline', dest='offline', help='change state to offline', metavar='NOTE' ),
-                make_option( '-c', '--clear', dest='clear', action='store_true', help='change state to down' ),
-                make_option( '-N', '--clearnote', dest='note', action='store_true', help='clear note of node' ),
-                make_option( '-m', '--modify', dest='modify', help='use this option the modify the note, it will replace the current one!', metavar='NOTE' ),
-                make_option( '-f', '--format', dest='format', nargs=2, help='specify how to display the information, also sets quiet to True' ),
-                make_option( '-t', '--ticket', dest='ticket', help='add/change/remove ticket number, removing use -t c' ),
+
+## Use the cli arguments to change the values
+ARGS_QUIET      = False
+ARGS_VERBOSE    = False
+ARGS_DRYRUN     = False
+
+####
+## Rewriting the print function, so it will work with all versions of Python
+def _print(*args, **kwargs):
+    '''A wrapper function to make the functionality for the print function the same for Python2.4 and higher'''
+
+    ## First try if we are running in Python3 and higher
+    try:
+        Print = eval('print')
+        Print(*args, **kwargs)
+    except SyntaxError:
+        ## Then Python2.6 and Python2.7
+        try:
+            D = dict()
+            exec('from __future__ import print_function\np=print', D)
+            D['p'](*args, **kwargs)
+            del D
+        ## Finally Python2.5 or lower
+        except SyntaxError:
+            del D
+            fout    = kwargs.get('file', sys.stdout)
+            write   = fout.write
+            if args:
+                write(str(args[0]))
+                sep = kwargs.get('sep', ' ')
+                for arg in args[1:]:
+                    write(sep)
+                    write(str(a))
+                write(kwargs.get('end', '\n'))
+
+## Import argparse here, as I need the _print function
+try:
+    import argparse
+except ImportError:
+    _print('Cannot find argparse module', file=sys.stderror)
+    sys.exit(1)
+
+####
+## BEGIN functions for hostrange parsing
+def l_range(start, end):
+    '''The equivalent for the range function, but then with letters, uses the ord function'''
+    start = ord(start)
+    end   = ord(end)
+    rlist = list()
+
+    ## A ord number must be between 96 (a == 97) and 122 (z == 122)
+    if start < 96 or start > 122 and end < 96 or end > 122:
+        raise Exception('You can only use letters a to z')
+    ## If start is greater then end, then the range is invalid
+    elif start > end:
+        raise Exception('The letters must be in alphabetical order')
+    ## Just revert the ord number to the char
+    for letter in range(start, end + 1):
+        rlist.append(chr(letter))
+    return rlist
+
+def return_range(string):
+    '''This function will return the possible values for the given ranges'''
+
+    ## First check if the first char is valid
+    if string.startswith(',') or string.startswith('-'):
+        raise Exception('Given pattern is invalid, you can\'t use , and - at the beginning')
+
+    numbers_chars        = list()
+    equal_width_length  = 0
+
+    ## First splitup the sections (divided by ,)
+    for section in string.split(','):
+        ## Within a section you can have a range, max two values
+        chars = section.split('-')
+        if len(chars) == 2:
+            ## When range is a digit, simply use the range function
+            if chars[0].isdigit() and chars[1].isdigit():
+                ## Owke, check for equal_width_length
+                if chars[0][0] == '0' or chars[1][0] == '0':
+                    if len(chars[0]) >= len(chars[1]):
+                        equal_width_length = len(chars[0])
+                    else:
+                        equal_width_length = len(chars[1])
+                ## Don't forget the +1
+                numbers_chars += range(int(chars[0]), int(chars[1])+1)
+            ## If one of the two is a digit, raise an exception
+            elif chars[0].isdigit() or chars[1].isdigit():
+                raise Exception('I can\'t combine integers with letters, change your range please')
+            ## Else use the l_range
+            else:
+                numbers_chars += l_range(chars[0], chars[1])
+        else:
+            ## If the value of the section is a integer value, check if it has a 0
+            if section.isdigit() and section[0] == '0':
+                if len(section) > equal_width_length:
+                    equal_width_length = len(section)
+            numbers_chars.append(section)
+
+        ## if the equal_width length is greater then 0, rebuild the list
+        ## 01, 02, 03, ... 10
+        if equal_width_length > 0:
+            tmp_list = list()
+            for number_char in numbers_chars:
+                if type(number_char) is types.IntType or number_char.isdigit():
+                    tmp_list.append('%0*d' % ( equal_width_length, int(number_char)))
+                else:
+                    tmp_list.append(number_char)
+            numbers_chars = tmp_list
+
+    return numbers_chars
+
+def product(*args, **kwargs):
+    '''Taken from the python docs, does the same as itertools.product, 
+    but this also works for py2.5'''
+    pools = map(tuple, args) * kwargs.get('repeat', 1)
+    result = [[]]
+    for pool in pools:
+        result = [x+[y] for x in result for y in pool]
+    for prod in result:
+        yield tuple(prod)
+
+def parse_args(args):
+    rlist = list()
+    for arg in args:
+        parts = re.findall(HOSTRANGE, arg)
+        if parts:
+            ## create a formatter string, sub the matched patternn with %s
+            string_format = re.sub(HOSTRANGE, '%s', arg)
+            ranges = list()
+
+            ## detect the ranges in the parts
+            for part in parts:
+                ranges.append(return_range(part))
+            
+            ## produce the hostnames
+            for combination in product(*ranges):
+                rlist.append(string_format % combination)
+        else:
+            rlist.append(arg)
+    return rlist
+
+## END functions for hostrange parsing
+####
+
+####
+## BEGIN functions for printing
+def _generate_index(string):
+    '''Is used to generate a index, this way we can also sort nummeric values in a string'''
+    return [ int(y) if y.isdigit() else y for y in re.split(r'(\d+)', string) ]
+
+def print_get_nodes(hosts=None):
+    '''This function retrieves the information from your batch environment'''
+    if ARGS_VERBOSE:
+        _print('func:print_get_nodes input:%s' % str(hosts), file=sys.stderr)
+
+    ## there are 2 possible filters, by hostname, or by state
+    pbsq         = PBSQuery.PBSQuery()
+    split_1     = dict()
+    split_2     = dict()
+
+    if ARGS_VERBOSE: 
+        _print('func:print_get_nodes fetching node information', file=sys.stderr)
+    ## We ask from the batch all nodes, and with the properties state and note
+    for host, properties in pbsq.getnodes(['state', 'note']).items():
+        do_host = None
+        ## Check if the current host matches our criterium (given with the arguments
+        ## or has the allowed state)
+        if hosts and host in hosts:
+            do_host = host
+        elif not hosts:
+            ## Do a intersection on both set's, if there is a match, then the host is allowed
+            if bool(ALLOWED_STATES.intersection(set(properties.state))):
+                do_host = host
+
+        ## when we have a do_host (means matches our criterium) then sort
+        ## them by basename
+        if do_host:
+            if SPLIT_SORT and re.findall(SPLIT_SORT, do_host):
+                split_1[host] = properties
+            else:
+                split_2[host] = properties
+    
+    if ARGS_VERBOSE: 
+        _print('func:print_get_nodes returning values', file=sys.stderr)
+    return split_1, split_2
+
+def print_process_dict(dictin):
+    '''This function processes the data from the batch system and make it for all hosts the same layout'''
+    if ARGS_VERBOSE: 
+        _print('func:print_process_dict input:%s' % str(dictin), file=sys.stderr)
+
+    line_print = list()
+    if ARGS_VERBOSE: 
+        _print('func:print_process_dict processing data', file=sys.stderr)
+
+    ## Generate a list containing a dictionary, so we can use the stringformatting functionality
+    ## of Python, fieldnames are: nodename, date_edit, date_add, username, ticket, note
+
+    ## Replaced real_sort with sorted, this means from 50 lines of code to 3
+    for host in sorted(dictin.keys(), key=_generate_index):
+        add_dict = dict()
+
+        add_dict['nodename'] = host
+        ## Glue the state list with a ,
+        add_dict['state'] = ", ".join(dictin[host]['state'] if dictin[host].has_key('state') else [])
+
+        ## Check if the given host has a note
+        note = dictin[host]['note'] if dictin[host].has_key('note') else []
+        if note:
+            add_dict['date_add']    = note[0]
+            add_dict['date_edit']   = note[1]
+            add_dict['username']    = note[2]
+            add_dict['ticket']      = note[3]
+            add_dict['note']        = ",".join(note[4:])
+
+            ## Create an extra date field, combined for date_edit and date_add
+            if add_dict['date_add'] and add_dict['date_edit']:
+                add_dict['date'] = '%s, %s' % (add_dict['date_add'], add_dict['date_edit'])
+            elif add_dict['date_add']:
+                add_dict['date'] = add_dict['date_add']
+            else:
+                add_dict['date'] = None
+
+        else:
+            ## If there is no note, just set the variables with a empty string
+            add_dict['date'] = add_dict['date_add'] = add_dict['date_edit'] = add_dict['username'] = add_dict['ticket'] = add_dict['note'] = ''
+
+        line_print.append(add_dict)
+
+    if ARGS_VERBOSE: 
+        _print('func:print_process_dict returning values', file=sys.stderr)
+    return line_print
+
+def print_create_list(values):
+    tmp_list = list()
+    for pair in values:
+        tmp_list.append('%-*s' % tuple(pair))
+    return tmp_list
+
+def print_overview_normal(hosts=None):
+    '''Print the default overview'''
+    if ARGS_VERBOSE: 
+        _print('func:print_overview_normal input:%s' % str(hosts), file=sys.stderr)
+
+    ## Determine some default values for the column width
+    w_nodename = 8
+    w_state = 5 
+    w_date = w_username = w_ticket = w_note = w_date_add = w_date_edit = 0
+
+    ## Get the data, make it one list, the rest first then the matched
+    matched, rest = print_get_nodes(hosts)
+    print_list = print_process_dict(rest)
+    print_list.extend(print_process_dict(matched))
+
+    ## Detect the max width for the columns
+    for line in print_list:
+        if line['nodename'] and len(line['nodename']) > w_nodename:
+            w_nodename = len(line['nodename'])
+        if line['state'] and len(line['state']) > w_state:
+            w_state = len(line['state'])
+        if line['date'] and len(line['date']) > w_date:
+            w_date = len(line['date'])
+        if line['date_add'] and len(line['date_add']) > w_date_add:
+            w_date_add = len(line['date_add'])
+        if line['date_edit'] and len(line['date_edit']) > w_date_edit:
+            w_date_edit = len(line['date_edit'])
+        if line['username'] and len(line['username']) > w_username:
+            w_username = len(line['username'])
+        if line['ticket'] and len(line['ticket']) > w_ticket:
+            w_ticket = len(line['ticket'])
+        if line['note'] and len(line['note']) > w_note:
+            w_note = len(line['note'])
+
+    ## The length of the full note
+    w_notefull  = w_date + w_username + w_ticket + w_note
+
+    if not ARGS_QUIET:
+        show_fields = [
+            [w_nodename, 'Nodename'],
+            [w_state, 'State'],
         ]
-
-        def __init__(self):
-                '''
-  sara_nodes [ <options> <nodenames> | [nodenames] ]
-
-  -f/--format needs 2 arguments, first the pattern secondly the variables
-        the pattern you specify must be the string format pattern of Python
-        fields: node, state, date_add, date_edit, user, ticket, remark'''
-
-                self.obj_sara_nodes = sara_nodes()
-                self.parser = PBSAdvancedParser.AdvancedParser( 
-                option_list=self.option_list, 
-                version=pbs.version,
-                usage=self.__init__.__doc__
-                )
-
-                self.parser.set_default('verbose', False)
-                self.parser.set_default('dryrun', False)
-                self.parser.set_default('offline', False)
-                self.parser.set_default('clear', False)
-                self.parser.set_default('note', False)
-                self.parser.set_default('quiet', False)
-                self.parser.set_default('ticket', None)
-                self.parser.set_default('modify', False)
-                self.parser.set_default('format', 'default' )
-
-                options, args = self.parser.parse_args()
-
-                if options.format == 'default':
-                    options.format = (' %-10s | %-19s | %-11s, %-11s %-6s %-5s: %s','node,state,date_add,date_edit,user,ticket,remark' )
-                else:
-                    options.quiet = True
-
-                if not options.quiet:
-                        self.obj_sara_nodes.dryrun = options.dryrun
-
-                        if options.dryrun or options.verbose:
-                                self.obj_sara_nodes.verbose = True
-
-                if str( options.offline ).rstrip() == '' or str( options.modify ).rstrip() == '':
-                        sys.stderr.write( 'sara_nodes: error: option requires an argument\n' )
-
-                        sys.exit( 1 )
-                
-                try:
-                        if options.offline and not options.clear:
-                                if args:
-                                        self.obj_sara_nodes.pbs_change_state_offline( args, options.offline, options.ticket )
-                                else:
-                                        raise sara_nodesException, 'No hostnames given'
-                        elif options.clear and not options.offline:
-                                if args:
-                                        self.obj_sara_nodes.pbs_change_state_down( args )
-                                else:
-                                        raise sara_nodesException, 'No hostnames given'
-                        elif options.note:
-                                if args:
-                                        self.obj_sara_nodes.pbs_change_note_clear( args )
-                                else:
-                                        raise sara_nodesException, 'No hostnames given'
-                        elif options.modify:
-                                if args:
-                                        self.obj_sara_nodes.pbs_change_note( args, options.modify, options.ticket )
-                                else:
-                                        raise sara_nodesException, 'No hostnames given'
-                        elif options.ticket:
-                                if args:
-                                        self.obj_sara_nodes.pbs_change_note_ticket( args, options.ticket )
-                                else:
-                                        raise sara_nodesException, 'No hostnames given'
-                        else:
-                                if not options.quiet:
-                                        print '\n Use option --help for help'
-                                self.print_list(args, options)
-
-
-                except sara_nodesException, msg:
-                        if msg.islist():
-                        	for item in msg.getlist():
-                                	sys.stderr.write( 'sara_nodes: error: %s\n' % item )
-                        else:
-                        	sys.stderr.write( 'sara_nodes: error: %s\n' % str( msg ) )
-
-                        sys.exit( 1 )
-
-        def return_note( self, pre_parts ):
-
-                if type(pre_parts) is types.ListType and len( pre_parts) >= 5:
-                        return { 
-                                'date_add': pre_parts[0].strip(), 
-                                'date_edit': pre_parts[1].strip(), 
-                                'user': pre_parts[2].strip(), 
-                                'ticket': pre_parts[3].strip(),
-                                'remark': ','.join( pre_parts[4:] )
-                        }
-                else:
-                        return { 
-                                'date_add': '', 
-                                'date_edit': '', 
-                                'user': '', 
-                                'ticket': '',
-                                'remark': str( pre_parts ) 
-                        }
-
-        def convert_format( self, format_options ):
-            pattern = r'\%([-|+]){0,1}([0-9]{0,2})([a-z]{1})'
-            parts = re.findall( pattern, format_options[0], re.VERBOSE )
-            line = re.sub( pattern, '%s', format_options[0], re.VERBOSE )
-
-            rlist = list()
-            counter = 0
-            for variable in format_options[1].split( ',' ):
-                rlist.append( '%s(%s)%s%s%s' % (
-                    '%',
-                    variable, 
-                    parts[ counter ][0], 
-                    parts[ counter ][1],
-                    parts[ counter ][2],
-                ) )
-                counter += 1
-            return line % tuple( rlist )
-
-        def print_table( self, node_list, pbs_nodes, format_options ):
-            '''
-            This method prints the rows of a table
-            '''
-
-            try:
-                line_format = self.convert_format( format_options )
-                for node in node_list:
-                    note = '' 
-                    if pbs_nodes[ node ].has_key('note'):
-                        note = pbs_nodes[ node ]['note']
-                                
-                    if self.allowed_state( pbs_nodes[ node ]['state'] ) or note:
-                        node_note = self.return_note( note )
-
-                        fields = { 
-                            'node': node,
-                            'state': ', '.join( pbs_nodes[ node ]['state'] ),
-                            'date_add': node_note['date_add'],
-                            'date_edit': node_note['date_edit'],
-                            'user': node_note['user'],
-                            'ticket': node_note['ticket'],
-                            'remark': node_note['remark'],
-                        }
-
-                        
-                        print line_format % fields
-
-            except KeyError, e:
-                raise sara_nodesException, 'Given host does not exist'
-
-        def print_list(self, args, options):
-                '''
-                A method that is used for collecting all nodes with the state down, offline or unknown
-                '''
-
-                
-                p = PBSQuery.PBSQuery()
-                if pbs.version_info >= ( 4,0,0 ):
-                        if self.obj_sara_nodes.verbose:
-                                print "Enabling new_data_structure for PBSQuery"
-                        p.new_data_structure()
-
-                try:
-                    nodes = p.getnodes( ['state', 'note'] )
-                except PBSQuery.PBSError, detail:
-                    print "PBSQuery error: %s" %detail
-                    sys.exit(1)
-
-                header = ' %-10s | %-19s | %s' % ( 'Nodename', 'State', 'Note' )
-                if not options.quiet:
-                	print '\n%s\n%s' % ( header, ( '-' * 80 ) )
-
-                if args:
-                        args = self.sort_nodes( args )
-                        self.print_table( args[0], nodes, options.format )
-                else:
-                        sorted_nodes, sorted_other = self.sort_nodes( nodes )
-
-                        self.print_table( sorted_other, nodes, options.format )
-                        self.print_table( sorted_nodes, nodes, options.format )
-
-        def real_sort( self, inlist ):
-                '''
-                Use this method instead of the x.sort(), because with x.sort()
-                numeric values in a string are not correctly sorted!
-                '''
-                indices = map(self._generate_index, inlist )
-                decorated = zip( indices, inlist )
-                decorated.sort()
-
-                return [ item for index, item in decorated ]
-
-        def _generate_index( self, str ):
-                '''
-                Spliting a string in aplha and numeric elements
-                '''
-
-                index = []
-
-                def _append( fragment, alist=index ):
-                        if fragment.isdigit():
-                                fragment = int( fragment )
-                        alist.append( fragment )
-
-                prev_isdigit = str[0].isdigit()
-                current_fragment = ''
-
-                for char in str:
-                        curr_isdigit = char.isdigit()
-
-                        if curr_isdigit == prev_isdigit:
-                                current_fragment += char
-                        else:
-                                _append( current_fragment )
-                                current_fragment = char
-                                prev_isdigit = curr_isdigit
-
-                _append( current_fragment )
-
-                return tuple( index )
-
-        def sort_nodes(self, nodes):
-                '''
-                Sorts the nodes list and returns two lists
-                the first the nodes secondly the other machines
-
-                When RE_PATTERN is not supplied then all names
-                will be sorted the same way.
-                '''
-
-                if not globals().has_key('RE_PATTERN'):
-                        global RE_PATTERN 
-                        RE_PATTERN = ''
-
-                pattern = re.compile( RE_PATTERN, re.VERBOSE )
-
-                tmplist = list()
-                tmplist_other = list()
-
-                for node in nodes:
-                        match = pattern.findall( node )
-
-                        if match and len( match ) == 1:
-                                tmplist.append( node )
-                        else:
-                                tmplist_other.append( node )
-
-                tmplist = self.real_sort( tmplist )
-                tmplist_other.sort()
-
-                return tmplist, tmplist_other
-
-        def allowed_state(self, state):
-                '''
-                This method checks is a node complies with the following states: 
-                down, offline and or unknown
-                '''
-                allowed_list = set( ['down', 'offline', 'unknown'] )
-
-                return bool( allowed_list.intersection( set( state ) ) )
-
-class sara_nodes:
-
-        def __init__(self):
-                '''
-                Just initialize two optional variables
-                '''
-                self.dryrun = False
-                self.verbose = False
-
-        def note_check_ticket( self, ticketno, oldticket ):
-                
-                if ticketno:
-                        try:
-                            return '#%d' % int( ticketno )
-                        except ValueError:
-                                if ticketno == 'c':
-                                        return ''
-
-                return oldticket
-
-        def note_return_username( self, old_username ):
-                try:
-                        username = os.getlogin()
-
-        	        if username != 'root':
-                	        return username
-                        else:
-        	                return old_username
-                except OSError, err:
-                        return 'root'
-
-        def note_create( self, new_note, mode = 'a', old_note = None ):
-                if mode == 'w':
-                        return new_note
-                else:
-                        if old_note and old_note.find( new_note ) < 0:
-                                return '%s, %s' % ( old_note, new_note )
-                        else:
-                                return new_note
-
-        def create_date( self ):
-                if not globals().has_key('TIME_SEPARATOR'):
-                        global TIME_SEPARATOR
-                        TIME_SEPARATOR = ':'
-
-                curtime = time.localtime()
-                day = time.strftime( '%d-%m', curtime )
-                hour = time.strftime( '%H', curtime )
-                minutes = time.strftime( '%M', curtime )
-                return '%s %s%s%s' % ( day, hour, TIME_SEPARATOR, minutes )
-
-        def note_init( self ):
-                current_date = self.create_date()
-                try:
-                        current_username = os.getlogin()
-                except OSError, err:
-                        current_username = 'root'
-
-                return [ current_date, current_date, current_username, '' ]
-
-        def note( self, node, note_attr ):
-                '''
-                This method combines all note methods and returns the new note
-                '''
-                p = PBSQuery.PBSQuery()
-                p.new_data_structure()
-                pbs_info = p.getnode( node )
-                
-                pre_parts = list()
-                old_note = None
-                new_note = None
-
-                if pbs_info.has_key( 'note' ):
-                        pbs_note = pbs_info[ 'note' ]
-                        if len( pbs_note ) > 4:
-                                pre_parts = pbs_note[:4]
-                                old_note = ', '.join( pbs_note[4:] )
-
-                                pre_parts[1] = self.create_date()
-                                pre_parts[2] = self.note_return_username( pre_parts[2] )
-
-                else:
-                        pre_parts = self.note_init()
-
-                if note_attr.has_key( 'ticket' ):
-                	pre_parts[3] = self.note_check_ticket( note_attr['ticket'], pre_parts[3] )
-
-                if note_attr.has_key( 'note' ) and note_attr.has_key( 'mode' ):
-                        if note_attr[ 'note' ] and note_attr[ 'mode' ] in [ 'a','w' ]:
-                                if old_note:
-                                        new_note = self.note_create( note_attr[ 'note' ], note_attr[ 'mode' ], old_note )
-                                else:
-                                        new_note = self.note_create( note_attr[ 'note' ], note_attr[ 'mode' ] )
-                        else:
-                                new_note = old_note
-
-                return '%s,%s' % ( ','.join( pre_parts ), new_note )
-
-        def verbose_print( self, msg ):
-                if self.verbose:
-                        print msg
-
-        def pbs_change_note_clear( self, nodes ):
-                attributes = pbs.new_attropl(1)
-                attributes[0].name = pbs.ATTR_NODE_note
-                attributes[0].value = ''
-                attributes[0].op = pbs.SET
-
-                self.verbose_print( '%*s: cleared' % ( 7, 'Note') )
-                self.pbs_batch( nodes, attributes )
-
-        def pbs_change_note_ticket( self, nodes, ticket ):
-                note_attributes = { 'note': None, 'ticket': ticket, 'mode': 'a' }
-                self.verbose_print( '%*s: %s' % ( 7, 'Ticket', ticket  ) )
-                self.pbs_batch( nodes, None, note_attributes)
-
-        def pbs_change_note( self, nodes, note, ticket=None ):
-                note_attributes = { 'note': note, 'ticket': ticket, 'mode': 'w' }
-
-                self.verbose_print( '%*s: %s' % ( 7, 'Note', note ) )
-                if ticket:
-                        self.verbose_print( '%*s: %s' % ( 7, 'Ticket', ticket ) )
-                self.pbs_batch( nodes, None, note_attributes)
-
-        def pbs_change_state_offline( self, nodes, note, ticket=None ):
-                attributes = pbs.new_attropl(1)
-                attributes[0].name = pbs.ATTR_NODE_state
-                attributes[0].value = 'offline'
-                attributes[0].op = pbs.SET
-
-                note_attributes = { 'note': note, 'ticket': ticket, 'mode': 'a' }
-
-                self.verbose_print( '%*s: offline' % ( 7, 'State') )
-                self.verbose_print( '%*s: %s' % ( 7, 'Note', note ) )
-                if ticket:
-                        self.verbose_print( '%*s: %s' % ( 7, 'Ticket', ticket ) )
-                self.pbs_batch( nodes, attributes, note_attributes )
-
-        def pbs_change_state_down( self, nodes ):
-                attributes = pbs.new_attropl(2)
-                attributes[0].name = pbs.ATTR_NODE_state
-                attributes[0].value = 'down'
-                attributes[0].op = pbs.SET
-
-                attributes[1].name = 'note'
-                attributes[1].value = ''
-
-                self.verbose_print( '%*s: down' % ( 7, 'State') )
-                self.verbose_print( '%*s: cleared' % ( 7, 'Note' ) )
-                self.pbs_batch( nodes, attributes )
-
-        def pbs_batch( self, nodes, attrs=None, note_attributes=None ):
-                nodeserror = list()
-                if not attrs and not note_attributes:
-                        raise sara_nodesException, 'attrs and note_attributes can not be empty together!'
-
-                if not self.dryrun:
-                        if note_attributes and len( note_attributes ) == 3:
-                                if attrs:
-                                        attributes = attrs + pbs.new_attropl(1)
-                                        attributes[1].name = pbs.ATTR_NODE_note
-                                        attributes[1].op = pbs.SET
-                                else:
-                                        attributes = pbs.new_attropl(1)
-                                        attributes[0].name = pbs.ATTR_NODE_note
-                                        attributes[0].op = pbs.SET
-                        else:
-                                attributes = attrs
-                        # Some hacking here because some limitation in the Torque 2.4 version
-                        # fetching note data first for all nodes!
-                        tmp_node_note = dict()
-
-                        for node in nodes:
-                                if note_attributes and len( note_attributes ) == 3:
-                                        tmp_node_note[ node ] = self.note( node, note_attributes )
-
-                        pbs_server = pbs.pbs_default()
-
-                        if not pbs_server:
-                                raise sara_nodesException, 'Default pbs server not found!'
-
-                        pbs_connection = pbs.pbs_connect( pbs_server )
-                        for node in nodes:
-                                if note_attributes and len( note_attributes ) == 3:
-                                        try:
-                                                if attrs:
-                                                        attributes[1].value = tmp_node_note[ node ]
-                                                else:
-                                                        attributes[0].value = tmp_node_note[ node ]
-                                        except KeyError:
-                                                pass
-                                rcode = pbs.pbs_manager( pbs_connection, pbs.MGR_CMD_SET, pbs.MGR_OBJ_NODE, node, attributes, 'NULL' )
-                                if rcode > 0:
-                                        errno, text = pbs.error()
-                                        nodeserror.append( '%s: %s (%s)' % ( node, text, errno ) )
-                else:
-                        p = PBSQuery.PBSQuery()
-                        pbsnodes = p.getnodes().keys()
-
-                        print '%*s:' % ( 7, 'Nodes' ),
-                        firstitem = True
-
-                        for node in nodes:
-
-                                if node in pbsnodes:
-                                        if firstitem:
-                                                print '%s' % node
-                                                firstitem = False
-                                        else:
-                                                print '%*s' % ( 17, node )
-                                else:
-                                        nodeserror.append( '%s: does not exist' % node )
-
-                if len( nodeserror ) > 0:
-                        raise sara_nodesException, nodeserror
+        if w_date > 0:
+            show_fields.append([w_date_add,'Added'])
+            show_fields.append([w_date_edit,'Modified'])
+            show_fields.append([w_username,'User'])
+            if w_ticket > 0:
+                if w_ticket < 6:
+                    w_ticket = 6
+                show_fields.append([w_ticket,'Ticket'])
+            show_fields.append([w_note,'Note'])
+
+        _print(' %s' % ' | '.join(print_create_list(show_fields)))
+        _print('+'.join([ '-' * (show_field[0]+2) for show_field in show_fields ]))
+
+    ## Show the information to the user
+    for line in print_list:
+        show_line_fields = [
+            [w_nodename, line['nodename']],
+            [w_state, line['state']],
+        ]
+        if w_date > 0:
+            show_line_fields.append([w_date_add,line['date_add']])
+            show_line_fields.append([w_date_edit,line['date_edit']])
+            show_line_fields.append([w_username,line['username']])
+            if w_ticket > 0:
+                show_line_fields.append([w_ticket,line['ticket']])
+            show_line_fields.append([w_note,line['note']])
+
+        _print(' %s' % ' | '.join(print_create_list(show_line_fields)))
+
+def print_overview_format(hosts=None, format=None):
+    '''Print the information in a certain format, when you want to use it in a
+    different program'''
+
+    matched, rest = print_get_nodes(hosts)
+    print_list = print_process_dict(rest)
+    print_list.extend(print_process_dict(matched))
+
+    for line in print_list:
+        _print(format % line)
+## END functions for printing
+####
+
+class SaraNodes(object):
+    '''This class is used to communicate with the batch server'''
+
+    ticket      = None
+
+    def _get_current_notes(self, nodes):
+        '''A function to retrieve the current message'''
+        if ARGS_VERBOSE: 
+            _print('class:SaraNodes func:_get_current_notes input:%s' % str(nodes), file=sys.stderr)
+
+        pbsq = PBSQuery.PBSQuery()
+        rdict = dict()
+
+        ## We are only intereseted in the note
+        for node, properties in pbsq.getnodes(['note']).items():
+            if node in nodes and properties.has_key('note'):
+                rdict[node] = properties['note']
+        return rdict
+
+    def _get_curdate(self):
+        '''Returns the current time'''
+        if ARGS_VERBOSE: 
+            _print('class:SaraNodes func:_get_curdate', file=sys.stderr)
+        return time.strftime('%d-%m %H:%M', time.localtime())
+
+    def _get_uid(self, prev_uid=None):
+        '''Get the username'''
+        if ARGS_VERBOSE: 
+            _print('class:SaraNodes func:_get_uid input:%s' % prev_uid, file=sys.stderr)
+        cur_uid = getpass.getuser()
+        if prev_uid and cur_uid == 'root':
+            return prev_uid
+        return cur_uid
+
+    def _get_ticket(self, prev_ticket=None):        
+        '''Check if we already have a ticket number'''
+        if ARGS_VERBOSE: 
+            _print('class:SaraNodes func:_get_ticket input:%s' % prev_ticket, file=sys.stderr)
+        cur_ticket = '#%s' % self.ticket
+        if prev_ticket and cur_ticket == prev_ticket:
+            return prev_ticket
+        elif self.ticket and self.ticket.isdigit():
+            return cur_ticket
+        elif self.ticket in ['c','clear','N',]:
+            return ''
+        elif prev_ticket:
+            return prev_ticket
+        return ''
+
+    def _generate_note(self, nodes=None, note=None, append=True):
+        '''Generates the node in a specific format'''
+        if ARGS_VERBOSE: 
+            _print('class:SaraNodes func:_generate_note input:%s,%s,%s' % (str(nodes), note, str(append)), file=sys.stderr)
+
+        ## First step, is to get the current info of a host
+        cur_data = self._get_current_notes(nodes)
+        rdict = dict()
+
+        for node in nodes:
+            date_add = date_edit = username = ticket = nnote = None
+            if node in cur_data.keys():
+                date_add    = cur_data[node][0]
+                date_edit   = self._get_curdate()
+                username    = self._get_uid(cur_data[node][2])
+                ticket      = self._get_ticket(cur_data[node][3])
+                nnote       = ",".join(cur_data[node][4:])
+            else:
+                date_add = date_edit = self._get_curdate()
+                username = self._get_uid()
+                ticket   = self._get_ticket()
+                nnote    = None
+
+            if nnote and append and note:
+                nnote = '%s, %s' % (nnote, note)
+            elif note:
+                nnote = note
+
+            rdict[node] = '%s,%s,%s,%s,%s' % (date_add, date_edit, username, ticket, nnote)
+        return rdict
+
+    def do_offline(self, nodes, note):
+        '''Change the state of node(s) to offline with a specific note'''
+
+        if ARGS_VERBOSE: 
+            _print('class:SaraNodes func:do_offline input:%s,%s' % (str(nodes), note), file=sys.stderr)
+        attributes          = pbs.new_attropl(2)
+        attributes[0].name  = pbs.ATTR_NODE_state
+        attributes[0].value = 'offline'
+        attributes[0].op    = pbs.SET
+        attributes[1].name  = pbs.ATTR_NODE_note
+        attributes[1].op    = pbs.SET
+
+        batch_list = list()
+
+        ## again a loop, now create the attrib dict list
+        for node, note in self._generate_note(nodes, note).items():
+            attributes[1].value = note
+            batch_list.append(tuple([node, attributes]))
+
+        self._process(batch_list)
+
+    def do_clear(self, nodes):
+        '''Clear the state on a node(s) to down, also clear the note'''
+
+        if ARGS_VERBOSE: 
+            _print('class:SaraNodes func:do_clear input:%s' % str(nodes), file=sys.stderr)
+        attributes          = pbs.new_attropl(2)
+        attributes[0].name  = pbs.ATTR_NODE_state
+        attributes[0].value = 'down'
+        attributes[0].op    = pbs.SET
+        attributes[1].name  = pbs.ATTR_NODE_note
+        attributes[1].op    = pbs.SET
+        attributes[1].value = ''
+
+        batch_list = list()
+
+        ## again a loop, now create the attrib dict list
+        for node in nodes:
+            batch_list.append(tuple([node, attributes]))
+
+        self._process(batch_list)
+  
+    def do_modify(self, nodes, note):
+        '''Modify the note on a node, override the previous note'''
+
+        if ARGS_VERBOSE: 
+            _print('class:SaraNodes func:do_modify input:%s,%s' % (str(nodes), note), file=sys.stderr)
+        attributes          = pbs.new_attropl(1)
+        attributes[0].name  = pbs.ATTR_NODE_note
+        attributes[0].op    = pbs.SET
+
+        batch_list = list()
+
+        ## again a loop, now create the attrib dict list
+        for node, note in self._generate_note(nodes, note, append=False).items():
+            attributes[0].value = note
+            batch_list.append(tuple([node, attributes]))
+
+        self._process(batch_list)
+
+    def do_clear_note(self, nodes):
+        '''Clear the  note on the node(s)'''
+
+        if ARGS_VERBOSE: 
+            _print('class:SaraNodes func:do_clear_note input:%s' % str(nodes), file=sys.stderr)
+        attributes          = pbs.new_attropl(1)
+        attributes[0].name  = pbs.ATTR_NODE_note
+        attributes[0].op    = pbs.SET
+        attributes[0].value = ''
+
+        batch_list = list()
+
+        ## again a loop, now create the attrib dict list
+        for node in nodes:
+            batch_list.append(tuple([node, attributes]))
+
+        self._process(batch_list)
+
+    def _process(self, batch_list):
+        '''This function execute the change to the batch server'''
+
+        if ARGS_VERBOSE: 
+            _print('class:SaraNodes func:_process input:%s' % str(batch_list), file=sys.stderr)
+
+        ## Always get the pbs_server name, even in dry-run mode
+        pbs_server = pbs.pbs_default()
+        if not pbs_server:
+            _print('Could not locate a pbs server', file=sys.stderr)
+            sys.exit(1)
+
+        if ARGS_VERBOSE:
+            _print('class:SaraNodes func:_process pbs_server:%s' % pbs_server, file=sys.stderr)
+
+        ## If dry-run is not specified create a connection
+        if not ARGS_DRYRUN:
+            pbs_connection = pbs.pbs_connect(pbs_server)
+
+        ## Execute the changes
+        for node in batch_list:
+            if not ARGS_DRYRUN:
+                pbs_connection = pbs.pbs_connect(pbs_server)
+                rcode = pbs.pbs_manager(pbs_connection, pbs.MGR_CMD_SET, pbs.MGR_OBJ_NODE, node[0], node[1], 'NULL')
+                if rcode > 0:
+                    errno, text = pbs.error()
+                    _print('PBS error for node \'%s\': %s (%s)' % (node[0], text, errno), file=sys.stderr)
+            else:
+                _print("pbs.pbs_manager(pbs_connection, pbs.MGR_CMD_SET, pbs.MGR_OBJ_NODE, %s, %s, 'NULL')" % (node[0], str(node[1])))
+
+        ## Close the connection with the batch system
+        if not ARGS_DRYRUN:
+            pbs.pbs_disconnect(pbs_connection)
 
 if __name__ == '__main__':
-        sara_nodesCli()
+    ## The arguments of sara_nodes
+    parser = argparse.ArgumentParser(
+        description=HELP_DESCRIPTION,
+        epilog=HELP_EPILOG,
+    )
+    parser.add_argument('nodes', metavar='NODES', nargs='*', type=str)
+    parser.add_argument('-v','--verbose', action='store_true', help='enables verbose mode')
+    parser.add_argument('-n','--dry-run', action='store_true', help='enables dry-run mode')
+    parser.add_argument('-q','--quiet', action='store_true', help='enables to supress all feedback')
+    parser.add_argument('-o','--offline', metavar='NOTE', help='change state to offline with message')
+    parser.add_argument('-m','--modify', metavar='NOTE', help='change the message of a node')
+    parser.add_argument('-c','--clear', action='store_true', help='change the state to down')
+    parser.add_argument('-N','--clear-note', action='store_true', help='clear the message of a node')
+    parser.add_argument('-f','--format', metavar='FORMAT', help='change the output of sara_nodes (see footer of --help)')
+    parser.add_argument('-t','--ticket', metavar='TICKET', help='add a ticket number to a node')
+    parser.add_argument('--version', action='version', version=pbs.version)
+
+    ## Parse the arguments
+    args = parser.parse_args()
+
+    ## The options quiet, verbose and dry-run are processed first
+    if args.quiet: 
+        ARGS_QUIET = True
+    if args.verbose: 
+        ARGS_VERBOSE = True
+    if args.dry_run: 
+        ARGS_DRYRUN = ARGS_VERBOSE = True
+
+    if ARGS_VERBOSE: 
+        _print('func:__main__ checking type of operation', file=sys.stderr)
+
+    if args.nodes:
+        args.nodes = parse_args(args.nodes)
+
+    ## If offline, modify, clear, clear_note or ticket then initiate the SaraNodes class
+    if args.offline or args.modify or args.clear or args.clear_note or args.ticket:
+        if not args.nodes:
+            _print('You did not specify any nodes, see --help', file=sys.stderr)
+            sys.exit(1)
+
+        sn = SaraNodes()
+        if args.ticket: 
+            sn.ticket = args.ticket 
+
+        if args.offline:
+            if ARGS_VERBOSE: 
+                _print('func:__main__ call sn.do_offline', file=sys.stderr)
+            sn.do_offline(args.nodes, args.offline)
+        elif args.modify:
+            if ARGS_VERBOSE: 
+                _print('func:__main__ call sn.do_modify', file=sys.stderr)
+            sn.do_modify(args.nodes, args.modify)
+        elif args.clear:
+            if ARGS_VERBOSE: 
+                _print('func:__main__ call sn.do_clear', file=sys.stderr)
+            sn.do_clear(args.nodes)
+        elif args.clear_note:
+            if ARGS_VERBOSE: 
+                _print('func:__main__ call sn.do_clear_note', file=sys.stderr)
+            sn.do_clear_note(args.nodes)
+        elif args.ticket:
+            if ARGS_VERBOSE: 
+                _print('func:__main__ call sn.do_modify')
+            sn.do_offline(args.nodes, '')
+    else:
+        if ARGS_DRYRUN:
+            _print('Dry-run is not available when we use PBSQuery', file=sys.stderr)
+
+        if args.format:
+            if ARGS_VERBOSE: 
+                _print('func:__main__ call print_overview_format', file=sys.stderr)
+            print_overview_format(args.nodes, args.format)
+        else:
+            if ARGS_VERBOSE: 
+                _print('func:__main__ call print_overview_normal', file=sys.stderr)
+            print_overview_normal(args.nodes)
+   
+    if ARGS_VERBOSE: 
+        _print('func:__main__ exit', file=sys.stderr)
Index: unk/src/PBSAdvancedParser.py
===================================================================
--- /trunk/src/PBSAdvancedParser.py	(revision 293)
+++ 	(revision )
@@ -1,175 +1,0 @@
-#
-# Author: Dennis Stam
-# Date  : 09-04-2009
-# Desc. : This class allows you use ranges within the given arguments. Very
-#         useful for specifying mutliple hosts. This class extends the
-#         OptionParser class.
-#
-# SVN Info:
-#       $Id: AdvancedParser.py 5338 2011-01-19 13:09:09Z dennis $
-#       $URL: https://subtrac.sara.nl/osd/beowulf/svn/trunk/python_modules/advancedparser/AdvancedParser.py $
-#
-from optparse import OptionParser
-import re
-import types
-
-__author__  = "Dennis Stam"
-__version__ = ( 1, 1, 2 )
-
-class AdvancedParser(OptionParser):
-        """
-        This class extends from OptionParser, where the method check_values has been
-        overrided.
-
-        In this function a extra parsing is done on the the rest of the arguments. This
-        extra parsing is the creating of multiple hostnames from a pattern/range.
-
-        When a user specifies this argument dr-r[15,17-20]n[1-5,10] then this class
-        returns 24 hosts. Besides using numbers you can also specify lower cased
-        letters from a-z.
-
-        Doctest:
-        >>> parser = AdvancedParser()
-        >>> parser.return_range('12-15,20')
-        [12, 13, 14, 15, '20']
-
-        >>> option, args = parser.parse_args(['dr-r7n[1-5]'])
-        >>> print args
-        ['dr-r7n1', 'dr-r7n2', 'dr-r7n3', 'dr-r7n4', 'dr-r7n5']
-        """
-
-        def parse_letters( self, chars ):
-            
-            if type( chars ) is not types.ListType and len( chars ) != 2:
-                self.error( 'Could not parse range' )
-
-            start   = ord( chars[ 0 ] )
-            end     = ord( chars[ 1 ] )
-            out     = list()
-
-            if start > 96 and start < 122 and end > 97 and end < 123:
-                lrange = range( start, end + 1 )
-
-                for ichar in lrange:
-                    out.append( chr( ichar ) )
-                
-                return out
-            else:
-                self.error( 'AdvanedParser module only handles letters from a to z ( lower cased )' )
-
-        def return_range(self, string):
-                """
-                This method uses the given numbers and converts them to ranges. When
-                ower cased letters are specified they will be converted to integer 
-                ordinal of a one-character string.
-                (ie. a = 97, z = 122)
-
-                The ranges will be return as lists
-                """
-                numbers_chars = list()
-                equal_width_length = 0
-
-                if string.find( ',' ) >= 0 and string.find( ',' ) < 1:
-                    self.error( 'You cannot start a range with \',\'. Given range: %s' % string )
-
-                if string.find( '-' ) >= 0 and string.find( '-' ) < 1:
-                    self.error( 'You cannot start a range with \'-\'. Given range: %s' % string )
-
-                for section in string.split( ',' ):
-                    if section.find( '-' ) > 0:
-                        if len( section.split( '-' ) ) != 2:
-                            self.error( 'A range must be consisted of two values' )
-                        
-                        chars = section.split( '-' )
-
-                        try:
-                            if chars[ 0 ][ 0 ] == '0' or chars[ 1 ][ 0 ]:
-                                if len( chars[ 0 ] ) >= len( chars[ 1 ] ):
-                                    equal_width_length = len( chars[ 0 ] )
-                                else:
-                                    equal_width_lenght = len( chars[ 1 ] )
-
-                            numbers_chars += range( int( chars[ 0 ] ), int( chars[ 1 ] ) + 1 )
-                        except ValueError:
-                            numbers_chars += self.parse_letters( chars )
-
-                    else:
-                        if equal_width_length != 0 and len( section ) > equal_width_lenght:
-                            equal_width_lenght = len( section )
-                        numbers_chars.append( section )
-
-                    tmp_list = list()
-                    if equal_width_length > 0:
-                        for number_char in numbers_chars:
-                            try:
-                                nnum = int( number_char )
-                                tmp_list.append( '%0*d' % ( equal_width_length, nnum ) )
-                            except ValueError:
-                                tmp_list.append( number_char )
-                        numbers_chars = tmp_list
-
-                return numbers_chars
-
-        def combine( self, pre, post):
-                '''
-                This method is used to combine a possibility of a combination
-                '''
-                if pre == '':
-                        return post
-                else:
-                        return '%s %s' % (pre, post)
-
-        def combinations( self, listin, prefix=''):
-                '''
-                This method creates from the given ranges all possible combinations
-                '''
-                outlist = list()
-
-                if len( listin ) > 1:
-                        for item in listin[0]:
-                                outlist += self.combinations( listin[1:], self.combine( prefix, str( item ) ) )
-                else:
-                        for item in listin[0]:
-                                outlist.append( tuple( self.combine( prefix, str( item ) ).split( ' ') ) )
-
-                return outlist
-
-        def args_parser(self, args):
-                '''
-                This method checks all given extra arguments for the given ranges between the
-                [ and ]
-                '''
-                findregex = re.compile( r'\[([0-9a-z\-,]+)\]', re.VERBOSE )
-                nodenames = list()
-
-                for arg in args:
-                        found = findregex.findall( arg )
-                        ranges = list()
-
-                        if found:
-                                pattern = findregex.sub( '%s', arg )
-
-                                for part in found:
-                                        ranges.append( self.return_range( part ) )
-
-                                combs = self.combinations( ranges )
-
-                                for comb in combs:
-                                        # Here the %s in the pattern are
-                                        # replaced by the correct value
-                                        nodenames.append( pattern % comb )
-                        else:
-                                nodenames.append( arg )
-
-                return nodenames
-
-        def check_values(self, values, args):
-                '''
-                Here we override the default method in OptionParser to
-                enable our extra parsing on the given Arguments
-                '''
-                return values, self.args_parser( args )
-
-if __name__ == "__main__":
-        import doctest
-        doctest.testmod()
