X-Git-Url: http://shamusworld.gotdns.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=build.py;h=fdead7edd4922f2131469c9ff8305221f20554a4;hb=7641355d6145c4c3f36287fc9d5949285a5a3713;hp=1645b0accaee0e56cd4312356c675d178deba9a5;hpb=da762c68fe2f69ec424a3af5b1360825d2dd9e9e;p=ardour-manual diff --git a/build.py b/build.py index 1645b0a..fdead7e 100755 --- a/build.py +++ b/build.py @@ -4,26 +4,37 @@ # finished manual/website. # # by James Hammons -# (C) 2017 Underground Software +# (C) 2020 Underground Software +# +# Contributors: Ed Ward # # Remnants (could go into the master document as the first header) -#bootstrap_path: /bootstrap-3.3.7 -#page_title: The Ardour Manual - 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'@@[^#"]*') -githuburl = 'https://github.com/Ardour/manual/edit/master/include/' +findLinks = re.compile(r'"@@[^#"]*[#"]') # # Create an all lowercase filename without special characters and with spaces @@ -69,23 +80,16 @@ def ParseHeader(fileObj): # Turn a "part" name into an int # def PartToLevel(s): - level = -1 + lvl = {'part': 0, 'chapter': 1, 'subchapter': 2, 'section': 3, 'subsection': 4 } + + if s in lvl: + return lvl[s] - if s == 'part': - level = 0 - elif s == 'chapter': - level = 1 - elif s == 'subchapter': - level = 2 - elif s == 'section': - level = 3 - elif s == 'subsection': - level = 4 + return -1 - return level # -# Converts a integer to a roman number +# 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')] @@ -104,10 +108,10 @@ def num2roman(num): # def GetFileStructure(): fs = [] - fnames = [None]*6 + fnames = [None] * 6 content = '' grab = False - mf = open('master-doc.txt') + mf = open(global_master_doc) for ln in mf: if ln.startswith('---'): @@ -203,11 +207,27 @@ def GetParent(fs, pos): return pos +# +# Change the hierarchy of titles :

->,

