* * 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, '\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 : '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 : 'XXX', 'UNKNOWN'); } } return $params; }//function get_signal_paramdef($signalparams, $classname = null) /** * generates links to corresponding getter- or setter functions * for inclusion in the 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 "$in_type"; } else if ($type_handler_class == 'enum_arg' || $type_handler_class == 'flags_arg') { // Type is an enum or a flag return "$in_type"; } 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 gtkgenerator.php [OPTION] defsfile [class ...]\n\n" . " -o use overrides in \n" . " -p use for docs\n" . " -r register types from \n" . " -d output files to this directory\n" . " -s documentation dir\n" . " -l 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: */ ?>