# # format.py # # This file contains global functions for formatting and printing data. This # file should be imported into individual log-parsing scripts located in # logs/*. Data is all formatted in HTML. Writing to disk and/or emailng data # is left to __main__.py. # import os import re import locale from string import Template import logparse from . import interface, util, config import logging logger = logging.getLogger(__name__) locale.setlocale(locale.LC_ALL, '') # inherit system locale #DEG = "°".encode('unicode_escape') DEG = u'\N{DEGREE SIGN}' CEL = "C" TIMEFMT = "%X" DATEFMT = "%x" CORNERCHARS_DOUBLE = ['╚', '╝', '╗', '╔'] CORNERCHARS_SINGLE = ['└', '┘', '┐', '┌'] LINECHARS_DOUBLE = ['║', '═'] LINECHARS_SINGLE = ['│', '─'] class Output: def __init__(self): self.content = "" self.destination = "" def append(self, content): self.content += content def write(self, destination=""): if destination == "": destination = self.destination if destination == "": logger.warning("No destination path provided") return 1 with open(destination, 'w') as f: f.write(self.content) logger.info("Written output to {}".format(destination)) class PlaintextOutput(Output): def __init__(self, linewidth=80): self.content = "" self.destination = "" self.linewidth = linewidth; def append_header(self, template=''): init_varfilter() box = PlaintextBox(content=Template("$title $version on $hostname\n\n$time $date").safe_substitute(varsubst), vpadding=2, hpadding="\t\t", linewidth=config.prefs['linewidth']) line = PlaintextLine(self.linewidth) self.append(box.draw() + line.draw()) def append_footer(self): init_varfilter() self.append(PlaintextLine(self.linewidth, vpadding=1).draw()) self.append(Template("$hostname $time $date").safe_substitute(varsubst)) def append_section(self, section): self.append(PlaintextBox(content=section.title, double=False, fullwidth=False, vpadding=0, hpadding=" ").draw()) self.append('\n'*2) for data in section.data: self.append(self._fmt_data(data.subtitle, data.items)) self.append('\n') def _fmt_data(self, subtitle, data = None): # write title and data if (subtitle == ""): logger.warning("No subtitle provided.. skipping section") return if (data == None or len(data) == 0): logger.debug("No data provided.. just printing subtitle") return subtitle + '\n' else: logger.debug("Received data " + str(data)) subtitle += ':' if (len(data) == 1): return subtitle + ' ' + data[0] + '\n' else: itemoutput = subtitle + '\n' for datum in data: datum = '• ' + datum if len(datum) > config.prefs['linewidth']: words = datum.split() if max(map(len, words)) > config.prefs['linewidth']: raise ValueError("Content width is too small") res, part, others = [], words[0], words[1:] for word in others: if len(' ') + len(word) > config.prefs['linewidth'] - len(part): res.append(part) part = word else: part += ' ' + word if part: res.append(part) datum = '\n'.join(res) itemoutput += datum + '\n' return itemoutput class HtmlOutput(Output): def __init__(self): self.content = "" self.destination = "" self.css = "" def embed_css(self, css): self.content = mail.mailprep(self.content, css) def append_header(self, template): # insert variables into header template file init_varfilter() headercontent = Template(open(template, 'r').read()) self.append(headercontent.safe_substitute(varsubst)) self.append(opentag('div', id='main')) def append_footer(self): self.append(closetag('div') + closetag('body') + closetag('html')) def append_section(self, section): self.append(opentag('div', 1, section.title, 'section')) self.append(self._gen_title(section.title)) for data in section.data: self.append(self._fmt_data(data.subtitle, data.items)) self.append(closetag('div', 1)) def _gen_title(self, title): # write title for a section if (title == '' or '\n' in title): logger.error("Invalid title") raise ValueError logger.debug("Writing title for " + title) return tag('h2', 0, title) def _fmt_data(self, subtitle, data = None): # write title and data if (subtitle == ""): logger.warning("No subtitle provided.. skipping section") return if (data == None or len(data) == 0): logger.debug("No data provided.. just printing subtitle") return tag('p', 0, subtitle) else: logger.debug("Received data " + str(data)) subtitle += ':' if (len(data) == 1): return tag('p', 0, subtitle + ' ' + data[0]) else: output = "" output += tag('p', 0, subtitle) output += opentag('ul', 1) coderegex = re.compile('`(.*)`') for datum in data: if datum == "" or datum == None: continue datum = coderegex.sub(r'{\1}', str(datum)) output += tag('li', 0, datum) output += closetag('ul', 1) return output class Section: def __init__(self, title): self.title = title self.data = [] def add_data(self, data): self.data.append(data) class Data: def __init__(self, subtitle, items=None): self.subtitle = subtitle self.items = items class PlaintextLine: def __init__(self, linewidth=80, double=True, vpadding=1, hpadding=""): self.linewidth = linewidth self.double = False self.vpadding = vpadding self.hpadding = hpadding def draw(self): line = (LINECHARS_DOUBLE[1] if self.double else LINECHARS_SINGLE[1]) return "\n" * self.vpadding + self.hpadding + line * (self.linewidth - 2 * len(self.hpadding)) + self.hpadding + "\n" * self.vpadding class PlaintextBox: def __init__(self, content="", double=True, fullwidth=True, linewidth=80, hpadding="\t", vpadding=1): self.content = content self.fullwidth = fullwidth self.linewidth = linewidth self.hpadding = hpadding self.vpadding = vpadding self.double = double def draw(self): if self.double == True: cornerchars = CORNERCHARS_DOUBLE linechars = LINECHARS_DOUBLE else: cornerchars = CORNERCHARS_SINGLE linechars = LINECHARS_SINGLE # Check hpadding has a definite width self.hpadding = self.hpadding.replace("\t", " "*4) # Calculate number of characters per line contentlines = self.content.splitlines() contentwidth = int((self.linewidth if self.linewidth > 0 else 80) if self.content.splitlines() else len(max(contentlines, key=len))) logger.debug("Contentwidth is {0}".format(str(contentwidth))) logger.debug("Longest line is {0}".format(len(max(contentlines, key=len)))) contentwidth += -2*(len(self.hpadding)+1) if not self.fullwidth: longestline = len(max(contentlines, key=len)) if longestline <= self.linewidth - 2*(len(self.hpadding)+1): contentwidth = longestline # Split lines that are too long for i, line in enumerate(contentlines): if len(line) > contentwidth: words = line.split() if max(map(len, words)) > contentwidth: raise ValueError("Content width is too small") res, part, others = [], words[0], words[1:] for word in others: if len(' ') + len(word) > contentwidth - len(part): res.append(part) part = word else: part += ' ' + word if part: res.append(part) contentlines[i] = res # Flatten list # Note list comprehension doesn't work here, so we must iterate through each item newlines = [] for line in contentlines: if isinstance(line, list): for subline in line: newlines.append(subline) else: newlines.append(line) contentlines = newlines # Add vertical padding for _ in range(self.vpadding): contentlines.insert(0, ' '*contentwidth) contentlines.append(' '*contentwidth) # Insert horizontal padding on lines that are too short contentlines = [linechars[0] + self.hpadding + x + ' '*(self.linewidth-(len(x)+2*len(self.hpadding)+2) if len(x) < contentwidth else 0) + self.hpadding + linechars[0] for x in contentlines] contentlines.insert(0, cornerchars[3] + linechars[1] * (contentwidth + len(self.hpadding)*2) + cornerchars[2]) contentlines.append(cornerchars[0] + linechars[1] * (contentwidth + len(self.hpadding)*2) + cornerchars[1]) return ('\n').join(contentlines) def init_varfilter(): global varfilter global varpattern global varsubst varfilter = {"$title$": config.prefs['title'], "$date$": interface.start.strftime(DATEFMT),"$time$": interface.start.strftime(TIMEFMT), "$hostname$": util.hostname(config.prefs['hostname-path']), "$version$": logparse.__version__, "$css$": os.path.relpath(config.prefs['css'], os.path.dirname(config.prefs['output']))} varfilter = dict((re.escape(k), v) for k, v in varfilter.items()) varpattern = re.compile("|".join(varfilter.keys())) varsubst = dict(title=config.prefs['title'], date=interface.start.strftime(DATEFMT), time=interface.start.strftime(TIMEFMT), hostname=util.hostname(config.prefs['hostname-path']), version=logparse.__version__, css=os.path.relpath(config.prefs['css'], os.path.dirname(config.prefs['output']))) def writetitle(title): # write title for a section if (title == '' or '\n' in title): logger.error("Invalid title") raise ValueError logger.debug("Writing title for " + title) return tag('h2', 0, title) def opentag(tag, block = 0, id = None, cl = None): # write html opening tag output = "" if (block): output += '\n' output += '<' + tag if (id != None): output += " id='" + id + "'" if (cl != None): output += " class='" + cl + "'" output += '>' if (block): output += '\n' return output def closetag(tag, block = 0): # write html closing tag if (block == 0): return "" else: return "\n\n" def tag(tag, block = 0, content = ""): # write html opening tag, content, and html closing tag o = opentag(tag, block) c = closetag(tag, block) return o + content + c def orderbyfreq(l): # order a list by the frequency of its elements and remove duplicates temp_l = l[:] l = list(set(l)) l = [[i, temp_l.count(i)] for i in l] # add count of each element l.sort(key=lambda x:temp_l.count(x[0])) # sort by count l = [i[0] + ' (' + str(i[1]) + ')' for i in l] # put element and count into string l = l[::-1] # reverse return l def addtag(l, tag): # add prefix and suffix tags to each item in a list l2 = ['<' + tag + '>' + i + '' for i in l] return l2 def truncl(input, limit): # truncate list if (len(input) > limit): more = str(len(input) - limit) output = input[:limit] output.append("+ " + more + " more") return(output) else: return(input) def plural(noun, quantity): # return "1 noun" or "n nouns" if (quantity == 1): return(str(quantity) + " " + noun) else: return(str(quantity) + " " + noun + "s") def parsesize(num, suffix='B'): # return human-readable size from number of bytes for unit in ['','Ki','Mi','Gi','Ti','Pi','Ei','Zi']: if abs(num) < 1024.0: return "%3.1f %s%s" % (num, unit, suffix) num /= 1024.0 return "%.1f%s%s" % (num, 'Yi', suffix) def fsubject(template): # Replace variables in the title template provided in config r = varpattern.sub(lambda m: varfilter[re.escape(m.group(0))], template) logger.debug("Returning subject line " + r) return r def writedata(subtitle, data = None): # write title and data if (subtitle == ""): logger.warning("No subtitle provided.. skipping section") return if (data == None or len(data) == 0): logger.debug("No data provided.. just printing subtitle") return tag('p', 0, subtitle) else: logger.debug("Received data " + str(data)) subtitle += ':' if (len(data) == 1): return tag('p', 0, subtitle + ' ' + data[0]) else: output = "" output += tag('p', 0, subtitle) output += opentag('ul', 1) for datum in data: output += tag('li', 0, datum) output += closetag('ul', 1) return output