1 2import re as re_mod 3import abc as abc_mod 4import collections as col_mod 5 6INDENT = " " 7_NODES = [] 8 9 10class ListAdapter(col_mod.MutableSequence): 11 def __init__(self, *args): 12 self.items = [] 13 for arg in args: 14 self.append(arg) 15 def __len__(self): 16 return len(self.items) 17 def __contains__(self, val): 18 return val in self.items 19 def __iter__(self): 20 return iter(self.items) 21 def __getitem__(self, index): 22 return self.items[index] 23 def __setitem__(self, index, val): 24 self.items[index] = val 25 def __delitem__(self, index): 26 del self.items[index] 27 def insert(self, index, val): 28 self.items.insert(index, val) 29 def __eq__(self, val): 30 return self.items == val 31 def __ne__(self, val): 32 return self.items != val 33 def __lt__(self, val): 34 return self.items < val 35 def __le__(self, val): 36 return self.items <= val 37 def __gt__(self, val): 38 return self.items > val 39 def __ge__(self, val): 40 return self.items >= val 41 def __repr__(self): 42 return repr(self.items) 43 44 45class ParserError(Exception): pass 46class InvalidLineError(ParserError): pass 47class NodeCompleteError(InvalidLineError): pass 48class DirectiveError(ParserError): pass 49class NestingLimitError(ParserError): pass 50class NodeMatchError(ParserError): pass 51 52 53class Node(object): 54 """ 55 The apache parser node. 56 57 """ 58 __metaclass__ = abc_mod.ABCMeta 59 60 MATCH_RE = ".*" 61 def __init__(self): 62 self.lines = [] 63 self._content = None 64 self._complete = False 65 self.changed = False 66 @property 67 def complete(self): 68 return self._complete 69 @complete.setter 70 def complete(self, val): 71 self._complete = val 72 @classmethod 73 def match(cls, line): 74 if line is None: 75 return False 76 return bool(re_mod.match(cls.MATCH_RE, line)) 77 @property 78 def content(self): 79 return "\n".join(self.lines) 80 @property 81 def stable(self): 82 return True 83 def add_line(self, line): 84 if "\n" in line: 85 raise InvalidLineError("Lines cannot contain newlines.") 86 if self.complete: 87 raise NodeCompleteError(line) 88 self.lines.append(line) 89 if not line.endswith("\\"): 90 self.complete = True 91 def __str__(self): 92 if self.changed: 93 return self.content or "" 94 return "\n".join(self.lines) 95 def pprint(self, depth=0): 96 return "%s%s" % (INDENT*depth, str(self).lstrip()) 97 98 99class CommentNode(Node): 100 """A comment.""" 101 MATCH_RE = r"\s*#(.*[^\\])?$" 102 def add_line(self, line): 103 if line.endswith("\\"): 104 raise InvalidLineError("Comments cannot have line continuations.") 105 super(CommentNode, self).add_line(line) 106 self._content = line.split("#", 1)[1] 107 def __str__(self): 108 if not self.lines: 109 raise NodeCompleteError("Can't turn an uninitialized comment node into a string.") 110 if self.changed: 111 return "#"+self._content 112 return super(CommentNode, self).__str__() 113_NODES.append(CommentNode) 114 115 116class BlankNode(Node): 117 """A blank line.""" 118 MATCH_RE = "\s*$" 119 def add_line(self, line): 120 if line.endswith("\\"): 121 raise InvalidLineError("Blank lines cannot have line continuations.") 122 super(BlankNode, self).add_line(line) 123 self._content = "" 124 def pprint(self, depth=0): 125 return "" 126_NODES.append(BlankNode) 127 128 129class Directive(Node): 130 """A configuration directive.""" 131 NAME_RE = "[a-zA-Z]\w*" 132 133 def __init__(self): 134 super(Directive, self).__init__() 135 self._stable = False 136 137 class Name(object): 138 # cannot be changed once set 139 def __get__(self, obj, cls): 140 if not hasattr(obj, "_name"): 141 raise NodeCompleteError("Name has not been set yet.") 142 return obj._name 143 def __set__(self, obj, value): 144 if hasattr(obj, "_name") and obj._name is not None: 145 raise DirectiveError("Name is already set. Cannot set to %s" % value) 146 if not re_mod.match(obj.NAME_RE, value): 147 raise DirectiveError("Invalid name: %s" % value) 148 obj._name = value 149 obj._stable = True # name is the first thing in the line 150 name = Name() 151 152 class Arguments(object): 153 def __get__(self, obj, cls): 154 if not hasattr(obj, "_arguments"): 155 class ValidatedArgs(ListAdapter): 156 """ 157 Validate the addition, change, or removal of arguments. 158 159 """ 160 def __setitem__(self, index, val): 161 #self.validate_general(val) 162 self.items[index] = val 163 if obj.complete: 164 obj.changed = True 165 def __delitem__(self, index): 166 del self.items[index] 167 if obj.complete: 168 obj.changed = True 169 def insert(self, index, val): 170 # append calls this 171 #self.validate_general(val) 172 self.items.insert(index, val) 173 if obj.complete: 174 obj.changed = True 175 def validate_general(self, val): 176 #if "<" in line or ">" in line: 177 #if re_mod.match("([^\"'<>]*('.*?'|\".*?\"))*[^<>]*$", line): 178 if not re_mod.match("'.*'$|\".*\"$", val) and ("<" in val or ">" in val): 179 raise DirectiveError("Angle brackets not allowed in directive args. Received: %s" % val) 180 obj._arguments = ValidatedArgs() 181 return obj._arguments 182 arguments = Arguments() 183 184 def add_to_header(self, line): 185 if self.complete: 186 raise NodeCompleteError("Cannot add to the header of a complete directive.") 187 if not line: 188 raise DirectiveError("An empty line is not a valid header line.") 189 stable = True 190 if line[-1] == "\\": 191 line = line[:-1] 192 stable = False 193 parts = line.strip().split() 194 try: 195 self.name 196 except NodeCompleteError: 197 self.name = parts[0] 198 parts = parts[1:] 199 for part in parts: 200 self.arguments.append(part) 201 self._stable = stable 202 203 @abc_mod.abstractmethod 204 def add_line(self, line): 205 super(Directive, self).add_line(line) 206 207 @property 208 def stable(self): 209 return self._stable 210 211 @property 212 def complete(self): 213 return super(Directive, Directive).complete.fget(self) 214 @complete.setter 215 def complete(self, val): 216 if val and not self.stable: 217 raise NodeCompleteError("Can't set an unstable Directive to complete.") 218 super(Directive, Directive).complete.fset(self, val) 219 220 @property 221 def content(self): 222 name = self.name if not self.arguments else self.name + " " 223 return "%s%s" % (name, " ".join(arg for arg in self.arguments)) 224 225 def __repr__(self): 226 name = self.name or "" 227 if name: 228 name += " " 229 return "<%sDirective at %s>" % ( 230 name, 231 id(self), 232 ) 233 def pprint(self, depth=0): 234 return "%s%s %s" % ( 235 INDENT*depth, 236 self.name, 237 " ".join([arg for arg in self.arguments]), 238 ) 239 240 241class SimpleDirective(Directive): 242 MATCH_RE = r"\s*%s(\s+.*)*\s*[\\]?$" % Directive.NAME_RE 243 def add_line(self, line): 244 try: 245 self.add_to_header(line) 246 except DirectiveError as e: 247 raise InvalidLineError(str(e)) 248 super(SimpleDirective, self).add_line(line) 249 def __str__(self): 250 if not self.lines: 251 raise NodeCompleteError("Can't turn an uninitialized simple directive into a string.") 252 return super(SimpleDirective, self).__str__() 253_NODES.append(SimpleDirective) 254 255 256class ComplexNode(Node): 257 """ 258 A node that is composed of a list of other nodes. 259 260 """ 261 NESTING_LIMIT = 10 262 def __init__(self, candidates): 263 super(ComplexNode, self).__init__() 264 self.candidates = candidates 265 266 class Nodes(object): 267 def __get__(self, obj, cls): 268 if not hasattr(obj, "_nodes"): 269 class ChangeAwareNodes(ListAdapter): 270 # disallow changing completion status of nodes once added? 271 def __setitem__(self, index, val): 272 self.items[index] = val 273 if obj.complete: 274 obj.changed = True 275 def __delitem__(self, index): 276 del self.items[index] 277 if obj.complete: 278 obj.changed = True 279 def insert(self, index, val): 280 self.items.insert(index, val) 281 if obj.complete: 282 obj.changed = True 283 @property 284 def stable(self): 285 #if not self.items or self.items[-1].complete: 286 # return True 287 #return False 288 return all(item.stable for item in self.items) 289 def __repr__(self): 290 return repr(self.items) 291 @property 292 def directives(self): 293 if not obj.complete or obj.changed or not hasattr(self, "_names"): 294 self._directives = {} 295 for item in self.items: 296 if isinstance(item, Directive): 297 if item.name not in self._directives: 298 self._directives[item.name] = [] 299 if item not in self._directives[item.name]: 300 self._directives[item.name].append(item) 301 return self._directives 302 #result = {} 303 #for item in self.items: 304 # if isinstance(item, Directive): 305 # if item.name not in result: 306 # result[item.name] = [] 307 # if item not in result[item.name]: 308 # result[item.name].append(item) 309 #return result 310 obj._nodes = ChangeAwareNodes() 311 return obj._nodes 312 nodes = Nodes() 313 314 @property 315 def complete(self): 316 return self._complete 317 @complete.setter 318 def complete(self, val): 319 if val and not self.nodes.stable: 320 raise NodeCompleteError("The node list is not stable. Likely the last node is still waiting for additional lines.") 321 super(ComplexNode, ComplexNode).complete.fset(self, val) 322 323 @property 324 def stable(self): 325 return self.nodes.stable 326 327 def get_node(self, line): 328 for node_cls in self.candidates: 329 if node_cls.match(line): 330 return node_cls() 331 raise NodeMatchError("No matching node: %s" % line) 332 333 def add_line(self, line, depth=0): 334 if self.complete: 335 raise NodeCompleteError("Can't add lines to a complete Node.") 336 if depth > self.NESTING_LIMIT: 337 raise NestingLimitError("Cannot nest directives more than %s levels." % self.NESTING_LIMIT) 338 339 if not self.nodes.stable: 340 node = self.nodes[-1] 341 if isinstance(node, ComplexDirective): 342 node.add_line(line, depth=depth+1) 343 else: 344 node.add_line(line) 345 else: 346 newnode = self.get_node(line) 347 newnode.add_line(line) 348 self.nodes.append(newnode) 349 if not self.nodes.stable: 350 self.complete = False 351 352 def __str__(self): 353 if not self.complete: 354 raise NodeCompleteError("Can't turn an incomplete complex node into a string.") 355 return "\n".join(str(node) for node in self.nodes) 356 def pprint(self, depth=0): 357 if not self.complete: 358 raise NodeCompleteError("Can't print an incomplete complex node.") 359 return "\n".join(node.pprint(depth) for node in self.nodes) 360 361 362class ComplexDirective(Directive): 363 MATCH_RE = r"\s*<\s*%s(\s+[^>]*)*\s*(>\s*|[\\])$" % Directive.NAME_RE 364 BODY_CLASS = ComplexNode 365 CANDIDATES = _NODES 366 367 def __init__(self): 368 super(ComplexDirective, self).__init__() 369 self.header = SimpleDirective() 370 self.body = self.BODY_CLASS(self.CANDIDATES) 371 self.tail = "" 372 self.tailmatch = False 373 374 @property 375 def name(self): 376 return self.header.name 377 @property 378 def arguments(self): 379 return self.header.arguments 380 @property 381 def stable(self): 382 #return self.header.stable and self.body.stable 383 return self.complete 384 385 @property 386 def complete(self): 387 if self.body.complete and not self.header.complete: 388 raise NodeCompleteError("Body is complete but header isn't.") 389 if self.tailmatch and not self.body.complete: 390 raise NodeCompleteError("Tail is matched but body is not complete.") 391 return self.header.complete and self.body.complete and self.tailmatch 392 @complete.setter 393 def complete(self, val): 394 if val and not (self.body.complete and self.header.complete and self.tailmatch): 395 raise NodeCompleteError("Cannot set a complex directive to complete if its parts aren't complete") 396 if not val and self.body.complete and self.header.complete and self.tailmatch: 397 raise NodeCompleteError("Cannot set a complex directive to not complete if its parts are all complete") 398 super(ComplexDirective, ComplexDirective).complete.fset(self, val) 399 400 def add_line(self, line, depth=0): 401 if self.complete: 402 raise NodeCompleteError("Can't add lines to a complete Node.") 403 if not self.header.complete: 404 try: 405 super(ComplexDirective, self).add_line(line) 406 except NodeCompleteError: 407 pass 408 if ">" in line: 409 header, remainder = line.split(">", 1) 410 if remainder.strip() != "": 411 raise InvalidLineError("Directive header has an extraneous tail: %s" % line) 412 else: 413 header = line 414 header = header.lstrip() 415 if header and header.startswith("<"): 416 try: 417 self.header.name 418 except NodeCompleteError: 419 header = header[1:] 420 if header and "<" in header: 421 raise InvalidLineError("Angle brackets not allowed in complex directive header. Received: %s" % line) 422 if header: 423 try: 424 self.header.add_to_header(header) 425 except (NodeCompleteError, DirectiveError) as e: 426 raise InvalidLineError(str(e)) 427 if ">" in line: 428 self.header.complete = True 429 elif not self.body.stable: 430 self.body.add_line(line, depth+1) 431 elif re_mod.match("\s*</%s>\s*$" % self.name, line): 432 self.body.complete = True 433 self.tail = line 434 self.tailmatch = True 435 elif not self.body.complete: 436 self.body.add_line(line, depth+1) 437 else: 438 raise InvalidLineError("Expecting closing tag. Got: %s" % line) 439 440 def __str__(self): 441 if not self.lines: 442 raise NodeCompleteError("Can't turn an uninitialized complex directive into a string.") 443 if not self.complete: 444 raise NodeCompleteError("Can't turn an incomplete complex directive into a string.") 445 return "%s\n%s%s%s" % ( 446 "\n".join(self.lines), 447 self.body, 448 "\n" if self.body.nodes else "", 449 self.tail, 450 ) 451 def pprint(self, depth=0): 452 if not self.complete: 453 raise NodeCompleteError("Can't print an incomplete complex directive.") 454 return "%s<%s>\n%s%s%s</%s>" % ( 455 INDENT*depth, 456 self.header.pprint(), 457 self.body.pprint(depth=depth+1), 458 "\n" if self.body.nodes else "", 459 INDENT*depth, 460 self.name, 461 ) 462_NODES.append(ComplexDirective) 463 464 465class ApacheConfParser(ComplexNode): 466 def __init__(self, source, infile=True, delay=False, count=None): 467 """Count is the starting number for line counting...""" 468 super(ApacheConfParser, self).__init__(_NODES) 469 self.source = source.splitlines() 470 if infile: 471 self.source = (line.strip("\n") for line in open(source)) 472 self.count = count 473 if not delay: 474 self.parse() 475 476 def parse(self): 477 if self.complete: 478 return 479 for line in self.source: 480 if self.count is not None: 481 print(self.count) 482 self.count += 1 483 self.add_line(line) 484 self.complete = True 485 486 487