def test_process_doxygen(self): s = dedent("""\ /** This is another style of comments @system name: Alchemist input_ports: - lead output_ports: - gold @endsystem And then we have some more insightful documentation... @system name: Researcher input_ports: - ideas output_ports: - gold @endsystem */ """) html_ish = mut.process_doxygen_system_tags(s) self.assertEqual(html_ish.count("\n<table"), 2) rst_ish = mut.process_doxygen_to_sphinx(s) self.assertEqual(rst_ish.count(".. pydrake_system::"), 2)
def test_html_works(self): s = R"""( /** This is another style of comments @system name: Alchemist<br/><img src="photo.jpg" /> input_ports: - lead output_ports: - <b>gold</b> @endsystem */ )""" t = process_doxygen_system_tags(s) self.assertRegex(t, 'Alchemist<br/><img src="photo.jpg" />') self.assertRegex(t, '<b>gold</b>')
def test_asterisk_comment(self): s = R"""( /** This is another style of comments @system name: Alchemist input_ports: // ignore - lead output_ports: - gold @endsystem */ )""" t = process_doxygen_system_tags(s) self.assertRegex(t, "\n<table") self.assertNotRegex(t, "ignore")
def test_line_asterisk_comment(self): s = R"""( /** This is another style of comments * * @system * name: Alchemist * input_ports: // ignore * - lead * output_ports: * - gold * @endsystem */ )""" t = process_doxygen_system_tags(s) self.assertRegex(t, r'\n \* <table') self.assertNotRegex(t, "ignore")
def test_triple_slash_comment(self): s = R"""() /// This is one style of comments /// /// @system /// name: Adder // ignore /// input_ports: /// - input(0) /// - ... /// - input(N-1) /// output_ports: /// - sum /// @endsystem /// )""" t = process_doxygen_system_tags(s) self.assertRegex(t, "/// <table") self.assertNotRegex(t, "ignore")
def test_multiple_system_tags(self): s = R"""( /** This is another style of comments @system name: Alchemist input_ports: - lead output_ports: - gold @endsystem And then we have some more insightful documentation... @system name: Researcher input_ports: - ideas output_ports: - gold @endsystem */ )""" t = process_doxygen_system_tags(s) self.assertEqual(t.count("\n<table"), 2)
def process_doxygen_commands(s): # Doxygen tags cpp_group = r'([\w:*()]+)' param_group = r'([\[\w,\]]+)' s = re.sub(r'[@\\][cp]\s+%s' % cpp_group, r'``\1``', s) s = re.sub(r'[@\\](?:a|e|em)\s+%s' % cpp_group, r'*\1*', s) s = re.sub(r'[@\\]b\s+%s' % cpp_group, r'**\1**', s) s = re.sub(r'[@\\]param%s?\s+%s' % (param_group, cpp_group), r'\n\n$Parameter ``\2``:\n\n', s) s = re.sub(r'[@\\]tparam%s?\s+%s' % (param_group, cpp_group), r'\n\n$Template parameter ``\2``:\n\n', s) # TODO (betsymcphail): Not tested s = re.sub(r'[@\\]retval\s+%s' % cpp_group, r'\n\n$Returns ``\1``:\n\n', s) # Ordering is significant for command names with a common prefix. for in_, out_ in ( ('result', 'Returns'), ('returns', 'Returns'), ('return', 'Returns'), ('attention', 'Attention'), ('authors', 'Authors'), ('author', 'Authors'), ('bug', 'Bug report'), ('copyright', 'Copyright'), ('date', 'Date'), ('deprecated', 'Deprecated'), ('exception', 'Raises'), ('invariant', 'Invariant'), ('note', 'Note'), ('post', 'Postcondition'), ('pre', 'Precondition'), ('remarks', 'Remark'), ('remark', 'Remark'), ('sa', 'See also'), ('see', 'See also'), ('since', 'Since'), ('extends', 'Extends'), ('throws', 'Raises'), ('throw', 'Raises'), ('test', 'Test case'), ('todo', 'Todo'), ('version', 'Version'), ('warning', 'Warning'), ): s = re.sub(r'[@\\]%s\s*' % in_, r'\n\n$%s:\n\n' % out_, s) s = re.sub(r'[@\\]details\s*', r'\n\n', s) s = re.sub(r'[@\\](?:brief|short)\s*', r'', s) # TODO (betsymcphail): Not tested s = re.sub(r'[@\\]ref\s+', r'', s) for start_, end_ in ( ('code', 'endcode'), ('verbatim', 'endverbatim') ): s = re.sub(r'[@\\]%s(?:\{\.\w+\})?\s?(.*?)\s?[@\\]%s' % (start_, end_), r"```\n\1\n```\n", s, flags=re.DOTALL) s = process_doxygen_system_tags(s, add_rst_preamble=True) # TODO (betsymcphail): Not tested s = re.sub(r'[@\\](?:end)?htmlonly\s+', r'', s) # These commands are always prefixed with an @ sign. # TODO (betsymcphail): Not tested s = re.sub(r'@[{}]\s*', r'', s) # Doxygen list commands. # TODO (betsymcphail): Not tested s = re.sub(r'[@\\](?:arg|li)\s+', r'\n\n* ', s) # Doxygen sectioning commands. s = replace_with_header(r'[@\\]section\s+\w+\s+(.*)', '=', s) s = replace_with_header(r'[@\\]subsection\s+\w+\s+(.*)', '-', s) s = re.sub(r'[@\\]subsubsection\s+\w+\s+(.*)', r'\n**\1**\n', s) # Doxygen LaTeX commands. s = re.sub(r'[@\\]f\$\s*(.*?)\s*[@\\]f\$', r':math:`\1`', s, flags=re.DOTALL) s = re.sub(r'[@\\]f\[\s*(.*?)\s*[@\\]f\]', r'\n\n.. math:: \1\n\n', s, flags=re.DOTALL) s = re.sub(r'[@\\]f\{([\w*]+)\}\s*(.*?)\s*[@\\]f\}', r'\n\n.. math:: \\begin{\1}\2\\end{\1}\n\n', s, flags=re.DOTALL) # Drake-specific Doxygen aliases. # TODO (betsymcphail): Not tested s = re.sub(r'[@\\]default\s+', r'\n$*Default:* ', s) # Omit tparam scalar type boilerplate; the python documentation already # presents this information in a more useful manner. # TODO (betsymcphail): Not tested s = re.sub(r'[@\\]tparam_default_scalar\s+', r'', s) s = re.sub(r'[@\\]tparam_nonsymbolic_scalar\s+', r'', s) s = re.sub(r'[@\\]tparam_double_only\s+', r'', s) # Remove these commands that take no argument. Ordering is significant for # command names with a common prefix. for cmd_ in ( '~english', '~', 'callergraph', 'callgraph', 'hidecallergraph', 'hidecallgraph', 'hideinitializer', 'nosubgrouping', 'privatesection', 'private', 'protectedsection', 'protected', 'publicsection', 'public', 'pure', 'showinitializer', 'static', 'tableofcontents', ): s = re.sub(r'[@\\]%s\s+' % cmd_, r'', s) # Remove these commands and their one optional single-word argument. # TODO (betsymcphail): Not tested for cmd_ in [ 'dir', 'file', ]: s = re.sub(r'[@\\]%s( +[\w:./]+)?\s+' % cmd_, r'', s) # Remove these commands and their one optional single-line argument. # TODO (betsymcphail): Not tested for cmd_ in [ 'mainpage', 'name' 'overload', ]: s = re.sub(r'[@\\]%s( +.*)?\s+' % cmd_, r'', s) # Remove these commands and their one single-word argument. Ordering is # significant for command names with a common prefix. for cmd_ in [ 'anchor', 'copybrief', 'copydetails', 'copydoc', 'def', 'dontinclude', 'enum', 'example', 'extends', 'htmlinclude', 'idlexcept', 'implements', 'includedoc', 'includelineno', 'include', 'latexinclude', 'memberof', 'namespace', 'package', 'relatedalso', 'related', 'relatesalso', 'relates', 'verbinclude', ]: s = re.sub(r'[@\\]%s\s+[\w:.]+\s+' % cmd_, r'', s) # Remove these commands and their one single-line argument. Ordering is # significant for command names with a common prefix. for cmd_ in [ 'addindex', 'fn', 'ingroup', 'line', 'property', 'skipline', 'skip', 'typedef', 'until', 'var', ]: s = re.sub(r'[@\\]%s\s+.*\s+' % cmd_, r'', s) # Remove this command and its one single-word argument and one # optional single-word argument. # TODO (betsymcphail): Not tested s = re.sub(r'[@\\]headerfile\s+[\w:.]+( +[\w:.]+)?\s+', r'', s) # Remove these commands and their one single-word argument and one # optional single-line argument. # TODO (betsymcphail): Not tested for cmd_ in [ 'addtogroup', 'weakgroup', ]: s = re.sub(r'[@\\]%s\s+[\w:.]( +.*)?\s+' % cmd_, r'', s) # Remove these commands and their one single-word argument and one # single-line argument. Ordering is significant for command names with a # common prefix. # TODO (betsymcphail): Not tested for cmd_ in [ 'snippetdoc', 'snippetlineno', 'snippet', ]: s = re.sub(r'[@\\]%s\s+[\w:.]\s+.*\s+' % cmd_, r'', s) # Remove these commands and their one single-word argument and two # optional single-word arguments. for cmd_ in [ 'category', 'class', 'interface', 'protocol', 'struct', 'union', ]: s = re.sub(r'[@\\]%s\s+[\w:.]+( +[\w:.]+){0,2}\s+' % cmd_, r'', s) # Remove these commands and their one single-word argument, one optional # quoted argument, and one optional single-word arguments. # TODO (betsymcphail): Not tested for cmd_ in [ 'diafile', 'dotfile', 'mscfile', ]: s = re.sub( r'[@\\]%s\s+[\w:.]+(\s+".*?")?(\s+[\w:.]+=[\w:.]+)?s+' % cmd_, r'', s) # Remove these pairs of commands and any text in between. for start_, end_ in ( ('cond', 'endcond'), ('docbookonly', 'enddocbookonly'), ('dot', 'enddot'), ('internal', 'endinternal'), ('latexonly', 'endlatexonly'), ('manonly', 'endmanonly'), ('msc', 'endmsc'), ('rtfonly', 'endrtfonly'), ('secreflist', 'endsecreflist'), ('startuml', 'enduml'), ('xmlonly', 'endxmlonly'), ): s = re.sub(r'[@\\]%s\s?(.*?)\s?[@\\]%s' % (start_, end_), r'', s, flags=re.DOTALL) # Some command pairs may bridge multiple comment blocks, so individual # start and end commands may appear alone. s = re.sub(r'[@\\]%s\s+' % start_, r'', s) s = re.sub(r'[@\\]%s\s+' % end_, r'', s) # Remove auto-linking character. Be sure to remove only leading % signs. # TODO (betsymcphail): Not tested s = re.sub(r'(\s+)%(\S+)', r'\1\2', s) # Doxygen escaped characters. # TODO (betsymcphail): Not tested s = re.sub(r'[@\\]n\s+', r'\n\n', s) # Ordering of ---, --, @, and \ is significant. # TODO (betsymcphail): Not tested for escaped_ in ( '---', '--', '::', r'\.', '"', '&', '#', '%', '<', '>', r'\$', '@', r'\\', ): s = re.sub(r'[@\\](%s)' % escaped_, r'\1', s) return s