build.py 9.09 KB
Newer Older
1 2 3 4 5 6 7
#!/usr/bin/env python3

import sys
import subprocess
from bs4 import BeautifulSoup
from datetime import datetime
from jinja2 import Environment, FileSystemLoader
8 9
from shutil import copy
from distutils import dir_util
10
import frontmatter
Matt Marshall's avatar
Matt Marshall committed
11
import glob
Matt Marshall's avatar
Matt Marshall committed
12
import argparse
13

Matt Marshall's avatar
Matt Marshall committed
14
WEBSITE_PATH = "./out/website"
15
OUT_PATH = "./out"
Matt Marshall's avatar
Matt Marshall committed
16
SRC_PATH = "./src"
17

18
# Builds the entire thesis
19
def buildThesis(format):
Matt Marshall's avatar
Matt Marshall committed
20
    print("Building Thesis in {}".format(format))
21 22
    # Output path
    outputPath = "{}/{}/".format(OUT_PATH, format)
23 24 25 26

    # Check for and create the folder
    subprocess.run(["mkdir", "-p", outputPath])

27
    # Create the file name now so it's easier to output to args
28 29
    filename = "thesis.{}".format(format)

30
    # define args now
31
    args = ["pandoc", "src/index.md", "src/frontmatter.md"]
32

33

Matt Marshall's avatar
Matt Marshall committed
34 35
    # Add chapters dynamically based on a pattern, count the chapters and them add in sequence based on number (not on filename order in the list)

Matt Marshall's avatar
Matt Marshall committed
36
    for i in range(getChapterCount()):
Matt Marshall's avatar
Matt Marshall committed
37 38 39 40 41 42 43 44
        args.append("src/chapter-{}.md".format(i+1))


    # Finish the bulk of arguments
    args.append("src/bibliography.md") # this always comes last after the chapters
    args.extend(["-s", "--toc", "--toc-depth=2", "--filter=pandoc-citeproc", "--self-contained", "--number-sections", "--output={}{}".format(outputPath, filename)])

    # Add for pdf (avoid latex pdfs)
45

46
    if format == "pdf":
Matt Marshall's avatar
Matt Marshall committed
47 48
        args.append("-t")
        args.append("html5")
Matt Marshall's avatar
Matt Marshall committed
49

50
    # Build
Matt Marshall's avatar
Matt Marshall committed
51
    subprocess.run(args)
52

Matt Marshall's avatar
Matt Marshall committed
53
    print("\tThesis built under " + outputPath+filename)
54

55 56 57
# Builds filename.md into filename.format; numberOffset may be set to change the section numbers
def buildFile(filename, format, chapterNumber=None):
    print("Building {} in {}".format(filename, format))
58

59
    # Define the output path where the file will be built, using the format argument and make the directory if it doesn't exist
60
    outputPath = "{}/{}/".format(OUT_PATH, format)
61 62
    subprocess.run(["mkdir", "-p", outputPath])

63 64
    # Define the output filename
    outputFile = "{}.{}".format(filename,format)
65

66 67
    # Build the initial pandoc command to build the file
    args = ["pandoc", "src/index.md", "src/{}.md".format(filename), "src/bibliography.md" , "--output={}{}".format(outputPath,outputFile), "-s", "--toc", "--filter=pandoc-citeproc", "--self-contained", "--number-sections"]
68

69
    # Append the pdf args for if they try to build a PDF file
70 71 72 73
    if format == "pdf":
        args.append("-t")
        args.append("html5")

74 75 76 77
    # Append the args for offsetting the section number (chapter number minus 1)
    if not chapterNumber == None:
        args.append("--number-offset={}".format(int(chapterNumber) - 1))

78
    # Run the pandoc command
79
    subprocess.run(args)
80

81 82 83 84 85 86 87 88 89 90 91
    print("\tBuilt {}{}".format(outputPath, outputFile))

# Builds a single chapter
def buildChapter(chapterNumber, format):

    # Get the filename to pass down
    filename = "chapter-{}".format(chapterNumber)

    # Run the buildFile command, pass down the chapterNumber to offset properly
    buildFile(filename, format, chapterNumber)

