b6a90a9b7c67802e5f7bf6a94844b0893ae5f7c4
Author: chawbacca
Date: 2009-01-30 11:13:38 -0600
diff --git a/README b/README
new file mode 100644
index 0000000..52ae006
--- /dev/null
+++ b/README
@@ -0,0 +1,40 @@
+Api Generator
+=============
+The Api Generator gives you an easy to use file browser, that generators API documentation for your application. It provides a file and class browser, for the files being parsed. It also affords Markdown syntax Doc blocks to be parsed into HTML.
+
+
+Requirements:
+-------------
+PHP5 - Requires PHP5.2. If you are on PHP4 too bad, time to upgrade anyways.
+
+Configuration
+-------------
+Api Generator has a number of configuration points. All are stored in the Configure class. See below for an explanation of each setting.
+
+ * ApiGenerator.basePath - the base path that ApiGenerator scans for files / classes to generate docs from. Files not in this path will not be linkable or readable. Defaults to APP
+ * ApiGenerator.excludeNonPublic - Exclude Non Public methods and variables from the generated documentation. Defaults to false.
+ * ApiGenerator.disableFileBrowser - Disable file browsing. Defaults to false.
+ * ApiGenerator.disableClassBrowser - Disable class broswing. Defaults to false.
+ * ApiGenerator.excludeClasses - Array of classes to exclude when generating class docs list. Defaults to array();
+
+Known Issues:
+-------------
+ * If you are generating an api for an application that is not the application containing the ApiGenerator, classes like AppController, and AppModel will cause fatal errors, as the files have not been used before but the classnames already exist.
+
+
+Goals of project:
+-----------------
+* Provide a file and class browser for explorer for exploring a project and viewing the classes it contains.
+* Provide easy to read and well designed api docs.
+* Allow configuration of api generation
+ * what path is used.
+ * what directories / files are not included.
+ * ability to disable either file or class browser.
+ * ability to omit certain classes from the docs
+ * allow / remove protected / private members from being displayed.
+
+Usage:
+-----------
+cake api_index initdb
+cake api_index update
+visit, http://localohst/api_generator/classes
\ No newline at end of file
diff --git a/api_generator_app_controller.php b/api_generator_app_controller.php
new file mode 100644
index 0000000..fc3fec3
--- /dev/null
+++ b/api_generator_app_controller.php
@@ -0,0 +1,71 @@
+<?php
+/* SVN FILE: $Id$ */
+/**
+ * Api Generator Plugin App Controller
+ *
+ *
+ * PHP version 5
+ *
+ * CakePHP : Rapid Development Framework <http://www.cakephp.org/>
+ * Copyright 2006-2008, Cake Software Foundation, Inc.
+ * 1785 E. Sahara Avenue, Suite 490-204
+ * Las Vegas, Nevada 89104
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright 2006-2008, Cake Software Foundation, Inc.
+ * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project
+ * @package api_generator
+ * @subpackage api_generator.controllers
+ * @since
+ * @version
+ * @modifiedby
+ * @lastmodified
+ * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
+class ApiGeneratorAppController extends AppController {
+/**
+ * theme
+ *
+ **/
+ public $theme = 'api';
+/**
+ * view
+ *
+ **/
+ public $view = 'Theme';
+/**
+ * beforeFilter callback
+ *
+ * @return void
+ **/
+ public function beforeFilter() {
+ $this->ApiConfig = ClassRegistry::init('ApiGenerator.ApiConfig');
+ $this->ApiConfig->read();
+ $path = $this->ApiConfig->getPath();
+ if (empty($path)) {
+ $path = APP;
+ $this->ApiConfig->data['paths'][$path] = true;
+ }
+ $this->path = Folder::slashTerm(realpath($path));
+ }
+/**
+ * Error Generating Page.
+ *
+ * @return void
+ **/
+ protected function _notFound($name = null, $message = null) {
+ $name = ($name) ? $name : 'Page Not Found';
+ $message = ($message) ? $message : $this->params['url']['url'];
+ $this->cakeError('error', array(
+ 'name' => $name,
+ 'message' => $message,
+ 'code' => 404,
+ 'url' => $this->params['url']['url']
+ ));
+ $this->_stop();
+ }
+}
+?>
\ No newline at end of file
diff --git a/api_generator_app_model.php b/api_generator_app_model.php
new file mode 100644
index 0000000..300063f
--- /dev/null
+++ b/api_generator_app_model.php
@@ -0,0 +1,45 @@
+<?php
+/* SVN FILE: $Id$ */
+/**
+ * ApiGenerator App Model class
+ *
+ * Base model class for models in ApiGenerator
+ *
+ * PHP versions 4 and 5
+ *
+ * CakePHP : Rapid Development Framework <http://www.cakephp.org/>
+ * Copyright 2006-2008, Cake Software Foundation, Inc.
+ * 1785 E. Sahara Avenue, Suite 490-204
+ * Las Vegas, Nevada 89104
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright 2006-2008, Cake Software Foundation, Inc.
+ * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project
+ * @package cake
+ * @subpackage cake.cake.libs.
+ * @since CakePHP v 1.2.0.4487
+ * @version
+ * @modifiedby
+ * @lastmodified
+ * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
+class ApiGeneratorAppModel extends AppModel {
+/**
+ * Inflect a slashed path to url safe path. Trims ApiGenerator.filePath off as well.
+ *
+ * @param string $slashPath The slashed path to slug.
+ * @return string
+ **/
+ public function slugPath($slashPath, $stripBase = true) {
+ if ($stripBase) {
+ $basePath = Configure::read('ApiGenerator.filePath');
+ $slashPath = trim($slashPath, $basePath);
+ }
+ $slugPath = strtolower(Inflector::slug($slashPath, '-'));
+ return $slugPath;
+ }
+}
+?>
\ No newline at end of file
diff --git a/config/sql/api_generator.php b/config/sql/api_generator.php
new file mode 100644
index 0000000..33ea2b5
--- /dev/null
+++ b/config/sql/api_generator.php
@@ -0,0 +1,48 @@
+<?php
+/* SVN FILE: $Id$ */
+/**
+ * Api Generator Schema file.
+ *
+ * Schema file for Api Generator.
+ *
+ * PHP versions 4 and 5
+ *
+ * CakePHP : Rapid Development Framework <http://www.cakephp.org/>
+ * Copyright 2006-2008, Cake Software Foundation, Inc.
+ * 1785 E. Sahara Avenue, Suite 490-204
+ * Las Vegas, Nevada 89104
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright 2006-2008, Cake Software Foundation, Inc.
+ * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project
+ * @package cake
+ * @subpackage cake.api_generator.config
+ * @since
+ * @version
+ * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
+/**
+ * ApiGenerator Plugin Schema
+ *
+ * @package cake.api_generator.config
+ */
+class ApiGeneratorSchema extends CakeSchema {
+/**
+ * api_classes table definition.
+ *
+ * @var array
+ */
+ public $api_classes = array(
+ 'id' => array('type' => 'string', 'default' => NULL, 'length' => 36, 'null' => false, 'key' => 'primary'),
+ 'name' => array('type' => 'string', 'length' => 200, 'null' => false),
+ 'slug' => array('type' => 'string', 'length' => 200, 'null' => false),
+ 'file_name' => array('type' => 'text'),
+ 'search_index' => array('type' => 'text'),
+ 'flags' => array('type' => 'integer', 'default' => 0, 'length' => 5),
+ 'created' => array('type' => 'datetime'),
+ 'modified' => array('type' => 'datetime'),
+ );
+}
\ No newline at end of file
diff --git a/controllers/api_generator_controller.php b/controllers/api_generator_controller.php
new file mode 100644
index 0000000..3b351f0
--- /dev/null
+++ b/controllers/api_generator_controller.php
@@ -0,0 +1,212 @@
+<?php
+/* SVN FILE: $Id$ */
+/**
+ * Api Generator Controller
+ *
+ *
+ *
+ * PHP versions 4 and 5
+ *
+ * CakePHP : Rapid Development Framework <http://www.cakephp.org/>
+ * Copyright 2006-2008, Cake Software Foundation, Inc.
+ * 1785 E. Sahara Avenue, Suite 490-204
+ * Las Vegas, Nevada 89104
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright 2006-2008, Cake Software Foundation, Inc.
+ * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project
+ * @package cake
+ * @subpackage cake.
+ * @version
+ * @modifiedby
+ * @lastmodified
+ * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
+class ApiGeneratorController extends ApiGeneratorAppController {
+/**
+ * Name property
+ *
+ * @var string
+ */
+ public $name = 'ApiGenerator';
+/**
+ * Uses array
+ *
+ * @var array
+ */
+ public $uses = array('ApiGenerator.ApiFile');
+/**
+ * Components array
+ *
+ * @var array
+ **/
+ public $components = array('RequestHandler');
+/**
+ * Helpers
+ *
+ * @var array
+ **/
+ public $helpers = array('ApiGenerator.ApiDoc', 'ApiGenerator.ApiUtils', 'Html', 'Javascript');
+/**
+ * Browse application files and find things you would like to generate API docs for.
+ *
+ * @return void
+ **/
+ public function index() {
+ $this->classes();
+ if (!empty($this->viewVars['classIndex'])) {
+ $this->render('classes');
+ return;
+ }
+ }
+
+/**
+ * Browse application files and find things you would like to generate API docs for.
+ *
+ * @return void
+ **/
+ public function source() {
+ if (count($this->passedArgs) == 1 && $this->passedArgs[0] == 'index') {
+ array_shift($this->passedArgs);
+ }
+ $currentPath = implode('/', $this->passedArgs);
+ $previousPath = implode('/', array_slice($this->passedArgs, 0, count($this->passedArgs) -1));
+ list($dirs, $files) = $this->ApiFile->read($this->path . $currentPath);
+ $this->set(compact('dirs', 'files', 'currentPath', 'previousPath'));
+ }
+
+/**
+ * all_files
+ *
+ * Gets a recursive list of all files that match documentor criteria.
+ *
+ * @access public
+ * @return void
+ */
+ public function files() {
+ $files = $this->ApiFile->fileList($this->path);
+ $this->set('files', $files);
+ }
+
+/**
+ * Browse the classes in the application / API files.
+ *
+ * @return void
+ **/
+ public function classes() {
+ $this->ApiClass = ClassRegistry::init('ApiGenerator.ApiClass');
+ $classIndex = $this->ApiClass->getClassIndex();
+ $this->set('classIndex', $classIndex);
+ }
+
+/**
+ * View the API docs for all interesting parts in a file.
+ *
+ * @return void
+ **/
+ public function view_file() {
+ $currentPath = implode('/', $this->passedArgs);
+ $fullPath = $this->path . $currentPath;
+ $previousPath = implode('/', array_slice($this->passedArgs, 0, count($this->passedArgs) -1));
+ $upOneFolder = implode('/', array_slice($this->passedArgs, 0, count($this->passedArgs) -2));
+
+ if (!file_exists($fullPath)) {
+ $this->_notFound(__('No file exists with that name', true));
+ }
+ try {
+ $docs = $this->ApiFile->loadFile($fullPath, array('useIndex' => true));
+ } catch(Exception $e) {
+ $this->_notFound($e->getMessage());
+ }
+ $classIndex = ClassRegistry::init('ApiGenerator.ApiClass')->getClassIndex();
+ list($dirs, $files) = $this->ApiFile->read($this->path . $previousPath);
+
+ if (!empty($docs)) {
+ $this->set('showSidebar', true);
+ $this->set('sidebarElement', 'sidebar/file_sidebar');
+ $this->set(compact('currentPath', 'previousPath', 'upOneFolder', 'docs', 'dirs', 'files', 'classIndex'));
+ } else {
+ $this->set('previousPath', $previousPath);
+ $this->render('no_class');
+ }
+ }
+
+/**
+ * View API docs for a single class used with browse_classes
+ *
+ * @return void
+ **/
+ public function view_class($classSlug = null) {
+ if (!$classSlug) {
+ $this->Session->setFlash(__('No class name was given', true));
+ $this->redirect($this->referer());
+ }
+ $this->ApiClass = ClassRegistry::init('ApiGenerator.ApiClass');
+ $classInfo = $this->ApiClass->findBySlug($classSlug);
+ if (empty($classInfo['ApiClass']['file_name'])) {
+ $this->_notFound(__('No class exists in the index with that name', true));
+ }
+ try {
+ $docs = $this->ApiFile->loadFile($classInfo['ApiClass']['file_name'], array('useIndex' => true));
+ $doc = $docs['class'][$classInfo['ApiClass']['name']];
+ } catch(Exception $e) {
+ $this->_notFound($e->getMessage());
+ }
+
+ $classIndex = $this->ApiClass->getClassIndex();
+ if (!empty($docs)) {
+ $this->set('showSidebar', true);
+ $this->set('sidebarElement', 'sidebar/class_sidebar');
+ $this->set(compact('doc', 'classIndex'));
+ } else {
+ $this->_notFound(__("Oops, seems we couldn't get the documentation for that class.", true));
+ }
+ }
+/**
+ * View the Source for a file.
+ *
+ * @return void
+ **/
+ public function view_source($classSlug = null) {
+ $this->ApiClass = ClassRegistry::init('ApiGenerator.ApiClass');
+ $classInfo = $this->ApiClass->findBySlug($classSlug);
+
+ if (empty($classInfo['ApiClass']['file_name'])) {
+ $this->_notFound(__('No class exists in the index with that name', true));
+ }
+ $fileContents = file_get_contents($classInfo['ApiClass']['file_name']);
+ $this->set('contents', $fileContents);
+ $this->set('filename', $classInfo['ApiClass']['file_name']);
+ }
+/**
+ * Search through the class index.
+ *
+ * @return void
+ **/
+ public function search() {
+ $this->ApiClass = ClassRegistry::init('ApiGenerator.ApiClass');
+ $conditions = array();
+ if (isset($this->params['url']['query'])) {
+ $query = $this->params['url']['query'];
+ $conditions = array('ApiClass.search_index LIKE' => '%' . $query . '%');
+ }
+ $this->paginate['fields'] = array('DISTINCT ApiClass.name', 'ApiClass.search_index');
+ $this->paginate['order'] = 'ApiClass.name ASC';
+ $results = $this->paginate($this->ApiClass, $conditions);
+ $classIndex = $this->ApiClass->getClassIndex();
+ $this->helpers[] = 'Text';
+ $this->set(compact('results', 'classIndex'));
+ }
+/**
+ * Extract all the useful config info out of the ApiConfig.
+ *
+ * @return void
+ **/
+ public function beforeRender() {
+ $this->set('basePath', $this->path);
+ $this->set($this->ApiFile->getExclusions());
+ }
+}
\ No newline at end of file
diff --git a/models/api_class.php b/models/api_class.php
new file mode 100644
index 0000000..01e92ef
--- /dev/null
+++ b/models/api_class.php
@@ -0,0 +1,121 @@
+<?php
+/* SVN FILE: $Id$ */
+/**
+ * Api Class Model
+ *
+ * Used for fetching information from the class index.
+ *
+ * PHP versions 4 and 5
+ *
+ * CakePHP : Rapid Development Framework <http://www.cakephp.org/>
+ * Copyright 2006-2008, Cake Software Foundation, Inc.
+ * 1785 E. Sahara Avenue, Suite 490-204
+ * Las Vegas, Nevada 89104
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright 2006-2008, Cake Software Foundation, Inc.
+ * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project
+ * @package cake
+ * @subpackage cake.api_generator.models
+ * @since
+ * @version
+ * @modifiedby
+ * @lastmodified
+ * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
+class ApiClass extends ApiGeneratorAppModel {
+/**
+ * Name
+ *
+ * @var string
+ */
+ public $name = 'ApiClass';
+/**
+ * Validation rules
+ *
+ * @var string
+ **/
+ public $validate = array(
+ 'name' => array(
+ 'empty' => array(
+ 'rule' => 'notEmpty',
+ 'message' => 'Name must not be empty',
+ )
+ ),
+ 'flags' => array(
+ 'number' => array(
+ 'rule' => 'numeric',
+ 'message' => 'Flags are numeric only',
+ )
+ ),
+ );
+
+/**
+ * Clears (truncates) the class index.
+ *
+ * @return void
+ **/
+ public function clearIndex() {
+ $db = ConnectionManager::getDataSource($this->useDbConfig);
+ $db->truncate($this->useTable);
+ }
+
+/**
+ * save the entry in the index for a ClassDocumentor object
+ *
+ * @param object $classDoc Instance of ClassDocumentor to add to database.
+ * @return boolean success
+ **/
+ public function saveClassDocs(ClassDocumentor $classDoc) {
+ $classDoc->getAll();
+ $slug = str_replace('_', '-', Inflector::underscore($classDoc->name));
+ $new = array(
+ 'name' => $classDoc->name,
+ 'slug' => $slug,
+ 'file_name' => $classDoc->classInfo['fileName'],
+ 'search_index' => $this->_generateSearchIndex($classDoc),
+ );
+ $this->set($new);
+ return $this->save();
+ }
+
+/**
+ * Get the class index listing
+ *
+ * @return array
+ **/
+ public function getClassIndex() {
+ return $this->find('list', array('fields' => array('slug', 'name'), 'order' => 'ApiClass.name ASC'));
+ }
+
+/**
+ * Generate a search index from all the properties and methods
+ * in a ClassDocumentor Object
+ *
+ * @return string
+ **/
+ protected function _generateSearchIndex($classDoc) {
+ $index = '';
+ $index .= $classDoc->classInfo['comment']['description'];
+ foreach ((array)$classDoc->properties as $prop) {
+ if ($prop['declaredInClass'] != $classDoc->classInfo['name']) {
+ continue;
+ }
+ $index .= ' ' . $prop['comment']['description'];
+ }
+ foreach ((array)$classDoc->methods as $method) {
+ if ($method['declaredInClass'] != $classDoc->classInfo['name']) {
+ continue;
+ }
+ $description = str_replace("\n", ' ', $method['comment']['description']);
+ $index .= ' ' . $description;
+ foreach ($method['args'] as $argument) {
+ $index .= ' ' . $argument['comment'];
+ }
+ }
+ return strtolower($index);
+ }
+}
\ No newline at end of file
diff --git a/models/api_config.php b/models/api_config.php
new file mode 100644
index 0000000..2f326ad
--- /dev/null
+++ b/models/api_config.php
@@ -0,0 +1,187 @@
+<?php
+/**
+ * Api Config Model
+ *
+ * For interacting with the Config files for ApiGenerator
+ *
+ *
+ * PHP versions 4 and 5
+ *
+ * CakePHP : Rapid Development Framework <http://www.cakephp.org/>
+ * Copyright 2006-2008, Cake Software Foundation, Inc.
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright Copyright 2006-2008, Cake Software Foundation, Inc.
+ * @link http://cakephp.org CakePHP Project
+ * @package cake
+ * @subpackage cake.cake.libs.
+ * @since CakePHP v 1.2.0.4487
+ * @version
+ * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
+class ApiConfig extends Object {
+/**
+ * holds data for read
+ *
+ * @var array
+ **/
+ public $data = array();
+/**
+ * holds path to current config file
+ *
+ * @var string
+ **/
+ public $path = null;
+
+/**
+ * Constructor
+ *
+ * @return void
+ *
+ **/
+ public function __construct() {
+ $this->path = dirname(dirname(__FILE__)) . DS . 'config' . DS . 'api_config.ini';
+ }
+/**
+ * Read from config file or passed array
+ *
+ * @param mixed $lines absolute file path, array, or string
+ * @return array
+ *
+ **/
+ public function read($lines = array()) {
+ if (empty($lines)) {
+ if (!empty($this->data)) {
+ return $this->data;
+ }
+ $lines = $this->path;
+ }
+
+ if (is_string($lines)) {
+ $isPath = $lines[0] === '/' || $lines[1] === ':';
+ if ($isPath && file_exists($lines)) {
+ $lines = file_get_contents($lines);
+ } elseif ($isPath && !file_exists($lines)) {
+ return array();
+ }
+
+ $lines = str_replace(array("\r\n", "\r"), "\n", $lines);
+ $lines = explode("\n", $lines);
+ }
+ if (empty($lines)) {
+ return array();
+ }
+ $ini = array();
+
+ $lines = array_filter($lines);
+ foreach ($lines as $line) {
+ $row = trim($line);
+ if (empty($row) || $row[0] == ';') {
+ continue;
+ }
+
+ if ($row[0] == '[' && substr($row, -1, 1) == ']') {
+ $section = preg_replace('/[\[\]]/', '', $row);
+ } else {
+ $delimiter = strpos($row, '=');
+ if ($delimiter > 0) {
+ $key = trim(substr($row, 0, $delimiter));
+ $value = trim(substr($row, $delimiter + 1));
+
+ if (substr($value, 0, 1) == '"' && substr($value, -1) == '"') {
+ $value = substr($value, 1, -1);
+ }
+ $ini[$section][$key] = stripcslashes($value);
+ } else {
+ if (!isset($section)) {
+ $section = '';
+ }
+ $ini[$section][trim($row)] = '';
+ }
+ }
+ }
+ return $this->data = $ini;
+ }
+/**
+ * Save a config
+ *
+ * @param mixed $path
+ * @param mixed $string
+ *
+ * @return boolean
+ *
+ **/
+ public function save($path = null, $string = null) {
+ if (empty($path) && empty($string)) {
+ return false;
+ }
+
+ if (!empty($path) && empty($string)) {
+ $string = $path;
+ $path = $this->path;
+ }
+
+ if (is_array($string)) {
+ $string = $this->toString($string);
+ }
+
+ $File = new File($path, true, 0755);
+ if ($File->write($string)) {
+ return true;
+ }
+ return false;
+ }
+/**
+ * Get the path at index
+ *
+ * @param int $index Index of file path to get. defaults to 0
+ * @return string Absolute file path read from config.
+ **/
+ public function getPath($index = 0) {
+ if (empty($this->data)) {
+ $this->read();
+ }
+ if (empty($this->data['paths'])) {
+ trigger_error(sprintf('Paths missing from %s', 'APP' . DS . 'config' . DS . 'api_config.ini'), E_USER_ERROR);
+ return false;
+ }
+ $paths = array_keys($this->data['paths']);
+ if (!isset($paths[$index])) {
+ return false;
+ }
+ return $paths[$index];
+ }
+
+/**
+ * Return data as a config string
+ *
+ * @return void
+ *
+ **/
+ public function toString($data = array()) {
+ if (empty($data)) {
+ if(empty($this->data)) {
+ return false;
+ }
+ $data = $this->data;
+ }
+ $result = array();
+ foreach ($data as $key => $value) {
+ if ($key[0] != '[') {
+ $result[] = "[$key]";
+ }
+ if (is_array($value)) {
+ foreach ($value as $key2 => $value2) {
+ $result[] = "{$key2} = " . trim(var_export($value2, true), "'");
+ }
+ }
+ }
+ if (empty($result)) {
+ $result = $data;
+ }
+ return join("\n", $result);
+ }
+}
+?>
\ No newline at end of file
diff --git a/models/api_file.php b/models/api_file.php
new file mode 100644
index 0000000..b0da193
--- /dev/null
+++ b/models/api_file.php
@@ -0,0 +1,474 @@
+<?php
+/* SVN FILE: $Id$ */
+/**
+ * Api File Model
+ *
+ * For interacting with the Filesystem specified by ApiGenerator.filePath
+ *
+ *
+ * PHP versions 4 and 5
+ *
+ * CakePHP : Rapid Development Framework <http://www.cakephp.org/>
+ * Copyright 2006-2008, Cake Software Foundation, Inc.
+ * 1785 E. Sahara Avenue, Suite 490-204
+ * Las Vegas, Nevada 89104
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright 2006-2008, Cake Software Foundation, Inc.
+ * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project
+ * @package cake
+ * @subpackage cake.cake.libs.
+ * @since CakePHP v 1.2.0.4487
+ * @version
+ * @modifiedby
+ * @lastmodified
+ * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
+App::import('Vendor', 'ApiGenerator.Introspector');
+
+class ApiFile extends Object {
+/**
+ * Name
+ *
+ * @var string
+ */
+ public $name = 'ApiFile';
+/**
+ * A list of folders to ignore.
+ *
+ * @var array
+ **/
+ public $excludeDirectories = array();
+/**
+ * A list of files to ignore.
+ *
+ * @var array
+ **/
+ public $excludeFiles = array();
+/**
+ * a list of extensions to scan for
+ *
+ * @var array
+ **/
+ public $allowedExtensions = array();
+/**
+ * Array of class dependancies map
+ *
+ * @var array
+ **/
+ public $dependencyMap = array();
+/**
+ * Mappings of funny named classes to files
+ *
+ * @var string
+ **/
+ public $classMap = array();
+/**
+ * A regexp for file names. (will be made case insenstive)
+ *
+ * @var string
+ **/
+ public $fileRegExp = '[a-z_\-0-9]+';
+/**
+ * Folder instance
+ *
+ * @var Folder
+ **/
+ protected $_Folder;
+/**
+ * ApiConfig Model instance
+ *
+ * @var object
+ **/
+ public $ApiConfig;
+/**
+ * Current Extractor instance
+ *
+ * @var object
+ **/
+ protected $_extractor;
+/**
+ * storage for defined classes
+ *
+ * @var array
+ **/
+ protected $_definedClasses = array();
+/**
+ * storage for defined functions
+ *
+ * @var array
+ **/
+ protected $_definedFunctions = array();
+/**
+ * Constructor
+ *
+ * @return void
+ **/
+ public function __construct() {
+ parent::__construct();
+ $this->ApiConfig = ClassRegistry::init('ApiGenerator.ApiConfig');
+ $this->_initConfig();
+ $this->_Folder = new Folder(APP);
+ }
+/**
+ * Read a path and return files and folders not in the excluded Folder list
+ *
+ * @param string $path The absolute path you wish to read.
+ * @return array
+ **/
+ public function read($path) {
+ if (preg_match('|\.\.|', $path)) {
+ return array(array(), array());
+ }
+ $this->_Folder->cd($path);
+ $ignore = $this->excludeFiles;
+ $ignore[] = '.';
+ $contents = $this->_Folder->read(true, $ignore);
+ $this->_filterFolders($contents[0], false);
+ $this->_filterFiles($contents[1]);
+ return $contents;
+ }
+/**
+ * Recursive Read a path and return files and folders not in the excluded Folder list
+ *
+ * @param string $path The path you wish to read.
+ * @return array
+ **/
+ public function fileList($path) {
+ $this->_Folder->cd($path);
+ $filePattern = $this->fileRegExp . '\.' . implode('|', $this->allowedExtensions);
+ $contents = $this->_Folder->findRecursive($filePattern);
+ $this->_filterFolders($contents);
+ $this->_filterFiles($contents);
+ return $contents;
+ }
+/**
+ * _filterFiles
+ *
+ * Filter a file list and remove excludeDirectories
+ *
+ * @param array $files List of files to filter and ignore. (reference)
+ * @return void
+ **/
+ protected function _filterFolders(&$fileList, $recursiveList = true) {
+ $count = count($fileList);
+ foreach ($this->excludeDirectories as $blackListed) {
+ if ($recursiveList) {
+ $blackListed = DS . $blackListed . DS;
+ }
+ for ($i = 0; $i < $count; $i++) {
+ if (isset($fileList[$i]) && strpos($fileList[$i], $blackListed) !== false) {
+ unset($fileList[$i]);
+ }
+ }
+ }
+ $fileList = array_values($fileList);
+ }
+/**
+ * remove files that don't match the allowedExtensions
+ * or are on the excludeFiles list
+ *
+ * @return void
+ **/
+ protected function _filterFiles(&$fileList) {
+ foreach ($this->excludeFiles as $ignored) {
+ $fileCount = count($fileList);
+ $fileList = array_values($fileList);
+ for ($i = 0; $i < $fileCount; $i++) {
+ $basename = basename($fileList[$i]);
+ if ($ignored == $basename) {
+ unset($fileList[$i]);
+ }
+ }
+ }
+ foreach ($this->allowedExtensions as $ext) {
+ $extPattern = '/\.' . $ext . '$/i';
+ foreach ($fileList as $i => $file) {
+ if (!preg_match($extPattern, $file)) {
+ unset($fileList[$i]);
+ }
+ }
+ }
+ $fileList = array_values($fileList);
+ }
+/**
+ * Loads the documentation extractor for a given classname.
+ *
+ * @param string $name Name of class to load.
+ * @access public
+ * @return void
+ */
+ public function loadExtractor($type, $name) {
+ $this->_extractor = Introspector::getReflector($type, $name);
+ }
+/**
+ * Get the documentor extractor instance
+ *
+ * @access public
+ * @return object
+ */
+ public function getExtractor() {
+ return $this->_extractor;
+ }
+/**
+ * Gets the parsed docs from the Extractor
+ *
+ * @return object Extractor with all docs processed.
+ **/
+ public function getDocs() {
+ if (!$this->_extractor) {
+ return array();
+ }
+ $this->_extractor->getAll();
+ return $this->_extractor;
+ }
+/**
+ * Load A File and extract docs for all classes contained in that file
+ *
+ * @param string $fullPath FullPath of the file you want to load.
+ * @param array $options Options to use
+ * - 'useIndex' boolean whether or not a search should be done on the ApiClass index for any missing classes
+ * defaults to false.
+ * @return array Array of all the docs from all the classes that were loaded as a result of the file being loaded.
+ * @throws MissingClassException If a dependancy cannot be solved, an exception will be thrown.
+ **/
+ public function loadFile($filePath, $options = array()) {
+ $docs = array('class' => array(), 'function' => array());
+ if (preg_match('|\.\.|', $filePath)) {
+ return $docs;
+ }
+ $this->_importCakeBaseClasses($filePath);
+ $this->_resolveDependancies($filePath, $options);
+ $this->_getDefinedObjects();
+ $newObjects = $this->findObjectsInFile($filePath);
+ foreach ($newObjects as $type => $objects) {
+ foreach ($objects as $element) {
+ $this->loadExtractor($type, $element);
+ $docs[$type][$element] = $this->getDocs();
+ }
+ }
+ return $docs;
+ }
+/**
+ * Import the core classes (Controller, View, Helper, Model)
+ *
+ * @return void
+ **/
+ public function importCoreClasses() {
+ App::import('Core', array('Controller', 'Model', 'View', 'Helper'));
+ }
+/**
+ * gets the currently defined functions and classes
+ * so comparisons to new files can be made
+ *
+ * @return void
+ **/
+ protected function _getDefinedObjects() {
+ $this->_definedClasses = get_declared_classes();
+ $funcs = get_defined_functions();
+ $this->_definedFunctions = $funcs['user'];
+ }
+/**
+ * Fetches the class names and functions contained in the target file.
+ * If first pass misses, a forceParse pass will be run.
+ *
+ * @param string $filePath Absolute file path to file you want to read.
+ * @param boolean $forceParse Force the manual read of a file.
+ * @return array
+ **/
+ public function findObjectsInFile($filePath, $forceParse = false) {
+ $new = $tmp = array();
+ $tmp['class'] = $this->_parseClassNamesInFile($filePath);
+ $tmp['function'] = $this->_parseFunctionNamesInFile($filePath);
+ $include = false;
+ foreach ($tmp['class'] as $classInFile) {
+ $include = false;
+ if (!class_exists($classInFile, false)) {
+ $include = true;
+ }
+ }
+ if (!$include || $forceParse) {
+ $new = $tmp;
+ } else {
+ ob_start();
+ include_once $filePath;
+ ob_clean();
+
+ $new['class'] = array_diff(get_declared_classes(), $this->_definedClasses);
+ $funcs = get_defined_functions();
+ $new['function'] = array_diff($funcs['user'], $this->_definedFunctions);
+ }
+ if (empty($new['class']) && empty($new['function']) && $forceParse === false) {
+ $new = $this->findObjectsInFile($filePath, true);
+ }
+ return $new;
+ }
+/**
+ * Retrieves the classNames defined in a file.
+ * Solves issues of reading docs from files that have already been included.
+ *
+ * @param string $filePath Absolute file path to file you want to parse.
+ * @param boolean $getParents Get the parent classes instead.
+ * @return array Array of class names that exist in the file.
+ **/
+ protected function _parseClassNamesInFile($fileName, $getParents = false) {
+ $foundClasses = array();
+ $fileContent = file_get_contents($fileName);
+ $pattern = '/^\s*(?:abstract\s*)?(?:class|interface)\s+([^\s\{\:]+)\s*[^\{]*\{/mi';
+ if ($getParents) {
+ $pattern = '/^\s*(?:abstract\s*)?(?:class|interface)\s+[^\s]*\s*(?:extends\s+([^\s\{\:]*))?(?:\s*implements\s*([^\s\{]*))?[^\{]*/mi';
+ }
+ preg_match_all($pattern, $fileContent, $matches, PREG_SET_ORDER);
+
+ foreach ($matches as $className) {
+ if (!empty($className[1])) {
+ $foundClasses[] = $className[1];
+ }
+ if (isset($className[2])) {
+ $foundClasses = array_merge($foundClasses, explode(', ', $className[2]));
+ }
+ }
+ return $foundClasses;
+ }
+/**
+ * Retrieves global function names defined in a file.
+ * Unlike the class parser which can cheat with regex.
+ * Functions are a bit trickier.
+ *
+ * @return array
+ **/
+ protected function _parseFunctionNamesInFile($fileName) {
+ $foundFuncs = array();
+ $fileContent = file_get_contents($fileName);
+ $funcNames = implode('|', $this->_definedFunctions);
+ preg_match_all('/^\s*function\s*(' . $funcNames . ')[\s|\(]+/mi', $fileContent, $matches, PREG_SET_ORDER);
+ foreach ($matches as $function) {
+ $foundFuncs[] = $function[1];
+ }
+ return $foundFuncs;
+ }
+/**
+ * Parses the file for any parent classes required by the file being loaded.
+ * Attempts to load those files.
+ *
+ * @param string $filePath absolute filepath to look in
+ * @param array $options Options to use.
+ * @return void
+ **/
+ protected function _resolveDependancies($filePath, $options = array()) {
+ $defaults = array('useIndex' => false);
+ $options = array_merge($defaults, $options);
+
+ $parentClasses = $this->_parseClassNamesInFile($filePath, true);
+ $classNamesInFile = $this->_parseClassNamesInFile($filePath);
+ $solved = false;
+ $loadClasses = array();
+ while ($solved === false && !empty($parentClasses)) {
+ $neededParent = array_pop($parentClasses);
+
+ $exists = (
+ class_exists($neededParent, false) ||
+ interface_exists($neededParent, false) ||
+ in_array($neededParent, $classNamesInFile)
+ );
+ if (!$exists && $options['useIndex']) {
+ $ApiClass = ClassRegistry::init('ApiGenerator.ApiClass');
+ $result = $ApiClass->findByName($neededParent);
+ if (!empty($result['ApiClass']['file_name'])) {
+ $this->classMap[$neededParent] = $result['ApiClass']['file_name'];
+ }
+ }
+
+ if (!$exists && isset($this->classMap[$neededParent])) {
+ array_unshift($loadClasses, $neededParent);
+ $newNeeds = $this->_parseClassNamesInFile($this->classMap[$neededParent], true);
+ $parentClasses = array_unique(array_merge($parentClasses, $newNeeds));
+ } elseif (!$exists) {
+ throw new MissingClassException($neededParent . ' could not be found using mappings, please add it to the mappings.');
+ }
+ if (empty($parentClasses)) {
+ $solved = true;
+ }
+ }
+ foreach ($loadClasses as $className) {
+ App::import('File', $className, true, array(), $this->classMap[$className]);
+ }
+ }
+/**
+ * Attempts to solve class dependancies by importing base CakePHP classes
+ *
+ * @return void
+ **/
+ protected function _importCakeBaseClasses($filePath) {
+ $baseClass = array();
+ if (strpos($filePath, 'controllers') !== false) {
+ $baseClass['Controller'] = 'App';
+ }
+ if (strpos($filePath, 'models') !== false) {
+ $baseClass['Model'] = 'App';
+ }
+ if (strpos($filePath, 'helpers') !== false) {
+ $baseClass['Helper'] = 'App';
+ }
+ if (strpos($filePath, 'view') !== false) {
+ $baseClass['View'] = 'View';
+ }
+ if (strpos($filePath, 'socket') !== false) {
+ $baseClass['Core'] = 'Socket';
+ }
+ foreach ($baseClass as $type => $class) {
+ App::import($type, $class);
+ }
+ }
+/**
+ * Get the Exclusions lists.
+ *
+ * @return array of stuff not allowed in views.
+ **/
+ public function getExclusions() {
+ $return = array();
+ $excludeProps = array('excludeMethods', 'excludeProperties');
+ foreach ($excludeProps as $var) {
+ $return[$var] = $this->{$var};
+ }
+ return $return;
+ }
+/**
+ * Initialize the configuration for ApiFile.
+ *
+ * @return void
+ **/
+ protected function _initConfig() {
+ $config = $this->ApiConfig->read();
+ if (isset($config['exclude']) && is_array($config['exclude'])) {
+ foreach ($config['exclude'] as $type => $exclusion) {
+ $var = 'exclude' . Inflector::camelize($type);
+ $this->{$var} = explode(', ', $exclusion);
+ }
+ }
+ if (isset($config['file']['extensions'])) {
+ $this->allowedExtensions = explode(', ', $config['file']['extensions']);
+ }
+ if (isset($config['file']['regex'])) {
+ $this->fileRegExp = $config['file']['regex'];
+ }
+ $varMap = array('dependencies' => 'dependencyMap', 'mappings' => 'classMap');
+ foreach ($varMap as $key => $var) {
+ if (isset($config[$key]) && is_array($config[$key])) {
+ foreach ($config[$key] as $name => $value) {
+ if ($var == 'classMap') {
+ $this->{$var}[$name] = $value;
+ } else {
+ $this->{$var}[$name] = explode(', ', $value);
+ }
+ }
+ }
+ }
+ }
+}
+
+class MissingClassException extends Exception { }
+?>
\ No newline at end of file
diff --git a/tests/cases/helpers/api_doc.test.php b/tests/cases/helpers/api_doc.test.php
new file mode 100644
index 0000000..dd48b8c
--- /dev/null
+++ b/tests/cases/helpers/api_doc.test.php
@@ -0,0 +1,107 @@
+<?php
+/* SVN FILE: $Id$ */
+/**
+ * Api Doc Helper Test
+ *
+ *
+ *
+ * PHP versions 4 and 5
+ *
+ * CakePHP : Rapid Development Framework <http://www.cakephp.org/>
+ * Copyright 2006-2008, Cake Software Foundation, Inc.
+ * 1785 E. Sahara Avenue, Suite 490-204
+ * Las Vegas, Nevada 89104
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright 2006-2008, Cake Software Foundation, Inc.
+ * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project
+ * @package cake
+ * @subpackage cake.cake.libs.
+ * @since CakePHP v 1.2.0.4487
+ * @version
+ * @modifiedby
+ * @lastmodified
+ * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
+App::import('Core', array('View', 'Controller'));
+App::import('Helper', array('ApiGenerator.ApiDoc', 'Html'));
+
+/**
+* ApiDocHelper test case
+*/
+class ApiDocHelperTestCase extends CakeTestCase {
+/**
+ * startTest
+ *
+ * @return void
+ **/
+ function startTest() {
+ $controller = new Controller();
+ $view = new View($controller);
+ $view->set('basePath', '/cake/tests/');
+ $this->ApiDoc = new ApiDocHelper();
+ $this->ApiDoc->Html = new HtmlHelper();
+ }
+/**
+ * test inBasePath
+ *
+ * @return void
+ **/
+ function testInBasePath() {
+ $this->assertFalse($this->ApiDoc->inBasePath('/foo/bar/path'));
+ $this->assertTrue($this->ApiDoc->inBasePath('/cake/tests/my/path'));
+ }
+/**
+ * undocumented function
+ *
+ * @return void
+ **/
+ function testTrimFileName() {
+ $result = $this->ApiDoc->trimFileName('/cake/tests/my/path');
+ $this->assertEqual($result, 'my/path');
+
+ $this->ApiDoc->setBasePath('/Users/markstory/Sites/cake_debug_kit/');
+ $result = $this->ApiDoc->trimFileName('/Users/markstory/Sites/cake_debug_kit/controllers/posts_controller.php');
+ $this->assertEqual($result, 'controllers/posts_controller.php');
+ }
+/**
+ * testFileLink
+ *
+ * Test file link / no link based on base path of file.
+ *
+ * @return void
+ **/
+ function testFileLink() {
+ $result = $this->ApiDoc->fileLink('/foo/bar');
+ $this->assertEqual($result, '/foo/bar');
+
+ $result = $this->ApiDoc->fileLink('/cake/tests/my/path');
+ $expected = array(
+ 'a' => array('href' => '/api_generator/view_file/my/path'),
+ 'my/path',
+ '/a'
+ );
+ $this->assertTags($result, $expected);
+
+ $result = $this->ApiDoc->fileLink('/cake/tests/my/path', array('controller' => 'foo', 'action' => 'bar'));
+ $expected = array(
+ 'a' => array('href' => '/api_generator/foo/bar/my/path'),
+ 'my/path',
+ '/a'
+ );
+ $this->assertTags($result, $expected);
+
+ }
+/**
+ * endTest
+ *
+ * @return void
+ **/
+ function endTest() {
+ ClassRegistry::flush();
+ unset($this->ApiDoc);
+ }
+}
diff --git a/tests/cases/models/api_class.test.php b/tests/cases/models/api_class.test.php
new file mode 100644
index 0000000..86cd4a2
--- /dev/null
+++ b/tests/cases/models/api_class.test.php
@@ -0,0 +1,168 @@
+<?php
+/* SVN FILE: $Id$ */
+/**
+ * ApiClass test case
+ *
+ *
+ *
+ * PHP versions 4 and 5
+ *
+ * CakePHP : Rapid Development Framework <http://www.cakephp.org/>
+ * Copyright 2006-2008, Cake Software Foundation, Inc.
+ * 1785 E. Sahara Avenue, Suite 490-204
+ * Las Vegas, Nevada 89104
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright 2006-2008, Cake Software Foundation, Inc.
+ * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project
+ * @package cake.api_generator
+ * @subpackage cake.api_generator.tests.
+ * @version
+ * @modifiedby
+ * @lastmodified
+ * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
+App::import('Model', 'ApiGenerator.ApiClass');
+App::import('Vendor', 'ApiGenerator.ClassDocumentor');
+/**
+ * ApiClassSampleClass doc block
+ *
+ * @package default
+ */
+class ApiClassSampleClass {
+/**
+ * foo property
+ *
+ * @var string
+ **/
+ public $foo = '';
+/**
+ * Test Function in Sample class
+ *
+ * @param string $one First parameter
+ * @param string $two Second parameter
+ * @return boolean
+ **/
+ public function testFunct($one, $two) {
+
+ }
+/**
+ * non-extended method
+ *
+ * @return boolean
+ **/
+ public function extended() {
+
+ }
+}
+/**
+ * ApiClassSampleClass doc block
+ *
+ * @package default
+ */
+class ApiClassSampleClassChild extends ApiClassSampleClass {
+/**
+ * onlyMe
+ *
+ * @var string
+ **/
+ public $onlyMe;
+/**
+ * primary function
+ *
+ * @return void
+ **/
+ public function primary() {
+
+ }
+/**
+ * extended-method this time
+ *
+ * @return void
+ **/
+ public function extended() {
+
+ }
+}
+
+/**
+ * ApiClassTestCase
+ *
+ * @package api_generator.tests
+ **/
+class ApiFileTestCase extends CakeTestCase {
+/**
+ * undocumented class variable
+ *
+ * @var string
+ **/
+ var $fixtures = array('plugin.api_generator.api_class');
+/**
+ * startTest
+ *
+ * @return void
+ **/
+ function startTest() {
+ $this->_path = APP . 'plugins' . DS . 'api_generator';
+ Configure::write('ApiGenerator.filePath', $this->_path);
+ $this->ApiClass = ClassRegistry::init('ApiClass');
+ }
+/**
+ * endTest
+ *
+ * @return void
+ **/
+ function endTest() {
+ unset($this->ApiClass);
+ }
+/**
+ * Test Saving of the class docs to the db.
+ *
+ * @return void
+ **/
+ function testSaveClassDocs() {
+ $docs = new ClassDocumentor('ApiClassSampleClass');
+
+ $result = $this->ApiClass->saveClassDocs($docs);
+ $this->assertTrue($result);
+
+ $result = $this->ApiClass->read();
+ $now = date('Y-m-d H:i:s');
+ $expected = array(
+ 'ApiClass' => array(
+ 'id' => $this->ApiClass->id,
+ 'name' => 'ApiClassSampleClass',
+ 'slug' => 'api-class-sample-class',
+ 'file_name' => __FILE__,
+ 'search_index' => 'apiclasssampleclass doc block foo property test function in sample class first parameter second parameter non-extended method',
+
+ 'flags' => 0,
+ 'created' => $now,
+ 'modified' => $now,
+ )
+ );
+ $this->assertEqual($result, $expected);
+
+ $docs = new ClassDocumentor('ApiClassSampleClassChild');
+ $result = $this->ApiClass->saveClassDocs($docs);
+ $this->assertTrue($result);
+ $result = $this->ApiClass->read();
+ $now = date('Y-m-d H:i:s');
+ $expected = array(
+ 'ApiClass' => array(
+ 'id' => $this->ApiClass->id,
+ 'name' => 'ApiClassSampleClassChild',
+ 'slug' => 'api-class-sample-class-child',
+ 'file_name' => __FILE__,
+ 'search_index' => 'apiclasssampleclass doc block onlyme primary function extended-method this time',
+ 'flags' => 0,
+ 'created' => $now,
+ 'modified' => $now,
+ )
+ );
+ $this->assertEqual($result, $expected);
+ }
+}
\ No newline at end of file
diff --git a/tests/cases/models/api_config.test.php b/tests/cases/models/api_config.test.php
new file mode 100644
index 0000000..69a8ef7
--- /dev/null
+++ b/tests/cases/models/api_config.test.php
@@ -0,0 +1,187 @@
+<?php
+/**
+ * ApiConfig test case
+ *
+ *
+ *
+ * PHP versions 4 and 5
+ *
+ * CakePHP : Rapid Development Framework <http://www.cakephp.org/>
+ * Copyright 2006-2008, Cake Software Foundation, Inc.
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright Copyright 2006-2008, Cake Software Foundation, Inc.
+ * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project
+ * @package cake.api_generator
+ * @subpackage cake.api_generator.tests.
+ * @version
+ * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
+App::import('Model', 'ApiGenerator.ApiConfig');
+/**
+ * ApiConfigTestCase
+ *
+ *
+ * @package api_generator.tests
+ **/
+/* SAMPLE
+ [paths]
+ /home/cake/plugins/api_generator = true
+ /home/cake/plugins/api_generator_extensions = true
+
+ [exclude]
+ properties = private
+ methods = private
+ classes = MyClass, MyOtherClass
+
+ [dependencies]
+ MyClass = MyBaseClass, MyInterface
+ MyOtherClass = MyClass
+
+ [mappings]
+ MyClass = My_Funky_File.php
+ MyOtherClass = My_Other_File.php
+*/
+
+class ApiConfigTestCase extends CakeTestCase {
+/**
+ * startTest
+ *
+ * @return void
+ **/
+ function startTest() {
+ $this->ApiConfig = ClassRegistry::init('ApiGenerator.ApiConfig');
+ $this->ApiConfig->path = TMP . 'api_config.ini';
+ }
+/**
+ * endTest
+ *
+ * @return void
+ **/
+ function endTest() {
+ $Cleanup = new File($this->ApiConfig->path);
+ $Cleanup->delete();
+ unset($this->ApiConfig);
+ }
+
+ function testSave() {
+ $data = array(
+ '[paths]',
+ '/home/cake/plugins/api_generator = true',
+ '[exclude]',
+ 'properties = private',
+ 'method = private',
+ '[dependencies]',
+ 'MyClass = MyBaseClass, MyInterface',
+ '[mappings]',
+ 'MyClass = My_Funky_File.php',
+ );
+ $this->assertTrue($this->ApiConfig->save($data));
+ $this->assertTrue($this->ApiConfig->save(TMP . 'api_config.ini', $data));
+
+ $data = "[paths]\n\n/home/cake/plugins/api_generator = true\n\n";
+ $this->assertTrue($this->ApiConfig->save($data));
+ $this->assertTrue($this->ApiConfig->save(TMP . 'api_config.ini', $data));
+
+ $data = array(
+ 'paths' => array(
+ '/home/cake/plugins/api_generator' => true
+ ),
+ 'exclude' => array(
+ 'properties' => 'private',
+ 'methods' => 'private'
+ ),
+ 'file' => array(
+ 'extensions' => 'php, ctp',
+ 'regex' => '[a-z]'
+ ),
+ );
+ $this->assertTrue($this->ApiConfig->save($data));
+ $this->assertTrue($this->ApiConfig->save(TMP . 'api_config.ini', $data));
+ }
+
+ function testRead() {
+ $result = $this->ApiConfig->read();
+ $this->assertEqual($result, array());
+
+ $data = "[paths]\n\n/home/cake/plugins/api_generator = true\n\n";
+ $this->assertTrue($this->ApiConfig->save($data));
+
+ $result = $this->ApiConfig->read();
+ $this->assertEqual($result, array(
+ 'paths' => array(
+ '/home/cake/plugins/api_generator' => true
+ )
+ ));
+
+ $result = $this->ApiConfig->read($data);
+ $this->assertEqual($result, array(
+ 'paths' => array(
+ '/home/cake/plugins/api_generator' => true
+ )
+ ));
+
+ $data = array(
+ '[paths]',
+ '/home/cake/plugins/api_generator = true',
+ );
+
+ $result = $this->ApiConfig->read();
+ $this->assertEqual($result, array(
+ 'paths' => array(
+ '/home/cake/plugins/api_generator' => true
+ )
+ ));
+
+
+ $data = "[paths]\n\n/Home/Cake/Plugins/Api_generator = true\n\n";
+ $result = $this->ApiConfig->read($data);
+ $this->assertEqual($result, array(
+ 'paths' => array(
+ '/Home/Cake/Plugins/Api_generator' => true
+ )
+ ));
+ }
+
+ function testToString() {
+ $data = array(
+ 'paths' => array(
+ '/home/cake/plugins/api_generator' => true
+ ),
+ 'exclude' => array(
+ 'properties' => 'private',
+ 'methods' => 'private'
+ ),
+ 'file' => array(
+ 'extensions' => 'php, ctp',
+ 'regex' => '[a-z]'
+ ),
+ );
+ $result = $this->ApiConfig->toString($data);
+ $expected = "[paths]\n/home/cake/plugins/api_generator = true\n";
+ $expected .= "[exclude]\nproperties = private\nmethods = private\n";
+ $expected .= "[file]\nextensions = php, ctp\nregex = [a-z]";
+ $this->assertEqual($result, $expected);
+ }
+
+ function testSaveAndRead() {
+ $data = array(
+ 'paths' => array(
+ '/home/cake/plugins/api_generator' => true
+ ),
+ 'exclude' => array(
+ 'properties' => 'private',
+ 'methods' => 'private'
+ ),
+ 'file' => array(
+ 'extensions' => 'php, ctp',
+ 'regex' => '[a-z]'
+ ),
+ );
+ $this->assertTrue($this->ApiConfig->save($data));
+ $result = $this->ApiConfig->read();
+ $this->assertEqual($result, $data);
+ }
+}
\ No newline at end of file
diff --git a/tests/cases/models/api_file.test.php b/tests/cases/models/api_file.test.php
new file mode 100644
index 0000000..871862e
--- /dev/null
+++ b/tests/cases/models/api_file.test.php
@@ -0,0 +1,235 @@
+<?php
+/* SVN FILE: $Id$ */
+/**
+ * ApiFile test case
+ *
+ *
+ *
+ * PHP versions 4 and 5
+ *
+ * CakePHP : Rapid Development Framework <http://www.cakephp.org/>
+ * Copyright 2006-2008, Cake Software Foundation, Inc.
+ * 1785 E. Sahara Avenue, Suite 490-204
+ * Las Vegas, Nevada 89104
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright 2006-2008, Cake Software Foundation, Inc.
+ * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project
+ * @package cake.api_generator
+ * @subpackage cake.api_generator.tests.
+ * @version
+ * @modifiedby
+ * @lastmodified
+ * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
+App::import('Model', 'ApiGenerator.ApiFile');
+
+/**
+ * ApiFileTestCase
+ *
+ * @package api_generator.tests
+ **/
+class ApiFileTestCase extends CakeTestCase {
+/**
+ * startTest
+ *
+ * @return void
+ **/
+ function startTest() {
+ $this->_path = APP . 'plugins' . DS . 'api_generator';
+ Configure::write('ApiGenerator.filePath', $this->_path);
+ $this->ApiFile = new ApiFile();
+ $this->_testAppPath = dirname(dirname(dirname(__FILE__))) . DS . 'test_app' . DS;
+ }
+/**
+ * endTest
+ *
+ * @return void
+ **/
+ function endTest() {
+ unset($this->ApiFile);
+ }
+/**
+ * test extractor loading
+ *
+ * @return void
+ **/
+ function testLoadExtractor() {
+ $this->ApiFile->loadExtractor('class', 'ApiFile');
+ $result = $this->ApiFile->getExtractor();
+ $this->assertTrue($result instanceof ReflectionClass);
+ $this->assertEqual($result->name, 'ApiFile');
+ }
+/**
+ * testReading files from a folder
+ * - test ignored files
+ * - test ignored folders
+ * - test extensions
+ * - test reading evil paths
+ *
+ * @return void
+ **/
+ function testRead() {
+ $result = $this->ApiFile->read($this->_path . DS . 'models');
+ $this->assertTrue(empty($result[0]));
+ $expected = array('api_config.php', 'api_class.php', 'api_file.php');
+ $this->assertEqual(sort($result[1]), sort($expected));
+
+ $this->ApiFile->excludeFiles[] = 'api_class.php';
+ $result = $this->ApiFile->read($this->_path . DS . 'models');
+ $expected = array('api_config.php', 'api_file.php');
+ $this->assertEqual($result[1], $expected);
+
+ $this->ApiFile->excludeDirectories = array('models', 'controllers');
+ $result = $this->ApiFile->read($this->_path);
+ $this->assertFalse(in_array('controllers', $result[0]));
+ $this->assertFalse(in_array('models', $result[0]));
+
+ $this->ApiFile->allowedExtensions = array('css');
+ $this->ApiFile->excludeDirectories = array('models');
+ $result = $this->ApiFile->read($this->_path);
+ $this->assertTrue(empty($result[1]), 'file with not allowed extension found. %s');
+ $this->assertFalse(in_array('models', $result[0]), 'file in ignored folder found %s');
+
+ $this->ApiFile->allowedExtensions = array('php');
+ $this->ApiFile->excludeDirectories = array();
+ $result = $this->ApiFile->read($this->_path . '../../../../../../');
+ $this->assertEqual($result, array(array(), array()), 'Evil file path read.');
+ }
+/**
+ * test file list generation
+ * - test folder exclusion
+ * - test extension permission
+ * - test file regex
+ *
+ * @return void
+ **/
+ function testGetFileList() {
+ $this->ApiFile->excludeDirectories = array('config', 'webroot');
+ $result = $this->ApiFile->fileList(APP);
+ $core = CONFIGS . 'core.php';
+ $vendorJs = WWW_ROOT . 'js' . DS . 'vendors.php';
+ $this->assertFalse(in_array($core, $result));
+ $this->assertFalse(in_array($vendorJs, $result));
+
+ $this->ApiFile->excludeDirectories = array();
+ $this->ApiFile->allowedExtensions = array('css');
+ $result = $this->ApiFile->fileList(APP);
+ $core = CONFIGS . 'core.php';
+ $vendorJs = WWW_ROOT . 'js' . DS . 'vendors.php';
+ $this->assertFalse(in_array($core, $result));
+ $this->assertFalse(in_array($vendorJs, $result));
+
+ $this->ApiFile->excludeDirectories = array();
+ $this->ApiFile->allowedExtensions = array('css');
+ $result = $this->ApiFile->fileList(APP);
+ $core = CONFIGS . 'core.php';
+ $vendorJs = WWW_ROOT . 'js' . DS . 'vendors.php';
+ $this->assertFalse(in_array($core, $result));
+ $this->assertFalse(in_array($vendorJs, $result));
+
+ $this->ApiFile->excludeDirectories = array();
+ $this->ApiFile->allowedExtensions = array('php');
+ $this->ApiFile->excludeFiles = array('index.php');
+ $this->ApiFile->fileRegExp = '[a-z_0-9]+';
+ $result = $this->ApiFile->fileList(APP);
+ $core = CONFIGS . 'core.php';
+ $index = APP . 'index.php';
+ $this->assertTrue(in_array($core, $result));
+ $this->assertFalse(in_array($index, $result));
+ }
+/**
+ * test Processed docs retrieval
+ *
+ * @return void
+ **/
+ function testGetDocs() {
+ $result = $this->ApiFile->getDocs();
+ $this->assertTrue(empty($result));
+
+ $this->ApiFile->loadExtractor('class', 'ApiFile');
+ $result = $this->ApiFile->getDocs();
+ $this->assertTrue($result instanceof ClassDocumentor);
+ $this->assertTrue(isset($result->classInfo));
+ }
+/**
+ * test loadFile() on a file that has already been included once
+ *
+ * @return void
+ **/
+ function testLoadFileOnAlreadyIncludedFile() {
+ $result = $this->ApiFile->loadFile(__FILE__);
+ $this->assertTrue(isset($result['class']));
+ $this->assertTrue(isset($result['function']));
+ $this->assertTrue($result['class'][__CLASS__] instanceof ClassDocumentor);
+
+ $result = $this->ApiFile->loadFile(APP . 'plugins' . DS . 'api_generator' . DS . 'models' . DS . 'api_class.php');
+ $this->assertTrue(isset($result['class']));
+ $this->assertTrue(isset($result['function']));
+ $this->assertTrue($result['class']['ApiClass'] instanceof ClassDocumentor);
+ }
+/**
+ * test loading an evil path that leads to a secured location
+ *
+ * @return void
+ **/
+ function testLoadingEvilPath() {
+ $result = $this->ApiFile->loadFile($this->_path . '../../../config/database.php');
+ $this->assertEqual($result, array('class' => array(), 'function' => array()));
+ }
+/**
+ * test loadFile() with a dependancy map.
+ *
+ * @return void
+ **/
+ function testLoadFileWithDependancyMap() {
+ $this->assertNoErrors();
+ $this->ApiFile->classMap['MappedRandomFile'] = $this->_testAppPath . 'mapped_file.php';
+ $this->ApiFile->classMap['SillyTestInterface'] = $this->_testAppPath . 'silly_interface_file.php';
+ $this->ApiFile->loadFile($this->_testAppPath . 'test_file.php');
+ }
+/**
+ * test loading a file whose classes have no parent classes
+ *
+ * @return void
+ **/
+ function testLoadingFileWithNoParentClass() {
+ $result = $this->ApiFile->loadFile($this->_testAppPath . 'no_parents.php');
+ $this->assertEqual(count($result['class']), 2);
+ }
+/**
+ * test loading files that contain both parent and child classes.
+ *
+ * @return void
+ **/
+ function testLoadingFileParentClassInSameFile() {
+ $result = $this->ApiFile->loadFile($this->_testAppPath . 'inline_parents.php');
+ $this->assertEqual(count($result['class']), 3);
+ }
+/**
+ * test loading files that have sloppy code conventions
+ *
+ * @return void
+ **/
+ function testLoadingSloppyFiles() {
+ $result = $this->ApiFile->loadFile($this->_testAppPath . 'sloppy_code.php');
+ $this->assertEqual(count($result['class']), 5);
+ }
+/**
+ * Test that ApiFile Throws down when needed.
+ *
+ * @return void
+ **/
+ function testExceptionThrowing() {
+ $this->ApiFile->classMap = array();
+ try {
+ $this->ApiFile->loadFile($this->_testAppPath . 'throw_down.php');
+ $this->assertFalse(true, 'No exception was thrown, when loading a garbage file');
+ } catch (MissingClassException $e) {
+ $this->assertTrue(true);
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/cases/models/api_generator_app_model.test.php b/tests/cases/models/api_generator_app_model.test.php
new file mode 100644
index 0000000..bdb70a3
--- /dev/null
+++ b/tests/cases/models/api_generator_app_model.test.php
@@ -0,0 +1,81 @@
+<?php
+/* SVN FILE: $Id$ */
+/**
+ *
+ *
+ *
+ *
+ * PHP versions 4 and 5
+ *
+ * CakePHP : Rapid Development Framework <http://www.cakephp.org/>
+ * Copyright 2006-2008, Cake Software Foundation, Inc.
+ * 1785 E. Sahara Avenue, Suite 490-204
+ * Las Vegas, Nevada 89104
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright 2006-2008, Cake Software Foundation, Inc.
+ * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project
+ * @package cake
+ * @subpackage cake.cake.libs.
+ * @since CakePHP v 1.2.0.4487
+ * @version
+ * @modifiedby
+ * @lastmodified
+ * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
+App::import('Model', 'ApiGenerator.AppModel');
+
+class ApiGeneratorAppTestModel extends ApiGeneratorAppModel {
+ public $name = 'ApiGeneratorAppTestModel';
+ public $useTable = false;
+
+}
+
+class ApiGeneratorAppModelTestCase extends CakeTestCase {
+/**
+ * startTest
+ *
+ * @return void
+ **/
+ function startTest() {
+ $this->Model = ClassRegistry::init('ApiGeneratorAppTestModel');
+ }
+/**
+ * test slugPath
+ *
+ * @return void
+ **/
+ function testSlugPath() {
+ Configure::write('ApiGenerator.filePath', '/this/is/');
+ $result = $this->Model->slugPath('/this/is/a/path/to_my/file.php');
+ $expected = 'a-path-to_my-file-php';
+ $this->assertEqual($result, $expected);
+
+ Configure::write('ApiGenerator.filePath', '/this/is/');
+ $result = $this->Model->slugPath('/this/is/a/path/to_my/f i le.php');
+ $expected = 'a-path-to_my-f-i-le-php';
+ $this->assertEqual($result, $expected);
+
+ Configure::write('ApiGenerator.filePath', 'C:\www');
+ $result = $this->Model->slugPath('C:\www\my Path\is Very Windows\file.php');
+ $expected = 'my-path-is-very-windows-file-php';
+ $this->assertEqual($result, $expected);
+
+ Configure::write('ApiGenerator.filePath', 'C:\www');
+ $result = $this->Model->slugPath('C:\www\my Path\is Very Windows\file.php', false);
+ $expected = 'c-www-my-path-is-very-windows-file-php';
+ $this->assertEqual($result, $expected);
+ }
+/**
+ * endTest
+ *
+ * @return void
+ **/
+ function endTest() {
+ unset($this->Model);
+ }
+}
+?>
\ No newline at end of file
diff --git a/tests/cases/vendors/class_documentor.test.php b/tests/cases/vendors/class_documentor.test.php
new file mode 100644
index 0000000..c71f770
--- /dev/null
+++ b/tests/cases/vendors/class_documentor.test.php
@@ -0,0 +1,246 @@
+<?php
+App::import('Vendor', 'ApiGenerator.ClassDocumentor');
+/**
+ * SimpleDocumentorSubjectClass
+ *
+ * A simple class to test ClassInfo introspection
+ *
+ * @package this is my package
+ * @another-tag long value
+ */
+class SimpleDocumentorSubjectClass extends StdClass implements Countable {
+
+/**
+ * This var is protected
+ *
+ * @var string
+ **/
+ protected $_protectedVar;
+/**
+ * This var is public
+ *
+ * @var string
+ **/
+ public $publicVar = 'value';
+/**
+ * This var is public static
+ *
+ * @var string
+ **/
+ public static $publicStatic;
+/**
+ * count
+ *
+ * Implementation of Countable interface
+ *
+ * @access public
+ * @return integer
+ */
+ public function count() { }
+/**
+ * something
+ *
+ * does something
+ *
+ * @param string $arg1 First arg
+ * @param string $arg2 Second arg
+ * @access public
+ * @return integer
+ */
+ protected function something($arg1, $arg2 = 'file') { }
+/**
+ * goGo
+ *
+ * does lots of cool things
+ * @param string $param a parameter
+ * @return void
+ **/
+ public static function goGo($param) { }
+}
+
+/**
+ * Test DocumentExtractor
+ *
+ * gives access to protected methods
+ *
+ * @package cake.api_documentor.tests
+ */
+class TestClassDocumentor extends ClassDocumentor {
+
+}
+class DocumentExtractorTestCase extends CakeTestCase {
+/**
+ * test the ClassInfo introspection
+ *
+ * @return void
+ **/
+ function testGetClassInfo() {
+ $Docs = new TestClassDocumentor('SimpleDocumentorSubjectClass');
+ $result = $Docs->getClassInfo();
+ $expected = array (
+ 'name' => 'SimpleDocumentorSubjectClass',
+ 'fileName' => __FILE__,
+ 'classDescription' => 'class SimpleDocumentorSubjectClass extends stdClass implements Countable ',
+ 'comment' => array (
+ 'description' => "SimpleDocumentorSubjectClass\n\nA simple class to test ClassInfo introspection",
+ 'tags' => array (
+ 'package' => 'this is my package',
+ 'another-tag' => 'long value'
+ ),
+ ),
+ 'parents' => array('stdClass'),
+ 'interfaces' => array('Countable')
+ );
+ $this->assertEqual($result, $expected);
+ $this->assertEqual($Docs->classInfo, $expected);
+ }
+
+/**
+ * Test getting properties and their info
+ *
+ * @return void
+ **/
+ function testGetProperties() {
+ $Docs = new TestClassDocumentor('SimpleDocumentorSubjectClass');
+ $result = $Docs->getProperties();
+ $expected = array(
+ array(
+ 'name' => '_protectedVar',
+ 'declaredInClass' => 'SimpleDocumentorSubjectClass',
+ 'comment' => array(
+ 'description' => 'This var is protected',
+ 'tags' => array(
+ 'var' => 'string'
+ )
+ ),
+ 'access' => 'protected'
+ ),
+ array(
+ 'name' => 'publicVar',
+ 'declaredInClass' => 'SimpleDocumentorSubjectClass',
+ 'comment' => array(
+ 'description' => 'This var is public',
+ 'tags' => array(
+ 'var' => 'string'
+ )
+ ),
+ 'access' => 'public'
+ ),
+ array(
+ 'name' => 'publicStatic',
+ 'declaredInClass' => 'SimpleDocumentorSubjectClass',
+ 'comment' => array(
+ 'description' => 'This var is public static',
+ 'tags' => array(
+ 'var' => 'string'
+ )
+ ),
+ 'access' => 'public static'
+ )
+ );
+ $this->assertEqual($result, $expected);
+ $this->assertEqual($Docs->properties, $expected);
+ }
+/**
+ * test getting of methods
+ *
+ * @return void
+ **/
+ function testGetMethods() {
+ $Docs = new TestClassDocumentor('SimpleDocumentorSubjectClass');
+ $result = $Docs->getMethods();
+ $expected = array(
+ array(
+ 'name' => 'count',
+ 'comment' => array(
+ 'description' => "count\n\nImplementation of Countable interface",
+ 'tags' => array(
+ 'access' => 'public',
+ 'return' => 'integer'
+ )
+ ),
+ 'startLine' => '39',
+ 'declaredInClass' => 'SimpleDocumentorSubjectClass',
+ 'declaredInFile' => __FILE__,
+ 'args' => array( ),
+ 'access' => 'public',
+ 'signature' => 'count( )',
+ 'isStatic' => false,
+ ),
+ array(
+ 'name' => 'something',
+ 'comment' => array(
+ 'description' => "something\n\ndoes something",
+ 'tags' => array(
+ 'access' => 'public',
+ 'return' => 'integer'
+ )
+ ),
+ 'args' => array(
+ 'arg1' => array(
+ 'optional' => false,
+ 'hasDefault' => false,
+ 'default' => NULL,
+ 'position' => 0,
+ 'type' => 'string',
+ 'comment' => 'First arg'
+ ),
+ 'arg2' => array(
+ 'optional' => true,
+ 'default' => 'file',
+ 'hasDefault' => true,
+ 'position' => 1,
+ 'type' => 'string',
+ 'comment' => 'Second arg'
+ )
+ ),
+ 'startLine' => '50',
+ 'declaredInClass' => 'SimpleDocumentorSubjectClass',
+ 'declaredInFile' => __FILE__,
+ 'access' => 'protected',
+ 'signature' => 'something( $arg1, $arg2 = \'file\' )',
+ 'isStatic' => false,
+ ),
+ array(
+ 'name' => 'goGo',
+ 'comment' => array(
+ 'description' => "goGo\n\ndoes lots of cool things",
+ 'tags' => array(
+ 'return' => 'void'
+ )
+ ),
+ 'args' => array(
+ 'param' => array(
+ 'optional' => false,
+ 'default' => NULL,
+ 'position' => 0,
+ 'type' => 'string',
+ 'hasDefault' => false,
+ 'comment' => 'a parameter'
+ )
+ ),
+ 'startLine' => '58',
+ 'declaredInClass' => 'SimpleDocumentorSubjectClass',
+ 'declaredInFile' => __FILE__,
+ 'access' => 'public',
+ 'signature' => 'goGo( $param )',
+ 'isStatic' => true,
+ )
+ );
+ $this->assertEqual($result, $expected);
+ $this->assertEqual($Docs->methods, $expected);
+ }
+/**
+ * test getAll()
+ *
+ * @return void
+ **/
+ function testGetAll() {
+ $Docs = new TestClassDocumentor('SimpleDocumentorSubjectClass');
+ $Docs->getAll();
+ $this->assertFalse(empty($Docs->classInfo));
+ $this->assertFalse(empty($Docs->properties));
+ $this->assertFalse(empty($Docs->methods));
+ }
+}
+?>
\ No newline at end of file
diff --git a/tests/cases/vendors/function_documentor.test.php b/tests/cases/vendors/function_documentor.test.php
new file mode 100644
index 0000000..05be520
--- /dev/null
+++ b/tests/cases/vendors/function_documentor.test.php
@@ -0,0 +1,93 @@
+<?php
+App::import('Vendor', 'ApiGenerator.FunctionDocumentor');
+/**
+ * my_random_test_function
+ *
+ * @param string $param this is a param
+ * @param string $one this is one
+ * @param string $two this is two
+ * @return void
+ */
+function my_random_test_function($param, $one = 'foo', $two = 'param') {
+
+}
+/**
+ * FunctionDocumentor Test Case
+ *
+ * @package default
+ * @author Mark Story
+ */
+class FunctionDocumentorTestCase extends CakeTestCase {
+/**
+ * testInfo Getting
+ *
+ * @return void
+ **/
+ function testGetInfo() {
+ $Docs = new FunctionDocumentor('my_random_test_function');
+ $result = $Docs->getInfo();
+ $expected = array(
+ 'name' => 'my_random_test_function',
+ 'declaredInFile' => __FILE__,
+ 'startLine' => 11,
+ 'endLine' => 13,
+ 'comment' => array(
+ 'description' => 'my_random_test_function',
+ 'tags' => array(
+ 'return' => 'void',
+ 'param' => array(
+ 'param' => array(
+ 'type' => 'string',
+ 'description' => 'this is a param',
+ ),
+ 'one' => array(
+ 'type' => 'string',
+ 'description' => 'this is one',
+ ),
+ 'two' => array(
+ 'type' => 'string',
+ 'description' => 'this is two',
+ )
+ )
+ )
+ ),
+ 'internal' => false,
+ 'signature' => 'my_random_test_function( $param, $one = \'foo\', $two = \'param\' )',
+ );
+ $this->assertEqual($result, $expected);
+ $this->assertEqual($Docs->info, $expected);
+ }
+/**
+ * test getParameters
+ *
+ * @return void
+ **/
+ function testGetParameters() {
+ $Docs = new FunctionDocumentor('my_random_test_function');
+ $result = $Docs->getParams();
+ $expected = array(
+ 'param' => array(
+ 'optional' => false,
+ 'default' => NULL,
+ 'position' => 0,
+ 'type' => 'string',
+ 'comment' => 'this is a param'
+ ),
+ 'one' => array(
+ 'optional' => true,
+ 'default' => 'foo',
+ 'position' => 1,
+ 'type' => 'string',
+ 'comment' => 'this is one'
+ ),
+ 'two' => array(
+ 'optional' => true,
+ 'default' => 'param',
+ 'position' => 2,
+ 'type' => 'string',
+ 'comment' => 'this is two'
+ )
+ );
+ $this->assertEqual($result, $expected);
+ }
+}
\ No newline at end of file
diff --git a/tests/cases/vendors/introspector.test.php b/tests/cases/vendors/introspector.test.php
new file mode 100644
index 0000000..fd9cad3
--- /dev/null
+++ b/tests/cases/vendors/introspector.test.php
@@ -0,0 +1,95 @@
+<?php
+
+App::import('Vendor', 'ApiGenerator.Introspector');
+
+class IntrospectorTestCase extends CakeTestCase {
+/**
+ * testGetReflector
+ *
+ * @access public
+ * @return void
+ */
+ function testGetReflector() {
+ $result = Introspector::getReflector('function', 'substr');
+ $this->assertTrue($result instanceof FunctionDocumentor);
+
+ $result = Introspector::getReflector('class', 'Introspector');
+ $this->assertTrue($result instanceof ClassDocumentor);
+
+ $result = Introspector::getReflector('Introspector');
+ $this->assertTrue($result instanceof ClassDocumentor);
+ }
+
+ /**
+ * test the correct parsing of comment blocks
+ *
+ * @return void
+ **/
+ function testCommentParsing() {
+ $comment = <<<EOD
+ /**
+ * This is the title
+ *
+ * This is my long description
+ *
+ * @param string \$foo Foo is an input
+ * @param int \$bar Bar is also an input
+ * @return string
+ */
+EOD;
+ $result = Introspector::parseDocBlock($comment);
+ $expected = array(
+ 'description' => "This is the title\n\nThis is my long description",
+ 'tags' => array (
+ 'param' => array(
+ 'foo' => array(
+ 'type' => 'string',
+ 'description' => 'Foo is an input',
+ ),
+ 'bar' => array(
+ 'type' => 'int',
+ 'description' => 'Bar is also an input',
+ )
+ ),
+ 'return' => 'string',
+ ),
+ );
+ $this->assertEqual($result, $expected);
+
+ $comment = <<<EOD
+ /**
+ * This is the title
+ *
+ * This is my long description
+ *
+ * @param string \$foo Foo is an input
+ * @param int \$bar Bar is also an input
+ * @param int \$baz Baz is also an input
+ * @return string
+ */
+EOD;
+ $result = Introspector::parseDocBlock($comment);
+ $expected = array(
+ 'description' => "This is the title\n\nThis is my long description",
+ 'tags' => array (
+ 'param' => array(
+ 'foo' => array(
+ 'type' => 'string',
+ 'description' => 'Foo is an input'
+ ),
+ 'bar' => array(
+ 'type' => 'int',
+ 'description' => 'Bar is also an input',
+ ),
+ 'baz' => array(
+ 'type' => 'int',
+ 'description' => 'Baz is also an input'
+ ),
+ ),
+ 'return' => 'string',
+ ),
+ );
+ $this->assertEqual($result, $expected);
+ }
+}
+?>
\ No newline at end of file
diff --git a/tests/fixtures/api_class_fixture.php b/tests/fixtures/api_class_fixture.php
new file mode 100644
index 0000000..1743e44
--- /dev/null
+++ b/tests/fixtures/api_class_fixture.php
@@ -0,0 +1,17 @@
+<?php
+
+class ApiClassFixture extends CakeTestFixture {
+ var $name = 'ApiClass';
+ var $fields = array(
+ 'id' => array('type' => 'string', 'default' => NULL, 'length' => 36, 'null' => false, 'key' => 'primary'),
+ 'name' => array('type' => 'string', 'length' => 200, 'null' => false),
+ 'slug' => array('type' => 'string', 'length' => 200, 'null' => false),
+ 'file_name' => array('type' => 'text'),
+ 'search_index' => array('type' => 'text'),
+ 'flags' => array('type' => 'integer', 'default' => 0, 'length' => 5),
+ 'created' => array('type' => 'datetime'),
+ 'modified' => array('type' => 'datetime'),
+ );
+
+ var $records = array();
+}
\ No newline at end of file
diff --git a/tests/test_app/inline_parents.php b/tests/test_app/inline_parents.php
new file mode 100644
index 0000000..b167c79
--- /dev/null
+++ b/tests/test_app/inline_parents.php
@@ -0,0 +1,13 @@
+<?php
+interface SomeOtherInterface {
+ const FOOO = 'foo';
+}
+class BazTestFile implements SomeOtherInterface {
+
+}
+class BarTestFile extends BazTestFile {
+
+}
+class FooBarTestFile extends BarTestFile {
+
+}
diff --git a/tests/test_app/mapped_file.php b/tests/test_app/mapped_file.php
new file mode 100644
index 0000000..cffdfdd
--- /dev/null
+++ b/tests/test_app/mapped_file.php
@@ -0,0 +1,7 @@
+<?php
+class MappedRandomFile extends Object implements SillyTestInterface {
+ function woot() {
+
+ }
+}
+?>
\ No newline at end of file
diff --git a/tests/test_app/no_parents.php b/tests/test_app/no_parents.php
new file mode 100644
index 0000000..7b2ba81
--- /dev/null
+++ b/tests/test_app/no_parents.php
@@ -0,0 +1,10 @@
+<?php
+class ApiFileNoParent {
+
+}
+
+abstract class ApiFileNoParentTwo {
+ function test() {
+ ClassRegistry::init('ApiClass');
+ }
+}
\ No newline at end of file
diff --git a/tests/test_app/silly_interface_file.php b/tests/test_app/silly_interface_file.php
new file mode 100644
index 0000000..24087f0
--- /dev/null
+++ b/tests/test_app/silly_interface_file.php
@@ -0,0 +1,4 @@
+<?php
+interface SillyTestInterface {
+ function woot();
+}
\ No newline at end of file
diff --git a/tests/test_app/sloppy_code.php b/tests/test_app/sloppy_code.php
new file mode 100644
index 0000000..4805124
--- /dev/null
+++ b/tests/test_app/sloppy_code.php
@@ -0,0 +1,18 @@
+<?php
+class SloppyClass{
+
+}
+class ExtraLinesClass
+{
+
+}
+abstract class AnohterStupidClass
+{
+
+}
+class Zend_random_file_with_really_long_name extends AnohterStupidClass{
+
+}
+class Really_Long_class_name extends Zend_random_file_with_really_long_name{
+
+}
\ No newline at end of file
diff --git a/tests/test_app/test_file.php b/tests/test_app/test_file.php
new file mode 100644
index 0000000..f9c3684
--- /dev/null
+++ b/tests/test_app/test_file.php
@@ -0,0 +1,9 @@
+<?php
+class ApiRandomFile extends Model {
+
+}
+
+class ApiRandomFileTwo extends MappedRandomFile {
+
+}
+?>
\ No newline at end of file
diff --git a/tests/test_app/throw_down.php b/tests/test_app/throw_down.php
new file mode 100644
index 0000000..8578213
--- /dev/null
+++ b/tests/test_app/throw_down.php
@@ -0,0 +1,5 @@
+<?php
+class ThrowDown extends TheClassThatShallNotBeCreated {
+
+}
+?>
\ No newline at end of file
diff --git a/vendors/class_documentor.php b/vendors/class_documentor.php
new file mode 100644
index 0000000..c080ecf
--- /dev/null
+++ b/vendors/class_documentor.php
@@ -0,0 +1,227 @@
+<?php
+App::import('Vendor', 'ApiGenerator.Introspector');
+
+/**
+ * Documentation Extractor
+ *
+ * Retrieves Documentation using PHP ReflectionClass
+ *
+ * @package api_generator
+ */
+class ClassDocumentor extends ReflectionClass {
+/**
+ * class Information
+ *
+ * @var array
+ **/
+ public $classInfo;
+/**
+ * properties
+ *
+ * @var array
+ **/
+ public $properties;
+/**
+ * methods in consumed class
+ *
+ * @var array
+ **/
+ public $methods;
+/**
+ * getClassInfo
+ *
+ * Get Basic classInfo about the current class
+ *
+ * @return array Information about Class
+ **/
+ public function getClassInfo(){
+ $this->classInfo['name'] = $this->getName();
+ $this->classInfo['fileName'] = $this->getFileName();
+
+ $desc = '';
+ if ($this->isFinal()) {
+ $desc .= 'final ';
+ }
+ if ($this->isAbstract()) {
+ $desc .= 'abstract ';
+ }
+ if ($this->isInterface()) {
+ $desc .= 'interface ';
+ } else {
+ $desc .= 'class ';
+ }
+ $desc .= $this->getName() . ' ';
+
+ $parents = array();
+ if ($this->getParentClass()) {
+ $desc .= 'extends ' . $this->getParentClass()->getName();
+ $object = $this->getParentClass();
+ $parents[] = $object->getName();
+ while ($parent = $object->getParentClass()) {
+ $parents[] = $parent->getName();
+ $object = $parent;
+ }
+ }
+
+ $interfaces = $this->getInterfaces();
+ $interfaceNames = array();
+ $number = count($interfaces);
+ if ($number > 0){
+ $desc .= ' implements ';
+ foreach ($interfaces as $int) {
+ $desc .= $int->getName() . ' ';
+ $interfaceNames[] = $int->getName();
+ }
+ }
+
+ $this->classInfo['classDescription'] = $desc;
+ $this->classInfo['parents'] = $parents;
+ $this->classInfo['interfaces'] = $interfaceNames;
+ $this->classInfo['comment'] = $this->_parseComment($this->getDocComment());
+
+ return $this->classInfo;
+ }
+
+/**
+ * getProperties
+ *
+ * gets All current properties for object with documentation
+ * split by access level
+ *
+ * @return array All properties separated by access type
+ **/
+ public function getProperties(){
+ $public = $protected = $private = array();
+ $properties = parent::getProperties();
+
+ foreach ($properties as $property) {
+ $name = $property->getName();
+ $doc = $this->_parseComment($property->getDocComment());
+
+ $prop = array(
+ 'name' => $name,
+ 'comment' => $doc,
+ 'declaredInClass' => $property->getDeclaringClass()->name
+ );
+
+ if ($property->isPublic()) {
+ $prop['access'] = 'public';
+ if (isset($doc['tags']['access'])) {
+ $prop['access'] = $doc['tags']['access'];
+ }
+ }
+ if ($property->isPrivate()) {
+ $prop['access'] = 'private';
+ }
+ if ($property->isProtected()) {
+ $prop['access'] = 'protected';
+ }
+ if ($property->isStatic()) {
+ $prop['access'] .= ' static';
+ }
+ $this->properties[] = $prop;
+ }
+ return $this->properties;
+ }
+/**
+ * getMethods
+ *
+ * Gets all current methods for object with documentation.
+ * Returns an array with the following structure
+ * 'name' => name of the method
+ * 'access' => level of access to the method
+ * 'args' => array of arguments that the method takes. Args are removed from comment['tags'] as they don't need to be repeated
+ * 'comment' => keyed array see cleanComment
+ *
+ * @see $this->_parseComment
+ * @return array multi-dimensional array of methods and their attributes
+ **/
+ public function getMethods() {
+ $methods = parent::getMethods();
+
+ foreach ($methods as $method) {
+ $doc = $this->_parseComment($method->getDocComment());
+
+ if (isset($doc['tags']['param'])) {
+ $doc['tags']['param'] = (array)$doc['tags']['param'];
+ }
+
+ $met = array(
+ 'name' => $method->getName(),
+ 'comment' => $doc,
+ 'startLine' => $method->getStartLine(),
+ 'declaredInClass' => $method->getDeclaringClass()->getName(),
+ 'declaredInFile' => $method->getDeclaringClass()->getFileName(),
+ 'signature' => Introspector::makeFunctionSignature($method),
+ 'isStatic' => $method->isStatic()
+ );
+
+ $params = $method->getParameters();
+ $args = array();
+ foreach ($params as $param) {
+ $type = $description = null;
+ if (isset($met['comment']['tags']['param'][$param->name])) {
+ extract($met['comment']['tags']['param'][$param->name]);
+ }
+
+ $args[$param->name] = array(
+ 'optional' => $param->isOptional(),
+ 'default' => null,
+ 'hasDefault' => false,
+ 'position' => $param->getPosition(),
+ 'type' => $type,
+ 'comment' => $description
+ );
+ if ($param->isDefaultValueAvailable()) {
+ $args[$param->name]['default'] = $param->getDefaultValue();
+ $args[$param->name]['hasDefault'] = true;
+ }
+ }
+ unset($met['comment']['tags']['param']);
+ $met['args'] = $args;
+
+ if ($method->isPublic()) {
+ $met['access'] = 'public';
+ if (isset($doc['tags']['access'])) {
+ $met['access'] = $doc['tags']['access'];
+ }
+ }
+ if ($method->isPrivate()) {
+ $met['access'] = 'private';
+ }
+ if ($method->isProtected()) {
+ $met['access'] = 'protected';
+ }
+ $this->methods[] = $met;
+ }
+ return $this->methods;
+ }
+/**
+ * _parseComment
+ *
+ * Cleans input comments of stars and /'s so it is more readable.
+ * Creates a multi dimensional array. That contains semi parsed comments
+ *
+ * Returns an array with the following
+ * 'title' contains the title / first line of the doc-block
+ * 'desc' contains the remainder of the doc block
+ * 'tags' contains all the doc-blocks @tags.
+ *
+ * @param string $comments The comment block to be cleaned
+ * @return array Array of Filtered and separated comments
+ **/
+ protected function _parseComment($comments){
+ return Introspector::parseDocBlock($comments);
+ }
+/**
+ * Get all docs for the reflected class
+ *
+ * @return void
+ **/
+ public function getAll() {
+ $this->getClassInfo();
+ $this->getMethods();
+ $this->getProperties();
+ }
+}
+?>
\ No newline at end of file
diff --git a/vendors/css/base.css b/vendors/css/base.css
new file mode 100644
index 0000000..15657af
--- /dev/null
+++ b/vendors/css/base.css
@@ -0,0 +1,527 @@
+/* @override http://localhost/cake_api_gen/api_generator/css/base.css */
+/****************
+* Base.css
+* @author Mark Story
+/
+/***************************************
+Reset
+***************************************/
+html, body, div, span, applet, object, iframe,
+h1, h2, h3, h4, h5, h6, p, blockquote, pre,
+a, abbr, acronym, address, big, cite, code,
+del, dfn, em, font, img, ins, kbd, q, s, samp,
+small, strike, strong, sub, sup, tt, var,
+b, u, i, center,
+dl, dt, dd, ol, ul, li,
+fieldset, form, label, legend,
+table, caption, tbody, tfoot, thead, tr, th, td {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ outline: 0;
+ font-size: 100%;
+ vertical-align: baseline;
+ background: transparent;
+}
+body {
+ line-height: 1;
+}
+ol, ul {
+ list-style: none;
+}
+blockquote, q {
+ quotes: none;
+}
+blockquote:before, blockquote:after,
+q:before, q:after {
+ content: '';
+}
+/* remember to define focus styles! */
+:focus {
+ outline: 0;
+}
+/* remember to highlight inserts somehow! */
+ins {
+ text-decoration: none;
+}
+del {
+ text-decoration: line-through;
+}
+/* tables still need 'cellspacing="0"' in the markup */
+table {
+ border-collapse: collapse;
+ border-spacing: 0;
+}
+.clearfix:after {
+ clear: both;
+ content: ".";
+ display: block;
+ height: 0;
+ visibility: hidden;
+}
+* + html .clearfix {
+ display:inline-block;
+}
+* html .clearfix {
+ height:1%;
+}
+/**
+Basic Tags
+**********************/
+body {
+ font:100 normal 62.5%/1.2 Tahoma, Verdana, sans-serif;
+}
+
+h1, h2, h3, h4, h5, h6, th, dt {
+ font-family: "Gill Sans", "Trebuchet MS", trebuchet, Helvetica, Tahoma, sans-serif;
+ font-weight:100;
+ color: #32270d;
+}
+h1 {
+ font-size: 24px;
+ line-height: 30px;
+}
+h2 {
+ font-size: 22px;
+ line-height: 30px;
+}
+a {
+ color: #94345A;
+}
+a:hover {
+ text-decoration: none;
+}
+table {
+ border-collapse: collapse;
+ width:100%;
+}
+td, th {
+ padding:0.4em;
+}
+h2 {
+ color: #333348;
+ font-size:1.6em;
+}
+h3 {
+ color: #333348;
+ font-size: 1.4em;
+ margin: 1em 0;
+}
+
+/* containers */
+#wrapper {
+ min-width:600px;
+ margin:0 auto;
+ position:relative;
+}
+#header {
+ background:#8BAAB2;
+ font-size:1.5em;
+}
+#content {
+ background: #fff;
+ color:#000;
+ width:90%;
+ padding:14px;
+ margin:0 auto;
+}
+/* with and without sidebar content regions */
+.with-sidebar #content-inner {
+ position: relative;
+ width:65%;
+ padding-left:2%;
+ float: right;
+ border-left: 1px solid #eee;
+}
+.with-sidebar #sidebar {
+ width: 20%;
+ float: left;
+}
+#footer {
+ background: #fff url(../img/footer_bkg.gif) bottom left no-repeat;
+ padding:1px 0 0 10px;
+}
+
+/* header elements */
+#header h1 {
+ color: #fff;
+ float: left;
+ margin: 1em 0 0 5%;
+ font-size: 20px;
+}
+#header h1 a {
+ color:#fff;
+}
+.api .navigation {
+ padding: 1em 5% 0;
+ height:36px;
+ clear: both;
+}
+.api .navigation li {
+ float: left;
+ margin: 0 2px;
+}
+.api .navigation li a {
+ padding: 10px 3em;
+ line-height: 16px;
+ font-size: 16px;
+ display: block;
+ text-decoration: none;
+ color:#fff;
+}
+.api .navigation li a:hover {
+ text-decoration:underline;
+}
+#header-search {
+ float: right;
+ padding: 1em 5% 0;
+}
+
+
+.breadcrumb,
+.breadcrumb a {
+ font-family: Monaco, Corsiva, "Courier New", Courier, monospaced;
+ font-size:14px;
+ margin-bottom: 1em;
+}
+/**
+File Browser Styles
+***************************************/
+#file-list,
+#file-browser {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+}
+.file a,
+.folder a {
+ padding: 8px 8px 8px 40px;
+ font-size:14px;
+ background-color: #EFEFEF;
+ margin-bottom:4px;
+ display: block;
+}
+
+/* files and folders in sidebar */
+#sidebar .file a,
+#sidebar .folder a,
+#sidebar #classIndex li a {
+ font-size: 10px;
+ line-height: 15px;
+ padding: 4px 4px 4px 24px;
+ display: block;
+}
+
+/**
+Documentation Page Styles
+***************************/
+.doc-block {
+ margin: 16px 0 0;
+ clear:both;
+}
+.class-info,
+.function-info {
+ clear: both;
+}
+.class-info {
+ margin:3em 0 0;
+}
+.class-info .doc-head h2 {
+ font-size: 28px;
+ line-height: 32px;
+}
+
+/* doc head */
+.doc-head {
+ position:relative;
+ border-bottom: 1px solid #bbb;
+}
+.doc-head a {
+ position:absolute;
+ right:10px;
+ top:8px;
+}
+.doc-head h2 {
+ color: #333348;
+ border-left:7px solid #fff;
+ margin:0;
+ padding: 0 0 0 8px;
+}
+.parent-method .doc-head {
+ background:#FFFFEF;
+}
+.parent-method h2 {
+ color:#3E4153;
+}
+/* header access indicators */
+.doc-head .public,
+.doc-head .public-static {
+ border-color: #70A280;
+}
+.doc-head .private,
+.doc-head .private-static {
+ border-color: #A47878;
+}
+.doc-head .protected,
+.doc-head .protected-static {
+ border-color: #EAE38B;
+}
+
+/* body styles */
+.doc-body {
+ padding:14px;
+}
+/* table styles */
+.doc-body table {
+ border-collapse:separate;
+ border-spacing: 2px;
+}
+.doc-body td {
+ background: #DAEBEE;
+ vertical-align: middle;
+}
+.doc-body .odd td {
+ background: #C9E2E6;
+}
+.doc-body > table td {
+ font-size:12px;
+}
+.doc-body td.access {
+ width: 40px;
+}
+.doc-body td p {
+ margin: 1em 0;
+}
+.doc-body td p:first-child {
+ margin: 0;
+}
+.doc-body p,
+.doc-body dd {
+ font-size:12px;
+ line-height:1.4em;
+ margin:0 0 1.2em 0;
+}
+.doc-body dd {
+ margin: 0 0 12px 12px;
+}
+.doc-body dd table {
+ margin:0;
+}
+.doc-body dt,
+.doc-body h3 {
+ color: #27676E;
+ font-size:16px;
+ line-height:1.4em;
+ margin:0;
+ clear:both;
+}
+.markdown-block pre {
+ font-size: 12px;
+ line-height: 14px;
+ font-family: Monaco, Corsiva, "Courier New", Courier;
+ background: #E9F6F8;
+ padding: 3px;
+ margin: 0 0 1em 0;
+ border: 1px solid #DAEBEE;
+ color: #333;
+ overflow-x:auto;
+}
+/** inheritance trees **/
+.inheritance-tree .parent-class {
+ display:block;
+ float:left;
+ clear:left;
+ background: url(../img/inheritance_arrow.png) center bottom no-repeat;
+}
+.inheritance-tree a {
+ min-width:100px;
+ padding:5px 15px;
+ border:1px solid #666;
+ display:block;
+ float:left;
+ margin-bottom:15px;
+ text-align:center;
+}
+.inheritance-tree .last {
+ background:none;
+}
+
+/* visibility controls */
+.doc-controls a {
+ padding:5px;
+ border: 1px solid #DAEBEE;
+ font-size:12px;
+ color: #333348;
+ display: -moz-inline-block;
+ display: inline-block;
+ border-radius:9px;
+ -webkit-border-radius:9px;
+ -moz-border-radius: 9px;
+ text-decoration: none;
+ margin-bottom:1em;
+}
+.doc-controls a.active {
+ background:#DAEBEE;
+}
+
+/* Access icon replacements */
+.access span {
+ width:14px;
+ padding-top:14px;
+ height:0px;
+ display:block;
+ overflow:hidden;
+ margin:0 auto;
+ background-repeat:none;
+ background-position:center center;
+}
+.protected span,
+.protected-static span {
+ background-image: url(../img/icon_protected.gif);
+}
+.public span,
+.public-static span {
+ background-image: url(../img/icon_public.gif);
+}
+.private span,
+.private-static span {
+ background-image: url(../img/icon_private.gif);
+}
+.parent-method .private span,
+.parent-property .private span {
+ background-image: url(../img/icon_private_parent.gif);
+}
+.parent-method .protected span,
+.parent-property .protected span {
+ background-image: url(../img/icon_protected_parent.gif);
+}
+.parent-method .public span,
+.parent-property .public span {
+ background-image: url(../img/icon_public_parent.gif);
+}
+
+/** Element table of contents **/
+#element-toc {
+ margin-bottom: 2em;
+ clear:both;
+}
+#element-toc ul {
+ list-style: disc;
+ margin: 0 0 0 20px
+}
+#element-toc li {
+ margin: 0.5em;
+ font-size: 12px;
+}
+#element-toc .functions,
+#element-toc .classes {
+ float: left;
+}
+#element-toc .classes {
+ padding-right: 2em;
+ margin-right: 2em;
+}
+
+/**
+* Class Index
+*****************************/
+.letter-links {
+ margin: 3em 0;
+}
+.letter-links a,
+.letter-links span {
+ padding:2px 10px;
+ border-left: 1px solid #ccc;
+}
+.letter-links a:first-child,
+.letter-links span:first-child {
+ border: 0;
+}
+.letter-section {
+ float: left;
+ margin: 0 1%;
+ width: 29%;
+}
+.letter-section h3 {
+ border-bottom: 1px solid #ccc;
+}
+.class-index {
+ list-style: square;
+ list-style-position: inside;
+ color: #8BAAB2;
+}
+.class-index li {
+ margin: 0.6em 0;
+ font-size: 14px;
+}
+/**
+* Sidebar
+*****************************/
+#sidebar h3 {
+ margin-bottom:0.3em;
+}
+#sidebar .class-index {
+ margin-left: 1em;
+}
+#sidebar .class-index li {
+ font-size: 12px;
+}
+/**
+* Code highlighting
+****************************/
+ol.code {
+ padding: 0;
+ font-size: 10px;
+ line-height: 1.8em;
+ overflow: hidden;
+ color: #939399;
+ text-align: left;
+ list-style:decimal;
+ list-style-position: inside;
+ border: 1px solid #d3d3d0;
+ clear:both;
+ overflow-x:scroll;
+}
+ol.code li {
+ white-space: nowrap;
+ margin: 0;
+ padding: 0 0 0 1%;
+ background: #fff;
+}
+ol.code li.even {
+ background: #f3f3f0;
+}
+
+ol.code li code {
+ font: 1.2em courier, monospace;
+ color: #c30;
+ white-space: pre;
+ padding-left: 0.5em;
+}
+.code .comment {
+ color: #939399;
+}
+.code .default {
+ color: #44c;
+}
+.code .keyword {
+ color: #373;
+}
+.code .string {
+ color: #c30;
+}
+
+/**
+* Search results
+****************************/
+#search-results {
+ margin: 10px;
+ padding: 4px 0;
+}
+#search-results li {
+ margin: 20px 10px;
+ font-size: 140%;
+}
+#search-results .highlight {
+ background: #C9E2E6;
+}
diff --git a/vendors/function_documentor.php b/vendors/function_documentor.php
new file mode 100644
index 0000000..06a846c
--- /dev/null
+++ b/vendors/function_documentor.php
@@ -0,0 +1,111 @@
+<?php
+/* SVN FILE: $Id$ */
+/**
+ * Function Documentor Class
+ *
+ * Used for parsing and extracting documentation and introspecting on functions
+ *
+ * PHP versions 4 and 5
+ *
+ * CakePHP : Rapid Development Framework <http://www.cakephp.org/>
+ * Copyright 2006-2008, Cake Software Foundation, Inc.
+ * 1785 E. Sahara Avenue, Suite 490-204
+ * Las Vegas, Nevada 89104
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright 2006-2008, Cake Software Foundation, Inc.
+ * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project
+ * @package cake
+ * @subpackage cake.api_generator.vendors
+ * @since
+ * @version
+ * @modifiedby
+ * @lastmodified
+ * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
+App::import('Vendor', 'ApiGenerator.Introspector');
+
+class FunctionDocumentor extends ReflectionFunction {
+/**
+ * Information about the function
+ *
+ * @var array
+ **/
+ public $info;
+/**
+ * Params the function has
+ *
+ * @var string
+ **/
+ public $params;
+/**
+ * get General information about the function
+ * doc block, declared line/file etc.
+ *
+ * @return array
+ **/
+ public function getInfo() {
+ $info = array(
+ 'name' => $this->getName(),
+ 'comment' => $this->_parseComment($this->getDocComment()),
+ 'declaredInFile' => $this->getFileName(),
+ 'startLine' => $this->getStartLine(),
+ 'endLine' => $this->getEndLine(),
+ 'internal' => $this->isInternal(),
+ );
+ $this->info = $info;
+ $this->info['signature'] = Introspector::makeFunctionSignature($this);
+ return $this->info;
+ }
+/**
+ * Get all the information for each parameter the function has
+ *
+ * @return array
+ **/
+ public function getParams() {
+ $params = parent::getParameters();
+ if (!isset($this->info['comment']['tags']['param'])) {
+ $this->getInfo();
+ }
+ foreach ($params as $param) {
+ $type = $description = null;
+ if (isset($this->info['comment']['tags']['param'][$param->name])) {
+ extract($this->info['comment']['tags']['param'][$param->name]);
+ }
+ $this->params[$param->name] = array(
+ 'optional' => $param->isOptional(),
+ 'default' => null,
+ 'position' => $param->getPosition(),
+ 'type' => $type,
+ 'comment' => $description,
+ 'hasDefault' => $param->isDefaultValueAvailable(),
+ 'default' => null
+ );
+ if ($param->isDefaultValueAvailable()) {
+ $this->params[$param->name]['default'] = $param->getDefaultValue();
+ }
+ }
+ return $this->params;
+ }
+/**
+ * getAll docs for the current function documentor
+ *
+ * @return object
+ **/
+ public function getAll() {
+ $this->getInfo();
+ $this->getParams();
+ }
+/**
+ * _parseComment
+ *
+ * @param string $comment Comment string to parse
+ * @return string
+ **/
+ protected function _parseComment($comment) {
+ return Introspector::parseDocBlock($comment);
+ }
+}
\ No newline at end of file
diff --git a/vendors/img/icon_minus.png b/vendors/img/icon_minus.png
new file mode 100644
index 0000000..0d4e81e
Binary files /dev/null and b/vendors/img/icon_minus.png differ
diff --git a/vendors/img/icon_plus.gif b/vendors/img/icon_plus.gif
new file mode 100644
index 0000000..31ea958
Binary files /dev/null and b/vendors/img/icon_plus.gif differ
diff --git a/vendors/img/icon_private.gif b/vendors/img/icon_private.gif
new file mode 100644
index 0000000..abc9750
Binary files /dev/null and b/vendors/img/icon_private.gif differ
diff --git a/vendors/img/icon_private_parent.gif b/vendors/img/icon_private_parent.gif
new file mode 100644
index 0000000..de89295
Binary files /dev/null and b/vendors/img/icon_private_parent.gif differ
diff --git a/vendors/img/icon_protected.gif b/vendors/img/icon_protected.gif
new file mode 100644
index 0000000..cc38f26
Binary files /dev/null and b/vendors/img/icon_protected.gif differ
diff --git a/vendors/img/icon_protected_parent.gif b/vendors/img/icon_protected_parent.gif
new file mode 100644
index 0000000..003d587
Binary files /dev/null and b/vendors/img/icon_protected_parent.gif differ
diff --git a/vendors/img/icon_public.gif b/vendors/img/icon_public.gif
new file mode 100644
index 0000000..e21fb78
Binary files /dev/null and b/vendors/img/icon_public.gif differ
diff --git a/vendors/img/icon_public_parent.gif b/vendors/img/icon_public_parent.gif
new file mode 100644
index 0000000..63ae507
Binary files /dev/null and b/vendors/img/icon_public_parent.gif differ
diff --git a/vendors/img/inheritance_arrow.png b/vendors/img/inheritance_arrow.png
new file mode 100644
index 0000000..d669fd8
Binary files /dev/null and b/vendors/img/inheritance_arrow.png differ
diff --git a/vendors/introspector.php b/vendors/introspector.php
new file mode 100644
index 0000000..8d73c5b
--- /dev/null
+++ b/vendors/introspector.php
@@ -0,0 +1,152 @@
+<?php
+/* SVN FILE: $Id$ */
+/**
+ * Introspector Introspect stuff
+ *
+ *
+ *
+ * PHP versions 4 and 5
+ *
+ * CakePHP : Rapid Development Framework <http://www.cakephp.org/>
+ * Copyright 2006-2008, Cake Software Foundation, Inc.
+ * 1785 E. Sahara Avenue, Suite 490-204
+ * Las Vegas, Nevada 89104
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright 2006-2008, Cake Software Foundation, Inc.
+ * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project
+ * @package cake
+ * @subpackage cake.api_generator.vendors
+ * @since
+ * @version
+ * @modifiedby
+ * @lastmodified
+ * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
+/**
+ * Introspector provides factory methods and common methods for
+ * reflection parsing
+ *
+ * @package cake.api_generator.vendors
+ */
+class Introspector {
+/**
+ * reflector classMappings
+ *
+ * @var array
+ **/
+ protected static $_reflectorMap = array(
+ 'class' => 'ClassDocumentor',
+ 'function' => 'FunctionDocumentor',
+ );
+/**
+ * Get the correct reflector type for the requested object
+ *
+ * @param string $type The type of reflector needed
+ * @param string $name The name of the function/class being reflected
+ * @return object constructed reflector type.
+ * @throws Exception
+ */
+ public static function getReflector($type, $name = null) {
+ if ($name === null) {
+ $name = $type;
+ $type = 'class';
+ }
+ if (!isset(self::$_reflectorMap[$type])) {
+ throw new Exception('Missing reflector mapping type');
+ }
+ if (!class_exists(self::$_reflectorMap[$type])) {
+ $reflectorName = 'ApiGenerator.' . self::$_reflectorMap[$type];
+ App::import('Vendor', $reflectorName);
+ }
+ return new self::$_reflectorMap[$type]($name);
+ }
+/**
+ * parseDocBlock
+ *
+ * Cleans input comments of stars and /'s so it is more readable.
+ * Creates a multi dimensional array. That contains semi parsed comments
+ *
+ * Returns an array with the following
+ * 'title' contains the title / first line of the doc-block
+ * 'desc' contains the remainder of the doc block
+ * 'tags' contains all the doc-blocks @tags.
+ *
+ * @param string $comments The comment block to be cleaned
+ * @return array Array of Filtered and separated comments
+ **/
+ public static function parseDocBlock($comments){
+ $com = array();
+
+ //remove stars and slashes
+ $tmp = preg_replace('#^(\s*/\*\*|\s*\*+/|\s+\* ?)#m', '', $comments);
+
+ //fix new lines
+ $tmp = str_replace("\r\n", "\n", $tmp);
+ $tmp = explode("\n", $tmp);
+
+ $desc = '';
+ $tags = array();
+ for ($i = 0, $count = count($tmp); $i < $count; $i++ ) {
+ $line = $tmp[$i];
+ if (substr($line, 0, 1) !== '@' && $line !== '*') {
+ $desc .= "\n" . $line;
+ }
+ if (preg_match('/@([a-z0-9_-]+)\s(.*)$/i', $tmp[$i], $parsedTag)) {
+ if (isset($tags[$parsedTag[1]]) && !is_array($tags[$parsedTag[1]])) {
+ $tags[$parsedTag[1]] = (array)$tags[$parsedTag[1]];
+ $tags[$parsedTag[1]][] = $parsedTag[2];
+ } elseif (isset($tags[$parsedTag[1]]) && is_array($tags[$parsedTag[1]])) {
+ $tags[$parsedTag[1]][] = $parsedTag[2];
+ } else {
+ $tags[$parsedTag[1]] = $parsedTag[2];
+ }
+ }
+
+ }
+ if (isset($tags['param'])) {
+ $params = (array)$tags['param'];
+ $tags['param'] = array();
+ foreach ($params as $param) {
+ $paramDoc = explode(' ', $param, 3);
+ switch (count($paramDoc)) {
+ case 2:
+ list($type, $name) = $paramDoc;
+ break;
+ case 3:
+ list($type, $name, $description) = $paramDoc;
+ break;
+ }
+ $name = @trim($name, '$');
+ $tags['param'][$name] = compact('type', 'description');
+ }
+ }
+ $com['description'] = trim($desc);
+ $com['tags'] = $tags;
+ return $com;
+ }
+/**
+ * Create a string representation of the method signature.
+ *
+ * @param ReflectionFunctionAbstract $func The function you want a signature for.
+ * @return void
+ **/
+ public static function makeFunctionSignature(ReflectionFunctionAbstract $func ) {
+ $signature = $func->getName() . '( ';
+ foreach ($func->getParameters() as $param) {
+ $signature .= '$' . $param->getName();
+ if ($param->isDefaultValueAvailable()) {
+ $signature .= ' = ' . var_export($param->getDefaultValue(), true);
+ }
+ $signature .= ', ';
+ }
+ if ($func->getNumberOfParameters() > 0) {
+ $signature = substr($signature, 0, -2);
+ }
+ $signature .= ' )';
+ return $signature;
+ }
+}
\ No newline at end of file
diff --git a/vendors/js/api_generator.js b/vendors/js/api_generator.js
new file mode 100644
index 0000000..a045c56
--- /dev/null
+++ b/vendors/js/api_generator.js
@@ -0,0 +1,129 @@
+/**
+ * Api Generator JS
+ *
+ */
+//Add some features to Element
+Element.implement({
+ //is an element visible
+ isVisible : function () {
+ return (this.getStyle('display') == 'none') ? false : true;
+ },
+ //toggle show/hide
+ toggle: function() {
+ return this[this.isVisible() ? 'hide' : 'show']();
+ },
+ //hide
+ hide : function() {
+ var original = this.getStyle('display');
+ this.store('toggle:originalDisplay', original);
+ this.setStyle('display', 'none');
+ return this;
+ },
+ //show an element
+ show : function() {
+ var restore = this.retrieve('toggle:originalDisplay', 'block');
+ this.setStyle('display', restore);
+ return this;
+ }
+});
+
+/**
+ * Simple FileExplorer.
+ *
+ */
+var FileExplorer = new Class({
+ Implements : [Options, Events],
+ options : {
+ folderSelector : 'li.folder',
+ },
+ elements : {},
+
+ initialize : function (container, options) {
+ this.container = $(container);
+ if (!this.container) {
+ return;
+ }
+ this.setOptions(options);
+ var attachEvent = this.attachEvents.bind(this);
+ this.container.set('load', {method : 'get', onComplete: attachEvent });
+ this.attachEvents();
+ },
+ // attach events to all the elements.
+ attachEvents : function() {
+ var elements = this.container.getElements(this.options.folderSelector + ' a');
+ var container = this.container;
+ for (var i = 0; i < elements.length; i++) {
+ elements[i].addEvent('click', function (event) {
+ event.stop();
+ container.load(this.get('href'));
+ });
+ }
+ }
+});
+
+ApiGenerator = {};
+
+ApiGenerator.init = function() {
+ for (prop in this) {
+ if (this[prop].init != undefined) {
+ this[prop].init();
+ }
+ }
+}
+/**
+ * Enable markdown conversion for .markdown-block
+ */
+ApiGenerator.docBlocks = {
+ init : function() {
+ var converter = new Showdown.converter(window.basePath);
+ $$('.markdown-block').each(function(item) {
+ item.set('html', converter.makeHtml(item.get('text').trim()));
+ });
+ }
+}
+
+/**
+ * Javascript used on Api doc Pages
+ *
+ */
+ApiGenerator.apiPages = {
+ init : function() {
+ var targets = {
+ 'hide-parent-methods' : '.parent-method',
+ 'hide-parent-properties' : '.parent-property'
+ }
+ this.attachVisibilityControls(targets);
+ var mySmoothScroll = new SmoothScroll({
+ links: '.scroll-link',
+ wheelStops: false
+ });
+ var FileTree = new FileExplorer('file-browser');
+ },
+
+ attachVisibilityControls : function(collection) {
+ for (button in collection) {
+ if (!$(button)) {
+ continue;
+ }
+ $(button).addEvent('click', function(e) {
+ e.stop();
+ var targets = $$(collection[this.get('id')]);
+ var showing = this.retrieve('showing', true);
+ if (showing) {
+ targets.fade('out');
+ this.addClass('active');
+ var setStyle = targets.setStyle;
+ setStyle.delay(400, targets, ['display', 'none']);
+ } else {
+ targets.fade('in').setStyle('display', null);
+ this.removeClass('active');
+ }
+ this.store('showing', !showing);
+ });
+ }
+ }
+};
+
+window.addEvent('domready', function() {
+ ApiGenerator.init();
+});
\ No newline at end of file
diff --git a/vendors/js/mootools.js b/vendors/js/mootools.js
new file mode 100644
index 0000000..5bcb5d3
--- /dev/null
+++ b/vendors/js/mootools.js
@@ -0,0 +1,132 @@
+//MooTools, <http://mootools.net>, My Object Oriented (JavaScript) Tools. Copyright (c) 2006-2008 Valerio Proietti, <http://mad4milk.net>, MIT Style License.
+
+var MooTools={'version':'1.2.1','build':'0d4845aab3d9a4fdee2f0d4a6dd59210e4b697cf'};var Native=function(options){options=options||{};var name=options.name;var legacy=options.legacy;var protect=options.protect;var methods=options.implement;var generics=options.generics;var initialize=options.initialize;var afterImplement=options.afterImplement||function(){};var object=initialize||legacy;generics=generics!==false;object.constructor=Native;object.$family={name:'native'};if(legacy&&initialize)object.prototype=legacy.prototype;object.prototype.constructor=object;if(name){var family=name.toLowerCase();object.prototype.$family={name:family};Native.typize(object,family);}
+var add=function(obj,name,method,force){if(!protect||force||!obj.prototype[name])obj.prototype[name]=method;if(generics)Native.genericize(obj,name,protect);afterImplement.call(obj,name,method);return obj;};object.alias=function(a1,a2,a3){if(typeof a1=='string'){if((a1=this.prototype[a1]))return add(this,a2,a1,a3);}
+for(var a in a1)this.alias(a,a1[a],a2);return this;};object.implement=function(a1,a2,a3){if(typeof a1=='string')return add(this,a1,a2,a3);for(var p in a1)add(this,p,a1[p],a2);return this;};if(methods)object.implement(methods);return object;};Native.genericize=function(object,property,check){if((!check||!object[property])&&typeof object.prototype[property]=='function')object[property]=function(){var args=Array.prototype.slice.call(arguments);return object.prototype[property].apply(args.shift(),args);};};Native.implement=function(objects,properties){for(var i=0,l=objects.length;i<l;i++)objects[i].implement(properties);};Native.typize=function(object,family){if(!object.type)object.type=function(item){return($type(item)===family);};};(function(){var natives={'Array':Array,'Date':Date,'Function':Function,'Number':Number,'RegExp':RegExp,'String':String};for(var n in natives)new Native({name:n,initialize:natives[n],protect:true});var types={'boolean':Boolean,'native':Native,'object':Object};for(var t in types)Native.typize(types[t],t);var generics={'Array':["concat","indexOf","join","lastIndexOf","pop","push","reverse","shift","slice","sort","splice","toString","unshift","valueOf"],'String':["charAt","charCodeAt","concat","indexOf","lastIndexOf","match","replace","search","slice","split","substr","substring","toLowerCase","toUpperCase","valueOf"]};for(var g in generics){for(var i=generics[g].length;i--;)Native.genericize(window[g],generics[g][i],true);};})();var Hash=new Native({name:'Hash',initialize:function(object){if($type(object)=='hash')object=$unlink(object.getClean());for(var key in object)this[key]=object[key];return this;}});Hash.implement({forEach:function(fn,bind){for(var key in this){if(this.hasOwnProperty(key))fn.call(bind,this[key],key,this);}},getClean:function(){var clean={};for(var key in this){if(this.hasOwnProperty(key))clean[key]=this[key];}
+return clean;},getLength:function(){var length=0;for(var key in this){if(this.hasOwnProperty(key))length++;}
+return length;}});Hash.alias('forEach','each');Array.implement({forEach:function(fn,bind){for(var i=0,l=this.length;i<l;i++)fn.call(bind,this[i],i,this);}});Array.alias('forEach','each');function $A(iterable){if(iterable.item){var array=[];for(var i=0,l=iterable.length;i<l;i++)array[i]=iterable[i];return array;}
+return Array.prototype.slice.call(iterable);};function $arguments(i){return function(){return arguments[i];};};function $chk(obj){return!!(obj||obj===0);};function $clear(timer){clearTimeout(timer);clearInterval(timer);return null;};function $defined(obj){return(obj!=undefined);};function $each(iterable,fn,bind){var type=$type(iterable);((type=='arguments'||type=='collection'||type=='array')?Array:Hash).each(iterable,fn,bind);};function $empty(){};function $extend(original,extended){for(var key in(extended||{}))original[key]=extended[key];return original;};function $H(object){return new Hash(object);};function $lambda(value){return(typeof value=='function')?value:function(){return value;};};function $merge(){var mix={};for(var i=0,l=arguments.length;i<l;i++){var object=arguments[i];if($type(object)!='object')continue;for(var key in object){var op=object[key],mp=mix[key];mix[key]=(mp&&$type(op)=='object'&&$type(mp)=='object')?$merge(mp,op):$unlink(op);}}
+return mix;};function $pick(){for(var i=0,l=arguments.length;i<l;i++){if(arguments[i]!=undefined)return arguments[i];}
+return null;};function $random(min,max){return Math.floor(Math.random()*(max-min+1)+min);};function $splat(obj){var type=$type(obj);return(type)?((type!='array'&&type!='arguments')?[obj]:obj):[];};var $time=Date.now||function(){return+new Date;};function $try(){for(var i=0,l=arguments.length;i<l;i++){try{return arguments[i]();}catch(e){}}
+return null;};function $type(obj){if(obj==undefined)return false;if(obj.$family)return(obj.$family.name=='number'&&!isFinite(obj))?false:obj.$family.name;if(obj.nodeName){switch(obj.nodeType){case 1:return'element';case 3:return(/\S/).test(obj.nodeValue)?'textnode':'whitespace';}}else if(typeof obj.length=='number'){if(obj.callee)return'arguments';else if(obj.item)return'collection';}
+return typeof obj;};function $unlink(object){var unlinked;switch($type(object)){case'object':unlinked={};for(var p in object)unlinked[p]=$unlink(object[p]);break;case'hash':unlinked=new Hash(object);break;case'array':unlinked=[];for(var i=0,l=object.length;i<l;i++)unlinked[i]=$unlink(object[i]);break;default:return object;}
+return unlinked;};var Browser=$merge({Engine:{name:'unknown',version:0},Platform:{name:(window.orientation!=undefined)?'ipod':(navigator.platform.match(/mac|win|linux/i)||['other'])[0].toLowerCase()},Features:{xpath:!!(document.evaluate),air:!!(window.runtime),query:!!(document.querySelector)},Plugins:{},Engines:{presto:function(){return(!window.opera)?false:((arguments.callee.caller)?960:((document.getElementsByClassName)?950:925));},trident:function(){return(!window.ActiveXObject)?false:((window.XMLHttpRequest)?5:4);},webkit:function(){return(navigator.taintEnabled)?false:((Browser.Features.xpath)?((Browser.Features.query)?525:420):419);},gecko:function(){return(document.getBoxObjectFor==undefined)?false:((document.getElementsByClassName)?19:18);}}},Browser||{});Browser.Platform[Browser.Platform.name]=true;Browser.detect=function(){for(var engine in this.Engines){var version=this.Engines[engine]();if(version){this.Engine={name:engine,version:version};this.Engine[engine]=this.Engine[engine+version]=true;break;}}
+return{name:engine,version:version};};Browser.detect();Browser.Request=function(){return $try(function(){return new XMLHttpRequest();},function(){return new ActiveXObject('MSXML2.XMLHTTP');});};Browser.Features.xhr=!!(Browser.Request());Browser.Plugins.Flash=(function(){var version=($try(function(){return navigator.plugins['Shockwave Flash'].description;},function(){return new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version');})||'0 r0').match(/\d+/g);return{version:parseInt(version[0]||0+'.'+version[1]||0),build:parseInt(version[2]||0)};})();function $exec(text){if(!text)return text;if(window.execScript){window.execScript(text);}else{var script=document.createElement('script');script.setAttribute('type','text/javascript');script[(Browser.Engine.webkit&&Browser.Engine.version<420)?'innerText':'text']=text;document.head.appendChild(script);document.head.removeChild(script);}
+return text;};Native.UID=1;var $uid=(Browser.Engine.trident)?function(item){return(item.uid||(item.uid=[Native.UID++]))[0];}:function(item){return item.uid||(item.uid=Native.UID++);};var Window=new Native({name:'Window',legacy:(Browser.Engine.trident)?null:window.Window,initialize:function(win){$uid(win);if(!win.Element){win.Element=$empty;if(Browser.Engine.webkit)win.document.createElement("iframe");win.Element.prototype=(Browser.Engine.webkit)?window["[[DOMElement.prototype]]"]:{};}
+win.document.window=win;return $extend(win,Window.Prototype);},afterImplement:function(property,value){window[property]=Window.Prototype[property]=value;}});Window.Prototype={$family:{name:'window'}};new Window(window);var Document=new Native({name:'Document',legacy:(Browser.Engine.trident)?null:window.Document,initialize:function(doc){$uid(doc);doc.head=doc.getElementsByTagName('head')[0];doc.html=doc.getElementsByTagName('html')[0];if(Browser.Engine.trident&&Browser.Engine.version<=4)$try(function(){doc.execCommand("BackgroundImageCache",false,true);});if(Browser.Engine.trident)doc.window.attachEvent('onunload',function(){doc.window.detachEvent('onunload',arguments.callee);doc.head=doc.html=doc.window=null;});return $extend(doc,Document.Prototype);},afterImplement:function(property,value){document[property]=Document.Prototype[property]=value;}});Document.Prototype={$family:{name:'document'}};new Document(document);Array.implement({every:function(fn,bind){for(var i=0,l=this.length;i<l;i++){if(!fn.call(bind,this[i],i,this))return false;}
+return true;},filter:function(fn,bind){var results=[];for(var i=0,l=this.length;i<l;i++){if(fn.call(bind,this[i],i,this))results.push(this[i]);}
+return results;},clean:function(){return this.filter($defined);},indexOf:function(item,from){var len=this.length;for(var i=(from<0)?Math.max(0,len+from):from||0;i<len;i++){if(this[i]===item)return i;}
+return-1;},map:function(fn,bind){var results=[];for(var i=0,l=this.length;i<l;i++)results[i]=fn.call(bind,this[i],i,this);return results;},some:function(fn,bind){for(var i=0,l=this.length;i<l;i++){if(fn.call(bind,this[i],i,this))return true;}
+return false;},associate:function(keys){var obj={},length=Math.min(this.length,keys.length);for(var i=0;i<length;i++)obj[keys[i]]=this[i];return obj;},link:function(object){var result={};for(var i=0,l=this.length;i<l;i++){for(var key in object){if(object[key](this[i])){result[key]=this[i];delete object[key];break;}}}
+return result;},contains:function(item,from){return this.indexOf(item,from)!=-1;},extend:function(array){for(var i=0,j=array.length;i<j;i++)this.push(array[i]);return this;},getLast:function(){return(this.length)?this[this.length-1]:null;},getRandom:function(){return(this.length)?this[$random(0,this.length-1)]:null;},include:function(item){if(!this.contains(item))this.push(item);return this;},combine:function(array){for(var i=0,l=array.length;i<l;i++)this.include(array[i]);return this;},erase:function(item){for(var i=this.length;i--;i){if(this[i]===item)this.splice(i,1);}
+return this;},empty:function(){this.length=0;return this;},flatten:function(){var array=[];for(var i=0,l=this.length;i<l;i++){var type=$type(this[i]);if(!type)continue;array=array.concat((type=='array'||type=='collection'||type=='arguments')?Array.flatten(this[i]):this[i]);}
+return array;},hexToRgb:function(array){if(this.length!=3)return null;var rgb=this.map(function(value){if(value.length==1)value+=value;return value.toInt(16);});return(array)?rgb:'rgb('+rgb+')';},rgbToHex:function(array){if(this.length<3)return null;if(this.length==4&&this[3]==0&&!array)return'transparent';var hex=[];for(var i=0;i<3;i++){var bit=(this[i]-0).toString(16);hex.push((bit.length==1)?'0'+bit:bit);}
+return(array)?hex:'#'+hex.join('');}});Function.implement({extend:function(properties){for(var property in properties)this[property]=properties[property];return this;},create:function(options){var self=this;options=options||{};return function(event){var args=options.arguments;args=(args!=undefined)?$splat(args):Array.slice(arguments,(options.event)?1:0);if(options.event)args=[event||window.event].extend(args);var returns=function(){return self.apply(options.bind||null,args);};if(options.delay)return setTimeout(returns,options.delay);if(options.periodical)return setInterval(returns,options.periodical);if(options.attempt)return $try(returns);return returns();};},run:function(args,bind){return this.apply(bind,$splat(args));},pass:function(args,bind){return this.create({bind:bind,arguments:args});},bind:function(bind,args){return this.create({bind:bind,arguments:args});},bindWithEvent:function(bind,args){return this.create({bind:bind,arguments:args,event:true});},attempt:function(args,bind){return this.create({bind:bind,arguments:args,attempt:true})();},delay:function(delay,bind,args){return this.create({bind:bind,arguments:args,delay:delay})();},periodical:function(periodical,bind,args){return this.create({bind:bind,arguments:args,periodical:periodical})();}});Number.implement({limit:function(min,max){return Math.min(max,Math.max(min,this));},round:function(precision){precision=Math.pow(10,precision||0);return Math.round(this*precision)/precision;},times:function(fn,bind){for(var i=0;i<this;i++)fn.call(bind,i,this);},toFloat:function(){return parseFloat(this);},toInt:function(base){return parseInt(this,base||10);}});Number.alias('times','each');(function(math){var methods={};math.each(function(name){if(!Number[name])methods[name]=function(){return Math[name].apply(null,[this].concat($A(arguments)));};});Number.implement(methods);})(['abs','acos','asin','atan','atan2','ceil','cos','exp','floor','log','max','min','pow','sin','sqrt','tan']);String.implement({test:function(regex,params){return((typeof regex=='string')?new RegExp(regex,params):regex).test(this);},contains:function(string,separator){return(separator)?(separator+this+separator).indexOf(separator+string+separator)>-1:this.indexOf(string)>-1;},trim:function(){return this.replace(/^\s+|\s+$/g,'');},clean:function(){return this.replace(/\s+/g,' ').trim();},camelCase:function(){return this.replace(/-\D/g,function(match){return match.charAt(1).toUpperCase();});},hyphenate:function(){return this.replace(/[A-Z]/g,function(match){return('-'+match.charAt(0).toLowerCase());});},capitalize:function(){return this.replace(/\b[a-z]/g,function(match){return match.toUpperCase();});},escapeRegExp:function(){return this.replace(/([-.*+?^${}()|[\]\/\\])/g,'\\$1');},toInt:function(base){return parseInt(this,base||10);},toFloat:function(){return parseFloat(this);},hexToRgb:function(array){var hex=this.match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/);return(hex)?hex.slice(1).hexToRgb(array):null;},rgbToHex:function(array){var rgb=this.match(/\d{1,3}/g);return(rgb)?rgb.rgbToHex(array):null;},stripScripts:function(option){var scripts='';var text=this.replace(/<script[^>]*>([\s\S]*?)<\/script>/gi,function(){scripts+=arguments[1]+'\n';return'';});if(option===true)$exec(scripts);else if($type(option)=='function')option(scripts,text);return text;},substitute:function(object,regexp){return this.replace(regexp||(/\\?\{([^{}]+)\}/g),function(match,name){if(match.charAt(0)=='\\')return match.slice(1);return(object[name]!=undefined)?object[name]:'';});}});Hash.implement({has:Object.prototype.hasOwnProperty,keyOf:function(value){for(var key in this){if(this.hasOwnProperty(key)&&this[key]===value)return key;}
+return null;},hasValue:function(value){return(Hash.keyOf(this,value)!==null);},extend:function(properties){Hash.each(properties,function(value,key){Hash.set(this,key,value);},this);return this;},combine:function(properties){Hash.each(properties,function(value,key){Hash.include(this,key,value);},this);return this;},erase:function(key){if(this.hasOwnProperty(key))delete this[key];return this;},get:function(key){return(this.hasOwnProperty(key))?this[key]:null;},set:function(key,value){if(!this[key]||this.hasOwnProperty(key))this[key]=value;return this;},empty:function(){Hash.each(this,function(value,key){delete this[key];},this);return this;},include:function(key,value){var k=this[key];if(k==undefined)this[key]=value;return this;},map:function(fn,bind){var results=new Hash;Hash.each(this,function(value,key){results.set(key,fn.call(bind,value,key,this));},this);return results;},filter:function(fn,bind){var results=new Hash;Hash.each(this,function(value,key){if(fn.call(bind,value,key,this))results.set(key,value);},this);return results;},every:function(fn,bind){for(var key in this){if(this.hasOwnProperty(key)&&!fn.call(bind,this[key],key))return false;}
+return true;},some:function(fn,bind){for(var key in this){if(this.hasOwnProperty(key)&&fn.call(bind,this[key],key))return true;}
+return false;},getKeys:function(){var keys=[];Hash.each(this,function(value,key){keys.push(key);});return keys;},getValues:function(){var values=[];Hash.each(this,function(value){values.push(value);});return values;},toQueryString:function(base){var queryString=[];Hash.each(this,function(value,key){if(base)key=base+'['+key+']';var result;switch($type(value)){case'object':result=Hash.toQueryString(value,key);break;case'array':var qs={};value.each(function(val,i){qs[i]=val;});result=Hash.toQueryString(qs,key);break;default:result=key+'='+encodeURIComponent(value);}
+if(value!=undefined)queryString.push(result);});return queryString.join('&');}});Hash.alias({keyOf:'indexOf',hasValue:'contains'});var Event=new Native({name:'Event',initialize:function(event,win){win=win||window;var doc=win.document;event=event||win.event;if(event.$extended)return event;this.$extended=true;var type=event.type;var target=event.target||event.srcElement;while(target&&target.nodeType==3)target=target.parentNode;if(type.test(/key/)){var code=event.which||event.keyCode;var key=Event.Keys.keyOf(code);if(type=='keydown'){var fKey=code-111;if(fKey>0&&fKey<13)key='f'+fKey;}
+key=key||String.fromCharCode(code).toLowerCase();}else if(type.match(/(click|mouse|menu)/i)){doc=(!doc.compatMode||doc.compatMode=='CSS1Compat')?doc.html:doc.body;var page={x:event.pageX||event.clientX+doc.scrollLeft,y:event.pageY||event.clientY+doc.scrollTop};var client={x:(event.pageX)?event.pageX-win.pageXOffset:event.clientX,y:(event.pageY)?event.pageY-win.pageYOffset:event.clientY};if(type.match(/DOMMouseScroll|mousewheel/)){var wheel=(event.wheelDelta)?event.wheelDelta/120:-(event.detail||0)/3;}
+var rightClick=(event.which==3)||(event.button==2);var related=null;if(type.match(/over|out/)){switch(type){case'mouseover':related=event.relatedTarget||event.fromElement;break;case'mouseout':related=event.relatedTarget||event.toElement;}
+if(!(function(){while(related&&related.nodeType==3)related=related.parentNode;return true;}).create({attempt:Browser.Engine.gecko})())related=false;}}
+return $extend(this,{event:event,type:type,page:page,client:client,rightClick:rightClick,wheel:wheel,relatedTarget:related,target:target,code:code,key:key,shift:event.shiftKey,control:event.ctrlKey,alt:event.altKey,meta:event.metaKey});}});Event.Keys=new Hash({'enter':13,'up':38,'down':40,'left':37,'right':39,'esc':27,'space':32,'backspace':8,'tab':9,'delete':46});Event.implement({stop:function(){return this.stopPropagation().preventDefault();},stopPropagation:function(){if(this.event.stopPropagation)this.event.stopPropagation();else this.event.cancelBubble=true;return this;},preventDefault:function(){if(this.event.preventDefault)this.event.preventDefault();else this.event.returnValue=false;return this;}});var Class=new Native({name:'Class',initialize:function(properties){properties=properties||{};var klass=function(){for(var key in this){if($type(this[key])!='function')this[key]=$unlink(this[key]);}
+this.constructor=klass;if(Class.prototyping)return this;var instance=(this.initialize)?this.initialize.apply(this,arguments):this;if(this.options&&this.options.initialize)this.options.initialize.call(this);return instance;};for(var mutator in Class.Mutators){if(!properties[mutator])continue;properties=Class.Mutators[mutator](properties,properties[mutator]);delete properties[mutator];}
+$extend(klass,this);klass.constructor=Class;klass.prototype=properties;return klass;}});Class.Mutators={Extends:function(self,klass){Class.prototyping=klass.prototype;var subclass=new klass;delete subclass.parent;subclass=Class.inherit(subclass,self);delete Class.prototyping;return subclass;},Implements:function(self,klasses){$splat(klasses).each(function(klass){Class.prototying=klass;$extend(self,($type(klass)=='class')?new klass:klass);delete Class.prototyping;});return self;}};Class.extend({inherit:function(object,properties){var caller=arguments.callee.caller;for(var key in properties){var override=properties[key];var previous=object[key];var type=$type(override);if(previous&&type=='function'){if(override!=previous){if(caller){override.__parent=previous;object[key]=override;}else{Class.override(object,key,override);}}}else if(type=='object'){object[key]=$merge(previous,override);}else{object[key]=override;}}
+if(caller)object.parent=function(){return arguments.callee.caller.__parent.apply(this,arguments);};return object;},override:function(object,name,method){var parent=Class.prototyping;if(parent&&object[name]!=parent[name])parent=null;var override=function(){var previous=this.parent;this.parent=parent?parent[name]:object[name];var value=method.apply(this,arguments);this.parent=previous;return value;};object[name]=override;}});Class.implement({implement:function(){var proto=this.prototype;$each(arguments,function(properties){Class.inherit(proto,properties);});return this;}});var Chain=new Class({$chain:[],chain:function(){this.$chain.extend(Array.flatten(arguments));return this;},callChain:function(){return(this.$chain.length)?this.$chain.shift().apply(this,arguments):false;},clearChain:function(){this.$chain.empty();return this;}});var Events=new Class({$events:{},addEvent:function(type,fn,internal){type=Events.removeOn(type);if(fn!=$empty){this.$events[type]=this.$events[type]||[];this.$events[type].include(fn);if(internal)fn.internal=true;}
+return this;},addEvents:function(events){for(var type in events)this.addEvent(type,events[type]);return this;},fireEvent:function(type,args,delay){type=Events.removeOn(type);if(!this.$events||!this.$events[type])return this;this.$events[type].each(function(fn){fn.create({'bind':this,'delay':delay,'arguments':args})();},this);return this;},removeEvent:function(type,fn){type=Events.removeOn(type);if(!this.$events[type])return this;if(!fn.internal)this.$events[type].erase(fn);return this;},removeEvents:function(events){if($type(events)=='object'){for(var type in events)this.removeEvent(type,events[type]);return this;}
+if(events)events=Events.removeOn(events);for(var type in this.$events){if(events&&events!=type)continue;var fns=this.$events[type];for(var i=fns.length;i--;i)this.removeEvent(type,fns[i]);}
+return this;}});Events.removeOn=function(string){return string.replace(/^on([A-Z])/,function(full,first){return first.toLowerCase();});};var Options=new Class({setOptions:function(){this.options=$merge.run([this.options].extend(arguments));if(!this.addEvent)return this;for(var option in this.options){if($type(this.options[option])!='function'||!(/^on[A-Z]/).test(option))continue;this.addEvent(option,this.options[option]);delete this.options[option];}
+return this;}});var Element=new Native({name:'Element',legacy:window.Element,initialize:function(tag,props){var konstructor=Element.Constructors.get(tag);if(konstructor)return konstructor(props);if(typeof tag=='string')return document.newElement(tag,props);return $(tag).set(props);},afterImplement:function(key,value){Element.Prototype[key]=value;if(Array[key])return;Elements.implement(key,function(){var items=[],elements=true;for(var i=0,j=this.length;i<j;i++){var returns=this[i][key].apply(this[i],arguments);items.push(returns);if(elements)elements=($type(returns)=='element');}
+return(elements)?new Elements(items):items;});}});Element.Prototype={$family:{name:'element'}};Element.Constructors=new Hash;var IFrame=new Native({name:'IFrame',generics:false,initialize:function(){var params=Array.link(arguments,{properties:Object.type,iframe:$defined});var props=params.properties||{};var iframe=$(params.iframe)||false;var onload=props.onload||$empty;delete props.onload;props.id=props.name=$pick(props.id,props.name,iframe.id,iframe.name,'IFrame_'+$time());iframe=new Element(iframe||'iframe',props);var onFrameLoad=function(){var host=$try(function(){return iframe.contentWindow.location.host;});if(host&&host==window.location.host){var win=new Window(iframe.contentWindow);new Document(iframe.contentWindow.document);$extend(win.Element.prototype,Element.Prototype);}
+onload.call(iframe.contentWindow,iframe.contentWindow.document);};(window.frames[props.id])?onFrameLoad():iframe.addListener('load',onFrameLoad);return iframe;}});var Elements=new Native({initialize:function(elements,options){options=$extend({ddup:true,cash:true},options);elements=elements||[];if(options.ddup||options.cash){var uniques={},returned=[];for(var i=0,l=elements.length;i<l;i++){var el=$.element(elements[i],!options.cash);if(options.ddup){if(uniques[el.uid])continue;uniques[el.uid]=true;}
+returned.push(el);}
+elements=returned;}
+return(options.cash)?$extend(elements,this):elements;}});Elements.implement({filter:function(filter,bind){if(!filter)return this;return new Elements(Array.filter(this,(typeof filter=='string')?function(item){return item.match(filter);}:filter,bind));}});Document.implement({newElement:function(tag,props){if(Browser.Engine.trident&&props){['name','type','checked'].each(function(attribute){if(!props[attribute])return;tag+=' '+attribute+'="'+props[attribute]+'"';if(attribute!='checked')delete props[attribute];});tag='<'+tag+'>';}
+return $.element(this.createElement(tag)).set(props);},newTextNode:function(text){return this.createTextNode(text);},getDocument:function(){return this;},getWindow:function(){return this.window;}});Window.implement({$:function(el,nocash){if(el&&el.$family&&el.uid)return el;var type=$type(el);return($[type])?$[type](el,nocash,this.document):null;},$$:function(selector){if(arguments.length==1&&typeof selector=='string')return this.document.getElements(selector);var elements=[];var args=Array.flatten(arguments);for(var i=0,l=args.length;i<l;i++){var item=args[i];switch($type(item)){case'element':elements.push(item);break;case'string':elements.extend(this.document.getElements(item,true));}}
+return new Elements(elements);},getDocument:function(){return this.document;},getWindow:function(){return this;}});$.string=function(id,nocash,doc){id=doc.getElementById(id);return(id)?$.element(id,nocash):null;};$.element=function(el,nocash){$uid(el);if(!nocash&&!el.$family&&!(/^object|embed$/i).test(el.tagName)){var proto=Element.Prototype;for(var p in proto)el[p]=proto[p];};return el;};$.object=function(obj,nocash,doc){if(obj.toElement)return $.element(obj.toElement(doc),nocash);return null;};$.textnode=$.whitespace=$.window=$.document=$arguments(0);Native.implement([Element,Document],{getElement:function(selector,nocash){return $(this.getElements(selector,true)[0]||null,nocash);},getElements:function(tags,nocash){tags=tags.split(',');var elements=[];var ddup=(tags.length>1);tags.each(function(tag){var partial=this.getElementsByTagName(tag.trim());(ddup)?elements.extend(partial):elements=partial;},this);return new Elements(elements,{ddup:ddup,cash:!nocash});}});(function(){var collected={},storage={};var props={input:'checked',option:'selected',textarea:(Browser.Engine.webkit&&Browser.Engine.version<420)?'innerHTML':'value'};var get=function(uid){return(storage[uid]||(storage[uid]={}));};var clean=function(item,retain){if(!item)return;var uid=item.uid;if(Browser.Engine.trident){if(item.clearAttributes){var clone=retain&&item.cloneNode(false);item.clearAttributes();if(clone)item.mergeAttributes(clone);}else if(item.removeEvents){item.removeEvents();}
+if((/object/i).test(item.tagName)){for(var p in item){if(typeof item[p]=='function')item[p]=$empty;}
+Element.dispose(item);}}
+if(!uid)return;collected[uid]=storage[uid]=null;};var purge=function(){Hash.each(collected,clean);if(Browser.Engine.trident)$A(document.getElementsByTagName('object')).each(clean);if(window.CollectGarbage)CollectGarbage();collected=storage=null;};var walk=function(element,walk,start,match,all,nocash){var el=element[start||walk];var elements=[];while(el){if(el.nodeType==1&&(!match||Element.match(el,match))){if(!all)return $(el,nocash);elements.push(el);}
+el=el[walk];}
+return(all)?new Elements(elements,{ddup:false,cash:!nocash}):null;};var attributes={'html':'innerHTML','class':'className','for':'htmlFor','text':(Browser.Engine.trident||(Browser.Engine.webkit&&Browser.Engine.version<420))?'innerText':'textContent'};var bools=['compact','nowrap','ismap','declare','noshade','checked','disabled','readonly','multiple','selected','noresize','defer'];var camels=['value','accessKey','cellPadding','cellSpacing','colSpan','frameBorder','maxLength','readOnly','rowSpan','tabIndex','useMap'];Hash.extend(attributes,bools.associate(bools));Hash.extend(attributes,camels.associate(camels.map(String.toLowerCase)));var inserters={before:function(context,element){if(element.parentNode)element.parentNode.insertBefore(context,element);},after:function(context,element){if(!element.parentNode)return;var next=element.nextSibling;(next)?element.parentNode.insertBefore(context,next):element.parentNode.appendChild(context);},bottom:function(context,element){element.appendChild(context);},top:function(context,element){var first=element.firstChild;(first)?element.insertBefore(context,first):element.appendChild(context);}};inserters.inside=inserters.bottom;Hash.each(inserters,function(inserter,where){where=where.capitalize();Element.implement('inject'+where,function(el){inserter(this,$(el,true));return this;});Element.implement('grab'+where,function(el){inserter($(el,true),this);return this;});});Element.implement({set:function(prop,value){switch($type(prop)){case'object':for(var p in prop)this.set(p,prop[p]);break;case'string':var property=Element.Properties.get(prop);(property&&property.set)?property.set.apply(this,Array.slice(arguments,1)):this.setProperty(prop,value);}
+return this;},get:function(prop){var property=Element.Properties.get(prop);return(property&&property.get)?property.get.apply(this,Array.slice(arguments,1)):this.getProperty(prop);},erase:function(prop){var property=Element.Properties.get(prop);(property&&property.erase)?property.erase.apply(this):this.removeProperty(prop);return this;},setProperty:function(attribute,value){var key=attributes[attribute];if(value==undefined)return this.removeProperty(attribute);if(key&&bools[attribute])value=!!value;(key)?this[key]=value:this.setAttribute(attribute,''+value);return this;},setProperties:function(attributes){for(var attribute in attributes)this.setProperty(attribute,attributes[attribute]);return this;},getProperty:function(attribute){var key=attributes[attribute];var value=(key)?this[key]:this.getAttribute(attribute,2);return(bools[attribute])?!!value:(key)?value:value||null;},getProperties:function(){var args=$A(arguments);return args.map(this.getProperty,this).associate(args);},removeProperty:function(attribute){var key=attributes[attribute];(key)?this[key]=(key&&bools[attribute])?false:'':this.removeAttribute(attribute);return this;},removeProperties:function(){Array.each(arguments,this.removeProperty,this);return this;},hasClass:function(className){return this.className.contains(className,' ');},addClass:function(className){if(!this.hasClass(className))this.className=(this.className+' '+className).clean();return this;},removeClass:function(className){this.className=this.className.replace(new RegExp('(^|\\s)'+className+'(?:\\s|$)'),'$1');return this;},toggleClass:function(className){return this.hasClass(className)?this.removeClass(className):this.addClass(className);},adopt:function(){Array.flatten(arguments).each(function(element){element=$(element,true);if(element)this.appendChild(element);},this);return this;},appendText:function(text,where){return this.grab(this.getDocument().newTextNode(text),where);},grab:function(el,where){inserters[where||'bottom']($(el,true),this);return this;},inject:function(el,where){inserters[where||'bottom'](this,$(el,true));return this;},replaces:function(el){el=$(el,true);el.parentNode.replaceChild(this,el);return this;},wraps:function(el,where){el=$(el,true);return this.replaces(el).grab(el,where);},getPrevious:function(match,nocash){return walk(this,'previousSibling',null,match,false,nocash);},getAllPrevious:function(match,nocash){return walk(this,'previousSibling',null,match,true,nocash);},getNext:function(match,nocash){return walk(this,'nextSibling',null,match,false,nocash);},getAllNext:function(match,nocash){return walk(this,'nextSibling',null,match,true,nocash);},getFirst:function(match,nocash){return walk(this,'nextSibling','firstChild',match,false,nocash);},getLast:function(match,nocash){return walk(this,'previousSibling','lastChild',match,false,nocash);},getParent:function(match,nocash){return walk(this,'parentNode',null,match,false,nocash);},getParents:function(match,nocash){return walk(this,'parentNode',null,match,true,nocash);},getChildren:function(match,nocash){return walk(this,'nextSibling','firstChild',match,true,nocash);},getWindow:function(){return this.ownerDocument.window;},getDocument:function(){return this.ownerDocument;},getElementById:function(id,nocash){var el=this.ownerDocument.getElementById(id);if(!el)return null;for(var parent=el.parentNode;parent!=this;parent=parent.parentNode){if(!parent)return null;}
+return $.element(el,nocash);},getSelected:function(){return new Elements($A(this.options).filter(function(option){return option.selected;}));},getComputedStyle:function(property){if(this.currentStyle)return this.currentStyle[property.camelCase()];var computed=this.getDocument().defaultView.getComputedStyle(this,null);return(computed)?computed.getPropertyValue([property.hyphenate()]):null;},toQueryString:function(){var queryString=[];this.getElements('input, select, textarea',true).each(function(el){if(!el.name||el.disabled)return;var value=(el.tagName.toLowerCase()=='select')?Element.getSelected(el).map(function(opt){return opt.value;}):((el.type=='radio'||el.type=='checkbox')&&!el.checked)?null:el.value;$splat(value).each(function(val){if(typeof val!='undefined')queryString.push(el.name+'='+encodeURIComponent(val));});});return queryString.join('&');},clone:function(contents,keepid){contents=contents!==false;var clone=this.cloneNode(contents);var clean=function(node,element){if(!keepid)node.removeAttribute('id');if(Browser.Engine.trident){node.clearAttributes();node.mergeAttributes(element);node.removeAttribute('uid');if(node.options){var no=node.options,eo=element.options;for(var j=no.length;j--;)no[j].selected=eo[j].selected;}}
+var prop=props[element.tagName.toLowerCase()];if(prop&&element[prop])node[prop]=element[prop];};if(contents){var ce=clone.getElementsByTagName('*'),te=this.getElementsByTagName('*');for(var i=ce.length;i--;)clean(ce[i],te[i]);}
+clean(clone,this);return $(clone);},destroy:function(){Element.empty(this);Element.dispose(this);clean(this,true);return null;},empty:function(){$A(this.childNodes).each(function(node){Element.destroy(node);});return this;},dispose:function(){return(this.parentNode)?this.parentNode.removeChild(this):this;},hasChild:function(el){el=$(el,true);if(!el)return false;if(Browser.Engine.webkit&&Browser.Engine.version<420)return $A(this.getElementsByTagName(el.tagName)).contains(el);return(this.contains)?(this!=el&&this.contains(el)):!!(this.compareDocumentPosition(el)&16);},match:function(tag){return(!tag||(tag==this)||(Element.get(this,'tag')==tag));}});Native.implement([Element,Window,Document],{addListener:function(type,fn){if(type=='unload'){var old=fn,self=this;fn=function(){self.removeListener('unload',fn);old();};}else{collected[this.uid]=this;}
+if(this.addEventListener)this.addEventListener(type,fn,false);else this.attachEvent('on'+type,fn);return this;},removeListener:function(type,fn){if(this.removeEventListener)this.removeEventListener(type,fn,false);else this.detachEvent('on'+type,fn);return this;},retrieve:function(property,dflt){var storage=get(this.uid),prop=storage[property];if(dflt!=undefined&&prop==undefined)prop=storage[property]=dflt;return $pick(prop);},store:function(property,value){var storage=get(this.uid);storage[property]=value;return this;},eliminate:function(property){var storage=get(this.uid);delete storage[property];return this;}});window.addListener('unload',purge);})();Element.Properties=new Hash;Element.Properties.style={set:function(style){this.style.cssText=style;},get:function(){return this.style.cssText;},erase:function(){this.style.cssText='';}};Element.Properties.tag={get:function(){return this.tagName.toLowerCase();}};Element.Properties.html=(function(){var wrapper=document.createElement('div');var translations={table:[1,'<table>','</table>'],select:[1,'<select>','</select>'],tbody:[2,'<table><tbody>','</tbody></table>'],tr:[3,'<table><tbody><tr>','</tr></tbody></table>']};translations.thead=translations.tfoot=translations.tbody;var html={set:function(){var html=Array.flatten(arguments).join('');var wrap=Browser.Engine.trident&&translations[this.get('tag')];if(wrap){var first=wrapper;first.innerHTML=wrap[1]+html+wrap[2];for(var i=wrap[0];i--;)first=first.firstChild;this.empty().adopt(first.childNodes);}else{this.innerHTML=html;}}};html.erase=html.set;return html;})();if(Browser.Engine.webkit&&Browser.Engine.version<420)Element.Properties.text={get:function(){if(this.innerText)return this.innerText;var temp=this.ownerDocument.newElement('div',{html:this.innerHTML}).inject(this.ownerDocument.body);var text=temp.innerText;temp.destroy();return text;}};Element.Properties.events={set:function(events){this.addEvents(events);}};Native.implement([Element,Window,Document],{addEvent:function(type,fn){var events=this.retrieve('events',{});events[type]=events[type]||{'keys':[],'values':[]};if(events[type].keys.contains(fn))return this;events[type].keys.push(fn);var realType=type,custom=Element.Events.get(type),condition=fn,self=this;if(custom){if(custom.onAdd)custom.onAdd.call(this,fn);if(custom.condition){condition=function(event){if(custom.condition.call(this,event))return fn.call(this,event);return true;};}
+realType=custom.base||realType;}
+var defn=function(){return fn.call(self);};var nativeEvent=Element.NativeEvents[realType];if(nativeEvent){if(nativeEvent==2){defn=function(event){event=new Event(event,self.getWindow());if(condition.call(self,event)===false)event.stop();};}
+this.addListener(realType,defn);}
+events[type].values.push(defn);return this;},removeEvent:function(type,fn){var events=this.retrieve('events');if(!events||!events[type])return this;var pos=events[type].keys.indexOf(fn);if(pos==-1)return this;events[type].keys.splice(pos,1);var value=events[type].values.splice(pos,1)[0];var custom=Element.Events.get(type);if(custom){if(custom.onRemove)custom.onRemove.call(this,fn);type=custom.base||type;}
+return(Element.NativeEvents[type])?this.removeListener(type,value):this;},addEvents:function(events){for(var event in events)this.addEvent(event,events[event]);return this;},removeEvents:function(events){if($type(events)=='object'){for(var type in events)this.removeEvent(type,events[type]);return this;}
+var attached=this.retrieve('events');if(!attached)return this;if(!events){for(var type in attached)this.removeEvents(type);this.eliminate('events');}else if(attached[events]){while(attached[events].keys[0])this.removeEvent(events,attached[events].keys[0]);attached[events]=null;}
+return this;},fireEvent:function(type,args,delay){var events=this.retrieve('events');if(!events||!events[type])return this;events[type].keys.each(function(fn){fn.create({'bind':this,'delay':delay,'arguments':args})();},this);return this;},cloneEvents:function(from,type){from=$(from);var fevents=from.retrieve('events');if(!fevents)return this;if(!type){for(var evType in fevents)this.cloneEvents(from,evType);}else if(fevents[type]){fevents[type].keys.each(function(fn){this.addEvent(type,fn);},this);}
+return this;}});Element.NativeEvents={click:2,dblclick:2,mouseup:2,mousedown:2,contextmenu:2,mousewheel:2,DOMMouseScroll:2,mouseover:2,mouseout:2,mousemove:2,selectstart:2,selectend:2,keydown:2,keypress:2,keyup:2,focus:2,blur:2,change:2,reset:2,select:2,submit:2,load:1,unload:1,beforeunload:2,resize:1,move:1,DOMContentLoaded:1,readystatechange:1,error:1,abort:1,scroll:1};(function(){var $check=function(event){var related=event.relatedTarget;if(related==undefined)return true;if(related===false)return false;return($type(this)!='document'&&related!=this&&related.prefix!='xul'&&!this.hasChild(related));};Element.Events=new Hash({mouseenter:{base:'mouseover',condition:$check},mouseleave:{base:'mouseout',condition:$check},mousewheel:{base:(Browser.Engine.gecko)?'DOMMouseScroll':'mousewheel'}});})();Element.Properties.styles={set:function(styles){this.setStyles(styles);}};Element.Properties.opacity={set:function(opacity,novisibility){if(!novisibility){if(opacity==0){if(this.style.visibility!='hidden')this.style.visibility='hidden';}else{if(this.style.visibility!='visible')this.style.visibility='visible';}}
+if(!this.currentStyle||!this.currentStyle.hasLayout)this.style.zoom=1;if(Browser.Engine.trident)this.style.filter=(opacity==1)?'':'alpha(opacity='+opacity*100+')';this.style.opacity=opacity;this.store('opacity',opacity);},get:function(){return this.retrieve('opacity',1);}};Element.implement({setOpacity:function(value){return this.set('opacity',value,true);},getOpacity:function(){return this.get('opacity');},setStyle:function(property,value){switch(property){case'opacity':return this.set('opacity',parseFloat(value));case'float':property=(Browser.Engine.trident)?'styleFloat':'cssFloat';}
+property=property.camelCase();if($type(value)!='string'){var map=(Element.Styles.get(property)||'@').split(' ');value=$splat(value).map(function(val,i){if(!map[i])return'';return($type(val)=='number')?map[i].replace('@',Math.round(val)):val;}).join(' ');}else if(value==String(Number(value))){value=Math.round(value);}
+this.style[property]=value;return this;},getStyle:function(property){switch(property){case'opacity':return this.get('opacity');case'float':property=(Browser.Engine.trident)?'styleFloat':'cssFloat';}
+property=property.camelCase();var result=this.style[property];if(!$chk(result)){result=[];for(var style in Element.ShortStyles){if(property!=style)continue;for(var s in Element.ShortStyles[style])result.push(this.getStyle(s));return result.join(' ');}
+result=this.getComputedStyle(property);}
+if(result){result=String(result);var color=result.match(/rgba?\([\d\s,]+\)/);if(color)result=result.replace(color[0],color[0].rgbToHex());}
+if(Browser.Engine.presto||(Browser.Engine.trident&&!$chk(parseInt(result)))){if(property.test(/^(height|width)$/)){var values=(property=='width')?['left','right']:['top','bottom'],size=0;values.each(function(value){size+=this.getStyle('border-'+value+'-width').toInt()+this.getStyle('padding-'+value).toInt();},this);return this['offset'+property.capitalize()]-size+'px';}
+if((Browser.Engine.presto)&&String(result).test('px'))return result;if(property.test(/(border(.+)Width|margin|padding)/))return'0px';}
+return result;},setStyles:function(styles){for(var style in styles)this.setStyle(style,styles[style]);return this;},getStyles:function(){var result={};Array.each(arguments,function(key){result[key]=this.getStyle(key);},this);return result;}});Element.Styles=new Hash({left:'@px',top:'@px',bottom:'@px',right:'@px',width:'@px',height:'@px',maxWidth:'@px',maxHeight:'@px',minWidth:'@px',minHeight:'@px',backgroundColor:'rgb(@, @, @)',backgroundPosition:'@px @px',color:'rgb(@, @, @)',fontSize:'@px',letterSpacing:'@px',lineHeight:'@px',clip:'rect(@px @px @px @px)',margin:'@px @px @px @px',padding:'@px @px @px @px',border:'@px @ rgb(@, @, @) @px @ rgb(@, @, @) @px @ rgb(@, @, @)',borderWidth:'@px @px @px @px',borderStyle:'@ @ @ @',borderColor:'rgb(@, @, @) rgb(@, @, @) rgb(@, @, @) rgb(@, @, @)',zIndex:'@','zoom':'@',fontWeight:'@',textIndent:'@px',opacity:'@'});Element.ShortStyles={margin:{},padding:{},border:{},borderWidth:{},borderStyle:{},borderColor:{}};['Top','Right','Bottom','Left'].each(function(direction){var Short=Element.ShortStyles;var All=Element.Styles;['margin','padding'].each(function(style){var sd=style+direction;Short[style][sd]=All[sd]='@px';});var bd='border'+direction;Short.border[bd]=All[bd]='@px @ rgb(@, @, @)';var bdw=bd+'Width',bds=bd+'Style',bdc=bd+'Color';Short[bd]={};Short.borderWidth[bdw]=Short[bd][bdw]=All[bdw]='@px';Short.borderStyle[bds]=Short[bd][bds]=All[bds]='@';Short.borderColor[bdc]=Short[bd][bdc]=All[bdc]='rgb(@, @, @)';});(function(){Element.implement({scrollTo:function(x,y){if(isBody(this)){this.getWindow().scrollTo(x,y);}else{this.scrollLeft=x;this.scrollTop=y;}
+return this;},getSize:function(){if(isBody(this))return this.getWindow().getSize();return{x:this.offsetWidth,y:this.offsetHeight};},getScrollSize:function(){if(isBody(this))return this.getWindow().getScrollSize();return{x:this.scrollWidth,y:this.scrollHeight};},getScroll:function(){if(isBody(this))return this.getWindow().getScroll();return{x:this.scrollLeft,y:this.scrollTop};},getScrolls:function(){var element=this,position={x:0,y:0};while(element&&!isBody(element)){position.x+=element.scrollLeft;position.y+=element.scrollTop;element=element.parentNode;}
+return position;},getOffsetParent:function(){var element=this;if(isBody(element))return null;if(!Browser.Engine.trident)return element.offsetParent;while((element=element.parentNode)&&!isBody(element)){if(styleString(element,'position')!='static')return element;}
+return null;},getOffsets:function(){if(Browser.Engine.trident){var bound=this.getBoundingClientRect(),html=this.getDocument().documentElement;return{x:bound.left+html.scrollLeft-html.clientLeft,y:bound.top+html.scrollTop-html.clientTop};}
+var element=this,position={x:0,y:0};if(isBody(this))return position;while(element&&!isBody(element)){position.x+=element.offsetLeft;position.y+=element.offsetTop;if(Browser.Engine.gecko){if(!borderBox(element)){position.x+=leftBorder(element);position.y+=topBorder(element);}
+var parent=element.parentNode;if(parent&&styleString(parent,'overflow')!='visible'){position.x+=leftBorder(parent);position.y+=topBorder(parent);}}else if(element!=this&&Browser.Engine.webkit){position.x+=leftBorder(element);position.y+=topBorder(element);}
+element=element.offsetParent;}
+if(Browser.Engine.gecko&&!borderBox(this)){position.x-=leftBorder(this);position.y-=topBorder(this);}
+return position;},getPosition:function(relative){if(isBody(this))return{x:0,y:0};var offset=this.getOffsets(),scroll=this.getScrolls();var position={x:offset.x-scroll.x,y:offset.y-scroll.y};var relativePosition=(relative&&(relative=$(relative)))?relative.getPosition():{x:0,y:0};return{x:position.x-relativePosition.x,y:position.y-relativePosition.y};},getCoordinates:function(element){if(isBody(this))return this.getWindow().getCoordinates();var position=this.getPosition(element),size=this.getSize();var obj={left:position.x,top:position.y,width:size.x,height:size.y};obj.right=obj.left+obj.width;obj.bottom=obj.top+obj.height;return obj;},computePosition:function(obj){return{left:obj.x-styleNumber(this,'margin-left'),top:obj.y-styleNumber(this,'margin-top')};},position:function(obj){return this.setStyles(this.computePosition(obj));}});Native.implement([Document,Window],{getSize:function(){var win=this.getWindow();if(Browser.Engine.presto||Browser.Engine.webkit)return{x:win.innerWidth,y:win.innerHeight};var doc=getCompatElement(this);return{x:doc.clientWidth,y:doc.clientHeight};},getScroll:function(){var win=this.getWindow();var doc=getCompatElement(this);return{x:win.pageXOffset||doc.scrollLeft,y:win.pageYOffset||doc.scrollTop};},getScrollSize:function(){var doc=getCompatElement(this);var min=this.getSize();return{x:Math.max(doc.scrollWidth,min.x),y:Math.max(doc.scrollHeight,min.y)};},getPosition:function(){return{x:0,y:0};},getCoordinates:function(){var size=this.getSize();return{top:0,left:0,bottom:size.y,right:size.x,height:size.y,width:size.x};}});var styleString=Element.getComputedStyle;function styleNumber(element,style){return styleString(element,style).toInt()||0;};function borderBox(element){return styleString(element,'-moz-box-sizing')=='border-box';};function topBorder(element){return styleNumber(element,'border-top-width');};function leftBorder(element){return styleNumber(element,'border-left-width');};function isBody(element){return(/^(?:body|html)$/i).test(element.tagName);};function getCompatElement(element){var doc=element.getDocument();return(!doc.compatMode||doc.compatMode=='CSS1Compat')?doc.html:doc.body;};})();Native.implement([Window,Document,Element],{getHeight:function(){return this.getSize().y;},getWidth:function(){return this.getSize().x;},getScrollTop:function(){return this.getScroll().y;},getScrollLeft:function(){return this.getScroll().x;},getScrollHeight:function(){return this.getScrollSize().y;},getScrollWidth:function(){return this.getScrollSize().x;},getTop:function(){return this.getPosition().y;},getLeft:function(){return this.getPosition().x;}});Native.implement([Document,Element],{getElements:function(expression,nocash){expression=expression.split(',');var items,local={};for(var i=0,l=expression.length;i<l;i++){var selector=expression[i],elements=Selectors.Utils.search(this,selector,local);if(i!=0&&elements.item)elements=$A(elements);items=(i==0)?elements:(items.item)?$A(items).concat(elements):items.concat(elements);}
+return new Elements(items,{ddup:(expression.length>1),cash:!nocash});}});Element.implement({match:function(selector){if(!selector||(selector==this))return true;var tagid=Selectors.Utils.parseTagAndID(selector);var tag=tagid[0],id=tagid[1];if(!Selectors.Filters.byID(this,id)||!Selectors.Filters.byTag(this,tag))return false;var parsed=Selectors.Utils.parseSelector(selector);return(parsed)?Selectors.Utils.filter(this,parsed,{}):true;}});var Selectors={Cache:{nth:{},parsed:{}}};Selectors.RegExps={id:(/#([\w-]+)/),tag:(/^(\w+|\*)/),quick:(/^(\w+|\*)$/),splitter:(/\s*([+>~\s])\s*([a-zA-Z#.*:\[])/g),combined:(/\.([\w-]+)|\[(\w+)(?:([!*^$~|]?=)(["']?)([^\4]*?)\4)?\]|:([\w-]+)(?:\(["']?(.*?)?["']?\)|$)/g)};Selectors.Utils={chk:function(item,uniques){if(!uniques)return true;var uid=$uid(item);if(!uniques[uid])return uniques[uid]=true;return false;},parseNthArgument:function(argument){if(Selectors.Cache.nth[argument])return Selectors.Cache.nth[argument];var parsed=argument.match(/^([+-]?\d*)?([a-z]+)?([+-]?\d*)?$/);if(!parsed)return false;var inta=parseInt(parsed[1]);var a=(inta||inta===0)?inta:1;var special=parsed[2]||false;var b=parseInt(parsed[3])||0;if(a!=0){b--;while(b<1)b+=a;while(b>=a)b-=a;}else{a=b;special='index';}
+switch(special){case'n':parsed={a:a,b:b,special:'n'};break;case'odd':parsed={a:2,b:0,special:'n'};break;case'even':parsed={a:2,b:1,special:'n'};break;case'first':parsed={a:0,special:'index'};break;case'last':parsed={special:'last-child'};break;case'only':parsed={special:'only-child'};break;default:parsed={a:(a-1),special:'index'};}
+return Selectors.Cache.nth[argument]=parsed;},parseSelector:function(selector){if(Selectors.Cache.parsed[selector])return Selectors.Cache.parsed[selector];var m,parsed={classes:[],pseudos:[],attributes:[]};while((m=Selectors.RegExps.combined.exec(selector))){var cn=m[1],an=m[2],ao=m[3],av=m[5],pn=m[6],pa=m[7];if(cn){parsed.classes.push(cn);}else if(pn){var parser=Selectors.Pseudo.get(pn);if(parser)parsed.pseudos.push({parser:parser,argument:pa});else parsed.attributes.push({name:pn,operator:'=',value:pa});}else if(an){parsed.attributes.push({name:an,operator:ao,value:av});}}
+if(!parsed.classes.length)delete parsed.classes;if(!parsed.attributes.length)delete parsed.attributes;if(!parsed.pseudos.length)delete parsed.pseudos;if(!parsed.classes&&!parsed.attributes&&!parsed.pseudos)parsed=null;return Selectors.Cache.parsed[selector]=parsed;},parseTagAndID:function(selector){var tag=selector.match(Selectors.RegExps.tag);var id=selector.match(Selectors.RegExps.id);return[(tag)?tag[1]:'*',(id)?id[1]:false];},filter:function(item,parsed,local){var i;if(parsed.classes){for(i=parsed.classes.length;i--;i){var cn=parsed.classes[i];if(!Selectors.Filters.byClass(item,cn))return false;}}
+if(parsed.attributes){for(i=parsed.attributes.length;i--;i){var att=parsed.attributes[i];if(!Selectors.Filters.byAttribute(item,att.name,att.operator,att.value))return false;}}
+if(parsed.pseudos){for(i=parsed.pseudos.length;i--;i){var psd=parsed.pseudos[i];if(!Selectors.Filters.byPseudo(item,psd.parser,psd.argument,local))return false;}}
+return true;},getByTagAndID:function(ctx,tag,id){if(id){var item=(ctx.getElementById)?ctx.getElementById(id,true):Element.getElementById(ctx,id,true);return(item&&Selectors.Filters.byTag(item,tag))?[item]:[];}else{return ctx.getElementsByTagName(tag);}},search:function(self,expression,local){var splitters=[];var selectors=expression.trim().replace(Selectors.RegExps.splitter,function(m0,m1,m2){splitters.push(m1);return':)'+m2;}).split(':)');var items,filtered,item;for(var i=0,l=selectors.length;i<l;i++){var selector=selectors[i];if(i==0&&Selectors.RegExps.quick.test(selector)){items=self.getElementsByTagName(selector);continue;}
+var splitter=splitters[i-1];var tagid=Selectors.Utils.parseTagAndID(selector);var tag=tagid[0],id=tagid[1];if(i==0){items=Selectors.Utils.getByTagAndID(self,tag,id);}else{var uniques={},found=[];for(var j=0,k=items.length;j<k;j++)found=Selectors.Getters[splitter](found,items[j],tag,id,uniques);items=found;}
+var parsed=Selectors.Utils.parseSelector(selector);if(parsed){filtered=[];for(var m=0,n=items.length;m<n;m++){item=items[m];if(Selectors.Utils.filter(item,parsed,local))filtered.push(item);}
+items=filtered;}}
+return items;}};Selectors.Getters={' ':function(found,self,tag,id,uniques){var items=Selectors.Utils.getByTagAndID(self,tag,id);for(var i=0,l=items.length;i<l;i++){var item=items[i];if(Selectors.Utils.chk(item,uniques))found.push(item);}
+return found;},'>':function(found,self,tag,id,uniques){var children=Selectors.Utils.getByTagAndID(self,tag,id);for(var i=0,l=children.length;i<l;i++){var child=children[i];if(child.parentNode==self&&Selectors.Utils.chk(child,uniques))found.push(child);}
+return found;},'+':function(found,self,tag,id,uniques){while((self=self.nextSibling)){if(self.nodeType==1){if(Selectors.Utils.chk(self,uniques)&&Selectors.Filters.byTag(self,tag)&&Selectors.Filters.byID(self,id))found.push(self);break;}}
+return found;},'~':function(found,self,tag,id,uniques){while((self=self.nextSibling)){if(self.nodeType==1){if(!Selectors.Utils.chk(self,uniques))break;if(Selectors.Filters.byTag(self,tag)&&Selectors.Filters.byID(self,id))found.push(self);}}
+return found;}};Selectors.Filters={byTag:function(self,tag){return(tag=='*'||(self.tagName&&self.tagName.toLowerCase()==tag));},byID:function(self,id){return(!id||(self.id&&self.id==id));},byClass:function(self,klass){return(self.className&&self.className.contains(klass,' '));},byPseudo:function(self,parser,argument,local){return parser.call(self,argument,local);},byAttribute:function(self,name,operator,value){var result=Element.prototype.getProperty.call(self,name);if(!result)return(operator=='!=');if(!operator||value==undefined)return true;switch(operator){case'=':return(result==value);case'*=':return(result.contains(value));case'^=':return(result.substr(0,value.length)==value);case'$=':return(result.substr(result.length-value.length)==value);case'!=':return(result!=value);case'~=':return result.contains(value,' ');case'|=':return result.contains(value,'-');}
+return false;}};Selectors.Pseudo=new Hash({checked:function(){return this.checked;},empty:function(){return!(this.innerText||this.textContent||'').length;},not:function(selector){return!Element.match(this,selector);},contains:function(text){return(this.innerText||this.textContent||'').contains(text);},'first-child':function(){return Selectors.Pseudo.index.call(this,0);},'last-child':function(){var element=this;while((element=element.nextSibling)){if(element.nodeType==1)return false;}
+return true;},'only-child':function(){var prev=this;while((prev=prev.previousSibling)){if(prev.nodeType==1)return false;}
+var next=this;while((next=next.nextSibling)){if(next.nodeType==1)return false;}
+return true;},'nth-child':function(argument,local){argument=(argument==undefined)?'n':argument;var parsed=Selectors.Utils.parseNthArgument(argument);if(parsed.special!='n')return Selectors.Pseudo[parsed.special].call(this,parsed.a,local);var count=0;local.positions=local.positions||{};var uid=$uid(this);if(!local.positions[uid]){var self=this;while((self=self.previousSibling)){if(self.nodeType!=1)continue;count++;var position=local.positions[$uid(self)];if(position!=undefined){count=position+count;break;}}
+local.positions[uid]=count;}
+return(local.positions[uid]%parsed.a==parsed.b);},index:function(index){var element=this,count=0;while((element=element.previousSibling)){if(element.nodeType==1&&++count>index)return false;}
+return(count==index);},even:function(argument,local){return Selectors.Pseudo['nth-child'].call(this,'2n+1',local);},odd:function(argument,local){return Selectors.Pseudo['nth-child'].call(this,'2n',local);}});Element.Events.domready={onAdd:function(fn){if(Browser.loaded)fn.call(this);}};(function(){var domready=function(){if(Browser.loaded)return;Browser.loaded=true;window.fireEvent('domready');document.fireEvent('domready');};if(Browser.Engine.trident){var temp=document.createElement('div');(function(){($try(function(){temp.doScroll('left');return $(temp).inject(document.body).set('html','temp').dispose();}))?domready():arguments.callee.delay(50);})();}else if(Browser.Engine.webkit&&Browser.Engine.version<525){(function(){(['loaded','complete'].contains(document.readyState))?domready():arguments.callee.delay(50);})();}else{window.addEvent('load',domready);document.addEvent('DOMContentLoaded',domready);}})();var Cookie=new Class({Implements:Options,options:{path:false,domain:false,duration:false,secure:false,document:document},initialize:function(key,options){this.key=key;this.setOptions(options);},write:function(value){value=encodeURIComponent(value);if(this.options.domain)value+='; domain='+this.options.domain;if(this.options.path)value+='; path='+this.options.path;if(this.options.duration){var date=new Date();date.setTime(date.getTime()+this.options.duration*24*60*60*1000);value+='; expires='+date.toGMTString();}
+if(this.options.secure)value+='; secure';this.options.document.cookie=this.key+'='+value;return this;},read:function(){var value=this.options.document.cookie.match('(?:^|;)\\s*'+this.key.escapeRegExp()+'=([^;]*)');return(value)?decodeURIComponent(value[1]):null;},dispose:function(){new Cookie(this.key,$merge(this.options,{duration:-1})).write('');return this;}});Cookie.write=function(key,value,options){return new Cookie(key,options).write(value);};Cookie.read=function(key){return new Cookie(key).read();};Cookie.dispose=function(key,options){return new Cookie(key,options).dispose();};var Fx=new Class({Implements:[Chain,Events,Options],options:{fps:50,unit:false,duration:500,link:'ignore'},initialize:function(options){this.subject=this.subject||this;this.setOptions(options);this.options.duration=Fx.Durations[this.options.duration]||this.options.duration.toInt();var wait=this.options.wait;if(wait===false)this.options.link='cancel';},getTransition:function(){return function(p){return-(Math.cos(Math.PI*p)-1)/2;};},step:function(){var time=$time();if(time<this.time+this.options.duration){var delta=this.transition((time-this.time)/this.options.duration);this.set(this.compute(this.from,this.to,delta));}else{this.set(this.compute(this.from,this.to,1));this.complete();}},set:function(now){return now;},compute:function(from,to,delta){return Fx.compute(from,to,delta);},check:function(caller){if(!this.timer)return true;switch(this.options.link){case'cancel':this.cancel();return true;case'chain':this.chain(caller.bind(this,Array.slice(arguments,1)));return false;}
+return false;},start:function(from,to){if(!this.check(arguments.callee,from,to))return this;this.from=from;this.to=to;this.time=0;this.transition=this.getTransition();this.startTimer();this.onStart();return this;},complete:function(){if(this.stopTimer())this.onComplete();return this;},cancel:function(){if(this.stopTimer())this.onCancel();return this;},onStart:function(){this.fireEvent('start',this.subject);},onComplete:function(){this.fireEvent('complete',this.subject);if(!this.callChain())this.fireEvent('chainComplete',this.subject);},onCancel:function(){this.fireEvent('cancel',this.subject).clearChain();},pause:function(){this.stopTimer();return this;},resume:function(){this.startTimer();return this;},stopTimer:function(){if(!this.timer)return false;this.time=$time()-this.time;this.timer=$clear(this.timer);return true;},startTimer:function(){if(this.timer)return false;this.time=$time()-this.time;this.timer=this.step.periodical(Math.round(1000/this.options.fps),this);return true;}});Fx.compute=function(from,to,delta){return(to-from)*delta+from;};Fx.Durations={'short':250,'normal':500,'long':1000};Fx.CSS=new Class({Extends:Fx,prepare:function(element,property,values){values=$splat(values);var values1=values[1];if(!$chk(values1)){values[1]=values[0];values[0]=element.getStyle(property);}
+var parsed=values.map(this.parse);return{from:parsed[0],to:parsed[1]};},parse:function(value){value=$lambda(value)();value=(typeof value=='string')?value.split(' '):$splat(value);return value.map(function(val){val=String(val);var found=false;Fx.CSS.Parsers.each(function(parser,key){if(found)return;var parsed=parser.parse(val);if($chk(parsed))found={value:parsed,parser:parser};});found=found||{value:val,parser:Fx.CSS.Parsers.String};return found;});},compute:function(from,to,delta){var computed=[];(Math.min(from.length,to.length)).times(function(i){computed.push({value:from[i].parser.compute(from[i].value,to[i].value,delta),parser:from[i].parser});});computed.$family={name:'fx:css:value'};return computed;},serve:function(value,unit){if($type(value)!='fx:css:value')value=this.parse(value);var returned=[];value.each(function(bit){returned=returned.concat(bit.parser.serve(bit.value,unit));});return returned;},render:function(element,property,value,unit){element.setStyle(property,this.serve(value,unit));},search:function(selector){if(Fx.CSS.Cache[selector])return Fx.CSS.Cache[selector];var to={};Array.each(document.styleSheets,function(sheet,j){var href=sheet.href;if(href&&href.contains('://')&&!href.contains(document.domain))return;var rules=sheet.rules||sheet.cssRules;Array.each(rules,function(rule,i){if(!rule.style)return;var selectorText=(rule.selectorText)?rule.selectorText.replace(/^\w+/,function(m){return m.toLowerCase();}):null;if(!selectorText||!selectorText.test('^'+selector+'$'))return;Element.Styles.each(function(value,style){if(!rule.style[style]||Element.ShortStyles[style])return;value=String(rule.style[style]);to[style]=(value.test(/^rgb/))?value.rgbToHex():value;});});});return Fx.CSS.Cache[selector]=to;}});Fx.CSS.Cache={};Fx.CSS.Parsers=new Hash({Color:{parse:function(value){if(value.match(/^#[0-9a-f]{3,6}$/i))return value.hexToRgb(true);return((value=value.match(/(\d+),\s*(\d+),\s*(\d+)/)))?[value[1],value[2],value[3]]:false;},compute:function(from,to,delta){return from.map(function(value,i){return Math.round(Fx.compute(from[i],to[i],delta));});},serve:function(value){return value.map(Number);}},Number:{parse:parseFloat,compute:Fx.compute,serve:function(value,unit){return(unit)?value+unit:value;}},String:{parse:$lambda(false),compute:$arguments(1),serve:$arguments(0)}});Fx.Tween=new Class({Extends:Fx.CSS,initialize:function(element,options){this.element=this.subject=$(element);this.parent(options);},set:function(property,now){if(arguments.length==1){now=property;property=this.property||this.options.property;}
+this.render(this.element,property,now,this.options.unit);return this;},start:function(property,from,to){if(!this.check(arguments.callee,property,from,to))return this;var args=Array.flatten(arguments);this.property=this.options.property||args.shift();var parsed=this.prepare(this.element,this.property,args);return this.parent(parsed.from,parsed.to);}});Element.Properties.tween={set:function(options){var tween=this.retrieve('tween');if(tween)tween.cancel();return this.eliminate('tween').store('tween:options',$extend({link:'cancel'},options));},get:function(options){if(options||!this.retrieve('tween')){if(options||!this.retrieve('tween:options'))this.set('tween',options);this.store('tween',new Fx.Tween(this,this.retrieve('tween:options')));}
+return this.retrieve('tween');}};Element.implement({tween:function(property,from,to){this.get('tween').start(arguments);return this;},fade:function(how){var fade=this.get('tween'),o='opacity',toggle;how=$pick(how,'toggle');switch(how){case'in':fade.start(o,1);break;case'out':fade.start(o,0);break;case'show':fade.set(o,1);break;case'hide':fade.set(o,0);break;case'toggle':var flag=this.retrieve('fade:flag',this.get('opacity')==1);fade.start(o,(flag)?0:1);this.store('fade:flag',!flag);toggle=true;break;default:fade.start(o,arguments);}
+if(!toggle)this.eliminate('fade:flag');return this;},highlight:function(start,end){if(!end){end=this.retrieve('highlight:original',this.getStyle('background-color'));end=(end=='transparent')?'#fff':end;}
+var tween=this.get('tween');tween.start('background-color',start||'#ffff88',end).chain(function(){this.setStyle('background-color',this.retrieve('highlight:original'));tween.callChain();}.bind(this));return this;}});Fx.Morph=new Class({Extends:Fx.CSS,initialize:function(element,options){this.element=this.subject=$(element);this.parent(options);},set:function(now){if(typeof now=='string')now=this.search(now);for(var p in now)this.render(this.element,p,now[p],this.options.unit);return this;},compute:function(from,to,delta){var now={};for(var p in from)now[p]=this.parent(from[p],to[p],delta);return now;},start:function(properties){if(!this.check(arguments.callee,properties))return this;if(typeof properties=='string')properties=this.search(properties);var from={},to={};for(var p in properties){var parsed=this.prepare(this.element,p,properties[p]);from[p]=parsed.from;to[p]=parsed.to;}
+return this.parent(from,to);}});Element.Properties.morph={set:function(options){var morph=this.retrieve('morph');if(morph)morph.cancel();return this.eliminate('morph').store('morph:options',$extend({link:'cancel'},options));},get:function(options){if(options||!this.retrieve('morph')){if(options||!this.retrieve('morph:options'))this.set('morph',options);this.store('morph',new Fx.Morph(this,this.retrieve('morph:options')));}
+return this.retrieve('morph');}};Element.implement({morph:function(props){this.get('morph').start(props);return this;}});Fx.implement({getTransition:function(){var trans=this.options.transition||Fx.Transitions.Sine.easeInOut;if(typeof trans=='string'){var data=trans.split(':');trans=Fx.Transitions;trans=trans[data[0]]||trans[data[0].capitalize()];if(data[1])trans=trans['ease'+data[1].capitalize()+(data[2]?data[2].capitalize():'')];}
+return trans;}});Fx.Transition=function(transition,params){params=$splat(params);return $extend(transition,{easeIn:function(pos){return transition(pos,params);},easeOut:function(pos){return 1-transition(1-pos,params);},easeInOut:function(pos){return(pos<=0.5)?transition(2*pos,params)/2:(2-transition(2*(1-pos),params))/2;}});};Fx.Transitions=new Hash({linear:$arguments(0)});Fx.Transitions.extend=function(transitions){for(var transition in transitions)Fx.Transitions[transition]=new Fx.Transition(transitions[transition]);};Fx.Transitions.extend({Pow:function(p,x){return Math.pow(p,x[0]||6);},Expo:function(p){return Math.pow(2,8*(p-1));},Circ:function(p){return 1-Math.sin(Math.acos(p));},Sine:function(p){return 1-Math.sin((1-p)*Math.PI/2);},Back:function(p,x){x=x[0]||1.618;return Math.pow(p,2)*((x+1)*p-x);},Bounce:function(p){var value;for(var a=0,b=1;1;a+=b,b/=2){if(p>=(7-4*a)/11){value=b*b-Math.pow((11-6*a-11*p)/4,2);break;}}
+return value;},Elastic:function(p,x){return Math.pow(2,10*--p)*Math.cos(20*p*Math.PI*(x[0]||1)/3);}});['Quad','Cubic','Quart','Quint'].each(function(transition,i){Fx.Transitions[transition]=new Fx.Transition(function(p){return Math.pow(p,[i+2]);});});var Request=new Class({Implements:[Chain,Events,Options],options:{url:'',data:'',headers:{'X-Requested-With':'XMLHttpRequest','Accept':'text/javascript, text/html, application/xml, text/xml, */*'},async:true,format:false,method:'post',link:'ignore',isSuccess:null,emulation:true,urlEncoded:true,encoding:'utf-8',evalScripts:false,evalResponse:false},initialize:function(options){this.xhr=new Browser.Request();this.setOptions(options);this.options.isSuccess=this.options.isSuccess||this.isSuccess;this.headers=new Hash(this.options.headers);},onStateChange:function(){if(this.xhr.readyState!=4||!this.running)return;this.running=false;this.status=0;$try(function(){this.status=this.xhr.status;}.bind(this));if(this.options.isSuccess.call(this,this.status)){this.response={text:this.xhr.responseText,xml:this.xhr.responseXML};this.success(this.response.text,this.response.xml);}else{this.response={text:null,xml:null};this.failure();}
+this.xhr.onreadystatechange=$empty;},isSuccess:function(){return((this.status>=200)&&(this.status<300));},processScripts:function(text){if(this.options.evalResponse||(/(ecma|java)script/).test(this.getHeader('Content-type')))return $exec(text);return text.stripScripts(this.options.evalScripts);},success:function(text,xml){this.onSuccess(this.processScripts(text),xml);},onSuccess:function(){this.fireEvent('complete',arguments).fireEvent('success',arguments).callChain();},failure:function(){this.onFailure();},onFailure:function(){this.fireEvent('complete').fireEvent('failure',this.xhr);},setHeader:function(name,value){this.headers.set(name,value);return this;},getHeader:function(name){return $try(function(){return this.xhr.getResponseHeader(name);}.bind(this));},check:function(caller){if(!this.running)return true;switch(this.options.link){case'cancel':this.cancel();return true;case'chain':this.chain(caller.bind(this,Array.slice(arguments,1)));return false;}
+return false;},send:function(options){if(!this.check(arguments.callee,options))return this;this.running=true;var type=$type(options);if(type=='string'||type=='element')options={data:options};var old=this.options;options=$extend({data:old.data,url:old.url,method:old.method},options);var data=options.data,url=options.url,method=options.method;switch($type(data)){case'element':data=$(data).toQueryString();break;case'object':case'hash':data=Hash.toQueryString(data);}
+if(this.options.format){var format='format='+this.options.format;data=(data)?format+'&'+data:format;}
+if(this.options.emulation&&['put','delete'].contains(method)){var _method='_method='+method;data=(data)?_method+'&'+data:_method;method='post';}
+if(this.options.urlEncoded&&method=='post'){var encoding=(this.options.encoding)?'; charset='+this.options.encoding:'';this.headers.set('Content-type','application/x-www-form-urlencoded'+encoding);}
+if(data&&method=='get'){url=url+(url.contains('?')?'&':'?')+data;data=null;}
+this.xhr.open(method.toUpperCase(),url,this.options.async);this.xhr.onreadystatechange=this.onStateChange.bind(this);this.headers.each(function(value,key){try{this.xhr.setRequestHeader(key,value);}catch(e){this.fireEvent('exception',[key,value]);}},this);this.fireEvent('request');this.xhr.send(data);if(!this.options.async)this.onStateChange();return this;},cancel:function(){if(!this.running)return this;this.running=false;this.xhr.abort();this.xhr.onreadystatechange=$empty;this.xhr=new Browser.Request();this.fireEvent('cancel');return this;}});(function(){var methods={};['get','post','put','delete','GET','POST','PUT','DELETE'].each(function(method){methods[method]=function(){var params=Array.link(arguments,{url:String.type,data:$defined});return this.send($extend(params,{method:method.toLowerCase()}));};});Request.implement(methods);})();Element.Properties.send={set:function(options){var send=this.retrieve('send');if(send)send.cancel();return this.eliminate('send').store('send:options',$extend({data:this,link:'cancel',method:this.get('method')||'post',url:this.get('action')},options));},get:function(options){if(options||!this.retrieve('send')){if(options||!this.retrieve('send:options'))this.set('send',options);this.store('send',new Request(this.retrieve('send:options')));}
+return this.retrieve('send');}};Element.implement({send:function(url){var sender=this.get('send');sender.send({data:this,url:url||sender.options.url});return this;}});Request.HTML=new Class({Extends:Request,options:{update:false,evalScripts:true,filter:false},processHTML:function(text){var match=text.match(/<body[^>]*>([\s\S]*?)<\/body>/i);text=(match)?match[1]:text;var container=new Element('div');return $try(function(){var root='<root>'+text+'</root>',doc;if(Browser.Engine.trident){doc=new ActiveXObject('Microsoft.XMLDOM');doc.async=false;doc.loadXML(root);}else{doc=new DOMParser().parseFromString(root,'text/xml');}
+root=doc.getElementsByTagName('root')[0];for(var i=0,k=root.childNodes.length;i<k;i++){var child=Element.clone(root.childNodes[i],true,true);if(child)container.grab(child);}
+return container;})||container.set('html',text);},success:function(text){var options=this.options,response=this.response;response.html=text.stripScripts(function(script){response.javascript=script;});var temp=this.processHTML(response.html);response.tree=temp.childNodes;response.elements=temp.getElements('*');if(options.filter)response.tree=response.elements.filter(options.filter);if(options.update)$(options.update).empty().set('html',response.html);if(options.evalScripts)$exec(response.javascript);this.onSuccess(response.tree,response.elements,response.html,response.javascript);}});Element.Properties.load={set:function(options){var load=this.retrieve('load');if(load)load.cancel();return this.eliminate('load').store('load:options',$extend({data:this,link:'cancel',update:this,method:'get'},options));},get:function(options){if(options||!this.retrieve('load')){if(options||!this.retrieve('load:options'))this.set('load',options);this.store('load',new Request.HTML(this.retrieve('load:options')));}
+return this.retrieve('load');}};Element.implement({load:function(){this.get('load').send(Array.link(arguments,{data:Object.type,url:String.type}));return this;}});
+//MooTools More, <http://mootools.net/more>. Copyright (c) 2006-2008 Valerio Proietti, <http://mad4milk.net>, MIT Style License.
+
+Fx.Slide=new Class({Extends:Fx,options:{mode:'vertical'},initialize:function(element,options){this.addEvent('complete',function(){this.open=(this.wrapper['offset'+this.layout.capitalize()]!=0);if(this.open&&Browser.Engine.webkit419)this.element.dispose().inject(this.wrapper);},true);this.element=this.subject=$(element);this.parent(options);var wrapper=this.element.retrieve('wrapper');this.wrapper=wrapper||new Element('div',{styles:$extend(this.element.getStyles('margin','position'),{'overflow':'hidden'})}).wraps(this.element);this.element.store('wrapper',this.wrapper).setStyle('margin',0);this.now=[];this.open=true;},vertical:function(){this.margin='margin-top';this.layout='height';this.offset=this.element.offsetHeight;},horizontal:function(){this.margin='margin-left';this.layout='width';this.offset=this.element.offsetWidth;},set:function(now){this.element.setStyle(this.margin,now[0]);this.wrapper.setStyle(this.layout,now[1]);return this;},compute:function(from,to,delta){var now=[];var x=2;x.times(function(i){now[i]=Fx.compute(from[i],to[i],delta);});return now;},start:function(how,mode){if(!this.check(arguments.callee,how,mode))return this;this[mode||this.options.mode]();var margin=this.element.getStyle(this.margin).toInt();var layout=this.wrapper.getStyle(this.layout).toInt();var caseIn=[[margin,layout],[0,this.offset]];var caseOut=[[margin,layout],[-this.offset,0]];var start;switch(how){case'in':start=caseIn;break;case'out':start=caseOut;break;case'toggle':start=(this.wrapper['offset'+this.layout.capitalize()]==0)?caseIn:caseOut;}
+return this.parent(start[0],start[1]);},slideIn:function(mode){return this.start('in',mode);},slideOut:function(mode){return this.start('out',mode);},hide:function(mode){this[mode||this.options.mode]();this.open=false;return this.set([-this.offset,0]);},show:function(mode){this[mode||this.options.mode]();this.open=true;return this.set([0,this.offset]);},toggle:function(mode){return this.start('toggle',mode);}});Element.Properties.slide={set:function(options){var slide=this.retrieve('slide');if(slide)slide.cancel();return this.eliminate('slide').store('slide:options',$extend({link:'cancel'},options));},get:function(options){if(options||!this.retrieve('slide')){if(options||!this.retrieve('slide:options'))this.set('slide',options);this.store('slide',new Fx.Slide(this,this.retrieve('slide:options')));}
+return this.retrieve('slide');}};Element.implement({slide:function(how,mode){how=how||'toggle';var slide=this.get('slide'),toggle;switch(how){case'hide':slide.hide(mode);break;case'show':slide.show(mode);break;case'toggle':var flag=this.retrieve('slide:flag',slide.open);slide[(flag)?'slideOut':'slideIn'](mode);this.store('slide:flag',!flag);toggle=true;break;default:slide.start(how,mode);}
+if(!toggle)this.eliminate('slide:flag');return this;}});Fx.Scroll=new Class({Extends:Fx,options:{offset:{'x':0,'y':0},wheelStops:true},initialize:function(element,options){this.element=this.subject=$(element);this.parent(options);var cancel=this.cancel.bind(this,false);if($type(this.element)!='element')this.element=$(this.element.getDocument().body);var stopper=this.element;if(this.options.wheelStops){this.addEvent('start',function(){stopper.addEvent('mousewheel',cancel);},true);this.addEvent('complete',function(){stopper.removeEvent('mousewheel',cancel);},true);}},set:function(){var now=Array.flatten(arguments);this.element.scrollTo(now[0],now[1]);},compute:function(from,to,delta){var now=[];var x=2;x.times(function(i){now.push(Fx.compute(from[i],to[i],delta));});return now;},start:function(x,y){if(!this.check(arguments.callee,x,y))return this;var offsetSize=this.element.getSize(),scrollSize=this.element.getScrollSize();var scroll=this.element.getScroll(),values={x:x,y:y};for(var z in values){var max=scrollSize[z]-offsetSize[z];if($chk(values[z]))values[z]=($type(values[z])=='number')?values[z].limit(0,max):max;else values[z]=scroll[z];values[z]+=this.options.offset[z];}
+return this.parent([scroll.x,scroll.y],[values.x,values.y]);},toTop:function(){return this.start(false,0);},toLeft:function(){return this.start(0,false);},toRight:function(){return this.start('right',false);},toBottom:function(){return this.start(false,'bottom');},toElement:function(el){var position=$(el).getPosition(this.element);return this.start(position.x,position.y);}});var SmoothScroll=new Class({Extends:Fx.Scroll,initialize:function(options,context){context=context||document;var doc=context.getDocument(),win=context.getWindow();this.parent(doc,options);this.links=(this.options.links)?$$(this.options.links):$$(doc.links);var location=win.location.href.match(/^[^#]*/)[0]+'#';this.links.each(function(link){if(link.href.indexOf(location)!=0)return;var anchor=link.href.substr(location.length);if(anchor&&$(anchor))this.useLink(link,anchor);},this);if(!Browser.Engine.webkit419)this.addEvent('complete',function(){win.location.hash=this.anchor;},true);},useLink:function(link,anchor){link.addEvent('click',function(event){this.anchor=anchor;this.toElement(anchor);event.stop();}.bind(this));}});
\ No newline at end of file
diff --git a/vendors/js/showdown.js b/vendors/js/showdown.js
new file mode 100644
index 0000000..6c659fc
--- /dev/null
+++ b/vendors/js/showdown.js
@@ -0,0 +1,54 @@
+<!--
+var Showdown={};Showdown.converter=function(baseUrl){var g_urls;var g_titles;var g_html_blocks;var g_list_level=0;this.makeHtml=function(text){g_urls=new Array();g_titles=new Array();g_html_blocks=new Array();text=text.replace(/~/g,"~T");text=text.replace(/\$/g,"~D");text=text.replace(/\r\n/g,"\n");text=text.replace(/\r/g,"\n");text="\n\n"+text+"\n\n";text=_Detab(text);text=text.replace(/^[ \t]+$/mg,"");text=_HashHTMLBlocks(text);text=_StripLinkDefinitions(text);text=_RunBlockGamut(text);text=_UnescapeSpecialChars(text);text=text.replace(/~D/g,"$$");text=text.replace(/~T/g,"~");return text;}
+var _StripLinkDefinitions=function(text){var text=text.replace(/^[ ]{0,3}\[(.+)\]:[ \t]*\n?[ \t]*<?(\S+?)>?[ \t]*\n?[ \t]*(?:(\n*)["(](.+?)[")][ \t]*)?(?:\n+|\Z)/gm,function(wholeMatch,m1,m2,m3,m4){m1=m1.toLowerCase();g_urls[m1]=_EncodeAmpsAndAngles(m2);if(m3){return m3+m4;}else if(m4){g_titles[m1]=m4.replace(/"/g,""");}
+return"";});return text;}
+var _HashHTMLBlocks=function(text){text=text.replace(/\n/g,"\n\n");var block_tags_a="p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del"
+var block_tags_b="p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math"
+text=text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del)\b[^\r]*?\n<\/\2>[ \t]*(?=\n+))/gm,hashElement);text=text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math)\b[^\r]*?.*<\/\2>[ \t]*(?=\n+)\n)/gm,hashElement);text=text.replace(/(\n[ ]{0,3}(<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g,hashElement);text=text.replace(/(\n\n[ ]{0,3}<!(--[^\r]*?--\s*)+>[ \t]*(?=\n{2,}))/g,hashElement);text=text.replace(/\n\n/g,"\n");return text;}
+var hashElement=function(wholeMatch,m1){var blockText=m1;blockText=blockText.replace(/\n\n/g,"\n");blockText=blockText.replace(/^\n/,"");blockText=blockText.replace(/\n+$/g,"");blockText="\n\n~K"+(g_html_blocks.push(blockText)-1)+"K\n\n";return blockText;};var _RunBlockGamut=function(text){text=_DoHeaders(text);var key=hashBlock("<hr />");text=text.replace(/^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$/gm,key);text=text.replace(/^[ ]{0,2}([ ]?\-[ ]?){3,}[ \t]*$/gm,key);text=text.replace(/^[ ]{0,2}([ ]?\_[ ]?){3,}[ \t]*$/gm,key);text=_DoLists(text);text=_DoCodeBlocks(text);text=_DoBlockQuotes(text);text=_HashHTMLBlocks(text);text=_FormParagraphs(text);return text;}
+var _RunSpanGamut=function(text){text=_DoCodeSpans(text);text=_EscapeSpecialCharsWithinTagAttributes(text);text=_EncodeBackslashEscapes(text);text=_DoImages(text);text=_DoSpecialRules(text);text=_DoAnchors(text);text=_DoAutoLinks(text);text=_EncodeAmpsAndAngles(text);text=_DoItalicsAndBold(text);text=text.replace(/ +\n/g," <br />\n");return text;}
+var _EscapeSpecialCharsWithinTagAttributes=function(text){var regex=/(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|<!(--.*?--\s*)+>)/gi;text=text.replace(regex,function(wholeMatch){var tag=wholeMatch.replace(/(.)<\/?code>(?=.)/g,"$1`");tag=escapeCharacters(tag,"\\`*_");return tag;});return text;}
+var _DoSpecialRules=function(text){text=text.replace(/(^|[^\w])(\#{1})(\b[0-9]{1,10}\b)+/gm,function(wholeMatch,m0,m1,m2){return hashBlock(m0+"<a href=\""+base+"tickets/view/"+m2+"\">"+m1+m2+"</a>");});text=text.replace(/(^|[^\w])(\[([A-Za-z0-9+]{1,40})\])+/gm,function(wholeMatch,m0,m1,m2){return hashBlock(m0+"<a href=\""+base+"commits/view/"+m2+"\">"+m1+"</a>");});text=text.replace(/(^|[^\w])(\[wiki:(.+?)\s(.+?)\])+/gm,function(wholeMatch,m0,m1,m2,m3){if(m2.substring(1,-1)!='/'){m2="wiki/"+m2;}else{m2=m2.slice(1);}
+return hashBlock(m0+"<a href=\""+base+m2+"\">"+m3+"</a>");});text=text.replace(/(^|[^\w])(\[ohloh:(.+?)\/(.+?)\])+/gm,function(wholeMatch,m0,m1,m2,m3){return hashBlock(m0+"<iframe frameborder=\"0\" height=\"100\" src=\"http://www.ohloh.net/projects/"+m2+"/widgets/"+m3+"\">"+m2+"</iframe>");});return text;}
+var _DoAnchors=function(text){text=text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,writeAnchorTag);text=text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\]\([ \t]*()<?(.*?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,writeAnchorTag);text=text.replace(/(\[([^\[\]]+)\])()()()()()/g,writeAnchorTag);return text;}
+var writeAnchorTag=function(wholeMatch,m1,m2,m3,m4,m5,m6,m7){if(m7==undefined)m7="";var whole_match=m1;var link_text=m2;var link_id=m3.toLowerCase();var url=m4;var title=m7;if(url==""){if(link_id==""){link_id=link_text.toLowerCase().replace(/ ?\n/g," ");}
+url="#"+link_id;if(g_urls[link_id]!=undefined){url=g_urls[link_id];if(g_titles[link_id]!=undefined){title=g_titles[link_id];}}
+else{if(whole_match.search(/\(\s*\)$/m)>-1){url="";}else{return whole_match;}}}
+url=escapeCharacters(url,"*_");var result="<a href=\""+url+"\"";if(title!=""){title=title.replace(/"/g,""");title=escapeCharacters(title,"*_");result+=" title=\""+title+"\"";}
+result+=">"+link_text+"</a>";return result;}
+var _DoImages=function(text){text=text.replace(/(!\[(.*?)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,writeImageTag);text=text.replace(/(!\[(.*?)\]\s?\([ \t]*()<?(\S+?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,writeImageTag);return text;}
+var writeImageTag=function(wholeMatch,m1,m2,m3,m4,m5,m6,m7){var whole_match=m1;var alt_text=m2;var link_id=m3.toLowerCase();var url=m4;var title=m7;if(!title)title="";if(url==""){if(link_id==""){link_id=alt_text.toLowerCase().replace(/ ?\n/g," ");}
+url="#"+link_id;if(g_urls[link_id]!=undefined){url=g_urls[link_id];if(g_titles[link_id]!=undefined){title=g_titles[link_id];}}
+else{return whole_match;}}
+alt_text=alt_text.replace(/"/g,""");url=escapeCharacters(url,"*_");var result="<img src=\""+url+"\" alt=\""+alt_text+"\"";title=title.replace(/"/g,""");title=escapeCharacters(title,"*_");result+=" title=\""+title+"\"";result+=" />";return result;}
+var _DoHeaders=function(text){text=text.replace(/^(.+)[ \t]*\n=+[ \t]*\n+/gm,function(wholeMatch,m1){return hashBlock("<h1>"+_RunSpanGamut(m1)+"</h1>");});text=text.replace(/^(.+)[ \t]*\n-+[ \t]*\n+/gm,function(matchFound,m1){return hashBlock("<h2>"+_RunSpanGamut(m1)+"</h2>");});text=text.replace(/^(\#{1,6})[ \t]*(.+?)[ \t]*\#*\n+/gm,function(wholeMatch,m1,m2){var h_level=m1.length;return hashBlock("<h"+h_level+">"+_RunSpanGamut(m2)+"</h"+h_level+">");});return text;}
+var _ProcessListItems;var _DoLists=function(text){text+="~0";var whole_list=/^(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm;if(g_list_level){text=text.replace(whole_list,function(wholeMatch,m1,m2){var list=m1;var list_type=(m2.search(/[*+-]/g)>-1)?"ul":"ol";list=list.replace(/\n{2,}/g,"\n\n\n");;var result=_ProcessListItems(list);result=result.replace(/\s+$/,"");result="<"+list_type+">"+result+"</"+list_type+">\n";return result;});}else{whole_list=/(\n\n|^\n?)(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/g;text=text.replace(whole_list,function(wholeMatch,m1,m2,m3){var runup=m1;var list=m2;var list_type=(m3.search(/[*+-]/g)>-1)?"ul":"ol";var list=list.replace(/\n{2,}/g,"\n\n\n");;var result=_ProcessListItems(list);result=runup+"<"+list_type+">\n"+result+"</"+list_type+">\n";return result;});}
+text=text.replace(/~0/,"");return text;}
+_ProcessListItems=function(list_str){g_list_level++;list_str=list_str.replace(/\n{2,}$/,"\n");list_str+="~0";list_str=list_str.replace(/(\n)?(^[ \t]*)([*+-]|\d+[.])[ \t]+([^\r]+?(\n{1,2}))(?=\n*(~0|\2([*+-]|\d+[.])[ \t]+))/gm,function(wholeMatch,m1,m2,m3,m4){var item=m4;var leading_line=m1;var leading_space=m2;if(leading_line||(item.search(/\n{2,}/)>-1)){item=_RunBlockGamut(_Outdent(item));}
+else{item=_DoLists(_Outdent(item));item=item.replace(/\n$/,"");item=_RunSpanGamut(item);}
+return"<li>"+item+"</li>\n";});list_str=list_str.replace(/~0/g,"");g_list_level--;return list_str;}
+var _DoCodeBlocks=function(text){text+="~0";text=text.replace(/(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=~0))/g,function(wholeMatch,m1,m2){var codeblock=m1;var nextChar=m2;codeblock=_EncodeCode(_Outdent(codeblock));codeblock=_Detab(codeblock);codeblock=codeblock.replace(/^\n+/g,"");codeblock=codeblock.replace(/\n+$/g,"");codeblock="<pre><code>"+codeblock+"\n</code></pre>";return hashBlock(codeblock)+nextChar;});text=text.replace(/~0/,"");text=text.replace(/(\{{3,}([\s\S]*?)\}{3,})/g,function(wholeMatch,m1,m2,m3){var codeblock=m2;codeblock=_EncodeCode(_Outdent(codeblock));codeblock=_Detab(codeblock);codeblock=codeblock.replace(/^\n+/g,"");codeblock=codeblock.replace(/\n+$/g,"");codeblock="<pre><code class='php'>"+codeblock+"\n</code></pre>";return hashBlock(codeblock);});return text;}
+var hashBlock=function(text){text=text.replace(/(^\n+|\n+$)/g,"");return"\n\n~K"+(g_html_blocks.push(text)-1)+"K\n\n";}
+var _DoCodeSpans=function(text){text=text.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm,function(wholeMatch,m1,m2,m3,m4){var c=m3;c=c.replace(/^([ \t]*)/g,"");c=c.replace(/[ \t]*$/g,"");c=_EncodeCode(c);return m1+"<code>"+c+"</code>";});return text;}
+var _EncodeCode=function(text){text=text.replace(/&/g,"&");text=text.replace(/</g,"<");text=text.replace(/>/g,">");text=escapeCharacters(text,"\*_{}[]\\",false);return text;}
+var _DoItalicsAndBold=function(text){text=text.replace(/(\*\*|__)(?=\S)([^\r]*?\S[*_]*)\1/g,"<strong>$2</strong>");text=text.replace(/[^\S](_)(?=\S)([^\r]*?\S)\1/g," <em>$2</em>");return text;}
+var _DoBlockQuotes=function(text){text=text.replace(/((^[ \t]*>[ \t]?.+\n(.+\n)*\n*)+)/gm,function(wholeMatch,m1){var bq=m1;bq=bq.replace(/^[ \t]*>[ \t]?/gm,"~0");bq=bq.replace(/~0/g,"");bq=bq.replace(/^[ \t]+$/gm,"");bq=_RunBlockGamut(bq);bq=bq.replace(/(^|\n)/g,"$1 ");bq=bq.replace(/(\s*<pre>[^\r]+?<\/pre>)/gm,function(wholeMatch,m1){var pre=m1;pre=pre.replace(/^ /mg,"~0");pre=pre.replace(/~0/g,"");return pre;});return hashBlock("<blockquote>\n"+bq+"\n</blockquote>");});return text;}
+var _FormParagraphs=function(text){text=text.replace(/^\n+/g,"");text=text.replace(/\n+$/g,"");var grafs=text.split(/\n{2,}/g);var grafsOut=new Array();var end=grafs.length;for(var i=0;i<end;i++){var str=grafs[i];if(str.search(/~K(\d+)K/g)>=0){grafsOut.push(str);}
+else if(str.search(/\S/)>=0){str=_RunSpanGamut(str);str=str.replace(/^([ \t]*)/g,"<p>");str+="</p>"
+grafsOut.push(str);}}
+end=grafsOut.length;for(var i=0;i<end;i++){while(grafsOut[i].search(/~K(\d+)K/)>=0){var blockText=g_html_blocks[RegExp.$1];blockText=blockText.replace(/\$/g,"$$$$");grafsOut[i]=grafsOut[i].replace(/~K\d+K/,blockText);}}
+return grafsOut.join("\n\n");}
+var _EncodeAmpsAndAngles=function(text){text=text.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g,"&");text=text.replace(/<(?![a-z\/?\$!])/gi,"<");return text;}
+var _EncodeBackslashEscapes=function(text){text=text.replace(/\\(\\)/g,escapeCharacters_callback);text=text.replace(/\\([`*_{}\[\]()>#+-.!])/g,escapeCharacters_callback);return text;}
+var _DoAutoLinks=function(text){text=text.replace(/<((https?|ftp|dict):[^'">\s]+)>/gi,"<a href=\"$1\">$1</a>");text=text.replace(/<(?:mailto:)?([-.\w]+\@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi,function(wholeMatch,m1){return _EncodeEmailAddress(_UnescapeSpecialChars(m1));});return text;}
+var _EncodeEmailAddress=function(addr){function char2hex(ch){var hexDigits='0123456789ABCDEF';var dec=ch.charCodeAt(0);return(hexDigits.charAt(dec>>4)+hexDigits.charAt(dec&15));}
+var encode=[function(ch){return"&#"+ch.charCodeAt(0)+";";},function(ch){return"&#x"+char2hex(ch)+";";},function(ch){return ch;}];addr="mailto:"+addr;addr=addr.replace(/./g,function(ch){if(ch=="@"){ch=encode[Math.floor(Math.random()*2)](ch);}else if(ch!=":"){var r=Math.random();ch=(r>.9?encode[2](ch):r>.45?encode[1](ch):encode[0](ch));}
+return ch;});addr="<a href=\""+addr+"\">"+addr+"</a>";addr=addr.replace(/">.+:/g,"\">");return addr;}
+var _UnescapeSpecialChars=function(text){text=text.replace(/~E(\d+)E/g,function(wholeMatch,m1){var charCodeToReplace=parseInt(m1);return String.fromCharCode(charCodeToReplace);});return text;}
+var _Outdent=function(text){text=text.replace(/^(\t|[ ]{1,4})/gm,"~0");text=text.replace(/~0/g,"")
+return text;}
+var _Detab=function(text){text=text.replace(/\t(?=\t)/g," ");text=text.replace(/\t/g,"~A~B");text=text.replace(/~B(.+?)~A/g,function(wholeMatch,m1,m2){var leadingText=m1;var numSpaces=4-leadingText.length%4;for(var i=0;i<numSpaces;i++)leadingText+=" ";return leadingText;});text=text.replace(/~A/g," ");text=text.replace(/~B/g,"");return text;}
+var escapeCharacters=function(text,charsToEscape,afterBackslash){var regexString="(["+charsToEscape.replace(/([\[\]\\])/g,"\\$1")+"])";if(afterBackslash){regexString="\\\\"+regexString;}
+var regex=new RegExp(regexString,"g");text=text.replace(regex,escapeCharacters_callback);return text;}
+var escapeCharacters_callback=function(wholeMatch,m1){var charCodeToEscape=m1.charCodeAt(0);return"~E"+charCodeToEscape+"E";}}
+-->
\ No newline at end of file
diff --git a/vendors/shells/api_index.php b/vendors/shells/api_index.php
new file mode 100644
index 0000000..e5646d4
--- /dev/null
+++ b/vendors/shells/api_index.php
@@ -0,0 +1,369 @@
+<?php
+/* SVN FILE: $Id$ */
+/**
+ * Api Index generation shell
+ *
+ * Helps generate and maintain Api Class index.
+ *
+ * PHP versions 4 and 5
+ *
+ * CakePHP : Rapid Development Framework <http://www.cakephp.org/>
+ * Copyright 2006-2008, Cake Software Foundation, Inc.
+ * 1785 E. Sahara Avenue, Suite 490-204
+ * Las Vegas, Nevada 89104
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright 2006-2008, Cake Software Foundation, Inc.
+ * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project
+ * @package cake
+ * @subpackage cake.
+ * @version
+ * @modifiedby
+ * @lastmodified
+ * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
+/**
+* Api Index Shell
+*/
+class ApiIndexShell extends Shell {
+/**
+ * Tasks used in the shell
+ *
+ * @var Array
+ **/
+ public $tasks = array('DbConfig');
+/**
+ * Holds ApiClass instance
+ *
+ * @var ApiClass
+ **/
+ public $ApiClass;
+/**
+ * instance of ApiFile
+ *
+ * @var ApiFile
+ **/
+ public $ApiFile;
+/**
+ * Holds current config
+ *
+ * @var ApiClass
+ **/
+ public $config = array();
+/**
+ * startup method
+ *
+ * @return void
+ **/
+ public function startup() {
+ if ($this->command && !in_array($this->command, array('help'))) {
+ if (!config('database')) {
+ $this->out(__("Your database configuration was not found. Take a moment to create one.", true), true);
+ $this->args = null;
+ return $this->DbConfig->execute();
+ }
+
+ if (!in_array($this->command, array('initdb', 'help'))) {
+ $this->ApiFile = ClassRegistry::init('ApiGenerator.ApiFile');
+ }
+ }
+ }
+/**
+ * Initialize the database and insert the schema.
+ *
+ * @return void
+ **/
+ public function initdb() {
+ $this->Dispatch->args = array('schema', 'run', 'create');
+ $this->Dispatch->params['name'] = 'ApiGenerator';
+ $this->Dispatch->params['path'] = dirname(dirname(dirname(__FILE__))) . DS. 'config' . DS . 'sql';
+ $this->Dispatch->dispatch();
+ }
+/**
+ * Initialize the database and insert the schema.
+ *
+ * @return void
+ **/
+ public function set_routes() {
+ $Routes = new File(CONFIGS . 'routes.php');
+ $new = array(
+ "Router::connect('/class/*', array('plugin' => 'api_generator', 'controller' => 'api_generator', 'action' => 'view_class'));",
+ "Router::connect('/file/*', array('plugin' => 'api_generator', 'controller' => 'api_generator','action' => 'view_file'));",
+ "Router::connect('/:action/*', array('plugin' => 'api_generator', 'controller' => 'api_generator'), array('action' => 'classes|source|files|view_source'));",
+ );
+
+ $data = rtrim(trim($Routes->read()), "?>") . "\n\n\t" . join("\n\n\t", $new);
+ if ($Routes->write($data)) {
+ $this->out(__('Routes file updated'));
+ return;
+ }
+ $this->out(__('Routes file NOT updated'));
+ return;
+ }
+/**
+ * Main method
+ *
+ * @return void
+ **/
+ public function main() {
+ return $this->help();
+ }
+/**
+ * Update the Api Class index.
+ *
+ * @return void
+ **/
+ public function update() {
+ $config = $this->config();
+
+ if (empty($config['paths'])) {
+ $this->err('Config could not be found');
+ return false;
+ }
+
+ $this->out('Clearing index and regenerating class index...');
+ $this->ApiClass = ClassRegistry::init('ApiGenerator.ApiClass');
+ $this->ApiClass->clearIndex();
+ $this->ApiFile->importCoreClasses();
+
+ foreach (array_keys($config['paths']) as $path) {
+ $fileList = $this->ApiFile->fileList($path);
+ foreach ($fileList as $file) {
+ try {
+ $docsInFile = $this->ApiFile->loadFile($file);
+ } catch (Exception $e) {
+ $this->err($e->getMessage());
+ }
+ foreach ($docsInFile['class'] as $classDocs) {
+ $this->ApiClass->create();
+ if ($this->ApiClass->saveClassDocs($classDocs)) {
+ $this->out('Added docs for ' . $classDocs->name . ' to index');
+ }
+ }
+ }
+ }
+
+ $this->out('Class index Regenerated.');
+ }
+/**
+ * Show the list of files that will be parsed.
+ *
+ * @return void
+ **/
+ public function showfiles() {
+ $config = $this->config();
+
+ if (empty($config['paths'])) {
+ $this->err('Config could not be found');
+ return false;
+ }
+
+ $this->out('The following files will be parsed when generating the API class index:');
+ $this->hr();
+ $this->out('');
+ foreach (array_keys($config['paths']) as $path) {
+ $files = $this->ApiFile->fileList($path);
+ $this->_paginate($files);
+ }
+ }
+/**
+ * Pagiantion of long file lists
+ *
+ * @return void
+ **/
+ protected function _paginate($list) {
+ if (count($list) > 20) {
+ $chunks = array_chunk($list, 10);
+ $chunkCount = count($chunks);
+ $this->out(implode("\n", array_shift($chunks)));
+ $chunkCount--;
+ while ($chunkCount && null == $this->in('Press <return> to see next 10 files')) {
+ $this->out(implode("\n", array_shift($chunks)));
+ $chunkCount--;
+ }
+ } else {
+ $this->out(implode("\n", $list));
+ }
+ }
+/**
+ * Shows a warning about default / no filePath been stored in Configure.
+ *
+ * @return void
+ **/
+ protected function config() {
+ $this->ApiConfig = ClassRegistry::init('ApiGenerator.ApiConfig');
+
+ if (empty($this->config)) {
+ $config = $this->ApiConfig->read();
+ if (!empty($config)) {
+ return $config;
+ }
+ }
+
+ $config = array();
+
+ $this->hr();
+ $this->out('api_config.ini could not be located.');
+ $this->out('Answer some questions to build it.');
+ $this->hr();
+
+ $path = null;
+ while($path == null && $path != 'q') {
+ $path = $this->in('Enter the path to the codebase.', '', $this->params['working']);
+ if ($path[0] != '/' && $path[1] != ':') {
+ $path = $this->params['working'] . DS . $path;
+ }
+ if (file_exists($path)) {
+ $config['paths'][$path] = true;
+ }
+
+ $stop = $this->in('Would you like to add another path?', array('y', 'n', 'q'), 'n');
+ if ($stop == 'y') {
+ $path = null;
+ }
+ }
+ $this->hr();
+ $this->out('Setup some excludes');
+ $this->out('excludes remove files, folders, properties and methods from the index.');
+ $this->out('Input a comma separated list for multiple options');
+ $this->out('to continue, just answer "n"');
+ $this->hr();
+
+ $exclude = null;
+ $exclude = $this->in('Exclude properties of the following types (private, protected, static)', '', 'private');
+ if ($exclude != 'q') {
+ $config['exclude']['properties'] = $exclude;
+ }
+
+ $exclude = $this->in('Exclude methods of the following types (private, protected, static)', '', 'private');
+ if ($exclude != 'n') {
+ $config['exclude']['methods'] = $exclude;
+ }
+
+ $exclude = $this->in('Comma separated list of directories to exclude', '', 'n');
+ if ($exclude != 'n') {
+ $config['exclude']['directories'] = $exclude;
+ }
+
+ $exclude = $this->in('Comma separated list of files to exclude', '', 'n');
+ if ($exclude != 'n') {
+ $config['exclude']['files'] = $exclude;
+ }
+
+ $this->hr();
+ $this->out('About the files in your codebase');
+ $this->out('input a comma separated list for multiple options');
+ $this->out('to continue, just answer "n"');
+ $this->hr();
+
+ $extensions = null;
+ while($extensions == null && $extensions != 'n') {
+ $extensions = $this->in('Extensions to parse (php, ctp, tpl)', '', 'php');
+ if ($extensions != 'n') {
+ $config['file']['extensions'] = $extensions;
+ }
+ }
+
+ $regex = null;
+ while($regex == null && $regex != 'n') {
+ $regex = $this->in('Regex for matching files', '', '[a-z_\-0-9]+');
+ if ($regex != 'n') {
+ $config['file']['regex'] = $regex;
+ }
+ }
+
+ $this->hr();
+ $this->out('Do you have some classes that do not map to a filename?');
+ $this->out('to continue, just answer "n"');
+ $this->hr();
+
+ $mapping = null;
+ while($mapping == null && $mapping != 'n') {
+ $class = $this->in('Class to map', '', 'n');
+ if ($class == 'n') {
+ $mapping = 'n';
+ } else {
+ $file = null;
+ while($file == null && $file != 'n') {
+ $file = $this->in('Enter the path to the file that holds ' . $class .'. this can be relative to the default path, or add a / in front to use an absolute path', '', $path);
+ if ($file[0] != '/') {
+ $file = $path. DS . $file;
+ }
+ if (file_exists($file)) {
+ $mapping = true;
+ $config['mappings'][$class] = $file;
+ }
+ }
+ $stop = $this->in('Add another mapping?', array('y', 'n', 'q'), 'n');
+ if ($stop == 'y') {
+ $mapping = null;
+ }
+ }
+ }
+
+ $this->hr();
+ $this->out('Usually we can find the dependencies, but ');
+ $this->out('sometimes we miss. If you have files that are not generating properly');
+ $this->out('Input a comma separated list for multiple options');
+ $this->out('to continue, just answer "n"');
+ $this->hr();
+
+ $dependencies = null;
+ while($dependencies == null && $dependencies != 'n') {
+ $class = $this->in('Class with dependancies', '', 'n');
+ if ($class == 'n') {
+ $dependencies = 'n';
+ } else {
+ $parent = null;
+ while($parent == null && $parent != 'n') {
+ $parent = $this->in('Enter the dependencies for ' . $class, '');
+ if ($parent != 'n') {
+ $dependencies = true;
+ $config['dependencies'][$class] = $parent;
+ }
+ }
+ $stop = $this->in('Add another dependency?', array('y', 'n', 'q'), 'n');
+ if ($stop == 'y') {
+ $dependencies = null;
+ }
+ }
+ }
+
+ $this->hr();
+ $this->out('Verify the config');
+ $this->hr();
+ $string = $this->ApiConfig->toString($config);
+ $this->out($string);
+ $this->hr();
+ $looksGood = $this->in('Does the config look correct?', array('y', 'n'), 'n');
+
+ if ($this->ApiConfig->save($string)) {
+ $this->out('The config was saved');
+ }
+ return $this->config = $config;
+ }
+/**
+ * Get help
+ *
+ * @return void
+ **/
+ public function help() {
+ $this->out('Api Generator Class Index Generation');
+ $this->hr();
+ $this->out('Available commands:');
+ $this->out(' initdb');
+ $this->out(' Create the schema used for the Api Generator Plugin');
+ $this->out(' showfiles');
+ $this->out(' Show the list of files that will be parsed for classes based on your configuration.');
+ $this->out(' Use to check if your config is going to parse the files you want.');
+ $this->out(' update');
+ $this->out(' Clear the existing class index and regenerate it.');
+ $this->out(' set_routes');
+ $this->out(' Add routes for Api generator to your routes file.');
+
+ }
+
+}
diff --git a/views/api_generator/classes.ctp b/views/api_generator/classes.ctp
new file mode 100644
index 0000000..89fe1fc
--- /dev/null
+++ b/views/api_generator/classes.ctp
@@ -0,0 +1,89 @@
+<?php
+/**
+ * Browse Classes View file
+ *
+ */
+
+/**
+ * Tittle height to class name height ratio
+ */
+$titleWeight = 2;
+
+/**
+ * Number of columns to print
+ */
+$columns = 3;
+
+$letterIndex = array_combine(range('A', 'Z'), array_fill(0, 26, null));
+$maxWeight = 0;
+foreach ($classIndex as $slug => $name):
+ $firstLetter = strtoupper(substr($name, 0, 1));
+ if (empty($letterIndex[$firstLetter])):
+ $letterIndex[$firstLetter] = true;
+ $maxWeight += $titleWeight;
+ endif;
+ $maxWeight += 1;
+endforeach;
+
+$maxWeight = floor($maxWeight / $columns);
+
+$classChunks = array();
+
+$chunk = 0;
+$weight = 0;
+$letter = '';
+foreach ($classIndex as $slug => $name) {
+ $firstLetter = strtoupper(substr($name, 0, 1));
+ if ($firstLetter != $letter) {
+ $weight += $titleWeight;
+ $letter = $firstLetter;
+ }
+
+ if ($weight > $maxWeight) {
+ $weight -= $maxWeight;
+ if ($chunk < 2) {
+ $chunk++;
+ }
+ }
+
+ $classChunks[$chunk][$firstLetter][$slug] = $name;
+ $weight ++;
+}
+?>
+<h1><?php __('Index'); ?></h1>
+
+<div class="letter-links">
+<?php
+
+foreach (array_keys($letterIndex) as $letter):
+ if (!$letterIndex[$letter]) {
+ echo '<span>' . $letter . '</span>';
+ } else {
+ echo $html->link($letter, '#letter-' . $letter);
+ }
+endforeach;
+?>
+</div>
+
+<?php $current = null; ?>
+<?php foreach ($classChunks as $column): ?>
+<div class="letter-section">
+ <?php foreach ($column as $letter => $classes): ?>
+ <?php if ($current != $letter): ?>
+ <h3><a id="letter-<?php echo $letter; ?>"></a><?php echo $letter; ?></h3>
+ <?php else: ?>
+ <h3><a id="letter-<?php echo $letter; ?>-cont"></a><?php echo $letter; ?> <?php __('(cont.)') ?></h3>
+ <?php endif; ?>
+ <?php $current = $letter; ?>
+ <ul class="class-index">
+ <?php foreach ($classes as $slug => $name): ?>
+ <li><?php
+ echo $html->link($name, array(
+ 'plugin' => 'api_generator', 'controller' => 'api_generator',
+ 'action' => 'view_class', $slug));
+ ?></li>
+ <?php endforeach; ?>
+ </ul>
+ <?php endforeach; ?>
+</div>
+<?php endforeach; ?>
\ No newline at end of file
diff --git a/views/api_generator/files.ctp b/views/api_generator/files.ctp
new file mode 100644
index 0000000..8176f58
--- /dev/null
+++ b/views/api_generator/files.ctp
@@ -0,0 +1,20 @@
+<?php
+/**
+ * Recursive Listing of all allowed files.
+ *
+ */
+?>
+<h1><?php __('All files')?></h1>
+<ul id="file-list">
+<?php if (!empty($files)): ?>
+<?php foreach ($files as $file): ?>
+ <li class="file">
+ <?php echo $apiDoc->fileLink($file); ?>
+ </li>
+<?php endforeach; ?>
+<?php else: ?>
+ <li class="file">
+ <?php __('No files'); ?>
+ </li>
+<?php endif; ?>
+</ul>
\ No newline at end of file
diff --git a/views/api_generator/index.ctp b/views/api_generator/index.ctp
new file mode 100644
index 0000000..ed8f981
--- /dev/null
+++ b/views/api_generator/index.ctp
@@ -0,0 +1,18 @@
+<?php
+/**
+ * Home Page for the Api Generator
+ *
+ */
+?>
+<div class="doc-body">
+<h1>Hey you have an API now!</h1>
+
+<p>Be sure to install the schema by running <code>cake api_index initdb</code></p>
+<p>ApiGenerator provides a File and Class browser. The file browser should be working right now! However, if you want to use the class index, and all the goodies it entails like searches and viewing source code for classes. Then you will need to generate a class index.</p>
+
+<p>To generate your class index run <code>cake api_index update</code> from the shell and you should get an index generated!</p>
+
+<p>Once you've got you index setup, you should probably edit this page. Do so by opening <code><?php echo APP; ?>plugins/api_generator/views/api_generator/index.ctp</code></p>
+
+<p>The ApiGenerator also supports a number of configuration options. You can setup a config file by using <code>cake api_index config</code>.</p>
+</div>
\ No newline at end of file
diff --git a/views/api_generator/no_class.ctp b/views/api_generator/no_class.ctp
new file mode 100644
index 0000000..114a3ad
--- /dev/null
+++ b/views/api_generator/no_class.ctp
@@ -0,0 +1,10 @@
+<?php
+/**
+ * No class view file. Displayed when no class is found.
+ *
+ */
+?>
+<h2><?php __('No classes were found in the requested file'); ?></h2>
+<p class="folder">
+ <?php echo $html->link('Up one folder', array('action' => 'source', $previousPath)); ?>
+</p>
diff --git a/views/api_generator/search.ctp b/views/api_generator/search.ctp
new file mode 100644
index 0000000..1adbef6
--- /dev/null
+++ b/views/api_generator/search.ctp
@@ -0,0 +1,30 @@
+<?php
+/**
+ * Api Search results
+ *
+ */
+
+$apiDoc->setClassIndex($classIndex);
+
+?>
+<h1><?php __('Search Results'); ?></h1>
+<?php if (empty($results)): ?>
+ <p class="error"><?php __('Your search returned no results'); ?></p>
+<?php else: ?>
+<ul id="search-results">
+ <?php foreach ($results as $result): ?>
+ <li><h4><?php echo $apiDoc->classLink($result['ApiClass']['name']); ?></h4>
+ <?php $excerpt = $text->excerpt(strip_tags($result['ApiClass']['search_index']), $this->params['url']['query']); ?>
+ <p><?php echo $text->highlight($excerpt, strtolower($this->params['url']['query'])); ?></p>
+ </li>
+ <?php endforeach;?>
+</ul>
+<?php endif; ?>
+<?php
+$paginator->options(array(
+ 'url' => array(
+ '?' => array('query' => $this->params['url']['query'])
+ )
+));
+?>
+<?php echo $this->element('paging'); ?>
\ No newline at end of file
diff --git a/views/api_generator/source.ctp b/views/api_generator/source.ctp
new file mode 100644
index 0000000..a6b9df9
--- /dev/null
+++ b/views/api_generator/source.ctp
@@ -0,0 +1,35 @@
+<?php
+/* SVN FILE: $Id$ */
+/**
+ * Browse view. Shows file listings and provides links to obtaining api docs from a file
+ * Doubles as an ajax view by omitting certain tags when params['isAjax'] is set.
+ */
+?>
+<?php if (!$this->params['isAjax']): ?>
+<h1 class="breadcrumb"><?php echo $this->element('breadcrumb'); ?></h1>
+<ul id="file-list">
+<?php endif; ?>
+
+ <li class="folder previous-folder">
+ <?php echo $html->link('Up one folder', array('action' => 'source', $previousPath)); ?>
+ </li>
+<?php foreach ($dirs as $dir): ?>
+ <li class="folder">
+ <?php echo $html->link($dir, array('action' => 'source', $currentPath . '/' . $dir)); ?>
+ </li>
+<?php endforeach; ?>
+<?php if (!empty($files)): ?>
+<?php foreach ($files as $file): ?>
+ <li class="file">
+ <?php echo $html->link($file, array('action' => 'view_file', $currentPath . '/' . $file)); ?>
+ </li>
+<?php endforeach; ?>
+<?php else: ?>
+ <li class="file">
+ <?php __('No files'); ?>
+ </li>
+<?php endif; ?>
+
+<?php if (!$this->params['isAjax']): ?>
+</ul>
+<?php endif; ?>
\ No newline at end of file
diff --git a/views/api_generator/view_class.ctp b/views/api_generator/view_class.ctp
new file mode 100644
index 0000000..f307a1c
--- /dev/null
+++ b/views/api_generator/view_class.ctp
@@ -0,0 +1,12 @@
+<?php
+/**
+ * View a single class
+ *
+ */
+$apiDoc->setClassIndex($classIndex);
+
+echo $this->element('class_info');
+echo $this->element('properties');
+echo $this->element('method_summary');
+echo $this->element('method_detail');
+?>
\ No newline at end of file
diff --git a/views/api_generator/view_file.ctp b/views/api_generator/view_file.ctp
new file mode 100644
index 0000000..5ec4ceb
--- /dev/null
+++ b/views/api_generator/view_file.ctp
@@ -0,0 +1,27 @@
+<?php
+/* SVN FILE: $Id$ */
+/**
+ * View view. Shows generated api docs from a file.
+ *
+ */
+$apiDoc->setClassIndex($classIndex);
+?>
+<h1 class="breadcrumb"><?php echo $this->element('breadcrumb'); ?></h1>
+<?php
+echo $this->element('element_toc');
+
+if (!empty($docs['function'])) :
+ foreach ($docs['function'] as &$function):
+ echo $this->element('function_summary', array('doc' => $function));
+ endforeach;
+endif;
+
+if (!empty($docs['class'])):
+ foreach ($docs['class'] as $class):
+ echo $this->element('class_info', array('doc' => $class));
+ echo $this->element('properties', array('doc' => $class));
+ echo $this->element('method_summary', array('doc' => $class));
+ echo $this->element('method_detail', array('doc' => $class));
+ endforeach;
+endif;
+?>
diff --git a/views/api_generator/view_source.ctp b/views/api_generator/view_source.ctp
new file mode 100644
index 0000000..37fa9b3
--- /dev/null
+++ b/views/api_generator/view_source.ctp
@@ -0,0 +1,9 @@
+<?php
+/**
+ * View the source code for a file.
+ *
+ */
+?>
+<h1><?php echo $filename; ?></h1>
+
+<?php echo $apiUtils->highlight($contents); ?>
diff --git a/views/elements/api_menu.ctp b/views/elements/api_menu.ctp
new file mode 100644
index 0000000..d202157
--- /dev/null
+++ b/views/elements/api_menu.ctp
@@ -0,0 +1,23 @@
+<ul class="navigation">
+ <li><?php
+ $class = ($this->action == 'classes') ? array('class' => 'on') : null;
+ echo $html->link(__('Classes', true), array(
+ 'plugin' => 'api_generator',
+ 'controller' => 'api_generator', 'action' => 'classes'
+ ), $class);?>
+ </li>
+ <li><?php
+ $class = ($this->action == 'source') ? array('class' => 'on') : null;
+ echo $html->link(__('Source', true), array(
+ 'plugin' => 'api_generator',
+ 'controller' => 'api_generator', 'action' => 'source'
+ ), $class);?>
+ </li>
+ <li><?php
+ $class = ($this->action == 'files') ? array('class' => 'on') : null;
+ echo $html->link(__('Files', true), array(
+ 'plugin' => 'api_generator',
+ 'controller' => 'api_generator', 'action' => 'files'
+ ), $class);?>
+ </li>
+</ul>
\ No newline at end of file
diff --git a/views/elements/breadcrumb.ctp b/views/elements/breadcrumb.ctp
new file mode 100644
index 0000000..035a385
--- /dev/null
+++ b/views/elements/breadcrumb.ctp
@@ -0,0 +1,16 @@
+<?php
+/**
+ * Breadcrumb element. Makes a breadcrumb trail for file browsing
+ *
+ */
+$pathSegments = explode('/', $currentPath);
+echo $html->link('/', array('action' => 'source')) . ' ';
+
+for ($i = 0, $pathCount = count($pathSegments) - 1; $i < $pathCount; $i++) :
+ $pathBit = array_slice($pathSegments, $i, 1);
+ $path = implode('/', array_slice($pathSegments, 0, $i + 1));
+ echo $html->link($pathBit[0], array('action' => 'source', $path)) . ' / ';
+endfor;
+
+echo $pathSegments[$pathCount];
+?>
\ No newline at end of file
diff --git a/views/elements/class_info.ctp b/views/elements/class_info.ctp
new file mode 100644
index 0000000..00d9a63
--- /dev/null
+++ b/views/elements/class_info.ctp
@@ -0,0 +1,45 @@
+<?php
+/**
+ * Class information element
+ *
+ */
+?>
+<a id="class-<?php echo $doc->name; ?>"></a>
+<div class="doc-block class-info">
+ <div class="doc-head"><h2><?php echo $doc->name; ?> Class Info:</h2></div>
+ <div class="doc-body">
+ <dl>
+ <dt><?php __('Class Declaration:'); ?></dt>
+ <dd><?php echo $doc->classInfo['classDescription']; ?></dd>
+
+ <dt><?php __('File name:'); ?></dt>
+ <dd><?php echo $apiDoc->trimFileName($doc->classInfo['fileName']); ?></dd>
+
+ <dt><?php __('Summary:'); ?></dt>
+ <dd class="markdown-block"><?php echo h($doc->classInfo['comment']['description']); ?></dd>
+
+ <?php if (!empty($doc->classInfo['parents'])): ?>
+ <dt><?php __('Class Inheritance'); ?></dt>
+ <dd><?php echo $apiDoc->inheritanceTree($doc->classInfo['parents']); ?></dd>
+ <?php endif;?>
+
+ <?php if (!empty($doc->classInfo['interfaces'])): ?>
+ <dt><?php __('Interfaces Implemented'); ?></dt>
+ <dd>
+ <?php foreach ($doc->classInfo['interfaces'] as $interfaces): ?>
+ <?php echo $apiDoc->classLink($interfaces); ?>
+ <?php endforeach; ?>
+ </dd>
+ <?php endif;?>
+
+ </dl>
+ <div class="tag-block">
+ <dl>
+ <?php foreach ($doc->classInfo['comment']['tags'] as $name => $value): ?>
+ <dt><?php echo $name; ?></dt>
+ <dd><?php echo h($value); ?></dd>
+ <?php endforeach; ?>
+ </dl>
+ </div>
+ </div>
+</div>
\ No newline at end of file
diff --git a/views/elements/element_toc.ctp b/views/elements/element_toc.ctp
new file mode 100644
index 0000000..dde5e8f
--- /dev/null
+++ b/views/elements/element_toc.ctp
@@ -0,0 +1,30 @@
+<?php
+/**
+ * A Table of Contents for the elements in the consumed file.
+ *
+ */
+?>
+<div id="element-toc" class="clearfix">
+ <?php if (!empty($docs['class'])): ?>
+ <div class="classes">
+ <h3><?php __('Defined Classes'); ?></h3>
+ <ul class="element-list">
+ <?php foreach (array_keys($docs['class']) as $class): ?>
+ <li class="class"><?php echo $html->link($class, "#class-".$class, array('class' => 'scroll-link')); ?></li>
+ <?php endforeach; ?>
+ </ul>
+ </div>
+ <?php endif; ?>
+
+ <?php if (!empty($docs['function'])): ?>
+ <div class="functions">
+ <a id="top-functions"></a>
+ <h3><?php __('Declared Functions'); ?></h3>
+ <ul class="element-list">
+ <?php foreach (array_keys($docs['function']) as $function): ?>
+ <li class="function"><?php echo $html->link($function, "#function-".$function, array('class' => 'scroll-link')); ?></li>
+ <?php endforeach; ?>
+ </ul>
+ </div>
+ <?php endif; ?>
+</div>
\ No newline at end of file
diff --git a/views/elements/function_summary.ctp b/views/elements/function_summary.ctp
new file mode 100644
index 0000000..f759078
--- /dev/null
+++ b/views/elements/function_summary.ctp
@@ -0,0 +1,48 @@
+<?php
+/**
+ * Function documentation element
+ *
+ */
+?>
+<a id="function-<?php echo $doc->name; ?>"></a>
+<div class="function-info">
+ <div class="doc-head">
+ <h2><?php echo $doc->name; ?></h2>
+ <a class="top-link scroll-link" href="#top-functions">top</a>
+ </div>
+
+ <div class="doc-body">
+ <div class="markdown-block"><?php echo $doc->info['comment']['description']; ?></div>
+ <dl>
+ <?php if (count($doc->params)): ?>
+ <dt><?php __('Parameters:'); ?></dt>
+ <dd>
+ <table>
+ <tbody>
+ <?php $i = 0; ?>
+ <?php foreach ($doc->params as $name => $paramInfo): ?>
+ <tr class="<?php echo ($i % 2) ? 'even' : 'odd'; ?>">
+ <td>$<?php echo $name; ?></td>
+ <td><?php echo $paramInfo['type']; ?></td>
+ <td><?php echo $paramInfo['comment']; ?></td>
+ <td><?php echo ($paramInfo['optional']) ? 'optional' : 'required'; ?></td>
+ <td><?php echo ($paramInfo['hasDefault']) ? var_export($paramInfo['default'], true) : __('(no default)', true); ?></td>
+ </tr>
+ <?php $i++;?>
+ <?php endforeach; ?>
+ </tbody>
+ </table>
+ </dd>
+ <?php endif; ?>
+
+ <dt><?php __('Function defined in file:'); ?></dt>
+ <dd><?php echo $apiDoc->fileLink($doc->info['declaredInFile']); ?></dd>
+
+ <dt>
+ <?php foreach ($doc->info['comment']['tags'] as $name => $value): ?>
+ <dt><?php echo $name; ?></dt>
+ <dd><?php echo $value; ?></dd>
+ <?php endforeach; ?>
+ </dt>
+ </div>
+</div>
\ No newline at end of file
diff --git a/views/elements/header_search.ctp b/views/elements/header_search.ctp
new file mode 100644
index 0000000..26d010d
--- /dev/null
+++ b/views/elements/header_search.ctp
@@ -0,0 +1,23 @@
+<?php
+/**
+ * Header search form
+ *
+ */
+?>
+<div id="header-search">
+<?php echo $form->create('ApiClass', array(
+ 'url' => array(
+ 'plugin' => 'api_generator', 'controller' => 'api_generator',
+ 'action' => 'search'
+ ),
+ 'type' => 'get',
+)); ?>
+<fieldset id="search-bar">
+ <?php
+ echo $form->text('Search.query', array(
+ 'class' => 'query'
+ )); ?>
+<?php echo $form->submit(__('Search', true), array('div' => false, 'class' => 'submit')); ?>
+</fieldset>
+<?php echo $form->end(null); ?>
+</div>
\ No newline at end of file
diff --git a/views/elements/method_detail.ctp b/views/elements/method_detail.ctp
new file mode 100644
index 0000000..7e3216c
--- /dev/null
+++ b/views/elements/method_detail.ctp
@@ -0,0 +1,70 @@
+<?php
+/**
+ * Method Detail element
+ *
+ */
+?>
+<?php foreach ($doc->methods as $method):
+ if ($apiDoc->excluded($method['access'], 'method')) :
+ continue;
+ endif;
+ $definedInThis = ($method['declaredInClass'] == $doc->classInfo['name']);
+?>
+<div class="doc-block <?php echo $definedInThis ? '' : 'parent-method'; ?>">
+ <a id="method-<?php echo $doc->name . $method['name']; ?>"></a>
+ <div class="doc-head">
+ <h2 class="<?php echo $method['access'] ?>"><?php echo $method['name']; ?></h2>
+ <a class="top-link scroll-link" href="#top-<?php echo $doc->name; ?>">top</a>
+ </div>
+
+ <div class="doc-body">
+ <div class="markdown-block"><?php echo h($method['comment']['description']); ?></div>
+ <dl>
+ <?php if (count($method['args'])): ?>
+ <dt><?php __('Parameters:'); ?></dt>
+ <dd>
+ <table>
+ <tbody>
+ <?php $i = 0; ?>
+ <?php foreach ($method['args'] as $name => $paramInfo): ?>
+ <tr class="<?php echo ($i % 2) ? 'even' : 'odd'; ?>">
+ <td>$<?php echo $name; ?></td>
+ <td><?php echo $paramInfo['type']; ?></td>
+ <td><?php echo h($paramInfo['comment']); ?></td>
+ <td><?php echo ($paramInfo['optional']) ? 'optional' : 'required'; ?></td>
+ <td><?php echo ($paramInfo['hasDefault']) ? var_export($paramInfo['default'], true) : __('(no default)', true); ?></td>
+ </tr>
+ <?php $i++;?>
+ <?php endforeach; ?>
+ </tbody>
+ </table>
+ </dd>
+ <?php endif; ?>
+
+ <dt><?php __('Method defined in class:'); ?></dt>
+ <dd><?php echo $apiDoc->classLink($method['declaredInClass']); ?></dd>
+
+ <dt><?php __('Method defined in file:'); ?></dt>
+ <dd><?php
+ echo $apiDoc->fileLink($method['declaredInFile']);
+
+ if ($apiDoc->inClassIndex($method['declaredInClass'])):
+ __(' on line ');
+ echo $html->link($method['startLine'], array(
+ 'controller' => 'api_generator',
+ 'action' => 'view_source',
+ $apiDoc->slugClassName($method['declaredInClass']),
+ '#line-'. $method['startLine']
+ ));
+ endif;
+ ?> </dd>
+
+ <dt>
+ <?php foreach ($method['comment']['tags'] as $name => $value): ?>
+ <dt><?php echo $name; ?></dt>
+ <dd><?php echo h($value); ?></dd>
+ <?php endforeach; ?>
+ </dt>
+ </div>
+</div>
+<?php endforeach; ?>
\ No newline at end of file
diff --git a/views/elements/method_summary.ctp b/views/elements/method_summary.ctp
new file mode 100644
index 0000000..3ec269e
--- /dev/null
+++ b/views/elements/method_summary.ctp
@@ -0,0 +1,41 @@
+<?php
+/**
+ * Method Summary Element
+ *
+ */
+
+$apiUtils->sortByName($doc->methods); ?>
+<div class="doc-block">
+ <a id="top-<?php echo $doc->name; ?>"></a>
+ <div class="doc-head"><h2><?php __('Method Summary:'); ?></h2></div>
+ <div class="doc-body">
+ <span class="doc-controls">
+ <a href="#" id="hide-parent-methods"><?php __('Show/Hide parent methods'); ?></a>
+ </span>
+ <table class="summary">
+ <tbody>
+ <?php $i = 0; ?>
+ <?php foreach ($doc->methods as $method): ?>
+ <?php
+ if ($apiDoc->excluded($method['access'], 'method')) :
+ continue;
+ endif;
+ $definedInThis = ($method['declaredInClass'] == $doc->classInfo['name']);
+ ?>
+ <tr class="<?php echo ($i % 2) ? 'even' : 'odd'; ?> <?php echo $definedInThis ? '' : 'parent-method'; ?>">
+ <td class="access <?php echo $method['access']; ?>"><span><?php echo $method['access']; ?></span></td>
+ <td>
+ <?php
+ echo $html->link($method['signature'],
+ '#method-' . $doc->name . $method['name'],
+ array('class' => 'scroll-link')
+ );
+ ?>
+ </td>
+ </tr>
+ <?php $i++;?>
+ <?php endforeach; ?>
+ </tbody>
+ </table>
+ </div>
+</div>
\ No newline at end of file
diff --git a/views/elements/paging.ctp b/views/elements/paging.ctp
new file mode 100644
index 0000000..f5ac759
--- /dev/null
+++ b/views/elements/paging.ctp
@@ -0,0 +1,19 @@
+<?php
+/**
+ * Paging Element
+ *
+ */
+?>
+<div class="paging">
+ <?php if ($paginator->hasPrev()): ?>
+ <?php echo $paginator->prev('<< '.__('previous', true), array(), null, array('class'=>'disabled'));?> |
+ <?php endif; ?>
+
+ <?php if ($paginator->hasPage(null, 2)): ?>
+ <?php echo $paginator->numbers(); ?>
+ <?php endif; ?>
+
+ <?php if ($paginator->hasNext()): ?>
+ <?php echo $paginator->next(__('next', true).' >>', array(), null, array('class'=>'disabled'));?>
+ <?php endif;?>
+</div>
\ No newline at end of file
diff --git a/views/elements/properties.ctp b/views/elements/properties.ctp
new file mode 100644
index 0000000..1b0825e
--- /dev/null
+++ b/views/elements/properties.ctp
@@ -0,0 +1,34 @@
+<?php
+/**
+ * Properties Element
+ *
+ */
+
+$apiUtils->sortByName($doc->properties); ?>
+<div class="doc-block">
+ <div class="doc-head"><h2><?php __('Properties:'); ?></h2></div>
+ <div class="doc-body">
+ <?php if (!empty($doc->properties)): ?>
+ <span class="doc-controls">
+ <a href="#" id="hide-parent-properties"><?php __('Show/Hide parent properties'); ?></a>
+ </span>
+ <table>
+ <?php $i = 0; ?>
+ <?php foreach ($doc->properties as $prop): ?>
+ <?php
+ if ($apiDoc->excluded($prop['access'], 'property')) :
+ continue;
+ endif;
+ $definedInThis = ($prop['declaredInClass'] == $doc->classInfo['name']);
+ ?>
+ <tr class="<?php echo ($i % 2) ? 'even' : 'odd'; ?> <?php echo $definedInThis ? '' : 'parent-property'; ?>">
+ <td class="access <?php echo $prop['access']; ?>"><span><?php echo $prop['access']; ?></span></td>
+ <td><?php echo $prop['name']; ?></td>
+ <td class="markdown-block"><?php echo h($prop['comment']['description']); ?></td>
+ </tr>
+ <?php $i++;?>
+ <?php endforeach; ?>
+ </table>
+ <?php endif; ?>
+ </div>
+</div>
\ No newline at end of file
diff --git a/views/elements/sidebar/class_sidebar.ctp b/views/elements/sidebar/class_sidebar.ctp
new file mode 100644
index 0000000..c17eece
--- /dev/null
+++ b/views/elements/sidebar/class_sidebar.ctp
@@ -0,0 +1,17 @@
+<?php
+/**
+ * Class List sidebar element
+ *
+ */
+?>
+<h3><?php __('Class Index'); ?></h3>
+<ul class="class-index">
+<?php foreach ($classIndex as $slug => $name): ?>
+ <li class="class">
+ <?php
+ echo $html->link($name, array(
+ 'plugin' => 'api_generator', 'controller' => 'api_generator',
+ 'action' => 'view_class', $slug
+ ));
+ ?></li>
+<?php endforeach; ?>
\ No newline at end of file
diff --git a/views/elements/sidebar/file_sidebar.ctp b/views/elements/sidebar/file_sidebar.ctp
new file mode 100644
index 0000000..a19bfb0
--- /dev/null
+++ b/views/elements/sidebar/file_sidebar.ctp
@@ -0,0 +1,31 @@
+<?php
+/**
+ * Displays a File Listing Sidebar Hopefully filled with Ajax love.
+ *
+ */
+?>
+<h3><?php __('File browser'); ?></h3>
+<ul id="file-browser">
+ <li class="up-dir folder">
+ <?php echo $html->link(__('Up one folder', true), array(
+ 'action' => 'source', $upOneFolder));
+ ?>
+ </li>
+ <?php foreach ($dirs as $dir): ?>
+ <li class="folder">
+ <?php echo $html->link($dir, array('action' => 'source', $previousPath . '/' . $dir)); ?>
+ </li>
+ <?php endforeach; ?>
+
+ <?php if (!empty($files)): ?>
+ <?php foreach ($files as $file): ?>
+ <li class="file">
+ <?php echo $html->link($file, array('action' => 'view_file', $previousPath . '/' . $file)); ?>
+ </li>
+ <?php endforeach; ?>
+ <?php else: ?>
+ <li class="file">
+ <?php __('No files'); ?>
+ </li>
+ <?php endif; ?>
+</ul>
\ No newline at end of file
diff --git a/views/helpers/api_doc.php b/views/helpers/api_doc.php
new file mode 100644
index 0000000..4eb89bc
--- /dev/null
+++ b/views/helpers/api_doc.php
@@ -0,0 +1,202 @@
+<?php
+/* SVN FILE: $Id$ */
+/**
+ * Api Docs Helper
+ *
+ * Wraps common docs pages view functions
+ *
+ * PHP versions 4 and 5
+ *
+ * CakePHP : Rapid Development Framework <http://www.cakephp.org/>
+ * Copyright 2006-2008, Cake Software Foundation, Inc.
+ * 1785 E. Sahara Avenue, Suite 490-204
+ * Las Vegas, Nevada 89104
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright 2006-2008, Cake Software Foundation, Inc.
+ * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project
+ * @package cake
+ * @subpackage cake.cake.libs.
+ * @since CakePHP v 1.2.0.4487
+ * @version
+ * @modifiedby
+ * @lastmodified
+ * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
+class ApiDocHelper extends AppHelper {
+/**
+ * Helpers used by ApiDocHelper
+ *
+ * @var array
+ */
+ public $helpers = array('Html');
+/**
+ * internal basePath used when browsing files. and making links to them
+ *
+ * @var string
+ **/
+ protected $_basePath;
+/**
+ * default Urls
+ *
+ * @var array
+ **/
+ protected $_defaultUrl = array(
+ 'file' => array(
+ 'controller' => 'api_generator',
+ 'action' => 'view_file',
+ 'plugin' => 'api_generator',
+ ),
+ 'class' => array(
+ 'controller' => 'api_generator',
+ 'action' => 'view_class',
+ 'plugin' => 'api_generator',
+ ),
+ );
+/**
+ * classList
+ *
+ * @var array
+ **/
+ protected $_classList = array();
+/**
+ * Constructor.
+ *
+ * @return void
+ **/
+ public function __construct($config = array()) {
+ $view = ClassRegistry::getObject('view');
+ $this->_basePath = $view->getVar('basePath');
+ }
+/**
+ * set the basePath
+ *
+ * @return void
+ **/
+ public function setBasePath($path) {
+ $this->_basePath = $path;
+ }
+/**
+ * inBasePath
+ *
+ * Check if a filename is within the ApiGenerator.basePath path
+ *
+ * @return boolean
+ **/
+ function inBasePath($filename) {
+ return (strpos($filename, $this->_basePath) !== false);
+ }
+/**
+ * Create a link to a filename if it is in the basePath
+ *
+ * @param string $filename Name of file to make link to.
+ * @param array $url Additional url params you want for some reason.
+ * @param array $attributes attributes for link if one is generated.
+ * @return string either a link or plain text depending on files location relative to basepath
+ **/
+ public function fileLink($filename, $url = array(), $attributes = array()) {
+ $url = array_merge($this->_defaultUrl['file'], $url);
+ if ($this->inBasePath($filename)) {
+ $trimmedName = $this->trimFileName($filename);
+ $url[] = $trimmedName;
+ return $this->Html->link($trimmedName, $url, $attributes);
+ }
+ return $filename;
+ }
+/**
+ * trim the basePath from a filename so it can be used in links
+ *
+ * @return string $filename Filename to trim basepath from
+ * @return string trimmed filename
+ **/
+ public function trimFileName($filename) {
+ return str_replace($this->_basePath, '', $filename);
+ }
+/**
+ * Set the Class list so that linkClassName will know which classes are in the index.
+ *
+ * @param array $classList The list of classes to use when making links.
+ * @return void
+ **/
+ public function setClassIndex($classList) {
+ $this->_classList = $classList;
+ }
+/**
+ * Check if a class is in the classIndex
+ *
+ * @param string $className The class
+ * @return boolean
+ **/
+ public function inClassIndex($className) {
+ return in_array($className, $this->_classList);
+ }
+/**
+ * Create a link to a class name if it exists in the classList
+ *
+ * @param string $className the class name you wish to make a link to
+ * @param array $url A url array to override defaults
+ * @param array $attributes Additional attributes for an html link if generated.
+ * @return string Html link or plaintext
+ **/
+ public function classLink($className, $url = array(), $attributes = array()) {
+ $url = array_merge($this->_defaultUrl['class'], $url);
+ $listFlip = array_flip($this->_classList);
+ if (array_key_exists($className, $listFlip)) {
+ $url[] = $listFlip[$className];
+ return $this->Html->link($className, $url, $attributes);
+ }
+ return $className;
+ }
+/**
+ * Check the access string against the excluded method access.
+ *
+ * @param string $accessString name of the accessString you are checking ie. public
+ * @param string $type Type of access to check, (method or property)
+ * @return boolean
+ **/
+ public function excluded($accessString, $type) {
+ $view = ClassRegistry::getObject('view');
+ $accessName = Inflector::variable('exclude_' . Inflector::pluralize($type));
+ $exclusions = $view->getVar($accessName);
+ if (in_array($accessString, $exclusions)) {
+ return true;
+ }
+ return false;
+ }
+/**
+ * Slugs a classname to match the format in the database.
+ *
+ * @param string $className Name of class to sluggify.
+ * @return string
+ **/
+ public function slugClassName($className) {
+ return str_replace('_', '-', Inflector::underscore($className));
+ }
+/**
+ * Create a nested inheritance tree from an array.
+ * Uses an array stack like a tree. So
+ * array('foo', 'bar', 'baz')
+ * will create a tree like
+ * * foo
+ * ** bar
+ * *** baz
+ *
+ * @param array $parents Array of parents you want to make into a tree
+ * @return string
+ **/
+ public function inheritanceTree($parents) {
+ $out = $endTags = '';
+ $totalParents = count($parents);
+ foreach ($parents as $i => $class) {
+ $htmlClass = 'parent-class';
+ if ($i == $totalParents - 1) {
+ $htmlClass .= ' last';
+ }
+ $out .= '<span class="' . $htmlClass . '">' . $this->classLink($class) . "</span>\n";
+ }
+ return '<p class="inheritance-tree">' . $out . '</p>';
+ }
+}
\ No newline at end of file
diff --git a/views/helpers/api_utils.php b/views/helpers/api_utils.php
new file mode 100644
index 0000000..8470adb
--- /dev/null
+++ b/views/helpers/api_utils.php
@@ -0,0 +1,138 @@
+<?php
+/* SVN FILE: $Id$ */
+/**
+ * Class to style php code as an ordered list.
+ *
+ * Originally from http://shiflett.org/blog/2006/oct/formatting-and-highlighting-php-code-listings
+ *
+ * Adapted into a helper for use in ApiGenerator
+ * Some minor modifications to allow it to work with php4.
+ *
+ * Also changed:
+ * - And to add line-# anchors to each line.
+ * - Removed whitespace reductions. Caused issues with source -> highlight links
+ *
+ * PHP versions 4 and 5
+ *
+ * @filesource
+ * @package api_generator.helpers
+ * @modifiedby $LastChangedBy: AD7six $
+ * @lastmodified $Date: 2008-11-05 11:30:07 +0100 (Wed, 05 Nov 2008) $
+ */
+class ApiUtilsHelper extends AppHelper {
+/**
+ * constructor
+ *
+ * @return void
+ **/
+ function __construct() {
+ ini_set('highlight.comment', 'comment');
+ ini_set('highlight.default', 'default');
+ ini_set('highlight.keyword', 'keyword');
+ ini_set('highlight.string', 'string');
+ ini_set('highlight.html', 'html');
+ }
+
+/**
+ * highlights code so it can be displayed
+ *
+ * @param string $code Code to highlight.
+ * @return string
+ **/
+ function highlight($code= "") {
+ $code= highlight_string($code, TRUE);
+ /* Clean Up */
+ if (phpversion() >= 5) {
+ $code= substr($code, 33, -15);
+ $code= str_replace('<span style="color: ', '<span class="', $code);
+ } else {
+ $code= substr($code, 25, -15);
+ $code= str_replace('<font color=', '<span class=', $code);
+ $code= str_replace('</font>', '</span>', $code);
+ }
+ $code= str_replace(' ', ' ', $code);
+ $code= str_replace('&', '&', $code);
+ $code= str_replace('<br />', "\n", $code);
+
+ /* Normalize Newlines */
+ $code= str_replace("\r", "\n", $code);
+
+ $lines= explode("\n", $code);
+
+ /* Previous Style */
+ $previous= FALSE;
+
+ /* Output Listing */
+ $return= " <ol class=\"code\">\n";
+ foreach ($lines as $key => $line) {
+ if (substr($line, 0, 7) == '</span>') {
+ $previous= FALSE;
+ $line= substr($line, 7);
+ }
+
+ if (empty ($line)) {
+ $line= ' ';
+ }
+
+ if ($previous) {
+ $line= "<span class=\"$previous\">" . $line;
+ }
+
+ /* Set Previous Style */
+ if (strpos($line, '<span') !== FALSE) {
+ switch (substr($line, strrpos($line, '<span') + 13, 1)) {
+ case 'c' :
+ $previous= 'comment';
+ break;
+ case 'd' :
+ $previous= 'default';
+ break;
+ case 'k' :
+ $previous= 'keyword';
+ break;
+ case 's' :
+ $previous= 'string';
+ break;
+ }
+ }
+
+ /* Unset Previous Style Unless Span Continues */
+ if (substr($line, -7) == '</span>') {
+ $previous= FALSE;
+ }
+ elseif ($previous) {
+ $line .= '</span>';
+ }
+ $lineno = $key + 1;
+ if ($key % 2) {
+ $return .= " <li class=\"even\"><a id=\"line-$lineno\"></a><code>$line</code></li>\n";
+ } else {
+ $return .= " <li><a id=\"line-$lineno\"></a><code>$line</code></li>\n";
+ }
+ }
+ $return .= " </ol>\n";
+ return $return;
+ }
+/**
+ * Sort a collection of arrays by the key 'name'
+ *
+ * @param array $collection Reference to the array needing sorting.
+ * @return void works by reference
+ **/
+ public function sortByName(&$collection) {
+ if (!is_array($collection)) {
+ return;
+ }
+ return usort($collection, array($this, '_sorter'));
+ }
+/**
+ * sortByName helper function
+ *
+ * @return integer
+ **/
+ protected function _sorter($one, $two) {
+ $cleanOne = str_replace('_', '', $one['name']);
+ $cleanTwo = str_replace('_', '', $two['name']);
+ return strnatcasecmp($cleanOne, $cleanTwo);
+ }
+}
\ No newline at end of file
diff --git a/views/layouts/default.ctp b/views/layouts/default.ctp
new file mode 100644
index 0000000..6e4b67f
--- /dev/null
+++ b/views/layouts/default.ctp
@@ -0,0 +1,75 @@
+<?php
+/* SVN FILE: $Id$ */
+/**
+ *
+ * PHP versions 5
+ *
+ * CakePHP(tm) : Rapid Development Framework (http://www.cakephp.org)
+ * Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+ * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP(tm) Project
+ * @package cake
+ * @subpackage cake.api_generator.layouts
+ * @version $Revision$
+ * @modifiedby $LastChangedBy$
+ * @lastmodified $Date$
+ * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
+?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <?php echo $html->charset(); ?>
+ <title>
+ <?php __('CakePHP: API Generator'); ?>
+ <?php echo $title_for_layout; ?>
+ </title>
+ <?php
+ echo $html->meta('icon');
+ echo $html->css('/api_generator/css/base.css');
+ ?>
+ <script type="text/javascript">var basePath = "<?php echo $this->base; ?>"; </script>
+ <?php
+ echo $javascript->link('/api_generator/js/mootools');
+ echo $javascript->link('/api_generator/js/showdown');
+ echo $javascript->link('/api_generator/js/api_generator');
+
+ echo $scripts_for_layout;
+ ?>
+</head>
+<body class="api">
+ <?php $bodyClass = (isset($showSidebar) && $showSidebar) ? 'with-sidebar' : 'no-sidebar'; ?>
+ <div id="wrapper" class="<?php echo $bodyClass; ?>">
+ <div id="header" class="clearfix">
+ <h1><?php echo $html->link(__('CakePHP: API Generator', true), 'http://cakephp.org'); ?></h1>
+ <?php echo $this->element('header_search'); ?>
+ <?php echo $this->element('api_menu');?>
+ </div>
+ <div id="content" class="clearfix">
+ <?php $session->flash(); ?>
+ <div id="content-inner">
+ <?php echo $content_for_layout; ?>
+ </div>
+ <?php if (isset($showSidebar) && $showSidebar): ?>
+ <div id="sidebar">
+ <?php echo $this->element($sidebarElement)?>
+ </div>
+ <?php endif; ?>
+ </div>
+ <div id="footer">
+ <?php echo $html->link(
+ $html->image('cake.power.gif', array('alt'=> __("CakePHP: the rapid development php framework", true), 'border'=>"0")),
+ 'http://www.cakephp.org/',
+ array('target'=>'_blank'), null, false
+ );
+ ?>
+ </div>
+ </div>
+ <?php echo $cakeDebug; ?>
+</body>
+</html>
\ No newline at end of file
