* Downloading Unfuddle tickets as an org file
@ 2009-03-01 7:57 John Wiegley
0 siblings, 0 replies; only message in thread
From: John Wiegley @ 2009-03-01 7:57 UTC (permalink / raw)
To: emacs-orgmode Org-Mode
[-- Attachment #1: Type: text/plain, Size: 230 bytes --]
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
[-- Attachment #2: cmdline.py --]
[-- Type: text/x-python-script, Size: 10667 bytes --]
#!/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] <BOUND-IP-ADDRESS>'
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()
[-- Attachment #3: tickets2org.py --]
[-- Type: text/x-python-script, Size: 4758 bytes --]
#!/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()
[-- Attachment #4: Type: text/plain, Size: 204 bytes --]
_______________________________________________
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
^ permalink raw reply [flat|nested] only message in thread
only message in thread, other threads:[~2009-03-01 7:57 UTC | newest]
Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2009-03-01 7:57 Downloading Unfuddle tickets as an org file John Wiegley
Code repositories for project(s) associated with this public inbox
https://git.savannah.gnu.org/cgit/emacs/org-mode.git
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).