Vearie

bz2cfg.py - BZ2 CFG File General Parser

Feb 6th, 2022 (edited)
1,539
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 6.67 KB | None | 0 0
  1. """This module is a generalized parser for .cfg style files in bz2, including bzone.cfg, shell configs, etc..."""
  2. VERSION = 1.11
  3.  
  4. # Usage:
  5. # cfg = CFG("bzgame_init.cfg") to load config file.
  6. # cfg.walk() can be used to traverse all containers and subcontainers.
  7. # cfg = cfg["containerName"] can be used to access container. (returns first found with the specified name)
  8. # 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.
  9. # cfg.get_attributes("attributeName") and cfg.get_containers("containerName") yield all matching items. If no name is specified, simply yields all containers or attributes.
  10. #
  11. # Containers and CFG objects both have all of these methods.
  12.  
  13. class ParseError(Exception):
  14.     def __init__(self, file="UNDEFINED", line=0, col=0, msg="Parsing Error"):
  15.         self.file = file
  16.         self.line = line
  17.         self.col = col
  18.         self.msg = msg
  19.    
  20.     def __str__(self):
  21.         return "%s:%d:%d - %s" % (self.file, self.line, self.col, self.msg)
  22.  
  23. class UnterminatedString(ParseError): pass
  24.  
  25. class ConfigContainer:
  26.     def walk(self):
  27.         for child in self.children:
  28.             yield child
  29.             yield from child.walk()
  30.    
  31.     def get_attribute(self, attribute_name, occurrence=1):
  32.         return self._find(attribute_name, self.attribute, occurrence)
  33.    
  34.     def get_attributes(self, name=None):
  35.         return self._findall(name, self.attribute, False)
  36.    
  37.     def get_container(self, container_name, occurrence=1):
  38.         return self._find(container_name, self.children, occurrence)
  39.    
  40.     def get_containers(self, name=None):
  41.         return self._findall(name, self.children)
  42.    
  43.     def _findall(self, name, _list, reverse=False):
  44.         if name:
  45.             name = name.casefold()
  46.        
  47.         for item in (reversed(_list) if reverse else _list):
  48.             if name is None or item.name.casefold() == name:
  49.                 yield item
  50.    
  51.     def _find(self, name, _list, occurrence=1):
  52.         reverse = occurrence < 0
  53.         occurrence = abs(occurrence)
  54.        
  55.         for index, container in enumerate(self._findall(name, _list, reverse), start=1):
  56.             if index == occurrence:
  57.                 return container
  58.        
  59.         raise KeyError("%r not found in %r" % (name, self.name))
  60.    
  61.     def __iter__(self):
  62.         for child in self.children:
  63.             yield child
  64.    
  65.     def __getitem__(self, container_name):
  66.         """Returns child container by name. Raises KeyError if not found."""
  67.         return self.get_container(container_name)
  68.  
  69. class CFG(ConfigContainer):
  70.     def __init__(self, filepath=None):
  71.         self.children = []
  72.         self.attribute = []
  73.         self.filepath = filepath
  74.        
  75.         self.name = filepath or "<Untitled - CFG>"
  76.        
  77.         if self.filepath:
  78.             with open(self.filepath, "r") as f:
  79.                 self.read(f)
  80.    
  81.     def __str__(self):
  82.         return "<%s %r>" % (__class__.__name__, self.name)
  83.    
  84.     # Yields either a word or a control character from a file, 1 at a time
  85.     def parse_words(self, f):
  86.         # Note: these characters do not add to the column counter
  87.         NON_CHARACTER = "\x00\r"
  88.  
  89.         # Characters which count as a valid name
  90.         NAME = "QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm0123456789_.+-"
  91.         WHITESPACE = " \t\n"
  92.        
  93.         line = col = 1
  94.         state = READ_ANY = 0
  95.         in_comment = 1
  96.         in_quote = None
  97.         word = ""
  98.        
  99.         for c in f.read():
  100.             if c in NON_CHARACTER:
  101.                 continue
  102.            
  103.             if c == "\n":
  104.                 line += 1
  105.                 col = 1
  106.             else:
  107.                 col += 1
  108.            
  109.             if in_quote:
  110.                 # Character matches quote, completing the string.
  111.                 # Note that BZ2 does not handle escapes (e.g. \").
  112.                 if c == in_quote:
  113.                     yield word, line, col
  114.                     word = ""
  115.                     in_quote = None
  116.                
  117.                 # New line without terminating quote.
  118.                 elif c == "\n":
  119.                     raise UnterminatedString(self.filepath, line, col, "Unterminated String")
  120.                
  121.                 else:
  122.                     word += c
  123.            
  124.             elif state == READ_ANY:
  125.                 if c in NAME:
  126.                     word += c
  127.                
  128.                 else:
  129.                     # End of continuous word
  130.                     if c in WHITESPACE:
  131.                         if word:
  132.                             yield word, line, col
  133.                             word = ""
  134.                    
  135.                     # Start of comment
  136.                     elif c == "/":
  137.                         if word:
  138.                             yield word, line, col
  139.                        
  140.                         state = in_comment
  141.                         word = c
  142.                    
  143.                     # Start of string quote
  144.                     elif c in "\"'":
  145.                         if word:
  146.                             yield word, line, col
  147.                             word = ""
  148.                        
  149.                         in_quote = c
  150.                    
  151.                     # Special control character
  152.                     elif c in "{}(),;":
  153.                         if word:
  154.                             yield word, line, col
  155.                             word = ""
  156.                        
  157.                         yield c, line, col
  158.                    
  159.                     # Unhandled
  160.                     else:
  161.                         raise ParseError(self.filepath, line, col, "Unexpected Character %r" % c)
  162.            
  163.             elif state == in_comment:
  164.                 # New line character indicates of comment
  165.                 if c == "\n":
  166.                     if word:
  167.                         pass # ignore comments
  168.                         # yield word
  169.                    
  170.                     word = ""
  171.                     state = READ_ANY
  172.                
  173.                 else:
  174.                     word += c
  175.    
  176.     def read(self, f):
  177.         EXPECT_NAME = 0
  178.         EXPECT_PARAM_OPEN = 1
  179.         EXPECT_PARAM = 2
  180.         EXPECT_CONTAINER_TYPE = 3
  181.         state = EXPECT_NAME
  182.        
  183.         # Temporary buffers
  184.         name = ""
  185.         parameters = []
  186.        
  187.         # Empty root container
  188.         container_at_level = [self]
  189.        
  190.         for word, line, col in self.parse_words(f):
  191.             if state == EXPECT_NAME:
  192.                 if word == "}":
  193.                     if len(container_at_level) <= 1:
  194.                         # Extra closing brace: Could raise an exception here, but I think it's safe to ignore.
  195.                         continue
  196.                    
  197.                     del(container_at_level[-1])
  198.                     continue
  199.                
  200.                 name = word
  201.                 parameters = []
  202.                 state = EXPECT_PARAM_OPEN
  203.                 continue
  204.            
  205.             elif state == EXPECT_PARAM_OPEN:
  206.                 if word == "(":
  207.                     state = EXPECT_PARAM
  208.                     continue
  209.            
  210.             elif state == EXPECT_PARAM:
  211.                 if word == ",":
  212.                     continue
  213.                
  214.                 elif word == ")":
  215.                     state = EXPECT_CONTAINER_TYPE
  216.                     continue
  217.                
  218.                 else:
  219.                     parameters += [word]
  220.                     continue
  221.            
  222.             elif state == EXPECT_CONTAINER_TYPE:
  223.                 # Start of a new container
  224.                 if word == "{":
  225.                     container = Container(name, *parameters)
  226.                     container_at_level[-1].children += [container]
  227.                     container_at_level.append(container)
  228.                    
  229.                     state = EXPECT_NAME
  230.                     continue
  231.                
  232.                 # End of attribute
  233.                 elif word == ";":
  234.                     container_at_level[-1].attribute += [Attribute(name, *parameters)]
  235.                     state = EXPECT_NAME
  236.                     continue
  237.            
  238.             raise ParseError(self.filepath, line, col)
  239.  
  240. class Container(ConfigContainer):
  241.     def __init__(self, name, *parameters):
  242.         self.name = name
  243.         self.parameter = parameters
  244.         self.children = []
  245.         self.attribute = []
  246.    
  247.     def __str__(self):
  248.         return "<%s %s(%s)>" % (__class__.__name__, self.name, ", ".join("\"%s\"" % p for p in self.parameter))
  249.  
  250. class Attribute:
  251.     def __init__(self, name, *parameters):
  252.         self.name = name
  253.         self.parameter = parameters
  254.    
  255.     def __str__(self):
  256.         return "<%s %s(%s)>" % (__class__.__name__, self.name, ", ".join("\"%s\"" % p for p in self.parameter))
  257.  
Advertisement
Add Comment
Please, Sign In to add comment