--- /dev/null
+content
+public
-[submodule "garden/third_party/parsedown"]
- path = garden/third_party/parsedown
+[submodule "third_party/parsedown"]
+ path = third_party/parsedown
url = https://github.com/erusev/parsedown.git
--- /dev/null
+site:
+ php garden.php config.php
+
+serve:
+ php -S localhost:8080 -t public
--- /dev/null
+Garden site generator
+
+To use this...
+ - Copy your content into this folder (or somewher you can access it)
+ - Refer to config.php to set up your configuration
+ - Refer to Makefile as to how to run the tool
<?php
-define('GARDEN_SITE_BASE_URL', 'https://bts.cx');
+// What is the URL of this website? (No trailing slash...)
+define('GARDEN_SITE_BASE_URL', 'http://localhost:8080');
+// Where is the markdown and other files that contain the content?
define('GARDEN_CONTENT_DIR', garden_path(__DIR__, 'content'));
+
+// Where are the templates?
define('GARDEN_TEMPLATE_DIR', garden_path(__DIR__, 'templates'));
+// Where do you want to write the website to?
define('GARDEN_OUTPUT_DIR', garden_path(__DIR__, 'public'));
-
-
// Put anything here you want the templates to be able to see...
+define('GARDEN_TEMPLATE_CONSTANTS', [
+ 'site_name' => 'garden',
+ 'site_language' => 'en',
+ 'site_copyline' => 'Content © Mr Fox',
+ 'site_author' => 'Mr Fox',
+
+ 'mastodon_link' => 'https://mastodon.website/@me',
+ 'mastodon_handle' => '@me',
-define('site_name', 'bts.cx');
+ 'itch_link' => 'https://itch.io',
+ 'itch_handle' => '@???.itch.io',
+]);
--- /dev/null
+<?php
+
+/*
+ _____ _ _____ _ _ _____ _
+ / ____| | | / ____(_) | / ____| | |
+ | | __ __ _ _ __ __| | ___ _ __ | (___ _| |_ ___ | | __ ___ _ __ ___ _ __ __ _| |_ ___ _ __
+ | | |_ |/ _` | '__/ _` |/ _ \ '_ \ \___ \| | __/ _ \ | | |_ |/ _ \ '_ \ / _ \ '__/ _` | __/ _ \| '__|
+ | |__| | (_| | | | (_| | __/ | | | ____) | | || __/ | |__| | __/ | | | __/ | | (_| | || (_) | |
+ \_____|\__,_|_| \__,_|\___|_| |_| |_____/|_|\__\___| \_____|\___|_| |_|\___|_| \__,_|\__\___/|_|
+
+ */
+
+///////////////////////////////////////////////////////////////////////////////
+// Logging
+///////////////////////////////////////////////////////////////////////////////
+
+function output_debug(...$str_segments) {
+ $str = join('', $str_segments);
+ echo($str . PHP_EOL);
+}
+
+function output_error(...$str_segments) {
+ $str = join('', $str_segments);
+ die($str);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Paths
+///////////////////////////////////////////////////////////////////////////////
+
+function garden_path(...$path_segments) {
+ $segments = array();
+ foreach ($path_segments as $path_segment) {
+ $inner_segments = explode(DIRECTORY_SEPARATOR, $path_segment);
+ foreach ($inner_segments as $inner_segment) {
+ if ($inner_segment != '') {
+ $segments[] = $inner_segment;
+ }
+ }
+ }
+ array_unshift($segments, '');
+ return join(DIRECTORY_SEPARATOR, $segments);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// String
+///////////////////////////////////////////////////////////////////////////////
+
+function garden_slug($str) {
+ $str = preg_replace('/\s+/', '-', $str);
+ $str = preg_replace('/\-+/', '-', $str);
+ $str = preg_replace('/[^\w\s\-\.]+/', '', $str);
+ $str = strtolower($str);
+ return $str;
+}
+
+function garden_url(...$url_segments) {
+ $segments = array();
+ foreach ($url_segments as $url_segment) {
+ $inner_segments = explode(DIRECTORY_SEPARATOR, $url_segment);
+ foreach ($inner_segments as $inner_segment) {
+ if ($inner_segment != '') {
+ $segments[] = garden_slug($inner_segment);
+ }
+ }
+ }
+ array_unshift($segments, '');
+ return join(DIRECTORY_SEPARATOR, $segments);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// I/O
+///////////////////////////////////////////////////////////////////////////////
+
+function garden_read_file($filename) {
+ return file_get_contents($filename);
+}
+
+function garden_write_file($filename, $content) {
+ $utf8_bom = "\xEF\xBB\xBF";
+ file_put_contents($filename, $utf8_bom . $content);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Items to process
+///////////////////////////////////////////////////////////////////////////////
+
+enum GardenItemType {
+ case Article;
+ case Image;
+ case Raw;
+}
+
+class GardenItem {
+ public $path;
+ public $type;
+ public $id;
+ public $title;
+ public $category;
+ public $url;
+ public $date;
+ public $source_filename;
+ public $target_filename;
+
+ public function __construct($path, $type, $id, $title, $category, $url, $date, $source_filename, $target_filename) {
+ $this->path = $path;
+ $this->type = $type;
+ $this->id = $id;
+ $this->title = $title;
+ $this->category = $category;
+ $this->url = $url;
+ $this->date = $date;
+ $this->source_filename = $source_filename;
+ $this->target_filename = $target_filename;
+ }
+}
+
+function garden_make_process_items($output_directory, $content_paths) {
+ $output_items = [];
+
+ foreach ($content_paths as $item) {
+ $id = garden_slug($item->filename);
+
+ $target_extension = $item->extension;
+
+ $ignore_file = false;
+ $type = GardenItemType::Raw;
+ switch ($item->extension) {
+ case 'php':
+ $ignore_file = true;
+ break;
+
+ case 'md':
+ $type = GardenItemType::Article;
+ $target_extension = 'html';
+ break;
+
+ case 'png':
+ case 'jpg':
+ case 'jpeg':
+ case 'gif':
+ $type = GardenItemType::Image;
+ break;
+ }
+
+ if ($ignore_file == true) {
+ continue;
+ }
+
+ $target_basename = $item->filename;
+ if ($target_extension != '') {
+ $target_basename .= '.' . $target_extension;
+ }
+
+ $category_components = explode(DIRECTORY_SEPARATOR, $item->path);
+ $category = count($category_components) >= 2 ? $category_components[1] : "";
+
+ $url = GARDEN_SITE_BASE_URL . garden_url($item->path, $target_basename);
+ $target_path = garden_path($output_directory, garden_url($item->path, $target_basename));
+
+ $date = filemtime($item->full_path);
+
+ $output_items[] = new GardenItem($item, $type, $id, $item->filename, $category, $url, $date, $item->full_path, $target_path);
+ }
+
+ return $output_items;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Paths
+///////////////////////////////////////////////////////////////////////////////
+
+class GardenContentPath {
+ public $full_path;
+ public $root;
+ public $path;
+ public $basename;
+ public $filename;
+ public $extension;
+
+ public function __construct($full_path, $root, $path, $path_parts) {
+ $this->full_path = $full_path;
+ $this->root = $root;
+ $this->path = $path;
+ $this->basename = $path_parts['basename'];
+ $this->filename = $path_parts['filename'];
+ $this->extension = $path_parts['extension'];
+ }
+}
+
+function garden_find_content_files($content_dir) {
+ $content_dir = realpath($content_dir);
+
+ $scan_paths = [''];
+ $output_paths = [];
+
+ while (count($scan_paths) > 0) {
+ $scan_path = array_shift($scan_paths);
+ $path_contents = scandir(garden_path($content_dir, $scan_path));
+ foreach ($path_contents as $item) {
+ if (str_starts_with($item, '.')) {
+ continue;
+ }
+
+ $full_path = garden_path($content_dir, $scan_path, $item);
+ if (is_dir($full_path)) {
+ $scan_paths[] = garden_path($scan_path, $item);
+ continue;
+ }
+
+ $path_parts = pathinfo($full_path);
+ $output_paths[] = new GardenContentPath($full_path, $content_dir, $scan_path, $path_parts);
+ }
+ }
+
+ return $output_paths;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Directories
+///////////////////////////////////////////////////////////////////////////////
+
+function garden_make_directories($content_items) {
+ foreach ($content_items as $item) {
+ $path_parts = pathinfo($item->target_filename);
+
+ $directory = $path_parts['dirname'];
+ if (is_dir($directory ) == false) {
+ mkdir($directory , 0777, true); // FIXME, permissions...
+ }
+ }
+
+ return $content_items;
+}
+
+function garden_move_raw($content_items) {
+ foreach ($content_items as $item) {
+ if ($item->type != GardenItemType::Raw && $item->type != GardenItemType::Image) { // FIXME, do we need to copy images?
+ continue;
+ }
+
+ $success = copy($item->source_filename, $item->target_filename);
+ if ($success != true) {
+ error('Failed to copy file: filename="', $item->source_filename, '"');
+ }
+ }
+
+ return $content_items;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Template
+///////////////////////////////////////////////////////////////////////////////
+
+function garden_template_render($name, $variables = null) {
+ global $garden_template_base, $garden_template_content;
+
+ $base_template = null;
+
+ $output = '';
+ while ($name != null) {
+ $path = garden_path(GARDEN_TEMPLATE_DIR, $name . '.php');
+
+ $base_template = null;
+ $base_template_variables = null;
+
+ $garden_template_base_previous = $garden_template_base;
+ $garden_template_base = function($name, $base_variables) use (&$base_template, &$base_template_variables) {
+ $base_template = $name;
+ $base_template_variables = $base_variables;
+ };
+
+ $garden_template_content_previous = $garden_template_content;
+ $garden_template_content = function() use ($output) {
+ return $output;
+ };
+
+ if ($variables != null) {
+ extract($variables);
+ }
+
+ ob_start();
+ include($path);
+ $output = ob_get_contents();
+ ob_end_clean();
+
+ $garden_template_base = $garden_template_base_previous;
+ $garden_template_content = $garden_template_content_previous;
+
+ $name = $base_template;
+ $variables = $base_template_variables;
+ }
+
+ return $output;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Template helpers
+///////////////////////////////////////////////////////////////////////////////
+
+function garden_template_base($name, $variables = null) {
+ global $garden_template_base;
+ $garden_template_base($name, $variables);
+}
+
+function garden_template_content() {
+ global $garden_template_content;
+ return $garden_template_content();
+}
+
+function garden_site_url(...$url_segments) {
+ return GARDEN_SITE_BASE_URL . garden_url(...$url_segments);
+}
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// Images
+///////////////////////////////////////////////////////////////////////////////
+
+class GardenImage {
+ public $content_item;
+ public $width;
+ public $height;
+ public $url;
+ public $target_filename;
+
+ public function __construct($content_item, $width, $height, $url, $target_filename) {
+ $this->content_item = $content_item;
+ $this->width = $width;
+ $this->height = $height;
+ $this->url = $url;
+ $this->target_filename = $target_filename;
+ }
+}
+
+function garden_make_images($post_process_items) {
+ foreach ($post_process_items as $item) {
+ $image = new Imagick($item->content_item->source_filename);
+ $image->thumbnailImage($item->width, $item->height);
+ $image->writeImage($item->target_filename);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// HTML
+///////////////////////////////////////////////////////////////////////////////
+
+require_once(garden_path(__DIR__, 'third_party', 'parsedown', 'Parsedown.php'));
+
+class GardenExtendedParsedown extends Parsedown {
+ public $post_process_items;
+ private $output_directory;
+ private $content_items;
+
+ public function __construct($output_directory, $content_items) {
+ $this->post_process_items = [];
+ $this->output_directory = $output_directory;
+ $this->content_items = $content_items;
+ $this->InlineTypes['!'][] = 'Youtube';
+ $this->InlineTypes['!'][] = 'Image';
+ $this->InlineTypes['['][] = 'WikiLinks';
+ $this->inlineMarkerList .= '!';
+ $this->inlineMarkerList .= '[';
+ }
+
+ protected function inlineImage($excerpt) {
+ if (preg_match('/^!\[\[([^\|\]]+)(\|([0-9]+)x([0-9]+))?\]\]/', $excerpt['text'], $matches)) {
+ $image_name = $matches[1];
+ $image_width = count($matches) == 5 ? $matches[3] : null;
+ $image_height = count($matches) == 5 ? $matches[4] : null;
+
+ if ($image_name == null) {
+ return;
+ }
+
+ $image_content_item = null;
+ foreach ($this->content_items as $content_item) {
+ if ($content_item->path->basename == $image_name) {
+ $image_content_item = $content_item;
+ break;
+ }
+ }
+
+ if ($image_content_item == null) {
+ return;
+ }
+
+ $target_url = $image_content_item->url;
+ if ($image_width != null && $image_height != null) {
+ $original_path = $image_content_item->path;
+
+ $target_basename = $original_path->filename . '_' . $image_width . 'x' . $image_height . '.png';
+
+ $target_url = GARDEN_SITE_BASE_URL . garden_url($original_path->path, $target_basename);
+ $target_path = garden_path($this->output_directory, garden_url($original_path->path, $target_basename));
+
+ $post_process_item = new GardenImage($image_content_item, $image_width, $image_height, $target_url, $target_path);
+ $this->post_process_items[] = $post_process_item;
+ }
+
+ return array(
+ 'extent' => strlen($matches[0]),
+ 'element' => array(
+ 'name' => 'img',
+ 'attributes' => array(
+ 'src' => $target_url,
+ ),
+ ),
+ );
+ }
+ }
+
+ protected function inlineYoutube($excerpt) {
+ if (preg_match('/^!\[\[yt:([^\]]+)\]\]/', $excerpt['text'], $matches)) {
+ $video_id = $matches[1];
+
+ if ($video_id == null) {
+ return;
+ }
+
+ return array(
+ 'extent' => strlen($matches[0]),
+ 'element' => array(
+ 'name' => 'iframe',
+ 'text' => '',
+ 'attributes' => array(
+ 'class' => "video",
+ 'type' => "text/html",
+ //'width' => "640",
+ //'height' => "360",
+ 'src' => "https://www.youtube.com/embed/" . $video_id,
+ 'frameborder' => "0",
+ 'loading' => "lazy",
+ 'referrerpolicy' => "no-referrer",
+ 'sandbox' => "allow-same-origin allow-scripts",
+ ),
+ ),
+ );
+ }
+ }
+
+ protected function inlineWikiLinks($excerpt) {
+ if (preg_match('/^\[\[(.+)\]\]/', $excerpt['text'], $matches)) {
+ $target_title = $matches[1];
+
+ if ($target_title == null) {
+ return;
+ }
+
+ $target_url = null;
+ foreach ($this->content_items as $content_item) {
+ if ($content_item->title == $target_title) {
+ $target_url = $content_item->url;
+ break;
+ }
+ }
+
+ if ($target_url == null) {
+ return;
+ }
+
+ return array(
+ 'extent' => strlen($matches[0]),
+ 'element' => array(
+ 'name' => 'a',
+ 'text' => $target_title,
+ 'attributes' => array(
+ 'href' => $target_url,
+ ),
+ ),
+ );
+ }
+ }
+}
+
+function garden_generate_html($output_directory, $content_items) {
+ $output_items = [];
+
+ $categorised_items = [];
+ foreach ($content_items as $item) {
+ if ($item->type != GardenItemType::Article) {
+ continue;
+ }
+
+ if (isset($categorised_items[$item->category]) == false) {
+ $categorised_items[$item->category] = [];
+ }
+ $categorised_items[$item->category][] = $item;
+ }
+
+ foreach ($content_items as $item) {
+ if ($item->type != GardenItemType::Article) {
+ continue;
+ }
+
+ $markdown_data = garden_read_file($item->source_filename);
+
+ $parsedown = new GardenExtendedParsedown($output_directory, $content_items);
+ $markdown_html = $parsedown->text($markdown_data);
+
+ if (count($parsedown->post_process_items) > 0) {
+ array_push($output_items, ...$parsedown->post_process_items);
+ }
+
+ $variables = GARDEN_TEMPLATE_CONSTANTS; // PHP will copy by default!
+ $variables['article'] = $item;
+ $variables['article_content'] = $markdown_html;
+ $variables['categories'] = $categorised_items;
+
+ $html_data = garden_template_render('article', $variables);
+
+ garden_write_file($item->target_filename, $html_data);
+ }
+
+ return $output_items;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Feed
+///////////////////////////////////////////////////////////////////////////////
+
+function garden_generate_atom($output_directory, $content_items) {
+ $output_items = [];
+
+ $recent_items = [];
+ foreach ($content_items as $item) {
+ if ($item->type != GardenItemType::Article) {
+ continue;
+ }
+
+ $recent_items[$item->date] = $item;
+ }
+
+ // Sort by date...
+ ksort($recent_items);
+ $recent_items = array_reverse($recent_items);
+
+ $variables = GARDEN_TEMPLATE_CONSTANTS; // PHP will copy by default!
+ $variables['recent_items'] = $recent_items;
+ $html_data = garden_template_render('atom', $variables);
+
+ garden_write_file(garden_path($output_directory, 'feed.atom'), $html_data);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Main
+///////////////////////////////////////////////////////////////////////////////
+
+function garden() {
+ $content_files = garden_find_content_files(GARDEN_CONTENT_DIR);
+ array_push($content_files, ...garden_find_content_files(GARDEN_TEMPLATE_DIR));
+ $process_items = garden_make_process_items(GARDEN_OUTPUT_DIR, $content_files);
+ garden_make_directories($process_items);
+ garden_move_raw($process_items);
+ $post_process_items = garden_generate_html(GARDEN_OUTPUT_DIR, $process_items);
+ garden_make_images($post_process_items);
+ garden_generate_atom(GARDEN_OUTPUT_DIR, $process_items);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Main code!
+///////////////////////////////////////////////////////////////////////////////
+
+assert(extension_loaded('imagick'), 'Needs Imagick');
+assert($argc >= 2, 'Please provide configuration file');
+
+// First parameter needs to be the configuration php
+$config_file = $argv[1];
+
+output_debug("Will use configuration: file='", $config_file, "'");
+require_once($config_file);
+
+garden();
+++ /dev/null
-<?php
-
-/*
- _____ _ _____ _ _ _____ _
- / ____| | | / ____(_) | / ____| | |
- | | __ __ _ _ __ __| | ___ _ __ | (___ _| |_ ___ | | __ ___ _ __ ___ _ __ __ _| |_ ___ _ __
- | | |_ |/ _` | '__/ _` |/ _ \ '_ \ \___ \| | __/ _ \ | | |_ |/ _ \ '_ \ / _ \ '__/ _` | __/ _ \| '__|
- | |__| | (_| | | | (_| | __/ | | | ____) | | || __/ | |__| | __/ | | | __/ | | (_| | || (_) | |
- \_____|\__,_|_| \__,_|\___|_| |_| |_____/|_|\__\___| \_____|\___|_| |_|\___|_| \__,_|\__\___/|_|
-
- */
-
-function output_debug(...$str_segments) {
- $str = join('', $str_segments);
- echo($str . PHP_EOL);
-}
-
-function output_error(...$str_segments) {
- $str = join('', $str_segments);
- die($str);
-}
-
-///////////////////////////////////////////////////////////////////////////////
-// Paths
-///////////////////////////////////////////////////////////////////////////////
-
-function garden_path(...$path_segments) {
- $segments = array();
- foreach ($path_segments as $path_segment) {
- $inner_segments = explode(DIRECTORY_SEPARATOR, $path_segment);
- foreach ($inner_segments as $inner_segment) {
- if ($inner_segment != '') {
- $segments[] = $inner_segment;
- }
- }
- }
- array_unshift($segments, '');
- return join(DIRECTORY_SEPARATOR, $segments);
-}
-
-///////////////////////////////////////////////////////////////////////////////
-// String
-///////////////////////////////////////////////////////////////////////////////
-
-function garden_slug($str) {
- $str = preg_replace('/\s+/', '-', $str);
- $str = preg_replace('/\-+/', '-', $str);
- $str = preg_replace('/[^\w\s\-\.]+/', '', $str);
- $str = strtolower($str);
- return $str;
-}
-
-function garden_url(...$url_segments) {
- $segments = array();
- foreach ($url_segments as $url_segment) {
- $inner_segments = explode(DIRECTORY_SEPARATOR, $url_segment);
- foreach ($inner_segments as $inner_segment) {
- if ($inner_segment != '') {
- $segments[] = garden_slug($inner_segment);
- }
- }
- }
- array_unshift($segments, '');
- return join(DIRECTORY_SEPARATOR, $segments);
-}
-
-///////////////////////////////////////////////////////////////////////////////
-// I/O
-///////////////////////////////////////////////////////////////////////////////
-
-function garden_read_file($filename) {
- return file_get_contents($filename);
-}
-
-function garden_write_file($filename, $content) {
- $utf8_bom = "\xEF\xBB\xBF";
- file_put_contents($filename, $utf8_bom . $content);
-}
-
-///////////////////////////////////////////////////////////////////////////////
-// Items to process
-///////////////////////////////////////////////////////////////////////////////
-
-enum GardenItemType {
- case Article;
- case Image;
- case Raw;
-}
-
-class GardenItem {
- public $path;
- public $type;
- public $id;
- public $title;
- public $url;
- public $date;
- public $source_filename;
- public $target_filename;
-
- public function __construct($path, $type, $id, $title, $url, $date, $source_filename, $target_filename) {
- $this->path = $path;
- $this->type = $type;
- $this->id = $id;
- $this->title = $title;
- $this->url = $url;
- $this->date = $date;
- $this->source_filename = $source_filename;
- $this->target_filename = $target_filename;
- }
-}
-
-function garden_make_process_items($output_directory, $content_paths) {
- $output_items = [];
-
- foreach ($content_paths as $item) {
- $id = garden_slug($item->filename);
-
- $target_extension = $item->extension;
-
- $ignore_file = false;
- $type = GardenItemType::Raw;
- switch ($item->extension) {
- case 'php':
- $ignore_file = true;
- break;
-
- case 'md':
- $type = GardenItemType::Article;
- $target_extension = 'html';
- break;
-
- case 'png':
- case 'jpg':
- case 'jpeg':
- case 'gif':
- $type = GardenItemType::Image;
- break;
- }
-
- if ($ignore_file == true) {
- continue;
- }
-
- $target_basename = $item->filename;
- if ($target_extension != '') {
- $target_basename .= '.' . $target_extension;
- }
-
- $url = GARDEN_SITE_BASE_URL . garden_url($item->path, $target_basename);
- $target_path = garden_path($output_directory, garden_url($item->path, $target_basename));
-
- $date = filemtime($item->full_path);
-
- $output_items[] = new GardenItem($item, $type, $id, $item->filename, $url, $date, $item->full_path, $target_path);
- }
-
- return $output_items;
-}
-
-///////////////////////////////////////////////////////////////////////////////
-// Paths
-///////////////////////////////////////////////////////////////////////////////
-
-class GardenContentPath {
- public $full_path;
- public $root;
- public $path;
- public $basename;
- public $filename;
- public $extension;
-
- public function __construct($full_path, $root, $path, $path_parts) {
- $this->full_path = $full_path;
- $this->root = $root;
- $this->path = $path;
- $this->basename = $path_parts['basename'];
- $this->filename = $path_parts['filename'];
- $this->extension = $path_parts['extension'];
- }
-}
-
-function garden_find_content_files($content_dir) {
- $content_dir = realpath($content_dir);
-
- $scan_paths = [''];
- $output_paths = [];
-
- while (count($scan_paths) > 0) {
- $scan_path = array_shift($scan_paths);
- $path_contents = scandir(garden_path($content_dir, $scan_path));
- foreach ($path_contents as $item) {
- if (str_starts_with($item, '.')) {
- continue;
- }
-
- $full_path = garden_path($content_dir, $scan_path, $item);
- if (is_dir($full_path)) {
- $scan_paths[] = garden_path($scan_path, $item);
- continue;
- }
-
- $path_parts = pathinfo($full_path);
- $output_paths[] = new GardenContentPath($full_path, $content_dir, $scan_path, $path_parts);
- }
- }
-
- return $output_paths;
-}
-
-///////////////////////////////////////////////////////////////////////////////
-// Directories
-///////////////////////////////////////////////////////////////////////////////
-
-function garden_make_directories($content_items) {
- foreach ($content_items as $item) {
- $path_parts = pathinfo($item->target_filename);
-
- $directory = $path_parts['dirname'];
- if (is_dir($directory ) == false) {
- mkdir($directory , 0777, true); // FIXME, permissions...
- }
- }
-}
-
-function garden_move_raw($content_items) {
- foreach ($content_items as $item) {
- if ($item->type != GardenItemType::Raw && $item->type != GardenItemType::Image) {
- continue;
- }
-
- $success = copy($item->source_filename, $item->target_filename);
- if ($success != true) {
- error('Failed to copy file: filename="', $item->source_filename, '"');
- }
- }
-}
-
-///////////////////////////////////////////////////////////////////////////////
-// Template
-///////////////////////////////////////////////////////////////////////////////
-
-function garden_template_render($name, $variables = null) {
- global $garden_template_base, $garden_template_content;
-
- $base_template = null;
-
- $output = '';
- while ($name != null) {
- $path = garden_path(GARDEN_TEMPLATE_DIR, $name . '.php');
-
- $base_template = null;
- $base_template_variables = null;
-
- $garden_template_base_previous = $garden_template_base;
- $garden_template_base = function($name, $base_variables) use (&$base_template, &$base_template_variables) {
- $base_template = $name;
- $base_template_variables = $base_variables;
- };
-
- $garden_template_content_previous = $garden_template_content;
- $garden_template_content = function() use ($output) {
- return $output;
- };
-
- if ($variables != null) {
- extract($variables);
- }
-
- ob_start();
- include($path);
- $output = ob_get_contents();
- ob_end_clean();
-
- $garden_template_base = $garden_template_base_previous;
- $garden_template_content = $garden_template_content_previous;
-
- $name = $base_template;
- $variables = $base_template_variables;
- }
-
- return $output;
-}
-
-///////////////////////////////////////////////////////////////////////////////
-// Template helpers
-///////////////////////////////////////////////////////////////////////////////
-
-function garden_template_base($name, $variables = null) {
- global $garden_template_base;
- $garden_template_base($name, $variables);
-}
-
-function garden_template_content() {
- global $garden_template_content;
- return $garden_template_content();
-}
-
-///////////////////////////////////////////////////////////////////////////////
-// HTML
-///////////////////////////////////////////////////////////////////////////////
-
-require_once(garden_path(__DIR__, 'third_party', 'parsedown', 'Parsedown.php'));
-
-class GardenExtendedParsedown extends Parsedown {
- private $content_items;
-
- public function __construct($content_items) {
- $this->content_items = $content_items;
- $this->InlineTypes['!'][] = 'Youtube';
- $this->InlineTypes['!'][] = 'Image';
- $this->InlineTypes['['][] = 'WikiLinks';
- $this->inlineMarkerList .= '!';
- $this->inlineMarkerList .= '[';
- }
-
- protected function inlineImage($excerpt) {
- if (preg_match('/^!\[\[([^\|\]]+)(\|([0-9]+)x([0-9]+))?\]\]/', $excerpt['text'], $matches)) {
- $image_name = $matches[1];
- $image_width = count($matches) == 5 ? $matches[3] : null;
- $image_height = count($matches) == 5 ? $matches[4] : null;
-
- if ($image_name == null) {
- return;
- }
-
- $target_url = null;
- foreach ($this->content_items as $content_item) {
- if ($content_item->path->basename == $image_name) {
- $target_url = $content_item->url;
- break;
- }
- }
-
- if ($target_url == null) {
- return;
- }
-
- return array(
- 'extent' => strlen($matches[0]),
- 'element' => array(
- 'name' => 'img',
- 'text' => '',
- 'attributes' => array(
- 'src' => $target_url,
- ),
- ),
- );
- }
- }
-
- protected function inlineYoutube($excerpt) {
- if (preg_match('/^!\[\[yt:([^\]]+)\]\]/', $excerpt['text'], $matches)) {
- $video_id = $matches[1];
-
- if ($video_id == null) {
- return;
- }
-
- return array(
- 'extent' => strlen($matches[0]),
- 'element' => array(
- 'name' => 'iframe',
- 'text' => '',
- 'attributes' => array(
- 'class' => "video",
- 'type' => "text/html",
- //'width' => "640",
- //'height' => "360",
- 'src' => "https://www.youtube.com/embed/" . $video_id,
- 'frameborder' => "0",
- 'loading' => "lazy",
- 'referrerpolicy' => "no-referrer",
- 'sandbox' => "allow-same-origin allow-scripts",
- ),
- ),
- );
- }
- }
-
- protected function inlineWikiLinks($excerpt) {
- if (preg_match('/^\[\[(.+)\]\]/', $excerpt['text'], $matches)) {
- $target_title = $matches[1];
-
- if ($target_title == null) {
- return;
- }
-
- $target_url = null;
- foreach ($this->content_items as $content_item) {
- if ($content_item->title == $target_title) {
- $target_url = $content_item->url;
- break;
- }
- }
-
- if ($target_url == null) {
- return;
- }
-
- return array(
- 'extent' => strlen($matches[0]),
- 'element' => array(
- 'name' => 'a',
- 'text' => $target_title,
- 'attributes' => array(
- 'href' => $target_url,
- ),
- ),
- );
- }
- }
-}
-
-function garden_generate_html($content_items) {
- $parsedown = new GardenExtendedParsedown($content_items);
-
- foreach ($content_items as $item) {
- if ($item->type != GardenItemType::Article) {
- continue;
- }
-
- $markdown_data = garden_read_file($item->source_filename);
- $markdown_html = $parsedown->text($markdown_data);
-
- $html_data = garden_template_render('article', [ 'article' => $item, 'article_content' => $markdown_html ]);
-
- garden_write_file($item->target_filename, $html_data);
- }
-}
-
-///////////////////////////////////////////////////////////////////////////////
-// Main
-///////////////////////////////////////////////////////////////////////////////
-
-function garden() {
- $content_files = garden_find_content_files(GARDEN_CONTENT_DIR);
- array_push($content_files, ...garden_find_content_files(GARDEN_TEMPLATE_DIR));
- $process_items = garden_make_process_items(GARDEN_OUTPUT_DIR, $content_files);
- garden_make_directories($process_items);
- garden_move_raw($process_items);
- garden_generate_html($process_items);
-}
-
-///////////////////////////////////////////////////////////////////////////////
-// Main code!
-///////////////////////////////////////////////////////////////////////////////
-
-assert($argc >= 2);
-
-// First parameter needs to be the configuration php
-$config_file = $argv[1];
-output_debug("Will use configuration: file='", $config_file, "'");
-require_once($config_file);
-
-garden();
+++ /dev/null
-Subproject commit 0b274ac959624e6c6d647e9c9b6c2d20da242004
<main>
<article>
<h1><a href="<?= $article->url ?>"><?= $article->title ?></a></h1>
- <p class="updated"><?= date('l, F jS, Y', $article->date) ?></p>
+ <p class="updated">Last updated: <?= date('l, F jS, Y', $article->date) ?></p>
<?= $article_content ?>
</article>
</main>
--- /dev/null
+<?= '<?xml version="1.0" encoding="utf-8"?>'."\n" /* So some installs of PHP don't get confused... */ ?>
+<feed xmlns="http://www.w3.org/2005/Atom">
+ <id><?= garden_site_url('/'); ?>/</id>
+
+ <title><?= $site_name; ?></title>
+
+ <updated><?= date(DATE_RFC3339, $recent_items[0]->date) ?></updated>
+
+ <link href="<?= garden_site_url('feed.atom'); ?>" rel="self" type="application/atom+xml" />
+ <link href="<?= garden_site_url('/'); ?>" rel="alternate" type="text/html" />
+
+ <author>
+ <name><?= $site_author ?></name>
+ </author>
+
+<?php foreach ($recent_items as $update): ?>
+ <entry>
+ <id><?= $update->url ?></id>
+ <title><?= $update->title ?></title>
+ <updated><?= date(DATE_RFC3339, $update->date) ?></updated>
+ <link href="<?= $update->url ?>" rel="alternate" type="text/html" />
+ <summary>An update was published.</summary>
+ <content type="xhtml">
+ <div xmlns="http://www.w3.org/1999/xhtml">
+ An update was published.
+ </div>
+ </content>
+ </entry>
+<?php endforeach; ?>
+
+</feed>
<!DOCTYPE html>
-<html lang="en">
+<html lang="<?= $site_language ?>">
<head>
+ <?php if (isset($article)): ?>
+ <title><?= $site_name ?> - <?= $article->title ?></title>
+ <?php else: ?>
+ <title><?= $site_name ?></title>
+ <?php endif ?>
+
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title><?= site_name ?> - <?= $article->title; ?></title>
+ <link rel="stylesheet" href="<?= garden_site_url('style', 'screen.css') ?>">
+ <link rel="alternate" type="application/atom+xml" href="<?= garden_site_url('feed.atom') ?>">
</head>
<body>
- <header>
- <p>Find me <a href="https://mastodon.gamedev.place/@btsherratt" rel="me">@btsherratt</a> or on <a href="https://bts.itch.io/">itch.io</a></p>
- </header>
+ <div class="container">
+ <header>
+ <h1><a href="<?= garden_site_url('/') ?>"><?= $site_name ?></a></h1>
+ <p>Find me <a href="<?= $mastodon_link ?>" rel="me"><?= $mastodon_handle ?></a> or <a href="<?= $itch_link ?>"><?= $itch_handle ?></a></p>
+ </header>
+
+ <nav>
+ <ul>
+ <?php foreach ($categories as $category_name => $category_items): ?>
+ <?php if ($category_name != ''): ?>
+ <li>
+ <?= $category_name ?>
+ <ul>
+ <?php foreach ($category_items as $category_item): ?>
+ <li><a href="<?= $category_item->url ?>"><?= $category_item->title ?></a></li>
+ <?php endforeach; ?>
+ </ul>
+ </li>
+ <?php endif ?>
+ <?php endforeach ?>
+ </ul>
+ </nav>
- <?= garden_template_content(); ?>
+ <?= garden_template_content(); ?>
- <footer>
- <div>
- </div>
- </footer>
+ <footer>
+ <div>
+ <?= $site_copyline ?>
+ </div>
+ </footer>
+ </div>
</body>
</html>
--- /dev/null
+html {
+ line-height: 1.15;
+ -webkit-text-size-adjust:100%
+}
+
+body {
+ background-color: rgba(255, 255, 255);
+ font-family: -apple-system, "BlinkMacSystemFont", "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
+ margin: 0;
+ padding: 0;
+}
+
+.container {
+ display: grid;
+ grid-template:
+ "head head" 7em
+ "nav main" auto
+ "nav foot" 2em / 15em 1fr;
+ margin: 0 auto;
+ min-height: 100vh;
+ padding: 0;
+ width: 60em;
+}
+
+header {
+ grid-area: head;
+}
+
+header h1 {
+ font-size: 2em;
+}
+
+nav {
+ background-color: rgb(250, 250, 250);
+ grid-area: nav;
+ padding: 1em;
+}
+
+nav > ul {
+ list-style-type: none;
+ padding: 0;
+}
+
+nav > ul > li > ul {
+ list-style-type: square;
+}
+
+main {
+ grid-area: main;
+ padding: 1em;
+}
+
+footer {
+ font-size: 0.6em;
+ grid-area: foot;
+ padding: 1em;
+ text-align: right;
+}
+
+img {
+ max-width: 100%;
+ margin: 0 auto;
+ max-height: 75vh;
+ border-radius:4px
+}
+
+a {
+ border-bottom: 1px solid rgb(217, 217, 217);
+ color: rgb(26, 26, 26);
+ text-decoration: none;
+ padding: 0 0.1em;
+}
+
+a:hover {
+ background: rgb(255, 250, 241);
+ color: #000 !important;
+}
+
+h1, h2, h3, h4, h5, h6 {
+ line-height: 1.3;
+ margin-bottom: 0;
+ padding-bottom: 0;
+}
+
+:is(h1, h2, h3) {
+ line-height: 1.2;
+}
+
+:is(h1, h2) {
+ max-width: 40ch;
+}
+
+:is(h2, h3):not(:first-child) {
+ margin-top: 2em;
+}
+
+hr {
+ margin: 4em auto;
+ border: 0;
+ height: 1px;
+ background: #ccc;
+}
+
+
+code:not([class*="language"]) {
+ font-family: Consolas, Monaco, Andale Mono, Ubuntu Mono, monospace;
+ font-size: 1.75ex;
+ color: #444;
+ background-color: rgba(0, 0, 0, 0.1);
+ padding-right: 0.15em;
+ padding-left: 0.15em;
+}
+
+pre code {
+ margin: 2rem 0;
+ padding: 0.5em 1rem;
+ display: block;
+ border-left: 3px solid rgba(0, 0, 0, 0.35);
+ background-color: rgba(0, 0, 0, 0.05);
+ border-radius: 0 0.25rem 0.25rem 0;
+ overflow-x: auto;
+ font-size: 0.8em !important;
+}
+
+blockquote {
+ margin: 2rem 0;
+ padding: 0.5em 1rem;
+ border-left: 3px solid rgba(0, 0, 0, 0.35);
+ background-color: rgba(0, 0, 0, 0.05);
+ border-radius: 0 0.25rem 0.25rem 0;
+}
+
--- /dev/null
+Subproject commit 0b274ac959624e6c6d647e9c9b6c2d20da242004