nicer comment for DE entry
[platform/upstream/crda.git] / dbparse.py
1 #!/usr/bin/env python
2
3 import sys
4
5 band_flags = {
6     'CCK': 1,
7     'OFDM': 2,
8 }
9
10 class FreqBand(object):
11     def __init__(self, start, end, bw, flags, comments=None):
12         self.start = start
13         self.end = end
14         self.maxbw = bw
15         self.flags = flags
16         self.comments = comments or []
17
18     def __cmp__(self, other):
19         s = self
20         o = other
21         if not isinstance(o, FreqBand):
22             return False
23         return cmp((s.start, s.end, s.maxbw, s.flags), (o.start, o.end, o.maxbw, o.flags))
24
25     def __hash__(self):
26         s = self
27         return hash((s.start, s.end, s.maxbw, s.flags))
28
29     def __str__(self):
30         flags = []
31         for f, v in band_flags.iteritems():
32             if self.flags & v:
33                 flags.append(f)
34         return '<FreqBand %.3f - %.3f @ %.3f%s>' % (
35                   self.start, self.end, self.maxbw, ', '.join(flags))
36
37 class PowerRestriction(object):
38     def __init__(self, environment, max_ant_gain, max_ir_ptmp,
39                  max_ir_ptp, max_eirp_ptmp, max_eirp_ptp,
40                  comments = None):
41         self.environment = environment
42         self.max_ant_gain = max_ant_gain
43         self.max_ir_ptmp = max_ir_ptmp
44         self.max_ir_ptp = max_ir_ptp
45         self.max_eirp_ptmp = max_eirp_ptmp
46         self.max_eirp_ptp = max_eirp_ptp
47         self.comments = comments or []
48
49     def __cmp__(self, other):
50         s = self
51         o = other
52         if not isinstance(o, PowerRestriction):
53             return False
54         return cmp((s.environment, s.max_ant_gain, s.max_ir_ptmp,
55                     s.max_ir_ptp, s.max_eirp_ptmp, s.max_eirp_ptp),
56                    (o.environment, o.max_ant_gain, o.max_ir_ptmp,
57                     o.max_ir_ptp, o.max_eirp_ptmp, o.max_eirp_ptp))
58
59     def __str__(self):
60         return '<PowerRestriction ...>'
61
62     def __hash__(self):
63         s = self
64         return hash((s.environment, s.max_ant_gain, s.max_ir_ptmp,
65                      s.max_ir_ptp, s.max_eirp_ptmp, s.max_eirp_ptp))
66
67 class Country(object):
68     def __init__(self, restrictions=None, comments=None):
69         # tuple of (freqband, powerrestriction)
70         self._restrictions = restrictions or []
71         self.comments = comments or []
72
73     def add(self, band, power):
74         assert isinstance(band, FreqBand)
75         assert isinstance(power, PowerRestriction)
76         self._restrictions.append((band, power))
77         self._restrictions.sort()
78
79     def __contains__(self, tup):
80         return tup in self._restrictions
81
82     def __str__(self):
83         r = ['(%s, %s)' % (str(b), str(p)) for b, p in self._restrictions]
84         return '<Country (%s)>' % (', '.join(r))
85
86     def _get_restrictions_tuple(self):
87         return tuple(self._restrictions)
88     restrictions = property(_get_restrictions_tuple)
89
90 class SyntaxError(Exception):
91     pass
92
93 class DBParser(object):
94     def __init__(self):
95         pass
96
97     def _syntax_error(self, txt=None):
98         txt = txt and ' (%s)' % txt or ''
99         raise SyntaxError("Syntax error in line %d%s" % (self._lineno, txt))
100
101     def _warn(self, txt):
102         sys.stderr.write("Warning (line %d): %s\n" % (self._lineno, txt))
103
104     def _parse_band_def(self, bname, banddef, dupwarn=True):
105         line = banddef
106         try:
107             freqs, line = line.split(',', 1)
108         except ValueError:
109             freqs = line
110             line = ''
111
112         flags = [f.upper() for f in line.split(',') if f]
113
114         try:
115             freqs, bw = freqs.split('@')
116             bw = float(bw)
117         except ValueError:
118             bw = 20.0
119
120         try:
121             start, end = freqs.split('-')
122             start = float(start)
123             end = float(end)
124         except ValueError:
125             self._syntax_error("band must have frequency range")
126
127         fl = 0
128         for f in flags:
129             if not f in band_flags:
130                 self._syntax_error("Invalid band flag")
131             fl |= band_flags[f]
132
133         b = FreqBand(start, end, bw, fl, comments=self._comments)
134         self._comments = []
135         self._banddup[bname] = bname
136         if b in self._bandrev:
137             if dupwarn:
138                 self._warn('Duplicate band definition ("%s" and "%s")' % (
139                               bname, self._bandrev[b]))
140             self._banddup[bname] = self._bandrev[b]
141         self._bands[bname] = b
142         self._bandrev[b] = bname
143         self._bandline[bname] = self._lineno
144
145     def _parse_band(self, line):
146         try:
147             bname, line = line.split(':', 1)
148             if not bname:
149                 self._syntax_error("'band' keyword must be followed by name")
150         except ValueError:
151             self._syntax_error("band name must be followed by colon")
152
153         self._parse_band_def(bname, line)
154
155     def _parse_power(self, line):
156         try:
157             pname, line = line.split(':', 1)
158             if not pname:
159                 self._syntax_error("'power' keyword must be followed by name")
160         except ValueError:
161             self._syntax_error("power name must be followed by colon")
162
163         self._parse_power_def(pname, line)
164
165     def _parse_power_def(self, pname, line, dupwarn=True):
166         try:
167             (environ,
168              max_ant_gain,
169              max_ir_ptmp,
170              max_ir_ptp,
171              max_eirp_ptmp,
172              max_eirp_ptp) = line.split(',')
173             max_ant_gain = float(max_ant_gain)
174             max_ir_ptmp = float(max_ir_ptmp)
175             max_ir_ptp = float(max_ir_ptp)
176             max_eirp_ptmp = float(max_eirp_ptmp)
177             max_eirp_ptp = float(max_eirp_ptp)
178         except ValueError:
179             self._syntax_error("invalid power data")
180
181         if environ == 'OI':
182             environ = ' '
183         if not environ in ('I', 'O', ' '):
184             self._syntax_error("Invalid environment specifier")
185
186         p = PowerRestriction(environ, max_ant_gain, max_ir_ptmp,
187                              max_ir_ptp, max_eirp_ptmp, max_eirp_ptp,
188                              comments=self._comments)
189         self._comments = []
190         self._powerdup[pname] = pname
191         if p in self._powerrev:
192             if dupwarn:
193                 self._warn('Duplicate power definition ("%s" and "%s")' % (
194                               pname, self._powerrev[p]))
195             self._powerdup[pname] = self._powerrev[p]
196         self._power[pname] = p
197         self._powerrev[p] = pname
198         self._powerline[pname] = self._lineno
199
200     def _parse_country(self, line):
201         try:
202             cname, line = line.split(':', 1)
203             if not cname:
204                 self._syntax_error("'country' keyword must be followed by name")
205             if line:
206                 self._syntax_error("extra data at end of country line")
207         except ValueError:
208             self._syntax_error("country name must be followed by colon")
209
210         if not cname in self._countries:
211             self._countries[cname] = Country(comments=self._comments)
212         self._comments = []
213         self._current_country = self._countries[cname]
214         self._current_country_name = cname
215
216     def _parse_country_item(self, line):
217         if line[0] == '(':
218             try:
219                 band, pname = line[1:].split('),')
220                 bname = 'UNNAMED %d' % self._lineno
221                 self._parse_band_def(bname, band, dupwarn=False)
222             except:
223                 self._syntax_error("Badly parenthesised band definition")
224         else:
225             try:
226                 bname, pname = line.split(',', 1)
227                 if not bname:
228                     self._syntax_error("country definition must have band")
229                 if not pname:
230                     self._syntax_error("country definition must have power")
231             except ValueError:
232                 self._syntax_error("country definition must have band and power")
233
234         if pname[0] == '(':
235             if not pname[-1] == ')':
236                 self._syntax_error("Badly parenthesised power definition")
237             power = pname[1:-1]
238             pname = 'UNNAMED %d' % self._lineno
239             self._parse_power_def(pname, power, dupwarn=False)
240
241         if not bname in self._bands:
242             self._syntax_error("band does not exist")
243         if not pname in self._power:
244             self._syntax_error("power does not exist")
245         self._bands_used[bname] = True
246         self._power_used[pname] = True
247         # de-duplicate so binary database is more compact
248         bname = self._banddup[bname]
249         pname = self._powerdup[pname]
250         b = self._bands[bname]
251         p = self._power[pname]
252         if (b, p) in self._current_country:
253             self._warn('Rule "%s, %s" added to "%s" twice' % (
254                           bname, pname, self._current_country_name))
255         else:
256             self._current_country.add(b, p)
257
258     def parse(self, f):
259         self._current_country = None
260         self._bands = {}
261         self._power = {}
262         self._countries = {}
263         self._bands_used = {}
264         self._power_used = {}
265         self._bandrev = {}
266         self._powerrev = {}
267         self._banddup = {}
268         self._powerdup = {}
269         self._bandline = {}
270         self._powerline = {}
271
272         self._comments = []
273
274         self._lineno = 0
275         for line in f:
276             self._lineno += 1
277             line = line.strip()
278             if line[0:1] == '#':
279                 self._comments.append(line[1:].strip())
280             line = line.replace(' ', '').replace('\t', '')
281             if not line:
282                 self._comments = []
283             line = line.split('#')[0]
284             if not line:
285                 continue
286             if line[0:4] == 'band':
287                 self._parse_band(line[4:])
288                 self._current_country = None
289                 self._comments = []
290             elif line[0:5] == 'power':
291                 self._parse_power(line[5:])
292                 self._current_country = None
293                 self._comments = []
294             elif line[0:7] == 'country':
295                 self._parse_country(line[7:])
296                 self._comments = []
297             elif self._current_country is not None:
298                 self._parse_country_item(line)
299                 self._comments = []
300             else:
301                 self._syntax_error("Expected band, power or country definition")
302
303         countries = {}
304         for k, v in self._countries.iteritems():
305             for k in k.split(','):
306                 countries[k] = v
307         bands = {}
308         for k, v in self._bands.iteritems():
309             if k in self._bands_used:
310                 bands[self._banddup[k]] = v
311                 continue
312             # we de-duplicated, but don't warn again about the dupes
313             if self._banddup[k] == k:
314                 self._lineno = self._bandline[k]
315                 self._warn('Unused band definition "%s"' % k)
316         power = {}
317         for k, v in self._power.iteritems():
318             if k in self._power_used:
319                 power[self._powerdup[k]] = v
320                 continue
321             # we de-duplicated, but don't warn again about the dupes
322             if self._powerdup[k] == k:
323                 self._lineno = self._powerline[k]
324                 self._warn('Unused power definition "%s"' % k)
325         return countries