Commits

Mateusz Łopaciński committed 727660c

Initial master commit.

Comments (0)

Files changed (20)

+
+#php 5.4 development server
+development.php
+start.bat
+
+#ignore thumbnails created by windows
+Thumbs.db
+#Ignore files build by Visual Studio
+*.obj
+*.exe
+*.pdb
+*.user
+*.aps
+*.pch
+*.vspscc
+*_i.c
+*_p.c
+*.ncb
+*.suo
+*.tlb
+*.tlh
+*.bak
+*.cache
+*.ilk
+*.log
+[Bb]in
+[Dd]ebug*/
+*.lib
+*.sbr
+obj/
+[Rr]elease*/
+_ReSharper*/
+[Tt]est[Rr]esult*

Ray/Exception/Call.php

+<?php
+
+namespace Ray\Exception;
+
+class Call extends \Exception{}

Ray/Exception/View/File.php

+<?php
+
+namespace Ray\Exception\View;
+
+class File extends \Exception{}

Ray/Exception/View/Mustache.php

+<?php
+
+namespace Ray\Exception\View;
+
+class Mustache extends \Exception{}
+<?php
+
+namespace Ray;
+
+class Ray{
+    
+    public  $config,
+            $router,
+            $view,
+            $request,
+            $response,
+            $errors,
+            $dispatch;
+    
+    public function __construct($config=array(), $preDispatch=null, $postDispatch=null){
+        spl_autoload_register(array('Ray\Ray', 'autoload'));
+
+        if ( @date_default_timezone_set(date_default_timezone_get()) === false ) {
+            date_default_timezone_set('UTC');
+        }
+        
+        if(!defined('DS')){
+            define('DS', DIRECTORY_SEPARATOR);
+        }
+        
+        if(!defined('ROOT')){
+            define('ROOT', realpath(dirname(__FILE__)  . DS . '..' . DS ));
+        }
+        
+        $this->config = array_merge(array(
+            'mode' => 'production',
+            'templates' => ROOT . DS . 'template',
+            'view' => 'Ray\View\Inline'
+        ), $config);
+        
+        if($preDispatch && is_callable($preDispatch)){
+            $this->dispatch['pre'] = $preDispatch;
+        }
+        
+        if($postDispatch && is_callable($postDispatch)){
+            $this->dispatch['post'] = $postDispatch;
+        }
+        
+        $this->router = new Router();
+        
+        $this->response = new Response();
+        
+        $this->request = new Request();
+        
+        $this->response->responseCode(Response::OK);
+    }
+    
+    public function __call($method, $args){
+        
+        $reflection = new \ReflectionObject($this->request);
+        
+        $via = 'METHOD_' . strtoupper($method);
+        
+        if($reflection->hasConstant($via)){
+            return $this->mapRoute($reflection->getConstant($via), $args);
+        }else{
+            throw new Exception\Call('What the heck is that!? <i>__call('.$method.', '.var_export($args, true).')</i>');
+        }
+    }
+    
+    public function __destruct(){
+        
+        if($this->router){
+            if(isset($this->dispatch['pre'])){
+                call_user_func($this->dispatch['pre']);
+            }
+            
+            $return = $this->router->proccess($this->request->getRequest());
+            
+            if($return){
+                $view = new $this->config['view']($return, $this->config);
+                $send = $view->render();
+            }elseif(isset($this->error[Response::NOT_FOUND])){
+                $response = Response::NOT_FOUND;
+                $send =  $this->error[Response::NOT_FOUND];
+            }else{
+                $response = Response::NOT_FOUND;
+                $send = Response::NOT_FOUND;
+            }
+            
+            if(isset($response)){
+                $this->response->responseCode($response);
+            }
+            
+            if(isset($this->dispatch['post'])){
+                call_user_func($this->dispatch['post']);
+            }
+            
+            $this->response->send($send);
+        }
+    }
+    
+    private function mapRoute($method, $args){
+        $pattern = $args[0];
+        $callable = end($args);
+        return $this->router->map($method, $pattern, $callable);
+    }
+    
+    public function map(){
+        $args = func_get_args();
+        return $this->mapRoute($args);
+    }
+    
+    public function error($code, $callable){
+        if(is_callable($callable)){
+            $this->errors[$code] = $callable;
+        }else{
+            throw new Exception\Call('Hey, man $callable isnt callable');
+        }
+    }
+    
+    /**
+     * Autoload
+     */
+    public static function autoload( $class ) {
+
+        $file = ROOT . DIRECTORY_SEPARATOR . str_replace('\\', DIRECTORY_SEPARATOR, $class) . '.php';
+        
+        if ( file_exists($file) ) {
+            require $file;
+        }
+    }
+}
+<?php
+
+namespace Ray;
+
+class Request{
+    
+    const METHOD_HEAD = 'HEAD';
+    
+    const METHOD_GET = 'GET';
+    
+    const METHOD_POST = 'POST';
+    
+    const METHOD_PUT = 'PUT';
+    
+    const METHOD_DELETE = 'DELETE';
+    
+    const METHOD_PATCH = 'PATCH';
+    
+    const METHOD_OPTIONS = 'OPTIONS';
+    
+    protected $additionalHeaders = array(
+        'content-type', 'content-length', 'php-auth-user',
+        'php-auth-pw', 'auth-type', 'x-requested-with'
+    );
+    
+    private $request;
+    
+    public function __construct(){
+        
+        $headers = array();
+        foreach ( $_SERVER as $key => $value ) {
+            $key = $this->convertHeader($key);
+            if ( strpos($key, 'http-') === 0 || in_array($key, $this->additionalHeaders) ) {
+                $name = str_replace('http-', '', $key);
+                $headers[$name] = $value;
+            }
+        }
+        
+        $this->request = (object) array(
+            'host'    => $_SERVER['HTTP_HOST'],
+            'method'  => isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : self::METHOD_GET,
+            'headers' => $headers,
+            'uri'     => $_SERVER['PATH_INFO']
+        );
+        
+    }
+
+    protected function convertHeader( $name ) {
+        return str_replace('_', '-', strtolower($name));
+    }
+    
+    public function isAjax() {
+        return ( isset($this->request->headers['X_REQUESTED_WITH']) &&
+                $this->request->headers['X_REQUESTED_WITH'] === 'XMLHttpRequest' );
+    }
+    
+    public function getRequest(){
+        return $this->request;
+    }
+    
+}
+<?php
+
+namespace Ray;
+
+class Response{
+
+    const OK = '200 OK';
+    const CREATED = '201 Created';
+    const ACCEPTED = '202 Accepted';
+    const NON_AUTH_INFO = '203 Non-Authoritative Information';
+    const NO_CONTENT = '204 No Content';
+    const RESET_CONTENT = '205 Reset Content';
+    const PARTIAL_CONTENT = '206 Partial Content';
+
+    const MULTIPLE_CHOICES = '300 Multiple Choices';
+    const MOVED_PERMANENTLY = '301 Moved Permanently';
+    const FOUND = '302 Found';
+    const SEE_OTHER = '303 See Other';
+    const NOT_MODIFIED = '304 Not Modified';
+    const USE_PROXY = '305 Use Proxy';
+    const UNUSED = '306 (Unused)';
+    const TEMPORARY_REDIRECT = '307 Temporary Redirect';
+
+    const BAD_REQUEST = '400 Bad Request';
+    const UNAUTHORIZED = '401 Unauthorized';
+    const FORBIDDEN = '403 Forbidden';
+    const NOT_FOUND = '404 Not Found';
+    const METHOD_NOT_ALLOWED = '405 Method Not Allowed';
+    const NOT_ACCEPTABLE = '406 Not Acceptable';
+    const REQUEST_TIMEOUT = '408 Request Timeout';
+    const CONFLICT = '409 Conflict';
+    const GONE = '410 Gone';
+    const LENGTH_REQUIRED = '411 Length Required';
+    const UNSUPPORTED_MEDIA_TYPE = '415 Unsupported Media Type';
+
+    const INTERNAL_ERROR = '500 Internal Server Error';
+    const NOT_IMPLEMENTED = '501 Not Implemented';
+    const BAD_GATEWAY = '502 Bad Gateway';
+    const SERVICE_UNAVAILABLE = '503 Service Unavailable';
+    const GATEWAY_TIMEOUT = '504 Gateway Timeout';
+    const HTTP_VERSION_NOT_SUPPORTED = '505 HTTP Version Not Supported';
+    
+    private $headers;
+    
+    private $responseCode;
+    
+    public function __construct(){
+        $this->responseCode = self::OK;
+        $this->headers['Content-Type'] = 'text/html';
+    }
+    
+    public function contentType($content){
+        $this->headers['Content-Type'] = 'text/html';
+        return $this;
+    }
+    
+    public function responseCode($code = self::OK){
+        $this->responseCode = $code;
+        return $this;
+    }
+    
+    public function header($key, $value=''){
+        $this->headers[$key] = $value;
+        return $this;
+    }
+    
+    public function send( $send=null ){
+        if( ! headers_sent() ){
+            
+            if ( substr(PHP_SAPI, 0, 3) === 'cgi') {
+                $header = 'Status: %s';
+            }else{
+                $header = 'HTTP/1.1 %s';
+            }
+            header(sprintf($header, $this->responseCode));
+            
+            foreach($this->headers as $key=>$value){
+                header($key . ': ' . $value);
+            }
+            
+            if($send){
+                echo $send;
+            }
+            
+            flush();
+        }
+    }
+    
+}
+<?php
+
+namespace Ray;
+
+class Router{
+
+    private $routes = array();
+    
+    public function map($method, $pattern, $callable){
+        $route = $this->routes[$method][$pattern] =  new Router\Route($pattern, $callable);
+        
+        return $route;
+    }
+    
+    public function proccess($request){
+
+        if(isset($this->routes[$request->method])){
+            $active = false;
+            
+            foreach($this->routes[$request->method] as $route){
+                if($route->check($request->uri) === true){
+                    $active = $route;
+                    break;
+                }
+            }
+            
+            if($active){
+                return $active->proccess();
+            }
+        }
+        
+        return false;
+    }
+    
+}

