#!/usr/local/bin/python3
#
# SSST
#
# A simple static site tool to maintain websites based on markdown and pandoc
#
# Author: Jaap-henk Hoepman (jhh@xs4all.nl)

import argparse, sys, os, re, tempfile, subprocess, shutil, datetime, yaml, filecmp
from pathlib import Path
from io import StringIO

# ---
# DelayedTextfile
#
# Create a textfile, whose contents is only actually written to external
# storage when closing the file and the new contents are different from
# the old contents (or when the file does not exist yet)
# ---

class DelayedTextfile(StringIO):

    # create a Delayedtextfile for external file fname,
    # to be written on file close
    def __init__(self,fname):
        super().__init__()
        self.path = Path(fname)

    # return true if the file was actually written, false otherwise    
    def close(self):
        newcontents = self.getvalue()
        if self.path.exists():
            with open(self.path,"r") as f:
                oldcontents = f.read()
                if oldcontents == newcontents:
                    return False
        with open(self.path,"w") as f:
            f.write(newcontents)
            return True

    # Allow with DelayedTextfile(name) as f: constructions
    # where file is automatically closed when with statement ends
    def __exit__(self, type, value, tb):
        self.close()



# ---
# CONSTANTS / PARAMETERS
# ---

# All items processed by SSST are *relative* paths to posts or pages, relative
# to the src_root and dest_root prefixes specified on the commandline
# and 'declared' as global variables here

# Path to the root of the source files tree
src_root = Path('./src')

# Path to the root of the destination files tree
dest_root = Path('./public')

# Root where the site is hosted
# (relative adressing, making the whole site relocateable, is the default)
hosting_root = ''

# Path to directory with templates
templates = Path('./templates')

# Verbosity
loglevel = -1

# Force remaking all files
force = False 

# Abort after warning
strict = False 

# Lenght of summaries (including YAML header)
summary_length = 20

# dictionary of all tags and files with those tags
items_with_tag = {}

# dictionary of all categories and files with those categories
items_with_cat = {}

# dictionary of all posts metadata (as tuples (date,title)) indexed
# by the relative pathname for the post
post_metadata = {}

# list of all relative pathnames for posts, sorted by date (with the help of post_metadata)
all_posts = []

# logfile
logfile = None 

# ---
# ERRORS / LOGGING
# ---

# Outpur the error message, and log it when required. Then exit
# - msg: the error message
def error(msg: str):
    m = "ssst: error: %s\n" % str(msg)
    sys.stderr.write(m)
    if logfile != None:
        logfile.write(m)
    sys.exit (1)

# Output the warning message, and write it to the log file if it was specified.
# Exit when strict processing was specified.
# - msg: the warning message
def warning(msg: str):
    m = "ssst: warning: %s\n" % str(msg)
    sys.stderr.write(m)
    if logfile != None:
        logfile.write(m)
    if strict:
        sys.exit (1)

# Output a log message when its level is lower than the specifief verbosity
# on the command line. Always write the message to the log file if it was
# specified 
# - level: the log-level for this message
# - msg: the log message
def log (level: int, msg: str):
    m = str(msg) + '\n'
    if (level <= loglevel):
        sys.stdout.write(m)
    if logfile != None:
        logfile.write(m)

# ---
# YAML
# ---

# Return the dictionary of entries found in the YAML block at the start of
# a file
# - src: full path to the file
def get_yaml_dict(src: Path):
    try:
        with open(src,"r") as f:
            yamls = yaml.load_all(f,Loader=yaml.SafeLoader)
            for y in yamls:
                return y
    except:
        warning('Invalid YAML block in ' + str(src))
        return {}

# ---
# FOLDER FUNCTIONS
# ---

# Return the full source folder containing item
# - item: relative path to the item
def src_folder_of(item: Path) -> Path:
    return src_root / item.parent # full source folder of item

# Return the full source folder pathname pointing to item
# - item: relative path to the item
def src_path_of(item: Path) -> Path:
    return src_root / item

