Source code for pyexpander.lib

"""The main pyexpander library.
"""

# pylint: disable=too-many-lines

import os
import os.path
import inspect

import sys
import locale
import keyword

if __name__ == "__main__":
    # if this module is directly called like a script, we have to add the path
    # ".." to the python search path in order to find modules named
    # "pyexpander.[module]".
    sys.path.append("..")

# pylint: disable=wrong-import-position
# pylint: disable=invalid-name
# pylint: disable=consider-using-f-string

import pyexpander.parser as EP

__version__= "2.1.2" #VERSION#
assert __version__==EP.__version__

# ---------------------------------------------
# constants
# ---------------------------------------------

PY_KEYWORDS= set(keyword.kwlist)

# line separator, python converts line endings always to '\n' no matter what
# the operating system is. So it seems that we can safely use '\n' instead of
# os.linesep here:
LINESEP='\n'

# length of line separator
LINESEP_LEN= len(LINESEP)

SYS_DEFAULT_ENCODING= locale.getpreferredencoding()

# INPUT_DEFAULT_ENCODING may be changed by expander.py:
INPUT_DEFAULT_ENCODING= SYS_DEFAULT_ENCODING

PURE_CMD_KEYWORDS= set([ \
                        "else",
                        "endif",
                        "endfor",
                        "endwhile",
                        "endmacro",
                        "begin",
                        "end",
                       ])

CMD_KEYWORDS=      set([ \
                        "py",
                        "include",
                        "include_begin",
                        "template",
                        "subst",
                        "pattern",
                        "default",
                        "if",
                        "elif",
                        "for",
                        "for_begin",
                        "while",
                        "while_begin",
                        "macro",
                        "nonlocal",
                        "extend",
                        "extend_expr",
                       ])

KEYWORDS= PURE_CMD_KEYWORDS | CMD_KEYWORDS | PY_KEYWORDS

# ---------------------------------------------
# dump utilities
# ---------------------------------------------

def _set2str(val):
    """convert an iterable to the repr string of a set."""
    elms= sorted(list(val))
    return "set(%s)" % repr(elms)

def _pr_set(val):
    """print an iterable as the repr string of a set."""
    print(_set2str(val))

