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