120 lines
3.2 KiB
D
120 lines
3.2 KiB
D
|
import std.exception;
|
||
|
import std.experimental.logger;
|
||
|
import std.file;
|
||
|
import std.process;
|
||
|
import std.stdio;
|
||
|
|
||
|
import dyaml;
|
||
|
import vibe.vibe;
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Exception thrown when a page has syntax errors e.g.
|
||
|
*/
|
||
|
class ArticleParseException : Exception {
|
||
|
mixin basicExceptionCtors;
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Represents a page on the blog
|
||
|
*/
|
||
|
class Page {
|
||
|
/**
|
||
|
* Internal name of the article. Usually the file name.
|
||
|
*/
|
||
|
protected string m_name;
|
||
|
|
||
|
/**
|
||
|
* Slug either manually assigned or generated based on file name.
|
||
|
* Only used in the url.
|
||
|
*/
|
||
|
protected string m_slug;
|
||
|
protected string m_title;
|
||
|
protected string m_content;
|
||
|
protected string m_contentSource;
|
||
|
|
||
|
/**
|
||
|
* Option for the markdown parser: the amount of levels the header found in the markdown should
|
||
|
* be shifted. For example, 0 means H1 -> H1, 1 means H1 -> H2, 2 means H1 -> H3 and so on.
|
||
|
*/
|
||
|
protected int m_headerShift = 1;
|
||
|
|
||
|
private bool hasCalledSuper = false;
|
||
|
|
||
|
/**
|
||
|
* Creates a page from a file
|
||
|
*/
|
||
|
this(string file) {
|
||
|
this.m_name = file;
|
||
|
this.m_contentSource = readText(file);
|
||
|
// Find the seperator and split the string in two
|
||
|
const long seperatorIndex = indexOf(m_contentSource, "---\n");
|
||
|
enforce!ArticleParseException(seperatorIndex >= 0);
|
||
|
string header = m_contentSource[0..seperatorIndex];
|
||
|
|
||
|
Node node = Loader.fromString(header).load();
|
||
|
loadHeader(node);
|
||
|
assert(hasCalledSuper);
|
||
|
|
||
|
this.m_content = Page.parseMarkdown(m_contentSource[seperatorIndex + 4..$],
|
||
|
this.m_headerShift);
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Parse metadata from the header.
|
||
|
* Params:
|
||
|
* headerNode = the YAML node to parse the header metadata from.
|
||
|
*/
|
||
|
@safe
|
||
|
protected void loadHeader(Node headerNode){
|
||
|
if (headerNode.containsKey("title")) {
|
||
|
this.m_title = headerNode["title"].as!string;
|
||
|
} else {
|
||
|
warningf("%s does not contain a title", this.m_name);
|
||
|
}
|
||
|
if (headerNode.containsKey("slug")) {
|
||
|
this.m_slug = headerNode["slug"].as!string;
|
||
|
} else {
|
||
|
this.m_slug = this.m_title;
|
||
|
infof("%s does not have a slug. Using %s", this.m_name, this.m_slug);
|
||
|
}
|
||
|
hasCalledSuper = true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Starts pandoc to convert MarkDown to HTML
|
||
|
* Params:
|
||
|
* source = The MarkDown source as a string (not a path!)
|
||
|
* shiftHeader = (Optional) The amount a header needs to be shifted. If for example, it
|
||
|
* is set to 1, first level headings within MarkDown become second level
|
||
|
* headers within HTML.
|
||
|
*/
|
||
|
public static string parseMarkdown(string source, int shiftHeader = 0) {
|
||
|
string[] args = ["pandoc",
|
||
|
"-f", "markdown",
|
||
|
"-t", "html"];
|
||
|
|
||
|
if (shiftHeader != 0) args ~= "--shift-heading-level-by=" ~ to!string(shiftHeader);
|
||
|
|
||
|
ProcessPipes pandoc = pipeProcess(args);
|
||
|
pandoc.stdin.write(source);
|
||
|
pandoc.stdin.writeln();
|
||
|
pandoc.stdin.flush();
|
||
|
pandoc.stdin.close();
|
||
|
string result;
|
||
|
string line;
|
||
|
while ((line = pandoc.stdout.readln()) !is null) {
|
||
|
result ~= line;
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
@property string name() { return m_name; }
|
||
|
@property string title() { return m_title; }
|
||
|
@property string slug() { return m_slug; }
|
||
|
@property string content() { return m_content; }
|
||
|
@property string contentSource() { return m_contentSource; }
|
||
|
}
|