pvl.syslog.tail: split into pvl.syslog.file/tail, clarify line/None/EOFError behaviour
authorTero Marttila <terom@paivola.fi>
Sun, 13 Jan 2013 01:50:25 +0200
changeset 114 2e88e1d8e604
parent 113 49e13576d77c
child 115 9772d43669fb
pvl.syslog.tail: split into pvl.syslog.file/tail, clarify line/None/EOFError behaviour
pvl/syslog/args.py
pvl/syslog/file.py
pvl/syslog/tail.py
--- a/pvl/syslog/args.py	Sun Jan 13 00:25:59 2013 +0200
+++ b/pvl/syslog/args.py	Sun Jan 13 01:50:25 2013 +0200
@@ -3,7 +3,7 @@
 from pvl.syslog.parser import SyslogParser
 from pvl.syslog.filter import SyslogFilter
 from pvl.syslog.syslog import SyslogSource
-from pvl.syslog import fifo, tail
+from pvl.syslog import fifo, tail, file
 
 # XXX: use optparse parser.error()?
 import logging; log = logging.getLogger('pvl.syslog.args')
@@ -46,7 +46,8 @@
 
         May log.error/sys.exit
     """
-
+    
+    # XXX: this belongs in pvl.syslog.source
     if options.syslog_fifo :
         # fifo pipe
         source = fifo.Fifo(options.syslog_fifo)
@@ -54,12 +55,12 @@
 
     elif options.syslog_tail :
         # tail file
-        source = tail.TailFile(options.syslog_tail, skip=True)
-        poll = 2.0 # select(float)
+        source = tail.Tail(options.syslog_tail, skip=True)
+        poll = tail.Tail.POLL # select(float)
 
     elif options.syslog_file :
         # read file
-        source = tail.Tail(open(options.syslog_file))
+        source = file.File(open(options.syslog_file))
         poll = False # do not loop, just read up to EOF
 
     elif optional :
@@ -70,7 +71,7 @@
         if sys.stdin.isatty() :
             log.warning("Reading syslog messages from TTY?")
         
-        source = tail.Tail(sys.stdin)
+        source = file.File(sys.stdin)
         poll = False # XXX: tty vs pipe vs file? False -> just block
     
     # options
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pvl/syslog/file.py	Sun Jan 13 01:50:25 2013 +0200
@@ -0,0 +1,73 @@
+"""
+    Iterate over lines in file-like objects (without buffering lines!), write (flushing output).
+"""
+
+import logging; log = logging.getLogger('pvl.syslog.file')
+
+class File (object) :
+    """
+        Follow a file-like object, reading lines until no more are available. Never raises EOFError.
+
+        Works with python file objects that buffer readlines() when using e.g. `tail -f ... | python -u ...`.
+
+        readline() may block once there is no more input available, or may return None for evermore.
+
+        There is no fileno(), this is not pollable. At all. Don't even iterate on this with a timeout.
+        XXX: should this really return None? Might really be better to raise EOFError.. except that readlines() should return normally at EOF...
+    """
+
+    @classmethod
+    def open (cls, path, mode='r', **opts) :
+        return cls(open(path, mode), **opts)
+
+    EOL = '\n'
+
+    def __init__ (self, file) :
+        log.debug("%s", file)
+
+        self.file = file
+        
+    def readline (self) :
+        """
+            Reads a line from the file, without trailing \n.
+
+            Returns None on EOF.
+        """
+
+        line = self.file.readline()
+
+        if not line :
+            line = None
+        else : 
+            line = line.rstrip('\r\n')
+
+        log.debug("%s", line)
+
+        return line
+
+    def readlines (self) :
+        """
+            Reads any available lines from the file.
+        """
+
+        while True :
+            line = self.readline()
+            
+            if line is None :
+                return
+            else :
+                yield line
+
+    __iter__ = readlines
+
+    def writeline (self, line, eol=EOL) :
+        """
+            Write out line, flushing.
+        """
+
+        self.file.write(line)
+        self.file.write(eol)
+        self.file.flush()
+
+    __call__ = writeline
+
--- a/pvl/syslog/tail.py	Sun Jan 13 00:25:59 2013 +0200
+++ b/pvl/syslog/tail.py	Sun Jan 13 01:50:25 2013 +0200
@@ -1,7 +1,5 @@
 """
