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