# Return the full destination folder to store the result for item
# - item: relative path to the item
def dest_folder_of(item: Path) -> Path:
    return dest_root / item.parent # full source folder of item

# Return the full destination path for the html file to be generated
# for the item
# - item: relative path to the item
def dest_of (item: Path) -> Path:
    dest = dest_root / item
    return dest.with_suffix('.html')

# Determine list of Paths containing the comments (empty if none) for the item
# I.e. all files matching <src_root> / <parent_folder> / *.mdc
def commentfiles_for (item: Path):
    return (src_root / item.parent).glob('*.mdc')

# Return path pointing to the root for the current item on the hosting site.
# This is either a relative path back to the root from the path containing the
# item, or simply hosting_root if set to non-empty on the command line 
# - item: relative path to item
def item_root (item: Path) -> str: 
    if hosting_root != "":
        return hosting_root
    else:
        item = str(item)
        log(5,"Computing root for " + item)
        res = ""
        for i in range(0,len(item)-1):
            if item[i] == '/':
                res = res + "../"
        # remove trailing backslash
        res = res[:-1]
        if res == "":
            res = "."
        log(5,"Computed root " + res)    
        return res

# Determine the link pointing to the item on the hosting site
# relative to context, i.e.
# the generated link works if is included in the page with path context
# - item: relative path to item
# - context: relative path to page that will contain the link to item 
def item_url (item: Path, context: Path) -> str:
    item = item.with_suffix('.html')
    return str(item_root(context) / item)

# Return the full path for a template, throw error if it does not exist
# - name: template file name to be found in the template directory  
def get_template(name: Path):
    template = templates / name        
    if not template.exists():
        error("Template %s not found" % template)
    return template

# ---
# OTHER FUNCTIONS
# ---

# Replace the contents of a file with new contents, but only change the file
# and its metadata when the new contents are different from the current
# contents.
# - current: file whose contents must be updated 
# - update: file containing new content
def replace_when_changed (current: Path, update: Path):
    if (not current.exists()) or \
       (not filecmp.cmp(current,update,shallow=False)): 
        log(4,"File changed "+ str(current))
        shutil.copy2(update,current)
    else:
        log(4,"File not changed "+ str(current))
    os.remove(update)

# Parse a date if it is a string, return unchanced otherwise.
# (Only parses wordpress generated dates like "Sun, 19 Apr 2020 08:42:00 +0000"
# that the YAML parser fails to parse)
def parse_date (s):
    if type(s) is str:
        time = datetime.datetime.strptime(s,"%a, %d %b %Y %H:%M:%S %z")
        return time.date()
    else:
        return s

# Convert a date (either a string like "Wed, 03 Apr 2013 12:02:24 +0000" or
# a datetime object) to something like "April 03, 2013"
# - date: datetime object to be converted to a string
def normalize_date(date) -> str:
    tmp = parse_date(date)
    # we do NOT want zero-padding on days
    return tmp.strftime("%B ") + str(tmp.day) + ", " + str(tmp.year)
        
# ---
# LaTeX EQUATIONS
# ---

# number of matches so far
eq_match_count = 0
# folder containing the currently processed file
basefolder = ""
# dictonary of processed equations; reset for every file
equations = {}

