All pastes #759506 Raw Edit

silan-0.0.1 development 01

public python v1 · immutable
·published 2007-11-03 05:17 UTC
rendered paste body
#!/usr/bin/python -tt## SILAN# Simple IRC Log Analyzer# http://slackforge.com/silan/## Version 0.0.1# An extremely simple IRC log analyzer --;# Only supports dircproxy logs, because that's what I use## Copyright (c) 2007 Shidayu <shidayu@gmail.com>## 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.### MODULE IMPORTS#import osimport reimport statimport sysimport globimport datetime## CONSTANTS## Application information_APP_VERSION = '0.0.1'# Regular expressions_RE_TIMESTAMP = '\[(\d\d) ([A-Z][a-z][a-z]) (\d\d):(\d\d)\]'_RE_LINESPLIT = '^' + _RE_TIMESTAMP + ' ([^\n\r\f\v]*)$'	# Group up log line_RE_SELFNICK = '--- You changed your nickname to (\S*)'_RE_NICKCHANGE = '--- (\S*) \(\S*\) changed nickname to (\S*)'_RE_QUIT = '<-- (\S*) \(\S*\) quit from IRC: ([^\t\n\r\f\v]*)$'_RE_JOIN = '--> (\S*) \([^\t\n\r\f\v)]*\) joined the channel$'_RE_PART = '<-- (\S*) \([^\t\n\r\f\v)]*\) left the channel$'_RE_MESSAGE = '<([^ \t\n\r\f\v!]*)![^\t\n\r\f\v>]*> ([^\n\r\f\v]*)$'_RE_DESCRIBE = '\* (\S*) ([^\n\r\f\v]*)'_RE_CHANNEL = '^(\S*).log$'		# Parse name of channel from filename of log# File pattern matching (for glob)_GLOB_SERVERLOG = 'Server.log'_GLOB_CHANLOG = '#*.log'# Months to translate from dircproxy's format_MONTHS = {	'Jan': 1,	'Feb': 2,	'Mar': 3,	'Apr': 4,	'May': 5,	'Jun': 6,	'Jul': 7,	'Aug': 8,	'Sep': 9,	'Oct': 10,	'Nov': 11,	'Dec': 12 }## DISPLAY PROGRAM INFORMATION#print '\n\nSILAN', _APP_VERSION, '- Simple IRC Log Analyzer - http://slackforge.com/silan/'print 'Copyright (c) 2007 Shidayu <shidayu@gmail.com>'## CLASSES## UserChannelActivity: Encapsulate activity for one user in one channelclass UserChannelActivity:	"""Activity data for one user in one channel"""		def __init__(self):		"""Default constructor"""		self.user = None		self.channel = None		self.messages = []		self.describes = []		self.topics = []	def __init__(self, user, channel):		"""Constructor with optional user and channel instances provided"""		self.user = user		self.channel = channel		self.messages = []		self.describes = []		self.topics = []		# User: Track IRC activity for a particular userclass User:	"""IRC activity for a particular user"""	def __init__(self):		"""Default constructor"""		self.nick_primary = None		self.nick_alternates = []		self.channels = {}		self.quit_messages = []		self.words = {}	def __init__(self, primary):		"""Constructor with optional primary nickname specified"""		self.nick_primary = primary		self.nick_alternates = []		self.channels = {}		self.quit_messages = []		self.words = {}	def is_known_as(self, alternate):		"""Return 1 if this IRCUser could be known as the provided argument"""		if alternate == self.nick_primary or alternate in self.nick_alternates:			return 1		else:			return 0# Channel: Track IRC activity for a particular channelclass Channel:	"""IRC activity for a particular channel"""	def __init__(self):		"""Default constructor"""		self.name = None		self.log = None		self.users = {}		self.topics = []	def __init__(self, args):		"""Constructor with optional arguments specified		A Channel can be instantiated with a string argument representing the		channel's name, or with a tuple argument in the format of		(channelname, logfilename)				"""		if isinstance(args, tuple):			self.name = args[0]			self.log = Log(args[1])		else:			self.name = args[0]			self.log = None				self.users = {}		self.topics = []# StampedStatement: Topics, describes, and messages fall under this categoryclass StampedStatement:	"""A timestamped bit of information such as a topic, describe, and message"""	def __init__(self):		"""Default constructor"""		self.dttm = None		self.text = None	def __init__(self, dttm, text):		"""Constructor with optional date/time and text provided"""		self.dttm = dttm		self.text = text# HistoricalTopic: StampedStatment specifically for storing history of channel topicsclass HistoricalTopic(StampedStatement):	"""Extended StampedStatement including user instance for topic history storage"""	def __init__(self):		"""Default constructor, calls parent constructor"""		StampedStatement.__init__()		self.user = None	def __init__(self, dttm, user, text):		"""Constructor with optional date/time, user instance, and text provided"""		StampedStatement.__init__(user, text)		self.user = user# Log: About an IRC logclass Log:	"""IRC log file information and contents"""	def __init__(self, filename):		"""Constructor, filename of log must be passed as an argument		The log file will immediately be read into self.contents			"""		self.filename = filename		self.dttm_start = self._find_start()		self.lines = self._prepare_log()	def _read_log(self):		"""Simply reads in a text file as a list and returns it"""		loghandler = open(self.filename, 'r')		loglist = loghandler.readlines()		loghandler.close()		return loglist			def _first_line(self):		"""Read in the first line of the log and return it"""		loghandler = open(self.filename, 'r')		firstline = loghandler.readline()		# readline is SINGULAR!		loghandler.close()				return firstline	def _find_start(self):		"""Determines the time the log started by combining the date/time the 		file was created and the date/time of the first line in the log				"""		logstat = os.stat(self.filename)		logctime = logstat.st_ctime		logcdttm = datetime.datetime.fromtimestamp(logctime)		logyear = logcdttm.year		firststamp = re.compile('^' + _RE_TIMESTAMP + ' .*$').match(				self._first_line())		# See CONSTANTS above for what's in _RE_TIMESTAMP		firststampday = int(firststamp.group(1))		firststampmonth = int(month_convert(firststamp.group(2)))	# Turns 'Oct' into '10'		firststamphour = int(firststamp.group(3))		firststampminute = int(firststamp.group(4))		return datetime.datetime(logyear, firststampmonth, firststampday, firststamphour, firststampminute)			def _prepare_log(self):		"""Loops through all lines in the log, splits the timestamp out, and		returns a new list of StampedStatement()s with the line's text and		a 'nice' date/time timestamp				"""		original = self._read_log()		stampedlist = []		dttm_last = self.dttm_start		splitregex = re.compile(_RE_LINESPLIT)	# See CONSTANTS above for what's in _RE_LINESPLIT		for line in original:			splitmatch = splitregex.match(line)						if not splitmatch:				sys.exit('\nError: Could not find timestamp in log entry. Are timestamps properly enabled?\n')			lineday = int(splitmatch.group(1))			linemonth = int(month_convert(splitmatch.group(2)))			linehour = int(splitmatch.group(3))			lineminute = int(splitmatch.group(4))			linetext = splitmatch.group(5)			#			# Takes a fairly good guess as to whether a year has advanced in			# the log. This may not always be accurate if, say, an entire year			# to-the-minute passes since the last item inserted into the log.			#			if linemonth < dttm_last.month or (linemonth == dttm_last.month and (lineday < dttm_last.day or (lineday == dttm_last.day and (linehour < dttm_last.hour or (linehour == dttm_last.hour and lineminute < dttm_last.minute))))):						# The preceding: Does it look like we've gone back in time?				dttm_last = datetime.datetime(dttm_last.year + 1, linemonth, lineday, linehour, lineminute)			else:				dttm_last = datetime.datetime(dttm_last.year, linemonth, lineday, linehour, lineminute)			stampedlist.append(StampedStatement(dttm_last, linetext))		return stampedlist## FUNCTIONS#def conjure_user(allusers, nick):	"""Return user instance a nickname belongs to. If none, one is created."""		existinguser = locate_nick(allusers, nick)	if not existinguser:		newuser = User(nick)		allusers[nick] = newuser		return newuser	else:		return existinguserdef locate_nick(allusers, nick):	"""Attempts to locate a nickname and return its User instance. If not there, None."""	if nick in allusers:		return allusers[nick]	else:		for user in allusers:			if allusers[user].nick_primary == nick or nick in allusers[user].nick_alternates:				return allusers[user]	return None	def month_convert(month):	"""Takes in three-letter month from dircproxy logs and returns two-digit number."""		return _MONTHS[month]		def find_channels():	"""Searches current working directory for all channel logs and returns 	a Dictionary of Channel()s based on this.		"""		chanlogs = glob.glob(_GLOB_CHANLOG)		# See CONSTANTS above for what's in _GLOB_CHANLOG		# Remove logs which are not regular files	for filename in chanlogs:		filestat = os.stat(filename)		filemode = filestat.st_mode		if not stat.S_ISREG(filemode):			chanlogs.remove(filename)				# Return None if there are... none	if len(chanlogs) < 1:		return None	else:		chandict = {}		chanregex = re.compile(_RE_CHANNEL)				for filename in chanlogs:			channame = chanregex.match(filename).group(1)						chandict[channame] = Channel((channame, filename))					return chandict## INITIALIZATION########### The below is for testing, wahahaha!########## Right now, some classes and functions aren't being tested and used########## That will come later :Dchannels = find_channels()for line in channels['#C&T'].log.lines:	print line.dttm, line.text