#! /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