--- /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();