# Turn a latex expression into an SVG graphic and write it to file.
# The filename to use is determined by the global variable basefolder
# (derived from the name of the file containing the equations) and
# number of processed equations for that file
# - latex_expr: latex expression (without surrounding $$)
# - returns: the name of the SVG file in which the graphic is stored
#   (relative to basename)
def process_eq(latex_expr):
    global eq_match_count
    eq_match_count = eq_match_count + 1
    # create the SVG output name
    svg_output_name = "index." + str(eq_match_count) + '.svg'
    svg_full_path = basefolder / svg_output_name
    log(4,'Storing equation in ' + str(svg_full_path))
    # create a temporary direcotry (will be removed when function finishes)
    with tempfile.TemporaryDirectory() as tmpdir:
        tmp_tex = tmpdir + "/htsv.tex"    
        tmp_pdf = tmpdir + "/htsv.pdf"
        # read the latex template and store it as a string in t
        eq_template = get_template('ssst-eq.tex')
        with open(eq_template,'r') as f:
            latex_template = f.read()
        # insert latex_expr at $$ in the template
        s = latex_template.split("$$")
        latex_document = s[0] + "$"+ latex_expr + "$" + s[1]
        # write the result to a tex file in the temporary directory
        with open(tmp_tex,'w') as w:
            w.write(latex_document)
        # create the pdf (in the temporary directory) from the latexfile
        subprocess.run('pdflatex -output-directory ' + tmpdir + ' ' + tmp_tex,
                       shell=True,capture_output=True)
        # create the SVG from the pdf
        subprocess.run('pdf2svg ' + tmp_pdf + ' ' + str(svg_full_path),
                       shell=True,capture_output=True)        
    return svg_output_name

# Match &lt;
sane_re = re.compile(r'(&lt;)')

# Replace any occurance of '&lt;' on the input with '<'
# (because the --gladtex option of pandoc turns < into &lt;)
def sanitize_eq (expr):
    ret = sane_re.sub('<',expr,count=0)
    log(5,'Sanitized '+ expr + ' to ' + ret)
    return ret

# Process to currently matched latex equation to create an SVG for it
# return the replacement text for the currently matched latex equation
# (ie an <img> tag pointing the SVG file generated for it)
# If the exact same equation was already made, immediately return the
# <img> tag pointing to the SVG file previously made.
# - matchobj: object found to match the equation regular expression 
def substitute_eq (matchobj):
    if matchobj:
        # get the matched latex expression string
        match = matchobj.group(1)
        # sanitize expressions
        latex_expr = sanitize_eq(match)
        log(4,'Processing equation ' + latex_expr)
        if latex_expr in equations:
            log(5,'Already made!')
            svg_output_name = equations[latex_expr]
        else:
            svg_output_name = process_eq(latex_expr)
            equations[latex_expr]=svg_output_name
        # return the <img> tag that points to the generated SVG
        return '<img class="latexeq"' \
            + ' src="' + svg_output_name + '"' \
            + ' alt="' + latex_expr + '">'
    
# Match <eq ...>LATEXEXPRESSION</eq>
# (use .*? for non-greedy matching, to ensure the first </eq> found
# will close the regulear expression)
eq_re = re.compile(r'<eq[^>]*>(.*?)</eq>',re.DOTALL)


# Process any equations in the item (determined using the regular
# expression eq_re)
# - item: relative path to the post to be processed
def process_equations (item):
    global eq_match_count
    global basefolder
    global equations
    log(2,'- Processing equations')
    eq_match_count = 0
    equations = {}
    fname = dest_of(item)
    basefolder = fname.parent
    # open the file for reading
    with open(fname,'r') as f:
        # read the file as one string
        orig_html = f.read()
    # process and substitute each occurence of a latex equation
    processed_html = eq_re.sub(substitute_eq, orig_html, count=0)
    # write the result back to the file
    with open(fname,'w') as f:
        f.write(processed_html)


# --- POSTS

# return the path to next post following item; '' if none
# (this uses that all posts is sorted)
def next_post(item):
    if item in all_posts:
        pos = all_posts.index(item)
        if pos+1 < len(all_posts):
            return all_posts[pos+1]
    return ''

# return the path to previous post following item; '' if none
# (this uses that all posts is sorted)
def prev_post(item):
    if item in all_posts:
        pos = all_posts.index(item)
        if pos-1 >= 0:
            return all_posts[pos-1]
    return ''



