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