Merge branch 'autocracy'
authorMichael Wallner <mike@php.net>
Tue, 17 Dec 2013 10:47:55 +0000 (11:47 +0100)
committerMichael Wallner <mike@php.net>
Tue, 17 Dec 2013 10:47:55 +0000 (11:47 +0100)
24 files changed:
.gitignore [new file with mode: 0644]
AUTHORS [new file with mode: 0644]
LICENSE [new file with mode: 0644]
VERSION [new file with mode: 0644]
composer.json [new file with mode: 0644]
composer.lock [new file with mode: 0644]
mdref/Action.php [new file with mode: 0644]
mdref/ExceptionHandler.php [new file with mode: 0644]
mdref/Finder.php [new file with mode: 0644]
mdref/Markdown.php [new file with mode: 0644]
mdref/Path.php [new file with mode: 0644]
mdref/RefEntry.php [new file with mode: 0644]
mdref/RefListing.php [new file with mode: 0644]
public/.htaccess [new file with mode: 0644]
public/index.css [new file with mode: 0644]
public/index.js [new file with mode: 0644]
public/index.php [new file with mode: 0644]
refs/http [new symlink]
views/edit.phtml [new file with mode: 0644]
views/footer.phtml [new file with mode: 0644]
views/index.phtml [new file with mode: 0644]
views/layout.phtml [new file with mode: 0644]
views/mdref.phtml [new file with mode: 0644]
views/sidebar.phtml [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..40b7eda
--- /dev/null
@@ -0,0 +1 @@
+/nbproject/
diff --git a/AUTHORS b/AUTHORS
new file mode 100644 (file)
index 0000000..67bbd91
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1 @@
+Michael Wallner <mike@php.net>
diff --git a/LICENSE b/LICENSE
new file mode 100644 (file)
index 0000000..816ff40
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,23 @@
+Copyright (c) 2013, Michael Wallner <mike@php.net>.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without 
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright notice, 
+      this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright 
+      notice, this list of conditions and the following disclaimer in the 
+      documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
diff --git a/VERSION b/VERSION
new file mode 100644 (file)
index 0000000..a163131
--- /dev/null
+++ b/VERSION
@@ -0,0 +1 @@
+0.1.0alpha
diff --git a/composer.json b/composer.json
new file mode 100644 (file)
index 0000000..2a36108
--- /dev/null
@@ -0,0 +1,5 @@
+{
+    "require": {
+        "m6w6/autocracy": "dev-master"
+    }
+}
diff --git a/composer.lock b/composer.lock
new file mode 100644 (file)
index 0000000..11a05e8
--- /dev/null
@@ -0,0 +1,65 @@
+{
+    "_readme": [
+        "This file locks the dependencies of your project to a known state",
+        "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file"
+    ],
+    "hash": "0147473751b895f221e765fd6fb1fc2b",
+    "packages": [
+        {
+            "name": "m6w6/autocracy",
+            "version": "dev-master",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/m6w6/autocracy.git",
+                "reference": "b6830cfce3dc276bf67288a7c6bfe64ffd290e21"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/m6w6/autocracy/zipball/b6830cfce3dc276bf67288a7c6bfe64ffd290e21",
+                "reference": "b6830cfce3dc276bf67288a7c6bfe64ffd290e21",
+                "shasum": ""
+            },
+            "type": "library",
+            "autoload": {
+                "psr-0": {
+                    "http\\Controller": "lib"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-2-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Michael Wallner",
+                    "email": "mike@php.net"
+                }
+            ],
+            "description": "http\\Controller preserves your autocracy",
+            "homepage": "http://github.com/m6w6/autocracy",
+            "keywords": [
+                "controller",
+                "http",
+                "pecl",
+                "pecl_http"
+            ],
+            "time": "2013-10-22 11:41:09"
+        }
+    ],
+    "packages-dev": [
+
+    ],
+    "aliases": [
+
+    ],
+    "minimum-stability": "stable",
+    "stability-flags": {
+        "m6w6/autocracy": 20
+    },
+    "platform": [
+
+    ],
+    "platform-dev": [
+
+    ]
+}
diff --git a/mdref/Action.php b/mdref/Action.php
new file mode 100644 (file)
index 0000000..462e35b
--- /dev/null
@@ -0,0 +1,56 @@
+<?php
+
+namespace mdref;
+
+use http\Controller\Observer;
+
+/**
+ * The sole action controller of mdref
+ */
+class Action extends Observer
+{
+       private function serveReference(\http\Controller $ctl) {
+               $payload = $ctl->getPayload();
+               $finder = new Finder($this->baseUrl, REFS);
+               $path = $finder->find(new \http\Url($ctl->getRequest()->getRequestUrl()));
+               $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;
+               }
+       }
+       
+       private function serveInternal(\http\Controller $ctl) {
+               $payload = $ctl->getPayload();
+               $finder = new Finder($this->baseUrl, ROOT);
+               $url = new \http\Url($ctl->getRequest()->getRequestUrl());
+               $path = $finder->find($url, "");
+               if ($path->isFile("")) {
+                       $payload->html = $path->toHtml();
+               } else if (strcmp($url, $this->baseUrl)) {
+                       throw new \http\Controller\Exception(404, "Could not find '$path'");
+               }
+       }
+
+       /**
+        * Implements \SplObserver
+        * @param \SplSubject $ctl
+        */
+       function update(\SplSubject $ctl) {
+               /* @var \http\Controller $ctl */
+               try {
+                       $ctl->getPayload()->baseUrl = $this->baseUrl;
+
+                       if (!$this->serveReference($ctl)) {
+                               $this->serveInternal($ctl);
+                       }
+               } catch (\Exception $e) {
+                       $ctl->getPayload()->exception = $e;
+               }
+       }
+}
diff --git a/mdref/ExceptionHandler.php b/mdref/ExceptionHandler.php
new file mode 100644 (file)
index 0000000..0edf728
--- /dev/null
@@ -0,0 +1,58 @@
+<?php
+
+namespace mdref;
+
+use http\Env as HTTP;
+
+/**
+ * mdref exception handler
+ */
+class ExceptionHandler
+{
+       function __construct() {
+               set_exception_handler($this);
+               set_error_handler($this);
+       }
+       
+       function __invoke($e, $msg = null) {
+               if ($e instanceof \Exception) {
+                       try {
+                               echo static::html($e);
+                       } catch (\Exception $ignore) {
+                               HTTP::sendStatusCode(500);
+                       }
+               } else {
+                       throw new \Exception($msg, $e);
+               }
+               return true;
+       }
+       
+       /**
+        * Format an exception as HTML and send appropriate exception info as HTTP headers
+        * @param \Exception $e
+        * @param array $title_tag
+        * @param array $message_tag
+        * @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'"]) {
+               if ($e instanceof \http\Controller\Exception) {
+                       $code = $e->getCode() ?: 500;
+                       foreach ($e->getHeaders() as $key => $val) {
+                               HTTP::sendResponseHeader($key, $val);
+                       }
+               } else {
+                       $code = 500;
+               }
+               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]);
+               if ($trace_tag) {
+                       $html .= sprintf("<%s>%s</%s>\n",
+                                       implode(" ", $trace_tag), $e->getTraceAsString(), $trace_tag[0]);
+               }
+               return $html;
+       }
+}
\ No newline at end of file
diff --git a/mdref/Finder.php b/mdref/Finder.php
new file mode 100644 (file)
index 0000000..e5cb7fb
--- /dev/null
@@ -0,0 +1,87 @@
+<?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())) {
+                       return glob($path->getFullPath($pattern), $flags);
+               }
+               $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)));
+               }
+               return $glob;
+       }
+}
diff --git a/mdref/Markdown.php b/mdref/Markdown.php
new file mode 100644 (file)
index 0000000..6d761c2
--- /dev/null
@@ -0,0 +1,34 @@
+<?php
+
+namespace mdref;
+
+class Markdown
+{
+       /**
+        * @var \mdref\Path
+        */
+       protected $path;
+       
+       /**
+        * @param \mdref\Path $path
+        */
+       function __construct(Path $path) {
+               $this->path = $path;
+       }
+       
+       /**
+        * @return string
+        */
+       function __toString() {
+               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;
+       }
+}
diff --git a/mdref/Path.php b/mdref/Path.php
new file mode 100644 (file)
index 0000000..efa208a
--- /dev/null
@@ -0,0 +1,100 @@
+<?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 = "") {
+               return $this->baseDir . DIRECTORY_SEPARATOR . $this->path . $ext;
+       }
+       
+       /**
+        * 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>"; 
+       }
+}
\ No newline at end of file
diff --git a/mdref/RefEntry.php b/mdref/RefEntry.php
new file mode 100644 (file)
index 0000000..9db99f7
--- /dev/null
@@ -0,0 +1,162 @@
+<?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 ($upper && !ctype_upper($parts[$i][0])) {
+                                       $link .= "::";
+                               } else {
+                                       $link .= "\\";
+                               }
+                       }
+                       $link .= $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 htmlspecialchars(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 htmlspecialchars(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
new file mode 100644 (file)
index 0000000..3816be2
--- /dev/null
@@ -0,0 +1,98 @@
+<?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());//$this->format($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/public/.htaccess b/public/.htaccess
new file mode 100644 (file)
index 0000000..affb2a0
--- /dev/null
@@ -0,0 +1,6 @@
+RewriteEngine On
+RewriteCond %{REQUEST_FILENAME} -f [OR]
+RewriteCond %{REQUEST_FILENAME} -d [OR]
+RewriteCond %{REQUEST_FILENAME} -l
+RewriteRule ^ - [L]
+RewriteRule ^ index.php [L]
diff --git a/public/index.css b/public/index.css
new file mode 100644 (file)
index 0000000..2f86d66
--- /dev/null
@@ -0,0 +1,188 @@
+* { 
+       font-size: 99.9%;
+}
+
+body, code {
+       font-family: Inconsolata, Monospace, 'Courier New', Courier, monospace;
+}
+body {
+       font-size: 1.5em;
+       margin: 0;
+       padding: 0;
+       color: #3f3f3f;
+}
+
+body>* {
+       margin-left: 1em;
+}
+body>ul {
+       margin-left: 2em;
+}
+
+.sidebar {
+       font-size: .9em;
+       float: right;
+       background: #f0f0f0;
+       border-bottom-left-radius: 10px;
+       padding: 0;
+       width: auto;
+       min-width: 200px;
+       padding-right: 1em;
+}
+.sidebar>ul {
+}
+.sidebar ul {
+       margin-left: 1em;
+       margin-top: .5em;
+       padding: 0;
+       list-style-type: none;
+}
+
+.sidebar .edit {
+       display: block;
+       position: absolute;
+       top: 2em;
+       right: 0.5em;
+       transform: rotate(45deg);
+       text-decoration: none;
+       color: white;
+       font-weight: bold;
+       text-shadow: 0 0 5px red;
+}
+
+code { 
+       display: inline-block;
+       border-radius: 2px;
+       padding: 0px 2px 2px 2px;
+       background: #e0e0e0;
+       color: #606060;
+       box-shadow: 0 0 1px #999;
+}
+
+code code {
+       display: inline;
+       padding: 0;
+       background: transparent;
+       border: none;
+       box-shadow: none;
+}
+
+pre>code {
+       padding: 1em;
+}
+pre>code, pre>code code {
+       background: #333;
+       color: #eee;
+}
+
+p, pre {
+       margin: 1em 2em;
+}
+
+li>p {
+       margin: 1em 0;
+}
+
+blockquote {
+       border-top: 1px solid #800000;
+       border-bottom: 1px solid #800000;
+       background: #ffe4e1;
+       margin: 2em 0;
+}
+
+ul {
+       margin-bottom: 2em;
+}
+li {
+       margin-bottom: .5em;
+}
+a, h1 code>a {
+       color: #2f4f4f;
+}
+a:hover {
+       text-decoration: none;
+}
+
+.var {
+       color: #800000;
+}
+.constant {
+       color: #2e8b57;
+}
+
+h1 {
+       line-height: 1.5;
+}
+h1 code {
+       font-weight: normal;
+       font-size: .9em;
+       line-height: 1.33;
+}
+
+footer, h1, li h3 {
+       background: #708090;
+       color: #f5f5dc;
+}
+
+footer, h1 {
+       margin: 0;
+       padding: 1em;
+}
+
+li h3 {
+       border-radius: 4px;
+       display: inline-block;
+       width: auto;
+       padding: .2em;
+       margin: .5em 0 0 0;
+}
+
+h1 .constant, pre>code .consant, li h3 .constant {
+       color: #98fb98;
+}
+
+h1 .var, pre>code .var, li h3 .var {
+       color: #f4a460;
+}
+
+footer a, h1 a, pre>code a, li h3 a {
+       color: #b0e0e6;
+}
+
+li h3 a {
+       text-decoration: none;
+}
+li h3 a:hover {
+       text-decoration: underline;
+}
+
+#disqus_thread {
+       margin-top: 8em;
+       margin-right: 2em;
+}
+
+footer {
+       font-size: smaller;
+       text-align: center;
+       clear: both;
+       margin-top: 8em;
+}
+
+footer ul {
+       margin: 0;
+       padding: 0;
+}
+
+footer li {
+       list-style-type: none;
+       display: inline-block;
+       margin: 0 1em;
+}
+
+footer a {
+       text-decoration: none;
+}
+
+footer a:hover {
+       text-decoration: underline;
+}
\ No newline at end of file
diff --git a/public/index.js b/public/index.js
new file mode 100644 (file)
index 0000000..62ccba6
--- /dev/null
@@ -0,0 +1,194 @@
+"use strict";
+
+$(function() {
+       var mdref = {
+               log: function log() {
+                       console.log.apply(console, arguments);
+               },
+               is_constant: function is_constant(s) {
+                       s = s.replace(/v\d+(_\d+)?$/, "");
+                       if (s.length < 2) {
+                               return false;
+                       }
+                       return s.toUpperCase(s) === s;
+               },
+               is_variable: function is_variable(s) {
+                       return s.substring(0,1) === "$";
+               },
+               type: function type(s, nn) {
+                       var i, j, t;
+                       // mdref.log("type", s);
+                       // nothing
+                       if (!s.match(/[a-zA-Z]/)) {
+                               return;
+                       }
+
+                       switch (s) {
+                       // types
+                       case "void":
+                       case "bool":
+                       case "int":
+                       case "float":
+                       case "string":
+                       case "resource":
+                       case "array":
+                       case "object":
+                       case "callable":
+                       case "mixed":
+                       // Zend/SPL
+                       case "stdClass":
+                       case "Exception":
+                       case "ErrorException":
+                       case "RuntimeException":
+                       case "UnexpectedValueException":
+                       case "DomainException":
+                       case "InvalidArgumentException":
+                       case "BadMethodCallException":
+                       case "Closure":
+                       case "Generator":
+                       case "Countable":
+                       case "Serializable":
+                       case "Traversable":
+                       case "Iterator":
+                       case "IteratorAggregate":
+                       case "ArrayAccess":
+                       case "ArrayObject":
+                       case "ArrayIterator":
+                       case "RecursiveArrayIterator":
+                       case "SplObserver":
+                       case "SplSubject":
+                       case "SplObjectStorage":
+                               return "<code>";
+
+                       // keywords
+                       case "is":
+                               if (nn !== "H1") {
+                                       return;
+                               }
+                       case "extends":
+                       case "implements":
+                               if (nn === "H1") {
+                                       return "<br>&nbsp;<em>";
+                               }
+                       case "class":
+                       case "interface":
+                       case "namespace":
+                       case "public":
+                       case "protected":
+                       case "private":
+                       case "static":
+                       case "final":
+                       case "abstract":
+                       case "self":
+                       case "parent":
+                       // phrases
+                       case "Optional":
+                       case "optional":
+                               return "<em>";
+                       }
+
+                       // class members
+                       if (-1 !== (i = s.indexOf("::"))) {
+                               t = s.substring(i+2);
+                               if (!mdref.is_constant(t) && !mdref.is_variable(t)) {
+                                       // methods
+                                       return "<a href=\"" + s.replace(/::|\\/g, "/") + "\">";
+                               }
+                       }
+                       if (-1 !== (j = s.indexOf("\\")) && s.substr(j+1,1) !== "n") {
+                               return "<a href=\"" + s.replace(/\\/g, "/").replace(/::|$/, "#") + "\">";
+                       }
+
+                       switch (s.toLowerCase()) {
+                       // variables
+                       default:
+                               if (!mdref.is_variable(s)) {
+                                       break;
+                               }
+                       // special constants
+                       case "null":
+                       case "true":
+                       case "false":
+                               return "<span class=\"var\">";
+                       }
+
+                       // constants
+                       if (mdref.is_constant(s)) {
+                               return "<span class=\"constant\">";
+                       }
+               },
+               node: function node(s, nn) {
+                       // mdref.log("node", s);
+                       var t;
+                       if ((t = mdref.type(s, nn))) {
+                               return $(t).text(s);
+                       }
+                       return document.createTextNode(s);
+               },
+               wrap: function wrap(n, nn) {
+                       var $n = $(n)
+                       var a = [];
+
+                       $n.text().split(/([^a-zA-Z0-9_\\\$:]+)/).forEach(function(v) {
+                               a.push(mdref.node(v, nn));
+                       });
+                       $n.replaceWith(a);
+               },
+               walk: function walk(i, e) {
+                       // mdref.log("walk", i, e);
+
+                       $.each($.makeArray(e.childNodes), function(i, n) {
+                               switch (n.nodeName) {
+                               case "A":
+                               case "BR":
+                               case "HR":
+                                       break;
+                               case "#text":
+                                       mdref.wrap(n, e.nodeName);
+                                       break;
+                               default:
+                                       mdref.walk(-1, n);
+                                       break;
+                               }
+                       });
+               },
+               blink: function blink(c) {
+                       var $c = $(c);
+
+                       $c.fadeOut("fast").queue(function(next) {
+                               this.style.color = "red";
+                               next();
+                       }).fadeIn("fast").fadeOut("slow").queue(function(next) {
+                               this.style.color = "";
+                               next();
+                       }).fadeIn("slow");
+               },
+               hashchange: function hashchange() {
+                       if (location.hash.length > 1) {
+                               var hash = location.hash.substring(1);
+                               var name = mdref.is_variable(hash) ? ".var" : ".constant";
+                               var scrolled = false;
+
+                               $(name).each(hash.substring(hash.length-1) === "_" ? function(i, c) {
+                                       if (c.textContent.substring(0, hash.length) === hash) {
+                                               if (!scrolled) {
+                                                       $(window).scrollTop($(c).offset().top - 100);
+                                                       scrolled = true;
+                                               }
+                                               mdref.blink(c);
+                                       }
+                               } : function(i, c) {
+                                       if (c.textContent === hash) {
+                                               $(window).scrollTop($(c).offset().top - 100);
+                                               mdref.blink(c);
+                                               return false;
+                                       }
+                               });
+                       }
+               }
+       };
+       
+       $("h1,h2,h3,h4,h5,h6,p,li,code").each(mdref.walk);
+       $(window).on("hashchange", mdref.hashchange);
+       mdref.hashchange();
+});
diff --git a/public/index.php b/public/index.php
new file mode 100644 (file)
index 0000000..d4518cd
--- /dev/null
@@ -0,0 +1,25 @@
+<?php
+
+define("ROOT", dirname(__DIR__));
+define("REFS", getenv("REFPATH") ?: implode(PATH_SEPARATOR, glob(ROOT."/refs/*")));
+
+$loader = require __DIR__ . "/../vendor/autoload.php";
+/* @var $loader \Composer\Autoload\ClassLoader */
+$loader->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)
+       ->attach(new Layout)
+       ->notify()
+       ->getResponse()
+       ->send();
diff --git a/refs/http b/refs/http
new file mode 120000 (symlink)
index 0000000..924216a
--- /dev/null
+++ b/refs/http
@@ -0,0 +1 @@
+../../mdref-http.git/
\ No newline at end of file
diff --git a/views/edit.phtml b/views/edit.phtml
new file mode 100644 (file)
index 0000000..aabbb02
--- /dev/null
@@ -0,0 +1,3 @@
+<?php if (isset($listing) && ($entry = $listing->getSelf()) && ($url = $entry->formatEditUrl())) : ?>
+       <a class="edit" href="<?=$url?>">Edit Page</a>
+<?php endif; ?>
diff --git a/views/footer.phtml b/views/footer.phtml
new file mode 100644 (file)
index 0000000..12ef7fb
--- /dev/null
@@ -0,0 +1,6 @@
+<ul>
+       <li><a href="https://github.com/m6w6/mdref">mdref-v<?php readfile(__DIR__."/../VERSION") ?></a></li>
+       <li><a href="LICENSE">&copy; <?= implode("-", array_unique([2013, idate("Y")])) ?>
+               All rights reserved.</a></li>
+       <li><?php include __DIR__."/edit.phtml" ?></li>
+</ul>
diff --git a/views/index.phtml b/views/index.phtml
new file mode 100644 (file)
index 0000000..dbd3017
--- /dev/null
@@ -0,0 +1,20 @@
+<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>
+                                       <?=$entry->readDescription()?>
+                                       <?php $recursor($entry, "/[A-Z]*.md") ?>
+                               </li>
+                       </ul>
+                       <?php }); ?>
+               <?php endforeach; ?>
+       <?php endif; ?>
+<?php endif; ?>
diff --git a/views/layout.phtml b/views/layout.phtml
new file mode 100644 (file)
index 0000000..43463ca
--- /dev/null
@@ -0,0 +1,32 @@
+<!doctype html>
+<html>
+       <head>
+               <meta charset="utf-8">
+               <title>
+                       <?php if ($title) : ?>
+                               <?= $title ?> - mdref
+                       <?php else: ?>
+                               mdref
+                       <?php endif; ?>
+               </title>
+               <base href="<?= $baseUrl ?>">
+               <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 include __DIR__."/mdref.phtml" ?>
+               <?php else: ?>
+                       <?php include __DIR__."/index.phtml" ?>
+               <?php endif; ?>
+
+               <div id="disqus_thread"></div>
+
+               <footer>
+                       <?php include __DIR__."/footer.phtml" ?>
+               </footer>
+               <script src="index.js"></script>
+       </body>
+</html>
diff --git a/views/mdref.phtml b/views/mdref.phtml
new file mode 100644 (file)
index 0000000..5cec21d
--- /dev/null
@@ -0,0 +1,14 @@
+<?= $html ?>
+
+<?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><?=$entry->readDescription()?></p>
+                       <p><?=$entry->readTitle()?></p>
+               </li>
+       <?php endforeach; ?>
+       </ul>
+<?php endif; ?>
diff --git a/views/sidebar.phtml b/views/sidebar.phtml
new file mode 100644 (file)
index 0000000..f6dccb4
--- /dev/null
@@ -0,0 +1,24 @@
+<?php if (isset($listing)) : ?>
+<div class="sidebar">
+       <?php include __DIR__."/edit.phtml" ?>
+       <ul>
+               <li>&lsh; <a href="">Home</a></li>
+               <?php if (($entry = $listing->getParent())) : ?>
+                       <li>&uarr; <a href="<?=$entry->formatUrl()?>"><?=$entry->formatLink()?></a></li>
+               <?php endif; ?>
+               <?php if (($entry = $listing->getSelf()) && ($link = $entry->formatLink())) : ?>
+                       <ul><li>&circlearrowright; <?= $link ?>
+               <?php endif; ?>
+               <?php if (count($listing)) : ?>
+                       <ul>
+                               <?php foreach ($listing as $entry) : ?>
+                               <li>&rdsh; <a href="<?=$entry->formatUrl()?>"><?=$entry->formatLink()?></a></li>
+                               <?php endforeach; ?>
+                       </ul>
+               <?php endif; ?>
+               <?php if (isset($link) && strlen($link)) : ?>
+                       </li></ul>
+               <?php endif; ?>
+       </ul>
+</div>
+<?php endif; ?>