-    Iterate over input lines in files. 
-    
-    Can be read up to eof, on blocking inputs, or polled with a timeout (see: pvl.syslog.syslog.SyslogSource)
+    Iterate over input lines in filesystem files. 
 """
 
 import os
@@ -10,77 +8,28 @@
 
 class Tail (object) :
     """
-        Follow a file-like object, reading lines until EOF.
-
-        Works with python file objects that buffer readlines() when using e.g. `tail -f ... | python -u ...`.
-    """
-
-    def __init__ (self, file, skip=None) :
-        self.file = file
-        
-        if skip :
-            self.skip()
-
-    def readline (self) :
-        """
-            Reads a line from the file, without trailing \n
-
-            Raises EOF on end-of-file.
-        """
-
-        line = self.file.readline()
-
-        if not line :
-            raise EOFError()
-        
-        line = line.rstrip('\n')
-
-        log.debug("%s", line)
-
-        return line
+        Follow a file on the filesystem, reading lines until EOF, and re-opening if replaced.
 
-    def readlines (self) :
-        """
-            Reads any available lines from the file.
-        """
-
-        while True :
-            try :
-                line = self.readline()
-
-            except EOFError :
-                log.debug("EOF")
-                break
-
-            else :
-                yield line
+        Never blocks, no fileno() to poll. Just poll(timeout=POLL).
 
-    def skip (self) :
-        """
-            Skip any available lines.
-        """
-
-        for line in self.readlines() :
-            pass
+        Not writable.
+    """
+    
+    POLL = 2.0 
 
-    __iter__ = readlines
-
-class TailFile (Tail) :
-    """
-        Follow a file on the filesystem, reading lines until EOF, and re-opening if replaced.
-    """
-
-    def __init__ (self, path, **opts) :
+    def __init__ (self, path, skip=None, **opts) :
         log.debug("%s", path)
 
         self.path = path
-        self._stat = self.stat()
+        self.reopen()
 
-        file = self.open()
-        
-        # may call skip -> readlines -> changed() -> _stat
-        Tail.__init__(self, file, **opts)
-    
+        if skip :
+            self.skip()
+   
+    def open (self) :
+        log.debug("%s", self)
+        return open(self.path, 'r')
+
     def stat (self) :
         """
             Return a key identifying the file at our path.
@@ -93,12 +42,13 @@
         log.debug("%s: %s", self, stat)
 
         return stat
-    
-    def open (self) :
-        log.debug("%s", self)
-        return open(self.path, 'r')
+ 
+    def reopen (self) :
+        """
+            Re-initialize our file state from path.
+        """
 
-    def reopen (self) :
+        # XXX: use fstat for "atomic" open+stat?
         self.file = self.open()
         self._stat = self.stat()
 
@@ -109,6 +59,31 @@
 
         return self.stat() != self._stat
 
+    def raedline (self) :
+        """
+            Reads any available line from file.
+
+            Returns None if at EOF.
+        """
+
+    def readline (self) :
+        """
+            Reads a line from the file, without trailing \n.
+
+            Returns None on EOF.
+        """
+
+        line = self.file.readline()
+
+        if not line :
+            line = None
+        else : 
+            line = line.rstrip('\r\n')
+
+        log.debug("%s", line)
+
+        return line
+
     def readlines (self, eof_mark=False) :
         """
             Reads any available lines from the file.
@@ -119,29 +94,36 @@
         """
 
         while True :
-            try :
-                line = self.readline()
+            line = self.readline()
+            
+            if line :
+                yield line
 
-            except EOFError :
-                if self.changed() :
-                    log.debug("EOF: reopen")
-                    self.reopen()
-                    
-                    if eof_mark :
-                        yield None # special token
-                    
-                    # keep going
-                    continue
-
-                else :
-                    log.debug("EOF")
-                    break # wait
+            elif self.changed() :
+                log.debug("EOF: reopen")
+                self.reopen()
+                
+                if eof_mark :
+                    yield None # special token
+                
+                # keep going
+                continue
 
             else :
-                yield line
+                log.debug("EOF: wait")
+                break
 
     __iter__ = readlines
 
+    def skip (self) :
+        """
+            Skip any available lines.
+        """
+
+        for line in self.readlines() :
+            pass
+
+
     def __str__ (self) :
         return self.path