<?php
/*
  +----------------------------------------------------------------------+
  | Copyright (c) 1997-2023 The PHP Group                                |
  +----------------------------------------------------------------------+
  | This source file is subject to version 3.01 of the PHP license,      |
  | that is bundled with this package in the file LICENSE, and is        |
  | available through the world-wide-web at the following url:           |
  | https://www.php.net/license/3_01.txt.                                |
  | If you did not receive a copy of the PHP license and are unable to   |
  | obtain it through the world-wide-web, please send a note to          |
  | license@php.net, so we can mail you a copy immediately.              |
  +----------------------------------------------------------------------+
  | Authors:    Felipe Pena <felipe@php.net>                             |
  |             Philip Olson <philip@php.net>                            |
  +----------------------------------------------------------------------+

  $Id$
*/

if (!(extension_loaded('reflection') && extension_loaded('pcre'))) {
	die("ERROR: Docgen requires the Reflection and PCRE extensions.\n");
}

/* Constants */
define('DOC_METHOD', 	1<<0);
define('DOC_CONSTRUCTOR', 1<<1);
define('DOC_PROPERTY', 	1<<2);
define('DOC_CLASS', 	1<<3);
define('DOC_EXTENSION',	1<<4);
define('DOC_FUNCTION',	1<<5);

/* Templates */
$TEMPLATE = array(
	DOC_METHOD 	    => 'method.tpl',
	DOC_CONSTRUCTOR => 'constructor.tpl',
	DOC_PROPERTY    => 'property.tpl',
	DOC_CLASS 	    => 'class.tpl',
	DOC_FUNCTION    => 'function.tpl'
);

/* Default files for extensions */
$DOC_EXT = array(
	'book.xml' 	         => 'book.tpl',
	'setup.xml'          => 'setup.tpl',
	'constants.xml'      => 'constants.tpl',
	'configure.xml'      => 'configure.tpl',
	'reference.xml'      => 'reference.tpl',
	'ini.xml'            => 'ini.tpl',
	'versions.xml'       => 'versions.tpl',
);

function usage() { /* {{{ */
	$v = PHP_VERSION;
	print <<<USAGE
Usage:
	php docgen.php <options>

Example options:
	-e dom                                (an entire extension)
	-f preg_replace                       (a single function)
	-c simplexmlelement -m xpath -m asxml (a couple class methods)

Notes:
	Long options are supported with PHP 5.3.0+, and you use $v
	Be sure the desired extension to document is loaded into PHP

Options:
	-c,--class 	-- class name
	-e,--extension	-- extension name
	-f,--function	-- function name
	-h,--help	-- show this help
	-i,--include	-- includes a PHP file
	(shortcut for: php -dauto_prepend_file=streams.php docgen.php)

	-m,--method	-- method name (require -c)
	-o,--output	-- output dir
	-p,--pecl	-- is a PECL extension
	-d,--phpdoc	-- path to files in phpdoc directory
	-a,--copy	-- copy created files to phpdoc directory
	-s,--seealso	-- add empty see also sections
	-x,--example	-- add empty example sections
	-q,--quiet	-- quiet mode
	-v,--version	-- show the version
	-V,--verbose 	-- disable show progress
	-t,--test	-- Test/debug mode (affects a few activities)


USAGE;
}
/* }}} */

function find_function($ext, ReflectionMethod $method = NULL, ReflectionFunction $func = NULL) { /* {{{ */
	$ext_name = strtolower($ext);
	$ext = new ReflectionExtension($ext);

	if ($method) {
		preg_match_all('/[A-Z][a-z]+|[a-z]+/', $method->name, $parts);

		$possibleNames = array(
			$ext_name .'_'. strtolower(implode('_', $parts[0])),
			$ext_name .'_'. strtolower(implode('_', array_reverse($parts[0])))
		);

		foreach ($ext->getFunctions() as $function) {
			if (in_array($function->name, $possibleNames)) {
				return $function;
			}
		}
	} else {
		$func_name = preg_replace('/^'. $ext_name .'_/', '', $func->name);
		preg_match_all('/[a-z]+/', $func_name, $parts);

		$possibleNames = array(
			strtolower(implode($parts[0])),
			strtolower(implode(array_reverse($parts[0])))
		);

		foreach ($ext->getClasses() as $class) {
			foreach ($class->getMethods() as $method) {
				if (in_array(strtolower($method->name), $possibleNames)) {
					return $method;
				}
			}
		}
	}
	return false;
}
/* }}} */

function add_warning($err_msg) { /* {{{ */
	global $WARNING, $INFO;

	$WARNING[$INFO['actual_file']][] = $err_msg;
}
/* }}} */

function create_markup_to_modifiers(int $modifierFlags): array { /* {{{ */
	$modifiers = Reflection::getModifierNames($modifierFlags);
	$result = [];

    foreach ($modifiers as $modifier) {
        $result[] = '<modifier>'. $modifier .'</modifier>';
    }

	return $result;
}
/* }}} */

function format_id($name) { /* {{{ */
	return preg_replace(array('/[^[:alnum:]]/', '/^-+/'), array('-', ''), strtolower($name));
}
/* }}} */

function format_filename($name) { /* {{{ */
        $name = strtolower(trim($name));
        $name = ltrim($name, '_');
        $name = str_replace(array('_', '\\'), array('-', '.'), $name);
        return $name;
}
/* }}} */

function format_config($name) { /* {{{ */
	return preg_replace('/_/', '-', $name);
}
/* }}} */

function get_ident_size($placeholder, $content) { /* {{{ */
	preg_match('/^([[:blank:]]*)\{'. $placeholder .'\}/m', $content, $match);
	return isset($match[1]) ? strlen($match[1]) : false;
}
/* }}} */

function save_file($filename, $content) { /* {{{ */
	global $OPTION;

	$filename = str_replace("\\", DIRECTORY_SEPARATOR, $filename);
	file_put_contents($filename, $content);
	if ($OPTION['verbose']) {
		printf("%s\n", $filename);
	}
}
/* }}} */

/** Returns an xml type tag or an entity to use in the generated xml for a PHP constant */
function get_xml_type_tag_or_entity($value, bool $allow_entity) { /* {{{ */
	// Entities from entities/global.ent are recommended for freeform text.
	if (is_int($value)) {
		return $allow_entity ? '&integer;' : '<type>int</type>';
	} elseif (is_float($value)) {
		return $allow_entity ? '&float;' : '<type>float</type>';
	} elseif (is_null($value)) {
		return '<type>null</type>';
	} elseif (is_bool($value)) {
		return $allow_entity ? '&boolean;' : '<type>bool</type>';
	}
	if ($allow_entity) {
		if (is_array($value)) {
			return '&array;';
		} elseif (is_object($value)) {
			return '&object;';
		} elseif (is_resource($value)) {
			return '&resource;';
		}
	}
	// Use <type>$type</type> for other types
	return '<type>' . gettype($value) . '</type>';
}
/* }}} */

