Navigation

Source code for pyquickhelper.helpgen._my_doxypy

"""Modification to doxypy.py(which you can found here: http://code.foosel.org/doxypy).The main tweaks are:- the documentation is not moved before the function or class definition- it uses a function to modify every line of documentation to use rst syntax.:githublink:`%|py|11`"""from__future__importprint_functionimportsysimportrefromargparseimportArgumentParserasOptionParser_allowed=re.compile("^([a-zA-Z]:)?[^:*?\"<>|]+$")

__applicationName__="doxypy"__blurb__="""doxypy is an input filter for Doxygen. It preprocesses pythonfiles so that docstrings of classes and functions are reformattedinto Doxygen-conform documentation blocks."""__doc__=__blurb__+ \
"""In order to make Doxygen preprocess files through doxypy, simplyadd the following lines to your Doxyfile: - ``FILTER_SOURCE_FILES = YES`` - ``INPUT_FILTER = "python /path/to/doxypy.py"``"""__version__="0.4.2"__date__="14th October 2009"__website__="http://code.foosel.org/doxypy"__author__=("Philippe 'demod' Neumann (doxypy at demod dot org)","Gina 'foosel' Haeussge (gina at foosel dot net)")__licenseName__="GPL v2"__license__="""This program is free software: you can redistribute it and/or modifyit under the terms of the GNU General Public License as published bythe Free Software Foundation, either version 2 of the License, or(at your option) any later version.This program is distributed in the hope that it will be useful,but WITHOUT ANY WARRANTY; without even the implied warranty ofMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See theGNU General Public License for more details.You should have received a copy of the GNU General Public Licensealong with this program. If not, see `GNU <http://www.gnu.org/licenses/>`_."""

[docs]classFSM(object):""" Implements a finite state machine. Transitions are given as 4-tuples, consisting of an origin state, a target state, a condition for the transition (given as a reference to a function which gets called with a given piece of input) and a pointer to a function to be called upon the execution of the given transition. .. list-table:: :widths: auto :header-rows: 1 * - attribute - meaning * - transitions - holds the transitions * - current_state - holds the current state * - current_input - holds the current input * - current_transition - hold the currently active transition :githublink:`%|py|93` """

[docs]defmakeTransition(self,input):""" Makes a transition based on the given input. :param input: input to parse by the FSM :githublink:`%|py|114` """fortransitioninself.transitions:[from_state,to_state,condition,callback]=transitioniffrom_state==self.current_state:match=condition(input)ifmatch:self.current_state=to_stateself.current_input=inputself.current_transition=transitionifoptions.debug:# pragma: no coversys.stderr.write("# FSM: executing ({} -> {}) for line '{}'\n".format(from_state,to_state,input))callback(match)return