[docs]def find_file(filename, include_paths): """find a file in a list of include paths. include_paths MUST CONTAIN "" in order to search the local directory. """ if include_paths is None: return None for path in include_paths: p= os.path.join(path, filename) if os.path.exists(p): if os.access(p, os.R_OK): return p print("warning: file \"%s\" found but it is not readable" % \ p) return None
# --------------------------------------------- # helper functions # ---------------------------------------------
[docs]def keyword_check(identifiers): """indentifiers must not be identical to keywords. This function may raise an exception. """ s= set(identifiers).intersection(KEYWORDS) if s: lst= ", ".join(["'%s'" % e for e in sorted(s)]) raise ValueError("keywords %s cannot be used as identifiers" % lst)
_valid_encodings= set()
[docs]def test_encoding(encoding): """test if an encoding is known. raises (by encode() method) a LookupError exception in case of an error. """ if encoding in _valid_encodings: return "a".encode(encoding) # may raise LookupError _valid_encodings.add(encoding)
[docs]def one_or_two_strings(arg): """arg must be a single string or a tuple of two strings.""" if isinstance(arg, str): return (arg, None) if not isinstance(arg, tuple): raise TypeError("one or two strings expected") if len(arg)>2: raise TypeError("too many arguments (only 2 allowed)") if not isinstance(arg[0], str): raise TypeError("1st argument must be a string") if not isinstance(arg[1], str): raise TypeError("2nd argument must be a string") return arg
[docs]def merge_dependecies(deps, new_deps): """merge contents of two dependency dicts. These are the "file_deps" as they are returned by functions like processToList. """ for k, v in new_deps.items(): if k in deps: deps[k].update(v) else: deps[k]= v
# --------------------------------------------- # parse a string or a file # ---------------------------------------------
[docs]def parseString(st): """parse a string.""" return EP.parseAll(EP.IndexedString(st), 0)
[docs]def parseFile(filename, encoding, no_stdin_warning): """parse a file.""" if filename is None: if not no_stdin_warning: sys.stderr.write("(reading from stdin)\n") try: st= sys.stdin.read() except KeyboardInterrupt: sys.exit(" interrupted\n") else: exc= None try: with open(filename, mode="rt", encoding=encoding) as f: st= f.read() except (IOError, UnicodeDecodeError) as e: exc= e if exc is not None: # we cannot re-raise UnicodeDecodeError since it needs 5 # undocumented argiments and we just want an error message here # that includes the filename: raise IOError("File %s: %s" % (repr(filename), str(exc))) return parseString(st)
# --------------------------------------------- # Result text class # ---------------------------------------------
[docs]class ResultText: """basically a list of strings with a current column property. """ def __init__(self): """initialize the object.""" self._list= [] self._column= -1
[docs] @staticmethod def current_column(st): r"""find current column if the string is printed. Note: With a string ending with '\n' this returns 1. Here are some examples: >>> ResultText.current_column("") -1 >>> ResultText.current_column("ab") -1 >>> ResultText.current_column("\nab") 3 >>> ResultText.current_column("\nab\n") 1 >>> ResultText.current_column("\nab\na") 2 """ idx= st.rfind(LINESEP) if idx<0: # not found return -1 # unknown column return len(st)-idx
[docs] def append(self, text): """append some text.""" c= self.__class__.current_column(text) if c<0: if self._column>0: self._column+= len(text) else: self._column= c self._list.append(text)
[docs] def list_(self): """return internal list.""" return self._list
[docs] def column(self): """return current column.""" return self._column
# --------------------------------------------- # process parse-list # ---------------------------------------------
[docs]class Block: """class that represents a block in the expander language. Each block has a parent-pointer, which may be None. Blocks are used to represent syntactical blocks in the expander language. They manage the global variable dictionary and some more properties that are needed during execution of the program. """ # pylint: disable=too-many-instance-attributes # pylint: disable=too-many-public-methods
[docs] def posmsg(self, msg=None, pos=None): """return a message together with a position. This method is mainly used for error messages. We usually want to print filename and the line and column number together with a message. This method returns the given message string <msg> together with this additional information. """ parts=[] if msg is not None: parts.append(msg) if self.filename is not None: parts.append("file \"%s\"" % self.filename) try: # posmsg is often called with pos==None. So we usually have to # determine the position ourselves. Usually posmsg is called when # an error occurs when processing self.parse_list[self.lst_pos]. So # if we call method rowcol(None) with this element (a ParsedItem # object) the *start* of the ParsedItem is calculated to a row and # a column and returned. p_elm= self.parse_list[self.lst_pos] parts.append("line %d, col %d" % p_elm.rowcol(pos)) except IndexError as _: parts.append("unknown position") return " ".join(parts)
def _append(self, lst, name, val=None): """append a line with property information to a list. This method is used to create an object dump in method _strlist. """ def elm2str(e): """convert an item to a "repr" like string.""" if isinstance(e,set): return _set2str(e) return str(e) if val is not None: lst.append(" %-20s: %s" % (name,val)) else: lst.append(" %-20s= %s" % (name,elm2str(getattr(self,name)))) def _strlist(self): """utility for __str__. This function can be used by descendent classes in order to implement the __str__ method in a simple way. Tested by the testcode in __str__. """ lst= [] self._append(lst, "has parent",self.previous is not None) self._append(lst, "filename") self._append(lst, "template") self._append(lst, "template_path") self._append(lst, "template_encoding") self._append(lst, "has template_parselist_cache", \ self.template_parselist_cache is not None) self._append(lst, "exported_syms") self._append(lst, "direct_vars") self._append(lst, "direct_funcs") self._append(lst, "macros", repr(sorted(self.macros.keys()))) self._append(lst, "new_scope") self._append(lst, "skip") self._append(lst, "safe_mode") self._append(lst, "indent") self._append(lst, "new_parse_list") self._append(lst, "lst_pos") self._append(lst, "start_pos") if self.lst_pos>=0 and self.lst_pos<len(self.parse_list): self._append(lst, "current parse elm", self.parse_list[self.lst_pos]) return lst def __init__(self, previous= None, new_scope= False, filename= None, parse_list=None, external_definitions=None): """The Block constructor. Properties of a Block: parse_list -- the list with parse objects new_parse_list -- a bool, True if the parselist was given to the constructor as parameter and was not taken from the 'previous' Block. lst_pos -- the position in the parse_list start_pos -- the start position of the block in the parse_list previous -- the previous block new_scope -- True if the block is also a new scope with respect to global variables. indent -- current automatic indent level, the default is 0 skip -- if True, skip a block in the text when the code is interpreted safe_mode -- if True, allow only "safe" commands that have no side effects. $() may only contain variable names, py() is disabled and all commands enabled with $extend() are disabled. globals_ -- a dictionary with the current global variables of the interpreter. exported_syms -- a list of symbols (names in globals_) that are copied to the globals_ dictionary of the parent block when the pop() method is called. filename -- name of the interpreted file, may be None if the filename is unknown. file_deps -- a dict mapping str to set. For all Block objects created so far, shows which file depends of what other files. template -- name of the file that "$subst" would include, usually None template_path -- complete path of template usually None template_encoding -- encoding of the template file usually None template_parselist_cache -- parselist of the template, usually None. This is a kind of optimization direct_vars -- a set that contains names of pyexpander variables that can be used directly like '$my_var'. direct_funcs -- a set that contains names of pyexpander functions that can be used directly like '$my_func()' macros -- a dict mapping macro names to macro Block objects. Here is an example: >>> b= Block(parse_list=[]) >>> print(b) Block{ has parent : False filename = None template = None template_path = None template_encoding = None has template_parselist_cache: False exported_syms = [] direct_vars = set([]) direct_funcs = set([]) macros : [] new_scope = False skip = False safe_mode = False indent = 0 new_parse_list = True lst_pos = -1 start_pos = 0 } >>> b= Block(b,True,parse_list=[]) >>> b.print_block_list() Block{ has parent : False filename = None template = None template_path = None template_encoding = None has template_parselist_cache: False exported_syms = [] direct_vars = set([]) direct_funcs = set([]) macros : [] new_scope = False skip = False safe_mode = False indent = 0 new_parse_list = True lst_pos = -1 start_pos = 0 } Block{ has parent : True filename = None template = None template_path = None template_encoding = None has template_parselist_cache: False exported_syms = [] direct_vars = set([]) direct_funcs = set([]) macros : [] new_scope = True skip = False safe_mode = False indent = 0 new_parse_list = True lst_pos = -1 start_pos = 0 } """ # pylint: disable=too-many-arguments, too-many-branches # pylint: disable=too-many-statements # Variable that indicates that globals_["__file__"] must be set: must_change_filevar= False if (filename is None) or (filename==""): self.filename= None else: self.filename= filename self.previous= previous if previous is None: self.new_scope= False self.skip= False self.safe_mode= False self.indent= 0 self.globals_= {} # set "magical" filename variable: self.globals_["__file__"]= self.get_filename() self.direct_vars=set() self.direct_funcs=set() self.macros= {} self.exported_syms=[] self.file_deps= { self.filename: set() } if parse_list is None: raise AssertionError("without previous, parse_list "+\ "is mandatory") self.new_parse_list= True self.template= None self.template_path= None self.template_encoding= None self.template_parselist_cache= None self.parse_list= parse_list self.lst_pos= -1 self.start_pos= 0 else: if not isinstance(previous,Block): raise AssertionError("previous is not a block: %s" % \ str(previous)) self.file_deps= previous.file_deps if self.filename is None: self.filename= previous.filename else: if self.filename != previous.filename: self.file_deps[previous.filename].add(self.filename) self.file_deps.setdefault(self.filename, set()) must_change_filevar= True if parse_list is None: self.new_parse_list= False self.parse_list= previous.parse_list self.lst_pos= previous.lst_pos if previous.lst_pos<0: self.start_pos= 0 else: self.start_pos= previous.lst_pos else: self.new_parse_list= True self.parse_list= parse_list self.lst_pos= -1 self.start_pos= 0 self.template= previous.template self.template_path= previous.template_path self.template_encoding= previous.template_encoding self.template_parselist_cache= previous.template_parselist_cache self.new_scope= new_scope self.skip= previous.skip self.safe_mode= previous.safe_mode self.indent= previous.indent if new_scope: self.globals_= dict(previous.globals_) self.direct_vars= set(previous.direct_vars) self.direct_funcs= set(previous.direct_funcs) self.macros= dict(previous.macros) self.exported_syms=[] else: self.globals_= previous.globals_ self.direct_vars= previous.direct_vars self.direct_funcs= previous.direct_funcs self.macros= previous.macros self.exported_syms= previous.exported_syms if must_change_filevar: # Set "magical" filename variable. When the new block and the # previous block share the same globals_ dict the we must ensure # that globals_["__file__"] is always the same as the block's # filename. self.globals_["__file__"]= self.get_filename() if external_definitions is not None: for (k,v) in list(external_definitions.items()): self.globals_[k]= v
[docs] def parse_loop(self): """loop across the items in the Block. returns False if there is nothing more to parse in the current block. """ self.lst_pos+= 1 return self.lst_pos < len(self.parse_list)
[docs] def parse_elm(self): """get the current parse element.""" return self.parse_list[self.lst_pos]
[docs] def eval_(self, st): """perform eval with the global variable dictionary of the block. Here is an example: >>> b= Block(parse_list=[]) >>> b.exec_("a=2") >>> b.eval_("3*a") 6 """ e= None try: # pylint: disable=eval-used return eval(st, self.globals_) except (SyntaxError, NameError, IndexError, TypeError) as _e: e= _e except Exception as _: sys.stderr.write("error at %s:\n" % self.posmsg()) raise if e is not None: # pylint: disable=raising-non-exception raise e.__class__("%s at %s" % (str(e), self.posmsg())) return None # never reached, makes pylint happy...
[docs] def str_eval(self, st): """perform eval with the global variable dictionary of the block. Here is an example: >>> b= Block(parse_list=[]) >>> b.exec_("a=2") >>> b.str_eval("3*a") '6' """ val= self.eval_(st) try: return str(val) except Exception as _: sys.stderr.write("error at %s:\n" % self.posmsg()) raise
[docs] def exec_(self, st): """perform exec with the global variable dictionary of the block. Here is an example: >>> b= Block(parse_list=[]) >>> b.exec_("a=1") >>> b["a"] 1 >>> b= Block(b,True) >>> b["a"] 1 >>> b.exec_("a=2") >>> b["a"] 2 >>> b.previous["a"] 1 """ e= None try: # pylint: disable=exec-used exec(st, self.globals_) except (SyntaxError, NameError, IndexError, TypeError) as _e: e= _e except Exception: sys.stderr.write("error at %s:\n" % self.posmsg()) raise if e is not None: # pylint: disable=raising-non-exception raise e.__class__("%s at %s" % (str(e), self.posmsg()))
def __getitem__(self, name): """looks up a value in the globals_ dictionary of the block. Here is an example: >>> b= Block(parse_list=[]) >>> b.globals_["a"]= 5 >>> b["a"] 5 Here we show how symbols with dots '.' are treated. We misuse os.path to create a variable os.path.myvar and store a reference to the module "os" in the Block "globals_" dictionary: >>> import os.path >>> os.path.myvar=100 >>> b.globals_["os"]= os >>> b["os.path.myvar"] 100 """ e= None try: # pylint: disable=eval-used return eval(name, self.globals_) except NameError as _e: e= _e if e is not None: # pylint: disable=raising-non-exception raise e.__class__("%s at %s" % (str(e), self.posmsg())) return None # never reached, makes pylint happy... def __setitem__(self, name, val): """sets a value in the globals_ dictionary of the block. Here is an example: >>> b= Block(parse_list=[]) >>> b["a"]= 5 >>> b.globals_["a"] 5 Here we show how symbols with dots '.' are treated. We misuse os.path to create a variable os.path.myvar and store a reference to the module "os" in the Block "globals_" dictionary: >>> import os.path >>> b["os"]= os >>> b["os.path.myvar"]= 200 >>> os.path.myvar 200 >>> b["os.path.myvar"] 200 """ self.globals_["__pyexpander_buffer"]= val e= None try: # pylint: disable=exec-used exec("%s= __pyexpander_buffer" % name, self.globals_) except AttributeError as _e: e= _e if e is not None: # pylint: disable=raising-non-exception raise e.__class__("%s at %s" % (str(e), self.posmsg())) def __delitem__(self, name): """deletes a value in the globals_ dictionary of the block. Here is an example: >>> import pprint >>> b= Block(parse_list=[]) >>> sorted(b.globals_.keys()) ['__file__'] >>> b["a"]= 5 >>> sorted(b.globals_.keys()) ['__builtins__', '__file__', '__pyexpander_buffer', 'a'] >>> b["a"] 5 >>> del b["a"] >>> sorted(b.globals_.keys()) ['__builtins__', '__file__', '__pyexpander_buffer'] >>> b["a"] Traceback (most recent call last): ... NameError: name 'a' is not defined at unknown position >>> del b["c"] Traceback (most recent call last): ... NameError: name 'c' is not defined at unknown position Here we show how symbols with dots '.' are treated. We use a dummy class 'T' to create a variable main.sub.myvar and store a reference to it in the Block's "globals_" dictionary: >>> class T(object): ... def __init__(self): ... pass ... >>> main= T() >>> main.sub= T() >>> main.sub.myvar=300 >>> b= Block(parse_list=[]) >>> b["main"]= main >>> b["main.sub.myvar"] 300 >>> del b["main.sub.myvar"] >>> b["main.sub.myvar"] Traceback (most recent call last): ... AttributeError: 'T' object has no attribute 'myvar' >>> main.sub.myvar Traceback (most recent call last): ... AttributeError: 'T' object has no attribute 'myvar' """ e= None try: # pylint: disable=exec-used exec("del %s" % name, self.globals_) except NameError as _e: e= _e if e is not None: # pylint: disable=raising-non-exception raise e.__class__("%s at %s" % (str(e), self.posmsg())) def __len__(self): """returns the number of items in the block. Here is an example: >>> b= Block(parse_list=[]) >>> sorted(b.globals_.keys()) ['__file__'] >>> b["a"]= 5 >>> b["b"]= 6 >>> sorted(b.globals_.keys()) ['__builtins__', '__file__', '__pyexpander_buffer', 'a', 'b'] >>> len(b) 5 """ return len(self.globals_)
[docs] def setdefault(self, name, val): """return a value from globals, set to a default if it's not defined. Here are some examples: >>> b= Block(parse_list=[]) >>> b["a"] Traceback (most recent call last): ... NameError: name 'a' is not defined at unknown position >>> b.setdefault("a",10) 10 >>> b["a"] 10 >>> b["a"]=11 >>> b.setdefault("a",10) 11 """ try: return self.__getitem__(name) except NameError as _: self.__setitem__(name,val) return val
[docs] def set_substfile(self, filename, encoding, tp): """set substitution filename. Used for the "$subst", "$template" and "$pattern" commands. """ if not isinstance(filename, str): raise EP.ParseException( \ self.posmsg("filename must be a string at", pos=tp.start())) if filename == self.template: return self.template_parselist_cache= None self.template_path= None self.template= filename self.template_encoding= encoding
[docs] def substfile_parselist(self, include_paths): """return the parselist for a given file. This method manages a cache of that assigns filenames to parselists. This can speed things up if a file is included several times. """ if self.template_parselist_cache is not None: return (self.template_path,self.template_parselist_cache) if self.template is None: raise ValueError(self.posmsg("substitition file name missing at")) self.template_path= find_file(self.template, include_paths) if self.template_path is None: raise ValueError(self.posmsg("file \"%s\" not found in" % \ self.template)) self.template_parselist_cache= parseFile(self.template_path, self.template_encoding, False) return (self.template_path,self.template_parselist_cache)
[docs] def export_symbols(self, lst): """appends items to the export_symbols list. This list is used by the pop() method in order to copy values from the current global variable dictionary to the global variable dictionary of the previous block. Here is an example: >>> b= Block(parse_list=[]) >>> b.export_symbols(["a","b"]) >>> b.exported_syms ['a', 'b'] >>> b.export_symbols(["d","e"]) >>> b.exported_syms ['a', 'b', 'd', 'e'] """ self.exported_syms.extend(lst)
[docs] def extend(self, lst): """adds items to the list of expander functions or variables. Here is an example: >>> a=1 >>> b=2 >>> def t(x): ... return x+1 ... >>> block= Block(parse_list=[],external_definitions=globals()) >>> block.extend(["a","b","t"]) >>> _pr_set(block.direct_vars) set(['a', 'b']) >>> _pr_set(block.direct_funcs) set(['t']) """ for elm in lst: obj= self.globals_[elm] if inspect.isbuiltin(obj): self.direct_funcs.add(elm) continue if inspect.isfunction(obj): self.direct_funcs.add(elm) continue # assume elm to be a variable: self.direct_vars.add(elm)
[docs] def set_safemode(self, value): """sets the safe_mode.""" self.safe_mode= value
[docs] def get_filename(self): """get the filename of the block, return '' instead of None.""" if self.filename is None: return "" return self.filename
[docs] def set_indent(self, value): """sets the indent.""" self.indent= value
[docs] def get_indent(self): """gets the indent.""" return self.indent
[docs] def format_text(self, text, indent_start): """currently does indent one or more lines.""" if text=="": return text if self.indent<=0: return text ind= " "*self.indent cut= False if text.endswith(LINESEP): cut= True text= text.replace(LINESEP, LINESEP+ind) if cut: text= text[0:-self.indent] if not indent_start: return text return ind+text
[docs] def add_macro(self, name, macro_block): """add a new macro block. """ if not isinstance(macro_block, MacBlock): raise AssertionError("wrong type: %s has type %s" % \ (name, type(macro_block))) self.macros[name]= macro_block
[docs] def pop(self): """removes the current block and returns the previous one. Here is an example: >>> b= Block(parse_list=[]) >>> b["a"]=1 >>> b["b"]=2 >>> b= Block(b,True) >>> b["a"]=10 >>> b["b"]=20 >>> b.export_symbols(["a"]) >>> b= b.pop() >>> b["a"] 10 >>> b["b"] 2 """ if self.previous is None: raise AssertionError(self.posmsg("block underflow (assertion) at")) if self.new_scope: old= self.previous.globals_ for elm in self.exported_syms: old[elm]= self.globals_[elm] # set the lst_pos in the parent Block: if not self.new_parse_list: self.previous.lst_pos= self.lst_pos if self.filename != self.previous.filename: # Set "magical" filename variable to the correct value. This is # needed when the blocks share the same globals_ dict. # globals_["__file__"] must *always* be the same as the block's # filename. self.previous.globals_["__file__"]= self.previous.get_filename() return self.previous
def __str__(self): """returns a string representation of the block. Here is an example: >>> b= Block(parse_list=[]) >>> print(b) Block{ has parent : False filename = None template = None template_path = None template_encoding = None has template_parselist_cache: False exported_syms = [] direct_vars = set([]) direct_funcs = set([]) macros : [] new_scope = False skip = False safe_mode = False indent = 0 new_parse_list = True lst_pos = -1 start_pos = 0 } >>> b= Block(b,True) >>> print(b) Block{ has parent : True filename = None template = None template_path = None template_encoding = None has template_parselist_cache: False exported_syms = [] direct_vars = set([]) direct_funcs = set([]) macros : [] new_scope = True skip = False safe_mode = False indent = 0 new_parse_list = False lst_pos = -1 start_pos = 0 } """ lst=["%s{" % "Block"] lst.extend(self._strlist()) lst.append("}") return "\n".join(lst)
[docs] def get_block_list(self): """returns all blocks of the list. The list is returned with the oldest block first. """ lst=[] block= self while block is not None: lst.append(block) block= block.previous lst.reverse() return lst
[docs] def str_block_list(self): """returns a string representation of all blocks in the list. The list is returned with the oldest block first. """ return [str(elm) for elm in self.get_block_list()]
[docs] def print_block_list(self): """print all blocks in the list. The list is returned with the oldest block first. Here is an example: >>> b= Block(parse_list=[]) >>> print(b) Block{ has parent : False filename = None template = None template_path = None template_encoding = None has template_parselist_cache: False exported_syms = [] direct_vars = set([]) direct_funcs = set([]) macros : [] new_scope = False skip = False safe_mode = False indent = 0 new_parse_list = True lst_pos = -1 start_pos = 0 } >>> b= Block(b,True) >>> print(b) Block{ has parent : True filename = None template = None template_path = None template_encoding = None has template_parselist_cache: False exported_syms = [] direct_vars = set([]) direct_funcs = set([]) macros : [] new_scope = True skip = False safe_mode = False indent = 0 new_parse_list = False lst_pos = -1 start_pos = 0 } >>> b.print_block_list() Block{ has parent : False filename = None template = None template_path = None template_encoding = None has template_parselist_cache: False exported_syms = [] direct_vars = set([]) direct_funcs = set([]) macros : [] new_scope = False skip = False safe_mode = False indent = 0 new_parse_list = True lst_pos = -1 start_pos = 0 } Block{ has parent : True filename = None template = None template_path = None template_encoding = None has template_parselist_cache: False exported_syms = [] direct_vars = set([]) direct_funcs = set([]) macros : [] new_scope = True skip = False safe_mode = False indent = 0 new_parse_list = False lst_pos = -1 start_pos = 0 } """ print("\n".join(self.str_block_list()))
[docs]class IncludeBlock(Block): """implements a $include(filename) block. This block is simply a variable scope, so it is derived from Block where the constructor is called with new_scope=True. """ def __init__(self, previous= None, new_scope= False, filename= None, encoding= SYS_DEFAULT_ENCODING, include_paths=None): # pylint: disable=too-many-arguments # include_dict: a dictionary to store include-dependencies path= find_file(filename, include_paths) if path is None: if previous is None: raise AssertionError("previous is None (shouldn't happen)") raise ValueError(previous.posmsg("file \"%s\" not found in" % \ filename)) parse_list= parseFile(path, encoding, False) Block.__init__(self, previous, new_scope, path, parse_list) def __str__(self): lst=["%s{" % "IncludeBlock"] lst.extend(self._strlist()) lst.append("}") return "\n".join(lst)
[docs]class SubstBlock(Block): """implements a $subst(parameters) block. This block is simply a variable scope, so it is derived from Block where the constructor is called with new_scope=True. """ def __init__(self, previous= None, filename= None, include_paths=None, external_definitions=None): (path,parse_list)= previous.substfile_parselist(include_paths) Block.__init__(self, previous, True, # always new scope path, parse_list, external_definitions) if path is None: raise ValueError(self.posmsg("file \"%s\" not found in" % filename)) def __str__(self): lst=["%s{" % "SubstBlock"] lst.extend(self._strlist()) lst.append("}") return "\n".join(lst)
[docs]class PatternBlock(Block): """implements a $pattern(parameters) block. This block is simply a variable scope, so it is derived from Block where the constructor is called with new_scope=True. """ def _strlist(self): lst= Block._strlist(self) self._append(lst, "heading") self._append(lst, "lines") self._append(lst, "curr_line") return lst def __init__(self, previous= None, filename= None, include_paths=None, heading=None, lines=None): # pylint: disable=too-many-arguments (path,parse_list)= previous.substfile_parselist(include_paths) Block.__init__(self, previous, False, # no new scope path, parse_list) if path is None: raise ValueError(self.posmsg("file \"%s\" not found in" % filename)) if heading is None: heading= [] if lines is None: lines= [] self.heading= heading self.lines= lines if len(self.lines)<1: raise ValueError(self.posmsg("no instantiation data")) self.curr_line= -1 self.def_vars()
[docs] def pop(self): if not self.def_vars(): return Block.pop(self) # reset position in file self.lst_pos= -1 return self
[docs] def def_vars(self): """define all the given variables in the PatternBlock.""" self.curr_line+=1 if self.curr_line >= len(self.lines): return False line= self.lines[self.curr_line] for i, hd in enumerate(self.heading): self[hd]= line[i] return True
def __str__(self): lst=["%s{" % "PatternBlock"] lst.extend(self._strlist()) lst.append("}") return "\n".join(lst)
[docs]class BeginBlock(Block): """implements a $begin .. $end block. This block is simply a variable scope, so it is derived from Block where the constructor is called with new_scope=True. """ def __init__(self, previous= None, filename= None): Block.__init__(self, previous, True) self.skip= previous.skip def __str__(self): lst=["%s{" % "BeginBlock"] lst.extend(self._strlist()) lst.append("}") return "\n".join(lst)
[docs]class IfBlock(Block): """implements a $if .. $else .. $endif block. An $if block never has a variable scope, so the base Block object is called with new_scope=False. """ def _strlist(self): lst= Block._strlist(self) self._append(lst, "prev_skip") self._append(lst, "in_else_part") self._append(lst, "found") return lst def __init__(self, previous= None, condition= True): """constructs the $if block. condition is the boolean value of the $if condition. """ Block.__init__(self, previous, False) self.prev_skip= previous.skip self.in_else_part= False if condition: self.found= True self.skip= self.prev_skip else: self.found= False self.skip= True
[docs] def enter_elif(self, condition): """enter the "elif" part in the if..endif block.""" if self.found: self.skip= True else: if condition: self.found= True self.skip= self.prev_skip else: self.skip= True
[docs] def enter_else(self): """this should be called when $else is encountered. """ if self.in_else_part: raise EP.ParseException( \ self.posmsg("one \"else\" too many at")) self.in_else_part= True if not self.found: self.skip= self.prev_skip else: self.skip= True
def __str__(self): lst=["%s{" % "IfBlock"] lst.extend(self._strlist()) lst.append("}") return "\n".join(lst)
[docs]class ForBlock(Block): """implements a $for .. $endfor block. """ def _strlist(self): lst= Block._strlist(self) self._append(lst, "value_list") self._append(lst, "index") self._append(lst, "var_expr") self._append(lst, "jump_lst_pos") self._append(lst, "jump parse elm",self.parse_list[self.jump_lst_pos]) return lst def __init__(self, previous= None, new_scope= False, value_list=None, var_expr=""): """constructor of the block. var_expr -- the expression that contains the loop variable or the tuple with the loop variables. """ Block.__init__(self, previous, new_scope) if value_list is None: value_list= [] self.value_list= value_list self.index=0 # current index within self.value_list self.var_expr= var_expr self.jump_lst_pos= self.lst_pos if len(value_list)<=0: self.skip= True else: self.skip= previous.skip
[docs] def set_loop_var(self): """set the loop variable to a new value.""" if not self.skip: self.__setitem__(self.var_expr, self.value_list[self.index])
[docs] def next_loop(self): """performs next loop. returns: True when the loop is not yet finished. """ if self.skip: return False self.index+=1 do_loop= self.index< len(self.value_list) if do_loop: self.lst_pos= self.jump_lst_pos return do_loop
def __str__(self): lst=["%s{" % "ForBlock"] lst.extend(self._strlist()) lst.append("}") return "\n".join(lst)
[docs]class WhileBlock(Block): """implements a $while .. $endwhile block. """ def _strlist(self): lst= Block._strlist(self) self._append(lst, "while_expr") self._append(lst, "jump parse elm",self.parse_list[self.jump_lst_pos]) return lst def __init__(self, previous= None, new_scope= False, while_expr=""): """constructor of the block. while_expr -- the expression that contains the loop variable or the tuple with the loop variables. """ Block.__init__(self, previous, new_scope) self.while_expr= while_expr self.jump_lst_pos= self.lst_pos if while_expr=="": self.skip= True elif not self.eval_(self.while_expr): self.skip= True else: self.skip= previous.skip
[docs] def next_loop(self): """performs next loop. returns: True when the loop is not yet finished. """ if self.skip: return False do_loop= self.eval_(self.while_expr) if do_loop: self.lst_pos= self.jump_lst_pos return do_loop
def __str__(self): lst=["%s{" % "WhileBlock"] lst.extend(self._strlist()) lst.append("}") return "\n".join(lst)
[docs]class MacBlock(Block): """implements a $macro...$endmacro block. """ def _strlist(self): lst= Block._strlist(self) self._append(lst, "parameter_list") self._append(lst, "is_declaration") return lst def __init__(self, previous= None, declaration_block= None, parameter_list= None): """constructor of the block. parameter_list: list of parameters of the macro. """ if declaration_block is None: # THIS is a macro declaration block Block.__init__(self, previous, new_scope= True) if parameter_list is None: parameter_list= [] self.parameter_list= parameter_list self.is_declaration= True self.skip= True else: # THIS is a macro instantiation block Block.__init__(self, previous, new_scope= True) self.parse_list= declaration_block.parse_list self.parameter_list= declaration_block.parameter_list self.lst_pos= declaration_block.start_pos self.is_declaration= False
[docs] def pop(self): """override pop() from base class. """ if self.is_declaration: return Block.pop(self) old_pos= self.previous.lst_pos prev= Block.pop(self) prev.lst_pos= old_pos return prev
def __str__(self): lst=["%s{" % "MacBlock"] lst.extend(self._strlist()) lst.append("}") return "\n".join(lst)
def __pyexpander_helper(*args, **kwargs): """a helper function needed at runtime. This evaluates named arguments. """ if len(args)==1: fn= args[0] elif len(args)>1: raise ValueError("only one unnamed argument is allowed") else: fn= None return(fn, kwargs) def __pyexpander_helper2(**kwargs): """a helper function needed at runtime. This evaluates named arguments. """ return kwargs def __pyexpander_helper3(*args, **kwargs): """a helper function needed at runtime. This evaluates unnamed and named arguments. """ return (args,kwargs)
[docs]def processToList(parse_list, filename=None, external_definitions=None, allow_nobracket_vars= False, auto_continuation= False, auto_indent= False, include_paths= None): """Expand a parse list to a list of strings. args: parse_list: A parse list created by parseString(). filename (str): The filename, if given, is included in possible error messages. external_definitions (dict): A dict with items to import to the globals() dictionary. allow_nobracket_vars (bool): If True, allow variables in the form $VAR instead of $(VAR). auto_continuation (bool): If True, remove newline at the end of lines with a command. This works like having an '\' at the end of each line with a command. auto_indent (bool): If True, indent the contents of macros to the same level as the macro invocation. include_paths (list): A list of paths that are searched for the $include command. returns a tuple containing: - The expanded text as a list of strings - The internal globals() dictionary. - The file_deps dict with file dependencies """ # pylint: disable=too-many-arguments, too-many-locals, too-many-branches # pylint: disable=too-many-statements # accept None for include_paths too: if include_paths is None: include_paths= [] # prepend the cwd to the list of search paths: include_paths.insert(0,"") # The initial block: my_external_definitions= { "__pyexpander_helper": \ globals()["__pyexpander_helper"], "__pyexpander_helper2": \ globals()["__pyexpander_helper2"], "__pyexpander_helper3": \ globals()["__pyexpander_helper3"], } if external_definitions is not None: my_external_definitions.update(external_definitions) # only needed for allow_nobracket_vars==True: # python keywords MUST NOT be checked. E.g. function __getitem__ would # raise an exception when called with "else" since eval("else") is a syntax # error. keyword_checks= PURE_CMD_KEYWORDS - PY_KEYWORDS # Storage for include dependencies, for each file # (or None if the filename is not known) there is a set # of other files this includes. block= Block(filename= filename, parse_list=parse_list, external_definitions= my_external_definitions) result= ResultText() # needed for tp_last: tp= None # pylint: disable=too-many-nested-blocks while True: # print "-" * 40 # block.print_block_list() if not block.parse_loop(): # pylint: disable=no-else-continue # no more data in the current block: if isinstance(block, IncludeBlock): # if current block is an IncludeBlock, go back to previous # block: block= block.pop() continue elif isinstance(block, SubstBlock): # if current block is a SubstBlock, go back to previous block: block= block.pop() continue elif isinstance(block, PatternBlock): # if current block is a PatternBlock, go back to previous # block: block= block.pop() continue else: # end of data, leave the loop: break # get the current parse element (base class: ParsedItem) tp_last= tp tp= block.parse_elm() # print("POS %3d: " % block.lst_pos, "tp: ",str(tp)) if isinstance(tp, EP.ParsedComment): # comments are ignored: continue if isinstance(tp, EP.ParsedLiteral): # literals are only taken if skip mode is off: if not block.skip: st_= tp.string() if auto_continuation and st_.startswith(LINESEP): if isinstance(tp_last, (EP.ParsedCommand, EP.ParsedPureCommand)): st_= st_[LINESEP_LEN:] # if current column is 1, we must do an initial indent: st_= block.format_text(st_, result.column()==1) result.append(st_) continue if isinstance(tp, EP.ParsedVar): # if skip mode is off, insert the current value of the variable. # The current block can be used like a dict in order to get values # of variables: if not block.skip: result.append(str(block[tp.string()])) continue if isinstance(tp, EP.ParsedEval): # if skip mode is off, evaluate the eval expression, # convert it to a string and insert the result: if block.safe_mode: raise EP.ParseException( \ block.posmsg("$(EXPRESSION) not allowed with " "safemode", pos= tp.start())) if not block.skip: result.append(block.str_eval(tp.string())) continue if isinstance(tp, EP.ParsedCommand): # if ParsedItem is a ParsedCommand: if tp.ident=="py": # $py(...) : # execute the string given within the brackets: if block.safe_mode: raise EP.ParseException( \ block.posmsg("$py() not allowed with " "safemode", pos= tp.start())) if not block.skip: block.exec_(tp.args()) elif tp.ident in ("include", "include_begin"): if not block.skip: # $include(...) or $include_begin(...) : # evaluate the filename of the file to include: # note: we do not use "raise ... from None" in order # to remain compatible with python 3.2.3: exc= None try: (filename, encoding)= one_or_two_strings(\ block.eval_(tp.args())) if encoding is None: encoding= INPUT_DEFAULT_ENCODING else: # may raise LookupError: test_encoding(encoding) except (TypeError, LookupError) as e: exc= e if exc is not None: raise EP.ParseException( \ block.posmsg(str(exc), pos=tp.start())) # create an instance of an IncludeBlock: block= IncludeBlock(previous= block, new_scope= (tp.ident=="include_begin"), filename= filename, encoding= encoding, include_paths= include_paths ) elif tp.ident=="template": if not block.skip: # $template(...) : # evaluate the filename of substfile: # note: we do not use "raise ... from None" in order # to remain compatible with python 3.2.3: exc= None try: (filename, encoding)= one_or_two_strings(\ block.eval_(tp.args())) if encoding is None: encoding= INPUT_DEFAULT_ENCODING else: # may raise LookupError: test_encoding(encoding) except (TypeError, LookupError) as e: exc= e if exc is not None: raise EP.ParseException( \ block.posmsg(str(exc), pos=tp.start())) # remember this filename in the current block object: block.set_substfile(filename, encoding, tp) elif tp.ident=="subst": if not block.skip: # $subst(...) : # evaluate all the named arguments: args= block.eval_("__pyexpander_helper2(%s)" % tp.args()) # create an instance of a SubstBlock: block= SubstBlock(previous= block, filename= block.template, include_paths= include_paths, external_definitions= args) elif tp.ident=="pattern": if not block.skip: # $pattern(...) : # create a tuple of all arguments: args= block.eval_("(%s)" % tp.args()) block= PatternBlock(previous= block, filename= block.template, include_paths= include_paths, heading= args[0], lines= args[1:]) elif tp.ident=="default": if not block.skip: # $default(...) : # evaluate all the named arguments: args= block.eval_("__pyexpander_helper2(%s)" % tp.args()) # set these defaults in the current block: for (k,v) in list(args.items()): block.setdefault(k, v) elif tp.ident=="if": # $if(...) : # evaluate the condition: if not block.skip: condition= block.eval_(tp.args()) else: # fake a condition: condition= True # create an instance of an IfBlock: block= IfBlock(previous= block, condition= condition) elif tp.ident=="elif": # elif(...) : # current block must be an IfBlock: if not isinstance(block,IfBlock): raise EP.ParseException( \ block.posmsg("unmatched elif at", \ pos=tp.start())) # evaluate the condition: if not block.prev_skip: # pylint: disable= no-member condition= block.eval_(tp.args()) else: # fake a condition: condition= False # enter the "elif" part of the if block by # calling enter_elif: block.enter_elif(condition) # pylint: disable= no-member elif tp.ident in ("for", "for_begin"): # $for(...) or $for_begin(...) : # assume the the parameters form a valid list comprehension e= None try: # try to parse the arguments of $for(): for_parts= EP.scanPyIn(tp.args()) except EP.ParseException as _e: e= _e if e is not None: # pylint: disable=raising-non-exception raise e.__class__( \ block.posmsg("error in %s command at" % tp.ident, \ pos= tp.start())) # create a list of loop items by using pythons # list comprehension mechanism: if not block.skip: for_list= block.eval_("list(%s)" % for_parts[2]) else: for_list= [] # create an instance of a ForBlock: block= ForBlock(previous= block, new_scope= (tp.ident=="for_begin"), value_list= for_list, var_expr= for_parts[0]) block.set_loop_var() elif tp.ident in ("while", "while_begin"): # $while(...) or $while_begin(...) : # create an instance of a WhileBlock: if not block.skip: expr= tp.args() else: expr= "" block= WhileBlock(previous= block, new_scope= (tp.ident=="while_begin"), while_expr= expr) elif tp.ident=="macro": # $macro(...) try: # try to parse the arguments of $macro(): identifiers= EP.scanPyIdentList(tp.args()) except EP.ParseException as _: raise EP.ParseException(\ block.posmsg("error in \"macro\" command at", \ pos= tp.start())) if len(identifiers)<=0: raise EP.ParseException( \ block.posmsg("error in \"macro\" command at", \ pos= tp.start())) e= None try: keyword_check(identifiers[0:1]) except ValueError as _e: e= _e if e is not None: raise EP.ParseException( \ block.posmsg(("error in \"macro\" command, " "%s at") % e, \ pos= tp.start())) block= MacBlock(previous= block, parameter_list= identifiers[1:]) # make macro known in the previous block: block.previous.add_macro(identifiers[0], block) # make macro known in this block, this allows recursive # macros: block.add_macro(identifiers[0], block) elif tp.ident=="nonlocal": if not block.skip: # $nonlocal(...) : e= None try: # try to parse the arguments of $nonlocal(): identifiers= EP.scanPyIdentList(tp.args()) except EP.ParseException as _e: e= _e if e is not None: # pylint: disable=raising-non-exception raise e.__class__( \ block.posmsg("error in \"nonlocal\" command at", \ pos= tp.start())) # mark them in the current block as exported symbols: block.export_symbols(identifiers) elif tp.ident in ("extend", "extend_expr"): if block.safe_mode: raise EP.ParseException( \ block.posmsg(("$%s not allowed with " "safemode") % tp.ident, pos= tp.start())) if not block.skip: if tp.ident=="extend_expr": # $extend_expr(...) : expr_= block.eval_(tp.args()) e= None try: identifiers= [str(elm) for elm in expr_] except TypeError as _e: e= _e if e is not None: # pylint: disable=raising-non-exception raise e.__class__("%s at %s" % \ (str(e), block.posmsg())) else: # $extend(...) : e= None try: # try to parse the arguments of $extend(): identifiers= EP.scanPyIdentList(tp.args()) except EP.ParseException as _e: e= _e if e is not None: # pylint: disable=raising-non-exception raise e.__class__( \ block.posmsg("error in \"extend\" command at", \ pos= tp.start())) e= None try: keyword_check(identifiers) except ValueError as _e: e= _e if e is not None: raise EP.ParseException( \ block.posmsg(("error in \"extend\" command, " "%s at") % e, \ pos= tp.start())) # mark them in the current block as "extended" identifiers: block.extend(identifiers) elif tp.ident in block.direct_funcs: if not block.skip: # $user-function-extended(...) # apply the function directly: result.append(block.str_eval("%s(%s)" % \ (tp.ident,tp.args()))) elif tp.ident in block.macros: if not block.skip: # $macro(args) block= MacBlock(previous= block, declaration_block= block.macros[tp.ident], parameter_list= identifiers[1:]) if auto_indent: # column of the '$' sign: col_= tp.rowcol()[1]-len(tp.ident)-2 block.set_indent(block.get_indent()+(col_-1)) (args,kwargs)= block.eval_("__pyexpander_helper3(%s)" % \ tp.args()) if len(args)>len(block.parameter_list): raise EP.ParseException( \ block.posmsg(("too many parameters at " "instantiation of macro " "\"%s\" at") % tp.ident, pos= tp.start())) for (i,name) in enumerate(block.parameter_list): if i<len(args): # unnamed parameter if name in kwargs: raise EP.ParseException( \ block.posmsg(("multiple values for " "keyword argument " "\"%s\" in macro " "\"%s\" at") % \ (name, tp.ident), pos= tp.start())) block.__setitem__(name, args[i]) continue if not name in kwargs: raise EP.ParseException( \ block.posmsg(("no value for argument " "\"%s\" in macro \"%s\" " "at") % \ (name, tp.ident), pos= tp.start())) block.__setitem__(name, kwargs[name]) else: # everything else is a ParseException, this shouldn't happen # since all ParsedItem objects that the expanderparser can # create should be handled here. raise EP.ParseException( \ block.posmsg("unknown command \"%s\" at" % tp.ident, \ pos= tp.start())) continue if isinstance(tp, EP.ParsedPureCommand): # a "pure" command, a command without arguments: ident= tp.string() if allow_nobracket_vars and (ident in keyword_checks): # an extra check for keyword conflicts do_warn= False try: block.__getitem__(ident) do_warn= True except NameError as _: pass if do_warn: sys.stderr.write(block.posmsg(("warning, variable '%s' " "shadowed by pyexpander " "keyword (This warning " "only printed once per " "keyword) at" % ident), pos= tp.start())+"\n") keyword_checks.remove(ident) if ident=="safemode": block.set_safemode(True) elif ident=="else": # $else : # current block must be an IfBlock: if not isinstance(block,IfBlock): raise EP.ParseException( \ block.posmsg("unmatched else at", \ pos= tp.start())) # enter the "else" part of the if block by # calling enter_else: block.enter_else() elif ident=="endif": # $endif # current block must be an IfBlock: if not isinstance(block,IfBlock): raise EP.ParseException( \ block.posmsg("unmatched endif at", \ pos= tp.start())) # go back to previous block: block= block.pop() elif ident=="endfor": # $endfor # current block must be a ForBlock: if not isinstance(block,ForBlock): raise EP.ParseException( \ block.posmsg("unmatched endfor at", \ pos= tp.start())) # test if we have to perform the loop block again: if not block.next_loop(): # no further loops, go back to the previous block: block= block.pop() else: # further loops, give the loop variable a new value: block.set_loop_var() elif ident=="endwhile": # $endwhile # current block must be a WhileBlock: if not isinstance(block,WhileBlock): raise EP.ParseException( \ block.posmsg("unmatched endwhile at", \ pos= tp.start())) # test if we have to loop again. next_loop also resets # the position, if we have to loop again: if not block.next_loop(): # if loop condition is False, go back to previous block, block= block.pop() elif ident=="endmacro": # $endmacro # current block must be a MacBlock: if not isinstance(block,MacBlock): raise EP.ParseException( \ block.posmsg("unmatched endmacro at", \ pos= tp.start())) block= block.pop() elif ident=="begin": # $begin # create an instance of a BeginBlock: block= BeginBlock(previous= block) elif ident=="end": # $end # current block must be a BeginBlock: if not isinstance(block,BeginBlock): raise EP.ParseException( \ block.posmsg("unmatched end at", \ pos= tp.start())) # go back to previous block: block= block.pop() elif ident in block.direct_vars: # $user-variable-extended # if skip mode is off, insert the current value of the # variable. The current block can be used like a dict in order # to get values of variables: if not block.skip: result.append(str(block.__getitem__(ident))) else: # if we are not in nobracket_vars mode, we have an # unknown command without arguments here: if not allow_nobracket_vars: raise EP.ParseException( \ block.posmsg("unknown command \"%s\" at" % ident, \ pos= tp.start())) # if skip mode is off, insert the current value of the # variable. The current block can be used like a dict in # order to get values of variables: if not block.skip: result.append(str(block.__getitem__(ident))) continue if block.previous is not None: raise EP.ParseException(block.posmsg("unclosed block at")) return (result.list_(),block.globals_,block.file_deps)
[docs]def processToPrint(parse_list, filename=None, external_definitions=None, allow_nobracket_vars= False, auto_continuation= False, auto_indent= False, include_paths=None, output_encoding=SYS_DEFAULT_ENCODING, print_mode="full"): """Gets a parse list, expand the text in it and print it. args: - parse_list: A parse list created by parseString(). - filename (str): The filename, if given, is included in possible error messages. - external_definitions (dict): A dict with items to import to the globals() dictionary. - allow_nobracket_vars (bool): If True, allow variables in the form $VAR instead of $(VAR). - auto_continuation (bool): If True, remove newline at the end of lines with a command. This works like having an '\' at the end of each line with a command. - auto_indent (bool): If True, indent the contents of macros to the same level as the macro invocation. - include_paths (list): A list of paths that are searched for the $include command. - output_encoding: encoding used to create output data - print_mode: one of 3 possible strings, + full: normal printing + repr: print the list of strings in python "repr" format, this is for debugging only. + none: do not print anything returns: - The internal globals() dictionary. - The file_deps dict with file dependencies """ # pylint: disable=too-many-arguments # debug: # for elm in parse_list: # print elm if print_mode not in ('full', 'repr', 'none'): raise ValueError("invalid print mode: %s" % print_mode) (result,exp_globals,file_deps)= processToList(parse_list, filename, external_definitions, allow_nobracket_vars, auto_continuation, auto_indent, include_paths) if print_mode=="repr": print(repr(result)) elif print_mode=="full": if output_encoding==SYS_DEFAULT_ENCODING: print("".join(result), end='') else: sys.stdout.buffer.write(("".join(result)).encode(output_encoding)) return exp_globals, file_deps
[docs]def expandToStr(st, filename=None, external_definitions=None, allow_nobracket_vars= False, auto_continuation= False, auto_indent= False, include_paths=None): """Get a string, expand the text in it and return it as a string. args: - st (str): The string that is to be expaned. - filename (str): The filename, if given, is included in possible error messages. - external_definitions (dict): A dict with items to import to the globals() dictionary. - allow_nobracket_vars (bool): If True, allow variables in the form $VAR instead of $(VAR). - auto_continuation (bool): If True, remove newline at the end of lines with a command. This works like having an '\' at the end of each line with a command. - auto_indent (bool): If True, indent the contents of macros to the same level as the macro invocation. - include_paths (list): A list of paths that are searched for the $include command. returns a tuple containing: - The expanded text as a single string. - The internal globals() dictionary. - The file_deps dict with file dependencies """ # pylint: disable=too-many-arguments (result,exp_globals,file_deps)= processToList(parseString(st), filename, external_definitions, allow_nobracket_vars, auto_continuation, auto_indent, include_paths) return ("".join(result), exp_globals, file_deps)
[docs]def expand(st, filename=None, external_definitions=None, allow_nobracket_vars= False, auto_continuation= False, auto_indent= False, include_paths=None, output_encoding=SYS_DEFAULT_ENCODING, print_mode="full"): r"""Get a string, expand the text in it and print it. args: - st (str): The string that is to be expaned. - filename (str): The filename, if given, is included in possible error messages. - external_definitions (dict): A dict with items to import to the globals() dictionary. - allow_nobracket_vars (bool): If True, allow variables in the form $VAR instead of $(VAR). - auto_continuation (bool): If True, remove newline at the end of lines with a command. This works like having an '\' at the end of each line with a command. - auto_indent (bool): If True, indent the contents of macros to the same level as the macro invocation. - include_paths (list): A list of paths that are searched for the $include command. - output_encoding: encoding used to create output data - print_mode: one of 3 possible strings, + full: normal printing + repr: print the list of strings in python "repr" format, this is for debugging only. + none: do not print anything returns: - The internal globals() dictionary. - The file_deps dict with file dependencies """ # pylint: disable=too-many-arguments return processToPrint(parseString(st), filename, external_definitions, allow_nobracket_vars, auto_continuation, auto_indent, include_paths, output_encoding, print_mode)
[docs]def expandFile(filename, encoding= SYS_DEFAULT_ENCODING, external_definitions=None, allow_nobracket_vars= False, auto_continuation= False, auto_indent= False, include_paths=None, no_stdin_warning= False, output_encoding=SYS_DEFAULT_ENCODING, print_mode="full"): """Get a filename, expand the text in it and print it. args: - filename (str): The name of the file - encoding: encoding of the file - external_definitions (dict): A dict with items to import to the globals() dictionary. - allow_nobracket_vars (bool): If True, allow variables in the form $VAR instead of $(VAR). - auto_continuation (bool): If True, remove newline at the end of lines with a command. This works like having an '\' at the end of each line with a command. - auto_indent (bool): If True, indent the contents of macros to the same level as the macro invocation. - include_paths (list): A list of paths that are searched for the $include command. - no_stdin_warning (bool): If True, print short message on stderr when the program is waiting on input from stdin. - output_encoding: encoding used to create output data - print_mode: one of 3 possible strings, + full: normal printing + repr: print the list of strings in python "repr" format, this is for debugging only. + none: do not print anything returns: - The internal globals() dictionary. - The file_deps dict with file dependencies """ # pylint: disable=too-many-arguments return processToPrint(parseFile(filename, encoding, no_stdin_warning), filename, external_definitions, allow_nobracket_vars, auto_continuation, auto_indent, include_paths, output_encoding, print_mode)
def _test(): """perform the doctest tests.""" # pylint: disable=import-outside-toplevel import doctest print("testing...") doctest.testmod() print("done") if __name__ == "__main__": _test()