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