mirror of https://github.com/abinit/abinit.git
223 lines
12 KiB
Python
Executable File
223 lines
12 KiB
Python
Executable File
"Yet Another Python Templating Utility, Version 1.2. Taken from http://code.activestate.com/recipes/52305/"
|
|
from __future__ import print_function, division, absolute_import #, unicode_literals
|
|
|
|
import sys
|
|
|
|
from .six import exec_
|
|
|
|
#def exec_(stat, globals, locals):
|
|
# """Comment the last line if you are using python3."""
|
|
# # Py2 version
|
|
# exec stat in globals, locals
|
|
# # Py3 version
|
|
# #exec(stat, globals, locals)
|
|
|
|
|
|
# utility stuff to avoid tests in the mainline code
|
|
class _nevermatch:
|
|
"Polymorphic with a regex that never matches"
|
|
def match(self, line):
|
|
return None
|
|
_never = _nevermatch() # one reusable instance of it suffices
|
|
def identity(string, why):
|
|
"A do-nothing-special-to-the-input, just-return-it function"
|
|
return string
|
|
def nohandle(string):
|
|
"A do-nothing handler that just re-raises the exception"
|
|
raise
|
|
|
|
# and now the real thing
|
|
class copier:
|
|
"Smart-copier (YAPTU) class"
|
|
def copyblock(self, i=0, last=None):
|
|
"Main copy method: process lines [i,last) of block"
|
|
def repl(match, self=self):
|
|
"return the eval of a found expression, for replacement"
|
|
# uncomment for debug: print '!!! replacing',match.group(1)
|
|
expr = self.preproc(match.group(1), 'eval')
|
|
try: return str(eval(expr, self.globals, self.locals))
|
|
except: return str(self.handle(expr))
|
|
block = self.locals['_bl']
|
|
if last is None: last = len(block)
|
|
while i<last:
|
|
line = block[i]
|
|
match = self.restat.match(line)
|
|
if match: # a statement starts "here" (at line block[i])
|
|
# i is the last line to _not_ process
|
|
stat = match.string[match.end(0):].strip()
|
|
j=i+1 # look for 'finish' from here onwards
|
|
nest=1 # count nesting levels of statements
|
|
while j<last:
|
|
line = block[j]
|
|
# first look for nested statements or 'finish' lines
|
|
if self.restend.match(line): # found a statement-end
|
|
nest = nest - 1 # update (decrease) nesting
|
|
if nest==0: break # j is first line to _not_ process
|
|
elif self.restat.match(line): # found a nested statement
|
|
nest = nest + 1 # update (increase) nesting
|
|
elif nest==1: # look for continuation only at this nesting
|
|
match = self.recont.match(line)
|
|
if match: # found a contin.-statement
|
|
nestat = match.string[match.end(0):].strip()
|
|
stat = '%s _cb(%s,%s)\n%s' % (stat,i+1,j,nestat)
|
|
i=j # again, i is the last line to _not_ process
|
|
j=j+1
|
|
stat = self.preproc(stat, 'exec')
|
|
stat = '%s _cb(%s,%s)' % (stat,i+1,j)
|
|
# for debugging, uncomment...: print "-> Executing: {"+stat+"}"
|
|
exec_(stat, self.globals, self.locals)
|
|
i=j+1
|
|
else: # normal line, just copy with substitution
|
|
self.ouf.write(self.regex.sub(repl,line))
|
|
i=i+1
|
|
def __init__(self, regex=_never, dict={},
|
|
restat=_never, restend=_never, recont=_never,
|
|
preproc=identity, handle=nohandle, ouf=sys.stdout):
|
|
"Initialize self's attributes"
|
|
self.regex = regex
|
|
self.globals = dict
|
|
self.locals = { '_cb':self.copyblock }
|
|
self.restat = restat
|
|
self.restend = restend
|
|
self.recont = recont
|
|
self.preproc = preproc
|
|
self.handle = handle
|
|
self.ouf = ouf
|
|
def copy(self, block=None, inf=sys.stdin):
|
|
"Entry point: copy-with-processing a file, or a block of lines"
|
|
if block is None: block = inf.readlines()
|
|
self.locals['_bl'] = block
|
|
self.copyblock()
|
|
|
|
if __name__=='__main__':
|
|
"Test: copy a block of lines, with full processing"
|
|
import re
|
|
rex=re.compile('@([^@]+)@')
|
|
rbe=re.compile(r'\+')
|
|
ren=re.compile('-')
|
|
rco=re.compile('= ')
|
|
x=23 # just a variable to try substitution
|
|
cop = copier(rex, globals(), rbe, ren, rco)
|
|
lines_block = [line+'\n' for line in """
|
|
A first, plain line -- it just gets copied.
|
|
A second line, with @x@ substitutions.
|
|
+ x+=1 # non-block statements MUST end with comments
|
|
-
|
|
Now the substitutions are @x@.
|
|
+ if x>23:
|
|
After all, @x@ is rather large!
|
|
= else:
|
|
After all, @x@ is rather small!
|
|
-
|
|
+ for i in range(3):
|
|
Also, @i@ times @x@ is @i*x@.
|
|
-
|
|
One last, plain line at the end.""".split('\n')]
|
|
print("*** input:")
|
|
print(''.join(lines_block))
|
|
print("*** output:")
|
|
cop.copy(lines_block)
|
|
|
|
|
|
__doc__ = """
|
|
We may often want to copy some "template" text (normally from an input file) to an output
|
|
file-like object, while expanding Python expressions (and possibily executing Python
|
|
statements, e.g. for selection or repetition) that may be "embedded" in the template text.
|
|
|
|
YAPTU is a small but complete Python module for this purpose, suitable for processing most
|
|
any kind of structured-text input, since it lets client-code decide which
|
|
regular-expressions will denote embedded Python expressions and/or statements (so, such
|
|
re's can be selected to avoid conflicting with whatever syntax is needed by the kind of
|
|
structured-text that is being processed -- be it HTML, a programming language, RTF, ...).
|
|
|
|
The compiled-re object that identifies expressions, if not None, is used for a .sub on
|
|
each line of the input; for each MatchObject "match" that results, match.group(1) is
|
|
eval'd as a Python expression, and the result, transformed to a string, gets substituted
|
|
in place; a dictionary to be used as the namespace for the evaluation must also be passed
|
|
as an argument. Many such (non-overlapping!) matches per line are possible, but the
|
|
resulting text is NOT re-scanned for 'embeddings'.
|
|
|
|
Python statements can also be embedded; this is mostly intended to be used with
|
|
if/elif/else, for, while, and is line-based. Statement-related lines are recognized
|
|
through three more regular-expression objects that are passed in, one each for
|
|
'statement', 'continuation', 'finish', to be used for regular-expression _match_ (i.e.,
|
|
from line start) [again, each can be None if no such statements are to be embedded].
|
|
|
|
The 'stat' and 'cont' re's are followed by the corresponding statement lines (beginning
|
|
statement, and continuation statement, respectively -- the latter normally makes sense
|
|
only if it's an 'else' or 'elif'). Statements can nest without limits.
|
|
|
|
If a statement must be embedded that does NOT end with a colon (e.g., an assignment
|
|
statement), then a Python comment MUST terminate its line; conversely, such comments are
|
|
NOT allowed on the kind of statements most often embedded (if, else, for, while) --
|
|
_their_ lines must terminate with their ':' (optionally followed by whitespace). This
|
|
peculiarity is due to the somewhat tricky technique used in YAPTU's implementation:
|
|
embedded statements (with their continuations) are exec'd with _recursive calls to yaptu's
|
|
copyblock function_ substituted in place of the blocks of template-text they contain,
|
|
taking advantage of the fact that such a single-statement "suite" can be placed on the
|
|
same line as the controlling statement, right after the colon, and this avoids any
|
|
whitespace-issue (yaptu does NOT rely on whitespace to discern embedded-statement
|
|
structure, but on the explicit statement/continuation/end markers!).
|
|
|
|
Net of comments, whitespace, and docstrings, YAPTU is just 50 source-lines of code, but
|
|
rather a lot happens within that small compass. Instances of the _nevermatch auxiliary
|
|
class are used in lieu of regular-expression objects that are passed in as 'None' -- their
|
|
polymorphism with compiled-re objects (regarding the only two methods of the latter that
|
|
yaptu uses, .sub and .match) saves quite a few tests in the main body of code, and
|
|
simplifies it -- a good general idiom to keep in mind.
|
|
|
|
An instance of the 'copier' class has a certain amount of state, besides the relevant
|
|
compiled-re's (or nevermatch instances) and the output file-like object being used (the
|
|
latter need only implement method .write), that is held in two dictionary attributes --
|
|
self.globals, the dictionary that was originally passed in for expression-substitution,
|
|
and self.locals, another dictionary which is used as the local-namespace for all of
|
|
yaptu's exec and eval uses. Two internal-use-only items in self.locals, in particular
|
|
(with names starting with _) indicate the block of template-text being 'copied' (a
|
|
sequence of lines, each ending in a '\n'), at key '_bl', and the bound-method that
|
|
performs the copying, self.copyblock, at key '_cb'.
|
|
|
|
Holding these two pieces of state in self.locals items is not quaint personal usage --
|
|
it's part of the key to yaptu's workings, since self.locals is what is guaranteed to be
|
|
made available to the code that yaptu exec's (self.globals, too, but yaptu does NOT dirty
|
|
THAT dictionary -- it is owned by its caller!). Since .copyblock must be recursive (the
|
|
simplest way to ensure no nesting limitations), it is important that nested recursive
|
|
calls be always able to further recurse, if needed, through their exec statements! Access
|
|
to _bl is similarly necessary -- .copyblock only takes as arguments the line _indices_
|
|
inside _bl that a given recursive call is processing (in the usual form -- index of first
|
|
line to process, index of first following line to AVOID processing; i.e.,
|
|
lower-bound-included, upper-bound-excluded, as everywhere in Python).
|
|
|
|
copyblock's 32 SLOCs are the heart of YAPTU. The repl nested function is the one that is
|
|
passed to the .sub method of compiled RE objects to get the text to be used for each
|
|
expression substitution -- it uses eval on the expression string, and str() on the result
|
|
to ensure it, too, is turned back into a string. Most of copyblock is a simple while loop
|
|
that examines relevant block lines from the start; when it doesn't match a statement-start
|
|
RE, it copies the line out to the output file-object, with substitutions; when it does
|
|
match statement-start, it enters a smaller nested-loop looking for statement continuations
|
|
and statement-end (with proper accounting for nesting-levels, of course!). As it goes, it
|
|
builds up in local variable 'stat' a string, with the original statement [and possibly its
|
|
continuations at the same nesting-level] followed by a recursive call to _cb(i,j) right
|
|
after each semicolon [with newlines as separators between continuations, if any]. Lastly,
|
|
'stat' gets passed to the exec statement; the nested loop terminates, and the main loop
|
|
resumes from right after the embedded-statement just processed. Note that the
|
|
exec-statement will inevitably invoke copyblock recursively, but that does not disturb the
|
|
loops' state [based on local variables unoriginally named i and j, since they are
|
|
loop-counters and indices on the _bl list...], thanks to perfectly normal
|
|
recursive-invocation mechanisms.
|
|
|
|
Are the slightly-tricky subtleties in yaptu justified -- or does it violate the Prime
|
|
Directive, to "do the simplest thing that can possibly work"? I lean towards the former
|
|
opinion -- that there is no _gratuitous_ subtlety in yaptu, that it uses the minimal
|
|
amount of trickiness compatible with doing its job sensibly -- its job being to supply a
|
|
reusable templating utility, flexible, effective, and small, for a variety of actual and
|
|
potential uses; I can be biased, but I can see no _substantial_ simplification that could
|
|
be made for the sake of clarity without impairing functionality. However, your comments
|
|
(and proposed rewrites!) are welcome, of course!
|
|
|
|
Late-breaking additions: all expressions and statements may now be "preprocessed" by
|
|
passing an optional callable "preproc" when creating the copier -- default is no
|
|
preprocessing. Exceptions in expressions (only) may be handled by passing an optional
|
|
callable "handle" (default is re-raising the exception, which terminates YAPTU's
|
|
processing and propagates outwards).
|
|
"""
|