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