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