more robust syntax checks in crontab parser
[dbuscron] / dbuscron / parser.py
1 from __future__ import with_statement
2 import re
3 from dbuscron.bus import DbusBus
4
5 def product(*args):
6     if args:
7         head, tail = args[0], args[1:]
8         for h in head:
9             for t in product(*tail):
10                 yield (h,) + t
11
12     else:
13         yield ()
14
15 class CrontabParserError(SyntaxError):
16     def __init__(self, message, lineno, expected=None):
17         if expected:
18             if isinstance(expected, (tuple, list)):
19                 exp = ' (expected %s or %s)' % (', '.join(expected[:-1]), expected[-1])
20         else:
21             exp = ''
22
23         msg = '%s%s at line %d' % (message, exp, lineno)
24
25         SyntaxError.__init__(self, msg)
26
27 class CrontabParser(object):
28     __fields_sep = re.compile(r'\s+')
29     __envvar_sep = re.compile(r'\s*=\s*')
30     __fields_chk = {
31             'bus_'         : None,
32             'type_'        : ('signal','method_call','method_return','error'),
33             'sender_'      : None,
34             'interface_'   : re.compile(r'^[a-zA-Z][a-zA-Z0-9_.]+$'),
35             'path_'        : re.compile(r'^/[a-zA-Z0-9_/]+$'),
36             'member_'      : re.compile(r'^[a-zA-Z][a-zA-Z0-9_]+$'),
37             'destination_' : None,
38             'args_'        : None,
39             }
40     __fields = [
41             'bus_',
42             'type_',
43             'sender_',
44             'interface_',
45             'path_',
46             'member_',
47             'destination_',
48             'args_',
49             ]
50
51     def __init__(self, fname):
52         self.__bus = DbusBus()
53         self.__filename = fname
54         self.__environ = dict()
55
56     @property
57     def environ(self):
58         return self.__environ
59
60     def __iter__(self):
61         # bus type sender interface path member destination args command
62         lineno = 0
63         with open(self.__filename) as f:
64             for line in f:
65                 lineno += 1
66                 line = line.strip()
67
68                 if not line or line.startswith('#'):
69                     continue
70
71                 parts = self.__fields_sep.split(line, 8)
72                 if len(parts) < 9:
73                     parts = self.__envvar_sep(line, 1)
74                     if len(parts) == 2:
75                         self.__environ[parts[0]] = parts[1]
76                         continue
77
78                     raise CrontabParserError('Unexpected number of records', lineno)
79
80                 rule = [('s','S'), self.__fields_chk['type_'], (None,), (None,), (None,), (None,), (None,), (None,)]
81
82                 for p in range(0, 8):
83                     if parts[p] != '*':
84                         rule[p] = parts[p].split(',')
85
86                 command = parts[8]
87  
88                 for r in product(*rule):
89                     r = list(r)
90                     if r[0] == 'S':
91                         r[0] = self.__bus.system
92                     elif r[0] == 's':
93                         r[0] = self.__bus.session
94                     else:
95                         raise CrontabParserError('Unexpected bus value', lineno, expected=('S', 's', '*'))
96
97                     if r[7]:
98                         r[7] = r[7].split(';')
99
100                     ruled = dict()
101                     for i, f in enumerate(self.__fields):
102                         if self.__fields_chk[f]:
103                             if isinstance(self.__fields_chk[f], tuple):
104                                 if r[i] not in self.__fields_chk[f]:
105                                     raise CrontabParserError('Unexpected %s value' % (f.strip('_')), lineno,
106                                             expected=self.__fields_chk[f])
107                             else:
108                                 if not self.__fields_chk[f].match(r[i]):
109                                     raise CrontabParserError('Incorrect %s value' % (f.strip('_')), lineno)
110                         ruled[f] = r[i]
111
112                     yield ruled, command
113
114 class OptionsParser(dict):
115     def __init__(self, opts, args=None):
116         super(OptionsParser, self).__init__()
117
118         if args is None:
119             import sys
120             args = sys.argv[1:]
121
122         from getopt import getopt
123         go, _ = getopt(args, opts)
124
125         for o, v in go:
126             k = o.strip('-')
127             withval = k+':' in opts
128
129             if self.has_key(k):
130                 if withval:
131                     if isinstance(self[k], list):
132                         self[k].append(v)
133                     else:
134                         self[k] = [ self[k], v ]
135
136                 else:
137                     self[k] += 1
138
139             else:
140                 self[k] = v if withval else 1
141
142     def __getitem__(self, k):
143         if not self.has_key(k):
144             return False
145         return super(OptionsParser, self).__getitem__(k)
146
147     def __getattr__(self, k):
148         return self[k]
149