pvl.syslog.tail: split into pvl.syslog.file/tail, clarify line/None/EOFError behaviour
--- 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