/nbproject/
/composer.phar
/vendor/
+/refs/
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
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
--- /dev/null
+<?php
+
+namespace mdref;
+
+/**
+ * A single reference entry
+ */
+class Entry implements \IteratorAggregate {
+ /**
+ * Compound name
+ * @var string
+ */
+ private $name;
+
+ /**
+ * Split name
+ * @var array
+ */
+ private $list;
+
+ /**
+ * The containing repository
+ * @var \mdref\Repo
+ */
+ private $repo;
+
+ /**
+ * The file path, if the refentry exists
+ * @var type
+ */
+ private $path;
+
+ /**
+ * The file instance of this entry
+ * @var \mdref\File
+ */
+ private $file;
+
+ /**
+ * @param string $name the compound name of the ref entry, e.g. "pq/Connection/exec"
+ * @param \mdref\Repo $repo the containing repository
+ */
+ public function __construct($name, Repo $repo) {
+ $this->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());
+ }
+}
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;
}
/**
* @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) {
} 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</%s>\n<%s>%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</%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</%s>\n",
+ implode(" ", $trace_tag), $trace, $trace_tag[0]);
+ }
}
+
return $html;
}
}
--- /dev/null
+<?php
+
+namespace mdref;
+
+/**
+ * A ref entry file
+ */
+class File {
+ /**
+ * @var resource
+ */
+ private $fd;
+
+ /**
+ * Open the file
+ * @param string $path
+ */
+ public function __construct($path) {
+ $this->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;
+ }
+}
+++ /dev/null
-<?php
-
-namespace mdref;
-
-/**
- * Find markdown reference files in several REFPATH paths.
- *
- * The base URL is used to extract the relative identifier out of the request
- * url in Finder::find().
- *
- * Use the created Path of Finder::find() for Finder::glob() to find subrefs.
- */
-class Finder
-{
- /**
- * Base URL
- * @var \http\Controller\Url
- */
- protected $baseUrl;
-
- /**
- * Reference paths
- * @var array
- */
- protected $refs = array();
-
- /**
- * @param \http\Controller\Url $baseUrl
- * @param mixed $paths array or string of paths with markdown references
- */
- function __construct(\http\Controller\Url $baseUrl, $paths = ".") {
- if (!is_array($paths)) {
- $paths = explode(PATH_SEPARATOR, $paths);
- }
- $this->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;
- }
-}
+++ /dev/null
-<?php
-
-namespace mdref;
-
-class Markdown
-{
- /**
- * @var \mdref\Path
- */
- protected $path;
-
- /**
- * @param \mdref\Path $path
- */
- function __construct(Path $path = null) {
- $this->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();
- }
-}
+++ /dev/null
-<?php
-
-namespace mdref;
-
-/**
- * A path made out of a base dir and an thereof relative path name.
- */
-class Path
-{
- /**
- * Computed path
- * @var string
- */
- protected $path = "";
-
- /**
- * The base directory where path is located
- * @var string
- */
- protected $baseDir = "";
-
- /**
- * @param string $baseDir
- * @param string $path
- */
- function __construct($baseDir = "", $path = "") {
- $this->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("<h1>%s</h1>\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 . "<pre>" . $html ."</pre>";
- }
-}
+++ /dev/null
-<?php
-
-namespace mdref;
-
-/**
- * The RefEntry class represents a reference entry, i.e. a .md file
- */
-class RefEntry
-{
- /**
- * @var \mdref\Path
- */
- protected $path;
-
- /**
- * @var string
- */
- protected $entry;
-
- /**
- * @var resource
- */
- protected $file;
-
- /**
- * @param \mdref\Path $path
- * @param type $entry
- */
- function __construct(Path $path, $entry = null) {
- $this->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);
- });
- }
- }
-}
+++ /dev/null
-<?php
-
-namespace mdref;
-
-/**
- * A list of markdown reference files
- */
-class RefListing implements \Countable, \Iterator
-{
- /**
- * @var \mdref\Path
- */
- protected $path;
-
- /**
- * @var array
- */
- protected $entries;
-
- /**
- * @param \mdref\Path $path
- * @param array $files
- */
- function __construct(Path $path, array $files) {
- $this->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);
- }
-}
--- /dev/null
+<?php
+
+namespace mdref;
+
+/**
+ * The complete available reference
+ */
+class Reference implements \IteratorAggregate {
+ /**
+ * List of mdref repositories
+ * @var array
+ */
+ private $repos = array();
+
+ /**
+ * @param array $refs list of mdref repository paths
+ */
+ public function __construct(array $refs) {
+ foreach ($refs as $path) {
+ $repo = new Repo($path);
+ $this->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);
+ }
+
+}
--- /dev/null
+<?php
+
+namespace mdref;
+
+
+/**
+ * A reference repo
+ */
+class Repo implements \IteratorAggregate {
+ /**
+ * The name of the repository
+ * @var string
+ */
+ private $name;
+
+ /**
+ * The path to the repository
+ * @var string
+ */
+ private $path;
+
+ /**
+ * The edit url template
+ * @var string
+ */
+ private $edit;
+
+ /**
+ * Path to the repository containing the name.mdref file
+ * @param string $path
+ * @throws \InvalidArgumentException
+ */
+ public function __construct($path) {
+ if (!($mdref = current(glob("$path/*.mdref")))) {
+ throw new \InvalidArgumentException(
+ sprintf("Not a reference, could not find '*.mdref': '%s'",
+ $path));
+ }
+
+ $this->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);
+ }
+}
--- /dev/null
+<?php
+
+namespace mdref;
+
+class Tree implements \RecursiveIterator {
+ /**
+ * The repository
+ * @var \mdref\Repo
+ */
+ private $repo;
+
+ /**
+ * List of first level entries
+ * @var array
+ */
+ private $list;
+
+ /**
+ * The list iterator
+ * @var array
+ */
+ private $iter;
+
+ /**
+ * @param string $path
+ * @param \mdref\Repo $repo
+ */
+ public function __construct($path, Repo $repo) {
+ if (!($list = glob("$path/*.md"))) {
+ $list = glob("$path/*/*.md");
+ }
+ $this->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();
+ }
+}
body>* {
margin-left: 1em;
}
-body>ul {
+body>ul, body>div>ul {
margin-left: 2em;
}
color: #eee;
}
-p, pre {
+p, pre, table {
margin: 1em 2em 2em 2em;
}
a:hover {
text-decoration: none;
}
+a[href^="http:"]:after, a[href^="https:"]:after {
+ content: " ⬈";
+}
.var {
color: #800000;
margin: .5em 0 0 0;
}
-body>h3 {
+body>h3, body>div>h3 {
margin-left: 2em;
}
$ctl = new Controller;
$ctl->setDependency("baseUrl", new Url)
- ->attach(new Action)
+ ->attach(new Action(["refpath" => REFS]))
->attach(new Layout)
->notify()
->getResponse()
<h1>mdref</h1>
-<?php if (isset($exception)) : ?>
- <?=\mdref\ExceptionHandler::html($exception, ["h2"], ["p"], ["pre", "style='overflow-x:scroll'"]); ?>
-<?php else : ?>
- <?php if (isset($listing) && count($listing)) : ?>
- <h2>Available References</h2>
- <?php foreach ($listing as $entry) : ?>
- <h3><a href="<?=$entry->formatUrl()?>"><?=$entry->formatLink()?></a></h3>
- <?php $entry->recurse($refs, "/*.md", function($entry, $pattern, callable $recursor) { ?>
- <ul>
- <li><p><a href="<?=$entry->formatUrl()?>"><?=$entry->formatLink()?></a></p>
- <?php
- if (!isset($html)) {
- $html = new \mdref\Markdown;
- }
- echo $html->quick($entry->readDescription());
- $recursor($entry, "/[A-Z]*.md");
- ?>
- </li>
- </ul>
- <?php }); ?>
+<?php if (isset($html)) : ?>
+ <?= $html ?>
+<?php elseif (isset($text)) : ?>
+ <p style="white-space:pre-wrap"><?= $view->esc($text) ?></p>
+<?php elseif (isset($refs)) : ?>
+ <?php foreach ($refs as $repo) : /* @var \mdref\Repo $repo */ ?>
+ <?php foreach ($repo as $entry) : /* @var \mdref\Entry $entry */ ?>
+ <h2>
+ <a href="<?= $view->esc($entry->getName()) ?>"
+ ><?= $view->esc($entry->getTitle()) ?></a></h2>
+ <div><?= $quick($entry->getIntro()) ?></div>
<?php endforeach; ?>
- <?php endif; ?>
-<?php endif; ?>
+ <?php endforeach; ?>
+<?php endif; ?>
\ No newline at end of file
mdref
</title>
<base href="<?= $baseUrl ?>">
+ <meta http-equiv="Content-Location" content="<?= $baseUrl . $ref ?>">
<link rel="stylesheet" href="index.css">
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
</head>
<body>
<?php include __DIR__."/sidebar.phtml" ?>
-
- <?php if (isset($html)) : ?>
+ <?php if (isset($exception)) : ?>
+ <?= \mdref\ExceptionHandler::htmlException($exception) ?>
+ <?php elseif (isset($entry)) : ?>
<?php include __DIR__."/mdref.phtml" ?>
<?php else: ?>
<?php include __DIR__."/index.phtml" ?>
<div id="disqus_thread"></div>
<script>
var disqus_shortname = 'mdref';
- var disqus_identifier = '<?=$permUrl?>';
+ var disqus_identifier = '<?=$ref?>';
(function() {
var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js';
-<?= $html ?>
+<?= $file($entry->getPath()) ?>
-<?php if (isset($sublisting) && count($sublisting)) : ?>
- <h2>Functions:</h2>
- <ul>
- <?php foreach($sublisting as $entry) : ?>
- <li>
- <h3><a href="<?=$entry->formatUrl()?>"><?=$entry->formatLink(true)?></a></h3>
- <p><?=$html->quick($entry->readDescription())?></p>
- <p><?=$view->esc($entry->readTitle())?></p>
- </li>
+<?php if ($entry->hasFunctions()) : ?>
+<h2>Functions:</h2>
+<ul>
+ <?php foreach($entry as $sub) : if (!$sub->isFunction()) continue; ?>
+ <li>
+ <h3><a href="<?= $view->esc($sub->getName()) ?>"><?= $view->esc($sub) ?></a></h3>
+ <p><?= $quick($sub->getDescription()) ?></p>
+ <p><?= $view->esc($sub->getTitle()) ?></p>
+ </li>
<?php endforeach; ?>
- </ul>
+</ul>
+<?php endif; ?>
+
+<?php if ($entry->hasNsClasses()) : ?>
+<h2>Namespaces, Interfaces and Classes:</h2>
+<ul>
+ <?php foreach ($entry as $sub) : if (!$sub->isNsClass()) continue; ?>
+ <li>
+ <h3><a href="<?= $view->esc($sub->getName()) ?>"><?= $view->esc($sub) ?></a></h3>
+ <p><?= $quick($sub->getDescription()) ?></p>
+ <p><?= $view->esc($sub->getTitle()) ?></p>
+ </li>
+ <?php endforeach; ?>
+</ul>
<?php endif; ?>
-<?php if (isset($listing)) : ?>
<div class="sidebar">
- <?php include __DIR__."/edit.phtml" ?>
<ul>
- <li>↰ <a href="">Home</a></li>
- <?php if (($entry = $listing->getParent())) : ?>
- <li>↑ <a href="<?=$entry->formatUrl()?>"><?=$entry->formatLink()?></a></li>
- <?php endif; ?>
- <?php if (($entry = $listing->getSelf()) && ($link = $entry->formatLink())) : ?>
- <ul><li>↻ <?= $link ?>
- <?php endif; ?>
- <?php if (count($listing)) : ?>
+ <li>↰ <a href="">Home</a>
+ <?php if (isset($entry)) : /* @var \mdref\Entry $entry */ ?>
<ul>
- <?php foreach ($listing as $entry) : ?>
- <li>↳ <a href="<?=$entry->formatUrl()?>"><?=$entry->formatLink()?></a></li>
+ <li>
+ <?php foreach ($entry->getParents() as $parent) if ($parent->isFile()) : ?>
+ ↑
+ <a href="<?= $view->esc($parent->getName()) ?>">
+ <?= $view->esc($entry->getRepo()->getEntry($parent)) ?>
+ </a>
+ <ul>
+ <li>
+ <?php endif; ?>
+ ↻ <a href="<?= $view->esc($entry->getName()) ?>"
+ ><?= $view->esc($entry) ?></a>
+ <ul>
+ <?php foreach ($entry as $sub) : /* @var \mdref\Entry $sub */ ?>
+ <li>
+ ↳ <a href="<?= $view->esc($sub->getName()) ?>"
+ ><?= $view->esc($sub) ?></a>
+ </li>
+ <?php endforeach; ?>
+ </ul>
+ <?php foreach ($entry->getParents() as $parent) if ($parent->isFile()) : ?>
+ </li>
+ </ul>
+ <?php endif; ?>
+ </li>
+ </ul>
+ <?php elseif (isset($refs)) : ?>
+ <ul>
+ <?php foreach ($refs as $repo) : /* @var \mdref\Repo $repo */ ?>
+ <?php foreach ($repo as $sub) : /* @var \mdref\Entry $entry */ ?>
+ <li>
+ ↳ <a href="<?= $view->esc($sub->getName()) ?>"
+ ><?= $view->esc($sub->getTitle()) ?></a>
+ </li>
+ <?php endforeach; ?>
<?php endforeach; ?>
</ul>
- <?php endif; ?>
- <?php if (isset($link) && strlen($link)) : ?>
- </li></ul>
- <?php endif; ?>
+ <?php endif; ?>
+ </li>
</ul>
</div>
-<?php endif; ?>
+