Ray/Router/Route.php

+<?php
+
+namespace Ray\Router;
+
+use Ray;
+
+class Route{
+    
+    private $pattern;
+    
+    private $callable;
+    
+    private $params;
+    
+    private $conditions = array();
+    
+    public function __construct($pattern, $callable){
+        $this->pattern = $pattern;
+        $this->callable = $callable;
+    }
+    
+    public function check($call){
+        
+        preg_match_all('@:([\w]+)@', $this->pattern, $params, PREG_PATTERN_ORDER);
+        $params = current($params);
+        
+        $regex = preg_replace_callback('@:[\w]+@', array($this, 'getRegex'), $this->pattern);
+        
+        if ( substr($this->pattern, -1) === '/' ) {
+            $regex = $regex . '?';
+        }
+        
+        if(preg_match('@^' . $regex . '$@', $call, $values)){
+            array_shift($values);
+            foreach($params as $k=>$v){
+                $val = substr($v, 1);
+                if(isset($values[$val])){
+                    $this->params[$val] = urldecode($values[$val]);
+                }
+            }
+            
+            return true;
+        }
+        
+        return false;
+    }
+    
+    public function conditions(array $conditions){
+        $this->conditions = $conditions;
+    }
+    
+    public function proccess(){
+        if(is_array($this->params)){
+            return call_user_func_array($this->callable, $this->params);
+        }
+        
+        return call_user_func($this->callable);
+    }
+    
+    protected function getRegex( $matches ) {
+        $key = substr(current($matches),1);
+        
+        $condition = isset($this->conditions[$key]) ? $this->conditions[$key] : 
+                     '.*?';
+        
+        return '(?P<'.$key.'>' . $condition . ')';
+    }
+    
+}
+<?php
+
+namespace Ray;
+
+interface View{
+    
+    public function render();
+    
+}