function get_type_by_string($str) { /* {{{ */
	if (is_numeric($str)) {
		if ($str && intval($str) == $str) {
			return 'int';
		} else if ($str && floatval($str) == $str) {
			return 'float';
		} else {
			return 'int';
		}
	} else {
		return 'string';
	}
}
/* }}} */

/** @return string|null */
function get_type_as_xml_string(ReflectionType $type = null) { /* {{{ */
	if ($type instanceof ReflectionNamedType) {
		$ret = "<type>{$type->getName()}</type>";
		if ($type->allowsNull()) {
			$ret = "<type class=\"union\">{$ret}<type>null</type></type>";
		}
		return $ret;
	}
	if ($type instanceof ReflectionUnionType) {
		$types = array_map(function($type) {return "<type>{$type->getName()}</type>";}, $type->getTypes());
		$ret = implode('', $types);
		if (count($types) > 1) {
			$ret = "<type class=\"union\">{$ret}</type>";
		}
		return $ret;
	}
	if ($type instanceof ReflectionType) {
		$ret = (string) $type;
		return "<type>{$ret}</type>";
	}
	return null;
}
/* }}} */

function create_dir($path) { /* {{{ */
	global $OPTION;

	$path = str_replace("\\", DIRECTORY_SEPARATOR, $path);
	if (!file_exists($path)) {
		if ($OPTION['verbose']) {
			printf("- Creating directory `%s'\n", $path);
		}
		if (!mkdir($path, 0777, true)) {
			add_warning("chmod: Permission denied `{$path}'");
			return;
		}
	}
}
/* }}} */

function create_markup_to_params(array $params, $ident) { /* {{{ */
	$count = 1;
	$markup = "";
	foreach ($params as $param) {
		/* Parameter type */
		$type = get_type_as_xml_string($param->getType());
		if ($type === null) {
			$type = '<type>mixed</type>';
			if (!$param->getName()) {
				add_warning(sprintf("Parameter name not found, param%d used", $count));
			}
			add_warning(sprintf("Type hint for parameter `%s' not found, 'mixed' used", ($param->getName() ? $param->getName() : $count)));
		}

		$markup .= sprintf("%s<methodparam%s>%s<parameter%s>%s</parameter></methodparam>". PHP_EOL,
			($markup ? str_repeat(' ', $ident) : ''),
			($param->isOptional() ? ' choice="opt"' : ''),
			$type,
			($param->isPassedByReference() ? ' role="reference"' : ''),
			($param->getName() ? $param->getName() : 'param'. $count));

		$count++;
	}

	return rtrim($markup, PHP_EOL);
}
/* }}} */

function global_check($content) { /* {{{ */
	global $INFO, $OPTION;

	if (!$INFO['actual_extension']) {
		$INFO['actual_extension'] = 'EXTENSION_NAME_HERE';
	}

	/* {EXT_NAME_ID} */
	$content = preg_replace('/\{EXT_NAME_ID\}/', format_id($INFO['actual_extension']), $content);

	/* {EXT_NAME} */
	$content = preg_replace('/\{EXT_NAME\}/', ucwords($INFO['actual_extension']), $content);

	/* {EMPTY_REVISION_KEYWORD} */
	$content = str_replace('{EMPTY_REVISION_KEYWORD}', '<!-- '. chr(36) .'Revision$ -->', $content);

	return $content;
}
/* }}} */

function create_markup_to_parameter_section(Reflector $obj, $content) { /* {{{ */
	$param_name = ($obj instanceof ReflectionMethod ? 'METHOD' : 'FUNCTION'). '_PARAMETERS';
	/* {PARAMETERS} */
	if ($obj->getNumberOfParameters()) {
		$ident = get_ident_size($param_name, $content);

		$parameters = $obj->getParameters();
		$content = preg_replace('/\{'. $param_name .'\}/', create_markup_to_params($parameters, $ident), $content);

		/* {PARAMETERS_DESCRIPTION} */
		if ($ident = get_ident_size('PARAMETERS_DESCRIPTION', $content)) {
			$count = 1;

			$markup = "<variablelist>". PHP_EOL;
			foreach ($parameters as $param) {
				$markup .= str_repeat(' ', $ident + 1) ."<varlistentry>". PHP_EOL;
				$markup .= str_repeat(' ', $ident + 2) .'<term><parameter>'. ($param->getName() ? $param->getName() : 'param'. $count) ."</parameter></term>". PHP_EOL;
				$markup .= str_repeat(' ', $ident + 2) ."<listitem>". PHP_EOL;
				$markup .= str_repeat(' ', $ident + 3) ."<simpara>". PHP_EOL;
				$markup .= str_repeat(' ', $ident + 4) . PHP_EOL;
				$markup .= str_repeat(' ', $ident + 3) ."</simpara>". PHP_EOL;
				$markup .= str_repeat(' ', $ident + 2) ."</listitem>". PHP_EOL;
				$markup .= str_repeat(' ', $ident + 1) ."</varlistentry>". PHP_EOL;

				$count++;
			}
			$markup .= str_repeat(' ', $ident) ."</variablelist>";

			$content = preg_replace('/\{PARAMETERS_DESCRIPTION\}/', $markup, $content, 1);
		}
	} else {
		$content = preg_replace('/\{'. $param_name .'\}/', '<void/>', $content, 1);
		$content = preg_replace('/\{PARAMETERS_DESCRIPTION\}/', '&no.function.parameters;', $content, 1);
	}

	return $content;
}
/* }}} */

function gen_function_markup(ReflectionFunction $function, $content) { /* {{{ */
	/* {FUNCTION_NAME_ID} */
	$content = preg_replace('/\{FUNCTION_NAME_ID\}/', format_id($function->getName()), $content);

	/* {FUNCTION_NAME} */
	$content = preg_replace('/\{FUNCTION_NAME\}/', $function->getName(), $content);

	/* {RETURN_TYPE} */
	$type = get_type_as_xml_string($function->getReturnType());
	if ($type === null) {
		$type = '<type>mixed</type>';
		add_warning(sprintf("Return type hint for function `%s' not found, 'mixed' used", $function->getName()));
	}
	$content = preg_replace('/\{RETURN_TYPE\}/', $type, $content, 1);

	/* {FUNCTION_PARAMETERS}, {PARAMETERS_DESCRIPTION} */
	$content = create_markup_to_parameter_section($function, $content);

	return $content;
}
/* }}} */

