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