Browse Source

Initial commit for lithium

Skamander 12 years ago
parent
commit
08cd3881b4
100 changed files with 9826 additions and 0 deletions
  1. 9 0
      php-lithium/.gitignore
  2. 3 0
      php-lithium/.gitmodules
  3. 5 0
      php-lithium/.htaccess
  4. 36 0
      php-lithium/README.md
  5. 0 0
      php-lithium/__init__.py
  6. 5 0
      php-lithium/app/.htaccess
  7. 85 0
      php-lithium/app/config/bootstrap.php
  8. 54 0
      php-lithium/app/config/bootstrap/action.php
  9. 59 0
      php-lithium/app/config/bootstrap/cache.php
  10. 72 0
      php-lithium/app/config/bootstrap/connections.php
  11. 34 0
      php-lithium/app/config/bootstrap/console.php
  12. 29 0
      php-lithium/app/config/bootstrap/errors.php
  13. 200 0
      php-lithium/app/config/bootstrap/g11n.php
  14. 125 0
      php-lithium/app/config/bootstrap/libraries.php
  15. 59 0
      php-lithium/app/config/bootstrap/media.php
  16. 51 0
      php-lithium/app/config/bootstrap/session.php
  17. 97 0
      php-lithium/app/config/routes.php
  18. 34 0
      php-lithium/app/controllers/BenchController.php
  19. 26 0
      php-lithium/app/controllers/HelloWorldController.php
  20. 42 0
      php-lithium/app/controllers/PagesController.php
  21. 0 0
      php-lithium/app/extensions/adapter/empty
  22. 0 0
      php-lithium/app/extensions/command/empty
  23. 0 0
      php-lithium/app/extensions/data/source/empty
  24. 0 0
      php-lithium/app/extensions/helper/empty
  25. 11 0
      php-lithium/app/index.php
  26. 0 0
      php-lithium/app/libraries/_source/empty
  27. 12 0
      php-lithium/app/models/World.php
  28. 0 0
      php-lithium/app/models/empty
  29. 0 0
      php-lithium/app/resources/g11n/empty
  30. 0 0
      php-lithium/app/resources/tmp/cache/templates/empty
  31. 31 0
      php-lithium/app/resources/tmp/cache/templates/template_views_layouts_default.html_0_1365705000_831.php
  32. 257 0
      php-lithium/app/resources/tmp/cache/templates/template_views_pages_home.html_0_1365705000_8876.php
  33. 0 0
      php-lithium/app/resources/tmp/logs/empty
  34. 0 0
      php-lithium/app/resources/tmp/tests/empty
  35. 0 0
      php-lithium/app/tests/cases/controllers/empty
  36. 0 0
      php-lithium/app/tests/cases/extensions/adapter/empty
  37. 0 0
      php-lithium/app/tests/cases/extensions/command/empty
  38. 1 0
      php-lithium/app/tests/cases/extensions/data/source/empty
  39. 0 0
      php-lithium/app/tests/cases/extensions/helper/empty
  40. 0 0
      php-lithium/app/tests/cases/models/empty
  41. 0 0
      php-lithium/app/tests/functional/empty
  42. 0 0
      php-lithium/app/tests/integration/empty
  43. 0 0
      php-lithium/app/tests/mocks/empty
  44. 108 0
      php-lithium/app/views/_errors/development.html.php
  45. 0 0
      php-lithium/app/views/elements/empty
  46. 1 0
      php-lithium/app/views/hello_world/index.html.php
  47. 31 0
      php-lithium/app/views/layouts/default.html.php
  48. 10 0
      php-lithium/app/views/layouts/default.xml.php
  49. 46 0
      php-lithium/app/views/layouts/error.html.php
  50. 257 0
      php-lithium/app/views/pages/home.html.php
  51. 35 0
      php-lithium/app/web.config
  52. 7 0
      php-lithium/app/webroot/.htaccess
  53. 626 0
      php-lithium/app/webroot/css/debug.css
  54. 294 0
      php-lithium/app/webroot/css/lithium.css
  55. BIN
      php-lithium/app/webroot/favicon.ico
  56. 0 0
      php-lithium/app/webroot/img/empty
  57. 43 0
      php-lithium/app/webroot/index.php
  58. 1 0
      php-lithium/app/webroot/js/empty
  59. 25 0
      php-lithium/app/webroot/web.config
  60. 13 0
      php-lithium/benchmark_config
  61. 125 0
      php-lithium/deploy/nginx.conf
  62. 9 0
      php-lithium/deploy/php-lithium
  63. 11 0
      php-lithium/index.php
  64. 5 0
      php-lithium/libraries/lithium/.gitignore
  65. 20 0
      php-lithium/libraries/lithium/.travis.yml
  66. 12 0
      php-lithium/libraries/lithium/CONTRIBUTING.md
  67. 25 0
      php-lithium/libraries/lithium/LICENSE.txt
  68. 313 0
      php-lithium/libraries/lithium/action/Controller.php
  69. 21 0
      php-lithium/libraries/lithium/action/DispatchException.php
  70. 269 0
      php-lithium/libraries/lithium/action/Dispatcher.php
  71. 640 0
      php-lithium/libraries/lithium/action/Request.php
  72. 175 0
      php-lithium/libraries/lithium/action/Response.php
  73. 1 0
      php-lithium/libraries/lithium/action/readme.md
  74. 205 0
      php-lithium/libraries/lithium/analysis/Debugger.php
  75. 122 0
      php-lithium/libraries/lithium/analysis/Docblock.php
  76. 590 0
      php-lithium/libraries/lithium/analysis/Inspector.php
  77. 196 0
      php-lithium/libraries/lithium/analysis/Logger.php
  78. 315 0
      php-lithium/libraries/lithium/analysis/Parser.php
  79. 81 0
      php-lithium/libraries/lithium/analysis/logger/adapter/Cache.php
  80. 83 0
      php-lithium/libraries/lithium/analysis/logger/adapter/File.php
  81. 194 0
      php-lithium/libraries/lithium/analysis/logger/adapter/FirePhp.php
  82. 247 0
      php-lithium/libraries/lithium/analysis/logger/adapter/Growl.php
  83. 86 0
      php-lithium/libraries/lithium/analysis/logger/adapter/Syslog.php
  84. 22 0
      php-lithium/libraries/lithium/composer.json
  85. 423 0
      php-lithium/libraries/lithium/console/Command.php
  86. 202 0
      php-lithium/libraries/lithium/console/Dispatcher.php
  87. 203 0
      php-lithium/libraries/lithium/console/Request.php
  88. 142 0
      php-lithium/libraries/lithium/console/Response.php
  89. 55 0
      php-lithium/libraries/lithium/console/Router.php
  90. 260 0
      php-lithium/libraries/lithium/console/command/Create.php
  91. 36 0
      php-lithium/libraries/lithium/console/command/G11n.php
  92. 322 0
      php-lithium/libraries/lithium/console/command/Help.php
  93. 729 0
      php-lithium/libraries/lithium/console/command/Library.php
  94. 140 0
      php-lithium/libraries/lithium/console/command/Route.php
  95. 325 0
      php-lithium/libraries/lithium/console/command/Test.php
  96. 84 0
      php-lithium/libraries/lithium/console/command/create/Controller.php
  97. 74 0
      php-lithium/libraries/lithium/console/command/create/Mock.php
  98. 33 0
      php-lithium/libraries/lithium/console/command/create/Model.php
  99. 105 0
      php-lithium/libraries/lithium/console/command/create/Test.php
  100. 92 0
      php-lithium/libraries/lithium/console/command/create/View.php

+ 9 - 0
php-lithium/.gitignore

@@ -0,0 +1,9 @@
+/app/cache
+/app/logs
+/bin
+/vendors
+/build
+/dist
+.DS_Store
+/tags
+.idea

+ 3 - 0
php-lithium/.gitmodules

@@ -0,0 +1,3 @@
+[submodule "libraries/lithium"]
+	path = libraries/lithium
+	url = git://github.com/UnionOfRAD/lithium.git

+ 5 - 0
php-lithium/.htaccess

@@ -0,0 +1,5 @@
+<IfModule mod_rewrite.c>
+    RewriteEngine on
+    RewriteRule    ^$ app/webroot/    [L]
+    RewriteRule    (.*) app/webroot/$1 [L]
+</IfModule>

+ 36 - 0
php-lithium/README.md

@@ -0,0 +1,36 @@
+# Lithium PHP Benchmarking Test
+
+This is the Lithium PHP portion of a [benchmarking test suite](../) comparing a variety of web development platforms.
+
+### JSON Encoding Test
+Uses the PHP standard [JSON encoder](http://www.php.net/manual/en/function.json-encode.php).
+
+* [JSON test controller](app/controllers/BenchController.php)
+
+
+### Data-Store/Database Mapping Test
+Uses the db model class from Lithium
+
+* [DB test controller](app/controllers/BenchController.php)
+
+
+## Infrastructure Software Versions
+The tests were run with:
+
+* [Lithium Version 0.11](http://lithify.me)
+* [PHP Version 5.4.13](http://www.php.net/) with FPM and APC
+* [nginx 1.2.7](http://nginx.org/)
+* [MySQL 5.5.29](https://dev.mysql.com/)
+
+## Test URLs
+### JSON Encoding Test
+
+http://localhost/json
+
+### Data-Store/Database Mapping Test
+
+http://localhost/db
+
+### Variable Query Test
+    
+http://localhost/db?queries=2

+ 0 - 0
php-lithium/__init__.py


+ 5 - 0
php-lithium/app/.htaccess

@@ -0,0 +1,5 @@
+<IfModule mod_rewrite.c>
+    RewriteEngine on
+    RewriteRule ^$   webroot/   [L]
+    RewriteRule (.*) webroot/$1 [L]
+</IfModule>

+ 85 - 0
php-lithium/app/config/bootstrap.php

@@ -0,0 +1,85 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright     Copyright 2013, Union of RAD (http://union-of-rad.org)
+ * @license       http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+
+/**
+ * This is the primary bootstrap file of your application, and is loaded immediately after the front
+ * controller (`webroot/index.php`) is invoked. It includes references to other feature-specific
+ * bootstrap files that you can turn on and off to configure the services needed for your
+ * application.
+ *
+ * Besides global configuration of external application resources, these files also include
+ * configuration for various classes to interact with one another, usually through _filters_.
+ * Filters are Lithium's system of creating interactions between classes without tight coupling. See
+ * the `Filters` class for more information.
+ *
+ * If you have other services that must be configured globally for the entire application, create a
+ * new bootstrap file and `require` it here.
+ *
+ * @see lithium\util\collection\Filters
+ */
+
+/**
+ * The libraries file contains the loading instructions for all plugins, frameworks and other class
+ * libraries used in the application, including the Lithium core, and the application itself. These
+ * instructions include library names, paths to files, and any applicable class-loading rules. This
+ * file also statically loads common classes to improve bootstrap performance.
+ */
+require __DIR__ . '/bootstrap/libraries.php';
+
+/**
+ * The error configuration allows you to use the filter system along with the advanced matching
+ * rules of the `ErrorHandler` class to provide a high level of control over managing exceptions in
+ * your application, with no impact on framework or application code.
+ */
+// require __DIR__ . '/bootstrap/errors.php';
+
+/**
+ * This file defines bindings between classes which are triggered during the request cycle, and
+ * allow the framework to automatically configure its environmental settings. You can add your own
+ * behavior and modify the dispatch cycle to suit your needs.
+ */
+require __DIR__ . '/bootstrap/action.php';
+
+/**
+ * This file contains configurations for connecting to external caching resources, as well as
+ * default caching rules for various systems within your application
+ */
+require __DIR__ . '/bootstrap/cache.php';
+
+/**
+ * Include this file if your application uses one or more database connections.
+ */
+require __DIR__ . '/bootstrap/connections.php';
+
+/**
+ * This file contains configuration for session (and/or cookie) storage, and user or web service
+ * authentication.
+ */
+// require __DIR__ . '/bootstrap/session.php';
+
+/**
+ * This file contains your application's globalization rules, including inflections,
+ * transliterations, localized validation, and how localized text should be loaded. Uncomment this
+ * line if you plan to globalize your site.
+ */
+// require __DIR__ . '/bootstrap/g11n.php';
+
+/**
+ * This file contains configurations for handling different content types within the framework,
+ * including converting data to and from different formats, and handling static media assets.
+ */
+// require __DIR__ . '/bootstrap/media.php';
+
+/**
+ * This file configures console filters and settings, specifically output behavior and coloring.
+ */
+if (PHP_SAPI === 'cli') {
+	require __DIR__ . '/bootstrap/console.php';
+}
+
+?>

+ 54 - 0
php-lithium/app/config/bootstrap/action.php

@@ -0,0 +1,54 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright     Copyright 2013, Union of RAD (http://union-of-rad.org)
+ * @license       http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+
+/**
+ * This file contains a series of method filters that allow you to intercept different parts of
+ * Lithium's dispatch cycle. The filters below are used for on-demand loading of routing
+ * configuration, and automatically configuring the correct environment in which the application
+ * runs.
+ *
+ * For more information on in the filters system, see `lithium\util\collection\Filters`.
+ *
+ * @see lithium\util\collection\Filters
+ */
+
+use lithium\core\Libraries;
+use lithium\core\Environment;
+use lithium\action\Dispatcher;
+
+/**
+ * This filter intercepts the `run()` method of the `Dispatcher`, and first passes the `'request'`
+ * parameter (an instance of the `Request` object) to the `Environment` class to detect which
+ * environment the application is running in. Then, loads all application routes in all plugins,
+ * loading the default application routes last.
+ *
+ * Change this code if plugin routes must be loaded in a specific order (i.e. not the same order as
+ * the plugins are added in your bootstrap configuration), or if application routes must be loaded
+ * first (in which case the default catch-all routes should be removed).
+ *
+ * If `Dispatcher::run()` is called multiple times in the course of a single request, change the
+ * `include`s to `include_once`.
+ *
+ * @see lithium\action\Request
+ * @see lithium\core\Environment
+ * @see lithium\net\http\Router
+ */
+Dispatcher::applyFilter('run', function($self, $params, $chain) {
+	Environment::set($params['request']);
+
+	foreach (array_reverse(Libraries::get()) as $name => $config) {
+		if ($name === 'lithium') {
+			continue;
+		}
+		$file = "{$config['path']}/config/routes.php";
+		file_exists($file) ? call_user_func(function() use ($file) { include $file; }) : null;
+	}
+	return $chain->next($self, $params, $chain);
+});
+
+?>

+ 59 - 0
php-lithium/app/config/bootstrap/cache.php

@@ -0,0 +1,59 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright     Copyright 2013, Union of RAD (http://union-of-rad.org)
+ * @license       http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+
+/**
+ * This file creates a default cache configuration using the most optimized adapter available, and
+ * uses it to provide default caching for high-overhead operations.
+ */
+use lithium\storage\Cache;
+use lithium\core\Libraries;
+use lithium\core\Environment;
+use lithium\action\Dispatcher;
+use lithium\storage\cache\adapter\Apc;
+
+/**
+ * If APC is not available and the cache directory is not writeable, bail out. This block should be
+ * removed post-install, and the cache should be configured with the adapter you plan to use.
+ */
+$cachePath = Libraries::get(true, 'resources') . '/tmp/cache';
+
+if (!(($apcEnabled = Apc::enabled()) || PHP_SAPI === 'cli') && !is_writable($cachePath)) {
+	return;
+}
+
+/**
+ * This configures the default cache, based on whether ot not APC user caching is enabled. If it is
+ * not, file caching will be used. Most of this code is for getting you up and running only, and
+ * should be replaced with a hard-coded configuration, based on the cache(s) you plan to use.
+ */
+Cache::config(array('default' => $apcEnabled ? array('adapter' => 'Apc') : array(
+	'adapter' => 'File', 'strategies' => array('Serializer')
+)));
+
+/**
+ * Caches paths for auto-loaded and service-located classes when in production.
+ */
+Dispatcher::applyFilter('run', function($self, $params, $chain) {
+	if (!Environment::is('production')) {
+		return $chain->next($self, $params, $chain);
+	}
+	$key = md5(LITHIUM_APP_PATH) . '.core.libraries';
+
+	if ($cache = Cache::read('default', $key)) {
+		$cache = (array) $cache + Libraries::cache();
+		Libraries::cache($cache);
+	}
+	$result = $chain->next($self, $params, $chain);
+
+	if ($cache != Libraries::cache()) {
+		Cache::write('default', $key, Libraries::cache(), '+1 day');
+	}
+	return $result;
+});
+
+?>

+ 72 - 0
php-lithium/app/config/bootstrap/connections.php

@@ -0,0 +1,72 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright     Copyright 2013, Union of RAD (http://union-of-rad.org)
+ * @license       http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+
+/**
+ * ### Configuring backend database connections
+ *
+ * Lithium supports a wide variety relational and non-relational databases, and is designed to allow
+ * and encourage you to take advantage of multiple database technologies, choosing the most optimal
+ * one for each task.
+ *
+ * As with other `Adaptable`-based configurations, each database configuration is defined by a name,
+ * and an array of information detailing what database adapter to use, and how to connect to the
+ * database server. Unlike when configuring other classes, `Connections` uses two keys to determine
+ * which class to select. First is the `'type'` key, which specifies the type of backend to
+ * connect to. For relational databases, the type is set to `'database'`. For HTTP-based backends,
+ * like CouchDB, the type is `'http'`. Some backends have no type grouping, like MongoDB, which is
+ * unique and connects via a custom PECL extension. In this case, the type is set to `'MongoDb'`,
+ * and no `'adapter'` key is specified. In other cases, the `'adapter'` key identifies the unique
+ * adapter of the given type, i.e. `'MySql'` for the `'database'` type, or `'CouchDb'` for the
+ * `'http'` type. Note that while adapters are always specified in CamelCase form, types are
+ * specified either in CamelCase form, or in underscored form, depending on whether an `'adapter'`
+ * key is specified. See the examples below for more details.
+ *
+ * ### Multiple environments
+ *
+ * As with other `Adaptable` classes, `Connections` supports optionally specifying different
+ * configurations per named connection, depending on the current environment. For information on
+ * specifying environment-based configurations, see the `Environment` class.
+ *
+ * @see lithium\core\Adaptable
+ * @see lithium\core\Environment
+ */
+use lithium\data\Connections;
+
+/**
+ * Uncomment this configuration to use MongoDB as your default database.
+ */
+// Connections::add('default', array(
+// 	'type' => 'MongoDb',
+// 	'host' => 'localhost',
+// 	'database' => 'my_app'
+// ));
+
+/**
+ * Uncomment this configuration to use CouchDB as your default database.
+ */
+// Connections::add('default', array(
+// 	'type' => 'http',
+// 	'adapter' => 'CouchDb',
+// 	'host' => 'localhost',
+// 	'database' => 'my_app'
+// ));
+
+/**
+ * Uncomment this configuration to use MySQL as your default database.
+ */
+ Connections::add('default', array(
+ 	'type' => 'database',
+ 	'adapter' => 'MySql',
+ 	'host' => '192.168.100.102',
+ 	'login' => 'benchmarkdbuser',
+ 	'password' => 'benchmarkdbpass',
+ 	'database' => 'hello_world',
+ 	'encoding' => 'UTF-8'
+ ));
+
+?>

+ 34 - 0
php-lithium/app/config/bootstrap/console.php

@@ -0,0 +1,34 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright     Copyright 2013, Union of RAD (http://union-of-rad.org)
+ * @license       http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+
+use lithium\console\Dispatcher;
+use lithium\core\Environment;
+
+/**
+ * This filter sets the environment based on the current request. By default, `$request->env`, for
+ * example in the command `li3 help --env=production`, is used to determine the environment.
+ *
+ */
+Dispatcher::applyFilter('run', function($self, $params, $chain) {
+	Environment::set($params['request']);
+	return $chain->next($self, $params, $chain);
+});
+
+/**
+ * This filter will convert {:heading} to the specified color codes. This is useful for colorizing
+ * output and creating different sections.
+ *
+ */
+// Dispatcher::applyFilter('_call', function($self, $params, $chain) {
+// 	$params['callable']->response->styles(array(
+// 		'heading' => '\033[1;30;46m'
+// 	));
+// 	return $chain->next($self, $params, $chain);
+// });
+
+?>

+ 29 - 0
php-lithium/app/config/bootstrap/errors.php

@@ -0,0 +1,29 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright     Copyright 2013, Union of RAD (http://union-of-rad.org)
+ * @license       http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+
+use lithium\core\ErrorHandler;
+use lithium\action\Response;
+use lithium\net\http\Media;
+
+ErrorHandler::apply('lithium\action\Dispatcher::run', array(), function($info, $params) {
+	$response = new Response(array(
+		'request' => $params['request'],
+		'status' => $info['exception']->getCode()
+	));
+
+	Media::render($response, compact('info', 'params'), array(
+		'library' => true,
+		'controller' => '_errors',
+		'template' => 'development',
+		'layout' => 'error',
+		'request' => $params['request']
+	));
+	return $response;
+});
+
+?>

+ 200 - 0
php-lithium/app/config/bootstrap/g11n.php

@@ -0,0 +1,200 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright     Copyright 2013, Union of RAD (http://union-of-rad.org)
+ * @license       http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+
+/**
+ * This bootstrap file contains configurations for all globalizing
+ * aspects of your application.
+ */
+use lithium\core\Libraries;
+use lithium\core\Environment;
+use lithium\g11n\Locale;
+use lithium\g11n\Catalog;
+use lithium\g11n\Message;
+use lithium\g11n\Multibyte;
+use lithium\util\Inflector;
+use lithium\util\Validator;
+use lithium\net\http\Media;
+use lithium\action\Dispatcher as ActionDispatcher;
+use lithium\console\Dispatcher as ConsoleDispatcher;
+
+/**
+ * Dates
+ *
+ * Sets the default timezone used by all date/time functions.
+ */
+date_default_timezone_set('UTC');
+
+/**
+ * Locales
+ *
+ * Adds globalization specific settings to the environment. The settings for
+ * the current locale, time zone and currency are kept as environment settings.
+ * This allows for _centrally_ switching, _transparently_ setting and
+ * retrieving globalization related settings.
+ *
+ * The environment settings are:
+ *
+ *  - `'locale'` The default effective locale.
+ *  - `'locales'` Application locales available mapped to names. The available locales are used
+ *               to negotiate he effective locale, the names can be used i.e. when displaying
+ *               a menu for choosing the locale to users.
+ *
+ * @see lithiumm\g11n\Message
+ * @see lithiumm\core\Environment
+ */
+$locale = 'en';
+$locales = array('en' => 'English');
+
+Environment::set('production', compact('locale', 'locales'));
+Environment::set('development', compact('locale', 'locales'));
+Environment::set('test', array('locale' => 'en', 'locales' => array('en' => 'English')));
+
+/**
+ * Effective/Request Locale
+ *
+ * Intercepts dispatching processes in order to set the effective locale by using
+ * the locale of the request or if that is not available retrieving a locale preferred
+ * by the client.
+ *
+ * @see lithiumm\g11n\Message
+ * @see lithiumm\core\Environment
+ */
+$setLocale = function($self, $params, $chain) {
+	if (!$params['request']->locale()) {
+		$params['request']->locale(Locale::preferred($params['request']));
+	}
+	Environment::set(true, array('locale' => $params['request']->locale()));
+
+	return $chain->next($self, $params, $chain);
+};
+ActionDispatcher::applyFilter('_callable', $setLocale);
+ConsoleDispatcher::applyFilter('_callable', $setLocale);
+
+/**
+ * Resources
+ *
+ * Globalization (g11n) catalog configuration.  The catalog allows for obtaining and
+ * writing globalized data. Each configuration can be adjusted through the following settings:
+ *
+ *   - `'adapter'` _string_: The name of a supported adapter. The builtin adapters are `Memory` (a
+ *     simple adapter good for runtime data and testing), `Php`, `Gettext`, `Cldr` (for
+ *     interfacing with Unicode's common locale data repository) and `Code` (used mainly for
+ *     extracting message templates from source code).
+ *
+ *   - `'path'` All adapters with the exception of the `Memory` adapter require a directory
+ *     which holds the data.
+ *
+ *   - `'scope'` If you plan on using scoping i.e. for accessing plugin data separately you
+ *     need to specify a scope for each configuration, except for those using the `Memory`,
+ *     `Php` or `Gettext` adapter which handle this internally.
+ *
+ * @see lithiumm\g11n\Catalog
+ * @link https://github.com/UnionOfRAD/li3_lldr
+ * @link https://github.com/UnionOfRAD/li3_cldr
+ */
+Catalog::config(array(
+	'runtime' => array(
+		'adapter' => 'Memory'
+	),
+	// 'app' => array(
+	// 	'adapter' => 'Gettext',
+	// 	'path' => Libraries::get(true, 'resources') . '/g11n'
+	// ),
+	'lithium' => array(
+		'adapter' => 'Php',
+		'path' => LITHIUM_LIBRARY_PATH . '/lithium/g11n/resources/php'
+	)
+) + Catalog::config());
+
+/**
+ * Multibyte Strings
+ *
+ * Configuration for the `Multibyte` class which allows to work with UTF-8
+ * encoded strings. At least one configuration named `'default'` must be
+ * present. Available adapters are `Intl`, `Mbstring` and `Iconv`. Please keep
+ * in mind that each adapter may act differently upon input containing bad
+ * UTF-8 sequences. These differences aren't currently equalized or abstracted
+ * away.
+ *
+ * @see lithiumm\g11n\Multibyte
+ */
+Multibyte::config(array(
+//	'default' => array('adapter' => 'Intl'),
+	'default' => array('adapter' => 'Mbstring'),
+//	'default' => array('adapter' => 'Iconv')
+));
+
+/**
+ * Transliteration
+ *
+ * Load locale specific transliteration rules through the `Catalog` class or
+ * specify them manually to make `Inflector::slug()` work better with
+ * characters specific to a locale.
+ *
+ * @see lithiumm\g11n\Catalog
+ * @see lithium\util\Inflector::slug()
+ */
+// Inflector::rules('transliteration', Catalog::read(true, 'inflection.transliteration', 'en'));
+// Inflector::rules('transliteration', array('/É|Ê/' => 'E'));
+
+/**
+ * Grammar
+ *
+ * If your application has custom singular or plural rules you can configure
+ * that by uncommenting the lines below.
+ *
+ * @see lithiumm\g11n\Catalog
+ * @see lithium\util\Inflector
+ */
+// Inflector::rules('singular', array('rules' => array('/rata/' => '\1ratus')));
+// Inflector::rules('singular', array('irregular' => array('foo' => 'bar')));
+// Inflector::rules('plural', array('rules' => array('/rata/' => '\1ratum')));
+// Inflector::rules('plural', array('irregular' => array('bar' => 'foo')));
+// Inflector::rules('uninflected', 'bord');
+// Inflector::rules('uninflected', array('bord', 'baird'));
+
+/**
+ * Validation
+ *
+ * Adds locale specific rules through the `Catalog` class. You can load more
+ * locale dependent rules into the by specifying them manually or retrieving
+ * them with the `Catalog` class.
+ *
+ * Enables support for multibyte strings through the `Multibyte` class by
+ * overwriting rules (currently just `lengthBetween`).
+ *
+ * @see lithiumm\g11n\Catalog
+ * @see lithiumm\g11n\Multibyte
+ * @see lithium\util\Validator
+ */
+foreach (array('phone', 'postalCode', 'ssn') as $name) {
+	Validator::add($name, Catalog::read(true, "validation.{$name}", 'en_US'));
+}
+Validator::add('lengthBetween', function($value, $format, $options) {
+	$length = Multibyte::strlen($value);
+	$options += array('min' => 1, 'max' => 255);
+	return ($length >= $options['min'] && $length <= $options['max']);
+});
+
+/**
+ * In-View Translation
+ *
+ * Integration with `View`. Embeds message translation aliases into the `View`
+ * class (or other content handler, if specified) when content is rendered. This
+ * enables translation functions, i.e. `<?=$t("Translated content"); ?>`.
+ *
+ * @see lithiumm\g11n\Message::aliases()
+ * @see lithiumm\net\http\Media
+ */
+Media::applyFilter('_handle', function($self, $params, $chain) {
+	$params['handler'] += array('outputFilters' => array());
+	$params['handler']['outputFilters'] += Message::aliases();
+	return $chain->next($self, $params, $chain);
+});
+
+?>

+ 125 - 0
php-lithium/app/config/bootstrap/libraries.php

@@ -0,0 +1,125 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright     Copyright 2013, Union of RAD (http://union-of-rad.org)
+ * @license       http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+
+/**
+ * The libraries file is where you configure the various plugins, frameworks, and other libraries
+ * to be used by your application, including your application itself. This file also defines some
+ * global constants used to tell Lithium where to find your application and support libraries
+ * (including Lithium itself). It uses the `Libraries` class to add configurations for the groups of
+ * classes used in your app.
+ *
+ * In Lithium, a _library_ is any collection of classes located in a single base directory, which
+ * all share the same class-to-file naming convention, and usually a common class or namespace
+ * prefix. While all collections of classes are considered libraries, there are two special types of
+ * libraries:
+ *
+ * - **Applications**: Applications are libraries which follow the organizational conventions that
+ *   Lithium defines for applications (see `Libraries::locate()` and `Libraries::paths()`), and
+ *   which also include a web-accessible document root (i.e. the `webroot/` folder), and can
+ *   dispatch HTTP requests (i.e. through `webroot/index.php`).
+ *
+ * - **Plugins**: Plugins are libraries which generally follow the same organizational conventions
+ *   as applications, but are designed to be used within the context of another application. They
+ *   _may_ include a public document root for supporting assets, but this requires a symlink from
+ *   `libraries/<plugin-name>/webroot` to `<app-name>/webroot/<plugin-name>` (recommended for
+ *   production), or a media filter to load plugin resources (see `/config/bootstrap/media.php`).
+ *
+ * Note that a library can be designed as both an application and a plugin, but this requires some
+ * special considerations in the bootstrap process, such as removing any `require` statements, and
+ * conditionally defining the constants below.
+ *
+ * By default, libraries are stored in the base `/libraries` directory, or in the
+ * application-specific `<app-name>/libraries` directory. Libraries can be loaded from either place
+ * without additional configuration, but note that if the same library is in both directories, the
+ * application-specific `libraries` directory will override the global one.
+ *
+ * The one exception to this is the _primary_ library, which is an application configured with
+ * `'default' => true` (see below); this library uses the `LITHIUM_APP_PATH` constant (also defined
+ * below) as its path. Note, however, that any library can be overridden with an arbitrary path by
+ * passing the `'path'` key to its configuration. See `Libraries::add()` for more options.
+ *
+ * @see lithium\core\Libraries
+ */
+
+/**
+ * This is the path to your application's directory.  It contains all the sub-folders for your
+ * application's classes and files.  You don't need to change this unless your webroot folder is
+ * stored outside of your app folder.
+ */
+define('LITHIUM_APP_PATH', dirname(dirname(__DIR__)));
+
+/**
+ * This is the path to the class libraries used by your application, and must contain a copy of the
+ * Lithium core.  By default, this directory is named `libraries`, and resides in the same
+ * directory as your application.  If you use the same libraries in multiple applications, you can
+ * set this to a shared path on your server.
+ */
+define('LITHIUM_LIBRARY_PATH', dirname(LITHIUM_APP_PATH) . '/libraries');
+
+/**
+ * Locate and load Lithium core library files.  Throws a fatal error if the core can't be found.
+ * If your Lithium core directory is named something other than `lithium`, change the string below.
+ */
+if (!include LITHIUM_LIBRARY_PATH . '/lithium/core/Libraries.php') {
+	$message  = "Lithium core could not be found.  Check the value of LITHIUM_LIBRARY_PATH in ";
+	$message .= __FILE__ . ".  It should point to the directory containing your ";
+	$message .= "/libraries directory.";
+	throw new ErrorException($message);
+}
+
+use lithium\core\Libraries;
+
+/**
+ * Optimize default request cycle by loading common classes.  If you're implementing custom
+ * request/response or dispatch classes, you can safely remove these.  Actually, you can safely
+ * remove them anyway, they're just there to give slightly you better out-of-the-box performance.
+ */
+require LITHIUM_LIBRARY_PATH . '/lithium/core/Object.php';
+require LITHIUM_LIBRARY_PATH . '/lithium/core/StaticObject.php';
+require LITHIUM_LIBRARY_PATH . '/lithium/util/Collection.php';
+require LITHIUM_LIBRARY_PATH . '/lithium/util/collection/Filters.php';
+require LITHIUM_LIBRARY_PATH . '/lithium/util/Inflector.php';
+require LITHIUM_LIBRARY_PATH . '/lithium/util/String.php';
+require LITHIUM_LIBRARY_PATH . '/lithium/core/Adaptable.php';
+require LITHIUM_LIBRARY_PATH . '/lithium/core/Environment.php';
+require LITHIUM_LIBRARY_PATH . '/lithium/net/Message.php';
+require LITHIUM_LIBRARY_PATH . '/lithium/net/http/Message.php';
+require LITHIUM_LIBRARY_PATH . '/lithium/net/http/Media.php';
+require LITHIUM_LIBRARY_PATH . '/lithium/net/http/Request.php';
+require LITHIUM_LIBRARY_PATH . '/lithium/net/http/Response.php';
+require LITHIUM_LIBRARY_PATH . '/lithium/net/http/Route.php';
+require LITHIUM_LIBRARY_PATH . '/lithium/net/http/Router.php';
+require LITHIUM_LIBRARY_PATH . '/lithium/action/Controller.php';
+require LITHIUM_LIBRARY_PATH . '/lithium/action/Dispatcher.php';
+require LITHIUM_LIBRARY_PATH . '/lithium/action/Request.php';
+require LITHIUM_LIBRARY_PATH . '/lithium/action/Response.php';
+require LITHIUM_LIBRARY_PATH . '/lithium/template/View.php';
+require LITHIUM_LIBRARY_PATH . '/lithium/template/view/Renderer.php';
+require LITHIUM_LIBRARY_PATH . '/lithium/template/view/Compiler.php';
+require LITHIUM_LIBRARY_PATH . '/lithium/template/view/adapter/File.php';
+require LITHIUM_LIBRARY_PATH . '/lithium/storage/Cache.php';
+require LITHIUM_LIBRARY_PATH . '/lithium/storage/cache/adapter/Apc.php';
+
+/**
+ * Add the Lithium core library.  This sets default paths and initializes the autoloader.  You
+ * generally should not need to override any settings.
+ */
+Libraries::add('lithium');
+
+/**
+ * Add the application.  You can pass a `'path'` key here if this bootstrap file is outside of
+ * your main application, but generally you should not need to change any settings.
+ */
+Libraries::add('app', array('default' => true));
+
+/**
+ * Add some plugins:
+ */
+// Libraries::add('li3_docs');
+
+?>

+ 59 - 0
php-lithium/app/config/bootstrap/media.php

@@ -0,0 +1,59 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright     Copyright 2013, Union of RAD (http://union-of-rad.org)
+ * @license       http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+
+/**
+ * The `Collection` class, which serves as the base class for some of Lithium's data objects
+ * (`RecordSet` and `Document`) provides a way to manage data collections in a very flexible and
+ * intuitive way, using closures and SPL interfaces. The `to()` method allows a `Collection` (or
+ * subclass) to be converted to another format, such as an array. The `Collection` class also allows
+ * other classes to be connected as handlers to convert `Collection` objects to other formats.
+ *
+ * The following connects the `Media` class as a format handler, which allows `Collection`s to be
+ * exported to any format with a handler provided by `Media`, i.e. JSON. This enables things like
+ * the following:
+ * {{{
+ * $posts = Post::find('all');
+ * return $posts->to('json');
+ * }}}
+ */
+use lithium\util\Collection;
+
+Collection::formats('lithium\net\http\Media');
+
+/**
+ * This filter is a convenience method which allows you to automatically route requests for static
+ * assets stored within active plugins. For example, given a JavaScript file `bar.js` inside the
+ * `li3_foo` plugin installed in an application, requests to `http://app/path/li3_foo/js/bar.js`
+ * will be routed to `/path/to/app/libraries/plugins/li3_foo/webroot/js/bar.js` on the filesystem.
+ * In production, it is recommended that you disable this filter in favor of symlinking each
+ * plugin's `webroot` directory into your main application's `webroot` directory, or adding routing
+ * rules in your web server's configuration.
+ */
+// use lithium\action\Dispatcher;
+// use lithium\action\Response;
+// use lithium\net\http\Media;
+//
+// Dispatcher::applyFilter('_callable', function($self, $params, $chain) {
+// 	list($library, $asset) = explode('/', $params['request']->url, 2) + array("", "");
+//
+// 	if ($asset && ($path = Media::webroot($library)) && file_exists($file = "{$path}/{$asset}")) {
+// 		return function() use ($file) {
+// 			$info = pathinfo($file);
+// 			$media = Media::type($info['extension']);
+// 			$content = (array) $media['content'];
+//
+// 			return new Response(array(
+// 				'headers' => array('Content-type' => reset($content)),
+// 				'body' => file_get_contents($file)
+// 			));
+// 		};
+// 	}
+// 	return $chain->next($self, $params, $chain);
+// });
+
+?>

+ 51 - 0
php-lithium/app/config/bootstrap/session.php

@@ -0,0 +1,51 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright     Copyright 2013, Union of RAD (http://union-of-rad.org)
+ * @license       http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+
+/**
+ * This configures your session storage. The Cookie storage adapter must be connected first, since
+ * it intercepts any writes where the `'expires'` key is set in the options array.
+ * The default name is based on the lithium app path. Remember, if your app is numeric or has
+ * special characters you might want to use Inflector::slug() or set this manually.
+ */
+use lithium\storage\Session;
+
+$name = basename(LITHIUM_APP_PATH);
+Session::config(array(
+	// 'cookie' => array('adapter' => 'Cookie', 'name' => $name),
+	'default' => array('adapter' => 'Php', 'session.name' => $name)
+));
+
+/**
+ * Uncomment the lines below to enable forms-based authentication. This configuration will attempt
+ * to authenticate users against a `Users` model. In a controller, run
+ * `Auth::check('default', $this->request)` to authenticate a user. This will check the POST data of
+ * the request (`lithium\action\Request::$data`) to see if the fields match the `'fields'` key of
+ * the configuration below. If successful, it will write the data returned from `Users::first()` to
+ * the session using the default session configuration.
+ *
+ * Once the session data is written, you can call `Auth::check('default')` to check authentication
+ * status or retrieve the user's data from the session. Call `Auth::clear('default')` to remove the
+ * user's authentication details from the session. This effectively logs a user out of the system.
+ * To modify the form input that the adapter accepts, or how the configured model is queried, or how
+ * the data is stored in the session, see the `Form` adapter API or the `Auth` API, respectively.
+ *
+ * @see lithium\security\auth\adapter\Form
+ * @see lithium\action\Request::$data
+ * @see lithium\security\Auth
+ */
+// use lithium\security\Auth;
+
+// Auth::config(array(
+// 	'default' => array(
+// 		'adapter' => 'Form',
+// 		'model' => 'Users',
+// 		'fields' => array('username', 'password')
+// 	)
+// ));
+
+?>

+ 97 - 0
php-lithium/app/config/routes.php

@@ -0,0 +1,97 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright     Copyright 2013, Union of RAD (http://union-of-rad.org)
+ * @license       http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+
+/**
+ * The routes file is where you define your URL structure, which is an important part of the
+ * [information architecture](http://en.wikipedia.org/wiki/Information_architecture) of your
+ * application. Here, you can use _routes_ to match up URL pattern strings to a set of parameters,
+ * usually including a controller and action to dispatch matching requests to. For more information,
+ * see the `Router` and `Route` classes.
+ *
+ * @see lithium\net\http\Router
+ * @see lithium\net\http\Route
+ */
+use lithium\net\http\Router;
+use lithium\core\Environment;
+
+/**
+ * With globalization enabled a localized route is configured by connecting a
+ * continuation route. Once the route has been connected, all the other
+ * application routes become localized and may now carry a locale.
+ *
+ * Requests to routes like `/en/posts/edit/1138` or `/fr/posts/edit/1138` will
+ * carry a locale, while `/posts/edit/1138` keeps on working as it did before.
+ */
+if ($locales = Environment::get('locales')) {
+	$template = '/{:locale:' . join('|', array_keys($locales)) . '}/{:args}';
+	Router::connect($template, array(), array('continue' => true));
+}
+
+/**
+ * Here, we are connecting `'/'` (the base path) to controller called `'Pages'`,
+ * its action called `view()`, and we pass a param to select the view file
+ * to use (in this case, `/views/pages/home.html.php`; see `app\controllers\PagesController`
+ * for details).
+ *
+ * @see app\controllers\PagesController
+ */
+Router::connect('/json', 'Bench::json');
+
+/**
+ * Connect the rest of `PagesController`'s URLs. This will route URLs like `/pages/about` to
+ * `PagesController`, rendering `/views/pages/about.html.php` as a static page.
+ */
+Router::connect('/db/{:queries}', array('Bench::db', 'queries' => 1));
+
+/**
+ * Add the testing routes. These routes are only connected in non-production environments, and allow
+ * browser-based access to the test suite for running unit and integration tests for the Lithium
+ * core, as well as your own application and any other loaded plugins or frameworks. Browse to
+ * [http://path/to/app/test](/test) to run tests.
+ */
+if (!Environment::is('production')) {
+	Router::connect('/test/{:args}', array('controller' => 'lithium\test\Controller'));
+	Router::connect('/test', array('controller' => 'lithium\test\Controller'));
+}
+
+/**
+ * ### Database object routes
+ *
+ * The routes below are used primarily for accessing database objects, where `{:id}` corresponds to
+ * the primary key of the database object, and can be accessed in the controller as
+ * `$this->request->id`.
+ *
+ * If you're using a relational database, such as MySQL, SQLite or Postgres, where the primary key
+ * is an integer, uncomment the routes below to enable URLs like `/posts/edit/1138`,
+ * `/posts/view/1138.json`, etc.
+ */
+// Router::connect('/{:controller}/{:action}/{:id:\d+}.{:type}', array('id' => null));
+// Router::connect('/{:controller}/{:action}/{:id:\d+}');
+
+/**
+ * If you're using a document-oriented database, such as CouchDB or MongoDB, or another type of
+ * database which uses 24-character hexidecimal values as primary keys, uncomment the routes below.
+ */
+// Router::connect('/{:controller}/{:action}/{:id:[0-9a-f]{24}}.{:type}', array('id' => null));
+// Router::connect('/{:controller}/{:action}/{:id:[0-9a-f]{24}}');
+
+/**
+ * Finally, connect the default route. This route acts as a catch-all, intercepting requests in the
+ * following forms:
+ *
+ * - `/foo/bar`: Routes to `FooController::bar()` with no parameters passed.
+ * - `/foo/bar/param1/param2`: Routes to `FooController::bar('param1, 'param2')`.
+ * - `/foo`: Routes to `FooController::index()`, since `'index'` is assumed to be the action if none
+ *   is otherwise specified.
+ *
+ * In almost all cases, custom routes should be added above this one, since route-matching works in
+ * a top-down fashion.
+ */
+Router::connect('/{:controller}/{:action}/{:args}');
+
+?>

+ 34 - 0
php-lithium/app/controllers/BenchController.php

@@ -0,0 +1,34 @@
+<?php
+
+namespace app\controllers;
+
+use  lithium\action\Controller;
+use  app\models\World;
+
+class BenchController extends Controller {
+
+    public function json() {
+        return $this->render(array(
+            'json' => array('message' => 'Hello World!')
+        ));
+    }
+
+    public function db() {
+        $queries = isset($this->request->query['queries'])
+            ? $this->request->query['queries']
+            : 1;
+        $worlds = array();
+
+        for ($i = 0; $i < $queries; ++$i) {
+            $worlds[] = World::first(array(
+                'conditions' => array(
+                    'id' => mt_rand(1, 10000)
+                )
+            ));
+        }
+
+        return $this->render(array(
+            'json' => $worlds
+        ));
+    }
+}

+ 26 - 0
php-lithium/app/controllers/HelloWorldController.php

@@ -0,0 +1,26 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright     Copyright 2013, Union of RAD (http://union-of-rad.org)
+ * @license       http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+
+namespace app\controllers;
+
+class HelloWorldController extends \lithium\action\Controller {
+
+	public function index() {
+		return $this->render(array('layout' => false));
+	}
+
+	public function to_string() {
+		return "Hello World";
+	}
+
+	public function to_json() {
+		return $this->render(array('json' => 'Hello World'));
+	}
+}
+
+?>

+ 42 - 0
php-lithium/app/controllers/PagesController.php

@@ -0,0 +1,42 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright     Copyright 2013, Union of RAD (http://union-of-rad.org)
+ * @license       http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+
+namespace app\controllers;
+
+/**
+ * This controller is used for serving static pages by name, which are located in the `/views/pages`
+ * folder.
+ *
+ * A Lithium application's default routing provides for automatically routing and rendering
+ * static pages using this controller. The default route (`/`) will render the `home` template, as
+ * specified in the `view()` action.
+ *
+ * Additionally, any other static templates in `/views/pages` can be called by name in the URL. For
+ * example, browsing to `/pages/about` will render `/views/pages/about.html.php`, if it exists.
+ *
+ * Templates can be nested within directories as well, which will automatically be accounted for.
+ * For example, browsing to `/pages/about/company` will render
+ * `/views/pages/about/company.html.php`.
+ */
+class PagesController extends \lithium\action\Controller {
+
+	public function view() {
+		$options = array();
+		$path = func_get_args();
+
+		if (!$path || $path === array('home')) {
+			$path = array('home');
+			$options['compiler'] = array('fallback' => true);
+		}
+
+		$options['template'] = join('/', $path);
+		return $this->render($options);
+	}
+}
+
+?>

+ 0 - 0
php-lithium/app/extensions/adapter/empty


+ 0 - 0
php-lithium/app/extensions/command/empty


+ 0 - 0
php-lithium/app/extensions/data/source/empty


+ 0 - 0
php-lithium/app/extensions/helper/empty


+ 11 - 0
php-lithium/app/index.php

@@ -0,0 +1,11 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright     Copyright 2013, Union of RAD (http://union-of-rad.org)
+ * @license       http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+
+require 'webroot/index.php';
+
+?>

+ 0 - 0
php-lithium/app/libraries/_source/empty


+ 12 - 0
php-lithium/app/models/World.php

@@ -0,0 +1,12 @@
+<?php
+
+namespace app\models;
+
+use \lithium\data\Model;
+
+class World extends Model {
+    // stop lithium from pluralizing the table name
+    protected $_meta = array(
+        'source' => 'World'
+    );
+}

+ 0 - 0
php-lithium/app/models/empty


+ 0 - 0
php-lithium/app/resources/g11n/empty


+ 0 - 0
php-lithium/app/resources/tmp/cache/templates/empty


+ 31 - 0
php-lithium/app/resources/tmp/cache/templates/template_views_layouts_default.html_0_1365705000_831.php

@@ -0,0 +1,31 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright     Copyright 2013, Union of RAD (http://union-of-rad.org)
+ * @license       http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+?>
+<!doctype html>
+<html>
+<head>
+	<?php echo $this->html->charset();?>
+	<title>Application &gt; <?php echo $this->title(); ?></title>
+	<?php echo $this->html->style(array('debug', 'lithium')); ?>
+	<?php echo $this->scripts(); ?>
+	<?php echo $this->html->link('Icon', null, array('type' => 'icon')); ?>
+</head>
+<body class="app">
+	<div id="container">
+		<div id="header">
+			<h1>Application</h1>
+			<h2>
+				Powered by <?php echo $this->html->link('Lithium', 'http://lithify.me/'); ?>.
+			</h2>
+		</div>
+		<div id="content">
+			<?php echo $this->content(); ?>
+		</div>
+	</div>
+</body>
+</html>

+ 257 - 0
php-lithium/app/resources/tmp/cache/templates/template_views_pages_home.html_0_1365705000_8876.php

@@ -0,0 +1,257 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright     Copyright 2013, Union of RAD (http://union-of-rad.org)
+ * @license       http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+
+use lithium\core\Libraries;
+use lithium\core\Environment;
+use lithium\data\Connections;
+
+$this->title('Home');
+
+$self = $this;
+
+$notify = function($status, $message, $solution = null) {
+	$html  = "<div class=\"test-result test-result-{$status}\">{$message}</div>";
+	$html .= "<div class=\"test-result solution\">{$solution}</div>";
+	return $html;
+};
+
+$support = function($classes) {
+	$result = '<ul class="indicated">';
+
+	foreach ($classes as $class => $enabled) {
+		$name = substr($class, strrpos($class, '\\') + 1);
+		$url = 'http://lithify.me/docs/' . str_replace('\\', '/', $class);
+		$class = $enabled ? 'enabled' : 'disabled';
+		$title = $enabled ? "Adapter `{$name}` is enabled." : "Adapter `{$name}` is disabled.";
+
+		$result .= "<li><a href=\"{$url}\" title=\"{$title}\" class=\"{$class}\">{$name}</a></li>";
+	}
+	$result .= '</ul>';
+
+	return $result;
+};
+
+$compiled = function($flag) {
+	ob_start();
+	phpinfo(INFO_GENERAL);
+	return strpos(ob_get_clean(), $flag) !== false;
+};
+
+$checks = array(
+	'resourcesWritable' => function() use ($notify) {
+		if (is_writable($path = Libraries::get(true, 'resources'))) {
+			return $notify('success', 'Resources directory is writable');
+		}
+		$path = str_replace(dirname(LITHIUM_APP_PATH) . '/', null, $path);
+		$solution = null;
+
+		if (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') {
+			$solution  = 'To fix this, run the following from the command line: ';
+			$solution .= "<code>$ chmod -R 0777 {$path}</code>.";
+		} else {
+			$path = realpath($path);
+			$solution  = 'To fix this, give <code>modify</code> rights to the user ';
+			$solution .= "<code>Everyone</code> on directory <code>{$path}</code>.";
+		}
+		return $notify(
+			'fail',
+			'Your resource path is not writeable',
+			$solution
+		);
+	},
+	'magicQuotes' => function() use ($notify) {
+		if (!get_magic_quotes_gpc()) {
+			return;
+		}
+		return $notify(
+			'fail',
+			'Magic quotes are enabled in your PHP configuration',
+			'Please set <code>magic_quotes_gpc = Off</code> in your <code>php.ini</code> settings.'
+		);
+	},
+	'registerGlobals' => function() use ($notify) {
+		if (!ini_get('register_globals')) {
+			return;
+		}
+		return $notify(
+			'fail',
+			'Register globals is enabled in your PHP configuration',
+			'Please set <code>register_globals = Off</code> in your <code>php.ini</code> settings.'
+		);
+	},
+	'curlwrappers' => function() use ($notify, $compiled) {
+		if (!$compiled('with-curlwrappers')) {
+			return;
+		}
+		return $notify(
+			'fail',
+			'Curlwrappers are enabled, some things might not work as expected.',
+			"This is an expiremental and usually broken feature of PHP.
+			Please recompile your PHP binary without using the <code>--with-curlwrappers</code>
+			flag or use a precompiled binary that was compiled without the flag."
+		);
+	},
+	'shortOpenTag' => function() use ($notify, $compiled) {
+		if (!ini_get('short_open_tag')) {
+			return;
+		}
+		return $notify(
+			'notice',
+			'Short open tags are enabled, you may want to disable them.',
+			"It is recommended to not rely on this option being enabled.
+			To increase the portability of your code disable this option by setting
+			<code>short_open_tag = Off</code> in your <code>php.ini</code>."
+		);
+	},
+	'dbSupport' => function() use ($notify, $support) {
+		$paths = array('data.source', 'adapter.data.source.database', 'adapter.data.source.http');
+		$list = array();
+
+		foreach ($paths as $path) {
+			$list = array_merge($list, Libraries::locate($path, null, array('recursive' => false)));
+		}
+		$list = array_filter($list, function($class) { return method_exists($class, 'enabled'); });
+		$map = array_combine($list, array_map(function($c) { return $c::enabled(); }, $list));
+
+		return $notify('notice', 'Database support', $support($map));
+	},
+	'cacheSupport' => function() use ($notify, $support) {
+		$list = Libraries::locate('adapter.storage.cache', null, array('recursive' => false));
+		$list = array_filter($list, function($class) { return method_exists($class, 'enabled'); });
+		$map = array_combine($list, array_map(function($c) { return $c::enabled(); }, $list));
+
+		return $notify('notice', 'Cache support', $support($map));
+	},
+	'database' => function() use ($notify) {
+		if ($config = Connections::config()) {
+			return $notify('success', 'Database connection(s) configured');
+		}
+		return $notify(
+			'notice',
+			'No database connection defined',
+			"To create a database connection:
+			<ol>
+				<li>Edit the file <code>config/bootstrap.php</code>.</li>
+				<li>
+					Uncomment the line having
+					<code>require __DIR__ . '/bootstrap/connections.php';</code>.
+				</li>
+				<li>Edit the file <code>config/bootstrap/connections.php</code>.</li>
+			</ol>"
+		);
+	},
+	'change' => function() use ($notify, $self) {
+		$template = $self->html->link('template', 'http://lithify.me/docs/lithium/template');
+
+		return $notify(
+			'notice',
+			"You're using the application's default home page",
+			"To change this {$template}, edit the file
+			<code>views/pages/home.html.php</code>.
+			To change the layout,
+			(that is what's wrapping content)
+			edit the file <code>views/layouts/default.html.php</code>."
+		);
+	},
+	'routing' => function() use ($notify, $self) {
+		$routing = $self->html->link('routing', 'http://lithify.me/docs/lithium/net/http/Router');
+
+		return $notify(
+			'notice',
+			'Use custom routing',
+			"Routes allow you to map custom URLs to your application code. To change the
+			{$routing}, edit the file <code>config/routes.php</code>."
+		);
+	},
+	'tests' => function() use ($notify, $self) {
+		if (Environment::is('production')) {
+			$docsLink = $self->html->link(
+				'the documentation',
+				'http://lithify.me/docs/lithium/core/Environment::is()'
+			);
+
+			return $notify(
+				'fail',
+				"Can't run tests",
+				"<p>Lithium's default environment detection rules have determined that you are
+				running in production mode. Therefore, you will not be able to run tests from the
+				web interface. You can do any of the following to remedy this:</p>
+				<ul>
+					<li>Run this application locally</li>
+					<li>Run tests from the console, using the <code>li3 test</code> command</li>
+					<li>
+						Implementing custom environment detection rules;
+						see {$docsLink} for examples
+					</li>
+				</ul>"
+			);
+		}
+		$tests = $self->html->link('run all tests', array(
+			'controller' => 'lithium\test\Controller',
+			'args' => 'all'
+		));
+		$dashboard = $self->html->link('test dashboard', array(
+			'controller' => 'lithium\test\Controller'
+		));
+		$ticket = $self->html->link(
+			'file a ticket', 'https://github.com/UnionOfRAD/lithium/issues'
+		);
+
+		return $notify(
+			'notice',
+			'Run the tests',
+			"Check the builtin {$dashboard} or {$tests} now to ensure Lithium
+			is working as expected. Do not hesitate to {$ticket} in case a test fails."
+		);
+	}
+);
+
+?>
+
+<?php foreach ($checks as $check): ?>
+	<?php echo $check(); ?>
+<?php endforeach; ?>
+
+<ul class="additional-resources">
+	<li>
+		<div class="test-result test-result-notice">Getting started</div>
+		<div class="test-result solution">
+			<?php echo $this->html->link(
+				'Quickstart', 'http://lithify.me/docs/manual/quickstart'
+			); ?> is a guide for PHP users who are looking to get a good idea of what Lithium can
+			do. The guide is part of the official Lithium manual, <?php echo $this->html->link(
+				'The Definitive Guide', 'http://lithify.me/docs/manual'
+			); ?>.
+		</div>
+	</li>
+	<li>
+		<div class="test-result test-result-notice">Learn more</div>
+		<div class="test-result solution">
+			The
+			<?php echo $this->html->link('API documentation', 'http://lithify.me/docs/lithium'); ?>
+			has all the implementation details you've been looking for.
+		</div>
+	</li>
+	<li>
+		Chat with other Lithium users and the team developing Lithium.
+		For <em>general support</em> hop on the
+		<?php echo $this->html->link('#li3 channel', 'irc://irc.freenode.net/#li3'); ?>
+		or read the
+		<?php echo $this->html->link('logs', 'http://lithify.me/bot/logs/li3'); ?>.
+		For <em>core discussions</em> join us in the
+		<?php echo $this->html->link('#li3-core channel', 'irc://irc.freenode.net/#li3-core'); ?>
+		or read the
+		<?php echo $this->html->link('logs', 'http://lithify.me/bot/logs/li3-core'); ?>.
+	</li>
+	<li>
+		Browse the Lithium
+		<?php echo $this->html->link('Repository', 'https://github.com/UnionOfRAD/lithium'); ?>
+		or read the
+		<?php echo $this->html->link('Wiki', 'https://github.com/UnionOfRAD/lithium/wiki'); ?>.
+	</li>
+</ul>

+ 0 - 0
php-lithium/app/resources/tmp/logs/empty


+ 0 - 0
php-lithium/app/resources/tmp/tests/empty


+ 0 - 0
php-lithium/app/tests/cases/controllers/empty


+ 0 - 0
php-lithium/app/tests/cases/extensions/adapter/empty


+ 0 - 0
php-lithium/app/tests/cases/extensions/command/empty


+ 1 - 0
php-lithium/app/tests/cases/extensions/data/source/empty

@@ -0,0 +1 @@
+

+ 0 - 0
php-lithium/app/tests/cases/extensions/helper/empty


+ 0 - 0
php-lithium/app/tests/cases/models/empty


+ 0 - 0
php-lithium/app/tests/functional/empty


+ 0 - 0
php-lithium/app/tests/integration/empty


+ 0 - 0
php-lithium/app/tests/mocks/empty


+ 108 - 0
php-lithium/app/views/_errors/development.html.php

@@ -0,0 +1,108 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright     Copyright 2013, Union of RAD (http://union-of-rad.org)
+ * @license       http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+
+use lithium\analysis\Debugger;
+use lithium\analysis\Inspector;
+
+$exception = $info['exception'];
+$replace = array('&lt;?php', '?&gt;', '<code>', '</code>', "\n");
+$context = 5;
+
+/**
+ * Set Lithium-esque colors for syntax highlighing.
+ */
+ini_set('highlight.string', '#4DDB4A');
+ini_set('highlight.comment', '#D42AAE');
+ini_set('highlight.keyword', '#D42AAE');
+ini_set('highlight.default', '#3C96FF');
+ini_set('highlight.htm', '#FFFFFF');
+
+$stack = Debugger::trace(array('format' => 'array', 'trace' => $exception->getTrace()));
+
+array_unshift($stack, array(
+	'functionRef' => '[exception]',
+	'file' => $exception->getFile(),
+	'line' => $exception->getLine()
+));
+
+?>
+<h3>Exception</h3>
+
+<div class="lithium-exception-class">
+	<?=get_class($exception);?>
+
+	<?php if ($code = $exception->getCode()): ?>
+		<span class="code">(code <?=$code; ?>)</span>
+	<?php endif ?>
+</div>
+
+<div class="lithium-exception-message"><?=$exception->getMessage(); ?></div>
+
+<h3 id="source">Source</h3>
+
+<div id="sourceCode"></div>
+
+<h3>Stack Trace</h3>
+
+<div class="lithium-stack-trace">
+	<ol>
+		<?php foreach ($stack as $id => $frame): ?>
+			<?php
+				$location = "{$frame['file']}: {$frame['line']}";
+				$lines = range($frame['line'] - $context, $frame['line'] + $context);
+				$code = Inspector::lines($frame['file'], $lines);
+			?>
+			<li>
+				<tt><a href="#source" id="<?=$id; ?>" class="display-source-excerpt">
+					<?=$frame['functionRef']; ?>
+				</a></tt>
+				<div id="sourceCode<?=$id; ?>" style="display: none;">
+
+					<div class="lithium-exception-location">
+						<?=$location; ?>
+					</div>
+
+					<div class="lithium-code-dump">
+						<pre><code><?php
+							foreach ($code as $num => $content):
+								$numPad = str_pad($num, 3, ' ');
+								$content = str_ireplace(array('<?php', '?>'), '', $content);
+								$content = highlight_string("<?php {$numPad}{$content} ?>", true);
+								$content = str_replace($replace, '', $content);
+
+								if ($frame['line'] === $num):
+									?><span class="code-highlight"><?php
+								endif;?><?php echo "{$content}\n"; ?><?php
+								if ($frame['line'] === $num):
+									?></span><?php
+								endif;
+
+							endforeach;
+						?></code></pre>
+					</div>
+				</div>
+			</li>
+		<?php endforeach; ?>
+	</ol>
+</div>
+
+<script type="text/javascript">
+	window.onload = function() {
+		var $ = function() { return document.getElementById.apply(document, arguments); };
+		var links = document.getElementsByTagName('a');
+
+		for (i = 0; i < links.length; i++) {
+			if (links[i].className.indexOf('display-source-excerpt') >= 0) {
+				links[i].onclick = function() {
+					$('sourceCode').innerHTML = $('sourceCode' + this.id).innerHTML;
+				}
+			}
+		}
+		$('sourceCode').innerHTML = $('sourceCode0').innerHTML;
+	}
+</script>

+ 0 - 0
php-lithium/app/views/elements/empty


+ 1 - 0
php-lithium/app/views/hello_world/index.html.php

@@ -0,0 +1 @@
+Hello World!

+ 31 - 0
php-lithium/app/views/layouts/default.html.php

@@ -0,0 +1,31 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright     Copyright 2013, Union of RAD (http://union-of-rad.org)
+ * @license       http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+?>
+<!doctype html>
+<html>
+<head>
+	<?php echo $this->html->charset();?>
+	<title>Application &gt; <?php echo $this->title(); ?></title>
+	<?php echo $this->html->style(array('debug', 'lithium')); ?>
+	<?php echo $this->scripts(); ?>
+	<?php echo $this->html->link('Icon', null, array('type' => 'icon')); ?>
+</head>
+<body class="app">
+	<div id="container">
+		<div id="header">
+			<h1>Application</h1>
+			<h2>
+				Powered by <?php echo $this->html->link('Lithium', 'http://lithify.me/'); ?>.
+			</h2>
+		</div>
+		<div id="content">
+			<?php echo $this->content(); ?>
+		</div>
+	</div>
+</body>
+</html>

+ 10 - 0
php-lithium/app/views/layouts/default.xml.php

@@ -0,0 +1,10 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright     Copyright 2013, Union of RAD (http://union-of-rad.org)
+ * @license       http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+?>
+<?php echo '<' . '?xml version="1.0" ?' . '>'; ?>
+<?=$this->content;?>

+ 46 - 0
php-lithium/app/views/layouts/error.html.php

@@ -0,0 +1,46 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright     Copyright 2013, Union of RAD (http://union-of-rad.org)
+ * @license       http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+
+/**
+ * This layout is used to render error pages in both development and production. It is recommended
+ * that you maintain a separate, simplified layout for rendering errors that does not involve any
+ * complex logic or dynamic data, which could potentially trigger recursive errors.
+ */
+?>
+<!doctype html>
+<html>
+<head>
+	<?php echo $this->html->charset(); ?>
+	<title>Unhandled exception</title>
+	<?php echo $this->html->style(array('debug', 'lithium')); ?>
+	<?php echo $this->scripts(); ?>
+	<?php echo $this->html->link('Icon', null, array('type' => 'icon')); ?>
+</head>
+<body class="app">
+	<div id="container">
+		<div id="header">
+			<h1>An unhandled exception was thrown</h1>
+			<h3>Configuration</h3>
+			<p>
+				This layout can be changed by modifying
+				<code><?php
+					echo realpath(LITHIUM_APP_PATH . '/views/layouts/error.html.php');
+				?></code>
+			</p><p>
+				To modify your error-handling configuration, see
+				<code><?php
+					echo realpath(LITHIUM_APP_PATH . '/config/bootstrap/errors.php');
+				?></code>
+			</p>
+		</div>
+		<div id="content">
+			<?php echo $this->content(); ?>
+		</div>
+	</div>
+</body>
+</html>

+ 257 - 0
php-lithium/app/views/pages/home.html.php

@@ -0,0 +1,257 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright     Copyright 2013, Union of RAD (http://union-of-rad.org)
+ * @license       http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+
+use lithium\core\Libraries;
+use lithium\core\Environment;
+use lithium\data\Connections;
+
+$this->title('Home');
+
+$self = $this;
+
+$notify = function($status, $message, $solution = null) {
+	$html  = "<div class=\"test-result test-result-{$status}\">{$message}</div>";
+	$html .= "<div class=\"test-result solution\">{$solution}</div>";
+	return $html;
+};
+
+$support = function($classes) {
+	$result = '<ul class="indicated">';
+
+	foreach ($classes as $class => $enabled) {
+		$name = substr($class, strrpos($class, '\\') + 1);
+		$url = 'http://lithify.me/docs/' . str_replace('\\', '/', $class);
+		$class = $enabled ? 'enabled' : 'disabled';
+		$title = $enabled ? "Adapter `{$name}` is enabled." : "Adapter `{$name}` is disabled.";
+
+		$result .= "<li><a href=\"{$url}\" title=\"{$title}\" class=\"{$class}\">{$name}</a></li>";
+	}
+	$result .= '</ul>';
+
+	return $result;
+};
+
+$compiled = function($flag) {
+	ob_start();
+	phpinfo(INFO_GENERAL);
+	return strpos(ob_get_clean(), $flag) !== false;
+};
+
+$checks = array(
+	'resourcesWritable' => function() use ($notify) {
+		if (is_writable($path = Libraries::get(true, 'resources'))) {
+			return $notify('success', 'Resources directory is writable');
+		}
+		$path = str_replace(dirname(LITHIUM_APP_PATH) . '/', null, $path);
+		$solution = null;
+
+		if (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') {
+			$solution  = 'To fix this, run the following from the command line: ';
+			$solution .= "<code>$ chmod -R 0777 {$path}</code>.";
+		} else {
+			$path = realpath($path);
+			$solution  = 'To fix this, give <code>modify</code> rights to the user ';
+			$solution .= "<code>Everyone</code> on directory <code>{$path}</code>.";
+		}
+		return $notify(
+			'fail',
+			'Your resource path is not writeable',
+			$solution
+		);
+	},
+	'magicQuotes' => function() use ($notify) {
+		if (!get_magic_quotes_gpc()) {
+			return;
+		}
+		return $notify(
+			'fail',
+			'Magic quotes are enabled in your PHP configuration',
+			'Please set <code>magic_quotes_gpc = Off</code> in your <code>php.ini</code> settings.'
+		);
+	},
+	'registerGlobals' => function() use ($notify) {
+		if (!ini_get('register_globals')) {
+			return;
+		}
+		return $notify(
+			'fail',
+			'Register globals is enabled in your PHP configuration',
+			'Please set <code>register_globals = Off</code> in your <code>php.ini</code> settings.'
+		);
+	},
+	'curlwrappers' => function() use ($notify, $compiled) {
+		if (!$compiled('with-curlwrappers')) {
+			return;
+		}
+		return $notify(
+			'fail',
+			'Curlwrappers are enabled, some things might not work as expected.',
+			"This is an expiremental and usually broken feature of PHP.
+			Please recompile your PHP binary without using the <code>--with-curlwrappers</code>
+			flag or use a precompiled binary that was compiled without the flag."
+		);
+	},
+	'shortOpenTag' => function() use ($notify, $compiled) {
+		if (!ini_get('short_open_tag')) {
+			return;
+		}
+		return $notify(
+			'notice',
+			'Short open tags are enabled, you may want to disable them.',
+			"It is recommended to not rely on this option being enabled.
+			To increase the portability of your code disable this option by setting
+			<code>short_open_tag = Off</code> in your <code>php.ini</code>."
+		);
+	},
+	'dbSupport' => function() use ($notify, $support) {
+		$paths = array('data.source', 'adapter.data.source.database', 'adapter.data.source.http');
+		$list = array();
+
+		foreach ($paths as $path) {
+			$list = array_merge($list, Libraries::locate($path, null, array('recursive' => false)));
+		}
+		$list = array_filter($list, function($class) { return method_exists($class, 'enabled'); });
+		$map = array_combine($list, array_map(function($c) { return $c::enabled(); }, $list));
+
+		return $notify('notice', 'Database support', $support($map));
+	},
+	'cacheSupport' => function() use ($notify, $support) {
+		$list = Libraries::locate('adapter.storage.cache', null, array('recursive' => false));
+		$list = array_filter($list, function($class) { return method_exists($class, 'enabled'); });
+		$map = array_combine($list, array_map(function($c) { return $c::enabled(); }, $list));
+
+		return $notify('notice', 'Cache support', $support($map));
+	},
+	'database' => function() use ($notify) {
+		if ($config = Connections::config()) {
+			return $notify('success', 'Database connection(s) configured');
+		}
+		return $notify(
+			'notice',
+			'No database connection defined',
+			"To create a database connection:
+			<ol>
+				<li>Edit the file <code>config/bootstrap.php</code>.</li>
+				<li>
+					Uncomment the line having
+					<code>require __DIR__ . '/bootstrap/connections.php';</code>.
+				</li>
+				<li>Edit the file <code>config/bootstrap/connections.php</code>.</li>
+			</ol>"
+		);
+	},
+	'change' => function() use ($notify, $self) {
+		$template = $self->html->link('template', 'http://lithify.me/docs/lithium/template');
+
+		return $notify(
+			'notice',
+			"You're using the application's default home page",
+			"To change this {$template}, edit the file
+			<code>views/pages/home.html.php</code>.
+			To change the layout,
+			(that is what's wrapping content)
+			edit the file <code>views/layouts/default.html.php</code>."
+		);
+	},
+	'routing' => function() use ($notify, $self) {
+		$routing = $self->html->link('routing', 'http://lithify.me/docs/lithium/net/http/Router');
+
+		return $notify(
+			'notice',
+			'Use custom routing',
+			"Routes allow you to map custom URLs to your application code. To change the
+			{$routing}, edit the file <code>config/routes.php</code>."
+		);
+	},
+	'tests' => function() use ($notify, $self) {
+		if (Environment::is('production')) {
+			$docsLink = $self->html->link(
+				'the documentation',
+				'http://lithify.me/docs/lithium/core/Environment::is()'
+			);
+
+			return $notify(
+				'fail',
+				"Can't run tests",
+				"<p>Lithium's default environment detection rules have determined that you are
+				running in production mode. Therefore, you will not be able to run tests from the
+				web interface. You can do any of the following to remedy this:</p>
+				<ul>
+					<li>Run this application locally</li>
+					<li>Run tests from the console, using the <code>li3 test</code> command</li>
+					<li>
+						Implementing custom environment detection rules;
+						see {$docsLink} for examples
+					</li>
+				</ul>"
+			);
+		}
+		$tests = $self->html->link('run all tests', array(
+			'controller' => 'lithium\test\Controller',
+			'args' => 'all'
+		));
+		$dashboard = $self->html->link('test dashboard', array(
+			'controller' => 'lithium\test\Controller'
+		));
+		$ticket = $self->html->link(
+			'file a ticket', 'https://github.com/UnionOfRAD/lithium/issues'
+		);
+
+		return $notify(
+			'notice',
+			'Run the tests',
+			"Check the builtin {$dashboard} or {$tests} now to ensure Lithium
+			is working as expected. Do not hesitate to {$ticket} in case a test fails."
+		);
+	}
+);
+
+?>
+
+<?php foreach ($checks as $check): ?>
+	<?php echo $check(); ?>
+<?php endforeach; ?>
+
+<ul class="additional-resources">
+	<li>
+		<div class="test-result test-result-notice">Getting started</div>
+		<div class="test-result solution">
+			<?php echo $this->html->link(
+				'Quickstart', 'http://lithify.me/docs/manual/quickstart'
+			); ?> is a guide for PHP users who are looking to get a good idea of what Lithium can
+			do. The guide is part of the official Lithium manual, <?php echo $this->html->link(
+				'The Definitive Guide', 'http://lithify.me/docs/manual'
+			); ?>.
+		</div>
+	</li>
+	<li>
+		<div class="test-result test-result-notice">Learn more</div>
+		<div class="test-result solution">
+			The
+			<?php echo $this->html->link('API documentation', 'http://lithify.me/docs/lithium'); ?>
+			has all the implementation details you've been looking for.
+		</div>
+	</li>
+	<li>
+		Chat with other Lithium users and the team developing Lithium.
+		For <em>general support</em> hop on the
+		<?php echo $this->html->link('#li3 channel', 'irc://irc.freenode.net/#li3'); ?>
+		or read the
+		<?php echo $this->html->link('logs', 'http://lithify.me/bot/logs/li3'); ?>.
+		For <em>core discussions</em> join us in the
+		<?php echo $this->html->link('#li3-core channel', 'irc://irc.freenode.net/#li3-core'); ?>
+		or read the
+		<?php echo $this->html->link('logs', 'http://lithify.me/bot/logs/li3-core'); ?>.
+	</li>
+	<li>
+		Browse the Lithium
+		<?php echo $this->html->link('Repository', 'https://github.com/UnionOfRAD/lithium'); ?>
+		or read the
+		<?php echo $this->html->link('Wiki', 'https://github.com/UnionOfRAD/lithium/wiki'); ?>.
+	</li>
+</ul>

+ 35 - 0
php-lithium/app/web.config

@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+	This file provides out-of-the-box support for Microsoft IIS 7 or higher.
+	If you are using another webserver you can safely remove it. Do note that
+	the Rewrite Module (http://www.iis.net/download/urlrewrite) is not (always)
+	installed by default, but required for the proper working of the rules
+	defined in the <rewrite> section of <system.webServer>.
+-->
+<configuration>
+    <system.webServer>
+        <rewrite>
+            <rules>
+                <clear />
+                <rule name="Set Webroot">
+                    <match url="(.*)" />
+                    <conditions logicalGrouping="MatchAll" trackAllCaptures="false">
+                    </conditions>
+                    <action type="Rewrite" url="webroot/{R:1}" />
+                </rule>
+                <rule name="Lithium Redirect from App">
+                    <match url="webroot/(.*)" />
+                    <conditions>
+                        <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
+                        <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
+                        <add input="{REQUEST_FILENAME}" pattern="favicon.ico$" negate="true" />
+                    </conditions>
+                    <action type="Rewrite" url="webroot/index.php?url={R:1}" />
+                </rule>
+            </rules>
+            <rewriteMaps>
+                <rewriteMap name="Rewrite To Webroot from App" />
+            </rewriteMaps>
+        </rewrite>
+    </system.webServer>
+</configuration>

+ 7 - 0
php-lithium/app/webroot/.htaccess

@@ -0,0 +1,7 @@
+<IfModule mod_rewrite.c>
+    RewriteEngine On
+    RewriteCond %{REQUEST_FILENAME} !-d
+    RewriteCond %{REQUEST_FILENAME} !-f
+    RewriteCond %{REQUEST_FILENAME} !favicon.ico$
+    RewriteRule ^ index.php [QSA,L]
+</IfModule>

+ 626 - 0
php-lithium/app/webroot/css/debug.css

@@ -0,0 +1,626 @@
+/*-------------------------------------------------------------------------------------------------
+   Lithium: the most rad php framework
+
+   @copyright     Copyright 2013, Union of RAD (http://union-of-rad.org)
+   @license       http://opensource.org/licenses/bsd-license.php The BSD License
+-------------------------------------------------------------------------------------------------*/
+
+/*--- Reset ---*/
+* { margin: 0; padding: 0; }
+html, body { height: 100%; min-height: 100%; }
+
+/*--- Test Dashboard ---*/
+body.test-dashboard {
+	font-family: Helvetica, Arial, sans-serif;
+	font-size: 14px;
+	line-height: 1.5em;
+	color: #0d0d0d;
+	background-color: #fff;
+	margin: 0;
+	min-width: 800px;
+}
+
+body.test-dashboard a {
+	color: #333;
+}
+
+body.test-dashboard #header h1 {
+	margin: .25em 1% 0 0;
+	float:right;
+	font-weight: normal;
+}
+
+body.test-dashboard #header h1 a {
+	text-decoration: none;
+	display: block;
+	padding: .45em 0.75em 0 0;
+	color: rgba(0,0,0,.15);
+}
+
+body.test-dashboard .triangle:before {
+	content: '\25B2';
+	font-size: 1em;
+}
+
+body.test-dashboard #header {
+	padding: 0;
+}
+
+body.test-dashboard #header:after {
+	display: block;
+	content: ' ';
+	clear: both;
+}
+
+body.test-dashboard .article {
+	clear:both;
+}
+
+body.test-dashboard .test-content {
+	float:left;
+	padding: 0 2% 4em;
+	width: 74%;
+}
+
+.test-content h2 {
+	font-weight: normal;
+	font-size: 1.45em;
+	margin-bottom: .5em;
+	float: left;
+}
+.test-content h2 span {
+	color: #bbb;
+	display: block;
+	font-size: .55em;
+}
+.test-content h3 {
+	font-weight: normal;
+	margin: 1.5em 0 1em;
+}
+
+body.test-dashboard a.test-button,
+body.test-dashboard a.test-button:link,
+body.test-dashboard a.test-button:visited,
+body.test-dashboard a.test-button:hover,
+body.test-dashboard a.test-button:active {
+	display: block;
+	float: right;
+	font-weight: bold;
+	font-size: 1.25em;
+	background-color:#f5f5f5;
+	border-color: #e6e6e6;
+	color: #999;
+	padding: .5em 1em;
+	margin: 0;
+	background-color: white;
+	border: 1px solid #e5e5e5;
+	text-decoration: none;
+	-moz-border-radius: 4px;
+	-webkit-border-radius: 4px;
+	border-radius: 4px;
+	-moz-box-shadow: 0 0 6px rgba(0,0,0,.1);
+	-webkit-box-shadow: 0 0 6px rgba(0,0,0,.1);
+	box-shadow: 0 0 6px rgba(0,0,0,.1);
+}
+
+body.test-dashboard a.test-button:hover,
+body.test-dashboard a.test-button:active {
+	color: black;
+	background: white;
+	-moz-box-shadow: inset 0 0 6px rgba(0,0,0,.15);
+	-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.15);
+	box-shadow: inset 0 0 6px rgba(0,0,0,.15);
+}
+
+body.test-dashboard ul {
+	margin: .25em 0;
+	padding: 0.2em 0 0 0;
+}
+
+body.test-dashboard ul ul li {
+	display: block;
+	margin: 0 0 1px .5em;
+	padding: 0.25em 0 0 0.75em;
+	border: 1px solid rgba(0,0,0,0.05);
+	border-width: 0 0 0 1px;
+}
+
+/*--- Test Dashboard: Menu ---*/
+body.test-dashboard .test-menu {
+	float: left;
+	padding: .75em 0 1em 1%;
+	width: 20%;
+	background: #f6f6f6;
+}
+
+.test-dashboard .test-menu > ul {
+	margin-top: 0;
+	padding-top: 1px;
+}
+.test-dashboard .test-menu ul li ul {
+	margin-top: .1em;
+}
+.test-dashboard .test-menu li:hover {
+}
+.test-dashboard .test-menu li > ul {
+	display: block;
+}
+.test-dashboard .test-menu li:hover > ul {
+	display: block;
+}
+ul.menu, ul.menu ul {
+	list-style: none;
+}
+ul.menu a {
+	color: #666;
+	text-decoration: none;
+	display: block;
+}
+
+ul.menu a:hover, ul.menu a:active, ul.menu a.menu-folder:hover {
+	color: black;
+}
+
+ul.menu a.menu-folder {
+	color: #333;
+	font-weight: bold;
+	text-decoration: none;
+	font-size: 1.2em;
+}
+
+/*--- Test Dashboard: Test All ---*/
+a.test-all {
+	display: block;
+	float: left;
+	font-size: 1.5em;
+	text-align: center;
+	text-decoration: none;
+	padding: .75em 0;
+	width: 21%;
+	color: #666;
+	background: #e6e6e6;
+}
+a.test-all:hover {
+	background: #00a6f5;
+	-moz-box-shadow: inset 0 0 12px rgba(0,0,0,.25);
+	-webkit-box-shadow: inset 0 0 12px rgba(0,0,0,.25);
+	box-shadow: inset 0 0 12px rgba(0,0,0,.25);
+	color: white !important;
+	text-shadow: 0px 0px 6px rgba(0,0,0,.5);
+}
+
+/*--- Menu ---*/
+ul.menu a {
+	display: block;
+	padding: 0.1em 0;
+}
+
+ul.menu a:before, a.menu-folder:before, ul.metrics li:before {
+	display: inline !important;
+	float: none !important;
+	padding: 0 0.5em 0 0;
+	content: '\25B4';
+	font-weight: normal;
+	color: rgba(0,0,0,.1);
+}
+a.menu-folder:before {
+	padding: 0 !important;
+	content: '\25B2' !important;
+}
+ul.menu a:hover:before, a.menu-folder:hover:before, ul.metrics li:hover:before {
+	color: #00A6F5;
+}
+
+/*--- Benchmarking ---*/
+table.metrics {
+	border: 1px solid #e6e6e6;
+}
+table.metrics {
+	border-collapse: collapse;
+}
+table.metrics th {
+	padding: .5em 	1em;
+	color: black;
+	background: #e6e6e6;
+	font-weight: normal;
+}
+table.metrics th, table.metrics td {
+	border-bottom: 1px solid rgba(0,0,0,.05);
+}
+
+td.metric-name {
+	text-align: left;
+	white-space: nowrap;
+	padding: 6px 8px;
+	background: #e6e6e6;
+	width: 35%;
+}
+tr:hover td.metric-name {
+	background: #f5f5f5;
+}
+td.metric {
+	font-family: 'Andale Mono', Monaco, Courier, monospace !important;
+	font-weight: bold;
+	padding: 6px 8px;
+	text-align: right;
+	width: 65%;
+	background: #f5f5f5;
+}
+tr:hover td.metric {
+	background: white;
+}
+
+ul.classes, ul.files {
+	list-style-type: none;
+	font-family: 'Andale Mono', Monaco, Courier, monospace !important;
+}
+
+ul.metrics {
+	list-style-type: none;
+	padding: .5em !important;
+}
+
+ul.metrics li {
+	padding: .25em;
+}
+
+/*--- Test Results ---*/
+div.test-result {
+	clear: both;
+	margin: 1em 0 1.5em;
+	padding: .75em 1em .55em;
+	color: #FFFFFF;
+	background: #666;
+	border-radius: 4px;
+}
+
+.test-dashboard .test-result {
+	font-size: 1.15em;
+}
+.test-dashboard .digit {
+	font-weight: bold;
+}
+
+div.test-result-success {
+	background-color: #4ddb4a;
+	border-color: #467F0D;
+}
+
+div.test-result-fail {
+	background-color: #d13ef2;
+	border-color: #7F0D0D;
+}
+
+div.test-result-exception {
+	background-color: #E58F16;
+	border-color: #995F0F;
+}
+div.test-result-notice {
+	background-color: #00a6f5;
+	border-color: #8EA7CF;
+}
+
+div.test-assert, div.test-exception, div.test-skip {
+	margin: 0 0 1.5em -14px;
+	padding: 0 0 0 10px;
+	color: #000000;
+	border: 1px solid rgba(0,0,0,.05);
+	border-width: 0 0 0 4px;
+	font-size: 16px;
+}
+
+div.test-assert-passed {
+	border-left-color: #D0F9E0;
+}
+
+div.test-assert-failed {
+	color: #7F0D0D;
+	border-left-color: #CC1414;
+}
+
+div.test-exception {
+	color: #995F0F;
+	border-left-color: #E58F16;
+}
+
+div.test-skip {
+	background-color: #fafafa;
+	color: #666;
+}
+
+.test-assert span.content,
+.test-exception span.content,
+.test-skip span.content,
+.test-assert span.trace,
+.test-exception span.trace,
+.test-skip span.trace {
+	display: block;
+	clear: both;
+	white-space: pre;
+	color: #111;
+	font-size: 12px;
+	padding: .5em 1em;
+	margin: .5em 0;
+	background: #fafafa;
+	border: 1px solid rgba(0,0,0,.1);
+	font-family: 'Andale Mono', Monaco, Courier, monospace !important;
+}
+
+.test-assert span.trace {
+	padding: 0 .5em;
+	margin: .25em 0 .25em .5em;
+	background: #FAFAFA;
+}
+
+div.test-skip span.content {
+	color: #999;
+	padding: 0 1em;
+	-moz-box-shadow: none;
+	-webkit-box-shadow: none;
+	box-shadow: none;
+	border: none;
+}
+
+/*--- SQL Dumps ---*/
+.lithium-sql-log table {
+	background: #f4f4f4;
+}
+
+.lithium-sql-log td {
+	padding: 4px 8px;
+	text-align: left;
+}
+
+
+/*--- Debugger Dumps ---*/
+pre {
+	color: #000;
+	background: #f0f0f0;
+	padding: 1em;
+}
+
+pre.lithium-debug {
+	background: #ffcc00;
+	font-size: 1.2em;
+	line-height: 1.5em;
+	margin-top: 1em;
+	overflow: auto;
+	position: relative;
+}
+
+div.lithium-exception-class, div.lithium-exception-location {
+	font-weight: bold;
+}
+
+div.lithium-exception-message {
+	color: #000;
+	background: #f0f0f0;
+	padding: 1em;
+}
+
+div.lithium-stack-trace {
+	background: #fff;
+	border: 4px dotted #ffcc00;
+	color: #333;
+	margin: 0px;
+	padding: 6px;
+	font-size: 1.2em;
+	line-height: 1.5em;
+	overflow: auto;
+	position: relative;
+}
+
+/*--- Code Highlighting ---*/
+div.lithium-code-dump pre {
+	position: relative;
+	overflow: auto;
+}
+
+div.lithium-stack-trace pre, div.lithium-code-dump pre {
+	color: #000;
+	background-color: #F0F0F0;
+	margin: 0px;
+	padding: 1em;
+	overflow: auto;
+}
+
+div.lithium-code-dump pre, div.lithium-code-dump pre code {
+	clear: both;
+	font-size: 1em;
+	line-height: 1.5em;
+	margin: 4px 2px;
+	padding: 4px;
+	overflow: auto;
+}
+
+div.lithium-code-dump span.code-highlight {
+	background-color: #ff0;
+}
+
+/*--- Code Coverage Analysis ---*/
+span.filters {
+	display: block;
+	float: right;
+	margin: 1em 0 .5em 0;
+}
+span.filters a {
+	display: block;
+	float: left;
+	padding: .5em 1em;
+	margin-left: .25em;
+	text-decoration:none;
+	-moz-border-radius: 4px;
+	-webkit-border-radius: 4px;
+	border-radius: 4px;
+	background: #e6e6e6;
+	color: #666;
+}
+span.filters a:hover, span.filters a.active {
+	-moz-box-shadow: none;
+	-webkit-box-shadow: none;
+	box-shadow: none;
+	color: black;
+	background: #f5f5f5;
+}
+
+span.filters a.active {
+	background: #00a6f5;
+	text-shadow: 0px 0px 6px rgba(0,0,0,.5);
+	color: white;
+}
+
+div.code-coverage-results, h4.code-coverage-name {
+	clear: both;
+	color: #000000;
+	font-size: .8em;
+	font-family: 'Andale Mono', Monaco, Courier, monospace !important;
+	background-color: #fafafa;
+	border: 1px solid #e6e6e6;
+	border: 1px solid rgba(0,0,0,.1);
+	-moz-box-shadow: 0 0 6px rgba(0,0,0,.15);
+	-webkit-box-shadow: 0 0 6px rgba(0,0,0,.15);
+	box-shadow: 0 0 6px rgba(0,0,0,.15);
+}
+
+h4.coverage {
+	clear: both;
+	color: #454545;
+	font-weight: normal;
+	margin-bottom: .5em;
+}
+
+h4.code-coverage-name {
+	color: #999;
+	background-color: #ECECEC;
+	border-top: none;
+	padding: 0.25em 0.5em;
+	margin: 0 1px 0 0;
+	font-weight: normal;
+	float: right;
+}
+
+div.code-coverage-results div.code-line {
+	display: block;
+	float: none;
+	clear: both;
+}
+
+div.code-coverage-results span.content {
+	display: block;
+	clear: right;
+	white-space: pre;
+	line-height: 1.5em;
+	min-height: 1.5em;
+	color: #111;
+}
+
+div.code-coverage-results div.uncovered span.line-num {
+	border-color: #CC1414;
+}
+div.code-coverage-results div.uncovered span.content {
+	color: #7F0D0D;
+}
+div.code-coverage-results div.ignored span.content {
+	color: #999;
+	background: #ececec;
+}
+
+div.code-coverage-results span.line-num {
+	display: block;
+	float: left;
+	width: 3em;
+	color: #999;
+	background-color: #ECECEC;
+	text-align: right;
+	border-right: 1px solid #ccc;
+	padding-right: 4px;
+	margin-right: 5px;
+	line-height: 1.5em;
+}
+
+div.code-coverage-results .code-line:hover span.line-num {
+	background: #ddd;
+	color: #666;
+}
+
+div.code-coverage-results span.line-num strong {
+	color: #666;
+}
+
+div.code-coverage-results div.start {
+	margin-top: 30px;
+	padding-top: 5px;
+	border: 1px solid #aaa;
+	border-width: 1px 1px 0px 1px;
+}
+
+div.code-coverage-results div.end {
+	margin-bottom: 30px;
+	padding-bottom: 5px;
+	border: 1px solid #aaa;
+	border-width: 0px 1px 1px 1px;
+}
+
+div.code-coverage-results div.realstart {
+	margin-top: 0px;
+}
+
+div.code-coverage-results p.note {
+	color: #bbb;
+	padding: 5px;
+	margin: 5px 0 10px;
+	font-size: .8em;
+}
+
+div.code-coverage-results span.result-bad {
+	color: #a00;
+}
+
+div.code-coverage-results span.result-ok {
+	color: #fa0;
+}
+
+div.code-coverage-results span.result-good {
+	color: #0a0;
+}
+
+/*--- Application Home Page ---*/
+.app .test-result {
+	width: 640px;
+	margin-bottom: 0;
+}
+.app .test-result.solution {
+	padding: 0;
+	margin: .5em 0 1.5em 0;
+	color: #111;
+	background: none;
+}
+.app .test-result.solution ol {
+	margin-left: 1.5em;
+}
+.app .additional-resources {
+	width: 560px;
+	list-style: none;
+	margin: 0;
+}
+.app .additional-resources li {
+	margin-bottom: .5em;
+}
+.app .indicated {
+	margin: 0;
+	list-style: none;
+}
+.app .indicated li {
+	display: inline;
+}
+.app .indicated li + li:before {
+	content: " / ";
+}
+.app .indicated .enabled:after {
+	content: "\0020 \2714";
+	color: #4ddb4a;
+}
+.app .indicated .disabled:after {
+	content: "\0020 \2718";
+	color: #d13ef2;
+}

+ 294 - 0
php-lithium/app/webroot/css/lithium.css

@@ -0,0 +1,294 @@
+/*-------------------------------------------------------------------------------------------------
+   Lithium: the most rad php framework
+
+   @copyright     Copyright 2013, Union of RAD (http://union-of-rad.org)
+   @license       http://opensource.org/licenses/bsd-license.php The BSD License
+-------------------------------------------------------------------------------------------------*/
+
+/*--- Reset ---*/
+* { margin: 0; padding: 0; }
+html, body { height: 100%; min-height: 100%; }
+
+/*--- Layout ---*/
+body {
+	font-family: Helvetica, Arial, sans-serif;
+	font-size: 14px;
+	line-height: 1.5em;
+	color: #0d0d0d;
+	background-color: #fff;
+}
+#container {
+	position: relative;
+	padding: 60px 10%;
+}
+
+/*--- Basics ---*/
+h1, h2, h3, h4, h5, h6 {
+	font-weight:normal;
+	color:#111;
+	line-height: 1;
+	margin: 1.5em 0 0.5em 0;
+}
+h1 { font-size: 2.6em; }
+h2, h5 { font-size: 2em; color: #666; }
+h3, h6 { font-size: 1.7em; color: #00a8e6; }
+h4 { font-size: 1.4em; }
+h5 { font-size: 1.2em; }
+h6 { font-size: 1em; }
+p { margin-bottom: 1em; }
+strong { font-weight: bold; }
+em { font-style: italic; }
+a { text-decoration: none; color: #666; }
+a, h1 a, h2 a { text-decoration: none; }
+a:hover { color: #00bbff; }
+a:visited:hover { color: #ff59ff; }
+a img { border: none; }
+
+/*--- Code ---*/
+pre > code {
+	display: block;
+	color: white;
+	background: #141414;
+	border: 1px solid white;
+	padding: 1em !important; /* remove if base important is removed, too */
+	overflow: auto;
+	font-size: .9em;
+}
+a code {
+	background: transparent;
+	border: 0;
+}
+a:hover code {
+	color: inherit;
+}
+code, pre, .fixed {
+	font-family: Monaco, Courier, monospace !important;
+	font-weight: normal;
+	font-size: 0.85em;
+	white-space: pre;
+}
+code {
+	padding: .2em .25em !important;
+	border: 1px solid #F0F0F0;
+	background: #FAFAFA;
+}
+pre {
+	background: none;
+	padding: 0 0 .5em 0 !important;
+}
+
+/*--- Lists ---*/
+li ul, li ol { margin: 0; }
+ul, ol { margin: 0 0 1em 2.75em; }
+dt, dd {
+	font-style: italic;
+	margin: .5em 0;
+}
+dt {
+	font-weight: bold;
+}
+dd {
+	margin-left: 1em;
+}
+/*--- Header ---*/
+#header h1 {
+	margin: .1em 0;
+	font-size: 35px;
+}
+#header h2 {
+	width: 70%;
+	margin: .5em .5em 2em 0;
+	color: #666;
+	font-size: 22px;
+	line-height: 28px;
+}
+
+/*--- Tables ---*/
+table {
+	clear: both;
+	width: 100%;
+	border-collapse: collapse;
+	border-spacing: 0;
+	border: 1px solid #e6e6e6;
+	background: #fafafa;
+	margin: 12px 0;
+}
+td, th {
+	padding: .25em 1em;
+	border: 1px solid #e6e6e6;
+	vertical-align: middle;
+	text-align: left;
+	font-weight: normal;
+	color: #666;
+	font-size: 0.85em;
+}
+tr:nth-child(even) {
+	background: #fff;
+}
+thead th, tfoot td {
+	background: #f3f3f3;
+	color: #333;
+	text-align: left;
+	font-size: 1em;
+	font-weight: bold;
+	padding: .5em .75em;
+}
+/*--- Forms ---*/
+form {
+	display: block;
+	clear: both;
+	background: #fafafa;
+	padding: 1em 2em 2em 2em;
+	border: 1px solid #e6e6e6;
+}
+fieldset {
+	padding: 2em;
+	margin: 0 0 1em 0;
+	border: 1px solid #e6e6e6;
+	background: #f3f3f3;
+}
+legend {
+	padding: .5em 1em;
+	border: 1px solid #e6e6e6;
+	background: #fff;
+	font-size: 22px;
+}
+label {
+	padding: 0 1em 0 0;
+	color: #454545;
+	font-weight: normal;
+}
+input, textarea, button {
+	font-family: Helvetica, Arial, sans-serif;
+	padding: 2px 4px;
+	border: 1px solid #e5e5e5;
+	color: #454545;
+	font-size: 1em;
+	line-height: 1.25em;
+}
+input[type=text], input[type=password], input[type=submit], textarea {
+	clear: both;
+	display: block;
+	padding: .25em .5em;
+}
+input[type=text], input[type=password], textarea {
+	width: 97%;
+	max-width: 950px;
+	margin: .5em 0 1em 0;
+	padding:.5em;
+}
+input[type=submit], input[type=button], input[type=reset], input[type=file], button {
+	-moz-border-radius: 4px;
+	-webkit-border-radius: 4px;
+	border-radius: 4px;
+	padding: .5em 1em;
+	margin: 0 .75em 0 0;
+	background-color: white;
+	color: black !important;
+	border: 1px solid #e5e5e5 !important;
+	cursor: pointer;
+}
+select {
+	clear: both;
+	display: block;
+	margin: .5em 0 1em 0;
+}
+div.checkbox {
+	clear: both;
+	padding: 1em 0;
+}
+.checkbox label {
+	display: inline;
+}
+input[type=submit] {
+	margin: 1em inherit;
+	border: none;
+	color: #000;
+	font-size: 16px;
+	font-weight: bold;
+}
+input[type=text]:focus, input[type=password]:focus, textarea:focus, select:focus {
+	border-color: #00a8e6;
+	outline: none;
+}
+
+/*--- Misc ---*/
+hr {
+	border: none;
+	height: 0;
+	border-bottom: 1px solid #e6e6e6;
+	margin:1em 0;
+}
+sup, sub {
+	color: #666;
+	font-size: .65em;
+}
+acronym {
+	font-weight: bold;
+	font-style: italic;
+	color: #333;
+}
+abbr {
+	color: #333;
+}
+
+blockquote {
+	padding: 0.15em .5em;
+	margin: 0.5em 0;
+	font-size: 2em;
+	color: #666;
+	display: block;
+	font-style: italic;
+}
+
+blockquote:before, blockquote:after {
+	display: inline;
+	color: #e5e5e5;
+	font-size: 3em;
+	position: relative;
+	top: 0.25em;
+	left: -0.1em;
+}
+
+blockquote:before {
+	content: '\D \201C';
+}
+
+blockquote:after {
+	content: '\201D';
+}
+
+/*--- Shadows ---*/
+code {
+	-moz-box-shadow: 0 0 3px rgba(0,0,0,.1);
+	-webkit-box-shadow: 0 0 3px rgba(0,0,0,.1);
+	box-shadow: 0 0 3px rgba(0,0,0,.1);
+	color: #666;
+}
+table, form, pre > code {
+	margin-top: 0px;
+	margin-bottom: 12px;
+}
+table, form, pre > code, .shadow {
+	-moz-box-shadow: 2px 2px 12px rgba(0,0,0,.15);
+	-webkit-box-shadow: 2px 2px 12px rgba(0,0,0,0.15);
+	box-shadow: 2px 2px 12px rgba(0,0,0,.15);
+}
+img.shadow {
+	border: 1px solid rgba(255,255,255,.15);
+}
+input[type=submit], input[type=button], input[type=reset], input[type=file], button {
+	-moz-box-shadow: 0 0 6px rgba(0,0,0,.1);
+	-webkit-box-shadow: 0 0 6px rgba(0,0,0,.1);
+	box-shadow: 0 0 6px rgba(0,0,0,.1);
+}
+input[type=submit]:hover, input[type=button]:hover, input[type=reset]:hover, button:hover {
+	-moz-box-shadow: inset 0 0 6px rgba(0,0,0,.15);
+	-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.15);
+	box-shadow: inset 0 0 6px rgba(0,0,0,.15);
+}
+input, textarea {
+	-moz-box-shadow: inset 0 0 3px rgba(0,0,0,.1);
+	-webkit-box-shadow: inset 0 0 3px rgba(0,0,0,.1);
+	box-shadow: inset 0 0 3px rgba(0,0,0,.1);
+}

BIN
php-lithium/app/webroot/favicon.ico


+ 0 - 0
php-lithium/app/webroot/img/empty


+ 43 - 0
php-lithium/app/webroot/index.php

@@ -0,0 +1,43 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright     Copyright 2013, Union of RAD (http://union-of-rad.org)
+ * @license       http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+
+/**
+ * Welcome to Lithium! This front-controller file is the gateway to your application. It is
+ * responsible for intercepting requests, and handing them off to the `Dispatcher` for processing.
+ *
+ * @see lithium\action\Dispatcher
+*/
+
+/**
+ * If you're sharing a single Lithium core install or other libraries among multiple
+ * applications, you may need to manually set things like `LITHIUM_LIBRARY_PATH`. You can do that in
+ * `config/bootstrap.php`, which is loaded below:
+ */
+require dirname(__DIR__) . '/config/bootstrap.php';
+
+/**
+ * The following will instantiate a new `Request` object and pass it off to the `Dispatcher` class.
+ * By default, the `Request` will automatically aggregate all the server / environment settings, URL
+ * and query string parameters, request content (i.e. POST or PUT data), and HTTP method and header
+ * information.
+ *
+ * The `Request` is then used by the `Dispatcher` (in conjunction with the `Router`) to determine
+ * the correct `Controller` object to dispatch to, and the correct response type to render. The
+ * response information is then encapsulated in a `Response` object, which is returned from the
+ * controller to the `Dispatcher`, and finally echoed below. Echoing a `Response` object causes its
+ * headers to be written, and its response body to be written in a buffer loop.
+ *
+ * @see lithium\action\Request
+ * @see lithium\action\Response
+ * @see lithium\action\Dispatcher
+ * @see lithium\net\http\Router
+ * @see lithium\action\Controller
+ */
+echo lithium\action\Dispatcher::run(new lithium\action\Request());
+
+?>

+ 1 - 0
php-lithium/app/webroot/js/empty

@@ -0,0 +1 @@
+

+ 25 - 0
php-lithium/app/webroot/web.config

@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+	This file provides out-of-the-box support for Microsoft IIS 7 or higher.
+	If you are using another webserver you can safely remove it. Do note that
+	the Rewrite Module (http://www.iis.net/download/urlrewrite) is not (always)
+	installed by default, but required for the proper working of the rules
+	defined in the <rewrite> section of <system.webServer>.
+-->
+<configuration>
+    <system.webServer>
+        <rewrite>
+            <rules>
+                <rule name="Lithium Redirect" stopProcessing="true">
+                    <match url="^(.*)$" ignoreCase="false" />
+                    <conditions logicalGrouping="MatchAll">
+                        <add input="{REQUEST_FILENAME}" matchType="IsDirectory" ignoreCase="false" negate="true" />
+                        <add input="{REQUEST_FILENAME}" matchType="IsFile" ignoreCase="false" negate="true" />
+                        <add input="{REQUEST_FILENAME}" pattern="favicon.ico$" ignoreCase="false" negate="true" />
+                    </conditions>
+                    <action type="Rewrite" url="index.php?url={R:1}" appendQueryString="true" />
+                </rule>
+            </rules>
+        </rewrite>
+    </system.webServer>
+</configuration>

+ 13 - 0
php-lithium/benchmark_config

@@ -0,0 +1,13 @@
+{
+  "framework": "lithium",
+  "tests": [{
+    "default": {
+      "setup_file": "setup",
+      "json_url": "/json",
+      "db_url": "/db",
+      "query_url": "/db?queries=",
+      "port": 8080,
+      "sort": 56
+    }
+  }]
+}

+ 125 - 0
php-lithium/deploy/nginx.conf

@@ -0,0 +1,125 @@
+#user  nobody;
+worker_processes  8;
+
+#error_log  logs/error.log;
+#error_log  logs/error.log  notice;
+#error_log  logs/error.log  info;
+
+#pid        logs/nginx.pid;
+
+
+events {
+    worker_connections  1024;
+}
+
+
+http {
+    include       /usr/local/nginx/conf/mime.types;
+    default_type  application/octet-stream;
+
+    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
+    #                  '$status $body_bytes_sent "$http_referer" '
+    #                  '"$http_user_agent" "$http_x_forwarded_for"';
+
+    #access_log  logs/access.log  main;
+
+    sendfile        on;
+    #tcp_nopush     on;
+
+    #keepalive_timeout  0;
+    keepalive_timeout  65;
+
+    #gzip  on;
+
+    server {
+        listen       8080;
+        server_name  localhost;
+
+        #charset koi8-r;
+
+        #access_log  logs/host.access.log  main;
+
+        #location / {
+        #    root   html;
+        #    index  index.html index.htm;
+        #}
+
+        #error_page  404              /404.html;
+
+        # redirect server error pages to the static page /50x.html
+        #
+        #error_page   500 502 503 504  /50x.html;
+        #location = /50x.html {
+        #    root   html;
+        #}
+
+        # proxy the PHP scripts to Apache listening on 127.0.0.1:80
+        #
+        #location ~ \.php$ {
+        #    proxy_pass   http://127.0.0.1;
+        #}
+
+        root /home/ubuntu/FrameworkBenchmarks/php-lithium/;
+        index  index.php;
+
+        location / {
+            try_files $uri $uri/ /index.php?$uri&$args;
+        }
+
+        # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
+        #
+        location ~ \.php$ {
+            try_files $uri =404;
+            fastcgi_pass   127.0.0.1:9001;
+            fastcgi_index  index.php;
+#            fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
+            fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
+            include        /usr/local/nginx/conf/fastcgi_params;
+        }
+
+        # deny access to .htaccess files, if Apache's document root
+        # concurs with nginx's one
+        #
+        #location ~ /\.ht {
+        #    deny  all;
+        #}
+    }
+
+
+    # another virtual host using mix of IP-, name-, and port-based configuration
+    #
+    #server {
+    #    listen       8000;
+    #    listen       somename:8080;
+    #    server_name  somename  alias  another.alias;
+
+    #    location / {
+    #        root   html;
+    #        index  index.html index.htm;
+    #    }
+    #}
+
+
+    # HTTPS server
+    #
+    #server {
+    #    listen       443;
+    #    server_name  localhost;
+
+    #    ssl                  on;
+    #    ssl_certificate      cert.pem;
+    #    ssl_certificate_key  cert.key;
+
+    #    ssl_session_timeout  5m;
+
+    #    ssl_protocols  SSLv2 SSLv3 TLSv1;
+    #    ssl_ciphers  HIGH:!aNULL:!MD5;
+    #    ssl_prefer_server_ciphers   on;
+
+    #    location / {
+    #        root   html;
+    #        index  index.html index.htm;
+    #    }
+    #}
+
+}

+ 9 - 0
php-lithium/deploy/php-lithium

@@ -0,0 +1,9 @@
+<VirtualHost *:8080>
+  Alias /php-lithium/ "/home/ubuntu/FrameworkBenchmarks/php-lithium/"
+  <Directory /home/ubuntu/FrameworkBenchmarks/php-lithium>
+          Options Indexes FollowSymLinks MultiViews
+          #AllowOverride None
+          Order allow,deny
+          allow from all
+  </Directory>
+</VirtualHost>

+ 11 - 0
php-lithium/index.php

@@ -0,0 +1,11 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright     Copyright 2013, Union of RAD (http://union-of-rad.org)
+ * @license       http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+
+require 'app/webroot/index.php';
+
+?>

+ 5 - 0
php-lithium/libraries/lithium/.gitignore

@@ -0,0 +1,5 @@
+# OS X
+.DS_Store
+
+# Vim
+.*.sw[a-z]

+ 20 - 0
php-lithium/libraries/lithium/.travis.yml

@@ -0,0 +1,20 @@
+language: php
+
+env:
+  - OPCODE_CACHE=apc
+ #- OPCODE_CACHE=xcache
+
+php:
+  - 5.3
+  - 5.4
+
+before_script:
+ - php tests/ci_depends.php $OPCODE_CACHE
+# - cd ../ && git clone git://github.com/UnionOfRAD/li3_quality.git && cd lithium
+
+script:
+ - console/li3 test --filters=Profiler tests/cases
+# - console/li3 test --filters=Profiler tests/cases && cd ../li3_quality && for FILE in $(cd ../lithium/ && git diff-index --name-only --diff-filter=AM HEAD~1); do ../lithium/console/li3 quality syntax ../lithium/${FILE} --silent; done
+
+notifications:
+    irc: "irc.freenode.org#li3-core"

+ 12 - 0
php-lithium/libraries/lithium/CONTRIBUTING.md

@@ -0,0 +1,12 @@
+# Contributing guidelines
+
+Thank you for your interest in contributing to Lithium! This project is built by a thriving community of developers who value cutting-edge technology and concise, maintainable code. If you've found a bug, or have an idea for a feature, we encourage your participation in making Lithium better.
+
+Here's what you need to stick to in order to have the best chance of getting your code pushed to the core:
+
+ * **Integration**: all pull requests should be submitted against the [`dev`](https://github.com/UnionOfRAD/lithium/tree/dev) branch for integration testing
+ * **Conceptual integrity**: code should conform to the goals of the framework
+ * **Maintainability**: code should pass existing tests, have adequate test coverage and should conform to our coding standards & QA guidelines
+ * **Comprehensibility**: code should be concise and expressive, and should be accompanied by new documentation as appropriate, or updates to existing docs
+
+Please see the full documentation over at the [contribution guidelines](http://lithify.me/docs/manual/appendices/contributing.wiki) at [lithify.me](http://lithify.me/)

+ 25 - 0
php-lithium/libraries/lithium/LICENSE.txt

@@ -0,0 +1,25 @@
+Copyright (c) 2013, Union of RAD http://union-of-rad.org
+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.
+    * Neither the name of Lithium, Union of Rad, nor the names of its contributors 
+		may be used to endorse or promote products derived from this software 
+		without specific prior written permission.
+
+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 HOLDER 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.

+ 313 - 0
php-lithium/libraries/lithium/action/Controller.php

@@ -0,0 +1,313 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright     Copyright 2012, Union of RAD (http://union-of-rad.org)
+ * @license       http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+
+namespace lithium\action;
+
+use lithium\util\Inflector;
+use lithium\action\DispatchException;
+use lithium\core\Libraries;
+
+/**
+ * The `Controller` class is the fundamental building block of your application's request/response
+ * cycle. Controllers are organized around a single logical entity, usually one or more model
+ * classes (i.e. `lithium\data\Model`) and are tasked with performing operations against that
+ * entity.
+ *
+ * Each controller has a series of 'actions' which are defined as class methods of the `Controller`
+ * classes. Each action has a specific responsibility, such as listing a set of objects, updating an
+ * object, or deleting an object.
+ *
+ * A controller object is instantiated by the `Dispatcher` (`lithium\action\Dispatcher`), and is
+ * given an instance of the `lithium\action\Request` class, which contains all necessary request
+ * state, including routing information, `GET` & `POST` data, and server variables. The controller
+ * is then invoked (using PHP's magic `__invoke()` syntax), and the proper action is called,
+ * according to the routing information stored in the `Request` object.
+ *
+ * A controller then returns a response (i.e. using `redirect()` or `render()`) which includes HTTP
+ * headers, and/or a serialized data response (JSON or XML, etc.) or HTML webpage.
+ *
+ * For more information on returning serialized data responses for web services, or manipulating
+ * template rendering from within your controllers, see the settings in `$_render` and the
+ * `lithium\net\http\Media` class.
+ *
+ * @see lithium\net\http\Media
+ * @see lithium\action\Dispatcher
+ * @see lithium\action\Controller::$_render
+ */
+class Controller extends \lithium\core\Object {
+
+	/**
+	 * Contains an instance of the `Request` object with all the details of the HTTP request that
+	 * was dispatched to the controller object. Any parameters captured in routing, such as
+	 * controller or action name are accessible as properties of this object, i.e.
+	 * `$this->request->controller` or `$this->request->action`.
+	 *
+	 * @see lithium\action\Request
+	 * @var object
+	 */
+	public $request = null;
+
+	/**
+	 * Contains an instance of the `Response` object which aggregates the headers and body content
+	 * to be written back to the client (browser) when the result of the request is rendered.
+	 *
+	 * @see lithium\action\Response
+	 * @var object
+	 */
+	public $response = null;
+
+	/**
+	 * Lists the rendering control options for responses generated by this controller.
+	 *
+	 * - The `'type'` key is the content type that will be rendered by default, unless another is
+	 *   explicitly specified (defaults to `'html'`).
+	 * - The `'data'` key contains an associative array of variables to be sent to the view,
+	 *   including any variables created in `set()`, or if an action returns any variables (as an
+	 *   associative array).
+	 * - When an action is invoked, it will by default attempt to render a response, set the
+	 *   `'auto'` key to `false` to prevent this behavior.
+	 * - If you manually call `render()` within an action, the `'hasRendered'` key stores this
+	 *   state, so that responses are not rendered multiple times, either manually or automatically.
+	 * - The `'layout'` key specifies the name of the layout to be used (defaults to `'default'`).
+	 *   Typically, layout files are looked up as
+	 *   `<app-path>/views/layouts/<layout-name>.<type>.php`. Based on the default settings, the
+	 *   actual path would be `path-to-app/views/layouts/default.html.php`.
+	 * - Though typically introspected from the action that is executed, the `'template'` key can be
+	 *   manually specified. This sets the template to be rendered, and is looked up (by default) as
+	 *   `<app-path>/views/<controller>/<action>.<type>.php`, i.e.:
+	 *   `path-to-app/views/posts/index.html.php`.
+	 * - To enable automatic content-type negotiation (i.e. determining the content type of the
+	 *   response based on the value of the
+	 *   [HTTP Accept header](http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html)), set the
+	 *   `'negotiate'` flag to `true`. Otherwise, the response will only be based on the `type`
+	 *   parameter of the request object (defaulting to `'html'` if no type is present in the
+	 *   `Request` parameters).
+	 *
+	 * Keep in mind that most of these settings may be passed to `Controller::render()` as well. To
+	 * change how these settings operate (i.e. template paths, default render settings for
+	 * individual media types), see the `Media` class.
+	 *
+	 * @var array
+	 * @see lithium\action\Controller::render()
+	 * @see lithium\net\http\Media::type()
+	 * @see lithium\net\http\Media::render()
+	 */
+	protected $_render = array(
+		'type'        => null,
+		'data'        => array(),
+		'auto'        => true,
+		'layout'      => 'default',
+		'template'    => null,
+		'hasRendered' => false,
+		'negotiate'   => false
+	);
+
+	/**
+	 * Lists `Controller`'s class dependencies. For details on extending or replacing a class,
+	 * please refer to that class's API.
+	 *
+	 * @var array
+	 */
+	protected $_classes = array(
+		'media' => 'lithium\net\http\Media',
+		'router' => 'lithium\net\http\Router',
+		'response' => 'lithium\action\Response'
+	);
+
+	/**
+	 * Auto configuration properties.
+	 *
+	 * @var array
+	 */
+	protected $_autoConfig = array('render' => 'merge', 'classes' => 'merge');
+
+	public function __construct(array $config = array()) {
+		$defaults = array(
+			'request' => null, 'response' => array(), 'render' => array(), 'classes' => array()
+		);
+		parent::__construct($config + $defaults);
+	}
+
+	/**
+	 * Populates the `$response` property with a new instance of the `Response` class passing it
+	 * configuration, and sets some rendering options, depending on the incoming request.
+	 *
+	 * @return void
+	 */
+	protected function _init() {
+		parent::_init();
+		$this->request = $this->request ?: $this->_config['request'];
+		$this->response = $this->_instance('response', $this->_config['response']);
+
+		if (!$this->request || $this->_render['type']) {
+			return;
+		}
+		if ($this->_render['negotiate']) {
+			$this->_render['type'] = $this->request->accepts();
+			return;
+		}
+		$this->_render['type'] = $this->request->get('params:type') ?: 'html';
+	}
+
+	/**
+	 * Called by the Dispatcher class to invoke an action.
+	 *
+	 * @param object $request The request object with URL and HTTP info for dispatching this action.
+	 * @param array $dispatchParams The array of parameters that will be passed to the action.
+	 * @param array $options The dispatch options for this action.
+	 * @return object Returns the response object associated with this controller.
+	 * @filter This method can be filtered.
+	 */
+	public function __invoke($request, $dispatchParams, array $options = array()) {
+		$render =& $this->_render;
+		$params = compact('request', 'dispatchParams', 'options');
+
+		return $this->_filter(__METHOD__, $params, function($self, $params) use (&$render) {
+			$dispatchParams = $params['dispatchParams'];
+
+			$action = isset($dispatchParams['action']) ? $dispatchParams['action'] : 'index';
+			$args = isset($dispatchParams['args']) ? $dispatchParams['args'] : array();
+			$result = null;
+
+			if (substr($action, 0, 1) === '_' || method_exists(__CLASS__, $action)) {
+				throw new DispatchException('Attempted to invoke a private method.');
+			}
+			if (!method_exists($self, $action)) {
+				throw new DispatchException("Action `{$action}` not found.");
+			}
+			$render['template'] = $render['template'] ?: $action;
+
+			if ($result = $self->invokeMethod($action, $args)) {
+				if (is_string($result)) {
+					$self->render(array('text' => $result));
+					return $self->response;
+				}
+				if (is_array($result)) {
+					$self->set($result);
+				}
+			}
+
+			if (!$render['hasRendered'] && $render['auto']) {
+				$self->render();
+			}
+			return $self->response;
+		});
+	}
+
+	/**
+	 * This method is used to pass along any data from the controller to the view and layout
+	 *
+	 * @param array $data sets of `<variable name> => <variable value>` to pass to view layer.
+	 * @return void
+	 */
+	public function set($data = array()) {
+		$this->_render['data'] = (array) $data + $this->_render['data'];
+	}
+
+	/**
+	 * Uses results (typically coming from a controller action) to generate content and headers for
+	 * a `Response` object.
+	 *
+	 * @see lithium\action\Controller::$_render
+	 * @param array $options An array of options, as follows:
+	 *        - `'data'`: An associative array of variables to be assigned to the template. These
+	 *          are merged on top of any variables set in `Controller::set()`.
+	 *        - `'head'`: If true, only renders the headers of the response, not the body. Defaults
+	 *          to `false`.
+	 *        - `'template'`: The name of a template, which usually matches the name of the action.
+	 *          By default, this template is looked for in the views directory of the current
+	 *          controller, i.e. given a `PostsController` object, if template is set to `'view'`,
+	 *          the template path would be `views/posts/view.html.php`. Defaults to the name of the
+	 *          action being rendered.
+	 *
+	 * The options specified here are merged with the values in the `Controller::$_render`
+	 * property. You may refer to it for other options accepted by this method.
+	 * @return object Returns the `Response` object associated with this `Controller` instance.
+	 */
+	public function render(array $options = array()) {
+		$media = $this->_classes['media'];
+		$class = get_class($this);
+		$name = preg_replace('/Controller$/', '', substr($class, strrpos($class, '\\') + 1));
+		$key = key($options);
+
+		if (isset($options['data'])) {
+			$this->set($options['data']);
+			unset($options['data']);
+		}
+		$defaults = array(
+			'status'     => null,
+			'location'   => false,
+			'data'       => null,
+			'head'       => false,
+			'controller' => Inflector::underscore($name),
+			'library'    => Libraries::get($class)
+		);
+
+		$options += $this->_render + $defaults;
+
+		if ($key && $media::type($key)) {
+			$options['type'] = $key;
+			$this->set($options[$key]);
+			unset($options[$key]);
+		}
+
+		$this->_render['hasRendered'] = true;
+		$this->response->type($options['type']);
+		$this->response->status($options['status']);
+		$this->response->headers('Location', $options['location']);
+
+		if ($options['head']) {
+			return;
+		}
+		$response = $media::render($this->response, $this->_render['data'], $options + array(
+			'request' => $this->request
+		));
+		return ($this->response = $response ?: $this->response);
+	}
+
+	/**
+	 * Creates a redirect response by calling `render()` and providing a `'location'` parameter.
+	 *
+	 * @see lithium\net\http\Router::match()
+	 * @see lithium\action\Controller::$response
+	 * @param mixed $url The location to redirect to, provided as a string relative to the root of
+	 *              the application, a fully-qualified URL, or an array of routing parameters to be
+	 *              resolved to a URL. Post-processed by `Router::match()`.
+	 * @param array $options Options when performing the redirect. Available options include:
+	 *              - `'status'` _integer_: The HTTP status code associated with the redirect.
+	 *                Defaults to `302`.
+	 *              - `'head'` _boolean_: Determines whether only headers are returned with the
+	 *                response. Defaults to `true`, in which case only headers and no body are
+	 *                returned. Set to `false` to render a body as well.
+	 *              - `'exit'` _boolean_: Exit immediately after rendering. Defaults to `false`.
+	 *                Because `redirect()` does not exit by default, you should always prefix calls
+	 *                with a `return` statement, so that the action is always immediately exited.
+	 * @return object Returns the instance of the `Response` object associated with this controller.
+	 * @filter This method can be filtered.
+	 */
+	public function redirect($url, array $options = array()) {
+		$router = $this->_classes['router'];
+		$defaults = array('location' => null, 'status' => 302, 'head' => true, 'exit' => false);
+		$options += $defaults;
+		$params = compact('url', 'options');
+
+		$this->_filter(__METHOD__, $params, function($self, $params) use ($router) {
+			$options = $params['options'];
+			$location = $options['location'] ?: $router::match($params['url'], $self->request);
+			$self->render(compact('location') + $options);
+		});
+
+		if ($options['exit']) {
+			$this->response->render();
+			$this->_stop();
+		}
+		return $this->response;
+	}
+}
+
+?>

+ 21 - 0
php-lithium/libraries/lithium/action/DispatchException.php

@@ -0,0 +1,21 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright     Copyright 2012, Union of RAD (http://union-of-rad.org)
+ * @license       http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+
+namespace lithium\action;
+
+/**
+ * This exception covers a range of scenarios that generally revolve around attempting to dispatch
+ * to something which cannot handle a request, i.e. a controller which can't be found, objects
+ * which aren't callable, or un-routable (private) controller methods.
+ */
+class DispatchException extends \RuntimeException {
+
+	protected $code = 404;
+}
+
+?>

+ 269 - 0
php-lithium/libraries/lithium/action/Dispatcher.php

@@ -0,0 +1,269 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright     Copyright 2012, Union of RAD (http://union-of-rad.org)
+ * @license       http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+
+namespace lithium\action;
+
+use lithium\util\String;
+use lithium\util\Inflector;
+use lithium\core\Libraries;
+use lithium\action\DispatchException;
+use lithium\core\ClassNotFoundException;
+
+/**
+ * `Dispatcher` is the outermost layer of the framework, responsible for both receiving the initial
+ * HTTP request and sending back a response at the end of the request's life cycle.
+ *
+ * After either receiving or instantiating a `Request` object instance, the `Dispatcher` passes that
+ * instance to the `Router`, which produces the parameters necessary to dispatch the request
+ * (unless no route matches, in which case an exception is thrown).
+ *
+ * Using these parameters, the `Dispatcher` loads and instantiates the correct `Controller` object,
+ * and passes it the `Request` object instance. The `Controller` returns a `Response` object to the
+ * `Dispatcher`, where the headers and content are rendered and sent to the browser.
+ *
+ * @see lithium\net\http\Router
+ * @see lithium\action\Request
+ * @see lithium\action\Response
+ * @see lithium\action\Controller
+ */
+class Dispatcher extends \lithium\core\StaticObject {
+
+	/**
+	 * Fully-namespaced router class reference.  Class must implement a `parse()` method,
+	 * which must return an array with (at a minimum) 'controller' and 'action' keys.
+	 *
+	 * @see lithium\net\http\Router::parse()
+	 * @var array
+	 */
+	protected static $_classes = array(
+		'router' => 'lithium\net\http\Router'
+	);
+
+	/**
+	 * Contains pre-process format strings for changing Dispatcher's behavior based on 'rules'.
+	 *
+	 * Each key in the array represents a 'rule'; if a key that matches the rule is present (and
+	 * not empty) in a route, (i.e. the result of `lithium\net\http\Router::parse()`) then the
+	 * rule's value will be applied to the route before it is dispatched.  When applying a rule, any
+	 * array elements of the flag which are present in the route will be modified
+	 * using a `lithium\util\String::insert()`-formatted string.  Alternatively,
+	 * a callback can be used to do custom transformations other than the
+	 * default `lithium\util\String::insert()`.
+	 *
+	 * For example, to implement action prefixes (i.e. `admin_index()`), set a rule named 'admin',
+	 * with a value array containing a modifier key for the `action` element of a route, i.e.:
+	 * `array('action' => 'admin_{:action}')`. Now, if the `'admin'` key is present and not empty
+	 * in the parameters returned from routing, the value of `'action'` will be rewritten per the
+	 * settings in the rule.
+	 *
+	 * Here's another example.  To support normalizing actions,
+	 * set a rule named 'action' with a value array containing a callback that uses
+	 * `lithium\util\Inflector` to camelize the action:
+	 *
+	 * {{{
+	 * use lithium\action\Dispatcher;
+	 * use lithium\util\Inflector;
+	 *
+	 * Dispatcher::config(array('rules' => array(
+	 * 	'action' => array('action' => function($params) {
+	 * 		return Inflector::camelize(strtolower($params['action']), false);
+	 * 	})
+	 * )));
+	 * }}}
+	 *
+	 * The rules can be a callback as well:
+	 *
+	 * {{{
+	 * Dispatcher::config(array('rules' => function($params) {
+	 * 	if (isset($params['admin'])) {
+	 * 		return array('special' => array('action' => 'special_{:action}'));
+	 * 	}
+	 * 	return array();
+	 * }));
+	 * }}}
+	 *
+	 * @see lithium\action\Dispatcher::config()
+	 * @see lithium\util\String::insert()
+	 */
+	protected static $_rules = array();
+
+	/**
+	 * Used to set configuration parameters for the `Dispatcher`.
+	 *
+	 * @see lithium\action\Dispatcher::$_rules
+	 * @param array $config Possible key settings are `'classes'` which sets the class dependencies
+	 *              for `Dispatcher` (i.e. `'request'` or `'router'`) and `'rules'`, which sets the
+	 *              pre-processing rules for routing parameters. For more information on the
+	 *              `'rules'` setting, see the `$_rules` property.
+	 * @return array If no parameters are passed, returns an associative array with the current
+	 *         configuration, otherwise returns `null`.
+	 */
+	public static function config(array $config = array()) {
+		if (!$config) {
+			return array('rules' => static::$_rules);
+		}
+
+		foreach ($config as $key => $val) {
+			$key = "_{$key}";
+			if (!is_array($val)) {
+				static::${$key} = $val;
+				continue;
+			}
+			if (isset(static::${$key})) {
+				static::${$key} = $val + static::${$key};
+			}
+		}
+	}
+
+	/**
+	 * Dispatches a request based on a request object (an instance or subclass of
+	 * `lithium\net\http\Request`).
+	 *
+	 * @see lithium\action\Request
+	 * @see lithium\action\Response
+	 * @param object $request An instance of a request object (usually `lithium\action\Request`)
+	 *               with HTTP request information.
+	 * @param array $options
+	 * @return mixed Returns the value returned from the callable object retrieved from
+	 *         `Dispatcher::_callable()`, which is either a string or an instance of
+	 *         `lithium\action\Response`.
+	 * @filter
+	 */
+	public static function run($request, array $options = array()) {
+		$router = static::$_classes['router'];
+		$params = compact('request', 'options');
+
+		return static::_filter(__FUNCTION__, $params, function($self, $params) use ($router) {
+			$request = $params['request'];
+			$options = $params['options'];
+
+			if (($result = $router::process($request)) instanceof Response) {
+				return $result;
+			}
+			$params = $self::applyRules($result->params);
+
+			if (!$params) {
+				throw new DispatchException('Could not route request.');
+			}
+			$callable = $self::invokeMethod('_callable', array($result, $params, $options));
+			return $self::invokeMethod('_call', array($callable, $result, $params));
+		});
+	}
+
+	/**
+	 * Attempts to apply a set of formatting rules from `$_rules` to a `$params` array, where each
+	 * formatting rule is applied if the key of the rule in `$_rules` is present and not empty in
+	 * `$params`.  Also performs sanity checking against `$params` to ensure that no value
+	 * matching a rule is present unless the rule check passes.
+	 *
+	 * @param array $params An array of route parameters to which rules will be applied.
+	 * @return array Returns the `$params` array with formatting rules applied to array values.
+	 */
+	public static function applyRules(&$params) {
+		$result = array();
+		$values = array();
+		$rules = static::$_rules;
+
+		if (!$params) {
+			return false;
+		}
+
+		if (isset($params['controller']) && is_string($params['controller'])) {
+			$controller = $params['controller'];
+
+			if (strpos($controller, '.') !== false) {
+				list($library, $controller) = explode('.', $controller);
+				$controller = $library . '.' . Inflector::camelize($controller);
+				$params += compact('library');
+			} elseif (strpos($controller, '\\') === false) {
+				$controller = Inflector::camelize($controller);
+
+				if (isset($params['library'])) {
+					$controller = "{$params['library']}.{$controller}";
+				}
+			}
+			$values = compact('controller');
+		}
+		$values += $params;
+
+		if (is_callable($rules)) {
+			$rules = $rules($params);
+		}
+		foreach ($rules as $rule => $value) {
+			if (!isset($values[$rule])) {
+				continue;
+			}
+			foreach ($value as $k => $v) {
+				if (is_callable($v)) {
+					$result[$k] = $v($values);
+					continue;
+				}
+				$match = preg_replace('/\{:\w+\}/', '@', $v);
+				$match = preg_replace('/@/', '.+', preg_quote($match, '/'));
+				if (preg_match('/' . $match . '/i', $values[$k])) {
+					continue;
+				}
+				$result[$k] = String::insert($v, $values);
+			}
+		}
+		return $result + $values;
+	}
+
+	/**
+	 * Accepts parameters generated by the `Router` class in `Dispatcher::run()`, and produces a
+	 * callable controller object. By default, this method uses the `'controller'` path lookup
+	 * configuration in `Libraries::locate()` to return a callable object.
+	 *
+	 * @param object $request The instance of the `Request` class either passed into or generated by
+	 *               `Dispatcher::run()`.
+	 * @param array $params The parameter array generated by routing the request.
+	 * @param array $options Not currently implemented.
+	 * @return object Returns a callable object which the request will be routed to.
+	 * @filter
+	 */
+	protected static function _callable($request, $params, $options) {
+		$params = compact('request', 'params', 'options');
+
+		return static::_filter(__FUNCTION__, $params, function($self, $params) {
+			$options = array('request' => $params['request']) + $params['options'];
+			$controller = $params['params']['controller'];
+
+			try {
+				return Libraries::instance('controllers', $controller, $options);
+			} catch (ClassNotFoundException $e) {
+				throw new DispatchException("Controller `{$controller}` not found.", null, $e);
+			}
+		});
+	}
+
+	/**
+	 * Invokes the callable object returned by `_callable()`, and returns the results, usually a
+	 * `Response` object instance.
+	 *
+	 * @see lithium\action
+	 * @param object $callable Typically a closure or instance of `lithium\action\Controller`.
+	 * @param object $request An instance of `lithium\action\Request`.
+	 * @param array $params An array of parameters to pass to `$callable`, along with `$request`.
+	 * @return mixed Returns the return value of `$callable`, usually an instance of
+	 *         `lithium\action\Response`.
+	 * @throws lithium\action\DispatchException Throws an exception if `$callable` is not a
+	 *         `Closure`, or does not declare the PHP magic `__invoke()` method.
+	 * @filter
+	 */
+	protected static function _call($callable, $request, $params) {
+		$params = compact('callable', 'request', 'params');
+		return static::_filter(__FUNCTION__, $params, function($self, $params) {
+			if (is_callable($callable = $params['callable'])) {
+				return $callable($params['request'], $params['params']);
+			}
+			throw new DispatchException('Result not callable.');
+		});
+	}
+}
+
+?>

+ 640 - 0
php-lithium/libraries/lithium/action/Request.php

@@ -0,0 +1,640 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright     Copyright 2012, Union of RAD (http://union-of-rad.org)
+ * @license       http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+
+namespace lithium\action;
+
+use lithium\util\Set;
+use lithium\util\Validator;
+
+/**
+ * A `Request` object is passed into the `Dispatcher`, and is responsible for identifying and
+ * storing all the information about an HTTP request made to an application,  including status,
+ * headers, and any GET, POST or PUT data, as well as any data returned from the
+ * `Router`, after the `Request` object has been matched against a `Route`. Includes a property
+ * accessor method (`__get()`) which allows any parameters returned from routing to be accessed as
+ * properties of the `Request` object.
+ *
+ * @see lithium\action\Dispatcher
+ * @see lithium\action\Controller
+ * @see lithium\net\http\Router
+ * @see lithium\net\http\Route
+ * @see lithium\action\Request::__get()
+ */
+class Request extends \lithium\net\http\Request {
+
+	/**
+	 * Current url of request.
+	 *
+	 * @var string
+	 */
+	public $url = null;
+
+	/**
+	 * Params for request.
+	 *
+	 * @var array
+	 */
+	public $params = array();
+
+	/**
+	 * Route parameters that should persist when generating URLs in this request context.
+	 *
+	 * @var array
+	 */
+	public $persist = array();
+
+	/**
+	 * POST data.
+	 *
+	 * @var data
+	 */
+	public $data = array();
+
+	/**
+	 * GET data.
+	 *
+	 * @var string
+	 */
+	public $query = array();
+
+	/**
+	 * Base path.
+	 *
+	 * @var string
+	 */
+	protected $_base = null;
+
+	/**
+	 * Holds the environment variables for the request. Retrieved with env().
+	 *
+	 * @var array
+	 * @see lithium\action\Request::env()
+	 */
+	protected $_env = array();
+
+	/**
+	 * Classes used by `Request`.
+	 *
+	 * @var array
+	 */
+	protected $_classes = array('media' => 'lithium\net\http\Media');
+
+	/**
+	 * If POST / PUT data is coming from an input stream (rather than `$_POST`), this specified
+	 * where to read it from.
+	 *
+	 * @var stream
+	 */
+	protected $_stream = null;
+
+	/**
+	 * Options used to detect request type.
+	 *
+	 * @see lithium\action\Request::detect()
+	 * @var array
+	 */
+	protected $_detectors = array(
+		'mobile'  => array('HTTP_USER_AGENT', null),
+		'ajax'    => array('HTTP_X_REQUESTED_WITH', 'XMLHttpRequest'),
+		'flash'   => array('HTTP_USER_AGENT', 'Shockwave Flash'),
+		'ssl'     => 'HTTPS',
+		'get'     => array('REQUEST_METHOD', 'GET'),
+		'post'    => array('REQUEST_METHOD', 'POST'),
+		'put'     => array('REQUEST_METHOD', 'PUT'),
+		'delete'  => array('REQUEST_METHOD', 'DELETE'),
+		'head'    => array('REQUEST_METHOD', 'HEAD'),
+		'options' => array('REQUEST_METHOD', 'OPTIONS')
+	);
+
+	/**
+	 * Auto configuration properties.
+	 *
+	 * @var array
+	 */
+	protected $_autoConfig = array(
+		'classes' => 'merge', 'env', 'detectors' => 'merge', 'base', 'type', 'stream'
+	);
+
+	/**
+	 * Contains an array of content-types, sorted by quality (the priority which the browser
+	 * requests each type).
+	 *
+	 * @var array
+	 */
+	protected $_acceptContent = array();
+
+	/**
+	 * Holds the value of the current locale, set through the `locale()` method.
+	 *
+	 * @var string
+	 */
+	protected $_locale = null;
+
+	/**
+	 * Initialize request object, pulling request data from superglobals.
+	 *
+	 * Defines an artificial `'PLATFORM'` environment variable as either
+	 * `'IIS'`, `'CGI'` or `null` to allow checking for the SAPI in a
+	 * normalized way.
+	 *
+	 * @return void
+	 */
+	protected function _init() {
+		parent::_init();
+
+		$mobile = array(
+			'iPhone', 'MIDP', 'AvantGo', 'BlackBerry', 'J2ME', 'Opera Mini', 'DoCoMo', 'NetFront',
+			'Nokia', 'PalmOS', 'PalmSource', 'portalmmm', 'Plucker', 'ReqwirelessWeb', 'iPod',
+			'SonyEricsson', 'Symbian', 'UP\.Browser', 'Windows CE', 'Xiino', 'Android'
+		);
+		if (!empty($this->_config['detectors']['mobile'][1])) {
+			$mobile = array_merge($mobile, (array) $this->_config['detectors']['mobile'][1]);
+		}
+		$this->_detectors['mobile'][1] = $mobile;
+		$defaults = array('REQUEST_METHOD' => 'GET', 'CONTENT_TYPE' => 'text/html');
+		$this->_env += (array) $_SERVER + (array) $_ENV + $defaults;
+		$envs = array('isapi' => 'IIS', 'cgi' => 'CGI', 'cgi-fcgi' => 'CGI');
+		$this->_env['PLATFORM'] = isset($envs[PHP_SAPI]) ? $envs[PHP_SAPI] : null;
+		$this->_base = $this->_base();
+		$this->url = $this->_url();
+
+		if (!empty($this->_config['query'])) {
+			$this->query = $this->_config['query'];
+		}
+		if (isset($_GET)) {
+			$this->query += $_GET;
+		}
+		if (!empty($this->_config['data'])) {
+			$this->data = $this->_config['data'];
+		}
+		if (isset($_POST)) {
+			$this->data += $_POST;
+		}
+		if (isset($this->data['_method'])) {
+			$this->_env['HTTP_X_HTTP_METHOD_OVERRIDE'] = strtoupper($this->data['_method']);
+			unset($this->data['_method']);
+		}
+		if (!empty($this->_env['HTTP_X_HTTP_METHOD_OVERRIDE'])) {
+			$this->_env['REQUEST_METHOD'] = $this->_env['HTTP_X_HTTP_METHOD_OVERRIDE'];
+		}
+		$type = $this->type($this->_config['type'] ?: $this->env('CONTENT_TYPE'));
+		$this->method = $method = strtoupper($this->_env['REQUEST_METHOD']);
+		$hasBody = in_array($method, array('POST', 'PUT', 'PATCH'));
+
+		if (!$this->data && $hasBody && $type !== 'html') {
+			$this->_stream = $this->_stream ?: fopen('php://input', 'r');
+			$media = $this->_classes['media'];
+			$this->data = (array) $media::decode($type, stream_get_contents($this->_stream));
+			fclose($this->_stream);
+		}
+		$this->data = Set::merge((array) $this->data, $this->_parseFiles());
+	}
+
+	/**
+	 * Allows request parameters to be accessed as object properties, i.e. `$this->request->action`
+	 * instead of `$this->request->params['action']`.
+	 *
+	 * @see lithium\action\Request::$params
+	 * @param string $name The property name/parameter key to return.
+	 * @return mixed Returns the value of `$params[$name]` if it is set, otherwise returns null.
+	 */
+	public function __get($name) {
+		if (isset($this->params[$name])) {
+			return $this->params[$name];
+		}
+	}
+
+	/**
+	 * Allows request parameters to be checked using short-hand notation. See the `__get()` method
+	 * for more details.
+	 *
+	 * @see lithium\action\Request::__get()
+	 * @param string $name The name of the request parameter to check.
+	 * @return boolean Returns true if the key in `$name` is set in the `$params` array, otherwise
+	 *         `false`.
+	 */
+	public function __isset($name) {
+		return isset($this->params[$name]);
+	}
+
+	/**
+	 * Queries PHP's environment settings, and provides an abstraction for standardizing expected
+	 * environment values across varying platforms, as well as specify custom environment flags.
+	 *
+	 * @param string $key The environment variable required.
+	 * @return string The requested variables value.
+	 * @todo Refactor to lazy-load environment settings
+	 */
+	public function env($key) {
+		if (strtolower($key) === 'base') {
+			return $this->_base;
+		}
+
+		if ($key === 'SCRIPT_NAME' && !isset($this->_env['SCRIPT_NAME'])) {
+			if ($this->_env['PLATFORM'] === 'CGI' || isset($this->_env['SCRIPT_URL'])) {
+				$key = 'SCRIPT_URL';
+			}
+		}
+
+		$val = array_key_exists($key, $this->_env) ? $this->_env[$key] : getenv($key);
+		$this->_env[$key] = $val;
+
+		if ($key == 'REMOTE_ADDR') {
+			$https = array('HTTP_X_FORWARDED_FOR', 'HTTP_PC_REMOTE_ADDR', 'HTTP_X_REAL_IP');
+			foreach ($https as $altKey) {
+				if ($addr = $this->env($altKey)) {
+					$val = $addr;
+					break;
+				}
+			}
+		}
+
+		if ($val !== null && $val !== false && $key !== 'HTTPS') {
+			return $val;
+		}
+
+		switch ($key) {
+			case 'HTTPS':
+				if (isset($this->_env['SCRIPT_URI'])) {
+					return (strpos($this->_env['SCRIPT_URI'], 'https://') === 0);
+				}
+				if (isset($this->_env['HTTPS'])) {
+					return (!empty($this->_env['HTTPS']) && $this->_env['HTTPS'] !== 'off');
+				}
+				return false;
+			case 'SERVER_ADDR':
+				if (empty($this->_env['SERVER_ADDR']) && !empty($this->_env['LOCAL_ADDR'])) {
+					return $this->_env['LOCAL_ADDR'];
+				}
+				return $this->_env['SERVER_ADDR'];
+			case 'SCRIPT_FILENAME':
+				if ($this->_env['PLATFORM'] == 'IIS') {
+					return str_replace('\\\\', '\\', $this->env('PATH_TRANSLATED'));
+				}
+				return $this->env('DOCUMENT_ROOT') . $this->env('PHP_SELF');
+			case 'DOCUMENT_ROOT':
+				$fileName = $this->env('SCRIPT_FILENAME');
+				$offset = (!strpos($this->env('SCRIPT_NAME'), '.php')) ? 4 : 0;
+				$offset = strlen($fileName) - (strlen($this->env('SCRIPT_NAME')) + $offset);
+				return substr($fileName, 0, $offset);
+			case 'PHP_SELF':
+				return str_replace('\\', '/', str_replace(
+					$this->env('DOCUMENT_ROOT'), '', $this->env('SCRIPT_FILENAME')
+				));
+			case 'CGI':
+			case 'CGI_MODE':
+				return ($this->_env['PLATFORM'] === 'CGI');
+			case 'HTTP_BASE':
+				return preg_replace('/^([^.])*/i', null, $this->_env['HTTP_HOST']);
+		}
+	}
+
+	/**
+	 * Returns information about the type of content that the client is requesting.
+	 *
+	 * @see lithium\net\http\Media::negotiate()
+	 * @param $type mixed If not specified, returns the media type name that the client prefers,
+	 *              using content negotiation. If a media type name (string) is passed, returns
+	 *              `true` or `false`, indicating whether or not that type is accepted by the client
+	 *              at all. If `true`, returns the raw content types from the `Accept` header,
+	 *              parsed into an array and sorted by client preference.
+	 * @return string Returns a simple type name if the type is registered (i.e. `'json'`), or
+	 *         a fully-qualified content-type if not (i.e. `'image/jpeg'`), or a boolean or array,
+	 *         depending on the value of `$type`.
+	 */
+	public function accepts($type = null) {
+		if ($type === true) {
+			return $this->_parseAccept();
+		}
+		if (!$type && isset($this->params['type'])) {
+			return $this->params['type'];
+		}
+		$media = $this->_classes['media'];
+		return $media::negotiate($this) ?: 'html';
+	}
+
+	protected function _parseAccept() {
+		if ($this->_acceptContent) {
+			return $this->_acceptContent;
+		}
+		$accept = $this->env('HTTP_ACCEPT');
+		$accept = (preg_match('/[a-z,-]/i', $accept)) ? explode(',', $accept) : array('text/html');
+
+		foreach (array_reverse($accept) as $i => $type) {
+			unset($accept[$i]);
+			list($type, $q) = (explode(';q=', $type, 2) + array($type, 1.0 + $i / 100));
+			$accept[$type] = ($type === '*/*') ? 0.1 : floatval($q);
+		}
+		arsort($accept, SORT_NUMERIC);
+
+		if (isset($accept['application/xhtml+xml']) && $accept['application/xhtml+xml'] >= 1) {
+			unset($accept['application/xml']);
+		}
+		$media = $this->_classes['media'];
+
+		if (isset($this->params['type']) && ($handler = $media::type($this->params['type']))) {
+			if (isset($handler['content'])) {
+				$type = (array) $handler['content'];
+				$accept = array(current($type) => 1) + $accept;
+			}
+		}
+		return $this->_acceptContent = array_keys($accept);
+	}
+
+	/**
+	 * This method allows easy extraction of any request data using a prefixed key syntax. By
+	 * passing keys in the form of `'prefix:key'`, it is possible to query different information of
+	 * various different types, including GET and POST data, and server environment variables. The
+	 * full list of prefixes is as follows:
+	 *
+	 * - `'data'`: Retrieves values from POST data.
+	 * - `'params'`: Retrieves query parameters returned from the routing system.
+	 * - `'query'`: Retrieves values from GET data.
+	 * - `'env'`: Retrieves values from the server or environment, such as `'env:https'`, or custom
+	 *   environment values, like `'env:base'`. See the `env()` method for more info.
+	 * - `'http'`: Retrieves header values (i.e. `'http:accept'`), or the HTTP request method (i.e.
+	 *   `'http:method'`).
+	 *
+	 * This method is used in several different places in the framework in order to provide the
+	 * ability to act conditionally on different aspects of the request. See `Media::type()` (the
+	 * section on content negotiation) and the routing system for more information.
+	 *
+	 *  _Note_: All keys should be _lower-cased_, even when getting HTTP headers.
+	 * @see lithium\action\Request::env()
+	 * @see lithium\net\http\Media::type()
+	 * @see lithium\net\http\Router
+	 * @param string $key A prefixed key indicating what part of the request data the requested
+	 *               value should come from, and the name of the value to retrieve, in lower case.
+	 * @return string Returns the value of a GET, POST, routing or environment variable, or an
+	 *         HTTP header or method name.
+	 */
+	public function get($key) {
+		list($var, $key) = explode(':', $key);
+
+		switch (true) {
+			case in_array($var, array('params', 'data', 'query')):
+				return isset($this->{$var}[$key]) ? $this->{$var}[$key] : null;
+			case ($var === 'env'):
+				return $this->env(strtoupper($key));
+			case ($var === 'http' && $key === 'method'):
+				return $this->env('REQUEST_METHOD');
+			case ($var === 'http'):
+				return $this->env('HTTP_' . strtoupper($key));
+		}
+	}
+
+	/**
+	 * Provides a simple syntax for making assertions about the properties of a request.
+	 * By default, the `Request` object is configured with several different types of assertions,
+	 * which are individually known as _detectors_. Detectors are invoked by calling the `is()` and
+	 * passing the name of the detector flag, i.e. `$request->is('<name>')`, which returns `true` or
+	 * `false`, depending on whether or not the the properties (usually headers or data) contained
+	 * in the request match the detector. The default detectors include the following:
+	 *
+	 * - `'mobile'`: Uses a regular expression to match common mobile browser user agents.
+	 * - `'ajax'`: Checks to see if the `X-Requested-With` header is present, and matches the value
+	 *    `'XMLHttpRequest'`.
+	 * - `'flash'`: Checks to see if the user agent is `'Shockwave Flash'`.
+	 * - `'ssl'`: Verifies that the request is SSL-secured.
+	 * - `'get'` / `'post'` / `'put'` / `'delete'` / `'head'` / `'options'`: Checks that the HTTP
+	 *   request method matches the one specified.
+	 *
+	 * In addition to the above, this method also accepts media type names (see `Media::type()`) to
+	 * make assertions against the format of the request body (for POST or PUT requests), i.e.
+	 * `$request->is('json')`. This will return `true` if the client has made a POST request with
+	 * JSON data.
+	 *
+	 * For information about adding custom detectors or overriding the ones in the core, see the
+	 * `detect()` method.
+	 *
+	 * While these detectors are useful in controllers or other similar contexts, they're also
+	 * useful when performing _content negotiation_, which is the process of modifying the response
+	 * format to suit the client (see the `'conditions'` field of the `$options` parameter in
+	 * `Media::type()`).
+	 *
+	 * @see lithium\action\Request::detect()
+	 * @see lithium\net\http\Media::type()
+	 * @param string $flag The name of the flag to check, which should be the name of a valid
+	 *               detector (that is either built-in or defined with `detect()`).
+	 * @return boolean Returns `true` if the detector check succeeds (see the details for the
+	 *         built-in detectors above, or `detect()`), otherwise `false`.
+	 */
+	public function is($flag) {
+		$media = $this->_classes['media'];
+
+		if (!isset($this->_detectors[$flag])) {
+			if (!in_array($flag, $media::types())) {
+				return false;
+			}
+			return $this->type() == $flag;
+		}
+		$detector = $this->_detectors[$flag];
+
+		if (!is_array($detector) && is_callable($detector)) {
+			return $detector($this);
+		}
+		if (!is_array($detector)) {
+			return (boolean) $this->env($detector);
+		}
+		list($key, $check) = $detector + array('', '');
+
+		if (is_array($check)) {
+			$check = '/' . join('|', $check) . '/i';
+		}
+		if (Validator::isRegex($check)) {
+			return (boolean) preg_match($check, $this->env($key));
+		}
+		return ($this->env($key) == $check);
+	}
+
+	/**
+	 * Sets/Gets the content type. If `'type'` is null, the method will attempt to determine the
+	 * type from the params, then from the environment setting
+	 *
+	 * @param string $type a full content type i.e. `'application/json'` or simple name `'json'`
+	 * @return string A simple content type name, i.e. `'html'`, `'xml'`, `'json'`, etc., depending
+	 *         on the content type of the request.
+	 */
+	public function type($type = null) {
+		if (!$type && !empty($this->params['type'])) {
+			$type = $this->params['type'];
+		}
+		return parent::type($type);
+	}
+
+	/**
+	 * Creates a _detector_ used with `Request::is()`.  A detector is a boolean check that is
+	 * created to determine something about a request.
+	 *
+	 * A detector check can be either an exact string match or a regular expression match against a
+	 * header or environment variable. A detector check can also be a closure that accepts the
+	 * `Request` object instance as a parameter.
+	 *
+	 * For example, to detect whether a request is from an iPhone, you can do the following:
+	 * {{{ embed:lithium\tests\cases\action\RequestTest::testDetect(11-12) }}}
+	 *
+	 * @see lithium\action\Request::is()
+	 * @param string $flag The name of the detector check. Used in subsequent calls to
+	 *               `Request::is()`.
+	 * @param mixed $detector Detectors can be specified in four different ways:
+	 *              - The name of an HTTP header or environment variable. If a string, calling the
+	 *                detector will check that the header or environment variable exists and is set
+	 *                to a non-empty value.
+	 *              - A two-element array containing a header/environment variable name, and a value
+	 *                to match against. The second element of the array must be an exact match to
+	 *                the header or variable value.
+	 *              - A two-element array containing a header/environment variable name, and a
+	 *                regular expression that matches against the value, as in the example above.
+	 *              - A closure which accepts an instance of the `Request` object and returns a
+	 *                boolean value.
+	 * @return void
+	 */
+	public function detect($flag, $detector = null) {
+		if (is_array($flag)) {
+			$this->_detectors = $flag + $this->_detectors;
+		} else {
+			$this->_detectors[$flag] = $detector;
+		}
+	}
+
+	/**
+	 * Gets the referring URL of this request.
+	 *
+	 * @param string $default Default URL to use if HTTP_REFERER cannot be read from headers.
+	 * @param boolean $local If true, restrict referring URLs to local server.
+	 * @return string Referring URL.
+	 */
+	public function referer($default = null, $local = false) {
+		if ($ref = $this->env('HTTP_REFERER')) {
+			if (!$local) {
+				return $ref;
+			}
+			if (strpos($ref, '://') === false) {
+				return $ref;
+			}
+		}
+		return ($default != null) ? $default : '/';
+	}
+
+	/**
+	 * Overrides `lithium\net\http\Request::to()` to provide the correct options for generating
+	 * URLs. For information about this method, see the parent implementation.
+	 *
+	 * @see lithium\net\http\Request::to()
+	 * @param string $format The format to convert to.
+	 * @param array $options Override options.
+	 * @return mixed The return value type depends on `$format`.
+	 */
+	public function to($format, array $options = array()) {
+		$defaults = array(
+			'scheme' => $this->env('HTTPS') ? 'https' : 'http',
+			'host' => $this->env('HTTP_HOST'),
+			'path' => $this->_base . '/' . $this->url
+		);
+		return parent::to($format, $options + $defaults);
+	}
+
+	/**
+	 * Sets or returns the current locale string. For more information, see
+	 * "[Globalization](http://lithify.me/docs/manual/07_globalization)" in the manual.
+	 *
+	 * @param string $locale An optional locale string like `'en'`, `'en_US'` or `'de_DE'`. If
+	 *               specified, will overwrite the existing locale.
+	 * @return Returns the currently set locale string.
+	 */
+	public function locale($locale = null) {
+		if ($locale) {
+			$this->_locale = $locale;
+		}
+		if ($this->_locale) {
+			return $this->_locale;
+		}
+		if (isset($this->params['locale'])) {
+			return $this->params['locale'];
+		}
+	}
+
+	/**
+	 * Find the base path of the current request.
+	 *
+	 * @todo Replace string directory names with configuration.
+	 * @return string
+	 */
+	protected function _base() {
+		if (isset($this->_base)) {
+			return $this->_base;
+		}
+		$base = str_replace('\\', '/', dirname($this->env('PHP_SELF')));
+		return rtrim(str_replace(array("/app/webroot", '/webroot'), '', $base), '/');
+	}
+
+	/**
+	 * Return the full url of the current request.
+	 *
+	 * @return string
+	 */
+	protected function _url() {
+		if (isset($this->_config['url'])) {
+			return rtrim($this->_config['url'], '/');
+		}
+		if (!empty($_GET['url'])) {
+			return rtrim($_GET['url'], '/');
+		}
+		if ($uri = $this->env('REQUEST_URI')) {
+			list($uri) = explode('?', $uri, 2);
+			$base = '/^' . preg_quote($this->env('base'), '/') . '/';
+			return trim(preg_replace($base, '', $uri), '/') ?: '/';
+		}
+		return '/';
+	}
+
+	/**
+	 * Normalize the data in $_FILES
+	 *
+	 * @return array
+	 */
+	protected function _parseFiles() {
+		if (isset($_FILES) && $_FILES) {
+			$result = array();
+
+			$normalize = function($key, $value) use ($result, &$normalize){
+				foreach ($value as $param => $content) {
+					foreach ($content as $num => $val) {
+						if (is_numeric($num)) {
+							$result[$key][$num][$param] = $val;
+							continue;
+						}
+						if (is_array($val)) {
+							foreach ($val as $next => $one) {
+								$result[$key][$num][$next][$param] = $one;
+							}
+							continue;
+						}
+						$result[$key][$num][$param] = $val;
+					}
+				}
+				return $result;
+			};
+			foreach ($_FILES as $key => $value) {
+				if (isset($value['name'])) {
+					if (is_string($value['name'])) {
+						$result[$key] = $value;
+						continue;
+					}
+					if (is_array($value['name'])) {
+						$result += $normalize($key, $value);
+					}
+				}
+			}
+			return $result;
+		}
+		return array();
+	}
+}
+
+?>

+ 175 - 0
php-lithium/libraries/lithium/action/Response.php

@@ -0,0 +1,175 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright     Copyright 2012, Union of RAD (http://union-of-rad.org)
+ * @license       http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+
+namespace lithium\action;
+
+/**
+ * A `Response` object is typically instantiated automatically by the `Controller`. It is assigned
+ * any headers set in the course of the request, as well as any content rendered by the
+ * `Controller`. Once completed, the `Controller` returns the `Response` object to the `Dispatcher`.
+ *
+ * The `Response` object is responsible for writing its body content to output, and writing any
+ * headers to the browser.
+ *
+ * @see lithium\action\Dispatcher
+ * @see lithium\action\Controller
+ */
+class Response extends \lithium\net\http\Response {
+
+	/**
+	 * Classes used by Response.
+	 *
+	 * @var array
+	 */
+	protected $_classes = array(
+		'router' => 'lithium\net\http\Router',
+		'media' => 'lithium\net\http\Media'
+	);
+
+	protected $_autoConfig = array('classes' => 'merge');
+
+	public function __construct(array $config = array()) {
+		$defaults = array(
+			'buffer' => 8192,
+			'location' => null,
+			'status' => 0,
+			'request' => null,
+			'decode' => false
+		);
+		parent::__construct($config + $defaults);
+	}
+
+	protected function _init() {
+		parent::_init();
+		$config = $this->_config;
+		$this->status($config['status']);
+		unset($this->_config['status']);
+
+		if ($config['location']) {
+			$classes = $this->_classes;
+			$location = $classes['router']::match($config['location'], $config['request']);
+			$this->headers('Location', $location);
+		}
+	}
+
+	/**
+	 * Controls how or whether the client browser and web proxies should cache this response.
+	 *
+	 * @param mixed $expires This can be a Unix timestamp indicating when the page expires, or a
+	 *              string indicating the relative time offset that a page should expire, i.e.
+	 *              `"+5 hours". Finally, `$expires` can be set to `false` to completely disable
+	 *              browser or proxy caching.
+	 * @return void
+	 */
+	public function cache($expires) {
+		if ($expires === false) {
+			return $this->headers(array(
+				'Expires' => 'Mon, 26 Jul 1997 05:00:00 GMT',
+				'Cache-Control' => array(
+					'no-store, no-cache, must-revalidate',
+					'post-check=0, pre-check=0',
+					'max-age=0'
+				),
+				'Pragma' => 'no-cache'
+			));
+		}
+		$expires = is_int($expires) ? $expires : strtotime($expires);
+
+		return $this->headers(array(
+			'Expires' => gmdate('D, d M Y H:i:s', $expires) . ' GMT',
+			'Cache-Control' => 'max-age=' . ($expires - time()),
+			'Pragma' => 'cache'
+		));
+	}
+
+	/**
+	 * Sets/Gets the content type. If `'type'` is null, the method will attempt to determine the
+	 * type from the params, then from the environment setting
+	 *
+	 * @param string $type a full content type i.e. `'application/json'` or simple name `'json'`
+	 * @return string A simple content type name, i.e. `'html'`, `'xml'`, `'json'`, etc., depending
+	 *         on the content type of the request.
+	 */
+	public function type($type = null) {
+		if ($type === null && $this->_type === null) {
+			$type = 'html';
+		}
+		return parent::type($type);
+	}
+
+	/**
+	 * Render a response by writing headers and output. Output is echoed in chunks because of an
+	 * issue where `echo` time increases exponentially on long message bodies.
+	 *
+	 * @return void
+	 */
+	public function render() {
+		$code = null;
+		$hasLocation = (isset($this->headers['location']) || isset($this->headers['Location']));
+
+		if ($hasLocation && $this->status['code'] === 200) {
+			$code = 302;
+		}
+		$this->_writeHeader($this->status($code) ?: $this->status(500));
+
+		foreach ($this->headers as $name => $value) {
+			$key = strtolower($name);
+
+			if ($key == 'location') {
+				$this->_writeHeader("Location: {$value}", $this->status['code']);
+			} elseif ($key == 'download') {
+				$this->_writeHeader('Content-Disposition: attachment; filename="' . $value . '"');
+			} elseif (is_array($value)) {
+				$this->_writeHeader(
+					array_map(function($v) use ($name) { return "{$name}: {$v}"; }, $value)
+				);
+			} elseif (!is_numeric($name)) {
+				$this->_writeHeader("{$name}: {$value}");
+			}
+		}
+		if ($code == 302 || $code == 204) {
+			return;
+		}
+		$chunked = $this->body(null, $this->_config);
+
+		foreach ($chunked as $chunk) {
+			echo $chunk;
+		}
+	}
+
+	/**
+	 * Casts the Response object to a string.  This doesn't actually return a string, but does
+	 * a direct render and returns null.
+	 *
+	 * @return string An empty string.
+	 */
+	public function __toString() {
+		$this->render();
+		return '';
+	}
+
+	/**
+	 * Writes raw headers to output.
+	 *
+	 * @param string|array $header Either a raw header string, or an array of header strings. Use
+	 *        an array if a single header must be written multiple times with different values.
+	 *        Otherwise, additional values for duplicate headers will overwrite previous values.
+	 * @param integer $code Optional. If present, forces a specific HTTP response code.  Used
+	 *        primarily in conjunction with the 'Location' header.
+	 * @return void
+	 */
+	protected function _writeHeader($header, $code = null) {
+		if (is_array($header)) {
+			array_map(function($h) { header($h, false); }, $header);
+			return;
+		}
+		$code ? header($header, true, $code) : header($header, true);
+	}
+}
+
+?>

+ 1 - 0
php-lithium/libraries/lithium/action/readme.md

@@ -0,0 +1 @@
+The `action` namespace relies on `lithium\http`, and includes classes required to route and dispatch HTTP requests.

+ 205 - 0
php-lithium/libraries/lithium/analysis/Debugger.php

@@ -0,0 +1,205 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright     Copyright 2012, Union of RAD (http://union-of-rad.org)
+ * @license       http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+
+namespace lithium\analysis;
+
+use ReflectionClass;
+use lithium\util\String;
+use lithium\analysis\Inspector;
+
+/**
+ * The `Debugger` class provides basic facilities for generating and rendering meta-data about the
+ * state of an application in its current context.
+ */
+class Debugger extends \lithium\core\StaticObject {
+
+	/**
+	 * Used for temporary closure caching.
+	 *
+	 * @see lithium\analysis\Debugger::_closureDef()
+	 * @var array
+	 */
+	protected static $_closureCache = array();
+
+	/**
+	 * Outputs a stack trace based on the supplied options.
+	 *
+	 * @param array $options Format for outputting stack trace. Available options are:
+	 *        - `'args'`: A boolean indicating if arguments should be included.
+	 *        - `'depth'`: The maximum depth of the trace.
+	 *        - `'format'`: Either `null`, `'points'` or `'array'`.
+	 *        - `'includeScope'`: A boolean indicating if items within scope
+	 *           should be included.
+	 *        - `'scope'`: Scope for items to include.
+	 *        - `'start'`: The depth to start with.
+	 *        - `'trace'`: A trace to use instead of generating one.
+	 * @return string Stack trace formatted according to `'format'` option.
+	 */
+	public static function trace(array $options = array()) {
+		$defaults = array(
+			'depth' => 999,
+			'format' => null,
+			'args' => false,
+			'start' => 0,
+			'scope' => array(),
+			'trace' => array(),
+			'includeScope' => true,
+			'closures' => true
+		);
+		$options += $defaults;
+
+		$backtrace = $options['trace'] ?: debug_backtrace();
+		$scope = $options['scope'];
+		$count = count($backtrace);
+		$back = array();
+		$traceDefault = array(
+			'line' => '??', 'file' => '[internal]', 'class' => null, 'function' => '[main]'
+		);
+
+		for ($i = $options['start']; $i < $count && $i < $options['depth']; $i++) {
+			$trace = array_merge(array('file' => '[internal]', 'line' => '??'), $backtrace[$i]);
+			$function = '[main]';
+
+			if (isset($backtrace[$i + 1])) {
+				$next = $backtrace[$i + 1] + $traceDefault;
+				$function = $next['function'];
+
+				if (!empty($next['class'])) {
+					$function = $next['class'] . '::' . $function . '(';
+					if ($options['args'] && isset($next['args'])) {
+						$args = array_map(array('static', 'export'), $next['args']);
+						$function .= join(', ', $args);
+					}
+					$function .= ')';
+				}
+			}
+
+			if ($options['closures'] && strpos($function, '{closure}') !== false) {
+				$function = static::_closureDef($backtrace[$i], $function);
+			}
+			if (in_array($function, array('call_user_func_array', 'trigger_error'))) {
+				continue;
+			}
+			$trace['functionRef'] = $function;
+
+			if ($options['format'] === 'points' && $trace['file'] !== '[internal]') {
+				$back[] = array('file' => $trace['file'], 'line' => $trace['line']);
+			} elseif (is_string($options['format']) && $options['format'] != 'array') {
+				$back[] = String::insert($options['format'], array_map(
+					function($data) { return is_object($data) ? get_class($data) : $data; },
+					$trace
+				));
+			} elseif (empty($options['format'])) {
+				$back[] = $function . ' - ' . $trace['file'] . ', line ' . $trace['line'];
+			} else {
+				$back[] = $trace;
+			}
+
+			if (!empty($scope) && array_intersect_assoc($scope, $trace) == $scope) {
+				if (!$options['includeScope']) {
+					$back = array_slice($back, 0, count($back) - 1);
+				}
+				break;
+			}
+		}
+
+		if ($options['format'] === 'array' || $options['format'] === 'points') {
+			return $back;
+		}
+		return join("\n", $back);
+	}
+
+	/**
+	 * Returns a parseable string representation of a variable.
+	 *
+	 * @param mixed $var The variable to export.
+	 * @return string The exported contents.
+	 */
+	public static function export($var) {
+		$export = var_export($var, true);
+
+		if (is_array($var)) {
+			$replace = array(" (", " )", "  ", " )", "=> \n\t");
+			$with = array("(", ")", "\t", "\t)", "=> ");
+			$export = str_replace($replace, $with, $export);
+		}
+		return $export;
+	}
+
+	/**
+	 * Locates original location of closures.
+	 *
+	 * @param mixed $reference File or class name to inspect.
+	 * @param integer $callLine Line number of class reference.
+	 */
+	protected static function _definition($reference, $callLine) {
+		if (file_exists($reference)) {
+			foreach (array_reverse(token_get_all(file_get_contents($reference))) as $token) {
+				if (!is_array($token) || $token[2] > $callLine) {
+					continue;
+				}
+				if ($token[0] === T_FUNCTION) {
+					return $token[2];
+				}
+			}
+			return;
+		}
+		list($class, $method) = explode('::', $reference);
+
+		if (!class_exists($class)) {
+			return;
+		}
+
+		$classRef = new ReflectionClass($class);
+		$methodInfo = Inspector::info($reference);
+		$methodDef = join("\n", Inspector::lines($classRef->getFileName(), range(
+			$methodInfo['start'] + 1, $methodInfo['end'] - 1
+		)));
+
+		foreach (array_reverse(token_get_all("<?php {$methodDef} ?>")) as $token) {
+			if (!is_array($token) || $token[2] > $callLine) {
+				continue;
+			}
+			if ($token[0] === T_FUNCTION) {
+				return $token[2] + $methodInfo['start'];
+			}
+		}
+	}
+
+	protected static function _closureDef($frame, $function) {
+		$reference = '::';
+		$frame += array('file' => '??', 'line' => '??');
+		$cacheKey = "{$frame['file']}@{$frame['line']}";
+
+		if (isset(static::$_closureCache[$cacheKey])) {
+			return static::$_closureCache[$cacheKey];
+		}
+
+		if ($class = Inspector::classes(array('file' => $frame['file']))) {
+			foreach (Inspector::methods(key($class), 'extents') as $method => $extents) {
+				$line = $frame['line'];
+
+				if (!($extents[0] <= $line && $line <= $extents[1])) {
+					continue;
+				}
+				$class = key($class);
+				$reference = "{$class}::{$method}";
+				$function = "{$reference}()::{closure}";
+				break;
+			}
+		} else {
+			$reference = $frame['file'];
+			$function = "{$reference}::{closure}";
+		}
+		$line = static::_definition($reference, $frame['line']) ?: '?';
+		$function .= " @ {$line}";
+		return static::$_closureCache[$cacheKey] = $function;
+	}
+}
+
+?>

+ 122 - 0
php-lithium/libraries/lithium/analysis/Docblock.php

@@ -0,0 +1,122 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright     Copyright 2012, Union of RAD (http://union-of-rad.org)
+ * @license       http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+
+namespace lithium\analysis;
+
+/**
+ * A source code doc block parser.
+ *
+ * This parser may be used as the basis for a variety of secondary tools, including
+ * a reflection-based API generator, a code metrics analyzer, and various other code or structural
+ * analysis tools.
+ */
+class Docblock extends \lithium\core\StaticObject {
+
+	/**
+	 * List of supported docblock tags.
+	 *
+	 * @var array
+	 */
+	public static $tags = array(
+		'todo', 'discuss', 'fix', 'important', 'var',
+		'param', 'return', 'throws', 'see', 'link',
+		'task', 'dependencies', 'filter'
+	);
+
+	/**
+	 * Parses a doc block into its major components of `description`, `text` and `tags`.
+	 *
+	 * @param string $comment The doc block string to be parsed
+	 * @return array An associative array of the parsed comment, whose keys are `description`,
+	 *         `text` and `tags`.
+	 */
+	public static function comment($comment) {
+		$text = null;
+		$tags = array();
+		$description = null;
+		$comment = trim(preg_replace('/^(\s*\/\*\*|\s*\*{1,2}\/|\s*\* ?)/m', '', $comment));
+		$comment = str_replace("\r\n", "\n", $comment);
+
+		if ($items = preg_split('/\n@/ms', $comment, 2)) {
+			list($description, $tags) = $items + array('', '');
+			$tags = $tags ? static::tags("@{$tags}") : array();
+		}
+
+		if (strpos($description, "\n\n")) {
+			list($description, $text) = explode("\n\n", $description, 2);
+		}
+		$text = trim($text);
+		$description = trim($description);
+		return compact('description', 'text', 'tags');
+	}
+
+	/**
+	 * Parses `@<tagname>` docblock tags and their descriptions from a docblock.
+	 *
+	 * See the `$tags` property for the list of supported tags.
+	 *
+	 * @param string $string The string to be parsed for tags
+	 * @return array Returns an array where each docblock tag is a key name, and the corresponding
+	 *         values are either strings (if one of each tag), or arrays (if multiple of the same
+	 *         tag).
+	 */
+	public static function tags($string) {
+		$regex = '/\n@(?P<type>' . join('|', static::$tags) . ")/msi";
+		$string = trim($string);
+
+		$result = preg_split($regex, "\n$string", -1, PREG_SPLIT_DELIM_CAPTURE);
+		$tags = array();
+
+		for ($i = 1; $i < count($result) - 1; $i += 2) {
+			$type = trim(strtolower($result[$i]));
+			$text = trim($result[$i + 1]);
+
+			if (isset($tags[$type])) {
+				$tags[$type] = is_array($tags[$type]) ? $tags[$type] : (array) $tags[$type];
+				$tags[$type][] = $text;
+			} else {
+				$tags[$type] = $text;
+			}
+		}
+
+		if (isset($tags['param'])) {
+			$params = $tags['param'];
+			$tags['params'] = static::_params((array) $tags['param']);
+			unset($tags['param']);
+		}
+		return $tags;
+	}
+
+	/**
+	 * Parses `@param` docblock tags to separate out the parameter type from the description.
+	 *
+	 * @param array $params An array of `@param` tags, as parsed from the `tags()` method.
+	 * @return array Returns an array where each key is a parameter name, and each value is an
+	 *         associative array containing `'type'` and `'text'` keys.
+	 */
+	protected static function _params(array $params) {
+		$result = array();
+		foreach ($params as $param) {
+			$param = explode(' ', $param, 3);
+			$type = $name = $text = null;
+
+			foreach (array('type', 'name', 'text') as $i => $key) {
+				if (!isset($param[$i])) {
+					break;
+				}
+				${$key} = $param[$i];
+			}
+			if ($name) {
+				$result[$name] = compact('type', 'text');
+			}
+		}
+		return $result;
+	}
+}
+
+?>

+ 590 - 0
php-lithium/libraries/lithium/analysis/Inspector.php

@@ -0,0 +1,590 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright     Copyright 2012, Union of RAD (http://union-of-rad.org)
+ * @license       http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+
+namespace lithium\analysis;
+
+use Exception;
+use ReflectionClass;
+use ReflectionProperty;
+use ReflectionException;
+use SplFileObject;
+use lithium\core\Libraries;
+
+/**
+ * General source code inspector.
+ *
+ * This inspector provides a simple interface to the PHP Reflection API that
+ * can be used to gather information about any PHP source file for purposes of
+ * test metrics or static analysis.
+ */
+class Inspector extends \lithium\core\StaticObject {
+
+	/**
+	 * classes used
+	 *
+	 * @var array
+	 */
+	protected static $_classes = array(
+		'collection' => 'lithium\util\Collection'
+	);
+
+	/**
+	 * Maps reflect method names to result array keys.
+	 *
+	 * @var array
+	 */
+	protected static $_methodMap = array(
+		'name'      => 'getName',
+		'start'     => 'getStartLine',
+		'end'       => 'getEndLine',
+		'file'      => 'getFileName',
+		'comment'   => 'getDocComment',
+		'namespace' => 'getNamespaceName',
+		'shortName' => 'getShortName'
+	);
+
+	/**
+	 * Will determine if a method can be called.
+	 *
+	 * @param  string|object $class      Class to inspect.
+	 * @param  string        $method     Method name.
+	 * @param  bool          $internal   Interal call or not.
+	 * @return bool
+	 */
+	public static function isCallable($object, $method, $internal = false) {
+		$methodExists = method_exists($object, $method);
+		$callable = function($object, $method) {
+			return is_callable(array($object, $method));
+		};
+		return $internal ? $methodExists : $methodExists && $callable($object, $method);
+	}
+
+	/**
+	 * Determines if a given $identifier is a class property, a class method, a class itself,
+	 * or a namespace identifier.
+	 *
+	 * @param string $identifier The identifier to be analyzed
+	 * @return string Identifier type. One of `property`, `method`, `class` or `namespace`.
+	 */
+	public static function type($identifier) {
+		$identifier = ltrim($identifier, '\\');
+
+		if (strpos($identifier, '::')) {
+			return (strpos($identifier, '$') !== false) ? 'property' : 'method';
+		}
+		if (is_readable(Libraries::path($identifier))) {
+			if (class_exists($identifier) && in_array($identifier, get_declared_classes())) {
+				return 'class';
+			}
+		}
+		return 'namespace';
+	}
+
+	/**
+	 * Detailed source code identifier analysis
+	 *
+	 * Analyzes a passed $identifier for more detailed information such
+	 * as method/property modifiers (e.g. `public`, `private`, `abstract`)
+	 *
+	 * @param string $identifier The identifier to be analyzed
+	 * @param array $info Optionally restrict or expand the default information
+	 *        returned from the `info` method. By default, the information returned
+	 *        is the same as the array keys contained in the `$_methodMap` property of
+	 *        Inspector.
+	 * @return array An array of the parsed meta-data information of the given identifier.
+	 */
+	public static function info($identifier, $info = array()) {
+		$info = $info ?: array_keys(static::$_methodMap);
+		$type = static::type($identifier);
+		$result = array();
+		$class = null;
+
+		if ($type === 'method' || $type === 'property') {
+			list($class, $identifier) = explode('::', $identifier);
+
+			try {
+				$classInspector = new ReflectionClass($class);
+			} catch (Exception $e) {
+				return null;
+			}
+
+			if ($type === 'property') {
+				$identifier = substr($identifier, 1);
+				$accessor = 'getProperty';
+			} else {
+				$identifier = str_replace('()', '', $identifier);
+				$accessor = 'getMethod';
+			}
+
+			try {
+				$inspector = $classInspector->{$accessor}($identifier);
+			} catch (Exception $e) {
+				return null;
+			}
+			$result['modifiers'] = static::_modifiers($inspector);
+		} elseif ($type === 'class') {
+			$inspector = new ReflectionClass($identifier);
+		} else {
+			return null;
+		}
+
+		foreach ($info as $key) {
+			if (!isset(static::$_methodMap[$key])) {
+				continue;
+			}
+			if (method_exists($inspector, static::$_methodMap[$key])) {
+				$setAccess = (
+					($type === 'method' || $type === 'property') &&
+					array_intersect($result['modifiers'], array('private', 'protected')) != array()
+					&& method_exists($inspector, 'setAccessible')
+				);
+
+				if ($setAccess) {
+					$inspector->setAccessible(true);
+				}
+				$result[$key] = $inspector->{static::$_methodMap[$key]}();
+
+				if ($setAccess) {
+					$inspector->setAccessible(false);
+					$setAccess = false;
+				}
+			}
+		}
+
+		if ($type === 'property' && !$classInspector->isAbstract()) {
+			$inspector->setAccessible(true);
+
+			try {
+				$result['value'] = $inspector->getValue(static::_class($class));
+			} catch (Exception $e) {
+				return null;
+			}
+		}
+
+		if (isset($result['start']) && isset($result['end'])) {
+			$result['length'] = $result['end'] - $result['start'];
+		}
+		if (isset($result['comment'])) {
+			$result += Docblock::comment($result['comment']);
+		}
+		return $result;
+	}
+
+	/**
+	 * Gets the executable lines of a class, by examining the start and end lines of each method.
+	 *
+	 * @param mixed $class Class name as a string or object instance.
+	 * @param array $options Set of options:
+	 *        - `'self'` _boolean_: If `true` (default), only returns lines of methods defined in
+	 *          `$class`, excluding methods from inherited classes.
+	 *        - `'methods'` _array_: An arbitrary list of methods to search, as a string (single
+	 *          method name) or array of method names.
+	 *        - `'filter'` _boolean_: If `true`, filters out lines containing only whitespace or
+	 *          braces. Note: for some reason, the Zend engine does not report `switch` and `try`
+	 *          statements as executable lines, as well as parts of multi-line assignment
+	 *          statements, so they are filtered out as well.
+	 * @return array Returns an array of the executable line numbers of the class.
+	 */
+	public static function executable($class, array $options = array()) {
+		$defaults = array(
+			'self' => true,
+			'filter' => true,
+			'methods' => array(),
+			'empty' => array(' ', "\t", '}', ')', ';'),
+			'pattern' => null,
+			'blockOpeners' => array('switch (', 'try {', '} else {', 'do {', '} while')
+		);
+		$options += $defaults;
+
+		if (empty($options['pattern']) && $options['filter']) {
+			$pattern = str_replace(' ', '\s*', join('|', array_map(
+				function($str) { return preg_quote($str, '/'); },
+				$options['blockOpeners']
+			)));
+			$pattern = join('|', array(
+				"({$pattern})",
+				"\\$(.+)\($",
+				"\s*['\"]\w+['\"]\s*=>\s*.+[\{\(]$",
+				"\s*['\"]\w+['\"]\s*=>\s*['\"]*.+['\"]*\s*"
+			));
+			$options['pattern'] = "/^({$pattern})/";
+		}
+
+		if (!$class instanceof ReflectionClass) {
+			$class = new ReflectionClass(is_object($class) ? get_class($class) : $class);
+		}
+		$options += array('group' => false);
+		$result = array_filter(static::methods($class, 'ranges', $options));
+
+		if ($options['filter'] && $class->getFileName() && $result) {
+			$lines = static::lines($class->getFileName(), $result);
+			$start = key($lines);
+
+			$code = implode("\n", $lines);
+			$tokens = token_get_all('<' . '?php' . $code);
+			$tmp = array();
+
+			foreach ($tokens as $token) {
+				if (is_array($token)) {
+					if (!in_array($token[0], array(T_COMMENT, T_DOC_COMMENT, T_WHITESPACE))) {
+						$tmp[] = $token[2];
+					}
+				}
+			}
+
+			$filteredLines = array_values(array_map(
+				function($ln) use ($start) { return $ln + $start - 1; },
+				array_unique($tmp))
+			);
+
+			$lines = array_intersect_key($lines, array_flip($filteredLines));
+
+			$result = array_keys(array_filter($lines, function($line) use ($options) {
+				$line = trim($line);
+				$empty = preg_match($options['pattern'], $line);
+				return $empty ? false : (str_replace($options['empty'], '', $line) !== '');
+			}));
+		}
+		return $result;
+	}
+
+	/**
+	 * Returns various information on the methods of an object, in different formats.
+	 *
+	 * @param mixed $class A string class name or an object instance, from which to get methods.
+	 * @param string $format The type and format of data to return. Available options are:
+	 *        - `null`: Returns a `Collection` object containing a `ReflectionMethod` instance
+	 *         for each method.
+	 *        - `'extents'`: Returns a two-dimensional array with method names as keys, and
+	 *         an array with starting and ending line numbers as values.
+	 *        - `'ranges'`: Returns a two-dimensional array where each key is a method name,
+	 *         and each value is an array of line numbers which are contained in the method.
+	 * @param array $options
+	 * @return mixed array|null|object
+	 */
+	public static function methods($class, $format = null, array $options = array()) {
+		$defaults = array('methods' => array(), 'group' => true, 'self' => true);
+		$options += $defaults;
+
+		if (!(is_object($class) && $class instanceof ReflectionClass)) {
+			try {
+				$class = new ReflectionClass($class);
+			} catch (ReflectionException $e) {
+				return null;
+			}
+		}
+		$options += array('names' => $options['methods']);
+		$methods = static::_items($class, 'getMethods', $options);
+		$result = array();
+
+		switch ($format) {
+			case null:
+				return $methods;
+			case 'extents':
+				if ($methods->getName() == array()) {
+					return array();
+				}
+
+				$extents = function($start, $end) { return array($start, $end); };
+				$result = array_combine($methods->getName(), array_map(
+					$extents, $methods->getStartLine(), $methods->getEndLine()
+				));
+			break;
+			case 'ranges':
+				$ranges = function($lines) {
+					list($start, $end) = $lines;
+					return ($end <= $start + 1) ? array() : range($start + 1, $end - 1);
+				};
+				$result = array_map($ranges, static::methods(
+					$class, 'extents', array('group' => true) + $options
+				));
+			break;
+		}
+
+		if ($options['group']) {
+			return $result;
+		}
+		$tmp = $result;
+		$result = array();
+
+		array_map(function($ln) use (&$result) { $result = array_merge($result, $ln); }, $tmp);
+		return $result;
+	}
+
+	/**
+	 * Returns various information on the properties of an object.
+	 *
+	 * @param mixed $class A string class name or an object instance, from which to get methods.
+	 * @param array $options Set of options:
+	 *        -'self': If true (default), only returns properties defined in `$class`,
+	 *         excluding properties from inherited classes.
+	 * @return mixed object lithium\analysis\Inspector._items.map|null
+	 */
+	public static function properties($class, array $options = array()) {
+		$defaults = array('properties' => array(), 'self' => true);
+		$options += $defaults;
+
+		if (!(is_object($class) && $class instanceof ReflectionClass)) {
+			try {
+				$class = new ReflectionClass($class);
+			} catch (ReflectionException $e) {
+				return null;
+			}
+		}
+		$options += array('names' => $options['properties']);
+
+		return static::_items($class, 'getProperties', $options)->map(function($item) {
+			$class = __CLASS__;
+			$modifiers = array_values($class::invokeMethod('_modifiers', array($item)));
+			$setAccess = (
+				array_intersect($modifiers, array('private', 'protected')) != array()
+			);
+			if ($setAccess) {
+				$item->setAccessible(true);
+			}
+			$result = compact('modifiers') + array(
+				'docComment' => $item->getDocComment(),
+				'name' => $item->getName(),
+				'value' => $item->getValue($item->getDeclaringClass())
+			);
+			if ($setAccess) {
+				$item->setAccessible(false);
+			}
+			return $result;
+		}, array('collect' => false));
+	}
+
+	/**
+	 * Returns an array of lines from a file, class, or arbitrary string, where $data is the data
+	 * to read the lines from and $lines is an array of line numbers specifying which lines should
+	 * be read.
+	 *
+	 * @param string $data If `$data` contains newlines, it will be read from directly, and have
+	 *        its own lines returned.  If `$data` is a physical file path, that file will be
+	 *        read and have its lines returned.  If `$data` is a class name, it will be
+	 *        converted into a physical file path and read.
+	 * @param array $lines The array of lines to read. If a given line is not present in the data,
+	 *        it will be silently ignored.
+	 * @return array Returns an array where the keys are matching `$lines`, and the values are the
+	 *         corresponding line numbers in `$data`.
+	 * @todo Add an $options parameter with a 'context' flag, to pull in n lines of context.
+	 */
+	public static function lines($data, $lines) {
+		$c = array();
+
+		if (strpos($data, PHP_EOL) !== false) {
+			$c = explode(PHP_EOL, PHP_EOL . $data);
+		} else {
+			if (!file_exists($data)) {
+				$data = Libraries::path($data);
+				if (!file_exists($data)) {
+					return null;
+				}
+			}
+
+			$file = new SplFileObject($data);
+			foreach ($file as $current) {
+				$c[$file->key() + 1] = rtrim($file->current());
+			}
+		}
+
+		if (!count($c) || !count($lines)) {
+			return null;
+		}
+		return array_intersect_key($c, array_combine($lines, array_fill(0, count($lines), null)));
+	}
+
+	/**
+	 * Gets the full inheritance list for the given class.
+	 *
+	 * @param string $class Class whose inheritance chain will be returned
+	 * @param array $options Option consists of:
+	 *        - `'autoLoad'` _boolean_: Whether or not to call `__autoload` by default. Defaults
+	 *          to `true`.
+	 * @return array An array of the name of the parent classes of the passed `$class` parameter,
+	 *         or `false` on error.
+	 * @link http://php.net/manual/en/function.class-parents.php PHP Manual: `class_parents()`.
+	 */
+	public static function parents($class, array $options = array()) {
+		$defaults = array('autoLoad' => false);
+		$options += $defaults;
+		$class = is_object($class) ? get_class($class) : $class;
+
+		if (!class_exists($class, $options['autoLoad'])) {
+			return false;
+		}
+		return class_parents($class);
+	}
+
+	/**
+	 * Gets an array of classes and their corresponding definition files, or examines a file and
+	 * returns the classes it defines.
+	 *
+	 * @param array $options
+	 * @return array Associative of classes and their corresponding definition files
+	 * @todo Document valid options
+	 */
+	public static function classes(array $options = array()) {
+		$defaults = array('group' => 'classes', 'file' => null);
+		$options += $defaults;
+
+		$list = get_declared_classes();
+		$files = get_included_files();
+		$classes = array();
+
+		if ($file = $options['file']) {
+			$loaded = static::_instance('collection', array('data' => array_map(
+				function($class) { return new ReflectionClass($class); }, $list
+			)));
+			$classFiles = $loaded->getFileName();
+
+			if (in_array($file, $files) && !in_array($file, $classFiles)) {
+				return array();
+			}
+			if (!in_array($file, $classFiles)) {
+				include $file;
+				$list = array_diff(get_declared_classes(), $list);
+			} else {
+				$filter = function($class) use ($file) { return $class->getFileName() === $file; };
+				$list = $loaded->find($filter)->map(function ($class) {
+					return $class->getName() ?: $class->name;
+				}, array('collect' => false));
+			}
+		}
+
+		foreach ($list as $class) {
+			$inspector = new ReflectionClass($class);
+
+			if ($options['group'] === 'classes') {
+				$inspector->getFileName() ? $classes[$class] = $inspector->getFileName() : null;
+			} elseif ($options['group'] === 'files') {
+				$classes[$inspector->getFileName()][] = $inspector;
+			}
+		}
+		return $classes;
+	}
+
+	/**
+	 * Gets the static and dynamic dependencies for a class or group of classes.
+	 *
+	 * @param mixed $classes Either a string specifying a class, or a numerically indexed array
+	 *        of classes
+	 * @param array $options
+	 * @return array An array of the static and dynamic class dependencies
+	 * @todo Document valid options
+	 */
+	public static function dependencies($classes, array $options = array()) {
+		$defaults = array('type' => null);
+		$options += $defaults;
+		$static = $dynamic = array();
+		$trim = function($c) { return trim(trim($c, '\\')); };
+		$join = function ($i) { return join('', $i); };
+
+		foreach ((array) $classes as $class) {
+			$data = explode("\n", file_get_contents(Libraries::path($class)));
+			$data = "<?php \n" . join("\n", preg_grep('/^\s*use /', $data)) . "\n ?>";
+
+			$classes = array_map($join, Parser::find($data, 'use *;', array(
+				'return'      => 'content',
+				'lineBreaks'  => true,
+				'startOfLine' => true,
+				'capture'     => array('T_STRING', 'T_NS_SEPARATOR')
+			)));
+
+			if ($classes) {
+				$static = array_unique(array_merge($static, array_map($trim, $classes)));
+			}
+			$classes = static::info($class . '::$_classes', array('value'));
+
+			if (isset($classes['value'])) {
+				$dynamic = array_merge($dynamic, array_map($trim, array_values($classes['value'])));
+			}
+		}
+
+		if (empty($options['type'])) {
+			return array_unique(array_merge($static, $dynamic));
+		}
+		$type = $options['type'];
+		return isset(${$type}) ? ${$type} : null;
+	}
+
+	/**
+	 * Returns an instance of the given class without directly instantiating it. Inspired by the
+	 * work of Sebastian Bergmann on the PHP Object Freezer project.
+	 *
+	 * @link http://sebastian-bergmann.de/archives/831-Freezing-and-Thawing-PHP-Objects.html
+	 *       Freezing and Thawing PHP Objects
+	 * @param string $class The name of the class to return an instance of.
+	 * @return object Returns an instance of the object given by `$class` without calling that
+	 *        class' constructor.
+	 */
+	protected static function _class($class) {
+		if (!class_exists($class)) {
+			throw new RuntimeException(sprintf('Class `%s` could not be found.', $class));
+		}
+		return unserialize(sprintf('O:%d:"%s":0:{}', strlen($class), $class));
+	}
+
+	/**
+	 * Helper method to get an array of `ReflectionMethod` or `ReflectionProperty` objects, wrapped
+	 * in a `Collection` object, and filtered based on a set of options.
+	 *
+	 * @param ReflectionClass $class A reflection class instance from which to fetch.
+	 * @param string $method A getter method to call on the `ReflectionClass` instance, which will
+	 *               return an array of items, i.e. `'getProperties'` or `'getMethods'`.
+	 * @param array $options The options used to filter the resulting method list.
+	 * @return object Returns a `Collection` object instance containing the results of the items
+	 *         returned from the call to the method specified in `$method`, after being passed
+	 *         through the filters specified in `$options`.
+	 */
+	protected static function _items($class, $method, $options) {
+		$defaults = array('names' => array(), 'self' => true, 'public' => true);
+		$options += $defaults;
+
+		$params = array(
+			'getProperties' => ReflectionProperty::IS_PUBLIC | (
+				$options['public'] ? 0 : ReflectionProperty::IS_PROTECTED
+			)
+		);
+		$data = isset($params[$method]) ? $class->{$method}($params[$method]) : $class->{$method}();
+
+		if (!empty($options['names'])) {
+			$data = array_filter($data, function($item) use ($options) {
+				return in_array($item->getName(), (array) $options['names']);
+			});
+		}
+
+		if ($options['self']) {
+			$data = array_filter($data, function($item) use ($class) {
+				return ($item->getDeclaringClass()->getName() === $class->getName());
+			});
+		}
+
+		if ($options['public']) {
+			$data = array_filter($data, function($item) { return $item->isPublic(); });
+		}
+		return static::_instance('collection', compact('data'));
+	}
+
+	/**
+	 * Helper method to determine if a class applies to a list of modifiers.
+	 *
+	 * @param string $inspector ReflectionClass instance.
+	 * @param array|string $list List of modifiers to test.
+	 * @return boolean Test result.
+	 */
+	protected static function _modifiers($inspector, $list = array()) {
+		$list = $list ?: array('public', 'private', 'protected', 'abstract', 'final', 'static');
+		return array_filter($list, function($modifier) use ($inspector) {
+			$method = 'is' . ucfirst($modifier);
+			return (method_exists($inspector, $method) && $inspector->{$method}());
+		});
+	}
+}
+
+?>

+ 196 - 0
php-lithium/libraries/lithium/analysis/Logger.php

@@ -0,0 +1,196 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright     Copyright 2012, Union of RAD (http://union-of-rad.org)
+ * @license       http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+
+namespace lithium\analysis;
+
+use UnexpectedValueException;
+
+/**
+ * The `Logger` class provides a consistent, application-wide interface for configuring and writing
+ * log messages. As with other subclasses of `Adaptable`, `Logger` can be configured with a series
+ * of named configurations, each containing a log adapter to write to. `Logger` exposes a single
+ * method, `write()`, which can write to one or more log adapters.
+ *
+ * When configuring adapters, you may specify one or more priorities for each, using the
+ * `'priority'` key. This key can be a single priority level (string), or an array of multiple
+ * levels. When a log message is written, all adapters that are configured to accept the priority
+ * level with which the message was written will receive the message.
+ *
+ * {{{
+ * Logger::config(array(
+ * 	'default' => array('adapter' => 'Syslog'),
+ * 	'badnews' => array(
+ * 		'adapter' => 'File',
+ * 		'priority' => array('emergency', 'alert', 'critical', 'error')
+ * 	)
+ * ));
+ * }}}
+ *
+ * In the above configuration, all messages will be written to the system log (`syslogd`), but only
+ * messages with the priority `error` or higher will be logged to a file. Messages can then be
+ * written to the log(s) using the `write()` method:
+ *
+ * {{{ Logger::write('alert', 'This is an alert-level message that will be logged in 2 places'); }}}
+ *
+ * Messages can also be written using the log priority as a method name:
+ *
+ * {{{ Logger::alert('This is an alert-level message that will be logged in 2 places'); }}}
+ *
+ * This works identically to the above. The message priority levels which `Logger` supports are as
+ * follows: `emergency`, `alert`, `critical`, `error`, `warning`, `notice`, `info` and `debug`.
+ * Attempting to use any other priority level will raise an exception. See the list of available
+ * adapters for more information on what adapters are available, and how to configure them.
+ *
+ * @see lithium\analysis\logger\adapter
+ */
+class Logger extends \lithium\core\Adaptable {
+
+	/**
+	 * Stores configurations for cache adapters.
+	 *
+	 * @var object `Collection` of logger configurations.
+	 */
+	protected static $_configurations = array();
+
+	/**
+	 * Libraries::locate() compatible path to adapters for this class.
+	 *
+	 * @see lithium\core\Libraries::locate()
+	 * @var string Dot-delimited path.
+	 */
+	protected static $_adapters = 'adapter.analysis.logger';
+
+	/**
+	 * An array of valid message priorities.
+	 *
+	 * @var array
+	 */
+	protected static $_priorities = array(
+		'emergency' => 0,
+		'alert'     => 1,
+		'critical'  => 2,
+		'error'     => 3,
+		'warning'   => 4,
+		'notice'    => 5,
+		'info'      => 6,
+		'debug'     => 7
+	);
+
+	/**
+	 * Writes a message to one or more log adapters, where the adapters that are written to are the
+	 * ones that respond to the given priority level.
+	 *
+	 * @param string $priority The priority of the log message to be written.
+	 * @param string $message The message to be written.
+	 * @param array $options An array of adapter-specific options that may be passed when writing
+	 *              log messages. Some options are also handled by `Logger` itself:
+	 *              - `'name'` _string_: This option can be specified if you wish to write to a
+	 *                specific adapter configuration, instead of writing to the adapter(s) that
+	 *                respond to the given priority.
+	 * @return boolean Returns `true` if all log writes succeeded, or `false` if _any or all_ writes
+	 *         failed.
+	 * @throws UnexpectedValueException If the value of `$priority` is not a defined priority value,
+	 *         an `UnexpectedValueException` will be thrown.
+	 * @filter
+	 */
+	public static function write($priority, $message, array $options = array()) {
+		$defaults = array('name' => null);
+		$options += $defaults;
+		$result = true;
+
+		if (isset(self::$_configurations[$options['name']])) {
+			$name = $options['name'];
+			$methods = array($name => static::adapter($name)->write($priority, $message, $options));
+		} elseif (!isset(static::$_priorities[$priority])) {
+			$message = "Attempted to write log message with invalid priority `{$priority}`.";
+			throw new UnexpectedValueException($message);
+		} else {
+			$methods = static::_configsByPriority($priority, $message, $options);
+		}
+
+		foreach ($methods as $name => $method) {
+			$params = compact('priority', 'message', 'options');
+			$config = static::_config($name);
+			$result &= static::_filter(__FUNCTION__, $params, $method, $config['filters']);
+		}
+		return $methods ? $result : false;
+	}
+
+	/**
+	 * Acts as a proxy for the `write()` method, allowing log message priority names to be called as
+	 * methods, i.e.:
+	 * {{{
+	 * Logger::emergency('Something bad happened.');
+	 * // This is equivalent to Logger::write('emergency', 'Something bad happened')
+	 * }}}
+	 *
+	 * @param string $priority The name of the method called on the `Logger` class. This should map
+	 *               to a log type.
+	 * @param array $params An array of parameters passed in the method.
+	 * @return boolean Returns `true` or `false`, depending on the success of the `write()` method.
+	 */
+	public static function __callStatic($priority, $params) {
+		$params += array(null, array());
+		return static::write($priority, $params[0], $params[1]);
+	}
+
+	/**
+	 * Custom check to determine if our given magic methods can be responded to.
+	 *
+	 * @param  string  $method     Method name.
+	 * @param  bool    $internal   Interal call or not.
+	 * @return bool
+	 */
+	public static function respondsTo($method, $internal = false) {
+		return isset(static::$_priorities[$method]) || parent::respondsTo($method, $internal);
+	}
+
+	/**
+	 * This method is called automatically to initialize the default configuration of a log adapter,
+	 * such that the adapter defaults to accepting log messages of any priority (i.e. the
+	 * `'priority'` key is set to `true`).
+	 *
+	 * @param string $name The name of the logger configuration.
+	 * @param array $config The logger configuration as specified in application code.
+	 * @return array Returns an array of configuration data, merged with default values.
+	 */
+	protected static function _initConfig($name, $config) {
+		$defaults = array('priority' => true);
+		return parent::_initConfig($name, $config) + $defaults;
+	}
+
+	/**
+	 * Gets the names of the adapter configurations that respond to a specific priority. The list
+	 * of adapter configurations returned will be used to write a message with the given priority.
+	 *
+	 * @param string $priority The priority level of a message to be written.
+	 * @param string $message The message to write to the adapter.
+	 * @param array $options Adapter-specific options.
+	 * @return array Returns an array of names of configurations which are set up to respond to the
+	 *         message priority specified in `$priority`, or configured to respond to _all_ message
+	 *        priorities.
+	 */
+	protected static function _configsByPriority($priority, $message, array $options = array()) {
+		$configs = array();
+		$key = 'priority';
+
+		foreach (array_keys(static::$_configurations) as $name) {
+			$config = static::config($name);
+			$nameMatch = ($config[$key] === true || $config[$key] === $priority);
+			$arrayMatch = (is_array($config[$key]) && in_array($priority, $config[$key]));
+
+			if ($nameMatch || $arrayMatch) {
+				$method = static::adapter($name)->write($priority, $message, $options);
+				$method ? $configs[$name] = $method : null;
+			}
+		}
+		return $configs;
+	}
+}
+
+?>

+ 315 - 0
php-lithium/libraries/lithium/analysis/Parser.php

@@ -0,0 +1,315 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright     Copyright 2012, Union of RAD (http://union-of-rad.org)
+ * @license       http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+
+namespace lithium\analysis;
+
+use lithium\util\Set;
+use lithium\util\Collection;
+
+/**
+ * The parser class uses PHP's tokenizer to provide methods and tools for performing static analysis
+ * on PHP code.
+ */
+class Parser extends \lithium\core\StaticObject {
+
+	/**
+	 * Convenience method to get the token name of a PHP code string. If multiple tokens are
+	 * present in the string, only the first is returned.
+	 *
+	 * @param string $string String of PHP code to get the token name of, i.e. `'=>'` or `'static'`.
+	 * @param array $options
+	 * @return mixed
+	 */
+	public static function token($string, array $options = array()) {
+		$defaults = array('id' => false);
+		$options += $defaults;
+
+		if (empty($string) && $string !== '0') {
+			return false;
+		}
+		list($token) = static::tokenize($string);
+		return $token[($options['id']) ? 'id' : 'name'];
+	}
+
+	/**
+	 * Splits the provided `$code` into PHP language tokens.
+	 *
+	 * @param string $code Source code to be tokenized.
+	 * @param array $options Options consists of:
+	 *        -'wrap': Boolean indicating whether or not to wrap the supplied
+	 *          code in PHP tags.
+	 *        -'ignore': An array containing PHP language tokens to ignore.
+	 *        -'include': If supplied, an array of the only language tokens
+	 *         to include in the output.
+	 * @return array An array of tokens in the supplied source code.
+	 */
+	public static function tokenize($code, array $options = array()) {
+		$defaults = array('wrap' => true, 'ignore' => array(), 'include' => array());
+		$options += $defaults;
+		$tokens = array();
+		$line = 1;
+
+		if ($options['wrap']) {
+			$code = "<?php {$code}?>";
+		}
+		foreach (token_get_all($code) as $token) {
+			$token = (isset($token[1])) ? $token : array(null, $token, $line);
+			list($id, $content, $line) = $token;
+			$name = $id ? token_name($id) : $content;
+
+			if (!empty($options['include'])) {
+				if (!in_array($name, $options['include']) && !in_array($id, $options['include'])) {
+					continue;
+				}
+			}
+
+			if (!empty($options['ignore'])) {
+				if (in_array($name, $options['ignore']) || in_array($id, $options['ignore'])) {
+					continue;
+				}
+			}
+			$tokens[] = array('id' => $id, 'name' => $name, 'content' => $content, 'line' => $line);
+
+			$line += count(preg_split('/\r\n|\r|\n/', $content)) - 1;
+		}
+
+		if ($options['wrap'] && empty($options['include'])) {
+			$tokens = array_slice($tokens, 1, count($tokens) - 2);
+		}
+		return $tokens;
+	}
+
+	/**
+	 * Finds a pattern in a block of code.
+	 *
+	 * @param string $code
+	 * @param string $pattern
+	 * @param array $options The list of options to be used when parsing / matching `$code`:
+	 *              - 'ignore': An array of token names to ignore while parsing, defaults to
+	 *               `array('T_WHITESPACE')`
+	 *              - 'lineBreaks': If true, all tokens in a single pattern match must appear on the
+	 *                same line of code, defaults to false
+	 *              - 'startOfLine': If true, the pattern must match starting with the beginning of
+	 *                the line of code to be matched, defaults to false
+	 * @return array
+	 */
+	public static function find($code, $pattern, array $options = array()) {
+		$defaults = array(
+			'all' => true, 'capture' => array(), 'ignore' => array('T_WHITESPACE'),
+			'return' => true, 'lineBreaks' => false, 'startOfLine' => false
+		);
+		$options += $defaults;
+		$results = array();
+		$matches = array();
+		$patternMatch = array();
+		$ret = $options['return'];
+
+		$tokens = new Collection(array('data' => static::tokenize($code, $options)));
+		$pattern = new Collection(array('data' => static::tokenize($pattern, $options)));
+
+		$breaks = function($token) use (&$tokens, &$matches, &$patternMatch, $options) {
+			if (!$options['lineBreaks']) {
+				return true;
+			}
+			if (empty($patternMatch) && !$options['startOfLine']) {
+				return true;
+			}
+
+			if (empty($patternMatch)) {
+				$prev = $tokens->prev();
+				$tokens->next();
+			} else {
+				$prev = reset($patternMatch);
+			}
+
+			if (empty($patternMatch) && $options['startOfLine']) {
+				return ($token['line'] > $prev['line']);
+			}
+			return ($token['line'] == $prev['line']);
+		};
+
+		$capture = function($token) use (&$matches, &$patternMatch, $tokens, $breaks, $options) {
+			if (is_null($token)) {
+				$matches = $patternMatch = array();
+				return false;
+			}
+
+			if (empty($patternMatch)) {
+				$prev = $tokens->prev();
+				$tokens->next();
+				if ($options['startOfLine'] && $token['line'] == $prev['line']) {
+					$patternMatch = $matches = array();
+					return false;
+				}
+			}
+			$patternMatch[] = $token;
+
+			if (empty($options['capture']) || !in_array($token['name'], $options['capture'])) {
+				return true;
+			}
+			if (!$breaks($token)) {
+				$matches = array();
+				return true;
+			}
+			$matches[] = $token;
+			return true;
+		};
+
+		$executors = array(
+			'*' => function(&$tokens, &$pattern) use ($options, $capture) {
+				$closing = $pattern->next();
+				$tokens->prev();
+
+				while (($t = $tokens->next()) && !Parser::matchToken($closing, $t)) {
+					$capture($t);
+				}
+				$pattern->next();
+			}
+		);
+
+		$tokens->rewind();
+		$pattern->rewind();
+
+		while ($tokens->valid()) {
+			if (!$pattern->valid()) {
+				$pattern->rewind();
+
+				if (!empty($matches)) {
+					$results[] = array_map(
+						function($i) use ($ret) { return isset($i[$ret]) ? $i[$ret] : $i; },
+						$matches
+					);
+				}
+				$capture(null);
+			}
+
+			$p = $pattern->current();
+			$t = $tokens->current();
+
+			switch (true) {
+				case (static::matchToken($p, $t)):
+					$capture($t) ? $pattern->next() : $pattern->rewind();
+				break;
+				case (isset($executors[$p['name']])):
+					$exec = $executors[$p['name']];
+					$exec($tokens, $pattern);
+				break;
+				default:
+					$capture(null);
+					$pattern->rewind();
+				break;
+			}
+			$tokens->next();
+		}
+		return $results;
+	}
+
+	/**
+	 * Token pattern matching.
+	 *
+	 * @param string $code Source code to be analyzed.
+	 * @param string $parameters An array containing token patterns to be matched.
+	 * @param array $options The list of options to be used when matching `$code`:
+	 *              - 'ignore': An array of language tokens to ignore.
+	 *              - 'return': If set to 'content' returns an array of matching tokens.
+	 * @return array Array of matching tokens.
+	 */
+	public static function match($code, $parameters, array $options = array()) {
+		$defaults = array('ignore' => array('T_WHITESPACE'), 'return' => true);
+		$options += $defaults;
+		$parameters = static::_prepareMatchParams($parameters);
+
+		$tokens = is_array($code) ? $code : static::tokenize($code, $options);
+		$results = array();
+
+		foreach ($tokens as $i => $token) {
+			if (!array_key_exists($token['name'], $parameters)) {
+				if (!in_array('*', $parameters)) {
+					continue;
+				}
+			}
+			$param = $parameters[$token['name']];
+
+			if (isset($param['before']) && $i > 0) {
+				if (!in_array($tokens[$i - 1]['name'], (array) $param['before'])) {
+					continue;
+				}
+			}
+
+			if (isset($param['after']) && $i + 1 < count($tokens)) {
+				 if (!in_array($tokens[$i + 1]['name'], (array) $param['after'])) {
+					continue;
+				}
+			}
+			$results[] = isset($token[$options['return']]) ? $token[$options['return']] : $token;
+		}
+		return $results;
+	}
+
+	/**
+	 * Compares two PHP language tokens.
+	 *
+	 * @param array $pattern Pattern token.
+	 * @param array $token Token to be compared.
+	 * @return boolean Match result.
+	 */
+	public static function matchToken($pattern, $token) {
+		if ($pattern['name'] != $token['name']) {
+			return false;
+		}
+
+		if (!isset($pattern['content'])) {
+			return true;
+		}
+
+		$match = $pattern['content'];
+		$content = $token['content'];
+
+		if ($pattern['name'] === 'T_VARIABLE') {
+			$match = substr($match, 1);
+			$content = substr($content, 1);
+		}
+
+		switch (true) {
+			case ($match === '_' || $match == $content):
+				return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Helper function to normalize parameters for token matching.
+	 *
+	 * @see lithium\analysis\Parser::match()
+	 * @param array $parameters Params to be normalized.
+	 * @return array Normalized parameters.
+	 */
+	protected static function _prepareMatchParams($parameters) {
+		foreach (Set::normalize($parameters) as $token => $scope) {
+			if (strpos($token, 'T_') !== 0) {
+				unset($parameters[$token]);
+
+				foreach (array('before', 'after') as $key) {
+					if (!isset($scope[$key])) {
+						continue;
+					}
+					$items = array();
+
+					foreach ((array) $scope[$key] as $item) {
+						$items[] = (strpos($item, 'T_') !== 0)  ? static::token($item) : $item;
+					}
+					$scope[$key] = $items;
+				}
+				$parameters[static::token($token)] = $scope;
+			}
+		}
+		return $parameters;
+	}
+}
+
+?>

+ 81 - 0
php-lithium/libraries/lithium/analysis/logger/adapter/Cache.php

@@ -0,0 +1,81 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright     Copyright 2012, Union of RAD (http://union-of-rad.org)
+ * @license       http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+
+namespace lithium\analysis\logger\adapter;
+
+use lithium\util\String;
+
+/**
+ * The `Cache` logger allows log messages to be written to cache configurations set up in
+ * `lithium\storage\Cache`. In order to use this adapter, you must first configure a cache adapter
+ * for it to write to, as follows:
+ *
+ * {{{ lithium\storage\Cache::config(array(
+ * 	'storage' => array('adapter' => 'Redis', 'host' => '127.0.0.1:6379')
+ * ));}}}
+ *
+ * Then, you can configure the `Cache` logger with the `'storage'` config:
+ * {{{ lithium\analysis\Logger::config(array(
+ * 	'debug' => array('adapter' => 'Cache', 'config' => 'storage')
+ * ));
+ * }}}
+ *
+ * You can then send messages to the logger which will be written to the cache store:
+ * {{{
+ * lithium\analysis\Logger::write('debug', 'This message will be written to a Redis data store.');
+ * }}}
+ *
+ * @see lithium\storage\Cache
+ */
+class Cache extends \lithium\core\Object {
+
+	/**
+	 * Classes used by `Cache`.
+	 *
+	 * @var array
+	 */
+	protected $_classes = array(
+		'cache' => 'lithium\storage\Cache'
+	);
+
+	/**
+	 * Class constructor.
+	 *
+	 * @param array $config
+	 */
+	public function __construct(array $config = array()) {
+		$defaults = array(
+			'config' => null,
+			'expiry' => '+999 days',
+			'key' => 'log_{:type}_{:timestamp}'
+		);
+		parent::__construct($config + $defaults);
+	}
+
+	/**
+	 * Writes the message to the configured cache adapter.
+	 *
+	 * @param string $type
+	 * @param string $message
+	 * @return closure Function returning boolean `true` on successful write, `false` otherwise.
+	 */
+	public function write($type, $message) {
+		$config = $this->_config + $this->_classes;
+
+		return function($self, $params) use ($config) {
+			$params += array('timestamp' => strtotime('now'));
+			$key = $config['key'];
+			$key = is_callable($key) ? $key($params) : String::insert($key, $params);
+
+			$cache = $config['cache'];
+			return $cache::write($config['config'], $key, $params['message'], $config['expiry']);
+		};
+	}
+}
+
+?>

+ 83 - 0
php-lithium/libraries/lithium/analysis/logger/adapter/File.php

@@ -0,0 +1,83 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright     Copyright 2012, Union of RAD (http://union-of-rad.org)
+ * @license       http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+
+namespace lithium\analysis\logger\adapter;
+
+use lithium\util\String;
+use lithium\core\Libraries;
+
+/**
+ * A simple log adapter that writes messages to files. By default, messages are written to
+ * `resources/tmp/logs/<type>.log`, where `<type>` is the log message priority level.
+ *
+ * {{{
+ * use lithium\analysis\Logger;
+ *
+ * Logger::config(array(
+ * 	'simple' => array('adapter' => 'File')
+ * ));
+ * Logger::write('debug', 'Something happened!');
+ * }}}
+ *
+ * This will cause the message and the timestamp of the log event to be written to
+ * `resources/tmp/logs/debug.log`. For available configuration options for this adapter, see
+ * the `__construct()` method.
+ *
+ * @see lithium\analysis\logger\adapter\File::__construct()
+ */
+class File extends \lithium\core\Object {
+
+	/**
+	 * Class constructor.
+	 *
+	 * @see lithium\util\String::insert()
+	 * @param array $config Settings used to configure the adapter. Available options:
+	 *              - `'path'` _string_: The directory to write log files to. Defaults to
+	 *                `<app>/resources/tmp/logs`.
+	 *              - `'timestamp'` _string_: The `date()`-compatible timestamp format. Defaults to
+	 *                `'Y-m-d H:i:s'`.
+	 *              - `'file'` _closure_: A closure which accepts two parameters: an array
+	 *                containing the current log message details, and an array containing the `File`
+	 *                adapter's current configuration. It must then return a file name to write the
+	 *                log message to. The default will produce a log file name corresponding to the
+	 *                priority of the log message, i.e. `"debug.log"` or `"alert.log"`.
+	 *              - `'format'` _string_: A `String::insert()`-compatible string that specifies how
+	 *                the log message should be formatted. The default format is
+	 *                `"{:timestamp} {:message}\n"`.
+	 */
+	public function __construct(array $config = array()) {
+		$defaults = array(
+			'path' => Libraries::get(true, 'resources') . '/tmp/logs',
+			'timestamp' => 'Y-m-d H:i:s',
+			'file' => function($data, $config) { return "{$data['priority']}.log"; },
+			'format' => "{:timestamp} {:message}\n"
+		);
+		parent::__construct($config + $defaults);
+	}
+
+	/**
+	 * Appends a message to a log file.
+	 *
+	 * @see lithium\analysis\Logger::$_priorities
+	 * @param string $priority The message priority. See `Logger::$_priorities`.
+	 * @param string $message The message to write to the log.
+	 * @return closure Function returning boolean `true` on successful write, `false` otherwise.
+	 */
+	public function write($priority, $message) {
+		$config = $this->_config;
+
+		return function($self, $params) use (&$config) {
+			$path = $config['path'] . '/' . $config['file']($params, $config);
+			$params['timestamp'] = date($config['timestamp']);
+			$message = String::insert($config['format'], $params);
+			return file_put_contents($path, $message, FILE_APPEND);
+		};
+	}
+}
+
+?>

+ 194 - 0
php-lithium/libraries/lithium/analysis/logger/adapter/FirePhp.php

@@ -0,0 +1,194 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright     Copyright 2012, Union of RAD (http://union-of-rad.org)
+ * @license       http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+
+namespace lithium\analysis\logger\adapter;
+
+/**
+ * The `FirePhp` log adapter allows you to log messages to [ FirePHP](http://www.firephp.org/).
+ *
+ * This allows you to inspect native PHP values and objects inside the FireBug console.
+ *
+ * Because this adapter interacts directly with the `Response` object, some additional code is
+ * required to use it. The simplest way to achieve this is to add a filter to the `Dispatcher`. For
+ * example, the following can be placed in a bootstrap file:
+ *
+ * {{{
+ * use lithium\action\Dispatcher;
+ * use lithium\analysis\Logger;
+ *
+ * Logger::config(array(
+ * 	'default' => array('adapter' => 'FirePhp')
+ * ));
+ *
+ * Dispatcher::applyFilter('_call', function($self, $params, $chain) {
+ * 	if (isset($params['callable']->response)) {
+ * 		Logger::adapter('default')->bind($params['callable']->response);
+ * 	}
+ * 	return $chain->next($self, $params, $chain);
+ * });
+ * }}}
+ *
+ * This will cause the message and other debug settings added to the header of the
+ * response, where FirePHP is able to locate and print it accordingly. As this adapter
+ * implements the protocol specification directly, you don't need another vendor library to
+ * use it.
+ *
+ * Now, in you can use the logger in your application code (like controllers, views and models).
+ *
+ * {{{
+ * class PagesController extends \lithium\action\Controller {
+ * 	public function view() {
+ * 		//...
+ * 		Logger::error("Something bad happened!");
+ * 		//...
+ * 	}
+ * }
+ * }}}
+ *
+ * Because this adapter also has a queue implemented, it is possible to log messages even when the
+ * `Response` object is not yet generated. When it gets generated (and bound), all queued messages
+ * get flushed instantly.
+ *
+ * Because FirePHP is not a conventional logging destination like a file or a database, you can
+ * pass everything (except resources) to the logger and inspect it further in FirePHP. In fact,
+ * every message that is passed will be encoded via `json_encode()`, so check out this built-in
+ * method for more information on how your message will be encoded.
+ *
+ * {{{
+ * Logger::debug(array('debug' => 'me'));
+ * Logger::debug(new \lithium\action\Response());
+ * }}}
+ *
+ * @see lithium\action\Response
+ * @see lithium\net\http\Message::headers()
+ * @link http://www.firephp.org/ FirePHP
+ * @link http://www.firephp.org/Wiki/Reference/Protocol FirePHP Protocol Reference
+ * @link http://php.net/manual/en/function.json-encode.php PHP Manual: `json_encode()`
+ */
+class FirePhp extends \lithium\core\Object {
+
+	/**
+	 * These headers are specified by FirePHP and get added as headers to the response.
+	 *
+	 * @var array
+	 */
+	protected $_headers = array(
+		'X-Wf-Protocol-1' => 'http://meta.wildfirehq.org/Protocol/JsonStream/0.2',
+		'X-Wf-1-Plugin-1' =>
+			'http://meta.firephp.org/Wildfire/Plugin/FirePHP/Library-FirePHPCore/0.3',
+		'X-Wf-1-Structure-1' =>
+			'http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1'
+	);
+
+	/**
+	 * This is a mapping table that maps Lithium log levels to FirePHP log levels as they
+	 * do not correlate directly and FirePHP only accepts a distinct set.
+	 *
+	 * @var array
+	 */
+	protected $_levels = array(
+		'emergency' => 'ERROR',
+		'alert'     => 'ERROR',
+		'critical'  => 'ERROR',
+		'error'     => 'ERROR',
+		'warning'   => 'WARN',
+		'notice'    => 'INFO',
+		'info'      => 'INFO',
+		'debug'     => 'LOG'
+	);
+
+	/**
+	 * This self-incrementing counter allows the user to log more than one message per request.
+	 *
+	 * @var integer
+	 */
+	protected $_counter = 1;
+
+	/**
+	 * Holds the response object where the headers will be inserted.
+	 */
+	protected $_response = null;
+
+	/**
+	 * Contains messages that have been written to the log before the bind() call.
+	 */
+	protected $_queue = array();
+
+	/**
+	 * Binds the response object to the logger and sets the required Wildfire
+	 * protocol headers.
+	 *
+	 * @param object $response An instance of a response object (usually `lithium\action\Response`)
+	 *               with HTTP request information.
+	 * @return void
+	 */
+	public function bind($response) {
+		$this->_response = $response;
+		$this->_response->headers += $this->_headers;
+
+		foreach ($this->_queue as $message) {
+			$this->_write($message);
+		}
+	}
+
+	/**
+	 * Appends a log message to the response header for FirePHP.
+	 *
+	 * @param string $priority Represents the message priority.
+	 * @param string $message Contains the actual message to store.
+	 * @return boolean Always returns `true`. Note that in order for message-writing to take effect,
+	 *                 the adapter must be bound to the `Response` object instance associated with
+	 *                 the current request. See the `bind()` method.
+	 */
+	public function write($priority, $message) {
+		$_self =& $this;
+
+		return function($self, $params) use (&$_self) {
+			$priority = $params['priority'];
+			$message = $params['message'];
+			$message = $_self->invokeMethod('_format', array($priority, $message));
+			$_self->invokeMethod('_write', array($message));
+			return true;
+		};
+	}
+
+	/**
+	 * Heper method that writes the message to the header of a bound `Response` object. If no
+	 * `Response` object is bound when this method is called, it is stored in a message queue.
+	 *
+	 * @see lithium\analysis\logger\adapter\FirePhp::_format()
+	 * @param array $message A message containing the key and the content to store.
+	 * @return void
+	 */
+	protected function _write($message) {
+		if (!$this->_response) {
+			return $this->_queue[] = $message;
+		}
+		$this->_response->headers[$message['key']] = $message['content'];
+	}
+
+	/**
+	 * Generates a string representation of the type and message, suitable for FirePHP.
+	 *
+	 * @param string $type Represents the message priority.
+	 * @param string $message Contains the actual message to store.
+	 * @return array Returns the encoded string representations of the priority and message, in the
+	 *               `'key'` and `'content'` keys, respectively.
+	 */
+	protected function _format($type, $message) {
+		$key = 'X-Wf-1-1-1-' . $this->_counter++;
+
+		$content = array(array('Type' => $this->_levels[$type]), $message);
+		$content = json_encode($content);
+		$content = strlen($content) . '|' . $content . '|';
+
+		return compact('key', 'content');
+	}
+}
+
+?>

+ 247 - 0
php-lithium/libraries/lithium/analysis/logger/adapter/Growl.php

@@ -0,0 +1,247 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright     Copyright 2012, Union of RAD (http://union-of-rad.org)
+ * @license       http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+
+namespace lithium\analysis\logger\adapter;
+
+use lithium\util\Inflector;
+use lithium\core\NetworkException;
+
+/**
+ * The `Growl` logger implements support for the [ Growl](http://growl.info/) notification system
+ * for Mac OS X. Writing to this logger will display small, customizable status messages on the
+ * screen.
+ */
+class Growl extends \lithium\core\Object {
+
+	/**
+	 * Array that maps `Logger` message priority names to Growl-compatible priority levels.
+	 *
+	 * @var array
+	 */
+	protected $_priorities = array(
+		'emergency' => 2,
+		'alert'     => 1,
+		'critical'  => 1,
+		'error'     => 1,
+		'warning'   => 0,
+		'notice'    => -1,
+		'info'      => -2,
+		'debug'     => -2
+	);
+
+	/**
+	 * The Growl protocol version used to send messages.
+	 */
+	const PROTOCOL_VERSION = 1;
+
+	/**
+	 * There are two types of messages sent to Growl: one to register applications, and one to send
+	 * notifications. This type registers the application with Growl's settings.
+	 */
+	const TYPE_REG = 0;
+
+	/**
+	 * This message type is for sending notifications to Growl.
+	 */
+	const TYPE_NOTIFY = 1;
+
+	/**
+	 * Holds the connection resource used to send messages to Growl.
+	 *
+	 * @var resource
+	 */
+	protected $_connection = null;
+
+	/**
+	 * Flag indicating whether the logger has successfully registered with the Growl server.
+	 * Registration only needs to happen once, but may fail for several reasons, including inability
+	 * to connect to the server, or the server requires a password which has not been specified.
+	 *
+	 * @var boolean
+	 */
+	protected $_registered = false;
+
+	/**
+	 * Allow the Growl connection resource to be auto-configured from constructor parameters.
+	 *
+	 * @var array
+	 */
+	protected $_autoConfig = array('connection', 'registered');
+
+	/**
+	 * Growl logger constructor. Accepts an array of settings which are merged with the default
+	 * settings and used to create the connection and handle notifications.
+	 *
+	 * @see lithium\analysis\Logger::write()
+	 * @param array $config The settings to configure the logger. Available settings are as follows:
+	 *              - `'name`' _string_: The name of the application as it should appear in Growl's
+	 *                system settings. Defaults to the directory name containing your application.
+	 *              - `'host'` _string_: The Growl host with which to communicate, usually your
+	 *                local machine. Use this setting to send notifications to another machine on
+	 *                the network. Defaults to `'127.0.0.1'`.
+	 *              - `'port'` _integer_: Port of the host machine. Defaults to the standard Growl
+	 *                port, `9887`.
+	 *              - `'password'` _string_: Only required if the host machine requires a password.
+	 *                If notification or registration fails, check this against the host machine's
+	 *                Growl settings.
+	 *              - '`protocol'` _string_: Protocol to use when opening socket communication to
+	 *                Growl. Defaults to `'udp'`.
+	 *              - `'title'` _string_: The default title to display when showing Growl messages.
+	 *                The default value is the same as `'name'`, but can be changed on a per-message
+	 *                basis by specifying a `'title'` key in the `$options` parameter of
+	 *                `Logger::write()`.
+	 *              - `'notification'` _array_: A list of message types you wish to register with
+	 *                Growl to be able to send. Defaults to `array('Errors', 'Messages')`.
+	 */
+	public function __construct(array $config = array()) {
+		$name = basename(LITHIUM_APP_PATH);
+
+		$defaults = compact('name') + array(
+			'host'     => '127.0.0.1',
+			'port'     => 9887,
+			'password' => null,
+			'protocol' => 'udp',
+			'title'    => Inflector::humanize($name),
+			'notifications' => array('Errors', 'Messages'),
+			'registered' => false
+		);
+		parent::__construct($config + $defaults);
+	}
+
+	/**
+	 * Writes `$message` to a new Growl notification.
+	 *
+	 * @param string $type The `Logger`-based priority of the message. This value is mapped to
+	 *               a Growl-specific priority value if possible.
+	 * @param string $message Message to be shown.
+	 * @param array $options Any options that are passed to the `notify()` method. See the
+	 *              `$options` parameter of `notify()`.
+	 * @return closure Function returning boolean `true` on successful write, `false` otherwise.
+	 */
+	public function write($type, $message, array $options = array()) {
+		$_self =& $this;
+		$_priorities = $this->_priorities;
+
+		return function($self, $params) use (&$_self, $_priorities) {
+			$priority = 0;
+			$options = $params['options'];
+
+			if (isset($options['priority']) && isset($_priorities[$options['priority']])) {
+				$priority = $_priorities[$options['priority']];
+			}
+			return $_self->notify($params['message'], compact('priority') + $options);
+		};
+	}
+
+	/**
+	 * Posts a new notification to the Growl server.
+	 *
+	 * @param string $description Message to be displayed.
+	 * @param array $options Options consists of:
+	 *        -'title': The title of the displayed notification. Displays the
+	 *         name of the application's parent folder by default.
+	 * @return boolean Always returns `true`.
+	 */
+	public function notify($description = '', $options = array()) {
+		$this->_register();
+
+		$defaults = array('sticky' => false, 'priority' => 0, 'type' => 'Messages');
+		$options += $defaults + array('title' => $this->_config['title']);
+		$type = $options['type'];
+		$title = $options['title'];
+
+		$message = compact('type', 'title', 'description') + array('app' => $this->_config['name']);
+		$message = array_map('utf8_encode', $message);
+
+		$flags = ($options['priority'] & 7) * 2;
+		$flags = ($options['priority'] < 0) ? $flags |= 8 : $flags;
+		$flags = ($options['sticky']) ? $flags | 256 : $flags;
+
+		$params = array('c2n5', static::PROTOCOL_VERSION, static::TYPE_NOTIFY, $flags);
+		$lengths = array_map('strlen', $message);
+
+		$data = call_user_func_array('pack', array_merge($params, $lengths));
+		$data .= join('', $message);
+		$data .= pack('H32', md5($data . $this->_config['password']));
+
+		$this->_send($data);
+		return true;
+	}
+
+	/**
+	 * Growl server connection registration and initialization.
+	 *
+	 * @return boolean True
+	 */
+	protected function _register() {
+		if ($this->_registered) {
+			return true;
+		}
+		$ct = count($this->_config['notifications']);
+		$app = utf8_encode($this->_config['name']);
+		$nameEnc = $defaultEnc = '';
+
+		foreach ($this->_config['notifications'] as $i => $name) {
+			$name = utf8_encode($name);
+			$nameEnc .= pack('n', strlen($name)) . $name;
+			$defaultEnc .= pack('c', $i);
+		}
+		$data = pack('c2nc2', static::PROTOCOL_VERSION, static::TYPE_REG, strlen($app), $ct, $ct);
+		$data .= $app . $nameEnc . $defaultEnc;
+		$checksum = pack('H32', md5($data . $this->_config['password']));
+		$data .= $checksum;
+
+		$this->_send($data);
+		return $this->_registered = true;
+	}
+
+	/**
+	 * Creates a connection to the Growl server using the protocol, host and port configurations
+	 * specified in the constructor.
+	 *
+	 * @return resource Returns a connection resource created by `fsockopen()`.
+	 */
+	protected function _connection() {
+		if ($this->_connection) {
+			return $this->_connection;
+		}
+		$host = "{$this->_config['protocol']}://{$this->_config['host']}";
+
+		if ($this->_connection = fsockopen($host, $this->_config['port'], $message, $code)) {
+			return $this->_connection;
+		}
+		throw new NetworkException("Growl connection failed: (`{$code}`) `{$message}`.");
+	}
+
+	/**
+	 * Sends binary data to the Growl server.
+	 *
+	 * @throws NetworkException Throws an exception if the server connection could not be written
+	 *         to.
+	 * @param string $data The raw binary data to send to the Growl server.
+	 * @return boolean Always returns `true`.
+	 */
+	protected function _send($data) {
+		if (fwrite($this->_connection(), $data, strlen($data)) === false) {
+			throw new NetworkException('Could not send registration to Growl Server.');
+		}
+		return true;
+	}
+
+	/**
+	 * Destructor method. Closes and releases the socket connection to Growl.
+	 */
+	public function __destruct() {
+		if (is_resource($this->_connection)) {
+			fclose($this->_connection);
+			unset($this->_connection);
+		}
+	}
+}
+
+?>

+ 86 - 0
php-lithium/libraries/lithium/analysis/logger/adapter/Syslog.php

@@ -0,0 +1,86 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright     Copyright 2012, Union of RAD (http://union-of-rad.org)
+ * @license       http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+
+namespace lithium\analysis\logger\adapter;
+
+/**
+ * The Syslog adapter facilitates logging messages to a `syslogd` backend. See the constructor for
+ * information on configuring this adapter.
+ *
+ * @see lithium\analysis\logger\adapter\Syslog::__construct()
+ */
+class Syslog extends \lithium\core\Object {
+
+	/**
+	 * Flag indicating whether or not the connection to `syslogd` has been opened yet.
+	 *
+	 * @var boolean
+	 */
+	protected $_isConnected = false;
+
+	/**
+	 * Array that maps `Logger` message priority names to `syslog`-compatible priority constants.
+	 *
+	 * @var array
+	 */
+	protected $_priorities = array(
+		'emergency' => LOG_EMERG,
+		'alert'     => LOG_ALERT,
+		'critical'  => LOG_CRIT,
+		'error'     => LOG_ERR,
+		'warning'   => LOG_WARNING,
+		'notice'    => LOG_NOTICE,
+		'info'      => LOG_INFO,
+		'debug'     => LOG_DEBUG
+	);
+
+	/**
+	 * Class constructor. Configures the `Syslog` adapter instance with the default settings. For
+	 * more information on these settings, see the documentation for
+	 * [the `openlog()` function](http://php.net/openlog).
+	 *
+	 * @param array $config Available configuration settings for this adapter:
+	 *              - `'identity'` _string_: The identity string to be attached to each message in
+	 *                the system log. This is usually a string that meaningfully identifies your
+	 *                application. Defaults to `false`.
+	 *              - `'options'` _integer_: The flags to use when opening the log. Defaults to
+	 *                `LOG_ODELAY`.
+	 *              - `'facility'` _integer_: A flag specifying the program to use to log the
+	 *                messages. See the `openlog()` documentation for more information. Defaults to
+	 *                `LOG_USER`.
+	 */
+	public function __construct(array $config = array()) {
+		$defaults = array('identity' => false, 'options'  => LOG_ODELAY, 'facility' => LOG_USER);
+		parent::__construct($config + $defaults);
+	}
+
+	/**
+	 * Appends `$message` to the system log.
+	 *
+	 * @param string $priority The message priority string. Maps to a `syslogd` priority constant.
+	 * @param string $message The message to write.
+	 * @return closure Function returning boolean `true` on successful write, `false` otherwise.
+	 */
+	public function write($priority, $message) {
+		$config = $this->_config;
+		$_priorities = $this->_priorities;
+
+		if (!$this->_isConnected) {
+			closelog();
+			openlog($config['identity'], $config['options'], $config['facility']);
+			$this->_isConnected = true;
+		}
+
+		return function($self, $params) use ($_priorities) {
+			$priority = $_priorities[$params['priority']];
+			return syslog($priority, $params['message']);
+		};
+	}
+}
+
+?>

+ 22 - 0
php-lithium/libraries/lithium/composer.json

@@ -0,0 +1,22 @@
+{
+	"name": "UnionOfRAD/lithium",
+	"type": "lithium-library",
+	"description": "The core library of the Lithium PHP framework",
+	"keywords": ["lithium", "framework"],
+	"homepage": "http://lithify.me",
+	"license": "BSD-3-Clause",
+	"authors": [
+		{
+			"name": "Union of RAD",
+			"homepage": "http://union-of-rad.org"
+		},
+		{
+			"name": "The Lithium Community",
+			"homepage": "http://github.com/UnionOfRAD/lithium/graphs/contributors"
+		}
+	],
+	"require": {
+		"php": ">=5.3.6",
+		"composer/installers": "dev-master"
+	}
+}

+ 423 - 0
php-lithium/libraries/lithium/console/Command.php

@@ -0,0 +1,423 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright     Copyright 2013, Union of RAD (http://union-of-rad.org)
+ * @license       http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+
+namespace lithium\console;
+
+use Exception;
+use lithium\console\command\Help;
+
+/**
+ * All Commands to be run from the Lithium console must extend this class.
+ *
+ * The `run` method is automatically called if it exists. Otherwise, if a method does not exist
+ * the `Help` command will be run.
+ *
+ * {{{
+ * $ li3 example
+ * $ li3 example --format=json
+ * }}}
+ *
+ */
+class Command extends \lithium\core\Object {
+
+	/**
+	 * If -h or --help param exists a help screen will be returned.
+	 * Similar to running `li3 help COMMAND`.
+	 *
+	 * @var boolean
+	 */
+	public $help = false;
+
+	/**
+	 * A Request object.
+	 *
+	 * @see lithium\console\Request
+	 * @var object
+	 */
+	public $request;
+
+	/**
+	 * A Response object.
+	 *
+	 * @see lithium\console\Response
+	 * @var object
+	 */
+	public $response;
+
+	/**
+	 * Only shows only text output without styles.
+	 *
+	 * @var boolean
+	 */
+	public $plain = false;
+
+	/**
+	 * Only shows error output.
+	 *
+	 * @var boolean
+	 */
+	public $silent = false;
+
+	/**
+	 * Dynamic dependencies.
+	 *
+	 * @var array
+	 */
+	protected $_classes = array(
+		'response' => 'lithium\console\Response'
+	);
+
+	/**
+	 * Auto configuration.
+	 *
+	 * @var array
+	 */
+	protected $_autoConfig = array('classes' => 'merge');
+
+	/**
+	 * Constructor.
+	 *
+	 * @param array $config
+	 * @return void
+	 */
+	public function __construct(array $config = array()) {
+		$defaults = array('request' => null, 'response' => array(), 'classes' => $this->_classes);
+		parent::__construct($config + $defaults);
+	}
+
+	/**
+	 * Command Initializer.
+	 *
+	 * Populates the `$response` property with a new instance of the `Response` class passing it
+	 * configuration and assigns the values from named parameters of the request (if applicable) to
+	 * properties of the command.
+	 *
+	 * @return void
+	 */
+	protected function _init() {
+		parent::_init();
+		$this->request = $this->_config['request'];
+
+		if (!is_object($this->request) || !$this->request->params) {
+			return;
+		}
+		$this->response = $this->_config['response'];
+
+		if (!is_object($this->response)) {
+			$this->response = $this->_instance('response', $this->response);
+		}
+		$default = array('command' => null, 'action' => null, 'args' => null);
+		$params = array_diff_key((array) $this->request->params, $default);
+
+		foreach ($params as $key => $param) {
+			$this->{$key} = $param;
+		}
+	}
+
+	/**
+	 * Called by the `Dispatcher` class to invoke an action.
+	 *
+	 * @see lithium\console\Dispatcher
+	 * @see lithium\console\Response
+	 * @param string $action The name of the method to run.
+	 * @param array $args The arguments from the request.
+	 * @param array $options
+	 * @return object The response object associated with this command.
+	 * @todo Implement filters.
+	 */
+	public function __invoke($action, $args = array(), $options = array()) {
+		try {
+			$this->response->status = 1;
+			$result = $this->invokeMethod($action, $args);
+
+			if (is_int($result)) {
+				$this->response->status = $result;
+			} elseif ($result || $result === null) {
+				$this->response->status = 0;
+			}
+		} catch (Exception $e) {
+			$this->error($e->getMessage());
+		}
+		return $this->response;
+	}
+
+	/**
+	 * Invokes the `Help` command.
+	 *
+	 * The invoked Help command will take over request and response objects of
+	 * the originally invoked command. Thus the response of the Help command
+	 * becomes the response of the original one.
+	 *
+	 * @return boolean
+	 */
+	protected function _help() {
+		$help = new Help(array(
+			'request' => $this->request,
+			'response' => $this->response,
+			'classes' => $this->_classes
+		));
+		return $help->run(get_class($this));
+	}
+
+	/**
+	 * Writes a string to the output stream.
+	 *
+	 * @param string $output The string to write.
+	 * @param integer|string|array $options
+	 *        integer as the number of new lines.
+	 *        string as the style
+	 *        array as :
+	 *        - nl : number of new lines to add at the end
+	 *        - style : the style name to wrap around the
+	 * @return integer
+	 */
+	public function out($output = null, $options = array('nl' => 1)) {
+		if ($this->silent) {
+			return;
+		}
+		return $this->_response('output', $output, $options);
+	}
+
+	/**
+	 * Writes a string to error stream.
+	 *
+	 * @param string $error The string to write.
+	 * @param integer|string|array $options
+	 *        integer as the number of new lines.
+	 *        string as the style
+	 *        array as :
+	 *        - nl : number of new lines to add at the end
+	 *        - style : the style name to wrap around the
+	 * @return integer
+	 */
+	public function error($error = null, $options = array('nl' => 1)) {
+		return $this->_response('error', $error, $options);
+	}
+
+	/**
+	 * Handles input. Will continue to loop until `$options['quit']` or
+	 * result is part of `$options['choices']`.
+	 *
+	 * @param string $prompt
+	 * @param array $options
+	 * @return string Returns the result of the input data. If the input is equal to the `quit`
+	 *          option boolean `false` is returned
+	 */
+	public function in($prompt = null, array $options = array()) {
+		$defaults = array('choices' => null, 'default' => null, 'quit' => 'q');
+		$options += $defaults;
+		$choices = null;
+
+		if (is_array($options['choices'])) {
+			$choices = '(' . implode('/', $options['choices']) . ')';
+		}
+		$default = $options['default'] ? "[{$options['default']}] " : '';
+
+		do {
+			$this->out("{$prompt} {$choices} \n {$default}> ", false);
+			$result = trim($this->request->input());
+		} while (
+			!empty($options['choices']) && !in_array($result, $options['choices'], true)
+			&& (empty($options['quit']) || $result !== $options['quit'])
+			&& (!$options['default'] || $result !== '')
+		);
+
+		if ($result == $options['quit']) {
+			return false;
+		}
+
+		if ($options['default'] !== null && $result == '') {
+			return $options['default'];
+		}
+		return $result;
+	}
+
+	/**
+	 * Writes a header to the output stream. In addition to the actual text,
+	 * horizontal lines before and afterwards are written. The lines will have
+	 * the same length as the text. This behavior can be modified by providing
+	 * the length of lines as a second paramerter.
+	 *
+	 * Given the text `'Lithium'` this generates following output:
+	 *
+	 * {{{
+	 * -------
+	 * Lithium
+	 * -------
+	 * }}}
+	 *
+	 * @param string $text The heading text.
+	 * @param integer $line The length of the line. Defaults to the length of text.
+	 * @return void
+	 */
+	public function header($text, $line = null) {
+		if (!$line) {
+			$line = strlen($text);
+		}
+		$this->hr($line);
+		$this->out($text, 1, 'heading');
+		$this->hr($line);
+	}
+
+	/**
+	 * Writes rows of columns.
+	 *
+	 * This method expects asceding integer values as the keys, which map to the appropriate
+	 * columns. Currently, there is no special "header" option, but you can define them for your
+	 * own.
+	 *
+	 * Example Usage:
+	 *
+	 * {{{
+	 * $output = array(
+	 *     array('Name', 'Age'),
+	 *     array('----', '---'),
+	 * );
+	 * foreach($users as $user) {
+	 *     $output[] = array($user->name, $user->age);
+	 * }
+	 * $this->columns($output);
+	 * }}}
+	 *
+	 * Would render something similar to:
+	 *
+	 * {{{
+	 * Name       Age
+	 * ----       ---
+	 * Jane Doe   22
+	 * Foo Bar    18
+	 * }}}
+	 *
+	 * This method also calculates the needed space between the columns. All option params given
+	 * also get passed down to the `out()` method, which allow custom formatting. Passing something
+	 * like `$this->columns($output, array('style' => 'red)` would print the table in red.
+	 *
+	 * @see lithium\console\Response::styles()
+	 * @param array $rows The rows to print, with each column as an array element.
+	 * @param array $options Optional params:
+	 *      - separator : Different column separator, defaults to `\t`
+	 *      - style : the style name to wrap around the columns output
+	 * @return void
+	 */
+	public function columns($rows, $options = array()) {
+		$defaults = array('separator' => "\t", "error" => false);
+		$options += $defaults;
+		$lengths = array_reduce($rows, function($columns, $row) {
+			foreach ((array) $row as $key => $val) {
+				if (!isset($columns[$key]) || strlen($val) > $columns[$key]) {
+					$columns[$key] = strlen($val);
+				}
+			}
+			return $columns;
+		});
+		$rows = array_reduce($rows, function($rows, $row) use ($lengths, $options) {
+			$text = '';
+			foreach ((array) $row as $key => $val) {
+				$text = $text . str_pad($val, $lengths[$key]) . $options['separator'];
+			}
+			$rows[] = $text;
+			return $rows;
+		});
+		if ($options['error']) {
+			$this->error($rows, $options);
+			return;
+		}
+		$this->out($rows, $options);
+	}
+
+	/**
+	 * Add newlines ("\n") a given number of times and return them in a single string.
+	 *
+	 * @param integer $number The number of new lines to fill into a string.
+	 * @return string
+	 */
+	public function nl($number = 1) {
+		return str_repeat("\n", $number);
+	}
+
+	/**
+	 * Adds a horizontal line to output stream.
+	 *
+	 * @param integer $length The length of the line, defaults to 80.
+	 * @param integer $newlines How many new lines to print afterwards, defaults to 1.
+	 * @return integer
+	 */
+	public function hr($length = 80, $newlines = 1) {
+		return $this->out(str_repeat('-', $length), $newlines);
+	}
+
+	/**
+	 * Clears the entire screen.
+	 *
+	 * @return void
+	 */
+	public function clear() {
+		passthru(strtoupper(substr(PHP_OS, 0, 3)) == 'WIN' ? 'cls' : 'clear');
+	}
+
+	/**
+	 * Stop execution, by exiting the script.
+	 *
+	 * @param integer $status Numeric value that will be used on `exit()`.
+	 * @param boolean $message An optional message that will be written to the stream.
+	 * @return void
+	 */
+	public function stop($status = 0, $message = null) {
+		if ($message) {
+			($status == 0) ? $this->out($message) : $this->error($message);
+		}
+		exit($status);
+	}
+
+	/**
+	 * Handles the response that is sent to the stream.
+	 *
+	 * @param string $type the stream either output or error
+	 * @param string $string the message to render
+	 * @param integer|string|array $options
+	 *        integer as the number of new lines.
+	 *        string as the style
+	 *        array as :
+	 *        - nl : number of new lines to add at the end
+	 *        - style : the style name to wrap around the
+	 * @return void
+	 */
+	protected function _response($type, $string, $options) {
+		$defaults = array('nl' => 1, 'style' => null);
+
+		if (!is_array($options)) {
+			if (!$options || is_int($options)) {
+				$options = array('nl' => $options);
+			} else if (is_string($options)) {
+				$options = array('style' => $options);
+			} else {
+				$options = array();
+			}
+		}
+		$options += $defaults;
+
+		if (is_array($string)) {
+			$method = ($type == 'error' ? $type : 'out');
+			foreach ($string as $out) {
+				$this->{$method}($out, $options);
+			}
+			return;
+		}
+		extract($options);
+
+		if ($style !== null && !$this->plain) {
+			$string = "{:{$style}}{$string}{:end}";
+		}
+		if ($nl) {
+			$string = $string . $this->nl($nl);
+		}
+		return $this->response->{$type}($string);
+	}
+}
+
+?>

+ 202 - 0
php-lithium/libraries/lithium/console/Dispatcher.php

@@ -0,0 +1,202 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright     Copyright 2013, Union of RAD (http://union-of-rad.org)
+ * @license       http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+
+namespace lithium\console;
+
+use lithium\core\Libraries;
+use lithium\core\Environment;
+use UnexpectedValueException;
+
+/**
+ * The `Dispatcher` is the outermost layer of the framework, responsible for both receiving the
+ * initial console request and returning back a response at the end of the request's life cycle.
+ *
+ * The console dispatcher is responsible for accepting requests from scripts called from the command
+ * line, and executing the appropriate `Command` class(es). The `run()` method accepts an instance
+ * of `lithium\console\Request`, which encapsulates the console environment and any command-line
+ * parameters passed to the script. `Dispatcher` then invokes `lithium\console\Router` to determine
+ * the correct `Command` class to invoke, and which method should be called.
+ */
+class Dispatcher extends \lithium\core\StaticObject {
+
+	/**
+	 * Fully-namespaced router class reference.
+	 *
+	 * Class must implement a `parse()` method, which must return an array with (at a minimum)
+	 * 'command' and 'action' keys.
+	 *
+	 * @see lithium\console\Router::parse()
+	 * @var array
+	 */
+	protected static $_classes = array(
+		'request' => 'lithium\console\Request',
+		'router' => 'lithium\console\Router'
+	);
+
+	/**
+	 * Contains pre-process format strings for changing Dispatcher's behavior based on 'rules'.
+	 *
+	 * Each key in the array represents a 'rule'; if a key that matches the rule is present (and
+	 * not empty) in a route, (i.e. the result of `lithium\console\Router::parse()`) then the rule's
+	 * value will be applied to the route before it is dispatched.  When applying a rule, any array
+	 * elements array elements of the flag which are present in the route will be modified using a
+	 * `lithium\util\String::insert()`-formatted string.
+	 *
+	 * @see lithium\console\Dispatcher::config()
+	 * @see lithium\util\String::insert()
+	 * @var array
+	 */
+	protected static $_rules = array(
+		'command' => array(array('lithium\util\Inflector', 'camelize')),
+		'action' => array(array('lithium\util\Inflector', 'camelize', array(false)))
+	);
+
+	/**
+	 * Used to set configuration parameters for the Dispatcher.
+	 *
+	 * @param array $config Optional configuration params.
+	 * @return array If no parameters are passed, returns an associative array with the
+	 *         current configuration, otherwise returns null.
+	 */
+	public static function config($config = array()) {
+		if (!$config) {
+			return array('rules' => static::$_rules);
+		}
+		foreach ($config as $key => $val) {
+			if (isset(static::${'_' . $key})) {
+				static::${'_' . $key} = $val + static::${'_' . $key};
+			}
+		}
+	}
+
+	/**
+	 * Dispatches a request based on a request object (an instance of `lithium\console\Request`).
+	 *
+	 *  If `$request` is `null`, a new request object is instantiated based on the value of the
+	 * `'request'` key in the `$_classes` array.
+	 *
+	 * @param object $request An instance of a request object with console request information.  If
+	 *        `null`, an instance will be created.
+	 * @param array $options
+	 * @return object The command action result which is an instance of `lithium\console\Response`.
+	 */
+	public static function run($request = null, $options = array()) {
+		$defaults = array('request' => array());
+		$options += $defaults;
+		$classes = static::$_classes;
+		$params = compact('request', 'options');
+
+		return static::_filter(__FUNCTION__, $params, function($self, $params) use ($classes) {
+			$request = $params['request'];
+			$options = $params['options'];
+			$router = $classes['router'];
+			$request = $request ?: new $classes['request']($options['request']);
+			$request->params = $router::parse($request);
+			$params = $self::applyRules($request->params);
+			Environment::set($request);
+			try {
+				$callable = $self::invokeMethod('_callable', array($request, $params, $options));
+				return $self::invokeMethod('_call', array($callable, $request, $params));
+			} catch (UnexpectedValueException $e) {
+				return (object) array('status' => $e->getMessage() . "\n");
+			}
+		});
+	}
+
+	/**
+	 * Determines which command to use for current request.
+	 *
+	 * @param object $request An instance of a `Request` object.
+	 * @param array $params Request params that can be accessed inside the filter.
+	 * @param array $options
+	 * @return class lithium\console\Command Returns the instantiated command object.
+	 */
+	protected static function _callable($request, $params, $options) {
+		$params = compact('request', 'params', 'options');
+		return static::_filter(__FUNCTION__, $params, function($self, $params) {
+			$request = $params['request'];
+			$params = $params['params'];
+			$name = $params['command'];
+
+			if (!$name) {
+				$request->params['args'][0] = $name;
+				$name = 'lithium\console\command\Help';
+			}
+			if (class_exists($class = Libraries::locate('command', $name))) {
+				return new $class(compact('request'));
+			}
+			throw new UnexpectedValueException("Command `{$name}` not found.");
+		});
+	}
+
+	/**
+	 * Attempts to apply a set of formatting rules from `$_rules` to a `$params` array.
+	 *
+	 * Each formatting rule is applied if the key of the rule in `$_rules` is present and not empty
+	 * in `$params`.  Also performs sanity checking against `$params` to ensure that no value
+	 * matching a rule is present unless the rule check passes.
+	 *
+	 * @param array $params An array of route parameters to which rules will be applied.
+	 * @return array Returns the `$params` array with formatting rules applied to array values.
+	 */
+	public static function applyRules($params) {
+		$result = array();
+
+		if (!$params) {
+			return false;
+		}
+
+		foreach (static::$_rules as $name => $rules) {
+			foreach ($rules as $rule) {
+				if (!empty($params[$name]) && isset($rule[0])) {
+					$options = array_merge(
+						array($params[$name]), isset($rule[2]) ? (array) $rule[2] : array()
+					);
+					$result[$name] = call_user_func_array(array($rule[0], $rule[1]), $options);
+				}
+			}
+		}
+		return $result + array_diff_key($params, $result);
+	}
+
+	/**
+	 * Calls a given command with the appropriate action.
+	 *
+	 * This method is responsible for calling a `$callable` command and returning its result.
+	 *
+	 * @param string $callable The callable command.
+	 * @param string $request The associated `Request` object.
+	 * @param string $params Additional params that should be passed along.
+	 * @return mixed Returns the result of the called action, typically `true` or `false`.
+	 */
+	protected static function _call($callable, $request, $params) {
+		$params = compact('callable', 'request', 'params');
+		return static::_filter(__FUNCTION__, $params, function($self, $params) {
+			if (is_callable($callable = $params['callable'])) {
+				$request = $params['request'];
+				$params = $params['params'];
+
+				if (!method_exists($callable, $params['action'])) {
+					array_unshift($params['args'], $request->params['action']);
+					$params['action'] = 'run';
+				}
+				$isHelp = (
+					!empty($params['help']) || !empty($params['h'])
+					|| !method_exists($callable, $params['action'])
+				);
+				if ($isHelp) {
+					$params['action'] = '_help';
+				}
+				return $callable($params['action'], $params['args']);
+			}
+			throw new UnexpectedValueException("Callable `{$callable}` is actually not callable.");
+		});
+	}
+}
+
+?>

+ 203 - 0
php-lithium/libraries/lithium/console/Request.php

@@ -0,0 +1,203 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright     Copyright 2013, Union of RAD (http://union-of-rad.org)
+ * @license       http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+
+namespace lithium\console;
+
+/**
+ * The `Request` class represents a console request and holds information about it's
+ * environment as well as passed arguments.
+ *
+ * @see lithium\console\Dispatcher
+ */
+class Request extends \lithium\core\Object {
+
+	/**
+	 * The raw data passed from the command line
+	 *
+	 * @var array
+	 */
+	public $argv = array();
+
+	/**
+	 * Parameters parsed from arguments.
+	 *
+	 * @see lithium\console\Router
+	 * @var array
+	 */
+	public $params = array(
+		'command' => null, 'action' => 'run', 'args' => array()
+	);
+
+	/**
+	 * Input (STDIN).
+	 *
+	 * @var resource
+	 */
+	public $input;
+
+	/**
+	 * Enviroment variables.
+	 *
+	 * @var array
+	 */
+	protected $_env = array();
+
+	/**
+	 * Holds the value of the current locale, set through the `locale()` method.
+	 *
+	 * @var string
+	 */
+	protected $_locale = null;
+
+	/**
+	 * Auto configuration
+	 *
+	 * @var array
+	 */
+	protected $_autoConfig = array('env' => 'merge');
+
+	/**
+	 * Class Constructor
+	 *
+	 * @param array $config
+	 */
+	public function __construct($config = array()) {
+		$defaults = array('args' => array(), 'input' => null);
+		$config += $defaults;
+		parent::__construct($config);
+	}
+
+	/**
+	 * Initialize request object, pulling request data from superglobals.
+	 *
+	 * Defines an artificial `'PLATFORM'` environment variable as `'CLI'` to
+	 * allow checking for the SAPI in a normalized way. This is also for
+	 * establishing consistency with this class' sister classes.
+	 *
+	 * @see lithium\action\Request::_init()
+	 * @return void
+	 */
+	protected function _init() {
+		$this->_env += (array) $_SERVER + (array) $_ENV;
+		$this->_env['working'] = getcwd() ?: null;
+		$argv = (array) $this->env('argv');
+		$this->_env['script'] = array_shift($argv);
+		$this->_env['PLATFORM'] = 'CLI';
+		$this->argv += $argv + (array) $this->_config['args'];
+		$this->input = $this->_config['input'];
+
+		if (!is_resource($this->_config['input'])) {
+			$this->input = fopen('php://stdin', 'r');
+		}
+		parent::_init();
+	}
+
+	/**
+	 * Allows request parameters to be accessed as object properties, i.e. `$this->request->action`
+	 * instead of `$this->request->params['action']`.
+	 *
+	 * @see lithium\action\Request::$params
+	 * @param string $name The property name/parameter key to return.
+	 * @return mixed Returns the value of `$params[$name]` if it is set, otherwise returns null.
+	 */
+	public function __get($name) {
+		if (isset($this->params[$name])) {
+			return $this->params[$name];
+		}
+	}
+
+	public function __isset($name) {
+		return isset($this->params[$name]);
+	}
+
+	/**
+	 * Get the value of a command line argument at a given key
+	 *
+	 * @param integer $key
+	 * @return mixed returns null if key does not exist or the value of the key in the args array
+	 */
+	public function args($key = 0) {
+		if (!empty($this->args[$key])) {
+			return $this->args[$key];
+		}
+		return null;
+	}
+
+	/**
+	 * Get environment variables.
+	 *
+	 * @param string $key
+	 * @return mixed Returns the environment key related to the `$key` argument. If `$key` is equal
+	 * to null the result will be the entire environment array. If `$key` is set but not
+	 * available, `null` will be returned.
+	 */
+	public function env($key = null) {
+		if (!empty($this->_env[$key])) {
+			return $this->_env[$key];
+		}
+		if ($key === null) {
+			return $this->_env;
+		}
+		return null;
+	}
+
+	/**
+	 * Moves params up a level. Sets command to action, action to passed[0], and so on.
+	 *
+	 * @param integer $num how many times to shift
+	 * @return self
+	 */
+	public function shift($num = 1) {
+		for ($i = $num; $i > 1; $i--) {
+			$this->shift(--$i);
+		}
+		$this->params['command'] = $this->params['action'];
+		if (isset($this->params['args'][0])) {
+			$this->params['action'] = array_shift($this->params['args']);
+		}
+		return $this;
+	}
+
+	/**
+	 * Reads a line from input.
+	 *
+	 * @return string
+	 */
+	public function input() {
+		return fgets($this->input);
+	}
+
+	/**
+	 * Sets or returns the current locale string. For more information, see
+	 * "[Globalization](http://lithify.me/docs/manual/07_globalization)" in the manual.
+	 *
+	 * @param string $locale An optional locale string like `'en'`, `'en_US'` or `'de_DE'`. If
+	 *               specified, will overwrite the existing locale.
+	 * @return Returns the currently set locale string.
+	 */
+	public function locale($locale = null) {
+		if ($locale) {
+			$this->_locale = $locale;
+		}
+		return $this->_locale;
+	}
+
+	/**
+	 * Return input
+	 * Destructor. Closes input.
+	 *
+	 * @return void
+	 */
+	public function __destruct() {
+		if ($this->input) {
+			fclose($this->input);
+		}
+	}
+}
+
+?>

+ 142 - 0
php-lithium/libraries/lithium/console/Response.php

@@ -0,0 +1,142 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright     Copyright 2012, Union of RAD (http://union-of-rad.org)
+ * @license       http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+
+namespace lithium\console;
+
+use lithium\util\String;
+
+/**
+ * The `Response` class is used by other console classes to generate output. It contains stream
+ * resources for writing output and errors, as well as shell coloring information, and the response
+ * status code for the currently-executing command.
+ */
+class Response extends \lithium\core\Object {
+
+	/**
+	 * Output stream, STDOUT
+	 *
+	 * @var stream
+	 */
+	public $output = null;
+
+	/**
+	 * Error stream, STDERR
+	 *
+	 * @var stream
+	 */
+	public $error = null;
+
+	/**
+	 * Status code, most often used for setting an exit status.
+	 *
+	 * It should be expected that only status codes in the range of 0-255
+	 * can be properly evaluated.
+	 *
+	 * @var integer
+	 * @see lithium\console\Command
+	 */
+	public $status = 0;
+
+	/**
+	 * Construct Request object
+	 *
+	 * @param array $config
+	 *              - request object lithium\console\Request
+	 *              - output stream
+	 *              _ error stream
+	 */
+	public function __construct($config = array()) {
+		$defaults = array('output' => null, 'error' => null);
+		$config += $defaults;
+
+		$this->output = $config['output'];
+
+		if (!is_resource($this->output)) {
+			$this->output = fopen('php://stdout', 'r');
+		}
+
+		$this->error = $config['error'];
+
+		if (!is_resource($this->error)) {
+			$this->error = fopen('php://stderr', 'r');
+		}
+		parent::__construct($config);
+	}
+
+	/**
+	 * Writes string to output stream
+	 *
+	 * @param string $output
+	 * @return mixed
+	 */
+	public function output($output) {
+		return fwrite($this->output, String::insert($output, $this->styles()));
+	}
+
+	/**
+	 * Writes string to error stream
+	 *
+	 * @param string $error
+	 * @return mixed
+	 */
+	public function error($error) {
+		return fwrite($this->error, String::insert($error, $this->styles()));
+	}
+
+	/**
+	 * Destructor to close streams
+	 *
+	 * @return void
+	 *
+	 */
+	public function __destruct() {
+		if ($this->output) {
+			fclose($this->output);
+		}
+		if ($this->error) {
+			fclose($this->error);
+		}
+	}
+
+	/**
+	 * Handles styling output.
+	 *
+	 * @param array $styles
+	 * @return array
+	 */
+	public function styles($styles = array()) {
+		$defaults = array(
+			'end'    => "\033[0m",
+			'black'  => "\033[0;30m",
+			'red'    => "\033[0;31m",
+			'green'  => "\033[0;32m",
+			'yellow' => "\033[0;33m",
+			'blue'   => "\033[0;34m",
+			'purple' => "\033[0;35m",
+			'cyan'   => "\033[0;36m",
+			'white'  => "\033[0;37m",
+			'heading' => "\033[1;36m",
+			'option'  => "\033[0;35m",
+			'command' => "\033[0;35m",
+			'error'   => "\033[0;31m",
+			'success' => "\033[0;32m",
+			'bold'    => "\033[1m",
+		);
+		if ($styles === false) {
+			return array_combine(array_keys($defaults), array_pad(array(), count($defaults), null));
+		}
+		$styles += $defaults;
+
+		if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
+			return $this->styles(false);
+		}
+		return $styles;
+	}
+}
+
+?>

+ 55 - 0
php-lithium/libraries/lithium/console/Router.php

@@ -0,0 +1,55 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright     Copyright 2013, Union of RAD (http://union-of-rad.org)
+ * @license       http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+
+namespace lithium\console;
+
+/**
+ * The `Router` class uses an instance of `lithium\console\Request`, which represents an incoming
+ * command-line invocation, to parse the correct command, and sub-command(s) and parameters, which
+ * are used by `lithium\console\Dispatcher` to load and execute the proper `Command` class.
+ */
+class Router extends \lithium\core\Object {
+
+	/**
+	 * Parse incoming request from console. Short and long (GNU-style) options
+	 * in the form of `-f`, `--foo`, `--foo-bar` and `--foo=bar` are parsed.
+	 * XF68-style long options (i.e. `-foo`) are not supported but support
+	 * can be added by extending this class.
+	 *
+	 * @param object $request lithium\console\Request
+	 * @return array $params
+	 */
+	public static function parse($request = null) {
+		$defaults = array('command' => null, 'action' => 'run', 'args' => array());
+		$params = $request ? (array) $request->params + $defaults : $defaults;
+
+		if (!empty($request->argv)) {
+			$args = $request->argv;
+
+			while ($arg = array_shift($args)) {
+				if (preg_match('/^-(?P<key>[a-zA-Z0-9])$/i', $arg, $match)) {
+					$params[$match['key']] = true;
+					continue;
+				}
+				if (preg_match('/^--(?P<key>[a-z0-9-]+)(?:=(?P<val>.+))?$/i', $arg, $match)) {
+					$params[$match['key']] = !isset($match['val']) ? true : $match['val'];
+					continue;
+				}
+				$params['args'][] = $arg;
+			}
+		}
+		foreach (array('command', 'action') as $param) {
+			if (!empty($params['args'])) {
+				$params[$param] = array_shift($params['args']);
+			}
+		}
+		return $params;
+	}
+}
+
+?>

+ 260 - 0
php-lithium/libraries/lithium/console/command/Create.php

@@ -0,0 +1,260 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright     Copyright 2012, Union of RAD (http://union-of-rad.org)
+ * @license       http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+
+namespace lithium\console\command;
+
+use lithium\util\String;
+use lithium\core\Libraries;
+use lithium\util\Inflector;
+use lithium\core\ClassNotFoundException;
+
+/**
+ * The `create` command allows you to rapidly develop your models, views, controllers, and tests
+ * by generating the minimum code necessary to test and run your application.
+ *
+ * `li3 create --template=controller Posts`
+ * `li3 create --template=model Posts`
+ *
+ */
+class Create extends \lithium\console\Command {
+
+	/**
+	 * Name of library to use
+	 *
+	 * @var string
+	 */
+	public $library = null;
+
+	/**
+	 * The name of the template to use to generate the file. This allows you to add a custom
+	 * template to be used in place of the core template for each command. Place templates in
+	 * `<library>\extensions\command\create\template`.
+	 *
+	 * @var string
+	 */
+	public $template = null;
+
+	/**
+	 * Holds library data from `lithium\core\Libraries::get()`.
+	 *
+	 * @var array
+	 */
+	protected $_library = array();
+
+	/**
+	 * Class initializer. Parses template and sets up params that need to be filled.
+	 *
+	 * @return void
+	 */
+	protected function _init() {
+		parent::_init();
+		$this->library = $this->library ?: true;
+		$defaults = array('prefix' => null, 'path' => null);
+		$this->_library = (array) Libraries::get($this->library) + $defaults;
+	}
+
+	/**
+	 * Run the create command. Takes `$command` and delegates to `$command::$method`
+	 *
+	 * @param string $command
+	 * @return boolean
+	 */
+	public function run($command = null) {
+		if ($command && !$this->request->args()) {
+			return $this->_default($command);
+		}
+		$this->request->shift();
+		$this->template = $this->template ?: $command;
+
+		if (!$command) {
+			return false;
+		}
+		if ($this->_execute($command)) {
+			return true;
+		}
+		$this->error("{$command} could not be created.");
+		return false;
+	}
+
+	/**
+	 * Execute the given sub-command for the current request.
+	 *
+	 * @param string $command The sub-command name. example: Model, Controller, Test
+	 * @return boolean
+	 */
+	protected function _execute($command) {
+		try {
+			if (!$class = $this->_instance($command)) {
+				return false;
+			}
+		} catch (ClassNotFoundException $e) {
+			return false;
+		}
+		$data = array();
+		$params = $class->invokeMethod('_params');
+
+		foreach ($params as $i => $param) {
+			$data[$param] = $class->invokeMethod("_{$param}", array($this->request));
+		}
+
+		if ($message = $class->invokeMethod('_save', array($data))) {
+			$this->out($message);
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Run through the default set. model, controller, test model, test controller
+	 *
+	 * @param string $name class name to create
+	 * @return boolean
+	 */
+	protected function _default($name) {
+		$commands = array(
+			array('model', Inflector::pluralize($name)),
+			array('controller', Inflector::pluralize($name)),
+			array('test', 'model', Inflector::pluralize($name)),
+			array('test', 'controller', Inflector::pluralize($name))
+		);
+		foreach ($commands as $args) {
+			$command = $this->template = $this->request->params['command'] = array_shift($args);
+			$this->request->params['action'] = array_shift($args);
+			$this->request->params['args'] = $args;
+
+			if (!$this->_execute($command)) {
+				return false;
+			}
+		}
+		return true;
+	}
+
+	/**
+	 * Get the namespace.
+	 *
+	 * @param string $request
+	 * @param array $options
+	 * @return string
+	 */
+	protected function _namespace($request, $options  = array()) {
+		$name = $request->command;
+		$defaults = array(
+			'prefix' => $this->_library['prefix'],
+			'prepend' => null,
+			'spaces' => array(
+				'model' => 'models', 'view' => 'views', 'controller' => 'controllers',
+				'command' => 'extensions.command', 'adapter' => 'extensions.adapter',
+				'helper' => 'extensions.helper'
+			)
+		);
+		$options += $defaults;
+
+		if (isset($options['spaces'][$name])) {
+			$name = $options['spaces'][$name];
+		}
+		return str_replace('.', '\\', $options['prefix'] . $options['prepend'] . $name);
+	}
+
+	/**
+	 * Parse a template to find available variables specified in `{:name}` format. Each variable
+	 * corresponds to a method in the sub command. For example, a `{:namespace}` variable will
+	 * call the namespace method in the model command when `li3 create model Post` is called.
+	 *
+	 * @return array
+	 */
+	protected function _params() {
+		$contents = $this->_template();
+
+		if (empty($contents)) {
+			return array();
+		}
+		preg_match_all('/(?:\{:(?P<params>[^}]+)\})/', $contents, $keys);
+
+		if (!empty($keys['params'])) {
+			return array_values(array_unique($keys['params']));
+		}
+		return array();
+	}
+
+	/**
+	 * Returns the contents of the template.
+	 *
+	 * @return string
+	 */
+	protected function _template() {
+		$file = Libraries::locate('command.create.template', $this->template, array(
+			'filter' => false, 'type' => 'file', 'suffix' => '.txt.php'
+		));
+		if (!$file || is_array($file)) {
+			return false;
+		}
+		return file_get_contents($file);
+	}
+
+	/**
+	 * Get an instance of a sub-command
+	 *
+	 * @param string $name the name of the sub-command to instantiate
+	 * @param array $config
+	 * @return object;
+	 */
+	protected function _instance($name, array $config = array()) {
+		if ($class = Libraries::locate('command.create', Inflector::camelize($name))) {
+			$this->request->params['template'] = $this->template;
+
+			return new $class(array(
+				'request' => $this->request,
+				'classes' => $this->_classes
+			));
+		}
+		return parent::_instance($name, $config);
+	}
+
+
+	/**
+	 * Save a template with the current params. Writes file to `Create::$path`.
+	 *
+	 * @param array $params
+	 * @return string A result string on success of writing the file. If any errors occur along
+	 * the way such as missing information boolean false is returned.
+	 */
+	protected function _save(array $params = array()) {
+		$defaults = array('namespace' => null, 'class' => null);
+		$params += $defaults;
+
+		if (empty($params['class']) || empty($this->_library['path'])) {
+			return false;
+		}
+		$contents = $this->_template();
+		$result = String::insert($contents, $params);
+		$namespace = str_replace($this->_library['prefix'], '\\', $params['namespace']);
+		$path = str_replace('\\', '/', "{$namespace}\\{$params['class']}");
+		$path = $this->_library['path'] . stristr($path, '/');
+		$file = str_replace('//', '/', "{$path}.php");
+		$directory = dirname($file);
+		$relative = str_replace($this->_library['path'] . '/', "", $file);
+
+		if ((!is_dir($directory)) && !mkdir($directory, 0755, true)) {
+			return false;
+		}
+		if (file_exists($file)) {
+			$prompt = "{$relative} already exists. Overwrite?";
+			$choices = array('y', 'n');
+			if ($this->in($prompt, compact('choices')) !== 'y') {
+				return "{$params['class']} skipped.";
+			}
+		}
+
+		if (file_put_contents($file, "<?php\n\n{$result}\n\n?>")) {
+			return "{$params['class']} created in {$relative}.";
+		}
+		return false;
+	}
+}
+
+?>

+ 36 - 0
php-lithium/libraries/lithium/console/command/G11n.php

@@ -0,0 +1,36 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright     Copyright 2013, Union of RAD (http://union-of-rad.org)
+ * @license       http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+
+namespace lithium\console\command;
+
+use lithium\console\command\g11n\Extract;
+
+/**
+ * The `G11n` set of commands deals with the extraction and merging of message templates.
+ */
+class G11n extends \lithium\console\Command {
+
+	/**
+	 * The main method of the command.
+	 *
+	 * @return void
+	 */
+	public function run() {}
+
+	/**
+	 * Runs the `Extract` command.
+	 *
+	 * @return void
+	 */
+	public function extract() {
+		$extract = new Extract(array('request' => $this->request));
+		return $extract->run();
+	}
+}
+
+?>

+ 322 - 0
php-lithium/libraries/lithium/console/command/Help.php

@@ -0,0 +1,322 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright     Copyright 2013, Union of RAD (http://union-of-rad.org)
+ * @license       http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+
+namespace lithium\console\command;
+
+use lithium\core\Libraries;
+use lithium\core\Environment;
+use lithium\util\Inflector;
+use lithium\analysis\Inspector;
+use lithium\analysis\Docblock;
+
+/**
+ * Get information about a particular class including methods, properties,
+ * and descriptions.
+ */
+class Help extends \lithium\console\Command {
+
+	/**
+	 * Auto run the help command.
+	 *
+	 * @param string $command Name of the command to return help about.
+	 * @return void
+	 */
+	public function run($command = null) {
+		$message = 'Lithium console started in the ' . Environment::get() . ' environment.';
+		$message .= ' Use the --env=environment key to alter this.';
+		$this->out($message);
+
+		if (!$command) {
+			$this->_renderCommands();
+			return true;
+		}
+
+		if (!preg_match('/\\\\/', $command)) {
+			$command = Inflector::camelize($command);
+		}
+
+		if (!$class = Libraries::locate('command', $command)) {
+			$this->error("Command `{$command}` not found");
+			return false;
+		}
+
+		if (strpos($command, '\\') !== false) {
+			$command = join('', array_slice(explode("\\", $command), -1));
+		}
+		$command = strtolower(Inflector::slug($command));
+
+		$run = null;
+		$methods = $this->_methods($class);
+		$properties = $this->_properties($class);
+		$info = Inspector::info($class);
+
+		$this->out('USAGE', 'heading');
+
+		if (isset($methods['run'])) {
+			$run = $methods['run'];
+			unset($methods['run']);
+			$this->_renderUsage($command, $run, $properties);
+		}
+		foreach ($methods as $method) {
+			$this->_renderUsage($command, $method);
+		}
+
+		if (!empty($info['description'])) {
+			$this->nl();
+			$this->_renderDescription($info);
+			$this->nl();
+		}
+
+		if ($properties || $methods) {
+			$this->out('OPTIONS', 'heading');
+		}
+		if ($run) {
+			$this->_render($run['args']);
+		}
+		if ($methods) {
+			$this->_render($methods);
+		}
+		if ($properties) {
+			$this->_render($properties);
+		}
+		return true;
+	}
+
+	/**
+	 * Gets the API for the class.
+	 *
+	 * @param string $class fully namespaced class in dot notation.
+	 * @param string $type method|property
+	 * @param string $name the name of the method or property.
+	 * @return array
+	 */
+	public function api($class = null, $type = null, $name = null) {
+		$class = str_replace(".", "\\", $class);
+
+		switch ($type) {
+			default:
+				$info = Inspector::info($class);
+				$result = array('class' => array(
+					'name' => $info['shortName'],
+					'description' => trim($info['description'] . PHP_EOL . PHP_EOL . $info['text'])
+				));
+			break;
+			case 'method':
+				$result = $this->_methods($class, compact('name'));
+			break;
+			case 'property':
+				$result = $this->_properties($class, compact('name'));
+			break;
+		}
+		$this->_render($result);
+	}
+
+	/**
+	 * Get the methods for the class.
+	 *
+	 * @param string $class
+	 * @param array $options
+	 * @return array
+	 */
+	protected function _methods($class, $options = array()) {
+		$defaults = array('name' => null);
+		$options += $defaults;
+
+		$map = function($item) {
+			if ($item->name[0] === '_') {
+				return;
+			}
+			$modifiers = array_values(Inspector::invokeMethod('_modifiers', array($item)));
+			$setAccess = array_intersect($modifiers, array('private', 'protected')) != array();
+
+			if ($setAccess) {
+				$item->setAccessible(true);
+			}
+			$args = array();
+
+			foreach ($item->getParameters() as $arg) {
+				$args[] = array(
+					'name' => $arg->getName(),
+					'optional' => $arg->isOptional(),
+					'description' => null
+				);
+			}
+			$result = compact('modifiers', 'args') + array(
+				'docComment' => $item->getDocComment(),
+				'name' => $item->getName()
+			);
+			if ($setAccess) {
+				$item->setAccessible(false);
+			}
+			return $result;
+		};
+
+		$methods = Inspector::methods($class)->map($map, array('collect' => false));
+		$results = array();
+
+		foreach (array_filter($methods) as $method) {
+			$comment = Docblock::comment($method['docComment']);
+
+			$name = $method['name'];
+			$description = trim($comment['description'] . PHP_EOL . $comment['text']);
+			$args = $method['args'];
+			$return = null;
+
+			foreach ($args as &$arg) {
+				if (isset($comment['tags']['params']['$' . $arg['name']])) {
+					$arg['description'] = $comment['tags']['params']['$' . $arg['name']]['text'];
+				}
+				$arg['usage'] = $arg['optional'] ? "[<{$arg['name']}>]" : "<{$arg['name']}>";
+			}
+			if (isset($comment['tags']['return'])) {
+				$return = trim(strtok($comment['tags']['return'], ' '));
+			}
+			$results[$name] = compact('name', 'description', 'return', 'args');
+
+			if ($name && $name == $options['name']) {
+				return array($name => $results[$name]);
+			}
+		}
+		return $results;
+	}
+
+	/**
+	 * Get the properties for the class.
+	 *
+	 * @param string $class
+	 * @param array $options
+	 * @return array
+	 */
+	protected function _properties($class, $options = array()) {
+		$defaults = array('name' => null);
+		$options += $defaults;
+
+		$properties = Inspector::properties($class);
+		$results = array();
+
+		foreach ($properties as &$property) {
+			$name = str_replace('_', '-', Inflector::underscore($property['name']));
+
+			$comment = Docblock::comment($property['docComment']);
+			$description = trim($comment['description']);
+			$type = isset($comment['tags']['var']) ? strtok($comment['tags']['var'], ' ') : null;
+
+			$usage = strlen($name) == 1 ? "-{$name}" : "--{$name}";
+
+			if ($type != 'boolean') {
+				$usage .= "=<{$type}>";
+			}
+			$usage = "[{$usage}]";
+
+			$results[$name] = compact('name', 'description', 'type', 'usage');
+
+			if ($name == $options['name']) {
+				return array($name => $results[$name]);
+			}
+		}
+		return $results;
+	}
+
+	/**
+	 * Output the formatted properties or methods.
+	 *
+	 * @see lithium\console\command\Help::_properties()
+	 * @see lithium\console\command\Help::_methods()
+	 * @param array $params From `_properties()` or `_methods()`.
+	 * @return void
+	 */
+	protected function _render($params) {
+		foreach ($params as $name => $param) {
+			if ($name === 'run' || empty($param['name'])) {
+				continue;
+			}
+			$usage = (!empty($param['usage'])) ? trim($param['usage'], ' []') : $param['name'];
+			$this->out($this->_pad($usage), 'option');
+
+			if ($param['description']) {
+				$this->out($this->_pad($param['description'], 2));
+			}
+			$this->nl();
+		}
+	}
+
+	/**
+	 * Output the formatted available commands.
+	 *
+	 * @return void
+	 */
+	protected function _renderCommands() {
+		$commands = Libraries::locate('command', null, array('recursive' => false));
+
+		foreach ($commands as $key => $command) {
+			$library = strtok($command, '\\');
+
+			if (!$key || strtok($commands[$key - 1] , '\\') != $library) {
+				$this->out("{:heading}COMMANDS{:end} {:blue}via {$library}{:end}");
+			}
+			$info = Inspector::info($command);
+			$name = strtolower(Inflector::slug($info['shortName']));
+
+			$this->out($this->_pad($name) , 'heading');
+			$this->out($this->_pad($info['description']), 2);
+		}
+
+		$message  = 'See `{:command}li3 help COMMAND{:end}`';
+		$message .= ' for more information on a specific command.';
+		$this->out($message, 2);
+	}
+
+	/**
+	 * Output the formatted usage.
+	 *
+	 * @see lithium\console\command\Help::_methods()
+	 * @see lithium\console\command\Help::_properties()
+	 * @param string $command The name of the command.
+	 * @param array $method Information about the method of the command to render usage for.
+	 * @param array $properties From `_properties()`.
+	 * @return void
+	 */
+	protected function _renderUsage($command, $method, $properties = array()) {
+		$params = array_reduce($properties, function($a, $b) {
+			return "{$a} {$b['usage']}";
+		});
+		$args = array_reduce($method['args'], function($a, $b) {
+			return "{$a} {$b['usage']}";
+		});
+		$format = "{:command}li3 %s%s{:end}{:command}%s{:end}{:option}%s{:end}";
+		$name = $method['name'] == 'run' ? '' : " {$method['name']}";
+		$this->out($this->_pad(sprintf($format, $command ?: 'COMMAND', $name, $params, $args)));
+	}
+
+	/**
+	 * Output the formatted command description.
+	 *
+	 * @param array $info Info from inspecting the class of the command.
+	 * @return void
+	 */
+	protected function _renderDescription($info) {
+		$this->out('DESCRIPTION', 'heading');
+		$break = PHP_EOL . PHP_EOL;
+		$description = trim("{$info['description']}{$break}{$info['text']}");
+		$this->out($this->_pad($description, PHP_EOL));
+	}
+
+	/**
+	 * Add left padding for prettier display.
+	 *
+	 * @param string $message the text to render.
+	 * @param integer|string $level the level of indentation.
+	 * @return string
+	 */
+	protected function _pad($message, $level = 1) {
+		$padding = str_repeat(' ', $level * 4);
+		return $padding . str_replace("\n", "\n{$padding}", $message);
+	}
+}
+
+?>

+ 729 - 0
php-lithium/libraries/lithium/console/command/Library.php

@@ -0,0 +1,729 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright     Copyright 2013, Union of RAD (http://union-of-rad.org)
+ * @license       http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+
+namespace lithium\console\command;
+
+use Phar;
+use Exception;
+use RuntimeException;
+use lithium\core\Libraries;
+use lithium\util\String;
+use lithium\util\Inflector;
+
+/**
+ * The Library command is used to archive and extract Phar::GZ archives. Requires zlib extension.
+ * In addition, communicate with the a given server to add plugins and extensions to the
+ * current application. Push archived plugins to the server.
+ *
+ */
+class Library extends \lithium\console\Command {
+
+	/**
+	 * Absolute path to config file.
+	 *
+	 * @var string
+	 */
+	public $conf = null;
+
+	/**
+	 * Path to where plugins will be installed. Relative to current working directory.
+	 *
+	 * @var string
+	 */
+	public $path = null;
+
+	/**
+	 * Server host to query for plugins.
+	 *
+	 * @var string
+	 */
+	public $server = 'lab.lithify.me';
+
+	/**
+	 * The port for the server.
+	 *
+	 * @var string
+	 */
+	public $port = 80;
+
+	/**
+	 * The username for the server authentication.
+	 *
+	 * @var string
+	 */
+	public $username = '';
+
+	/**
+	 * The password for corresponding username.
+	 *
+	 * @var string
+	 */
+	public $password = '';
+
+	/**
+	 * @see `force`
+	 * @var boolean
+	 */
+	public $f = false;
+
+	/**
+	 * Force operation to complete. Typically used for overwriting files.
+	 *
+	 * @var string
+	 */
+	public $force = false;
+
+	/**
+	 * Filter used for including files in archive.
+	 *
+	 * @var string
+	 */
+	public $filter = '/\.(php|htaccess|jpg|png|gif|css|js|ico|json|ini)|(empty)$/';
+
+	/**
+	 * Namespace used for newly extracted libraries.
+	 * Will default to the basename of the directory
+	 * the library is being extracted to.
+	 *
+	 * @var string
+	 */
+	public $namespace = null;
+
+	/**
+	 * When extracting a library, custom replacements
+	 * can be made on the extracted files that
+	 * are defined in this json file.
+	 *
+	 * @var string
+	 */
+	public $replacementsFile = '_replacements.json';
+
+	/**
+	 * The path to use for the `LITHIUM_LIBRARY_PATH`
+	 * in extracted templates.  It defaults to the
+	 * current value of the `LITHIUM_LIBRARY_PATH`
+	 * constant.  If `LITHIUM_LIBRARY_PATH` is not the same
+	 * as `dirname(LITHIUM_APP_PATH) . '/libraries'` then
+	 * the value of `LITHIUM_LIBRARY_PATH` will be hard-coded
+	 * to the `config/bootstrap/libraries.php` file in the
+	 * extracted library.  If you want it to use a custom
+	 * value, then pass it to this option.  For example,
+	 * if you keep your apps in the same directory as your
+	 * libraries, you could set it to `dirname(LITHIUM_APP_PATH)`
+	 *
+	 * @var string
+	 */
+	public $lithiumLibraryPath = LITHIUM_LIBRARY_PATH;
+
+	/**
+	 * Holds settings from conf file
+	 *
+	 * @var array
+	 */
+	protected $_settings = array();
+
+	/**
+	 * some classes
+	 *
+	 * @var array
+	 */
+	protected $_classes = array(
+		'service' => 'lithium\net\http\Service',
+		'response' => 'lithium\console\Response'
+	);
+
+	/**
+	 * Auto configuration properties.
+	 *
+	 * @var array
+	 */
+	protected $_autoConfig = array(
+		'classes' => 'merge', 'env', 'detectors' => 'merge', 'base', 'type', 'stream'
+	);
+
+	/**
+	 * Initialize _settings from `--conf`.
+	 *
+	 * Throws an exception if the command is  initialized without a request object
+	 * which is needed by `_toPath()` in order to determine the current working directory.
+	 * This most often happens if the command is inspected using the `ReflectionClass`.
+	 *
+	 * @return void
+	 */
+	protected function _init() {
+		parent::_init();
+		if ($this->server) {
+			$this->_settings['servers'][$this->server] = true;
+		}
+		if (file_exists($this->conf)) {
+			$this->_settings += (array) json_decode($this->conf, true);
+		}
+		$this->path = $this->_toPath($this->path ?: 'libraries');
+		$this->force = $this->f ? $this->f : $this->force;
+	}
+
+	/**
+	 * Add configuration and write data in json format.
+	 *
+	 * @param string $key (server)
+	 * @param string $value value of key
+	 * @param boolean|string $options [optional]
+	 * @return mixed Returns all settings if `$key` and `$value` aren't set. The only option for
+	 *         `$key` right now is 'server'. Returns the bytes written to the configuration file.
+	 */
+	public function config($key = null, $value = null, $options = true) {
+		if (empty($key) || empty($value)) {
+			return $this->_settings;
+		}
+		switch ($key) {
+			case 'server':
+				$this->_settings['servers'][$value] = $options;
+			break;
+		}
+		return file_put_contents($this->conf, json_encode($this->_settings));
+	}
+
+	/**
+	 * Extract an archive into a path. If one param exists, the app.phar.gz template will be used.
+	 * If both parameters exist, then the first will be the template archive and the second will be
+	 * the name of the extracted archive
+	 *
+	 * - `li3 library extract myapp` : uses command/create/template/app.phar.gz
+	 * - `li3 library extract another_archive myapp` : uses
+	 *       command/create/template/another_archive.phar.gz
+	 * - `li3 library extract plugin li3_plugin` : uses command/create/template/plugin.phar.gz
+	 * - `li3 library extract /full/path/to/a.phar.gz myapp` : paths that begin with a '/'
+	 *       can extract from archives outside of the default command/create/template/
+	 *       location
+	 *
+	 * @param string $name if only param, command/create/template/app.phar.gz extracted to $name
+	 *     otherwise, the template name or full path to extract `from` phar.gz.
+	 * @param string $result if exists $name is extracted to $result
+	 * @return boolean
+	 */
+	public function extract($name = 'new', $result = null) {
+		$from = 'app';
+		$to = $name;
+
+		if ($result) {
+			$from = $name;
+			$to = $result;
+		}
+
+		$to = $this->_toPath($to);
+
+		if ($from[0] !== '/') {
+			$from = Libraries::locate('command.create.template', $from, array(
+				'filter' => false, 'type' => 'file', 'suffix' => '.phar.gz'
+			));
+			if (!$from || is_array($from)) {
+				return false;
+			}
+		}
+
+		if (file_exists($from)) {
+			try {
+				$archive = new Phar($from);
+			} catch (Exception $e) {
+				$this->error($e->getMessage());
+				return false;
+			}
+
+			if ($archive->extractTo($to)) {
+				$this->out(basename($to) . " created in " . dirname($to) . " from {$from}");
+
+				if (empty($this->namespace)) {
+					$this->namespace = Inflector::underscore(basename($to));
+				}
+
+				$replacements = $this->_findReplacements($to);
+				return $this->_replaceAfterExtract($to, compact('namespace', 'replacements'));
+			}
+		}
+		$this->error("Could not extract {$to} from {$from}");
+		return false;
+	}
+
+	/**
+	 * Helper method for `console\command\Library::extract()` to gather
+	 * replacements to perform on the newly extracted files
+	 *
+	 * It looks for a json file specified by `$this->replacementsFile`
+	 * which defaults to _replacements.json.
+	 *
+	 * Running eval on a php file to get the `$replacements`
+	 * would be more flexible than using json, but definitely much more of a
+	 * security hole if the library is not trusted.
+	 *
+	 * @param string $base File path to the extracted library
+	 * @return array A multi-dimensional array.  Keys on the top level
+	 *     are filenames or glob-style paths.  Those hold an array
+	 *     with keys being the search param and values being the
+	 *     replacement values
+	 */
+	protected function _findReplacements($base = null) {
+		$replacements = null;
+		if (file_exists($base . '/' . $this->replacementsFile)) {
+			$replacementsFilename = $base . '/' . $this->replacementsFile;
+			$replacements = json_decode(file_get_contents($replacementsFilename), true);
+			if ($replacements !== false) {
+				unlink($base . '/' . $this->replacementsFile);
+			}
+		}
+		return $replacements;
+	}
+
+	/**
+	 * Helper method for `console\command\Library::extract()` to perform after-extract string
+	 * replacements.
+	 *
+	 * In the current implementation, it only sets the correct `LITHIUM_LIBRARY_PATH` when the
+	 * app.phar.gz archive was extracted. If you get any errors, please make sure that the console
+	 * script has read and write permissions to the extracted directory.
+	 *
+	 * @param string $extracted contains the path to the extracted archive.
+	 * @param array $options Valid options are:
+	 *     - `'replacements'`: an array of string replacements indexed by filename.
+	 *       It's also possible to use glob-style wildcards in the filename such
+	 *       as `*` or `*.php` or `resources/g11n/*`.  If the filename starts
+	 *       with `*`, then that filename pattern will be recursively found
+	 *       in every sub-directory.  Additionally, each replacement can
+	 *       use `String::insert()` style strings that will be replaced
+	 *       with the data in the `data` option.
+	 *     - `'data'`: an array with data that will be used to replace
+	 *       `String::insert`-style placeholders in the `replacements` option.
+	 *       By default, this includes 'namespace' and 'library' which are
+	 *       both set to the extracted library's namespace.
+	 * @return boolean
+	 */
+	protected function _replaceAfterExtract($extracted, $options = array()) {
+		$namespace = $this->namespace;
+		$library = $namespace;
+		$data = compact('namespace', 'library');
+		$replacements = array();
+		extract($options);
+
+		if (empty($replacements)) {
+			$replacements = array(
+				'config/bootstrap/libraries.php' => array(
+					"Libraries::add('app'" => "Libraries::add('{:namespace}'"
+				),
+				'*.php' => array(
+					"namespace app\\" => "namespace {:namespace}\\"
+				)
+			);
+		}
+
+		if (dirname(LITHIUM_APP_PATH) . '/libraries' !== $this->lithiumLibraryPath) {
+			$pathinfo = pathinfo($this->lithiumLibraryPath);
+			if ($pathinfo['dirname'] !== '.') {
+				$this->lithiumLibraryPath = "'" . $this->lithiumLibraryPath . "'";
+			}
+
+			$search = 'define(\'LITHIUM_LIBRARY_PATH\', ';
+			$search .= 'dirname(LITHIUM_APP_PATH) . \'/libraries\');';
+			$replace = 'define(\'LITHIUM_LIBRARY_PATH\', ';
+			$replace .= $this->lithiumLibraryPath . ');';
+
+			if (!isset($replacements['config/bootstrap/libraries.php'])) {
+				$replacements['config/bootstrap/libraries.php'] = array();
+			}
+			$replacements['config/bootstrap/libraries.php'][$search] = $replace;
+		}
+
+		foreach ($replacements as $filename => $definitions) {
+			foreach ($definitions as $search => $replace) {
+				unset($definitions[$search]);
+				$search = String::insert($search, $data);
+				$replace = String::insert($replace, $data);
+				$definitions[$search] = $replace;
+			}
+			$paths = $this->_wildcardPaths($filename, $extracted);
+			foreach ($paths as $filepath) {
+				if (file_exists($filepath)) {
+					$content = file_get_contents($filepath);
+					if ($content === '') {
+						continue;
+					}
+					$content = str_replace(
+						array_keys($definitions),
+						array_values($definitions),
+						$content
+					);
+					if (!file_put_contents($filepath, $content)) {
+						$this->error("Could not replace content in {$filepath}");
+						return false;
+					}
+				}
+			}
+		}
+		return true;
+	}
+
+	/**
+	 * Utility function that will return an array of
+	 * file paths relative to the `$base` path that
+	 * are found using a glob-style asterisk wildcards
+	 * such as `*` or `*.php` or `resources/g11n/*`.  If the path starts
+	 * with `*`, then that filename pattern will be recursively found
+	 * in every sub-directory.
+	 *
+	 * @param string $path
+	 * @param string $base Base directory to search for matching files
+	 * @return array
+	 */
+	protected function _wildcardPaths($path, $base = '') {
+		if (strpos($path, '*') === false) {
+			return array($base . '/' . $path);
+		}
+		if ($path[0] === '*') {
+			$paths = array();
+			$dirs = array($base);
+			while (!empty($dirs)) {
+				$dir = array_shift($dirs);
+				$paths = array_merge($paths, glob($dir . '/' . $path));
+				$dirs = array_merge(
+					$dirs,
+					array_filter(glob($dir . '/*'), function($path) {
+						return is_dir($path);
+					})
+				);
+			}
+		} else {
+			$paths = array_filter(glob($base . '/' . $path), function($path) {
+				$basename = basename($path);
+				return $basename !== '.' && $basename !== '..';
+			});
+		}
+		return $paths;
+	}
+
+	/**
+	 * Create the Phar::GZ archive from a given directory. If no params, the current working
+	 * directory is archived with the name of that directory. If one param, the current working
+	 * directory will be archive with the name provided. If both params, the first is the
+	 * name or path to the library to archive and the second is the name of the resulting archive
+	 *
+	 * - `li3 library archive my_archive` : archives current working directory to my_archive.phar.gz
+	 * - `li3 library archive myapp my_archive` : archives 'myapp' to 'my_archive.phar.gz'
+	 *
+	 * @param string $name if only param, the archive name for the current working directory
+	 *     otherwise, The library name or path to the directory to compress.
+	 * @param string $result if exists, The name of the resulting archive
+	 * @return boolean
+	 */
+	public function archive($name = null, $result = null) {
+		if (ini_get('phar.readonly') === '1') {
+			throw new RuntimeException('Set `phar.readonly` to `0` in `php.ini`.');
+		}
+		$from = $name;
+		$to = $name;
+
+		if ($result) {
+			$from = $name;
+			$to = $result;
+		}
+		$path = $this->_toPath($to);
+
+		if (file_exists("{$path}.phar")) {
+			if (!$this->force) {
+				$this->error(basename($path) . ".phar already exists in " . dirname($path));
+				return false;
+			}
+			Phar::unlinkArchive("{$path}.phar");
+		}
+		try {
+			$archive = new Phar("{$path}.phar");
+		} catch (Exception $e) {
+			$this->error($e->getMessage());
+			return false;
+		}
+		$result = null;
+		$from = $this->_toPath($from);
+
+		if (is_dir($from)) {
+			$result = (boolean) $archive->buildFromDirectory($from, $this->filter);
+		}
+		if (file_exists("{$path}.phar.gz")) {
+			if (!$this->force) {
+				$this->error(basename($path) . ".phar.gz already exists in " . dirname($path));
+				return false;
+			}
+			Phar::unlinkArchive("{$path}.phar.gz");
+		}
+		if ($result) {
+			$archive->compress(Phar::GZ);
+			$this->out(basename($path) . ".phar.gz created in " . dirname($path) . " from {$from}");
+			return true;
+		}
+		$this->error("Could not create archive from {$from}");
+		return false;
+	}
+
+
+	/**
+	 * List all the plugins and extensions available on the server.
+	 *
+	 * @param string $type plugins|extensions
+	 */
+	public function find($type = 'plugins') {
+		$results = array();
+
+		foreach ($this->_settings['servers'] as $server => $enabled) {
+			if (!$enabled) {
+				continue;
+			}
+			$service = $this->_instance('service', array(
+				'host' => $server, 'port' => $this->port
+			));
+			$results[$server] = json_decode($service->get("lab/{$type}.json"));
+
+			if (empty($results[$server])) {
+				$this->out("No {$type} at {$server}");
+				continue;
+			}
+			foreach ((array) $results[$server] as $data) {
+				$name = isset($data->class) ? $data->class : $data->name;
+				$header = "{$server} > {$name}";
+				$out = array(
+					"{$data->summary}",
+					"Version: {$data->version}",
+					"Created: {$data->created}"
+				);
+				$this->header($header);
+				$this->out(array_filter($out));
+			}
+		}
+	}
+
+	/**
+	 * Install plugins or extensions to the current application.
+	 * For plugins, the install commands specified in the formula is run.
+	 *
+	 * @param string $name name of plugin to add
+	 * @return boolean
+	 */
+	public function install($name = null) {
+		$results = array();
+
+		foreach ($this->_settings['servers'] as $server => $enabled) {
+			if (!$enabled) {
+				continue;
+			}
+			$service = $this->_instance('service', array('host' => $server, 'port' => $this->port));
+
+			if ($plugin = json_decode($service->get("lab/{$name}.json"))) {
+				break;
+			}
+		}
+		if (empty($plugin->sources)) {
+			$this->error("{$name} not found.");
+			return false;
+		}
+		$hasGit = function () {
+			return (strpos(shell_exec('git --version'), 'git version') !== false);
+		};
+		foreach ((array) $plugin->sources as $source) {
+			if (strpos($source, 'phar.gz') !== false && file_exists($source)) {
+				$written = file_put_contents(
+					"{$this->path}/{$plugin->name}.phar.gz", file_get_contents($source)
+				);
+				if (!$written) {
+					$this->error("{$plugin->name}.phar.gz could not be saved");
+					return false;
+				}
+				$this->out("{$plugin->name}.phar.gz saved to {$this->path}");
+
+				try {
+					$archive = new Phar("{$this->path}/{$plugin->name}.phar.gz");
+
+					if ($archive->extractTo("{$this->path}/{$plugin->name}")) {
+						$this->out("{$plugin->name} installed to {$this->path}/{$plugin->name}");
+						$this->out("Remember to update the bootstrap.");
+						return true;
+					}
+				} catch (Exception $e) {
+					$this->error($e->getMessage());
+				}
+			}
+			$url = parse_url($source);
+
+			if (!empty($url['scheme']) && $url['scheme'] === 'git' && $hasGit()) {
+				$cmd = "cd {$this->path} && git clone --quiet {$source} {$plugin->name}";
+				$result = shell_exec($cmd);
+
+				if (is_dir("{$this->path}/{$plugin->name}")) {
+					$this->out("{$plugin->name} installed to {$this->path}/{$plugin->name}");
+					$this->out("Remember to update your bootstrap.");
+					return true;
+				}
+			}
+		}
+		$this->out("{$plugin->name} not installed.");
+		return false;
+	}
+
+	/**
+	 * Create a formula for the given library name
+	 *
+	 * @param string $name the library name or full path to the plugin
+	 * @return boolean
+	 */
+	public function formulate($name = null) {
+		if (!$name) {
+			$name = $this->in("please supply a name");
+		}
+		$result = false;
+		$path = $this->_toPath($name);
+		$name = basename($path);
+		$formula = "{$path}/config/{$name}.json";
+
+		$data = array();
+
+		if (file_exists($formula)) {
+			$data = json_decode(file_get_contents($formula), true);
+		}
+		if (empty($data['version'])) {
+			$data['version'] = $this->in("please supply a version");
+		}
+		if (empty($data['summary'])) {
+			$data['summary'] = $this->in("please supply a summary");
+		}
+		if (file_exists($path) && !file_exists($formula)) {
+			$defaults = array(
+				'name' => $name, 'version' => '0.1',
+				'summary' => "a plugin called {$name}",
+				'maintainers' => array(array(
+					'name' => '', 'email' => '', 'website' => ''
+				)),
+				'sources' => array("http://{$this->server}/lab/download/{$name}.phar.gz"),
+				'commands' => array(
+					'install' => array(), 'update' => array(), 'remove' => array()
+				),
+				'requires' => array()
+			);
+			$data += $defaults;
+
+			if (!is_dir(dirname($formula)) && !mkdir(dirname($formula), 0755, true)) {
+				$this->error("Formula for {$name} not created in {$path}");
+				return false;
+			}
+		}
+		if (is_dir(dirname($formula)) && file_put_contents($formula, json_encode($data))) {
+			$this->out("Formula for {$name} created in {$path}.");
+			return true;
+		}
+		$this->error("Formula for {$name} not created in {$path}");
+		return false;
+	}
+
+	/**
+	 * Send a plugin archive to the server. The plugin must have a formula.
+	 *
+	 * @param string $name the library name or full path to the archive to send
+	 * @return void
+	 */
+	public function push($name = null) {
+		if (!$name) {
+			$name = $this->in("please supply a name");
+		}
+		$path = $this->_toPath($name);
+		$name = basename($name);
+		$file = "{$path}.phar.gz";
+
+		if (!file_exists("phar://{$file}/config/{$name}.json")) {
+			$this->error(array(
+				"The formula for {$name} is missing.", "Run li3 library formulate {$name}"
+			));
+			return false;
+		}
+		$formula = json_decode(file_get_contents("phar://{$file}/config/{$name}.json"));
+		$isValid = (
+			!empty($formula->name) && !empty($formula->version)
+			&& !empty($formula->summary) && !empty($formula->sources)
+		);
+		if (!$isValid) {
+			$this->error(array(
+				"The formula for {$name} is not valid.", "Run li3 library formulate {$name}"
+			));
+			return false;
+		}
+		if (file_exists($file)) {
+			$service = $this->_instance('service', array(
+				'host' => $this->server, 'port' => $this->port,
+				'auth' => 'Basic', 'username' => $this->username, 'password' => $this->password
+			));
+			$boundary = md5(date('r', time()));
+			$headers = array("Content-Type: multipart/form-data; boundary={$boundary}");
+			$name = basename($file);
+			$data = join("\r\n", array(
+				"--{$boundary}",
+				"Content-Disposition: form-data; name=\"phar\"; filename=\"{$name}\"",
+				"Content-Type: application/phar", "",
+				base64_encode(file_get_contents($file)),
+				"--{$boundary}--"
+			));
+			$result = json_decode($service->post(
+				'/lab/server/receive', $data, compact('headers')
+			));
+
+			if ($service->last->response->status['code'] == 201) {
+				$this->out(array(
+					"{$result->name} added to {$this->server}.",
+					"See http://{$this->server}/lab/plugins/view/{$result->id}"
+				));
+				return $result;
+			}
+			if (!empty($result->error)) {
+				$this->error($result->error);
+				return false;
+			}
+			$this->error((array) $result);
+			return false;
+		}
+		$this->error(array("{$file} does not exist.", "Run li3 library archive {$name}"));
+		return false;
+	}
+
+	/**
+	 * Update installed plugins. For plugins, runs update commands specified in Formula.
+	 *
+	 * @todo implement
+	 * @return void
+	 */
+	public function update() {
+		$this->error('Please implement me');
+	}
+
+	/**
+	 * Take a name and return the path.
+	 *
+	 * If the name already appears to be a path, it is returned directly. Otherwise, the
+	 * `Library` class is used to find the associated path.
+	 *
+	 * @param string $name
+	 * @return string
+	 */
+	protected function _toPath($name = null) {
+		$pathinfo = pathinfo($name);
+		if ($name && $pathinfo['dirname'] !== '.') {
+			return $name;
+		}
+
+		$library = Libraries::get($name);
+
+		if (!empty($library['path'])) {
+			return $library['path'];
+		}
+
+		$path = $this->request->env('working');
+		return (!empty($name)) ? "{$path}/{$name}" : $path;
+	}
+}
+
+?>

+ 140 - 0
php-lithium/libraries/lithium/console/command/Route.php

@@ -0,0 +1,140 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright     Copyright 2013, Union of RAD (http://union-of-rad.org)
+ * @license       http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+namespace lithium\console\command;
+
+use lithium\core\Libraries;
+use lithium\action\Request;
+use lithium\net\http\Router;
+use lithium\core\Environment;
+
+/**
+ * The route command lets you inspect your routes and issue requests against the router.
+ */
+class Route extends \lithium\console\Command {
+
+	/**
+	 * Override the default 'development' environment.
+	 *
+	 * For example:
+	 * {{{
+	 * li3 route --env=production
+	 * li3 route show /foo --env=test
+	 * }}}
+	 *
+	 * @var string
+	 */
+	public $env = 'development';
+
+	/**
+	 * Load the routes file and set the environment.
+	 *
+	 * @param array $config The default configuration, wherein the absolute path to the routes file
+	 *              to load may be specified, using the `'routes'` key.
+	 */
+	public function __construct($config = array()) {
+		$defaults = array('routes' => Libraries::get(true, 'path') . '/config/routes.php');
+		parent::__construct($config + $defaults);
+	}
+
+	protected function _init() {
+		parent::_init();
+		Environment::set($this->env);
+
+		if (file_exists($this->_config['routes'])) {
+			return require $this->_config['routes'];
+		}
+		$this->error("The routes file for this library doesn't exist or can't be found.");
+	}
+
+	/**
+	 * Lists all connected routes to the router. See the `all()`
+	 * method for details and examples.
+	 *
+	 * @return void
+	 */
+	public function run() {
+		$this->all();
+	}
+
+	/**
+	 * Lists all connected routes to the router. This is a convenience
+	 * alias for the `show()` method.
+	 *
+	 * Example:
+	 * {{{
+	 * li3 route
+	 * li3 route all
+	 * }}}
+	 *
+	 * Will return an output similar to:
+	 *
+	 * {{{
+	 * Template                        	Params
+	 * --------                        	------
+	 * /                               	{"controller":"pages","action":"view"}
+	 * /pages/{:args}                  	{"controller":"pages","action":"view"}
+	 * /{:slug:[\w\-]+}                	{"controller":"posts","action":"show"}
+	 * /{:controller}/{:action}/{:args}	{"action":"index"}
+	 * }}}
+	 *
+	 * @return void
+	 */
+	public function all() {
+		$routes = Router::get();
+		$columns = array(array('Template', 'Params'), array('--------', '------'));
+
+		foreach ($routes As $route) {
+			$info = $route->export();
+			$columns[] = array($info['template'], json_encode($info['params']));
+		}
+		$this->columns($columns);
+	}
+
+	/**
+	 * Returns the corresponding params for a given URL and an optional request
+	 * method.
+	 *
+	 * Examples:
+	 * {{{
+	 * 1: li3 route show /foo
+	 * 2: li3 route show post /foo/bar/1
+	 * 3: li3 route show /test
+	 * 4: li3 route show /test --env=production
+	 * }}}
+	 *
+	 * Will return outputs similar to:
+	 *
+	 * {{{
+	 * 1: {"controller":"foo","action":"index"	}
+	 * 2: {"controller":"foo","action":"bar","args":["1"]}
+	 * 3: {"controller":"lithium\\test\\Controller","action":"index"}
+	 * 4: {"controller":"test","action":"index"}
+	 * }}}
+	 *
+	 * @return void
+	 */
+	public function show() {
+		$url = join(" ", $this->request->params['args']);
+		$method = 'GET';
+
+		if (!$url) {
+			$this->error('Please provide a valid URL');
+		}
+
+		if (preg_match('/^(GET|POST|PUT|DELETE|HEAD|OPTIONS) (.+)/i', $url, $matches)) {
+			$method = strtoupper($matches[1]);
+			$url = $matches[2];
+		}
+
+		$request = new Request(compact('url') + array('env' => array('REQUEST_METHOD' => $method)));
+		$result = Router::process($request);
+		$this->out($result->params ? json_encode($result->params) : "No route found.");
+	}
+}
+
+?>

+ 325 - 0
php-lithium/libraries/lithium/console/command/Test.php

@@ -0,0 +1,325 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright     Copyright 2013, Union of RAD (http://union-of-rad.org)
+ * @license       http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+
+namespace lithium\console\command;
+
+use lithium\core\Libraries;
+use lithium\test\Dispatcher;
+use lithium\test\Unit;
+
+/**
+ * Runs a given set of tests and outputs the results.
+ *
+ * @see lithium\test
+ */
+class Test extends \lithium\console\Command {
+
+	/**
+	 * Used as the exit code for errors where no test was mapped to file.
+	 */
+	const EXIT_NO_TEST = 4;
+
+	/**
+	 * List of filters to apply before/during/after test run, separated by commas.
+	 *
+	 * For example:
+	 * {{{
+	 * lithium test lithium/tests/cases/core/ObjectTest.php --filters=Coverage
+	 * lithium test lithium/tests/cases/core/ObjectTest.php --filters=Coverage,Profiler
+	 * }}}
+	 *
+	 * @var string Name of a filter or a comma separated list of filter names. Builtin filters:
+	 *      - `Affected`:   Adds tests to the run affected by the classes covered by current tests.
+	 *      - `Complexity`: Calculates the cyclomatic complexity of class methods, and shows
+	 *                      worst-offenders and statistics.
+	 *      - `Coverage`:   Runs code coverage analysis for the executed tests.
+	 *      - `Profiler`:   Tracks timing and memory usage information for each test method.
+	 */
+	public $filters;
+
+	/**
+	 * Format to use for rendering results. Any other format than `txt` will
+	 * cause the command to enter quiet mode, surpressing headers and any other
+	 * decoration.
+	 *
+	 * @var string Either `txt` or `json`.
+	 */
+	public $format = 'txt';
+
+	/**
+	 * Enable verbose output especially for the `txt` format.
+	 *
+	 * @var boolean
+	 */
+	public $verbose = false;
+
+	/**
+	 * Enable plain mode to prevent any headers or similar decoration being output.
+	 * Good for command calls embedded into other scripts.
+	 *
+	 * @var boolean
+	 */
+	public $plain = false;
+
+	/**
+	 * An array of closures, mapped by type, which are set up to handle different test output
+	 * formats.
+	 *
+	 * @var array
+	 */
+	protected $_handlers = array();
+
+	/**
+	 * Initializes the output handlers.
+	 *
+	 * @see lithium\console\command\Test::$_handlers
+	 * @return void
+	 */
+	protected function _init() {
+		parent::_init();
+		$command = $this;
+
+		$this->_handlers += array(
+			'txt' => function($runner, $path) use ($command) {
+				if (!$command->plain) {
+					$command->header('Test');
+					$command->out(null, 1);
+				}
+				$colorize = function($result) {
+					switch (trim($result)) {
+						case '.':
+							return $result;
+						case 'pass':
+							return "{:green}{$result}{:end}";
+						case 'F':
+						case 'fail':
+							return "{:red}{$result}{:end}";
+						case 'E':
+						case 'exception':
+							return "{:purple}{$result}{:end}";
+						case 'S':
+						case 'skip':
+							return "{:cyan}{$result}{:end}";
+						default:
+							return "{:yellow}{$result}{:end}";
+					}
+				};
+
+				if ($command->verbose) {
+					$reporter = function($result) use ($command, $colorize) {
+						$command->out(sprintf(
+							'[%s] on line %4s in %s::%s()',
+							$colorize(sprintf('%9s', $result['result'])),
+							isset($result['line']) ? $result['line'] : '??',
+							isset($result['class']) ? $result['class'] : '??',
+							isset($result['method']) ? $result['method'] : '??'
+						));
+					};
+				} else {
+					$i = 0;
+					$columns = 60;
+
+					$reporter = function($result) use ($command, &$i, $columns, $colorize) {
+						$shorten = array('fail', 'skip', 'exception');
+
+						if ($result['result'] === 'pass') {
+							$symbol = '.';
+						} elseif (in_array($result['result'], $shorten)) {
+							$symbol = strtoupper($result['result'][0]);
+						} else {
+							$symbol = '?';
+						}
+						$command->out($colorize($symbol), false);
+
+						$i++;
+						if ($i % $columns === 0) {
+							$command->out();
+						}
+					};
+				}
+				$report = $runner(compact('reporter'));
+
+				if (!$command->plain) {
+					$stats = $report->stats();
+
+					$command->out(null, 2);
+					$command->out($report->render('result', $stats));
+					$command->out($report->render('errors', $stats));
+
+					if ($command->verbose) {
+						$command->out($report->render('skips', $stats));
+					}
+
+					foreach ($report->filters() as $filter => $options) {
+						$data = $report->results['filters'][$filter];
+						$command->out($report->render($options['name'], compact('data')));
+					}
+				}
+				return $report;
+			},
+			'json' => function($runner, $path) use ($command) {
+				$report = $runner();
+
+				if ($results = $report->filters()) {
+					$filters = array();
+
+					foreach ($results as $filter => $options) {
+						$filters[$options['name']] = $report->results['filters'][$filter];
+					}
+				}
+				$command->out($report->render('stats', $report->stats() + compact('filters')));
+				return $report;
+			}
+		);
+	}
+
+	/**
+	 * Runs tests given a path to a directory or file containing tests. The path to the
+	 * test(s) may be absolute or relative to the current working directory.
+	 *
+	 * {{{
+	 * li3 test lithium/tests/cases/core/ObjectTest.php
+	 * li3 test lithium/tests/cases/core
+	 * }}}
+	 *
+	 * If you are in the working directory of an application or plugin and wish to run all tests,
+	 * simply execute the following:
+	 *
+	 * {{{
+	 * li3 test tests/cases
+	 * }}}
+	 *
+	 * If you are in the working directory of an application and wish to run a plugin, execute one
+	 * of the following:
+	 *
+	 * {{{
+	 * li3 test libraries/<plugin>/tests/cases
+	 * li3 test <plugin>/tests/cases
+	 * }}}
+	 *
+	 *
+	 * This will run `<library>/tests/cases/<package>/<class>Test.php`:
+	 *
+	 * {{{
+	 * li3 test <library>/<package>/<class>.php
+	 * }}}
+	 *
+	 * @param string $path Absolute or relative path to tests or a file which
+	 *                     corresponding test should be run.
+	 * @return boolean Will exit with status `1` if one or more tests failed otherwise with `0`.
+	 */
+	public function run($path = null) {
+		if (!$path = $this->_path($path)) {
+			return false;
+		}
+		if (!preg_match('/(tests|Test\.php)/', $path)) {
+			if (!$path = Unit::get($path)) {
+				$this->error('Cannot map path to test path.');
+				return static::EXIT_NO_TEST;
+			}
+		}
+		$handlers = $this->_handlers;
+
+		if (!isset($handlers[$this->format]) || !is_callable($handlers[$this->format])) {
+			$this->error(sprintf('No handler for format `%s`... ', $this->format));
+			return false;
+		}
+		$filters = $this->filters ? array_map('trim', explode(',', $this->filters)) : array();
+		$params = compact('filters') + array('format' => $this->format);
+		$runner = function($options = array()) use ($path, $params) {
+			error_reporting(E_ALL | E_STRICT | E_DEPRECATED);
+			return Dispatcher::run($path, $params + $options);
+		};
+		$report = $handlers[$this->format]($runner, $path);
+		$stats = $report->stats();
+		return $stats['success'];
+	}
+
+	/**
+	 * Finds a library for given path.
+	 *
+	 * @param string $path Normalized (to slashes) absolute or relative path.
+	 * @return string the name of the library
+	 */
+	protected function _library($path) {
+		foreach (Libraries::get() as $name => $library) {
+			if (strpos($path, $library['path']) !== 0) {
+				continue;
+			}
+			return $name;
+		}
+	}
+
+	/**
+	 * Validates and gets a fully-namespaced class path from an absolute or
+	 * relative physical path to a directory or file. The final class path may
+	 * be partial in that in doesn't contain the class name.
+	 *
+	 * This method can be thought of the reverse of `Libraries::path()`.
+	 *
+	 * {{{
+	 * lithium/tests/cases/core/ObjectTest.php -> lithium\tests\cases\core\ObjectTest
+	 * lithium/tests/cases/core                -> lithium\tests\cases\core
+	 * lithium/core/Object.php                 -> lithium\core\Object
+	 * lithium/core/                           -> lithium\core
+	 * lithium/core                            -> lithium\core
+	 * }}}
+	 *
+	 * @see lithium\core\Libraries::path()
+	 * @param string $path The directory of or file path to one or more classes.
+	 * @return string Returns a fully-namespaced class path, or `false`, if an error occurs.
+	 */
+	protected function _path($path) {
+		$path = rtrim(str_replace('\\', '/', $path), '/');
+
+		if (!$path) {
+			$this->error('Please provide a path to tests.');
+			return false;
+		}
+		if ($path[0] === '/') {
+			$library = $this->_library($path);
+		}
+		if ($path[0] !== '/') {
+			$libraries = array_reduce(Libraries::get(), function($v, $w) {
+				$v[] = basename($w['path']);
+				return $v;
+			});
+
+			$library = basename($this->request->env('working'));
+			$parts = explode('/', str_replace("../", "", $path));
+			$plugin = array_shift($parts);
+
+			if ($plugin === 'libraries') {
+				$plugin = array_shift($parts);
+			}
+			if (in_array($plugin, $libraries)) {
+				$library = $plugin;
+				$path = join('/', $parts);
+			}
+		}
+		if (empty($library)) {
+			$this->error("No library found in path `{$path}`.");
+			return false;
+		}
+		if (!$config = Libraries::get($library)) {
+			$this->error("Library `{$library}` does not exist.");
+			return false;
+		}
+		$path = str_replace($config['path'], null, $path);
+		$realpath = $config['path'] . '/' . $path;
+
+		if (!realpath($realpath)) {
+			$this->error("Path `{$realpath}` not found.");
+			return false;
+		}
+		$class = str_replace(".php", "", str_replace('/', '\\', ltrim($path, '/')));
+		return $config['prefix'] . $class;
+	}
+}
+
+?>

+ 84 - 0
php-lithium/libraries/lithium/console/command/create/Controller.php

@@ -0,0 +1,84 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright     Copyright 2012, Union of RAD (http://union-of-rad.org)
+ * @license       http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+
+namespace lithium\console\command\create;
+
+use lithium\util\Inflector;
+
+/**
+ * Generate a Controller class in the `--library` namespace
+ *
+ * `li3 create controller Posts`
+ * `li3 create --library=li3_plugin controller Posts`
+ *
+ */
+class Controller extends \lithium\console\command\Create {
+
+	/**
+	 * Get the fully-qualified model class that is used by the controller.
+	 *
+	 * @param string $request
+	 * @return string
+	 */
+	protected function _use($request) {
+		$request->params['command'] = 'model';
+		return $this->_namespace($request) . '\\' . $this->_model($request);
+	}
+
+	/**
+	 * Get the controller class name.
+	 *
+	 * @param string $request
+	 * @return string
+	 */
+	protected function _class($request) {
+		return $this->_name($request) . 'Controller';
+	}
+
+	/**
+	 * Returns the name of the controller class, minus `'Controller'`.
+	 *
+	 * @param string $request
+	 * @return string
+	 */
+	protected function _name($request) {
+		return Inflector::camelize(Inflector::pluralize($request->action));
+	}
+
+	/**
+	 * Get the plural variable used for data in controller methods.
+	 *
+	 * @param string $request
+	 * @return string
+	 */
+	protected function _plural($request) {
+		return Inflector::pluralize(Inflector::camelize($request->action, false));
+	}
+
+	/**
+	 * Get the model class used in controller methods.
+	 *
+	 * @param string $request
+	 * @return string
+	 */
+	protected function _model($request) {
+		return Inflector::camelize(Inflector::pluralize($request->action));
+	}
+
+	/**
+	 * Get the singular variable to use for data in controller methods.
+	 *
+	 * @param string $request
+	 * @return string
+	 */
+	protected function _singular($request) {
+		return Inflector::singularize(Inflector::camelize($request->action, false));
+	}
+}
+
+?>

+ 74 - 0
php-lithium/libraries/lithium/console/command/create/Mock.php

@@ -0,0 +1,74 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright     Copyright 2012, Union of RAD (http://union-of-rad.org)
+ * @license       http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+
+namespace lithium\console\command\create;
+
+use lithium\util\Inflector;
+
+/**
+ * Generate a Mock that extends the name of the given class in the `--library` namespace.
+ *
+ * `li3 create mock model Posts`
+ * `li3 create --library=li3_plugin mock model Posts`
+ *
+ */
+class Mock extends \lithium\console\command\Create {
+
+	/**
+	 * Get the namespace for the mock.
+	 *
+	 * @param string $request
+	 * @param array|string $options
+	 * @return string
+	 */
+	protected function _namespace($request, $options = array()) {
+		$request->params['command'] = $request->action;
+		return parent::_namespace($request, array('prepend' => 'tests.mocks.'));
+	}
+
+	/**
+	 * Get the parent for the mock.
+	 *
+	 * @param string $request
+	 * @return string
+	 */
+	protected function _parent($request) {
+		$namespace = parent::_namespace($request);
+		$class = Inflector::pluralize($request->action);
+		return "\\{$namespace}\\{$class}";
+	}
+
+	/**
+	 * Get the class name for the mock.
+	 *
+	 * @param string $request
+	 * @return string
+	 */
+	protected function _class($request) {
+		$type = $request->action;
+		$name = $request->args();
+
+		if ($command = $this->_instance($type)) {
+			$request->params['action'] = $name;
+			$name = $command->invokeMethod('_class', array($request));
+		}
+		return  Inflector::pluralize("Mock{$name}");
+	}
+
+	/**
+	 * Get the methods for the mock to override
+	 *
+	 * @param string $request
+	 * @return string
+	 */
+	protected function _methods($request) {
+		return null;
+	}
+}
+
+?>

+ 33 - 0
php-lithium/libraries/lithium/console/command/create/Model.php

@@ -0,0 +1,33 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright     Copyright 2012, Union of RAD (http://union-of-rad.org)
+ * @license       http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+
+namespace lithium\console\command\create;
+
+use lithium\util\Inflector;
+
+/**
+ * Generate a Model class in the `--library` namespace
+ *
+ * `li3 create model Posts`
+ * `li3 create --library=li3_plugin model Posts`
+ *
+ */
+class Model extends \lithium\console\command\Create {
+
+	/**
+	 * Get the class name for the model.
+	 *
+	 * @param string $request
+	 * @return string
+	 */
+	protected function _class($request) {
+		return Inflector::camelize(Inflector::pluralize($request->action));
+	}
+}
+
+?>

+ 105 - 0
php-lithium/libraries/lithium/console/command/create/Test.php

@@ -0,0 +1,105 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright     Copyright 2012, Union of RAD (http://union-of-rad.org)
+ * @license       http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+
+namespace lithium\console\command\create;
+
+use lithium\core\Libraries;
+use lithium\util\Inflector;
+use lithium\analysis\Inspector;
+use lithium\core\ClassNotFoundException;
+
+/**
+ * Generate a Test class in the `--library` namespace
+ *
+ * `li3 create test model Posts`
+ * `li3 create --library=li3_plugin test model Posts`
+ *
+ */
+class Test extends \lithium\console\command\Create {
+
+	/**
+	 * Get the namespace for the test case.
+	 *
+	 * @param string $request
+	 * @param array $options
+	 * @return string
+	 */
+	protected function _namespace($request, $options = array()) {
+		$request->params['command'] = $request->action;
+		return parent::_namespace($request, array('prepend' => 'tests.cases.'));
+	}
+
+	/**
+	 * Get the class used by the test case.
+	 *
+	 * @param string $request
+	 * @return string
+	 */
+	protected function _use($request) {
+		return parent::_namespace($request) . '\\' . $this->_name($request);
+	}
+
+	/**
+	 * Get the class name for the test case.
+	 *
+	 * @param string $request
+	 * @return string
+	 */
+	protected function _class($request) {
+		$name = $this->_name($request);
+		return  Inflector::classify("{$name}Test");
+	}
+
+	/**
+	 * Get the methods to test.
+	 *
+	 * @param string $request
+	 * @return string
+	 */
+	protected function _methods($request) {
+		$use = $this->_use($request);
+		$path = Libraries::path($use);
+
+		if (!file_exists($path)) {
+			return "";
+		}
+		$methods = (array) Inspector::methods($use, 'extents');
+		$testMethods = array();
+
+		foreach (array_keys($methods) as $method) {
+			$testMethods[] = "\tpublic function test" . ucwords($method) . "() {}";
+		}
+		return join("\n", $testMethods);
+	}
+
+	/**
+	 * Get the class to be tested
+	 *
+	 * @param string $request
+	 * @return string
+	 */
+	protected function _name($request) {
+		$type = $request->action;
+		$name = $request->args();
+
+		try {
+			$command = $this->_instance($type);
+		} catch (ClassNotFoundException $e) {
+			$command = null;
+		}
+
+		if ($command) {
+			$request->params['action'] = $name;
+			$name = $command->invokeMethod('_class', array($request));
+		}
+		$request->params['action'] = $type;
+		return $name;
+	}
+}
+
+?>

+ 92 - 0
php-lithium/libraries/lithium/console/command/create/View.php

@@ -0,0 +1,92 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright     Copyright 2012, Union of RAD (http://union-of-rad.org)
+ * @license       http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+
+namespace lithium\console\command\create;
+
+use lithium\util\Inflector;
+use lithium\util\String;
+
+/**
+ * Generate a View file in the `--library` namespace
+ *
+ * `li3 create view Posts index`
+ * `li3 create --library=li3_plugin view Posts index`
+ *
+ */
+class View extends \lithium\console\command\Create {
+
+	/**
+	 * Returns the name of the controller class, minus `'Controller'`.
+	 *
+	 * @param string $request
+	 * @return string
+	 */
+	protected function _name($request) {
+		return Inflector::camelize(Inflector::pluralize($request->action));
+	}
+
+	/**
+	 * Get the plural data variable that is sent down from controller method.
+	 *
+	 * @param string $request
+	 * @return string
+	 */
+	protected function _plural($request) {
+		return Inflector::pluralize(Inflector::camelize($request->action, false));
+	}
+
+	/**
+	 * Get the singular data variable that is sent down from controller methods.
+	 *
+	 * @param string $request
+	 * @return string
+	 */
+	protected function _singular($request) {
+		return Inflector::singularize(Inflector::camelize($request->action, false));
+	}
+
+	/**
+	 * Override the save method to handle view specific params.
+	 *
+	 * @param array $params
+	 * @return mixed
+	 */
+	protected function _save(array $params = array()) {
+		$params['path'] = Inflector::underscore($this->request->action);
+		$params['file'] = $this->request->args(0);
+
+		$contents = $this->_template();
+		$result = String::insert($contents, $params);
+
+		if (!empty($this->_library['path'])) {
+			$path = $this->_library['path'] . "/views/{$params['path']}/{$params['file']}";
+			$file = str_replace('//', '/', "{$path}.php");
+			$directory = dirname($file);
+
+			if (!is_dir($directory)) {
+				if (!mkdir($directory, 0755, true)) {
+					return false;
+				}
+			}
+			$directory = str_replace($this->_library['path'] . '/', '', $directory);
+			if (file_exists($file)) {
+				$prompt = "{$file} already exists. Overwrite?";
+				$choices = array('y', 'n');
+				if ($this->in($prompt, compact('choices')) !== 'y') {
+					return "{$params['file']} skipped.";
+				}
+			}
+			if (is_int(file_put_contents($file, $result))) {
+				return "{$params['file']}.php created in {$directory}.";
+			}
+		}
+		return false;
+	}
+}
+
+?>

Some files were not shown because too many files changed in this diff