--- /dev/null
+confparser.txt
+setup
+setup.py
+tuxeenet/__index__.py
+tuxeenet/basicparser.py
+tuxeenet/basicvalidator.py
+tuxeenet/confparser.py
+tuxeenet/confparser_ext.py
+++ /dev/null
-# -*- 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:
+++ /dev/null
-# -*- 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:
+++ /dev/null
-# -*- 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:
+++ /dev/null
-# -*- 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:
+++ /dev/null
-#!/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:
+++ /dev/null
-# -*- 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:
--- /dev/null
+setup.py
\ No newline at end of file
--- /dev/null
+#!/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' )
--- /dev/null
+# -*- 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:
--- /dev/null
+# -*- 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:
--- /dev/null
+# -*- 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:
--- /dev/null
+# -*- 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: