1# dictfile.py
2#
3# Copyright 2009 Kristoffer Gronlund <kristoffer.gronlund@purplescout.se>
4
5""" Dictionary File
6
7Implements an iterable file format that handles the
8RADIUS $INCLUDE directives behind the scene.
9"""
10
11import os
12import six
13
14
15class _Node(object):
16    """Dictionary file node
17
18    A single dictionary file.
19    """
20    __slots__ = ('name', 'lines', 'current', 'length', 'dir')
21
22    def __init__(self, fd, name, parentdir):
23        self.lines = fd.readlines()
24        self.length = len(self.lines)
25        self.current = 0
26        self.name = os.path.basename(name)
27        path = os.path.dirname(name)
28        if os.path.isabs(path):
29            self.dir = path
30        else:
31            self.dir = os.path.join(parentdir, path)
32
33    def Next(self):
34        if self.current >= self.length:
35            return None
36        self.current += 1
37        return self.lines[self.current - 1]
38
39
40class DictFile(object):
41    """Dictionary file class
42
43    An iterable file type that handles $INCLUDE
44    directives internally.
45    """
46    __slots__ = ('stack')
47
48    def __init__(self, fil):
49        """
50        @param fil: a dictionary file to parse
51        @type fil: string or file
52        """
53        self.stack = []
54        self.__ReadNode(fil)
55
56    def __ReadNode(self, fil):
57        node = None
58        parentdir = self.__CurDir()
59        if isinstance(fil, six.string_types):
60            fname = None
61            if os.path.isabs(fil):
62                fname = fil
63            else:
64                fname = os.path.join(parentdir, fil)
65            fd = open(fname, "rt")
66            node = _Node(fd, fil, parentdir)
67            fd.close()
68        else:
69            node = _Node(fil, '', parentdir)
70        self.stack.append(node)
71
72    def __CurDir(self):
73        if self.stack:
74            return self.stack[-1].dir
75        else:
76            return os.path.realpath(os.curdir)
77
78    def __GetInclude(self, line):
79        line = line.split("#", 1)[0].strip()
80        tokens = line.split()
81        if tokens and tokens[0].upper() == '$INCLUDE':
82            return " ".join(tokens[1:])
83        else:
84            return None
85
86    def Line(self):
87        """Returns line number of current file
88        """
89        if self.stack:
90            return self.stack[-1].current
91        else:
92            return -1
93
94    def File(self):
95        """Returns name of current file
96        """
97        if self.stack:
98            return self.stack[-1].name
99        else:
100            return ''
101
102    def __iter__(self):
103        return self
104
105    def __next__(self):
106        while self.stack:
107            line = self.stack[-1].Next()
108            if line == None:
109                self.stack.pop()
110            else:
111                inc = self.__GetInclude(line)
112                if inc:
113                    self.__ReadNode(inc)
114                else:
115                    return line
116        raise StopIteration
117    next = __next__  # BBB for python <3
118