function gen_method_markup(ReflectionMethod $method, $content) { /* {{{ */
	/* {METHOD_PARAMETERS}, {PARAMETERS_DESCRIPTION} */
	$content = create_markup_to_parameter_section($method, $content);

	/* {CLASS_NAME_ID} */
	$content = preg_replace('/\{CLASS_NAME_ID\}/', format_id($method->class), $content);

	/* {METHOD_NAME_ID} */
	$content = preg_replace('/\{METHOD_NAME_ID\}/', format_id($method->name), $content);

	/* {FULL_METHOD_NAME} */
	$content = preg_replace('/\{FULL_METHOD_NAME\}/', $method->class .'::'. $method->name, $content);

	/* {METHOD_NAME} */
	$content = preg_replace('/\{METHOD_NAME\}/', $method->name, $content);

	/* {MODIFIERS} */
	$content = preg_replace('/\{MODIFIERS\}/', implode(" ", create_markup_to_modifiers($method->getModifiers())), $content, 1);

	/* {RETURN_TYPE} */
	if (!$method->isConstructor()) {
		$type = get_type_as_xml_string($method->getReturnType());
		if ($type === null) {
			$type = '<type>mixed</type>';
			add_warning(sprintf("Return type hint for method `%s' not found, 'mixed' used", $method->getName()));
		}
		$content = preg_replace('/\{RETURN_TYPE\}/', $type, $content, 1);
	} else {
		$content = preg_replace('/\{RETURN_TYPE\}/', '', $content, 1);
	}

	return $content;
}
/* }}} */

function gen_mapping_markup(ReflectionMethod $method, ReflectionFunction $function, $content) { /* {{{ */
	if ($method->getNumberOfParameters() || $function->getNumberOfParameters()) {
		/* {PARAMETERS_DESCRIPTION} */
		if ($ident = get_ident_size('PARAMETERS_DESCRIPTION', $content)) {
			$count = 1;

			$func_params = array();
			$method_params = array();
			foreach ($function->getParameters() as $param) {
				$func_params[] = $param->getName();
			}
			foreach ($method->getParameters() as $param) {
				$method_params[] = $param->getName();
			}

			$markup = str_repeat(' ', $ident) ."<variablelist>". PHP_EOL;
			foreach ($func_params as $param) {
				$markup .= str_repeat(' ', $ident + 1) ."<varlistentry>". PHP_EOL;
				$markup .= str_repeat(' ', $ident + 2) .'<term><parameter>'. ($param ? $param : 'param'. $count) ."</parameter></term>". PHP_EOL;
				$markup .= str_repeat(' ', $ident + 2) ."<listitem>". PHP_EOL;
      			$markup .= str_repeat(' ', $ident + 3) ."<simpara>". PHP_EOL;
       			$markup .= str_repeat(' ', $ident + 4) . PHP_EOL;
       			$markup .= str_repeat(' ', $ident + 3) ."</simpara>". PHP_EOL;
     			$markup .= str_repeat(' ', $ident + 2) ."</listitem>". PHP_EOL;
    			$markup .= str_repeat(' ', $ident + 1) ."</varlistentry>". PHP_EOL;
    			$count++;
			}
			$diff_params = array_diff($method_params, $func_params);

			foreach ($method_params as $param) {
				if (!($param && in_array($param, $diff_params))) {
					continue;
				}
				$markup .= str_repeat(' ', $ident + 1) ."<varlistentry>". PHP_EOL;
				$markup .= str_repeat(' ', $ident + 2) .'<term><parameter>'. ($param ? $param : 'param'. $count) ."</parameter></term>". PHP_EOL;
				$markup .= str_repeat(' ', $ident + 2) ."<listitem>". PHP_EOL;
				$markup .= str_repeat(' ', $ident + 3) ."<simpara>". PHP_EOL;
				$markup .= str_repeat(' ', $ident + 4) . PHP_EOL;
				$markup .= str_repeat(' ', $ident + 3) ."</simpara>". PHP_EOL;
				$markup .= str_repeat(' ', $ident + 2) ."</listitem>". PHP_EOL;
				$markup .= str_repeat(' ', $ident + 1) ."</varlistentry>". PHP_EOL;
				$count++;
			}
			$markup .= str_repeat(' ', $ident) ."</variablelist>". PHP_EOL;

			$content = preg_replace('/\{PARAMETERS_DESCRIPTION\}/', $markup, $content, 1);
		}
	} else {
		$content = preg_replace('/\{PARAMETERS\}/', '<void/>', $content, 1);
		$content = preg_replace('/\{PARAMETERS_DESCRIPTION\}/', '&no.function.parameters;', $content, 1);
	}

	$content = gen_method_markup($method, $content);
	$content = gen_function_markup($function, $content);

	return $content;
}
/* }}} */

