--- /dev/null
+This is a repository for useful small snippets that really belong in version
+control in a central spot, instead of someone's Public. Please add stuff
+here via git:
+
+add git
+git clone /mit/snippets
+cd snippets
+[create or copy a file, let's call it "item"]
+git commit item
+git push
+
+If you're not on gsipb and want to commit, ask geofft (or someone on
+sipb-acl) to add you to snippets-committers. You can't write to
+/mit/snippets directly, but if you push (to the master branch) from your
+own checkout, it will automatically update this checkout.
+
+There's a (currently very simple) web interface at
+http://snippets.scripts.mit.edu/
+
+snippets has support for submodules, if things outgrow this repository.
+You can use 'git submodule update --init' to populate them if you want,
+or look at .gitmodules to find where they live now.
--- /dev/null
+build
+dist
+*.pyc
+*.egg-info
--- /dev/null
+Copyright (C) 2011 by Evan Broder, Geoffrey Thomas, Anders Kaseorg,
+Jonathan Reed
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
--- /dev/null
+To install the TracZephyrPlugin, first run
+
+ $ python setup.py bdist_egg
+
+then copy the .egg file in the dist/ directory into the plugins/ directory of
+your trac install.
+
+To enable the plugin, you must configure the class that zephyr updates should
+go to. To do this, add a section like the following to your trac.ini:
+
+ [ZephyrPlugin]
+ class = debathena
+
+Then be sure to restart the FastCGI processes if there are any.
--- /dev/null
+# -*- coding: utf-8 -*-
+
+from trac.core import *
+from trac.ticket import ITicketChangeListener
+import subprocess
+import re
+import textwrap
+import shlex
+
+quoted_re = re.compile('^(?:> ?\n)*> .+\n(?:>(?: .*)?\n)*', re.MULTILINE)
+
+class ZephyrPlugin(Component):
+ implements(ITicketChangeListener)
+
+ def zwrite(self, id, message, extra_sig=None):
+ zclass = self.config.get('ZephyrPlugin', 'class')
+ if zclass == '':
+ return
+ command = shlex.split(self.config.get('ZephyrPlugin', 'command').encode('utf-8'))
+ if not command:
+ command = ['zwrite', '-q', '-l', '-d', '-x', 'UTF-8']
+ opcode = self.config.get('ZephyrPlugin', 'opcode')
+ if opcode:
+ command += ['-O', opcode]
+ signature = self.config.get('ZephyrPlugin', 'signature')
+ if extra_sig:
+ if signature:
+ signature = "%s: %s" % (signature, extra_sig, )
+ else:
+ signature = extra_sig
+ if signature:
+ command += ['-s', signature]
+ p = subprocess.Popen(command +
+ ['-c', zclass,
+ '-i', 'trac-#%s' % id],
+ stdin=subprocess.PIPE)
+ p.stdin.write(message.replace('@', '@@').encode('utf-8', 'replace'))
+ p.stdin.close()
+ p.wait()
+
+ def format_text(self, text):
+ text = re.sub(quoted_re, u'> […]\n', text)
+ lines = textwrap.fill(text).split('\n')
+ if len(lines) > 5:
+ lines = lines[:5] + [u'[…]']
+ return '\n'.join(lines)
+
+ def get_url(self, ticket):
+ return ticket.env.abs_href.ticket(ticket.id)
+
+ def ticket_created(self, ticket):
+ ttype='ticket'
+ if ticket['type'] != 'defect':
+ ttype=ticket['type']
+ message = "%s filed a new %s %s:\n%s\n\n%s" % (ticket['reporter'],
+ ticket['priority'],
+ ttype,
+ ticket['summary'],
+ self.format_text(ticket['description']))
+ self.zwrite(ticket.id, message, extra_sig=self.get_url(ticket))
+
+ def ticket_changed(self, ticket, comment, author, old_values):
+ message = "(%s)\n" % ticket['summary']
+ for field in ticket.fields:
+ name = field['name']
+ if name not in old_values:
+ pass
+ elif field['type'] == 'textarea':
+ message += "%s changed %s to:\n%s\n" % (author, name, self.format_text(ticket[name]))
+ elif ticket[name] and old_values[name]:
+ message += "%s changed %s from %s to %s.\n" % (author, name, old_values[name], ticket[name])
+ elif ticket[name]:
+ message += "%s set %s to %s.\n" % (author, name, ticket[name])
+ elif old_values[name]:
+ message += "%s deleted %s.\n" % (author, name)
+ else:
+ message += "%s changed %s.\n" % (author, name)
+ if comment:
+ message += "%s commented:\n%s\n" % (author, self.format_text(comment))
+ self.zwrite(ticket.id, message, extra_sig=self.get_url(ticket))
+
+ def ticket_deleted(self, ticket):
+ message = "%s deleted ticket %d" % (author, ticket.id)
+ self.zwrite(ticket.id, message, extra_sig=self.get_url(ticket))
--- /dev/null
+#!/usr/bin/python
+
+from setuptools import find_packages, setup
+
+setup(
+ name='TracZephyrPlugin',
+ version='1.4.2',
+ author='Evan Broder and the SIPB Snippets team',
+ author_email='snippets@mit.edu',
+ description='Send a zephyr when a Trac ticket is created or updated',
+ py_modules=["ZephyrPlugin"],
+ entry_points = """
+ [trac.plugins]
+ ZephyrPlugin = ZephyrPlugin
+ """,
+)
--- /dev/null
+DPkg::Pre-Install-Pkgs {"/usr/local/sbin/apt-zephyr --pre-install-pkgs || :";};
+DPkg::Post-Invoke {"/usr/local/sbin/apt-zephyr --post-invoke || :";};
+DPkg::Tools::options::/usr/local/sbin/apt-zephyr::Version "2";
--- /dev/null
+apt-zephyr hook
+
+Author: Anders Kaseorg <andersk@mit.edu>
+
+Usage: install these files to
+
+/etc/apt/apt.conf.d/90zephyr
+/usr/local/sbin/apt-zephyr
+
+and configure /etc/apt-zephyr.conf, e.g.
+
+CLASS="linerva"
+INSTANCE="apt.$(hostname)"
+
+Valid options include CLASS, INSTANCE, REALM, RECIPIENTS, SIG (default
+"$(hostname -f)"), OPCODE (default "auto"), ZAUTH (default "", set
+nonempty to enable).
--- /dev/null
+#!/bin/sh
+set -e
+
+# Defaults
+CLASS=
+INSTANCE=
+REALM=
+RECIPIENTS=
+SIG="$(hostname -f)"
+OPCODE=auto
+ZAUTH=
+
+# Read configuration
+. /etc/apt-zephyr.conf
+
+send_zephyr ()
+{
+ zwrite \
+ ${CLASS:+-c "$CLASS"} \
+ ${INSTANCE:+-i "$INSTANCE"} \
+ ${REALM:+-r "$REALM"} \
+ ${SIG:+-s "$SIG"} \
+ ${OPCODE:+-O "$OPCODE"} \
+ ${ZAUTH:--d} \
+ ${RECIPIENTS:+$RECIPIENTS}
+}
+
+package ()
+{
+ package=$1
+ oldver=$2
+ cmp=$3
+ newver=$4
+
+ if [ "$newver" = '-' ] && [ "$oldver" = '-' ]; then
+ echo "Purging $package"
+ elif [ "$newver" = '-' ]; then
+ echo "Removing $package $oldver"
+ elif [ "$oldver" = '-' ]; then
+ echo "Installing $package $newver"
+ elif [ "$cmp" = '<' ]; then
+ echo "Upgrading $package $oldver to $newver"
+ elif [ "$cmp" = '=' ]; then
+ echo "Reinstalling $package $newver"
+ elif [ "$cmp" = '>' ]; then
+ echo "Downgrading $package $oldver to $newver"
+ else
+ echo "I'm confused: $*"
+ fi
+}
+
+parse_v1 ()
+{
+ oldpkgs=$(mktemp -t "apt-zephyr-old.XXXXXX") || exit $?
+ newpkgs=$(mktemp -t "apt-zephyr-new.XXXXXX") || exit $?
+ xargs -r -d '\n' dpkg-deb -W | sort -o "$newpkgs"
+ cut -f 1 "$newpkgs" | xargs -r -d '\n' dpkg-query -W | \
+ sort -o "$oldpkgs"
+ join -t ' ' -j 1 -e '-' "$oldpkgs" "$newpkgs" | \
+ while IFS=' ' read -r package oldver newver; do
+ if dpkg --compare-versions "$oldver" lt "$newver"; then
+ package "$package" "$oldver" '<' "$newver"
+ elif dpkg --compare-versions "$oldver" eq "$newver"; then
+ package "$package" "$oldver" '=' "$newver"
+ else
+ package "$package" "$oldver" '>' "$newver"
+ fi
+ done
+ rm -f "$oldpkgs" "$newpkgs"
+}
+
+parse_v2 ()
+{
+ while read -r line && [ -n "$line" ]; do :; done
+ while read -r package oldver cmp newver action; do
+ case "$action" in
+ '**CONFIGURE**' | '**REMOVE**')
+ package "$package" "$oldver" "$cmp" "$newver"
+ ;;
+ '**ERROR**')
+ echo "ERROR on $package $newver"
+ ;;
+ esac
+ done
+}
+
+pre_install_pkgs ()
+{
+ read -r line
+ case "$line" in
+ 'VERSION 2')
+ parse_v2 | send_zephyr
+ ;;
+ 'VERSION *')
+ echo "$0: unrecognized version: $line" | send_zephyr
+ ;;
+ '')
+ ;;
+ *)
+ (echo "$line"; cat) | parse_v1 | send_zephyr
+ ;;
+ esac
+}
+
+post_invoke ()
+{
+ echo 'Done.' | send_zephyr
+}
+
+if [ "$1" = "--pre-install-pkgs" ]; then
+ pre_install_pkgs
+elif [ "$1" = "--post-invoke" ]; then
+ post_invoke
+else
+ echo "usage: $0 {--pre-install-pkgs | --post-invoke}" >&2
+ echo "(Hint: you probably need to update /etc/apt/apt.conf.d/90zephyr.)" >&2
+fi
+
+exit 0
--- /dev/null
+#!/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.
+##
+## from http://www.unixuser.org/~euske/python/index.html:
+## The following files are in public domain except where otherwise noted. THESE FILES COME WITH ABSOLUTELY NO WARRANTY.
+
+
+## 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
+
--- /dev/null
+#!/usr/bin/env python
+
+"""
+Subscribes to zephyr via tzc and sends messages to notification drivers (growl or libnotify).
+"""
+
+import sexpr
+import os
+import subprocess
+import fcntl
+import select
+import sys
+from abstfilter import AbstractConsumer
+import optparse
+import time
+
+class Notifier(AbstractConsumer):
+ def __init__(self, usegrowl, usenotify, useprint):
+ self.usegrowl = usegrowl
+ self.usenotify = usenotify
+ if usenotify:
+ import pynotify
+ pynotify.init("Zephyr")
+ self.pings = {}
+ self.pynotify = pynotify
+ self.useprint = useprint
+ return
+ def feed(self, s):
+ if s is None or type(s) is type(''): return
+ 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 = "%02d:%02d" % time.strptime(d['time'])[3:5]
+ zmessage = d['message']
+ idtuple = (zclass, zinstance, zsender, ztime)
+ id = '%s/\n%s/\n%s\n %s' % idtuple
+ if zop == 'ping':
+ header = '%s (%s)' % (id, zsender)
+ message = '...'
+ elif zop == 'nil':
+ header = '%s (%s)' % (id, len(zmessage) > 0 and zmessage[0] or zsender)
+ message = '%s' % (len(zmessage) > 1 and zmessage[1] or '')
+ else:
+ return
+ if self.useprint:
+ print (id, header)
+ print message
+ if self.usegrowl:
+ growlnotify = ['growlnotify', '-H', 'localhost', '-a', 'MacZephyr', '-n', 'zephyr', '-d', id, '-t', header]
+ g = subprocess.Popen(growlnotify, stdin=subprocess.PIPE)
+ g.stdin.write(message)
+ g.stdin.close()
+ if self.usenotify:
+ if idtuple in self.pings:
+ self.pings[idtuple].update(header, message)
+ self.pings[idtuple].show()
+ else:
+ n = self.pynotify.Notification(header, message)
+ n.show()
+ if zop == 'ping':
+ self.pings[idtuple] = n
+ self.pings = dict(filter(lambda ((c, i, s, time), v): time == idtuple[3], self.pings.items()))
+ def close(self):
+ return
+
+def main(argv):
+ parser = optparse.OptionParser(usage = '%prog [-s "username@machine"] (--growl | --notify | --print)',
+ description = __doc__.strip())
+ parser.add_option('-s', '--ssh',
+ type = 'string',
+ default = None,
+ dest = 'ssh',
+ help = 'optional remote host to run tzc')
+ parser.add_option('-g', '--growl',
+ action = 'store_true',
+ default = False,
+ dest = 'growl',
+ help = 'use growlnotify for output')
+ parser.add_option('-n', '--notify',
+ action = 'store_true',
+ default = False,
+ dest = 'notify',
+ help = 'use notify-send for output')
+ parser.add_option('-p', '--print',
+ action = 'store_true',
+ default = False,
+ dest = 'useprint',
+ help = 'use stdout for output')
+ opts, args = parser.parse_args()
+
+ usegrowl = opts.growl
+ usenotify = opts.notify
+ useprint = opts.useprint
+ if not usegrowl and not usenotify and not useprint:
+ parser.print_help(sys.stderr)
+ return 1
+ ssh = opts.ssh
+
+ if ssh is None:
+ retval = subprocess.call(['which', 'tzc'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+ if retval:
+ print 'tzc not in path. Please add -s username@machine to specify remote host.'
+ return 1
+
+ if ssh is not None:
+ command = "ssh -o GSSAPIAuthentication=yes -o GSSAPIDelegateCredentials=yes -o GSSAPIKeyExchange=yes %s 'tzc -si'" % ssh
+ else:
+ command = "tzc -si"
+ p = os.popen(command)
+ r = sexpr.SExprReader(Notifier(usegrowl, usenotify, useprint))
+
+ flags = fcntl.fcntl(p, fcntl.F_GETFL)
+ fcntl.fcntl(p, fcntl.F_SETFL, flags | os.O_NONBLOCK)
+
+ try:
+ while 1:
+ [i,o,e] = select.select([p], [], [], 5)
+ if i: s = p.read(1024)
+ else: s = ''
+
+ if s != '':
+ r.feed(s)
+ except KeyboardInterrupt:
+ pass
+ return 0
+
+if __name__ == "__main__":
+ sys.exit(main(sys.argv))
--- /dev/null
+#!/usr/bin/env python
+##
+## sexpr.py - by Yusuke Shinyama
+##
+## * public domain *
+##
+## from http://www.unixuser.org/~euske/python/index.html:
+## The following files are in public domain except where otherwise noted. THESE FILES COME WITH ABSOLUTELY NO WARRANTY.
+
+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()
--- /dev/null
+# Automatically colors personals by hashing the username. Inspired by geofft's
+# .bashrc. Needs a lot of tweaking to make perfect...
+#
+# To use:
+# :perl do '/mit/snippets/barnowl/personal-colors.pl'
+# :view -s colors
+
+package BarnOwl::Style::Colors;
+our @ISA=qw(BarnOwl::Style::Default);
+use Digest::MD5;
+
+sub description {"Colors for personals";}
+
+sub format_chat {
+ my $self = shift;
+ my $m = shift;
+ my $body = $self->indent_body($m);
+ if ($m->is_personal) {
+ my @colors = qw{red green blue yellow magenta cyan};
+ my $hash = ord Digest::MD5::md5(($m->direction eq "out") ? $m->pretty_recipient : $m->pretty_sender);
+ $body = '@[@color(' . $colors[$hash % scalar @colors] . ")$body]";
+ }
+ return $self->chat_header($m) . "\n". $body;
+}
+
+BarnOwl::create_style("colors", "BarnOwl::Style::Colors");
+
+1;
--- /dev/null
+# This perl module adds a :decrypt command that uses barnowl's zcrypt
+# binary via Perl to decrypt zcrypt'd zephyrs after they have been
+# received.
+#
+# To use this code, type
+# :perl do '/mit/snippets/barnowl/zcrypt.pl'
+#
+
+use IPC::Open2;
+BarnOwl::new_command(decrypt => sub {
+ my $msg = BarnOwl::getcurmsg();
+ my $cmd = shift;
+ my @args = @_;
+ if (scalar @args == 0) {
+ @args = ('-c', $msg->class, '-i', $msg->instance);
+ }
+ my ($zo, $zi);
+ my $pid = open2($zo, $zi, 'athrun', 'barnowl', 'zcrypt', '-D', @args) or die "Couldn't launch zcrypt\n";
+ my $decrypted;
+ print $zi $msg->fields->[1] . "\n";
+ close $zi;
+ while (<$zo>) {
+ chomp;
+ last if $_ eq "**END**";
+ $decrypted .= "$_\n";
+ }
+ BarnOwl::popless_ztext($decrypted);
+ waitpid $pid, 0;
+ },
+ {summary => "Decrypt a zcrypted message once",
+ usage => "decrypt [args]",
+ description => "Invokes athrun barnowl zcrypt on the current message,\n
+using the class and instance to find the crypt key, and pops up the\n
+decrypted output. If args are specified, they are passed to zcrypt and the\n
+class and instance are ignored.\n\n
+SEE ALSO: zcrypt(1)"});
+
+1;
--- /dev/null
+# newline-fix.bash: Force the bash prompt to start after a newline
+# http://snippets.scripts.mit.edu/gitweb.cgi/.git/blob/HEAD:/bash/newline-fix.bash
+#
+# Some commands don’t print a newline at the end of their output, or
+# take long enough to return that you’ve already typed part of the
+# next command. Either causes bash to start its prompt in the middle
+# of a line, which confuses it. This script sets $PROMPT_COMMAND to
+# echo a magic sequence of terminal commands that will display a red
+# “<no LF>\n” before the prompt if it would otherwise start in the
+# middle of a line.
+#
+# Copyright © 2010 Anders Kaseorg <andersk@mit.edu>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# Usage: Source this file from your ~/.bashrc.
+
+show_no_lf () {
+ tput -S <<NOLF && echo -n '<no LF>' && \
+ tput -S <<SPSP && echo -n ' ' && \
+ tput -S <<EOF
+sc
+tbc
+cr
+cuf 8
+hts
+rc
+setaf 1
+rev
+NOLF
+sgr0
+ht
+sc
+tbc
+cr
+$(for ((i=8; i<=320; i+=8)); do echo cuf 8; echo hts; done)
+rc
+SPSP
+cr
+el
+EOF
+}
+
+show_no_lf="$(show_no_lf)" && \
+PROMPT_COMMAND="echo -n \"\$show_no_lf\" >&2;$PROMPT_COMMAND"
--- /dev/null
+#!/bin/bash
+
+set -e
+
+usage() {
+ cat <<EOF
+Usage: $0 <cert.p12> <cert.pem>
+
+Transforms a .p12 file, for instance as exported by Firefox's
+cerfiticate "backup" feature, into a PEM file that contains your
+private key and certificate.
+
+To export your certificate from Firefox, go to Edit|Preferences,
+Advanced|Security|View Certificates, and ``Backup'' your certificate
+to a file. Firefox will save it as a PKCS12 certificate. You must
+enter a passphrase, which this script will prompt you for.
+
+EOF
+ exit 1
+}
+
+[ "$#" -eq 2 ] || usage
+
+pkcs="$1"
+pem="$2"
+
+openssl pkcs12 -in "$pkcs" -nodes -out "$pem"
+
+cat >&2 <<EOF
+Private key and certificate written to $pem
+
+Keep this file safe!
+
+You can pass this to wget's --certificate and --private-key options,
+or to curl's --cert option.
+
+To use it with perl's LWP, set the following environment variables:
+
+EOF
+
+pem=$(readlink -f "$pem")
+
+printf 'HTTPS_CERT_FILE=%q\n' "$pem"
+printf 'HTTPS_KEY_FILE=%q\n' "$pem"
+
--- /dev/null
+#!/usr/bin/env perl
+use strict;
+use warnings;
+use Digest::MD5 qw(md5_hex);
+
+=head1 NAME
+
+crondiff.pl - Run command periodically and compare their output.
+
+=head1 DESCRIPTION
+
+crondiff.pl is designed to be run from cron periodically. It takes as
+input a list of commands to run. Each time it is invoked, it runs the
+commands listed, and sends mail to the user running the script if the
+output of that command has changed since the last run.
+
+=head1 USAGE
+
+=over 4
+
+=item
+
+Create a file C<~/.crondiff/cmdlist>, containing one line per file, of
+commands to be checked.
+
+=item
+
+Add C<crondiff.pl> to your crontab. The following example runs it
+directly out of the locker (not necessarily a good idea!) every hour:
+
+ # m h dom mon dow command
+ 0 * * * * PATH=/bin:/usr/bin: perl /mit/snippets/crondiff/crondiff.pl
+
+=back
+
+=head1 ASSUMPTIONS
+
+=over 4
+
+=item
+
+Mail goes to C<$USER@mit.edu>, for the user who runs this script.
+
+=item
+
+Mail will be sent using C<mutt -x>.
+
+=back
+
+If you don't like these assumptions, patches are welcome :)
+
+=cut
+
+my $confdir = $ENV{HOME} . "/.crondiff";
+unless($ENV{USER}) {
+ chomp(my $user = `id -un`);
+ $ENV{USER} = $user;
+};
+my $mailto = $ENV{USER} . '@mit.edu';
+my @mailer = qw(mutt -x);
+
+my $cachedir = "$confdir/cache";
+
+mkdir("$cachedir") unless -e "$cachedir";
+
+open(my $clist, "<", "$confdir/cmdlist")
+ or exit;
+
+while(my $cmd = <$clist>) {
+ chomp($cmd);
+ my $hash = md5_hex($cmd);
+ my $pid = fork;
+ if($pid == 0) {
+ # Child
+ close(STDOUT);
+ close(STDERR);
+ open(STDOUT, ">>", "$cachedir/$hash.new");
+ open(STDERR, ">>", "$cachedir/$hash.new");
+ exec($cmd);
+ }
+ waitpid($pid, 0);
+ if($? != 0) {
+ # Exited with error, should send mail, but punting for now.
+ unlink("$cachedir/$hash.new");
+ next;
+ } elsif(-f "$cachedir/$hash"
+ && system("diff -q $cachedir/$hash.new $cachedir/$hash > /dev/null 2>&1") != 0) {
+ # Output changed
+ if(($pid = fork) == 0) {
+ close(STDOUT);
+ open(STDOUT, "|-", @mailer, $mailto, '-s', "Output of [$cmd]");
+ system("date");
+ print "----\n";
+ system("diff", "-u", "-U", "0", "$cachedir/$hash", "$cachedir/$hash.new");
+ exit;
+ } else {
+ waitpid($pid, 0);
+ }
+ }
+ rename("$cachedir/$hash.new", "$cachedir/$hash");
+}
--- /dev/null
+Copyright (C) 2010--2011 by Alex Dehnert
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
--- /dev/null
+/mit/__init__.py contains some useful functionality for Django applications
+running at MIT, particularly on the scripts.mit.edu platform
+
+- zephyr(msg, clas, instance, rcpt) sends a zephyr (by shelling out to zwrite).
+ This may be useful for debugging or logging
+- ScriptsRemoteUserMiddleware and ScriptsRemoteUserBackend work together to
+ auto-create users from certificates on scripts.mit.edu-hosted sites. Account
+ details are automatically retrieved from LDAP.
+- scripts_login is a view that tries to log users into a site using certs.
+
+To use them, you'll probably want to symlink or copy the "mit" directory into
+your project, add it to your apps list, and modify your middlewares and auth
+backend appropriately. Do *not* link this (the "django" directory) into your
+app; it's reasonably likely to break your "import django.foo" statements.
+
+This code is descended from work on Remit (https://remit.scripts.mit.edu/trac/)
+and the ASA DB (https://asa.scripts.mit.edu/trac/); current and past bugs are
+likely to be filed there.
--- /dev/null
+#!/bin/sh
+#
+# A hook script that blocks committer and author names that are
+# root (or a configured blacklisted name).
+# XXX: should allow only applying this to certain branches.
+#
+# Called by "git receive-pack" with arguments: refname sha1-old sha1-new
+#
+# Config
+# ------
+# hooks.noroot.branches
+# List of branches for which commits by root should be disallowed.
+# hooks.noroot.match
+# XXX: figure out format
+#
+
+set -e
+
+# --- Command line
+refname="$1"
+oldrev="$2"
+newrev="$3"
+
+# --- Safety check
+if [ -z "$GIT_DIR" ]; then
+ echo "Don't run this script from the command line." >&2
+ echo " (if you want, you could supply GIT_DIR then run" >&2
+ echo " $0 <ref> <oldrev> <newrev>)" >&2
+ exit 1
+fi
+
+if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then
+ echo "Usage: $0 <ref> <oldrev> <newrev>" >&2
+ exit 1
+fi
+
+# --- Config
+branches=$(git config --get hooks.noroot.branches || echo "")
+# XXX: hooks.noroot.match
+
+# --- Check types
+# if $newrev is 0000...0000, it's a commit to delete a ref.
+zero="0000000000000000000000000000000000000000"
+if [ "$newrev" = "$zero" ]; then
+ newrev_type=delete
+else
+ newrev_type=$(git cat-file -t $newrev)
+fi
+
+case "$refname","$newrev_type" in
+ refs/heads/*,commit)
+ git log --pretty="format:%h \"%an\" \"%cn\"%n" "$oldrev".."$newrev" | \
+ while read hash an cn; do
+ if [ "$an" = "\"root\"" -o "$cn" = "\"root\"" ]; then
+ echo "*** Committing as root not allowed in this repository," >&2
+ echo "*** Please fix your GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL,"
+ echo "*** GIT_COMMITTER_NAME and GIT_COMMITTER_EMAIL."
+ echo "*** Offending commit was $hash."
+ exit 1
+ fi
+ done
+ ;;
+ refs/remotes/*,commit)
+ # tracking branch
+ ;;
+ refs/tags/*,*)
+ # we could track tags, but we've decided they're not
+ # interesting
+ ;;
+ *,delete)
+ # not interesting
+ ;;
+ *)
+ # Anything else (is there anything else?)
+ echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2
+ exit 1
+ ;;
+esac
+
+# --- Finished
+exit 0
--- /dev/null
+#!/bin/bash
+#
+# This script is run after receive-pack has accepted a pack and the
+# repository has been updated. It is passed arguments in through stdin
+# in the form
+# <oldrev> <newrev> <refname>
+# For example:
+# aa453216d1b3e49e7f6f98441fa56946ddcd6a20 68f7abf4e6f922807889f52bc043ecd31b79f814 refs/heads/master
+
+export LC_ALL=en_US.UTF-8
+
+class=`git config zephyr.class`
+instance=`git config zephyr.instance`
+zsig=`git config zephyr.zsig`
+color=`git config --bool zephyr.color`
+maxlines=`git config --int zephyr.maxlines 2>/dev/null || echo 50`
+
+if [ "${color:-true}" = "true" ]; then
+ usecolor="--color"
+else
+ usecolor=""
+fi
+
+if [ -z "$zsig" ]; then
+ if [ -e "$GIT_DIR/description" ]; then
+ zsig=`cat "$GIT_DIR/description"`
+ fi
+ if [ -z "$zsig" ] || \
+ [ "$zsig" = "Unnamed repository; edit this file to name it for gitweb." ] || \
+ [ "$zsig" = "Unnamed repository; edit this file 'description' to name the repository." ]; then
+ zsig=$(basename "$(cd "$GIT_DIR" && pwd)")
+ if [ "$zsig" = ".git" ]; then
+ zsig=$(basename "$(cd "$GIT_DIR/.." && pwd)")
+ fi
+ fi
+fi
+
+if [ -z "$class" ]; then
+ echo "I don't know where to send a commit zephyr!" >&2
+ echo "Please set the zephyr.class config variable in" >&2
+ echo "$PWD/config." >&2
+ exit 1
+fi
+
+let max=10
+check_max () {
+ if ! let --max; then
+ zwrite -c "$class" -i "${instance:-git}" -s "Aperture Science Emergency Intelligence Incinerator" -d \
+ -m 'Aborting zephyr hook to prevent zwrite flood.'
+ exit 0
+ fi
+}
+
+while read oldrev newrev refname; do
+ if [ "$oldrev" = "0000000000000000000000000000000000000000" ]; then
+ check_max
+ # dammit git
+ zwrite -c "$class" -i "${instance:-${refname#refs/heads/}}" -s "$zsig: $refname" -d \
+ -m "New branch $refname created, currently at $newrev."
+ continue
+ fi
+ while read rev; do
+ check_max
+ shortrev=`git log -1 --pretty=format:%h "$rev"`
+ lines=`git show -M "$rev" | wc -l`
+ if [ $lines -lt $maxlines ]; then
+ stat=""
+ else
+ stat="--stat"
+ fi
+ (git show -M $stat $usecolor "$rev" |
+ sed -e 's/@/@@/g' \
+ -e 's/}/@(})/g' \
+ -e 's/\e\[m/}@{/g' \
+ -e 's/\e\[1m/}@b{/g' \
+ -e 's/\e\[33m/@color(yellow)/g' \
+ -e 's/\e\[31m/@color(red)/g' \
+ -e 's/\e\[32m/@color(green)/g' \
+ -e 's/\e\[36m/@color(cyan)/g' \
+ -e '1s/^/@{/' \
+ -e '$s/$/}/') |
+ zwrite -c "$class" -i "${instance:-$shortrev}" -s "$zsig: $refname" -d
+ done < <(git rev-list --first-parent --reverse "$oldrev..$newrev")
+done
--- /dev/null
+#!/bin/sh
+# This is a trivial wrapper around git-cvsimport.
+
+dir="$1"
+shift
+if [ -z "$dir" ]; then
+ echo "Usage: git-rcsimport path/to/dir"
+ exit 1
+fi
+if [ ! -d "$dir/RCS" ]; then
+ echo "$dir/RCS does not exist."
+ exit 1
+fi
+
+tmpdir=`mktemp -d -t git-rcsimport-XXXXXXXXXX`
+mkdir "$tmpdir/CVSROOT" "$tmpdir/locks" "$tmpdir/rcs"
+echo "LockDir=$tmpdir/locks" > "$tmpdir/CVSROOT/config"
+absdir=`cd "$dir"; pwd`
+(cd "$absdir"; find . -name RCS -type d) | while read dir; do
+ rcsdir=$tmpdir/rcs/`dirname "$dir"`
+ mkdir -p "$rcsdir"
+ ln -s "$absdir/$dir"/* "$rcsdir"
+done
+CVSROOT="$tmpdir" git cvsimport "$@" rcs
+rm -r "$tmpdir"
--- /dev/null
+#!/bin/sh
+#
+# usage (e.g.): sudo sh iptables-nat.sh [public interface] [private interface]
+#
+# Sets up iptables on Linux 2.6 to run a NAT for all machines on the
+# private interface via the IP bound to the public interface. You should
+# manually configure a private RFC1918 network for the machines on the
+# private interface.
+#
+# Derived from the Linux IP masquerade HOWTO.
+
+public=${1:-ath0}
+private=${2:-eth0}
+
+# permit forwarding connections that have to do with the NAT
+iptables -A FORWARD -i $private -o $public -j ACCEPT
+iptables -A FORWARD -i $public -o $private -m state --state ESTABLISHED,RELATED -j ACCEPT
+
+# drop other connections, else the remaining commands will NAT public requests
+# routed to this machine
+iptables -P FORWARD DROP
+
+# enable NAT
+iptables -t nat -A POSTROUTING -o $public -j MASQUERADE
+
+# this is usually not on by default
+echo 1 > /proc/sys/net/ipv4/ip_forward
--- /dev/null
+# -*- mode: sh -*-
+# kdo is a shell function for interacting with multiple Kerberos
+# credential caches.
+#
+# To use kdo, add this snippet to your .bashrc or .bashrc.mine file.
+#
+# To run a command with a different set of credentials from your
+# default, run
+#
+# kdo <principal> <command>
+#
+# e.g.,
+#
+# kdo broder/root aklog
+#
+# If you lack credentials for the specified principal, you'll be
+# prompted for the password.
+#
+# If kdo needs to acquire tickets, it will pass the value of
+# ${kdo_args[@]} to kinit. I use this to get tickets that last for 15
+# minutes, that are renewable for 60 minutes, and aren't forwardable.
+#
+# To add kdo support for a new platform, you need to provide an
+# interface to multiple credential caches by defining two functions:
+#
+# - kcaches::
+# Print one line per current credential cache of the form "<PRINCIPAL> <KRB5CCNAME>"
+# - knewcache::
+# Without changing the current credentials cache, get credentials
+# for the principal in $1, passing the remaining arguments to
+# kinit.
+# knewcache should set the variable cache with the KRB5CCNAME
+# value for the newly created credential cache
+#
+# Also included is krootssh, a wrapper around ssh for using your
+# root-instance tickets with ssh. It ensures that your tickets don't
+# get accidentally forwarded, on the off chance that you have
+# forwardable tickets.
+
+# CONFIGURATION
+kdo_args=(-l15m -r60m -F)
+
+# CC interface for OS X
+if [ "Darwin" = "$(uname)" ]; then
+ kcaches () {
+ klist -A | perl -ne '$cache = $1 if /^Kerberos 5 ticket cache: '\''(.*)'\''/; print "$1 $cache\n" if /^Default principal: (.*)$/'
+ }
+
+ knewcache () {
+ princ="$1"; shift
+ local oldcache="$(klist | grep 'Kerberos 5 ticket cache' | cut -f 2 -d "'")"
+ # " # <-- emacs thinks there's an unbalanced " on the previous line.
+ kinit "$@" "$princ" || return 1
+ cache="$(kfindcache "$princ")"
+ # On OS X, kinit will switch your default credential cache to
+ # that of the newly acquired tickets, so switch back if we can
+ if [ -z "$oldcache" ]; then
+ echo "W: Tickets for $princ are now in your default credential cache" >&2
+ else
+ kswitch -c "$oldcache"
+ fi
+ }
+fi
+
+# If kcaches and knewcache have been defined for this platform, then
+# setup kdo. Otherwise, add a helpful error.
+if hash kcaches &>/dev/null && hash knewcache &>/dev/null; then
+ kfindcache () {
+ kcaches | fgrep "$1" | cut -d' ' -f2-
+ }
+
+ kdo () {
+ local princ="$1"; shift
+ local cache="$(kfindcache "$princ")"
+ # If the cache that we want to use has expired tickets, then
+ # destroy that cache so we don't try to use it again and clear
+ # $cache so that we'll revert to acquiring a new set of
+ # tickets
+ if [ -n "$cache" ] && ! klist -s "$cache"; then
+ KRB5CCNAME="$cache" kdestroy
+ cache=""
+ fi
+ if [ -z "$cache" ]; then
+ knewcache "$princ" "${kdo_args[@]}" || return 1
+ fi
+ echo "I: Running $1 with cache $cache (for principal $princ)" >&2
+ KRB5CCNAME="$cache" "$@"
+ }
+ _kdo () {
+ local cur
+ COMPREPLY=()
+ cur="${COMP_WORDS[COMP_CWORD]}"
+ opts="$(kcaches | awk '{ print $1 }')"
+ case $COMP_CWORD in
+ 1)
+ COMPREPLY=($(compgen -W "${opts}" -- "${cur}"))
+ ;;
+ 2)
+ COMPREPLY=($(compgen -c -- "${cur}"))
+ esac
+ }
+ complete -o bashdefault -F _kdo kdo
+
+ krootssh () {
+ kdo ${ATHENA_USER:-$USER}/root@ATHENA.MIT.EDU ssh -o GSSAPIDelegateCredentials=no "$@"
+ }
+else
+ kdo () {
+ echo "kdo has not been ported to this platform yet." >&2
+ return 1
+ }
+
+ krootssh () {
+ echo "kdo has not been ported to this plastform yet." >&2
+ return 1
+ }
+fi
+
--- /dev/null
+#!/bin/sh
+export KRB5CCNAME=/tmp/krb5cc_$(id -u).root
+export KRBTKFILE=/tmp/tkt$(id -u).root
+
+case $1 in
+ init)
+ shift;
+ exec kinit -F -5 -l15m -r15m $USER/root@ATHENA.MIT.EDU "$@"
+ ;;
+ destroy)
+ exec kdestroy -45
+ ;;
+ shell)
+ klist -s || krbroot init || exit 1;
+ HOST="`hostname` (krbroot)" pagsh -c $SHELL
+ ;;
+ ssh)
+ klist -s || krbroot init || exit 1;
+ shift
+ exec ssh -k -l root "$@"
+ ;;
+ rlogin)
+ klist -s || krbroot init || exit 1;
+ exec rlogin -x -l root $2
+ ;;
+ *)
+ if [ $# = 0 ]; then
+ echo "Usage: $0 init" >&2
+ echo " $0 destroy" >&2
+ echo " $0 shell" >&2
+ echo " $0 ssh [args]" >&2
+ echo " $0 rlogin [args]" >&2
+ echo " $0 [cmd]" >&2
+ else
+ exec "$@"
+ fi
+ ;;
+esac
--- /dev/null
+#!/bin/sh
+
+case "$1" in
+ -o) args="$2"
+ shift 2;;
+esac
+
+file=$(mktemp)
+echo "$*" | xxd -r -p > "$file"
+objdump -D -b binary -m i386 $args "$file" | tail -n +7
+rm "$file"
--- /dev/null
+#!/bin/sh
+
+if [ "$#" = 0 ]; then
+ echo "usage: $0 [-w wrapper] <C code...>" >&2
+ exit 1
+fi
+
+if [ "$1" = -w ]; then
+ wrapper="$2"
+ shift 2
+fi
+
+f=$(mktemp -d -t gccrun.XXXXXXXX) || exit 1
+cat > "$f/command.c" << EOF
+#define _GNU_SOURCE
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <sys/ptrace.h>
+#include <sys/syscall.h>
+#include <arpa/inet.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+int
+main(int argc, char *argv[], char *envp[])
+{
+ $1;
+ return 0;
+}
+EOF
+shift
+if ! gcc -o "$f/command" "$f/command.c" $@; then
+ exit 1
+fi
+if [ -n "$wrapper" ]; then
+ $wrapper "$f/command"
+else
+ "$f/command"
+fi
+r=$?
+rm -r "$f"
+exit $r
--- /dev/null
+use strict;
+use warnings;
+
+use inc::Module::Install;
+
+barnowl_module('RT');
+
+WriteAll;
--- /dev/null
+Barnowl RT module
+
+To install barnowl RT
+ln -s /mit/snippets/rt/BarnOwl ~/.owl/modules/RT
+
+Go to help.mit.edu to set you rt password
+
+In ~/.rtrc add the following lines:
+user <username>
+passwd <rtpassword>
+
+
+Source tree is located at /mit/pweaver/git/rt-barnowl.git/
+Email rt-barnowl if you would like commit access or if you have
+questions or bugs.
\ No newline at end of file
--- /dev/null
+use warnings;
+use strict;
+
+=head1 NAME
+
+BarnOwl::Module::Rt
+
+=head1 DESCRIPTION
+
+Foo
+
+=cut
+
+package BarnOwl::Module::RT;
+use IPC::Open3;
+use Text::ParseWords;
+
+our $VERSION = '1.0.1';
+
+my %queuemap;
+my %commands = (
+ "((?:set|add|del)\\s.*)", "edit ticket/\$t \$1",
+ "status\\s(deleted|resolved|rejected|open|new|waiting)", "edit ticket/\$t set status=\$1",
+ "(d|del|delete)", "edit ticket/\$t set status=deleted",
+ "(r|res|resolve)", "edit ticket/\$t set status=resolved",
+ "(rej|reject|rejected)", "edit ticket/\$t set status=rejected",
+ "show", "show -l ticket/\$t/history",
+ "show (\\d+)", "show -l ticket/\$1/history",
+ "list", 'rt list -o +Created "((Status=new or Status=stalled or Status=open) and (Queue=\'$q\'))"',
+ "list (\\w+)", 'rt list -o +Created "((Status=new or Status=stalled or Status=open) and (Queue=\'\$1\'))"',
+ "merge (\\d+)", "rt merge \$t \$1",
+ "(take|untake|steal)", "rt \$1 \$t",
+ "(?:owner|give)\\s(\\w+)", "edit ticket/\$t set owner=\$1",
+ );
+
+
+
+my $cfg = BarnOwl::get_config_dir();
+my $file_path = "$cfg/rtqueuemap";
+if(-r "$file_path") {
+ open(my $fh, "<:encoding(UTF-8)", "$file_path") or die("Unable to read $file_path:$!\n");
+ while(defined(my $line = <$fh>)) {
+ next if $line =~ /^\s+#/;
+ next if $line =~ /^\s+$/;
+ my ($class, $q) = quotewords('\s+', 0, $line);
+ $queuemap{lc($class)} = $q;
+ }
+ close($fh);
+}
+
+my $file_path = "$cfg/rtcommands";
+if(-r "$file_path") {
+ open(my $fh, "<:encoding(UTF-8)", "$file_path") or die("Unable to read $file_path:$!\n");
+ while(defined(my $line = <$fh>)) {
+ next if $line =~ /^\s+#/;
+ next if $line =~ /^\s+$/;
+ my ($match, $command) = quotewords('\s+', 0, $line);
+ $commands{$match} = $command;
+ }
+ close($fh);
+}
+
+
+
+sub cmd_rt{
+ shift @_;
+ my $args = join(' ', @_);
+ my $m = owl::getcurmsg();
+ my ($ticket) = $m->instance =~ m/^\D*(\d{7})\D*$/;
+ my ($class) = $m->class =~ /^(?:un)*(.+?)(?:[.]d)*$/i;
+ my $queue = $queuemap{$class};
+
+ for my $key (keys %commands)
+ {
+ my $value = $commands{$key};
+ if($args =~ m/^\s*$key\s*$/){
+ my $match = $value;
+ my @numargs = @+;
+ #my $match = qr/\Q$key\E/
+ if($value =~ m/\$t/){
+ if(!$ticket){
+ BarnOwl::error("Command 'rt " . $args . "' requires a message with ticket number selected");
+ return;
+ }
+ $match =~ s/\$t/$ticket/;
+ }
+ if($value =~ m/\$q/){
+ if(!$queue){
+ BarnOwl::error("Command 'rt " . $args . "' requires a class in rtqueuemap selected");
+ return;
+ }
+ $match =~ s/\$q/$queue/;
+ }
+ for my $digit ($value =~ m/\$(\d)/g){
+ $args =~ m/^\s*$key\s*$/;
+ my $replace = substr($args, $-[$digit], $+[$digit] - $-[$digit]);
+ $match =~ s/\$$digit/$replace/;
+ }
+ return run_rt_command( quotewords('\s+', 0, $match) );
+ }
+ }
+
+ BarnOwl::error("No Matching RT command found for: '" . $args . "'" );
+ return;
+}
+
+sub run_rt_command{
+ my @args = ("athrun","tooltime","rt");
+ push (@args, @_);
+ local(*IN, *OUT, *ERR);
+ my $pid = open3(*IN, *OUT, *ERR, @args) || die("RT threw $!");
+ close(*IN);
+ my $out = do { local $/; <OUT> };
+ close(*OUT);
+ $out .= do { local $/; <ERR> };
+ close(*ERR);
+
+ waitpid( $pid, 0 );
+
+ if (($out =~ tr/\n//) eq 1){
+ return $out;
+ }
+ BarnOwl::popless_text($out);
+ return;
+}
+
+BarnOwl::new_command("rt",
+ \&cmd_rt,
+ {
+ summary => "rt commands in barnowl",
+ usage => "rt <args>",
+ description => <<END_DESCR
+
+Examples:
+ rt [set|add|del] <args> - runs rt (set|add|del) with relevent args - Dangerous if not careful
+ rt [d|del|delete] - mark a ticket deleted
+ rt [r|res|resolve] - mark a ticked resolved
+ rt [rej|reject|rejected] - mark a ticked rejected
+ rt status [deleted|resolved|new|open|waiting|rejected] - set status of a ticket
+ rt show - show detailed history of selected ticket
+ rt show <ticket> - show history of <ticket>
+ rt list - list open tickets of current queue
+ rt list <queue> - lists open tickets of <queue>
+ rt merge <ticket> - merges current ticket with <ticket>
+ rt [take|untake|steal] - takes, untakes, or steals ticket
+ rt [owner|give] <user> - gives selected ticket to <user>
+
+config:
+ Go to help.mit.edu to set you rt password
+
+ In ~\/.rtrc add the following lines:
+ user <username>
+ passwd <password>
+
+
+rtqueuemap:
+ ~\/.owl\/rtqueuemap is a list of queues in the form of
+ class queue
+ which is used for the queue in commands like the rt list function which select the current queue
+ help "Some Help Queue"
+rtcommands:
+ ~\/.owl\/rtcommands is a file where you can put custom
+ commands to map the barnowl rt module with the rt command
+ line tool
+
+ It is a good place to put custom queries which will be used frequently.
+ Examples:
+ "list-owner (\w+)" "rt list -o +Created \"((Status=new or Status=stalled or Status=open) and (Queue='\$q') and 'Owner='\$1')\""
+
+ \$t is the current ticket
+ \$q is the current queue
+ \$[digit] matches control groups in the first reg-exp
+END_DESCR
+ });
+
+
+sub cmd_rt_reply {
+ my $cmd = shift;
+ my $type = "comment";
+ if ($cmd eq "rt-reply"){
+ $type = "correspond";
+ }
+
+ my $m = owl::getcurmsg();
+ my ($ticket) = $m->instance =~ m/^\D*(\d{7})\D*$/;
+ if (!$ticket){
+ BarnOwl::error("Command: '" . $cmd . "' requires a message with a ticket number selected");
+ return;
+ }
+
+ if(@_) {
+ return run_rt_command("rt", $type, "-m", join(" ", @_), $ticket);
+ }
+ return BarnOwl::start_edit_win($cmd . " ticket " . $ticket, sub {run_rt_command("rt", $type, "-m", @_, $ticket)});
+
+}
+
+BarnOwl::new_command("rt-reply",
+ \&cmd_rt_reply,
+ {
+ summary => "Reply to current ticket",
+ usage => "rt-reply [message]",
+ description => <<END_DESCR
+Replies to the currently selected ticket.
+END_DESCR
+ });
+
+BarnOwl::new_command("rt-comment",
+ \&cmd_rt_reply,
+ {
+ summary => "Comment on the current ticket",
+ usage => "rt-reply [message]",
+ description => <<END_DESCR
+Comments on the currently selected ticket.
+END_DESCR
+ });
+
+
+#owl::command('bindkey recv "M-r" command reply-un');
+
+
+1;
--- /dev/null
+my $class = 'scripts-test';
+my $instance_prefix = 'rt.';
+my @zwrite = ('/usr/athena/bin/zwrite', '-d', '-O', 'auto', '-c', $class);
+
+# RT-to-Zephyr notification scrip
+# http://snippets.scripts.mit.edu/gitweb.cgi/.git/blob/HEAD:/rt/Scrips/zephyr.pl
+#
+# Copyright © 2010 Anders Kaseorg <andersk@mit.edu>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# Usage: configure $class and $instance_prefix above, and create a
+# scrip as follows.
+#
+# Description: Send Zephyr
+# Condition: On Transaction
+# Action: User Defined
+# Template: Global template: Blank
+# Stage: TransactionCreate
+# Custom action preparation code:
+# 1;
+# Custom action cleanup code:
+# [insert this code]
+
+sub send_notice {
+ my ($instance, $body, $extra) = @_;
+ open my $out, '|-', @zwrite, '-i', $instance, defined $extra ? ('-s', $extra) : ();
+ print $out $body;
+ close $out;
+};
+
+local $SIG{__DIE__} = sub {
+ my ($err) = @_;
+ $err =~ s/@/@@/g;
+ send_notice "${instance_prefix}error", "Internal error in Zephyr scrip:\n$err";
+};
+
+(my $id = $self->TransactionObj->Ticket) =~ s/@/@@/g;
+(my $description = $self->TransactionObj->Description) =~ s/@/@@/g;
+(my $subject = $self->TransactionObj->TicketObj->Subject) =~ s/@/@@/g;
+
+send_notice "$instance_prefix$id", $description, $subject;
+1;
--- /dev/null
+#!/bin/bash
+#
+# This is a script that can be called from a Subversion post-commit hook
+# to zephyr a summary of the commit or the full commit.
+#
+# Use by putting something like the following in hooks/post-commit:
+# REPOS="$1"
+# REV="$2"
+# /mit/snippets/svn-hooks/commit-zephyr "$REPOS" "$REV" -c scripts
+# /mit/snippets/svn-hooks/commit-zephyr "$REPOS" "$REV" --full -c scripts-auto -i commits
+
+export LC_ALL=en_US.UTF-8
+
+CLASS=test
+INSTANCE=@
+FULL=0
+
+OPTS=$(getopt -o c:i:f -l class:,instance:,full -n "$0" -- "$@") || exit $?
+eval set -- "$OPTS"
+while :; do
+ case "$1" in
+ -c|--class) CLASS=$2; shift 2;;
+ -i|--instance) INSTANCE=$2; shift 2;;
+ -f|--full) FULL=1; shift;;
+ --) shift; break;;
+ *) exit 1;;
+ esac
+done
+[ $# -ge 2 ] || exit 1
+REPOS=$1
+REV=$2
+
+if [ "$INSTANCE" = "${INSTANCE%@}@" ]; then
+ INSTANCE=${INSTANCE%@}r$REV
+fi
+
+dirs=$(svnlook dirs-changed "$REPOS" -r "$REV")
+svnlook info "$REPOS" -r "$REV" | (
+ read -r author
+ read -r datestamp
+ read -r logsize
+ log=$(cat)
+ echo "r$REV by $author $datestamp"
+ echo "$log"
+ svnlook changed "$REPOS" -r "$REV"
+ if [ "$FULL" -eq 1 ]; then
+ echo
+ svnlook diff "$REPOS" -r "$REV"
+ else
+ echo svnlook diff "$REPOS" -r "$REV"
+ fi
+) | zwrite -d -c "$CLASS" -i "$INSTANCE" -O "auto" -s "SVN: r$REV"
--- /dev/null
+#!/bin/sh
+
+export LC_ALL=en_US.UTF-8
+
+REPOS="$1"
+REV="$2"
+USER="$3"
+PROPNAME="$4"
+OLD_VAL="$(cat)"
+NEW_VAL="$(svnlook propget --revprop -r "$REV" "$REPOS" "$PROPNAME")"
+#export PATH="/usr/bin:/bin"
+
+(echo "$USER @(@color(red)changing revprop) @b($PROPNAME)"; echo "from: $OLD_VAL"; echo "to: $NEW_VAL") | zwrite -d -c test -i "r$REV"