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')] 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): 220 global numberErrors 221 HEADERDIR = 'doc/manualpages/MANSECHeaders' 222 dirs = glob.glob(os.path.join(PETSC_DIR,'doc','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 subprocess.CalledProcessError: 239 print("WARNING: checking branch for man page edit links failed") 240 numberErrors = numberErrors + 1 241 edit_branch = 'main' 242 243 for dirname in mandirs: 244 outfilename = dirname + '/index.md' 245 dname,secname = posixpath.split(dirname) 246 headfilename = PETSC_DIR + '/' + HEADERDIR + '/' + secname 247 table = createtable(dirname,levels,secname,edit_branch) 248 if not table: continue 249 singlelist = addtolist(dirname,singlelist) 250 printindex(outfilename,headfilename,levels,titles,table) 251 252 alphabet_dict = createdict(singlelist) 253 outfilename = os.path.join(PETSC_DIR,'doc','manualpages','singleindex.md') 254 printsingleindex (outfilename,alphabet_dict) 255 if numberErrors: 256 raise RuntimeError('Stopping document build since errors were detected in generating manual page indices') 257 258if __name__ == '__main__': 259 main(os.path.abspath(os.environ['PETSC_DIR'])) 260