# Copyright (C) 2004 Scott W. Dunlop <sdunlop at users.sourceforge.net>
#
# 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.
from exceptions import Exception,KeyError
from types import StringType,FileType,ListType,TupleType
from cStringIO import StringIO
from sets import Set
import os, re, sys
re_whiteline = re.compile( '^\\s*$' )
re_meta_cmd = re.compile( '^@(\\S+)\\s+(\\S+)\\s*$')
re_statement = re.compile( '^\\.(.*)$' )
re_comment = re.compile( '^#(.*)$' )
re_print = re.compile( '^:(.*)$' )
re_indentation = re.compile( '^(\\s*)(.*)$' )
def break_indent( line ):
m = re_indentation.match( line )
if m:
i = 0
for ch in m.group(1):
if ch == '\t': i += 8
else: i += 1
return i, m.group(2)
else:
return 0, line
class FragmentException( Exception ):
pass
class ParseError( FragmentException ):
pass
class InputError( FragmentException ):
def __str__( self ):
"Invalid data supplied to fragment initializer."
class Fragment( object ):
__slots__ = (
'code', 'prog', 'origin', 'indent', 'name', 'dependencies'
)
def __init__( self, data = None, file = None ):
self.code = StringIO()
self.indent = 0
self.dependencies = Set()
if data is not None:
if isinstance( data, StringType ):
self.parseString( data )
elif isinstance( data, ListType ):
self.parseLines( data )
elif isinstance( data, TupleType ):
self.parseLines( data )
else:
raise InputError()
self.origin = "(string)"
elif isinstance( file, FileType ):
try: self.origin = file.name
except AttributeError: self.origin = "(file)"
self.parseFile( file )
elif isinstance( file, StringType ):
self.origin = file
self.parseFile( open( file, "r" ) )
else:
raise InputError()
self.compile()
def parseString( self, data ):
for line in data.splitlines():
self.parseLine( line )
def parseLines( self, data ):
for line in data:
self.parseLine( line )
def parseFile( self, file ):
for line in file.readlines():
self.parseLine( line )
def parseLine( self, line ):
if re_whiteline.match( line ): return
line = self.parseIndentation( line.rstrip() )
m = re_meta_cmd.match( line )
if m: return self.parseMetaCmd( *m.groups() )
m = re_statement.match( line )
if m: return self.parseStatement( *m.groups () )
m = re_print.match( line )
if m: return self.parsePrint( *m.groups () )
m = re_comment.match( line )
if m: return self.parseComment( *m.groups() )
return self.parsePrint( line )
def parseIndentation( self, line ):
indent, line = break_indent( line )
if line: self.indent = indent
return line
def parseMetaCmd( self, cmd, arg ):
cmd = cmd.lower()
if cmd == 'export': return self.parseExport( arg )
if cmd == 'import': return self.parseImport( arg )
raise ParseError( 'This version of the fragment module only understands import and export metacommands.' )
def parseStatement( self, stmt ):
self.writeCodeLine( stmt )
def parseComment( self, comment ):
self.writeCodeLine( "# " + comment )
def parsePrint( self, line ):
frags = line.split( ">>" )
for frag in frags[:-1]:
r = frag.split( "<<" )
if len( r ) == 1:
self.parsePrintText( r[0] + ">>" )
else:
self.parsePrintText( r[0] )
self.parsePrintExpr( r[1] )
self.parsePrintText( frags[-1] + "\n" )
def parsePrintText( self, frag ):
self.writeCodeLine(
'write( "' +
frag.replace(
"\\", "\\\\",
).replace(
'"', '\\"'
).replace(
"\n", "\\n"
) +
'" )'
)
def parsePrintExpr( self, frag ):
self.writeCodeLine(
'write( str(' + frag + ') )'
)
def parseExport( self, arg ):
self.name = arg.replace( "-", "_" )
def parseImport( self, arg ):
arg = arg.replace( "-", "_" )
self.writeCodeLine(
'exec _fragment_' +
arg +
" in globals(), locals()"
)
self.dependencies.add( arg )
def writeCodeLine( self, line ):
self.code.write( ' ' * self.indent )
self.code.write( line )
self.code.write( '\n' )
def compile( self ):
self.prog = compile( self.code.getvalue(), self.origin, 'exec' )
def getDependencies( self ):
return dependencies
class Lexicon( object ):
__slots__ = 'globals', 'fragments', 'directory'
def __init__( self, directory = None ):
if directory is None:
self.directory = ''
else:
self.directory = directory
self.fragments = {}
self.globals = {}
def makeFragment( self, data = None, file = None ):
if file is not None:
file = os.path.join( self.directory, file )
fragment = Fragment( data, file )
if fragment.name is not None:
self.fragments[ fragment.name ] = fragment
self.globals[ '_fragment_' + fragment.name ] = fragment.prog
return fragment
def findFragment( self, key ):
return self.fragments[key.replace( "-", "_" ) ]
def execFragment( self, fragment, write = sys.stdout.write, env = {} ):
env['write'] = write
if isinstance( fragment, Fragment ):
exec fragment.prog in self.globals, env
else:
exec self.fragments[fragment].prog in self.globals, env
def loadFragment( self, key ):
try:
return self.findFragment( key )
except KeyError, exc:
pass
unresolved = Set()
resolved = Set()
fragments = []
unresolved.add( key )
while len( unresolved ) > 0:
k = unresolved.pop( )
p = k.replace( "_", "-" )
try:
frag = Fragment( file = os.path.join( self.directory, p ) )
except ParseError, exc:
invalid.add( k )
raise exc
else:
fragments.append( frag )
resolved.add( k )
for dep in frag.dependencies:
unresolved.add( dep )
for fragment in fragments:
self.fragments[ fragment.name ] = fragment
self.globals[ '_fragment_' + fragment.name ] = fragment.prog
return self.findFragment( key )
def setGlobal( self, key, value ):
self.globals[ intern( key ) ] = value
if __name__ == '__main__':
l = Lexicon()
l.makeFragment((
"@export html-head",
"<html><head>",
"<title><< title >></title>",
"</head><body>",
"<h2><< title >></h2>"
))
l.makeFragment((
"@export html-tail",
":</body></html>"
))
l.makeFragment((
"@export hello-cells",
".title = 'Hello, Cells!'",
"@import html-head",
"<table>",
".for y in xrange( 1, 3 ):",
" <tr>",
" .for x in xrange( 1, 3 ):",
" <td>Hello << x >>, << y >></td>",
" </tr>",
"</table>",
"@import html-tail"
))
l.execFragment( 'hello_cells' )
|