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