generate static pages with league/commonmark
authorMichael Wallner <mike@php.net>
Thu, 9 Sep 2021 13:18:46 +0000 (15:18 +0200)
committerMichael Wallner <mike@php.net>
Thu, 9 Sep 2021 13:18:46 +0000 (15:18 +0200)
bin/ref2html [new file with mode: 0755]
composer.json
mdref/Formatter.php [new file with mode: 0644]
mdref/Formatter/Discount.php [new file with mode: 0644]
mdref/Formatter/League.php [new file with mode: 0644]
mdref/Reference.php
public/index.php
views/stub.phtml

diff --git a/bin/ref2html b/bin/ref2html
new file mode 100755 (executable)
index 0000000..0ccc331
--- /dev/null
@@ -0,0 +1,125 @@
+#!/usr/bin/env php
+<?php
+
+namespace mdref;
+
+use http\Env\Request;
+use http\Env\Response;
+use http\Message\Body;
+use function file_put_contents;
+
+require_once __DIR__."/../vendor/autoload.php";
+define("ROOT", dirname(__DIR__));
+define("REF2HTML", true);
+
+if ($argc < 3) {
+       fprintf(STDERR, "Usage: %s <basedir> <ref>[ <ref> ...]\n", $argv[0]);
+       fprintf(STDERR, "       Note: the basedir will also be used as <base href>\n");
+       exit(1);
+}
+function say($fmt, ...$args) {
+       return fprintf(STDERR, $fmt, ...$args);
+};
+$out = $argv[1];
+if (!is_dir($out) && !mkdir($out, 0775, true)) {
+       fprintf(STDERR, "Could not create output directory %s\n", $out);
+       exit(1);
+}
+$nul = fopen("/dev/null", "w");
+$url = new BaseUrl("/" . $out . "/");
+$url->scheme = null;
+$url->host = null;
+$ref = new Reference(array_slice($argv, 2));
+$fmt = function(string $src, string $dst) use($ref, $out, $nul, $url) {
+       $req = new Request;
+       $req->setRequestMethod("GET");
+       $req->setRequestUrl($url . "./" . $src);
+       $res = new Response;
+       $res->setBody(new Body(fopen($dst, "w+")));
+       $act = new Action($ref, $req, $res, $url, $nul);
+       $act->handle();
+};
+$xfm = [
+       "php/PropertyProxy" => "propro/php/PropertyProxy",
+       "pq/Gateway" => "pq-gateway/pq/Gateway",
+       "pq/Query" => "pq-gateway/pq/Query",
+];
+$red = function($from, $dest, $name) use($out, $url) {
+       $from = $out . "/" . str_replace($dest, $from, $name);
+       if (!is_dir(dirname($from))) {
+               mkdir(dirname($from), 0775, true);
+       }
+       file_put_contents($from . ".html", <<<EOF
+<html>
+       <base href='$url'>
+       <meta http-equiv='refresh' content='0; $name'>
+</html>
+EOF
+);
+};
+$gen = function(Entry $entry) use($fmt, $out, $xfm, $red, &$gen) {
+       $src = $entry->getName();
+       $dir = $out . "/" . $src;
+       $dst = $dir . ".html";
+       foreach ($xfm as $from => $dest) {
+               if (strpos($src, $dest) !== false) {
+                       say("Redirecting from %s to %s\n", $from, $dest);
+                       $red($from, $dest, $src);
+                       break;
+               }
+       }
+       if ($entry->hasIterator()) {
+               if (!is_dir($dir)) {
+                       mkdir($dir, 0755, true);
+               }
+               foreach ($entry as $subentry) {
+                       $gen($subentry);
+               }
+       }
+       say("Generating %s from %s\n", $dst, $src);
+       $fmt($src, $dst);
+};
+/** @var $repo Repo */
+foreach ($ref as $repo) {
+       say("Entering ref %s\n", $repo->getName());
+       if (is_file($stub = $repo->getPath($repo->getName().".stub.php"))) {
+               copy($stub, $out . "/" . basename($stub));
+       }
+       foreach ($repo as $root) {
+               $gen($root);
+       }
+}
+$fmt("", $out . "/" . "index.html");
+
+$presets = [
+       "AUTHORS",
+       "LICENSE",
+       "VERSION",
+       "public/index.css" => "index.css",
+       "public/index.js" => "index.js",
+       "public/favicon.ico" => "favicon.ico",
+];
+foreach ($presets as $src => $dst) {
+       if (!is_string($src)) {
+               $src = $dst;
+       }
+       copy(ROOT . "/" . $src, $out . "/" . $dst);
+}
+// no jekyll
+touch($out . "/.nojekyll");
+// htacess for apache
+file_put_contents($out . "/.htaccess", <<<EOF
+Options -Indexes +MultiViews +FollowSymLinks
+DirectorySlash Off
+
+RewriteEngine On
+RewriteCond %{REQUEST_FILENAME} -d
+RewriteRule ^(.+)$ $1.html [L]
+
+<Files *.php>
+    ForceType text/x-php
+    SetHandler default-handler
+</Files>
+
+EOF
+);
index b017f4147bf53f226e089d0badfc202418b92b75..01de530ee30a826f24c04e03168fa295c32936c8 100644 (file)
@@ -8,21 +8,24 @@
             "email": "mike@php.net"
         }
     ],
