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