add plain text output capability
[logparse.git] / logparse / formatting.py
index aa4b71f6d79fe3e6b8170d34d5f278d4977ac339..d7a9b4d4d99a31821bba41318691d9558344a9d7 100644 (file)
@@ -24,6 +24,250 @@ 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'<code>{\1}</code>', 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
@@ -66,16 +310,6 @@ def tag(tag, block = 0, content = ""):  # write html opening tag, content, and h
     c = closetag(tag, block)
     return o + content + c
 
-def header(template):   # return a parsed html header from file
-#    try:
-#        copyfile(config['css'], config['dest'] + '/' + os.path.basename(config['css']))
-#        logger.debug("copied main.css")
-#    except Exception as e:
-#        logger.warning("could not copy main.css - " + str(e))
-    init_varfilter()
-    headercontent = Template(open(template, 'r').read())
-    return headercontent.safe_substitute(varsubst)
-
 def orderbyfreq(l):     # order a list by the frequency of its elements and remove duplicates
     temp_l = l[:]
     l = list(set(l))