Code for barn-growl utility for OS X.
authorPeter Iannucci <iannucci@mit.edu>
Sun, 1 Mar 2009 00:19:52 +0000 (19:19 -0500)
committerPeter Iannucci <iannucci@mit.edu>
Sun, 1 Mar 2009 00:19:52 +0000 (19:19 -0500)
barn-growl/abstfilter.py [new file with mode: 0755]
barn-growl/barn-growl.py [new file with mode: 0755]
barn-growl/sexpr.py [new file with mode: 0644]

diff --git a/barn-growl/abstfilter.py b/barn-growl/abstfilter.py
new file mode 100755 (executable)
index 0000000..c8a6e11
--- /dev/null
@@ -0,0 +1,92 @@
+#!/usr/bin/env python
+##  $Id: abstfilter.py,v 1.1.1.1 2003/07/01 23:28:27 euske Exp $
+##
+##  abstfilter.py - A framework for cascade filters.
+##
+
+
+##  AbstractFeeder
+##
+class AbstractFeeder(object):
+
+  def __init__(self, next_filter):
+    self.next_filter = next_filter
+    return
+
+  def feed_next(self, s):
+    self.next_filter.feed(s)
+    return
+
+  def close(self):
+    self.next_filter.close()
+    return
+
+
+##  AbstractFilter
+##
+class AbstractFilter(object):
+
+  def __init__(self, next_filter):
+    self.next_filter = next_filter
+    return
+
+  def process(self, s):
+    raise NotImplementedError
+  
+  def feed(self, s):
+    self.feed_next(self.process(s))
+    return
+
+  def feed_next(self, s):
+    self.next_filter.feed(s)
+    return
+
+  def close(self):
+    self.next_filter.close()
+    return
+
+
+##  AbstractConsumer
+##
+class AbstractConsumer(object):
+
+  def __init__(self):
+    return
+
+  def feed(self, s):
+    raise NotImplementedError
+  
+  def close(self):
+    return
+
+
+##  FileGenerator
+##
+class FileGenerator(AbstractFeeder):
+  def __init__(self, next_type):
+    next_filter = next_type(self)
+    AbstractFeeder.__init__(self, next_filter)
+    self.results = []
+    return
+  
+  def feed(self, s):
+    self.results.append(s)
+    return
+
+  def close(self):
+    return
+  
+  def pullopen(self, f):
+    while 1:
+      s = f.readline()
+      if not s: break
+      self.feed_next(s)
+      if self.results:
+        for s in self.results:
+          yield s
+        self.results = []
+    for s in self.results:
+      yield s
+    self.results = []
+    return
+
diff --git a/barn-growl/barn-growl.py b/barn-growl/barn-growl.py
new file mode 100755 (executable)
index 0000000..6930f36
--- /dev/null
@@ -0,0 +1,69 @@
+#!/usr/bin/env python
+
+import sexpr
+import os
+import fcntl
+import select
+import sys
+from abstfilter import AbstractConsumer
+
+class Growler(AbstractConsumer):
+    def __init__(self):
+        return
+    def feed(self, s):
+        if s is None or type(s) is type(''): return
+        print repr(s)
+        d = dict([(ss[0], len(ss) > 2 and ss[2] or None) for ss in s])
+        if d['tzcspew'] == 'message':
+            zclass = d['class'].lower()
+            zinstance = d['instance'].lower()
+            zop = d['opcode'].lower()
+            zsender = d['sender'].lower()
+            zauth = d['auth'].lower() == 'yes'
+            ztime = ':'.join(d['time'].split(' ')[3].split(':')[0:2])
+            zmessage = d['message']
+            id = '%s/\n%s/\n%s\n %s' % (zclass, zinstance, zsender, ztime)
+            if zop == 'ping':
+                header = '%s (%s)' % (id, zsender)
+                message = '...'
+            elif zop == 'nil':
+                header = '%s (%s)' % (id, zmessage[0])
+                message = '%s' % zmessage[1]
+            else:
+                return
+            g = os.popen("growlnotify -a MacZephyr -n zephyr -d '%s' -t '%s'" % (id, header), 'w')
+            g.write(message)
+            g.close()
+    def close(self):
+        return
+
+def main(argv):
+    if len(argv) < 2:
+        print """barn-growl v.0.0.1
+
+Usage:
+barn-growl USERNAME"""
+        return 0
+
+    username = argv[1]
+    principal = username
+    if principal.find("@") == -1:
+        principal += '@ATHENA.MIT.EDU'
+    bash = "/bin/bash -lc \"kdo %s ssh %s@linerva.mit.edu 'tzc -si'\" 2>/dev/null </dev/null" % (principal, username)
+    p = os.popen(bash)
+    r = sexpr.SExprReader(Growler())
+
+    flags = fcntl.fcntl(p, fcntl.F_GETFL)
+    fcntl.fcntl(p, fcntl.F_SETFL, flags | os.O_NONBLOCK)
+
+    while 1:
+        [i,o,e] = select.select([p], [], [], 5)
+        if i: s = p.read(1024)
+        else: s = ''
+
+        if s != '':
+            r.feed(s)
+    return 0
+
+if __name__ == "__main__":
+    sys.exit(main(sys.argv))
diff --git a/barn-growl/sexpr.py b/barn-growl/sexpr.py
new file mode 100644 (file)
index 0000000..0d09ef6
--- /dev/null
@@ -0,0 +1,213 @@
+#!/usr/bin/env python
+##
+##  sexpr.py - by Yusuke Shinyama
+##
+##  * public domain *
+##
+
+from abstfilter import AbstractFeeder, AbstractFilter, AbstractConsumer
+
+
+##  SExprReader
+##
+class SExprReader(AbstractFilter):
+  """Usage:
+  
+  reader = SExprReader(consumer)
+  reader.feed("(this is (sexpr))")
+  reader.close()
+  """
+  
+  COMMENT_BEGIN = ";"
+  COMMENT_END = "\n"
+  SEPARATOR = " \t\n"
+  PAREN_BEGIN = "("
+  PAREN_END = ")"
+  QUOTE = '"'
+  ESCAPE = "\\"
+
+  def __init__(self, next_filter,
+               comment_begin=COMMENT_BEGIN,
+               comment_end=COMMENT_END,
+               separator=SEPARATOR,
+               paren_begin=PAREN_BEGIN,
+               paren_end=PAREN_END,
+               quote=QUOTE,
+               escape=ESCAPE):
+    AbstractFilter.__init__(self, next_filter)
+    self.comment_begin = comment_begin
+    self.comment_end = comment_end
+    self.separator = separator
+    self.paren_begin = paren_begin
+    self.paren_end = paren_end
+    self.quote = quote
+    self.escape = escape
+    self.special = comment_begin + separator + paren_begin + paren_end + quote + escape
+    self.reset()
+    return
+
+  # SExprReader ignores any error and
+  # try to continue as long as possible.
+  # if you want to throw exception however,
+  # please modify these methods.
+  
+  # called if redundant parantheses are found.
+  def illegal_close_paren(self, i):
+    print "Ignore a close parenthesis: %d" % i
+    return
+  # called if it reaches the end-of-file while the stack is not empty.
+  def premature_eof(self, i, x):
+    print "Premature end of file: %d parens left, partial=%s" % (i, x)
+    return
+
+  # reset the internal states.
+  def reset(self):
+    self.incomment = False              # if within a comment.
+    self.inquote = False                # if within a quote.
+    self.inescape = False               # if within a escape.
+    self.sym = ''                       # partially constructed symbol.
+    # NOTICE: None != nil (an empty list)
+    self.build = None                   # partially constructed list.
+    self.build_stack = []     # to store a chain of partial lists.
+    return self
+
+  # analyze strings
+  def feed(self, tokens):
+    for (i,c) in enumerate(tokens):
+      if self.incomment:
+        # within a comment - skip
+        self.incomment = (c not in self.comment_end)
+      elif self.inescape or (c not in self.special):
+        # add to the current working symbol
+        self.sym += c
+        self.inescape = False
+      elif c in self.escape:
+        # escape
+        self.inescape = True
+      elif self.inquote and (c not in self.quote):
+        self.sym += c
+      else:
+        # special character (blanks, parentheses, or comment)
+        if self.sym:
+          # close the current symbol
+          if self.build == None:
+            self.feed_next(self.sym)
+          else:
+            self.build.append(self.sym)
+          self.sym = ''
+        if c in self.comment_begin:
+          # comment
+          self.incomment = True
+        elif c in self.quote:
+          # quote
+          self.inquote = not self.inquote
+        elif c in self.paren_begin:
+          # beginning a new list.
+          self.build_stack.append(self.build)
+          empty = []
+          if self.build == None:
+            # begin from a scratch.
+            self.build = empty
+          else:
+            # begin from the end of the current list.
+            self.build.append(empty)
+            self.build = empty
+        elif c in self.paren_end:
+          # terminating the current list
+          if self.build == None:
+            # there must be a working list.
+            self.illegal_close_paren(i)
+          else:
+            if len(self.build_stack) == 1:
+              # current working list is the last one in the stack.
+              self.feed_next(self.build)
+            self.build = self.build_stack.pop()
+    return self
+
+  # terminate
+  def terminate(self):
+    # a working list should not exist.
+    if self.build != None:
+      # error - still try to construct a partial structure.
+      if self.sym:
+        self.build.append(self.sym)
+        self.sym = ''
+      if len(self.build_stack) == 1:
+        x = self.build
+      else:
+        x = self.build_stack[1]
+      self.build = None
+      self.build_stack = []
+      self.premature_eof(len(self.build_stack), x)
+    elif self.sym:
+      # flush the current working symbol.
+      self.feed_next(self.sym)
+    self.sym = ''
+    return self
+
+  # closing.
+  def close(self):
+    AbstractFilter.close(self)
+    self.terminate()
+    return
+
+
+##  StrictSExprReader
+##
+class SExprIllegalClosingParenError(ValueError):
+  """It throws an exception with an ill-structured input."""
+  pass
+class SExprPrematureEOFError(ValueError):
+  pass
+class StrictSExprReader(SExprReader):
+  def illegal_close_paren(self, i):
+    raise SExprIllegalClosingParenError(i)
+  def premature_eof(self, i, x):
+    raise SExprPrematureEOFError(i, x)
+  
+
+##  str2sexpr
+##
+class _SExprStrConverter(AbstractConsumer):
+  results = []
+  def feed(self, s):
+    _SExprStrConverter.results.append(s)
+    return
+_str_converter = SExprReader(_SExprStrConverter())
+_str_converter_strict = StrictSExprReader(_SExprStrConverter())
+
+def str2sexpr(s):
+  """parse a string as a sexpr."""
+  _SExprStrConverter.results = []
+  _str_converter.reset().feed(s).terminate()
+  return _SExprStrConverter.results
+def str2sexpr_strict(s):
+  """parse a string as a sexpr."""
+  _SExprStrConverter.results = []
+  _str_converter_strict.reset().feed(s).terminate()
+  return _SExprStrConverter.results
+
+
+##  sexpr2str
+##
+def sexpr2str(e):
+  """convert a sexpr into Lisp-like representation."""
+  if not isinstance(e, list):
+    return e
+  return "("+" ".join(map(sexpr2str, e))+")"
+
+
+# test stuff
+def test():
+  assert str2sexpr("(this ;comment\n is (a test (sentences) (des()) (yo)))") == \
+         [["this", "is", ["a", "test", ["sentences"], ["des", []], ["yo"]]]]
+  assert str2sexpr('''(paren\\(\\)theses_in\\#symbol "space in \nsymbol"
+                   this\\ way\\ also. "escape is \\"better than\\" quote")''') == \
+         [['paren()theses_in#symbol', 'space in \nsymbol', 'this way also.', 'escape is "better than" quote']]
+  str2sexpr("(this (is (a (parial (sentence")
+  return  
+
+
+# main
+if __name__ == "__main__":
+  test()