#!/usr/bin/python3 # # Script to take the master document and ancillary files and create the # finished manual/website. # # by James Hammons # (C) 2020 Underground Software # # Contributors: Ed Ward # # Remnants (could go into the master document as the first header) import os import re import shutil import argparse import datetime # Global vars global_bootstrap_path = '/bootstrap-3.3.7' global_page_title = 'The Ardour Manual' global_site_dir = './website/' global_manual_url = 'http://manual.ardour.org' global_githuburl = 'https://github.com/Ardour/manual/edit/master/include/' global_screen_template = 'page-template.html' global_onepage_template = 'onepage-template.html' global_pdf_template = 'pdf-template.html' global_master_doc = 'master-doc.txt' global_pdflink = '' from datetime import datetime global_today = datetime.today().strftime('%Y-%m-%d') # This matches all *non* letter/number, ' ', '.', '-', and '_' chars cleanString = re.compile(r'[^a-zA-Z0-9 \._-]+') # This matches new 'unbreakable' links, up to the closing quote or anchor findLinks = re.compile(r'"@@[^#"]*[#"]') # # Create an all lowercase filename without special characters and with spaces # replaced with dashes. # def MakeFilename(s): global cleanString # Clean up the file name, removing all non letter/number or " .-_" chars. # Also, convert to lower case and replace all spaces with dashes. fn = cleanString.sub('', s).lower().replace(' ', '-') # Double dashes can creep in from the above replacement, so we check for # that here. fn = fn.replace('--', '-') return fn # # Parse headers into a dictionary # def ParseHeader(fileObj): header = {} while (True): hdrLine = fileObj.readline().rstrip('\r\n') # Break out of the loop if we hit the end of header marker if hdrLine.startswith('---'): break # Check to see that we have a well-formed header construct match = re.findall(': ', hdrLine) if match: # Parse out foo: bar pairs & put into header dictionary a = re.split(': ', hdrLine, 1) header[a[0]] = a[1] return header # # Turn a "part" name into an int # def PartToLevel(s): lvl = {'part': 0, 'chapter': 1, 'subchapter': 2, 'section': 3, 'subsection': 4 } if s in lvl: return lvl[s] return -1 # # Converts a integer to a Roman numeral # def num2roman(num): num_map = [(1000, 'M'), (900, 'CM'), (500, 'D'), (400, 'CD'), (100, 'C'), (90, 'XC'), (50, 'L'), (40, 'XL'), (10, 'X'), (9, 'IX'), (5, 'V'), (4, 'IV'), (1, 'I')] roman = '' while num > 0: for i, r in num_map: while num >= i: roman += r num -= i return roman # # Capture the master document's structure (and content, if any) in a list # def GetFileStructure(): fs = [] fnames = [None] * 6 content = '' grab = False mf = open(global_master_doc) for ln in mf: if ln.startswith('---'): # First, stuff any content that we may have read into the current # header's dictionary if grab: fs[-1]['content'] = content grab = False content = '' # Then, get the new header and do things to it hdr = ParseHeader(mf) level = PartToLevel(hdr['part']) hdr['level'] = level fnames[level] = MakeFilename(hdr['title']) # Ickyness--user specified URIs if 'uri' in hdr: hdr['filename'] = hdr['uri'] else: fullName = '' for i in range(level + 1): fullName = fullName + fnames[i] + '/' # Strip trailing '/' on filename hdr['filename'] = fullName[:-1] fs.append(hdr) if ('include' not in hdr) and (level > 0): grab = True else: if grab: content = content + ln # Catch the last file, since it would be missed above if grab: fs[-1]['content'] = content mf.close() return fs # # Determine if a particular node has child nodes # def HaveChildren(fs, pos): # If we're at the end of the list, there can be no children if pos == len(fs) - 1: return False # If the next node is at a lower level than the current node, we have # children. if fs[pos]['level'] < fs[pos + 1]['level']: return True # Otherwise, no children at this node. return False # # Get the children at this level, and return them in a list # def GetChildren(fs, pos): children = [] pos = pos + 1 childLevel = fs[pos]['level'] while fs[pos]['level'] >= childLevel: if fs[pos]['level'] == childLevel: children.append(pos) pos = pos + 1 # Sanity check if pos == len(fs): break return children # # Get the parent at this level # def GetParent(fs, pos): thisLevel = fs[pos]['level'] pos = pos - 1 while pos >= 0 and fs[pos]['level'] >= thisLevel: pos = pos - 1 return pos # # Change the hierarchy of titles : h1->hn, h2->hn+1, etc... n being delta-1 # def reheader(txt, delta): for i in range(6, 0, -1): txt = txt.replace('' while pos >= 0: pos = GetParent(fs, pos) if pos >= 0: breadcrumbs='
  • '+ fs[pos]['title'] + '
  • '+ breadcrumbs breadcrumbs = '' return breadcrumbs # # Make an array of children attached to each node in the file structure # (It's a quasi-tree structure, and can be traversed as such.) # def FindChildren(fs): childArray = [] for i in range(len(fs)): if HaveChildren(fs, i): childArray.append(GetChildren(fs, i)) else: childArray.append([]) return childArray # # Make an array of the top level nodes in the file structure # def FindTopLevelNodes(fs): level0 = [] for i in range(len(fs)): if fs[i]['level'] == 0: level0.append(i) return level0 # # Find all header links and create a dictionary out of them # def FindInternalLinks(fs): linkDict = {} for hdr in fs: if 'link' in hdr: linkDict['"@@' + hdr['link'] + '"'] = '"/' + hdr['filename'] + '/"' linkDict['"@@' + hdr['link'] + '#'] = '"/' + hdr['filename'] + '/index.html#' return linkDict # # Same as above, but create anchors (for the one-page version) # def FindInternalAnchors(fs): linkDict = {} for hdr in fs: if 'link' in hdr: linkDict['"@@' + hdr['link'] + '"'] = '"#' + hdr['link'] + '"' linkDict['"@@' + hdr['link'] + '#'] = '"#' + hdr['link'] + '"' return linkDict # # Internal links are of the form '@@link-name', which are references to the # 'link:' field in the part header. We have to find all occurrences and replace # them with the appropriate link. # def FixInternalLinks(links, content, title): global findLinks match = findLinks.findall(content) missing = [] if len(match) > 0: for s in match: if s in links: content = content.replace(s, links[s]) else: missing.append(s) # Report missing link targets to the user (if any) if len(missing) > 0: print('\nMissing link target' + ('s' if len(missing) > 1 else '') + ' in "' + title + '":') for s in missing: print(' ' + s) print() return content # # Recursively build a list of links based on the location of the page we're # looking at currently # def BuildList(lst, fs, pagePos, cList): content = '\n' return content # # Builds the sidebar for the one-page version # def BuildOnePageSidebar(fs): content = '\n\n\n' return content # # Create link sidebar given a position in the list. # def CreateLinkSidebar(fs, pos, childList): # Build the list recursively from the top level nodes content = BuildList(FindTopLevelNodes(fs), fs, pos, childList) # Shove the TOC link and one file link at the top... active = ' class=active' if pos < 0 else '' content = content.replace('