diff options
Diffstat (limited to 'longbow/src/python/site-packages/longbow/StyleReport.py')
-rwxr-xr-x | longbow/src/python/site-packages/longbow/StyleReport.py | 382 |
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 |