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