# ref-check.py # # This file is part of LilyPond, the GNU music typesetter. # # Copyright (C) 2010--2020 Trevor Daniels # # LilyPond is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # LilyPond is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with LilyPond. If not, see . """ *** RefCheck Flow Read files On @defManual manual refManual eg @defManual LM rlearning Associate manual name 'manual' with reference @'refManual'{ ... } Add 'refManual' to list of keywords On @setManual manual set current manual name to 'manual' On @include open and process new file On @node add node to Nodes list with current manual name and current file name On @ref add contents to References list with refManual, current manual name and current file name On one of refManual keywords add contents to References list with ditto Match refs and nodes Process References list: Check reference is contained in Nodes list with refManual in Nodes = refManual in References Print results Known issues Node names containing commas are only checked up to the comma Spurious warnings "Xref should be internal" for files in /included """ ################################################## class CrossRefs: """ Holds References and Nodes """ def __init__(self): self.Manuals = {} self.Nodes = {} self.nodeNames = {} # used to check for duplicates only self.Refs = [] self.nodeCount = 0 def addManual(self, manualName, refManual): self.Manuals[refManual] = manualName def getRefManuals(self): return list(self.Manuals.keys()) def getManualName(self, refManual): return self.Manuals[refManual] def addNode(self, nodeName, manualName, fileName): global returnCode # print "Node: ", nodeName, " in ", manualName, " found in ", fileName if "\\" in nodeName: returnCode = 1 print("nodeName: ", nodeName, " in ", fileName, " contains backslash") if manualName+"/"+nodeName in list(self.Nodes.keys()): print("Error: Duplicate nodename ", nodeName, " in ", fileName, " and ", self.Nodes[manualName+"/"+nodeName][1]) returnCode = 1 self.Nodes[manualName + "/" + nodeName] = [manualName, fileName] self.nodeNames[nodeName] = fileName def addRef(self, toManualName, toHeading, inFileName): global returnCode if "\\" in toHeading: returnCode = 1 print("ref to: ", toHeading, " in ", inFileName, " contains backslash") # if inFileName == "notation/vocal.itely": # print "Ref to ", toManualName, "/",toHeading, " found in ", inFileName self.Refs.append([toManualName + "/" + toHeading, inFileName]) def check(self): noErrors = True for [refHeading, refFileName] in self.Refs: try: targetFileName = self.Nodes[refHeading] # if refFileName == "notation/vocal.itely": # print "ref to: ", refHeading, " in ", refFileName, " found in ", targetFileName except KeyError: noErrors = False print("ref to: ", refHeading, " in ", refFileName, " not found") if noErrors: print(" All references satisfied") else: returnCode = 1 ################################################## class File: """ Process an included file """ # Class variables CurrentManualName = "" DefaultPath = "" Excludes = [] Paths = {} # Methods def __init__(self, fileName): self.fileName = fileName try: self.fullFileName = File.Paths[fileName] + fileName except KeyError: self.fullFileName = File.DefaultPath + fileName def read(self, crossRefs): """ Process File """ skip = False try: myfile = open(self.fullFileName, 'r', encoding='utf8') except IOError: print("File ", self.fullFileName, " referenced in ", File.CurrentManualName, " but not found") return remainderLine = "" lineNo = 0 for line in myfile: lineNo += 1 words = line.split() if len(words) > 0: if words[0] == "@ignore" or words[0] == "@macro": skip = True if skip and len(words) > 1: if words[0] == "@end" and (words[1].find("ignore") >= 0 or words[1].find("macro") >= 0): skip = False if not skip and words[0] != "@c": if words[0].find("@defManual") >= 0: # Manual definition found - extract manual name and refManual string manualName = words[1] refManual = words[2] crossRefs.addManual(manualName, refManual) # print manualName, refManual elif words[0].find("@defaultPath") >= 0: File.DefaultPath = words[1] elif words[0].find("@path") >= 0: File.Paths[words[1]] = words[2] elif words[0].find("@setManual") >= 0: File.CurrentManualName = words[1] # print " Checking ", File.CurrentManualName elif words[0].find("@exclude") >= 0: File.Excludes.append(words[1]) elif words[0].find("@include") >= 0: if words[1] not in File.Excludes: currentFileName = words[1] # print " File: ", currentFileName currentFile = File(currentFileName) currentFile.read(crossRefs) elif words[0] == "@node": nodeName = line[6:-1] crossRefs.addNode( nodeName, File.CurrentManualName, self.fileName) # Find references twoLines = remainderLine + ' ' + line.strip() manualRefStrings = crossRefs.getRefManuals() refFound = False for manualRefString in manualRefStrings: toManualName = crossRefs.getManualName(manualRefString) actualToManualName = toManualName if toManualName == "this": toManualName = File.CurrentManualName refString = "@" + manualRefString + "{" refStart = twoLines.find(refString) if refStart >= 0: refFound = True if actualToManualName == File.CurrentManualName: print("Warning: should xref be internal around line ", lineNo, " in ", self.fileName, "?") twoLines = twoLines[refStart:] refNodeStart = twoLines.find("{") + 1 # TODO Need to check here for nested {} refNodeEnd = twoLines.find("}") refNodeEndComma = twoLines.find(",") if refNodeEndComma > 0: refNodeEnd = min(refNodeEnd, refNodeEndComma) if refNodeEnd >= 0: crossRefs.addRef( toManualName, twoLines[refNodeStart:refNodeEnd], self.fileName) remainderLine = twoLines[refNodeEnd+1:] if refFound: refFound = False break if not refFound: remainderLine = "" myfile.close() return topFile = File("scripts/auxiliar/ref_check.tely") # TODO get from input params print("RefCheck ver 0.1") returnCode = 0 crossRefs = CrossRefs() topFile.read(crossRefs) crossRefs.check() if returnCode > 0: print("Errors found: status code: ", returnCode)