92

93
# Iterates and builds all individual chapters as separate files.
94
def buildChapters(format):
Matt Marshall's avatar
Matt Marshall committed
95 96 97

    # Add chapters dynamically based on a pattern, count the chapters and them add in sequence based on number (not on filename order in the list)

Matt Marshall's avatar
Matt Marshall committed
98 99
    for i in range(getChapterCount()):
        # print("Building Chapter %s" % str(i+1))
Matt Marshall's avatar
Matt Marshall committed
100
        buildChapter(str(i+1), format)
101 102 103

# Builds the HTML coversheet for the thesis
def buildCoversheet(outputPath):
Matt Marshall's avatar
Matt Marshall committed
104 105
    print("Building Coversheet")

106 107 108 109
    # Load the template
    env = Environment(loader=FileSystemLoader('templates'))
    template = env.get_template("index.html")

110
    # Load index.md frontmatter and parse it to get title and author info
Matt Marshall's avatar
Matt Marshall committed
111

112 113 114 115
    config = frontmatter.load('./src/index.md')

    thesis_title = config['title']
    thesis_author = config['author']
Matt Marshall's avatar
Matt Marshall committed
116
    thesis_source = config['thesis-source']
Matt Marshall's avatar
Matt Marshall committed
117 118
    build_formats = config['website-build-formats']
    download_button_format = config['download-button-format']
119

120 121 122 123
    # Get the last updated
    date = datetime.now()
    last_updated = date.strftime("%A %d %B %Y at %H:%M")

124 125
    # Get a list of items to include in the table row
    items = []
126 127 128

    # To find the title the best way forward is to nab the title from the HTML file as that's easier to parse than Markdown

Matt Marshall's avatar
Matt Marshall committed
129 130
    # Open the thesis file, it's cheaper to parse the file once and nab the title in the loop
    html_file = open('./out/html/thesis.html')
131 132 133 134 135

    html_content = html_file.read()
    html_file.close()
    soup = BeautifulSoup(html_content, 'html.parser') # Parse it

Matt Marshall's avatar
Matt Marshall committed
136 137

    for i in range(getChapterCount()):
138
        item = {}
Matt Marshall's avatar
Matt Marshall committed
139
        item["file"] = "chapter-{}".format(i+1)
140

Matt Marshall's avatar
Matt Marshall committed
141
        title = soup.find("h1", id="chapter-{}".format(i+1)).contents # Nab the h1 element with the chapter title
142
        item["title"] = "{}{}".format(title[0],title[1])
143

144 145
        items.append(item)

146 147
    # Write the file
    output = open(outputPath + "index.html", "w")
Matt Marshall's avatar
Matt Marshall committed
148
    output.write(template.render(thesis_title=thesis_title, thesis_author=thesis_author,last_updated=last_updated, thesis_source=thesis_source, items=items, build_formats=build_formats, download_button_format=download_button_format))
149 150
    output.close()

Matt Marshall's avatar
Matt Marshall committed
151 152
    print("\tCoversheet built under ./out/website/index.html")

153 154 155 156 157
# Builds the website along with cover page
def buildWebsite():
    # Check for and create the folder
    subprocess.run(["mkdir", "-p", WEBSITE_PATH])

Matt Marshall's avatar
Matt Marshall committed
158 159 160 161 162
    # Load the config file
    config = frontmatter.load('./src/index.md')

    # Copy the styles file
    copy("./src/styles.css", WEBSITE_PATH)
Matt Marshall's avatar
Matt Marshall committed
163

Matt Marshall's avatar
Matt Marshall committed
164 165
    # First build in HTML for the web, then loop over the format list and build for that
    buildThesis("html")
Matt Marshall's avatar
Matt Marshall committed
166
    buildChapters("html")
Matt Marshall's avatar
Matt Marshall committed
167 168
    buildFile("frontmatter", "html")
    dir_util.copy_tree("{}/{}/".format(OUT_PATH, "html"), "{}/{}/".format(WEBSITE_PATH, "html") )
