parser fixes
[dbuscron] / dbuscron / parser.py
index a6f1c1d..73ab3a0 100644 (file)
@@ -1,7 +1,27 @@
+# encoding: utf-8
 from __future__ import with_statement
+import os
 import re
 from dbuscron.bus import DbusBus
 
+def unescape_():
+    h = '[0-9A-Fa-f]'
+    r = re.compile(r'\\x('+h+r'{2})|\\u('+h+'{4})')
+
+    def unescape(value):
+        if not (value and
+                (r'\x' in value or r'\u' in value)):
+            return value
+
+        return r.sub(
+            lambda m: chr(int(m.group(1), 16))
+                if m.group(1) is not None else
+                    unichr(int(m.group(2), 16))
+                        .encode('utf-8'),\
+            value)
+    return unescape
+unescape = unescape_()
+
 def product(*args):
     if args:
         head, tail = args[0], args[1:]
@@ -24,12 +44,30 @@ class CrontabParserError(SyntaxError):
 
         SyntaxError.__init__(self, msg)
 
-class CrontabParser(object):
+def CrontabParser(*filenames):
+    for filename in filenames:
+        if not os.path.exists(filename):
+            continue
+
+        if os.path.isfile(filename):
+            parser_class = FileParser
+
+        elif os.path.isdir(filename):
+            parser_class = DirectoryParser
+
+        else:
+            raise SystemError("I can parse only directories or simple files.")
+
+        return parser_class(filename)
+
+    raise SystemError("Can't find any config file or directory.")
+
+class FileParser(object):
     __fields_sep = re.compile(r'\s+')
     __envvar_sep = re.compile(r'\s*=\s*')
     __fields_chk = {
             'bus_'         : None,
-            'type_'        : ('signal','method_call','method_return','error'),
+            'type_'        : ('signal', 'method_call', 'method_return', 'error'),
             'sender_'      : None,
             'interface_'   : re.compile(r'^[a-zA-Z][a-zA-Z0-9_.]+$'),
             'path_'        : re.compile(r'^/[a-zA-Z0-9_/]+$'),
@@ -49,18 +87,22 @@ class CrontabParser(object):
             ]
 
     def __init__(self, fname):
-        self.__bus = DbusBus()
-        self.__filename = fname
-        self.__environ = dict()
+        self._bus = DbusBus()
+        self._filename = fname
+        self._environ = dict()
 
     @property
     def environ(self):
-        return self.__environ
+        return self._environ
 
-    def __iter__(self):
+    @property
+    def filename(self):
+        return self._filename
+
+    def _iterate_file(self, filename):
         # bus type sender interface path member destination args command
         lineno = 0
-        with open(self.__filename) as f:
+        with open(filename) as f:
             for line in f:
                 lineno += 1
                 line = line.strip()
@@ -70,36 +112,36 @@ class CrontabParser(object):
 
                 parts = self.__fields_sep.split(line, 8)
                 if len(parts) < 9:
-                    parts = self.__envvar_sep(line, 1)
+                    parts = self.__envvar_sep.split(line, 1)
                     if len(parts) == 2:
                         self.__environ[parts[0]] = parts[1]
                         continue
 
                     raise CrontabParserError('Unexpected number of records', lineno)
 
-                rule = [('s','S'), self.__fields_chk['type_'], (None,), (None,), (None,), (None,), (None,), (None,)]
+                rule = [('s', 'S'), self.__fields_chk['type_'], (None,), (None,), (None,), (None,), (None,), (None,)]
 
                 for p in range(0, 8):
                     if parts[p] != '*':
                         rule[p] = parts[p].split(',')
 
                 command = parts[8]
+
                 for r in product(*rule):
                     r = list(r)
                     if r[0] == 'S':
-                        r[0] = self.__bus.system
+                        r[0] = self._bus.system
                     elif r[0] == 's':
-                        r[0] = self.__bus.session
+                        r[0] = self._bus.session
                     else:
                         raise CrontabParserError('Unexpected bus value', lineno, expected=('S', 's', '*'))
 
                     if r[7]:
-                        r[7] = r[7].split(';')
+                        r[7] = map(unescape, r[7].split(';'))
 
                     ruled = dict()
                     for i, f in enumerate(self.__fields):
-                        if self.__fields_chk[f]:
+                        if r[i] is not None and self.__fields_chk[f]:
                             if isinstance(self.__fields_chk[f], tuple):
                                 if r[i] not in self.__fields_chk[f]:
                                     raise CrontabParserError('Unexpected %s value' % (f.strip('_')), lineno,
@@ -111,39 +153,45 @@ class CrontabParser(object):
 
                     yield ruled, command
 
-class OptionsParser(dict):
-    def __init__(self, opts, args=None):
-        super(OptionsParser, self).__init__()
+    def __iter__(self):
+        return self._iterate_file(self._filename)
 
-        if args is None:
-            import sys
-            args = sys.argv[1:]
+class DirectoryParser(FileParser):
 
-        from getopt import getopt
-        go, _ = getopt(args, opts)
+    def __init__(self, dirname, recursive=False):
+        self.__recursive = recursive
+        super(DirectoryParser, self).__init__(dirname)
 
-        for o, v in go:
-            k = o.strip('-')
-            withval = k+':' in opts
+    def _dirwalker_plain(self):
+        for i in os.listdir(self._filename):
+            f = os.path.join(self._filename, i)
+            if os.path.isfile(f):
+                yield f
 
-            if self.has_key(k):
-                if withval:
-                    if isinstance(self[k], list):
-                        self[k].append(v)
-                    else:
-                        self[k] = [ self[k], v ]
+    def _dirwalker_recursive(self):
+        for r, d, f in os.walk(self._filename):
+            for i in f:
+                yield os.path.join(r, i)
+
+    def __iter__(self):
+        if self.__recursive:
+            dirwalker = self._dirwalker_recursive
+        else:
+            dirwalker = self._dirwalker_plain
 
-                else:
-                    self[k] += 1
+        for fname in dirwalker():
+            for item in self._iterate_file(fname):
+                yield item
 
-            else:
-                self[k] = v if withval else 1
+def OptionsParser(args=None, help=u'', **opts):
 
-    def __getitem__(self, k):
-        if not self.has_key(k):
-            return False
-        return super(OptionsParser, self).__getitem__(k)
+    from optparse import OptionParser
+    import dbuscron
+    parser = OptionParser(usage=help, version="%prog " + dbuscron.__version__)
+    for opt, desc in opts.iteritems():
+        names = desc.pop('names')
+        desc['dest'] = opt
+        parser.add_option(*names, **desc)
 
-    def __getattr__(self, k):
-        return self[k]
+    return parser.parse_args(args)[0]