Source code for pyndn.util.boost_info_parser

# -*- Mode:python; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
#
# Copyright (C) 2014-2016 Regents of the University of California.
# Author: Adeola Bannis
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
# A copy of the GNU Lesser General Public License is in the file COPYING.

from collections import OrderedDict
from pyndn.util.common import Common

[docs]def shlex_split(s): """ Similar to shlex.split, split s into an array of strings which are separated by whitespace, treating a string within quotes as a single entity regardless of whitespace between the quotes. Also allow a backslash to escape the next character. :param str s: The input string to split. :return: An array of strings. :rtype: list of str """ result = [] if s == "": return result whiteSpace = " \t\n\r" iStart = 0 while True: # Move iStart past whitespace. while s[iStart] in whiteSpace: iStart += 1 if iStart >= len(s): # Done. return result # Move iEnd to the end of the token. iEnd = iStart inQuotation = False token = "" while True: if s[iEnd] == '\\': # Append characters up to the backslash, skip the backslash and # move iEnd past the escaped character. token += s[iStart:iEnd] iStart = iEnd + 1 iEnd = iStart if iEnd >= len(s): # An unusual case: A backslash at the end of the string. break else: if inQuotation: if s[iEnd] == '\"': # Append characters up to the end quote and skip. token += s[iStart:iEnd] iStart = iEnd + 1 inQuotation = False else: if s[iEnd] == '\"': # Append characters up to the start quote and skip. token += s[iStart:iEnd] iStart = iEnd + 1 inQuotation = True else: if s[iEnd] in whiteSpace: break iEnd += 1 if iEnd >= len(s): break token += s[iStart:iEnd] result.append(token) if iEnd >= len(s): # Done. return result iStart = iEnd
""" This class is provided for compatibility with the Boost INFO property list format used in ndn-cxx. Each node in the tree may have a name and a value as well as associated sub-trees. The sub-tree names are not unique, and so sub-trees are stored as dictionaries where the key is a sub-tree name and the values are the sub-trees sharing the same name. Nodes can be accessed with a path syntax, as long as nodes in the path do not contain the path separator '/' in their names. """
[docs]class BoostInfoTree(object): def __init__(self, value = None, parent = None): super(BoostInfoTree, self).__init__() self.subtrees = OrderedDict() self.value = value self.parent = parent self.lastChild = None
[docs] def clone(self): """ Create a deep copy of this tree. """ copy = BoostInfoTree(self.value) for subtreeName, subtrees in self.subtrees.items(): for t in subtrees: newTree = t.clone() copy.addSubtree(subtreeName, newTree) return copy
[docs] def addSubtree(self, treeName, newTree): """ Insert a BoostInfoTree as a sub-tree with the given name. :param str treeName: The name of the new sub-tree. :param BoostInfoTree newTree: The sub-tree to add. """ if treeName in self.subtrees: self.subtrees[treeName].append(newTree) else: self.subtrees[treeName] = [newTree] newTree.parent = self self.lastChild = newTree
[docs] def createSubtree(self, treeName, value=None ): """ Create a new BoostInfo and insert it as a sub-tree with the given name. :param str treeName: The name of the new sub-tree. :param str value: The value associated with the new sub-tree. :return: The created sub-tree. :rtype: BoostInfoTree """ newTree = BoostInfoTree(value, self) self.addSubtree(treeName, newTree) return newTree
def __getitem__(self, key): key = key.lstrip('/') path = key.split('/') if len(key) == 0: return [self] subtrees = self.subtrees[path[0]] if len(path) == 1: return subtrees newPath = '/'.join(path[1:]) foundVals = [] for t in subtrees: foundVals.extend(t.__getitem__(newPath)) return foundVals
[docs] def getValue(self): """ :return: The value associated with this tree. :rtype: str """ return self.value
def _prettyprint(self, indentLevel=1): prefix = " "*indentLevel s = "" if self.parent is not None: if self.value is not None and len(self.value) > 0: s += "\"" + str(self.value) + "\"" s+= "\n" if len(self.subtrees) > 0: if self.parent is not None: s += prefix+ "{\n" nextLevel = " "*(indentLevel+2) for t in self.subtrees: for subtree in self.subtrees[t]: s += nextLevel + str(t) + " " + subtree._prettyprint(indentLevel+2) if self.parent is not None: s += prefix + "}\n" return s def __str__(self): return self._prettyprint()
""" This class reads files in Boost's INFO format and constructs a BoostInfoTree. """
[docs]class BoostInfoParser(object): def __init__(self): self._root = BoostInfoTree()
[docs] def read(self, fileNameOrInput, inputName = None): """ Add the contents of the file or input string to the root BoostInfoTree. There are two forms: read(fileName) reads fileName from the file system. read(input, inputName) reads from the input, in which case inputName is used only for log messages, etc. :param str fileName: The path to the INFO file. :param str input: The contents of the INFO file, with lines separated by NL or CR/NL. :param str inputName: Use with input for log messages, etc. """ if Common.typeIsString(inputName): input = fileNameOrInput else: # No inputName, so assume the first arg is the file name. fileName = fileNameOrInput inputName = fileName f = open(fileName, 'r') input = f.read() f.close() self._read(input, self._root)
[docs] def readPropertyList(self, fromDict): """ Import a python dict as a BoostInfoTree. Only leaf nodes will have associated values. :param dict fromDict: The dictionary to import. """ if not isinstance(fromDict, dict): raise TypeError('BoostInfoTree must be initialized from dictionary') self._readDict(fromDict, self._root) return self._root
def _read(self, input, ctx): """ Internal import method with an explicit context node. :param str input: The contents of the INFO file, with lines separated by "\n" or "\r\n". :param BoostInfoTree ctx: The node currently being populated. :return: The ctx. :rtype: BoostInfoTree """ for line in input.splitlines(): ctx = self._parseLine(line.strip(), ctx) return ctx def _readList(self, fromList, intoNode, keyName): """ Helper method for reading lists inside imported dictionaries. """ # we can have lists of strings or dicts, ONLY for v in fromList: if hasattr(v, 'keys'): newNode = intoNode.createSubtree(keyName) self._readDict(v, newNode) else: intoNode.createSubtree(keyName, v) def _readDict(self, fromDict, currentNode): """ Helper method for reading dictionaries inside imported dictionaries. """ for k,v in fromDict.items(): # HACK if k == '__name__': continue if hasattr(v, 'keys'): newNode = currentNode.createSubtree(k) self._readDict(v, newNode) elif hasattr(v, '__iter__'): self._readList(v, currentNode, k) else: # should be a string, should I check? currentNode.createSubtree(k,v)
[docs] def write(self, filename): """ Write the root tree of this BoostInfoParser as file in Boost's INFO format. :param str filename: The output path. """ with open(filename, 'w') as stream: stream.write(str(self._root))
def _parseLine(self, string, context): """ Internal helper method for parsing INFO files line by line. """ # skip blank lines and comments commentStart = string.find(";") if commentStart >= 0: string = string[:commentStart].strip() if len(string) == 0: return context # usually we are expecting key and optional value strings = shlex_split(string) isSectionStart = False isSectionEnd=False for s in strings: isSectionStart = isSectionStart or s == '{' isSectionEnd = isSectionEnd or s == '}' if not isSectionStart and not isSectionEnd: key = strings[0] if len(strings) > 1: val = strings[1] else: val = None #if it is an "#include", load the new file instead of inserting keys if key == "#include": f = open(val, 'r') input = f.read() f.close() context = self._read(input, context) else: newTree = context.createSubtree(key, val) return context # ok, who is the joker who put a { on the same line as the key name?! sectionStart = string.find('{') if sectionStart > 0: firstPart = string[:sectionStart] secondPart = string[sectionStart:] ctx = self._parseLine(firstPart, context) return self._parseLine(secondPart, ctx) #if we encounter a {, we are beginning a new context # TODO: error if there was already a subcontext here if string[0] == '{': context = context.lastChild return context # if we encounter a }, we are ending a list context if string[0] == '}': context = context.parent return context raise RuntimeError("BoostInfoParser: input line is malformed")
[docs] def getRoot(self): """ :return: The root tree of this parser. :rtype: BoostInfoTree """ return self._root
def __getitem__(self, key): return self._root.__getitem__(key)