Ray/View/File.php

+<?php
+
+namespace Ray\View;
+
+use Ray;
+
+class File implements Ray\View{
+    
+    private $config;
+    
+    private $options = array(
+        'ext' => '.php',
+        'layout' => 'default',
+        'actionDir' => 'action',
+        'action' => '404',
+        'params' => array()
+    );
+    
+    public function __construct(array $response, $config){
+        
+        $this->config = $config;
+        
+        $this->options = array_merge($this->options, $response);
+        
+    }
+    
+    private function view($file, $params=array()){
+        $view = new Ray\View\File\View($file, $params, $this->config, $this->options);
+        return $view->render();
+    }
+    
+    public function render(){
+        
+        $layout = $this->config['templates'] . DS . $this->options['layout'] . $this->options['ext'];
+        
+        $action = $this->config['templates'] . DS . $this->options['actionDir'] . DS . $this->options['action'] . $this->options['ext'];
+        
+        if(file_exists($layout)){
+            
+            if(file_exists($action)){
+                
+                $params = $this->options['params'];
+                
+                $params['action'] = $this->view($action, $params);
+                
+                return $this->view($layout, $params);
+            }else{
+                throw new Ray\Exception\View\File('Action template file not found. Path: '. $action);
+            }
+            
+        }else{
+            throw new Ray\Exception\View\File('Template file not found. Path: ' . $layout);
+        }
+        
+        return;
+    }
+    
+}

Ray/View/File/View.php

+<?php
+
+namespace Ray\View\File;
+
+class View{
+    
+    private $__file;
+    
+    private $__render;
+    
+    private $__params;
+    
+    private $__config;
+    
+    private $__options;
+    
+    public function __construct($file, $params=array(), $config, $options){
+        
+        $this->__file = $file;
+        
+        $this->__config = $config;
+        
+        $this->__options = $options;
+        
+        $this->__params = $params;
+        
+        foreach($params as $key => $value){
+            if(is_string($key)){
+                $this->$key = $value;
+            }
+        }
+        
+    }
+    
+    public function element($element, $params=array(), $dir='element'){
+        $file = $this->__config['templates'] . DS . $this->__options['actionDir']
+              . ($dir ? DS . $dir : '' ) . DS . $element . $this->__options['ext'];
+              
+        if(file_exists($file)){
+            $view = new self($file, array_merge($this->__params, $params), $this->__config, $this->__options);
+            
+            return $view->render();
+        }else{
+            throw new Ray\Exception\View\File('Element template file not found. Path: '. $file);
+        }
+        
+        return;
+    }
+    
+    public function render(){
+        if(!$this->__render){
+            ob_start();
+            
+            require $this->__file;
+            
+            $this->__render = ob_get_contents();
+            
+            ob_end_clean();
+        }
+        
+        return $this->__render;
+    }
+    
+}

Ray/View/Inline.php

+<?php
+
+namespace Ray\View;
+
+use Ray;
+
+class Inline implements Ray\View{
+    
+    private $html;
+    
+    public function __construct($response, $config){
+        $this->html = $response;
+    }
+    
+    public function render(){
+        return $this->html;
+    }
+    
+}

Ray/View/Mustache.php

+<?php
+
+namespace Ray\View;
+
+use Ray;
+
+class Mustache implements Ray\View{
+    
+    private $options = array(
+        'ext' => '.mustache',
+        'layout' => 'default',
+        'actionDir' => 'action',
+        'action' => 'error404',
+        'params' => array()
+    );
+    
+    private $config;
+    
+    private $instance;
+    
+    public function __construct(array $response, $config){
+        
+        $this->instance = new Ray\View\Mustache\Compiler();
+        
+        $this->options = array_merge($this->options, $response);
+        
+        $this->config = $config;
+        
+    }
+    
+    public function render(){
+        
+        $layout = $this->config['templates'] . DS . $this->options['layout'] . $this->options['ext'];
+        
+        $action = $this->config['templates'] . DS . $this->options['actionDir'] . DS . $this->options['action'] . $this->options['ext'];
+        
+        if(file_exists($layout)){
+            $template = file_get_contents($layout);
+            
+            $params = is_array($this->options['params']) ? $this->options['params'] : array();
+            
+            if(file_exists($action)){
+                $partial = array(
+                    'action' => file_get_contents($action)
+                );
+                
+                return $this->instance->render($template, $params, $partial);
+            }else{
+                throw new Ray\Exception\View\Mustache('Action template file not found. Path: '. $action);
+            }
+            
+        }else{
+            throw new Ray\Exception\Mustache('Template file not found. Path: ' . $layout);
+        }
+        
+        return;
+    }
+    
+}

Ray/View/Mustache/Compiler.php

