--- /dev/null
+<?php
+// Hoplite
+// Copyright (c) 2011 Blue Static
+//
+// This program is free software: you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation, either version 3 of the License, or any later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+// more details.
+//
+// You should have received a copy of the GNU General Public License along with
+// this program. If not, see <http://www.gnu.org/licenses/>.
+/*
+namespace hoplite\test;
+use hoplite\views\Template;
+
+require_once TEST_ROOT . '/tests/views.php';
+
+class ViewTest extends \PHPUnit_Framework_TestCase
+{
+ public $saved_singleton = array();
+
+ public function setUp()
+ {
+ $this->saved_singleton['template_path'] = View::template_path();
+ $this->saved_singleton['cache_path'] = View::cache_path();
+
+ $path = dirname(__FILE__) . '/data/cache/';
+ $files = scandir($path);
+ foreach ($files as $file)
+ if ($file[0] != '.')
+ unlink($path . $file);
+ }
+
+ public function tearDown()
+ {
+ View::set_template_path($this->saved_singleton['template_path']);
+ View::set_cache_path($this->saved_singleton['cache_path']);
+ }
+
+ public function testTemplatePath()
+ {
+ $this->assertEquals('%s.tpl', View::template_path());
+
+ $path = '/webapp/views/%s.tpl';
+ View::set_template_path($path);
+ $this->assertEquals($path, View::template_path());
+ }
+
+ public function testSetCachePath()
+ {
+ $this->assertEquals('/tmp/phalanx_views', View::cache_path());
+
+ $path = '/cache/path';
+ View::set_cache_path($path);
+ $this->assertEquals($path, View::cache_path());
+ }
+
+ public function testCtorAndTemplateName()
+ {
+ $view = $this->getMock('phalanx\views\View', NULL, array('test_tpl'));
+ $this->assertEquals('test_tpl', $view->template_name());
+
+ $this->assertType('phalanx\base\Dictionary', $view->vars());
+ $this->assertEquals(0, $view->vars()->Count());
+ }
+
+ public function testProcessTemplateEntities()
+ {
+ $view = new TestView('test');
+ $data = '<strong>Some day, is it not, <'.'?php echo "Rob" ?'.'>?</strong>';
+ $this->assertEquals($data, $view->T_ProcessTemplate($data));
+ }
+
+ public function testProcessTemplateMacro()
+ {
+ $view = new TestView('test');
+ $in = 'foo $[some.value] bar';
+ $out = 'foo <?php echo $view->GetHTML("some.value") ?> bar';
+ $this->assertEquals($out, $view->T_ProcessTemplate($in));
+ }
+
+ public function testProcessTemplateShortTags()
+ {
+ $view = new TestView('test');
+ $in = 'foo <?php echo "Not this one"; ?> bar <? echo "But this one!" ?> moo';
+ $out = 'foo <?php echo "Not this one"; ?> bar <?php echo "But this one!" ?> moo';
+ $this->assertEquals($out, $view->T_ProcessTemplate($in));
+ }
+
+ public function testMagicGetterSetter()
+ {
+ $view = new View('test');
+ $view->foo = 'abc';
+ $this->assertEquals('abc', $view->foo);
+ $this->assertEquals('abc', $view->vars()->Get('foo'));
+
+ $view->foo = array();
+ $view->{"foo.bar"} = '123';
+ $this->assertEquals('123', $view->{"foo.bar"});
+ $this->assertEquals('123', $view->vars()->Get('foo.bar'));
+ }
+
+ public function testCachePath()
+ {
+ View::set_cache_path('/test/value');
+ $view = new TestView('name');
+ $this->assertEquals('/test/value/name.phpi', $view->T_CachePath($view->template_name()));
+ }
+
+ public function testCacheMiss()
+ {
+ TestView::SetupPaths();
+ $view = new TestView('cache_test');
+
+ $files = scandir(View::cache_path());
+ $this->assertEquals(2, count($files)); // Only dotfiles.
+
+ $view->T_Cache();
+
+ $files = scandir(View::cache_path());
+ $this->assertEquals(3, count($files));
+
+ $expected = file_get_contents(sprintf(View::template_path(), 'cache_test'));
+ $actual = file_get_contents(View::cache_path() . '/cache_test.phpi');
+ $this->assertEquals($expected, $actual);
+ }
+
+ public function testCacheHit()
+ {
+ $expected = 'Cache hit!';
+ TestView::SetupPaths();
+ file_put_contents(View::cache_path() . '/cache_test.phpi', $expected);
+ $view = new TestView('cache_test');
+
+ $files = scandir(View::cache_path());
+ $this->assertEquals(3, count($files));
+
+ $view->T_Cache();
+
+ $files = scandir(View::cache_path());
+ $this->assertEquals(3, count($files));
+
+ $actual = file_get_contents(View::cache_path() . '/cache_test.phpi');
+ $this->assertEquals($expected, $actual);
+ }
+
+ public function testCacheInvalidate()
+ {
+ TestView::SetupPaths();
+ file_put_contents(View::cache_path() . '/cache_test.phpi', 'Invalid template data');
+ $view = new TestView('cache_test');
+
+ // Need to wait for the mtime to make a difference.
+ sleep(1);
+ clearstatcache();
+
+ // Touch the template to update its mtime.
+ touch(sprintf(View::template_path(), 'cache_test'));
+
+ $files = scandir(View::cache_path());
+ $this->assertEquals(3, count($files));
+
+ $view->T_Cache();
+
+ $files = scandir(View::cache_path());
+ $this->assertEquals(3, count($files));
+
+ $expected = file_get_contents(sprintf(View::template_path(), 'cache_test'));
+ $actual = file_get_contents(View::cache_path() . '/cache_test.phpi');
+ $this->assertEquals($expected, $actual);
+ }
+
+ public function testRender()
+ {
+ TestView::SetupPaths();
+
+ $view = new TestView('render_test');
+ $view->name = 'Rob';
+
+ ob_start();
+ $view->Render();
+ $actual = ob_get_contents();
+ ob_end_clean();
+
+ $this->assertEquals('Hi, my name is Rob. This is undefined: .', $actual);
+ }
+}
+*/
\ No newline at end of file
--- /dev/null
+<?php
+// Hoplite
+// Copyright (c) 2011 Blue Static
+//
+// This program is free software: you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation, either version 3 of the License, or any later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+// more details.
+//
+// You should have received a copy of the GNU General Public License along with
+// this program. If not, see <http://www.gnu.org/licenses/>.
+
+namespace hoplite\views;
+
+/*!
+ This class knows how to load and cache templates to the file system.
+*/
+class TemplateLoader
+{
+ /*! @var TemplateLoader Singleton instance */
+ static private $instance = NULL;
+
+ /*! @var string Base path for loading the template file. Use %s to indicate
+ where the name (passed to the constructor) should be
+ substituted.
+ */
+ protected $template_path = '%s.tpl';
+
+ /*! @var string The cache path for templates. Unlike |$template_path|, this
+ should only be a path, to which the cached template name will
+ be appended. This should not end with a trailing slash.
+ */
+ protected $cache_path = '/tmp/phalanx_views';
+
+ /*! @var string A header to put at the beginning of each cached template file,
+ common for setting include paths or cache debug information.
+ */
+ protected $cache_prefix = '';
+
+ /*! @var array An array of Template objects, keyed by the template name. */
+ protected $cache = array();
+
+ /*! Gets the singleton instance. */
+ static public function GetInstance()
+ {
+ if (!self::$instance)
+ self::$instance = new __class__();
+ return self::$instance();
+ }
+ /*! Sets the singleton instance. */
+ static public function SetInstance($instance) { self::$instance = $instance; }
+
+ /*! Accessors */
+ public function set_template_path($path) { $this->$template_path = $path; }
+ function template_path() { return $this->$template_path; }
+
+ public function set_cache_path($path) { $this->$cache_path = $path; }
+ public function cache_path() { return $this->$cache_path; }
+
+ /*!
+ Loads a template from a file, creates a Template object, and returns a copy
+ of that object.
+
+ @param string Template name, with wich the template plath is formatted.
+
+ @return Template Clone of the cached template.
+ */
+ public function Load($name)
+ {
+ // First check the memory cache.
+ if (isset($this->cache[$name]))
+ return clone $this->cache[$name];
+
+ // Then check the filesystem cache.
+ $template = $this->_LoadIfCached($name);
+ if ($template) {
+ $this->cache[$name] = $template;
+ return clone $template;
+ }
+
+ // Finally, parse and cache the template.
+ $template = $this->_Cache($name);
+ $this->cache[$name] = $template;
+ return clone $template;
+ }
+
+ /*!
+ Loads a cached filesystem template if it is up-to-date.
+
+ @param string Template name
+
+ @return Template|NULL
+ */
+ protected function _LoadIfCached($name)
+ {
+ $cache_path = $this->_CachePath($name);
+ $tpl_path = $this->_TemplatePath($name);
+
+ // Make sure the cached file exists and hasn't gotten out-of-date.
+ if (!file_exists($cache_path) || filemtime($cache_path) < filemtime($tpl_path)) {
+ return NULL;
+
+ // Load the contents of the cache.
+ $data = @file_get_contents($cache_path);
+ if ($data === FALSE)
+ return NULL;
+
+ return Template::NewWithData($data);
+ }
+
+ /*!
+ Loads a raw template from the file system, stores the compiled template in
+ the file system, and returns a new template object with that data.
+
+ @param string Template name.
+
+ @return Template
+ */
+ protected function _Cache($name)
+ {
+ $cache_path = $this->_CachePath($name);
+ $tpl_path = $this->_TemplatePath($name);
+
+ $data = @file_get_contents($tpl_path);
+ if ($data === FALSE)
+ throw new TemplateLoaderException('Could not load template ' . $this->template_name);
+
+ $template = Template::NewWithCompiledData($data);
+
+ // Cache the file.
+ if (!file_put_contents($cache_path, $this->cache_prefix . $template->template()))
+ throw new TemplateLoaderException('Could not cache ' . $this->template_name . ' to ' . $cache_path);
+ }
+
+ /*! Returns the template path for a given template name. */
+ protected function _TemplatePath($name)
+ {
+ return sprintf($this->template_path, $name);
+ }
+
+ /*! Returns the cache path for a given template name. */
+ protected function _CachePath($name)
+ {
+ return self::$cache_path . '/' . $name . '.phpi';
+ }
+}
+
+class TemplateLoaderException extends \Exception {}