/* TODO This is outdated */
function gen_class_markup(ReflectionClass $class, $content) { /* {{{ */
	$id = format_id($class->getName());
	$escapedName = addslashes($class->getName());

	/* {CLASS_NAME} */
	$content = preg_replace('/\{CLASS_NAME\}/', $class->getName(), $content);

	/* {CLASS_TYPE} */
	$type = ($class->isInterface() ? 'interface' : 'class');
	$content = preg_replace('/\{CLASS_TYPE\}/', $type, $content);

	/* {CLASS_NAME_ID} */
	$content = preg_replace('/{CLASS_NAME_ID\}/', $id, $content);

	/* {EXTENDS} */
	if ($parent = $class->getParentClass()) {
		$ident = get_ident_size('EXTENDS', $content);

		$markup  = PHP_EOL;
		$markup .= str_repeat(' ', $ident) . "<ooclass>". PHP_EOL;
	  	$markup .= str_repeat(' ', $ident + 1) ."<modifier>extends</modifier>". PHP_EOL;
	  	$markup .= str_repeat(' ', $ident + 1) .'<classname>'. $parent->getName() ."</classname>". PHP_EOL;
	 	$markup .= str_repeat(' ', $ident) .'</ooclass>';

		$content = preg_replace('/\{EXTENDS\}/', $markup, $content, 1);
	} else {
		$content = preg_replace('/^\s*\{EXTENDS\}.*?\n/m', '', $content, 1);
	}

	/* {IMPLEMENTS} */
	if ($interfaces = $class->getInterfaces()) {
		$ident = get_ident_size('IMPLEMENTS', $content);

		// Don't get inherited interfaces, e.g. Traversable if we already have Iterator.
		foreach ($interfaces as $interface) {
			foreach (array_keys($interface->getInterfaces()) as $inherited_interface) {
				unset($interfaces[$inherited_interface]);
			}
		}

		$markup = PHP_EOL;
		foreach ($interfaces as $interface) {
			$markup .= str_repeat(' ', $ident) ."<oointerface>". PHP_EOL;
			$markup .= str_repeat(' ', $ident + 1) .'<interfacename>'. $interface->getName() ."</interfacename>". PHP_EOL;
			$markup .= str_repeat(' ', $ident) ."</oointerface>". PHP_EOL . PHP_EOL;
		}

		$content = preg_replace('/\{IMPLEMENTS\}/', rtrim($markup, PHP_EOL), $content, 1);
	} else {
		$content = preg_replace('/^\s*\{IMPLEMENTS\}.*?\n/m', '', $content, 1);
	}

	/* {CONSTANTS_LIST} */
	if ($constants = $class->getConstants()) {
		$ident = get_ident_size('CONSTANTS_LIST', $content);
		$markup = "<classsynopsisinfo role=\"comment\">Constants</classsynopsisinfo>". PHP_EOL;

		foreach ($constants as $constant => $value) {
			$markup .= str_repeat(' ', $ident) ."<fieldsynopsis>". PHP_EOL;
			$markup .= str_repeat(' ', $ident + 1) ."<modifier>const</modifier>". PHP_EOL;
			$markup .= str_repeat(' ', $ident + 1) .get_xml_type_tag_or_entity($value, false). PHP_EOL; // For the class synopsis we use explicit <type> elements
      		$markup .= str_repeat(' ', $ident + 1) .'<varname linkend="'. $id .'.constants.'. format_id($constant) .'">'. $class->getName() .'::'. $constant ."</varname>". PHP_EOL;
      		$markup .= str_repeat(' ', $ident + 1) .'<initializer>'. $value ."</initializer>". PHP_EOL;
     		$markup .= str_repeat(' ', $ident) ."</fieldsynopsis>". PHP_EOL;
     	}

     	$content = preg_replace('/\{CONSTANTS_LIST\}/', $markup, $content, 1);
	} else {
		$content = preg_replace('/^\s*\{CONSTANTS_LIST\}.*?\n/m', '', $content, 1);
	}

	/* {CONSTANTS} */
	if ($constants) {
		$ident = get_ident_size('CONSTANTS', $content);

		$markup  = PHP_EOL ."<!-- {{{ ". $class->getName() ." constants -->". PHP_EOL;
		$markup .= str_repeat(' ', $ident) .'<section xml:id="'. $id .".constants\">". PHP_EOL;
		$markup .= str_repeat(' ', $ident + 1) ."&reftitle.constants;". PHP_EOL;
    	$markup .= str_repeat(' ', $ident + 1) ."<variablelist>". PHP_EOL . PHP_EOL;

		foreach ($constants as $constant => $value) {
     		$markup .= str_repeat(' ', $ident + 2) .'<varlistentry xml:id="'. $id .".constants.". format_id($constant) ."\">". PHP_EOL;
     		$markup .= str_repeat(' ', $ident + 3) .'<term><constant>'. $class->getName() .'::'. $constant ."</constant></term>". PHP_EOL;
      		$markup .= str_repeat(' ', $ident + 3) ."<listitem>". PHP_EOL;
       		$markup .= str_repeat(' ', $ident + 4) ."<simpara></simpara>". PHP_EOL;
      		$markup .= str_repeat(' ', $ident + 3) ."</listitem>". PHP_EOL;
     		$markup .= str_repeat(' ', $ident + 2) ."</varlistentry>". PHP_EOL . PHP_EOL;
		}

		$markup .= str_repeat(' ', $ident + 1) ."</variablelist>". PHP_EOL;
  		$markup .= str_repeat(' ', $ident) ."</section>". PHP_EOL;
  		$markup .= "<!-- }}} -->". PHP_EOL;

  		$content = preg_replace('/\{CONSTANTS\}/', $markup, $content, 1);
	} else {
		$content = preg_replace('/^\s*\{CONSTANTS\}.*?\n/m', '', $content, 1);
	}


	/* {PROPERTIES_LIST} */
	if ($properties = $class->getProperties()) {
		$ident = get_ident_size('PROPERTIES_LIST', $content);
		$inherited = array();

		$markup = "";
		foreach ($properties as $key => $property) {
			/* Don't get inherited properties */
			$declaring_class = $property->getDeclaringClass()->name;
			if ($declaring_class != $class->name) {
				$inherited[$declaring_class] = $declaring_class;
				unset($properties[$key]);
				continue;
			}

			$markup .= str_repeat(' ', $ident) ."<fieldsynopsis>". PHP_EOL;
			$markup .= str_repeat(' ', $ident + 1) .
                implode(
                    str_repeat(' ', $ident + 1) . PHP_EOL,
                    create_markup_to_modifiers($property->getModifiers())
                ) . PHP_EOL;
            if (PHP_VERSION_ID >= 74000 && $property->hasType()) {
                $markup .= str_repeat(' ', $ident + 1) . get_type_as_xml_string($property->getType()). PHP_EOL;
            }
			$markup .= str_repeat(' ', $ident + 1) . '<varname linkend="'. $id .'.props.'. format_id($property->getName()) .'">'. $property->getName() ."</varname>". PHP_EOL;
			$markup .= str_repeat(' ', $ident) . "</fieldsynopsis>". PHP_EOL;
		}

		if ($markup) {
			$markup = PHP_EOL . str_repeat(' ', $ident) . "<classsynopsisinfo role=\"comment\">&Properties;</classsynopsisinfo>". PHP_EOL . $markup;
		}

		if ($inherited) {
			if ($markup) {
				$markup .= PHP_EOL . str_repeat(' ', $ident);
			}
			$markup .= '<classsynopsisinfo role="comment">&InheritedProperties;</classsynopsisinfo>'. PHP_EOL;
			foreach ($inherited as $declaring_class) {
				$markup .= str_repeat(' ', $ident) ."<xi:include xpointer=\"xmlns(db=http://docbook.org/ns/docbook) xpointer(id('" . strtolower($declaring_class) . ".synopsis')/descendant::db:fieldsynopsis)\">" . PHP_EOL . str_repeat(' ', $ident + 1) . "<xi:fallback/>" . PHP_EOL . str_repeat(' ', $ident) . "</xi:include>". PHP_EOL;
			}
		}

		$content = preg_replace('/\{PROPERTIES_LIST\}/', $markup, $content, 1);
	} else {
		$content = preg_replace('/^\s*\{PROPERTIES_LIST\}.*?\n/m', '', $content, 1);
	}

	/* {PROPERTIES} */
	if ($properties) {
		$ident = get_ident_size('PROPERTIES', $content);

		$markup  = PHP_EOL ."<!-- {{{ ". $class->getName() ." properties -->". PHP_EOL;
		$markup .= str_repeat(' ', $ident) ."<section xml:id=\"". $id .".props\">". PHP_EOL;
		$markup .= str_repeat(' ', $ident + 1) ."&reftitle.properties;". PHP_EOL;
		$markup .= str_repeat(' ', $ident + 1) ."<variablelist>". PHP_EOL;

		foreach ($properties as $property) {
			$markup .= str_repeat(' ', $ident + 2) .'<varlistentry xml:id="'. $id .'.props.'. format_id($property->getName()) ."\">". PHP_EOL;
			$markup .= str_repeat(' ', $ident + 3) .'<term><varname>'. $property->getName() ."</varname></term>". PHP_EOL;
     		$markup .= str_repeat(' ', $ident + 3) ."<listitem>". PHP_EOL;
      		$markup .= str_repeat(' ', $ident + 4) ."<simpara></simpara>". PHP_EOL;
     		$markup .= str_repeat(' ', $ident + 3) ."</listitem>". PHP_EOL;
    		$markup .= str_repeat(' ', $ident + 2) ."</varlistentry>". PHP_EOL;
		}

		$markup .= str_repeat(' ', $ident + 1) ."</variablelist>". PHP_EOL;
  		$markup .= str_repeat(' ', $ident) ."</section>". PHP_EOL;
  		$markup .= "<!-- }}} -->". PHP_EOL;

    	$content = preg_replace('/\{PROPERTIES\}/', $markup, $content, 1);
	} else {
		$content = preg_replace('/^\s*\{PROPERTIES\}.*?\n/m', '', $content, 1);
    }

	/* {PROPERTY_XINCLUDE} */
	$content = preg_replace('/\{PROPERTY_XINCLUDE\}/',
		"<xi:include xpointer=\"xmlns(db=http://docbook.org/ns/docbook) xpointer(id('class.". $id ."')/db:refentry/db:refsect1[@role='description']/descendant::db:fieldsynopsis[1])\"><xi:fallback/></xi:include>". PHP_EOL,
		$content, 1);

	/* {METHOD_XINCLUDE} */
	$ident = get_ident_size('METHOD_XINCLUDE', $content);
	$content = preg_replace('/\{METHOD_XINCLUDE\}/',
		PHP_EOL . str_repeat(' ', $ident) . "<classsynopsisinfo role=\"comment\">&Methods;</classsynopsisinfo>". PHP_EOL.
		str_repeat(' ', $ident) ."<xi:include xpointer=\"xmlns(db=http://docbook.org/ns/docbook) xpointer(id('class.". $id ."')/db:refentry/db:refsect1[@role='description']/descendant::db:methodsynopsis[@role='" . $escapedName . "'])\">" . PHP_EOL . str_repeat(' ', $ident + 1) . "<xi:fallback/>" . PHP_EOL . str_repeat(' ', $ident) . "</xi:include>",
		$content, 1);

	/* {INHERITED_XINCLUDE} */
	if ($parent) {
		$ident = get_ident_size('INHERITED_XINCLUDE', $content);
		$content = preg_replace('/\{INHERITED_XINCLUDE\}/',
			PHP_EOL . str_repeat(' ', $ident) ."<classsynopsisinfo role=\"comment\">&InheritedMethods;</classsynopsisinfo>". PHP_EOL.
			str_repeat(' ', $ident) ."<xi:include xpointer=\"xmlns(db=http://docbook.org/ns/docbook) xpointer(id('class.". format_id($parent->getName()) ."')/db:refentry/db:refsect1[@role='description']/descendant::db:methodsynopsis[@role='" . $escapedName . "'])\">" . PHP_EOL . str_repeat(' ', $ident + 1) . "<xi:fallback/>" . PHP_EOL . str_repeat(' ', $ident) . "</xi:include>". PHP_EOL,
			$content, 1);
	} else {
		$content = preg_replace('/^\s*\{INHERITED_XINCLUDE\}.*?\n/m', '', $content, 1);
	}

	return $content;
}
/* }}} */

