A simple orgmode HTTP server written with Tornado
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

196 lines
6.9 KiB

import datetime
import orgparse
import os
import subprocess
contentDirectory = "content"
renderedDirectory = "renderedContent"
# Pairs of extension, pandoc read type
# HTML->HTML is essentially a copy, with some pandoc sugar added (see pandoc docs)
convertableContentTypes = [(".org", "org"), (".html", "html")]
contentExtensions = []
for contentType in convertableContentTypes:
contentExtensions.append(contentType[0])
contentExtensions = tuple(contentExtensions)
renderedContentTypes = [(".html", "html")]
defaultRenderedTypeIndex = 0
renderedContentExtensions = []
for contentType in renderedContentTypes:
renderedContentExtensions.append(contentType[0])
renderedContentExtensions = tuple(renderedContentExtensions)
def getContentReadType(contentFilename):
contentExtension = contentFilename[contentFilename.rfind("."):]
for contentType in convertableContentTypes:
if contentExtension == contentType[0]:
return contentType[1]
# Strip the content directory
def getContentLocalName(contentFilename):
return contentFilename[len(contentDirectory):]
# Strip the rendered directory
def getRenderedLocalName(filename):
return filename[len(renderedDirectory):]
def stripExtension(filename):
return filename[:filename.rfind(".")]
def contentFilenameToRenderedFilename(contentFilename):
return "{}{}{}".format(renderedDirectory,
stripExtension(getContentLocalName(contentFilename)),
renderedContentTypes[defaultRenderedTypeIndex][0])
def renderContent(contentFilename):
# Content modified recently; render it
print("\tRendering {}".format(contentFilename))
outputFilename = contentFilenameToRenderedFilename(contentFilename)
# Make subdirectory if necessary
if not os.path.exists(os.path.dirname(outputFilename)):
os.makedirs(os.path.dirname(outputFilename))
# TODO: Support multiple output formats?
subprocess.run(["pandoc",
"-r", getContentReadType(contentFilename),
"-w", renderedContentTypes[defaultRenderedTypeIndex][1],
"-o", outputFilename, contentFilename])
# List of all content files
contentCache = []
renderedCache = []
renderedContentDictionary = {}
class RenderedContentMetadata:
def __init__(self, contentFile, contentPath, renderedFilePath):
self.contentFile = contentFile
self.contentPath = contentPath
self.renderedFilePath = renderedFilePath
# Properties inherited from the org properties. Code-queried properties added by default
self.properties = {}
# Set a default date far in the past which is obviously wrong (so you'll fix your data)
self.properties["PUBLISHED"] = datetime.datetime(2000, 1, 1)
self.properties["TITLE"] = None
def metadataGetOrgProperties(metadata):
if not os.path.exists(metadata.contentFile):
print('ERROR: Could not find associated org file "{}" for content file "{}"\n'
'\tThe file will be missing necessary metadata'.format(metadata.contentFile,
metadata.renderedFilePath))
return
orgRoot = orgparse.load(metadata.contentFile)
for node in orgRoot[1:]:
for property, value in node.properties.items():
if property == "PUBLISHED":
metadata.properties[property] = datetime.datetime.strptime(value, '%Y-%m-%d')
else:
metadata.properties[property] = value
# Set TITLE as the first node if it's not a property
if not metadata.properties["TITLE"]:
metadata.properties["TITLE"] = node.heading
def updateRenderedCache():
global renderedCache
global renderedContentDictionary
renderedCache = []
# Get all rendered files
for root, dirs, files in os.walk(renderedDirectory):
for file in files:
if file.endswith(renderedContentExtensions):
renderedFile = os.path.join(root, file)
renderedCache.append(renderedFile)
# The path actually used to look up the content (strip '/' from front)
contentPath = getRenderedLocalName(stripExtension(renderedFile))[1:]
print("\t'{}' = '{}'".format(contentPath, renderedFile))
# This sucks
contentFile = "{}/{}.{}".format(contentDirectory, contentPath, "org")
metadata = RenderedContentMetadata(contentFile, contentPath, renderedFile)
metadataGetOrgProperties(metadata)
renderedContentDictionary[contentPath] = metadata
"""
Interface
"""
def getAllPostsList():
allPosts = []
for key, value in renderedContentDictionary.items():
# Hide folders and files with '_hidden' (they can still be retrieved though)
if '_hidden' in key:
continue
allPosts.append(key)
return allPosts
def getRenderedContentDictionary():
return renderedContentDictionary
def getRenderedBody(contentPath):
body = None
if contentPath in renderedContentDictionary:
renderedFilename = renderedContentDictionary[contentPath].renderedFilePath
renderedFile = open(renderedFilename)
body = renderedFile.readlines()
body = "".join(body)
renderedFile.close()
return body
def checkForContentChange():
global contentCache
print("Checking for content updates...")
# Get all content files
contentCache = []
for root, dirs, files in os.walk(contentDirectory):
# I'm going to use git to version content, so ignore the .git dir
if '.git' in root:
continue
for file in files:
if file.endswith(contentExtensions):
contentCache.append(os.path.join(root, file))
updateRenderedCache()
numRenderedFiles = 0
# Compare timestamps to determine which files need regeneration
for contentFilename in contentCache:
contentModified = os.path.getmtime(contentFilename)
contentLocalName = getContentLocalName(contentFilename)
renderedFileFound = False
for renderedFile in renderedCache:
if stripExtension(renderedFile[len(renderedDirectory):]) != stripExtension(contentLocalName):
continue
renderedFileFound = True
renderedModified = os.path.getmtime(renderedFile)
# Modified content
if contentModified > renderedModified:
renderContent(contentFilename)
numRenderedFiles += 1
# New content file
if not renderedFileFound:
renderContent(contentFilename)
numRenderedFiles += 1
# We generated new content; make sure the cache is up-to-date
if numRenderedFiles:
updateRenderedCache()
print("Checking for content updates done. Rendered {} files".format(numRenderedFiles))