# Make the html post from the md source specified by item, merging
# in any comments
def make_post(item,tmpdir):
    log(2,'- Processing post')
    post_template = get_template('ssst-post.html')
    # create destination folder if it doesnt exist yet
    os.makedirs(dest_folder_of(item), exist_ok=True)
    # blog-post path for reply links: strip the source prefix in front
    # and the index.md at the end; add comment.
    reply_path = str(item.parent) + '/comment.'
    # create metadata for next and prev links when relevant
    extra_metadata_str = ''
    next = next_post(item)
    if next != '':
        extra_metadata_str = ' --metadata next="' + item_url(next,item) + '"'
    prev = prev_post(item)
    if prev != '':
        extra_metadata_str = extra_metadata_str + \
            ' --metadata prev="' + item_url(prev,item) + '"'
    # add normalised date metadata for posts (archives and tag/category indices
    # do  not have this, but are treated as posts for the time being)
    if item in post_metadata:
        date = post_metadata[item][0]
        extra_metadata_str = extra_metadata_str + \
            ' --metadata date="' + normalize_date(date) + '"' 
    # build the command string
    src_path = src_path_of(item)
    dest_path = str(dest_of(item))
    src_path = str(src_path)
    command = 'pandoc --gladtex -s -f markdown --template=' + str(post_template) + \
        ' -A '+ tmpdir + "/comments.html" + \
        ' --metadata reply=' + reply_path + \
        ' --metadata root=' + str(item_root(item)) + \
        extra_metadata_str + \
        ' -o '+ dest_path + ' ' + src_path
    log(4,'Command ' + command)
    subprocess.run(command,shell=True,capture_output=False)

# --- COMMENTS

# Assume all comment files have been converted to html and are stored in
# the temporary directory. 
# Recursively fold all coments at the current level (specified by path in
# the temporary directory)
# and return them as a string; try: <path>.1.html (and fold its children)
# then  <path>.2
def fold_comments(path):
    log(4,'Folding ' + str(path)) 
    folded = ""
    i = 1
    while True:
        prefix = path + '.' + str(i)
        filename = prefix + '.html'
        if not os.path.exists(filename):
            break
        with open(filename,'r') as f:
            folded = folded \
                + '<div class="comment">\n'\
                + f.read() \
                + fold_comments(prefix) \
                + '</div>\n'
        i = i + 1
    return folded
    
# Process the comments associated with the item. 
# - first convert individual comments to html in tmpdir
# - then fold them into one html file (keeping the hierarchical order)
# Stores the result in tmpdir/comments.html for further processing
# by make post (this file is empty if there are no comments)
def make_comments(item,tmpdir):
    log(2,'- Processing comments')
    commentfiles = commentfiles_for(item)
    comment_template = get_template('ssst-comment.html')
    # first convert all .mdc files in src_root to .html files in tmpdir
    for c in commentfiles:
        # filename to store html converted comment in (in tmpdir)
        outf = tmpdir + "/" + str(c.name)[:-3] + "html"
        # blog-post path and comment depth indicator for reply links
        # strip the source prefix in front and mdc from the back
        reply_path = str(c.relative_to(src_root))
        reply_path = reply_path[:-3]
        log(3,'Making comment HTML ' + outf)
        command = 'pandoc -f markdown'+ \
            ' --metadata title=None'  + \
            ' --metadata reply=' + reply_path + \
            ' --template=' + str(comment_template) + \
            ' -o ' + outf + ' ' + str(c)
        log(4,'Command ' + command)
        subprocess.run(command,shell=True,capture_output=False)
    # now recursively fold in comments created in tmpdir
    comments = fold_comments(tmpdir + '/comment')
    log(5,comments)
    # and store the comments in commetns.html in the tmpdir
    with open(tmpdir + "/comments.html","w") as f:
        f.write(comments)

# --- MEDIA

media_types = ['jpg','gif','pdf','png']

def copy_media(item):
    log(2,'- Copying mediafiles')
    src_folder = src_folder_of(item)
    dest_folder = dest_folder_of(item)
    for t in media_types:
        for f in src_folder.glob("*." + t):
            log(4,'Copying media file ' + str(f) + ' to ' + str(dest_folder))
            shutil.copy2(f,dest_folder) # keep metadata

