Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- """This module is a generalized parser for .cfg style files in bz2, including bzone.cfg, shell configs, etc..."""
- VERSION = 1.11
- # Usage:
- # cfg = CFG("bzgame_init.cfg") to load config file.
- # cfg.walk() can be used to traverse all containers and subcontainers.
- # cfg = cfg["containerName"] can be used to access container. (returns first found with the specified name)
- # cfg.get_attribute("attributeName", 1) or cfg.get_container("name", 1) can be used to get items where multiple exist with the same name, negative number find in reverse.
- # cfg.get_attributes("attributeName") and cfg.get_containers("containerName") yield all matching items. If no name is specified, simply yields all containers or attributes.
- #
- # Containers and CFG objects both have all of these methods.
- class ParseError(Exception):
- def __init__(self, file="UNDEFINED", line=0, col=0, msg="Parsing Error"):
- self.file = file
- self.line = line
- self.col = col
- self.msg = msg
- def __str__(self):
- return "%s:%d:%d - %s" % (self.file, self.line, self.col, self.msg)
- class UnterminatedString(ParseError): pass
- class ConfigContainer:
- def walk(self):
- for child in self.children:
- yield child
- yield from child.walk()
- def get_attribute(self, attribute_name, occurrence=1):
- return self._find(attribute_name, self.attribute, occurrence)
- def get_attributes(self, name=None):
- return self._findall(name, self.attribute, False)
- def get_container(self, container_name, occurrence=1):
- return self._find(container_name, self.children, occurrence)
- def get_containers(self, name=None):
- return self._findall(name, self.children)
- def _findall(self, name, _list, reverse=False):
- if name:
- name = name.casefold()
- for item in (reversed(_list) if reverse else _list):
- if name is None or item.name.casefold() == name:
- yield item
- def _find(self, name, _list, occurrence=1):
- reverse = occurrence < 0
- occurrence = abs(occurrence)
- for index, container in enumerate(self._findall(name, _list, reverse), start=1):
- if index == occurrence:
- return container
- raise KeyError("%r not found in %r" % (name, self.name))
- def __iter__(self):
- for child in self.children:
- yield child
- def __getitem__(self, container_name):
- """Returns child container by name. Raises KeyError if not found."""
- return self.get_container(container_name)
- class CFG(ConfigContainer):
- def __init__(self, filepath=None):
- self.children = []
- self.attribute = []
- self.filepath = filepath
- self.name = filepath or "<Untitled - CFG>"
- if self.filepath:
- with open(self.filepath, "r") as f:
- self.read(f)
- def __str__(self):
- return "<%s %r>" % (__class__.__name__, self.name)
- # Yields either a word or a control character from a file, 1 at a time
- def parse_words(self, f):
- # Note: these characters do not add to the column counter
- NON_CHARACTER = "\x00\r"
- # Characters which count as a valid name
- NAME = "QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm0123456789_.+-"
- WHITESPACE = " \t\n"
- line = col = 1
- state = READ_ANY = 0
- in_comment = 1
- in_quote = None
- word = ""
- for c in f.read():
- if c in NON_CHARACTER:
- continue
- if c == "\n":
- line += 1
- col = 1
- else:
- col += 1
- if in_quote:
- # Character matches quote, completing the string.
- # Note that BZ2 does not handle escapes (e.g. \").
- if c == in_quote:
- yield word, line, col
- word = ""
- in_quote = None
- # New line without terminating quote.
- elif c == "\n":
- raise UnterminatedString(self.filepath, line, col, "Unterminated String")
- else:
- word += c
- elif state == READ_ANY:
- if c in NAME:
- word += c
- else:
- # End of continuous word
- if c in WHITESPACE:
- if word:
- yield word, line, col
- word = ""
- # Start of comment
- elif c == "/":
- if word:
- yield word, line, col
- state = in_comment
- word = c
- # Start of string quote
- elif c in "\"'":
- if word:
- yield word, line, col
- word = ""
- in_quote = c
- # Special control character
- elif c in "{}(),;":
- if word:
- yield word, line, col
- word = ""
- yield c, line, col
- # Unhandled
- else:
- raise ParseError(self.filepath, line, col, "Unexpected Character %r" % c)
- elif state == in_comment:
- # New line character indicates of comment
- if c == "\n":
- if word:
- pass # ignore comments
- # yield word
- word = ""
- state = READ_ANY
- else:
- word += c
- def read(self, f):
- EXPECT_NAME = 0
- EXPECT_PARAM_OPEN = 1
- EXPECT_PARAM = 2
- EXPECT_CONTAINER_TYPE = 3
- state = EXPECT_NAME
- # Temporary buffers
- name = ""
- parameters = []
- # Empty root container
- container_at_level = [self]
- for word, line, col in self.parse_words(f):
- if state == EXPECT_NAME:
- if word == "}":
- if len(container_at_level) <= 1:
- # Extra closing brace: Could raise an exception here, but I think it's safe to ignore.
- continue
- del(container_at_level[-1])
- continue
- name = word
- parameters = []
- state = EXPECT_PARAM_OPEN
- continue
- elif state == EXPECT_PARAM_OPEN:
- if word == "(":
- state = EXPECT_PARAM
- continue
- elif state == EXPECT_PARAM:
- if word == ",":
- continue
- elif word == ")":
- state = EXPECT_CONTAINER_TYPE
- continue
- else:
- parameters += [word]
- continue
- elif state == EXPECT_CONTAINER_TYPE:
- # Start of a new container
- if word == "{":
- container = Container(name, *parameters)
- container_at_level[-1].children += [container]
- container_at_level.append(container)
- state = EXPECT_NAME
- continue
- # End of attribute
- elif word == ";":
- container_at_level[-1].attribute += [Attribute(name, *parameters)]
- state = EXPECT_NAME
- continue
- raise ParseError(self.filepath, line, col)
- class Container(ConfigContainer):
- def __init__(self, name, *parameters):
- self.name = name
- self.parameter = parameters
- self.children = []
- self.attribute = []
- def __str__(self):
- return "<%s %s(%s)>" % (__class__.__name__, self.name, ", ".join("\"%s\"" % p for p in self.parameter))
- class Attribute:
- def __init__(self, name, *parameters):
- self.name = name
- self.parameter = parameters
- def __str__(self):
- return "<%s %s(%s)>" % (__class__.__name__, self.name, ", ".join("\"%s\"" % p for p in self.parameter))
Advertisement
Add Comment
Please, Sign In to add comment