From mboxrd@z Thu Jan 1 00:00:00 1970 From: John Wiegley Subject: Downloading Unfuddle tickets as an org file Date: Sun, 1 Mar 2009 03:57:02 -0400 Message-ID: <0DD61293-903E-4AB2-ADCB-EBCA11BCD739@newartisans.com> Mime-Version: 1.0 (Apple Message framework v930.3) Content-Type: multipart/mixed; boundary=Apple-Mail-2--111015540 Return-path: Received: from mailman by lists.gnu.org with tmda-scanned (Exim 4.43) id 1LdgY4-0000uA-On for emacs-orgmode@gnu.org; Sun, 01 Mar 2009 02:57:20 -0500 Received: from exim by lists.gnu.org with spam-scanned (Exim 4.43) id 1LdgY1-0000nj-TE for emacs-orgmode@gnu.org; Sun, 01 Mar 2009 02:57:20 -0500 Received: from [199.232.76.173] (port=45329 helo=monty-python.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1LdgY1-0000nO-A8 for emacs-orgmode@gnu.org; Sun, 01 Mar 2009 02:57:17 -0500 Received: from mx20.gnu.org ([199.232.41.8]:45568) by monty-python.gnu.org with esmtps (TLS-1.0:RSA_AES_256_CBC_SHA1:32) (Exim 4.60) (envelope-from ) id 1LdgY0-0008Nu-Is for emacs-orgmode@gnu.org; Sun, 01 Mar 2009 02:57:16 -0500 Received: from johnwiegley.com ([208.70.150.153] helo=mail.johnwiegley.com) by mx20.gnu.org with esmtp (Exim 4.60) (envelope-from ) id 1LdgXw-0003YM-0g for emacs-orgmode@gnu.org; Sun, 01 Mar 2009 02:57:12 -0500 Received: from [192.168.2.100] (unknown [72.51.86.119]) by mail.johnwiegley.com (Postfix) with ESMTP id DC5044220C8 for ; Sun, 1 Mar 2009 01:57:06 -0600 (CST) List-Id: "General discussions about Org-mode." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: emacs-orgmode-bounces+geo-emacs-orgmode=m.gmane.org@gnu.org Errors-To: emacs-orgmode-bounces+geo-emacs-orgmode=m.gmane.org@gnu.org To: emacs-orgmode Org-Mode --Apple-Mail-2--111015540 Content-Type: text/plain; charset=US-ASCII; format=flowed; delsp=yes Content-Transfer-Encoding: 7bit The following Python script will download your Unfuddle tickets as an org-mode file. It still needs work (like doing something with attachments). But it allows you to carry your Unfuddle tickets around with you. :) John --Apple-Mail-2--111015540 Content-Disposition: attachment; filename=cmdline.py Content-Type: text/x-python-script; x-unix-mode=0711; name="cmdline.py" Content-Transfer-Encoding: 7bit #!/usr/bin/env python """Base class for building command line applications. This class includes logging, exception handling, and argument processing. It also sets up an option parser for the user to simply extend. Version 1.1 Version 1.0 was by ??? Copyright (c) 2009, John Wiegley. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - Neither the name of New Artisans LLC nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ import sys import os import time import optparse import inspect import logging import logging.handlers LEVELS = {'DEBUG': logging.DEBUG, 'INFO': logging.INFO, 'WARNING': logging.WARNING, 'ERROR': logging.ERROR, 'CRITICAL': logging.CRITICAL} class CommandLineApp(object): "Base class for building command line applications." force_exit = True # If true, always ends run() with sys.exit() log_handler = None options = { 'debug': False, 'verbose': False, 'logfile': False, 'loglevel': False } def __init__(self): "Initialize CommandLineApp." # Create the logger self.log = logging.getLogger(os.path.basename(sys.argv[0])) ch = logging.StreamHandler() formatter = logging.Formatter("%(name)s: %(levelname)s: %(message)s") ch.setFormatter(formatter) self.log.addHandler(ch) self.log_handler = ch # Setup the options parser usage = 'usage: %prog [options] ' op = self.option_parser = optparse.OptionParser(usage = usage) op.add_option('', '--debug', action='store_true', dest='debug', default=False, help='show debug messages and pass exceptions') op.add_option('-v', '--verbose', action='store_true', dest='verbose', default=False, help='show informational messages') op.add_option('-q', '--quiet', action='store_true', dest='quiet', default=False, help='do not show log messages on console') op.add_option('', '--log', metavar='FILE', type='string', action='store', dest='logfile', default=False, help='append logging data to FILE') op.add_option('', '--loglevel', metavar='LEVEL', type='string', action='store', dest='loglevel', default=False, help='set log level: DEBUG, INFO, WARNING, ERROR, CRITICAL') return def main(self, *args): """Main body of your application. This is the main portion of the app, and is run after all of the arguments are processed. Override this method to implment the primary processing section of your application.""" pass def handleInterrupt(self): """Called when the program is interrupted via Control-C or SIGINT. Returns exit code.""" self.log.error('Canceled by user.') return 1 def handleMainException(self): "Invoked when there is an error in the main() method." if not self.options.debug: self.log.exception('Caught exception') return 1 ## INTERNALS (Subclasses should not need to override these methods) def run(self): """Entry point. Process options and execute callback functions as needed. This method should not need to be overridden, if the main() method is defined.""" # Process the options supported and given self.options, main_args = self.option_parser.parse_args() if self.options.logfile: fh = logging.handlers.RotatingFileHandler(self.options.logfile, maxBytes = (1024 * 1024), backupCount = 5) formatter = logging.Formatter("%(asctime)s - %(levelname)s: %(message)s") fh.setFormatter(formatter) self.log.addHandler(fh) if self.options.quiet: self.log.removeHandler(self.log_handler) ch = logging.handlers.SysLogHandler() formatter = logging.Formatter("%(name)s: %(levelname)s: %(message)s") ch.setFormatter(formatter) self.log.addHandler(ch) self.log_handler = ch if self.options.loglevel: self.log.setLevel(LEVELS[self.options.loglevel]) elif self.options.debug: self.log.setLevel(logging.DEBUG) elif self.options.verbose: self.log.setLevel(logging.INFO) exit_code = 0 try: # We could just call main() and catch a TypeError, but that would # not let us differentiate between application errors and a case # where the user has not passed us enough arguments. So, we check # the argument count ourself. argspec = inspect.getargspec(self.main) expected_arg_count = len(argspec[0]) - 1 if len(main_args) >= expected_arg_count: exit_code = self.main(*main_args) else: self.log.debug('Incorrect argument count (expected %d, got %d)' % (expected_arg_count, len(main_args))) self.option_parser.print_help() exit_code = 1 except KeyboardInterrupt: exit_code = self.handleInterrupt() except SystemExit, msg: exit_code = msg.args[0] except Exception: exit_code = self.handleMainException() if self.options.debug: raise if self.force_exit: sys.exit(exit_code) return exit_code def http_uptest(self, proc, hostname = 'localhost', port = 80, url = u'/'): import httplib conn = httplib.HTTPConnection(hostname, port = port) try: conn.request("GET", url) except Exception: self.log.exception('-- http_uptest exception:') return False resp = conn.getresponse() conn.close() if resp.status == 200: self.log.debug('-- http_uptest succeeded: %s' % (resp.status, resp.reason)) return True else: self.log.warning('-- http_uptest FAILED: %s %s' % (resp.status, resp.reason)) return False def spawn_and_wait(self, cmd, *args, **kwargs): from subprocess import Popen p = Popen((cmd,) + args) while True: # Wait a bit before checking on the process if kwargs.has_key('poll'): time.sleep(kwargs['poll']) else: time.sleep(1) # Check whether the process aborted entirely for any reason. If # so, log the fact and then let our outer loop run it again. sts = p.poll() if sts is not None: self.log.info('-- %s exited: %d' % (cmd, sts)) return sts # The process is still running. Check whether it is still viable # by calling the given callback. death = False try: if kwargs.has_key('uptest') and \ callable(kwargs['uptest']) and \ not kwargs['uptest'](p): death = True except Exception: self.log.exception('-- %s exception:' % cmd) # If the process is no longer viable, we kill it and exit if death is True: try: import win32api import win32con import win32process handle = win32api.OpenProcess(win32con.PROCESS_ALL_ACCESS, True, p.pid) exitcode = win32process.GetExitCodeProcess(handle) if exitcode == win32con.STILL_ACTIVE: win32api.TerminateProcess(handle, 0) self.log.warning('-- %s killed' % cmd) except: import signal try: os.kill(p.pid, signal.SIGHUP) except: pass try: os.kill(p.pid, signal.SIGINT) except: pass try: os.kill(p.pid, signal.SIGQUIT) except: pass try: os.kill(p.pid, signal.SIGKILL) except: pass self.log.warning('-- %s killed' % cmd) return -1 if __name__ == "__main__": import tempfile class sample(CommandLineApp): def main(self, *args): self.log.info("Saw args: %s" % (args,)) temp = tempfile.NamedTemporaryFile() temp.write("Args were: %s" % (args,)) temp.flush() name = temp.name self.log.info("Wrote args to '%s'" % name) self.spawn_and_wait('/bin/ls', '-l', name) del temp self.spawn_and_wait('/bin/ls', '-l', name) self.spawn_and_wait('/bin/sleep', '30', uptest = self.http_uptest) x = 1 / 0 # logged as an exception sample().run() --Apple-Mail-2--111015540 Content-Disposition: attachment; filename=tickets2org.py Content-Type: text/x-python-script; x-unix-mode=0755; name="tickets2org.py" Content-Transfer-Encoding: 7bit #!/usr/bin/env python import logging import urllib2 from cmdline import CommandLineApp from hashlib import sha1 from datetime import datetime from xml.dom.minidom import parseString def getText(nodelist): rc = "" for node in nodelist: if node.nodeType == node.TEXT_NODE: rc = rc + node.data return rc class Tickets2Org(CommandLineApp): def __init__(self): CommandLineApp.__init__(self) self.project = None self.number = None self.username = None self.password = None self.versions = {} self.milestones = {} self.people = {} self.tickets = {} #op = self.option_parser #op.add_option('-P', '--port', metavar='PORT', # type='string', action='store', dest='port', # default=80, help='HTTP port to connect to (default: 80)') self.log.setLevel(logging.INFO) def download(self, which): url = 'http://%s.unfuddle.com/api/v1/projects/%s/%s' % \ (self.project, self.number, which) auth_handler = urllib2.HTTPBasicAuthHandler() auth_handler.add_password(realm='Unfuddle API', uri=url, user=self.username, passwd=self.password) opener = urllib2.build_opener(auth_handler) # ...and install it globally so it can be used with urlopen. urllib2.install_opener(opener) req = urllib2.Request(url) req.add_header('Accept', 'application/xml') f = urllib2.urlopen(req) items = {} topNode = parseString(f.read()).childNodes[0] for childNode in topNode.childNodes: if childNode.nodeType == childNode.TEXT_NODE: continue item = {} for propertyNode in childNode.childNodes: if propertyNode.nodeType == propertyNode.TEXT_NODE: continue item[propertyNode.nodeName] = getText(propertyNode.childNodes) items[item['id']] = item return items def main(self, project, number, username, password): self.project = project self.number = number self.username = username self.password = password self.versions = self.download('versions') self.milestones = self.download('milestones') self.people = self.download('people') self.tickets = self.download('tickets') for ticket in self.tickets.values(): self.ticket_to_org(ticket) def ticket_to_org(self, ticket): # jww (2009-03-01): Download attachments as :DATA:/:END: print "** TODO", if ticket['priority'] == '1' or ticket['priority'] == '2': print "[#A]", if ticket['priority'] == '3': print "[#B]", if ticket['priority'] == '4' or ticket['priority'] == '5': print "[#C]", print ticket['summary'] if ticket['due-on']: deadline = datetime.strptime(ticket['created-at'], '%Y-%m-%dT%H:%M:%S-04:00') print " DEADLINE: <%s -2w>" % \ deadline.strftime('[%Y-%m-%d %a %H:%M]') print ticket['description'] print " :PROPERTIES:" reporter = "%s %s <%s>" % \ (self.people[ticket['reporter-id']]['first-name'], self.people[ticket['reporter-id']]['last-name'], self.people[ticket['reporter-id']]['email']) print " :Submitter:", reporter if ticket['version-id']: print " :Version:", \ self.versions[ticket['version-id']]['name'] if ticket['milestone-id']: print " :Milestone:", \ self.milestones[ticket['milestone-id']]['title'] print " :Ticket:", ticket['id'] #if ticket['hours-estimate-current']: # print " :Effort:", ticket['hours-estimate-initial'] created = datetime.strptime(ticket['created-at'], '%Y-%m-%dT%H:%M:%S-04:00') timestamp = created.strftime('[%Y-%m-%d %a %H:%M]') m = sha1() m.update(reporter) m.update(ticket['id']) m.update(timestamp) h = m.hexdigest().upper() print " :ID: %s-%s-%s-%s-%s" % \ (h[0:8], h[8:12], h[12:16], h[16:20], h[20:32]) print " :END:" created = datetime.strptime(ticket['created-at'], '%Y-%m-%dT%H:%M:%S-04:00') print " ", timestamp if __name__ == "__main__": Tickets2Org().run() --Apple-Mail-2--111015540 Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Content-Disposition: inline _______________________________________________ Emacs-orgmode mailing list Remember: use `Reply All' to send replies to the list. Emacs-orgmode@gnu.org http://lists.gnu.org/mailman/listinfo/emacs-orgmode --Apple-Mail-2--111015540--