# --- CATEGORIES AND TAGS

# Process (and store) post metadata.
# Determine the categories and the tags assiociated with a post with pathname sp
# and add the pathname to the necessary categories and tag dictionaries
# Also store post metadata for each post
def process_post_metadata(post):
    global items_with_tag
    global items_with_cat
    global post_metadata
    log(2,'- Processing categories and tags for ' + str(post))
    yaml = get_yaml_dict(post)
    post_relpath = post.relative_to(src_root)
    if 'categories' in yaml:
        cats = yaml['categories']
        log(3,'Categories: ' + str(cats))
        for cat in cats:
            if cat not in items_with_cat:
                items_with_cat[cat] = []
            items_with_cat[cat].append(post_relpath)
    if 'keywords' in yaml:
        tags = yaml['keywords']
        log(3,'Tags: ' + str(tags))
        for tag in tags:
            if tag not in items_with_tag:
                items_with_tag[tag] = []
            items_with_tag[tag].append(post_relpath)
    if 'title' in yaml:
        title = yaml['title']
    else:
        title = ''
    if 'date' in yaml: 
        tmp = yaml['date']
        date = parse_date(tmp)
    else:
        date = datetime.date.today()
        warning("Date expected but not found in " + str(post) + ". Using today.")
    post_metadata[post_relpath] = (date,title)

def process_posts (srcpath):
    log(0,"Processing posts")
    for post in Path(src_root).glob('**/*.md'):
        process_post_metadata(post)
    # sort all posts by date
    for post in post_metadata:
        all_posts.append(post)
    all_posts.sort(key=lambda entry: post_metadata[entry])

    
        
# Write a link (entry_url) describing an entry (using its title and date)
# to the output stream outf
def dump_entry(entry,outname,outf):
    (date,title) = post_metadata[entry]
    datestr = normalize_date(date)
    outf.write('- [' + title + '](' + item_url(entry,outname) + \
               ') (' + datestr + ')\n')    
    

# Create a markdown document into out_name (relative path)
# (with title as title and label as subtitle)
# containing a list of links to all posts in filelist
# (assumed to be sorted by date)
def dump(out_name,title,label,filelist):
    # sort the list of pathnames by date
    # (using the fact that the path encodes the file date)
    filelist.sort()
    os.makedirs(src_folder_of(out_name),exist_ok=True)
#    with open(out_name,"w") as outf:
    with DelayedTextfile(src_path_of(out_name)) as outf:
        outf.write("---\n")
        outf.write("title: '" + title + "'\n")        
        outf.write("subtitle: '" + label.capitalize() + "'\n")
        outf.write("---\n")        
        for entry in filelist:
            dump_entry(entry,out_name,outf)
                
# Dump the category and tag dictionaries to the necessary pages in the
# ./category/.. and ./tag/... directories in the src_root (SOURCE!) tree;
# These are markdown files that will subsequently be converted to html
# through the make routine
def dump_cats_and_tags(src_root):
    log(0,'Dumping category and tag files.')
    for cat in items_with_cat:
        dump(Path("category/" + cat + "/index.sst"),
             "Category",cat,items_with_cat[cat])
    for tag in items_with_tag:
        dump(Path("tag/" + tag + "/index.sst"),
             "Tag",tag,items_with_tag[tag])

# --- ARCHIVES

# Create archive files containing all posts for a particular month in 
# ./yyyy/mm/index.md in the src_root (SOURCE!) tree;
# Also create a list of all known archives in src_root/archives.md
# These are markdown files that will subsequently be converted to html
# through the make routine
def dump_archives(src_root):
    global archive_selector
    log(0,'Dumping archives.')
    # dictionary with DelayedTextFile descriptors, indexed by archivepath
    archive_paths = {}  
    # create the root archive file
