generate_handout.py 3.88 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""

DESCRIPTION

    Generate PDF handout from multiple iPython/Jupyter Notebooks
    - iterates over all .ipynb notebooks in current directory 
    - extracts the JSON content
    - merges it into a single file
    - calls jupyter nbconvert to generate a latex file
    - tweaks the resulting latex file
    - calls pdflatex to compile the latex file into pdf
    - calls pdfjoin to merge a title page to the front

AUTHOR

    Holger Dinkel <dinkel@embl.de>

"""
VERSION='0.1'


import sys
import os
import traceback
import json
import glob
import optparse 
import subprocess 
import re

def main ():

    global options, args

    all_json = None
    out_filename = os.path.basename(os.getcwd()) + '_handout.ipynb'
    tex_filename = os.path.splitext(out_filename)[0] + '.tex'
    pdf_filename = os.path.splitext(out_filename)[0] + '.pdf'
42
#    pdf_cheatsheet_filename = 'CheatSheet.pdf'
43 44 45 46 47 48
    title_pdf_filename = os.path.basename(os.getcwd()) + '_title.pdf'

# Combine all ipynb notebooks into one (merge JSON 'cells'):
    for filename in sorted(glob.glob('*.ipynb')):
        if filename == out_filename:
            continue
49
        print("Reading ipython notebook '%s'" % filename)
50 51 52 53 54 55 56 57 58 59 60 61
        with open(filename, 'r') as json_file:
            json_str = json_file.read()
        j = json.loads(json_str)
        if not all_json:
            all_json = j
        else:
            for i in j['cells']:
                all_json['cells'].append(i)

    with open(out_filename, 'w') as outfile:
        json.dump(all_json, outfile, indent=1, sort_keys=True)

62
    subprocess.check_call("jupyter nbconvert --execute --allow-errors --to=latex %s" % out_filename, shell=True)
63 64 65 66 67

    with open(tex_filename, 'r') as texfile:
        tex = texfile.read()

    tex = re.sub("\\\section\{.*?\}", "", tex, flags=re.DOTALL)  # Delete all sections (would be duplicate)
68
    tex = re.sub("\\\subsection\{(\d+\. )?", "\\\chapter{", tex) # rename numbered subsections to chapters
Toby Hodges's avatar
Toby Hodges committed
69
    tex = re.sub("\\\documentclass\[(\d+pt)\]\{.*?\}", "\\\documentclass[\\1]{report}", tex) # change documentclass to report
70 71 72 73 74 75 76
    tex = re.sub("\\\maketitle", "\\\\tableofcontents", tex) # include tableof

    with open(tex_filename, 'w') as texfile:
        texfile.writelines(tex)

    subprocess.call("pdflatex -interaction=nonstopmode %s" % tex_filename, shell=True)

77 78
    pdf_joined_filename = os.path.splitext(pdf_filename)[0] + '-joined.pdf'

79
    if not os.path.isfile(pdf_filename):
80
        print("PDF file '%s' does not exist! Stopping." % pdf_filename)
81 82
        sys.exit(1)
    elif not os.path.isfile(title_pdf_filename):
83
        print("PDF file '%s' does not exist! Stopping." % title_pdf_filename)
84 85
        sys.exit(1)
    else:
86
#        subprocess.call("pdfjoin --paper a4paper --no-landscape --twoside --rotateoversize false --outfile %s %s %s %s"% (pdf_joined_filename, title_pdf_filename, pdf_filename, pdf_cheatsheet_filename ), shell=True)
Toby Hodges's avatar
Toby Hodges committed
87
        subprocess.call("pdfjoin --paper a4paper --no-landscape --twoside --rotateoversize false --outfile %s %s %s" % (pdf_joined_filename, title_pdf_filename, pdf_filename), shell=True)
88
    print(pdf_joined_filename )
89
    if os.path.isfile(pdf_joined_filename):
90
        print("\n\nPDF file '%s' was generated...\n\n" % pdf_joined_filename)
91 92
        sys.exit(0)
    else:
93
        print("\n\nPDF file '%s' was not generated! See error messages above for details...\n\n" % pdf_joined_filename)
94 95 96 97 98 99 100 101 102 103
        sys.exit(1)


if __name__ == '__main__':
    try:
        parser = optparse.OptionParser(formatter=optparse.TitledHelpFormatter(), usage=globals()['__doc__'], version=VERSION)
        parser.add_option ('-v', '--verbose', action='store_true', default=False, help='verbose output')
        (options, args) = parser.parse_args()
        main()
        sys.exit(0)
104 105 106
    except Exception as e:
        print('ERROR, UNEXPECTED EXCEPTION')
        print(str(e))
107 108 109
        traceback.print_exc()
        os._exit(1)