Make ReadConfigLine an iterator
[mining-tools:gitdm.git] / ConfigFile.py
1 #
2 # Stuff for dealing with configuration files.
3 #
4 #
5 # This code is part of the LWN git data miner.
6 #
7 # Copyright 2007-11 Eklektix, Inc.
8 # Copyright 2007-11 Jonathan Corbet <corbet@lwn.net>
9 #
10 # This file may be distributed under the terms of the GNU General
11 # Public License, version 2.
12 #
13 import sys, re, datetime, os.path
14 import database
15
16 class ReadConfigLine:
17     """
18         ReadConfigLine provides a iterator to extract line
19         from an config file without comments.
20
21         Typical use case:
22
23             fd = open(filename, 'r')
24             for line in ReadConfigLine(fd):
25                 parse_line(line)
26             fd.close(fd)
27     """
28
29     def __init__(self, fd):
30         self.fd = fd
31         self.buffer = None
32         self.patch = []
33
34     def __iter__(self):
35         return self
36
37     def next(self):
38         line = self.fd.readline()
39         while line:
40             line = line.split('#')[0] # Get rid of any comments
41             line = line.strip()       # and extra white space
42             if len(line) == 0:       # we got rid of everything
43                 line = self.fd.readline()
44             else:
45                 break
46
47         if not line:
48             raise StopIteration
49
50         return line
51
52
53 #
54 # Give up and die.
55 #
56 def croak (message):
57     sys.stderr.write (message + '\n')
58     sys.exit (1)
59
60 #
61 # Read a list of email aliases.
62 #
63 def ReadEmailAliases (name):
64     try:
65         fd = open (name, 'r')
66     except IOError:
67         croak ('Unable to open email alias file %s' % (name))
68
69     for line in ReadConfigLine (fd):
70         m = re.match ('^("[^"]+"|\S+)\s+(.+)$', line)
71         if not m or len (m.groups ()) != 2:
72             croak ('Funky email alias line "%s"' % (line))
73         if m and m.group (2).find ('@') <= 0:
74             croak ('Non-addresses in email alias "%s"' % (line))
75         database.AddEmailAlias (m.group (1).replace ('"', ''), m.group (2))
76  
77     fd.close ()
78
79 #
80 # The Email/Employer map
81 #
82 EMMpat = re.compile (r'^([^\s]+)\s+([^<]+)\s*(<\s*(\d+-\d+-\d+)\s*)?$')
83
84 def ReadEmailEmployers (name):
85     try:
86         fd = open (name, 'r')
87     except IOError:
88         croak ('Unable to open email/employer file %s' % (name))
89
90     for line in ReadConfigLine (fd):
91         m = EMMpat.match (line)
92         if not m:
93             croak ('Funky email/employer line "%s"' % (line))
94         email = m.group (1)
95         company = m.group (2).strip ()
96         enddate = ParseDate (m.group (4))
97         database.AddEmailEmployerMapping (email, company, enddate)
98  
99     fd.close ()
100
101 def ParseDate (cdate):
102     if not cdate:
103         return None
104     sdate = cdate.split ('-')
105     return datetime.date (int (sdate[0]), int (sdate[1]), int (sdate[2]))
106
107
108 def ReadGroupMap (fname, employer):
109     try:
110         fd = open (fname, 'r')
111     except IOError:
112         croak ('Unable to open group map file %s' % (fname))
113
114     for line in ReadConfigLine (fd):
115         database.AddEmailEmployerMapping (line, employer)
116
117     fd.close ()
118
119 #
120 # Read in a virtual employer description.
121 #
122 def ReadVirtual (fd, name):
123     ve = database.VirtualEmployer (name)
124
125     for line in ReadConfigLine (fd):
126         sl = line.split (None, 1)
127         first = sl[0]
128         if first == 'end':
129             ve.store ()
130             return
131         #
132         # Zap the "%" syntactic sugar if it's there
133         #
134         if first[-1] == '%':
135             first = first[:-1]
136         try:
137             percent = int (first)
138         except ValueError:
139             croak ('Bad split value "%s" for virtual empl %s' % (first, name))
140         if not (0 < percent <= 100):
141             croak ('Bad split value "%s" for virtual empl %s' % (first, name))
142         ve.addsplit (' '.join (sl[1:]), percent/100.0)
143     #
144     # We should never get here
145     #
146     croak ('Missing "end" line for virtual employer %s' % (name))
147
148 #
149 # Read file type patterns for more fine graned reports
150 #
151 def ReadFileType (filename):
152     try:
153         fd = open (filename, 'r')
154     except IOError:
155         croak ('Unable to open file type mapping file %s' % (filename))
156     patterns = {}
157     order = []
158     regex_order = re.compile ('^order\s+(.*)$')
159     regex_file_type = re.compile ('^filetype\s+(\S+)\s+(.+)$')
160
161     for line in ReadConfigLine (fd):
162         o = regex_order.match (line)
163         if o:
164             # Consider only the first definition in the config file
165             elements = o.group(1).replace (' ', '')
166             order = order or elements.split(',')
167             continue
168
169         m = regex_file_type.match (line)
170         if not m or len (m.groups ()) != 2:
171             ConfigFile.croak ('Funky file type line "%s"' % (line))
172         if not patterns.has_key (m.group (1)):
173             patterns[m.group (1)] = []
174         if m.group (1) not in order:
175             print '%s not found, appended to the last order' % m.group (1)
176             order.append (m.group (1))
177
178         patterns[m.group (1)].append (re.compile (m.group (2), re.IGNORECASE))
179
180     fd.close ()
181     return patterns, order
182
183 #
184 # Read an overall config file.
185 #
186
187 def ConfigFile (name, confdir):
188     try:
189         fd = open (name, 'r')
190     except IOError:
191         croak ('Unable to open config file %s' % (name))
192
193     for line in ReadConfigLine (fd):
194         sline = line.split (None, 2)
195         if len (sline) < 2:
196             croak ('Funky config line: "%s"' % (line))
197         if sline[0] == 'EmailAliases':
198             ReadEmailAliases (os.path.join (confdir, sline[1]))
199         elif sline[0] == 'EmailMap':
200             ReadEmailEmployers (os.path.join (confdir, sline[1]))
201         elif sline[0] == 'GroupMap':
202             if len (sline) != 3:
203                 croak ('Funky group map line "%s"' % (line))
204             ReadGroupMap (os.path.join (confdir, sline[1]), sline[2])
205         elif sline[0] == 'VirtualEmployer':
206             ReadVirtual (file, ' '.join (sline[1:]))
207         elif sline[0] == 'FileTypeMap':
208             patterns, order = ReadFileType (os.path.join (confdir, sline[1]))
209             database.FileTypes = database.FileType (patterns, order)
210         else:
211             croak ('Unrecognized config line: "%s"' % (line))
212
213
214 if __name__ == '__main__':
215     '''Test the iterato for reading configuration files'''
216     try:
217         fd = open(sys.argv[1])
218     except:
219         croak('Usage: %s <config-file>' % sys.argv[0])
220     
221     for line in ReadConfigLine(fd):
222         print line
223