#    with open(src_root + '/archives.sst','w') as archivef:
    archive = src_path_of('archives.sst')
    log(3,'- Writing all archives to ' + str(archive))
    with DelayedTextfile(archive) as archivef:        
        archivef.write("---\n")
        archivef.write("title: 'Archives'\n")
        archivef.write("---\n")
        # process all posts and put them in the appropriate archive
        for post in all_posts:
            date = post_metadata[post][0]
            monthyear = date.strftime("%B %Y")
            archive_relative_path = date.strftime("%Y/%m/index.sst")
            archive_path = src_path_of(archive_relative_path)
            if archive_path not in archive_paths:
                # write a link to this new archive to the main archive file
                archivef.write('- [' + monthyear +'](' + \
                               item_url(Path(archive_relative_path),Path('archives.sst')) + ')\n' )
                # create a new delayed file for this monthly archvie
                log(3,'- Creating new monthly archive ' + str(archive_path))
                outf = DelayedTextfile(archive_path)
                archive_paths[archive_path] = outf
                # create a header for a new archive
                outf.write("---\n")
                outf.write("title: 'Archive " + monthyear + "'\n")        
                outf.write("---\n")
            # append the entry
            outf = archive_paths[archive_path]
            dump_entry(post,archive_relative_path,outf)
    # close all archive files
    for archive_path in archive_paths:
        archive_paths[archive_path].close()
    
# --- HOME

def summarize_post(post,tmpfile):
    date = post_metadata[post][0]
    # create a URL for the  post to be summarised
    post_url = item_url(post,Path('index.html'))
    post_path = src_path_of(post)                    
    summary_entry_template = get_template('ssst-summary-entry.html')
    command = 'head -n ' + str(summary_length) + ' ' + str(post_path) + \
            ' | pandoc -s -f markdown --template=' + str(summary_entry_template) + \
        ' --metadata entrydate="' + normalize_date(date) + '"' + \
        ' --metadata root=' + str(item_root(Path('index.html'))) + \
        ' --metadata path=' + post_url + \
        ' -o '+ str(tmpfile)
    log(4,'Command ' + command)
    subprocess.run(command,shell=True,capture_output=False)


def dump_home():
    log(0,'Making home page.')
    recent_posts = all_posts[-5:]
    with tempfile.TemporaryDirectory() as tmpdir:
        count = 0
        for post in recent_posts:
            # create a temporary html file summarizing this entry
            count = count + 1
            summarize_post(post,tmpdir + "/summary." + str(count) + ".html")
        # create the home page, including the summarized entries    
        summary_template = get_template('ssst-summary.html')
        src = src_path_of('index.sst')                       
        dest = dest_of('index.html')
        dest_tmp = dest.with_suffix('.tmp')
        command = 'pandoc -s -f markdown -t html --template=' + \
            str(summary_template) + \
            ' -A '+ tmpdir + '/summary.5.html' + \
            ' -A '+ tmpdir + '/summary.4.html' + \
            ' -A '+ tmpdir + '/summary.3.html' + \
            ' -A '+ tmpdir + '/summary.2.html' + \
            ' -A '+ tmpdir + '/summary.1.html' + \
            ' --metadata root=' + str(item_root(Path('index.html'))) + \
            ' -o '+ str(dest_tmp) + ' ' + str(src)
        log(4,'Command ' + command)
        subprocess.run(command,shell=True,capture_output=False)
        replace_when_changed(dest,dest_tmp)    

    
# --- MAKE

# Make the output (in dest_root) for the item (stored src_root)
def make(item):
    log(1,'Processing ' + str(src_root / item))
    with tempfile.TemporaryDirectory() as tmpdir:
        make_comments(item,tmpdir)
        make_post(item,tmpdir)
    process_equations(item)
    copy_media(item)

