# Released under the MIT License. See LICENSE for details.#"""Tools for parsing/filtering makefiles."""from__future__importannotationsimportcopyfromdataclassesimportdataclassfromtypingimportTYPE_CHECKINGifTYPE_CHECKING:pass
[docs]@dataclassclassSection:"""Represents a section of a Makefile."""name:str|Noneparagraphs:list[Paragraph]
[docs]@dataclassclassParagraph:"""Represents a continuous set of non-blank lines in a Makefile."""contents:str
[docs]defget_logical_lines(self)->list[str]:"""Return contents broken into logical lines. Lines joined by continuation chars are considered a single line. """returnself.contents.replace('\\\n','').splitlines()
[docs]classMakefile:"""Represents an entire Makefile."""header_line_full='#'*80header_line_empty='#'+' '*78+'#'def__init__(self,contents:str):self.sections:list[Section]=[]self._original=copy.copy(contents)lines=contents.splitlines()paragraphs:list[Paragraph]=[]# First off, break into paragraphs (continuous sets of lines)plines:list[str]=[]forlineinlines:ifline.strip()=='':ifplines:paragraphs.append(Paragraph(contents='\n'.join(plines)))plines=[]continueplines.append(line)ifplines:paragraphs.append(Paragraph(contents='\n'.join(plines)))# Now break all paragraphs into sections.section=Section(name=None,paragraphs=[])self.sections.append(section)forparagraphinparagraphs:# Look for our very particular section headers and start# a new section whenever we come across one.plines=paragraph.contents.splitlines()# pylint: disable=too-many-boolean-expressionsif(len(plines)==5andplines[0]==self.header_line_fullandplines[1]==self.header_line_emptyandlen(plines[2])==80andplines[2][0]=='#'andplines[2][-1]=='#'andplines[3]==self.header_line_emptyandplines[4]==self.header_line_full):section=Section(name=plines[2][1:-1].strip(),paragraphs=[])self.sections.append(section)else:section.paragraphs.append(paragraph)
[docs]deffind_assigns(self,name:str)->list[tuple[Section,int]]:"""Return section/index pairs for paragraphs containing an assign. Note that the paragraph may contain other statements as well. """found:list[tuple[Section,int]]=[]forsectioninself.sections:fori,paragraphinenumerate(section.paragraphs):ifany(line.split('=')[0].strip()==nameforlineinparagraph.get_logical_lines()):found.append((section,i))returnfound
[docs]deffind_targets(self,name:str)->list[tuple[Section,int]]:"""Return section/index pairs for paragraphs containing a target. Note that the paragraph may contain other statements as well. """found:list[tuple[Section,int]]=[]forsectioninself.sections:fori,paragraphinenumerate(section.paragraphs):ifany(line.split()[0]==name+':'forlineinparagraph.get_logical_lines()):found.append((section,i))returnfound
[docs]defget_output(self)->str:"""Generate a Makefile from the current state."""output=''forsectioninself.sections:did_first_entry=Falseifsection.nameisnotNone:output+='\n\n'+self.header_line_full+'\n'output+=self.header_line_empty+'\n'spacelen=78-len(section.name)output+='#'+' '*(spacelen//2)+section.namespacelen-=spacelen//2output+=' '*spacelen+'#\n'output+=self.header_line_empty+'\n'output+=self.header_line_full+'\n'did_first_entry=Trueforparagraphinsection.paragraphs:ifdid_first_entry:output+='\n'output+=paragraph.contents+'\n'did_first_entry=True# print(output)returnoutput