Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
tree: b549cc2e46
Fetching contributors…

Cannot retrieve contributors at this time

1019 lines (858 sloc) 32.667 kb
<?php
/*
* PHP-GTK - The PHP language bindings for GTK+
*
* Copyright (C) 2001-2008 Andrei Zmievski <andrei@php.net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/* $Id$ */
if (!class_exists('gtk')) {
//required for signal generation. no problem if its not available; then they are just omitted
if (!@dl('php_gtk2.' . PHP_SHLIB_SUFFIX)) {
echo "Signals will NOT be generated as you don't have php-gtk2 installed\r\n";
$GLOBALS['disable_signal_generation'] = true;
}
}
set_time_limit(300);
require 'Getopt.php';
require 'arg_types.php';
require 'override.php';
require 'scheme.php';
require 'docmerger2.php';
require 'doc_templates.php';
/**
* DocGenerator class. It generates docs.
*/
class DocGenerator {
protected $parser = null;
protected $overrides = null;
protected $prefix = null;
protected $output_dir = null;
protected $fp = null;
protected $docmerger = null;
/**
* Constructor.
*
* @access public
* @param &object $parser
* @param &resource $overrides File pointer to the overrides file.
* @param &object $docmerger
* @param string $prefix The prefix for the docs.
* @param string $output_dir The directory to write to.
* @return void
*/
function DocGenerator(&$parser, &$overrides, &$docmerger, $prefix, $output_dir)
{
$this->parser = &$parser;
$this->overrides = &$overrides;
$this->docmerger = &$docmerger;
$this->prefix = $prefix;
// Check to see if an output_dir was passed.
if ($output_dir) {
$this->output_dir = $output_dir;
} else {
// If not write to stdout
$this->fp = fopen('php://stdout', 'w');
}
}
/**
* Creates the docs. Workhorse for class.
*
* @access public
* @param array $classes Associative array of classes to build docs for.
* @return void
*/
function create_docs($classes)
{
global $refentry_start_tpl,
$refentry_end_tpl;
// Make each classname lowercase
foreach ($classes as $key => $val) {
$classes[$key] = strtolower($val);
}
/**
* Object sorter. Puts objects in alphabetical order.
*/
function sort_objects($a, $b)
{
return strcmp($a->c_name, $b->c_name);
}
// Get objects from parser.
$parser_objects = $this->parser->objects;
// Sort objects from parser.
usort($parser_objects, 'sort_objects');
// Create the docs for each object.
foreach ($parser_objects as $object) {
// Get object name as lowercase module + name
$object_lcname = strtolower($object->in_module . $object->name);
// Skip objects not in classes array.
if (count($classes) && !in_array($object_lcname, $classes))
continue;
// If we have an output directory, write the docs there.
if ($this->output_dir) {
print "Generating $object_lcname.xml...";
//$this->fp = fopen($this->output_dir.'/'.$object_lcname.'.xml', 'w');
$this->write_class($object, $this->output_dir.'/'.$object_lcname.'.xml');
//fclose($this->fp);
print "\n";
} else {
// Otherwise write the docs to stdout.
$this->write_class($object);
}
}
// Check to see if we are writing miscellaneous functions.
// ex: gtk::timeout_add()
if (count($classes) == 0) {
// Write to the output directory.
if ($this->output_dir) {
print "Generating ".$this->prefix."-functions.xml...";
$this->fp = fopen($this->output_dir.'/'.$this->prefix.'-functions.xml', 'w');
fwrite($this->fp,
sprintf($refentry_start_tpl,
strtolower($this->prefix),
ucfirst(strtolower($this->prefix)),
ucfirst(strtolower($this->prefix))
));
$this->write_functions();
fwrite($this->fp, $refentry_end_tpl);
fclose($this->fp);
print "\n";
} else {
// Write to stdout
$this->write_functions();
}
}
}
/**
* Registers object types?
*
* @access public
* @param object $parser
* @return void
*/
function register_types($parser = null)
{
global $matcher;
// Use the classes parser if one wasn't given to
// the function.
if (!$parser) {
$parser = $this->parser;
}
// Register each object in the parser.
foreach ($parser->objects as $object) {
$matcher->register_object($object->c_name, $object->typecode);
}
// Register all of the enums.
foreach ($parser->enums as $enum) {
if ($enum->def_type == 'flags') {
$matcher->register_flag($enum->c_name);
} else {
$matcher->register_enum($enum->c_name);
}
}
}
/**
* Writes the docs for the given class.
*
* @access public
* @param object $object The class to write docs for.
* @return void
*/
function write_class($object, $path = NULL)
{
global $class_start_tpl,
$class_end_tpl,
$update_docs;
// Create the name as module + name
$object_name = $object->in_module . $object->name;
// Prep-a-r-e the docs if we are just updating.
// I was under the impression that this doesn't work.
if($update_docs) {
$this->docmerger->prepair($object_name);
}
// Open the file after the merge if we can.
if (!empty($path)) {
$this->fp = @fopen($path, 'w');
if (!$this->fp) {
echo "\r\nERROR: can't open file " . $path . " for writing\r\n";
exit(2);
}
}
// Check for the classmeta from file.
$id = $this->prefix . '.' . strtolower($object_name);
$merged = $this->docmerger->getSectionContentsById($id);
if (empty($merged)) {
// Write the opening of the doc file from a template.
fwrite($this->fp,
sprintf($class_start_tpl,
$this->prefix,
strtolower($object_name),
$object_name,
$object->parent,
NULL, NULL
)
);
} else {
fwrite($this->fp, '<classentry id="' . $this->prefix . '.' . strtolower($object_name) . "\">\n");
fwrite($this->fp, $merged);
}
// Write the constructor.
$this->write_constructors($object);
// Write the class methods.
$this->write_methods($object);
// Write the properties.
$this->write_properties($object);
$this->write_signals($object);
// If updating, write something else. I have to come back to this.
if($update_docs) {
fwrite($this->fp,
$this->docmerger->getSectionContentsByName('signals')
);
}
// Close off the file with another template.
fwrite($this->fp, $class_end_tpl);
fclose($this->fp);
$GLOBALS['docgenstats']['classes']++;
}
/**
* Writes the class properties to the current file.
*
* @access public
* @param object $object The class to write docs for.
* @return void.
*/
function write_properties($object)
{
global $props_start_tpl,
$props_end_tpl;
// Check to see if the class has any publicly
// accessible properties.
if (count($object->fields) == 0) {
return;
}
// Write the opening for the properties section.
fwrite($this->fp, $props_start_tpl);
// Write each property.
usort($object->fields, array(&$this, 'compareProperties'));
foreach ($object->fields as $field) {
$this->write_property($object, $field);
}
// Write the closing of the properties section.
fwrite($this->fp, $props_end_tpl);
}//function write_properties($object)
/**
* user-defined comparator for sorting properties
*/
function compareProperties($prop1, $prop2)
{
return strcmp($prop1[1], $prop2[1]);
}//function compareProperties($prop1, $prop2)
/**
* Writes one property to the current file.
*
* @access public
* @param object $object The class to write docs for.
* @param array $field The property to create docs for.
* @return void
*/
function write_property($object, $field)
{
// The property templates.
global $prop_start_tpl,
$prop_end_tpl;
// Get the property type and name.
list($field_type, $field_name) = $field;
// If we can't determine what the real type is use XXX
try {
$doc_type = $this->get_type($field_type);
} catch (Exception $ex) {
$doc_type = 'XXX';
}
// Write the opening from a template.
fwrite($this->fp, sprintf($prop_start_tpl, $this->prefix,
strtolower($object->in_module . $object->name),
$field_name, $field_name, $doc_type));
// Write the closing from a template.
fwrite($this->fp, sprintf($prop_end_tpl,
$this->gen_etter_links(
$field_name,
$this->parser->find_methods($object),
null,
true)
));
$GLOBALS['docgenstats']['properties']++;
}//function write_property($object, $field)
/**
* Write the class methods to the current file.
*
* @access public
* @param object $object The class to write docs for.
* @return void
*/
function write_methods($object)
{
// The methods section templates.
global $methods_start_tpl,
$methods_end_tpl;
// Get methods from the object.
$methods = $this->parser->find_methods($object);
// Only write the template if there are methods
// for this class.
if (count($methods)) {
fwrite($this->fp, $methods_start_tpl);
}
usort($methods, array(&$this, 'compareMethods'));
// Write each method to the current file.
foreach ($methods as $method) {
if ($this->overrides->is_overriden($method->c_name)) {
// The default method was overriden.
$this->write_method($method, true, $methods, $object->fields);
} else if (!$this->overrides->is_ignored($method->c_name)) {
// Don't write a method that is ignored.
$this->write_method($method, false, $methods, $object->fields);
}
}
// Write the closing template if there were methods
// to be written.
if (count($methods)) {
fwrite($this->fp, $methods_end_tpl);
}
}//function write_methods($object)
/**
* user-defined comparator for method sorting
*/
function compareMethods($method1, $method2)
{
return strcmp($method1->name, $method2->name);
}//function compareMethods($method1, $method2)
/**
* Writes functions that don't belong to a particular class.
*
* @access public
* @param none
* @return void
*/
function write_functions()
{
// Write each function.
foreach ($this->parser->functions as $function) {
if ($this->overrides->is_overriden($function->c_name)) {
// Function was overriden.
$this->write_method($function, true);
} else if (!$this->overrides->is_ignored($function->c_name)) {
// Don't write functions that are ignored.
$this->write_method($function, false);
}
}
}
/**
* Writes the class constructor.
*
* @access public
* @param object $object The class to write docs for.
* @return void
*/
function write_constructors($object)
{
// The constructor templates.
global $constructors_start_tpl,
$constructors_end_tpl,
$constructor_start_tpl,
$constructor_alt_start_tpl,
$constructor_end_tpl,
$funcproto_tpl,
$no_parameter_tpl,
$update_docs;
// Get the constructor method from the class.
$constructed = false;
$constructors = $this->parser->find_constructor($object, $this->overrides);
if (count($constructors) > 0) {
foreach ($constructors as $constr_number => $constructor) {
// If a constructor was found, check to see if it was
// overriden or ignore.
if ($constructor) {
if ($this->overrides->is_overriden($constructor->c_name)) {
$overriden = true;
} else if (!$this->overrides->is_ignored($constructor->c_name)) {
$overriden = false;
} else {
break;
}
} else {
// No constructor found.
break;
}
// If it hasn't been overriden, get the parameters.
if (!$overriden) {
if (($paramdef = $this->get_paramdef($constructor)) === false) {
// No parameters found!
break;
}
}
// Get the class that the constructor instantiates.
$object_name = $constructor->is_constructor_of;
$is_main_constructor = $constr_number == 0;
if ($is_main_constructor) {
$constructed = true;
fwrite($this->fp, $constructors_start_tpl);
}
if (!$is_main_constructor) {
//get the real function name
$function_name = substr($constructor->c_name, strpos($constructor->c_name, "_new_") + 1);
}
$id = $this->prefix . '.' . strtolower($object_name) . '.constructor';
$merged = $this->docmerger->getSectionContentsById($id);
if (empty($merged)) {
// Write the constructor opening template.
if ($is_main_constructor) {
//first constructor
fwrite($this->fp,
sprintf($constructor_start_tpl,
$this->prefix,
strtolower($object_name),
$constr_number));
} else {
//not the first constructor -> static factory method
fwrite($this->fp,
sprintf($constructor_alt_start_tpl,
$this->prefix,
strtolower($object_name),
$function_name));
}
// Create the function prototype docs from a template.
$function_fullname = $is_main_constructor ? $object_name : $object_name . '::' . $function_name;
$funcdef = sprintf($funcproto_tpl,
'',
$function_fullname,
$overriden ? sprintf($no_parameter_tpl, 'XXX') : $paramdef);
// Write the function prototype docs to the current file.
fwrite($this->fp, preg_replace('!^ !m', '', $funcdef));
// Write the constructor closing from a template.
fwrite($this->fp,
sprintf($constructor_end_tpl,
NULL,
NULL
)
);
$GLOBALS['docgenstats']['constructors']++;
} else {
fwrite($this->fp, $merged);
}
}//foreach constructor
if ($constructed) {
fwrite($this->fp, $constructors_end_tpl);
}
}//there is at least oneconstructor
}//function write_constructors($object)
/**
* Writes a single method to the current file.
*
* @access public
* @param object $method The method to write docs for.
* @param bool $overriden Whether or not the method was overriden.
* @param array $methods Array with all methods for this object. Used to cross-link getter and setter
* @param array $properties Array with all properties for this object. Used to cross-link them with the method
* @return void
*/
function write_method($method, $overriden, $methods = null, $properties = null)
{
// The method templates.
global $method_start_tpl,
$method_func_start_tpl,
$method_end_tpl,
$funcproto_tpl,
$no_parameter_tpl,
$update_docs;
// Get the parameters if the method was not overriden.
if (!$overriden) {
//TODO: check if alright
try {
$paramdef = $this->get_paramdef($method);
if ($paramdef == '') {
$paramdef = sprintf($no_parameter_tpl, 'void');
}
$return = $this->get_type($method->return_type);
} catch (Exception $ex) {
return;
}
}
// Write the method to the current file with the object name.
if (isset($method->of_object)) {
$object_name = $method->of_object;
$id = $this->prefix . '.' . strtolower($object_name) . '.method.' . $method->name;
$merged = $this->docmerger->getSectionContentsById($id);
if (empty($merged)) {
fwrite($this->fp,
sprintf($method_start_tpl,
$this->prefix,
strtolower($object_name),
$method->name));
} else {
fwrite($this->fp, $merged);
}
} else {
// The object name is not available.
fwrite($this->fp,
sprintf($method_func_start_tpl,
$this->prefix,
$method->name));
}
if (empty($merged)) {
// Write the method docs to the current file.
fwrite($this->fp,
sprintf($funcproto_tpl,
$overriden ? 'XXX' : $return,
$method->name,
$overriden ? sprintf($no_parameter_tpl, 'XXX') : $paramdef));
// Close off the method docs.
fwrite($this->fp,
sprintf($method_end_tpl,
NULL,
$this->gen_etter_links($method->name, $methods, $properties)
)
);
}
$GLOBALS['docgenstats']['functions']++;
}//function write_method($method, $overriden, $methods = null, $properties = null)
/**
* write the signals for the given class object.
* works only if php-gtk2 module is installed and working
*
* @param $object The class object which is passes to write_class
*/
function write_signals($object)
{
global $signals_start_tpl,
$signal_start_tpl,
$cbfuncproto_tpl,
$no_parameter_tpl,
$signal_end_tpl,
$signals_end_tpl;
if (isset($GLOBALS['disable_signal_generation']) && $GLOBALS['disable_signal_generation']) {
//is the second warning necessary?
echo "signals are NOT generated as you don't have php-gtk2 installed\r\n";
return;
}
$classname = $object->c_name;
//why can't I do "$classname::gtype"?
eval("\$gtype = $classname::gtype;");
$signals = GObject::signal_list_names($gtype);
if (count($signals) == 0) {
return;
}
fwrite($this->fp, $signals_start_tpl);
sort($signals);
foreach ($signals as $signalname) {
$GLOBALS['docgenstats']['signals']++;
fwrite($this->fp,
sprintf($signal_start_tpl,
$this->prefix,
strtolower($classname),
$signalname,
$signalname));
$signal_info = GObject::signal_query($signalname, $gtype);
$return_type = $this->get_type($signal_info[4]->name);
if (!$return_type && $signal_info[4]->name == 'void') {
$return_type = $signal_info[4]->name;
}
fwrite($this->fp,
sprintf($cbfuncproto_tpl,
$return_type ? $return_type : '<!-- was: ' . $signal_info[4]->name . ' -->XXX',
$this->get_signal_paramdef($signal_info[5], $classname)
));
fwrite($this->fp, $signal_end_tpl);
}
fwrite($this->fp, $signals_end_tpl);
}//function write_signals($object)
/**
* Gets the parameters for the given function.
*
* @access public
* @param object $function The function to get params for.
* @return void
*/
function get_paramdef($function)
{
global $matcher,
$parameter_tpl,
$no_parameter_tpl,
$opt_parameter_tpl;
// Check to see if the function has variable arguments.
if ($function->varargs) {
return false;
}
$paramdef = '';
if (count($function->params) > 0) {
// Get the info about each parameter.
foreach ($function->params as $params_array) {
list($param_type, $param_name, $param_default, $param_null) = $params_array;
// Get the php type from the c type.
if (($doc_type = $this->get_type($param_type)) === false) {
return false;
}
// Check for a default value.
if (isset($param_default)) {
$paramdef .= sprintf($opt_parameter_tpl, $doc_type, $param_name, $param_default);
} else {
$paramdef .= sprintf($parameter_tpl, $doc_type, $param_name);
}
}
}
// Return either the parameter string or a no param string
return $paramdef != null ? $paramdef : sprintf($no_parameter_tpl, 'void');
}//function get_paramdef($function)
/**
* Returns the parameters for the signal callback
* The normal get_paramdef can't be used as it requires
* parameters which signal_query doesn't support
*
* @param array $signalparams element 5 of the GObject::signal_query return
* @param string The class name of the class for which the signals are generated
* @return string The signal parameters as string
*/
function get_signal_paramdef($signalparams, $classname = null)
{
global $parameter_tpl,
$no_parameter_tpl;
$params = '';
if ($classname != null) {
$params .= sprintf($parameter_tpl,
$classname,
strtolower(substr($classname, 3)));
}
if (count($signalparams) > 0) {
foreach ($signalparams as $param) {
$type = $this->get_type($param->name);
$params .= sprintf($parameter_tpl,
$type ? $type : '<!-- was: ' . $param->name . ' -->XXX',
'UNKNOWN');
}
}
return $params;
}//function get_signal_paramdef($signalparams, $classname = null)
/**
* generates links to corresponding getter- or setter functions
* for inclusion in the <desc> section of the function method
*
* does the same for add_*, remove_* and so
*
* @return string The content to be written in the desc section
*/
function gen_etter_links($methodname, $methods, $properties, $is_property = false)
{
global $etter_start_tpl,
$etter_end_tpl,
$etter_link,
$prop_link;
if (!$is_property) {
$base = substr($methodname, strpos($methodname, '_') + 1);
} else {
$base = $methodname;
}
if ($methods === null || count($methods) == 0 || $base == substr($methodname, 1)) {
return '';
}
//other ?etter functions
$foundone = false;
$all = '';
$funcs = array();
foreach ($methods as $method) {
if (substr($method->name, strpos($method->name, '_') + 1) == $base && $method->name != $methodname) {
$funcs[] = sprintf($etter_link,
$method->of_object,
$method->name
);
}
}//foreach method
//now: check if a property with the same name exists
if ($properties != null && !$is_property && count($properties) > 0) {
foreach ($properties as $property) {
if ($property[1] == $base) {
$funcs[] = sprintf($prop_link,
$methods[0]->of_object,
$property[1]
);
}
}
}
if (count($funcs) > 0) {
$all .= $etter_start_tpl;
$all .= implode($funcs, ', ');
$all .= $etter_end_tpl;
}
return $all;
}//function gen_etter_links($methodname, $methods, $properties)
/**
* Gets a PHP type from a C type.
*
* @access public
* @param string $in_type The C type.
* @return string The PHP type or false if no match
*/
function get_type($in_type)
{
global $matcher;
// Key = C Type, Val = PHP type
static $type_map = array('none' => 'void',
'char*' => 'string',
'gchar*' => 'string',
'const-char*' => 'string',
'const-gchar*' => 'string',
'string' => 'string',
'static_string' => 'string',
'unsigned-char*' => 'string',
'guchar*' => 'string',
'char' => 'char',
'gchar' => 'char',
'guchar' => 'char',
'int' => 'int',
'gint' => 'int',
'guint' => 'int',
'short' => 'int',
'gshort' => 'int',
'gushort' => 'int',
'long' => 'int',
'glong' => 'int',
'gulong' => 'int',
'guint8' => 'int',
'gint8' => 'int',
'guint16' => 'int',
'gint16' => 'int',
'guint32' => 'int',
'gint32' => 'int',
'GtkType' => 'int',
'gboolean' => 'bool',
'double' => 'double',
'gdouble' => 'double',
'float' => 'double',
'gfloat' => 'double',
'GdkDrawable*' => 'GdkWindow');
// Check for a type handler.
try {
$type_handler = &$matcher->get($in_type);
} catch (Exception $ex) {
return false;
}
// If we have an exact match, return it.
if (isset($type_map[$in_type])) {
return $type_map[$in_type];
} else {
// Otherwise try to figure it out.
$in_type = str_replace('*', '', $in_type);
// See if the type is an object.
$type_handler_class = get_class($type_handler);
if ($type_handler_class == 'object_arg' ||
$type_handler_class == 'boxed_arg') {
return "<classname>$in_type</classname>";
} else if ($type_handler_class == 'enum_arg' ||
$type_handler_class == 'flags_arg') {
// Type is an enum or a flag
return "<enumname>$in_type</enumname>";
} else {
// Can't figure it out. Just give it back.
return $in_type;
}
}
}
}
/* For backwards compatibility. */
chdir(dirname(__FILE__));
// Use $_SERVER if available.
if (isset($_SERVER)) {
$argc = $_SERVER['argc'];
$argv = $_SERVER['argv'];
} else {
$argc = $HTTP_SERVER_VARS['argc'];
$argv = $HTTP_SERVER_VARS['argv'];
}
$GLOBALS['docgenstats'] = array(
'classes' => 0,
'constructors' => 0,
'functions' => 0,
'properties' => 0,
'signals' => 0
);
/* An ugly hack to counteract PHP's pernicious desire to treat + as an argument
separator in command-line version. */
// I don't need this. It causes trouble.
// array_walk($argv, create_function('&$x', '$x = urldecode($x);'));
// Use PEAR::Console_Getopt to get the arguments from the
// command line.
$result = Console_Getopt::getopt($argv, 'o:p:r:d:s:l:u');
if (!$result || count($result[1]) < 2) {
// Set up the help message.
die(
"Usage: php -q generator.php [OPTION] defsfile [class ...]\n\n" .
" -o <file> use overrides in <file>\n" .
" -p <prefix> use <prefix> for docs\n" .
" -r <file> register types from <file>\n" .
" -d <path> output files to this directory\n" .
" -s <path> documentation dir\n" .
" -l <lang> Language\n" .
" -u Update existing docs\n"
);
}
list($opts, $argv) = $result;
// Set the default options.
$prefix = 'gtk';
$overrides = new Overrides();
$lang = 'en';
$update_docs = FALSE;
// Override the defaults with the command line options.
foreach ($opts as $opt) {
list($opt_spec, $opt_arg) = $opt;
if ($opt_spec == 'o') {
$overrides = new Overrides($opt_arg);
} else if ($opt_spec == 'p') {
$prefix = $opt_arg;
} else if ($opt_spec == 'r') {
$type_parser = new Defs_Parser($opt_arg);
$type_parser->start_parsing();
DocGenerator::register_types($type_parser);
} else if ($opt_spec == 'd') {
$output_dir = $opt_arg;
} else if ($opt_spec == 's') {
$docs_dir = $opt_arg;
} else if ($opt_spec == 'l') {
$lang = $opt_arg;
} else if ($opt_spec == 'u') {
$update_docs = TRUE;
}
}
// Create a new defs parser.
$parser = new Defs_Parser($argv[1]);
// Create a DocMerger if updating.
//if($update_docs) {
$docmerger = new DocMerger($docs_dir, $lang, $prefix);
//}
// Create the generator.
$generator = new DocGenerator($parser, $overrides, $docmerger, $prefix, $output_dir);
// Get to work.
$parser->start_parsing();
$generator->register_types();
$generator->create_docs(array_slice($argv, 2));
//stats
echo 'Some statistics:' . "\r\n";
foreach ($GLOBALS['docgenstats'] as $name => $value) {
echo ' ' . str_pad($name, 12) . ' ' . str_pad($value, 4, ' ', STR_PAD_LEFT) . "\r\n";
}
/*
* Local variables:
* tab-width: 4
* c-basic-offset: 4
* End:
*/
?>
Jump to Line
Something went wrong with that request. Please try again.