-    "bin": ["bin/ref2stub"],
+    "bin": [
+        "bin/ref2stub",
+        "bin/ref2html"
+    ],
     "autoload": {
         "psr-4": {
             "mdref\\": "mdref/"
         }
     },
     "require": {
-        "php": "^7.3",
-        "ext-ctype": "^7.3",
-        "ext-http": "^3.2",
-        "ext-filter": "^7.3",
-        "ext-pcre": "^7.3"
+        "php": "^7.3 || ^8.0",
+        "ext-ctype": "*",
+        "ext-filter": "*",
+        "ext-pcre": "*",
+        "ext-http": "^3.2 || ^4.2",
+        "league/commonmark": "^2.0"
     },
     "suggests": {
-        "ext-discount": "A Markdown renderer is needed, this is the preferred one.",
-        "ext-cmark": "A Markdown renderer is needed, this is the fallback one."
+        "ext-discount": "A Markdown renderer is needed, this is the preferred one."
     }
 }
diff --git a/mdref/Formatter.php b/mdref/Formatter.php
new file mode 100644 (file)
index 0000000..ecde240
--- /dev/null
@@ -0,0 +1,20 @@
+<?php
+
+namespace mdref;
+
+use function class_exists;
+
+abstract class Formatter {
+    abstract function formatString(string $string) : string;
+    abstract function formatFile(string $file) : string;
+
+       static function factory() : Formatter {
+               if (class_exists("League\\CommonMark\\GithubFlavoredMarkdownConverter", true)) {
+                       return new Formatter\League;
+               }
+               if (extension_loaded("discount")) {
+                       return new Formatter\Discount;
+               }
+               throw new \Exception("No Markdown implementation found");
+       }
+}
diff --git a/mdref/Formatter/Discount.php b/mdref/Formatter/Discount.php
new file mode 100644 (file)
index 0000000..c787c7a
--- /dev/null
@@ -0,0 +1,32 @@
+<?php
+
+namespace mdref\Formatter;
+
+use mdref\Exception;
+use mdref\Formatter;
+
+use MarkdownDocument;
+
+class Discount extends Formatter {
+
+       function formatString(string $string) : string {
+               $md = \MarkdownDocument::createFromString($string);
+               $md->compile(\MarkdownDocument::AUTOLINK);
+               return $md->getHtml();
+       }
+
+       function formatFile(string $file) : string {
+               $fd = fopen($file, "r");
+               if (!$fd) {
+                       throw Exception::fromLastError();
+               }
+
+               $md = \MarkdownDocument::createFromStream($fd);
+               $md->compile(\MarkdownDocument::AUTOLINK | \MarkdownDocument::TOC);
+               $html = $md->getHtml();
+
+               fclose($fd);
+
+               return $html;
+       }
+}
diff --git a/mdref/Formatter/League.php b/mdref/Formatter/League.php
new file mode 100644 (file)
index 0000000..60ff7d4
--- /dev/null
@@ -0,0 +1,59 @@
+<?php
+
+namespace mdref\Formatter;
+
+use mdref\Exception;
+use mdref\Formatter;
+
+use League\CommonMark\Extension;
+use League\CommonMark\GithubFlavoredMarkdownConverter;
+use League\CommonMark\Normalizer;
+
+use function file_get_contents;
+use function preg_replace;
+
+class League extends Formatter {
+       private $md;
+
+       function __construct() {
+               $this->md = new GithubFlavoredMarkdownConverter([
+                       "slug_normalizer" => [
+                               "instance" => new class implements Normalizer\TextNormalizerInterface {
+                                       function normalize(string $text, $context = null) : string {
+                                               return preg_replace("/[^[:alnum:]:._-]/", ".", $text);
+                                       }
+                               }
+                       ],
+                       "heading_permalink" => [
+                               "html_class" => "permalink",
+                               "id_prefix" => "",
+                               "fragment_prefix" => "",
+                               "title" => "",
+                               "symbol" => "#",
+                               "insert" => "after",
+                               "min_heading_level" => 2,
+                       ]
+               ]);
+               $this->md->getEnvironment()->addExtension(
+                       new Extension\DescriptionList\DescriptionListExtension
+               );
+               $this->md->getEnvironment()->addExtension(
+                       new Extension\HeadingPermalink\HeadingPermalinkExtension
+               );
+               $this->md->getEnvironment()->addExtension(
+                       new Extension\Attributes\AttributesExtension
+               );
+       }
+
+       function formatString(string $string) : string {
+               return $this->md->convertToHtml($string);
+       }
+
+       function formatFile(string $file) : string {
+               $string = file_get_contents($file);
+               if ($string === false) {
+                       throw Exception::fromLastError();
+               }
+               return $this->md->convertToHtml($string);
+       }
+}
index 561bd1e2e8515f56890d7442deb2a95c23ce7c80..6e3b27e29d53de3a31f3941c92db007beb5f3711 100644 (file)
@@ -18,14 +18,20 @@ class Reference implements IteratorAggregate {
         */
        private $repos = array();
 
+       /**
+        * @var Formatter
+        */
+       private $fmt;
+
        /**
         * @param array $refs list of mdref repository paths
         */
-       public function __construct(array $refs) {
+       public function __construct(array $refs, Formatter $fmt = null) {
                foreach ($refs as $path) {
                        $repo = new Repo($path);
                        $this->repos[$repo->getName()] = $repo;
                }
+               $this->fmt = $fmt ?: Formatter::factory();
        }
 
        /**
@@ -66,39 +72,18 @@ class Reference implements IteratorAggregate {
        /**
         * @param string $string
         * @return string
-        * @throws \Exception
+        * @throws \Exception, Exception
         */
        public function formatString(string $string) : string {
-               if (extension_loaded("discount")) {
-                       $md = \MarkdownDocument::createFromString($string);
-                       $md->compile(\MarkdownDocument::AUTOLINK);
-                       return $md->getHtml();
-               }
-               if (extension_loaded("cmark")) {
-                       $node = \CommonMark\Parse($string);
-                       return \CommonMark\Render\HTML($node);
-               }
-               throw new \Exception("No Markdown implementation found");
+               return $this->fmt->formatString($string);
        }
 
        /**
         * @param string $file
         * @return string
-        * @throws \Exception
+        * @throws \Exception, Exception
         */
        public function formatFile(string $file) : string {
-               if (extension_loaded("discount")) {
-                       $fd = fopen($file, "r");
-                       $md = \MarkdownDocument::createFromStream($fd);
-                       $md->compile(\MarkdownDocument::AUTOLINK | \MarkdownDocument::TOC);
-                       $html = $md->getHtml();
-                       fclose($fd);
-                       return $html;
-               }
-               if (extension_loaded("cmark")) {
-                       $node = \CommonMark\Parse(file_get_contents($file));
-                       return \CommonMark\Render\HTML($node);
-               }
-               throw new \Exception("No Markdown implementation found");
+               return $this->fmt->formatFile($file);
        }
 }
index 2e43a16b22b6bf5a46973c561963242a9d7e31b2..65ad2d2e033f457b91500e7a444d65fd46c9b450 100644 (file)
@@ -6,9 +6,6 @@ use http\Env\Request;
 use http\Env\Response;
 use function ini_get;
 use function ini_set;
-use function spl_autoload_register;
-use function strncmp;
-use function strtr;
 use const GLOB_ONLYDIR;
 use const PATH_SEPARATOR;
 use const REFS;
@@ -23,11 +20,7 @@ if (!ini_get("date.timezone")) {
        date_default_timezone_set("UTC");
 }
 
-spl_autoload_register(function($c) {
-       if (!strncmp($c, "mdref\\", 6)) {
-               return require ROOT . "/" . strtr($c, "\\", "/") . ".php";
-       }
-});
+require_once __DIR__ . "/../vendor/autoload.php";
 
 $response = new Response;
 $ehandler = new ExceptionHandler($response);
index 05679ab1a2e914a75044f153bea99cc53b2bc09f..140cd064d783e4f9602ac638626630109200a963 100644 (file)
@@ -2,7 +2,11 @@
                                        <p><strong>Download the Stub file:</strong></p>
                                        <ul style="list-style-type: '&raquo;'">
                                                <li>
+                                                       <?php if (defined("REF2HTML") && constant("REF2HTML")) :?>
+                                                       <a href="<?= $entry->getName() ?>.stub.php"><?= $entry->getName() ?>.stub.php</a><br>
+                                                       <?php else : ?>
                                                        <a href="stub?ref=<?= $entry->getName() ?>"><?= $entry->getName() ?>.stub.php</a><br>
+                                                       <?php endif; ?>
                                                        <small>
                                                                Last modified:
                                                                <?= date_create("@".filemtime($stub))->setTimezone(new DateTimezone("UTC"))->format("Y-m-d H:i:s T") ?>
@@ -10,4 +14,3 @@
                                                </li>
                                        </ul>
                                </div>
-