From 6a97dab17a6ee9a5a87ebb396da78fe87847794d Mon Sep 17 00:00:00 2001 From: Michael Wallner Date: Wed, 20 Aug 2014 15:45:38 +0200 Subject: [PATCH 01/16] upgrade autocracy --- mdref/Action.php | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/mdref/Action.php b/mdref/Action.php index 46ab755..d85e300 100644 --- a/mdref/Action.php +++ b/mdref/Action.php @@ -9,11 +9,11 @@ use http\Controller\Observer; */ class Action extends Observer { - private function serveReference(\http\Url $url, \http\Controller\Payload $payload) { + private function serveReference(\http\Url $url, \stdClass $payload) { $finder = new Finder($this->baseUrl, REFS); $path = $finder->find($url); $payload->listing = new RefListing($path, - $finder->glob($path, "/[_a-zA-Z]*.md")); + $finder->glob($path, "/[:_a-zA-Z]*.md")); $payload->title = $payload->listing->getSelf()->formatLink(); $payload->refs = $finder; if ($path->isFile()) { @@ -24,7 +24,7 @@ class Action extends Observer } } - private function serveInternal(\http\Url $url, \http\Controller\Payload $payload) { + private function serveInternal(\http\Url $url, \stdClass $payload) { $finder = new Finder($this->baseUrl, ROOT); $path = $finder->find($url, ""); if ($path->isFile("")) { @@ -65,15 +65,18 @@ class Action extends Observer function update(\SplSubject $ctl) { /* @var \http\Controller $ctl */ try { - $pld = $ctl->getPayload(); + $pld = new \stdClass; + $ctl[Observer\View::class] = function() use($pld) { + return $pld; + }; + $pld->baseUrl = $this->baseUrl; $url = $this->baseUrl->mod($ctl->getRequest()->getRequestUrl()); $pld->permUrl = implode("/", $this->baseUrl->params($url)); - if ($this->serveReference($url, $pld) || $this->serveInternal($url, $pld)) { return; } elseif ($this->servePublic($url, $ctl->getResponse())) { - $ctl->detachAll("\\http\\Controller\\Observer\\View"); + $ctl->detachAll(Observer\View::class); return; } @@ -81,8 +84,10 @@ class Action extends Observer if (strcmp($url->path, $this->baseUrl->path)) { throw new \http\Controller\Exception(404, "Could not find '$url'"); } - } catch (\Exception $e) { - $ctl->getPayload()->exception = $e; + } catch (\Exception $exception) { + $ctl[Observer\View::class] = function() use($exception) { + return compact("exception"); + }; } } } -- 2.30.2 From e95ff5f1866c75b3d1d63d640324af3f6c3f87b9 Mon Sep 17 00:00:00 2001 From: Michael Wallner Date: Tue, 9 Sep 2014 11:35:19 +0200 Subject: [PATCH 02/16] fix markdown in descriptions --- mdref/Markdown.php | 11 ++++++++++- mdref/RefEntry.php | 4 ++-- mdref/RefListing.php | 2 +- views/index.phtml | 9 +++++++-- views/mdref.phtml | 4 ++-- 5 files changed, 22 insertions(+), 8 deletions(-) diff --git a/mdref/Markdown.php b/mdref/Markdown.php index 6d761c2..e8c5cb6 100644 --- a/mdref/Markdown.php +++ b/mdref/Markdown.php @@ -12,7 +12,7 @@ class Markdown /** * @param \mdref\Path $path */ - function __construct(Path $path) { + function __construct(Path $path = null) { $this->path = $path; } @@ -20,6 +20,9 @@ class Markdown * @return string */ function __toString() { + if (!$this->path) { + return ""; + } try { $r = fopen($this->path->getFullPath(".md"), "r"); $md = \MarkdownDocument::createFromStream($r); @@ -31,4 +34,10 @@ class Markdown } return $html; } + + function quick($string) { + $md = \MarkdownDocument::createFromString($string); + $md->compile(\MarkdownDocument::AUTOLINK); + return $md->getHtml(); + } } diff --git a/mdref/RefEntry.php b/mdref/RefEntry.php index 2ad3d27..6c54674 100644 --- a/mdref/RefEntry.php +++ b/mdref/RefEntry.php @@ -114,7 +114,7 @@ class RefEntry function readTitle() { $this->openFile(); fseek($this->file, 1, SEEK_SET); - return htmlspecialchars(fgets($this->file)); + return fgets($this->file); } /** @@ -126,7 +126,7 @@ class RefEntry fseek($this->file, 0, SEEK_SET); fgets($this->file); fgets($this->file); - return htmlspecialchars(fgets($this->file)); + return fgets($this->file); } /** diff --git a/mdref/RefListing.php b/mdref/RefListing.php index 3816be2..4c3b629 100644 --- a/mdref/RefListing.php +++ b/mdref/RefListing.php @@ -71,7 +71,7 @@ class RefListing implements \Countable, \Iterator * @return \mdref\RefEntry */ function current() { - return new RefEntry($this->path, $this->key());//$this->format($this->key()); + return new RefEntry($this->path, $this->key()); } /** diff --git a/views/index.phtml b/views/index.phtml index dbd3017..417419d 100644 --- a/views/index.phtml +++ b/views/index.phtml @@ -10,8 +10,13 @@ recurse($refs, "/*.md", function($entry, $pattern, callable $recursor) { ?> diff --git a/views/mdref.phtml b/views/mdref.phtml index 5cec21d..08df2e1 100644 --- a/views/mdref.phtml +++ b/views/mdref.phtml @@ -6,8 +6,8 @@
  • formatLink(true)?>

    -

    readDescription()?>

    -

    readTitle()?>

    +

    quick($entry->readDescription())?>

    +

    esc($entry->readTitle())?>

  • -- 2.30.2 From da32cc003ce0fce6fae3139e803b7550efa89ad8 Mon Sep 17 00:00:00 2001 From: Michael Wallner Date: Mon, 6 Oct 2014 10:28:34 +0200 Subject: [PATCH 03/16] nice sorting --- mdref/Finder.php | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/mdref/Finder.php b/mdref/Finder.php index 26397ff..e3734e3 100644 --- a/mdref/Finder.php +++ b/mdref/Finder.php @@ -74,14 +74,16 @@ class Finder */ function glob(Path $path, $pattern, $flags = GLOB_BRACE) { if (strlen($path->getBaseDir())) { - return glob($path->getFullPath($pattern), $flags) ?: array(); - } - $glob = array(); - foreach ($this->refs as $ref) { - $glob = array_merge($glob, array_map(function ($fn) use ($ref) { - return substr($fn, strlen($ref)); - }, glob($ref . $pattern, $flags))); + $glob = glob($path->getFullPath($pattern), $flags) ?: array(); + } else { + $glob = array(); + foreach ($this->refs as $ref) { + $glob = array_merge($glob, array_map(function ($fn) use ($ref) { + return substr($fn, strlen($ref)); + }, glob($ref . $pattern, $flags))); + } } + sort($glob, SORT_STRING|SORT_FLAG_CASE); return $glob; } } -- 2.30.2 From 6478b415c59070f70ed860f3a592377eef9783b1 Mon Sep 17 00:00:00 2001 From: Michael Wallner Date: Wed, 8 Oct 2014 11:34:42 +0200 Subject: [PATCH 04/16] re-implement all the things --- .gitignore | 1 + VERSION | 2 +- bin/cli-server | 7 +- mdref/Action.php | 198 ++++++++++++++++++----------- mdref/Entry.php | 254 +++++++++++++++++++++++++++++++++++++ mdref/ExceptionHandler.php | 77 +++++++++-- mdref/File.php | 73 +++++++++++ mdref/Finder.php | 89 ------------- mdref/Markdown.php | 43 ------- mdref/Path.php | 108 ---------------- mdref/RefEntry.php | 164 ------------------------ mdref/RefListing.php | 98 -------------- mdref/Reference.php | 47 +++++++ mdref/Repo.php | 135 ++++++++++++++++++++ mdref/Tree.php | 113 +++++++++++++++++ public/index.css | 9 +- public/index.php | 2 +- views/index.phtml | 35 ++--- views/layout.phtml | 8 +- views/mdref.phtml | 35 +++-- views/sidebar.phtml | 56 +++++--- 21 files changed, 909 insertions(+), 645 deletions(-) create mode 100644 mdref/Entry.php create mode 100644 mdref/File.php delete mode 100644 mdref/Finder.php delete mode 100644 mdref/Markdown.php delete mode 100644 mdref/Path.php delete mode 100644 mdref/RefEntry.php delete mode 100644 mdref/RefListing.php create mode 100644 mdref/Reference.php create mode 100644 mdref/Repo.php create mode 100644 mdref/Tree.php diff --git a/.gitignore b/.gitignore index 030f4e0..bc34c0b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /nbproject/ /composer.phar /vendor/ +/refs/ diff --git a/VERSION b/VERSION index a163131..3eefcb9 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.1.0alpha +1.0.0 diff --git a/bin/cli-server b/bin/cli-server index 15b4b55..05d700b 100755 --- a/bin/cli-server +++ b/bin/cli-server @@ -35,8 +35,11 @@ then printf "\t list of those (optional, multiple)\n\n" printf "Environment:\n" printf "\tREFPATH colon separated list of refpaths\n\n" - printf "At least one refpath must be given, either through the environment " - printf "with REFPATH or as command line argument.\n\n" + printf "Examples:\n" + printf "\t\$ REFPATH=refs/foo:refs/bar ./bin/cli-server\n\n" + printf "\t\$ ./bin/cli-server refs/*\n\n" + printf "\tAt least one refpath must be given, either through the environment\n" + printf "\twith REFPATH or as command line argument.\n\n" exit 1 fi diff --git a/mdref/Action.php b/mdref/Action.php index d85e300..dafc8e5 100644 --- a/mdref/Action.php +++ b/mdref/Action.php @@ -5,89 +5,145 @@ namespace mdref; use http\Controller\Observer; /** - * The sole action controller of mdref + * Request handler */ -class Action extends Observer -{ - private function serveReference(\http\Url $url, \stdClass $payload) { - $finder = new Finder($this->baseUrl, REFS); - $path = $finder->find($url); - $payload->listing = new RefListing($path, - $finder->glob($path, "/[:_a-zA-Z]*.md")); - $payload->title = $payload->listing->getSelf()->formatLink(); - $payload->refs = $finder; - if ($path->isFile()) { - $payload->html = new Markdown($path); - $payload->sublisting = new RefListing($path, - $finder->glob($path, "/[_a-z]*.md")); - return true; - } - } +class Action extends Observer { + /** + * Reference paths + * @var string + */ + protected $refpath; - private function serveInternal(\http\Url $url, \stdClass $payload) { - $finder = new Finder($this->baseUrl, ROOT); - $path = $finder->find($url, ""); - if ($path->isFile("")) { - $payload->html = $path->toHtml(); - return true; - } + /** + * The reference + * @var \mdref\Reference + */ + private $reference; + + /** + * Initialize the reference + */ + protected function init() { + $this->reference = new Reference(explode(PATH_SEPARATOR, $this->refpath)); } - private function getType($file) { - static $inf = null; - static $typ = array(".css" => "text/css", ".js" => "applicatin/javascript"); + /** + * Create the view payload + * @param \http\Controller $ctl + * @return \stdClass + */ + private function createPayload(\http\Controller $ctl) { + $pld = new \stdClass; - $ext = strrchr($file, "."); - if (isset($typ[$ext])) { - return $typ[$ext]; + try { + $pld->quick = function($string) { + $md = \MarkdownDocument::createFromString($string); + $md->compile(\MarkdownDocument::AUTOLINK); + return $md->getHtml(); + }; + + $pld->file = function($file) { + $fd = fopen($file, "r"); + $md = \MarkdownDocument::createFromStream($fd); + $md->compile(\MarkdownDocument::AUTOLINK | \MarkdownDocument::TOC); + $html = $md->getHtml(); + fclose($fd); + return $html; + }; + + $pld->ref = implode("/", $this->baseUrl->params( + $this->baseUrl->mod($ctl->getRequest()->getRequestUrl()))); + + $pld->refs = $this->reference; + $pld->baseUrl = $this->baseUrl; + + } catch (\Exception $e) { + $pld->exception = $e; } - if (!$inf) { - $inf = new \FINFO(FILEINFO_MIME_TYPE); - } - return $inf->file($file); + return $pld; + } + + /** + * Redirect to canononical url + * @param \http\Controller $ctl + * @param string $cnn + */ + private function serveCanonical($ctl, $cnn) { + $ctl->detachAll(Observer\View::class); + $ctl->getResponse()->setHeader("Location", $this->baseUrl->mod($cnn)); + $ctl->getResponse()->setResponseCode(301); + } + + /** + * Serve index.css + * @param \http\Controller $ctl + */ + private function serveStylesheet($ctl) { + $ctl->detachAll(Observer\View::class); + $ctl->getResponse()->setHeader("Content-Type", "text/css"); + $ctl->getResponse()->setBody(new \http\Message\Body(fopen(ROOT."/public/index.css", "r"))); } - private function servePublic(\http\Url $url, \http\Env\Response $res) { - $finder = new Finder($this->baseUrl, ROOT."/public"); - $path = $finder->find($url, ""); - if ($path->isFile("")) { - $res->setHeader("Content-Type", $this->getType($path->getFullPath(""))); - $res->setBody(new \http\Message\Body(fopen($path->getFullPath(""),"r"))); - return true; + /** + * Serve index.js + * @param \http\Controller $ctl + */ + private function serveJavascript($ctl) { + $ctl->detachAll(Observer\View::class); + $ctl->getResponse()->setHeader("Content-Type", "application/javascript"); + $ctl->getResponse()->setBody(new \http\Message\Body(fopen(ROOT."/public/index.js", "r"))); + } + + /** + * Serve a preset + * @param \http\Controller $ctl + * @param \stdClass $pld + * @throws \http\Controller\Exception + */ + private function servePreset($ctl, $pld) { + switch ($pld->ref) { + case "AUTHORS": + case "LICENSE": + case "VERSION": + $pld->text = file_get_contents(ROOT."/$pld->ref"); + break; + case "index.css": + $this->serveStylesheet($ctl); + break; + case "index.js": + $this->serveJavascript($ctl); + break; + default: + throw new \http\Controller\Exception(404, "$pld->ref not found"); } } - + /** - * Implements \SplObserver - * @param \SplSubject $ctl + * Implements Observer + * @param \SplSubject $ctl \http\Controller */ - function update(\SplSubject $ctl) { - /* @var \http\Controller $ctl */ - try { - $pld = new \stdClass; - $ctl[Observer\View::class] = function() use($pld) { - return $pld; - }; - - $pld->baseUrl = $this->baseUrl; - $url = $this->baseUrl->mod($ctl->getRequest()->getRequestUrl()); - $pld->permUrl = implode("/", $this->baseUrl->params($url)); - if ($this->serveReference($url, $pld) || $this->serveInternal($url, $pld)) { - return; - } elseif ($this->servePublic($url, $ctl->getResponse())) { - $ctl->detachAll(Observer\View::class); - return; - } - - /* fallthrough */ - if (strcmp($url->path, $this->baseUrl->path)) { - throw new \http\Controller\Exception(404, "Could not find '$url'"); - } - } catch (\Exception $exception) { - $ctl[Observer\View::class] = function() use($exception) { - return compact("exception"); - }; + public function update(\SplSubject $ctl) { + /* @var http\Controller $ctl */ + $pld = $this->createPayload($ctl); + $ctl[Observer\View::class] = function() use($pld) { + return $pld; + }; + + if (!isset($pld->ref) || !strlen($pld->ref)) { + /* front page */ + return; + } + + if (($repo = $this->reference->getRepoForEntry($pld->ref, $cnn))) { + /* direct match */ + $pld->entry = $repo->getEntry($pld->ref); + } else if (strlen($cnn)) { + /* redirect */ + $this->serveCanonical($ctl, $cnn); + } else { + $this->servePreset($ctl, $pld); } } -} + +} \ No newline at end of file diff --git a/mdref/Entry.php b/mdref/Entry.php new file mode 100644 index 0000000..587cd1e --- /dev/null +++ b/mdref/Entry.php @@ -0,0 +1,254 @@ +repo = $repo; + $this->name = $name; + $this->list = explode("/", $name); + $this->path = $repo->hasEntry($name); + } + + /** + * Get the compound name, e.g. "pq/Connection/exec" + * @return string + */ + public function getName() { + return $this->name; + } + + /** + * Get the containing repository + * @return \mdref\Repo + */ + public function getRepo() { + return $this->repo; + } + + /** + * Get the file path, if any + * @return string + */ + public function getPath() { + return $this->path; + } + + /** + * Get the file instance of this entry + * @return \mdref\File + */ + public function getFile() { + if (!$this->file) { + $this->file = new File($this->path); + } + return $this->file; + } + + /** + * Read the title of the ref entry file + * @return string + */ + public function getTitle() { + if ($this->isFile()) { + return $this->getFile()->readTitle(); + } + if ($this->isRoot()) { + return $this->repo->getRootEntry()->getTitle(); + } + return $this->name; + } + + /** + * Read the description of the ref entry file + * @return string + */ + public function getDescription() { + if ($this->isFile()) { + return $this->getFile()->readDescription(); + } + if ($this->isRoot()) { + return $this->repo->getRootEntry()->getDescription(); + } + return $this; + } + + /** + * Read the intriductory section of the refentry file + * @return string + */ + public function getIntro() { + if ($this->isFile()) { + return $this->getFile()->readIntro(); + } + if ($this->isRoot()) { + return $this->repo->getRootEntry()->getIntro(); + } + return ""; + } + + /** + * Check if the refentry exists + * @return bool + */ + public function isFile() { + return strlen($this->path) > 0; + } + + /** + * Check if this is the first entry of the reference tree + * @return bool + */ + public function isRoot() { + return count($this->list) === 1; + } + + /** + * Get the parent ref entry + * @return \mdref\Entry + */ + public function getParent() { + if ("." !== ($dirn = dirname($this->name))) { + return $this->repo->getEntry($dirn); + } + } + + /** + * Get the list of parents up-down + * @return array + */ + public function getParents() { + $parents = array(); + for ($parent = $this->getParent(); $parent; $parent = $parent->getParent()) { + array_unshift($parents, $parent); + } + return $parents; + } + + /** + * Guess whether this ref entry is about a function or method + * @return bool + */ + public function isFunction() { + $base = end($this->list); + return $base{0} === "_" || ctype_lower($base{0}); + } + + /** + * Guess whether this ref entry is about a namespace, interface or class + * @return bool + */ + public function isNsClass() { + $base = end($this->list); + return ctype_upper($base{0}); + } + + /** + * Display name + * @return string + */ + public function __toString() { + $parts = explode("/", trim($this->getName(), "/")); + $myself = array_pop($parts); + if (!$parts) { + return $myself; + } + $parent = end($parts); + + switch ($myself{0}) { + case ":": + return "★" . substr($myself, 1); + + default: + if (!ctype_lower($myself{0}) || ctype_lower($parent{0})) { + return $myself; + } + case "_": + return $parent . "::" . $myself; + } + } + + /** + * Get the base name of this ref entry + * @return string + */ + public function getBasename() { + return dirname($this->path) . "/" . basename($this->path, ".md"); + } + + /** + * Guess whether there are any child nodes + * @param string $glob + * @return boolean + */ + function hasIterator($glob = null) { + if (strlen($glob)) { + return glob($this->getBasename() . "/$glob"); + } elseif ($this->isRoot()) { + return true; + } else { + return is_dir($this->getBasename()); + } + } + + /** + * Guess whether there are namespace/interface/class child nodes + * @return bool + */ + function hasNsClasses() { + return $this->hasIterator("/[A-Z]*.md"); + } + + /** + * Guess whether there are function/method child nodes + * @return bool + */ + function hasFunctions() { + return $this->hasIterator("/[a-z_]*.md"); + } + + /** + * Implements \IteratorAggregate + * @return \mdref\Tree child nodes + */ + function getIterator() { + return new Tree($this->getBasename(), $this->repo, $this->isRoot()); + } +} diff --git a/mdref/ExceptionHandler.php b/mdref/ExceptionHandler.php index 1c9b7da..13e7f18 100644 --- a/mdref/ExceptionHandler.php +++ b/mdref/ExceptionHandler.php @@ -5,26 +5,50 @@ namespace mdref; use http\Env as HTTP; /** - * mdref exception handler + * Exception and error handler */ class ExceptionHandler { - function __construct() { + /** + * Set up error/exception/shutdown handler + */ + public function __construct() { set_exception_handler($this); set_error_handler($this); + register_shutdown_function($this); } - function __invoke($e, $msg = null) { + /** + * The exception/error/shutdown handler callback + */ + public function __invoke($e = null, $msg = null) { if ($e instanceof \Exception) { try { - echo static::html($e); + echo static::htmlException($e); } catch (\Exception $ignore) { headers_sent() or HTTP::setResponseCode(500); + die("FATAL ERROR"); } - } else { + } elseif (isset($e, $msg)) { throw new \Exception($msg, $e); + } elseif (($error = error_get_last())) { + switch ($error["type"]) { + case E_PARSE: + case E_ERROR: + case E_USER_ERROR: + case E_CORE_ERROR: + case E_COMPILE_ERROR: + while (ob_get_level()) { + if (!@ob_end_clean()) { + break; + } + } + $message = sprintf("%s in %s at line %d", + $error["message"], $error["file"], $error["line"]); + echo static::htmlError("Application Error", $message, 500, ""); + break; + } } - return true; } /** @@ -35,7 +59,7 @@ class ExceptionHandler * @param array $trace_tag * @return string */ - static function html(\Exception $e, array $title_tag = ["h1"], array $message_tag = ["p"], array $trace_tag = ["pre", "style='font-size:smaller'"]) { + public static function htmlException(\Exception $e, array $title_tag = ["h1"], array $message_tag = ["p"], array $trace_tag = ["pre", "style='font-size:smaller;overflow-x:scroll'"]) { if ($e instanceof \http\Controller\Exception) { $code = $e->getCode() ?: 500; foreach ($e->getHeaders() as $key => $val) { @@ -44,15 +68,44 @@ class ExceptionHandler } else { $code = 500; } + + for ($html = ""; $e; $e = $e->getPrevious()) { + $html .= static::htmlError(HTTP::getResponseStatusForCode($code), + $e->getMessage(), $code, $e->getTraceAsString(), + $title_tag, $message_tag, $trace_tag); + } + return $html; + } + + /** + * Format an error as HTML + * @param string $title + * @param string $message + * @param int $code + * @param string $trace + * @param array $title_tag + * @param array $message_tag + * @param array $trace_tag + * @return string + */ + public static function htmlError($title, $message, $code, $trace = null, array $title_tag = ["h1"], array $message_tag = ["p"], array $trace_tag = ["pre", "style='font-size:smaller;overflow-x:scroll'"]) { HTTP::setResponseCode($code); - $name = HTTP::getResponseStatusForCode($code); + $html = sprintf("<%s>%s\n<%s>%s\n", - implode(" ", $title_tag), $name, $title_tag[0], - implode(" ", $message_tag), $e->getMessage(), $message_tag[0]); + implode(" ", $title_tag), $title, $title_tag[0], + implode(" ", $message_tag), $message, $message_tag[0]); if ($trace_tag) { - $html .= sprintf("<%s>%s\n", - implode(" ", $trace_tag), $e->getTraceAsString(), $trace_tag[0]); + if (!isset($trace)) { + ob_start(); + debug_print_backtrace(); + $trace = ob_get_clean(); + } + if (!empty($trace)) { + $html .= sprintf("<%s>%s\n", + implode(" ", $trace_tag), $trace, $trace_tag[0]); + } } + return $html; } } diff --git a/mdref/File.php b/mdref/File.php new file mode 100644 index 0000000..0ac3da2 --- /dev/null +++ b/mdref/File.php @@ -0,0 +1,73 @@ +fd = fopen($path, "rb"); + } + + /** + * Read the title of the refentry + * @return string + */ + public function readTitle() { + if (0 === fseek($this->fd, 1, SEEK_SET)) { + return fgets($this->fd); + } + } + + /** + * Read the description of the refentry + * @return string + */ + public function readDescription() { + if (0 === fseek($this->fd, 0, SEEK_SET) + && (false !== fgets($this->fd)) + && (false !== fgets($this->fd))) { + return fgets($this->fd); + } + } + + /** + * Read the first subsection of a global refentry + * @return string + */ + public function readIntro() { + $intro = ""; + if (0 === fseek($this->fd, 0, SEEK_SET)) { + $header = false; + + while (!feof($this->fd)) { + if (false === ($line = fgets($this->fd))) { + break; + } + /* search first header and read until next header*/ + if ("## " === substr($line, 0, 3)) { + if ($header) { + break; + } else { + $header = true; + continue; + } + } + if ($header) { + $intro .= $line; + } + } + } + return $intro; + } +} diff --git a/mdref/Finder.php b/mdref/Finder.php deleted file mode 100644 index e3734e3..0000000 --- a/mdref/Finder.php +++ /dev/null @@ -1,89 +0,0 @@ -refs = $paths; - $this->baseUrl = $baseUrl; - } - - /** - * @return \http\Controller\Url - */ - function getBaseUrl() { - return $this->baseUrl; - } - - /** - * Find a markdown reference file in one REFPATH. If nothing could be found - * an empty Path will be returned. - * - * @param \http\Url $requestUrl - * @return Path - */ - function find(\http\Url $requestUrl, $ext = ".md") { - $file = implode(DIRECTORY_SEPARATOR, $this->baseUrl->params($requestUrl)); - - foreach ($this->refs as $base) { - $path = new Path($base, $file); - if ($path->isFile($ext)) { - return $path; - } - } - - return new Path; - } - - /** - * Glob either in a Path's base dir, or, if the path does not have a base - * dir set, in each REFPATH paths. - * - * @param \mdref\Path $path - * @param string $pattern glob pattern - * @param int $flags glob flags - * @return array glob result - */ - function glob(Path $path, $pattern, $flags = GLOB_BRACE) { - if (strlen($path->getBaseDir())) { - $glob = glob($path->getFullPath($pattern), $flags) ?: array(); - } else { - $glob = array(); - foreach ($this->refs as $ref) { - $glob = array_merge($glob, array_map(function ($fn) use ($ref) { - return substr($fn, strlen($ref)); - }, glob($ref . $pattern, $flags))); - } - } - sort($glob, SORT_STRING|SORT_FLAG_CASE); - return $glob; - } -} diff --git a/mdref/Markdown.php b/mdref/Markdown.php deleted file mode 100644 index e8c5cb6..0000000 --- a/mdref/Markdown.php +++ /dev/null @@ -1,43 +0,0 @@ -path = $path; - } - - /** - * @return string - */ - function __toString() { - if (!$this->path) { - return ""; - } - try { - $r = fopen($this->path->getFullPath(".md"), "r"); - $md = \MarkdownDocument::createFromStream($r); - $md->compile(\MarkdownDocument::AUTOLINK | \MarkdownDocument::TOC); - $html = $md->getHtml(); - fclose($r); - } catch (\Exception $e) { - $html = ExceptionHandler::html($e); - } - return $html; - } - - function quick($string) { - $md = \MarkdownDocument::createFromString($string); - $md->compile(\MarkdownDocument::AUTOLINK); - return $md->getHtml(); - } -} diff --git a/mdref/Path.php b/mdref/Path.php deleted file mode 100644 index e0d00c7..0000000 --- a/mdref/Path.php +++ /dev/null @@ -1,108 +0,0 @@ -baseDir = $baseDir; - $this->path = $path; - } - - /** - * Create a copy of this path with a different path name - * - * @param string $path - * @return \mdref\Path - */ - function __invoke($path) { - $that = clone $this; - $that->path = $path; - return $that; - } - - /** - * Retrurns the full path as string - * @return string - */ - function __toString() { - return $this->getFullPath(); - } - - /** - * The base directory - * @return string - */ - function getBaseDir() { - return $this->baseDir; - } - - /** - * The path name relative to the base dir - * @return string - */ - function getPathName() { - return $this->path; - } - - /** - * The full path - * @param string $ext extension - * @return string - */ - function getFullPath($ext = "") { - $path = ""; - if (strlen($this->baseDir)) { - $path .= $this->baseDir . DIRECTORY_SEPARATOR; - } - if (strlen($this->path)) { - $path .= $this->path; - } - $path .= $ext; - return $path; - } - - /** - * Retrieve a another subpath within the base dir - * @param type $path - * @return string - */ - function getSubPath($path) { - return trim(substr($path, strlen($this->baseDir)), DIRECTORY_SEPARATOR); - } - - function isFile($ext = ".md") { - return is_file($this->getFullPath($ext)); - } - - function toHtml() { - $head = sprintf("

    %s

    \n", htmlspecialchars(basename($this->getPathName()))); - if ($this->isFile()) { - $html = htmlspecialchars(file_get_contents($this->getFullPath())); - } elseif ($this->isFile("")) { - $html = htmlspecialchars(file_get_contents($this->getFullPath(""))); - } else { - throw new \http\Controller\Exception(404, "Not Found: {$this->getPathName()}"); - } - return $head . "
    " . $html ."
    "; - } -} diff --git a/mdref/RefEntry.php b/mdref/RefEntry.php deleted file mode 100644 index 6c54674..0000000 --- a/mdref/RefEntry.php +++ /dev/null @@ -1,164 +0,0 @@ -path = $path; - $this->entry = trim($entry ?: $path->getPathName(), DIRECTORY_SEPARATOR); - } - - /** - * Clean up the file handle - */ - function __destruct() { - if (is_resource($this->file)) { - fclose($this->file); - } - } - - /** - * Format as URL - * @return string - */ - function formatUrl() { - return htmlspecialchars($this->entry); - } - - private function joinLink(array $parts) { - $link = ""; - $upper = ctype_upper($parts[0][0]);; - for ($i = 0; $i < count($parts); ++$i) { - if (!strlen($parts[$i]) || $parts[$i] === ".") { - continue; - } - if (strlen($link)) { - if ($parts[$i][0] === ":") { - $link = ""; - } elseif ($upper && !ctype_upper($parts[$i][0])) { - $link .= "::"; - } else { - $link .= "\\"; - } - } - $link .= trim($parts[$i], ": "); - $upper = ctype_upper($parts[$i][0]); - } - return $link; - } - - /** - * Format as link text - * @param bool $basename whether to use the basename only - * @return string - */ - function formatLink($basename = false) { - $link = ""; - if (strlen($this->entry)) { - $parts = explode(DIRECTORY_SEPARATOR, $this->entry); - $link = $basename ? end($parts) : $this->joinLink($parts); - } - return htmlspecialchars($link); - } - - /** - * Create a consolidated Path of this entry - * @return \mdref\Path - */ - function getPath() { - $path = $this->path; - $file = $path($this->entry); - return $file; - } - - private function openFile() { - if (!is_resource($this->file)) { - $file = $this->getPath(); - - if (!$file->isFile()) { - throw new \Exception("Not a file: '{$file}'"); - } - if (!$this->file = fopen($file->getFullPath(".md"), "r")) { - throw new \Exception("Could not open {$file}"); - } - } - } - - /** - * Read the title of the refentry - * @return string - */ - function readTitle() { - $this->openFile(); - fseek($this->file, 1, SEEK_SET); - return fgets($this->file); - } - - /** - * Read the description of the refentry - * @return string - */ - function readDescription() { - $this->openFile(); - fseek($this->file, 0, SEEK_SET); - fgets($this->file); - fgets($this->file); - return fgets($this->file); - } - - /** - * Format a "Edit me" URL. The project reference top directory needs a - * »name«.mdref file besides its »name«.md entry point with the edit URL - * printf template as content. The sole printf argument is the relative - * path of the entry. - * @return string - */ - function formatEditUrl() { - $path = $this->path; - $base = current(explode(DIRECTORY_SEPARATOR, $path->getPathName())); - $file = $path($base); - if ($file->isFile(".mdref")) { - return sprintf(file_get_contents($file->getFullPath(".mdref")), - $this->entry); - } - } - - /** - * Recurse into the reference tree - * @param \mdref\Finder $refs - * @param string $pattern - * @param callable $cb - */ - function recurse(Finder $refs, $pattern, callable $cb) { - $path = $refs->find($refs->getBaseUrl()->mod($this->entry)); - foreach (new RefListing($path, $refs->glob($path, $pattern)) as $entry) { - /* @var $entry RefEntry */ - $cb($entry, $pattern, function($entry, $pattern) use ($refs, $cb) { - $entry->recurse($refs, $pattern, $cb); - }); - } - } -} diff --git a/mdref/RefListing.php b/mdref/RefListing.php deleted file mode 100644 index 4c3b629..0000000 --- a/mdref/RefListing.php +++ /dev/null @@ -1,98 +0,0 @@ -path = $path; - $this->entries = array_map(function($fn) { - return substr(trim($fn, DIRECTORY_SEPARATOR), 0, -3); - }, $files); - } - - /** - * Implements \Countable - * @return int - */ - function count() { - return count($this->entries); - } - - /** - * Implements \Iterator - */ - function rewind() { - reset($this->entries); - } - - /** - * Implements \Iterator - * @return bool - */ - function valid() { - return null !== key($this->entries); - } - - /** - * Implements \Iterator - * @return string - */ - function key() { - return $this->path->getSubPath(current($this->entries)); - } - - /** - * Implements \Iterator - */ - function next() { - next($this->entries); - } - - /** - * Implements \Iterator - * @return \mdref\RefEntry - */ - function current() { - return new RefEntry($this->path, $this->key()); - } - - /** - * Get the parent reference entry - * @return null|\mdref\RefEntry - */ - function getParent() { - switch ($parent = dirname($this->path->getPathName())) { - case ".": - case "": - return null; - default: - return new RefEntry($this->path, $parent); - } - } - - /** - * Get the reference entry this reflist is based of - * @return \mdref\RefEntry - */ - function getSelf() { - return new RefEntry($this->path); - } -} diff --git a/mdref/Reference.php b/mdref/Reference.php new file mode 100644 index 0000000..153004f --- /dev/null +++ b/mdref/Reference.php @@ -0,0 +1,47 @@ +repos[$repo->getName()] = $repo; + } + } + + /** + * Lookup the repo containing a ref entry + * @param string $entry requested reference entry, e.g. "pq/Connection/exec" + * @param type $canonical + * @return \mdref\Repo|NULL + */ + public function getRepoForEntry($entry, &$canonical = null) { + foreach ($this->repos as $repo) { + if ($repo->hasEntry($entry, $canonical)) { + return $repo; + } + } + } + + /** + * Implements \IteratorAggregate + * @return \ArrayIterator repository list + */ + public function getIterator() { + return new \ArrayIterator($this->repos); + } + +} diff --git a/mdref/Repo.php b/mdref/Repo.php new file mode 100644 index 0000000..f59a82e --- /dev/null +++ b/mdref/Repo.php @@ -0,0 +1,135 @@ +path = realpath($path); + $this->name = basename($mdref, ".mdref"); + $this->edit = trim(file_get_contents($mdref)); + } + + /** + * Get the repository's name + * @return string + */ + public function getName() { + return $this->name; + } + + /** + * Get the path of the repository or a file in it + * @param string $file + * @return string + */ + public function getPath($file = "") { + return $this->path . "/$file"; + } + + /** + * Get the edit url for a ref entry + * @param string $entry + * @return string + */ + public function getEditUrl($entry) { + return sprintf($this->edit, $entry); + } + + /** + * Get the file path of an entry in this repo + * @param string $entry + * @return string file path + */ + public function hasEntry($entry, &$canonical = null) { + $file = $this->getPath("$entry.md"); + if (is_file($file)) { + return $file; + } + $file = $this->getPath($this->getName()."/$entry.md"); + if (is_file($file)) { + $canonical = $this->getName() . "/" . $entry; + return $file; + } + } + + /** + * Get the canonical entry name of a file in this repo + * @param string $file + * @return string entry + */ + public function hasFile($file) { + if (($file = realpath($file))) { + $path = $this->getPath(); + $plen = strlen($path); + if (!strncmp($file, $path, $plen)) { + $dirname = dirname(substr($file, $plen)); + $basename = basename($file, ".md"); + + if ($dirname === ".") { + return $basename; + } + + return $dirname . "/". $basename; + } + } + } + + /** + * Get an Entry instance + * @param string $entry + * @return \mdref\Entry + * @throws \OutOfBoundsException + */ + public function getEntry($entry) { + return new Entry($entry, $this); + } + + /** + * Get the root Entry instance + * @return \mdref\Entry + */ + public function getRootEntry() { + return new Entry($this->name, $this); + } + + /** + * Implements \IteratorAggregate + * @return \mdref\Tree + */ + public function getIterator() { + return new Tree($this->path, $this); + } +} diff --git a/mdref/Tree.php b/mdref/Tree.php new file mode 100644 index 0000000..d9e2b0d --- /dev/null +++ b/mdref/Tree.php @@ -0,0 +1,113 @@ +list = array_filter($list, $this->generateFilter($list)); + sort($this->list, SORT_STRING); + $this->repo = $repo; + } + + /** + * @param array $list + * @return callable + */ + private function generateFilter(array $list) { + return function($v) use($list) { + if ($v{0} === ".") { + return false; + } + if (false !== array_search("$v.md", $list, true)) { + return false; + } + + $pi = pathinfo($v); + if (isset($pi["extension"]) && "md" !== $pi["extension"]) { + return false; + } + + return true; + }; + } + + /** + * Implements \Iterator + * @return \mdref\Entry + */ + public function current() { + return $this->repo->getEntry($this->repo->hasFile(current($this->iter))); + } + + /** + * Implements \Iterator + */ + public function next() { + next($this->iter); + } + + /** + * Implements \Iterator + * @return int + */ + public function key() { + return key($this->iter); + } + + /** + * Implements \Iterator + */ + public function rewind() { + $this->iter = $this->list; + reset($this->iter); + } + + /** + * Implements \Iterator + * @return bool + */ + public function valid() { + return null !== key($this->iter); + } + + /** + * Implements \RecursiveIterator + * @return bool + */ + public function hasChildren() { + return $this->current()->hasIterator(); + } + + /** + * Implements \RecursiveIterator + * @return \mdref\Tree + */ + public function getChildren() { + return $this->current()->getIterator(); + } +} diff --git a/public/index.css b/public/index.css index 0fcd088..6fc3a88 100644 --- a/public/index.css +++ b/public/index.css @@ -16,7 +16,7 @@ body { body>* { margin-left: 1em; } -body>ul { +body>ul, body>div>ul { margin-left: 2em; } @@ -77,7 +77,7 @@ pre>code, pre>code code { color: #eee; } -p, pre { +p, pre, table { margin: 1em 2em 2em 2em; } @@ -104,6 +104,9 @@ a, h1 code>a { a:hover { text-decoration: none; } +a[href^="http:"]:after, a[href^="https:"]:after { + content: " ⬈"; +} .var { color: #800000; @@ -139,7 +142,7 @@ li h3 { margin: .5em 0 0 0; } -body>h3 { +body>h3, body>div>h3 { margin-left: 2em; } diff --git a/public/index.php b/public/index.php index 23a1be2..1e6d37f 100644 --- a/public/index.php +++ b/public/index.php @@ -20,7 +20,7 @@ new ExceptionHandler; $ctl = new Controller; $ctl->setDependency("baseUrl", new Url) - ->attach(new Action) + ->attach(new Action(["refpath" => REFS])) ->attach(new Layout) ->notify() ->getResponse() diff --git a/views/index.phtml b/views/index.phtml index 417419d..6b4f2b1 100644 --- a/views/index.phtml +++ b/views/index.phtml @@ -1,25 +1,16 @@

    mdref

    - - - - -

    Available References

    - -

    formatLink()?>

    - recurse($refs, "/*.md", function($entry, $pattern, callable $recursor) { ?> -
      -
    • formatLink()?>

      - quick($entry->readDescription()); - $recursor($entry, "/[A-Z]*.md"); - ?> -
    • -
    - + + + +

    esc($text) ?>

    + + + +

    + esc($entry->getTitle()) ?>

    +
    getIntro()) ?>
    - - + + \ No newline at end of file diff --git a/views/layout.phtml b/views/layout.phtml index 6ef8686..6b18f85 100644 --- a/views/layout.phtml +++ b/views/layout.phtml @@ -9,13 +9,15 @@ mdref + - - + + + @@ -25,7 +27,7 @@
    @@ -25,7 +32,7 @@ - +
    + + + + + diff --git a/views/sidebar.phtml b/views/sidebar.phtml index e371d6d..4ecce30 100644 --- a/views/sidebar.phtml +++ b/views/sidebar.phtml @@ -1,23 +1,30 @@ -- 2.30.2 From 3b4575f6845cfdb14d9ec579abde579b83b7a9da Mon Sep 17 00:00:00 2001 From: Michael Wallner Date: Mon, 2 Mar 2015 13:35:20 +0100 Subject: [PATCH 13/16] better line-spacing --- public/index.css | 3 +++ 1 file changed, 3 insertions(+) diff --git a/public/index.css b/public/index.css index b4175ed..e305c7a 100644 --- a/public/index.css +++ b/public/index.css @@ -6,6 +6,7 @@ body, code { font-family: Inconsolata, Monospace, 'Courier New', Courier, monospace; } body { + line-height: 140%; font-size: 1.1em; margin: 0; padding: 0; @@ -73,6 +74,7 @@ pre>code { display: inline-block; padding: 1em; min-width: 50%; + line-height: 110%; } pre>code, pre>code code { background: #333; @@ -95,6 +97,7 @@ blockquote { } ul { + margin-top: 1em; margin-bottom: 2em; } li { -- 2.30.2 From 31c2b666caffa28ea1576ed9c2f4c2280f2a2d45 Mon Sep 17 00:00:00 2001 From: Michael Wallner Date: Mon, 20 Jul 2015 08:16:41 +0200 Subject: [PATCH 14/16] static generation does not work with overlapping namespaces --- bin/gen-static | 14 ---- mdref/Action.php | 2 +- mdref/Generator.php | 180 -------------------------------------------- public/.htaccess | 2 - views/layout.phtml | 8 -- 5 files changed, 1 insertion(+), 205 deletions(-) delete mode 100755 bin/gen-static delete mode 100644 mdref/Generator.php diff --git a/bin/gen-static b/bin/gen-static deleted file mode 100755 index 15e8609..0000000 --- a/bin/gen-static +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env php -add("mdref", ROOT); - -$g = new mdref\Generator(REFS, @$argv[1]); -$g->run(); diff --git a/mdref/Action.php b/mdref/Action.php index 3e318be..37b3d51 100644 --- a/mdref/Action.php +++ b/mdref/Action.php @@ -59,7 +59,7 @@ class Action extends Observer { */ private function serveCanonical($ctl, $cnn) { $ctl->detachAll(Observer\View::class); - $ctl->getResponse()->setHeader("Location", $this->baseUrl->mod($cnn)); + $ctl->getResponse()->setHeader("Location", $this->baseUrl->mod(["path" => $cnn])); $ctl->getResponse()->setResponseCode(301); } diff --git a/mdref/Generator.php b/mdref/Generator.php deleted file mode 100644 index 7a58387..0000000 --- a/mdref/Generator.php +++ /dev/null @@ -1,180 +0,0 @@ -reference = new Reference(explode(PATH_SEPARATOR, $refs)); - $this->renderer = new Generator\Renderer($dir ?: "public/static"); - } - - /** - * Run the generator - */ - public function run() { - $this->generateRoot(); - foreach ($this->reference as $repo) { - $iter = new \RecursiveIteratorIterator($repo, - \RecursiveIteratorIterator::SELF_FIRST); - foreach ($iter as $ref) { - $this->generateEntry($ref); - } - } - } - - /** - * Generate index.html and LICENSE.html - */ - private function generateRoot() { - printf("Generating index ...\n"); - $data = $this->createPayload(null); - $data->ref = "index"; - $this->renderer->persist($data); - - printf("Generating LICENSE ...\n"); - $data->text = file_get_contents(__DIR__."/../LICENSE"); - $data->ref = "LICENSE"; - $this->renderer->persist($data); - } - - /** - * Generate HTML for an entry - * @param \mdref\Entry $ref - */ - private function generateEntry(Entry $ref) { - printf("Generating %s ...\n", $ref->getName()); - $data = $this->createPayload($ref); - $this->renderer->persist($data); - } - - /** - * Create the view payload - * @param \mdref\Entry $ref - * @param \mdref\Generator\Renderer $view - * @return \stdClass - */ - private function createPayload(Entry $ref = null) { - $pld = new \stdClass; - - $pld->quick = [$this->reference, "formatString"]; - $pld->file = [$this->reference, "formatFile"]; - $pld->refs = $this->reference; - $pld->view = $this->renderer; - if ($ref) { - $pld->entry = $ref; - $pld->ref = $ref->getName(); - } - - return $pld; - } -} - -namespace mdref\Generator; - -class Renderer -{ - /** - * @var string - */ - private $dir; - - /** - * @param string $dir output directory - */ - public function __construct($dir = "public/static") { - $this->dir = $dir; - } - - /** - * HTML entity encode special characters - * @param string $string - * @return string - */ - public function esc($string) { - return htmlspecialchars($string); - } - - /** - * Render mdref page - * @param \stdClass $pld - * @return string - */ - public function render(\stdClass $pld) { - $content = ""; - ob_start(function($data) use(&$content) { - $content .= $data; - return true; - }); - static::renderFile("views/layout.phtml", (array) $pld); - ob_end_flush(); - return $content; - } - - /** - * Persist mdref page to output directory - * @param \stdClass $data - */ - public function persist(\stdClass $data) { - $html = $this->render($data); - $file = sprintf("%s/%s.html", $this->dir, $data->ref); - $this->saveFile($file, $html); - $this->linkIndex(dirname($file)); - } - - /** - * Save data to file (write to $file.tmp and rename to $file) - * @param string $file - * @param string $data - * @throws \Exception - */ - private function saveFile($file, $data) { - $dir = dirname($file); - if (!is_dir($dir) && !mkdir($dir, 0755, true)) { - throw new \Exception("Failed to create directory '$dir'"); - } - if (!file_put_contents("$file.tmp", $data)) { - throw new \Exception("Failed to save file '$file.tmp'"); - } - if (!rename("$file.tmp", $file)) { - throw new \Exception("Failed to rename to '$file'"); - } - } - - private function linkIndex($dir) { - $index = "$dir.html"; - $link = "$dir/index.html"; - if (is_file($index) && !is_file($link)) { - printf("Generating index for '%s'\n", substr($dir, strlen($this->dir))); - link($index, $link); - } - } - - /** - * Render file - */ - static private function renderFile() { - if (func_num_args() > 1) { - extract(func_get_arg(1)); - } - include func_get_arg(0); - } -} diff --git a/public/.htaccess b/public/.htaccess index c236b73..87e2468 100644 --- a/public/.htaccess +++ b/public/.htaccess @@ -1,7 +1,5 @@ DirectorySlash Off RewriteEngine On -RewriteRule ^static.*\.html - [L] -RewriteRule ^(static/.+) $1.html [L] RewriteCond %{REQUEST_FILENAME} -f [OR] RewriteCond %{REQUEST_FILENAME} -d [OR] RewriteCond %{REQUEST_FILENAME} -l diff --git a/views/layout.phtml b/views/layout.phtml index 05367b9..6cc571b 100644 --- a/views/layout.phtml +++ b/views/layout.phtml @@ -14,10 +14,6 @@ - - @@ -50,10 +46,6 @@ - - -- 2.30.2 From fc6b583627d21a26de8268df800fd44fbb9ac30d Mon Sep 17 00:00:00 2001 From: Michael Wallner Date: Mon, 20 Jul 2015 12:07:57 +0200 Subject: [PATCH 15/16] autocracy is obsolete --- VERSION | 2 +- composer.json | 3 - mdref/Action.php | 147 +++++++++++++++++++------------------ mdref/BaseUrl.php | 38 ++++++++++ mdref/Entry.php | 6 +- mdref/Exception.php | 18 +++++ mdref/ExceptionHandler.php | 2 +- public/index.js | 4 +- public/index.php | 30 ++++---- views/index.phtml | 6 +- views/mdref.phtml | 8 +- views/sidebar.phtml | 18 ++--- 12 files changed, 173 insertions(+), 109 deletions(-) create mode 100644 mdref/BaseUrl.php create mode 100644 mdref/Exception.php diff --git a/VERSION b/VERSION index 3eefcb9..cd5ac03 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.0.0 +2.0 diff --git a/composer.json b/composer.json index e8a1761..726f6d3 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,4 @@ "name": "m6w6/mdref", "description": "Quick Markdown Reference Browser", "license": "BSD-2-Clause", - "require": { - "m6w6/autocracy": "dev-master" - } } diff --git a/mdref/Action.php b/mdref/Action.php index 37b3d51..e35b3f5 100644 --- a/mdref/Action.php +++ b/mdref/Action.php @@ -2,29 +2,46 @@ namespace mdref; -use http\Controller\Observer; +use http\Env\Request; +use http\Env\Response; /** * Request handler */ -class Action extends Observer { - /** - * Reference paths - * @var string - */ - protected $refpath; - +class Action { /** * The reference * @var \mdref\Reference */ private $reference; + + /** + * @var \http\Request + */ + private $request; + + /** + * @var \http\Response + */ + private $response; + + /** + * @var \http\Url + */ + private $baseUrl; /** * Initialize the reference */ - protected function init() { - $this->reference = new Reference(explode(PATH_SEPARATOR, $this->refpath)); + public function __construct(Reference $ref, Request $req, Response $res, BaseUrl $baseUrl) { + $this->reference = $ref; + $this->request = $req; + $this->response = $res; + $this->baseUrl = $baseUrl; + } + + function esc($txt) { + return htmlspecialchars($txt); } /** @@ -32,64 +49,53 @@ class Action extends Observer { * @param \http\Controller $ctl * @return \stdClass */ - private function createPayload(\http\Controller $ctl) { + private function createPayload() { $pld = new \stdClass; - try { - $pld->quick = [$this->reference, "formatString"]; - $pld->file = [$this->reference, "formatFile"]; - - $pld->ref = implode("/", $this->baseUrl->params( - $this->baseUrl->mod($ctl->getRequest()->getRequestUrl()))); - - $pld->refs = $this->reference; - $pld->baseUrl = $this->baseUrl; + $pld->esc = "htmlspecialchars"; + $pld->quick = [$this->reference, "formatString"]; + $pld->file = [$this->reference, "formatFile"]; + + $pld->ref = $this->baseUrl->pathinfo( + $this->baseUrl->mod($this->request->getRequestUrl())); + + $pld->refs = $this->reference; + $pld->baseUrl = $this->baseUrl; - } catch (\Exception $e) { - $pld->exception = $e; - } - return $pld; } /** * Redirect to canononical url - * @param \http\Controller $ctl * @param string $cnn */ - private function serveCanonical($ctl, $cnn) { - $ctl->detachAll(Observer\View::class); - $ctl->getResponse()->setHeader("Location", $this->baseUrl->mod(["path" => $cnn])); - $ctl->getResponse()->setResponseCode(301); + private function serveCanonical($cnn) { + $this->response->setHeader("Location", $this->baseUrl->mod(["path" => $cnn])); + $this->response->setResponseCode(301); } /** * Serve index.css - * @param \http\Controller $ctl */ - private function serveStylesheet($ctl) { - $ctl->detachAll(Observer\View::class); - $ctl->getResponse()->setHeader("Content-Type", "text/css"); - $ctl->getResponse()->setBody(new \http\Message\Body(fopen(ROOT."/public/index.css", "r"))); + private function serveStylesheet() { + $this->response->setHeader("Content-Type", "text/css"); + $this->esponse->setBody(new \http\Message\Body(fopen(ROOT."/public/index.css", "r"))); } /** * Serve index.js - * @param \http\Controller $ctl */ - private function serveJavascript($ctl) { - $ctl->detachAll(Observer\View::class); - $ctl->getResponse()->setHeader("Content-Type", "application/javascript"); - $ctl->getResponse()->setBody(new \http\Message\Body(fopen(ROOT."/public/index.js", "r"))); + private function serveJavascript() { + $this->response->setHeader("Content-Type", "application/javascript"); + $this->response->setBody(new \http\Message\Body(fopen(ROOT."/public/index.js", "r"))); } /** * Serve a preset - * @param \http\Controller $ctl * @param \stdClass $pld - * @throws \http\Controller\Exception + * @throws Exception */ - private function servePreset($ctl, $pld) { + private function servePreset($pld) { switch ($pld->ref) { case "AUTHORS": case "LICENSE": @@ -103,38 +109,39 @@ class Action extends Observer { $this->serveJavascript($ctl); break; default: - throw new \http\Controller\Exception(404, "$pld->ref not found"); + throw new Exception(404, "$pld->ref not found"); } } + + private function serve() { + extract((array) func_get_arg(0)); + include ROOT."/views/layout.phtml"; + } - /** - * Implements Observer - * @param \SplSubject $ctl \http\Controller - */ - public function update(\SplSubject $ctl) { - /* @var http\Controller $ctl */ - $pld = $this->createPayload($ctl); - $ctl[Observer\View::class] = function() use($pld) { - return $pld; - }; - - if (!isset($pld->ref) || !strlen($pld->ref)) { - /* front page */ - return; - } - - $cnn = null; - if (($repo = $this->reference->getRepoForEntry($pld->ref, $cnn))) { - if (strlen($cnn)) { - /* redirect */ - $this->serveCanonical($ctl, $cnn); - } else { - /* direct match */ - $pld->entry = $repo->getEntry($pld->ref); + public function handle() { + try { + + $pld = $this->createPayload(); + + if (strlen($pld->ref)) { + $cnn = null; + if (($repo = $this->reference->getRepoForEntry($pld->ref, $cnn))) { + if (strlen($cnn)) { + /* redirect */ + return $this->serveCanonical($cnn); + } else { + /* direct match */ + $pld->entry = $repo->getEntry($pld->ref); + } + } else { + return $this->servePreset($pld); + } } - } else { - $this->servePreset($ctl, $pld); + + } catch (\Exception $e) { + $pld->exception = $e; } - } + $this->serve($pld); + } } \ No newline at end of file diff --git a/mdref/BaseUrl.php b/mdref/BaseUrl.php new file mode 100644 index 0000000..7476a82 --- /dev/null +++ b/mdref/BaseUrl.php @@ -0,0 +1,38 @@ + filter_input(INPUT_SERVER, "HTTPS", FILTER_VALIDATE_BOOLEAN) ? "https":"http", + "path" => dirname(filter_input(INPUT_SERVER, "SCRIPT_NAME"))."/" + ); + parent::__construct($self, $url, + self::JOIN_PATH | + self::SANITIZE_PATH | + self::STRIP_QUERY | + self::STRIP_AUTH | + self::FROM_ENV + ); + } + /** + * Extract path info + * + * @param mixed $url full request url + * @return string + */ + function pathinfo($url) { + $url = new Url($this, $url, Url::FROM_ENV | Url::STRIP_QUERY); + $info = substr($url, strlen($this)); + return urldecode($info); + } + + +} diff --git a/mdref/Entry.php b/mdref/Entry.php index 8c6673b..db858a9 100644 --- a/mdref/Entry.php +++ b/mdref/Entry.php @@ -153,7 +153,11 @@ class Entry implements \IteratorAggregate { * @return \mdref\Entry */ public function getParent() { - if ("." !== ($dirn = dirname($this->name))) { + switch ($dirn = dirname($this->name)) { + case ".": + case "/": + break; + default: return $this->repo->getEntry($dirn); } } diff --git a/mdref/Exception.php b/mdref/Exception.php new file mode 100644 index 0000000..013d861 --- /dev/null +++ b/mdref/Exception.php @@ -0,0 +1,18 @@ +setResponseCode($this->code); + $res->setBody(new http\Message\Body); + $res->getBody()->append($this->message); + } +} diff --git a/mdref/ExceptionHandler.php b/mdref/ExceptionHandler.php index 13e7f18..aeff40b 100644 --- a/mdref/ExceptionHandler.php +++ b/mdref/ExceptionHandler.php @@ -60,7 +60,7 @@ class ExceptionHandler * @return string */ public static function htmlException(\Exception $e, array $title_tag = ["h1"], array $message_tag = ["p"], array $trace_tag = ["pre", "style='font-size:smaller;overflow-x:scroll'"]) { - if ($e instanceof \http\Controller\Exception) { + if ($e instanceof Exception) { $code = $e->getCode() ?: 500; foreach ($e->getHeaders() as $key => $val) { HTTP::setResponseHeader($key, $val); diff --git a/public/index.js b/public/index.js index 27e368a..93128bf 100644 --- a/public/index.js +++ b/public/index.js @@ -100,7 +100,7 @@ $(function() { if (-1 !== (j = s.lastIndexOf("\\")) && s.substr(j+1,1) !== "n") { t = s.substring(j+1); if (!mdref.is_constant(t)) { - return ""; + return ""; } return ""; } @@ -130,7 +130,7 @@ $(function() { $n.text().split(/([^a-zA-Z0-9_\\\$:]+)/).forEach(function(v) { var t; - if ((t = mdref.type(v, nn))) { + if ((t = mdref.type(v.replace(/:$/, ""), nn))) { a.push($(t).text(v)); } else if (a.length && a[a.length-1].nodeName === "#text") { /* if we already have a text node and the next is also gonna be a text diff --git a/public/index.php b/public/index.php index 6c477ee..853c091 100644 --- a/public/index.php +++ b/public/index.php @@ -1,7 +1,14 @@ add("mdref", ROOT); -use http\Controller; -use http\Controller\Url; -use http\Controller\Observer\Layout; - -use mdref\ExceptionHandler; -use mdref\Action; - new ExceptionHandler; -$ctl = new Controller; -$ctl->setDependency("baseUrl", new Url) - ->attach(new Action(["refpath" => REFS])) - ->attach(new Layout) - ->notify() - ->getResponse() - ->send(); +$reference = new Reference(($refs = getenv("REFPATH")) ? explode(PATH_SEPARATOR, $refs) : glob(ROOT."/refs/*")); +$action = new Action($reference, new Request, new Response, new BaseUrl); + +ob_start($response); +$action->handle(); +ob_end_flush(); +$response->send(); diff --git a/views/index.phtml b/views/index.phtml index 6b4f2b1..acab9e2 100644 --- a/views/index.phtml +++ b/views/index.phtml @@ -3,13 +3,13 @@ -

    esc($text) ?>

    +

    - esc($entry->getTitle()) ?>

    + getTitle()) ?>
    getIntro()) ?>
    diff --git a/views/mdref.phtml b/views/mdref.phtml index e50a008..a9e34ef 100644 --- a/views/mdref.phtml +++ b/views/mdref.phtml @@ -5,9 +5,9 @@
      isFunction()) continue; ?>
    • -

      esc($sub) ?>

      +

      getDescription()) ?>

      -

      esc($sub->getTitle()) ?>

      +

      getTitle()) ?>

    @@ -18,9 +18,9 @@
      isNsClass()) continue; ?>
    • -

      esc($sub) ?>

      +

      getDescription()) ?>

      -

      esc($sub->getTitle()) ?>

      +

      getTitle()) ?>

    diff --git a/views/sidebar.phtml b/views/sidebar.phtml index 4ecce30..35647b8 100644 --- a/views/sidebar.phtml +++ b/views/sidebar.phtml @@ -1,7 +1,7 @@