aboutsummaryrefslogtreecommitdiffstats
path: root/longbow/src/python/site-packages/longbow/StyleReport.py
diff options
context:
space:
mode:
Diffstat (limited to 'longbow/src/python/site-packages/longbow/StyleReport.py')
-rwxr-xr-xlongbow/src/python/site-packages/longbow/StyleReport.py382
1 files changed, 382 insertions, 0 deletions
diff --git a/longbow/src/python/site-packages/longbow/StyleReport.py b/longbow/src/python/site-packages/longbow/StyleReport.py
new file mode 100755
index 00000000..7e8d72e0
--- /dev/null
+++ b/longbow/src/python/site-packages/longbow/StyleReport.py
@@ -0,0 +1,382 @@
+#! /usr/bin/env python
+# Copyright (c) 2017 Cisco and/or its affiliates.
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at:
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+#
+
+import sys
+import os
+import tempfile
+import subprocess
+import difflib
+import csv
+import argparse
+
+import LongBow
+import ANSITerm
+import FileUtil
+import pprint
+
+def getExemplar(fileName, command, config):
+ """Create the exemplar formatted file into memory as a string"""
+
+ with open(fileName) as inputFile:
+ result = subprocess.check_output([command, "-q", "-c", config], stdin=inputFile)
+ return result;
+
+
+def diff(exemplar, fileName):
+ d = difflib.Differ()
+ differ = d.compare(exemplar.splitlines(), fileName.splitlines())
+ return differ
+
+
+class Ratchet:
+ def __init__(self):
+ self.currentValue = 0
+ self.signal = 0
+
+ def value(self):
+ return self.currentValue
+
+ def toggle(self, signal):
+ if self.signal == "-":
+ if signal == "-":
+ self.currentValue = self.currentValue + 1
+ elif signal == "?":
+ self.currentValue = self.currentValue + 1
+ self.signal = 0
+ else:
+ self.currentValue = self.currentValue + 1
+ self.signal = 0
+ pass
+ elif self.signal == "+":
+ if signal == "-":
+ self.currentValue = self.currentValue + 1
+ elif signal == "?":
+ self.currentValue = self.currentValue + 1
+ self.signal = 0
+ else:
+ self.currentValue = self.currentValue + 1
+ self.signal = 0
+ pass
+ else:
+ self.signal = signal;
+
+ return self.currentValue
+
+
+def computeNonCompliantLines(differ):
+ lines = 0
+ changes = Ratchet()
+
+ for l in differ:
+ if l.startswith('-'):
+ changes.toggle(l[0])
+ lines = lines - 1
+ elif l.startswith('+'):
+ changes.toggle(l[0])
+ lines = lines + 1
+ elif l.startswith('?'):
+ pass
+ elif l.startswith(' '):
+ lines = lines +1
+ else:
+ print "What is this:", l
+
+ return changes.value()
+
+
+def reportWhy(differ):
+ print '\n'.join(diff)
+ return
+
+
+class SyntaxCompliance:
+ def __init__(self, fileName, exemplarCommand, exemplarConfig):
+ self.fileName = fileName
+ self.nonCompliantLines = 0
+ self.score = 0
+ self.exemplarCommand = exemplarCommand
+ self.exemplarConfig = exemplarConfig
+ try:
+ self.fileData = FileUtil.readFileString(self.fileName)
+ self.totalLines = len(self.fileData.splitlines())
+ except IOError, e:
+ print >> sys.stderr, e
+ sys.exit(1)
+ pass
+
+ def check(self):
+ self.exemplarData = getExemplar(self.fileName, self.exemplarCommand, self.exemplarConfig)
+ differ = diff(self.fileData, self.exemplarData)
+
+ self.nonCompliantLines = computeNonCompliantLines(differ)
+
+ return self
+
+ def report(self):
+ result = { "fileName" : self.fileName,
+ "label": "style",
+ "score": self.getScore(),
+ "totalLines" : self.getTotalLines(),
+ "nonCompliantLines" : self.getNonCompliantLines()
+ }
+ return result
+
+ def getFileName(self):
+ return self.fileName
+
+ def getExemplarCommand(self):
+ return self.exemplarCommand;
+
+ def getExemplarConfig(self):
+ return self.exemplarConfig;
+
+ def getScore(self):
+ result = 0
+ try:
+ result = int(100 * (1.0 - (float(self.getNonCompliantLines()) / float(self.getTotalLines()))))
+ except ZeroDivisionError:
+ pass
+ return result
+
+ def getTotalLines(self):
+ return self.totalLines
+
+ def getNonCompliantLines(self):
+ return self.nonCompliantLines
+
+ def explain(self):
+ self.exemplarData = getExemplar(self.fileName, self.exemplarCommand, self.exemplarConfig)
+ differ = diff(self.fileData, self.exemplarData)
+
+ ansiTerm = ANSITerm.ANSITerm()
+
+ for l in differ:
+ if l[0] == '-':
+ ansiTerm.printColorized("red", l)
+ elif l[0] == '+':
+ ansiTerm.printColorized("green", l)
+ elif l[0] == '?':
+ ansiTerm.printColorized("yellow", l[0:len(l)-1])
+ else:
+ print l
+ pass
+ return
+
+
+def csvScore(distribution, report):
+ string = "style,%s,%d,%d,%.2f" % (report["fileName"], report["totalLines"], report["nonCompliantLines"], report["score"])
+ LongBow.scorePrinter(distribution, report["score"], string)
+ return
+
+
+def csvAverage(distribution, complianceList):
+ scores = map(lambda target: target.getScore(), complianceList)
+ sum = reduce(lambda sum, score : sum + score, scores)
+ value = float(sum) / float(len(complianceList))
+ LongBow.scorePrinter(distribution, value, "%.2f" % (value))
+ return
+
+
+def csvTotal(distribution, complianceList):
+ totalLines = reduce(lambda sum, x: sum + x, map(lambda element : element.getTotalLines(), complianceList))
+ totalNonCompliantLines = reduce(lambda sum, x: sum + x, map(lambda element : element.getNonCompliantLines(), complianceList))
+ value = 100.0 - (100.0 * float(totalNonCompliantLines) / float(totalLines))
+ LongBow.scorePrinter(distribution, value, "%.2f" % (value))
+ return
+
+
+def csvSummary(distribution, complianceList):
+ map(lambda target: csvScore(distribution, target.report()), complianceList)
+ return
+
+
+def textScore(distribution, report, maxFileNameLength, prefix=""):
+ '''
+
+ '''
+ format = "%s%-*s %6d %6d %6.2f"
+ string = format % (prefix, maxFileNameLength, report["fileName"], report["totalLines"], report["nonCompliantLines"], report["score"])
+ LongBow.scorePrinter(distribution, report["score"], string)
+ return
+
+
+def textAverage(distribution, complianceList):
+ scores = map(lambda target: target.getScore(), complianceList)
+ sum = reduce(lambda sum, score : sum + score, scores)
+ value = float(sum) / float(len(complianceList))
+ LongBow.scorePrinter(distribution, value, "%.2f" % (value))
+ return
+
+
+def textTotal(distribution, complianceList):
+ totalLines = reduce(lambda sum, x: sum + x, map(lambda element : element.getTotalLines(), complianceList))
+ totalNonCompliantLines = reduce(lambda sum, x: sum + x, map(lambda element : element.getNonCompliantLines(), complianceList))
+ value = 100.0 - (100.0 * float(totalNonCompliantLines) / float(totalLines))
+ LongBow.scorePrinter(distribution, value, "%.2f" % (value))
+ return
+
+
+def textSummary(distribution, complianceList, prefix=""):
+ if len(complianceList) > 0:
+ maxFileNameLength = max(max(map(lambda target: len(target.getFileName()), complianceList)), len("File Name"))
+
+ print "%s%-*s %6s %6s %6s" % (prefix, maxFileNameLength, "File Name", "Lines", "Errors", "Score")
+ map(lambda target: textScore(distribution, target.report(), maxFileNameLength, prefix), complianceList)
+
+ return
+
+
+def textVisual(complianceList):
+ map(lambda target: target.explain(), complianceList)
+ return
+
+
+def openDiff(sourceFile, exemplarCommand, exemplarConfig):
+ exemplar = getExemplar(sourceFile, exemplarCommand, exemplarConfig);
+ temporaryExemplarFile = tempfile.NamedTemporaryFile(suffix=".c", delete=False)
+ try:
+ with open(temporaryExemplarFile.name, "w") as exemplarOutput:
+ exemplarOutput.write(exemplar)
+
+ subprocess.check_output(["opendiff", sourceFile, temporaryExemplarFile.name, "-merge", sourceFile])
+ finally:
+ pass
+
+ return
+
+
+def displaySummary(args, complianceList):
+ distribution = eval(args.distribution)
+
+ if args.output == "text":
+ textSummary(distribution, complianceList)
+ elif args.output == "gui":
+ textSummary(distribution, complianceList)
+ else:
+ csvSummary(distribution, complianceList)
+ return
+
+
+def displayAverage(args, complianceList):
+
+ distribution = eval(args.distribution)
+
+ if args.output == "text":
+ textAverage(distribution, complianceList)
+ elif args.output == "gui":
+ textAverage(distribution, complianceList)
+ else:
+ csvAverage(distribution, complianceList)
+ return
+
+
+def displayTotal(args, complianceList):
+ distribution = eval(args.distribution)
+
+ if args.output == "text":
+ textTotal(distribution, complianceList)
+ elif args.output == "gui":
+ textTotal(distribution, complianceList)
+ else:
+ csvTotal(distribution, complianceList)
+ return
+
+
+def guiVisual(args, complianceList):
+ map(lambda target: openDiff(target.getFileName(), target.getExemplarCommand(), target.getExemplarConfig()), complianceList)
+ return
+
+
+def displayVisual(args, complianceList):
+ if args.output == "text":
+ textVisual(complianceList)
+ elif args.output == "gui":
+ guiVisual(args, complianceList)
+ else:
+ print >> sys.stderr, "Unsupported output format '%s'. Expected 'text' or 'gui'." % (args.output)
+ sys.exit(1)
+ return
+
+
+def sortComplianceList(args, complianceList):
+
+ sorter = {
+ "name" : { "function" : lambda k: k.getFileName(), "reverse" : False },
+ "descending-name" : { "function" : lambda k: k.getFileName(), "reverse" : True },
+ "score" : { "function" : lambda k: k.getScore(), "reverse" : False },
+ "descending-score" : { "function" : lambda k: k.getScore(), "reverse" : True },
+ "size" : { "function" : lambda k: k.getTotalLines(), "reverse" : False },
+ "descending-size" : { "function" : lambda k: k.getTotalLines(), "reverse" : True },
+ }
+
+ if args.key == "help":
+ print >> sys.stderr, "Supported sort keys:"
+ map(lambda k: sys.stderr.write("'" + k + "' "), sorted(sorter))
+ print
+ sys.exit(1)
+
+ if args.key in sorter:
+ complianceList = sorted(complianceList, key=sorter[args.key]["function"], reverse=sorter[args.key]["reverse"])
+ else:
+ print >> sys.stderr, "Unsupported sort key '%s'. Type '--key help'" % (args.key)
+ sys.exit(1)
+
+ return complianceList
+
+
+def exclude(args, complianceList):
+ excluded = map(lambda token : token.strip(), args.exclude.split(","))
+ complianceList = filter(lambda entry: LongBow.score(eval(args.distribution), entry.getScore()) not in excluded, complianceList)
+
+ return complianceList
+
+
+def gradeAndPrint(targets, exemplarCommand, exemplarConfig, problemsOnly=False, prefix=""):
+ complianceList = []
+ problemList = []
+ for target in targets:
+ try:
+ complianceList.append(SyntaxCompliance(target, exemplarCommand, exemplarConfig).check())
+ except:
+ problemList.append(target)
+ pass
+ complianceList = sorted(complianceList, key=lambda k: k.getFileName())
+ if problemsOnly:
+ complianceList = filter(lambda entry: entry.getScore() < 100, complianceList)
+ distribution=[99,90]
+ textSummary(distribution, complianceList, prefix)
+
+ for target in problemList:
+ print LongBow.buildRed("%s%s could not be evaluated" % (prefix, target))
+
+
+def commandLineMain(args, targets, exemplarCommand, exemplarConfig):
+ complianceList = map(lambda target: SyntaxCompliance(target, exemplarCommand, exemplarConfig).check(), targets)
+
+ complianceList = sortComplianceList(args, complianceList)
+
+ complianceList = exclude(args, complianceList)
+
+ if args.summary:
+ displaySummary(args, complianceList)
+ elif args.average:
+ displayAverage(args, complianceList)
+ elif args.total:
+ displayTotal(args, complianceList)
+ elif args.visual:
+ displayVisual(args, complianceList)
+ return