function gen_extension_markup(ReflectionExtension $obj, $content, $xml_file) { /* {{{ */
	global $INFO, $OPTION;

	switch ($xml_file) {
		case 'ini.xml':
			if ($ini = ini_get_all($obj->name)) {

				$visibility = array(
				  INI_USER   => 'INI_USER',
				  INI_PERDIR => 'INI_PERDIR',
				  INI_SYSTEM => 'INI_SYSTEM',
				  INI_ALL    => 'INI_ALL',
				);

				$ident = get_ident_size('INI_ENTRIES', $content);

				$markup = "<tbody>". PHP_EOL;
				$markup2 = '';
				foreach ($ini as $config => $value) {
					$id = "ini.". format_config($config);
					$markup .= str_repeat(' ', $ident + 1) ."<row>". PHP_EOL;
					$markup .= str_repeat(' ', $ident + 2) ."<entry><link linkend=\"". $id ."\">". $config ."</link></entry>". PHP_EOL;
					$markup .= str_repeat(' ', $ident + 2) ."<entry>". $value['global_value'] ."</entry>". PHP_EOL;
					$markup .= str_repeat(' ', $ident + 2) ."<entry>" . (isset($visibility[$value['access']]) ? ('<constant>' . $visibility[$value['access']] . '</constant>') : $value['access']) . "</entry>". PHP_EOL;
					$markup .= str_repeat(' ', $ident + 2) ."<entry><!-- leave empty, this will be filled by an automatic script --></entry>". PHP_EOL;
					$markup .= str_repeat(' ', $ident + 1) ."</row>". PHP_EOL;

					$markup2 .= ($markup2 ? str_repeat(' ', $ident) : '') ."<varlistentry xml:id=\"". $id ."\">". PHP_EOL;
					$markup2 .= str_repeat(' ', $ident + 1) ."<term>". PHP_EOL;
					$markup2 .= str_repeat(' ', $ident + 2) ."<parameter>". $config ."</parameter>". PHP_EOL;
					$markup2 .= str_repeat(' ', $ident + 2) ."<type>". get_type_by_string($value['global_value']) ."</type>". PHP_EOL;
					$markup2 .= str_repeat(' ', $ident + 1) ."</term>". PHP_EOL;
					$markup2 .= str_repeat(' ', $ident + 1) ."<listitem>". PHP_EOL;
					$markup2 .= str_repeat(' ', $ident + 2) ."<simpara>". PHP_EOL;
					$markup2 .= str_repeat(' ', $ident + 3) . PHP_EOL;
					$markup2 .= str_repeat(' ', $ident + 2) ."</simpara>". PHP_EOL;
    				$markup2 .= str_repeat(' ', $ident + 1) ."</listitem>". PHP_EOL;
   					$markup2 .= str_repeat(' ', $ident) ."</varlistentry>". PHP_EOL;
				}
				$markup .= str_repeat(' ', $ident) ."</tbody>";

				/* {INI_ENTRIES} */
				$content = preg_replace('/\{INI_ENTRIES\}/', $markup, $content, 1);

				/* {INI_ENTRIES_DESCRIPTION} */
				$content = preg_replace('/\{INI_ENTRIES_DESCRIPTION\}/', $markup2, $content, 1);

			} else {
				return false; /* Abort */
			}
		break;

		case 'constants.xml':
			if ($constants = $obj->getConstants()) {
				$ident = get_ident_size('CONSTANTS', $content);

				$markup  = "&extension.constants;". PHP_EOL;
				$markup .= str_repeat(' ', $ident) ."<variablelist>". PHP_EOL;

				foreach ($constants as $name => $value) {
					$markup .= str_repeat(' ', $ident + 1) .'<varlistentry xml:id="constant.'. format_id($name) .'">'. PHP_EOL;
					$markup .= str_repeat(' ', $ident + 2) ."<term>". PHP_EOL;
					$markup .= str_repeat(' ', $ident + 3) ."<constant>". $name ."</constant>". PHP_EOL;
					$markup .= str_repeat(' ', $ident + 3) ."(". get_xml_type_tag_or_entity($value, true) .")". PHP_EOL;
					$markup .= str_repeat(' ', $ident + 2) ."</term>". PHP_EOL;
					$markup .= str_repeat(' ', $ident + 2) ."<listitem>". PHP_EOL;
					$markup .= str_repeat(' ', $ident + 3) ."<simpara>". PHP_EOL;
					$markup .= str_repeat(' ', $ident + 3) ."</simpara>". PHP_EOL;
					$markup .= str_repeat(' ', $ident + 2) ."</listitem>". PHP_EOL;
					$markup .= str_repeat(' ', $ident + 1) ."</varlistentry>". PHP_EOL;
				}

				$markup .= str_repeat(' ', $ident) ."</variablelist>". PHP_EOL;

				$content = preg_replace('/\{CONSTANTS\}/', $markup, $content, 1);
			} else {
				$content = preg_replace('/\{CONSTANTS\}/', '&no.constants;', $content, 1);
			}
		break;

		case 'configure.xml':

			$ident  = get_ident_size('EXT_INSTALL_MAIN', $content);
			$ident2 = get_ident_size('EXT_INSTALL_WIN',  $content);

			$markup  = '';
			$markup2 = '';
			if ($OPTION['pecl'] === true) {

				$markup .= "<simpara>". PHP_EOL;
				$markup .= str_repeat(' ', $ident + 1) ."&pecl.info;". PHP_EOL;
				$markup .= str_repeat(' ', $ident + 1) ."<link xlink:href=\"&url.pecl.package;{EXT_NAME_ID}\">&url.pecl.package;{EXT_NAME_ID}</link>". PHP_EOL;
				$markup .= str_repeat(' ', $ident) . "</simpara>". PHP_EOL;

				/*
				$markup2 .= "<simpara>". PHP_EOL;
				$markup2 .= str_repeat(' ', $ident2 + 1) ."The latest PECL/{EXT_NAME_ID} Win32 DLL is available here:". PHP_EOL;
				$markup2 .= str_repeat(' ', $ident2 + 1) ."<link xlink:href=\"&url.pecl.win.ext;php_{EXT_NAME_ID}.dll\">php_{EXT_NAME_ID}.dll</link>". PHP_EOL;
				$markup2 .= str_repeat(' ', $ident2) ."</simpara>". PHP_EOL;
				*/
			} else {

				$markup .= "<simpara>". PHP_EOL;
				$markup .= str_repeat(' ', $ident + 1) ."Use <option role=\"configure\">--with-{EXT_NAME_ID}[=DIR]</option> when compiling PHP.". PHP_EOL;
				$markup .= str_repeat(' ', $ident) ."</simpara>". PHP_EOL;

				$markup2 .= "<simpara>". PHP_EOL;
				$markup2 .= str_repeat(' ', $ident2 + 1) ."Windows users should include <filename>php_{EXT_NAME_ID}.dll</filename> into &php.ini;". PHP_EOL;
				$markup2 .= str_repeat(' ', $ident2) ."</simpara>". PHP_EOL;
			}

			$content = str_replace('{EXT_INSTALL_MAIN}', $markup, $content);
			$content = str_replace('{EXT_INSTALL_WIN}', $markup2, $content);

		break;

		case 'versions.xml':

			$version_default = 'PHP 5 &gt;= Unknown';
			if ($OPTION['pecl'] === true) {
				$version_default = 'PECL {EXT_NAME_ID} &gt;= Unknown';
			}

			$markup = "";
			/* Function list */
			if ($functions = $obj->getFunctions()) {
				$markup .= "<!-- Functions -->". PHP_EOL;
				foreach ($functions as $function) {
					$markup .= " <function name='". strtolower($function->getName()) ."' from='$version_default'/>". PHP_EOL;
				}
			}
			/* Method list */
			if ($classes = $obj->getClasses()) {
				$markup .= " <!-- Classes and Methods -->". PHP_EOL;
				foreach ($classes as $class) {
					$markup .= PHP_EOL;
					$markup .= " <function name='". strtolower($class->name) ."' from='$version_default'/>". PHP_EOL;
					foreach ($class->getMethods() as $method) {
						$markup .= " <function name='". strtolower($class->name .'::'. $method->getName()) ."' from='$version_default'/>". PHP_EOL;
					}
				}
			}
			$content = preg_replace('/\{VERSIONS\}/', rtrim($markup), $content);
		break;
	}

	return $content;
}
/* }}} */

