e5a30739635347dbb7345e40284401e79dd544df
[ais.git] / bin / ntools.py
1 # -*- coding: utf-8 -*-
2 from __future__ import division
3
4 import os
5 import calendar
6 import logging
7
8 __all__ = [
9     'IPV4_IN_IPV6_PREFIX',
10     'datetime_to_timestamp',
11     'read_cfg',
12     'clean_ais_charset',
13     'clean_alnum',
14     'clean_alnum_unicode',
15     'open_with_mkdirs',
16     'logliner',
17     'dumpsource',
18     'xml_escape',
19     'alarm',
20     'str_split_column_ipv6',
21     'formataddr',
22     'LatLonFormatError',
23     'clean_latitude',
24     'clean_longitude',
25     ]
26
27 IPV4_IN_IPV6_PREFIX = '::ffff:'
28
29 def datetime_to_timestamp(dt):
30     return calendar.timegm(dt.utctimetuple())
31
32 def read_cfg(filename):
33     '''
34     Function that reads a file in the form
35     key=value
36     and returns the resulting dictionary
37     '''
38     cfg = {}
39     for line in file(filename).readlines():
40         line = line.rstrip('\r\n\0')
41         line = unicode(line, 'utf-8')
42         if line.startswith(u'#'):
43             continue # skip comments
44         spl = line.split(u'=', 1)
45         if len(spl) == 2:
46             cfg[spl[0]] = spl[1]
47         else:
48             cfg[spl[0]] = None
49     return cfg
50
51
52 def clean_ais_charset(txt):
53     assert isinstance(txt, str)
54     result = ''
55     for c in txt:
56         oc = ord(c)
57         if oc < 32 or oc > 95:
58             result += ''
59         else:
60             result += c
61     return result
62
63 def clean_alnum(txt):
64     assert isinstance(txt, str)
65     result = ''
66     for c in txt:
67         if ( c >= '0' and c <= '9' ) or ( c >= 'A' and c <= 'Z' ):
68             result += c
69     return result
70
71 def clean_alnum_unicode(txt):
72     assert isinstance(txt, unicode)
73     return unicode(clean_alnum(txt.encode('ascii7', 'replace')))
74     
75     
76 def open_with_mkdirs(filename, mode):
77     try:
78         return file(filename, mode)
79     except IOError, ioerr:
80         logging.warning("file(%s,%s): errno %s %s", filename, mode, ioerr.errno, ioerr.strerror)
81         # FIXME only if doesn't exists ...
82         #print 'Creating directory', os.path.dirname(filename)
83         os.makedirs(os.path.dirname(filename))
84         return file(filename, mode)
85
86
87 # log file source
88 def logliner(filename):
89     for line in file(filename).readlines():
90         yield line
91
92
93 # debug/display wraper source
94 def dumpsource(source):
95     for line in source:
96         while line and line[-1] in '\r\n\0':
97             line = line[:-1]
98         print "INPUT", line
99         yield line
100
101 def xml_escape(txt):
102     return txt.replace(u'&', u'&amp;').replace(u'<', u'&lt;')
103
104 def alarm():
105     os.system('touch /home/nirgal/kod/ais/alarm &')
106
107
108 def str_split_column_ipv6(txt):
109     '''
110     Helper function that will split a column separated string of tokens.
111     It will take care not to ignore : in [] for ipv6 support.
112     '''
113     res = []
114     in_bracket = False
115     iprev = None
116     for i in range(len(txt)):
117         if not in_bracket:
118             if txt[i] == ':':
119                 res.append(txt[iprev:i])
120                 iprev = i+1
121             elif txt[i] == '[':
122                 in_bracket = True
123         else: # in bracket
124             if txt[i] == ']':
125                 in_bracket = False
126     res.append(txt[iprev:])
127     return res
128
129
130 def formataddr(addr):
131     if addr.startswith(IPV4_IN_IPV6_PREFIX):
132         return addr[7:]
133     elif ':' in addr:
134         return '['+addr+']'
135     else:
136         return addr
137
138
139
140
141 class LatLonFormatError(ValueError):
142     pass
143
144
145 def clean_latitude(data):
146     '''
147     Convert a string latitude into a float.
148     Raises LatLonFormatError on error.
149     '''
150     data = data.replace(u"''", u'"') # common mistake
151     data = data.replace(u' ', u'') # remove spaces
152     sides = u'SN'
153     if not data:
154         return None
155     tmp, side = data[:-1], data[-1]
156     if side == sides[0]:
157         side = -1
158     elif side == sides[1]:
159         side = 1
160     else:
161         raise LatLonFormatError(u'Last character must be either %s or %s.', sides[0], sides[1])
162     spl = tmp.split(u'°')
163     if len(spl) == 1:
164         raise LatLonFormatError(u'You need to use the ° character.')
165     d, tmp = spl
166     try:
167         d = float(d)
168     except ValueError:
169         raise LatLonFormatError(u'Degrees must be an number. It\'s %s.', d)
170     spl = tmp.split(u"'", 1)
171     if len(spl) == 1:
172         # no ' sign: ok only if there is nothing but the side after °
173         # we don't accept seconds if there is no minutes:
174         # It might be an entry mistake
175         tmp = spl[0]
176         if len(tmp) == 0:
177             m = s = 0
178         else:
179             raise LatLonFormatError(u'You must use the \' character between ° and %s.', data[-1])
180     else:
181         m, tmp = spl
182         try:
183             m = float(m)
184         except ValueError:
185             raise LatLonFormatError(u'Minutes must be an number. It\'s %s.', m)
186         if len(tmp) == 0:
187             s = 0
188         else:
189             if tmp[-1] != '"':
190                 raise LatLonFormatError(u'You must use the " character between seconds and %s.', data[-1])
191             s = tmp[:-1]
192             try:
193                 s = float(s)
194             except ValueError:
195                 raise LatLonFormatError(u'Seconds must be an number. It\'s %s.', s)
196     data = side * ( d + m / 60 + s / 3600)
197
198     if data < -90 or data > 90:
199         raise LatLonFormatError(u'%s in not in -90..90 range', data)
200     return data
201
202
203
204 def clean_longitude(data):
205     '''
206     Convert a string latitude into a float.
207     Raises LatLonFormatError on error.
208     '''
209     data = data.replace(u"''", u'"') # common mistake
210     data = data.replace(u' ', u'') # remove spaces
211     sides = u'WE'
212     if not data:
213         return None
214     tmp, side = data[:-1], data[-1]
215     if side == sides[0]:
216         side = -1
217     elif side == sides[1]:
218         side = 1
219     else:
220         raise LatLonFormatError(u'Last character must be either %s or %s.', sides[0], sides[1])
221     spl = tmp.split(u'°')
222     if len(spl) == 1:
223         raise LatLonFormatError(u'You need to use the ° character.')
224     d, tmp = spl
225     try:
226         d = float(d)
227     except ValueError:
228         raise LatLonFormatError(u'Degrees must be an number. It\'s %s.', d)
229     spl = tmp.split(u"'", 1)
230     if len(spl) == 1:
231         # no ' sign: ok only if there is nothing but the side after °
232         # we don't accept seconds if there is no minutes:
233         # It might be an entry mistake
234         tmp = spl[0]
235         if len(tmp) == 0:
236             m = s = 0
237         else:
238             raise LatLonFormatError(u'You must use the \' character between ° and %s.', data[-1])
239     else:
240         m, tmp = spl
241         try:
242             m = float(m)
243         except ValueError:
244             raise LatLonFormatError(u'Minutes must be an number. It\'s %s.', m)
245         if len(tmp) == 0:
246             s = 0
247         else:
248             if tmp[-1] != '"':
249                 raise LatLonFormatError(u'You must use the " character between seconds and %s.', data[-1])
250             s = tmp[:-1]
251             try:
252                 s = float(s)
253             except ValueError:
254                 raise LatLonFormatError(u'Seconds must be an number. It\'s %s.', s)
255     data = side * ( d + m / 60 + s / 3600)
256
257     if data < -180 or data > 180:
258         raise LatLonFormatError(u'%s in not in -180..180 range', data)
259     return data
260
261 #if __name__ == '__main__':
262 #    for test in ('12:34:56:78',
263 #                 ':12::56:',
264 #                 '1:2:3:abc:[bd:ef::]:45',
265 #                 '1:2:3:abc:[bd:ef:]:]:45'):
266 #        print test, str_split_column_ipv6(test)