Tue, 04 Aug 2015 22:39:22 +0300
Mercurial handling major overhaul. Also get some stuff ready for Python 3
--- a/bt.py Mon Aug 03 19:45:57 2015 +0300 +++ b/bt.py Tue Aug 04 22:39:22 2015 +0300 @@ -42,13 +42,12 @@ def get_ticket_data (bot, replyto, ticket, withlink): if suds_active == False: - print "suds is not active" return data = {} try: data = get_issue (ticket) - except Exception, e: + except Exception as e: bot.privmsg (replyto, "Failed to get info for issue %s: %s" % (ticket, e)) if data: @@ -90,13 +89,12 @@ global btannounce_id try: - print 'Initializing MantisBT connection...' + print ('Initializing MantisBT connection...') suds_import = suds.xsd.doctor.Import ('http://schemas.xmlsoap.org/soap/encoding/', 'http://schemas.xmlsoap.org/soap/encoding/') suds_client = suds.client.Client ('https://zandronum.com/tracker/api/soap/mantisconnect.php?wsdl', plugins=[suds.xsd.doctor.ImportDoctor (suds_import)]) suds_active = True except Exception as e: - print 'Failed to establish MantisBT connection: ' + `e` - pass + print ('Failed to establish MantisBT connection: %s' % e) if suds_active: sys.stdout.write ('Retrieving latest tracker ticket... ') @@ -104,7 +102,7 @@ btannounce_id = suds_client.service.mc_issue_get_biggest_id (user, password, 0) btannounce_active = True update_checktimeout() - print btannounce_id + print (btannounce_id) def update_checktimeout(): global btannounce_timeout @@ -119,9 +117,8 @@ def get_issue (ticket): global suds_client user, password = credentials() - print "get_issue: Retrieving issue data for %s" % ticket + print ("Retrieving issue data for %s..." % ticket) result = suds_client.service.mc_issue_get (user, password, ticket) - print "Issue data recieved." return result def poll():
--- a/calc.py Mon Aug 03 19:45:57 2015 +0300 +++ b/calc.py Tue Aug 04 22:39:22 2015 +0300 @@ -26,6 +26,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ''' +from __future__ import print_function import re import cmath import math @@ -146,7 +147,7 @@ 'or': { 'symbol': '||', 'operands': 2, 'priority': 604, 'function': lambda x, y: x or y }, } -for name, data in OperatorData.iteritems(): +for name, data in OperatorData.items(): Operators.append (Operator (name=name, symbol=data['symbol'], operands=data['operands'], priority=data['priority'], function=data['function'])) @@ -230,11 +231,11 @@ SymbolTypes = {} Symbols = [] -for name, value in Constants.iteritems(): +for name, value in Constants.items(): Symbols.append (name) SymbolTypes[name] = SymbolType.CONSTANT -for name, data in Functions.iteritems(): +for name, data in Functions.items(): Symbols.append (name) SymbolTypes[name] = SymbolType.FUNCTION @@ -258,7 +259,7 @@ return (len(li) - 1) - li[::-1].index(value) def realPrint (x): - print x + print (x) Attributes = { 'hex': lambda self: self.set_preferred_base (0x10),
--- a/cobalt.py Mon Aug 03 19:45:57 2015 +0300 +++ b/cobalt.py Tue Aug 04 22:39:22 2015 +0300 @@ -27,6 +27,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ''' +from __future__ import print_function import asyncore import sys import traceback @@ -93,7 +94,7 @@ autoconnects = Config.get_value ('autoconnect', []) if len (autoconnects) == 0: - print "Nowhere to connect." + print ('Nowhere to connect.') quit() for aconn in autoconnects: @@ -102,7 +103,7 @@ Irc.irc_client (conndata, 0) break else: - raise ValueError ("unknown autoconnect entry %s" % (aconn)) + raise ValueError ('unknown autoconnect entry "%s"' % (aconn)) # Start the REST server if Config.get_node ('rest').get_value ('active', True):
--- a/configfile.py Mon Aug 03 19:45:57 2015 +0300 +++ b/configfile.py Tue Aug 04 22:39:22 2015 +0300 @@ -26,6 +26,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ''' +from __future__ import print_function import json, sys IsInitialized = False Config = None @@ -95,10 +96,10 @@ with open (ConfigFileName, 'w') as fp: json.dump (self.obj, fp, sort_keys = True, indent = 1) - print "Config saved." + print ("""Config saved.""") def find_developer_by_email (self, commit_email): - for developer, emails in self.get_value ('developer_emails', default={}).iteritems(): + for developer, emails in self.get_value ('developer_emails', default={}).items(): if commit_email in emails: return developer @@ -115,13 +116,13 @@ ConfigFileName = filename if not IsInitialized: - print """Loading configuration...""" + print ("""Loading configuration...""") try: with open (filename, 'r') as fp: jsondata = json.loads (fp.read()) except IOError as e: - print ("""couldn't open %s: %s""" % (filename, e)) + print ("""Couldn't open %s: %s""" % (filename, e)) quit() global Config
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgdb.py Tue Aug 04 22:39:22 2015 +0300 @@ -0,0 +1,117 @@ +''' + Copyright 2015 Teemu Piippo + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. 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. + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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 os +import sqlite3 +import hgpoll +from datetime import datetime +from configfile import Config + +class HgCommitsDatabase (object): + def __init__(self): + dbname = Config.get_node('hg').get_value('commits_db', 'commits.db') + needNew = not os.path.isfile (dbname) + self.db = sqlite3.connect (dbname) + + if needNew: + self.create_new() + + def create_new (self): + self.db.executescript (''' + DROP TABLE IF EXISTS COMMITS; + DROP TABLE IF EXISTS REPOS; + DROP TABLE IF EXISTS REPOCOMMITS; + CREATE TABLE IF NOT EXISTS COMMITS + ( + Node text NOT NULL, + Dateversion text NOT NULL, + PRIMARY KEY (Node) + ); + CREATE TABLE IF NOT EXISTS REPOS + ( + Name text NOT NULL, + PRIMARY KEY (Name) + ); + CREATE TABLE IF NOT EXISTS REPOCOMMITS + ( + Reponame text, + Node text, + FOREIGN KEY (Reponame) REFERENCES REPOS(Name), + FOREIGN KEY (Node) REFERENCES COMMITS(Node) + ); + ''') + + print ('Building commits.db...') + for repo in hgpoll.Repositories: + print ('Adding commits from %s...' % repo.name) + + for line in repo.hg ('log', '--template', '{node} {date|hgdate}\n').splitlines(): + changeset, timestamp, tz = line.split(' ') + self.add_commit (repo, changeset, int (timestamp)) + + self.commit() + + def add_commit (self, repo, changeset, timestamp): + dateversion = datetime.utcfromtimestamp (timestamp).strftime ('%y%m%d-%H%M') + self.db.execute (''' + INSERT OR IGNORE INTO REPOS + VALUES (?) + ''', (repo.name,)) + + self.db.execute (''' + INSERT OR IGNORE INTO COMMITS + VALUES (?, ?) + ''', (changeset, dateversion)) + + self.db.execute (''' + INSERT INTO REPOCOMMITS + VALUES (?, ?) + ''', (repo.name, changeset)) + + def get_commit_repos (self, node): + cursor = self.db.execute (''' + SELECT Reponame + FROM REPOCOMMITS + WHERE Node LIKE ? + ''', (node + '%',)) + + names = cursor.fetchall() + names = set (zip (*names)[0]) if names else set() + return [hgpoll.RepositoriesByName[name] for name in names] + + def find_commit_by_dateversion (self, dateversion): + cursor = self.db.execute (''' + SELECT Node + FROM COMMITS + WHERE Dateversion = ? + ''', (dateversion,)) + result = cursor.fetchone() + return result[0] if result else None + + def commit(self): + self.db.commit() \ No newline at end of file
--- a/hgpoll.py Mon Aug 03 19:45:57 2015 +0300 +++ b/hgpoll.py Tue Aug 04 22:39:22 2015 +0300 @@ -26,118 +26,22 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ''' +from __future__ import print_function import time import re import bt as Bt import irc as Irc -import os, sqlite3, subprocess -from datetime import datetime +import os from configfile import Config import utility -import subprocess import random -import math - -g_CommitsDb = None -ZDoomRevNumber = 0 - -def all_repo_names(): - return Config.get_node ('hg').get_value ('repos', {}).keys() - -class CommitsDb (object): - def __init__(self): - dbname = Config.get_node('hg').get_value('commits_db', 'commits.db') - needNew = not os.path.isfile (dbname) - self.db = sqlite3.connect (dbname) - - if needNew: - self.create_new() - - def create_new (self): - self.db.executescript (''' - DROP TABLE IF EXISTS COMMITS; - DROP TABLE IF EXISTS REPOS; - DROP TABLE IF EXISTS REPOCOMMITS; - CREATE TABLE IF NOT EXISTS COMMITS - ( - Node text NOT NULL, - Dateversion text NOT NULL, - PRIMARY KEY (Node) - ); - CREATE TABLE IF NOT EXISTS REPOS - ( - Name text NOT NULL, - PRIMARY KEY (Name) - ); - CREATE TABLE IF NOT EXISTS REPOCOMMITS - ( - Reponame text, - Node text, - FOREIGN KEY (Reponame) REFERENCES REPOS(Name), - FOREIGN KEY (Node) REFERENCES COMMITS(Node) - ); - ''') - - print 'Building commits.db...' - for repo in all_repo_names(): - print 'Adding commits from %s...' % repo - - data = subprocess.check_output (['hg', '--cwd', repo, 'log', '--template', - '{node} {date|hgdate}\n']).splitlines() +from hgrepo import HgRepository +from hgdb import HgCommitsDatabase +import traceback +import sys - for line in data: - changeset, timestamp, tz = line.split(' ') - self.add_commit (repo, changeset, int (timestamp)) - - self.commit() - - def add_commit (self, repo, changeset, timestamp): - dateversion = datetime.utcfromtimestamp (timestamp).strftime ('%y%m%d-%H%M') - self.db.execute (''' - INSERT OR IGNORE INTO REPOS - VALUES (?) - ''', (repo,)) - - self.db.execute (''' - INSERT OR IGNORE INTO COMMITS - VALUES (?, ?) - ''', (changeset, dateversion)) - - self.db.execute (''' - INSERT INTO REPOCOMMITS - VALUES (?, ?) - ''', (repo, changeset)) - - def get_commit_repos (self, node): - cursor = self.db.execute (''' - SELECT Reponame - FROM REPOCOMMITS - WHERE Node LIKE ? - ''', (node + '%',)) - - results = cursor.fetchall() - return list (set (zip (*results)[0])) if results else [] - - def find_commit_by_dateversion (self, dateversion): - cursor = self.db.execute (''' - SELECT Node - FROM COMMITS - WHERE Dateversion = ? - ''', (dateversion,)) - result = cursor.fetchone() - return result[0] if result else None - - def commit(self): - self.db.commit() - -def color_for_repo (repo_name): - repo_name = repo_name.lower() - repoconfig = Config.get_node ('hg').get_node ('repos') - - if repoconfig.has_node (repo_name): - return repoconfig.get_node(repo_name).get_value ('colorcode', 0) - - return 0 +Repositories = [] +RepositoriesByName = {} def prettify_bookmarks (bookmarks): if bookmarks: @@ -145,112 +49,113 @@ else: return '' -def get_repo_info (reponame): - reponame = reponame.lower() - repoconfig = Config.get_node ('hg').get_node ('repos') +def get_repo_by_name (name): + global Repositories, RepositoriesByName + name = name.lower() - if repoconfig.has_node (reponame): - return repoconfig.get_node (reponame) + if name not in RepositoriesByName: + repo = HgRepository (name) + Repositories.append (repo) + RepositoriesByName[name] = repo + else: + repo = RepositoriesByName[name] - return None + return repo -def check_repo_exists (repo_name): +def check_repo_exists (name): ' Check if a repository exists ' - print ('Checking that %s exists...' % repo_name) + print ('Checking that %s exists...' % name) - if not os.path.exists (repo_name): - os.makedirs (repo_name) + repo = get_repo_by_name (name) - if not os.path.isfile (os.path.join (repo_name, '.hg', 'hgrc')): + if not os.path.exists (repo.name): + os.makedirs (repo.name) + + if not repo.is_valid(): # If the repo does not exist, clone it. - repo_url = get_repo_info (repo_name).get_value ('url') try: - print ('Cloning %s...' % repo_name) - subprocess.call (['hg', 'clone', repo_url, repo_name]) + repo.clone() # We need to un-alias a few things, they may be aliased on the host machine (e.g. mine) comms=['log', 'incoming', 'pull', 'commit', 'push', 'outgoing', 'strip', 'transplant'] try: - with open (os.path.join (repo_name, '.hg', 'hgrc'), 'a') as fp: + with open (os.path.join (repo.name, '.hg', 'hgrc'), 'a') as fp: fp.write ('\n[alias]\n' + ''.join(['%s=%s\n' % (x, x) for x in comms])) except Exception as e: - print ('Warning: unable to alter hgrc of %s: %s' % repo_name, e) + print ('Warning: unable to alter hgrc of %s: %s' % repo.name, e) print ('Cloning done.') except Exception as e: - print ('Unable to clone %s from %s: %s' % (repo_name, repo_url, e)) + raise HgProcessError ('Unable to clone %s from %s: %s' % (repo.name, repo.url, e)) quit (1) + if not repo.is_valid(): + raise HgProcessError ('''%s is not a valid repository after cloning ''' + '''(.hg is missing)''' % repo.name) + class HgProcessError (Exception): def __init__ (self, value): self.message = value def __str__ (self): return self.message -def contains_published_repositories (reponames): - for reponame in reponames: - if is_published (reponame): +def contains_published_repositories (repos): + for repo in repos: + if repo.published: return True return False -def is_published (reponame): - repoinfo = get_repo_info (reponame) - return (repoinfo and not repoinfo.get_value ('extrarepo', default=False)) - -def announce_ticket_resolved (ticket_id, cset): +def announce_ticket_resolved (ticket_id, cset, db): ticket_id = int (ticket_id) - reponames = g_CommitsDb.get_commit_repos (cset) + repos = db.get_commit_repos (cset) - if not contains_published_repositories (reponames): + for repo in repos: + if repo.published: + break + else: raise HgProcessError ('Changeset %s is only committed to non-published repositories: %s' % - (cset, ', '.join (reponames))) - - repo_url = repoinfo.get_value ('url', default=None) - - if not repo_url: - raise HgProcessError ('Repo %s has no url!' % reponame) + (cset, ', '.join (repos))) # Acquire additional data - moredata = get_commit_data (reponame, cset, - r"{author|person}\n{date(date, '%A %d %B %Y %H:%M:%S')}\n{author|email}").split('\n') + commit = repo.get_commit_data (rev=cset, + author='author|person', + date='date(date, "%A %d %B %Y %H:%M:%S")', + email='author|email', + message='desc') if len (moredata) != 2: - raise HgProcessError ('malformed hg data while processing %s' % cset) + raise HgProcessError ('Received invalid hg data while processing %s' % cset) - commit_author = moredata[0] - commit_date = moredata[1] - commit_email = moredata[2] - commit_message = subprocess.check_output (['hg', '--cwd', reponame, 'log', '--rev', cset, '--template', '{desc}']) - commit_diffstat = subprocess.check_output (['hg', '--cwd', reponame, 'diff', '--change', cset, '--stat']) + diffstat = repo.hg ('diff', '--change', cset, '--stat') try: ticket_data = Bt.get_issue (ticket_id) except Exception as e: raise HgProcessError ("error while processing %s: %s" % (cset, e)) - if len(commit_diffstat) > 0: - commit_diffstat = 'Changes in files:\n[code]\n' + commit_diffstat + '\n[/code]' + if len(diffstat) > 0: + diffstat = 'Changes in files:\n[code]\n' + diffstat + '\n[/code]' else: - commit_diffstat = 'No changes in files.' + diffstat = 'No changes in files.' # Compare the email addresses against known developer usernames - commit_trackeruser = Config.find_developer_by_email (commit_email) + username = Config.find_developer_by_email (commit['email']) - if commit_trackeruser != '': - commit_author += ' [%s]' % commit_trackeruser + if username: + commit['author'] += ' [%s]' % username - commit_message = commit_message.replace ("\n", " ") + commit['message'] = commit['message'].replace ("\n", " ") message = 'Issue addressed by commit %s: [b][url=%s/commits/%s]%s[/url][/b]' \ - % (cset, repo_url, cset, commit_message) + % (cset, repo.url, cset, commit['message']) message += "\nCommitted by %s on %s\n\n%s" \ - % (commit_author, commit_date, commit_diffstat) + % (commit['author'], commit['date'], diffstat) need_update = False # If not already set, set handler - if not 'handler' in ticket_data: - ticket_data['handler'] = {'name': commit_trackeruser} + if username and not 'handler' in ticket_data: + ticket_data['handler'] = {'name': username} need_update = True # Find out the status level of the ticket @@ -271,7 +176,7 @@ if channel.get_value ('btannounce', default=True): irc_client.privmsg (channel.get_value ('name'), "\003%d%s\003: commit\0035 %s\003 addresses issue\002\0032 %d\002" % \ - (color_for_repo (reponame), reponame, cset, ticket_id)) + (repo.color, repo.name, cset, ticket_id)) irc_client.privmsg (channel.get_value ('name'), "Read all about it here: " + Bt.get_ticket_url (ticket_id)) @@ -285,159 +190,131 @@ def init(): global repocheck_timeout - global g_CommitsDb - for reponame in all_repo_names(): - check_repo_exists (reponame) + for name in Config.get_node ('hg').get_value ('repos', {}).keys(): + check_repo_exists (name) - g_CommitsDb = CommitsDb() - repocheck_timeout = time.time() + 15 + # Let the database check if commits.db needs to be built + HgCommitsDatabase() -def get_commit_data (reponame, rev, template): - return subprocess.check_output (['hg', '--cwd', reponame, 'log', '--limit', '1', '--rev', rev, '--template', template]) + repocheck_timeout = time.time() + 15 def poll(): global repocheck_timeout - if time.time() < repocheck_timeout: - return - - hgns = Config.get_node ('hg') - repocheck_timeout = time.time() + hgns.get_value ('checkinterval', default=15) * 60 - - for reponame in all_repo_names(): - poll_one_repo (reponame) - -def poll_one_repo (repo_name): - global repocheck_timeout - hgns = Config.get_node ('hg') - - if not hgns.get_value ('track', default=True): - return - - commit_data = [] - delimeter = '^^^^^^^^^^' - delimeter2 = '@@@@@@@@@@' - maxcommits = 15 - numcommits = 0 - print 'Checking %s for updates' % repo_name try: - data = subprocess.check_output (['hg', '--cwd', repo_name, 'incoming', - '--limit', str(maxcommits), '--quiet', '--template', - delimeter.join (['{node|short}', '{desc}']) + delimeter2]).split (delimeter2) - except subprocess.CalledProcessError: - return + if time.time() < repocheck_timeout: + return + + hgns = Config.get_node ('hg') + repocheck_timeout = time.time() + hgns.get_value ('checkinterval', default=15) * 60 + maxcommits = 15 + numcommits = 0 + + for repo in Repositories: + numcommits += poll_one_repo (repo, maxcommits=maxcommits - numcommits) + + if numcommits >= maxcommits: + # There may be more coming so recheck sooner + print ('Processed %d commits, checking for new commits in 1 minute...' % numcommits) + repocheck_timeout = time.time() + 60 + return except Exception as e: - Irc.broadcast (e.__class__.__name__ + ": " + str (e)) + print (traceback.format_exception (*sys.exc_info())) + Irc.broadcast (str (e)) + +def poll_one_repo (repo, maxcommits): + if not Config.get_node ('hg').get_value ('track', default=True): return - for line in data: - if line: - numcommits += 1 - commit_data.append (line.split (delimeter)) - - process_new_commits (repo_name, commit_data) - - if numcommits == maxcommits: - # If we have 25 commits here, there may be more coming so recheck sooner - global repocheck_timeout - print ('Processed %d commits, checking for new commits in 1 minute...' % len(data)) - repocheck_timeout = time.time() + 60 - -def process_new_commits (repo_name, commit_data): - if len (commit_data) == 0: - return + print ('Checking %s for updates' % repo.name) + commits = repo.incoming (maxcommits=maxcommits, node='node|short', message='desc') + process_new_commits (repo, commits) + return len(commits) - repo_name = repo_name.lower() - repo_url = get_repo_info (repo_name).get_value ('url') - isExtraRepo = get_repo_info (repo_name).get_value ('extrarepo', False) - print ('%d new commits on %s' % (len (commit_data), repo_name)) - pull_args = [] - messages = [[], [], []] - - for commit in commit_data: - pull_args.append ('-r'); - pull_args.append (commit[0]); - - print ('Pulling new commits...') - try: - subprocess.call (['hg', '--cwd', repo_name, 'pull'] + pull_args) - except Exception as e: - Irc.broadcast ('Warning: unable to pull: %s' % `e`) +def process_new_commits (repo, commits): + if not commits: return - LENGTH_MINIMUM, LENGTH_SHORT, LENGTH_FULL = range (0, 3) + print ('%d new commits on %s' % (len (commits), repo.name)) + pull_args = ['pull'] + messages = [[], [], []] + + for commit in commits: + pull_args.append ('-r'); + pull_args.append (commit['node']); - for commit in commit_data: - commit_node = commit[0] - commit_message = commit[1] - print ('Processing new commit %s...' % commit_node) + print ('Pulling new commits...') + repo.hg (*pull_args) + LENGTH_MINIMUM, LENGTH_SHORT, LENGTH_FULL = range (0, 3) + db = HgCommitsDatabase() - try: - existingrepos = g_CommitsDb.get_commit_repos (commit_node) - alreadyAdded = len (existingrepos) > 0 + for commit in commits: + print ('Processing new commit %s...' % commit['node']) - delim = '@@@@@@@@@@' - data = get_commit_data (repo_name, commit_node, delim.join (['{node}', '{author|person}', - '{bookmarks}', '{date|hgdate}', '{author|email}'])).split (delim) - commit_full_node = data[0] - commit_author = data[1] - commit_bookmarks = prettify_bookmarks (data[2]) - commit_time = int (data[3].split (' ')[0]) - commit_url = '%s/commits/%s' % (repo_url, commit_node) - commit_email = data[4] - isMergeFromSandbox = False + existingrepos = db.get_commit_repos (commit['node']) + alreadyAdded = len (existingrepos) > 0 - # If the commit was already in the commits database, it is not a new one and we should - # not react to it. Still add it to the db though so that the new repo name is added. - g_CommitsDb.add_commit (repo=repo_name, changeset=commit_full_node, timestamp=commit_time) + commit = dict (commit, **repo.get_commit_data (rev=commit['node'], + fullnode='node', + author='author|person', + bookmarks='bookmarks', + date='date|hgdate', + email='author|email')) + commit['bookmarks'] = prettify_bookmarks (commit['bookmarks']) + commit['time'] = int (commit['date'].split (' ')[0]) + commit['url'] = '%s/commits/%s' % (repo.url, commit['node']) + isMergeFromSandbox = False - if alreadyAdded: - if not contains_published_repositories (existingrepos) and is_published (repo_name): - isMergeFromSandbox = True - print ('''%s appears to be a merge from sandbox (exists in %s)''' % - (commit_node, existingrepos)) - else: - print ('''I already know of %s - they're in %s - not announcing.''' % - (commit_node, existingrepos)) - continue + # If the commit was already in the commits database, it is not a new one and we should + # not react to it (unless a merge from sandbox). Still add it to the db though so that + # the new repo name is added. + db.add_commit (repo=repo, changeset=commit['fullnode'], + timestamp=commit['time']) - commit_trackeruser = Config.find_developer_by_email (commit_email) - committer = commit_trackeruser if commit_trackeruser else commit_author - commitDescriptor = """commit""" if int (random.random() * 100) != 0 else """KERMIT""" - - if not isMergeFromSandbox: - commitMessage = """\003%d%s\003: new %s\0035 %s%s\003 by\0032 %s\003: %s""" % \ - (color_for_repo (repo_name), repo_name, commitDescriptor, commit_node, commit_bookmarks, - committer, utility.shorten_link (commit_url)) - - for length in [LENGTH_MINIMUM, LENGTH_SHORT, LENGTH_FULL]: - messages[length].append (commitMessage) - - messages[LENGTH_SHORT].append (' ' + commit_message.splitlines()[0]) - - for line in commit_message.splitlines()[0:4]: - messages[LENGTH_FULL].append (' ' + line) + if alreadyAdded: + if not contains_published_repositories (existingrepos) and repo.published: + isMergeFromSandbox = True + print ('''%s appears to be a merge from sandbox (exists in %s)''' % + (commit['node'], existingrepos)) else: - commitMessage = """\003%d%s\003: %s\0035 %s%s\003 by\0032 %s\003 was pulled: %s""" % \ - (color_for_repo (repo_name), repo_name, commitDescriptor, commit_node, commit_bookmarks, - committer, utility.shorten_link (commit_url)) + print ('''I already know of %s - they're in %s - not announcing.''' % + (commit['node'], existingrepos)) + continue + + username = Config.find_developer_by_email (commit['email']) + committer = username if username else commit['author'] + descriptor = """commit""" if int (random.random() * 100) != 0 else """KERMIT""" - for length in [LENGTH_MINIMUM, LENGTH_SHORT, LENGTH_FULL]: - messages[length].append (commitMessage) + if not isMergeFromSandbox: + commitMessage = """\003%d%s\003: new %s\0035 %s%s\003 by\0032 %s\003: %s""" % \ + (repo.color, repo.name, descriptor, commit['node'], commit['bookmarks'], + committer, utility.shorten_link (commit['url'])) - if not isExtraRepo: - rex = re.compile (r'^.*(fixes|resolves|addresses|should fix) ([0-9]+).*$') + for length in [LENGTH_MINIMUM, LENGTH_SHORT, LENGTH_FULL]: + messages[length].append (commitMessage) + + messages[LENGTH_SHORT].append (' ' + commit['message'].splitlines()[0]) - for line in commit_message.splitlines(): - match = rex.match (line) + for line in commit['message'].splitlines()[:4]: + messages[LENGTH_FULL].append (' ' + line) + else: + commitMessage = """\003%d%s\003: %s\0035 %s\003 by\0032 %s\003 was pulled: %s""" % \ + (repo.color, repo.name, descriptor, commit['node'], committer, + utility.shorten_link (commit['url'])) + + for length in [LENGTH_MINIMUM, LENGTH_SHORT, LENGTH_FULL]: + messages[length].append (commitMessage) - if match: - announce_ticket_resolved (match.group (2), commit_node) - break - except Exception as e: - Irc.broadcast ('Error while processing %s: %s' % (commit_node, e)) - continue + if repo.published: + rex = re.compile (r'^.*(fixes|resolves|addresses|should fix) ([0-9]+).*$') + + for line in commit['message'].splitlines(): + match = rex.match (line) + + if match: + announce_ticket_resolved (match.group (2), commit['node'], db) + break # Encode messages for messagelist in messages: @@ -460,19 +337,12 @@ if not channel.get_value ('btannounce', False): continue - if isExtraRepo and not channel.get_value ('allpublishing', False): + if not repo.published and not channel.get_value ('allpublishing', False): continue irc_client.privmsg (channel.get_value ('name'), message) - g_CommitsDb.commit() - -def make_progress_bar (p, barLength, colored=True): - BoldChar, ColorChar = (Irc.BoldChar, Irc.ColorChar) - return BoldChar + '[' + BoldChar \ - + ColorChar + '2,2' + ('=' * int (round (p * barLength))) \ - + ColorChar + '1,1' + ('-' * int (barLength - round (p * barLength))) \ - + ColorChar + BoldChar + ']' + BoldChar + db.commit() def force_poll(): global repocheck_timeout
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgrepo.py Tue Aug 04 22:39:22 2015 +0300 @@ -0,0 +1,109 @@ +''' + Copyright 2015 Teemu Piippo + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. 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. + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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 subprocess +import sys +import os +from configfile import Config + +class HgRepository (object): + def __init__ (self, reponame): + reponame = reponame.lower() + repoconfig = Config.get_node ('hg').get_node ('repos') + repoinfo = repoconfig.get_node (reponame) + + if not repoinfo: + raise ValueError ('Unknown repository "%s"' % reponame) + + self.name = reponame + self.published = not bool (repoinfo.get_value ('extrarepo', default=False)) + self.url = repoinfo.get_value ('url') + self.color = int (repoinfo.get_value ('colorcode', default=0)) + + if not self.url: + raise ValueError ('Repository %s has no url!' % reponame) + + def hg (self, *args): + output = subprocess.check_output (['hg', '--cwd', self.name] + list (args)) + + if sys.version_info >= (3, 0): + output = output.decode ('utf-8', 'ignore') + + return output + + def is_valid (self): + return os.path.isdir (os.path.join (self.name, '.hg')) + + def split_template (self, kvargs, separator): + return separator.join (['{%s}' % x[1] for x in kvargs.items()]) + + def merge_template (self, data, args): + result = {} + items = list (args.items()) + assert (len (items) == len (data)), '''Bad amount of items from hg log''' + + for i in range (0, len (items)): + result[items[i][0]] = data[i] + + return result + + def get_commit_data (self, rev, **kvargs): + if not kvargs: + raise ValueError ('template arguments must be provided') + + separator = '^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^' + template = self.split_template (kvargs, separator) + data = self.hg ('log', '--limit', '1', '--rev', rev, '--template', template) + data = data.split (separator) + return self.merge_template (data, kvargs) + + def incoming (self, maxcommits=0, **kvargs): + if not kvargs: + raise ValueError ('template arguments must be provided') + + commit_data = [] + separator = '^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^' + separator2 = '@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@' + template = self.split_template (kvargs, separator) + limit = ['--limit', str(maxcommits)] if maxcommits else [] + + try: + args = ['incoming'] + limit + ['--quiet', '--template', template + separator2] + data = self.hg (*args) + data = data.split (separator2) + + if not data[-1]: + data = data[:-1] + + return [self.merge_template (data=x.split (separator), args=kvargs) for x in data] + except subprocess.CalledProcessError: + return [] + + def clone(): + print ('Cloning %s...' % repo.name) + subprocess.call (['hg', 'clone', repo.url, repo.name]) \ No newline at end of file
--- a/irc.py Mon Aug 03 19:45:57 2015 +0300 +++ b/irc.py Tue Aug 04 22:39:22 2015 +0300 @@ -26,6 +26,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ''' +from __future__ import print_function import asyncore import socket import sys @@ -60,7 +61,8 @@ # Prints a line to log channel(s) # def broadcast (line): - print line + print (line) + for client in all_clients: if not client.flags & CLIF_CONNECTED: continue @@ -104,7 +106,8 @@ all_clients.append (self) asyncore.dispatcher.__init__ (self) self.create_socket (socket.AF_INET, socket.SOCK_STREAM) - print "[%s] Connecting to [%s] %s:%d..." % (get_timestamp(), self.name, self.host, self.port) + print ("[%s] Connecting to [%s] %s:%d..." % \ + (get_timestamp(), self.name, self.host, self.port)) self.connect ((self.host, self.port)) ClientsByName[self.name] = self @@ -116,17 +119,21 @@ self.write ("NICK %s" % self.mynick) def handle_connect (self): - print "[%s] Connected to [%s] %s:%d" % (get_timestamp(), self.name, self.host, self.port) + print ("[%s] Connected to [%s] %s:%d" % (get_timestamp(), self.name, self.host, self.port)) self.register_to_irc() def write (self, utfdata): try: - self.send_buffer.append ("%s" % utfdata.decode("utf-8","ignore").encode("ascii","ignore")) + if sys.version_info < (3, 0): + self.send_buffer.append (utfdata.decode("utf-8","ignore").encode("ascii","ignore")) + else: + self.send_buffer.append (utfdata) except UnicodeEncodeError: pass def handle_close (self): - print "[%s] Connection to [%s] %s:%d terminated." % (get_timestamp(), self.name, self.host, self.port) + print ("[%s] Connection to [%s] %s:%d terminated." % + (get_timestamp(), self.name, self.host, self.port)) self.close() def handle_write (self): @@ -141,24 +148,31 @@ def send_all_now (self): for line in self.send_buffer: if self.verbose: - print "[%s] [%s] <- %s" % (get_timestamp(), self.name, line) - self.send ("%s\n" % line) + print ("[%s] [%s] <- %s" % (get_timestamp(), self.name, line)) + + if sys.version_info >= (3, 0): + self.send (bytes (line + '\n', 'UTF-8')) + else: + self.send (line + '\n') time.sleep (0.25) self.send_buffer = [] def handle_read (self): lines = self.recv (4096).splitlines() - for utfline in lines: + for line in lines: try: - line = utfline.decode("utf-8","ignore").encode("ascii","ignore") + line = line.decode ('utf-8', 'ignore') + + if sys.version_info < (3, 0): + line = line.encode ('ascii', 'ignore') except UnicodeDecodeError: continue if self.verbose: - print "[%s] [%s] -> %s" % (get_timestamp(), self.name, line) + print ("[%s] [%s] -> %s" % (get_timestamp(), self.name, line)) - if line.startswith ("PING :"): - self.write ("PONG :%s" % line[6:]) + if line[:len('PING :')] == ('PING :'): + self.write ("PONG :%s" % line[len('PING :'):]) self.send_all_now() # pings must be responded to immediately! else: words = line.split(" ") @@ -172,7 +186,9 @@ self.write ('MODE %s %s' % (self.mynick, self.cfg.get_value ('umode', ''))) for channel in self.channels: - self.write ("JOIN %s %s" % (channel.get_value ('name'), channel.get_value ('password', default=''))) + self.write ("JOIN %s %s" % + (channel.get_value ('name'), + channel.get_value ('password', default=''))) elif words[1] == "PRIVMSG": self.handle_privmsg (line) elif words[1] == 'QUIT': @@ -271,7 +287,7 @@ return def handle_error(self): - raise RestartError (traceback.format_exception(sys.exc_type, sys.exc_value, sys.exc_traceback)) + raise RestartError (traceback.format_exception (*sys.exc_info())) def restart(self): raise RestartError('')
--- a/mod_admin.py Mon Aug 03 19:45:57 2015 +0300 +++ b/mod_admin.py Tue Aug 04 22:39:22 2015 +0300 @@ -1,3 +1,4 @@ +from __future__ import print_function from modulecore import command_error import sys
--- a/mod_bridge.py Mon Aug 03 19:45:57 2015 +0300 +++ b/mod_bridge.py Tue Aug 04 22:39:22 2015 +0300 @@ -26,6 +26,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ''' +from __future__ import print_function import re from configfile import Config import irc as Irc
--- a/mod_bt.py Mon Aug 03 19:45:57 2015 +0300 +++ b/mod_bt.py Tue Aug 04 22:39:22 2015 +0300 @@ -26,7 +26,8 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ''' -import bt as Bt +from __future__ import print_function +import bt ModuleData = { 'commands': @@ -62,16 +63,16 @@ } def cmd_ticket (bot, args, replyto, **rest): - Bt.get_ticket_data (bot, replyto, args['ticket'], True) + bt.get_ticket_data (bot, replyto, args['ticket'], True) def cmd_testannounce (bot, args, **rest): - Bt.announce_new_issue (bot, Bt.get_issue (args['ticket'])) + bt.announce_new_issue (bot, bt.get_issue (args['ticket'])) def cmd_checkbt (bot, **rest): - Bt.poll() + bt.poll() def cmd_btsoap (bot, args, reply, **rest): - result = Bt.custom_query (args['func'], args['args'].split (' ') if 'args' in args else []) + result = bt.custom_query (args['func'], args['args'].split (' ') if 'args' in args else []) for line in result.splitlines(): reply (line) \ No newline at end of file
--- a/mod_config.py Mon Aug 03 19:45:57 2015 +0300 +++ b/mod_config.py Tue Aug 04 22:39:22 2015 +0300 @@ -26,6 +26,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ''' +from __future__ import print_function from modulecore import command_error from configfile import Config
--- a/mod_hg.py Mon Aug 03 19:45:57 2015 +0300 +++ b/mod_hg.py Tue Aug 04 22:39:22 2015 +0300 @@ -26,10 +26,10 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ''' +from __future__ import print_function from datetime import datetime import hgpoll as HgPoll import re -import bt as Bt import subprocess from configfile import Config from modulecore import command_error
--- a/mod_idgames.py Mon Aug 03 19:45:57 2015 +0300 +++ b/mod_idgames.py Tue Aug 04 22:39:22 2015 +0300 @@ -26,10 +26,11 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ''' +from __future__ import print_function from modulecore import command_error import urllib -import urllib2 import json +import utility ModuleData = { 'commands': @@ -48,7 +49,7 @@ def cmd_idgames (bot, args, reply, **rest): try: url = g_idgamesSearchURL % urllib.quote (args['wad']) - response = urllib2.urlopen (url).read() + response = utility.read_url (url) data = json.loads (response) if 'content' in data and 'file' in data['content']:
--- a/mod_util.py Mon Aug 03 19:45:57 2015 +0300 +++ b/mod_util.py Tue Aug 04 22:39:22 2015 +0300 @@ -26,8 +26,8 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ''' +from __future__ import print_function import math -import urllib2 import json import subprocess import re @@ -147,7 +147,7 @@ def cmd_ud (bot, args, reply, **rest): try: url = 'http://api.urbandictionary.com/v0/define?term=%s' % (args['term'].replace (' ', '%20')) - response = urllib2.urlopen (url).read() + response = utility.read_url (url) data = json.loads (response) if not 'list' in data \ @@ -198,7 +198,7 @@ def cmd_calcfunctions (bot, reply, **rest): reply ('Available functions for .calc: %s' % \ - ', '.join (sorted ([name for name, data in calc.Functions.iteritems()]))) + ', '.join (sorted ([name for name, data in calc.Functions.items()]))) def cmd_calc (bot, reply, args, **rest): reply (calc.Calculator().calc (args['expression']))
--- a/modulecore.py Mon Aug 03 19:45:57 2015 +0300 +++ b/modulecore.py Tue Aug 04 22:39:22 2015 +0300 @@ -26,6 +26,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ''' +from __future__ import print_function import os import re import time @@ -82,7 +83,7 @@ cmd['argnames'].append (argname) if 'hooks' in module.ModuleData: - for key, hooks in module.ModuleData['hooks'].iteritems(): + for key, hooks in module.ModuleData['hooks'].items(): for hook in hooks: if key not in Hooks: Hooks[key] = [] @@ -90,9 +91,9 @@ Hooks[key].append ({'name': hook, 'func': getattr (module, hook)}) numHooks += 1 - print "Loaded module %s" % fn + print ('''Loaded module %s''' % fn) - print ('Loaded %d commands and %d hooks in %d modules' % + print ('''Loaded %d commands and %d hooks in %d modules''' % (len (Commands), numHooks, len (Modules))) # @@ -184,7 +185,7 @@ try: func = getattr (commandObject['module'], 'cmd_' + cmdname) except AttributeError: - command_error ('command "%s" is not defined!' % cmdname) + command_error ('''command '%s' does not have a definition''' % cmdname) try: func (**commandObject) @@ -211,16 +212,17 @@ try: cmd = Commands[cmdname] except KeyError: + print ('''No such command %s''' % cmdname) return - if not is_available (cmd, kvargs['ident'], kvargs['host']): - command_error ("you may not use %s" % cmdname) + if not is_available (cmd=cmd, ident=kvargs['ident'], host=kvargs['host']): + command_error ('''you may not use %s''' % cmdname) match = re.compile (cmd['regex']).match (message) if match == None: # didn't match - command_error ('invalid arguments\nusage: %s %s' % (cmd['name'], cmd['args'])) + command_error ('''usage: %s %s''' % (cmd['name'], cmd['args'])) # .more is special as it is an interface to the page system. # Anything else resets it. @@ -235,7 +237,7 @@ args[argname] = match.group (i) i += 1 - print "ModuleCore: %s called by %s" % (cmdname, kvargs['sender']) + print ('''%s was called by %s''' % (cmdname, kvargs['sender'])) commandObject = kvargs commandObject['bot'] = bot commandObject['cmdname'] = cmdname @@ -295,7 +297,7 @@ def get_available_commands (ident, host): result=[] - for cmdname,cmd in Commands.iteritems(): + for cmdname,cmd in Commands.items(): if not is_available (cmd, ident, host): continue @@ -329,11 +331,11 @@ for arg in arglist.split (' '): if gotvariadic: - raise CommandError ('variadic argument is not last') + raise CommandError ('''variadic argument is not last''') if arg == '': continue - + gotliteral = False if arg[0] == '[' and arg[-1] == ']': @@ -341,7 +343,7 @@ gotoptional = True elif arg[0] == '<' and arg[-1] == '>': if gotoptional: - raise CommandError ('mandatory argument after optional one') + raise CommandError ('''mandatory argument after optional one''') arg = arg[1:-1] else: @@ -349,7 +351,7 @@ if arg[-3:] == '...': gotvariadic = True - arg = arg[0:-3] + arg = arg[:-3] if gotoptional == False: regex += r'\s+' @@ -368,11 +370,8 @@ regex += r'(.+)' else: regex += r'([^ ]+)' - #fi - #done if not gotvariadic: regex += r'\s*' return '^[^ ]+%s$' % regex -#enddef
--- a/rest.py Mon Aug 03 19:45:57 2015 +0300 +++ b/rest.py Tue Aug 04 22:39:22 2015 +0300 @@ -26,6 +26,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ''' +from __future__ import print_function import ssl import socket import errno @@ -44,15 +45,17 @@ g_portnumber = None g_throttle = [] +# TODO: get rid of this array valid_repos = ['torr_samaho/zandronum', 'torr_samaho/zandronum-stable', - 'crimsondusk/zandronum-sandbox', 'crimsondusk/zandronum-sandbox-stable', 'crimsondusk/zfc9000', 'blzut3/doomseeker'] + 'crimsondusk/zandronum-sandbox', 'crimsondusk/zandronum-sandbox-stable', + 'crimsondusk/zfc9000', 'blzut3/doomseeker'] def is_throttled (address): i = 0 while i < len (g_throttle): if g_throttle[i][1] <= datetime.utcnow(): - print 'Throttle of %s expired' % g_throttle[i][0] + print ('Throttle of %s expired' % g_throttle[i][0]) item = g_throttle.pop (i) # expired if item[0] == address: @@ -154,7 +157,7 @@ try: self.socket.do_handshake() break - except ssl.SSLError, err: + except ssl.SSLError as err: if err.args[0] == ssl.SSL_ERROR_WANT_READ: select.select([self.socket], [], []) elif err.args[0] == ssl.SSL_ERROR_WANT_WRITE: @@ -211,7 +214,7 @@ self.setsockopt (socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.bind (('', g_portnumber)) self.listen (5) - print 'REST server initialized' + print ('REST server initialized') def handle_accept (self): sock, address = self.accept()
--- a/utility.py Mon Aug 03 19:45:57 2015 +0300 +++ b/utility.py Tue Aug 04 22:39:22 2015 +0300 @@ -26,18 +26,43 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ''' -import bitly_api +from __future__ import print_function +import sys import irc as Irc from configfile import Config +try: + import bitly_api +except ImportError as e: + print ('Unable to import bitly_api: %s' % e) + +try: + import urllib.request +except ImportError: + import urllib2 + def shorten_link (link): - bitly_token = Config.get_node ('bitly').get_value ('access_token', '') + if 'bitly_api' in sys.modules: + bitly_token = Config.get_node ('bitly').get_value ('access_token', '') - if bitly_token: - c = bitly_api.Connection (access_token = bitly_token) - try: - return c.shorten (link)['url'] - except Exception as e: - Irc.broadcast ('Error while shortening link "%s": %s' % (link, e)) + if bitly_token: + c = bitly_api.Connection (access_token = bitly_token) + try: + return c.shorten (link)['url'] + except Exception as e: + Irc.broadcast ('Error while shortening link "%s": %s' % (link, e)) + + return link - return link \ No newline at end of file +def read_url (url): + if 'urllib.request' in sys.modules: + return urllib.request.urlopen (urllib.request.Request (url)).read() + else: + return urllib2.urlopen (url).read() + +def make_progress_bar (p, barLength, colored=True): + BoldChar, ColorChar = (Irc.BoldChar, Irc.ColorChar) + return BoldChar + '[' + BoldChar \ + + ColorChar + '2,2' + ('=' * int (round (p * barLength))) \ + + ColorChar + '1,1' + ('-' * int (barLength - round (p * barLength))) \ + + ColorChar + BoldChar + ']' + BoldChar \ No newline at end of file