From: Alex Dehnert Date: Sat, 30 Nov 2013 06:45:06 +0000 (-0500) Subject: Update Django plugin with current upstream code X-Git-Url: https://snippets.scripts.mit.edu/gitweb.cgi/Scripts/git/.git/commitdiff_plain/5a2e6861a908cf663ddd2f95572a3a484825ad0c?hp=5ba5fe8d4f31b66af67137cc36f79597138b76b0 Update Django plugin with current upstream code * commit '5ba5fe8d4f31b66af67137cc36f79597138b76b0': Scripts auth: don't activate on 127.0.0.1 either Use API function instead of UNUSABLE_PASSWORD Set a password of UNUSABLE_PASSWORD kinit when creating a MoiraList object Wrappers for safely calling commands in a new PAG Validate constitution_url (ASA-#76) Django MIT plugin: Don't crash on users with hidden emails (ASA Trac: #63) Function to create an MIT user with LDAP data --- diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f3d74a9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.pyc +*~ diff --git a/README b/README new file mode 100644 index 0000000..64c8f29 --- /dev/null +++ b/README @@ -0,0 +1,22 @@ +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. diff --git a/TracZephyrPlugin/.gitignore b/TracZephyrPlugin/.gitignore new file mode 100644 index 0000000..f07a57d --- /dev/null +++ b/TracZephyrPlugin/.gitignore @@ -0,0 +1,4 @@ +build +dist +*.pyc +*.egg-info diff --git a/TracZephyrPlugin/COPYING b/TracZephyrPlugin/COPYING new file mode 100644 index 0000000..c8e2113 --- /dev/null +++ b/TracZephyrPlugin/COPYING @@ -0,0 +1,20 @@ +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. diff --git a/TracZephyrPlugin/INSTALL b/TracZephyrPlugin/INSTALL new file mode 100644 index 0000000..28c6bf2 --- /dev/null +++ b/TracZephyrPlugin/INSTALL @@ -0,0 +1,14 @@ +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. diff --git a/TracZephyrPlugin/ZephyrPlugin.py b/TracZephyrPlugin/ZephyrPlugin.py new file mode 100644 index 0000000..0c904b4 --- /dev/null +++ b/TracZephyrPlugin/ZephyrPlugin.py @@ -0,0 +1,84 @@ +# -*- 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)) diff --git a/TracZephyrPlugin/setup.py b/TracZephyrPlugin/setup.py new file mode 100755 index 0000000..75e33d0 --- /dev/null +++ b/TracZephyrPlugin/setup.py @@ -0,0 +1,16 @@ +#!/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 + """, +) diff --git a/apt-zephyr/90zephyr b/apt-zephyr/90zephyr new file mode 100644 index 0000000..563e792 --- /dev/null +++ b/apt-zephyr/90zephyr @@ -0,0 +1,3 @@ +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"; diff --git a/apt-zephyr/README b/apt-zephyr/README new file mode 100644 index 0000000..fc20d47 --- /dev/null +++ b/apt-zephyr/README @@ -0,0 +1,17 @@ +apt-zephyr hook + +Author: Anders Kaseorg + +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). diff --git a/apt-zephyr/apt-zephyr b/apt-zephyr/apt-zephyr new file mode 100755 index 0000000..ed362fb --- /dev/null +++ b/apt-zephyr/apt-zephyr @@ -0,0 +1,119 @@ +#!/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 diff --git a/barn-growl/abstfilter.py b/barn-growl/abstfilter.py new file mode 100755 index 0000000..34755d5 --- /dev/null +++ b/barn-growl/abstfilter.py @@ -0,0 +1,94 @@ +#!/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 + diff --git a/barn-growl/barn-growl.py b/barn-growl/barn-growl.py new file mode 100755 index 0000000..e36f719 --- /dev/null +++ b/barn-growl/barn-growl.py @@ -0,0 +1,132 @@ +#!/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)) diff --git a/barn-growl/sexpr.py b/barn-growl/sexpr.py new file mode 100644 index 0000000..a00f9c5 --- /dev/null +++ b/barn-growl/sexpr.py @@ -0,0 +1,215 @@ +#!/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() diff --git a/barnowl/personal-colors.pl b/barnowl/personal-colors.pl new file mode 100644 index 0000000..0e40c42 --- /dev/null +++ b/barnowl/personal-colors.pl @@ -0,0 +1,28 @@ +# 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; diff --git a/barnowl/zcrypt.pl b/barnowl/zcrypt.pl new file mode 100644 index 0000000..e60b0de --- /dev/null +++ b/barnowl/zcrypt.pl @@ -0,0 +1,38 @@ +# 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; diff --git a/bash/newline-fix.bash b/bash/newline-fix.bash new file mode 100644 index 0000000..8295e87 --- /dev/null +++ b/bash/newline-fix.bash @@ -0,0 +1,53 @@ +# 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 +# “\n” before the prompt if it would otherwise start in the +# middle of a line. +# +# Copyright © 2010 Anders Kaseorg +# +# 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 <' && \ + tput -S <&2;$PROMPT_COMMAND" diff --git a/certs/pkcs2pem b/certs/pkcs2pem new file mode 100755 index 0000000..513b32e --- /dev/null +++ b/certs/pkcs2pem @@ -0,0 +1,45 @@ +#!/bin/bash + +set -e + +usage() { + cat < + +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 <, containing one line per file, of +commands to be checked. + +=item + +Add C 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. + +=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"); +} diff --git a/django/COPYING b/django/COPYING new file mode 100644 index 0000000..682a399 --- /dev/null +++ b/django/COPYING @@ -0,0 +1,19 @@ +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. diff --git a/django/README b/django/README new file mode 100644 index 0000000..5975e8d --- /dev/null +++ b/django/README @@ -0,0 +1,18 @@ +/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. diff --git a/__init__.py b/django/mit/__init__.py similarity index 100% rename from __init__.py rename to django/mit/__init__.py diff --git a/git-hooks/repo-noroot-update b/git-hooks/repo-noroot-update new file mode 100755 index 0000000..66b2efe --- /dev/null +++ b/git-hooks/repo-noroot-update @@ -0,0 +1,81 @@ +#!/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 )" >&2 + exit 1 +fi + +if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then + echo "Usage: $0 " >&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 diff --git a/git-hooks/zephyr-post-receive b/git-hooks/zephyr-post-receive new file mode 100755 index 0000000..595234c --- /dev/null +++ b/git-hooks/zephyr-post-receive @@ -0,0 +1,84 @@ +#!/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 +# +# 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/\[m/}@{/g' \ + -e 's/\[1m/}@b{/g' \ + -e 's/\[33m/@color(yellow)/g' \ + -e 's/\[31m/@color(red)/g' \ + -e 's/\[32m/@color(green)/g' \ + -e 's/\[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 diff --git a/git-rcsimport b/git-rcsimport new file mode 100755 index 0000000..4b06a00 --- /dev/null +++ b/git-rcsimport @@ -0,0 +1,25 @@ +#!/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" diff --git a/iptables-nat.sh b/iptables-nat.sh new file mode 100644 index 0000000..eb9e5f8 --- /dev/null +++ b/iptables-nat.sh @@ -0,0 +1,27 @@ +#!/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 diff --git a/kerberos/kdo b/kerberos/kdo new file mode 100644 index 0000000..e06910f --- /dev/null +++ b/kerberos/kdo @@ -0,0 +1,118 @@ +# -*- 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 +# +# 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 " " +# - 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 + diff --git a/kerberos/krbroot b/kerberos/krbroot new file mode 100755 index 0000000..5078d15 --- /dev/null +++ b/kerberos/krbroot @@ -0,0 +1,38 @@ +#!/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 diff --git a/programming/disasm b/programming/disasm new file mode 100755 index 0000000..ad014eb --- /dev/null +++ b/programming/disasm @@ -0,0 +1,11 @@ +#!/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" diff --git a/programming/gccrun b/programming/gccrun new file mode 100755 index 0000000..5241931 --- /dev/null +++ b/programming/gccrun @@ -0,0 +1,48 @@ +#!/bin/sh + +if [ "$#" = 0 ]; then + echo "usage: $0 [-w wrapper] " >&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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 diff --git a/rt/BarnOwl/Makefile.PL b/rt/BarnOwl/Makefile.PL new file mode 100644 index 0000000..b32d97d --- /dev/null +++ b/rt/BarnOwl/Makefile.PL @@ -0,0 +1,8 @@ +use strict; +use warnings; + +use inc::Module::Install; + +barnowl_module('RT'); + +WriteAll; diff --git a/rt/BarnOwl/README b/rt/BarnOwl/README new file mode 100644 index 0000000..27e9b4b --- /dev/null +++ b/rt/BarnOwl/README @@ -0,0 +1,15 @@ +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 +passwd + + +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 diff --git a/rt/BarnOwl/lib/BarnOwl/Module/RT.pm b/rt/BarnOwl/lib/BarnOwl/Module/RT.pm new file mode 100644 index 0000000..b4ee67f --- /dev/null +++ b/rt/BarnOwl/lib/BarnOwl/Module/RT.pm @@ -0,0 +1,222 @@ +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 $/; }; + close(*OUT); + $out .= do { local $/; }; + 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 ", + description => < - 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 - show history of + rt list - list open tickets of current queue + rt list - lists open tickets of + rt merge - merges current ticket with + rt [take|untake|steal] - takes, untakes, or steals ticket + rt [owner|give] - gives selected ticket to + +config: + Go to help.mit.edu to set you rt password + + In ~\/.rtrc add the following lines: + user + passwd + + +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 => < "Comment on the current ticket", + usage => "rt-reply [message]", + description => < +# +# 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; diff --git a/svn-hooks/commit-zephyr b/svn-hooks/commit-zephyr new file mode 100755 index 0000000..c0b48d7 --- /dev/null +++ b/svn-hooks/commit-zephyr @@ -0,0 +1,52 @@ +#!/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" diff --git a/svn-hooks/zephyr-post-revprop-change b/svn-hooks/zephyr-post-revprop-change new file mode 100755 index 0000000..af15922 --- /dev/null +++ b/svn-hooks/zephyr-post-revprop-change @@ -0,0 +1,13 @@ +#!/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"