#!/usr/bin/perl -w # # gtk-doc - GTK DocBook documentation generator. # Copyright (C) 1998 Damon Chaplin # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # ############################################################################# # Script : gtkdoc-mktmpl # Description : This creates or updates the template files which contain the # manually-edited documentation. (A 'template' is a simple text # form which is filled in with the description of a function, # macro, enum, or struct. For functions and macros it also # contains fields for describing the parameters.) # # This script reads in the existing templates, found in # tmpl/*.sgml, moves these files to tmpl/*.sgml.bak, and then # recreates the .sgml files according to the structure given in # the file $MODULE-sections.txt. # # Any new templates added, or new function parameters, are # marked with 'FIXME' so you can do a grep to see which parts # need updating. # # Any templates which are no longer used (i.e. they are remove # from $MODULE-sections.txt) are placed in the file # tmpl/$MODULE-unused.txt. If they are included again later # they are automatically copied back into position. # If you are certain that these templates will never be used # again you can delete them from $MODULE-unused.txt. # # Any parameters to functions which are no longer used are # separated from the rest of the parameters with the line # ''. It may be that the parameter # name has just been changed, in which case you can copy the # description to the parameter with the new name. You can delete # the unused parameter descriptions when no longer needed. ############################################################################# use strict; use Getopt::Long; # Options # name of documentation module my $MODULE; my $TMPL_DIR; my $FLAG_CHANGES; my %optctl = (module => \$MODULE, 'flag-changes' => \$FLAG_CHANGES, 'output-dir' => \$TMPL_DIR); GetOptions(\%optctl, "module=s", "flag-changes!", "output-dir:s"); my $ROOT_DIR = "."; # The directory containing the template files. $TMPL_DIR = $TMPL_DIR ? $TMPL_DIR : "$ROOT_DIR/tmpl"; # This file contains the object hierarchy. my $OBJECT_TREE_FILE = "$ROOT_DIR/$MODULE.hierarchy"; # The file containing signal handler prototype information. my $SIGNALS_FILE = "$ROOT_DIR/$MODULE.signals"; # The file containing Arg information. my $ARGS_FILE = "$ROOT_DIR/$MODULE.args"; # Set the flag to indicate changes, if requested. my $CHANGES_FLAG = $FLAG_CHANGES ? "FIXME" : ""; # These global arrays store information on signals. Each signal has an entry # in each of these arrays at the same index, like a multi-dimensional array. my @SignalObjects; # The GtkObject which emits the signal. my @SignalNames; # The signal name. my @SignalReturns; # The return type. my @SignalPrototypes; # The rest of the prototype of the signal handler. # These global arrays store information on Args. Each Arg has an entry # in each of these arrays at the same index, like a multi-dimensional array. my @ArgObjects; # The GtkObject which has the Arg. my @ArgNames; # The Arg name. my @ArgTypes; # The Arg type - gint, GtkArrowType etc. my @ArgFlags; # How the Arg can be used - readable/writable etc. # These global hashes store declaration info keyed on a symbol name. my %Declarations; my %DeclarationTypes; my %DeclarationConditional; my %DeclarationOutput; # These global hashes store the existing documentation. my %SymbolDocs; my %SymbolTypes; my %SymbolParams; # These global arrays store GtkObject and subclasses and the hierarchy. my @Objects; my @ObjectLevels; &ReadSignalsFile ($SIGNALS_FILE); &ReadArgsFile ($ARGS_FILE); &ReadObjectHierarchy; &ReadExistingTemplates; &BackupExistingTemplates; &UpdateTemplates ("$ROOT_DIR/$MODULE-sections.txt"); &OutputUnusedTemplates; &CheckAllDeclarationsOutput; ############################################################################# # Function : ReadExistingTemplates # Description : This reads in all the existing documentation, into the global # variables %SymbolDocs, %SymbolTypes, and %SymbolParams (a # hash of arrays). # Arguments : none ############################################################################# sub ReadExistingTemplates { %SymbolDocs = (); %SymbolTypes = (); %SymbolParams = (); # Read the unused docs first, so they get overridden by any real docs. # (though this shouldn't happen often). my $unused_doc = "$TMPL_DIR/$MODULE-unused.sgml"; if (-e $unused_doc) { &ReadTemplateFile ($unused_doc, 0); } while (<$TMPL_DIR/*.sgml>) { # print "Reading $_\n"; if ($_ eq $unused_doc) { # print "skipping $unused_doc\n"; } else { &ReadTemplateFile ($_, 0); } } } ############################################################################# # Function : BackupExistingTemplates # Description : This moves all existing .sgml to .sgml.bak. # Arguments : none ############################################################################# sub BackupExistingTemplates { while (<$TMPL_DIR/*.sgml>) { my $backup_file = $_ . ".bak"; # print "Backing up $_ to $backup_file\n"; if (-e $backup_file) { unlink ($backup_file) || die "Can't delete old backup file: $backup_file"; } rename ($_, $backup_file) || die "Can't move $_ to $backup_file"; } } ############################################################################# # Function : UpdateTemplates # Description : This collects the output for each section of the docs, and # outputs each file when the end of the section is found. # Arguments : $file - the file containing the sections of the docs. ############################################################################# sub UpdateTemplates { my ($file) = @_; # print "Reading: $file\n"; open (INPUT, $file) || die "Can't open $file"; # Create the top output directory if it doesn't exist. if (! -e $TMPL_DIR) { mkdir ("$TMPL_DIR", 0777) || die "Can't create directory: $TMPL_DIR"; } my $title = ""; my $subsection = ""; my $output; while () { if (m/^#/) { next; } elsif (m/^
/) { $output = ""; } elsif (m/^/i) { $subsection = $1; next; } elsif (m/^(.*)<\/TITLE>/) { $title = $1; # print "Section: $title\n"; # We don't want warnings if object & class structs aren't used. # $DeclarationOutput{$title} = 1; $DeclarationOutput{"${title}Class"} = 1; } elsif (m/^<FILE>(.*)<\/FILE>/) { $file = $1; } elsif (m/^<INCLUDE>(.*)<\/INCLUDE>/) { next; } elsif (m/^<\/SECTION>/) { if ($title eq "") { $title = $file; } # print "End of section: $title\n"; $file =~ s/\s/_/g; $file .= ".sgml"; &OutputTemplateFile ($file, $title, \$output); $title = ""; $subsection = ""; } elsif (m/^(\S+)/) { my $symbol = $1; # print " Symbol: $symbol\n"; my $declaration = $Declarations{$1}; if (defined ($declaration)) { # We don't want templates for standard macros/functions of # GtkObjects or private declarations. if ($subsection ne "Standard" && $subsection ne "Private") { $output .= &OutputDeclaration ($DeclarationTypes {$symbol}, $symbol, $declaration); } # Note that the declaration has been output. $DeclarationOutput{$symbol} = 1; if ($declaration eq '##conditional##') { # print "Conditional $DeclarationTypes{$symbol}\n"; } } else { print "WARNING: No declaration for: $1\n"; } } } close (INPUT); } ############################################################################# # Function : CheckAllDeclarationsOutput # Description : This steps through all the declarations that were loaded, and # makes sure that each one has been output, by checking the # corresponding flag in the %DeclarationOutput hash. It is # intended to check that any new declarations in new versions # of GTK/Gnome get added to the $MODULE-sections.txt file. # Arguments : none ############################################################################# sub CheckAllDeclarationsOutput { my $num_unused = 0; open (UNUSED, ">$ROOT_DIR/$MODULE-unused.txt") || die "Can't open $ROOT_DIR/$MODULE-unused.txt"; my ($symbol); foreach $symbol (keys (%Declarations)) { if (!defined ($DeclarationOutput{$symbol})) { print (UNUSED "$symbol\n"); $num_unused++; } } close (UNUSED); if ($num_unused != 0) { print <<EOF; ============================================================================= WARNING: $num_unused unused declarations. These can be found in $MODULE-unused.txt. They should be added to $MODULE-sections.txt in the appropriate place. ============================================================================= EOF } } ############################################################################# # Function : OutputDeclaration # Description : This returns the template for one symbol & declaration. # Note that it uses the global %SymbolDocs and %SymbolParams to # lookup any existing documentation. # Arguments : $type - the type of the symbol ('FUNCTION'/'MACRO' etc.) # $symbol - the symbol name. # $declaration - the declaration of the symbol. ############################################################################# sub OutputDeclaration { my ($type, $symbol, $declaration) = @_; my ($output) = ""; # print "Outputting $type: $symbol\n"; # See if symbol already has a description. my ($symbol_desc) = $SymbolDocs{$symbol}; my ($template_exists); if (defined ($symbol_desc)) { $template_exists = 1; $symbol_desc =~ s/\s+$//; } else { $template_exists = 0; $symbol_desc = "<para>\n$CHANGES_FLAG\n</para>"; } $output .= <<EOF; <!-- ##### $type $symbol ##### --> $symbol_desc EOF # For functions, function typedefs and macros, we output the arguments. # For functions and function typedefs we also output the return value. if ($type eq "FUNCTION" || $type eq "USER_FUNCTION") { # Take out the return type $declaration =~ s/<RETURNS>\s*(const\s+|unsigned\s+)*(\w+)\s*(\**)\s*<\/RETURNS>\n//; my ($ret_type) = $2; my ($param_num) = 0; my ($name); while ($declaration ne "") { if ($declaration =~ s/^[\s,]+//) { # skip whitespace and commas next; } elsif ($declaration =~ s/^void\s*[,\n]//) { if ($param_num != 0) { print "WARNING: void used as parameter in function $symbol\n"; } } elsif ($declaration =~ s/^...\s*[,\n]//) { $output .= &OutputParam ($symbol, "Varargs", $template_exists, 1, ""); # Try to match a standard parameter. } elsif ($declaration =~ s/^(const\s+|unsigned\s+)*(struct\s+)?(\w+)\s*(\**)\s*(const\s+)?(\**)?\s*(\w+)?\s*(\[\d*\])?\s*[,\n]//) { if (defined ($7)) { $name = $7; } else { $name = "Param" . ($param_num + 1); } $output .= &OutputParam ($symbol, $name, $template_exists, 1, ""); # Try to match parameters which are functions. } elsif ($declaration =~ s/^(const\s+|unsigned\s+)*(struct\s+)?(\w+)\s*(\**)\s*(const\s+)?\(\s*\*\s*(\w+)\s*\)\s*\(([^)]*)\)\s*[,\n]//) { $name = $6; $output .= &OutputParam ($symbol, $name, $template_exists, 1, ""); } else { print "###Can't parse args for function $symbol: $declaration\n"; last; } $param_num++; } if ($ret_type ne "void") { $output .= &OutputParam ($symbol, "Returns", $template_exists, 1, ""); } $output .= &OutputOldParams ($symbol); $output .= "\n"; } if ($type eq "MACRO") { if ($declaration =~ m/^\s*#\s*define\s+\w+\(([^\)]*)\)/) { my ($param); foreach $param (split (/,/, $1)) { $param =~ s/^\s+//; $param =~ s/\s*$//; if ($param =~ m/\S/) { $output .= &OutputParam ($symbol, $param, $template_exists, 1, ""); } } } $output .= &OutputParam ($symbol, "Returns", $template_exists, 0, ""); $output .= &OutputOldParams ($symbol); $output .= "\n"; } if ($type eq "STRUCT") { my $is_object_struct = CheckIsObject ($symbol); my @fields = ParseStructDeclaration($declaration, $is_object_struct); for (my $i = 0; $i <= $#fields; $i += 2) { my $field_name = $fields[$i]; $output .= &OutputParam ($symbol, $field_name, $template_exists, 1, ""); } } if ($type eq "ENUM") { my @members = ParseEnumDeclaration($declaration); for my $member (@members) { $output .= &OutputParam ($symbol, $member, $template_exists, 1, ""); } } $output .= "\n"; # Remove the used docs from the hashes. if ($template_exists) { delete $SymbolDocs{$symbol}; delete $SymbolParams{$symbol}; } return $output; } ############################################################################# # Function : OutputParam # Description : This outputs the part of a template for one parameter. # It first checks if the parameter is already described, and if # so it uses that description, and clears it so it isn't output # as an old param. # Arguments : $symbol - the symbol (function or macro) name. # $param_to_output - the parameter to add. # $template_exists - TRUE if the template already existed in # template files. If it did, then we will flag any changes # with 'FIXME'. # $force_output - TRUE if the parameter should be output even # if it didn't already exist in the template. (The return # values of macros are added manually if required, and so we # never add it here - we only copy it if it already exists.) # $default_description - the default description of the # parameter to be used if it doesn't already exist. (Signal # handlers have a few common parameters.) ############################################################################# sub OutputParam { my ($symbol, $param_to_output, $template_exists, $force_output, $default_description) = @_; my ($j); my ($params) = $SymbolParams{$symbol}; if (defined ($params)) { for ($j = 0; $j <= $#$params; $j += 2) { my $param_name = $$params[$j]; my $param_desc = $$params[$j + 1]; if ($param_name eq $param_to_output) { $param_desc =~ s/\s+$//; $$params[$j] = ""; $$params[$j + 1] = ""; return "\@$param_name: $param_desc\n"; } } } # If the template was already in a file, flag the new parameter. # If not, the template itself will be flagged, so we don't need to flag # all the new parameters as well. if ($force_output) { if ($default_description ne "") { $default_description =~ s/\s+$//; return "\@$param_to_output: $default_description\n"; } else { if ($template_exists) { return "\@$param_to_output: $CHANGES_FLAG\n"; } else { return "\@$param_to_output: \n"; } } } return ""; } ############################################################################# # Function : OutputOldParams # Description : This returns all the existing documentation for parameters of # the given function/macro/signal symbol which are unused, with # a comment before them. # Arguments : $symbol - the symbol (function/macro/signal) name. ############################################################################# sub OutputOldParams { my ($symbol) = @_; my $output = ""; my ($params) = $SymbolParams{$symbol}; if (defined ($params)) { my $j; for ($j = 0; $j <= $#$params; $j += 2) { my $param_name = $$params[$j]; my $param_desc = $$params[$j + 1]; if ($param_name ne "") { $param_desc =~ s/\s+$//; $output .= "\@$param_name: $param_desc\n"; } } } if ($output) { $output = "<!-- # Unused Parameters # -->\n" . $output; } return $output; } ############################################################################# # Function : OutputTemplateFile # Description : This outputs one template file. # Arguments : $file - the basename of the file to output. # $title - the title from the $MODULE-sections.txt file. This # will be overridden by any title given in the template file. # $output - reference to the templates to output. ############################################################################# sub OutputTemplateFile { my ($file, $title, $output) = @_; my ($short_desc, $long_desc, $see_also); if (defined ($SymbolDocs{"$TMPL_DIR/$file:Title"})) { $title = $SymbolDocs{"$TMPL_DIR/$file:Title"}; delete $SymbolDocs{"$TMPL_DIR/$file:Title"}; } if (defined ($SymbolDocs{"$TMPL_DIR/$file:Short_Description"})) { $short_desc = $SymbolDocs{"$TMPL_DIR/$file:Short_Description"}; delete $SymbolDocs{"$TMPL_DIR/$file:Short_Description"}; } else { $short_desc = ""; } if (defined ($SymbolDocs{"$TMPL_DIR/$file:Long_Description"})) { $long_desc = $SymbolDocs{"$TMPL_DIR/$file:Long_Description"}; delete $SymbolDocs{"$TMPL_DIR/$file:Long_Description"}; } else { $long_desc = "<para>\n\n</para>\n"; } if (defined ($SymbolDocs{"$TMPL_DIR/$file:See_Also"})) { $see_also = $SymbolDocs{"$TMPL_DIR/$file:See_Also"}; delete $SymbolDocs{"$TMPL_DIR/$file:See_Also"}; } else { $see_also = "<para>\n\n</para>\n"; } open (OUTPUT, ">$TMPL_DIR/$file") || die "Can't create $TMPL_DIR/$file"; print (OUTPUT <<EOF); <!-- ##### SECTION Title ##### --> $title <!-- ##### SECTION Short_Description ##### --> $short_desc <!-- ##### SECTION Long_Description ##### --> $long_desc <!-- ##### SECTION See_Also ##### --> $see_also EOF print (OUTPUT $$output); &OutputSignalTemplates ($title); &OutputArgTemplates ($title); close (OUTPUT); } ############################################################################# # Function : OutputSignalTemplates # Description : Outputs templates for signal handlers. # Arguments : $title - the title from the $MODULE-sections.txt file. If the # file is describing a GtkObject subclass, the title should # be the name of the class, e.g. 'GtkButton'. ############################################################################# sub OutputSignalTemplates { my ($title) = @_; my $output = ""; my ($i, $template_exists); for ($i = 0; $i <= $#SignalObjects; $i++) { if ($SignalObjects[$i] eq $title) { # print "Found signal: $SignalObjects[$i]\n"; my ($symbol) = "$SignalObjects[$i]::$SignalNames[$i]"; # See if symbol already has a description. my ($symbol_desc) = $SymbolDocs{$symbol}; if (defined ($symbol_desc)) { $template_exists = 1; $symbol_desc =~ s/\s+$//; delete $SymbolDocs{$symbol}; } else { $template_exists = 0; $symbol_desc = "<para>\n$CHANGES_FLAG\n</para>"; } $output .= <<EOF; <!-- ##### SIGNAL $symbol ##### --> $symbol_desc EOF my @params = split ("[,\n]", $SignalPrototypes[$i]); my ($j, $name); for ($j = 0; $j <= $#params; $j++) { my $param = $params[$j]; $param =~ s/^\s+//; $param =~ s/\s*$//; if ($param =~ m/^\s*$/) { next; } if ($param =~ m/^void$/) { next; } if ($param =~ m/^\s*(\w+)\s*(\**)\s*([\w\[\]]+)?\s*$/) { if (defined($3)) { $name = $3; } else { $name = "Param" . ($j + 1); } if ($j == 0) { $output .= &OutputParam ($symbol, $name, $template_exists, 1, "the object which received the signal."); } else { $output .= &OutputParam ($symbol, $name, $template_exists, 1, ""); } } } if ($SignalReturns[$i] ne "void") { $output .= &OutputParam ($symbol, "Returns", $template_exists, 1, ""); } $output .= &OutputOldParams ($symbol); $output .= "\n"; } } print (OUTPUT $output); } ############################################################################# # Function : OutputArgTemplates # Description : Outputs templates for Args. # Arguments : $title - the title from the $MODULE-sections.txt file. If the # file is describing a GtkObject subclass, the title should # be the name of the class, e.g. 'GtkButton'. ############################################################################# sub OutputArgTemplates { my ($title) = @_; my $output = ""; my $i; for ($i = 0; $i <= $#ArgObjects; $i++) { if ($ArgObjects[$i] eq $title) { # print "Found arg: $ArgObjects[$i]\n"; # I've only used one colon so we don't clash with signals. my ($symbol) = "$ArgObjects[$i]:$ArgNames[$i]"; # See if symbol already has a description. my ($symbol_desc) = $SymbolDocs{$symbol}; if (defined ($symbol_desc)) { delete $SymbolDocs{$symbol}; $symbol_desc =~ s/\s+$//; } else { $symbol_desc = "<para>\n$CHANGES_FLAG\n</para>"; } $output .= <<EOF; <!-- ##### ARG $symbol ##### --> $symbol_desc EOF } } print (OUTPUT $output); } ############################################################################# # Function : OutputUnusedTemplates # Description : This saves any unused documentation into $MODULE-unused.sgml. # Arguments : none ############################################################################# sub OutputUnusedTemplates { my ($unused_file) = "$TMPL_DIR/$MODULE-unused.sgml"; open (UNUSED, ">$unused_file") || die "Can't open file: $unused_file"; my $output = ""; my ($symbol, $symbol_desc); while (($symbol, $symbol_desc) = each (%SymbolDocs)) { # print "Unused: $symbol\n"; my $type = $SymbolTypes{$symbol}; if (!defined ($type)) { $type = "UNKNOWN"; print "WARNING: Unused symbol $symbol has unknown type\n"; } $output .= <<EOF; <!-- ##### $type $symbol ##### --> $symbol_desc EOF my ($params) = $SymbolParams{$symbol}; if (defined ($params)) { my $j; for ($j = 0; $j <= $#$params; $j += 2) { my $param_name = $$params[$j]; my $param_desc = $$params[$j + 1]; $param_desc =~ s/\s+$//; $output .= "\@$param_name: $param_desc\n"; } } $output .= "\n"; } print UNUSED $output; close (UNUSED); } ############################################################################# # LIBRARY FUNCTIONS - These functions are used in both gtkdoc-mkdb and # gtkdoc-mktmpl and should eventually be moved to a # separate library. ############################################################################# ############################################################################# # Function : ReadDeclarationsFile # Description : This reads in a file containing the function/macro/enum etc. # declarations. # # Note that in some cases there are several declarations with # the same name, e.g. for conditional macros. In this case we # set a flag in the %DeclarationConditional hash so the # declaration is not shown in the docs. # # If a macro and a function have the same name, e.g. for # gtk_object_ref, the function declaration takes precedence. # # Some opaque structs are just declared with 'typedef struct # _name name;' in which case the declaration may be empty. # The structure may have been found later in the header, so # that overrides the empty declaration. # # Arguments : $file - the declarations file to read # $override - if declarations in this file should override # any current declaration. ############################################################################# sub ReadDeclarationsFile { my ($file, $override) = @_; if ($override == 0) { %Declarations = (); %DeclarationTypes = (); %DeclarationConditional = (); %DeclarationOutput = (); } open (INPUT, $file) || die "Can't open $file"; my $declaration_type = ""; my $declaration_name; my $declaration; while (<INPUT>) { if (!$declaration_type) { if (m/^<([^>]+)>/) { $declaration_type = $1; $declaration_name = ""; # print "Found declaration: $declaration_type\n"; $declaration = ""; } } else { if (m%^<NAME>(.*)</NAME>%) { $declaration_name = $1; } elsif (m%^</$declaration_type>%) { # print "Found end of declaration: $declaration_name\n"; # Check that the declaration has a name if ($declaration_name eq "") { print "ERROR: $declaration_type has no name $file:$.\n"; } # Check if the symbol is already defined. if (defined ($Declarations{$declaration_name}) && $override == 0) { # Function declarations take precedence. if ($DeclarationTypes{$declaration_name} eq 'FUNCTION') { # Ignore it. } elsif ($declaration_type eq 'FUNCTION') { $Declarations{$declaration_name} = $declaration; $DeclarationTypes{$declaration_name} = $declaration_type; } elsif ($DeclarationTypes{$declaration_name} eq $declaration_type) { # If the existing declaration is empty override it. if ($declaration_type eq 'STRUCT') { if ($Declarations{$declaration_name} =~ m/^\s*$/) { $Declarations{$declaration_name} = $declaration; } elsif ($declaration =~ m/^\s*$/) { # Ignore an empty declaration. } else { print "WARNING: Structure has multiple definitions: $declaration_name\n"; } } else { # set flag in %DeclarationConditional hash for # multiply defined macros/typedefs. $DeclarationConditional{$declaration_name} = 1; } } else { print "WARNING: $declaration_name has multiple definitions\n"; } } else { $Declarations{$declaration_name} = $declaration; $DeclarationTypes{$declaration_name} = $declaration_type; } $declaration_type = ""; } else { $declaration .= $_; } } } close (INPUT); } ############################################################################# # Function : ReadSignalsFile # Description : This reads in an existing file which contains information on # all GTK signals. It creates the arrays @SignalNames and # @SignalPrototypes containing info on the signals. The first # line of the SignalPrototype is the return type of the signal # handler. The remaining lines are the parameters passed to it. # The last parameter, "gpointer user_data" is always the same # so is not included. # Arguments : $file - the file containing the signal handler prototype # information. ############################################################################# sub ReadSignalsFile { my ($file) = @_; my $in_signal = 0; my $signal_object; my $signal_name; my $signal_returns; my $signal_prototype; # Reset the signal info. @SignalObjects = (); @SignalNames = (); @SignalReturns = (); @SignalPrototypes = (); if (! -f $file) { return; } if (!open (INPUT, $file)) { warn "Can't open $file - skipping signals\n"; return; } while (<INPUT>) { if (!$in_signal) { if (m/^<SIGNAL>/) { $in_signal = 1; $signal_object = ""; $signal_name = ""; $signal_returns = ""; $signal_prototype = ""; } } else { if (m/^<NAME>(.*)<\/NAME>/) { $signal_name = $1; if ($signal_name =~ m/^(.*)::(.*)$/) { $signal_object = $1; $signal_name = $2; # print "Found signal: $signal_name\n"; } else { print "Invalid signal name: $signal_name\n"; } } elsif (m/^<RETURNS>(.*)<\/RETURNS>/) { $signal_returns = $1; } elsif (m%^</SIGNAL>%) { # print "Found end of signal: ${signal_object}::${signal_name}\nReturns: ${signal_returns}\n${signal_prototype}"; push (@SignalObjects, $signal_object); push (@SignalNames, $signal_name); push (@SignalReturns, $signal_returns); push (@SignalPrototypes, $signal_prototype); $in_signal = 0; } else { $signal_prototype .= $_; } } } close (INPUT); } ############################################################################# # Function : ReadTemplateFile # Description : This reads in the manually-edited documentation file # corresponding to the file currently being created, so we can # insert the documentation at the appropriate places. # It outputs %SymbolTypes, %SymbolDocs and %SymbolParams, which # is a hash of arrays. # NOTE: This function is duplicated in gtkdoc-mkdb (but # slightly different). # Arguments : $docsfile - the template file to read in. # $skip_unused_params - 1 if the unused parameters should be # skipped. ############################################################################# sub ReadTemplateFile { my ($docsfile, $skip_unused_params) = @_; # print "Reading $docsfile\n"; if (! -f $docsfile) { print "File doesn't exist: $docsfile\n"; return; } my $CurrentType = ""; # Type of symbol being read. my $CurrentSymbol = ""; # Name of symbol being read. my $SymbolDoc = ""; # Description of symbol being read. my @Params; # Parameter names and descriptions of current # function/macro/function typedef. my $CurrentParam = -1; # Index of parameter currently being read. # Note that the param array contains pairs # of param name & description. my $InUnusedParameters = 0; # True if we are reading in the unused params. open (DOCS, $docsfile) || die "Can't open file $docsfile: $!"; while (<DOCS>) { if (m/^<!-- ##### ([A-Z_]+) (\S+) ##### -->/) { my $type = $1; my $symbol = $2; if ($symbol eq "Title" || $symbol eq "Short_Description" || $symbol eq "Long_Description" || $symbol eq "See_Also") { $symbol = $docsfile . ":" . $symbol; # print "Found symbol: $symbol\n"; } # Store previous symbol, but remove any trailing blank lines. if ($CurrentSymbol ne "") { $SymbolDoc =~ s/\s+$//; $SymbolTypes{$CurrentSymbol} = $CurrentType; $SymbolDocs{$CurrentSymbol} = $SymbolDoc; if ($CurrentParam >= 0) { $SymbolParams{$CurrentSymbol} = [ @Params ]; } else { # Delete any existing params in case we are overriding a # previously read template. delete $SymbolParams{$CurrentSymbol}; } } $CurrentType = $type; $CurrentSymbol = $symbol; $CurrentParam = -1; $InUnusedParameters = 0; $SymbolDoc = ""; @Params = (); } elsif (m/^<!-- # Unused Parameters # -->/) { $InUnusedParameters = 1; next; } else { # Check if param found if (s/^\@(\S+):\s*//) { my $param_name = $1; # Allow variations of 'Returns' if ($param_name =~ m/^[Rr]eturns?$/) { $param_name = "Returns"; } # print "Found param: $param_name\n"; push (@Params, $param_name); push (@Params, $_); $CurrentParam += 2; next; } # When outputting the DocBook we skip unused parameters. if (!$InUnusedParameters || !$skip_unused_params) { if ($CurrentParam >= 0) { $Params[$CurrentParam] .= $_; } else { $SymbolDoc .= $_; } } } } # Remember to finish the current symbol doccs. if ($CurrentSymbol ne "") { $SymbolDoc =~ s/\s+$//; $SymbolTypes{$CurrentSymbol} = $CurrentType; $SymbolDocs{$CurrentSymbol} = $SymbolDoc; if ($CurrentParam >= 0) { $SymbolParams{$CurrentSymbol} = [ @Params ]; } else { delete $SymbolParams{$CurrentSymbol}; } } close (DOCS); } ############################################################################# # Function : ReadObjectHierarchy # Description : This reads in the $MODULE-hierarchy.txt file containing all # the GtkObject subclasses described in this module (and their # ancestors). # It places them in the @Objects array, and places their level # in the widget hierarchy in the @ObjectLevels array, at the # same index. GtkObject, the root object, has a level of 1. # # FIXME: the version in gtkdoc-mkdb also generates tree_index.sgml # as it goes along, this should be split out into a separate # function. # # Arguments : none ############################################################################# sub ReadObjectHierarchy { @Objects = (); @ObjectLevels = (); if (! -f $OBJECT_TREE_FILE) { return; } if (!open (INPUT, $OBJECT_TREE_FILE)) { warn "Can't open $OBJECT_TREE_FILE - skipping object tree\n"; return; } while (<INPUT>) { if (m/\S+/) { my $object = $&; my $level = (length($`)) / 2 + 1; # print ("Level: $level Object: $object\n"); push (@Objects, $object); push (@ObjectLevels, $level); } } close (INPUT); } ############################################################################# # Function : ReadArgsFile # Description : This reads in an existing file which contains information on # all GTK args. It creates the arrays @ArgObjects, @ArgNames, # @ArgTypes and @ArgFlags containing info on the args. # Arguments : $file - the file containing the arg information. ############################################################################# sub ReadArgsFile { my ($file) = @_; my $in_arg = 0; my $arg_object; my $arg_name; my $arg_type; my $arg_flags; # Reset the signal info. @ArgObjects = (); @ArgNames = (); @ArgTypes = (); @ArgFlags = (); if (! -f $file) { return; } if (!open (INPUT, $file)) { warn "Can't open $file - skipping args\n"; return; } while (<INPUT>) { if (!$in_arg) { if (m/^<ARG>/) { $in_arg = 1; $arg_object = ""; $arg_name = ""; $arg_type = ""; $arg_flags = ""; } } else { if (m/^<NAME>(.*)<\/NAME>/) { $arg_name = $1; if ($arg_name =~ m/^(.*)::(.*)$/) { $arg_object = $1; $arg_name = $2; # print "Found arg: $arg_name\n"; } else { print "Invalid arg name: $arg_name\n"; } } elsif (m/^<TYPE>(.*)<\/TYPE>/) { $arg_type = $1; } elsif (m/^<FLAGS>(.*)<\/FLAGS>/) { $arg_flags = $1; } elsif (m%^</ARG>%) { # print "Found end of arg: ${arg_object}::${arg_name}\n${arg_type} : ${arg_flags}\n"; push (@ArgObjects, $arg_object); push (@ArgNames, $arg_name); push (@ArgTypes, $arg_type); push (@ArgFlags, $arg_flags); $in_arg = 0; } } } close (INPUT); } ############################################################################# # Function : CheckIsObject # Description : Returns 1 if the given name is a GtkObject or a subclass. # It uses the global @Objects array. # Note that the @Objects array only contains classes in the # current module and their ancestors - not all GTK classes. # Arguments : $name - the name to check. ############################################################################# sub CheckIsObject { my ($name) = @_; my $object; foreach $object (@Objects) { if ($object eq $name) { return 1; } } return 0; } ############################################################################# # Function : ParseStructDeclaration # Description : This function takes a structure declaration and # breaks it into individual type declarations. # Arguments : $declaration - the declaration to parse # $is_object - true if this is an object structure # $typefunc - function reference to apply to type # $namefunc - function reference to apply to name ############################################################################# sub ParseStructDeclaration { my ($declaration, $is_object, $typefunc, $namefunc) = @_; # Remove all private parts of the declaration # For objects, assume private if ($is_object) { $declaration =~ s!(struct\s+\w*\s*\{) .*? (?:/\*\s*<\s*public\s*>\s*\*/|(?=\}))!$1!msgx; } $declaration =~ s!\n?[ \t]*/\*\s*<\s*private\s*>\s*\*/ .*? (?:/\*\s*<\s*public\s*>\s*\*/|(?=\}))!!msgx; # Remove all other comments; $declaration =~ s@/\*([^*]+|\*(?!/))*\*/@ @g; my @result = (); if ($declaration =~ /^\s*$/) { return @result; } # Prime match after "struct {" declaration if (!scalar($declaration =~ m/struct\s+\w*\s*\{/msg)) { die "Structure declaration '$declaration' does not begin with struct [NAME] {\n"; } # Treat lines in sequence, allowing singly nested anonymous structs # and unions. while ($declaration =~ m/\s*([^{;]+(\{[^\}]*\}[^{;]+)?);/msg) { my $line = $1; last if $line =~ /^\s*\}\s*\w*\s*$/; # FIXME: Just ignore nested structs and unions for now next if $line =~ /{/; # FIXME: The regexes here are the same as in OutputFunction; # this functionality should be separated out. if ($line =~ m/^ (const\s+|unsigned\s+)*(struct\s+)? # mod1 (\w+)\s* # type (\**)\s* # ptr1 (const\s+)? # mod2 (\**)?\s* # ptr2 (\w+(?:\s*,\s*\w+)*)\s* # name (?:((?:\[[^\]]*\]\s*)+) | # array (:\s*\d+))?\s* # bits $/x) { my $mod1 = defined($1) ? $1 : ""; if (defined($2)) { $mod1 .= $2; } my $type = $3; my $ptr1 = $4; my $mod2 = defined($5) ? $5 : ""; my $ptr2 = $6; my $name = $7; $ptr1 = " " . $ptr1; my $array = defined($8) ? $8 : ""; my $bits = defined($9) ? " $9" : ""; my $ptype = defined $typefunc ? $typefunc->($type) : $type; # FIXME: # As a hack, we allow the "name" to be of the form # "a, b, c". This isn't the correct C syntax, but # at least we get "gint16 x, y" right. Such constructs # should really be completely removed from the source. # Or we should really try to understand the C syntax # here... my @names = split /\s*,\s*/, $name; for my $n (@names) { push @result, $n; if (defined $namefunc) { $n = $namefunc->($n); } push @result, "$mod1$ptype$ptr1$mod2$ptr2$n$array$bits"; } # Try to match structure members which are functions } elsif ($line =~ m/^ (const\s+|unsigned\s+)*(struct\s+)? # mod1 (\w+)\s* # type (\**)\s* # ptr1 (const\s+)? # mod2 \(\s*\*\s*(\w+)\s*\)\s* # name \(([^)]*)\)\s* # func_params $/x) { my $mod1 = defined($1) ? $1 : ""; if (defined($2)) { $mod1 .= $2; } my $type = $3; my $ptr1 = $4; my $mod2 = defined($5) ? $5 : ""; my $name = $6; my $func_params = $7; my $ptype = defined $typefunc ? $typefunc->($type) : $type; my $pname = defined $namefunc ? $namefunc->($name) : $name; push @result, $name; push @result, "$mod1$ptype$ptr1$mod2 (*$pname) ($func_params)"; } else { warn "Cannot parse structure field $line"; } } return @result; } ############################################################################# # Function : ParseEnumDeclaration # Description : This function takes a enumeration declaration and # breaks it into individual enum member declarations. # Arguments : $declaration - the declaration to parse ############################################################################# sub ParseEnumDeclaration { my ($declaration, $is_object) = @_; # Remove comments; $declaration =~ s@/\*([^*]+|\*(?!/))*\*/@ @g; my @result = (); if ($declaration =~ /^\s*$/) { return @result; } # Remove parenthesized expressions (in macros like GTK_BLAH = BLAH(1,3)) # to avoid getting confused by commas they might contain. This # doesn't handle nested parentheses correctly. $declaration =~ s/\([^)]*\)//g; # Prime match after "typedef enum {" declaration if (!scalar($declaration =~ m/typedef\s+enum\s*\{/msg)) { die "Enum declaration '$declaration' does not begin with typedef enum {\n"; } # Treat lines in sequence. while ($declaration =~ m/\s*([^,\}]+)([,\}])/msg) { my $line = $1; my $terminator = $2; if ($line =~ m/^(\w+)\s*(=.*)?$/msg) { push @result, $1; # Special case for GIOCondition, where the values are specified by # macros which expand to include the equal sign like '=1'. } elsif ($line =~ m/^(\w+)\s*GLIB_SYSDEF_POLL/msg) { push @result, $1; # Special case include of <gdk/gdkcursors.h>, just ignore it } elsif ($line =~ m/^#include/) { last; } else { warn "Cannot parse enumeration member $line"; } last if $terminator eq '}'; } return @result; }