Browse Source

Added metadata and date sorting, updated readme

* Metadata is now parsed from the org file
* Added character encoding stuff to HTML templates
* Improved styling
master
Macoy Madson 4 years ago
parent
commit
8c6e772c71
  1. 55
      ContentConverter.py
  2. 26
      ReadMe.org
  3. 29
      SimpleBlogServer.py
  4. 2
      templates/BlogPost.html
  5. 32
      templates/Home.html
  6. 39
      webResources/styles.css

55
ContentConverter.py

@ -1,3 +1,5 @@
import datetime
import orgparse
import os
import subprocess
@ -62,11 +64,41 @@ contentCache = []
renderedCache = []
renderedDictionary = {}
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 renderedDictionary
global renderedContentDictionary
renderedCache = []
# Get all rendered files
for root, dirs, files in os.walk(renderedDirectory):
@ -76,9 +108,15 @@ def updateRenderedCache():
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))
# No use for the value yet, we just want fast key lookups
renderedDictionary[contentPath] = renderedFile
# This sucks
contentFile = "{}/{}.{}".format(contentDirectory, contentPath, "org")
metadata = RenderedContentMetadata(contentFile, contentPath, renderedFile)
metadataGetOrgProperties(metadata)
renderedContentDictionary[contentPath] = metadata
"""
Interface
@ -86,7 +124,7 @@ Interface
def getAllPostsList():
allPosts = []
for key, value in renderedDictionary.items():
for key, value in renderedContentDictionary.items():
# Hide folders and files with '_hidden' (they can still be retrieved though)
if '_hidden' in key:
continue
@ -94,11 +132,14 @@ def getAllPostsList():
allPosts.append(key)
return allPosts
def getRenderedContentDictionary():
return renderedContentDictionary
def getRenderedBody(contentPath):
body = None
if contentPath in renderedDictionary:
renderedFilename = renderedDictionary[contentPath]
if contentPath in renderedContentDictionary:
renderedFilename = renderedContentDictionary[contentPath].renderedFilePath
renderedFile = open(renderedFilename)
body = renderedFile.readlines()
body = "".join(body)

26
ReadMe.org

@ -5,13 +5,15 @@ This project is meant to be a quick-and-dirty blog based on org-mode formatted d
You can put your ~.org~ files in ~content/~ and it will be rendered into ~renderedContent~ within fifteen minutes, or immediately after starting the server.
** Setup
<<Setup>>
This project requires Python 3.
*** 1. Install ~tornado~
*** 1. Install Python dependencies
The server is powered by ~tornado~. I use ~orgparse~ to parse org document properties. ~pandoc~ is used for converting org files into html files.
#+BEGIN_SRC sh
pip3 install tornado
pip3 install tornado orgparse
#+END_SRC
*** 2. Install pandoc
@ -29,12 +31,19 @@ This will be used to convert ~.org~ files into ~.html~.
openssl req -x509 -nodes -days 365 -newkey rsa:1024 -keyout certificates/server_jupyter_based.crt.key -out certificates/server_jupyter_based.crt.pem
#+END_SRC
If you have a domain name, you should use [[https://certbot.eff.org/][Certbot]] to generate your certificates. These won't pop the security warning like the self-signing ones will.
*** 4. Run the server
#+BEGIN_SRC sh
python3 simple-org-blog.py
python3 SimpleBlogServer.py
#+END_SRC
If you want to redirect all HTTP visits to the blog server (which is HTTPS only), also run the redirect server:
#+BEGIN_SRC sh
python3 RedirectToHttpsServer.py
#+END_SRC
*** 5. Trust the certificate
Open your browser and visit ~https://localhost:8888~.
@ -44,3 +53,14 @@ Your web browser should complain that the website's owner cannot be verified. Th
You can safely click ~Advanced~ and add the certificate as trustworthy, because you've signed the certificate and trust yourself :).
If you want to get rid of this, you'll need to get a signing authority like ~LetsEncrypt~ to generate your certificate.
** Deploying on the internet
I used [[https://lightsail.aws.amazon.com][Amazon AWS Lightsail]] to host this server for [[https://macoy.me][my personal blog]]:
1. Create an Ubuntu machine. I used the lowest spec ($3.50/month option) because I don't expect much traffic
2. Open up port ~80~ and ~443~ in the Lightsail *Network* configuration page
3. Use the Amazon SSH stuff to start setting up the machine
4. ~sudo apt update && sudo apt upgrade~ is necessary to install Python and such
5. Use [[https://certbot.eff.org/][Certbot]] to generate certificates
6. Modify [[file:SimpleBlogServer.py::80][SimpleBlogServer port]] to be port 443 (HTTPS default)
7. Follow the [[Setup]] section normally
8. Use [[https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AccessingInstancesLinux.html][SCP]] to copy content from local machine into Amazon ~content/~

29
SimpleBlogServer.py

@ -32,8 +32,35 @@ def getBlogHtmlBody(requestedContent):
class HomeHandler(tornado.web.RequestHandler):
def get(self):
allPosts = ContentConverter.getAllPostsList()
# Build content list
contentListHtml = ''
renderedContentDictionary = ContentConverter.getRenderedContentDictionary()
metadataList = []
for contentPath, metadata in renderedContentDictionary.items():
metadataList.append(metadata)
# Sort by newest to oldest
metadataList.sort(key = lambda metadata: metadata.properties["PUBLISHED"], reverse = True)
for metadata in metadataList:
if "_hidden" in metadata.contentPath:
continue
# Turn folders into tags
tags = metadata.contentPath.split('/')[:-1]
tagsHtml = ''
for tag in tags:
tagsHtml += '<label>{}</label>'.format(tag)
contentListHtml += ('<div class="blogPostLinkContainer"><a class="blogPostLink" href="blog/{contentPath}">{title}</a><time class="publishedDate">— {published}</time>{tagsHtml}</div>\n'
.format(contentPath = metadata.contentPath,
title = metadata.properties["TITLE"],
published = metadata.properties["PUBLISHED"].strftime("%B %d, %Y"),
tagsHtml = tagsHtml))
# Home is also just a rendered content file, just with a special name
renderedHomeBody = getBlogHtmlBody('Home_hidden')
self.render("templates/Home.html", allPosts=allPosts, homeBody=renderedHomeBody)
self.render("templates/Home.html", allPosts=allPosts, homeBody=renderedHomeBody, contentList=contentListHtml)
class BlogHandler(tornado.web.RequestHandler):
def get(self, request):

2
templates/BlogPost.html

@ -1,5 +1,7 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>{{ title }}</title>
<!-- For mobile -->
<meta name="viewport" content="width=device-width, initial-scale=1">

32
templates/Home.html

@ -1,18 +1,18 @@
<!doctype html>
<html>
<head>
<title>Home</title>
<!-- For mobile -->
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="/webResources/styles.css">
</head>
<body>
{% raw homeBody %}
<head>
<meta charset="utf-8">
<title>Home</title>
<!-- For mobile -->
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="/webResources/styles.css">
</head>
<body>
{% raw homeBody %}
<nav>
{% for post in allPosts %}
<a href="blog/{{post}}">{{post}}</a><br />
{% end %}
</nav>
</body>
</html>
<nav>
{% raw contentList %}
</nav>
</body>
</html>

39
webResources/styles.css

@ -19,29 +19,42 @@ a,
li,
tr,
td,
th {
th,
time {
color: #c7ae95;
font-family: Arial;
}
.blogPostLinkContainer {
margin-bottom: 7px;
}
.blogPostLink {
font-size: larger;
}
/* unvisited link */
a:link {
/* color: red; */
text-decoration: none;
}
/* visited link */
a:visited {
color: #c7c795;
color: #c7c795;
text-decoration: none;
}
/* mouse over link */
a:hover {
color: #aec795;
color: #aec795;
text-decoration: underline;
}
/* selected link */
a:active {
color: #95c7ae;
color: #95c7ae;
text-decoration: underline;
}
p,
@ -52,6 +65,17 @@ td {
color: #95aec7;
}
time {
color: #95aec7;
/* color: #c7ae9; */
font-style: italic;
font-size: smaller;
}
.publishedDate {
margin-left: 20px;
}
/* Font sizes for consistently sized text */
p,
blockquote,
@ -61,8 +85,11 @@ li {
}
label {
font-size: large;
color: #f07f43;
/* font-size: large; */
/* color: #f07f43; */
color: #c79595;
margin-left: 10px;
font-style: italic;
}
img {

Loading…
Cancel
Save