function write_doc(Reflector $obj, $type) { /* {{{ */
	global $OPTION, $INFO, $TEMPLATE, $DOC_EXT;

	switch ($type) {
		case DOC_EXTENSION:
			foreach ($DOC_EXT as $xml_file => $tpl_file) {
				$filename = $OPTION['output'] .'/'. format_filename($xml_file);
				$INFO['actual_file'] = $filename;

				$content = file_get_contents(dirname(__FILE__) .'/'. $tpl_file);
				if ($content = gen_extension_markup($obj, $content, $xml_file)) {
					save_file($filename, global_check($content));
				}
			}
			break;

		/* Methods */
		case DOC_METHOD:
		case DOC_CONSTRUCTOR:
			$path = $OPTION['output'] .'/'. strtolower($obj->class);
			$filename = $path .'/'. format_filename($obj->name) .'.xml';

			create_dir($path);

			$INFO['actual_file'] = $filename;
			$INFO['mappeds'][] = $filename;

			/* Mappeds */
			if ($function = find_function($INFO['actual_extension'], $obj, NULL)) {
				$content = file_get_contents(dirname(__FILE__) .'/mapping.tpl');
				$content = gen_mapping_markup($obj, $function, $content);
				$content = str_replace('{DEFAULT_EXAMPLE}', get_default_role('example_mapping', "{$obj->class}::{$obj->name}", $OPTION['example']), $content);
			} else {
				$content = file_get_contents(dirname(__FILE__) .'/'. $TEMPLATE[$type]);
				$content = gen_method_markup($obj, $content);
				$content = str_replace('{DEFAULT_EXAMPLE}', get_default_role('example', "{$obj->class}::{$obj->name}", $OPTION['example']), $content);
			}
			$content = str_replace('{DEFAULT_SEEALSO}', get_default_role('seealso', "{$obj->class}::{$obj->name}", $OPTION['seealso']), $content);
			save_file($filename, global_check($content));
		break;

		/* Properties */
		case DOC_PROPERTY:
			/* Doesn't exists separated file documenting property, actually
			 * they are documented in DOC_METHOD */
		break;

		/* Classes */
		case DOC_CLASS:
			$path = $OPTION['output'];
			$filename = $path .'/'. format_filename($obj->getName()) .'.xml';

			$INFO['actual_file'] = $filename;
			$content = file_get_contents(dirname(__FILE__) .'/'. $TEMPLATE[$type]);
			$content = gen_class_markup($obj, $content);

			/* classname.xml */
			save_file($filename, global_check($content));
		break;

		case DOC_FUNCTION:
			if ($method = find_function($INFO['actual_extension'], NULL, $obj)) {
				$path = $OPTION['output'] .'/'. strtolower($method->class);
				$filename = $path .'/'. format_filename($method->name) .'.xml';

				if (in_array($filename, $INFO['mappeds'])) {
					return;
				}

				create_dir($path);
				$INFO['actual_file'] = $filename;

				$content = file_get_contents(dirname(__FILE__) .'/mapping.tpl');
				$content = gen_mapping_markup($method, $obj, $content);
				$content = str_replace('{DEFAULT_EXAMPLE}', get_default_role('example_mapping', $obj->getName(), $OPTION['example']), $content);
			} else {
				$path = $OPTION['output'] .'/functions';
				$filename = $path .'/'. format_filename($obj->getName()) .'.xml';

				create_dir($path);
				$INFO['actual_file'] = $filename;

				$content = file_get_contents(dirname(__FILE__) .'/'. $TEMPLATE[$type]);
				$content = gen_function_markup($obj, $content);
				$content = str_replace('{DEFAULT_EXAMPLE}', get_default_role('example', $obj->getName(), $OPTION['example']), $content);
			}
			$content = str_replace('{DEFAULT_SEEALSO}', get_default_role('seealso', $obj->getName(), $OPTION['seealso']), $content);
			save_file($filename, global_check($content));
		break;
	}
}
/* }}} */

