xref: /petsc/doc/build_man_index.py (revision cac3c07dbc4e95423e22cb699bb64807a71d0bfe)
1#!/usr/bin/env python
2#!/bin/env python
3
4""" Reads in all the generated manual pages, and creates the index
5for the manualpages, ordering the indices into sections based
6on the 'Level of Difficulty'.
7"""
8
9import os
10import sys
11import re
12import glob
13import posixpath
14import subprocess
15
16numberErrors = 0
17HLIST_COLUMNS = 3
18
19# Read an optional header file, whose contents are first copied over
20# Use the level info, and print a formatted index table of all the manual pages
21#
22def printindex(outfilename, headfilename, levels, titles, tables):
23      global numberErrors
24      # Read in the header file
25      headbuf = ''
26      if posixpath.exists(headfilename) :
27            with open(headfilename, "r") as fd:
28                headbuf = fd.read()
29                headbuf = headbuf.replace('PETSC_DIR', '../../../')
30      else:
31            print('Error! SUBMANSEC header file "%s" does not exist' % headfilename)
32            print('Likley you introduced a new set of manual pages but did not add the header file that describes them')
33            numberErrors = numberErrors + 1
34
35      with open(outfilename, "w") as fd:
36          # Since it uses three columns we must remove right sidebar so all columns are displayed completely
37          # https://pydata-sphinx-theme.readthedocs.io/en/stable/user_guide/page-toc.html
38          fd.write(':html_theme.sidebar_secondary.remove: true\n')
39          fd.write(headbuf)
40          fd.write('\n')
41          all_names = []
42          for i, level in enumerate(levels):
43                title = titles[i]
44                if not tables[i]:
45                      if level != 'none' and level != 'deprecated':
46                          fd.write('\n## No %s routines\n' % level)
47                      continue
48
49                fd.write('\n## %s\n' % title)
50                fd.write('```{hlist}\n')
51                fd.write("---\n")
52                fd.write("columns: %d\n" % HLIST_COLUMNS)
53                fd.write("---\n")
54
55                for filename in tables[i]:
56                      path,name     = posixpath.split(filename)
57                      func_name,ext = posixpath.splitext(name)
58                      fd.write('- [](%s)\n' % name)
59                      all_names.append(name)
60                fd.write('```\n\n\n')
61
62          fd.write('\n## Single list of manual pages\n')
63          fd.write('```{hlist}\n')
64          fd.write("---\n")
65          fd.write("columns: %d\n" % HLIST_COLUMNS)
66          fd.write("---\n")
67          for name in sorted(all_names):
68              fd.write('- [](%s)\n' % name)
69          fd.write('```\n\n\n')
70
71
72# This routine takes in as input a dictionary, which contains the
73# alhabetical index to all the man page functions, and prints them all in
74# a single index page
75def printsingleindex(outfilename, alphabet_dict):
76      global numberErrors
77      with open(outfilename, "w") as fd:
78          fd.write("# Single Index of all PETSc Manual Pages\n\n")
79          fd.write(" Also see the [Manual page table of contents, by section](/manualpages/index.rst).\n\n")
80          for key in sorted(alphabet_dict.keys()):
81                fd.write("## %s\n\n" % key.upper())
82                fd.write("```{hlist}\n")
83                fd.write("---\n")
84                fd.write("columns: %d\n" % HLIST_COLUMNS)
85                fd.write("---\n")
86                function_dict = alphabet_dict[key]
87                for name in sorted(function_dict.keys()):
88                      if name:
89                            path_name = function_dict[name]
90                      else:
91                            path_name = ''
92                      fd.write("- [%s](%s)\n" % (name, path_name))
93                fd.write("```\n")
94
95
96# Read in the filename contents, and search for the formatted
97# String 'Level:' and return the level info.
98# Also adds the BOLD HTML format to Level field
99def modifylevel(filename,secname,edit_branch):
100      global numberErrors
101      with open(filename, "r") as fd:
102          buf = fd.read()
103
104      re_name = re.compile('\*\*Location:\*\*(.*)')  # As defined in myst.def
105      m = re_name.search(buf)
106      if m:
107        loc_html = m.group(1)
108        if loc_html:
109          pattern = re.compile(r"<A.*>(.*)</A>")
110          loc = re.match(pattern, loc_html)
111          if loc:
112              source_path = loc.group(1)
113              buf += "\n\n---\n[Edit on GitLab](https://gitlab.com/petsc/petsc/-/edit/%s/%s)\n\n" % (edit_branch, source_path)
114          else:
115              print("Warning. Could not find source path in %s" % filename)
116      else:
117        print('Error! No location in file:', filename)
118        numberErrors = numberErrors + 1
119
120      re_level = re.compile(r'(Level:)\s+(\w+)')
121      m = re_level.search(buf)
122      level = 'none'
123      if m:
124            level = m.group(2)
125      else:
126            print('Error! No level info in file:', filename)
127            numberErrors = numberErrors + 1
128
129      # Reformat level and location
130      tmpbuf = re_level.sub('',buf)
131      re_loc = re.compile('(\*\*Location:\*\*)')
132      tmpbuf = re_loc.sub('\n## Level\n' + level + '\n\n## Location\n',tmpbuf)
133
134      # Modify .c#,.h#,.cu#,.cxx# to .c.html#,.h.html#,.cu.html#,.cxx.html#
135      tmpbuf = re.sub('.c#', '.c.html#', tmpbuf)
136      tmpbuf = re.sub('.h#', '.h.html#', tmpbuf)
137      tmpbuf = re.sub('.cu#', '.cu.html#', tmpbuf)
138      tmpbuf = re.sub('.cxx#', '.cxx.html#', tmpbuf)
139
140      # Add footer links
141      outbuf = tmpbuf + '\n[Index of all %s routines](index.md)  \n' % secname + '[Table of Contents for all manual pages](/manualpages/index.rst)  \n' + '[Index of all manual pages](/manualpages/singleindex.md)  \n'
142
143      # write the modified manpage
144      with open(filename, "w") as fd:
145          fd.write(':orphan:\n'+outbuf)
146
147      return level
148
149# Go through each manpage file, present in dirname,
150# and create and return a table for it, wrt levels specified.
151def createtable(dirname,levels,secname,editbranch):
152      global numberErrors
153      listdir =  os.listdir(dirname)
154      mdfiles = [os.path.join(dirname,f) for f in listdir if f.endswith('.md')]
155      mdfiles.sort()
156      if mdfiles == []:
157            return None
158
159      table = []
160      for level in levels: table.append([])
161
162      for filename in mdfiles:
163            level = modifylevel(filename,secname,editbranch)
164            if level.lower() in levels:
165                  table[levels.index(level.lower())].append(filename)
166            else:
167                  print('Error! Unknown level \''+ level + '\' in', filename)
168                  numberErrors = numberErrors + 1
169      return table
170
171# This routine is called for each man dir. Each time, it
172# adds the list of manpages, to the given list, and returns
173# the union list.
174
175def addtolist(dirname,singlelist):
176      global numberErrors
177      mdfiles = [os.path.join(dirname,f) for f in os.listdir(dirname) if f.endswith('.md')]
178      mdfiles.sort()
179      if mdfiles == []:
180            print('Error! Empty directory:',dirname)
181            numberErrors = numberErrors + 1
182            return None
183
184      singlelist.extend(mdfiles)
185
186      return singlelist
187
188# This routine creates a dictionary, with entries such that each
189# key is the alphabet, and the vaue corresponds to this key is a dictionary
190# of FunctionName/PathToFile Pair.
191def createdict(singlelist):
192      global numberErrors
193      newdict = {}
194      for filename in singlelist:
195            path,name     = posixpath.split(filename)
196            # grab the short path Mat from /wired/path/Mat
197            junk,path     = posixpath.split(path)
198            index_char    = name[0:1].lower()
199            # remove the .name suffix from name
200            func_name,ext = posixpath.splitext(name)
201            if index_char not in newdict:
202                  newdict[index_char] = {}
203            newdict[index_char][func_name] = path + '/' + name
204
205      return newdict
206
207
208def getallmandirs(dirs):
209      """ Gets the list of man* dirs present in the doc dir. Each dir will have an index created for it. """
210      global numberErrors
211      mandirs = []
212      for filename in dirs:
213            path,name = posixpath.split(filename)
214            if name == 'RCS' or name == 'sec' or name == "concepts" or name  == "SCCS" : continue
215            if posixpath.isdir(filename):
216                  mandirs.append(filename)
217      return mandirs
218
219
220def main(PETSC_DIR):
221      global numberErrors
222      HEADERDIR = 'doc/manualpages/MANSECHeaders'
223      dirs      = glob.glob(os.path.join(PETSC_DIR,'doc','manualpages','*'))
224      mandirs   = getallmandirs(dirs)
225
226      levels = ['beginner','intermediate','advanced','developer','deprecated','none']
227      titles = ['Beginner - Basic usage',
228                'Intermediate - Setting options for algorithms and data structures',
229                'Advanced - Setting more advanced options and customization',
230                'Developer - Interfaces rarely needed by applications programmers',
231                'Deprecated - Functionality scheduled for removal in the future',
232                'None: Not yet cataloged']
233
234      singlelist = []
235      git_ref = subprocess.check_output(['git', 'rev-parse', 'HEAD']).rstrip()
236      try:
237        git_ref_release = subprocess.check_output(['git', 'rev-parse', 'origin/release']).rstrip()
238        edit_branch = 'release' if git_ref == git_ref_release else 'main'
239      except subprocess.CalledProcessError:
240        print("WARNING: checking branch for man page edit links failed")
241        numberErrors = numberErrors + 1
242        edit_branch = 'main'
243
244      for dirname in mandirs:
245            outfilename  = dirname + '/index.md'
246            dname,secname  = posixpath.split(dirname)
247            headfilename = PETSC_DIR + '/' + HEADERDIR + '/' + secname
248            table        = createtable(dirname,levels,secname,edit_branch)
249            if not table: continue
250            singlelist   = addtolist(dirname,singlelist)
251            printindex(outfilename,headfilename,levels,titles,table)
252
253      alphabet_dict = createdict(singlelist)
254      outfilename   = os.path.join(PETSC_DIR,'doc','manualpages','singleindex.md')
255      printsingleindex (outfilename,alphabet_dict)
256      if numberErrors:
257        raise RuntimeError('Stopping document build since errors were detected in generating manual page indices')
258
259if __name__ == '__main__':
260      main(os.path.abspath(os.environ['PETSC_DIR']))
261