# Determine whether the osurce file sp (given src and dest directories)
# should be remade: if the destination does not exist, or when it is older
# than the source file or any of its associated comments
def is_workitem(item):
    src = src_path_of(item)
    src_time = os.path.getmtime(src)
    dest = dest_of(item)
    log(4,'Destination should be ' + str(dest))
    if not dest.exists():
        return True
    log(4,'Destination exists.')    
    dest_time = os.path.getmtime(dest)
    if (dest_time <= src_time) :
        return True
    comments = commentfiles_for(item)
    for comment in comments:
        log(5,'Comment found: ' + str(comment))
        comment_time = os.path.getmtime(comment)
        if (dest_time <= comment_time):
            return True
    return False


# match all files with extension in the tree rooted at src_root and return a
# list of all files that need to be (re)made
# the list containts Paths, relative to src_root
def worklist_for (src_root,dest_root,extension):
    list = []
    # traverse all files with extension in the subtree rooted at src
    # (This returns a 'list' of Paths (not strings))
    for src in Path(src_root).glob('**/*' + extension):
        log(2,'Considering: ' + str(src))
        item = src.relative_to(src_root)
        if force or is_workitem(item):
            log(2,'Adding ' + str(src))
            # strip the src_root
            list.append(item)
        else:
            log(2,'Ignoring ' +  str(src))
    return list

# Determine posts, pages and generated pages that need to be (re)made
def worklist (src_root,dest_root):
    log(0,'Determining files to process...')
    list = worklist_for(src_root,dest_root,".md")
    list = list + worklist_for(src_root,dest_root,".sst")
    log(0,str(len(list)) + ' files to process found')
    return list


# ---
# MAIN
# ---

# Set up arguments parsing
parser = argparse.ArgumentParser()
parser.add_argument("-s", "--source", default=src_root,
                    help="Path to the root of the source files tree")
parser.add_argument("-d", "--destination", default=dest_root,
                    help="Path to the root of the destination files tree")
parser.add_argument("-r", "--root", default=hosting_root,
                    help="Root where the site is hosted. If omitted, the root is found using relative addressing, making the site relocateable")
parser.add_argument("-t", "--templates", default=templates,
                    help="Path to directory with templates")
parser.add_argument("-v", "--verbosity", default=loglevel,
                    help="Verbosity (-1, the default, is silent)")
parser.add_argument("-l", "--logfile", default="ssst.log",
                    help="Name of file to store all log messages in")
parser.add_argument("-f", "--force", default = False, action="store_true",
                    help="(Re)make everything")
parser.add_argument("-x", "--strict", default = False, action="store_true",
                    help="Abort after warning")
parser.add_argument("-z", "--summarylength", default=summary_length,
                    help="Number of lines in post (including YAML header) to include in a summary")

args = parser.parse_args()

# Apply arguments
src_root = Path(args.source)
if not src_root.exists():
    error('Source tree %s does not exist.' % src_root)
dest_root = Path(args.destination)
if not dest_root.exists():
    error('Path to destination tree %s does not exist.' % dest_root)
hosting_root = args.root
templates = Path(args.templates)
if not templates.exists():
    error('Templates directory %s does not exist.' % templates)
loglevel = int(args.verbosity)
force = args.force
strict = args.strict
logname = args.logfile
summary_length = int(args.summarylength)

# open logfile
if logname != "":
    logfile = open(logname,"w")

# first collect all metadate (like tags and categories, but also title and date
# for all blog entries in the source tree. This initializes
# - items_with_cat
# - items_with_tag
# - post_metadata
# - all_posts (essentially the tags of post_metadata, but sorted by date)

process_posts(src_root)
log(3,str(items_with_cat))
log(3,str(items_with_tag))    
log(3,str(all_posts))

# dump the category and tag files in ./category/<categorystring>/index.sst and
# ./tag/<tagstring>/index.sst
dump_cats_and_tags(src_root)

# dump all archive files ./yyyy/mm/index.sst and ./archives.sst
dump_archives(src_root)

# make all html files
for item in worklist(src_root,dest_root):
    make(item)

# create the home page containing a summary of the most recent posts
dump_home()

if logfile != None:
    logfile.close()


