From ad9525022e75139592f54f91413860ff230714c0 Mon Sep 17 00:00:00 2001 From: Ruben De Smet Date: Sun, 11 Jan 2015 16:56:33 +0100 Subject: [PATCH] Implemented a documentation compiler, initially for Aircraft Forum thread: http://forum.flightgear.org/viewtopic.php?f=72&t=25119 The documentation compiler will iterate over all Aircraft and generate LaTeX files for each aircraft. It will then compile them to a pdf. Please refer to the forum thread for an example of such a pdf file. It will also generate a separate pdf file just for the checklists. Currently, the compiler uses the checklist files and additional files in a Docs/ subdirectory of the aircraft. To use this script, you need python > 2.7 and a recent version of pdflatex with hyperref support installed. --- Docs/compile_docs.py | 226 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 226 insertions(+) create mode 100755 Docs/compile_docs.py diff --git a/Docs/compile_docs.py b/Docs/compile_docs.py new file mode 100755 index 000000000..e48aa137e --- /dev/null +++ b/Docs/compile_docs.py @@ -0,0 +1,226 @@ +#!/usr/bin/python + +import xml.etree.ElementTree as etree +import os +import shutil +import re +from subprocess import call + +FG_ROOT="../" + +def tex_escape(text): + """ + :param text: a plain text message + :return: the message escaped to appear correctly in LaTeX + """ + conv = { + '&': r'\&', + '%': r'\%', + '$': r'\$', + '#': r'\#', + '_': r'\_', + '{': r'\{', + '}': r'\}', + '~': r'\textasciitilde{}', + '^': r'\^{}', + '\\': r'\textbackslash{}', + '<': r'\textless', + '>': r'\textgreater', + } + regex = re.compile('|'.join(re.escape(unicode(key)) for key in sorted(conv.keys(), key = lambda item: - len(item)))) + return regex.sub(lambda match: conv[match.group()], text) + +def generate_airplane_latex(source): + """ + Generates the LaTeX files and directory structure for the procedures + """ + root = os.path.join("procedures", source['name']) + checklists_path = os.path.join(root, "checklists") + if(not os.path.isdir(root)): + os.mkdir(root) + if(not os.path.isdir(checklists_path)): + os.mkdir(checklists_path) + + # Write some warning notes + preamble = open(os.path.join(root, "preamble.tex"), "w") + + preamble.write("\\section{Preamble}") + preamble.write("This procedure list should be considered incomplete and it is not intended for real world use. It is automatically generated documentation intended for FlightGear flight simulator.") + + documentation_tex = open(os.path.join(root, source['dir'] + "_documentation.tex"), "w") + documentation_tex.write("\\documentclass{article}\n") + documentation_tex.write("\\usepackage{hyperref}\n\usepackage{graphicx}\n" + "\\usepackage{fancyhdr}\\pagestyle{fancy}\n\\lfoot{Intended for FlightGear}\\rfoot{Not for real world use!}") + documentation_tex.write("\\title{"+source['name']+" documentation}\n") + documentation_tex.write("\\author{FlightGear team}\n") + documentation_tex.write("\\renewcommand{\\familydefault}{\\sfdefault}") + documentation_tex.write("\\begin{document}\n") + documentation_tex.write("\\maketitle\n") + + checklist_tex = open(os.path.join(root, source['dir'] + "_checklist.tex"), "w") + checklist_tex.write("\\documentclass{article}\n") + checklist_tex.write("\\usepackage{fancyhdr}\\pagestyle{fancy}\n\\lfoot{Intended for FlightGear}\\rfoot{Not for real world use!}") + checklist_tex.write("\\begin{document}\n") + + # If there's a thumbnail available, copy it into the documentation and include it + # just before the beginning of the document, just after the title. + if(source['thumbnail']): + shutil.copyfile(FG_ROOT + "Aircraft/" + source['dir'] + "/thumbnail.jpg", + os.path.join(root, "thumbnail.jpg")) + documentation_tex.write("\\begin{figure}[h!]\\centering") + documentation_tex.write("\\includegraphics[width=5cm,height=5cm,keepaspectratio]{thumbnail.jpg}") + documentation_tex.write("\\end{figure}") + documentation_tex.write("\\tableofcontents\n") + documentation_tex.write("\\input{preamble.tex}\n") + + # If any additional documentation, copy it to the procedures directory and + # add it to the procedures.tex file + if source['extra_documentation']: + extra_docs_dir = os.path.join(FG_ROOT + "Aircraft/" + source['dir'], "Docs") + extra_docs_dest_dir = os.path.join("procedures/" + source['name'], "Docs") + if os.path.isdir(extra_docs_dest_dir): + shutil.rmtree(extra_docs_dest_dir) + shutil.copytree(extra_docs_dir, extra_docs_dest_dir) + documentation_tex.write("\\input{Docs/" + source['dir'] + "_documentation}\n") + + # If there are any checklist files parsed, write a title + if(source['sources'] > 0): + documentation_tex.write("\\input{checklists.tex}\n") + checklists_tex = open(os.path.join(root, "checklists.tex"), "w") + checklists_tex.write("\\section{Checklists}\n") + + # and the checklists themselves. + for xmlfile in source['sources']: + tree = etree.parse(xmlfile) + chkl_root = tree.getroot() + for checklist in chkl_root: + if checklist.tag != "checklist": + print "Unrecognised tag in", xmlfile + continue + title = checklist.find("title") + if title is None: + title = "Untitled" + else: + title = tex_escape(title.text) + items = [] + + for item in checklist.findall("item"): + name = item.find("name") + value = item.find("value") + if name is None or value is None: + continue + if name.text is None or value.text is None: + continue + items.append({ + 'name': tex_escape(name.text), + 'value': tex_escape(value.text) + }) + filename = title + ".tex" + filename = filename.replace("/", "_") + filename = filename.replace(" ", "_") + checklists_tex.write("\\subsection{" + title + "}\n") + checklist_tex.write("\\section*{" + title + "}\n") + checklists_tex.write("\\input{checklists/"+filename+"}\n") + checklist_tex.write("\\input{checklists/"+filename+"}\n") + f = open(os.path.join(checklists_path,filename), "w") + if len(items) > 0: + f.write("\\begin{description}\n") + for item in items: + f.write("\\item["+item['name']+"] \dotfill " + item['value']+"\n") + f.write("\\end{description}\n") + checklist_tex.write("\\clearpage\n") + + documentation_tex.write("\\end{document}\n") + checklist_tex.write("\\end{document}\n") + +def gather_aircraft_metadata(directory = FG_ROOT + "Aircraft"): + aircrafts = [] + for aircraft_directory in os.listdir(directory): + for rootfile in os.listdir(os.path.join(directory, aircraft_directory)): + rootfile = os.path.join(os.path.join(directory, aircraft_directory), rootfile) + if not (os.path.isfile(rootfile) and rootfile.endswith("-set.xml")): + continue + # Houston, we found a set.xml. Let's read it. This implies an aircraft + tree = etree.parse(rootfile) + name = aircraft_directory + + + for descr in tree.iter('description'): + name = descr.text + break + aircraft = { + 'sources' : [], + 'name' : name, + 'dir' : aircraft_directory, + 'thumbnail': False, + 'extra_documentation': False + } + # Check if the aircraft provides additional documentation + aircraft['extra_documentation'] = os.path.isdir(os.path.join(directory, + aircraft_directory + + "/Docs/")) & os.path.isfile(os.path.join(directory, aircraft_directory + + "/Docs/" + + aircraft_directory + + "_documentation.tex")) + # Check if there is a thumbnail + if os.path.isfile(os.path.join(directory, aircraft_directory + "/thumbnail.jpg")): + aircraft['thumbnail'] = True + + for checklist in tree.iter('checklists'): + aircraft['sources'].append(os.path.join(directory, aircraft_directory, checklist.attrib['include'])) + if 'include' in tree.getroot().attrib: + try: + tree = etree.parse(os.path.join(directory, aircraft_directory) + "/" + tree.getroot().attrib['include']) + for checklist in tree.iter('checklists'): + if 'include' in checklist.attrib: + aircraft['sources'].append(os.path.join(directory, aircraft_directory, checklist.attrib['include'])) + except: + pass + + + + if(len(aircraft['sources']) > 0 or aircraft['extra_documentation']): + aircrafts.append(aircraft) + + # Check if there are checklists that can be parsed + # for root, dirs, files in os.walk(os.path.join(directory, aircraft_directory), topdown=False): + # for name in files: + # if(name.endswith("checklists.xml")): + # aircraft['sources'].append(os.path.join(root,name)) + + return aircrafts + + +def compile_airplane_latex(source): + """ + Compile the procedures using pdflatex + """ + wd = os.getcwd() + root = os.path.join("procedures", source['name']) + os.chdir(root) + with open(os.devnull, "w") as fnull: + mainfile = source['dir'] + "_documentation.tex" + call(["pdflatex", "-interaction", "nonstopmode", mainfile], stdout=fnull,stderr=fnull) + call(["pdflatex", "-interaction", "nonstopmode", mainfile], stdout=fnull,stderr=fnull) + mainfile = source['dir'] + "_checklist.tex" + call(["pdflatex", "-interaction", "nonstopmode", mainfile], stdout=fnull,stderr=fnull) + call(["pdflatex", "-interaction", "nonstopmode", mainfile], stdout=fnull,stderr=fnull) + os.chdir(wd) + + +def generate_airplane_documentation(): + # First generate the index of what should be included in the + # documentation + aircrafts = gather_aircraft_metadata("../Aircraft") + if(len(aircrafts) == 0 ): + print "No aircraft found; wrong directory?" + if(not os.path.isdir("procedures")): + os.mkdir("procedures") + + # Then generate the documentation per airplane + for aircraft in aircrafts: + generate_airplane_latex(aircraft) + compile_airplane_latex(aircraft) + +# The main procedure +generate_airplane_documentation()