function gen_docs($name, $type) {	/* {{{ */
	global $OPTION, $INFO;

	if ($type & DOC_EXTENSION) {
		try {
			$extension = new ReflectionExtension($name);

			$INFO['actual_extension'] = $name;

			write_doc($extension, DOC_EXTENSION);

			foreach ($extension->getClasses() as $class) {
				gen_docs($class->name, DOC_CLASS);
			}

			foreach ($extension->getFunctions() as $function) {
				gen_docs($function->name, DOC_FUNCTION);
			}
		} catch (Exception $e) {
			die('Error: '. $e->getMessage() ."\n");
		}
	} else if ($type & DOC_FUNCTION) {
		try {
			$function = new ReflectionFunction($name);

			if (!$INFO['actual_extension']) {
				if ($extname = $function->getExtensionName()) {
					$INFO['actual_extension'] = $extname;
				} else {
					add_warning("The function {$name} lacks Reflection information");
				}
			}

			write_doc($function, DOC_FUNCTION);
		} catch (Exception $e) {
			die('Error: '. $e->getMessage() ."\n");
		}
	} else if ($type & DOC_METHOD) {
		try {
			$class = new ReflectionClass($OPTION['class']);

			if (!$INFO['actual_extension']) {
				if ($extname = $class->getExtensionName()) {
					$INFO['actual_extension'] = $extname;
				} else {
					add_warning("The method {$name} lacks Reflection information");
				}
			}

			foreach ($class->getMethods() as $method) {
				/* Don't get the inherited methods */
				if ($method->getDeclaringClass()->name == $class->name &&
					((is_array($OPTION['method']) && in_array(strtolower($method->getName()), $OPTION['method']))
					|| $OPTION['method'] == strtolower($method->getName()))) {
					write_doc($method, $method->isConstructor() ? DOC_CONSTRUCTOR : DOC_METHOD);
				}
			}
		} catch (Exception $e) {
			die('Error: '. $e->getMessage() ."\n");
		}
	} else if ($type & DOC_CLASS) {
		try {
			$class = new ReflectionClass($name);

			if (!$INFO['actual_extension']) {
				if ($extname = $class->getExtensionName()) {
					$INFO['actual_extension'] = $extname;
				} else {
					add_warning("The class {$name} lacks Reflection information");
				}
			}
			write_doc($class, DOC_CLASS);

			foreach ($class->getMethods() as $method) {
				/* Don't get the inherited methods */
				if ($method->getDeclaringClass()->name == $class->name) {
					write_doc($method, $method->isConstructor() ? DOC_CONSTRUCTOR : DOC_METHOD);
				}
			}
		} catch (Exception $e) {
			die('Error: '. $e->getMessage() ."\n");
		}
	}
}
/* }}} */

function get_default_role ($role, $funcname, $default) {

	$out = '';

	if (!$default) {
		return $out;
	}

	if ($role === 'seealso') {

		$out = <<<ROLE
 <refsect1 role="seealso">
  &reftitle.seealso;
  <simplelist>
   <member></member>
  </simplelist>
 </refsect1>
ROLE;
	}

	if ($role === 'example') {

		$out = <<<ROLE
 <refsect1 role="examples">
  &reftitle.examples;
  <example>
   <title><function>$funcname</function> example</title>
   <programlisting role="php">
<![CDATA[
<?php

/* ... */

?>
]]>
   </programlisting>
   &example.outputs.similar;
   <screen>
<![CDATA[
...
]]>
   </screen>
  </example>
 </refsect1>
ROLE;
	}

	if ($role === 'example_mapping') {

		$out = <<<ROLE
 <refsect1 role="examples">
  &reftitle.examples;
  <example>
   <title>&style.oop;</title>
   <programlisting role="php">
<![CDATA[
<?php
/* ... */
?>
]]>
    </programlisting>
    &example.outputs.similar;
    <screen>
<![CDATA[
...
]]>
   </screen>
  </example>
  <example>
   <title>&style.procedural;</title>
   <programlisting role="php">
<![CDATA[
<?php
/* ... */
?>
]]>
    </programlisting>
    &example.outputs.similar;
    <screen>
<![CDATA[
...
]]>
   </screen>
  </example>
 </refsect1>
ROLE;
	}

	return PHP_EOL . $out . PHP_EOL;
}

$OPTION  = array();
$INFO 	 = array('actual_extension' => false, 'mappeds' => array());
$WARNING = array();
$OPTION['extension'] = NULL;
$OPTION['method']	 = NULL;
$OPTION['class']	 = NULL;
$OPTION['function']  = NULL;
$OPTION['output']	 = getcwd() . '/output';
$OPTION['verbose']   = true;
$OPTION['quiet']	 = false;
$OPTION['pecl']		 = false;
$OPTION['copy']		 = false;
$OPTION['test']		 = false;
$OPTION['phpdoc']	 = NULL;
$OPTION['docbase']	 = NULL;
$OPTION['seealso']	 = false;
$OPTION['example']	 = false;