classDoxypy(object):def__init__(self,print_output,process_comment,information):""" Constructor for Doxypy. :param print_output: function which will receive the output :param process_comment: function applied to the help to modify it :param information: a dictionary with additional information such as filename, first_row :githublink:`%|py|140` """string_prefixes="[uU]?[rR]?"self.start_single_comment_re=re.compile("^\\s*%s(''')"%string_prefixes)self.end_single_comment_re=re.compile("(''')\\s*$")self.start_double_comment_re=re.compile("^\\s*%s(\"\"\")"%string_prefixes)self.end_double_comment_re=re.compile("(\"\"\")\\s*$")self.single_comment_re=re.compile("^\\s*%s(''').*(''')\\s*$"%string_prefixes)self.double_comment_re=re.compile("^\\s*%s(\"\"\").*(\"\"\")\\s*$"%string_prefixes)self.defclass_re=re.compile("^(\\s*)(def .+:|class .+:)\\s*([#].*?)?$")self.empty_re=re.compile("^\\s*$")self.hashline_re=re.compile("^\\s*#.*$")self.importline_re=re.compile("^\\s*(import |from .+ import)")self.multiline_defclass_start_re=re.compile("^(\\s*)(def|class)(\\s.*)?$")self.multiline_defclass_end_re=re.compile(":\\s*([#].*?)?$")self.print_output=print_outputself.process_comment=process_commentself.information=information# Transition list format# ["FROM", "TO", condition, action]transitions=[# FILEHEAD# single line comments["FILEHEAD","FILEHEAD",self.single_comment_re.search,self.appendCommentLine],["FILEHEAD","FILEHEAD",self.double_comment_re.search,self.appendCommentLine],# multiline comments["FILEHEAD","FILEHEAD_COMMENT_SINGLE",self.start_single_comment_re.search,self.appendCommentLine],["FILEHEAD_COMMENT_SINGLE","FILEHEAD",self.end_single_comment_re.search,self.appendCommentLine],["FILEHEAD_COMMENT_SINGLE","FILEHEAD_COMMENT_SINGLE",self.catchall,self.appendCommentLine],["FILEHEAD","FILEHEAD_COMMENT_DOUBLE",self.start_double_comment_re.search,self.appendCommentLine],["FILEHEAD_COMMENT_DOUBLE","FILEHEAD",self.end_double_comment_re.search,self.appendCommentLine],["FILEHEAD_COMMENT_DOUBLE","FILEHEAD_COMMENT_DOUBLE",self.catchall,self.appendCommentLine],# other lines["FILEHEAD","FILEHEAD",self.empty_re.search,self.appendFileheadLine],["FILEHEAD","FILEHEAD",self.hashline_re.search,self.appendFileheadLine],["FILEHEAD","FILEHEAD",self.importline_re.search,self.appendFileheadLine],["FILEHEAD","DEFCLASS",self.defclass_re.search,self.resetCommentSearch],["FILEHEAD","DEFCLASS_MULTI",self.multiline_defclass_start_re.search,self.resetCommentSearch],["FILEHEAD","DEFCLASS_BODY",self.catchall,self.appendFileheadLine],# DEFCLASS# single line comments["DEFCLASS","DEFCLASS_BODY",self.single_comment_re.search,self.appendCommentLine],["DEFCLASS","DEFCLASS_BODY",self.double_comment_re.search,self.appendCommentLine],# multiline comments["DEFCLASS","COMMENT_SINGLE",self.start_single_comment_re.search,self.appendCommentLine],["COMMENT_SINGLE","DEFCLASS_BODY",self.end_single_comment_re.search,self.appendCommentLine],["COMMENT_SINGLE","COMMENT_SINGLE",self.catchall,self.appendCommentLine],["DEFCLASS","COMMENT_DOUBLE",self.start_double_comment_re.search,self.appendCommentLine],["COMMENT_DOUBLE","DEFCLASS_BODY",self.end_double_comment_re.search,self.appendCommentLine],["COMMENT_DOUBLE","COMMENT_DOUBLE",self.catchall,self.appendCommentLine],# other lines["DEFCLASS","DEFCLASS",self.empty_re.search,self.appendDefclassLine],["DEFCLASS","DEFCLASS",self.defclass_re.search,self.resetCommentSearch],["DEFCLASS","DEFCLASS_MULTI",self.multiline_defclass_start_re.search,self.resetCommentSearch],["DEFCLASS","DEFCLASS_BODY",self.catchall,self.stopCommentSearch],# DEFCLASS_BODY["DEFCLASS_BODY","DEFCLASS",self.defclass_re.search,self.startCommentSearch],["DEFCLASS_BODY","DEFCLASS_MULTI",self.multiline_defclass_start_re.search,self.startCommentSearch],["DEFCLASS_BODY","DEFCLASS_BODY",self.catchall,self.appendNormalLine],# DEFCLASS_MULTI["DEFCLASS_MULTI","DEFCLASS",self.multiline_defclass_end_re.search,self.appendDefclassLine],["DEFCLASS_MULTI","DEFCLASS_MULTI",self.catchall,self.appendDefclassLine],]self.fsm=FSM("FILEHEAD",transitions)self.outstream=sys.stdoutself.output=[]self.comment=[]self.filehead=[]self.defclass=[]self.indent=""def__closeComment(self):""" Appends any open comment block and triggering block to the output. :githublink:`%|py|268` """ifoptions.autobrief:iflen(self.comment)==1 \
or(len(self.comment)>2andself.comment[1].strip()==''):self.comment[0]=self.__docstringSummaryToBrief(self.comment[0])ifself.defclass:self.output.extend(self.defclass)ifself.comment:block=self.makeCommentBlock()self.output.extend(block)def__docstringSummaryToBrief(self,line):""" Adds \\brief to the docstrings summary line. A \\brief is prepended, provided no other doxygen command is at the start of the line. :githublink:`%|py|289` """stripped=line.strip()ifstrippedandnotstripped[0]in('@','\\'):return"@brief "+lineelse:returnlinedef__flushBuffer(self):""" Flushes the current outputbuffer to the outstream. :githublink:`%|py|299` """ifself.output:ifoptions.debug:# pragma: no coversys.stderr.write("# OUTPUT: {0}\n".format(self.output))self.print_output("\n".join(self.output),file=self.outstream)self.outstream.flush()self.output=[]defcatchall(self,input):""" The catchall-condition, always returns true. :githublink:`%|py|310` """returnTruedefresetCommentSearch(self,match):""" Restarts a new comment search for a different triggering line. Closes the current commentblock and starts a new comment search. :githublink:`%|py|318` """ifoptions.debug:# pragma: no coversys.stderr.write("# CALLBACK: resetCommentSearch")self.__closeComment()self.startCommentSearch(match)defstartCommentSearch(self,match):""" Starts a new comment search. Saves the triggering line, resets the current comment and saves the current indentation. :githublink:`%|py|330` """ifoptions.debug:# pragma: no coversys.stderr.write("# CALLBACK: startCommentSearch")self.defclass=[self.fsm.current_input]self.comment=[]self.indent=match.group(1)defstopCommentSearch(self,match):""" Stops a comment search. Closes the current commentblock, resets the triggering line and appends the current line to the output. :githublink:`%|py|343` """ifoptions.debug:# pragma: no coversys.stderr.write("# CALLBACK: stopCommentSearch")self.__closeComment()self.defclass=[]self.output.append(self.fsm.current_input)defappendFileheadLine(self,match):""" Appends a line in the FILEHEAD state. Closes the open comment block, resets it and appends the current line. :githublink:`%|py|356` """ifoptions.debug:# pragma: no coversys.stderr.write("# CALLBACK: appendFileheadLine")self.__closeComment()self.comment=[]self.output.append(self.fsm.current_input)defappendCommentLine(self,match):""" Appends a comment line. The comment delimiter is removed from multiline start and ends as well as singleline comments. :githublink:`%|py|369` """ifoptions.debug:# pragma: no coversys.stderr.write("# CALLBACK: appendCommentLine")from_state,to_state,condition,callback=self.fsm.current_transition# pylint: disable=W0612# single line commentif(from_state=="DEFCLASS"andto_state=="DEFCLASS_BODY") \
or(from_state=="FILEHEAD"andto_state=="FILEHEAD"):# remove comment delimiter from begin and end of the lineactiveCommentDelim=match.group(1)line=self.fsm.current_inputself.comment.append(line[line.find(activeCommentDelim)+len(activeCommentDelim):line.rfind(activeCommentDelim)])if(to_state=="DEFCLASS_BODY"):self.__closeComment()self.defclass=[]# multiline starteliffrom_statein("DEFCLASS","FILEHEAD"):# remove comment delimiter from begin of the lineactiveCommentDelim=match.group(1)line=self.fsm.current_inputself.comment.append(line[line.find(activeCommentDelim)+len(activeCommentDelim):])# multiline endelifto_statein("DEFCLASS_BODY","FILEHEAD"):# remove comment delimiter from end of the lineactiveCommentDelim=match.group(1)line=self.fsm.current_inputself.comment.append(line[0:line.rfind(activeCommentDelim)])if(to_state=="DEFCLASS_BODY"):self.__closeComment()self.defclass=[]# in multiline commentelse:# just append the comment lineself.comment.append(self.fsm.current_input)defappendNormalLine(self,match):""" Appends a line to the output. :githublink:`%|py|410` """ifoptions.debug:# pragma: no coverself.print_output("# CALLBACK: appendNormalLine",file=sys.stderr)self.output.append(self.fsm.current_input)defappendDefclassLine(self,match):""" Appends a line to the triggering block. :githublink:`%|py|418` """ifoptions.debug:# pragma: no coverself.print_output("# CALLBACK: appendDefclassLine",file=sys.stderr)self.defclass.append(self.fsm.current_input)defmakeCommentBlock(self):""" Indents the current comment block with respect to the current indentation level. :return:s a list of indented comment lines :githublink:`%|py|430` """ifoptions.debug:# pragma: no coverself.print_output("# makeCommentBlock",file=sys.stderr)indent4=" "iflen(self.defclass)>0else""doxyStart="%s\"\"\""%indent4doxyEnd="%s\"\"\""%indent4commentLines=self.commentfull_indent=self.indent+indent4iffull_indent:ifcommentLinesandcommentLines[0].lstrip()==commentLines[0]:commentLines[0]=full_indent+commentLines[0]# commentLines = ["%s%s%s" % (self.indent, indent4, x) for x in commentLines]commentLines=self.process_comment(commentLines,self.information.get("first_row",0)+self._index_row+1,self.information.get("filename","filename is not present"))# We remove the indentation.whilecommentLinesandcommentLines[0].strip()=='':delcommentLines[0]whilecommentLinesandcommentLines[-1].strip()=='':delcommentLines[-1]# Back to doxypy.li=[self.indent+doxyStart]li.extend(commentLines)li.append(self.indent+doxyEnd)returnlidefparse(self,input):""" Parses a python file given as input string and returns the doxygen- compatible representation. :param input: the python code to parse :return:s the modified python code :githublink:`%|py|469` """lines=input.split("\n")forlineinlines:self.fsm.makeTransition(line)ifself.fsm.current_state=="DEFCLASS":self.__closeComment()return"\n".join(self.output)defparseFile(self,filename):""" Parses a :epkg:`python` file given as input string and returns` the :epkg:`doxygen` compatible representation. :param filename: the :epkg:`python` code to parse (filename) :githublink:`%|py|486` """self._index_row=0ifisinstance(filename,list):forlineinfilename:self.parseLine(line.rstrip('\r\n'))self._index_row+=1else:importosifis_file_string(filename)andos.path.exists(filename):withopen(filename,'r')asfh:forlineinfh:self.parseLine(line.rstrip('\r\n'))self._index_row+=1else:forlineinfilename.split("\n"):self.parseLine(line.rstrip('\r\n'))self._index_row+=1ifself.fsm.current_state=="DEFCLASS":self.__closeComment()self.__flushBuffer()defparseLine(self,line):""" Parses one line of python and flush the resulting output to the outstream. :param line: the python code line to parse :githublink:`%|py|514` """self.fsm.makeTransition(line)self.__flushBuffer()

[docs]defmain(file=None,print_output=None):""" Starts the parser on the file given by the filename as the first argument on the commandline. :param file: if equal to None, take this one on the command line :param print_output: every string is sent to that funtion :githublink:`%|py|555` """filename=fileiffileisnotNoneelseoptParse()fsm=Doxypy(print_output,lambdaa,b,c:a,{})fsm.parseFile(filename)

[docs]defprocess_string(content,print_output,process_comment,filename,first_row,debug=False):""" Applies the doxypy like process to a string. :param content: string :param print_output: every string is sent to that funtion :param process_comment: function applied to the help to modifies it :param filename: or None if there is no file :param first_row: True: to display error messages :param debug: if True, display more information :githublink:`%|py|581` """options.debug=debugfsm=Doxypy(print_output,process_comment,{"filename":filename,"first_row":first_row})fsm.parseFile(content)