Cloned SEACAS for EXODUS library with extra build files for internal package management.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

272 lines
12 KiB

import argparse
import os
import re
from shutil import copyfile, copytree
import subprocess
import sys
try:
doc_path = f"{os.sep}".join(os.path.abspath(__file__).split(os.sep)[:-2])
sys.path.append(doc_path)
project_path = f"{os.sep}".join(os.path.abspath(__file__).split(os.sep)[:-4])
sys.path.append(project_path)
except Exception as e:
print(f"Can not add project path to system path! Exiting!\nERROR: {e}")
exit(1)
def is_rst_file(file_path: str) -> bool:
""" Checks if file_path has .rst extension and if file_path is a file.
"""
if os.path.splitext(file_path)[-1] == '.rst' and os.path.isfile(file_path):
return True
return False
def change_paths_and_get_includes(source_file: str, src_file_path: str, start_path: str, rst_dir: str,
tribits_base_dir: str, copy_file: bool = True) -> tuple:
""" Changes paths in source file, to be relative to sphinx_path or parent .rst document.
Returns a tuple with .rst file content and includes(absolute_path, relative_to).
"""
with open(source_file, 'r') as src_file:
source_file_str = src_file.read()
source_file_list = list()
include_file_list = set()
for line in source_file_str.split('\n'):
splitted_line = line.split()
if 'include::' in splitted_line:
incl_index = splitted_line.index('include::')
path_index = incl_index + 1
if len(splitted_line) > path_index:
new_line = []
spaces_indented = line.find('.') # Below 'join()' statement adds a space!
if spaces_indented > 0:
new_line.append(' ' * (spaces_indented - 1))
new_line.extend(splitted_line[:path_index])
abs_path = os.path.abspath(os.path.join(src_file_path,
splitted_line[path_index]))
tbd = tribits_base_dir.split(os.sep)[1:]
path_elem = abs_path.split(os.sep)[len(tbd) + 1:]
new_path = os.path.join(rst_dir, *path_elem)
os.makedirs(os.path.dirname(new_path), exist_ok=True)
if not os.path.isfile(new_path) and copy_file:
copyfile(src=abs_path, dst=new_path, follow_symlinks=True)
if is_rst_file(file_path=new_path):
include_file_list.add(abs_path)
rel_path_from_sphinx_dir = os.path.relpath(path=new_path, start=start_path)
new_line.append(rel_path_from_sphinx_dir)
new_line = ' '.join(new_line)
# Make sure version is formatted correctly:
if ':Version:' in new_line:
new_line = f":Version:\n {new_line.split(':Version:')[-1]}"
source_file_list.append(new_line)
else:
source_file_list.append(line)
else:
source_file_list.append(line)
abs_path_str = '\n'.join(source_file_list)
return abs_path_str, include_file_list
class SphinxRstGenerator:
""" Changes include paths to relative to Sphinx build dir. Saves three main .rst docs files inside Sphinx dir.
"""
def __init__(self):
self.paths = {
'maintainers_guide': {
'src': os.path.join(doc_path, 'guides', 'maintainers_guide', 'TribitsMaintainersGuide.rst'),
'src_path': os.path.join(doc_path, 'guides', 'maintainers_guide'),
'final_path': os.path.join(doc_path, 'sphinx', 'maintainers_guide', 'index.rst'),
'sphinx_path': os.path.join(doc_path, 'sphinx', 'maintainers_guide'),
'title': 'TriBITS Maintainers Guide and Reference'},
'users_guide': {
'src': os.path.join(doc_path, 'guides', 'users_guide', 'TribitsUsersGuide.rst'),
'src_path': os.path.join(doc_path, 'guides', 'users_guide'),
'final_path': os.path.join(doc_path, 'sphinx', 'users_guide', 'index.rst'),
'sphinx_path': os.path.join(doc_path, 'sphinx', 'users_guide'),
'title': 'TriBITS Users Guide and Reference'},
'build_ref': {
'src': os.path.join(doc_path, 'build_ref', 'TribitsBuildReference.rst'),
'src_path': os.path.join(doc_path, 'build_ref'),
'final_path': os.path.join(doc_path, 'sphinx', 'build_ref', 'index.rst'),
'sphinx_path': os.path.join(doc_path, 'sphinx', 'build_ref'),
'title': 'Generic TriBITS Project, Build, Test, and Install Reference Guide'}}
self.rst_dir = os.path.join(doc_path, 'sphinx', 'copied_files')
self.tribits_base_dir = self._cli()
self.already_modified_files = set()
self.create_rst_dir()
self.build_docs()
@staticmethod
def _cli() -> str:
""" Support for common line arguments. """
parser = argparse.ArgumentParser()
parser.add_argument("--copy-base-dir", help="Path to TriBITS base directory")
args = parser.parse_args()
abs_path = os.path.abspath(args.copy_base_dir)
if not abs_path or not os.path.exists(abs_path):
print(f"\n==> Path: `{abs_path}` is not correct!")
sys.exit(1)
print(f"Provided TriBITS base dir: {abs_path}")
return abs_path
def create_rst_dir(self) -> None:
""" Creates copied_files directory in Sphinx directory. All include files will be copy
there.
"""
if self.rst_dir is not None:
if not os.path.exists(self.rst_dir):
os.makedirs(self.rst_dir)
@staticmethod
def build_docs() -> None:
""" Builds TriBITS documentation based on shell scripts.
"""
build_script_path = os.path.join(doc_path, 'build_docs.sh')
subprocess.call([build_script_path, '--skip-final-generation'])
@staticmethod
def run_sphinx(cwd: str) -> None:
""" Runs Sphinx for each documentation.
"""
sphinx_command = ["make", "html"]
subprocess.call(sphinx_command, cwd=cwd)
def combine_documentation(self, docs_dir: str, change_url_to_landing_page: bool = True,
change_title_of_docs_main_page: bool = True, title: str = '') -> None:
""" Renames and moves directory of generated static pages into combined directory
"""
new_name = os.path.split(docs_dir)[-1]
dir_to_rename = os.path.join(docs_dir, '_build', 'html')
new_name_path = os.path.join(docs_dir, '_build', new_name)
os.rename(src=dir_to_rename, dst=new_name_path)
static_dir = os.path.join(doc_path, 'sphinx', 'combined_docs', new_name)
copytree(src=new_name_path, dst=static_dir)
if change_url_to_landing_page:
self.change_url_to_landing_page(docs_static_dir=static_dir)
if change_title_of_docs_main_page:
self.change_title_of_docs_main_page(docs_static_dir=static_dir, new_title=title)
@staticmethod
def change_url_to_landing_page(docs_static_dir: str) -> None:
""" Changes home url of documentation page, so it points to landing page.
"""
index_html = os.path.join(docs_static_dir, 'index.html')
with open(index_html, 'r') as index_read:
index_str = index_read.read()
repl_url = index_str.replace('<a href="#" class="icon icon-home"> TriBITS',
'<a href="../index.html" class="icon icon-home"> TriBITS')
with open(index_html, 'w') as index_write:
index_write.write(repl_url)
@staticmethod
def change_title_of_docs_main_page(docs_static_dir: str, new_title: str) -> None:
""" Changes home url of documentation page, so it points to landing page.
"""
index_html = os.path.join(docs_static_dir, 'index.html')
with open(index_html, 'r') as index_read:
index_str = index_read.read()
repl_url = re.sub('(<title>1 Introduction &mdash;).+(documentation</title>)',
f'<title>{new_title}</title>', index_str)
with open(index_html, 'w') as index_write:
index_write.write(repl_url)
@staticmethod
def save_rst(file_path: str, file_content: str) -> None:
""" Saves .rst file with given pathh and content
"""
with open(file_path, 'w') as dest_file:
dest_file.write(file_content)
def generate_rst(self, source_file: str, final_path: str = None, src_path: str = None,
start_path: str = None) -> set:
""" Generate correct links in .rst files, so Sphinx can find them
"""
if final_path is None:
overwrite_source = True
else:
overwrite_source = False
file_content, includes = change_paths_and_get_includes(source_file=source_file, src_file_path=src_path,
start_path=start_path, rst_dir=self.rst_dir,
tribits_base_dir=self.tribits_base_dir)
if overwrite_source:
self.save_rst(file_path=source_file, file_content=file_content)
else:
self.save_rst(file_path=final_path, file_content=file_content)
return includes
def remove_title_numbering(self) -> None:
""" Removes numbering from docs.
"""
for doc_name, sources in self.paths.items():
str_to_replace = '.. rubric::'
with open(sources.get('final_path'), 'r') as src_file:
org_str = src_file.read()
org_list = org_str.split('\n')
if org_list[0].startswith('====='):
del org_list[0]
if org_list[1].startswith('====='):
del org_list[1]
org_list[0] = f'{str_to_replace} {org_list[0]}'
mod_str = '\n'.join(org_list)
with open(sources.get('final_path'), 'w') as dst_file:
dst_file.write(mod_str)
def main(self):
""" Main routine goes for nested .rst docs
"""
child_rst = set()
for doc_name, sources in self.paths.items():
includes = self.generate_rst(source_file=sources.get('src'), src_path=sources.get('src_path'),
final_path=sources.get('final_path'), start_path=sources.get('sphinx_path'))
child_rst.update(includes)
self.already_modified_files.update(child_rst)
tbd = self.tribits_base_dir.split(os.sep)[1:]
child_rst_lst = list(child_rst)
sphinx_rel_path = self.paths.get('maintainers_guide').get('sphinx_path')
grand_child_rst = set()
for child in child_rst_lst:
path_elem = child.split(os.sep)[len(tbd) + 1:]
final_path = os.path.join(self.rst_dir, *path_elem)
os.makedirs(os.path.dirname(final_path), exist_ok=True)
src_path = os.path.split(child)[0]
includes_grand = self.generate_rst(source_file=child, src_path=src_path,
final_path=final_path, start_path=sphinx_rel_path)
grand_child_rst.update(includes_grand)
grand_child_rst_lst = [gc_rst for gc_rst in grand_child_rst if gc_rst not in self.already_modified_files]
grand_grand_child_rst = set()
for grand_child in grand_child_rst_lst:
path_elem = grand_child.split(os.sep)[len(tbd) + 1:]
final_path = os.path.join(self.rst_dir, *path_elem)
os.makedirs(os.path.dirname(final_path), exist_ok=True)
src_path = os.path.split(grand_child)[0]
includes_grand_grand = self.generate_rst(source_file=grand_child, src_path=src_path,
final_path=final_path, start_path=sphinx_rel_path)
grand_grand_child_rst.update(includes_grand_grand)
if not grand_grand_child_rst:
print('DONE! ALL GOOD!\n')
else:
print('NOT DONE!\n')
self.remove_title_numbering()
print('===> Generating Sphinx documentation:\n')
for doc_name, sources in self.paths.items():
cwd = sources.get('sphinx_path')
print(f'===> Generating {doc_name}\n')
self.run_sphinx(cwd=cwd)
self.combine_documentation(docs_dir=cwd, title=sources.get('title'))
if __name__ == '__main__':
SphinxRstGenerator().main()