$arropts = array(
	'verbose' 		=> 'v',  /* verbose */
	'quiet'   		=> 'q',  /* quiet */
	'include:'		=> 'i:',  /* include */
	'help'	  		=> 'h',  /* help */
	'pecl'			=> 'p',  /* pecl */
	'phpdoc:'		=> 'd:', /* phpdoc dir*/
	'docbase:'		=> 'b:', /* doc-base dir */
	'copy'			=> 'a',  /* copy */
	'test'			=> 't',  /* test */
	'example'		=> 'x',  /* example */
	'seealso'		=> 's',  /* seealso */
	'output:' 		=> 'o:', /* output dir */
	'class:'  		=> 'c:', /* classname */
	'extension:' 	=> 'e:', /* extension */
	'function:' 	=> 'f:', /* function */
	'method:' 		=> 'm:'  /* method */
);

$options = @getopt(implode($arropts), array_keys($arropts));

if (!$options) {
	usage();
	exit;
}

foreach ($options as $opt => $value) {
	switch ($opt) {
		case 'v':
		case 'verbose':
			$OPTION['verbose'] = false;
			break;
		case 'h':
		case 'help':
			usage();
			break;
		case 'e':
		case 'extension':
			$OPTION['extension'] = $value;
			break;
		case 'f':
		case 'function':
			$OPTION['function'] = $value;
			break;
		case 'm':
		case 'method':
			if (!array_key_exists('c', $options) && !array_key_exists('class', $options)) {
				die("Error: The class name should be supplied (i.e. -c classname)\n");
			}
			$OPTION['method'] = is_array($value) ? array_map('strtolower', $value) : strtolower($value);
			break;
		case 'c':
		case 'class':
			$OPTION['class'] = $value;
			break;
		case 'o':
		case 'output':
			$OPTION['output'] = $value;
			break;
		case 'p':
		case 'pecl':
			$OPTION['pecl'] = true;
			break;
		case 'd':
		case 'phpdoc':
			$OPTION['phpdoc'] = realpath($value);
			break;
		case 'b':
		case 'docbase':
			$OPTION['docbase'] = realpath($value);
			break;
		case 'a':
		case 'copy':
			$OPTION['copy'] = true;
			break;
		case 't':
		case 'test':
			$OPTION['test'] = true;
			break;
		case 'q':
		case 'quiet':
			$OPTION['quiet'] = true;
			break;
		case 's':
		case 'seealso':
			$OPTION['seealso'] = true;
			break;
		case 'x':
		case 'example':
			$OPTION['example'] = true;
			break;
    case 'i':
    case 'include':
      foreach((array)$value as $filename) {
        if (stream_is_local($filename) && file_exists($filename)) {
          include $filename;
        } else {
          echo "- Cannot include '$filename': ", stream_is_local($filename) ? "doesn't exist" : "isn't local file", "\n";
        }
      }
      break;
	}
}

if (!empty($OPTION['example'])) {
	$DOC_EXT['examples.xml'] = 'examples.tpl';
}

if (!file_exists($OPTION['output']) || !is_writable($OPTION['output'])) {
	if (mkdir($OPTION['output'])) {
		echo "- Created output directory: $OPTION[output]\n";
	} else {
		die("Error: The output directory ($OPTION[output]) must be writable\n");
	}
}

if (!empty($OPTION['extension'])) {
	if (is_array($OPTION['extension'])) {
		foreach ($OPTION['extension'] as $extension) {
			gen_docs($extension, DOC_EXTENSION);
		}
	} else {
		gen_docs($OPTION['extension'], DOC_EXTENSION);
	}
}

if (!empty($OPTION['function'])) {
	gen_docs($OPTION['function'], DOC_FUNCTION);
}

if (!empty($OPTION['method'])) {
	gen_docs($OPTION['method'], DOC_METHOD);
}

if (empty($OPTION['method']) && !empty($OPTION['class'])) {
	if (is_array($OPTION['class'])) {
		foreach ($OPTION['class'] as $classname) {
			gen_docs($classname, DOC_CLASS);
		}
	} else {
		gen_docs($OPTION['class'], DOC_CLASS);
	}
}

// Copy new skeletons to the appropriate directory
// Example: --output out --class domdocument --copy --phpdoc ../../../en/reference/dom/
// That will copy over new domdocument files, while not overwriting any
if (!empty($OPTION['copy']) && !empty($OPTION['phpdoc'])) {

	if (!is_dir($OPTION['phpdoc'])) {
		echo "ERROR: The provided phpdoc path is not a directory: $OPTION[phpdoc]\n";
		exit;
	}

	if (!empty($OPTION['test'])) {
		echo "INFO: Test mode, so will not copy over files.\n";
	}

	$count_gen  = 0;
	$count_copy = 0;
	foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($OPTION['output'])) as $file) {

		$filepathname = $file->getPathName();
		$filename     = $file->getBaseName();
		$count_gen++;

		if (!$file->isFile() || pathinfo($filename, PATHINFO_EXTENSION) !== 'xml') {
			continue;
		}

		// fileid is equal in docgen output and phpdoc
		$fileid = str_replace($OPTION['output'], '', $filepathname);

		// Do not overwrite
		if (file_exists($OPTION['phpdoc'] . $fileid)) {
			echo "INFO: will not overwrite: $OPTION[phpdoc]$fileid\n";
			continue;
		}

		if (empty($OPTION['test'])) {
			// Hack to create the directory
			$dir = str_replace($filename, '', $OPTION['phpdoc'] . $fileid);
			if (!is_dir($dir)) {
				echo "INFO: Created directory $dir\n";
				create_dir($dir);
			}
			// Do the copy
			copy($OPTION['output'] . $fileid, $OPTION['phpdoc'] . $fileid);
		} else {
			echo "DEBUG: $OPTION[output]$fileid TO $OPTION[phpdoc]$fileid\n";
		}
		$count_copy++;
	}

	echo "INFO: Copied over $count_copy files, after generating $count_gen files.\n";
	if ($count_copy > 0) {
		echo "INFO: Be sure to add version information to $OPTION[phpdoc]/version.xml, because I am unsure how to do that (yet).\n";
	}
}

/* Warnings */
if (!empty($WARNING) && !$OPTION['quiet']) {
	print "\nWarnings:\n";
	foreach ($WARNING as $file => $messages) {
		printf("- %s\n", realpath($file));
		foreach ($messages as $message) {
			printf("\t-- %s\n", $message);
		}
	}
}

// vim: set noet ts=4 sw=4:
