X-Git-Url: http://shamusworld.gotdns.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=build.py;h=d7753fe60a31441341bce9136b075d33032dff40;hb=01263a844bbe2fb45c5df31d1c6053d323c69446;hp=df16505ecb95b75dab47a2ca526b86fd418b01c2;hpb=dfd7223eb7ee339e9f03052737403e0027e15784;p=ardour-manual diff --git a/build.py b/build.py index df16505..d7753fe 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/' # # 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: @@ -260,9 +280,9 @@ def FindInternalLinks(fs): 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) # @@ -274,7 +294,6 @@ def FindInternalAnchors(fs): linkDict['"@@' + hdr['link'] + '"'] = '"#' + hdr['link'] + '"' linkDict['"@@' + hdr['link'] + '#'] = '"#' + hdr['link'] + '"' - return linkDict @@ -312,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: @@ -332,9 +351,7 @@ 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' + content = content + '\n' return content @@ -343,23 +360,43 @@ def BuildList(lst, fs, pagePos, cList): # Builds the sidebar for the one-page version # def BuildOnePageSidebar(fs): - content = '\n\n
      \n' + + 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 < fs[i]['level']: - content = content + '
          \n' + while lvl < level: + content = content + '
            \n' lvl = lvl + 1 - while lvl > fs[i]['level']: + + while lvl > level: content = content + '
          \n' lvl = lvl - 1 - content = content + '
        • ' + fs[i]['title'] + '
        • \n' + content = content + '
        • ' + txtlevel + fs[i]['title'] + '
        • \n' content = content + '
        \n' @@ -372,13 +409,14 @@ def BuildOnePageSidebar(fs): def CreateLinkSidebar(fs, pos, childList): # Build the list recursively from the top level nodes - #content = BuildList(FindTopLevelNodes(fs), fs, pos, childList) content = BuildList(FindTopLevelNodes(fs), fs, pos, childList) # Shove the TOC link and one file link at the top... - content = content[:7] + '
        Table of Contents
        \n' + content[7:] + active = ' class=active' if pos < 0 else '' + content = content.replace('
          ', '
            Table of Contents\n', 1) return content + # Preliminaries # We have command line arguments now, so deal with them @@ -386,54 +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('onepage-template.txt') +# 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) -onepage = onepage.replace('{{page.bootstrap_path}}', '/bootstrap-3.3.7') -onepage = onepage.replace('{{page.page_title}}', 'The Ardour Manual') +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 docuemnt's structure into a dictionary list +# 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 @@ -443,12 +492,12 @@ nodeChildren = FindChildren(fileStruct) 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() @@ -470,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; @@ -494,32 +537,17 @@ for header in fileStruct: print(header['title']) - # Handle TOC scriblings and one-page titles... - opl = '' - - if 'link' in header: - opl = ' id="' + header['link'] + '"' - else: - opl = ' id="' + header['filename'] + '"' - + # Handle TOC scriblings... if level == 0: toc = toc + '

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

            \n'; - oph = '

            ' + num2roman(levelNums[level]) + ' - ' + header['title'] + '

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

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

            \n' - oph = '

            ' + str(levelNums[level-1]) + '.' + str(levelNums[level]) + ' - ' + header['title'] + '

            \n'; elif level == 2: toc = toc + '\t\t

            ' + header['title'] + '

            \n' - oph = '

            ' + str(levelNums[level-2]) + '.' + str(levelNums[level-1]) + '.' + str(levelNums[level]) + ' - ' + header['title'] + '

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

            ' + header['title'] + '

            \n' - oph = '

            ' + str(levelNums[level-3]) + '.' + str(levelNums[level-2]) + '.' + str(levelNums[level-1]) + '.' + str(levelNums[level]) + ' - ' + header['title'] + '

            \n'; + toc = toc + '

            ' + header['title'] + '

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

            ' + header['title'] + '

            \n' - oph = '

            ' + str(levelNums[level-4]) + '.' + str(levelNums[level-3]) + '.' + str(levelNums[level-2]) + '.' + str(levelNums[level-1]) + '.' + str(levelNums[level]) + ' - ' + header['title'] + '

            \n'; - - - + toc = toc + '

            ' + header['title'] + '

            \n' # Make the 'this thing contains...' stuff if HaveChildren(fileStruct, pageNumber): @@ -561,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 = 'Edit on GitHub' + githubedit = 'Edit in GitHub' content = srcFile.read() srcFile.close() @@ -583,25 +612,35 @@ for header in fileStruct: # Add header information to the page if in dev mode if devmode: devnote ='' + content - # ----- One page version ----- + # ----- One page and PDF version ----- # Fix up any internal links opcontent = FixInternalLinks(oplinks, content, header['title']) + opcontent = remapheader(opcontent, level+2) - # Create the link sidebar - opsidebar = BuildOnePageSidebar(fileStruct) + # Create "one page" header + oph = '' + header['title'] + '\n'; # Set up the actual page from the template - onepage = onepage.replace('{% tree %}', opsidebar) - onepage = onepage.replace('{{ content }}', oph + '\n' + opcontent + '{{ content }}') + 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 ----- @@ -625,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() @@ -647,17 +686,45 @@ 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() # Create the one-page version of the documentation -onepageFile = open(siteDir + 'ardourmanual.html', 'w') +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 not quiet: +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 + from weasyprint.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.') +