]> Shamusworld >> Repos - ardour-manual-diverged/commitdiff
Update to minimal build. minimal
authorShamus Hammons <jlhamm@acm.org>
Sat, 4 Feb 2017 02:01:38 +0000 (20:01 -0600)
committerShamus Hammons <jlhamm@acm.org>
Sat, 4 Feb 2017 02:01:38 +0000 (20:01 -0600)
README.md
build.py
build.rb [deleted file]
explode.py
implode.py
include/favorite-plugins-window.html
include/kde-plasma-5.html
master-doc.txt

index 581a9c634d1bd5329f6e6cb626b097df98fb7cd9..4645752e1be99a1261a89473a1f0ff3758eece7d 100644 (file)
--- a/README.md
+++ b/README.md
@@ -1,10 +1,7 @@
 
 # The Ardour Manual
 
-
-This is the project that generates the static ardour manual website available at [manual.ardour.org](http://manual.ardour.org).
-
-The site is built using ruby (I use 1.9[.3]) and [liquid](http://liquidmarkup.org/), a ruby gem.
+This is the project that generates the static ardour manual website available at [manual.ardour.org](http://manual.ardour.org). The site is built using python 3.
 
 ### Get the code
 
@@ -15,80 +12,45 @@ The site is built using ruby (I use 1.9[.3]) and [liquid](http://liquidmarkup.or
 
 There are 2 different types of content:
 
-- special `_manual` content
-- normal content
-
-### Special `_manual` content
-
-This is content that ends up as part of the tree on the left.
-
-The _raw_ content is in `_manual/` directory and has a naming convention as follows:
+- a master document which describes the overall structure of the manual
+- normal content, which is described in the master document
 
-    # content for a page at http://manual.ardour.org/<slug>/
+### The Master Document
 
-    <ordering>_<slug>.<html|md|textile>
-       ^          ^     ^
-       |          |     |
-       |          |   extension is removed later
-       |          |
-       |     ends up as part of URL
-       |
-     only used for ordering
+This is a text file (master-doc.txt) which describes the structure of the manual. It does this
+through headers which tell the build script where the content lives, what its
+relationship to the overall structure is, as well as a few other things.
 
+All headers have a similar structure, and have to have at least the following
+minimal structure:
 
-    # a folder for subcontent is like this
-
-    <ordering>_<slug>/
-
-    # more things can then go in here for http://manual.ardour.org/<slug>/<slug2>/
+    ---
+    title: Some Wordy and Expressive Title
+    part: part
+    ---
 
-    <ordering>_<slug>/<ordering2>_<slug2>.html
+Keywords that go into the header are of the form:
 
-So, for example:
+       keyword: value
 
+Here are the keywords you can put in, and a brief description of what they do:
 
-| this file                        | appears at url      |
-|--------------------------------------------------------|
-| _manual/01_main.html             | /main/              |
-| _manual/01_main/01_subpage.html  | /main/subpage/      |
 
+| Keyword | Meaning  |
+| ------- | -------- |
+| title   | Sets the title for the content that follows |
+| menu_title | Sets the title for the content that follows which will appear in the menu link sidebar. If this is not specified, it defaults to the value of the `title` keyword |
+| part    | Sets the hierarchy for the content that follows. It must be one of the following (listed in order of lowering hierarchy): part, chapter, subchapter, section, subsection.  |
+| link    | Sets the unbreakable link to the content that follows. Links in the *content* should be prefixed with a double at-sign (@@) to tell the build system that the link is an internal one |
+| include | Tells the build system that the content lives in an external file; these normally live in the `include/` directory. Note that the filename should **not** be prefixed with `include/` |
+| exclude | Tells the `implode` and `explode` scripts that file referred to by the `include` keyword should be ignored. Note that the value of this keyword is ignored |
+| style   | Sets an alternate CSS stylesheet; the name should match the one referred to (sans the `.css` suffix) in the `source/css` directory |
 
 ### Normal content
 
-This is anything else, css files, images, fixed pages, layouts. This content lives in the `source` directory.
-
-If you added `source/images/horse.png` is would be available at the url `/images/horse.png` after publishing it.
-
-Content processing is applied to normal content if it has the correct header as described below.
-
-
-## Content processing
-
-Three types of content can have special processing done.
-
-- `.html` liquid/HTML files
-- `.md` markdown files (NOPE!)
-- `.textile` textile files (NOPE!)
-
-All files to be processed should also have a special header at the top too:
-
-    ---
-    layout: default
-    title: Some Very Wordy and Expressive Title
-    menu_title: Some Title
-    ---
-
-    <p>My Actual Content</p>
-
-The `title` field will end up as an `h1` in the right panel. The `menu_title` is what is used in the menu tree on the left (if not preset it will default to using `title`).
-
-### `.html` files
-
-These are almost normal html, but extended with [Liquid templates](http://liquidmarkup.org/). There are a couple of special tags created for this project.
-
-- `{% tree %}` is what shows the manual structure in the left column
-- `{% children %}` shows the immediate list of children for a page
+Manual content goes into the `include/` directory (or in the Master Document itself); and consists of normal HTML, sans the usual headers that is normally seen in regular HTML web pages. Any other content, such as css files, images, files and fixed pages goes into the `source/` directory.
 
+Adding `source/images/horse.png` makes it available at the url `/images/horse.png` after publishing it; things work similarly for `source/files/` and `source/css/`.
 
 ## More Advanced Stuff
 
@@ -98,19 +60,17 @@ notes just in case you decide to anyway.
 ### Run it locally
 
 You may want the manual available on a machine that doesn't have constant
-internet access. You will need `git`, `ruby`, and the ruby gem `liquid` installed.
+internet access. You will need `git`, and `python` installed.
 
 1. Download code and build manual
 
   ```
   git clone <repo-url> ardour-manual
   cd ardour-manual
-  cp -r source _site
-  ruby ./build.rb
-  chmod -R a+rx _site
+  ./build.py
   ```
 
-2. open `ardour-manual/_site/index.html` in your favorite web browser
+2. open `ardour-manual/website/index.html` in your favorite web browser
 
   If this page doesn't open and function correctly, follow these optional steps to serve up the page with nginx.
   N.B.: Step 2 will *never* work; you *must* install nginx if you want to see it!
@@ -123,7 +83,7 @@ internet access. You will need `git`, `ruby`, and the ruby gem `liquid` installe
       listen 80;
       server_name localhost;
 
-      root ...path_to_.../ardour-manual/_site;
+      root ...path_to_.../ardour-manual/website;
       index index.html;
   }
   ```
@@ -134,30 +94,8 @@ internet access. You will need `git`, `ruby`, and the ruby gem `liquid` installe
 
 6. The manual will now be available at http://localhost
 
-### manual.rb plugin
-
-Much of the functionality comes from `_plugins/manual.rb` - it takes the _manual format_ (contained in `_manual/`) and mushes it around a bit into a tmp directory.
-
-This is to enable the directory tree to be understood, child page lists to be constructed, clean URLs, and the correct ordering of pages maintained.
-
-### Clean URLs
-
-To allow the clean URLs (no `.html` extension) _and_ to support simple hosting (no `.htaccess` or apache configuration required) each page ends up in it's own directory with an `index.html` page for the content.
-
-E.g. `02_main/05_more/02_blah.html` after all processing is complete would end up in `_site/main/more/blah/index.html`.
-
-The page format contained in the `_manual/` directory is different to the final rendered output (see special `_manual` content above) to make it simple to create content (you don't need to think about the `index.html` files).
-
-### New Stuff
-
-The above will have to be revised eventually, but for now, here's what's what:
-
-1. All content lives in master-doc.txt and in include/ .
-
-2. Run the 'build' scripts:
-
-        ./build.py && ./build.rb
+### Helper scripts: `implode` and `explode`
 
-3. Now _site/ should contain the correct built site. Have fun! ;-)
+The `implode` and `explode` scripts exist in order to accomodate different working styles. `implode` takes all the files referenced by the `include` keywords in the headers in the Master Document and automagically puts them into the Master Document in their proper places. Note that any header that has an `exclude` keyword will remain in the `include/` directory. `explode` does the inverse of `implode`; it takes all the content in the Master Document and blows it into individual files in the `include/` directory. Filenames are automagically derived from the value of the `title` keyword.
 
 
index 2a8c8493530b119d41c0808ecdda7bf50d654665..8ac84d4efb4e93859685ad7fb7f0962ebad7de26 100755 (executable)
--- a/build.py
+++ b/build.py
-#!/usr/bin/python
+#!/usr/bin/python3
 #
-# Script to take the master document and create something that build.rb wants.
-# Eventually, this will replace build.rb
-#
-# Ultimately, we will write directly to the finished web site structure instead
-# of creating this half-assed thing that we then rely on the other ruby script
-# to handle. It works, but isn't optimal. :-P
+# Script to take the master document and ancillary files and create the
+# finished manual/website.
 #
 # by James Hammons
 # (C) 2017 Underground Software
 #
 
+# Remnants (could go into the master document as the first header)
+
+#bootstrap_path: /bootstrap-2.2.2
+#page_title: The Ardour Manual
+
 import os
 import re
 import shutil
+import argparse
+
 
 #
 # Create an all lowercase filename without special characters and with spaces
 # replaced with dashes.
 #
 def MakeFilename(s):
-       # This RE is shitty, but I can't think of a better one right now
-       fn = re.sub("[?!'&#:;_*()/\\,.]+", "", s)
+       # Cleans up the file name, removing all non ASCII or .-_ chars
+       fn = re.sub(r'[^.\-_a-zA-Z0-9 ]', '', s)
        fn = fn.lower()
        fn = fn.replace(' ', '-')
        return fn
 
 
-# Preliminaries
+#
+# Parse headers into a dictionary
+#
+def ParseHeader(fileObj):
+       header = {}
 
-buildDir = './_manual.munge/'
+       while (True):
+               hdrLine = fileObj.readline().rstrip('\r\n')
 
-if os.access(buildDir, os.F_OK):
-       print('Removing stale manual data...')
-       shutil.rmtree(buildDir)
+               # Break out of the loop if we hit the end of header marker
+               if hdrLine.startswith('---'):
+                       break
 
-os.mkdir(buildDir, 0o774)
-
-# Yeah, need to make a symlink in include/ too :-P
-# [this will go away when the rewrite happens]
-if (os.access('include/_manual', os.F_OK) == False):
-       os.symlink('../_manual/', 'include/_manual')
+               # 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]
 
-# Here we go!
+       return header
 
-master = open('master-doc.txt')
 
-toc = open(buildDir + '00_toc.html', 'w')
-toc.write('---\n' + 'title: Ardour Table of Contents\n' + '---\n\n')
+#
+# Turn a "part" name into an int
+#
+def PartToLevel(s):
+       level = -1
+
+       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 level
 
+#
+# Converts a integer to a roman number
+#
+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 = ''
 
-roman = [ '0', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X',
-       'XI', 'XII', 'XIII', 'XIV', 'XV', 'XVI', 'XVII', 'XVIII', 'XIX', 'XX',
-       'XXI', 'XXII', 'XXIII', 'XXIV', 'XXV', 'XXVI', 'XXVII', 'XXVIII', 'XXIX', 'XXX' ]
-level = 0
-lastLevel = 0
-lineCount = 0
-levelNames = [None]*6
-levelNums = [0]*6
-writingToFile = False
-# This is a shitty way of getting a filehandle...
-htmlFile = open('master-doc.txt')
-htmlFile.close()
-lastFile = ''
+       while num > 0:
+               for i, r in num_map:
+                       while num >= i:
+                               roman += r
+                               num -= i
 
-firstLine = master.readline().rstrip('\r\n')
+       return roman
 
-if firstLine == '<!-- exploded -->':
-       print('Parsing exploded file...\n')
-elif firstLine == '<!-- imploded -->':
-       print('Parsing imploded file...\n')
-else:
-       print('Parsing unknown type...\n')
+#
+# Capture the master document's structure (and content, if any) in a list
+#
+def GetFileStructure():
+       fs = []
+       fnames = [None]*6
+       content = ''
+       grab = False
+       mf = open('master-doc.txt')
+
+       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'])
+                       fullName = ''
+
+                       for i in range(level + 1):
+                               fullName = fullName + fnames[i] + '/'
+
+                       hdr['filename'] = fullName.rstrip('/')
+                       fs.append(hdr)
+
+                       if ('include' not in hdr) and (level > 0):
+                               grab = True
+               else:
+                       if grab:
+                               content = content + ln
 
-for line in master:
-       lineCount = lineCount + 1
+       # Catch the last file, since it would be missed above
+       if grab:
+               fs[-1]['content'] = content
 
-       # Do any header parsing if needed...
-       if line.startswith('---'):
+       mf.close()
+       return fs
 
-               # Close any open files that may have been opened from the last header...
-               if writingToFile:
-                       htmlFile.close()
-                       writingToFile = False
 
-               header = {}
+#
+# 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
 
-               while (True):
-                       hdrLine = master.readline().rstrip('\r\n')
-                       lineCount = lineCount + 1
+       # 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
 
-                       # Break out of the loop if we hit the end of header marker
-                       if hdrLine.startswith('---'):
-                               break
+       # Otherwise, no children at this node.
+       return False
 
-                       # Parse out foo: bar pairs & put into header dictionary
-                       a = re.split(': ', hdrLine, 1)
-                       header[a[0]] = a[1]
 
-               # Header has been parsed, now do something about it...
-               lastLevel = level
-
-               # Handle Part/Chapter/subchapter/section/subsection numbering
-               if (header['part'] == 'part'):
-                       level = 0
-                       levelNums[2] = 0
-               elif (header['part'] == 'chapter'):
-                       level = 1
-                       levelNums[2] = 0
-               elif (header['part'] == 'subchapter'):
-                       level = 2
-                       levelNums[3] = 0
-               elif (header['part'] == 'section'):
-                       level = 3
-                       levelNums[4] = 0
-               elif (header['part'] == 'subsection'):
-                       level = 4
-
-               levelNums[level] = levelNums[level] + 1;
-
-               # This is totally unnecessary, but nice; besides which, you can capture
-               # the output to a file to look at later if you like :-)
-               for i in range(level):
-                       print('\t', end='')
+#
+# Get the children at this level, and return them in a list
+#
+def GetChildren(fs, pos):
+       children = []
+       pos = pos + 1
+       childLevel =  fs[pos]['level']
 
-               if (level == 0):
-                       print('\nPart ' + roman[levelNums[0]] + ': ', end='')
-               elif (level == 1):
-                       print('\n\tChapter ' + str(levelNums[1]) + ': ', end='')
+       while fs[pos]['level'] >= childLevel:
+               if fs[pos]['level'] == childLevel:
+                       children.append(pos)
 
-               print(header['title'])
+               pos = pos + 1
 
-               # Make a filename from the title...
-               levelNames[level] = MakeFilename(header['title'])
-               path = ''
+               # Sanity check
+               if pos == len(fs):
+                       break
 
-               for i in range(level):
-                       path = path + str(levelNums[i]).zfill(2) + '_' + levelNames[i] + '/'
-
-               path = path + str(levelNums[level]).zfill(2) + '_' + levelNames[level]
-
-               # Append the appropriate footer to the last file, if the current file
-               # is one level down from the previous...
-               if ((level > 0) and (level > lastLevel)):
-                       nfile = open(buildDir + lastFile + '.html', 'a')
-                       nfile.write('\n{% children %}\n\n')
-                       nfile.close()
-
-               # Handle TOC scriblings...
-               if level == 0:
-                       toc.write('<h2>Part ' + roman[levelNums[level]] + ': ' + header['title'] + '</h2>\n');
-               elif level == 1:
-                       toc.write('  <p id=chapter>Ch. ' + str(levelNums[level]) + ':&nbsp;&nbsp;<a href="/' + levelNames[0] + '/' + levelNames[1] + '/">' + header['title'] + '</a></p>\n')
-               elif level == 2:
-                       toc.write('    <a id=subchapter href="/' + levelNames[0] + '/' + levelNames[1] + '/' + levelNames[2] + '/">' + header['title'] + '</a><br>\n')
-               elif level == 3:
-                       toc.write('      <a id=subchapter href="/' + levelNames[0] + '/' + levelNames[1] + '/' + levelNames[2] + '/' + levelNames[3] + '/">' + header['title'] + '</a><br>\n')
-               elif level == 4:
-                       toc.write('      <a id=subchapter href="/' + levelNames[0] + '/' + levelNames[1] + '/' + levelNames[2] + '/' + levelNames[3] + '/' + levelNames[4] + '/">' + header['title'] + '</a><br>\n')
-
-               # Parts DO NOT have any content, they are ONLY an organizing construct!
-               if (level == 0):
-                       os.mkdir(buildDir + path, 0o774)
-                       nfile = open(buildDir + path + '.html', 'w')
-                       nfile.write('---\n' + 'title: ' + header['title'] + '\n')
+       return children
 
-                       if ('menu_title' in header):
-                               nfile.write('menu_title: ' + header['menu_title'] + '\n')
 
-                       nfile.write('---\n\n')
-                       nfile.close()
+#
+# 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 = []
 
-               # Chapters, subchapters, sections & subsections all can have content.
-               # But the basic fundamental organizing unit WRT content is still the
-               # chapter.
+       for i in range(len(fs)):
+               if HaveChildren(fs, i):
+                       childArray.append(GetChildren(fs, i))
                else:
-                       os.mkdir(buildDir + path, 0o774)
+                       childArray.append([])
 
-                       if ('include' in header):
-                               shutil.copy('include/' + header['include'], buildDir + path + '.html')
-                       else:
-                               htmlFile = open(buildDir + path + '.html', 'w')
-                               writingToFile = True
-                               htmlFile.write('---\n' + 'title: ' + header['title'] + '\n')
+       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'] + '/'
+
+       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
+# them with the appropriate link.
+#
+def FixInternalLinks(links, content, title):
 
-                               if ('menu_title' in header):
-                                       htmlFile.write('menu_title: ' + header['menu_title'] + '\n')
+       # Make key1|key2|key3|... out of our links keys
+       pattern = re.compile('|'.join(links.keys()))
 
-                               if ('style' in header):
-                                       htmlFile.write('style: ' + header['style'] + '\n')
+       # Use a lambda callback to substitute each occurance found
+       result = pattern.sub(lambda x: links[x.group()], content)
 
-                               htmlFile.write('---\n\n')
+       # Check for missing link targets, and report them to the user
+       match = re.findall('"@@.*"', result)
 
-               # Save filename for next header...
-               lastFile = path
+       if len(match) > 0:
+               print('\nMissing link target' + ('s' if len(match) > 1 else '') + ' in "' + title + '":')
 
-       # No header, in that case, just dump the lines into the currently open file
+               for s in match:
+                       print('  ' + s[3:-1])
+
+               print()
+
+       return result
+
+
+#
+# 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\n<dl>\n'
+
+       for i in range(len(lst)):
+               curPos = lst[i]
+               nextPos = lst[i + 1] if i + 1 < len(lst)  else len(fs)
+
+               active = ' class=active' if curPos == pagePos else ''
+               content = content + '<dt' + active + '><a href="/' + fs[curPos]['filename'] + '/">' + fs[curPos]['title'] + '</a></dt><dd' + active + '>'
+
+               # If the current page is our page, and it has children, enumerate them
+               if curPos == pagePos:
+                       if len(cList[curPos]) > 0:
+                               content = content + BuildList(cList[curPos], fs, -1, cList)
+
+               # Otherwise, if our page lies between the current one and the next,
+               # build a list of links from those nodes one level down.
+               elif (pagePos > curPos) and (pagePos < nextPos):
+                       content = content + BuildList(cList[curPos], fs, pagePos, cList)
+
+               content = content + '</dd>\n'
+
+       content = content + '</dl>\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 in the top...
+       content = content[:7] + '<dt><a href="/toc/">Table of Contents</a></dt><dd></dd>\n' + content[7:]
+
+       return content
+
+
+# Preliminaries
+
+# We have command line arguments now, so deal with them
+parser = argparse.ArgumentParser(description='A build script for the Ardour Manual')
+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)')
+args = parser.parse_args()
+verbose = args.verbose
+quiet = args.quiet
+
+if quiet:
+       verbose = False
+
+#verbose = False
+level = 0
+fileCount = 0
+levelNums = [0]*6
+lastFile = ''
+page = ''
+toc = ''
+pageNumber = 0
+
+siteDir = './website/'
+
+if os.access(siteDir, os.F_OK):
+       if not quiet:
+               print('Removing stale HTML data...')
+
+       shutil.rmtree(siteDir)
+
+shutil.copytree('./source', siteDir)
+
+# Yeah, need to make a symlink in include/ too :-P
+# [this will go away when the rewrite happens]
+if (os.access('include/_manual', os.F_OK) == False):
+       os.symlink('../_manual/', 'include/_manual')
+
+
+# Read the template, and fix the stuff that's fixed for all pages
+temp = open('page-template.txt')
+template = temp.read()
+temp.close()
+
+template = template.replace('{{page.bootstrap_path}}', '/bootstrap-2.2.2')
+template = template.replace('{{page.page_title}}', 'The Ardour Manual')
+
+
+# Parse out the master docuemnt's structure into a dictionary list
+fileStruct = GetFileStructure()
+
+# Build a quasi-tree structure listing children at level + 1 for each node
+nodeChildren = FindChildren(fileStruct)
+
+# Create a dictionary for translation of internal links to real links
+links = FindInternalLinks(fileStruct)
+
+if not quiet:
+       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')
+       firstLine = master.readline().rstrip('\r\n')
+       master.close()
+
+       if firstLine == '<!-- exploded -->':
+               print('Parsing exploded file...')
+       elif firstLine == '<!-- imploded -->':
+               print('Parsing imploded file...')
        else:
-               if (writingToFile and (level > 0)):
-                       htmlFile.write(line)
+               print('Parsing unknown type...')
+
+# Here we go!
+
+for header in fileStruct:
+       fileCount = fileCount + 1
+       content = ''
+       more = ''
+
+       lastLevel = level
+       level = header['level']
+
+       # Handle Part/Chapter/subchapter/section/subsection numbering
+       if level == 0:
+               levelNums[2] = 0
+       elif level == 1:
+               levelNums[2] = 0
+       elif level == 2:
+               levelNums[3] = 0
+       elif level == 3:
+               levelNums[4] = 0
+
+       levelNums[level] = levelNums[level] + 1;
+
+       # This is totally unnecessary, but nice; besides which, you can capture
+       # the output to a file to look at later if you like :-)
+       if verbose:
+               for i in range(level):
+                       print('\t', end='')
+
+               if (level == 0):
+                       print('\nPart ' + num2roman(levelNums[0]) + ': ', end='')
+               elif (level == 1):
+                       print('\n\tChapter ' + str(levelNums[1]) + ': ', end='')
 
+               print(header['title'])
 
-master.close()
-toc.close()
+       # Handle TOC scriblings...
+       if level == 0:
+               toc = toc + '<h2>Part ' + num2roman(levelNums[level]) + ': ' + header['title'] + '</h2>\n';
+       elif level == 1:
+               toc = toc + '  <p id=chapter>Ch. ' + str(levelNums[level]) + ':&nbsp;&nbsp;<a href="/' + header['filename'] + '/">' + header['title'] + '</a></p>\n'
+       elif level == 2:
+               toc = toc + '    <a id=subchapter href="/' + header['filename'] + '/">' + header['title'] + '</a><br>\n'
+       elif level == 3:
+               toc = toc + '      <a id=subchapter href="/' + header['filename'] + '/">' + header['title'] + '</a><br>\n'
+       elif level == 4:
+               toc = toc + '      <a id=subchapter href="/' + header['filename'] + '/">' + header['title'] + '</a><br>\n'
+
+       # Make the 'this thing contains...' stuff
+       if HaveChildren(fileStruct, pageNumber):
+               pages = GetChildren(fileStruct, pageNumber)
+
+               for pg in pages:
+                       more = more + '<li>' + '<a href="/' + fileStruct[pg]['filename'] + '/">' + fileStruct[pg]['title'] + '</a>' + '</li>\n'
+
+               more = '<div id=subtopics>\n' + '<h2>This section contains the following topics:</h2>\n' + '<ul>\n' + more + '</ul>\n' + '</div>\n'
+
+       # Make the 'Previous' & 'Next' content
+       nLink = ''
+       pLink = ''
+
+       if pageNumber > 0:
+               pLink = '<li><a title="' + fileStruct[pageNumber - 1]['title'] + '" href="/' + fileStruct[pageNumber - 1]['filename'] + '/" class="previous"> &lt; Previous </a></li>'
+
+       if pageNumber < len(fileStruct) - 1:
+               nLink = '<li><a title="' + fileStruct[pageNumber + 1]['title'] + '" href="/' + fileStruct[pageNumber + 1]['filename'] + '/" class="next"> Next &gt; </a></li>'
+
+       prevnext = '<ul class=pager>' + pLink + nLink + '</ul>'
+
+       # Create the link sidebar
+       sidebar = CreateLinkSidebar(fileStruct, pageNumber, nodeChildren)
+
+       # Parts DO NOT have any content, they are ONLY an organizing construct!
+       # Chapters, subchapters, sections & subsections can all have content,
+       # but the basic fundamental organizing unit WRT content is still the
+       # chapter.
+       if level > 0:
+               if 'include' in header:
+                       srcFile = open('include/' + header['include'])
+                       content = srcFile.read()
+                       srcFile.close()
+
+                       # Get rid of any extant header in the include file
+                       # (once this is accepted, we can nuke this bit, as content files
+                       # will not have any headers or footers in them)
+                       content = re.sub('---.*\n(.*\n)*---.*\n', '', content)
+                       content = content.replace('{% children %}', '')
 
-if writingToFile:
-       htmlFile.close()
+               else:
+                       if 'content' in header:
+                               content = header['content']
+                       else:
+                               content = '[something went wrong]'
 
-print('\nProcessed ' + str(lineCount) + ' lines.')
+       # 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)
+       else:
+               page = template.replace('{{page.style}}', header['style'])
+               page = page.replace('{% if page.style %}', '')
+               page = page.replace('{% endif %}', '')
+
+       page = page.replace('{{ page.title }}', header['title'])
+       page = page.replace('{% tree %}', sidebar)
+       page = page.replace('{% prevnext %}', prevnext)
+       page = page.replace('{{ content }}', content + more)
+
+       # Create the directory for the index.html file to go into
+       os.mkdir(siteDir + header['filename'], 0o775)
+
+       # Finally, write the file!
+       destFile = open(siteDir + header['filename'] + '/index.html', 'w')
+       destFile.write(page)
+       destFile.close()
+
+       # Save filename for next header...
+       lastFile = header['filename']
+       pageNumber = pageNumber + 1
+
+# Finally, create the TOC
+sidebar = CreateLinkSidebar(fileStruct, -1, nodeChildren)
+
+page = re.sub("{% if page.style %}.*\n.*\n{% endif %}.*\n", "", template)
+page = page.replace('{{ page.title }}', 'Ardour Table of Contents')
+page = page.replace('{% tree %}', sidebar)
+page = page.replace('{{ content }}', toc)
+page = page.replace('{% prevnext %}', '')
+
+os.mkdir(siteDir + 'toc', 0o775)
+tocFile = open(siteDir + 'toc/index.html', 'w')
+tocFile.write(page)
+tocFile.close()
+
+if not quiet:
+       print('Processed ' + str(fileCount) + ' files.')
diff --git a/build.rb b/build.rb
deleted file mode 100755 (executable)
index 4c981d2..0000000
--- a/build.rb
+++ /dev/null
@@ -1,285 +0,0 @@
-#!/usr/bin/env ruby
-
-require 'pathname'
-require 'yaml'
-require 'optparse'
-
-begin require 'liquid'
-rescue LoadError
-    puts "Please install the 'liquid' Ruby gem (available in Debian/Ubuntu as 'ruby-liquid')"
-    exit 1
-end
-
-CONFIG = {
-    pages_dir: '_manual.munge',
-    layouts_dir: '_layouts',
-    static_dir: 'source',
-    output_dir: '_site'
-}
-
-def child_url?(a, b)
-    a.start_with?(b) && b.count('/') + 1 == a.count('/')
-end
-
-class Site
-    attr_reader :pages, :layouts
-
-    def initialize()
-        @pages = []
-        @layouts = {}
-    end
-    
-    def build()
-        print "Building... "
-
-        read_layouts()
-        read_pages()
-        copy_static()
-        process_pages()
-
-        puts "done."
-    end
-
-    def read_layouts()
-        Pathname.glob(layouts_dir + Pathname('*.html')) do |path|
-            next if !path.file?
-            layout = Layout.new(self, path)
-            layout.read
-            @layouts[path.basename('.html').to_s] = layout
-        end
-    end
-    
-    def read_pages()
-        pages_dir.find do |path|
-            if path.file? && path.extname == '.html'
-                page = Page.new(self, path)
-                page.read
-                @pages << page
-            end
-        end
-    end
-
-    def process_pages()
-        @pages.each {|page| page.process}
-    end
-    
-    def copy_static()
-        unless system("rsync -a --delete --exclude='*~' #{static_dir}/. #{output_dir}")
-            puts "Couldn't copy static files, is rsync installed?"
-        end
-    end
-    
-    def find_children(url)
-        sorted_pages.select { |p| child_url?(p.url, url) }
-    end
-    
-    def toplevel() @toplevel_memo ||= find_children('/') end
-    def sorted_pages() @sorted_pages_memo ||= @pages.sort_by{ |p| p.sort_url } end
-
-    def pages_dir() @pages_dir_memo ||= Pathname(CONFIG[:pages_dir]) end
-    def layouts_dir() @layouts_dir_memo ||= Pathname(CONFIG[:layouts_dir]) end
-    def static_dir() @static_dir_memo ||= Pathname(CONFIG[:static_dir]) end
-    def output_dir() @output_dir_memo ||= Pathname(CONFIG[:output_dir]) end
-end
-
-class Page
-    attr_reader :path, :out_path, :url, :sort_url
-
-    def initialize(site, path)
-        @site = site
-        @path = path
-
-       relative_path = @path.relative_path_from(@site.pages_dir);
-       a = relative_path.each_filename.map do |x|
-            x.sub(/^[0-9]*[-_]/, '')
-        end
-       a[-1].sub!(/\.html$/, '')
-        s = a.join('/')
-
-        @out_path = @site.output_dir + Pathname(s) + Pathname("index.html")
-        @url = "/#{s}/"
-        @sort_url = @path.to_s.sub(/\.html$/, '')
-    end
-
-    def related_to?(p)
-        # should we show p in the index on selfs page?
-        url.start_with?(p.url) || child_url?(url, p.url)
-    end
-
-    def title()
-        @page_context['title'] || ""
-    end
-
-    def menu_title()
-        @page_context['menu_title'] || title
-    end
-
-    def read()
-        content = @path.read
-        frontmatter, @content = split_frontmatter(content) || abort("File not well-formatted: #{@path}") 
-        @page_context = YAML.load(frontmatter)
-        @template = Liquid::Template.parse(@content)
-    end        
-
-    def split_frontmatter(txt)
-        @split_regex ||= /\A---[ \t\r]*\n(?<frontmatter>.*?)^---[ \t\r]*\n(?<content>.*)\z/m
-        match = @split_regex.match txt 
-        match ? [match['frontmatter'], match['content']] : nil
-    end
-    
-    def find_layout()
-        @site.layouts[@page_context['layout'] || 'default']
-    end
-
-    def children()
-        @children ||= @site.find_children(@url)
-    end
-    
-    def render()
-        registers = {page: self, site: @site}
-        context = {'page' => @page_context}
-        content = @template.render!(context, registers: registers)
-        find_layout.render(context.merge({'content' => content}), registers)
-    end
-    
-    def process()
-        path = out_path
-        path.dirname.mkpath
-        path.open('w') { |f| f.write(render) }
-    end
-end
-
-class Layout < Page
-    def render(context, registers)
-        context = context.dup
-        context['page'] = @page_context.merge(context['page'])
-        content = @template.render!(context, registers: registers)
-        if @page_context.has_key?('layout')
-            find_layout.render(context.merge({'content' => content}), registers)
-        else
-            content
-        end
-    end
-end
-
-class Tag_tree < Liquid::Tag
-    def join(children_html)
-        children_html.empty? ? "" : "<dl>\n" + children_html.join + "</dl>\n"
-    end
-
-    def render(context)
-        current = context.registers[:page]
-        site = context.registers[:site]
-
-        format_entry = lambda do |page|
-            children = page.children
-            
-            css = (page == current) ? ' class="active"' : ""
-            children_html = current.related_to?(page) ? join(children.map(&format_entry)) : ""
-            
-            %{
-          <dt#{css}>
-            <a href='#{page.url}'>#{page.menu_title}</a>
-          </dt>
-          <dd#{css}>
-            #{children_html}
-          </dd>
-        }
-        end
-
-        join(site.toplevel.map(&format_entry))
-    end
-end
-
-class Tag_children < Liquid::Tag
-    def render(context)
-        children = context.registers[:page].children
-        entries = children.map {|p| "<li><a href='#{p.url}'>#{p.title}</a></li>" }
-        
-        "<div id='subtopics'>
-        <h2>This chapter covers the following topics:</h2>
-        <ul>
-          #{entries.join}
-        </ul>
-        </div>
-      "
-    end
-end
-
-class Tag_prevnext < Liquid::Tag
-    def render(context)
-        current = context.registers[:page]
-        pages = context.registers[:site].sorted_pages
-        
-        index = pages.index { |page| page == current }
-        return '' if !index
-        
-        link = lambda do |p, cls, txt| 
-            "<li><a title='#{p.title}' href='#{p.url}' class='#{cls}'>#{txt}</a></li>"
-        end
-        prev_link = index > 0 ? link.call(pages[index-1], "previous", " &lt; Previous ") : ""
-        next_link = index < pages.length-1 ? link.call(pages[index+1], "next", " Next &gt; ") : ""
-        
-        "<ul class='pager'>#{prev_link}#{next_link}</ul>"
-    end
-end
-
-class Server
-    def start_watcher()
-        begin require 'listen'
-        rescue LoadError
-            puts "To use the --watch function, please install the 'listen' Ruby gem"
-            puts "(available in Debian/Ubuntu as 'ruby-listen')"
-            return nil
-        end
-
-        listener = Listen.to(CONFIG[:pages_dir], wait_for_delay: 1.0, only: /.html$/) do |modified, added, removed|
-            Site.new.build
-        end
-        listener.start
-        listener
-    end
-
-    def run(options)
-        require 'webrick'
-       listener = options[:watch] && start_watcher
-        port = options[:port] || 8000
-           
-        puts "Serving at http://localhost:#{port}/ ..."
-        server = WEBrick::HTTPServer.new :Port => port, :DocumentRoot => CONFIG[:output_dir]
-       trap 'INT' do 
-            server.shutdown 
-        end
-       server.start
-        listener.stop if listener
-    end  
-end
-
-def main
-    Liquid::Template.register_tag('tree', Tag_tree)
-    Liquid::Template.register_tag('children', Tag_children)
-    Liquid::Template.register_tag('prevnext', Tag_prevnext)
-
-    if defined? Liquid::Template.error_mode
-        Liquid::Template.error_mode = :strict
-    end
-
-    options = {}
-    OptionParser.new do |opts| 
-       opts.banner = %{Usage: build.rb <command> [options]
-
-Use 'build.rb' to build the manual.  Use 'build.rb serve' to also
-start a web server; setting any web server options implies "serve".
-}
-        opts.on("-w", "--watch", "Watch for changes") { options[:watch] = true }
-        opts.on("-p", "--port N", Integer, "Specify port for web server") { |p| options[:port] = p }
-    end.parse!
-
-    Site.new.build
-
-    if options[:watch] || options[:port] || (ARGV.length > 0 && "serve".start_with?(ARGV[0]))
-        Server.new.run(options)
-    end
-end
-
-main
index 85eb250147be570ef77d330594a1cc4bc03b56af..a18ecee11ac2aa470475c42507d55ef3b2c2fb46 100755 (executable)
@@ -11,20 +11,47 @@ import os
 import re
 import shutil
 
+lineCount = 0
+
 
 #
 # Create an all lowercase filename without special characters and with spaces
 # replaced with dashes.
 #
 def MakeFilename(s):
-       # This RE is shitty, but I can't think of a better one right now
-       fn = re.sub("[?!'&#:;_*()/\\,.]+", "", s)
+       # Cleans up the file name, removing all non ASCII or .-_ chars
+       fn = re.sub(r'[^.\-_a-zA-Z0-9 ]', '', s)
        fn = fn.lower()
        fn = fn.replace(' ', '-')
        return fn
 
 
-lineCount = 0
+#
+# Parse headers into a dictionary
+#
+def ParseHeader(fileObj):
+       global lineCount
+       header = {}
+
+       while (True):
+               hdrLine = fileObj.readline().rstrip('\r\n')
+               lineCount = lineCount + 1
+
+               # 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
+
+
 fileCount = 0
 writingFile = False
 toFile = open('master-doc.txt')
@@ -60,33 +87,24 @@ for line in master:
                        toFile.close()
                        writingFile = False
 
-               header = {}
                noMove = False
-
-               while (True):
-                       hdrLine = master.readline().rstrip('\r\n')
-                       lineCount = lineCount + 1
-
-                       # Break out of the loop if we hit the end of header marker
-                       if hdrLine.startswith('---'):
-                               break
-
-                       # Parse out foo: bar pairs & put into header dictionary
-                       a = re.split(': ', hdrLine, 1)
-                       header[a[0]] = a[1]
+               header = ParseHeader(master)
 
                # Make sure the filename we're making is unique...
-               inclFile = MakeFilename(header['title']) + '.html'
+               basename = MakeFilename(header['title'])
+               inclFile = basename + '.html'
 
-               if inclFile in filenames:
-                       suffix = 2
-                       inclFile = MakeFilename(header['title']) + str(suffix) + '.html'
+               if 'file' in header:
+                       inclFile = header['file']
+               else:
+                       suffix = 1
 
                        while inclFile in filenames:
                                suffix = suffix + 1
-                               inclFile = MakeFilename(header['title']) + str(suffix) + '.html'
+                               inclFile = basename + '_' + str(suffix) + '.html'
 
-               # Pull in files and write the result to the master file
+               # Find all files in the master file and write them out to include/,
+               # while removing it from the master file.
                explode.write('\n---\n' + 'title: ' + header['title'] + '\n')
 
                if header['part'] != 'part':
@@ -119,15 +137,7 @@ for line in master:
 
                                toFile = open('include/' + inclFile, 'w')
                                writingFile = True
-                               toFile.write('---\n' + 'title: ' + header['title'] + '\n')
-
-                               if 'menu_title' in header:
-                                       toFile.write('menu_title: ' + header['menu_title'] + '\n')
-
-                               if 'style' in header:
-                                       toFile.write('style: ' + header['style'] + '\n')
 
-                               toFile.write('---\n')
        else:
                if writingFile:
                        toFile.write(line)
index 46c26e5ff984b40313f317d79442f2f95612c358..fcc0e21318d59f1283d6e39c078858f0a27a0ac2 100755 (executable)
@@ -11,7 +11,38 @@ import os
 import re
 import shutil
 
+lineCount = 0
+
+
+#
+# Parse headers into a dictionary
+#
+def ParseHeader(fileObj):
+       global lineCount
+       header = {}
+
+       while (True):
+               hdrLine = fileObj.readline().rstrip('\r\n')
+               lineCount = lineCount + 1
+
+               # 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
+
+
+#
+# Check to see if a given file has a header (it shouldn't)
+#
 def CheckForHeader(fn):
        check = open(fn)
 
@@ -24,7 +55,6 @@ def CheckForHeader(fn):
        return False
 
 
-lineCount = 0
 fileCount = 0
 delList = []
 
@@ -51,20 +81,8 @@ for line in master:
        # Do any header parsing if needed...
        if line.startswith('---'):
 
-               header = {}
                noMove = False
-
-               while (True):
-                       hdrLine = master.readline().rstrip('\r\n')
-                       lineCount = lineCount + 1
-
-                       # Break out of the loop if we hit the end of header marker
-                       if hdrLine.startswith('---'):
-                               break
-
-                       # Parse out foo: bar pairs & put into header dictionary
-                       a = re.split(': ', hdrLine, 1)
-                       header[a[0]] = a[1]
+               header = ParseHeader(master)
 
                # Pull in files and write the result to the master file
                implode.write('\n---\n' + 'title: ' + header['title'] + '\n')
@@ -79,6 +97,8 @@ for line in master:
                        if 'style' in header:
                                implode.write('style: ' + header['style'] + '\n')
 
+                       implode.write('file: ' + header['include'] + '\n')
+
                        if ('exclude' in header) and ('include' in header):
                                noMove = True
                                implode.write('include: ' + header['include'] + '\n')
@@ -102,6 +122,7 @@ for line in master:
                                        os.rename('master-doc.bak', 'master-doc.txt')
                                        exit(-1)
 
+#eventually this will go away, as this will never happen again...
                                if CheckForHeader(inclFile) == True:
 
                                        # Skip the header
@@ -123,6 +144,7 @@ implode.close()
 print('Processed ' + str(lineCount) + ' lines.')
 print('Imploded master document from ' + str(fileCount) + ' files.')
 
+# Cleanup after successful implode
 os.remove('master-doc.bak')
 
 for name in delList:
index eec0f8eb23428edd47226f1b4dee3c5c692bb23f..6e83d38fd92c3017b1f658a8e192ea7c259de868 100644 (file)
@@ -21,7 +21,7 @@ ow">
 </p>
 
 <ul>
-  <li>Plugins can be dragged from the window to any track or bus <a href="/working-with-plugins/processor-box/"><dfn>processor box</dfn></a>, which will add the plugin to that track or bus at the given position.</li>
+  <li>Plugins can be dragged from the window to any track or bus <a href="@@processor-box"><dfn>processor box</dfn></a>, which will add the plugin to that track or bus at the given position.</li>
   <li>The list includes user-presets for the plugins. Dragging a preset to a given track or bus will load that preset after adding the plugin.</li>
   <li>Double-clicking on a plugin or preset adds the given plugin to all selected tracks/busses pre-fader. Other insert positions are available from the context menu (right click).</li>
   <li>Dragging a plugin from a track into the window will add it to the list and optionally create a new preset from the current settings. The horizontal line in the list shows the spot where the plugin will land.</li>
@@ -30,6 +30,6 @@ ow">
 </ul>
 
 <p class="note">
-  When favorites are added with the <a href="/working-with-plugins/plugin-manager">Plugin Manager</a>, they are appended to the bottom of the list.
+  When favorites are added with the <a href="@@plugin-manager">Plugin Manager</a>, they are appended to the bottom of the list.
 </p>
 
index 16c0792e4046959a08206b6410c5e5b11b80e9dd..dc15947847d1892b374d29de39958317a64a0be9 100644 (file)
@@ -19,7 +19,7 @@ title: KDE Plasma 5
   <li>Open <kbd class="menu">Workspace &gt; Window Managment</kbd>.</li>
   <li>Select <kbd class="menu">Window Rules</kbd> in the left-hand sidebar. It
   should default to the <kbd class="menu">Window matching</kbd> tab.</li>
-  <li>Click on the <kbd class="button">New...</kbd> button.</li>
+  <li>Click on the <kbd class="menu">New...</kbd> button.</li>
   <li>On the line that says <kbd class="menu">Window class (application)</kbd>,
   set the combo box to <kbd class="menu">Substring Match</kbd> and type <kbd
   class="user">ardour</kbd> in the text entry field.</li>
@@ -31,7 +31,7 @@ title: KDE Plasma 5
   <li>Check the box next to the <kbd class="menu">Keep above</kbd> option. On
   the same line, select <kbd class="menu">Force</kbd> from the combo box, then
   click on the <kbd class="menu">Yes</kbd> radio button for that line.</li>
-  <li>Click on the <kbd class="button">OK</kbd> button to dismiss the dialog.
+  <li>Click on the <kbd class="menu">OK</kbd> button to dismiss the dialog.
   </li>
 </ol>
 
index 14251136b078c52c2ad81f18ba8395906add3bd9..d263e68e1df4927f43a48402961b49aa73157ee2 100644 (file)
@@ -1475,6 +1475,12 @@ include: _manual/17_signal-routing/08_signal-flow.html
 part: subchapter
 ---
 
+---
+title: Sidechaining
+include: _manual/17_signal-routing/09_sidechaining.html
+part: subchapter
+---
+
 ---
 title: Muting and Soloing
 include: _manual/20_mixing/01_muting-and-soloing.html
@@ -1519,12 +1525,14 @@ part: subchapter
 
 ---
 title: Processor Box
+link: processor-box
 include: _manual/13_working-with-plugins/03_processor-box.html
 part: subchapter
 ---
 
 ---
 title: Plugin Manager
+link: plugin-manager
 include: _manual/13_working-with-plugins/02_plugin-manager.html
 part: subchapter
 ---
@@ -1888,6 +1896,7 @@ part: subchapter
 
 ---
 title: Class Reference
+style: luadoc
 include: _manual/27_lua-scripting/02_class_reference.html
 exclude: yes
 part: subchapter