->, so that the +# highest hyerarchy level is maxlevel +# +def remapheader(txt, maxlevel): + maxlvl=1 + # find the highest hierarchy level in the content + while maxlvl < 7 and txt.find('> is for Bootstrap pre-3.0 breadcrumbs = '
  • '+ fs[pos]['title'] + '
  • ' while pos >= 0: @@ -257,14 +277,29 @@ def FindInternalLinks(fs): for hdr in fs: if 'link' in hdr: - linkDict['@@' + hdr['link']] = '/' + hdr['filename'] + '/' + 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 occurances and replace +# 'link:' field in the part header. We have to find all occurrences and replace # them with the appropriate link. # def FixInternalLinks(links, content, title): @@ -296,15 +331,15 @@ def FixInternalLinks(links, content, title): # looking at currently # def BuildList(lst, fs, pagePos, cList): - content = '\n\n
    \n' + content = '
      \n' for i in range(len(lst)): curPos = lst[i] - nextPos = lst[i + 1] if i + 1 < len(lst) else len(fs) + nextPos = lst[i + 1] if i + 1 < len(lst) else len(fs) active = ' class=active' if curPos == pagePos else '' menuTitle = fs[curPos]['menu_title'] if 'menu_title' in fs[curPos] else fs[curPos]['title'] - content = content + '' + menuTitle + '' + content = content + '\t' + menuTitle + '\n' # If the current page is our page, and it has children, enumerate them if curPos == pagePos: @@ -316,12 +351,58 @@ def BuildList(lst, fs, pagePos, cList): elif (pagePos > curPos) and (pagePos < nextPos): content = content + BuildList(cList[curPos], fs, pagePos, cList) - content = content + '\n' + content = content + '
    \n' + + return content + + +# +# Builds the sidebar for the one-page version +# +def BuildOnePageSidebar(fs): + + content = '\n\n
      \n' + lvl = 0 + levelNums = [0] * 5 + + for i in range(len(fs)): + # Handle Part/Chapter/subchapter/section/subsection numbering + level = fs[i]['level'] + + if level < 2: + levelNums[2] = 0 + + levelNums[level] = levelNums[level] + 1; + j = level + txtlevel = '' + + while j > 0: #level 0 is the part number which is not shown + txtlevel = str(levelNums[j]) + '.' + txtlevel + j = j - 1 + + if len(txtlevel) > 0: + txtlevel = txtlevel[:-1] + ' - ' + + if 'link' in fs[i]: + anchor = fs[i]['link'] + else: + anchor = fs[i]['filename'] + + while lvl < level: + content = content + '
        \n' + lvl = lvl + 1 + + while lvl > level: + content = content + '
      \n' + lvl = lvl - 1 - content = content + '
    \n' + content = content + '
  • ' + txtlevel + fs[i]['title'] + '
  • \n' + + content = content + '\n' return content + # # Create link sidebar given a position in the list. # @@ -329,8 +410,9 @@ 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 in the top... - content = content[:7] + '
    Table of Contents
    \n' + content[7:] + # Shove the TOC link and one file link at the top... + active = ' class=active' if pos < 0 else '' + content = content.replace('
      ', '
        Table of Contents\n', 1) return content @@ -342,46 +424,65 @@ parser = argparse.ArgumentParser(description='A build script for the Ardour Manu parser.add_argument('-v', '--verbose', action='store_true', help='Display the high-level structure of the manual') parser.add_argument('-q', '--quiet', action='store_true', help='Suppress all output (overrides -v)') parser.add_argument('-d', '--devmode', action='store_true', help='Add content to pages to help developers debug them') +parser.add_argument('-p', '--pdf', action='store_true', help='Automatically generate PDF from content') args = parser.parse_args() verbose = args.verbose -quiet = args.quiet +noisy = not args.quiet devmode = args.devmode +pdf = args.pdf -if quiet: +# --quiet overrides --verbose, so tell it to shut up if user did both +if not noisy: verbose = False level = 0 fileCount = 0 -levelNums = [0]*6 +levelNums = [0] * 5 lastFile = '' page = '' +onepage = '' +pdfpage = '' toc = '' pageNumber = 0 -siteDir = './website/' - -if not quiet and devmode: +if noisy and devmode: print('Devmode active: scribbling extra junk to the manual...') -if os.access(siteDir, os.F_OK): - if not quiet: +if os.access(global_site_dir, os.F_OK): + if noisy: print('Removing stale HTML data...') - shutil.rmtree(siteDir) - -shutil.copytree('./source', siteDir) + shutil.rmtree(global_site_dir) +shutil.copytree('./source', global_site_dir) # Read the template, and fix the stuff that's fixed for all pages -temp = open('page-template.txt') +temp = open(global_screen_template) template = temp.read() temp.close() +template = template.replace('{{page.bootstrap_path}}', global_bootstrap_path) +template = template.replace('{{page.page_title}}', global_page_title) +if pdf: + template = template.replace('{{page.page_pdflink}}', global_pdflink) +else: + template = template.replace('{{page.page_pdflink}}', '') -template = template.replace('{{page.bootstrap_path}}', '/bootstrap-3.3.7') -template = template.replace('{{page.page_title}}', 'The Ardour Manual') +# Same as above, but for the "One-Page" version +temp = open(global_onepage_template) +onepage = temp.read() +temp.close() +onepage = onepage.replace('{{page.bootstrap_path}}', global_bootstrap_path) +onepage = onepage.replace('{{page.page_title}}', global_page_title) -# Parse out the master docuemnt's structure into a dictionary list +if pdf: + # Same as above, but for the PDF version + temp = open(global_pdf_template) + pdfpage = temp.read() + temp.close() + pdfpage = pdfpage.replace('{{page.page_title}}', global_page_title) + +# Parse out the master document's structure into a dictionary list fileStruct = GetFileStructure() # Build a quasi-tree structure listing children at level + 1 for each node @@ -389,13 +490,14 @@ nodeChildren = FindChildren(fileStruct) # Create a dictionary for translation of internal links to real links links = FindInternalLinks(fileStruct) +oplinks = FindInternalAnchors(fileStruct) -if not quiet: +if noisy: print('Found ' + str(len(links)) + ' internal link target', end='') print('.') if len(links) == 1 else print('s.') -if not quiet: - master = open('master-doc.txt') +if noisy: + master = open(global_master_doc) firstLine = master.readline().rstrip('\r\n') master.close() @@ -417,14 +519,8 @@ for header in fileStruct: level = header['level'] # Handle Part/Chapter/subchapter/section/subsection numbering - if level == 0: - levelNums[2] = 0 - elif level == 1: + if level < 2: levelNums[2] = 0 - elif level == 2: - levelNums[3] = 0 - elif level == 3: - levelNums[4] = 0 levelNums[level] = levelNums[level] + 1; @@ -445,13 +541,13 @@ for header in fileStruct: if level == 0: toc = toc + '

        Part ' + num2roman(levelNums[level]) + ': ' + header['title'] + '

        \n'; elif level == 1: - toc = toc + '

        Ch. ' + str(levelNums[level]) + ':  ' + header['title'] + '

        \n' + toc = toc + '\t

        Ch. ' + str(levelNums[level]) + ':  ' + header['title'] + '

        \n' elif level == 2: - toc = toc + '

        ' + header['title'] + '

        \n' + toc = toc + '\t\t

        ' + header['title'] + '

        \n' elif level == 3: - toc = toc + '

        ' + header['title'] + '

        \n' + toc = toc + '

        ' + header['title'] + '

        \n' elif level == 4: - toc = toc + '

        ' + header['title'] + '

        \n' + toc = toc + '

        ' + header['title'] + '

        \n' # Make the 'this thing contains...' stuff if HaveChildren(fileStruct, pageNumber): @@ -493,10 +589,11 @@ for header in fileStruct: # but the basic fundamental organizing unit WRT content is still the # chapter. githubedit = '' + if level > 0: if 'include' in header: srcFile = open('include/' + header['include']) - githubedit = '' + githubedit = 'Edit in GitHub' content = srcFile.read() srcFile.close() @@ -512,20 +609,44 @@ for header in fileStruct: else: content = '[something went wrong]' - # Fix up any internal links - content = FixInternalLinks(links, content, header['title']) - # Add header information to the page if in dev mode if devmode: devnote ='' + content + # ----- One page and PDF version ----- + + # Fix up any internal links + opcontent = FixInternalLinks(oplinks, content, header['title']) + opcontent = remapheader(opcontent, level+2) + + # Create "one page" header + oph = '' + header['title'] + '\n'; + + # Set up the actual page from the template + onepage = onepage.replace('{{ content }}', oph + '\n' + opcontent + '\n{{ content }}') + + if pdf: + if not 'pdf-exclude' in header: + pdfpage = pdfpage.replace('{{ content }}', oph + '\n' + opcontent + '\n{{ content }}') + else: + pdfpage = pdfpage.replace('{{ content }}', oph + '\n' + 'Please refer to the online manual.\n{{ content }}') + + # ----- Normal version ----- + + # Fix up any internal links + content = FixInternalLinks(links, content, header['title']) + # Set up the actual page from the template if 'style' not in header: page = re.sub("{% if page.style %}.*\n.*\n{% endif %}.*\n", "", template) @@ -543,10 +664,10 @@ for header in fileStruct: # Create the directory for the index.html file to go into (we use makedirs, # because we have to in order to accomodate the 'uri' keyword) - os.makedirs(siteDir + header['filename'], 0o775, exist_ok=True) + os.makedirs(global_site_dir + header['filename'], 0o775, exist_ok=True) # Finally, write the file! - destFile = open(siteDir + header['filename'] + '/index.html', 'w') + destFile = open(global_site_dir + header['filename'] + '/index.html', 'w') destFile.write(page) destFile.close() @@ -565,10 +686,48 @@ page = page.replace('{% prevnext %}', '') page = page.replace('{% githubedit %}', '') page = page.replace('{% breadcrumbs %}', '') -os.mkdir(siteDir + 'toc', 0o775) -tocFile = open(siteDir + 'toc/index.html', 'w') +os.mkdir(global_site_dir + 'toc', 0o775) +tocFile = open(global_site_dir + 'toc/index.html', 'w') tocFile.write(page) tocFile.close() -if not quiet: +# Create the one-page version of the documentation +onepageFile = open(global_site_dir + 'ardourmanual.html', 'w') +opsidebar = BuildOnePageSidebar(fileStruct) # create the link sidebar +onepage = onepage.replace('{% tree %}', opsidebar) +onepage = onepage.replace('{{ content }}', '') # cleans up the last spaceholder +onepageFile.write(onepage) +onepageFile.close() + +if pdf: + if noisy: + print('Generating the PDF...') + import logging + logger = logging.getLogger('weasyprint') + logger.addHandler(logging.StreamHandler()) + + # Create the PDF version of the documentation + pdfpage = pdfpage.replace('{% tree %}', opsidebar) # create the TOC + pdfpage = pdfpage.replace('{{ content }}', '') # cleans up the last spaceholder + pdfpage = pdfpage.replace('{{ today }}', global_today) + pdfpage = pdfpage.replace('src="/images/', 'src="images/') # makes images links relative + pdfpage = pdfpage.replace('url(\'/images/', 'url(\'images/') # CSS images links relative + # Write it to disk (optional, can be removed) + pdfpageFile = open(global_site_dir + 'pdf.html', 'w') + pdfpageFile.write(pdfpage) + pdfpageFile.close() + + # Generating the actual PDF with weasyprint (https://weasyprint.org/) + from weasyprint import HTML, __version__ + if float(__version__) < 53.0: # handle an API change + from weasyprint.fonts import FontConfiguration + else: + from weasyprint.text.fonts import FontConfiguration + + html_font_config = FontConfiguration() + doc = HTML(string = pdfpage, base_url = global_site_dir) + doc.write_pdf(global_site_dir + 'manual.pdf', font_config = html_font_config) + +if noisy: print('Processed ' + str(fileCount) + ' files.') +