summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid T. Sadler <davidtsadler@googlemail.com>2021-05-07 17:11:05 +0100
committerDavid T. Sadler <davidtsadler@googlemail.com>2021-05-07 17:11:05 +0100
commit6b0d2b51f546a9964f18b58e794ade7bd7096475 (patch)
treef27bdb3fd5a04199a9ebc58e5a29588547a0c7c7
parentc709d0c51f03b3010d8f2f19ee73f96c6a06ffe0 (diff)
Add build script
-rw-r--r--html_templates/default.html12
-rw-r--r--scripts/build.php112
-rw-r--r--scripts/functions.php312
-rw-r--r--www_assets/images/favicon.pngbin0 -> 190 bytes
4 files changed, 436 insertions, 0 deletions
diff --git a/html_templates/default.html b/html_templates/default.html
new file mode 100644
index 0000000..21abb9a
--- /dev/null
+++ b/html_templates/default.html
@@ -0,0 +1,12 @@
+<!doctype html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <title>{{ $title }}</title>
+ <link rel="shortcut icon" href="/images/favicon.png">
+ </head>
+ <body>
+ <section>{{ $contents }}</section>
+ </body>
+</html>
diff --git a/scripts/build.php b/scripts/build.php
new file mode 100644
index 0000000..ecb0d9d
--- /dev/null
+++ b/scripts/build.php
@@ -0,0 +1,112 @@
+<?php declare(strict_types=1);
+
+require_once __DIR__.DIRECTORY_SEPARATOR.'functions.php';
+
+$hostname = null;
+$output = null;
+
+$opts = getopt(
+ 'h:o:',
+ [
+ 'hostname:',
+ 'help::',
+ 'output:',
+ ]
+);
+
+if ($opts === false) {
+ usage();
+}
+
+foreach($opts as $opt => $value) {
+ if ($value === false) {
+ continue;
+ }
+
+ switch ($opt)
+ {
+ case 'h':
+ case 'hostname':
+ $hostname = $value;
+ break;
+ case 'help':
+ usage();
+ break;
+ case 'o':
+ case 'output':
+ $output = $value;
+ break;
+ }
+}
+
+
+if (!$hostname || !$output) {
+ usage();
+}
+
+if (!is_dir($output)) {
+ echo "Unable to locate specified output directory $output.\n\n";
+ exit(1);
+}
+
+if (!is_writable($output)) {
+ echo "Output directory $output is not writable.\n\n";
+ exit(1);
+}
+
+$geminiOutput = "$output/gemini";
+
+if (file_exists($geminiOutput)) {
+ echo "Gemini build directory $geminiOutput already exists.\n\n";
+ exit(1);
+}
+
+$wwwOutput = "$output/www";
+
+if (file_exists($wwwOutput)) {
+ echo "WWW build directory $wwwOutput already exists.\n\n";
+ exit(1);
+}
+
+$src = __DIR__.DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR.'src';
+
+if (!is_dir($src)) {
+ echo "Unable to locate src directory $src.\n\n";
+ exit(1);
+}
+
+if (!is_readable($src)) {
+ echo "Src directory $src is not readable.\n\n";
+ exit(1);
+}
+
+$htmlTemplateDiretory = __DIR__.DIRECTORY_SEPARATOR.'../html_templates';
+
+if (!is_dir($htmlTemplateDiretory)) {
+ echo "Unable to locate html template directory $htmlTemplateDiretory.\n\n";
+ exit(1);
+}
+
+if (!is_readable($htmlTemplateDiretory)) {
+ echo "Src directory $htmlTemplateDiretory is not readable.\n\n";
+ exit(1);
+}
+
+$assetsDiretory = __DIR__.DIRECTORY_SEPARATOR.'../www_assets';
+
+if (!is_dir($assetsDiretory)) {
+ echo "Unable to locate assets directory $assetsDiretory.\n\n";
+ exit(1);
+}
+
+if (!is_readable($assetsDiretory)) {
+ echo "Src directory $assetsDiretorY is not readable.\n\n";
+ exit(1);
+}
+
+$siteMetaData = getSiteMetaData($src);
+
+buildGeminiSite($siteMetaData, $geminiOutput);
+
+buildWWWSite($siteMetaData, $wwwOutput, $htmlTemplateDiretory, $assetsDiretory);
+
diff --git a/scripts/functions.php b/scripts/functions.php
new file mode 100644
index 0000000..de563fc
--- /dev/null
+++ b/scripts/functions.php
@@ -0,0 +1,312 @@
+<?php declare(strict_types=1);
+
+const HEADER = '/^(#+)\s+(.*)/';
+const LINK = '/^=&gt;\s+(\S+)\s*(.*)/';
+const LIST_ITEM = '/^\*\s+(.*)/';
+const PRE = '/^```(.*)?/';
+const QUOTE = '/^&gt;\s+(.*)/';
+
+function usage(): void
+{
+ echo "Usage: php build.php [options]
+
+ -h --hostname <hostname> Hostname of site. Used when generating HTML.
+ --help Displays this message.
+ -o --output <directory> Directory where site will be built into.
+
+";
+ exit(1);
+}
+
+function getSiteMetaData(string $src): array
+{
+ $src = realpath($src);
+
+ $iter = new \RecursiveIteratorIterator(
+ new \RecursiveDirectoryIterator($src, \FilesystemIterator::SKIP_DOTS),
+ \RecursiveIteratorIterator::SELF_FIRST
+ );
+
+ $metaData = [];
+
+ foreach ($iter as $fileInfo) {
+ if (!is_file($fileInfo->getRealPath())) {
+ continue;
+ }
+ $metaData[] = getFileMetaData($src, $fileInfo);
+ }
+
+ return $metaData;
+}
+
+function getFileMetaData(string $src, \SplFileInfo $fileInfo): array
+{
+ $input = $fileInfo->getRealPath();
+
+ $url = str_replace($src, '', $input);
+
+ $urlData = parseUrl($url);
+ $contentData = parseContent(file_get_contents($input));
+
+ return [
+ 'input' => $input,
+ 'url' => $url,
+ 'date' => $urlData['date'],
+ 'category' => $urlData['category'],
+ 'isPost' => $urlData['isPost'],
+ 'title' => $contentData['title'],
+ 'author' => $contentData['author'],
+ ];
+}
+
+function parseUrl(string $url): array
+{
+ $date = null;
+ $category = null;
+ $isPost = false;
+
+ /**
+ * Assume that only posts have both a date and a category.
+ */
+ if (preg_match('/\/posts\/(.+)\/(\d\d\d\d-\d\d-\d\d)\//', $url, $matches) === 1) {
+ $date = $matches[2];
+ $category = $matches[1];
+ $isPost = true;
+ }
+
+ return [
+ 'date' => $date,
+ 'category' => $category,
+ 'isPost' => $isPost,
+ ];
+}
+
+function parseContent(string $content): array
+{
+ $title = null;
+ $author = null;
+
+ if (preg_match('/^# (.+)$/m', $content, $matches) === 1) {
+ $title = $matches[1];
+ }
+
+ if (preg_match('/^> .+ By (.+)$/m', $content, $matches) === 1) {
+ $author = $matches[1];
+ }
+
+ return [
+ 'title' => $title,
+ 'author' => $author,
+ ];
+}
+
+function buildGeminiSite(array $siteMetaData, string $output): void
+{
+ foreach ($siteMetaData as $fileMetaData) {
+ $destFile = $output.DIRECTORY_SEPARATOR.$fileMetaData['url'];
+
+ $destDirectory = dirname($destFile);
+
+ if (!file_exists($destDirectory) && !mkdir($destDirectory, 0777, true)) {
+ echo "Unable to create Gemini site directory $destDirectory.";
+ exit(1);
+ }
+
+ copy($fileMetaData['input'], $destFile);
+ }
+}
+
+function buildWWWSite(array $siteMetaData, string $output, string $htmlTemplateDiretory, $assetsDiretory): void
+{
+ foreach ($siteMetaData as $fileMetaData) {
+ $destFile = $output.DIRECTORY_SEPARATOR.$fileMetaData['url'];
+
+ $destDirectory = dirname($destFile);
+
+ if (!file_exists($destDirectory) && !mkdir($destDirectory, 0777, true)) {
+ echo "Unable to create WWW site directory $destDirectory.";
+ exit(1);
+ }
+
+ file_put_contents(
+ str_replace('.gmi', '.html', $destFile),
+ buildHtmlFile(
+ $fileMetaData,
+ gemtext2hmtl(file_get_contents($fileMetaData['input'])),
+ $htmlTemplateDiretory
+ )
+ );
+ }
+
+ copyWWWAssets($assetsDiretory, $output);
+}
+
+function buildHtmlFile(array $fileMetaData, string $contents, string $htmlTemplateDiretory): string
+{
+ return str_replace(
+ [
+ '{{ $title }}',
+ '{{ $contents }}'
+ ],
+ [
+ $fileMetaData['title'],
+ $contents,
+ ],
+ file_get_contents($htmlTemplateDiretory.DIRECTORY_SEPARATOR.'default.html')
+ );
+}
+
+function gemtext2hmtl(string $gemtext): string
+{
+ return renderGemtextTokens(parseGemtext($gemtext));
+}
+
+function parseGemtext(string $gemtext): array
+{
+ $tokens = [];
+
+ $lines = preg_split('/\r?\n/', htmlspecialchars($gemtext));
+
+ $index = 0;
+ $numLines = count($lines);
+ $line = fn($index) => trim($lines[$index]);
+
+ while($index < $numLines) {
+ if (preg_match(HEADER, $line($index), $matches) === 1) {
+ [, $levels, $content] = $matches;
+
+ $tokens[] = [
+ 'type' => HEADER,
+ 'level' => strlen($levels),
+ 'content' => $content,
+ ];
+ } elseif (preg_match(LINK, $line($index), $matches) === 1) {
+ [, $href, $content] = $matches;
+
+ $tokens[] = [
+ 'type' => LINK,
+ 'href' => $href,
+ 'content' => $content != '' ? $content : $href,
+ ];
+ } elseif (preg_match(LIST_ITEM, $line($index), $matches) === 1) {
+ $items = [];
+
+ while ($index < $numLines) {
+ if (preg_match(LIST_ITEM, $line($index), $matches) === 0) {
+ break;
+ }
+
+ [, $content] = $matches;
+
+ $items[] = $content;
+
+ $index++;
+
+ }
+
+ $index--;
+
+ $tokens[] = [
+ 'type' => LIST_ITEM,
+ 'items' => $items,
+ ];
+ } elseif (preg_match(PRE, $line($index), $matches) === 1) {
+ [, $alt] = $matches;
+
+ $items = [];
+
+ $index++;
+
+ while ($index < $numLines) {
+ $item = $line($index);
+
+ if (preg_match(PRE, $item, $matches) === 1) {
+ break;
+ }
+
+ $items[] = $item;
+
+ $index++;
+ }
+
+ $tokens[] = [
+ 'type' => PRE,
+ 'alt' => $alt,
+ 'items' => $items,
+ ];
+ } elseif (preg_match(QUOTE, $line($index), $matches) === 1) {
+ [, $content] = $matches;
+
+ $tokens[] = [
+ 'type' => QUOTE,
+ 'content' => $content,
+ ];
+ } else {
+ $tokens[] = [
+ 'type' => null,
+ 'content' => $line($index),
+ ];
+ }
+
+ $index++;
+ }
+
+ return $tokens;
+}
+
+function renderGemtextTokens(array $tokens): string
+{
+ return implode("\n", array_filter(array_map(function ($token) {
+ switch ($token['type']) {
+ case HEADER:
+ return sprintf('<h%s>%s</h%s>', $token['level'], $token['content'], $token['level']);
+ case LINK:
+ return sprintf('<a href="%s">%s</a>', $token['href'], $token['content']);
+ case LIST_ITEM:
+ return sprintf(
+ "<ul>\n%s\n</ul>",
+ implode("\n", array_map(function ($item) { return sprintf("<li>%s</li>", $item);}, $token['items']))
+ );
+ case PRE:
+ return $token['alt'] ?
+ sprintf("<pre><code class=\"%s\">\n%s\n</code></pre>", $token['alt'], implode("\n", $token['items']))
+ :
+ sprintf("<pre>\n%s\n</pre>", $token['alt'], implode('', $token['items']));
+ case QUOTE:
+ return sprintf('<blockquote>%s</blockquote>', $token['content']);
+ default:
+ return $token['content'] !== '' ? sprintf('<p>%s</p>', $token['content']) : null;
+ }
+ }, $tokens)));
+}
+
+function copyWWWAssets(string $assetsDiretory, string $output): void
+{
+ $assetsDiretory = realpath($assetsDiretory);
+
+ $iter = new \RecursiveIteratorIterator(
+ new \RecursiveDirectoryIterator($assetsDiretory, \FilesystemIterator::SKIP_DOTS),
+ \RecursiveIteratorIterator::SELF_FIRST
+ );
+
+ foreach ($iter as $fileInfo) {
+ if (!is_file($fileInfo->getRealPath())) {
+ continue;
+ }
+
+ $input = $fileInfo->getRealPath();
+ $url = str_replace($assetsDiretory, '', $input);
+
+ $destFile = $output.DIRECTORY_SEPARATOR.$url;
+
+ $destDirectory = dirname($destFile);
+
+ if (!file_exists($destDirectory) && !mkdir($destDirectory, 0777, true)) {
+ echo "Unable to create WWW asset directory $destDirectory.";
+ exit(1);
+ }
+
+ copy($input, $destFile);
+ }
+}
+
diff --git a/www_assets/images/favicon.png b/www_assets/images/favicon.png
new file mode 100644
index 0000000..4909792
--- /dev/null
+++ b/www_assets/images/favicon.png
Binary files differ