169

Matt Marshall's avatar
Matt Marshall committed
170 171 172 173 174 175 176 177
    for format in config['website-build-formats']:
        # Build the thesis + the individual chapters separately
        buildThesis(format)
        buildChapters(format)
        buildFile("frontmatter", format)

        # Move the new files over to the website directory
        dir_util.copy_tree("{}/{}/".format(OUT_PATH, format), "{}/{}/".format(WEBSITE_PATH, format) )
178 179


180
    # Generate cover sheet
Matt Marshall's avatar
Matt Marshall committed
181
    buildCoversheet("{}/".format(WEBSITE_PATH))
182

Matt Marshall's avatar
Matt Marshall committed
183 184 185 186 187 188 189
# Returns a count of all chapters, used in loops to manipulate chapters dynamically
def getChapterCount():
    pattern = "chapter-[0-9]*.md"

    return len(glob.glob("{}/{}".format(SRC_PATH, pattern)))


190

191 192
# Main function to check arguments
def main(argv):
Matt Marshall's avatar
Matt Marshall committed
193

Matt Marshall's avatar
Matt Marshall committed
194 195
    # Define a top level parser for commands
    parser = argparse.ArgumentParser(description="Markdown Thesis: Write your thesis in markdown and manage conversions into other formats")
196

Matt Marshall's avatar
Matt Marshall committed
197 198 199 200 201 202 203 204
    # The command line options differ for each command so we create subparsers for each
    subparsers = parser.add_subparsers(dest="subparser",help="Sub-command help")

    # Parser for building individual chapters
    chapter_parser = subparsers.add_parser("chapter", help="Builds an individual chapter in [format]")
    chapter_parser.add_argument("chapter_number", type=int, help="The number of the chapter you want to build")
    chapter_parser.add_argument("format", type=str, help="The format you want to build into e.g. html or docx")

205 206 207 208
    # Parser for building the frontmatter
    frontmatter_parser = subparsers.add_parser("frontmatter", help="Builds only frontmatter.md in [format]")
    frontmatter_parser.add_argument("format", type=str, help="The format you want to build into e.g. html or docx")

Matt Marshall's avatar
Matt Marshall committed
209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237
    # Parser for building the entire thesis or all chapters simultaneously
    thesis_parser = subparsers.add_parser("thesis", help="Builds the entire thesis in an appropriate format")
    thesis_parser.add_argument("format", type=str, help="The format you want to build into e.g. html or docx")

    # Parser for building all chapters at once
    chapters_parser = subparsers.add_parser("chapters", help="Builds all chapters in [format]")
    chapters_parser.add_argument("format", type=str, help="The format you want to build into e.g. html or docx")

    # Parser for building an entire website
    website_parser = subparsers.add_parser("website", help="Generates a website including a frontpage and html, docx, and odt versions of each chapter and entire thesis")

    # Run
    args = parser.parse_args()
    markdown_thesis(args)

# Takes the result of args and passes it down to the relevant function
def markdown_thesis(args):

    if args.subparser == "thesis":
        print("Building Thesis in {}\n======\n".format(args.format))
        buildThesis(args.format)

    elif args.subparser == "chapters":
        print("Building all chapters in {}\n======\n".format(args.format))
        buildChapters(args.format)

    elif args.subparser == "website":
        print ("Building website \n======\n")
        buildWebsite()
238

Matt Marshall's avatar
Matt Marshall committed
239 240 241
    elif args.subparser == "chapter":
        print("Building Chapter in {}\n======\n".format(args.format))
        buildChapter(args.chapter_number, args.format)
242

243 244 245 246 247
    elif args.subparser == "frontmatter":
        print("Building frontmatter in {}\n======\n".format(args.format))
        # buildFrontmatter(args.format)
        buildFile("frontmatter", args.format)

248 249

main(sys.argv)
Matt Marshall's avatar
Matt Marshall committed
250
print("\n=====\nEnd")