+<?php
+
+namespace Ray\View\Mustache;
+
+/**
+ * A Mustache implementation in PHP.
+ *
+ * {@link http://defunkt.github.com/mustache}
+ *
+ * Mustache is a framework-agnostic logic-less templating language. It enforces separation of view
+ * logic from template files. In fact, it is not even possible to embed logic in the template.
+ *
+ * This is very, very rad.
+ *
+ * @author Justin Hileman {@link http://justinhileman.com}
+ */
+class Compiler {
+
+	const VERSION      = '0.9.0';
+	const SPEC_VERSION = '1.1.2';
+
+	/**
+	 * Should this Mustache throw exceptions when it finds unexpected tags?
+	 *
+	 * @see self::_throwsException()
+	 */
+	protected $_throwsExceptions = array(
+		MustacheException::UNKNOWN_VARIABLE         => false,
+		MustacheException::UNCLOSED_SECTION         => true,
+		MustacheException::UNEXPECTED_CLOSE_SECTION => true,
+		MustacheException::UNKNOWN_PARTIAL          => false,
+		MustacheException::UNKNOWN_PRAGMA           => true,
+	);
+
+	// Override charset passed to htmlentities() and htmlspecialchars(). Defaults to UTF-8.
+	protected $_charset = 'UTF-8';
+
+	/**
+	 * Pragmas are macro-like directives that, when invoked, change the behavior or
+	 * syntax of Mustache.
+	 *
+	 * They should be considered extremely experimental. Most likely their implementation
+	 * will change in the future.
+	 */
+
+	/**
+	 * The {{%UNESCAPED}} pragma swaps the meaning of the {{normal}} and {{{unescaped}}}
+	 * Mustache tags. That is, once this pragma is activated the {{normal}} tag will not be
+	 * escaped while the {{{unescaped}}} tag will be escaped.
+	 *
+	 * Pragmas apply only to the current template. Partials, even those included after the
+	 * {{%UNESCAPED}} call, will need their own pragma declaration.
+	 *
+	 * This may be useful in non-HTML Mustache situations.
+	 */
+	const PRAGMA_UNESCAPED    = 'UNESCAPED';
+
+	/**
+	 * Constants used for section and tag RegEx
+	 */
+	const SECTION_TYPES = '\^#\/';
+	const TAG_TYPES = '#\^\/=!<>\\{&';
+
+	protected $_otag = '{{';
+	protected $_ctag = '}}';
+
+	protected $_tagRegEx;
+
+	protected $_template = '';
+	protected $_context  = array();
+	protected $_partials = array();
+	protected $_pragmas  = array();
+
+	protected $_pragmasImplemented = array(
+		self::PRAGMA_UNESCAPED
+	);
+
+	protected $_localPragmas = array();
+
+	/**
+	 * Mustache class constructor.
+	 *
+	 * This method accepts a $template string and a $view object. Optionally, pass an associative
+	 * array of partials as well.
+	 *
+	 * Passing an $options array allows overriding certain Mustache options during instantiation:
+	 *
+	 *     $options = array(
+	 *         // `charset` -- must be supported by `htmlspecialentities()`. defaults to 'UTF-8'
+	 *         'charset' => 'ISO-8859-1',
+	 *
+	 *         // opening and closing delimiters, as an array or a space-separated string
+	 *         'delimiters' => '<% %>',
+	 *
+	 *         // an array of pragmas to enable/disable
+	 *         'pragmas' => array(
+	 *             Mustache::PRAGMA_UNESCAPED => true
+	 *         ),
+	 *     );
+	 *
+	 * @access public
+	 * @param string $template (default: null)
+	 * @param mixed $view (default: null)
+	 * @param array $partials (default: null)
+	 * @param array $options (default: array())
+	 * @return void
+	 */
+	public function __construct($template = null, $view = null, $partials = null, array $options = null) {
+		if ($template !== null) $this->_template = $template;
+		if ($partials !== null) $this->_partials = $partials;
+		if ($view !== null)     $this->_context = array($view);
+		if ($options !== null)  $this->_setOptions($options);
+	}
+
+	/**
+	 * Helper function for setting options from constructor args.
+	 *
+	 * @access protected
+	 * @param array $options
+	 * @return void
+	 */
+	protected function _setOptions(array $options) {
+		if (isset($options['charset'])) {
+			$this->_charset = $options['charset'];
+		}
+
+		if (isset($options['delimiters'])) {
+			$delims = $options['delimiters'];
+			if (!is_array($delims)) {
+				$delims = array_map('trim', explode(' ', $delims, 2));
+			}
+			$this->_otag = $delims[0];
+			$this->_ctag = $delims[1];
+		}
+
+		if (isset($options['pragmas'])) {
+			foreach ($options['pragmas'] as $pragma_name => $pragma_value) {
+				if (!in_array($pragma_name, $this->_pragmasImplemented, true)) {
+					throw new MustacheException('Unknown pragma: ' . $pragma_name, MustacheException::UNKNOWN_PRAGMA);
+				}
+			}
+			$this->_pragmas = $options['pragmas'];
+		}
+	}
+
+	/**
+	 * Mustache class clone method.
+	 *
+	 * A cloned Mustache instance should have pragmas, delimeters and root context
+	 * reset to default values.
+	 *
+	 * @access public
+	 * @return void
+	 */
+	public function __clone() {
+		$this->_otag = '{{';
+		$this->_ctag = '}}';
+		$this->_localPragmas = array();
+
+		if ($keys = array_keys($this->_context)) {
+			$last = array_pop($keys);
+			if ($this->_context[$last] instanceof Compiler) {
+				$this->_context[$last] =& $this;
+			}
+		}
+	}
+
+	/**
+	 * Render the given template and view object.
+	 *
+	 * Defaults to the template and view passed to the class constructor unless a new one is provided.
+	 * Optionally, pass an associative array of partials as well.
+	 *
+	 * @access public
+	 * @param string $template (default: null)
+	 * @param mixed $view (default: null)
+	 * @param array $partials (default: null)
+	 * @return string Rendered Mustache template.
+	 */
+	public function render($template = null, $view = null, $partials = null) {
+		if ($template === null) $template = $this->_template;
+		if ($partials !== null) $this->_partials = $partials;
+
+		$otag_orig = $this->_otag;
+		$ctag_orig = $this->_ctag;
+
+		if ($view) {
+			$this->_context = array($view);
+		} else if (empty($this->_context)) {
+			$this->_context = array($this);
+		}
+
+		$template = $this->_renderPragmas($template);
+		$template = $this->_renderTemplate($template, $this->_context);
+
+		$this->_otag = $otag_orig;
+		$this->_ctag = $ctag_orig;
+
+		return $template;
+	}
+
+	/**
+	 * Wrap the render() function for string conversion.
+	 *
+	 * @access public
+	 * @return string
+	 */
+	public function __toString() {
+		// PHP doesn't like exceptions in __toString.
+		// catch any exceptions and convert them to strings.
+		try {
+			$result = $this->render();
+			return $result;
+		} catch (Exception $e) {
+			return "Error rendering mustache: " . $e->getMessage();
+		}
+	}
+
+	/**
+	 * Internal render function, used for recursive calls.
+	 *
+	 * @access protected
+	 * @param string $template
+	 * @return string Rendered Mustache template.
+	 */
+	protected function _renderTemplate($template) {
+		if ($section = $this->_findSection($template)) {
+			list($before, $type, $tag_name, $content, $after) = $section;
+
+			$rendered_before = $this->_renderTags($before);
+
+			$rendered_content = '';
+			$val = $this->_getVariable($tag_name);
+			switch($type) {
+				// inverted section
+				case '^':
+					if (empty($val)) {
+						$rendered_content = $this->_renderTemplate($content);
+					}
+					break;
+
+				// regular section
+				case '#':
+					// higher order sections
+					if ($this->_varIsCallable($val)) {
+						$rendered_content = $this->_renderTemplate(call_user_func($val, $content));
+					} else if ($this->_varIsIterable($val)) {
+						foreach ($val as $local_context) {
+							$this->_pushContext($local_context);
+							$rendered_content .= $this->_renderTemplate($content);
+							$this->_popContext();
+						}
+					} else if ($val) {
+						if (is_array($val) || is_object($val)) {
+							$this->_pushContext($val);
+							$rendered_content = $this->_renderTemplate($content);
+							$this->_popContext();
+						} else {
+							$rendered_content = $this->_renderTemplate($content);
+						}
+					}
+					break;
+			}
+
+			return $rendered_before . $rendered_content . $this->_renderTemplate($after);
+		}
+
+		return $this->_renderTags($template);
+	}
+
+	/**
+	 * Prepare a section RegEx string for the given opening/closing tags.
+	 *
+	 * @access protected
+	 * @param string $otag
+	 * @param string $ctag
+	 * @return string
+	 */
+	protected function _prepareSectionRegEx($otag, $ctag) {
+		return sprintf(
+			'/(?:(?<=\\n)[ \\t]*)?%s(?:(?P<type>[%s])(?P<tag_name>.+?)|=(?P<delims>.*?)=)%s\\n?/s',
+			preg_quote($otag, '/'),
+			self::SECTION_TYPES,
+			preg_quote($ctag, '/')
+		);
+	}
+
+	/**
+	 * Extract the first section from $template.
+	 *
+	 * @access protected
+	 * @param string $template
+	 * @return array $before, $type, $tag_name, $content and $after
+	 */
+	protected function _findSection($template) {
+		$regEx = $this->_prepareSectionRegEx($this->_otag, $this->_ctag);
+
+		$section_start = null;
+		$section_type  = null;
+		$content_start = null;
+
+		$search_offset = 0;
+
+		$section_stack = array();
+		$matches = array();
+		while (preg_match($regEx, $template, $matches, PREG_OFFSET_CAPTURE, $search_offset)) {
+			if (isset($matches['delims'][0])) {
+				list($otag, $ctag) = explode(' ', $matches['delims'][0]);
+				$regEx = $this->_prepareSectionRegEx($otag, $ctag);
+				$search_offset = $matches[0][1] + strlen($matches[0][0]);
+				continue;
+			}
+
+			$match    = $matches[0][0];
+			$offset   = $matches[0][1];
+			$type     = $matches['type'][0];
+			$tag_name = trim($matches['tag_name'][0]);
+
+			$search_offset = $offset + strlen($match);
+
+			switch ($type) {
+				case '^':
+				case '#':
+					if (empty($section_stack)) {
+						$section_start = $offset;
+						$section_type  = $type;
+						$content_start = $search_offset;
+					}
+					array_push($section_stack, $tag_name);
+					break;
+				case '/':
+					if (empty($section_stack) || ($tag_name !== array_pop($section_stack))) {
+						if ($this->_throwsException(MustacheException::UNEXPECTED_CLOSE_SECTION)) {
+							throw new MustacheException('Unexpected close section: ' . $tag_name, MustacheException::UNEXPECTED_CLOSE_SECTION);
+						}
+					}
+
+					if (empty($section_stack)) {
+						// $before, $type, $tag_name, $content, $after
+						return array(
+							substr($template, 0, $section_start),
+							$section_type,
+							$tag_name,
+							substr($template, $content_start, $offset - $content_start),
+							substr($template, $search_offset),
+						);
+					}
+					break;
+			}
+		}
+
+		if (!empty($section_stack)) {
+			if ($this->_throwsException(MustacheException::UNCLOSED_SECTION)) {
+				throw new MustacheException('Unclosed section: ' . $section_stack[0], MustacheException::UNCLOSED_SECTION);
+			}
+		}
+	}
+
+	/**
+	 * Prepare a pragma RegEx for the given opening/closing tags.
+	 *
+	 * @access protected
+	 * @param string $otag
+	 * @param string $ctag
+	 * @return string
+	 */
+	protected function _preparePragmaRegEx($otag, $ctag) {
+		return sprintf(
+			'/%s%%\\s*(?P<pragma_name>[\\w_-]+)(?P<options_string>(?: [\\w]+=[\\w]+)*)\\s*%s\\n?/s',
+			preg_quote($otag, '/'),
+			preg_quote($ctag, '/')
+		);
+	}
+
+	/**
+	 * Initialize pragmas and remove all pragma tags.
+	 *
+	 * @access protected
+	 * @param string $template
+	 * @return string
+	 */
+	protected function _renderPragmas($template) {
+		$this->_localPragmas = $this->_pragmas;
+
+		// no pragmas
+		if (strpos($template, $this->_otag . '%') === false) {
+			return $template;
+		}
+
+		$regEx = $this->_preparePragmaRegEx($this->_otag, $this->_ctag);
+		return preg_replace_callback($regEx, array($this, '_renderPragma'), $template);
+	}
+
+	/**
+	 * A preg_replace helper to remove {{%PRAGMA}} tags and enable requested pragma.
+	 *
+	 * @access protected
+	 * @param mixed $matches
+	 * @return void
+	 * @throws MustacheException unknown pragma
+	 */
+	protected function _renderPragma($matches) {
+		$pragma         = $matches[0];
+		$pragma_name    = $matches['pragma_name'];
+		$options_string = $matches['options_string'];
+
+		if (!in_array($pragma_name, $this->_pragmasImplemented)) {
+			throw new MustacheException('Unknown pragma: ' . $pragma_name, MustacheException::UNKNOWN_PRAGMA);
+		}
+
+		$options = array();
+		foreach (explode(' ', trim($options_string)) as $o) {
+			if ($p = trim($o)) {
+				$p = explode('=', $p);
+				$options[$p[0]] = $p[1];
+			}
+		}
+
+		if (empty($options)) {
+			$this->_localPragmas[$pragma_name] = true;
+		} else {
+			$this->_localPragmas[$pragma_name] = $options;
+		}
+
+		return '';
+	}
+
+	/**
+	 * Check whether this Mustache has a specific pragma.
+	 *
+	 * @access protected
+	 * @param string $pragma_name
+	 * @return bool
+	 */
+	protected function _hasPragma($pragma_name) {
+		if (array_key_exists($pragma_name, $this->_localPragmas) && $this->_localPragmas[$pragma_name]) {
+			return true;
+		} else {
+			return false;
+		}
+	}
+
+	/**
+	 * Return pragma options, if any.
+	 *
+	 * @access protected
+	 * @param string $pragma_name
+	 * @return mixed
+	 * @throws MustacheException Unknown pragma
+	 */
+	protected function _getPragmaOptions($pragma_name) {
+		if (!$this->_hasPragma($pragma_name)) {
+			throw new MustacheException('Unknown pragma: ' . $pragma_name, MustacheException::UNKNOWN_PRAGMA);
+		}
+
+		return (is_array($this->_localPragmas[$pragma_name])) ? $this->_localPragmas[$pragma_name] : array();
+	}
+
+	/**
+	 * Check whether this Mustache instance throws a given exception.
+	 *
+	 * Expects exceptions to be MustacheException error codes (i.e. class constants).
+	 *
+	 * @access protected
+	 * @param mixed $exception
+	 * @return void
+	 */
+	protected function _throwsException($exception) {
+		return (isset($this->_throwsExceptions[$exception]) && $this->_throwsExceptions[$exception]);
+	}
+
+	/**
+	 * Prepare a tag RegEx for the given opening/closing tags.
+	 *
+	 * @access protected
+	 * @param string $otag
+	 * @param string $ctag
+	 * @return string
+	 */
+	protected function _prepareTagRegEx($otag, $ctag, $first = false) {
+		return sprintf(
+			'/(?P<leading>(?:%s\\r?\\n)[ \\t]*)?%s(?P<type>[%s]?)(?P<tag_name>.+?)(?:\\2|})?%s(?P<trailing>\\s*(?:\\r?\\n|\\Z))?/s',
+			($first ? '\\A|' : ''),
+			preg_quote($otag, '/'),
+			self::TAG_TYPES,
+			preg_quote($ctag, '/')
+		);
+	}
+
+	/**
+	 * Loop through and render individual Mustache tags.
+	 *
+	 * @access protected
+	 * @param string $template
+	 * @return void
+	 */
+	protected function _renderTags($template) {
+		if (strpos($template, $this->_otag) === false) {
+			return $template;
+		}
+
+		$first = true;
+		$this->_tagRegEx = $this->_prepareTagRegEx($this->_otag, $this->_ctag, true);
+
+		$html = '';
+		$matches = array();
+		while (preg_match($this->_tagRegEx, $template, $matches, PREG_OFFSET_CAPTURE)) {
+			$tag      = $matches[0][0];
+			$offset   = $matches[0][1];
+			$modifier = $matches['type'][0];
+			$tag_name = trim($matches['tag_name'][0]);
+
+			if (isset($matches['leading']) && $matches['leading'][1] > -1) {
+				$leading = $matches['leading'][0];
+			} else {
+				$leading = null;
+			}
+
+			if (isset($matches['trailing']) && $matches['trailing'][1] > -1) {
+				$trailing = $matches['trailing'][0];
+			} else {
+				$trailing = null;
+			}
+
+			$html .= substr($template, 0, $offset);
+
+			$next_offset = $offset + strlen($tag);
+			if ((substr($html, -1) == "\n") && (substr($template, $next_offset, 1) == "\n")) {
+				$next_offset++;
+			}
+			$template = substr($template, $next_offset);
+
+			$html .= $this->_renderTag($modifier, $tag_name, $leading, $trailing);
+
+			if ($first == true) {
+				$first = false;
+				$this->_tagRegEx = $this->_prepareTagRegEx($this->_otag, $this->_ctag);
+			}
+		}
+
+		return $html . $template;
+	}
+
+	/**
+	 * Render the named tag, given the specified modifier.
+	 *
+	 * Accepted modifiers are `=` (change delimiter), `!` (comment), `>` (partial)
+	 * `{` or `&` (don't escape output), or none (render escaped output).
+	 *
+	 * @access protected
+	 * @param string $modifier
+	 * @param string $tag_name
+	 * @param string $leading Whitespace
+	 * @param string $trailing Whitespace
+	 * @throws MustacheException Unmatched section tag encountered.
+	 * @return string
+	 */
+	protected function _renderTag($modifier, $tag_name, $leading, $trailing) {
+		switch ($modifier) {
+			case '=':
+				return $this->_changeDelimiter($tag_name, $leading, $trailing);
+				break;
+			case '!':
+				return $this->_renderComment($tag_name, $leading, $trailing);
+				break;
+			case '>':
+			case '<':
+				return $this->_renderPartial($tag_name, $leading, $trailing);
+				break;
+			case '{':
+				// strip the trailing } ...
+				if ($tag_name[(strlen($tag_name) - 1)] == '}') {
+					$tag_name = substr($tag_name, 0, -1);
+				}
+			case '&':
+				if ($this->_hasPragma(self::PRAGMA_UNESCAPED)) {
+					return $this->_renderEscaped($tag_name, $leading, $trailing);
+				} else {
+					return $this->_renderUnescaped($tag_name, $leading, $trailing);
+				}
+				break;
+			case '#':
+			case '^':
+			case '/':
+				// remove any leftover section tags
+				return $leading . $trailing;
+				break;
+			default:
+				if ($this->_hasPragma(self::PRAGMA_UNESCAPED)) {
+					return $this->_renderUnescaped($modifier . $tag_name, $leading, $trailing);
+				} else {
+					return $this->_renderEscaped($modifier . $tag_name, $leading, $trailing);
+				}
+				break;
+		}
+	}
+
+	/**
+	 * Returns true if any of its args contains the "\r" character.
+	 *
+	 * @access protected
+	 * @param string $str
+	 * @return boolean
+	 */
+	protected function _stringHasR($str) {
+		foreach (func_get_args() as $arg) {
+			if (strpos($arg, "\r") !== false) {
+				return true;
+			}
+		}
+		return false;
+	}
+
+	/**
+	 * Escape and return the requested tag.
+	 *
+	 * @access protected
+	 * @param string $tag_name
+	 * @param string $leading Whitespace
+	 * @param string $trailing Whitespace
+	 * @return string
+	 */
+	protected function _renderEscaped($tag_name, $leading, $trailing) {
+		$rendered = htmlentities($this->_renderUnescaped($tag_name, '', ''), ENT_COMPAT, $this->_charset);
+		return $leading . $rendered . $trailing;
+	}
+
+	/**
+	 * Render a comment (i.e. return an empty string).
+	 *
+	 * @access protected
+	 * @param string $tag_name
+	 * @param string $leading Whitespace
+	 * @param string $trailing Whitespace
+	 * @return string
+	 */
+	protected function _renderComment($tag_name, $leading, $trailing) {
+		if ($leading !== null && $trailing !== null) {
+			if (strpos($leading, "\n") === false) {
+				return '';
+			}
+			return $this->_stringHasR($leading, $trailing) ? "\r\n" : "\n";
+		}
+		return $leading . $trailing;
+	}
+
+	/**
+	 * Return the requested tag unescaped.
+	 *
+	 * @access protected
+	 * @param string $tag_name
+	 * @param string $leading Whitespace
+	 * @param string $trailing Whitespace
+	 * @return string
+	 */
+	protected function _renderUnescaped($tag_name, $leading, $trailing) {
+		$val = $this->_getVariable($tag_name);
+
+		if ($this->_varIsCallable($val)) {
+			$val = $this->_renderTemplate(call_user_func($val));
+		}
+
+		return $leading . $val . $trailing;
+	}
+
+	/**
+	 * Render the requested partial.
+	 *
+	 * @access protected
+	 * @param string $tag_name
+	 * @param string $leading Whitespace
+	 * @param string $trailing Whitespace
+	 * @return string
+	 */
+	protected function _renderPartial($tag_name, $leading, $trailing) {
+		$partial = $this->_getPartial($tag_name);
+		if ($leading !== null && $trailing !== null) {
+			$whitespace = trim($leading, "\r\n");
+			$partial = preg_replace('/(\\r?\\n)(?!$)/s', "\\1" . $whitespace, $partial);
+		}
+
+		$view = clone($this);
+        
+		if ($leading !== null && $trailing !== null) {
+			return $leading . $view->render($partial);
+		} else {
+			return $leading . $view->render($partial) . $trailing;
+		}
+	}
+
+	/**
+	 * Change the Mustache tag delimiter. This method also replaces this object's current
+	 * tag RegEx with one using the new delimiters.
+	 *
+	 * @access protected
+	 * @param string $tag_name
+	 * @param string $leading Whitespace
+	 * @param string $trailing Whitespace
+	 * @return string
+	 */
+	protected function _changeDelimiter($tag_name, $leading, $trailing) {
+		list($otag, $ctag) = explode(' ', $tag_name);
+		$this->_otag = $otag;
+		$this->_ctag = $ctag;
+
+		$this->_tagRegEx = $this->_prepareTagRegEx($this->_otag, $this->_ctag);
+
+		if ($leading !== null && $trailing !== null) {
+			if (strpos($leading, "\n") === false) {
+				return '';
+			}
+			return $this->_stringHasR($leading, $trailing) ? "\r\n" : "\n";
+		}
+		return $leading . $trailing;
+	}
+
+	/**
+	 * Push a local context onto the stack.
+	 *
+	 * @access protected
+	 * @param array &$local_context
+	 * @return void
+	 */
+	protected function _pushContext(&$local_context) {
+		$new = array();
+		$new[] =& $local_context;
+		foreach (array_keys($this->_context) as $key) {
+			$new[] =& $this->_context[$key];
+		}
+		$this->_context = $new;
+	}
+
+	/**
+	 * Remove the latest context from the stack.
+	 *
+	 * @access protected
+	 * @return void
+	 */
+	protected function _popContext() {
+		$new = array();
+
+		$keys = array_keys($this->_context);
+		array_shift($keys);
+		foreach ($keys as $key) {
+			$new[] =& $this->_context[$key];
+		}
+		$this->_context = $new;
+	}
+
+	/**
+	 * Get a variable from the context array.
+	 *
+	 * If the view is an array, returns the value with array key $tag_name.
+	 * If the view is an object, this will check for a public member variable
+	 * named $tag_name. If none is available, this method will execute and return
+	 * any class method named $tag_name. Failing all of the above, this method will
+	 * return an empty string.
+	 *
+	 * @access protected
+	 * @param string $tag_name
+	 * @throws MustacheException Unknown variable name.
+	 * @return string
+	 */
+	protected function _getVariable($tag_name) {
+		if ($tag_name === '.') {
+			return $this->_context[0];
+		} else if (strpos($tag_name, '.') !== false) {
+			$chunks = explode('.', $tag_name);
+			$first = array_shift($chunks);
+
+			$ret = $this->_findVariableInContext($first, $this->_context);
+			while ($next = array_shift($chunks)) {
+				// Slice off a chunk of context for dot notation traversal.
+				$c = array($ret);
+				$ret = $this->_findVariableInContext($next, $c);
+			}
+			return $ret;
+		} else {
+			return $this->_findVariableInContext($tag_name, $this->_context);
+		}
+	}
+
+	/**
+	 * Get a variable from the context array. Internal helper used by getVariable() to abstract
+	 * variable traversal for dot notation.
+	 *
+	 * @access protected
+	 * @param string $tag_name
+	 * @param array $context
+	 * @throws MustacheException Unknown variable name.
+	 * @return string
+	 */
+	protected function _findVariableInContext($tag_name, $context) {
+		foreach ($context as $view) {
+			if (is_object($view)) {
+				if (method_exists($view, $tag_name)) {
+					return $view->$tag_name();
+				} else if (isset($view->$tag_name)) {
+					return $view->$tag_name;
+				}
+			} else if (is_array($view) && array_key_exists($tag_name, $view)) {
+				return $view[$tag_name];
+			}
+		}
+
+		if ($this->_throwsException(MustacheException::UNKNOWN_VARIABLE)) {
+			throw new MustacheException("Unknown variable: " . $tag_name, MustacheException::UNKNOWN_VARIABLE);
+		} else {
+			return '';
+		}
+	}
+
+	/**
+	 * Retrieve the partial corresponding to the requested tag name.
+	 *
+	 * Silently fails (i.e. returns '') when the requested partial is not found.
+	 *
+	 * @access protected
+	 * @param string $tag_name
+	 * @throws MustacheException Unknown partial name.
+	 * @return string
+	 */
+	protected function _getPartial($tag_name) {
+    
+        /*if(strpos($tag_name, '#') === 0){
+            
+        }
+        
+        var_dump($tag_name);die();
+        */
+        
+		if ((is_array($this->_partials) || $this->_partials instanceof ArrayAccess) && isset($this->_partials[$tag_name])) {
+			return $this->_partials[$tag_name];
+		}
+
+		if ($this->_throwsException(MustacheException::UNKNOWN_PARTIAL)) {
+			throw new MustacheException('Unknown partial: ' . $tag_name, MustacheException::UNKNOWN_PARTIAL);
+		} else {
+			return '';
+		}
+	}
+
+	/**
+	 * Check whether the given $var should be iterated (i.e. in a section context).
+	 *
+	 * @access protected
+	 * @param mixed $var
+	 * @return bool
+	 */
+	protected function _varIsIterable($var) {
+		return $var instanceof Traversable || (is_array($var) && !array_diff_key($var, array_keys(array_keys($var))));
+	}
+
+	/**
+	 * Higher order sections helper: tests whether the section $var is a valid callback.
+	 *
+	 * In Mustache.php, a variable is considered 'callable' if the variable is:
+	 *
+	 *  1. an anonymous function.
+	 *  2. an object and the name of a public function, i.e. `array($SomeObject, 'methodName')`
+	 *  3. a class name and the name of a public static function, i.e. `array('SomeClass', 'methodName')`
+	 *
+	 * @access protected
+	 * @param mixed $var
+	 * @return bool
+	 */
+	protected function _varIsCallable($var) {
+	  return !is_string($var) && is_callable($var);
+	}
+}
+
+
+/**
+ * MustacheException class.
+ *
+ * @extends Exception
+ */
+class MustacheException extends \Exception {
+
+	// An UNKNOWN_VARIABLE exception is thrown when a {{variable}} is not found
+	// in the current context.
+	const UNKNOWN_VARIABLE         = 0;
+
+	// An UNCLOSED_SECTION exception is thrown when a {{#section}} is not closed.
+	const UNCLOSED_SECTION         = 1;
+
+	// An UNEXPECTED_CLOSE_SECTION exception is thrown when {{/section}} appears
+	// without a corresponding {{#section}} or {{^section}}.
+	const UNEXPECTED_CLOSE_SECTION = 2;
+
+	// An UNKNOWN_PARTIAL exception is thrown whenever a {{>partial}} tag appears
+	// with no associated partial.
+	const UNKNOWN_PARTIAL          = 3;
+
+	// An UNKNOWN_PRAGMA exception is thrown whenever a {{%PRAGMA}} tag appears
+	// which can't be handled by this Mustache instance.
+	const UNKNOWN_PRAGMA           = 4;
+
+}
+<?php
+
+error_reporting(E_ALL | E_STRICT);
+
+require_once 'Ray/Ray.php';
+    
+try{
+    
+    $app = new Ray\Ray(array(
+        'view' => 'Ray\View\Mustache'
+    ));
+    
+    $app->error(404, function(){
+        return array(
+            'action' => '404'
+        );
+    });
+    
+    $app->get('/', function(){
+        return array(
+            'action' => 'index'
+        );
+    });
+    
+    $app->get('/post/:id/', function($id=7){
+        return array(
+            'action' => 'post'
+        );
+    });
+    
+}catch(\Exception $e){
+    echo '<pre>' . $e . '</pre>';
+}

template/action/index.mustache

+<h1>Hello index! Mustache Template</h1>

template/action/index.php

+<h1>Hello index! PHP Template</h1>

template/default.mustache

+<!DOCTYPE html>
+<html>
+    <head>
+        <title>Ray - micro framework for php</title>
+    </head>
+    <body>
+        <div class="wrapper">
+            {{>action}}
+        </div>
+    </body>
+</html>

template/default.php

+<!DOCTYPE html>
+<html>
+    <head>
+        <title>Ray - micro framework for php</title>
+    </head>
+    <body>
+        <div class="wrapper">
+            <?php echo $this->action ?>
+        </div>
+    </body>
+</html>
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.