Use distutils instead of my own installation script. master
authorFrédéric Jolliton <git@frederic.jolliton.com>
Tue, 11 Apr 2006 23:02:04 +0000 (01:02 +0200)
committerFrédéric Jolliton <git@frederic.jolliton.com>
Tue, 11 Apr 2006 23:02:04 +0000 (01:02 +0200)
14 files changed:
MANIFEST [new file with mode: 0644]
basicparser.py [deleted file]
basicvalidator.py [deleted file]
confparser.py [deleted file]
confparser_ext.py [deleted file]
install [deleted file]
install_conf.py [deleted file]
setup [new symlink]
setup.py [new file with mode: 0755]
tuxeenet/__index__.py [new file with mode: 0644]
tuxeenet/basicparser.py [new file with mode: 0644]
tuxeenet/basicvalidator.py [new file with mode: 0644]
tuxeenet/confparser.py [new file with mode: 0644]
tuxeenet/confparser_ext.py [new file with mode: 0644]

diff --git a/MANIFEST b/MANIFEST
new file mode 100644 (file)
index 0000000..63b9f5e
--- /dev/null
+++ b/MANIFEST
@@ -0,0 +1,8 @@
+confparser.txt
+setup
+setup.py
+tuxeenet/__index__.py
+tuxeenet/basicparser.py
+tuxeenet/basicvalidator.py
+tuxeenet/confparser.py
+tuxeenet/confparser_ext.py
diff --git a/basicparser.py b/basicparser.py
deleted file mode 100644 (file)
index 5a9dec2..0000000
+++ /dev/null
@@ -1,168 +0,0 @@
-# -*- coding: utf-8 -*-
-
-#
-# Basic parser
-# Copyright (C) 2005  Frédéric Jolliton <frederic@jolliton.com>
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-#
-
-import re
-
-#-----------------------------------------------------------------------------
-
-class ParserError( Exception ) : pass
-
-class Parser( object ) :
-
-       def __init__( self , tokens , text , defaultRegexFlags = 0 ) :
-
-               '''Create an instance to tokenise 'text' with tokens 'tokens'.'''
-
-               self.__tokenMatches = {}
-               self.__keys = []
-               if isinstance( tokens , dict ) :
-                       tokens = tokens.items()
-               self.__keys = map( lambda e : e[ 0 ] , tokens )
-               for k , v in tokens :
-                       if isinstance( v , ( str , unicode ) ) :
-                               v = re.compile( v , defaultRegexFlags )
-                       self.__tokenMatches[ k ] = v
-               self.__text = text
-               self.__pos = 0
-               self.__tokensToIgnore = ()
-               self.__x = 1
-               self.__y = 1
-
-       def clone( self ) :
-
-               newInstance = self.__new__( self.__class__)
-               newInstance.__tokenMatches = self.__tokenMatches
-               newInstance.__keys = self.__keys
-               newInstance.__text = self.__text
-               newInstance.__pos = self.__pos
-               newInstance.__tokensToIgnore = self.__tokensToIgnore
-               newInstance.__x = self.__x
-               newInstance.__y = self.__y
-               return newInstance
-
-       def __getX( self ) : return self.__x
-       def __getY( self ) : return self.__y
-
-       x = property( __getX )
-       y = property( __getY )
-
-       def ignore( self , *tokens ) :
-
-               '''Declare tokens 'tokens' as tokens to ignore when
-               parsing.'''
-
-               self.__tokensToIgnore = tokens
-
-       def peek( self , *set ) :
-
-               '''Try to match any of the defined tokens (or only
-               those from set 'set' if specified.) All matchings tokens
-               are returned, ordered (by decreasing order) by length.'''
-
-               tokens = []
-               if not set :
-                       set = self.__keys
-               for tokenName in set :
-                       tokenMatch = self.__tokenMatches.get( tokenName )
-                       if tokenMatch :
-                               m = tokenMatch.match( self.__text , self.__pos )
-                               if m :
-                                       tokens.append( ( tokenName , m.group( 0 ) ) + m.groups() )
-               tokens.sort( lambda a , b : cmp( len( b[ 1 ] ) , len( a[ 1 ] ) ) )
-               return tokens
-
-       def __advance( self , n ) :
-
-               '''Update current position (line,column) for the 'n'
-               next characters (if 'n' is an integer, or for the length of
-               the 'n' string otherwise.)'''
-
-               if isinstance( n , ( str , unicode ) ) :
-                       n = len( n )
-               p = self.__text.rfind( '\n' , self.__pos , self.__pos + n )
-               if p == -1 :
-                       self.__x += n
-               else :
-                       self.__x = self.__pos + n - p
-                       self.__y += self.__text.count( '\n' , self.__pos , self.__pos + n )
-               self.__pos += n
-
-       def snext( self , *set ) :
-
-               '''Try to match the longest string matching defined tokens
-               (or only from one of set 'set' if specified.) If no tokens
-               match, None is returned.'''
-
-               if set :
-                       set += self.__tokensToIgnore
-               r = None
-               while 1 :
-                       r = self.peek( *set )
-                       if not r :
-                               break
-                       r = r[ 0 ]
-                       self.__advance( r[ 1 ] )
-                       if r[ 0 ] not in self.__tokensToIgnore :
-                               break
-               return r or None
-
-       def next( self , *set ) :
-
-               '''Try to match the longest string matching defined tokens
-               (or only from one of set 'set' if specified.) If no tokens
-               match, an exception is raised.'''
-
-               r = self.snext( *set )
-               if not r :
-                       r = self.peek()
-                       if r :
-                               misc = 'found %r but ' % r[ 0 ][ 1 ]
-                       else :
-                               misc = ''
-                       raise ParserError( '%s, %sexpected one of the following tokens: %r'
-                               % ( self.getPos() , misc , list( set or self.__keys ) ) )
-               return r
-
-       def getPos( self ) :
-
-               '''Return current position in a texture representation.'''
-
-               return 'at line %d, column %d' % ( self.__y , self.__x )
-
-       def point( self ) :
-
-               '''Return a multiple-line string to show where an error
-               happened.'''
-
-               lineStart = self.__text.rfind( '\n' , 0 , self.__pos + 1 ) + 1
-               lineEnd = self.__text.find( '\n' , self.__pos + 1 )
-               if lineEnd == -1 :
-                       lineEnd = len( self.__text )
-               prefix = 'line %s: ' % self.__y
-               r = ''
-               r += prefix + self.__text[ lineStart : lineEnd ].replace( '\t' , ' ' ) + '\n'
-               r += prefix + ' ' * ( self.__pos - lineStart ) + '^' + '\n'
-               return r
-
-# Local Variables:
-# tab-width: 4
-# python-indent: 4
-# End:
diff --git a/basicvalidator.py b/basicvalidator.py
deleted file mode 100644 (file)
index 641cbc4..0000000
+++ /dev/null
@@ -1,87 +0,0 @@
-# -*- coding: utf-8 -*-
-
-#
-# Basic validator
-# Copyright (C) 2005  Frédéric Jolliton <frederic@jolliton.com>
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-#
-
-#
-# Base module to check structure validity of a configuration file.
-#
-
-class Error( Exception ) : pass
-
-def error( what ) :
-
-       raise Error( what )
-
-class Validator :
-
-       def __init__( self , name ) :
-
-               self.name = name
-
-       def descend( self , item ) :
-
-               '''Return a validator for the contents
-               of the node 'item', or throw an exception.'''
-
-               error( 'Invalid keyword `%r\' in %r.' % ( item , self.name ) )
-
-       def check( self , values ) :
-
-               '''Check node values.'''
-
-               if values :
-                       error( 'Unexpected values %r for %r.' % ( values , self.name ) )
-
-       def valid( self ) :
-
-               '''Called once node contents is valided.'''
-
-               pass
-
-def validate( node , validator ) :
-
-       '''Validate tree from node 'node' with validator 'validator'.'''
-
-       validate.lastNode = node
-
-       def _checkConf( node , syntaxNode ) :
-
-               validate.lastNode = node
-               name , values , contents , meta = node
-               r = syntaxNode.descend( name )
-               r.check( values )
-               for item in contents :
-                       _checkConf( item , r )
-               r.valid()
-
-       name , values , contents , meta = node
-       root = validator( '__root__' )
-       try :
-               for item in contents :
-                       _checkConf( item , root )
-       except Error , e :
-               meta = validate.lastNode[ 3 ]
-               raise Error( 'at line %d, column %d, %s' 
-                       % ( meta[ 0 ] , meta[ 1 ] , str( e ) ) )
-
-# Local Variables:
-# tab-width: 4
-# python-indent: 4
-# End:
diff --git a/confparser.py b/confparser.py
deleted file mode 100644 (file)
index 0034960..0000000
+++ /dev/null
@@ -1,272 +0,0 @@
-# -*- coding: utf-8 -*-
-
-#
-# Configuration parser
-# Copyright (C) 2005  Frédéric Jolliton <frederic@jolliton.com>
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-#
-
-import re
-import types
-import sys
-import os
-import stat
-import errno
-
-try :
-       import cPickle as pickle
-except :
-       import pickle
-
-import basicparser
-import basicvalidator
-
-class Error( Exception ) : pass
-
-#
-# Configuration parser check *syntax*.
-# Validator check *structure*, and eventually some values.
-#
-
-#--[ Parser ]-----------------------------------------------------------------
-
-def parseString( s ) :
-
-       '''Strip string delimiters.'''
-
-       if s.startswith( "'" ) :
-               return s[ 1 : -1 ]
-       elif s.startswith( '"' ) :
-               return s[ 1 : -1 ]
-       else :
-               return s
-
-def parseConf( p , meta = None ) :
-
-       def parseList() :
-
-               items = []
-               while 1 :
-                       t = p.next( 'string' , 'integer' , ']' )
-                       if t[ 0 ] == ']' :
-                               break
-                       elif t[ 0 ] == 'integer' :
-                               items.append( int( t[ 1 ] ) )
-                       elif t[ 0 ] == 'string' :
-                               items.append( parseString( t[ 1 ] ) )
-                       else :
-                               raise NotReached
-               return items
-
-       def parseNode() :
-
-               x , y = p.x , p.y
-               #
-               # Node name
-               #
-               t = p.next( 'keyword' )
-               kw = t[ 1 ]
-               #
-               # Values
-               #
-               values = []
-               while 1 :
-                       t = p.next( 'string' , 'integer' , '[' , '{' , ';' )
-                       if t[ 0 ] in [ '{' , ';' ] :
-                               break
-                       elif t[ 0 ] == '[' :
-                               values.append( parseList() )
-                       elif t[ 0 ] == 'integer' :
-                               values.append( int( t[ 1 ] ) )
-                       elif t[ 0 ] == 'string' :
-                               values.append( parseString( t[ 1 ] ) )
-                       else :
-                               raise NotReached
-               #
-               # Contents
-               #
-               subNodes = []
-               if t[ 0 ] == '{' :
-                       subNodes = []
-                       while not p.snext( '}' ) :
-                               r = parseNode()
-                               subNodes.append( r )
-               return ( kw , values , subNodes , ( y , x , meta ) )
-
-       nodes = []
-       #
-       # Parse the entire file
-       #
-       while not p.snext( 'eot' ) :
-               r = parseNode()
-               if not r :
-                       break
-               nodes.append( r )
-       return ('__root__',None,nodes,None)
-
-def parse( doc , filename = None ) :
-
-       kw = r'[_a-zA-Z](?:[-_a-zA-Z0-9]*[_a-zA-Z0-9])?'
-       tokenMatches = {
-                 'eot'     : '$'
-               , 'blank'   : r'\s+'
-               , 'integer' : r'[0-9]+'
-               , 'keyword' : kw
-               , 'string'  : kw + r"|'(?:[^\\']|\\.)*'"
-               , 'comment' : r'#[^\n]*(?:\n|$)'
-               , '{'       : '{'
-               , '}'       : '}'
-               , '['       : r'\['
-               , ']'       : r'\]'
-               , ';'       : ';'
-       }
-       p = basicparser.Parser( tokenMatches , doc )
-       p.ignore( 'blank' , 'comment' )
-       try :
-               result = parseConf( p , filename )
-       except basicparser.ParserError , e :
-               msg = p.point()
-               msg += str( e )
-               raise Error( msg )
-       return result
-
-#--[ Read&Write configuration ]-----------------------------------------------
-
-def lastModificationTime( filename ) :
-
-       try :
-               result = os.stat( filename )[ stat.ST_MTIME ]
-       except :
-               result = None
-       return result
-
-#
-# Return None | ( tree , isValid )
-#
-def readCachedConfiguration( filename ) :
-
-       result = None
-       cachedFilename = filename + '.cache'
-       #
-       # Check if cached file is older than the source.
-       #
-       dateCached = lastModificationTime( cachedFilename )
-       if dateCached is not None :
-               dateSource = lastModificationTime( filename )
-               if dateSource is not None and dateCached > dateSource :
-                       try :
-                               result = pickle.load( open( cachedFilename ) )
-                       except :
-                               pass
-       return result
-
-def writeCachedConfiguration( filename , tree , isValid ) :
-
-       try :
-               pickle.dump( ( tree , isValid ) , open( filename + '.cache' , 'w' ) )
-       except :
-               pass
-
-def readConfiguration( filename , validator = None ) :
-
-       try :
-               #
-               # 1. Read from cache file
-               #
-               r = readCachedConfiguration( filename )
-               cached = False
-               if r is not None :
-                       conf , isValid = r
-                       cached = True
-               else :
-                       isValid = False
-                       #
-                       # 2. Parse the file
-                       #
-                       conf = open( filename ).read()
-                       conf = parse( conf , filename )
-               if not isValid :
-                       #
-                       # 3. Validate it
-                       #
-                       if validator is not None :
-                               basicvalidator.validate( conf , validator )
-                               isValid = True
-                               cached = False
-               #
-               # 4. Keep cached result
-               #
-               if not cached :
-                       writeCachedConfiguration( filename , conf , isValid )
-       except IOError , e :
-               if e[ 0 ] == errno.ENOENT :
-                       conf = None
-               else :
-                       raise Exception( 'While reading file %s:\n%s' % ( filename , str( e ) ) )
-       except Exception , e :
-               raise Exception( 'While reading file %s:\n%s' % ( filename , str( e ) ) )
-       return conf
-
-#--[ Extended stuff ]---------------------------------------------------------
-
-def readConfigurationExt( filename , validator = None ) :
-
-       conf = readConfiguration( filename , validator )
-       if conf is not None :
-               import confparser_ext
-               conf = confparser_ext.confToNodeset( conf )
-       return conf
-
-def parseExt( doc , filename = None ) :
-
-       import confparser_ext
-       conf = parse( doc , filename )
-       return confparser_ext.confToNodeset( conf )
-
-#--[ Dump configuration tree ]------------------------------------------------
-
-def printTreeInner( t , prt = sys.stdout.write , prefix = '' , verbose = False ) :
-
-       prt( prefix )
-       prt( t[ 0 ] )
-       for kw in t[ 1 ] or [] :
-               prt( ' ' + str( kw ) )
-       if t[ 2 ] :
-               prt( ' {' )
-       else :
-               prt( ' ;' )
-       if t[ 3 ] and verbose :
-               prt( ' # ' )
-               if t[ 3 ][ 2 ] :
-                       prt( '%s:' % t[ 3 ][ 2 ] )
-               prt( '%s:%s' % ( t[ 3 ][ 0 ] , t[ 3 ][ 1 ] ) )
-       prt( '\n' )
-
-       if t[ 2 ] :
-               for sub in t[ 2 ] :
-                       printTreeInner( sub , prt , prefix + '  ' )
-               prt( prefix )
-               prt( '}\n' )
-
-def printTree( t , prt = sys.stdout.write , prefix = '' , verbose = False ) :
-
-       for sub in t[ 2 ] or [] :
-               printTreeInner( sub , prt , prefix , verbose )
-
-# Local Variables:
-# tab-width: 4
-# python-indent: 4
-# End:
diff --git a/confparser_ext.py b/confparser_ext.py
deleted file mode 100644 (file)
index c7d92ef..0000000
+++ /dev/null
@@ -1,299 +0,0 @@
-# -*- coding: utf-8 -*-
-
-#
-# Configuration parser extension
-# Copyright (C) 2005  Frédéric Jolliton <frederic@jolliton.com>
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-#
-
-#
-# This module is sort of prototype to support xpath-like
-# way to fetch certain nodes from a configuration tree.
-#
-# Code is absolutely not nice.. It's really just for experimentation
-# and need to be rewritten completely.
-#
-
-import re
-
-def flatten( lst ) :
-
-       '''Flatten a list or tuple.
-       
-       [[1,2],[3,[4,5]]] => [1,2,3,4,5]'''
-
-       if isinstance( lst , list ) :
-               r = []
-               for item in lst :
-                       r += flatten( item )
-       elif isinstance( lst , tuple ) :
-               r = ()
-               for item in lst :
-                       r += tuple( flatten( item ) )
-       else :
-               r = ( lst , )
-       return r
-
-def quote( s ) :
-
-       s = str( s )
-       if re.search( r'^(?:\d+|[a-z_][a-z0-9_-]*)$' , s , re.I ) is None :
-               s = '"%s"' % s.replace( '\\' , '\\\\' ).replace( '"' , '\\"' )
-       return s
-
-class NodeSet :
-
-       def __init__( self , *nodes ) :
-
-               self.__nodes = []
-               self.__iadd__( flatten( nodes ) )
-
-       def __iadd__( self , *others ) :
-
-               others = flatten( others )
-               for item in others :
-                       assert isinstance( item , ( Node , NodeSet ) )
-               for item in others :
-                       if isinstance( item , NodeSet ) :
-                               self.__nodes += item.__nodes
-                       else :
-                               self.__nodes.append( item )
-               return self
-
-       def __getitem__( self , place ) :
-
-               if isinstance( place , str ) :
-                       return select( self , place )
-               return self.__nodes[ place ]
-
-       def __len__( self ) :
-
-               return len( self.__nodes )
-
-       def pstr( self , indent = '' ) :
-
-               return '\n'.join( [ node.pstr( indent ) for node in self.__nodes ] )
-
-       def __repr__( self ) :
-
-               return '<NodeSet with %d elements>' % len( self.__nodes )
-
-class Node :
-
-       def __init__( self , name , *values ) :
-
-               self.__name = name
-               self.__values = flatten( values )
-               self.__subs = NodeSet()
-
-       def __iadd__( self , *others ) :
-
-               self.__subs += others
-               return self
-
-       def __getName( self ) :
-
-               return self.__name
-
-       name = property( __getName )
-
-       def __getSubs( self ) :
-
-               return self.__subs
-
-       subs = property( __getSubs )
-
-       def __getValues( self ) :
-
-               return self.__values
-
-       values = property( __getValues )
-
-       def pstr( self , indent = '' ) :
-
-               r = indent + quote( self.__name ) + ' '
-               if self.__values :
-                       r += ' '.join( map( quote , self.__values ) ) + ' '
-               if len( self.__subs ) != 0 :
-                       r += '{\n' + self.__subs.pstr( indent + '  ' ) + '\n' + indent + '}'
-               else :
-                       r += ';'
-               return r
-
-       def __getitem__( self , place ) :
-
-               if isinstance( place , str ) :
-                       return select( self , place )
-               return self.subs[ place ]
-
-       def __repr__( self ) :
-
-               return '<Node :name %r>' % ( self.__name , )
-
-def confToNodeset( node ) :
-
-       def confToNodeset_( node ) :
-
-               if isinstance( node , tuple ) :
-                       result = Node( node[ 0 ] , node[ 1 ] or [] )
-                       result += confToNodeset_( node[ 2 ] )
-               elif isinstance( node , list ) :
-                       result = NodeSet( [ confToNodeset_( sub ) for sub in node or () ] )
-               else :
-                       result = None
-               return result
-
-       return NodeSet( confToNodeset_( node ) )
-
-def select( nodeset , path ) :
-
-       def matchValue_( match , value ) :
-
-               return re.match( match
-                                                .replace( '.' , '\\.' )
-                                                .replace( '*' , '.*' )
-                                                .replace( '?' , '. ') , str( value ) , re.I ) is not None
-
-       def select_( nodeset , path ) :
-
-               '''Search *children* of nodeset matching 'path'.'''
-
-               assert path
-               assert isinstance( nodeset , NodeSet )
-
-               # Extract first path element
-               element , subPath = path[ 0 ] , path[ 1 : ]
-
-               #
-               # Remove leading empty element(s) and set recurse flag
-               # in such case.
-               #
-               recurse = False
-               while element == '' : # the '//' path element
-                       assert subPath , '// must to be followed by a path element.'
-                       element , subPath = subPath[ 0 ] , subPath[ 1 : ]
-                       recurse = True
-
-               #
-               # Build predicates
-               #
-               if ':' in element :
-                       element = element.split( ':' )
-                       def matcher( sub ) :
-                               result = ( element[ 0 ] in [ sub.name , '*' ] and len( sub.values ) == len( element ) - 1 )
-                               if result :
-                                       for v1 , v2 in zip( sub.values , element[ 1 : ] ) :
-                                               if not matchValue_( v2 , v1 ) :
-                                                       result = False
-                                                       break
-                               return result
-               else :
-                       def matcher( sub ) :
-                               return element in [ sub.name , '*' ]
-
-               #
-               # process either continue filtering process, or keep current
-               # result according if path fully processed.
-               #
-               if subPath :
-                       def process( result ) :
-                               return select_( NodeSet( result ) , subPath )
-               else :
-                       def process( result ) :
-                               return result
-
-               #
-               # Select subnodes according to the predicate
-               #
-               result = NodeSet()
-               for item in nodeset :
-                       for sub in item.subs :
-                               if matcher( sub ) :
-                                       result += process( sub )
-                               if recurse :
-                                       result += select_( NodeSet( sub ) , path )
-
-               return result
-
-       if not path.startswith( '/' ) :
-               # Always assume path from the root
-               # (It's the only possible case anyway.)
-               path = '/' + path
-
-       nodeset = NodeSet( nodeset )
-
-       path = path.split( '/' )[ 1 : ]
-
-       def finally_( result ) :
-               return result
-
-       if path :
-               if path[ -1 ] == '@@@' :
-                       def finally_( result ) :
-                               return [ ( node.name , ) + node.values for node in result ]
-                       path.pop()
-               elif path[ -1 ] == '@@' :
-                       # FIXME: Factorize with @@@
-                       def finally_( result ) :
-                               return [ node.values for node in result ]
-                       path.pop()
-               elif path[ -1 ] == '@' :
-                       # FIXME: Factorize with @@ or @@@
-                       def finally_( result ) :
-                               return sum( [ node.values for node in result ] , () )
-                       path.pop()
-               elif path[ -1 ] == '?' :
-                       def finally_( result ) :
-                               if len( result ) :
-                                       return ' '.join( [ str( s ) for node in result for s in node.values ] )
-                       path.pop()
-       if not path or ( len( path ) == 1 and path[ 0 ] == '' ) :
-               result = nodeset
-       else :
-               result = select_( nodeset , path )
-       result = finally_( result )
-
-       return result
-
-def test() :
-
-       filename = 'vserver.conf'
-
-       import confparser
-       n = confparser.readConfigurationExt( filename )
-       if n is not None :
-               print n[ 'default/unhide' ].pstr()
-               print '--'
-               print n[ 'default/unhide' ] # the nodeset
-               print n[ 'default/unhide/?' ] # the string for the whole nodesets
-               print n[ 'default/unhide/@' ] # the flatten list of text
-               print n[ 'default/unhide/@@' ] # the list of list of text
-               print n[ 'default/unhide/@@@' ] # the list of list of text, include node name
-               print n[ 'server/@' ]
-               print n[ 'server:m*/rootdev/@' ]
-               print n[ '*/@@' ]
-               print n[ '//*/@@' ]
-               print n[ '//*/@@@' ]
-       else :
-               print 'Unable to parse %r' % filename
-
-if __name__ == '__main__' :
-       test()
-
-# Local Variables:
-# tab-width: 4
-# python-indent: 4
-# End:
diff --git a/install b/install
deleted file mode 100755 (executable)
index 96dd0f6..0000000
--- a/install
+++ /dev/null
@@ -1,201 +0,0 @@
-#!/bin/env python
-# -*- coding: utf-8 -*-
-
-#
-# TuxeeNet installer
-# Copyright (C) 2005  Frédéric Jolliton <frederic@jolliton.com>
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-#
-
-#--[ Configuration ]----------------------------------------------------------
-
-from install_conf import *
-
-#--[ Global variables ]-------------------------------------------------------
-
-g_verbose = False
-g_pretend = False
-
-#-----------------------------------------------------------------------------
-
-import sys
-import os
-import errno
-import time
-import py_compile
-
-from getopt import GetoptError
-try :
-       from getopt import gnu_getopt as getopt
-except :
-       from getopt import getopt
-
-from distutils.sysconfig import get_python_lib
-
-def isoDate() :
-
-       return '%04d-%02d-%02dT%02d:%02d:%02dZ' % time.gmtime()[ : 6 ]
-
-def archVersion() :
-
-       try :
-               treeVersion = os.popen( 'tla tree-version' ).read().splitlines()[ 0 ]
-               patchLevel = os.popen( 'tla logs' ).read().splitlines()
-               if patchLevel :
-                       patchLevel = patchLevel[ -1 ]
-               else :
-                       patchLevel = 'empty'
-               result = treeVersion + '--' + patchLevel
-       except :
-               result = None
-       return result
-
-def touch( filename ) :
-
-       open( filename , 'a+' ).close()
-
-def createFile( filename , contents ) :
-
-       f = open( filename , 'w' )
-       f.write( contents )
-       f.close()
-
-def byteCompile( filename ) :
-
-       if g_verbose :
-               print 'Byte-compiling %s' % filename
-       py_compile.compile( filename )
-
-def installPath() :
-
-       '''Install Python path.'''
-
-       path = get_python_lib()
-       path += '/' + g_globalModule + '.pth'
-       target = g_targetPrefix
-       if g_verbose or g_pretend :
-               print 'Creating Python path from %s to %s.' %  ( path , target )
-       if not g_pretend :
-               try :
-                       createFile( path , target )
-               except IOError , e :
-                       print '** Error creating Python path into %s' % path
-                       print e
-
-def installModule() :
-
-       '''Install modules.'''
-
-       myPrefix = g_targetPrefix + '/' + g_globalModule
-
-       if g_verbose or g_pretend :
-               print 'Creating directory %s' % myPrefix
-       if not g_pretend :
-               try :
-                       os.makedirs( myPrefix )
-               except OSError , e :
-                       if e[ 0 ] == errno.EEXIST :
-                               pass
-                       else :
-                               raise
-
-       if g_verbose or g_pretend :
-               print 'Creating module %s' % g_globalModule
-       if not g_pretend :
-               p = myPrefix + '/__init__.py'
-               try :
-                       touch( p )
-               except IOError :
-                       print '** Error touching %s' % p
-               else :
-                       byteCompile( p )
-       for module in g_modules :
-               if g_verbose or g_pretend :
-                       print 'Copying module %s -> %s' % ( module , myPrefix + '/' )
-               if not g_pretend :
-                       mod = open( module ).read()
-                       try :
-                               createFile( myPrefix + '/' + module , mod )
-                       except IOError , e :
-                               print '** Error copying %s -> %s' % ( module , myPrefix + '/' )
-                               print e
-                       else :
-                               byteCompile( myPrefix + '/' + module )
-
-       if g_verbose or g_pretend :
-               print 'Storing Arch version'
-
-       currentVersion = archVersion()
-       if currentVersion is None :
-               print '  Unable to compute Arch version'
-       else :
-               logFilename = myPrefix + '/install.log'
-               try :
-                       f = open( logFilename , 'a+' )
-                       f.write( isoDate() + ' Version %s installed.\n' % currentVersion )
-                       f.write( isoDate() + '   including files: %r\n' % ( g_modules , ) )
-                       f.close()
-               except ( IOError , OSError ) :
-                       print '** Error updating log file %s' % logFilename
-
-def usage() :
-
-       print '''Usage: install [OPTIONS]
-
- -h, --help     Print this help.
- -n, --pretend  Display operations, but do nothing.
- -v, --verbose  Verbose output.
-     --pth      Install Python path (.pth) only.
-
-Report bugs to <fj@tuxee.net>.'''
-
-def main() :
-
-       global g_verbose
-       global g_pretend
-
-       try :
-               options , paramaters = getopt( sys.argv[ 1 : ] ,
-                       'hnv' , ( 'help' , 'pth' , 'pretend' , 'verbose' ) )
-       except GetoptError , e :
-               print 'install:' , e
-               print 'Try `install --help\' for more information.'
-               sys.exit( 1 )
-
-       pathOnly = False
-
-       for option , argument in options :
-               if option in [ '-h' , '--help' ] :
-                       usage()
-                       sys.exit( 0 )
-               elif option in [ '-v' , '--verbose' ] :
-                       g_verbose = True
-               elif option in [ '-n' , '--pretend' ] :
-                       g_pretend = True
-               elif option in [ '--pth' ] :
-                       pathOnly = True
-
-       installPath()
-       if not pathOnly :
-               installModule()
-
-if __name__ == '__main__' :
-       main()
-
-# Local Variables:
-# tab-width: 4
-# python-indent: 4
-# End:
diff --git a/install_conf.py b/install_conf.py
deleted file mode 100644 (file)
index 5ec8971..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-# -*- coding: utf-8 -*-
-
-#
-# Modules to install.
-#
-g_modules = [
-         'basicparser.py'
-       , 'basicvalidator.py'
-       , 'confparser.py'
-       , 'confparser_ext.py'
-]
-
-#
-# Module name.
-#
-g_globalModule = 'tuxeenet'
-
-#
-# Target directory.
-#
-g_targetPrefix = '/opt/tuxeenet/python'
-
-# Local Variables:
-# tab-width: 4
-# python-indent: 4
-# End:
diff --git a/setup b/setup
new file mode 120000 (symlink)
index 0000000..27f63ea
--- /dev/null
+++ b/setup
@@ -0,0 +1 @@
+setup.py
\ No newline at end of file
diff --git a/setup.py b/setup.py
new file mode 100755 (executable)
index 0000000..b0d65a6
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,14 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import sys
+
+from distutils.core import setup
+
+setup( name         = 'confparser' ,
+       version      = '0.1' ,
+       description  = 'Configuration parser' ,
+       packages     = [ 'tuxeenet' ] ,
+       license      = 'GPL',
+       author       = 'Frédéric Jolliton' ,
+       author_email = 'frederic@jolliton.com' )
diff --git a/tuxeenet/__index__.py b/tuxeenet/__index__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tuxeenet/basicparser.py b/tuxeenet/basicparser.py
new file mode 100644 (file)
index 0000000..5a9dec2
--- /dev/null
@@ -0,0 +1,168 @@
+# -*- coding: utf-8 -*-
+
+#
+# Basic parser
+# Copyright (C) 2005  Frédéric Jolliton <frederic@jolliton.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+#
+
+import re
+
+#-----------------------------------------------------------------------------
+
+class ParserError( Exception ) : pass
+
+class Parser( object ) :
+
+       def __init__( self , tokens , text , defaultRegexFlags = 0 ) :
+
+               '''Create an instance to tokenise 'text' with tokens 'tokens'.'''
+
+               self.__tokenMatches = {}
+               self.__keys = []
+               if isinstance( tokens , dict ) :
+                       tokens = tokens.items()
+               self.__keys = map( lambda e : e[ 0 ] , tokens )
+               for k , v in tokens :
+                       if isinstance( v , ( str , unicode ) ) :
+                               v = re.compile( v , defaultRegexFlags )
+                       self.__tokenMatches[ k ] = v
+               self.__text = text
+               self.__pos = 0
+               self.__tokensToIgnore = ()
+               self.__x = 1
+               self.__y = 1
+
+       def clone( self ) :
+
+               newInstance = self.__new__( self.__class__)
+               newInstance.__tokenMatches = self.__tokenMatches
+               newInstance.__keys = self.__keys
+               newInstance.__text = self.__text
+               newInstance.__pos = self.__pos
+               newInstance.__tokensToIgnore = self.__tokensToIgnore
+               newInstance.__x = self.__x
+               newInstance.__y = self.__y
+               return newInstance
+
+       def __getX( self ) : return self.__x
+       def __getY( self ) : return self.__y
+
+       x = property( __getX )
+       y = property( __getY )
+
+       def ignore( self , *tokens ) :
+
+               '''Declare tokens 'tokens' as tokens to ignore when
+               parsing.'''
+
+               self.__tokensToIgnore = tokens
+
+       def peek( self , *set ) :
+
+               '''Try to match any of the defined tokens (or only
+               those from set 'set' if specified.) All matchings tokens
+               are returned, ordered (by decreasing order) by length.'''
+
+               tokens = []
+               if not set :
+                       set = self.__keys
+               for tokenName in set :
+                       tokenMatch = self.__tokenMatches.get( tokenName )
+                       if tokenMatch :
+                               m = tokenMatch.match( self.__text , self.__pos )
+                               if m :
+                                       tokens.append( ( tokenName , m.group( 0 ) ) + m.groups() )
+               tokens.sort( lambda a , b : cmp( len( b[ 1 ] ) , len( a[ 1 ] ) ) )
+               return tokens
+
+       def __advance( self , n ) :
+
+               '''Update current position (line,column) for the 'n'
+               next characters (if 'n' is an integer, or for the length of
+               the 'n' string otherwise.)'''
+
+               if isinstance( n , ( str , unicode ) ) :
+                       n = len( n )
+               p = self.__text.rfind( '\n' , self.__pos , self.__pos + n )
+               if p == -1 :
+                       self.__x += n
+               else :
+                       self.__x = self.__pos + n - p
+                       self.__y += self.__text.count( '\n' , self.__pos , self.__pos + n )
+               self.__pos += n
+
+       def snext( self , *set ) :
+
+               '''Try to match the longest string matching defined tokens
+               (or only from one of set 'set' if specified.) If no tokens
+               match, None is returned.'''
+
+               if set :
+                       set += self.__tokensToIgnore
+               r = None
+               while 1 :
+                       r = self.peek( *set )
+                       if not r :
+                               break
+                       r = r[ 0 ]
+                       self.__advance( r[ 1 ] )
+                       if r[ 0 ] not in self.__tokensToIgnore :
+                               break
+               return r or None
+
+       def next( self , *set ) :
+
+               '''Try to match the longest string matching defined tokens
+               (or only from one of set 'set' if specified.) If no tokens
+               match, an exception is raised.'''
+
+               r = self.snext( *set )
+               if not r :
+                       r = self.peek()
+                       if r :
+                               misc = 'found %r but ' % r[ 0 ][ 1 ]
+                       else :
+                               misc = ''
+                       raise ParserError( '%s, %sexpected one of the following tokens: %r'
+                               % ( self.getPos() , misc , list( set or self.__keys ) ) )
+               return r
+
+       def getPos( self ) :
+
+               '''Return current position in a texture representation.'''
+
+               return 'at line %d, column %d' % ( self.__y , self.__x )
+
+       def point( self ) :
+
+               '''Return a multiple-line string to show where an error
+               happened.'''
+
+               lineStart = self.__text.rfind( '\n' , 0 , self.__pos + 1 ) + 1
+               lineEnd = self.__text.find( '\n' , self.__pos + 1 )
+               if lineEnd == -1 :
+                       lineEnd = len( self.__text )
+               prefix = 'line %s: ' % self.__y
+               r = ''
+               r += prefix + self.__text[ lineStart : lineEnd ].replace( '\t' , ' ' ) + '\n'
+               r += prefix + ' ' * ( self.__pos - lineStart ) + '^' + '\n'
+               return r
+
+# Local Variables:
+# tab-width: 4
+# python-indent: 4
+# End:
diff --git a/tuxeenet/basicvalidator.py b/tuxeenet/basicvalidator.py
new file mode 100644 (file)
index 0000000..641cbc4
--- /dev/null
@@ -0,0 +1,87 @@
+# -*- coding: utf-8 -*-
+
+#
+# Basic validator
+# Copyright (C) 2005  Frédéric Jolliton <frederic@jolliton.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+#
+
+#
+# Base module to check structure validity of a configuration file.
+#
+
+class Error( Exception ) : pass
+
+def error( what ) :
+
+       raise Error( what )
+
+class Validator :
+
+       def __init__( self , name ) :
+
+               self.name = name
+
+       def descend( self , item ) :
+
+               '''Return a validator for the contents
+               of the node 'item', or throw an exception.'''
+
+               error( 'Invalid keyword `%r\' in %r.' % ( item , self.name ) )
+
+       def check( self , values ) :
+
+               '''Check node values.'''
+
+               if values :
+                       error( 'Unexpected values %r for %r.' % ( values , self.name ) )
+
+       def valid( self ) :
+
+               '''Called once node contents is valided.'''
+
+               pass
+
+def validate( node , validator ) :
+
+       '''Validate tree from node 'node' with validator 'validator'.'''
+
+       validate.lastNode = node
+
+       def _checkConf( node , syntaxNode ) :
+
+               validate.lastNode = node
+               name , values , contents , meta = node
+               r = syntaxNode.descend( name )
+               r.check( values )
+               for item in contents :
+                       _checkConf( item , r )
+               r.valid()
+
+       name , values , contents , meta = node
+       root = validator( '__root__' )
+       try :
+               for item in contents :
+                       _checkConf( item , root )
+       except Error , e :
+               meta = validate.lastNode[ 3 ]
+               raise Error( 'at line %d, column %d, %s' 
+                       % ( meta[ 0 ] , meta[ 1 ] , str( e ) ) )
+
+# Local Variables:
+# tab-width: 4
+# python-indent: 4
+# End:
diff --git a/tuxeenet/confparser.py b/tuxeenet/confparser.py
new file mode 100644 (file)
index 0000000..0034960
--- /dev/null
@@ -0,0 +1,272 @@
+# -*- coding: utf-8 -*-
+
+#
+# Configuration parser
+# Copyright (C) 2005  Frédéric Jolliton <frederic@jolliton.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+#
+
+import re
+import types
+import sys
+import os
+import stat
+import errno
+
+try :
+       import cPickle as pickle
+except :
+       import pickle
+
+import basicparser
+import basicvalidator
+
+class Error( Exception ) : pass
+
+#
+# Configuration parser check *syntax*.
+# Validator check *structure*, and eventually some values.
+#
+
+#--[ Parser ]-----------------------------------------------------------------
+
+def parseString( s ) :
+
+       '''Strip string delimiters.'''
+
+       if s.startswith( "'" ) :
+               return s[ 1 : -1 ]
+       elif s.startswith( '"' ) :
+               return s[ 1 : -1 ]
+       else :
+               return s
+
+def parseConf( p , meta = None ) :
+
+       def parseList() :
+
+               items = []
+               while 1 :
+                       t = p.next( 'string' , 'integer' , ']' )
+                       if t[ 0 ] == ']' :
+                               break
+                       elif t[ 0 ] == 'integer' :
+                               items.append( int( t[ 1 ] ) )
+                       elif t[ 0 ] == 'string' :
+                               items.append( parseString( t[ 1 ] ) )
+                       else :
+                               raise NotReached
+               return items
+
+       def parseNode() :
+
+               x , y = p.x , p.y
+               #
+               # Node name
+               #
+               t = p.next( 'keyword' )
+               kw = t[ 1 ]
+               #
+               # Values
+               #
+               values = []
+               while 1 :
+                       t = p.next( 'string' , 'integer' , '[' , '{' , ';' )
+                       if t[ 0 ] in [ '{' , ';' ] :
+                               break
+                       elif t[ 0 ] == '[' :
+                               values.append( parseList() )
+                       elif t[ 0 ] == 'integer' :
+                               values.append( int( t[ 1 ] ) )
+                       elif t[ 0 ] == 'string' :
+                               values.append( parseString( t[ 1 ] ) )
+                       else :
+                               raise NotReached
+               #
+               # Contents
+               #
+               subNodes = []
+               if t[ 0 ] == '{' :
+                       subNodes = []
+                       while not p.snext( '}' ) :
+                               r = parseNode()
+                               subNodes.append( r )
+               return ( kw , values , subNodes , ( y , x , meta ) )
+
+       nodes = []
+       #
+       # Parse the entire file
+       #
+       while not p.snext( 'eot' ) :
+               r = parseNode()
+               if not r :
+                       break
+               nodes.append( r )
+       return ('__root__',None,nodes,None)
+
+def parse( doc , filename = None ) :
+
+       kw = r'[_a-zA-Z](?:[-_a-zA-Z0-9]*[_a-zA-Z0-9])?'
+       tokenMatches = {
+                 'eot'     : '$'
+               , 'blank'   : r'\s+'
+               , 'integer' : r'[0-9]+'
+               , 'keyword' : kw
+               , 'string'  : kw + r"|'(?:[^\\']|\\.)*'"
+               , 'comment' : r'#[^\n]*(?:\n|$)'
+               , '{'       : '{'
+               , '}'       : '}'
+               , '['       : r'\['
+               , ']'       : r'\]'
+               , ';'       : ';'
+       }
+       p = basicparser.Parser( tokenMatches , doc )
+       p.ignore( 'blank' , 'comment' )
+       try :
+               result = parseConf( p , filename )
+       except basicparser.ParserError , e :
+               msg = p.point()
+               msg += str( e )
+               raise Error( msg )
+       return result
+
+#--[ Read&Write configuration ]-----------------------------------------------
+
+def lastModificationTime( filename ) :
+
+       try :
+               result = os.stat( filename )[ stat.ST_MTIME ]
+       except :
+               result = None
+       return result
+
+#
+# Return None | ( tree , isValid )
+#
+def readCachedConfiguration( filename ) :
+
+       result = None
+       cachedFilename = filename + '.cache'
+       #
+       # Check if cached file is older than the source.
+       #
+       dateCached = lastModificationTime( cachedFilename )
+       if dateCached is not None :
+               dateSource = lastModificationTime( filename )
+               if dateSource is not None and dateCached > dateSource :
+                       try :
+                               result = pickle.load( open( cachedFilename ) )
+                       except :
+                               pass
+       return result
+
+def writeCachedConfiguration( filename , tree , isValid ) :
+
+       try :
+               pickle.dump( ( tree , isValid ) , open( filename + '.cache' , 'w' ) )
+       except :
+               pass
+
+def readConfiguration( filename , validator = None ) :
+
+       try :
+               #
+               # 1. Read from cache file
+               #
+               r = readCachedConfiguration( filename )
+               cached = False
+               if r is not None :
+                       conf , isValid = r
+                       cached = True
+               else :
+                       isValid = False
+                       #
+                       # 2. Parse the file
+                       #
+                       conf = open( filename ).read()
+                       conf = parse( conf , filename )
+               if not isValid :
+                       #
+                       # 3. Validate it
+                       #
+                       if validator is not None :
+                               basicvalidator.validate( conf , validator )
+                               isValid = True
+                               cached = False
+               #
+               # 4. Keep cached result
+               #
+               if not cached :
+                       writeCachedConfiguration( filename , conf , isValid )
+       except IOError , e :
+               if e[ 0 ] == errno.ENOENT :
+                       conf = None
+               else :
+                       raise Exception( 'While reading file %s:\n%s' % ( filename , str( e ) ) )
+       except Exception , e :
+               raise Exception( 'While reading file %s:\n%s' % ( filename , str( e ) ) )
+       return conf
+
+#--[ Extended stuff ]---------------------------------------------------------
+
+def readConfigurationExt( filename , validator = None ) :
+
+       conf = readConfiguration( filename , validator )
+       if conf is not None :
+               import confparser_ext
+               conf = confparser_ext.confToNodeset( conf )
+       return conf
+
+def parseExt( doc , filename = None ) :
+
+       import confparser_ext
+       conf = parse( doc , filename )
+       return confparser_ext.confToNodeset( conf )
+
+#--[ Dump configuration tree ]------------------------------------------------
+
+def printTreeInner( t , prt = sys.stdout.write , prefix = '' , verbose = False ) :
+
+       prt( prefix )
+       prt( t[ 0 ] )
+       for kw in t[ 1 ] or [] :
+               prt( ' ' + str( kw ) )
+       if t[ 2 ] :
+               prt( ' {' )
+       else :
+               prt( ' ;' )
+       if t[ 3 ] and verbose :
+               prt( ' # ' )
+               if t[ 3 ][ 2 ] :
+                       prt( '%s:' % t[ 3 ][ 2 ] )
+               prt( '%s:%s' % ( t[ 3 ][ 0 ] , t[ 3 ][ 1 ] ) )
+       prt( '\n' )
+
+       if t[ 2 ] :
+               for sub in t[ 2 ] :
+                       printTreeInner( sub , prt , prefix + '  ' )
+               prt( prefix )
+               prt( '}\n' )
+
+def printTree( t , prt = sys.stdout.write , prefix = '' , verbose = False ) :
+
+       for sub in t[ 2 ] or [] :
+               printTreeInner( sub , prt , prefix , verbose )
+
+# Local Variables:
+# tab-width: 4
+# python-indent: 4
+# End:
diff --git a/tuxeenet/confparser_ext.py b/tuxeenet/confparser_ext.py
new file mode 100644 (file)
index 0000000..c7d92ef
--- /dev/null
@@ -0,0 +1,299 @@
+# -*- coding: utf-8 -*-
+
+#
+# Configuration parser extension
+# Copyright (C) 2005  Frédéric Jolliton <frederic@jolliton.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+#
+
+#
+# This module is sort of prototype to support xpath-like
+# way to fetch certain nodes from a configuration tree.
+#
+# Code is absolutely not nice.. It's really just for experimentation
+# and need to be rewritten completely.
+#
+
+import re
+
+def flatten( lst ) :
+
+       '''Flatten a list or tuple.
+       
+       [[1,2],[3,[4,5]]] => [1,2,3,4,5]'''
+
+       if isinstance( lst , list ) :
+               r = []
+               for item in lst :
+                       r += flatten( item )
+       elif isinstance( lst , tuple ) :
+               r = ()
+               for item in lst :
+                       r += tuple( flatten( item ) )
+       else :
+               r = ( lst , )
+       return r
+
+def quote( s ) :
+
+       s = str( s )
+       if re.search( r'^(?:\d+|[a-z_][a-z0-9_-]*)$' , s , re.I ) is None :
+               s = '"%s"' % s.replace( '\\' , '\\\\' ).replace( '"' , '\\"' )
+       return s
+
+class NodeSet :
+
+       def __init__( self , *nodes ) :
+
+               self.__nodes = []
+               self.__iadd__( flatten( nodes ) )
+
+       def __iadd__( self , *others ) :
+
+               others = flatten( others )
+               for item in others :
+                       assert isinstance( item , ( Node , NodeSet ) )
+               for item in others :
+                       if isinstance( item , NodeSet ) :
+                               self.__nodes += item.__nodes
+                       else :
+                               self.__nodes.append( item )
+               return self
+
+       def __getitem__( self , place ) :
+
+               if isinstance( place , str ) :
+                       return select( self , place )
+               return self.__nodes[ place ]
+
+       def __len__( self ) :
+
+               return len( self.__nodes )
+
+       def pstr( self , indent = '' ) :
+
+               return '\n'.join( [ node.pstr( indent ) for node in self.__nodes ] )
+
+       def __repr__( self ) :
+
+               return '<NodeSet with %d elements>' % len( self.__nodes )
+
+class Node :
+
+       def __init__( self , name , *values ) :
+
+               self.__name = name
+               self.__values = flatten( values )
+               self.__subs = NodeSet()
+
+       def __iadd__( self , *others ) :
+
+               self.__subs += others
+               return self
+
+       def __getName( self ) :
+
+               return self.__name
+
+       name = property( __getName )
+
+       def __getSubs( self ) :
+
+               return self.__subs
+
+       subs = property( __getSubs )
+
+       def __getValues( self ) :
+
+               return self.__values
+
+       values = property( __getValues )
+
+       def pstr( self , indent = '' ) :
+
+               r = indent + quote( self.__name ) + ' '
+               if self.__values :
+                       r += ' '.join( map( quote , self.__values ) ) + ' '
+               if len( self.__subs ) != 0 :
+                       r += '{\n' + self.__subs.pstr( indent + '  ' ) + '\n' + indent + '}'
+               else :
+                       r += ';'
+               return r
+
+       def __getitem__( self , place ) :
+
+               if isinstance( place , str ) :
+                       return select( self , place )
+               return self.subs[ place ]
+
+       def __repr__( self ) :
+
+               return '<Node :name %r>' % ( self.__name , )
+
+def confToNodeset( node ) :
+
+       def confToNodeset_( node ) :
+
+               if isinstance( node , tuple ) :
+                       result = Node( node[ 0 ] , node[ 1 ] or [] )
+                       result += confToNodeset_( node[ 2 ] )
+               elif isinstance( node , list ) :
+                       result = NodeSet( [ confToNodeset_( sub ) for sub in node or () ] )
+               else :
+                       result = None
+               return result
+
+       return NodeSet( confToNodeset_( node ) )
+
+def select( nodeset , path ) :
+
+       def matchValue_( match , value ) :
+
+               return re.match( match
+                                                .replace( '.' , '\\.' )
+                                                .replace( '*' , '.*' )
+                                                .replace( '?' , '. ') , str( value ) , re.I ) is not None
+
+       def select_( nodeset , path ) :
+
+               '''Search *children* of nodeset matching 'path'.'''
+
+               assert path
+               assert isinstance( nodeset , NodeSet )
+
+               # Extract first path element
+               element , subPath = path[ 0 ] , path[ 1 : ]
+
+               #
+               # Remove leading empty element(s) and set recurse flag
+               # in such case.
+               #
+               recurse = False
+               while element == '' : # the '//' path element
+                       assert subPath , '// must to be followed by a path element.'
+                       element , subPath = subPath[ 0 ] , subPath[ 1 : ]
+                       recurse = True
+
+               #
+               # Build predicates
+               #
+               if ':' in element :
+                       element = element.split( ':' )
+                       def matcher( sub ) :
+                               result = ( element[ 0 ] in [ sub.name , '*' ] and len( sub.values ) == len( element ) - 1 )
+                               if result :
+                                       for v1 , v2 in zip( sub.values , element[ 1 : ] ) :
+                                               if not matchValue_( v2 , v1 ) :
+                                                       result = False
+                                                       break
+                               return result
+               else :
+                       def matcher( sub ) :
+                               return element in [ sub.name , '*' ]
+
+               #
+               # process either continue filtering process, or keep current
+               # result according if path fully processed.
+               #
+               if subPath :
+                       def process( result ) :
+                               return select_( NodeSet( result ) , subPath )
+               else :
+                       def process( result ) :
+                               return result
+
+               #
+               # Select subnodes according to the predicate
+               #
+               result = NodeSet()
+               for item in nodeset :
+                       for sub in item.subs :
+                               if matcher( sub ) :
+                                       result += process( sub )
+                               if recurse :
+                                       result += select_( NodeSet( sub ) , path )
+
+               return result
+
+       if not path.startswith( '/' ) :
+               # Always assume path from the root
+               # (It's the only possible case anyway.)
+               path = '/' + path
+
+       nodeset = NodeSet( nodeset )
+
+       path = path.split( '/' )[ 1 : ]
+
+       def finally_( result ) :
+               return result
+
+       if path :
+               if path[ -1 ] == '@@@' :
+                       def finally_( result ) :
+                               return [ ( node.name , ) + node.values for node in result ]
+                       path.pop()
+               elif path[ -1 ] == '@@' :
+                       # FIXME: Factorize with @@@
+                       def finally_( result ) :
+                               return [ node.values for node in result ]
+                       path.pop()
+               elif path[ -1 ] == '@' :
+                       # FIXME: Factorize with @@ or @@@
+                       def finally_( result ) :
+                               return sum( [ node.values for node in result ] , () )
+                       path.pop()
+               elif path[ -1 ] == '?' :
+                       def finally_( result ) :
+                               if len( result ) :
+                                       return ' '.join( [ str( s ) for node in result for s in node.values ] )
+                       path.pop()
+       if not path or ( len( path ) == 1 and path[ 0 ] == '' ) :
+               result = nodeset
+       else :
+               result = select_( nodeset , path )
+       result = finally_( result )
+
+       return result
+
+def test() :
+
+       filename = 'vserver.conf'
+
+       import confparser
+       n = confparser.readConfigurationExt( filename )
+       if n is not None :
+               print n[ 'default/unhide' ].pstr()
+               print '--'
+               print n[ 'default/unhide' ] # the nodeset
+               print n[ 'default/unhide/?' ] # the string for the whole nodesets
+               print n[ 'default/unhide/@' ] # the flatten list of text
+               print n[ 'default/unhide/@@' ] # the list of list of text
+               print n[ 'default/unhide/@@@' ] # the list of list of text, include node name
+               print n[ 'server/@' ]
+               print n[ 'server:m*/rootdev/@' ]
+               print n[ '*/@@' ]
+               print n[ '//*/@@' ]
+               print n[ '//*/@@@' ]
+       else :
+               print 'Unable to parse %r' % filename
+
+if __name__ == '__main__' :
+       test()
+
+# Local Variables:
+# tab-width: 4
+# python-indent: 4
+# End: