#!@PERL@ -w # -*- cperl -*- # # gtk-doc - GTK DocBook documentation generator. # Copyright (C) 1998 Damon Chaplin # 2007,2008,2009 Stefan Kost # # 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # ############################################################################# # Script : gtkdoc-mkdb # Description : This creates the DocBook files from the edited templates. ############################################################################# use warnings; use strict; use Getopt::Long; push @INC, '@PACKAGE_DATA_DIR@'; require "gtkdoc-common.pl"; # Options # name of documentation module my $MODULE; my $TMPL_DIR; my $DB_OUTPUT_DIR; my @SOURCE_DIRS; my $SOURCE_SUFFIXES = ""; my $IGNORE_FILES = ""; my $PRINT_VERSION; my $PRINT_HELP; my $MAIN_SGML_FILE; my $EXPAND_CONTENT_FILES = ""; my $INLINE_MARKUP_MODE; my $DEFAULT_STABILITY; my $DEFAULT_INCLUDES; my $OUTPUT_FORMAT; my $NAME_SPACE = ""; my $OUTPUT_ALL_SYMBOLS; my $OUTPUT_SYMBOLS_WITHOUT_SINCE; my %optctl = ('module' => \$MODULE, 'source-dir' => \@SOURCE_DIRS, 'source-suffixes' => \$SOURCE_SUFFIXES, 'ignore-files' => \$IGNORE_FILES, 'output-dir' => \$DB_OUTPUT_DIR, 'tmpl-dir' => \$TMPL_DIR, 'version' => \$PRINT_VERSION, 'help' => \$PRINT_HELP, 'main-sgml-file' => \$MAIN_SGML_FILE, 'expand-content-files' => \$EXPAND_CONTENT_FILES, 'sgml-mode' => \$INLINE_MARKUP_MODE, 'xml-mode' => \$INLINE_MARKUP_MODE, 'default-stability' => \$DEFAULT_STABILITY, 'default-includes' => \$DEFAULT_INCLUDES, 'output-format' => \$OUTPUT_FORMAT, 'name-space' => \$NAME_SPACE, 'outputallsymbols' => \$OUTPUT_ALL_SYMBOLS, 'outputsymbolswithoutsince' => \$OUTPUT_SYMBOLS_WITHOUT_SINCE ); GetOptions(\%optctl, "module=s", "source-dir:s", "source-suffixes:s", "ignore-files:s", "output-dir:s", "tmpl-dir:s", "version", "outputallsymbols", "outputsymbolswithoutsince", "expand-content-files:s", "main-sgml-file:s", "extra-db-files:s", "help", "sgml-mode", "xml-mode", "default-stability:s", "default-includes:s", "output-format:s", "name-space:s"); if ($PRINT_VERSION) { print "@VERSION@\n"; exit 0; } if (!$MODULE) { $PRINT_HELP = 1; } if ($DEFAULT_STABILITY && $DEFAULT_STABILITY ne "Stable" && $DEFAULT_STABILITY ne "Private" && $DEFAULT_STABILITY ne "Unstable") { $PRINT_HELP = 1; } if ($PRINT_HELP) { print <) { if (/^\s*<(book|chapter|article)/) { # check that the top-level tag or the doctype decl contain the xinclude namespace decl if (($_ !~ m/http:\/\/www.w3.org\/200[13]\/XInclude/) && ($doctype_header !~ m/http:\/\/www.w3.org\/200[13]\/XInclude/m)) { $doctype_header = ""; } last; } $doctype_header .= $_; } close(INPUT); $doctype_header =~ s/##g; } else { $doctype_header = "\n" . "\n" . "]>\n"; } } else { if (!$MAIN_SGML_FILE) { $MAIN_SGML_FILE = "${MODULE}-docs.sgml"; } $empty_element_end = ">"; $doctype_header = ""; } my $ROOT_DIR = "."; # All the files are written in subdirectories beneath here. $TMPL_DIR = $TMPL_DIR ? $TMPL_DIR : "$ROOT_DIR/tmpl"; # This is where we put all the DocBook output. $DB_OUTPUT_DIR = $DB_OUTPUT_DIR ? $DB_OUTPUT_DIR : "$ROOT_DIR/$OUTPUT_FORMAT"; # This file contains the object hierarchy. my $OBJECT_TREE_FILE = "$ROOT_DIR/$MODULE.hierarchy"; # This file contains the interfaces. my $INTERFACES_FILE = "$ROOT_DIR/$MODULE.interfaces"; # This file contains the prerequisites. my $PREREQUISITES_FILE = "$ROOT_DIR/$MODULE.prerequisites"; # This file contains signal arguments and names. my $SIGNALS_FILE = "$ROOT_DIR/$MODULE.signals"; # The file containing Arg information. my $ARGS_FILE = "$ROOT_DIR/$MODULE.args"; # 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 @SignalFlags; # Flags for the signal 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. my @ArgNicks; # The nickname of the Arg. my @ArgBlurbs; # Docstring of the Arg. my @ArgDefaults; # Default value of the Arg. my @ArgRanges; # The range of the Arg type # These global hashes store declaration info keyed on a symbol name. my %Declarations; my %DeclarationTypes; my %DeclarationConditional; my %DeclarationOutput; my %Deprecated; my %Since; my %StabilityLevel; my %StructHasTypedef; # These global hashes store the existing documentation. my %SymbolDocs; my %SymbolTypes; my %SymbolParams; my %SymbolSourceFile; my %SymbolSourceLine; my %SymbolAnnotations; # These global hashes store documentation scanned from the source files. my %SourceSymbolDocs; my %SourceSymbolParams; my %SourceSymbolSourceFile; my %SourceSymbolSourceLine; # all documentation goes in here, so we can do coverage analysis my %AllSymbols; my %AllIncompleteSymbols; my %AllUnusedSymbols; my %AllDocumentedSymbols; # Undeclared yet documented symbols my %UndeclaredSymbols; # These global arrays store GObject, subclasses and the hierarchy (also of # non-object derived types). my @Objects; my @ObjectLevels; my %ObjectRoots; my %Interfaces; my %Prerequisites; # holds the symbols which are mentioned in $MODULE-sections.txt and in which # section they are defined my %KnownSymbols; my %SymbolSection; my %SymbolSectionId; # collects index entries my %IndexEntriesFull; my %IndexEntriesSince; my %IndexEntriesDeprecated; # Standard C preprocessor directives, which we ignore for '#' abbreviations. my %PreProcessorDirectives; $PreProcessorDirectives{"assert"} = 1; $PreProcessorDirectives{"define"} = 1; $PreProcessorDirectives{"elif"} = 1; $PreProcessorDirectives{"else"} = 1; $PreProcessorDirectives{"endif"} = 1; $PreProcessorDirectives{"error"} = 1; $PreProcessorDirectives{"if"} = 1; $PreProcessorDirectives{"ifdef"} = 1; $PreProcessorDirectives{"ifndef"} = 1; $PreProcessorDirectives{"include"} = 1; $PreProcessorDirectives{"line"} = 1; $PreProcessorDirectives{"pragma"} = 1; $PreProcessorDirectives{"unassert"} = 1; $PreProcessorDirectives{"undef"} = 1; $PreProcessorDirectives{"warning"} = 1; # remember used annotation (to write minimal glossary) my %AnnotationsUsed; my %AnnotationDefinition = ( # the GObjectIntrospection annotations are defined at: # https://live.gnome.org/GObjectIntrospection/Annotations 'allow-none' => "NULL is OK, both for passing and for returning.", 'nullable' => "NULL may be passed as the value in, out, in-out; or as a return value.", 'optional' => "NULL may be passed instead of a pointer to a location.", 'array' => "Parameter points to an array of items.", 'attribute' => "Deprecated free-form custom annotation, replaced by (attributes) annotation.", 'attributes' => "Free-form key-value pairs.", 'closure' => "This parameter is a 'user_data', for callbacks; many bindings can pass NULL here.", 'constructor' => "This symbol is a constructor, not a static method.", 'destroy' => "This parameter is a 'destroy_data', for callbacks.", 'default' => "Default parameter value (for in case the shadows-to function has less parameters).", 'element-type' => "Generics and defining elements of containers and arrays.", 'error-domains' => "Typed errors. Similar to throws in Java.", 'foreign' => "This is a foreign struct.", 'get-value-func' => "The specified function is used to convert a struct from a GValue, must be a GTypeInstance.", 'in' => "Parameter for input. Default is transfer none.", 'inout' => "Parameter for input and for returning results. Default is transfer full.", 'in-out' => "Parameter for input and for returning results. Default is transfer full.", 'method' => "This is a method", 'not-error' => "A GError parameter is not to be handled like a normal GError.", 'out' => "Parameter for returning results. Default is transfer full.", 'out caller-allocates' => "Out parameter, where caller must allocate storage.", 'out callee-allocates' => "Out parameter, where caller must allocate storage.", 'ref-func' => "The specified function is used to ref a struct, must be a GTypeInstance.", 'rename-to' => "Rename the original symbol's name to SYMBOL.", 'scope call' => "The callback is valid only during the call to the method.", 'scope async' => "The callback is valid until first called.", 'scope notified' => "The callback is valid until the GDestroyNotify argument is called.", 'set-value-func' => "The specified function is used to convert from a struct to a GValue, must be a GTypeInstance.", 'skip' => "Exposed in C code, not necessarily available in other languages.", 'transfer container' => "Free data container after the code is done.", 'transfer floating' => "Alias for transfer none, used for objects with floating refs.", 'transfer full' => "Free data after the code is done.", 'transfer none' => "Don't free data after the code is done.", 'type' => "Override the parsed C type with given type.", 'unref-func' => "The specified function is used to unref a struct, must be a GTypeInstance.", 'virtual' => "This is the invoker for a virtual method.", 'value' => "The specified value overrides the evaluated value of the constant.", # Stability Level definition # https://bugzilla.gnome.org/show_bug.cgi?id=170860 'Stable' => < < < 1, "emphasis" => 1, "envar" => 1, "filename" => 1, "firstterm" => 1, "footnote" => 1, "function" => 1, "manvolnum" => 1, "option" => 1, "replaceable" => 1, "structfield" => 1, "structname" => 1, "title" => 1, "varname" => 1 ); my %MD_ESCAPABLE_CHARS = ( "\\" => 1, "`" => 1, "*" => 1, "_" => 1, "{" => 1, "}" => 1, "[" => 1, "]" => 1, "(" => 1, ")" => 1, ">" => 1, "#" => 1, "+" => 1, "-" => 1, "." => 1, "!" => 1 ); my %MD_GTK_ESCAPABLE_CHARS = ( "@" => 1, "%" => 1 ); # Create the root DocBook output directory if it doens't exist. if (! -e $DB_OUTPUT_DIR) { mkdir ("$DB_OUTPUT_DIR", 0777) || die "Can't create directory: $DB_OUTPUT_DIR"; } # Function and other declaration output settings. my $RETURN_TYPE_FIELD_WIDTH = 20; my $SYMBOL_FIELD_WIDTH = 36; my $MAX_SYMBOL_FIELD_WIDTH = 40; my $SIGNAL_FIELD_WIDTH = 16; my $PARAM_FIELD_COUNT = 2; &ReadKnownSymbols ("$ROOT_DIR/$MODULE-sections.txt"); &ReadSignalsFile ($SIGNALS_FILE); &ReadArgsFile ($ARGS_FILE); &ReadObjectHierarchy; &ReadInterfaces; &ReadPrerequisites; &ReadDeclarationsFile ("$ROOT_DIR/$MODULE-decl.txt", 0); if (-f "$ROOT_DIR/$MODULE-overrides.txt") { &ReadDeclarationsFile ("$ROOT_DIR/$MODULE-overrides.txt", 1); } for my $dir (@SOURCE_DIRS) { &ReadSourceDocumentation ($dir); } my $changed = &OutputDB ("$ROOT_DIR/$MODULE-sections.txt"); # If any of the DocBook files have changed, update the timestamp file (so # it can be used for Makefile dependencies). if ($changed || ! -e "$ROOT_DIR/sgml.stamp") { # try to detect the common prefix # GtkWidget, GTK_WIDGET, gtk_widget -> gtk if ($NAME_SPACE eq "") { $NAME_SPACE=""; my $pos=0; my $ratio=0.0; do { my %prefix; my $letter=""; foreach my $symbol (keys(%IndexEntriesFull)) { if(($NAME_SPACE eq "") || $symbol =~ /^$NAME_SPACE/i) { if (length($symbol)>$pos) { $letter=substr($symbol,$pos,1); # stop prefix scanning if ($letter eq "_") { # stop on "_" last; } # Should we also stop on a uppercase char, if last was lowercase # GtkWidget, if we have the 'W' and had the 't' before # or should we count upper and lowercase, and stop one 2nd uppercase, if we already had a lowercase # GtkWidget, the 'W' would be the 2nd uppercase and with 't','k' we had lowercase chars before # need to recound each time as this is per symbol $prefix{uc($letter)}++; } } } if ($letter ne "" && $letter ne "_") { my $maxletter=""; my $maxsymbols=0; foreach $letter (keys(%prefix)) { #print "$letter: $prefix{$letter}.\n"; if ($prefix{$letter}>$maxsymbols) { $maxletter=$letter; $maxsymbols=$prefix{$letter}; } } $ratio = scalar(keys(%IndexEntriesFull)) / $prefix{$maxletter}; #print "most symbols start with $maxletter, that is ". (100 * $ratio) ." %\n"; if ($ratio > 0.9) { # do another round $NAME_SPACE .= $maxletter; } $pos++; } else { $ratio=0.0; } } while ($ratio > 0.9); #print "most symbols start with $NAME_SPACE\n"; } &OutputIndexFull; &OutputDeprecatedIndex; &OutputSinceIndexes; &OutputAnnotationGlossary; open (TIMESTAMP, ">$ROOT_DIR/sgml.stamp") || die "Can't create $ROOT_DIR/sgml.stamp: $!"; print (TIMESTAMP "timestamp"); close (TIMESTAMP); } ############################################################################# # Function : OutputObjectList # Description : This outputs the alphabetical list of objects, in a columned # table. # FIXME: Currently this also outputs ancestor objects # which may not actually be in this module. # Arguments : none ############################################################################# sub OutputObjectList { my $cols = 3; # FIXME: use $OUTPUT_FORMAT # my $old_object_index = "$DB_OUTPUT_DIR/object_index.$OUTPUT_FORMAT"; my $old_object_index = "$DB_OUTPUT_DIR/object_index.sgml"; my $new_object_index = "$DB_OUTPUT_DIR/object_index.new"; open (OUTPUT, ">$new_object_index") || die "Can't create $new_object_index: $!"; if ($OUTPUT_FORMAT eq "xml") { my $header = $doctype_header; $header =~ s/ EOF my $count = 0; my $object; foreach $object (sort (@Objects)) { my $xref = &MakeXRef ($object); if ($count % $cols == 0) { print (OUTPUT "\n"); } print (OUTPUT "$xref\n"); if ($count % $cols == ($cols - 1)) { print (OUTPUT "\n"); } $count++; } if ($count == 0) { # emit an empty row, since empty tables are invalid print (OUTPUT " \n"); } else { if ($count % $cols > 0) { print (OUTPUT "\n"); } } print (OUTPUT < EOF close (OUTPUT); &UpdateFileIfChanged ($old_object_index, $new_object_index, 0); } ############################################################################# # Function : TrimTextBlock # Description : Trims extra whitespace. Empty lines inside a block are # preserved. # Arguments : $desc - the text block to trim. May contain newlines. ############################################################################# sub TrimTextBlock { my ($desc) = @_; # strip leading spaces on the block $desc =~ s/^\s+//s; # strip trailing spaces on every line $desc =~ s/\s+$/\n/mg; return $desc; } ############################################################################# # Function : OutputDB # 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 $MODULE-sections.txt file which contains all of # the functions/macros/structs etc. being documented, organised # into sections and subsections. ############################################################################# sub OutputDB { my ($file) = @_; @TRACE@("Reading: $file\n"); open (INPUT, $file) || die "Can't open $file: $!"; my $filename = ""; my $book_top = ""; my $book_bottom = ""; my $includes = (defined $DEFAULT_INCLUDES) ? $DEFAULT_INCLUDES : ""; my $section_includes = ""; my $in_section = 0; my $title = ""; my $section_id = ""; my $subsection = ""; my $num_symbols; my $changed = 0; my $functions_synop = ""; my $other_synop = ""; my $functions_details = ""; my $other_details = ""; my $signals_synop = ""; my $signals_desc = ""; my $args_synop = ""; my $child_args_synop = ""; my $style_args_synop = ""; my $args_desc = ""; my $child_args_desc = ""; my $style_args_desc = ""; my $hierarchy_str = ""; my @hierarchy = (); my $interfaces = ""; my $implementations = ""; my $prerequisites = ""; my $derived = ""; my @file_objects = (); my %templates = (); my %symbol_def_line = (); # merge the source docs, in case there are no templates &MergeSourceDocumentation; while () { if (m/^#/) { next; } elsif (m/^
/) { $num_symbols = 0; $in_section = 1; @file_objects = (); %symbol_def_line = (); } elsif (m/^/i) { $other_synop .= "\n"; $functions_synop .= "\n"; $subsection = $1; } elsif (m/^/) { } elsif (m/^(.*)<\/TITLE>/) { $title = $1; @TRACE@("Section: $title\n"); # We don't want warnings if object & class structs aren't used. $DeclarationOutput{$title} = 1; $DeclarationOutput{"${title}Class"} = 1; $DeclarationOutput{"${title}Iface"} = 1; $DeclarationOutput{"${title}Interface"} = 1; } elsif (m/^<FILE>(.*)<\/FILE>/) { $filename = $1; if (! defined $templates{$filename}) { if (&ReadTemplateFile ("$TMPL_DIR/$filename", 1)) { &MergeSourceDocumentation; $templates{$filename}=$.; } } else { &LogWarning ($file, $., "Double <FILE>$filename</FILE> entry. ". "Previous occurrence on line ".$templates{$filename}."."); } if (($title eq "") and (defined $SourceSymbolDocs{"$TMPL_DIR/$filename:Title"})) { $title = $SourceSymbolDocs{"$TMPL_DIR/$filename:Title"}; # Remove trailing blanks $title =~ s/\s+$//; } } elsif (m/^<INCLUDE>(.*)<\/INCLUDE>/) { if ($in_section) { $section_includes = $1; } else { if (defined $DEFAULT_INCLUDES) { &LogWarning ($file, $., "Default <INCLUDE> being overridden by command line option."); } else { $includes = $1; } } } elsif (m/^<\/SECTION>/) { @TRACE@("End of section: $title\n"); if ($num_symbols > 0) { # collect documents if ($OUTPUT_FORMAT eq "xml") { $book_bottom .= " <xi:include href=\"xml/$filename.xml\"/>\n"; } else { $book_top.="<!ENTITY $section_id SYSTEM \"sgml/$filename.sgml\">\n"; $book_bottom .= " &$section_id;\n"; } if (defined ($SourceSymbolDocs{"$TMPL_DIR/$filename:Include"})) { if ($section_includes) { &LogWarning ($file, $., "Section <INCLUDE> being overridden by inline comments."); } $section_includes = $SourceSymbolDocs{"$TMPL_DIR/$filename:Include"}; } if ($section_includes eq "") { $section_includes = $includes; } $signals_synop =~ s/^\n*//g; $signals_synop =~ s/\n+$/\n/g; if ($signals_synop ne '') { $signals_synop = <<EOF; <refsect1 id="$section_id.signals" role="signal_proto"> <title role="signal_proto.title">Signals ${signals_synop} EOF $signals_desc = TrimTextBlock($signals_desc); $signals_desc = < Signal Details $signals_desc EOF } $args_synop =~ s/^\n*//g; $args_synop =~ s/\n+$/\n/g; if ($args_synop ne '') { $args_synop = < Properties ${args_synop} EOF $args_desc = TrimTextBlock($args_desc); $args_desc = < Property Details $args_desc EOF } $child_args_synop =~ s/^\n*//g; $child_args_synop =~ s/\n+$/\n/g; if ($child_args_synop ne '') { $args_synop .= < Child Properties ${child_args_synop} EOF $child_args_desc = TrimTextBlock($child_args_desc); $args_desc .= < Child Property Details $child_args_desc EOF } $style_args_synop =~ s/^\n*//g; $style_args_synop =~ s/\n+$/\n/g; if ($style_args_synop ne '') { $args_synop .= < Style Properties ${style_args_synop} EOF $style_args_desc = TrimTextBlock($style_args_desc); $args_desc .= < Style Property Details $style_args_desc EOF } $hierarchy_str = &AddTreeLineArt(\@hierarchy); if ($hierarchy_str ne "") { $hierarchy_str = < Object Hierarchy $hierarchy_str EOF } $interfaces =~ TrimTextBlock($interfaces); if ($interfaces ne "") { $interfaces = < Implemented Interfaces $interfaces EOF } $implementations = TrimTextBlock($implementations); if ($implementations ne "") { $implementations = < Known Implementations $implementations EOF } $prerequisites = TrimTextBlock($prerequisites); if ($prerequisites ne "") { $prerequisites = < Prerequisites $prerequisites EOF } $derived = TrimTextBlock($derived); if ($derived ne "") { $derived = < Known Derived Interfaces $derived EOF } $functions_synop =~ s/^\n*//g; $functions_synop =~ s/\n+$/\n/g; if ($functions_synop ne '') { $functions_synop = < Functions ${functions_synop} EOF } $other_synop =~ s/^\n*//g; $other_synop =~ s/\n+$/\n/g; if ($other_synop ne '') { $other_synop = < Types and Values ${other_synop} EOF } my $file_changed = &OutputDBFile ($filename, $title, $section_id, $section_includes, \$functions_synop, \$other_synop, \$functions_details, \$other_details, \$signals_synop, \$signals_desc, \$args_synop, \$args_desc, \$hierarchy_str, \$interfaces, \$implementations, \$prerequisites, \$derived, \@file_objects); if ($file_changed) { $changed = 1; } } $title = ""; $section_id = ""; $subsection = ""; $in_section = 0; $section_includes = ""; $functions_synop = ""; $other_synop = ""; $functions_details = ""; $other_details = ""; $signals_synop = ""; $signals_desc = ""; $args_synop = ""; $child_args_synop = ""; $style_args_synop = ""; $args_desc = ""; $child_args_desc = ""; $style_args_desc = ""; $hierarchy_str = ""; @hierarchy = (); $interfaces = ""; $implementations = ""; $prerequisites = ""; $derived = ""; } elsif (m/^(\S+)/) { my $symbol = $1; @TRACE@(" Symbol: $symbol in subsection: $subsection\n"); # check for duplicate entries if (! defined $symbol_def_line{$symbol}) { my $declaration = $Declarations{$symbol}; if (defined ($declaration)) { if (&CheckIsObject ($symbol)) { push @file_objects, $symbol; } # We don't want standard macros/functions of GObjects, # or private declarations. if ($subsection ne "Standard" && $subsection ne "Private") { my ($synop, $desc) = &OutputDeclaration ($symbol, $declaration); my $type = $DeclarationTypes {$symbol}; if ($type eq 'FUNCTION' || $type eq 'USER_FUNCTION') { $functions_synop .= $synop; $functions_details .= $desc; } elsif ($type eq 'MACRO' && $declaration =~ /$symbol[ ]*\(/) { $functions_synop .= $synop; $functions_details .= $desc; } else { $other_synop .= $synop; $other_details .= $desc; } } my ($sig_synop, $sig_desc) = &GetSignals ($symbol); my ($arg_synop, $child_arg_synop, $style_arg_synop, $arg_desc, $child_arg_desc, $style_arg_desc) = &GetArgs ($symbol); my $ifaces = &GetInterfaces ($symbol); my $impls = &GetImplementations ($symbol); my $prereqs = &GetPrerequisites ($symbol); my $der = &GetDerived ($symbol); @hierarchy = &GetHierarchy ($symbol, \@hierarchy); $signals_synop .= $sig_synop; $signals_desc .= $sig_desc; $args_synop .= $arg_synop; $child_args_synop .= $child_arg_synop; $style_args_synop .= $style_arg_synop; $args_desc .= $arg_desc; $child_args_desc .= $child_arg_desc; $style_args_desc .= $style_arg_desc; $interfaces .= $ifaces; $implementations .= $impls; $prerequisites .= $prereqs; $derived .= $der; # Note that the declaration has been output. $DeclarationOutput{$symbol} = 1; } elsif ($subsection ne "Standard" && $subsection ne "Private") { $UndeclaredSymbols{$symbol} = 1; &LogWarning ($file, $., "No declaration found for $symbol."); } $num_symbols++; $symbol_def_line{$symbol}=$.; if ($section_id eq "") { if($title eq "" && $filename eq "") { &LogWarning ($file, $., "Section has no title and no file."); } # FIXME: one of those would be enough # filename should be an internal detail for gtk-doc if ($title eq "") { $title = $filename; } elsif ($filename eq "") { $filename = $title; } $filename =~ s/\s/_/g; $section_id = $SourceSymbolDocs{"$TMPL_DIR/$filename:Section_Id"}; if (defined ($section_id) && $section_id !~ m/^\s*$/) { # Remove trailing blanks and use as is $section_id =~ s/\s+$//; } elsif (&CheckIsObject ($title)) { # GObjects use their class name as the ID. $section_id = &CreateValidSGMLID ($title); } else { $section_id = &CreateValidSGMLID ("$MODULE-$title"); } } $SymbolSection{$symbol}=$title; $SymbolSectionId{$symbol}=$section_id; } else { &LogWarning ($file, $., "Double symbol entry for $symbol. ". "Previous occurrence on line ".$symbol_def_line{$symbol}."."); } } } close (INPUT); &OutputMissingDocumentation; &OutputUndeclaredSymbols; &OutputUnusedSymbols; if ($OUTPUT_ALL_SYMBOLS) { &OutputAllSymbols; } if ($OUTPUT_SYMBOLS_WITHOUT_SINCE) { &OutputSymbolsWithoutSince; } for $filename (split (' ', $EXPAND_CONTENT_FILES)) { my $file_changed = &OutputExtraFile ($filename); if ($file_changed) { $changed = 1; } } &OutputBook ($book_top, $book_bottom); return $changed; } ############################################################################# # Function : OutputIndex # Description : This writes an indexlist that can be included into the main- # document into an tag. ############################################################################# sub OutputIndex { my ($basename, $apiindexref ) = @_; my %apiindex = %{$apiindexref}; my $old_index = "$DB_OUTPUT_DIR/$basename.xml"; my $new_index = "$DB_OUTPUT_DIR/$basename.new"; my $lastletter = " "; my $divopen = 0; my $symbol; my $short_symbol; open (OUTPUT, ">$new_index") || die "Can't create $new_index"; my $header = $doctype_header; $header =~ s/\n"); @TRACE@("generate $basename index (".%apiindex." entries)\n"); # do a case insensitive sort while chopping off the prefix foreach my $hash ( sort { $$a{criteria} cmp $$b{criteria} or $$a{original} cmp $$b{original} } map { my $x = uc($_); $x =~ s/^$NAME_SPACE\_?(.*)/$1/i; { criteria => $x, original => $_, short => $1 } } keys %apiindex) { $symbol = $$hash{original}; if (defined($$hash{short})) { $short_symbol = $$hash{short}; } else { $short_symbol = $symbol; } # generate a short symbol description my $symbol_desc = ""; my $symbol_section = ""; my $symbol_section_id = ""; my $symbol_type = ""; if (defined($DeclarationTypes{$symbol})) { $symbol_type = lc($DeclarationTypes{$symbol}); } if ($symbol_type eq "") { @TRACE@("trying symbol $symbol\n"); if ($symbol =~ m/(.*)::(.*)/) { my $oname = $1; my $osym = $2; my $i; @TRACE@(" trying object signal ${oname}:$osym in ".$#SignalNames." signals\n"); for ($i = 0; $i <= $#SignalNames; $i++) { if ($SignalNames[$i] eq $osym) { $symbol_type = "object signal"; if (defined($SymbolSection{$oname})) { $symbol_section = $SymbolSection{$oname}; $symbol_section_id = $SymbolSectionId{$oname}; } last; } } } elsif ($symbol =~ m/(.*):(.*)/) { my $oname = $1; my $osym = $2; my $i; @TRACE@(" trying object property ${oname}::$osym in ".$#ArgNames." properties\n"); for ($i = 0; $i <= $#ArgNames; $i++) { @TRACE@(" ".$ArgNames[$i]."\n"); if ($ArgNames[$i] eq $osym) { $symbol_type = "object property"; if (defined($SymbolSection{$oname})) { $symbol_section = $SymbolSection{$oname}; $symbol_section_id = $SymbolSectionId{$oname}; } last; } } } } else { if (defined($SymbolSection{$symbol})) { $symbol_section = $SymbolSection{$symbol}; $symbol_section_id = $SymbolSectionId{$symbol}; } } if ($symbol_type ne "") { $symbol_desc=", $symbol_type"; if ($symbol_section ne "") { $symbol_desc.=" in $symbol_section"; #$symbol_desc.=" in ". &ExpandAbbreviations($symbol, "#$symbol_section"); } } my $curletter = uc(substr($short_symbol,0,1)); my $id = $apiindex{$symbol}; @TRACE@(" add symbol $symbol with $id to index in section $curletter\n"); if ($curletter ne $lastletter) { $lastletter = $curletter; if ($divopen == 1) { print (OUTPUT "\n"); } print (OUTPUT "$curletter\n"); $divopen = 1; } print (OUTPUT <$symbol$symbol_desc EOF } if ($divopen == 1) { print (OUTPUT "\n"); } print (OUTPUT "\n"); close (OUTPUT); &UpdateFileIfChanged ($old_index, $new_index, 0); } ############################################################################# # Function : OutputIndexFull # Description : This writes the full api indexlist that can be included into the # main document into an tag. ############################################################################# sub OutputIndexFull { &OutputIndex ("api-index-full", \%IndexEntriesFull); } ############################################################################# # Function : OutputDeprecatedIndex # Description : This writes the deprecated api indexlist that can be included # into the main document into an tag. ############################################################################# sub OutputDeprecatedIndex { &OutputIndex ("api-index-deprecated", \%IndexEntriesDeprecated); } ############################################################################# # Function : OutputSinceIndexes # Description : This writes the 'since' api indexlists that can be included into # the main document into an tag. ############################################################################# sub OutputSinceIndexes { my @sinces = keys %{{ map { $_ => 1 } values %Since }}; foreach my $version (@sinces) { @TRACE@("Since : [$version]\n"); # TODO make filtered hash #my %index = grep { $Since{$_} eq $version } %IndexEntriesSince; my %index = map { $_ => $IndexEntriesSince{$_} } grep { $Since{$_} eq $version } keys %IndexEntriesSince; &OutputIndex ("api-index-$version", \%index); } } ############################################################################# # Function : OutputAnnotationGlossary # Description : This writes a glossary of the used annotation terms into a # separate glossary file that can be included into the main # document. ############################################################################# sub OutputAnnotationGlossary { my $old_glossary = "$DB_OUTPUT_DIR/annotation-glossary.xml"; my $new_glossary = "$DB_OUTPUT_DIR/annotation-glossary.new"; my $lastletter = " "; my $divopen = 0; # if there are no annotations used return return if (! keys(%AnnotationsUsed)); # add acronyms that are referenced from acronym text rerun: foreach my $annotation (keys(%AnnotationsUsed)) { if(defined($AnnotationDefinition{$annotation})) { if($AnnotationDefinition{$annotation} =~ m/([\w ]+)<\/acronym>/) { if (!exists($AnnotationsUsed{$1})) { $AnnotationsUsed{$1} = 1; goto rerun; } } } } open (OUTPUT, ">$new_glossary") || die "Can't create $new_glossary"; my $header = $doctype_header; $header =~ s/ Annotation Glossary EOF foreach my $annotation (sort({lc $a cmp lc $b} keys(%AnnotationsUsed))) { if(defined($AnnotationDefinition{$annotation})) { my $def = $AnnotationDefinition{$annotation}; my $curletter = uc(substr($annotation,0,1)); if ($curletter ne $lastletter) { $lastletter = $curletter; if ($divopen == 1) { print (OUTPUT "\n"); } print (OUTPUT "$curletter\n"); $divopen = 1; } print (OUTPUT < $annotation $def EOF } } if ($divopen == 1) { print (OUTPUT "\n"); } print (OUTPUT "\n"); close (OUTPUT); &UpdateFileIfChanged ($old_glossary, $new_glossary, 0); } ############################################################################# # Function : ReadKnownSymbols # Description : This collects the names of non-private symbols from the # $MODULE-sections.txt file. # Arguments : $file - the $MODULE-sections.txt file which contains all of # the functions/macros/structs etc. being documented, organised # into sections and subsections. ############################################################################# sub ReadKnownSymbols { my ($file) = @_; my $subsection = ""; @TRACE@("Reading: $file\n"); open (INPUT, $file) || die "Can't open $file: $!"; while () { if (m/^#/) { next; } elsif (m/^
/) { $subsection = ""; } elsif (m/^/i) { $subsection = $1; } elsif (m/^/) { next; } elsif (m/^(.*)<\/TITLE>/) { next; } elsif (m/^<FILE>(.*)<\/FILE>/) { $KnownSymbols{"$TMPL_DIR/$1:Long_Description"} = 1; $KnownSymbols{"$TMPL_DIR/$1:Short_Description"} = 1; next; } elsif (m/^<INCLUDE>(.*)<\/INCLUDE>/) { next; } elsif (m/^<\/SECTION>/) { next; } elsif (m/^(\S+)/) { my $symbol = $1; if ($subsection ne "Standard" && $subsection ne "Private") { $KnownSymbols{$symbol} = 1; } else { $KnownSymbols{$symbol} = 0; } } } close (INPUT); } ############################################################################# # Function : OutputDeclaration # Description : Returns the synopsis and detailed description DocBook # describing one function/macro etc. # Arguments : $symbol - the name of the function/macro begin described. # $declaration - the declaration of the function/macro. ############################################################################# sub OutputDeclaration { my ($symbol, $declaration) = @_; my $type = $DeclarationTypes {$symbol}; if ($type eq 'MACRO') { return &OutputMacro ($symbol, $declaration); } elsif ($type eq 'TYPEDEF') { return &OutputTypedef ($symbol, $declaration); } elsif ($type eq 'STRUCT') { return &OutputStruct ($symbol, $declaration); } elsif ($type eq 'ENUM') { return &OutputEnum ($symbol, $declaration); } elsif ($type eq 'UNION') { return &OutputUnion ($symbol, $declaration); } elsif ($type eq 'VARIABLE') { return &OutputVariable ($symbol, $declaration); } elsif ($type eq 'FUNCTION') { return &OutputFunction ($symbol, $declaration, $type); } elsif ($type eq 'USER_FUNCTION') { return &OutputFunction ($symbol, $declaration, $type); } else { die "Unknown symbol type"; } } ############################################################################# # Function : OutputSymbolTraits # Description : Returns the Since and StabilityLevel paragraphs for a symbol. # Arguments : $symbol - the name of the function/macro begin described. ############################################################################# sub OutputSymbolTraits { my ($symbol) = @_; my $desc = ""; if (exists $Since{$symbol}) { my $link_id = "api-index-".$Since{$symbol}; $desc .= "<para role=\"since\">Since: <link linkend=\"$link_id\">$Since{$symbol}</link></para>"; } if (exists $StabilityLevel{$symbol}) { my $stability = $StabilityLevel{$symbol}; $AnnotationsUsed{$stability} = 1; $desc .= "<para role=\"stability\">Stability Level: <acronym>$stability</acronym></para>"; } return $desc; } ############################################################################# # Function : Output{Symbol,Section}ExtraLinks # Description : Returns extralinks for the symbol (if enabled). # Arguments : $symbol - the name of the function/macro begin described. ############################################################################# sub uri_escape { my $text = $_[0]; return undef unless defined $text; # Build a char to hex map my %escapes = (); for (0..255) { $escapes{chr($_)} = sprintf("%%%02X", $_); } # Default unsafe characters. RFC 2732 ^(uric - reserved) $text =~ s/([^A-Za-z0-9\-_.!~*'()])/$escapes{$1}/g; return $text; } sub OutputSymbolExtraLinks { my ($symbol) = @_; my $desc = ""; if (0) { # NEW FEATURE: needs configurability my $sstr = &uri_escape($symbol); my $mstr = &uri_escape($MODULE); $desc .= <<EOF; <ulink role="extralinks" url="http://www.google.com/codesearch?q=$sstr">code search</ulink> <ulink role="extralinks" url="http://library.gnome.org/edit?module=$mstr&symbol=$sstr">edit documentation</ulink> EOF } return $desc; } sub OutputSectionExtraLinks { my ($symbol,$docsymbol) = @_; my $desc = ""; if (0) { # NEW FEATURE: needs configurability my $sstr = &uri_escape($symbol); my $mstr = &uri_escape($MODULE); my $dsstr = &uri_escape($docsymbol); $desc .= <<EOF; <ulink role="extralinks" url="http://www.google.com/codesearch?q=$sstr">code search</ulink> <ulink role="extralinks" url="http://library.gnome.org/edit?module=$mstr&symbol=$dsstr">edit documentation</ulink> EOF } return $desc; } ############################################################################# # Function : OutputMacro # Description : Returns the synopsis and detailed description of a macro. # Arguments : $symbol - the macro. # $declaration - the declaration of the macro. ############################################################################# sub OutputMacro { my ($symbol, $declaration) = @_; my $id = &CreateValidSGMLID ($symbol); my $condition = &MakeConditionDescription ($symbol); my $synop = "<row><entry role=\"define_keyword\">#define</entry><entry role=\"function_name\"><link linkend=\"$id\">$symbol</link>"; my $desc; my @fields = ParseMacroDeclaration($declaration, \&CreateValidSGML); my $title = $symbol . (@fields ? "()" : ""); $desc = "<refsect2 id=\"$id\" role=\"macro\"$condition>\n<title>$title\n"; $desc .= MakeIndexterms($symbol, $id); $desc .= "\n"; $desc .= OutputSymbolExtraLinks($symbol); if (@fields) { $synop .= "()"; } $synop .= "\n"; # Don't output the macro definition if is is a conditional macro or it # looks like a function, i.e. starts with "g_" or "_?gnome_", or it is # longer than 2 lines, otherwise we get lots of complicated macros like # g_assert. if (!defined ($DeclarationConditional{$symbol}) && ($symbol !~ m/^g_/) && ($symbol !~ m/^_?gnome_/) && (($declaration =~ tr/\n//) < 2)) { my $decl_out = &CreateValidSGML ($declaration); $desc .= "$decl_out\n"; } else { $desc .= "" . &MakeReturnField("#define") . "$symbol"; if ($declaration =~ m/^\s*#\s*define\s+\w+(\([^\)]*\))/) { my $args = $1; my $pad = ' ' x ($RETURN_TYPE_FIELD_WIDTH - length ("#define ")); # Align each line so that if should all line up OK. $args =~ s/\n/\n$pad/gm; $desc .= &CreateValidSGML ($args); } $desc .= "\n"; } $desc .= &MakeDeprecationNote($symbol); my $parameters = &OutputParamDescriptions ("MACRO", $symbol, @fields); if (defined ($SymbolDocs{$symbol})) { my $symbol_docs = &ConvertMarkDown($symbol, $SymbolDocs{$symbol}); $desc .= $symbol_docs; } $desc .= $parameters; $desc .= OutputSymbolTraits ($symbol); $desc .= "\n"; return ($synop, $desc); } ############################################################################# # Function : OutputTypedef # Description : Returns the synopsis and detailed description of a typedef. # Arguments : $symbol - the typedef. # $declaration - the declaration of the typedef, # e.g. 'typedef unsigned int guint;' ############################################################################# sub OutputTypedef { my ($symbol, $declaration) = @_; my $id = &CreateValidSGMLID ($symbol); my $condition = &MakeConditionDescription ($symbol); my $desc = "\n$symbol\n"; my $synop = "typedef$symbol\n"; $desc .= MakeIndexterms($symbol, $id); $desc .= "\n"; $desc .= OutputSymbolExtraLinks($symbol); if (!defined ($DeclarationConditional{$symbol})) { my $decl_out = &CreateValidSGML ($declaration); $desc .= "$decl_out\n"; } $desc .= &MakeDeprecationNote($symbol); if (defined ($SymbolDocs{$symbol})) { $desc .= &ConvertMarkDown($symbol, $SymbolDocs{$symbol}); } $desc .= OutputSymbolTraits ($symbol); $desc .= "\n"; return ($synop, $desc); } ############################################################################# # Function : OutputStruct # Description : Returns the synopsis and detailed description of a struct. # We check if it is a object struct, and if so we only output # parts of it that are noted as public fields. # We also use a different IDs for object structs, since the # original ID is used for the entire RefEntry. # Arguments : $symbol - the struct. # $declaration - the declaration of the struct. ############################################################################# sub OutputStruct { my ($symbol, $declaration) = @_; my $is_gtype = 0; my $default_to_public = 1; if (&CheckIsObject ($symbol)) { @TRACE@("Found struct gtype: $symbol\n"); $is_gtype = 1; $default_to_public = $ObjectRoots{$symbol} eq 'GBoxed'; } my $id; my $condition; if ($is_gtype) { $id = &CreateValidSGMLID ($symbol . "_struct"); $condition = &MakeConditionDescription ($symbol . "_struct"); } else { $id = &CreateValidSGMLID ($symbol); $condition = &MakeConditionDescription ($symbol); } # Determine if it is a simple struct or it also has a typedef. my $has_typedef = 0; if ($StructHasTypedef{$symbol} || $declaration =~ m/^\s*typedef\s+/) { $has_typedef = 1; } my $type_output; my $desc; if ($has_typedef) { # For structs with typedefs we just output the struct name. $type_output = ""; $desc = "\n$symbol\n"; } else { $type_output = "struct"; $desc = "\nstruct $symbol\n"; } my $synop = "${type_output}$symbol\n"; $desc .= MakeIndexterms($symbol, $id); $desc .= "\n"; $desc .= OutputSymbolExtraLinks($symbol); # Form a pretty-printed, private-data-removed form of the declaration my $decl_out = ""; if ($declaration =~ m/^\s*$/) { @TRACE@("Found opaque struct: $symbol\n"); $decl_out = "typedef struct _$symbol $symbol;"; } elsif ($declaration =~ m/^\s*struct\s+\w+\s*;\s*$/) { @TRACE@("Found opaque struct: $symbol\n"); $decl_out = "struct $symbol;"; } else { my $public = $default_to_public; my $new_declaration = ""; my $decl_line; my $decl = $declaration; if ($decl =~ m/^\s*(typedef\s+)?struct\s*\w*\s*(?:\/\*.*\*\/)?\s*{(.*)}\s*\w*\s*;\s*$/s) { my $struct_contents = $2; foreach $decl_line (split (/\n/, $struct_contents)) { @TRACE@("Struct line: $decl_line\n"); if ($decl_line =~ m%/\*\s*<\s*public\s*>\s*\*/%) { $public = 1; } elsif ($decl_line =~ m%/\*\s*<\s*(private|protected)\s*>\s*\*/%) { $public = 0; } elsif ($public) { $new_declaration .= $decl_line . "\n"; } } if ($new_declaration) { # Strip any blank lines off the ends. $new_declaration =~ s/^\s*\n//; $new_declaration =~ s/\n\s*$/\n/; if ($has_typedef) { $decl_out = "typedef struct {\n" . $new_declaration . "} $symbol;\n"; } else { $decl_out = "struct $symbol {\n" . $new_declaration . "};\n"; } } } else { &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol), "Couldn't parse struct:\n$declaration"); } # If we couldn't parse the struct or it was all private, output an # empty struct declaration. if ($decl_out eq "") { if ($has_typedef) { $decl_out = "typedef struct _$symbol $symbol;"; } else { $decl_out = "struct $symbol;"; } } } $decl_out = &CreateValidSGML ($decl_out); $desc .= "$decl_out\n"; $desc .= &MakeDeprecationNote($symbol); if (defined ($SymbolDocs{$symbol})) { $desc .= &ConvertMarkDown($symbol, $SymbolDocs{$symbol}); } # Create a table of fields and descriptions # FIXME: Inserting  's into the produced type declarations here would # improve the output in most situations ... except for function # members of structs! my @fields = ParseStructDeclaration($declaration, !$default_to_public, 0, \&MakeXRef, sub { "$_[0]"; }); my $params = $SymbolParams{$symbol}; # If no parameters are filled in, we don't generate the description # table, for backwards compatibility. my $found = 0; if (defined $params) { for (my $i = 1; $i <= $#$params; $i += $PARAM_FIELD_COUNT) { if ($params->[$i] =~ /\S/) { $found = 1; last; } } } if ($found) { my %field_descrs = @$params; my $missing_parameters = ""; my $unused_parameters = ""; $desc .= <\nMembers EOF while (@fields) { my $field_name = shift @fields; my $text = shift @fields; my $field_descr = $field_descrs{$field_name}; my $param_annotations = ""; $desc .= "$text\n"; if (defined $field_descr) { ($field_descr,$param_annotations) = &ExpandAnnotation($symbol, $field_descr); $field_descr = &ConvertMarkDown($symbol, $field_descr); # trim $field_descr =~ s/^(\s|\n)+//msg; $field_descr =~ s/(\s|\n)+$//msg; $desc .= "$field_descr\n$param_annotations\n"; delete $field_descrs{$field_name}; } else { &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol), "Field description for $symbol"."::"."$field_name is missing in source code comment block."); if ($missing_parameters ne "") { $missing_parameters .= ", ".$field_name; } else { $missing_parameters = $field_name; } $desc .= "\n"; } $desc .= "\n"; } $desc .= "\n\n"; foreach my $field_name (keys %field_descrs) { &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol), "Field description for $symbol"."::"."$field_name is not used from source code comment block."); if ($unused_parameters ne "") { $unused_parameters .= ", ".$field_name; } else { $unused_parameters = $field_name; } } # remember missing/unused parameters (needed in tmpl-free build) if (($missing_parameters ne "") and (! exists ($AllIncompleteSymbols{$symbol}))) { $AllIncompleteSymbols{$symbol}=$missing_parameters; } if (($unused_parameters ne "") and (! exists ($AllUnusedSymbols{$symbol}))) { $AllUnusedSymbols{$symbol}=$unused_parameters; } } else { if (scalar(@fields) > 0) { if (! exists ($AllIncompleteSymbols{$symbol})) { $AllIncompleteSymbols{$symbol}=""; &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol), "Field descriptions for struct $symbol are missing in source code comment block."); @TRACE@("Remaining structs fields: ".@fields.":".join(',',@fields)."\n"); } } } $desc .= OutputSymbolTraits ($symbol); $desc .= "\n"; return ($synop, $desc); } ############################################################################# # Function : OutputUnion # Description : Returns the synopsis and detailed description of a union. # Arguments : $symbol - the union. # $declaration - the declaration of the union. ############################################################################# sub OutputUnion { my ($symbol, $declaration) = @_; my $is_gtype = 0; if (&CheckIsObject ($symbol)) { @TRACE@("Found union gtype: $symbol\n"); $is_gtype = 1; } my $id; my $condition; if ($is_gtype) { $id = &CreateValidSGMLID ($symbol . "_union"); $condition = &MakeConditionDescription ($symbol . "_union"); } else { $id = &CreateValidSGMLID ($symbol); $condition = &MakeConditionDescription ($symbol); } # Determine if it is a simple struct or it also has a typedef. my $has_typedef = 0; if ($StructHasTypedef{$symbol} || $declaration =~ m/^\s*typedef\s+/) { $has_typedef = 1; } my $type_output; my $desc; if ($has_typedef) { # For unions with typedefs we just output the union name. $type_output = ""; $desc = "\n$symbol\n"; } else { $type_output = "union"; $desc = "\nunion $symbol\n"; } my $synop = "${type_output}$symbol\n"; $desc .= MakeIndexterms($symbol, $id); $desc .= "\n"; $desc .= OutputSymbolExtraLinks($symbol); $desc .= &MakeDeprecationNote($symbol); if (defined ($SymbolDocs{$symbol})) { $desc .= &ConvertMarkDown($symbol, $SymbolDocs{$symbol}); } # Create a table of fields and descriptions # FIXME: Inserting  's into the produced type declarations here would # improve the output in most situations ... except for function # members of structs! my @fields = ParseStructDeclaration($declaration, 0, 0, \&MakeXRef, sub { "$_[0]"; }); my $params = $SymbolParams{$symbol}; # If no parameters are filled in, we don't generate the description # table, for backwards compatibility my $found = 0; if (defined $params) { for (my $i = 1; $i <= $#$params; $i += $PARAM_FIELD_COUNT) { if ($params->[$i] =~ /\S/) { $found = 1; last; } } } if ($found) { my %field_descrs = @$params; my $missing_parameters = ""; my $unused_parameters = ""; $desc .= <\nMembers EOF while (@fields) { my $field_name = shift @fields; my $text = shift @fields; my $field_descr = $field_descrs{$field_name}; my $param_annotations = ""; $desc .= "$text\n"; if (defined $field_descr) { ($field_descr,$param_annotations) = &ExpandAnnotation($symbol, $field_descr); $field_descr = &ConvertMarkDown($symbol, $field_descr); # trim $field_descr =~ s/^(\s|\n)+//msg; $field_descr =~ s/(\s|\n)+$//msg; $desc .= "$field_descr\n$param_annotations\n"; delete $field_descrs{$field_name}; } else { &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol), "Field description for $symbol"."::"."$field_name is missing in source code comment block."); if ($missing_parameters ne "") { $missing_parameters .= ", ".$field_name; } else { $missing_parameters = $field_name; } $desc .= "\n"; } $desc .= "\n"; } $desc .= "\n"; foreach my $field_name (keys %field_descrs) { &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol), "Field description for $symbol"."::"."$field_name is not used from source code comment block."); if ($unused_parameters ne "") { $unused_parameters .= ", ".$field_name; } else { $unused_parameters = $field_name; } } # remember missing/unused parameters (needed in tmpl-free build) if (($missing_parameters ne "") and (! exists ($AllIncompleteSymbols{$symbol}))) { $AllIncompleteSymbols{$symbol}=$missing_parameters; } if (($unused_parameters ne "") and (! exists ($AllUnusedSymbols{$symbol}))) { $AllUnusedSymbols{$symbol}=$unused_parameters; } } else { if (scalar(@fields) > 0) { if (! exists ($AllIncompleteSymbols{$symbol})) { $AllIncompleteSymbols{$symbol}=""; &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol), "Field descriptions for union $symbol are missing in source code comment block."); @TRACE@("Remaining union fields: ".@fields.":".join(',',@fields)."\n"); } } } $desc .= OutputSymbolTraits ($symbol); $desc .= "\n"; return ($synop, $desc); } ############################################################################# # Function : OutputEnum # Description : Returns the synopsis and detailed description of a enum. # Arguments : $symbol - the enum. # $declaration - the declaration of the enum. ############################################################################# sub OutputEnum { my ($symbol, $declaration) = @_; my $is_gtype = 0; if (&CheckIsObject ($symbol)) { @TRACE@("Found enum gtype: $symbol\n"); $is_gtype = 1; } my $id; my $condition; if ($is_gtype) { $id = &CreateValidSGMLID ($symbol . "_enum"); $condition = &MakeConditionDescription ($symbol . "_enum"); } else { $id = &CreateValidSGMLID ($symbol); $condition = &MakeConditionDescription ($symbol); } my $synop = "enum$symbol\n"; my $desc = "\nenum $symbol\n"; $desc .= MakeIndexterms($symbol, $id); $desc .= "\n"; $desc .= OutputSymbolExtraLinks($symbol); $desc .= &MakeDeprecationNote($symbol); if (defined ($SymbolDocs{$symbol})) { $desc .= &ConvertMarkDown($symbol, $SymbolDocs{$symbol}); } # Create a table of fields and descriptions my @fields = ParseEnumDeclaration($declaration); my $params = $SymbolParams{$symbol}; # If nothing at all is documented log a single summary warning at the end. # Otherwise, warn about each undocumented item. my $found = 0; if (defined $params) { for (my $i = 1; $i <= $#$params; $i += $PARAM_FIELD_COUNT) { if ($params->[$i] =~ /\S/) { $found = 1; last; } } } my %field_descrs = (defined $params ? @$params : ()); my $missing_parameters = ""; my $unused_parameters = ""; $desc .= <\nMembers EOF for my $field_name (@fields) { my $field_descr = $field_descrs{$field_name}; my $param_annotations = ""; $id = &CreateValidSGMLID ($field_name); $condition = &MakeConditionDescription ($field_name); $desc .= "$field_name\n"; if (defined $field_descr) { ($field_descr,$param_annotations) = &ExpandAnnotation($symbol, $field_descr); $field_descr = &ConvertMarkDown($symbol, $field_descr); $desc .= "$field_descr\n$param_annotations\n"; delete $field_descrs{$field_name}; } else { if ($found) { &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol), "Value description for $symbol"."::"."$field_name is missing in source code comment block."); if ($missing_parameters ne "") { $missing_parameters .= ", ".$field_name; } else { $missing_parameters = $field_name; } } $desc .= "\n"; } $desc .= "\n"; } $desc .= "\n"; foreach my $field_name (keys %field_descrs) { &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol), "Value description for $symbol"."::"."$field_name is not used from source code comment block."); if ($unused_parameters ne "") { $unused_parameters .= ", ".$field_name; } else { $unused_parameters = $field_name; } } # remember missing/unused parameters (needed in tmpl-free build) if (($missing_parameters ne "") and (! exists ($AllIncompleteSymbols{$symbol}))) { $AllIncompleteSymbols{$symbol}=$missing_parameters; } if (($unused_parameters ne "") and (! exists ($AllUnusedSymbols{$symbol}))) { $AllUnusedSymbols{$symbol}=$unused_parameters; } if (!$found) { if (scalar(@fields) > 0) { if (! exists ($AllIncompleteSymbols{$symbol})) { $AllIncompleteSymbols{$symbol}=""; &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol), "Value descriptions for $symbol are missing in source code comment block."); } } } $desc .= OutputSymbolTraits ($symbol); $desc .= "\n"; return ($synop, $desc); } ############################################################################# # Function : OutputVariable # Description : Returns the synopsis and detailed description of a variable. # Arguments : $symbol - the extern'ed variable. # $declaration - the declaration of the variable. ############################################################################# sub OutputVariable { my ($symbol, $declaration) = @_; my $id = &CreateValidSGMLID ($symbol); my $condition = &MakeConditionDescription ($symbol); @TRACE@("ouputing variable: '$symbol' '$declaration'"); my $type_output; if ($declaration =~ m/^\s*extern\s+((const\s+|signed\s+|unsigned\s+|long\s+|short\s+)*\w+)(\s+\*+|\*+|\s)(\s*)(const\s+)*([A-Za-z]\w*)\s*;/) { my $mod1 = defined ($1) ? $1 : ""; my $ptr = defined ($3) ? $3 : ""; my $space = defined ($4) ? $4 : ""; my $mod2 = defined ($5) ? $5 : ""; $type_output = "extern $mod1$ptr$space$mod2"; } elsif ($declaration =~ m/^\s*((const\s+|signed\s+|unsigned\s+|long\s+|short\s+)*\w+)(\s+\*+|\*+|\s)(\s*)(const\s+)*([A-Za-z]\w*)\s*=/) { my $mod1 = defined ($1) ? $1 : ""; my $ptr = defined ($3) ? $3 : ""; my $space = defined ($4) ? $4 : ""; my $mod2 = defined ($5) ? $5 : ""; $type_output = "$mod1$ptr$space$mod2"; } else { $type_output = "extern"; } my $synop = "${type_output}$symbol\n"; my $desc = "\n$symbol\n"; $desc .= MakeIndexterms($symbol, $id); $desc .= "\n"; $desc .= OutputSymbolExtraLinks($symbol); my $decl_out = &CreateValidSGML ($declaration); $desc .= "$decl_out\n"; $desc .= &MakeDeprecationNote($symbol); if (defined ($SymbolDocs{$symbol})) { $desc .= &ConvertMarkDown($symbol, $SymbolDocs{$symbol}); } if (defined ($SymbolAnnotations{$symbol})) { my $param_desc = $SymbolAnnotations{$symbol}; my $param_annotations = ""; ($param_desc,$param_annotations) = &ExpandAnnotation($symbol, $param_desc); if ($param_annotations ne "") { $desc .= "\n$param_annotations"; } } $desc .= OutputSymbolTraits ($symbol); $desc .= "\n"; return ($synop, $desc); } ############################################################################# # Function : OutputFunction # Description : Returns the synopsis and detailed description of a function. # Arguments : $symbol - the function. # $declaration - the declaration of the function. ############################################################################# sub OutputFunction { my ($symbol, $declaration, $symbol_type) = @_; my $id = &CreateValidSGMLID ($symbol); my $condition = &MakeConditionDescription ($symbol); # Take out the return type $1 $2 $3 $declaration =~ s/\s*((?:const\s+|G_CONST_RETURN\s+|signed\s+|unsigned\s+|long\s+|short\s+|struct\s+|enum\s+)*)(\w+)(\s*\**\s*(?:const|G_CONST_RETURN)?\s*\**\s*(?:restrict)?\s*)<\/RETURNS>\n//; my $type_modifier = defined($1) ? $1 : ""; my $type = $2; my $pointer = $3; # Trim trailing spaces as we are going to pad to $RETURN_TYPE_FIELD_WIDTH below anyway $pointer =~ s/\s+$//; my $xref = &MakeXRef ($type, &tagify($type, "returnvalue")); my $start = ""; #if ($symbol_type eq 'USER_FUNCTION') { # $start = "typedef "; #} # We output const rather than G_CONST_RETURN. $type_modifier =~ s/G_CONST_RETURN/const/g; $pointer =~ s/G_CONST_RETURN/const/g; $pointer =~ s/^\s+/ /g; my $ret_type_output; $ret_type_output = "$start$type_modifier$xref$pointer\n"; my $indent_len; $indent_len = length ($symbol) + 2; my $char1 = my $char2 = my $char3 = ""; if ($symbol_type eq 'USER_FUNCTION') { $indent_len += 3; $char1 = "("; $char2 = "*"; $char3 = ")"; } my ($symbol_output, $symbol_desc_output); $symbol_output = "$char1$char2$symbol$char3"; if ($indent_len < $MAX_SYMBOL_FIELD_WIDTH) { $symbol_desc_output = "$char1$char2$symbol$char3 "; } else { $indent_len = $MAX_SYMBOL_FIELD_WIDTH - 8; $symbol_desc_output = "$char1$char2$symbol$char3\n" . (' ' x ($indent_len - 1)); } my $synop = "${ret_type_output}${symbol_output} ()\n"; my $desc = "\n${symbol} ()\n"; $desc .= MakeIndexterms($symbol, $id); $desc .= "\n"; $desc .= OutputSymbolExtraLinks($symbol); $desc .= "${ret_type_output}$symbol_desc_output("; my @fields = ParseFunctionDeclaration($declaration, \&MakeXRef, sub { &tagify($_[0],"parameter"); }); for (my $i = 1; $i <= $#fields; $i += 2) { my $field_name = $fields[$i]; if ($i == 1) { $desc .= "$field_name"; } else { $desc .= ",\n" . (' ' x $indent_len) . "$field_name"; } } $desc .= ");\n"; $desc .= &MakeDeprecationNote($symbol); if (defined ($SymbolDocs{$symbol})) { $desc .= &ConvertMarkDown($symbol, $SymbolDocs{$symbol}); } if (defined ($SymbolAnnotations{$symbol})) { my $param_desc = $SymbolAnnotations{$symbol}; my $param_annotations = ""; ($param_desc,$param_annotations) = &ExpandAnnotation($symbol, $param_desc); if ($param_annotations ne "") { $desc .= "\n$param_annotations"; } } $desc .= &OutputParamDescriptions ("FUNCTION", $symbol, @fields); $desc .= OutputSymbolTraits ($symbol); $desc .= "\n"; return ($synop, $desc); } ############################################################################# # Function : OutputParamDescriptions # Description : Returns the DocBook output describing the parameters of a # function, macro or signal handler. # Arguments : $symbol_type - 'FUNCTION', 'MACRO' or 'SIGNAL'. Signal # handlers have an implicit user_data parameter last. # $symbol - the name of the function/macro being described. # @fields - parsed fields from the declaration, used to determine # undocumented/unused entries ############################################################################# sub OutputParamDescriptions { my ($symbol_type, $symbol, @fields) = @_; my $output = ""; my $params = $SymbolParams{$symbol}; my $num_params = 0; my %field_descrs = (); if (@fields) { %field_descrs = @fields; delete $field_descrs{"void"}; delete $field_descrs{"Returns"}; } if (defined $params) { my $returns = ""; my $params_desc = ""; my $missing_parameters = ""; my $unused_parameters = ""; my $j; for ($j = 0; $j <= $#$params; $j += $PARAM_FIELD_COUNT) { my $param_name = $$params[$j]; my $param_desc = $$params[$j + 1]; my $param_annotations = ""; ($param_desc,$param_annotations) = &ExpandAnnotation($symbol, $param_desc); $param_desc = &ConvertMarkDown($symbol, $param_desc); # trim $param_desc =~ s/^(\s|\n)+//msg; $param_desc =~ s/(\s|\n)+$//msg; if ($param_name eq "Returns") { $returns = $param_desc; if ($param_annotations ne "") { $returns .= "\n$param_annotations"; } } elsif ($param_name eq "void") { # FIXME: &LogWarning()? @TRACE@("!!!! void in params for $symbol?\n"); } else { if (@fields) { if (!defined $field_descrs{$param_name}) { &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol), "Parameter description for $symbol"."::"."$param_name is not used from source code comment block."); if ($unused_parameters ne "") { $unused_parameters .= ", ".$param_name; } else { $unused_parameters = $param_name; } } else { delete $field_descrs{$param_name}; } } if($param_desc ne "") { $params_desc .= "$param_name\n$param_desc\n$param_annotations\n"; $num_params++; } } } foreach my $param_name (keys %field_descrs) { &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol), "Parameter description for $symbol"."::"."$param_name is missing in source code comment block."); if ($missing_parameters ne "") { $missing_parameters .= ", ".$param_name; } else { $missing_parameters = $param_name; } } # Signals have an implicit user_data parameter which we describe. if ($symbol_type eq "SIGNAL") { $params_desc .= "user_data\nuser data set when the signal handler was connected.\n\n"; } # Start a table if we need one. if ($params_desc ne "") { $output .= <\nParameters EOF $output .= $params_desc; $output .= "\n"; } # Output the returns info last if ($returns ne "") { $output .= <\nReturns EOF $output .= $returns; $output .= "\n"; } # remember missing/unused parameters (needed in tmpl-free build) if (($missing_parameters ne "") and (! exists ($AllIncompleteSymbols{$symbol}))) { $AllIncompleteSymbols{$symbol}=$missing_parameters; } if (($unused_parameters ne "") and (! exists ($AllUnusedSymbols{$symbol}))) { $AllUnusedSymbols{$symbol}=$unused_parameters; } } if (($num_params == 0) && @fields && (scalar(keys(%field_descrs)) > 0)) { if (! exists ($AllIncompleteSymbols{$symbol})) { $AllIncompleteSymbols{$symbol}=""; } } return $output; } ############################################################################# # Function : ParseStabilityLevel # Description : Parses a stability level and outputs a warning if it isn't # valid. # Arguments : $stability - the stability text. # $file, $line - context for error message # $message - description of where the level is from, to use in # any error message. # Returns : The parsed stability level string. ############################################################################# sub ParseStabilityLevel { my ($stability, $file, $line, $message) = @_; $stability =~ s/^\s*//; $stability =~ s/\s*$//; if ($stability =~ m/^stable$/i) { $stability = "Stable"; } elsif ($stability =~ m/^unstable$/i) { $stability = "Unstable"; } elsif ($stability =~ m/^private$/i) { $stability = "Private"; } else { &LogWarning ($file, $line, "$message is $stability.". "It should be one of these: Stable, Unstable, or Private."); } return $stability; } ############################################################################# # Function : OutputDBFile # Description : Outputs the final DocBook file for one section. # Arguments : $file - the name of the file. # $title - the title from the $MODULE-sections.txt file, which # will be overridden by the title in the template file. # $section_id - the id to use for the toplevel tag. # $includes - comma-separates list of include files added at top of # synopsis, with '<' '>' around them (if not already enclosed in ""). # $functions_synop - reference to the DocBook for the Functions Synopsis part. # $other_synop - reference to the DocBook for the Types and Values Synopsis part. # $functions_details - reference to the DocBook for the Functions Details part. # $other_details - reference to the DocBook for the Types and Values Details part. # $signal_synop - reference to the DocBook for the Signal Synopsis part # $signal_desc - reference to the DocBook for the Signal Description part # $args_synop - reference to the DocBook for the Arg Synopsis part # $args_desc - reference to the DocBook for the Arg Description part # $hierarchy - reference to the DocBook for the Object Hierarchy part # $interfaces - reference to the DocBook for the Interfaces part # $implementations - reference to the DocBook for the Known Implementations part # $prerequisites - reference to the DocBook for the Prerequisites part # $derived - reference to the DocBook for the Derived Interfaces part # $file_objects - reference to an array of objects in this file ############################################################################# sub OutputDBFile { my ($file, $title, $section_id, $includes, $functions_synop, $other_synop, $functions_details, $other_details, $signals_synop, $signals_desc, $args_synop, $args_desc, $hierarchy, $interfaces, $implementations, $prerequisites, $derived, $file_objects) = @_; @TRACE@("Output docbook for file $file with title '$title'\n"); # The edited title overrides the one from the sections file. my $new_title = $SymbolDocs{"$TMPL_DIR/$file:Title"}; if (defined ($new_title) && $new_title !~ m/^\s*$/) { $title = $new_title; @TRACE@("Found title: $title\n"); } my $short_desc = $SymbolDocs{"$TMPL_DIR/$file:Short_Description"}; if (!defined ($short_desc) || $short_desc =~ m/^\s*$/) { $short_desc = ""; } else { # Don't use ConvertMarkDown here for now since we don't want blocks $short_desc = &ExpandAbbreviations("$title:Short_description", $short_desc); @TRACE@("Found short_desc: $short_desc"); } my $long_desc = $SymbolDocs{"$TMPL_DIR/$file:Long_Description"}; if (!defined ($long_desc) || $long_desc =~ m/^\s*$/) { $long_desc = ""; } else { $long_desc = &ConvertMarkDown("$title:Long_description", $long_desc); @TRACE@("Found long_desc: $long_desc"); } my $see_also = $SymbolDocs{"$TMPL_DIR/$file:See_Also"}; if (!defined ($see_also) || $see_also =~ m%^\s*()?\s*()?\s*$%) { $see_also = ""; } else { $see_also = &ConvertMarkDown("$title:See_Also", $see_also); @TRACE@("Found see_also: $see_also"); } if ($see_also) { $see_also = "\nSee Also\n$see_also\n\n"; } my $stability = $SymbolDocs{"$TMPL_DIR/$file:Stability_Level"}; if (!defined ($stability) || $stability =~ m/^\s*$/) { $stability = ""; } else { $stability = &ParseStabilityLevel($stability, $file, $., "Section stability level"); @TRACE@("Found stability: $stability"); } if ($stability) { $AnnotationsUsed{$stability} = 1; $stability = "\nStability Level\n$stability, unless otherwise indicated\n\n"; } elsif ($DEFAULT_STABILITY) { $AnnotationsUsed{$DEFAULT_STABILITY} = 1; $stability = "\nStability Level\n$DEFAULT_STABILITY, unless otherwise indicated\n\n"; } my $image = $SymbolDocs{"$TMPL_DIR/$file:Image"}; if (!defined ($image) || $image =~ m/^\s*$/) { $image = ""; } else { $image =~ s/^\s*//; $image =~ s/\s*$//; my $format; if ($image =~ /jpe?g$/i) { $format = "format='JPEG'"; } elsif ($image =~ /png$/i) { $format = "format='PNG'"; } elsif ($image =~ /svg$/i) { $format = "format='SVG'"; } else { $format = ""; } $image = " \n" } my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = gmtime (time); my $month = (qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec))[$mon]; $year += 1900; my $include_output = ""; if ($includes) { $include_output .= "Includes"; my $include; foreach $include (split (/,/, $includes)) { if ($include =~ m/^\".+\"$/) { $include_output .= "#include ${include}\n"; } else { $include =~ s/^\s+|\s+$//gs; $include_output .= "#include <${include}>\n"; } } $include_output .= "\n"; } my $extralinks = OutputSectionExtraLinks($title,"Section:$file"); my $old_db_file = "$DB_OUTPUT_DIR/$file.$OUTPUT_FORMAT"; my $new_db_file = "$DB_OUTPUT_DIR/$file.$OUTPUT_FORMAT.new"; open (OUTPUT, ">$new_db_file") || die "Can't create $new_db_file: $!"; my $object_anchors = ""; foreach my $object (@$file_objects) { next if ($object eq $section_id); my $id = CreateValidSGMLID($object); @TRACE@("Adding anchor for $object\n"); $object_anchors .= "" if ($OUTPUT_FORMAT eq "xml") { print OUTPUT $doctype_header; } print OUTPUT < $title 3 \U$MODULE\E Library$image $title $short_desc $stability $$functions_synop$$args_synop$$signals_synop$object_anchors$$other_synop$$hierarchy$$prerequisites$$derived$$interfaces$$implementations $include_output Description $extralinks$long_desc Functions $$functions_details Types and Values $$other_details $$args_desc$$signals_desc$see_also EOF close (OUTPUT); return &UpdateFileIfChanged ($old_db_file, $new_db_file, 0); } ############################################################################# # Function : OutputExtraFile # Description : Copies an "extra" DocBook file into the output directory, # expanding abbreviations # Arguments : $file - the source file. ############################################################################# sub OutputExtraFile { my ($file) = @_; my $basename; ($basename = $file) =~ s!^.*/!!; my $old_db_file = "$DB_OUTPUT_DIR/$basename"; my $new_db_file = "$DB_OUTPUT_DIR/$basename.new"; my $contents; open(EXTRA_FILE, "<$file") || die "Can't open $file"; { local $/; $contents = ; } open (OUTPUT, ">$new_db_file") || die "Can't create $new_db_file: $!"; print OUTPUT &ExpandAbbreviations ("$basename file", $contents); close (OUTPUT); return &UpdateFileIfChanged ($old_db_file, $new_db_file, 0); } ############################################################################# # Function : OutputBook # Description : Outputs the entities that need to be included into the # main docbook file for the module. # Arguments : $book_top - the declarations of the entities, which are added # at the top of the main docbook file. # $book_bottom - the references to the entities, which are # added in the main docbook file at the desired position. ############################################################################# sub OutputBook { my ($book_top, $book_bottom) = @_; my $old_file = "$DB_OUTPUT_DIR/$MODULE-doc.top"; my $new_file = "$DB_OUTPUT_DIR/$MODULE-doc.top.new"; open (OUTPUT, ">$new_file") || die "Can't create $new_file: $!"; print OUTPUT $book_top; close (OUTPUT); &UpdateFileIfChanged ($old_file, $new_file, 0); $old_file = "$DB_OUTPUT_DIR/$MODULE-doc.bottom"; $new_file = "$DB_OUTPUT_DIR/$MODULE-doc.bottom.new"; open (OUTPUT, ">$new_file") || die "Can't create $new_file: $!"; print OUTPUT $book_bottom; close (OUTPUT); &UpdateFileIfChanged ($old_file, $new_file, 0); # If the main docbook file hasn't been created yet, we create it here. # The user can tweak it later. if ($MAIN_SGML_FILE && ! -e $MAIN_SGML_FILE) { open (OUTPUT, ">$MAIN_SGML_FILE") || die "Can't create $MAIN_SGML_FILE: $!"; if ($OUTPUT_FORMAT eq "xml") { print OUTPUT < ]> EOF } else { print OUTPUT < EOF } print OUTPUT < $MODULE Reference Manual for $MODULE [VERSION]. The latest version of this documentation can be found on-line at http://[SERVER]/$MODULE/. [Insert title here] $book_bottom EOF if (-e $OBJECT_TREE_FILE) { print OUTPUT < Object Hierarchy EOF } else { print OUTPUT < Object Hierarchy --> EOF } print OUTPUT < API Index Index of deprecated API EOF if (keys(%AnnotationsUsed)) { print OUTPUT < EOF } else { print OUTPUT < --> EOF } print OUTPUT < EOF close (OUTPUT); } } ############################################################################# # Function : CreateValidSGML # Description : This turns any chars which are used in SGML into entities, # e.g. '<' into '<' # Arguments : $text - the text to turn into proper SGML. ############################################################################# sub CreateValidSGML { my ($text) = @_; $text =~ s/&/&/g; # Do this first, or the others get messed up. $text =~ s//>/g; # browers render single tabs inconsistently $text =~ s/([^\s])\t([^\s])/$1 $2/g; return $text; } ############################################################################# # Function : ConvertSGMLChars # Description : This is used for text in source code comment blocks, to turn # chars which are used in SGML into entities, e.g. '<' into # '<'. Depending on $INLINE_MARKUP_MODE, this is done # unconditionally or only if the character doesn't seem to be # part of an SGML construct (tag or entity reference). # Arguments : $text - the text to turn into proper SGML. ############################################################################# sub ConvertSGMLChars { my ($symbol, $text) = @_; if ($INLINE_MARKUP_MODE) { # For the XML/SGML mode only convert to entities outside CDATA sections. return &ModifyXMLElements ($text, $symbol, "]*>", \&ConvertSGMLCharsEndTag, \&ConvertSGMLCharsCallback); } else { # For the simple non-sgml mode, convert to entities everywhere. # First, convert freestanding & to & $text =~ s/&(?![a-zA-Z#]+;)/&/g; $text =~ s/" at beginning of string for blockquote markdown $text =~ s/(?<=[^\w\n"'\/-])>/>/g; return $text; } } sub ConvertSGMLCharsEndTag { if ($_[0] eq ""; } else { return ""; } } sub ConvertSGMLCharsCallback { my ($text, $symbol, $tag) = @_; if ($tag =~ m/^ specially here. return &ModifyXMLElements ($text, $symbol, "" at beginning of string for blockquote markdown $text =~ s/(?<=[^\w\n"'\/-])>/>/g; # Handle "#include " $text =~ s/#include(\s+)<([^>]+)>/#include$1<$2>/g; } return $text; } sub ConvertSGMLCharsCallback2 { my ($text, $symbol, $tag) = @_; # If we're not in CDATA convert to entities. # We could handle differently, though I'm not sure it helps. if ($tag eq "") { # replace only if its not a tag $text =~ s/&(?![a-zA-Z#]+;)/&/g; # Do this first, or the others get messed up. $text =~ s/<(?![a-zA-Z\/!])/</g; $text =~ s/(?/>/g; # Handle "#include " $text =~ s/#include(\s+)<([^>]+)>/#include$1<$2>/g; } return $text; } ############################################################################# # Function : ExpandAnnotation # Description : This turns annotations into acronym tags. # Arguments : $symbol - the symbol being documented, for error messages. # $text - the text to expand. ############################################################################# sub ExpandAnnotation { my ($symbol, $param_desc) = @_; my $param_annotations = ""; # look for annotations at the start of the comment part # function level annotations don't end with a colon ':' if ($param_desc =~ m%^\s*\((.*?)\)(:|$)%) { my @annotations; my $annotation; $param_desc = $'; @annotations = split(/\)\s*\(/,$1); @TRACE@("annotations for $symbol: '$1'\n"); foreach $annotation (@annotations) { # need to search for the longest key-match in %AnnotationDefinition my $match_length=0; my $match_annotation=""; my $annotationdef; foreach $annotationdef (keys %AnnotationDefinition) { if ($annotation =~ m/^$annotationdef/) { if (length($annotationdef)>$match_length) { $match_length=length($annotationdef); $match_annotation=$annotationdef; } } } my $annotation_extra = ""; if ($match_annotation ne "") { if ($annotation =~ m%$match_annotation\s+(.*)%) { $annotation_extra = " $1"; } $AnnotationsUsed{$match_annotation} = 1; $param_annotations .= "[$match_annotation$annotation_extra]"; } else { &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol), "unknown annotation \"$annotation\" in documentation for $symbol."); $param_annotations .= "[$annotation]"; } } chomp($param_desc); $param_desc =~ m/^(.*?)\.*\s*$/s; $param_desc = "$1. "; } if ($param_annotations ne "") { $param_annotations = "$param_annotations"; } return ($param_desc, $param_annotations); } ############################################################################# # Function : ExpandAbbreviations # Description : This turns the abbreviations function(), macro(), @param, # %constant, and #symbol into appropriate DocBook markup. # CDATA sections and parts are skipped. # Arguments : $symbol - the symbol being documented, for error messages. # $text - the text to expand. ############################################################################# sub ExpandAbbreviations { my ($symbol, $text) = @_; # Note: This is a fallback and normally done in the markdown parser # Convert "|[" and "]|" into the start and end of program listing examples. # Support \[ modifiers $text =~ s%\|\[%%g; # keep CDATA unmodified, preserve ulink tags (ideally we preseve all tags # as such) return &ModifyXMLElements ($text, $symbol, "]*>|]*>|"; } elsif ($start_tag eq ""; } elsif ($start_tag =~ m/<(\w+)/) { return ""; } } # Called inside or outside each CDATA or section. sub ExpandAbbreviationsCallback { my ($text, $symbol, $tag) = @_; if ($tag =~ m/^ sections, so we expand # any gtk-doc abbreviations. # Convert '@param()' # FIXME: we could make those also links ($symbol.$2), but that would be less # useful as the link target is a few lines up or down $text =~ s/(\A|[^\\])\@(\w+((\.|->)\w+)*)\s*\(\)/$1$2()<\/parameter>/g; # Convert 'function()' or 'macro()'. # if there is abc_*_def() we don't want to make a link to _def() # FIXME: also handle abc(def(....)) : but that would need to be done recursively :/ $text =~ s/([^\*.\w])(\w+)\s*\(\)/$1.&MakeXRef($2, &tagify($2 . "()", "function"));/eg; # handle #Object.func() $text =~ s/(\A|[^\\])#([\w\-:\.]+[\w]+)\s*\(\)/$1.&MakeXRef($2, &tagify($2 . "()", "function"));/eg; # Convert '@param', but not '\@param'. $text =~ s/(\A|[^\\])\@(\w+((\.|->)\w+)*)/$1$2<\/parameter>/g; $text =~ s/\\\@/\@/g; # Convert '%constant', but not '\%constant'. # Also allow negative numbers, e.g. %-1. $text =~ s/(\A|[^\\])\%(-?\w+)/$1.&MakeXRef($2, &tagify($2, "literal"));/eg; $text =~ s/\\\%/\%/g; # Convert '#symbol', but not '\#symbol'. $text =~ s/(\A|[^\\])#([\w\-:\.]+[\w]+)/$1.&MakeHashXRef($2, "type");/eg; $text =~ s/\\#/#/g; } return $text; } # This is called inside a sub ExpandAbbreviationsCallback2 { my ($text, $symbol, $tag) = @_; if ($tag eq "") { # We are inside a but outside any CDATA sections, # so we expand any gtk-doc abbreviations. # FIXME: why is this different from &ExpandAbbreviationsCallback(), # why not just call it $text =~ s/#(\w+)/&MakeHashXRef($1, "");/eg; } elsif ($tag eq ". # Arguments : $text - the text. # $symbol - the symbol currently being documented (only used for # error messages). # $start_tag_regexp - the regular expression to match start tags. # e.g. "]*>" to match # CDATA sections or programlisting elements. # $end_tag_func - function which is passed the matched start tag # and should return the appropriate end tag string regexp. # $callback - callback called with each part of the text. It is # called with a piece of text, the symbol being # documented, and the matched start tag or "" if the text # is outside the XML elements being matched. ############################################################################# sub ModifyXMLElements { my ($text, $symbol, $start_tag_regexp, $end_tag_func, $callback) = @_; my ($before_tag, $start_tag, $end_tag_regexp, $end_tag); my $result = ""; while ($text =~ m/$start_tag_regexp/s) { $before_tag = $`; # Prematch for last successful match string $start_tag = $&; # Last successful match $text = $'; # Postmatch for last successful match string $result .= &$callback ($before_tag, $symbol, ""); $result .= $start_tag; # get the matching end-tag for current tag $end_tag_regexp = &$end_tag_func ($start_tag); if ($text =~ m/$end_tag_regexp/s) { $before_tag = $`; $end_tag = $&; $text = $'; $result .= &$callback ($before_tag, $symbol, $start_tag); $result .= $end_tag; } else { &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol), "Can't find tag end: $end_tag_regexp in docs for: $symbol."); # Just assume it is all inside the tag. $result .= &$callback ($text, $symbol, $start_tag); $text = ""; } } # Handle any remaining text outside the tags. $result .= &$callback ($text, $symbol, ""); return $result; } sub noop { return $_[0]; } # Adds a tag around some text. # e.g tagify("Text", "literal") => "Text". sub tagify { my ($text, $elem) = @_; return "<" . $elem . ">" . $text . ""; } ############################################################################# # Function : MakeXRef # Description : This returns a cross-reference link to the given symbol. # Though it doesn't try to do this for a few standard C types # that it knows won't be in the documentation. # Arguments : $symbol - the symbol to try to create a XRef to. # $text - text text to put inside the XRef, defaults to $symbol ############################################################################# sub MakeXRef { my ($symbol, $text) = ($_[0], $_[1]); $symbol =~ s/^\s+//; $symbol =~ s/\s+$//; if (!defined($text)) { $text = $symbol; # Get rid of special suffixes ('-struct','-enum'). $text =~ s/-struct$//; $text =~ s/-enum$//; } if ($symbol =~ m/ /) { return "$text"; } @TRACE@("Getting type link for $symbol -> $text\n"); my $symbol_id = &CreateValidSGMLID ($symbol); return "$text"; } ############################################################################# # Function : MakeIndexterms # Description : This returns a indexterm elements for the given symbol # Arguments : $symbol - the symbol to create indexterms for ############################################################################# sub MakeIndexterms { my ($symbol, $id) = @_; my $terms = ""; my $sortas = ""; # make the index useful, by ommiting the namespace when sorting if ($NAME_SPACE ne "") { if ($symbol =~ m/^$NAME_SPACE\_?(.*)/i) { $sortas=" sortas=\"$1\""; } } if (exists $Deprecated{$symbol}) { $terms .= "$symbol"; $IndexEntriesDeprecated{$symbol}=$id; $IndexEntriesFull{$symbol}=$id; } if (exists $Since{$symbol}) { my $since = $Since{$symbol}; $since =~ s/^\s+//; $since =~ s/\s+$//; if ($since ne "") { $terms .= "$symbol"; } $IndexEntriesSince{$symbol}=$id; $IndexEntriesFull{$symbol}=$id; } if ($terms eq "") { $terms .= "$symbol"; $IndexEntriesFull{$symbol}=$id; } return $terms; } ############################################################################# # Function : MakeDeprecationNote # Description : This returns a deprecation warning for the given symbol. # Arguments : $symbol - the symbol to try to create a warning for. ############################################################################# sub MakeDeprecationNote { my ($symbol) = $_[0]; my $desc = ""; if (exists $Deprecated{$symbol}) { my $note; $desc .= "$symbol "; $note = $Deprecated{$symbol}; if ($note =~ /^\s*([0-9\.]+)\s*:?/) { $desc .= "has been deprecated since version $1 and should not be used in newly-written code."; } else { $desc .= "is deprecated and should not be used in newly-written code."; } $note =~ s/^\s*([0-9\.]+)\s*:?\s*//; $note =~ s/^\s+//; $note =~ s/\s+$//; if ($note ne "") { $note = &ConvertMarkDown($symbol, $note); $desc .= " " . $note; } $desc .= "\n"; } return $desc; } ############################################################################# # Function : MakeConditionDescription # Description : This returns a sumary of conditions for the given symbol. # Arguments : $symbol - the symbol to try to create the sumary. ############################################################################# sub MakeConditionDescription { my ($symbol) = $_[0]; my $desc = ""; if (exists $Deprecated{$symbol}) { if ($desc ne "") { $desc .= "|"; } if ($Deprecated{$symbol} =~ /^\s*(.*?)\s*$/) { $desc .= "deprecated:$1"; } else { $desc .= "deprecated"; } } if (exists $Since{$symbol}) { if ($desc ne "") { $desc .= "|"; } if ($Since{$symbol} =~ /^\s*(.*?)\s*$/) { $desc .= "since:$1"; } else { $desc .= "since"; } } if (exists $StabilityLevel{$symbol}) { if ($desc ne "") { $desc .= "|"; } $desc .= "stability:".$StabilityLevel{$symbol}; } if ($desc ne "") { my $cond = $desc; $cond =~ s/\"/"/g; $desc=" condition=\"".$cond."\""; @TRACE@("condition for '$symbol' = '$desc'\n"); } return $desc; } ############################################################################# # Function : GetHierarchy # Description : Returns the DocBook output describing the ancestors and # immediate children of a GObject subclass. It uses the # global @Objects and @ObjectLevels arrays to walk the tree. # # Arguments : $object - the GtkObject subclass. # @hierarchy - previous hierarchy ############################################################################# sub GetHierarchy { my ($object,$hierarchy_ref) = @_; my @hierarchy = @{$hierarchy_ref}; # Find object in the objects array. my $found = 0; my @children = (); my $i; my $level; my $j; for ($i = 0; $i < @Objects; $i++) { if ($found) { if ($ObjectLevels[$i] <= $level) { last; } elsif ($ObjectLevels[$i] == $level + 1) { push (@children, $Objects[$i]); } } elsif ($Objects[$i] eq $object) { $found = 1; $j = $i; $level = $ObjectLevels[$i]; } } if (!$found) { return @hierarchy; } # Walk up the hierarchy, pushing ancestors onto the ancestors array. my @ancestors = (); push (@ancestors, $object); @TRACE@("Level: $level\n"); while ($level > 1) { $j--; if ($ObjectLevels[$j] < $level) { push (@ancestors, $Objects[$j]); $level = $ObjectLevels[$j]; @TRACE@("Level: $level\n"); } } # Output the ancestors, indented and with links. my $last_index = 0; $level = 1; for ($i = $#ancestors; $i >= 0; $i--) { my $entry_text; my $alt_text; my $ancestor = $ancestors[$i]; my $ancestor_id = &CreateValidSGMLID ($ancestor); my $indent = ' ' x ($level * 4); # Don't add a link to the current object, i.e. when i == 0. if ($i > 0) { $entry_text = $indent . "$ancestor"; $alt_text = $indent . $ancestor; } else { $entry_text = $indent . $ancestor; $alt_text = $indent . "$ancestor"; } @TRACE@("Checking for '$entry_text' or '$alt_text'"); # Check if we already have this object my $index = -1; for ($j = 0; $j <= $#hierarchy; $j++) { if (($hierarchy[$j] eq $entry_text) or ($hierarchy[$j] eq $alt_text)) { $index = $j; last; } } if ($index == -1) { # We have a new entry, find insert position in alphabetical order my $found = 0; for ($j = $last_index; $j <= $#hierarchy; $j++) { if ($hierarchy[$j] !~ m/^${indent}/) { $last_index = $j; $found = 1; last; } elsif ($hierarchy[$j] =~ m/^${indent}[^ ]/) { my $stripped_text = $hierarchy[$j]; if ($entry_text !~ m/%%; $stripped_text =~ s%%%; } if ($entry_text lt $stripped_text) { $last_index = $j; $found = 1; last; } } } # Append to bottom if (!$found) { $last_index = 1 + $#hierarchy; } splice @hierarchy, $last_index, 0, ($entry_text); $last_index++; } else { # Already have this one, make sure we use the not linked version if ($entry_text !~ m/$children[$i]"; splice @hierarchy, $last_index, 0, ($indented_text); $last_index++; } return @hierarchy; } ############################################################################# # Function : GetInterfaces # Description : Returns the DocBook output describing the interfaces # implemented by a class. It uses the global %Interfaces hash. # Arguments : $object - the GtkObject subclass. ############################################################################# sub GetInterfaces { my ($object) = @_; my $text = ""; my $i; # Find object in the objects array. if (exists($Interfaces{$object})) { my @ifaces = split(' ', $Interfaces{$object}); $text = < $object implements EOF for ($i = 0; $i <= $#ifaces; $i++) { my $id = &CreateValidSGMLID ($ifaces[$i]); $text .= " $ifaces[$i]"; if ($i < $#ifaces - 1) { $text .= ', '; } elsif ($i < $#ifaces) { $text .= ' and '; } else { $text .= '.'; } } $text .= < EOF } return $text; } ############################################################################# # Function : GetImplementations # Description : Returns the DocBook output describing the implementations # of an interface. It uses the global %Interfaces hash. # Arguments : $object - the GtkObject subclass. ############################################################################# sub GetImplementations { my ($object) = @_; my @impls = (); my $text = ""; my $i; foreach my $key (keys %Interfaces) { if ($Interfaces{$key} =~ /\b$object\b/) { push (@impls, $key); } } if ($#impls >= 0) { @impls = sort @impls; $text = < $object is implemented by EOF for ($i = 0; $i <= $#impls; $i++) { my $id = &CreateValidSGMLID ($impls[$i]); $text .= " $impls[$i]"; if ($i < $#impls - 1) { $text .= ', '; } elsif ($i < $#impls) { $text .= ' and '; } else { $text .= '.'; } } $text .= < EOF } return $text; } ############################################################################# # Function : GetPrerequisites # Description : Returns the DocBook output describing the prerequisites # of an interface. It uses the global %Prerequisites hash. # Arguments : $iface - the interface. ############################################################################# sub GetPrerequisites { my ($iface) = @_; my $text = ""; my $i; if (exists($Prerequisites{$iface})) { $text = < $iface requires EOF my @prereqs = split(' ', $Prerequisites{$iface}); for ($i = 0; $i <= $#prereqs; $i++) { my $id = &CreateValidSGMLID ($prereqs[$i]); $text .= " $prereqs[$i]"; if ($i < $#prereqs - 1) { $text .= ', '; } elsif ($i < $#prereqs) { $text .= ' and '; } else { $text .= '.'; } } $text .= < EOF } return $text; } ############################################################################# # Function : GetDerived # Description : Returns the DocBook output describing the derived interfaces # of an interface. It uses the global %Prerequisites hash. # Arguments : $iface - the interface. ############################################################################# sub GetDerived { my ($iface) = @_; my $text = ""; my $i; my @derived = (); foreach my $key (keys %Prerequisites) { if ($Prerequisites{$key} =~ /\b$iface\b/) { push (@derived, $key); } } if ($#derived >= 0) { @derived = sort @derived; $text = < $iface is required by EOF for ($i = 0; $i <= $#derived; $i++) { my $id = &CreateValidSGMLID ($derived[$i]); $text .= " $derived[$i]"; if ($i < $#derived - 1) { $text .= ', '; } elsif ($i < $#derived) { $text .= ' and '; } else { $text .= '.'; } } $text .= < EOF } return $text; } ############################################################################# # Function : GetSignals # Description : Returns the synopsis and detailed description DocBook output # for the signal handlers of a given GtkObject subclass. # Arguments : $object - the GtkObject subclass, e.g. 'GtkButton'. ############################################################################# sub GetSignals { my ($object) = @_; my $synop = ""; my $desc = ""; my $i; for ($i = 0; $i <= $#SignalObjects; $i++) { if ($SignalObjects[$i] eq $object) { @TRACE@("Found signal: $SignalNames[$i]\n"); my $name = $SignalNames[$i]; my $symbol = "${object}::${name}"; my $id = &CreateValidSGMLID ("$object-$name"); $desc .= "The <literal>“$name”</literal> signal\n"; $desc .= MakeIndexterms($symbol, $id); $desc .= "\n"; $desc .= OutputSymbolExtraLinks($symbol); $desc .= ""; $SignalReturns[$i] =~ m/\s*(const\s+)?(\w+)\s*(\**)/; my $type_modifier = defined($1) ? $1 : ""; my $type = $2; my $pointer = $3; my $xref = &MakeXRef ($type, &tagify($type, "returnvalue")); my $ret_type_output = "$type_modifier$xref$pointer"; my $callback_name = "user_function"; $desc .= "${ret_type_output}\n${callback_name} ("; my $indentation = ' ' x (length($callback_name) + 2); my $pad = $indentation; my $sourceparams = $SourceSymbolParams{$symbol}; my @params = split ("\n", $SignalPrototypes[$i]); my $j; my $l; my $type_len = length("gpointer"); my $name_len = length("user_data"); # do two passes, the first one is to calculate padding for ($l = 0; $l < 2; $l++) { for ($j = 0; $j <= $#params; $j++) { my $param_name; # allow alphanumerics, '_', '[' & ']' in param names if ($params[$j] =~ m/^\s*(\w+)\s*(\**)\s*([\w\[\]]+)\s*$/) { $type = $1; $pointer = $2; if (defined($sourceparams)) { $param_name = $$sourceparams[$PARAM_FIELD_COUNT * $j]; } else { $param_name = $3; } if (!defined($param_name)) { $param_name = "arg$j"; } if ($l == 0) { if (length($type) + length($pointer) > $type_len) { $type_len = length($type) + length($pointer); } if (length($param_name) > $name_len) { $name_len = length($param_name); } } else { $xref = &MakeXRef ($type, &tagify($type, "type")); $pad = ' ' x ($type_len - length($type) - length($pointer)); $desc .= "$xref$pad $pointer${param_name},\n"; $desc .= $indentation; } } else { &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol), "Can't parse arg: $params[$j]\nArgs:$SignalPrototypes[$i]"); } } } $xref = &MakeXRef ("gpointer", &tagify("gpointer", "type")); $pad = ' ' x ($type_len - length("gpointer")); $desc .= "$xref$pad user_data)"; $desc .= "\n"; my $flags = $SignalFlags[$i]; my $flags_string = ""; if (defined ($flags)) { if ($flags =~ m/f/) { $flags_string = "Run First"; } elsif ($flags =~ m/l/) { $flags_string = "Run Last"; } elsif ($flags =~ m/c/) { $flags_string = "Cleanup"; $flags_string = "Cleanup"; } if ($flags =~ m/r/) { if ($flags_string) { $flags_string .= " / "; } $flags_string = "No Recursion"; } if ($flags =~ m/d/) { if ($flags_string) { $flags_string .= " / "; } $flags_string = "Has Details"; } if ($flags =~ m/a/) { if ($flags_string) { $flags_string .= " / "; } $flags_string = "Action"; } if ($flags =~ m/h/) { if ($flags_string) { $flags_string .= " / "; } $flags_string = "No Hooks"; } } $synop .= "${ret_type_output}${name}${flags_string}\n"; my $parameters = &OutputParamDescriptions ("SIGNAL", $symbol); $AllSymbols{$symbol} = 1; if (defined ($SymbolDocs{$symbol})) { my $symbol_docs = &ConvertMarkDown($symbol, $SymbolDocs{$symbol}); $desc .= $symbol_docs; if (!IsEmptyDoc($SymbolDocs{$symbol})) { $AllDocumentedSymbols{$symbol} = 1; } } if (defined ($SymbolAnnotations{$symbol})) { my $param_desc = $SymbolAnnotations{$symbol}; my $param_annotations = ""; ($param_desc,$param_annotations) = &ExpandAnnotation($symbol, $param_desc); if ($param_annotations ne "") { $desc .= "\n$param_annotations"; } } $desc .= &MakeDeprecationNote($symbol); $desc .= $parameters; if ($flags_string) { $desc .= "Flags: $flags_string\n"; } $desc .= OutputSymbolTraits ($symbol); $desc .= ""; } } return ($synop, $desc); } ############################################################################# # Function : GetArgs # Description : Returns the synopsis and detailed description DocBook output # for the Args of a given GtkObject subclass. # Arguments : $object - the GtkObject subclass, e.g. 'GtkButton'. ############################################################################# sub GetArgs { my ($object) = @_; my $synop = ""; my $desc = ""; my $child_synop = ""; my $child_desc = ""; my $style_synop = ""; my $style_desc = ""; my $i; for ($i = 0; $i <= $#ArgObjects; $i++) { if ($ArgObjects[$i] eq $object) { @TRACE@("Found arg: $ArgNames[$i]\n"); my $name = $ArgNames[$i]; my $flags = $ArgFlags[$i]; my $flags_string = ""; my $kind = ""; my $id_sep = ""; if ($flags =~ m/c/) { $kind = "child property"; $id_sep = "c-"; } elsif ($flags =~ m/s/) { $kind = "style property"; $id_sep = "s-"; } else { $kind = "property"; } # Remember only one colon so we don't clash with signals. my $symbol = "${object}:${name}"; # use two dashes and ev. an extra separator here for the same reason. my $id = &CreateValidSGMLID ("$object--$id_sep$name"); my $type = $ArgTypes[$i]; my $type_output; my $range = $ArgRanges[$i]; my $range_output = CreateValidSGML ($range); my $default = $ArgDefaults[$i]; my $default_output = CreateValidSGML ($default); if ($type eq "GtkString") { $type = "char *"; } if ($type eq "GtkSignal") { $type = "GtkSignalFunc, gpointer"; $type_output = &MakeXRef ("GtkSignalFunc") . ", " . &MakeXRef ("gpointer"); } elsif ($type =~ m/^(\w+)\*$/) { $type_output = &MakeXRef ($1, &tagify($1, "type")) . " *"; } else { $type_output = &MakeXRef ($type, &tagify($type, "type")); } if ($flags =~ m/r/) { $flags_string = "Read"; } if ($flags =~ m/w/) { if ($flags_string) { $flags_string .= " / "; } $flags_string .= "Write"; } if ($flags =~ m/x/) { if ($flags_string) { $flags_string .= " / "; } $flags_string .= "Construct"; } if ($flags =~ m/X/) { if ($flags_string) { $flags_string .= " / "; } $flags_string .= "Construct Only"; } $AllSymbols{$symbol} = 1; my $blurb = ""; if (defined($SymbolDocs{$symbol}) && !IsEmptyDoc($SymbolDocs{$symbol})) { $blurb = &ConvertMarkDown($symbol, $SymbolDocs{$symbol}); @TRACE@(".. [$SymbolDocs{$symbol}][$blurb]\n"); $AllDocumentedSymbols{$symbol} = 1; } else { if ($ArgBlurbs[$i] ne "") { $blurb = "" . &CreateValidSGML ($ArgBlurbs[$i]) . ""; $AllDocumentedSymbols{$symbol} = 1; } else { # FIXME: print a warning? @TRACE@(".. no description\n"); } } my $pad1 = " " x (24 - length ($name)); my $arg_synop = "$type_output$name$flags_string\n"; my $arg_desc = "The <literal>“$name”</literal> $kind\n"; $arg_desc .= MakeIndexterms($symbol, $id); $arg_desc .= "\n"; $arg_desc .= OutputSymbolExtraLinks($symbol); $arg_desc .= " “$name”$pad1 $type_output\n"; $arg_desc .= $blurb; if (defined ($SymbolAnnotations{$symbol})) { my $param_desc = $SymbolAnnotations{$symbol}; my $param_annotations = ""; ($param_desc,$param_annotations) = &ExpandAnnotation($symbol, $param_desc); if ($param_annotations ne "") { $arg_desc .= "\n$param_annotations"; } } $arg_desc .= &MakeDeprecationNote($symbol); if ($flags_string) { $arg_desc .= "Flags: $flags_string\n"; } if ($range ne "") { $arg_desc .= "Allowed values: $range_output\n"; } if ($default ne "") { $arg_desc .= "Default value: $default_output\n"; } $arg_desc .= OutputSymbolTraits ($symbol); $arg_desc .= "\n"; if ($flags =~ m/c/) { $child_synop .= $arg_synop; $child_desc .= $arg_desc; } elsif ($flags =~ m/s/) { $style_synop .= $arg_synop; $style_desc .= $arg_desc; } else { $synop .= $arg_synop; $desc .= $arg_desc; } } } return ($synop, $child_synop, $style_synop, $desc, $child_desc, $style_desc); } ############################################################################# # Function : ReadSourceDocumentation # Description : This reads in the documentation embedded in comment blocks # in the source code (for Gnome). # # Parameter descriptions override any in the template files. # Function descriptions are placed before any description from # the template files. # # It recursively descends the source directory looking for .c # files and scans them looking for specially-formatted comment # blocks. # # Arguments : $source_dir - the directory to scan. #############m############################################################### sub ReadSourceDocumentation { my ($source_dir) = @_; my ($file, $dir, @suffix_list, $suffix); # prepend entries from @SOURCE_DIR for my $dir (@SOURCE_DIRS) { # Check if the filename is in the ignore list. if ($source_dir =~ m%^\Q$dir\E/(.*)$% and $IGNORE_FILES =~ m/(\s|^)\Q$1\E(\s|$)/) { @TRACE@("Skipping source directory: $source_dir"); return; } else { @TRACE@("No match for: ".($1 || $source_dir)); } } @TRACE@("Scanning source directory: $source_dir"); # This array holds any subdirectories found. my (@subdirs) = (); @suffix_list = split (/,/, $SOURCE_SUFFIXES); opendir (SRCDIR, $source_dir) || die "Can't open source directory $source_dir: $!"; foreach $file (readdir (SRCDIR)) { if ($file =~ /^\./) { next; } elsif (-d "$source_dir/$file") { push (@subdirs, $file); } elsif (@suffix_list) { foreach $suffix (@suffix_list) { if ($file =~ m/\.\Q${suffix}\E$/) { &ScanSourceFile ("$source_dir/$file"); } } } elsif ($file =~ m/\.[ch]$/) { &ScanSourceFile ("$source_dir/$file"); } } closedir (SRCDIR); # Now recursively scan the subdirectories. foreach $dir (@subdirs) { &ReadSourceDocumentation ("$source_dir/$dir"); } } ############################################################################# # Function : ScanSourceFile # Description : Scans one source file looking for specially-formatted comment # blocks. Later &MergeSourceDocumentation is used to merge any # documentation found with the documentation already read in # from the template files. # # Arguments : $file - the file to scan. ############################################################################# sub ScanSourceFile { my ($file) = @_; my $basename; # prepend entries from @SOURCE_DIR for my $dir (@SOURCE_DIRS) { # Check if the filename is in the ignore list. if ($file =~ m%^\Q$dir\E/(.*)$% and $IGNORE_FILES =~ m/(\s|^)\Q$1\E(\s|$)/) { @TRACE@("Skipping source file: $file"); return; } } if ($file =~ m/^.*[\/\\]([^\/\\]*)$/) { $basename = $1; } else { &LogWarning ($file, 1, "Can't find basename for this filename."); $basename = $file; } # Check if the basename is in the list of files to ignore. if ($IGNORE_FILES =~ m/(\s|^)\Q${basename}\E(\s|$)/) { @TRACE@("Skipping source file: $file"); return; } @TRACE@("Scanning source file: $file"); open (SRCFILE, $file) || die "Can't open $file: $!"; my $in_comment_block = 0; my $symbol; my $in_part = ""; my ($description, $return_desc); my ($since_desc, $stability_desc, $deprecated_desc); my $current_param; my @params; while () { # Look for the start of a comment block. if (!$in_comment_block) { if (m%^\s*/\*.*\*/%) { #one-line comment - not gtkdoc } elsif (m%^\s*/\*\*\s%) { @TRACE@("Found comment block start\n"); $in_comment_block = 1; # Reset all the symbol data. $symbol = ""; $in_part = ""; $description = ""; $return_desc = ""; $since_desc = ""; $deprecated_desc = ""; $stability_desc = ""; $current_param = -1; @params = (); } next; } # We're in a comment block. Check if we've found the end of it. if (m%^\s*\*+/%) { if (!$symbol) { # maybe its not even meant to be a gtk-doc comment? &LogWarning ($file, $., "Symbol name not found at the start of the comment block."); } else { # Add the return value description onto the end of the params. if ($return_desc) { # TODO(ensonic): check for duplicated Return docs # &LogWarning ($file, $., "Multiple Returns for $symbol."); push (@params, "Returns"); push (@params, $return_desc); } # Convert special characters $description = &ConvertSGMLChars ($symbol, $description); my $k; for ($k = 1; $k <= $#params; $k += $PARAM_FIELD_COUNT) { $params[$k] = &ConvertSGMLChars ($symbol, $params[$k]); } # Handle Section docs if ($symbol =~ m/SECTION:\s*(.*)/) { my $real_symbol=$1; my $key; if (scalar %KnownSymbols) { if ((! defined($KnownSymbols{"$TMPL_DIR/$real_symbol:Long_Description"})) || $KnownSymbols{"$TMPL_DIR/$real_symbol:Long_Description"} != 1) { &LogWarning ($file, $., "Section $real_symbol is not defined in the $MODULE-sections.txt file."); } } @TRACE@("SECTION DOCS found in source for : '$real_symbol'\n"); for ($k = 0; $k <= $#params; $k += $PARAM_FIELD_COUNT) { @TRACE@(" '".$params[$k]."'\n"); $params[$k] = "\L$params[$k]"; undef $key; if ($params[$k] eq "short_description") { $key = "$TMPL_DIR/$real_symbol:Short_Description"; } elsif ($params[$k] eq "see_also") { $key = "$TMPL_DIR/$real_symbol:See_Also"; } elsif ($params[$k] eq "title") { $key = "$TMPL_DIR/$real_symbol:Title"; } elsif ($params[$k] eq "stability") { $key = "$TMPL_DIR/$real_symbol:Stability_Level"; } elsif ($params[$k] eq "section_id") { $key = "$TMPL_DIR/$real_symbol:Section_Id"; } elsif ($params[$k] eq "include") { $key = "$TMPL_DIR/$real_symbol:Include"; } elsif ($params[$k] eq "image") { $key = "$TMPL_DIR/$real_symbol:Image"; } if (defined($key)) { $SourceSymbolDocs{$key}=$params[$k+1]; $SourceSymbolSourceFile{$key} = $file; $SourceSymbolSourceLine{$key} = $.; } } $SourceSymbolDocs{"$TMPL_DIR/$real_symbol:Long_Description"}=$description; $SourceSymbolSourceFile{"$TMPL_DIR/$real_symbol:Long_Description"} = $file; $SourceSymbolSourceLine{"$TMPL_DIR/$real_symbol:Long_Description"} = $.; #$SourceSymbolTypes{$symbol} = "SECTION"; } else { @TRACE@("SYMBOL DOCS found in source for : '$symbol' ",length($description), "\n"); $SourceSymbolDocs{$symbol} = $description; $SourceSymbolParams{$symbol} = [ @params ]; # FIXME $SourceSymbolTypes{$symbol} = "STRUCT,SIGNAL,ARG,FUNCTION,MACRO"; #if (defined $DeclarationTypes{$symbol}) { # $SourceSymbolTypes{$symbol} = $DeclarationTypes{$symbol} #} $SourceSymbolSourceFile{$symbol} = $file; $SourceSymbolSourceLine{$symbol} = $.; } if ($since_desc) { ($since_desc, my @extra_lines) = split ("\n", $since_desc); $since_desc =~ s/^\s+//; $since_desc =~ s/\s+$//; @TRACE@("Since($symbol) : [$since_desc]\n"); $Since{$symbol} = &ConvertSGMLChars ($symbol, $since_desc); if(scalar @extra_lines) { &LogWarning ($file, $., "multi-line since docs found"); } } if ($stability_desc) { $stability_desc = &ParseStabilityLevel($stability_desc, $file, $., "Stability level for $symbol"); $StabilityLevel{$symbol} = &ConvertSGMLChars ($symbol, $stability_desc); } if ($deprecated_desc) { if (!exists $Deprecated{$symbol}) { # don't warn for signals and properties #if ($symbol !~ m/::?(.*)/) { if (defined $DeclarationTypes{$symbol}) { &LogWarning ($file, $., "$symbol is deprecated in the inline comments, but no deprecation guards were found around the declaration.". " (See the --deprecated-guards option for gtkdoc-scan.)"); } } $Deprecated{$symbol} = &ConvertSGMLChars ($symbol, $deprecated_desc); } } $in_comment_block = 0; next; } # Get rid of ' * ' at start of every line in the comment block. s%^\s*\*\s?%%; # But make sure we don't get rid of the newline at the end. if (!$_) { $_ = "\n"; } @TRACE@("scanning :$_"); # If we haven't found the symbol name yet, look for it. if (!$symbol) { if (m%^\s*(SECTION:\s*\S+)%) { $symbol = $1; @TRACE@("SECTION DOCS found in source for : '$symbol'\n"); } elsif (m%^\s*([\w:-]*\w)\s*:?\s*(\([-A-Za-z0-9._() ]+?\)\s*)*$%) { $symbol = $1; my $annotation = $2; @TRACE@("SYMBOL DOCS found in source for : '$symbol'\n"); if (defined($annotation)) { chomp($annotation); if ($annotation ne "") { $SymbolAnnotations{$symbol} = $annotation; @TRACE@("remaining text for $symbol: '$annotation'\n"); } } } next; } if ($in_part eq "description") { # Get rid of 'Description:' s%^\s*Description:%%; } if (m%^\s*(returns|return\s+value):%i) { # we're in param section and have not seen the blank line if($in_part ne "") { $return_desc = $'; $in_part = "return"; next; } } elsif (m%^\s*since:%i) { # we're in param section and have not seen the blank line if($in_part ne "param") { $since_desc = $'; $in_part = "since"; next; } } elsif (m%^\s*deprecated:%i) { # we're in param section and have not seen the blank line if($in_part ne "param") { $deprecated_desc = $'; $in_part = "deprecated"; next; } } elsif (m%^\s*stability:%i) { $stability_desc = $'; $in_part = "stability"; next; } if ($in_part eq "description") { $description .= $_; next; } elsif ($in_part eq "return") { $return_desc .= $_; next; } elsif ($in_part eq "since") { $since_desc .= $_; next; } elsif ($in_part eq "stability") { $stability_desc .= $_; next; } elsif ($in_part eq "deprecated") { $deprecated_desc .= $_; next; } # We must be in the parameters. Check for the empty line below them. if (m%^\s*$%) { $in_part = "description"; next; } # Look for a parameter name. if (m%^\s*@(\S+)\s*:\s*%) { my $param_name = $1; my $param_desc = $'; @TRACE@("Found parameter: $param_name\n"); # Allow varargs variations if ($param_name =~ m/^\.\.\.$/) { $param_name = "..."; } @TRACE@("Found param for symbol $symbol : '$param_name'= '$_'"); push (@params, $param_name); push (@params, $param_desc); $current_param += $PARAM_FIELD_COUNT; $in_part = "param"; next; } elsif ($in_part eq "") { @TRACE@("continuation for $symbol annotation '$_'"); my $annotation = $_; $annotation =~ s/^\s+|\s+$//g ; $SymbolAnnotations{$symbol} .= $annotation; next; } # We must be in the middle of a parameter description, so add it on # to the last element in @params. if ($current_param == -1) { &LogWarning ($file, $., "Parsing comment block file : parameter expected, but got '$_'"); } else { $params[$#params] .= $_; } } close (SRCFILE); } ############################################################################# # Function : OutputMissingDocumentation # Description : Outputs report of documentation coverage to a file # # Arguments : none ############################################################################# sub OutputMissingDocumentation { my $old_undocumented_file = "$ROOT_DIR/$MODULE-undocumented.txt"; my $new_undocumented_file = "$ROOT_DIR/$MODULE-undocumented.new"; my $n_documented = 0; my $n_incomplete = 0; my $total = 0; my $symbol; my $percent; my $msg; my $buffer = ""; my $buffer_deprecated = ""; my $buffer_descriptions = ""; open(UNDOCUMENTED, ">$new_undocumented_file") || die "Can't create $new_undocumented_file"; foreach $symbol (sort (keys (%AllSymbols))) { # FIXME: should we print LogWarnings for undocumented stuff? # DEBUG #my $ssfile = &GetSymbolSourceFile($symbol); #my $ssline = &GetSymbolSourceLine($symbol); #my $location = "defined at " . (defined($ssfile)?$ssfile:"?") . ":" . (defined($ssline)?$ssline:"0") . "\n"; # DEBUG if ($symbol !~ /:(Title|Long_Description|Short_Description|See_Also|Stability_Level|Include|Section_Id|Image)/) { $total++; if (exists ($AllDocumentedSymbols{$symbol})) { $n_documented++; if (exists ($AllIncompleteSymbols{$symbol})) { $n_incomplete++; $buffer .= $symbol . " (" . $AllIncompleteSymbols{$symbol} . ")\n"; #$buffer .= "\t0: ".$location; } } elsif (exists $Deprecated{$symbol}) { if (exists ($AllIncompleteSymbols{$symbol})) { $n_incomplete++; $buffer_deprecated .= $symbol . " (" . $AllIncompleteSymbols{$symbol} . ")\n"; #$buffer .= "\t1a: ".$location; } else { $buffer_deprecated .= $symbol . "\n"; #$buffer .= "\t1b: ".$location; } } else { if (exists ($AllIncompleteSymbols{$symbol})) { $n_incomplete++; $buffer .= $symbol . " (" . $AllIncompleteSymbols{$symbol} . ")\n"; #$buffer .= "\t2a: ".$location; } else { $buffer .= $symbol . "\n"; #$buffer .= "\t2b: ".$location; } } } elsif ($symbol =~ /:(Long_Description|Short_Description)/) { $total++; if (((exists ($SymbolDocs{$symbol})) && (length ($SymbolDocs{$symbol}) > 0)) || ((exists ($AllDocumentedSymbols{$symbol})) && (length ($AllDocumentedSymbols{$symbol}) > 0))) { $n_documented++; } else { # cut off the leading namespace ($TMPL_DIR) $symbol =~ m/^.*\/(.*)$/; $buffer_descriptions .= $1 . "\n"; } } } $buffer .= "\n" . $buffer_deprecated . "\n" . $buffer_descriptions; if ($total == 0) { $percent = 100; } else { $percent = ($n_documented / $total) * 100.0; } printf UNDOCUMENTED "%.0f%% symbol docs coverage.\n", $percent; print UNDOCUMENTED "$n_documented symbols documented.\n"; print UNDOCUMENTED "$n_incomplete symbols incomplete.\n"; print UNDOCUMENTED ($total - $n_documented) . " not documented.\n\n\n"; print UNDOCUMENTED $buffer; close (UNDOCUMENTED); return &UpdateFileIfChanged ($old_undocumented_file, $new_undocumented_file, 0); printf "%.0f%% symbol docs coverage", $percent; print "($n_documented symbols documented, $n_incomplete symbols incomplete, " . ($total - $n_documented) . " not documented)\n"; print "See $MODULE-undocumented.txt for a list of missing docs.\nThe doc coverage percentage doesn't include intro sections.\n"; } ############################################################################# # Function : OutputUndeclaredSymbols # Description : Outputs symbols that are listed in the section file, but not # declaration is found in the sources # # Arguments : none ############################################################################# sub OutputUndeclaredSymbols { my $old_undeclared_file = "$ROOT_DIR/$MODULE-undeclared.txt"; my $new_undeclared_file = "$ROOT_DIR/$MODULE-undeclared.new"; open(UNDECLARED, ">$new_undeclared_file") || die "Can't create $new_undeclared_file"; if (%UndeclaredSymbols) { print UNDECLARED (join("\n", sort keys %UndeclaredSymbols)); print UNDECLARED "\n"; print "See $MODULE-undeclared.txt for the list of undeclared symbols.\n" } close(UNDECLARED); return &UpdateFileIfChanged ($old_undeclared_file, $new_undeclared_file, 0); } ############################################################################# # Function : OutputUnusedSymbols # Description : Outputs symbols that are documented in comments, but not # declared in the sources # # Arguments : none ############################################################################# sub OutputUnusedSymbols { my $num_unused = 0; my $old_unused_file = "$ROOT_DIR/$MODULE-unused.txt"; my $new_unused_file = "$ROOT_DIR/$MODULE-unused.new"; open (UNUSED, ">$new_unused_file") || die "Can't open $new_unused_file"; my ($symbol); foreach $symbol (sort keys (%Declarations)) { if (!defined ($DeclarationOutput{$symbol})) { print (UNUSED "$symbol\n"); $num_unused++; } } foreach $symbol (sort (keys (%AllUnusedSymbols))) { print (UNUSED "$symbol(" . $AllUnusedSymbols{$symbol} . ")\n"); $num_unused++; } close (UNUSED); if ($num_unused != 0) { &LogWarning ($old_unused_file, 1, "$num_unused unused declarations.". "They should be added to $MODULE-sections.txt in the appropriate place."); } return &UpdateFileIfChanged ($old_unused_file, $new_unused_file, 0); } ############################################################################# # Function : OutputAllSymbols # Description : Outputs list of all symbols to a file # # Arguments : none ############################################################################# sub OutputAllSymbols { my $n_documented = 0; my $total = 0; my $symbol; my $percent; my $msg; open (SYMBOLS, ">$ROOT_DIR/$MODULE-symbols.txt") || die "Can't create $ROOT_DIR/$MODULE-symbols.txt: $!"; foreach $symbol (sort (keys (%AllSymbols))) { print SYMBOLS $symbol . "\n"; } close (SYMBOLS); } ############################################################################# # Function : OutputSymbolsWithoutSince # Description : Outputs list of all symbols without a since tag to a file # # Arguments : none ############################################################################# sub OutputSymbolsWithoutSince { my $n_documented = 0; my $total = 0; my $symbol; my $percent; my $msg; open (SYMBOLS, ">$ROOT_DIR/$MODULE-nosince.txt") || die "Can't create $ROOT_DIR/$MODULE-nosince.txt: $!"; foreach $symbol (sort (keys (%SourceSymbolDocs))) { if (!defined $Since{$symbol}) { print SYMBOLS $symbol . "\n"; } } close (SYMBOLS); } ############################################################################# # Function : MergeSourceDocumentation # Description : This merges documentation read from a source file into the # documentation read in from a template file. # # Parameter descriptions override any in the template files. # Function descriptions are placed before any description from # the template files. # # Arguments : none ############################################################################# sub MergeSourceDocumentation { my $symbol; my @Symbols; if (scalar %SymbolDocs) { @Symbols=keys (%SymbolDocs); @TRACE@("num existing entries: ".(scalar @Symbols)."\n"); } else { # filter scanned declarations, with what we suppress from -sections.txt my %tmp = (); foreach $symbol (keys (%Declarations)) { if (defined($KnownSymbols{$symbol}) && $KnownSymbols{$symbol} == 1) { $tmp{$symbol}=1; } } # , add the rest from -sections.txt foreach $symbol (keys (%KnownSymbols)) { if ($KnownSymbols{$symbol} == 1) { $tmp{$symbol}=1; } } # and add whats found in the source foreach $symbol (keys (%SourceSymbolDocs)) { $tmp{$symbol}=1; } @Symbols = keys (%tmp); @TRACE@("num source entries: ".(scalar @Symbols)."\n"); } foreach $symbol (@Symbols) { $AllSymbols{$symbol} = 1; my $have_tmpl_docs = 0; ## see if the symbol is documented in template my $tmpl_doc = defined ($SymbolDocs{$symbol}) ? $SymbolDocs{$symbol} : ""; my $check_tmpl_doc =$tmpl_doc; # remove all xml-tags and whitespaces $check_tmpl_doc =~ s/<.*?>//g; $check_tmpl_doc =~ s/\s//g; # anything left ? if ($check_tmpl_doc ne "") { $have_tmpl_docs = 1; } else { # if the docs have just an empty para, don't merge that. $check_tmpl_doc = $tmpl_doc; $check_tmpl_doc =~ s/(\s|\n)//msg; if ($check_tmpl_doc eq "") { $tmpl_doc = ""; } } if (exists ($SourceSymbolDocs{$symbol})) { my $type = $DeclarationTypes {$symbol}; @TRACE@("merging [$symbol] from source\n"); my $item = "Parameter"; if (defined ($type)) { if ($type eq 'STRUCT') { $item = "Field"; } elsif ($type eq 'ENUM') { $item = "Value"; } elsif ($type eq 'UNION') { $item = "Field"; } } else { $type="SIGNAL"; } my $src_doc = $SourceSymbolDocs{$symbol}; # remove leading and training whitespaces $src_doc =~ s/^\s+//; $src_doc =~ s/\s+$//; # Don't output warnings for overridden titles as titles are # automatically generated in the -sections.txt file, and thus they # are often overridden. if ($have_tmpl_docs && $symbol !~ m/:Title$/) { # check if content is different if ($tmpl_doc ne $src_doc) { #print "[$tmpl_doc] [$src_doc]\n"; &LogWarning ($SourceSymbolSourceFile{$symbol}, $SourceSymbolSourceLine{$symbol}, "Documentation in template ".$SymbolSourceFile{$symbol}.":".$SymbolSourceLine{$symbol}." for $symbol being overridden by inline comments."); } } if ($src_doc ne "") { $AllDocumentedSymbols{$symbol} = 1; } # Do not add to nothing, it breaks missing docs checks. my $src_doc_para = ""; if ($src_doc ne "") { $src_doc_para = $src_doc; } if ($symbol =~ m/$TMPL_DIR\/.+:Long_Description/) { $SymbolDocs{$symbol} = "$src_doc_para$tmpl_doc"; } elsif ($symbol =~ m/$TMPL_DIR\/.+:.+/) { # For the title/summary/see also section docs we don't want to # add any tags. $SymbolDocs{$symbol} = "$src_doc" } else { $SymbolDocs{$symbol} = "$src_doc_para$tmpl_doc"; } # merge parameters if ($symbol =~ m/.*::.*/) { # For signals we prefer the param names from the source docs, # since the ones from the templates are likely to contain the # artificial argn names which are generated by gtkdoc-scangobj. $SymbolParams{$symbol} = $SourceSymbolParams{$symbol}; # FIXME: we need to check for empty docs here as well! } else { # The templates contain the definitive parameter names and order, # so we will not change that. We only override the actual text. my $tmpl_params = $SymbolParams{$symbol}; if (!defined ($tmpl_params)) { @TRACE@("No merge needed for $symbol\n"); $SymbolParams{$symbol} = $SourceSymbolParams{$symbol}; # FIXME: we still like to get the number of params and merge # 1) we would noticed that params have been removed/renamed # 2) we would catch undocumented params # params are not (yet) exported in -decl.txt so that we # could easily grab them :/ } else { my $params = $SourceSymbolParams{$symbol}; my $j; @TRACE@("Merge needed for $symbol, tmpl_params: ",$#$tmpl_params,", source_params: ",$#$params," \n"); for ($j = 0; $j <= $#$tmpl_params; $j += $PARAM_FIELD_COUNT) { my $tmpl_param_name = $$tmpl_params[$j]; # Try to find the param in the source comment documentation. my $found = 0; my $k; @TRACE@(" try merge param $tmpl_param_name\n"); for ($k = 0; $k <= $#$params; $k += $PARAM_FIELD_COUNT) { my $param_name = $$params[$k]; my $param_desc = $$params[$k + 1]; @TRACE@(" test param $param_name\n"); # We accept changes in case, since the Gnome source # docs contain a lot of these. if ("\L$param_name" eq "\L$tmpl_param_name") { $found = 1; # Override the description. $$tmpl_params[$j + 1] = $param_desc; # Set the name to "" to mark it as used. $$params[$k] = ""; last; } } # If it looks like the parameters are there, but not # in the right place, try to explain a bit better. if ((!$found) && ($src_doc =~ m/\@$tmpl_param_name:/)) { &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol), "Parameters for $symbol must start on the line immediately after the function or macro name."); } } # Now we output a warning if parameters have been described which # do not exist. for ($j = 0; $j <= $#$params; $j += $PARAM_FIELD_COUNT) { my $param_name = $$params[$j]; if ($param_name) { # the template builder cannot detect if a macro returns # a result or not if(($type eq "MACRO") && ($param_name eq "Returns")) { # FIXME: do we need to add it then to tmpl_params[] ? my $num=$#$tmpl_params; @TRACE@(" adding Returns: to macro docs for $symbol.\n"); $$tmpl_params[$num+1]="Returns"; $$tmpl_params[$num+2]=$$params[$j+1]; next; } &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol), "$item described in source code comment block but does not exist. $type: $symbol $item: $param_name."); } } } } } else { if ($have_tmpl_docs) { $AllDocumentedSymbols{$symbol} = 1; @TRACE@("merging [$symbol] from template\n"); } else { @TRACE@("[$symbol] undocumented\n"); } } # if this symbol is documented, check if docs are complete $check_tmpl_doc = defined ($SymbolDocs{$symbol}) ? $SymbolDocs{$symbol} : ""; # remove all xml-tags and whitespaces $check_tmpl_doc =~ s/<.*?>//g; $check_tmpl_doc =~ s/\s//g; if ($check_tmpl_doc ne "") { my $tmpl_params = $SymbolParams{$symbol}; if (defined ($tmpl_params)) { my $type = $DeclarationTypes {$symbol}; my $item = "Parameter"; if (defined ($type)) { if ($type eq 'STRUCT') { $item = "Field"; } elsif ($type eq 'ENUM') { $item = "Value"; } elsif ($type eq 'UNION') { $item = "Field"; } } else { $type="SIGNAL"; } @TRACE@("Check param docs for $symbol, tmpl_params: ",$#$tmpl_params," entries, type=$type\n"); if ($#$tmpl_params > 0) { my $j; for ($j = 0; $j <= $#$tmpl_params; $j += $PARAM_FIELD_COUNT) { # Output a warning if the parameter is empty and # remember for stats. my $tmpl_param_name = $$tmpl_params[$j]; my $tmpl_param_desc = $$tmpl_params[$j + 1]; if ($tmpl_param_name ne "void" && $tmpl_param_desc !~ m/\S/) { if (exists ($AllIncompleteSymbols{$symbol})) { $AllIncompleteSymbols{$symbol}.=", ".$tmpl_param_name; } else { $AllIncompleteSymbols{$symbol}=$tmpl_param_name; } &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol), "$item description for $symbol"."::"."$tmpl_param_name is missing in source code comment block."); } } } else { if ($#$tmpl_params == 0) { $AllIncompleteSymbols{$symbol}=""; &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol), "$item descriptions for $symbol are missing in source code comment block."); } # $#$tmpl_params==-1 means we don't know about parameters # this unfortunately does not tell if there should be some } } } } @TRACE@("num doc entries: ".(scalar %SymbolDocs)."\n"); } ############################################################################# # Function : IsEmptyDoc # Description : Check if a doc-string is empty. Its also regarded as empty if # it only consist of whitespace or e.g. FIXME. # Arguments : the doc-string ############################################################################# sub IsEmptyDoc { my ($doc) = @_; if ($doc =~ /^\s*$/) { return 1; } if ($doc =~ /^\s*\s*(FIXME)?\s*<\/para>\s*$/) { return 1; } return 0; } ############################################################################# # Function : ConvertMarkDown # Description : Converts mark down syntax to the respective docbook. # http://de.wikipedia.org/wiki/Markdown # Inspired by the design of ParseDown # http://parsedown.org/ # Copyright (c) 2013 Emanuil Rusev, erusev.com # Arguments : the symbol name, the doc-string ############################################################################# sub ConvertMarkDown { my ($symbol, $text) = @_; $text = &MarkDownParse ($text, $symbol); return $text } # SUPPORTED MARKDOWN # ================== # # Atx-style Headers # ----------------- # # # Header 1 # # ## Header 2 ## # # Setext-style Headers # -------------------- # # Header 1 # ======== # # Header 2 # -------- # # Ordered (unnested) Lists # ------------------------ # # 1. item 1 # # 1. item 2 with loooong # description # # 3. item 3 # # Note: we require a blank line above the list items # # TODO(ensonic): it would be nice to add id parameters to the refsect2 elements sub MarkDownParseBlocks { my ($linesref, $symbol, $context) = @_; my $line; my @md_blocks = (); my $md_block = { type => "" }; OUTER: foreach $line (@$linesref) { my $first_char = substr ($line, 0, 1); my $deindented_line; @TRACE@("in '".$md_block->{"type"}."' state, parsing '$line'"); if ($md_block->{"type"} eq "markup") { if (!$md_block->{"closed"}) { if (index ($line, $md_block->{"start"}) != -1) { $md_block->{"depth"}++; } if (index ($line, $md_block->{"end"}) != -1) { if ($md_block->{"depth"} > 0) { $md_block->{"depth"}--; } else { @TRACE@("closing tag '$line'"); $md_block->{"closed"} = 1; # TODO(ensonic): reparse inner text with MarkDownParseLines? } } $md_block->{"text"} .= "\n" . $line; next OUTER; } } $deindented_line = $line; $deindented_line =~ s/^\s+//; if ($md_block->{"type"} eq "heading") { # a heading is ended by any level less than or equal if ($md_block->{"level"} == 1) { if ($line =~ /^={4,}[ \t]*$/) { my $text = pop @{$md_block->{"lines"}}; $md_block->{"interrupted"} = 0; push @md_blocks, $md_block; $md_block = { type => "heading", text => $text, lines => [], level => 1 }; next OUTER; } elsif ($line =~ /^[#][ \t]+(.+?)[ \t]*[#]*[ \t]*(?:{#([^}]+)})?[ \t]*$/) { $md_block->{"interrupted"} = 0; push @md_blocks, $md_block; $md_block = { type => "heading", text => $1, id => $2, lines => [], level => 1 }; next OUTER; } else { # push lines into the block until the end is reached push @{$md_block->{"lines"}}, $line; next OUTER; } } else { if ($line =~ /^[=]{4,}[ \t]*$/) { my $text = pop @{$md_block->{"lines"}}; $md_block->{"interrupted"} = 0; push @md_blocks, $md_block; $md_block = { type => "heading", text => $text, lines => [], level => 1 }; next OUTER; } elsif ($line =~ /^[-]{4,}[ \t]*$/) { my $text = pop @{$md_block->{"lines"}}; $md_block->{"interrupted"} = 0; push @md_blocks, $md_block; $md_block = { type => "heading", text => $text, lines => [], level => 2 }; next OUTER; } elsif ($line =~ /^([#]{1,2})[ \t]+(.+?)[ \t]*[#]*[ \t]*(?:{#([^}]+)})?[ \t]*$/) { $md_block->{"interrupted"} = 0; push @md_blocks, $md_block; $md_block = { type => "heading", text => $2, id => $3, lines => [], level => length($1) }; next OUTER; } else { # push lines into the block until the end is reached push @{$md_block->{"lines"}}, $line; next OUTER; } } } elsif ($md_block->{"type"} eq "code") { if ($line =~ /^[ \t]*\]\|/) { push @md_blocks, $md_block; $md_block = { type => "paragraph", text => "", lines => [] }; } else { push @{$md_block->{"lines"}}, $line; } next OUTER; } if ($deindented_line eq "") { $md_block->{"interrupted"} = 1; next; } if ($md_block->{"type"} eq "quote") { if (!$md_block->{"interrupted"}) { $line =~ s/^[ ]*>[ ]?//; push @{$md_block->{"lines"}}, $line; next OUTER; } } elsif ($md_block->{"type"} eq "li") { my $marker = $md_block->{"marker"}; if ($line =~ /^([ ]{0,3})($marker)[ ](.*)/) { my $indentation = $1; if ($md_block->{"indentation"} ne $indentation) { push @{$md_block->{"lines"}}, $line; } else { my $lines = $3; my $ordered = $md_block->{"ordered"}; $lines =~ s/^[ ]{0,4}//; $md_block->{"last"} = 0; push @md_blocks, $md_block; $md_block = { type => "li", ordered => $ordered, indentation => $indentation, marker => $marker, first => 0, last => 1, lines => [ $lines ] }; } next OUTER; } if ($md_block->{"interrupted"}) { if ($first_char eq " ") { push @{$md_block->{"lines"}}, ""; $line =~ s/^[ ]{0,4}//; push @{$md_block->{"lines"}}, $line; $md_block->{"interrupted"} = 0; next OUTER; } } else { $line =~ s/^[ ]{0,4}//; push @{$md_block->{"lines"}}, $line; next OUTER; } } # indentation sensitive types @TRACE@("parsing '$line'"); if ($line =~ /^([#]{1,2})[ \t]+(.+?)[ \t]*[#]*[ \t]*(?:{#([^}]+)})?[ \t]*$/) { # atx heading (#) push @md_blocks, $md_block; $md_block = { type => "heading", text => $2, id => $3, lines => [], level => length($1) }; next OUTER; } elsif ($line =~ /^={4,}[ \t]*$/) { # setext heading (====) if ($md_block->{"type"} eq "paragraph" && $md_block->{"interrupted"}) { push @md_blocks, $md_block; $md_block->{"type"} = "heading"; $md_block->{"lines"} = []; $md_block->{"level"} = 1; } next OUTER; } elsif ($line =~ /^-{4,}[ \t]*$/) { # setext heading (-----) if ($md_block->{"type"} eq "paragraph" && $md_block->{"interrupted"}) { push @md_blocks, $md_block; $md_block->{"type"} = "heading"; $md_block->{"lines"} = []; $md_block->{"level"} = 2; } next OUTER; } elsif ($line =~ /^[ \t]*\|\[[ ]*(?:)?/) { # code $md_block->{"interrupted"} = 1; push @md_blocks, $md_block; $md_block = { type => "code", language => $1, lines => [] }; next OUTER; } # indentation insensitive types if ($line =~ /^[ ]* "markup", text => $deindented_line, start => "<", end => ">", closed => 0, depth => 0 }; } elsif ($line =~ /^[ ]*<\??(\w+)[^>]*([\/\?])?[ \t]*>/) { # markup, including my $tag = $1; my $is_self_closing = defined($2); # FIXME: why do we need to skip https? here, if we generalize this to all # uri schemes we get parsing errors if (! $MD_TEXT_LEVEL_ELEMENTS{$tag} && $tag !~ /^https?/ && !defined($md_block->{"start"})) { push @md_blocks, $md_block; if ($is_self_closing) { @TRACE@("self-closing docbook '$tag'"); $md_block = { type => "self-closing tag", text => $deindented_line }; $is_self_closing = 0; next OUTER; } @TRACE@("new markup '$tag'"); $md_block = { type => "markup", text => $deindented_line, start => "<" . $tag . ">", end => "", closed => 0, depth => 0 }; if ($deindented_line =~ /<\/$tag>/) { $md_block->{"closed"} = 1; } next OUTER; } else { @TRACE@("text level docbook '$tag'"); if (!defined($md_block->{"start"}) && $MD_TEXT_LEVEL_ELEMENTS{$tag}) { $md_block->{"start"} = "<" . $tag . ">"; $md_block->{"end"} = ""; $md_block->{"closed"} = 0; @TRACE@("scanning for end of '$tag'"); } if (defined($md_block->{"start"}) && $deindented_line =~ /$md_block->{"end"}/) { $md_block->{"closed"} = 1; @TRACE@("found end of '$tag'"); } } } elsif ($line =~ /^([ ]*)[*+-][ ](.*)/) { # li push @md_blocks, $md_block; my $lines = $2; my $indentation = $1; $lines =~ s/^[ ]{0,4}//; $md_block = { type => "li", ordered => 0, indentation => $indentation, marker => "[*+-]", first => 1, last => 1, lines => [ $lines ] }; next OUTER; } elsif ($line =~ /^[ ]*>[ ]?(.*)/) { push @md_blocks, $md_block; $md_block = { type => "quote", lines => [ $1 ] }; next OUTER; } # list item if ($line =~ /^([ ]{0,4})\d+[.][ ]+(.*)/) { push @md_blocks, $md_block; my $lines = $2; my $indentation = $1; $lines =~ s/^[ ]{0,4}//; $md_block = { type => "li", ordered => 1, indentation => $indentation, marker => "\\d+[.]", first => 1, last => 1, lines => [ $lines ] }; next; } # paragraph if ($md_block->{"type"} eq "paragraph") { if ($md_block->{"interrupted"}) { push @md_blocks, $md_block; $md_block = { type => "paragraph", interrupted => 0, text => $line }; } else { $md_block->{"text"} .= "\n" . $line; } } else { push @md_blocks, $md_block; $md_block = { type => "paragraph", text => $line }; } } push @md_blocks, $md_block; shift @md_blocks; return @md_blocks; } sub MarkDownParseSpanElementsInner { my ($text, $markersref) = @_; my $markup = ""; my %markers = map { $_ => 1 } @$markersref; while ($text ne "") { my $closest_marker = ""; my $closest_marker_index = 0; my $closest_marker_position = -1; my $text_marker = ""; my $i = 0; my $offset = 0; my @markers_rest; my $marker; my $use; while ( ($marker, $use) = each %markers ) { my $marker_position; if (!$use) { next; } $marker_position = index ($text, $marker); if ($marker_position < 0) { $markers{$marker} = 0; next; } if ($closest_marker eq "" || $marker_position < $closest_marker_position) { $closest_marker = $marker; $closest_marker_index = $i; $closest_marker_position = $marker_position; } } if ($closest_marker_position >= 0) { $text_marker = substr ($text, $closest_marker_position); } if ($text_marker eq "") { $markup .= $text; $text = ""; next; # last } $markup .= substr ($text, 0, $closest_marker_position); $text = substr ($text, $closest_marker_position); @markers_rest = map { $markers{$_} ? ($_ eq $closest_marker ? () : $_) : () } keys %markers; if ($closest_marker eq "![" || $closest_marker eq "[") { my %element; if (index ($text, "]") && $text =~ /\[((?:[^][]|(?R))*)\]/) { my $remaining_text; %element = ( "!" => (substr ($text, 0, 1) eq "!"), "a" => $1 ); $offset = length ($&); if ($element{"!"}) { $offset++; } $remaining_text = substr ($text, $offset); if ($remaining_text =~ /^\([ ]*([^)'"]*?)(?:[ ]+['"](.+?)['"])?[ ]*\)/) { $element{"»"} = $1; if (defined ($2)) { $element{"#"} = $2; } $offset += length ($&); } elsif ($remaining_text =~ /^\s*\[([^\]<]*?)\]/) { $element{"ref"} = $1; $offset += length ($&); } else { undef %element; } } if (%element) { if ($element{"»"}) { $element{"»"} =~ s/&/&/g; $element{"»"} =~ s/"; if (defined ($element{"a"})) { $markup .= "" . $element{"a"} . ""; } $markup .= ""; } elsif ($element{"ref"}) { $element{"a"} = &MarkDownParseSpanElementsInner ($element{"a"}, \@markers_rest); $markup .= ""; } else { $element{"a"} = &MarkDownParseSpanElementsInner ($element{"a"}, \@markers_rest); $markup .= ""; } } else { $markup .= $closest_marker; if ($closest_marker eq "![") { $offset = 2; } else { $offset = 1; } } } elsif ($closest_marker eq "<") { if ($text =~ /^<(https?:[\/]{2}[^\s]+?)>/i) { my $element_url = $1; $element_url =~ s/&/&/g; $element_url =~ s/" . $element_url . ""; $offset = length ($&); } elsif ($text =~ /^<([A-Za-z0-9._-]+?@[A-Za-z0-9._-]+?)>/) { $markup .= "" . $1 . ""; $offset = length ($&); } elsif ($text =~ /^<[^>]+?>/) { $markup .= $&; $offset = length ($&); } else { $markup .= "<"; $offset = 1; } } elsif ($closest_marker eq "\\") { my $special_char = substr ($text, 1, 1); if ($MD_ESCAPABLE_CHARS{$special_char} || $MD_GTK_ESCAPABLE_CHARS{$special_char}) { $markup .= $special_char; $offset = 2; } else { $markup .= "\\"; $offset = 1; } } elsif ($closest_marker eq "`") { if ($text =~ /^(`+)([^`]+?)\1(?!`)/) { my $element_text = $2; $markup .= "" . $element_text . ""; $offset = length ($&); } else { $markup .= "`"; $offset = 1; } } elsif ($closest_marker eq "@") { # Convert '@param()' # FIXME: we could make those also links ($symbol.$2), but that would be less # useful as the link target is a few lines up or down if ($text =~ /^(\A|[^\\])\@(\w+((\.|->)\w+)*)\s*\(\)/) { $markup .= $1 . "" . $2 . "()\n"; $offset = length ($&); } elsif ($text =~ /^(\A|[^\\])\@(\w+((\.|->)\w+)*)/) { # Convert '@param', but not '\@param'. $markup .= $1 . "" . $2 . "\n"; $offset = length ($&); } elsif ($text =~ /^\\\@/) { $markup .= "\@"; $offset = length ($&); } else { $markup .= "@"; $offset = 1; } } elsif ($closest_marker eq "#") { if ($text =~ /^(\A|[^\\])#([\w\-:\.]+[\w]+)\s*\(\)/) { # handle #Object.func() $markup .= $1 . &MakeXRef ($2, &tagify ($2 . "()", "function")); $offset = length ($&); } elsif ($text =~ /^(\A|[^\\])#([\w\-:\.]+[\w]+)/) { # Convert '#symbol', but not '\#symbol'. $markup .= $1 . &MakeHashXRef ($2, "type"); $offset = length ($&); } elsif ($text =~ /^\\#/) { $markup .= "#"; $offset = length ($&); } else { $markup .= "#"; $offset = 1; } } elsif ($closest_marker eq "%") { if ($text =~ /^(\A|[^\\])\%(-?\w+)/) { # Convert '%constant', but not '\%constant'. # Also allow negative numbers, e.g. %-1. $markup .= $1 . &MakeXRef ($2, &tagify ($2, "literal")); $offset = length ($&); } elsif ($text =~ /^\\%/) { $markup .= "\%"; $offset = length ($&); } else { $markup .= "%"; $offset = 1; } } if ($offset > 0) { $text = substr ($text, $offset); } } return $markup; } sub MarkDownParseSpanElements { my ($text) = @_; my @markers = ( "\\", "<", "![", "[", "`", "%", "#", "@" ); $text = &MarkDownParseSpanElementsInner ($text, \@markers); # Convert 'function()' or 'macro()'. # if there is abc_*_def() we don't want to make a link to _def() # FIXME: also handle abc(def(....)) : but that would need to be done recursively :/ $text =~ s/([^\*.\w])(\w+)\s*\(\)/$1.&MakeXRef($2, &tagify($2 . "()", "function"));/eg; return $text; } sub ReplaceEntities { my ($text, $symbol) = @_; my $warn = ""; my @entities = ( [ "<", "<" ], [ ">", ">" ], [ "*", "*" ], [ "#", "#" ], [ "%", "%"], [ ":", ":" ], [ """, "\"" ], [ "'", "'" ], [ " ", " " ], [ "&", "&" ] ); # Do this last, or the others get messed up. my $i; # Expand entities in even inside CDATA since # we changed the definition of |[ to add CDATA for ($i = 0; $i <= $#entities; $i++) { $text =~ s/$entities[$i][0]/$entities[$i][1]/g; } return $text; } sub MarkDownOutputDocBook { my ($blocksref, $symbol, $context) = @_; my $output = ""; my $block; my @blocks = @$blocksref; foreach $block (@blocks) { my $text; my $title; if ($block->{"type"} eq "paragraph") { $text = &MarkDownParseSpanElements ($block->{"text"}); if ($context eq "li" && $output eq "") { if ($block->{"interrupted"}) { $output .= "\n"."".$text.""."\n"; } else { $output .= "".$text.""; if ($#blocks > 0) { $output .= "\n"; } } } else { $output .= "".$text.""."\n"; } } elsif ($block->{"type"} eq "heading") { my $tag; $title = &MarkDownParseSpanElements ($block->{"text"}); if ($block->{"level"} == 1) { $tag = "refsect2"; } else { $tag = "refsect3"; } $text = &MarkDownParseLines ($block->{"lines"}, $symbol, "heading"); if (defined ($block->{"id"})) { $output .= "<" . $tag . " id=\"" . $block->{"id"} . "\">"; } else { $output .= "<" . $tag . ">"; } $output .= "" . $title . "" . $text . "\n"; } elsif ($block->{"type"} eq "li") { my $tag = "itemizedlist"; if ($block->{"first"}) { if ($block->{"ordered"}) { $tag = "orderedlist"; } $output .= "<".$tag.">\n"; } if ($block->{"interrupted"}) { push @{$block->{"lines"}}, ""; } $text = &MarkDownParseLines ($block->{"lines"}, $symbol, "li"); $output .= "".$text."\n"; if ($block->{"last"}) { if ($block->{"ordered"}) { $tag = "orderedlist"; } $output .= "\n"; } } elsif ($block->{"type"} eq "quote") { $text = &MarkDownParseLines ($block->{"lines"}, $symbol, "quote"); $output .= "
\n" . $text . "
\n"; } elsif ($block->{"type"} eq "code") { if ($block->{"language"}) { $output .= "{"language"} . "\">{"lines"}}) { $output .= &ReplaceEntities ($_, $symbol) . "\n"; } $output .= "]]>\n"; } elsif ($block->{"type"} eq "markup") { $text = &ExpandAbbreviations($symbol, $block->{"text"}); $output .= $text."\n"; } else { $output .= $block->{"text"}."\n"; } } return $output; } sub MarkDownParseLines { my ($linesref, $symbol, $context) = @_; my $output; my @lines = @$linesref; my @blocks; @blocks = &MarkDownParseBlocks (\@lines, $symbol, $context); $output = &MarkDownOutputDocBook (\@blocks, $symbol, $context); return $output; } sub MarkDownParse { my ($text, $symbol) = @_; my @lines; # take out some variability in line endings $text =~ s%\r\n%\n%g; $text =~ s%\r%\n%g; # split lines @lines = split("\n", $text); $text = MarkDownParseLines(\@lines, $symbol, ""); return $text; } ############################################################################# # 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; my $is_deprecated = 0; while () { if (!$declaration_type) { if (m/^<([^>]+)>/) { $declaration_type = $1; $declaration_name = ""; @TRACE@("Found declaration: $declaration_type\n"); $declaration = ""; } } else { if (m%^(.*)%) { $declaration_name = $1; } elsif (m%^%) { $is_deprecated = 1; } elsif (m%^%) { @TRACE@("Found end of declaration: $declaration_name\n"); # Check that the declaration has a name if ($declaration_name eq "") { &LogWarning ($file, $., "$declaration_type has no name.\n"); } # If the declaration is an empty typedef struct _XXX XXX # set the flag to indicate the struct has a typedef. if (($declaration_type eq 'STRUCT' || $declaration_type eq 'UNION') && $declaration =~ m/^\s*$/) { @TRACE@("Struct has typedef: $declaration_name\n"); $StructHasTypedef{$declaration_name} = 1; } # 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') { if ($is_deprecated) { $Deprecated{$declaration_name} = ""; } $Declarations{$declaration_name} = $declaration; $DeclarationTypes{$declaration_name} = $declaration_type; } elsif ($DeclarationTypes{$declaration_name} eq $declaration_type) { # If the existing declaration is empty, or is just a # forward declaration of a struct, override it. if ($declaration_type eq 'STRUCT' || $declaration_type eq 'UNION') { if ($Declarations{$declaration_name} =~ m/^\s*((struct|union)\s+\w+\s*;)?\s*$/) { if ($is_deprecated) { $Deprecated{$declaration_name} = ""; } $Declarations{$declaration_name} = $declaration; } elsif ($declaration =~ m/^\s*((struct|union)\s+\w+\s*;)?\s*$/) { # Ignore an empty or forward declaration. } else { &LogWarning ($file, $., "Structure $declaration_name has multiple definitions."); } } else { # set flag in %DeclarationConditional hash for # multiply defined macros/typedefs. $DeclarationConditional{$declaration_name} = 1; } } else { &LogWarning ($file, $., "$declaration_name has multiple definitions."); } } else { if ($is_deprecated) { $Deprecated{$declaration_name} = ""; } $Declarations{$declaration_name} = $declaration; $DeclarationTypes{$declaration_name} = $declaration_type; } $declaration_type = ""; $is_deprecated = 0; } 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_flags; my $signal_prototype; # Reset the signal info. @SignalObjects = (); @SignalNames = (); @SignalReturns = (); @SignalFlags = (); @SignalPrototypes = (); if (! -f $file) { return; } if (!open (INPUT, $file)) { warn "Can't open $file - skipping signals\n"; return; } while () { if (!$in_signal) { if (m/^/) { $in_signal = 1; $signal_object = ""; $signal_name = ""; $signal_returns = ""; $signal_prototype = ""; } } else { if (m/^(.*)<\/NAME>/) { $signal_name = $1; if ($signal_name =~ m/^(.*)::(.*)$/) { $signal_object = $1; ($signal_name = $2) =~ s/_/-/g; @TRACE@("Found signal: $signal_name\n"); } else { &LogWarning ($file, $., "Invalid signal name: $signal_name."); } } elsif (m/^(.*)<\/RETURNS>/) { $signal_returns = $1; } elsif (m/^(.*)<\/FLAGS>/) { $signal_flags = $1; } elsif (m%^%) { @TRACE@("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 (@SignalFlags, $signal_flags); 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-mktmpl (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) = @_; my $template = "$docsfile.sgml"; if (! -f $template) { @TRACE@("File doesn't exist: $template\n"); return 0; } # start with empty hashes, we merge the source comment for each file # afterwards %SymbolDocs = (); %SymbolTypes = (); %SymbolParams = (); my $current_type = ""; # Type of symbol being read. my $current_symbol = ""; # Name of symbol being read. my $symbol_doc = ""; # Description of symbol being read. my @params; # Parameter names and descriptions of current # function/macro/function typedef. my $current_param = -1; # Index of parameter currently being read. # Note that the param array contains pairs # of param name & description. my $in_unused_params = 0; # True if we are reading in the unused params. my $in_deprecated = 0; my $in_since = 0; my $in_stability = 0; open (DOCS, "$template") || die "Can't open $template: $!"; @TRACE@("reading template $template"); while () { if (m/^/) { my $type = $1; my $symbol = $2; if ($symbol eq "Title" || $symbol eq "Short_Description" || $symbol eq "Long_Description" || $symbol eq "See_Also" || $symbol eq "Stability_Level" || $symbol eq "Include" || $symbol eq "Image") { $symbol = $docsfile . ":" . $symbol; } @TRACE@("Found symbol: $symbol\n"); # Remember file and line for the symbol $SymbolSourceFile{$symbol} = $template; $SymbolSourceLine{$symbol} = $.; # Store previous symbol, but remove any trailing blank lines. if ($current_symbol ne "") { $symbol_doc =~ s/\s+$//; $SymbolTypes{$current_symbol} = $current_type; $SymbolDocs{$current_symbol} = $symbol_doc; # Check that the stability level is valid. if ($StabilityLevel{$current_symbol}) { $StabilityLevel{$current_symbol} = &ParseStabilityLevel($StabilityLevel{$current_symbol}, $template, $., "Stability level for $current_symbol"); } if ($current_param >= 0) { $SymbolParams{$current_symbol} = [ @params ]; } else { # Delete any existing params in case we are overriding a # previously read template. delete $SymbolParams{$current_symbol}; } } $current_type = $type; $current_symbol = $symbol; $current_param = -1; $in_unused_params = 0; $in_deprecated = 0; $in_since = 0; $in_stability = 0; $symbol_doc = ""; @params = (); } elsif (m/^/) { @TRACE@("Found unused parameters\n"); $in_unused_params = 1; next; } elsif ($in_unused_params && $skip_unused_params) { # When outputting the DocBook we skip unused parameters. @TRACE@("Skipping unused param: $_"); next; } else { # Check if param found. Need to handle "..." and "format...". if (s/^\@([\w\.]+):\040?//) { my $param_name = $1; my $param_desc = $_; # Allow variations of 'Returns' if ($param_name =~ m/^[Rr]eturns?$/) { $param_name = "Returns"; } # Allow varargs variations if ($param_name =~ m/^.*\.\.\.$/) { $param_name = "..."; } # strip trailing whitespaces and blank lines s/\s+\n$/\n/m; s/\n+$/\n/sm; @TRACE@("Found param for symbol $current_symbol : '$param_name'= '$_'"); if ($param_name eq "Deprecated") { $in_deprecated = 1; $Deprecated{$current_symbol} = $_; } elsif ($param_name eq "Since") { $in_since = 1; chomp; $Since{$current_symbol} = $_; } elsif ($param_name eq "Stability") { $in_stability = 1; $StabilityLevel{$current_symbol} = $_; } else { push (@params, $param_name); push (@params, $param_desc); $current_param += $PARAM_FIELD_COUNT; } } else { # strip trailing whitespaces and blank lines s/\s+\n$/\n/m; s/\n+$/\n/sm; if (!m/^\s+$/) { if ($in_deprecated) { $Deprecated{$current_symbol} .= $_; } elsif ($in_since) { &LogWarning ($template, $., "multi-line since docs found"); #$Since{$current_symbol} .= $_; } elsif ($in_stability) { $StabilityLevel{$current_symbol} .= $_; } elsif ($current_param >= 0) { $params[$current_param] .= $_; } else { $symbol_doc .= $_; } } } } } # Remember to finish the current symbol doccs. if ($current_symbol ne "") { $symbol_doc =~ s/\s+$//; $SymbolTypes{$current_symbol} = $current_type; $SymbolDocs{$current_symbol} = $symbol_doc; # Check that the stability level is valid. if ($StabilityLevel{$current_symbol}) { $StabilityLevel{$current_symbol} = &ParseStabilityLevel($StabilityLevel{$current_symbol}, $template, $., "Stability level for $current_symbol"); } if ($current_param >= 0) { $SymbolParams{$current_symbol} = [ @params ]; } else { # Delete any existing params in case we are overriding a # previously read template. delete $SymbolParams{$current_symbol}; } } close (DOCS); return 1; } ############################################################################# # 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 object hierarchy in the @ObjectLevels array, at the # same index. GtkObject, the root object, has a level of 1. # # This also generates tree_index.sgml as it goes along. # # 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; } # Only emit objects if they are supposed to be documented, or if # they have documented children. To implement this, we maintain a # stack of pending objects which will be emitted if a documented # child turns up. my @pending_objects = (); my @pending_levels = (); my $root; my @tree = (); while () { if (m/\S+/) { my $object = $&; my $level = (length($`)) / 2 + 1; my $xref = ""; if ($level == 1) { $root = $object; } while (($#pending_levels >= 0) && ($pending_levels[$#pending_levels] >= $level)) { my $pobject = pop(@pending_objects); my $plevel = pop(@pending_levels); } push (@pending_objects, $object); push (@pending_levels, $level); if (exists($KnownSymbols{$object})) { while ($#pending_levels >= 0) { $object = shift @pending_objects; $level = shift @pending_levels; $xref = &MakeXRef ($object); push (@tree, ' ' x ($level * 4) . "$xref"); push (@Objects, $object); push (@ObjectLevels, $level); $ObjectRoots{$object} = $root; } } #else { # LogWarning ($OBJECT_TREE_FILE, $., "unknown type $object"); #} } } close (INPUT); # FIXME: use $OUTPUT_FORMAT # my $old_tree_index = "$DB_OUTPUT_DIR/tree_index.$OUTPUT_FORMAT"; my $old_tree_index = "$DB_OUTPUT_DIR/tree_index.sgml"; my $new_tree_index = "$DB_OUTPUT_DIR/tree_index.new"; open (OUTPUT, ">$new_tree_index") || die "Can't create $new_tree_index: $!"; if ($OUTPUT_FORMAT eq "xml") { my $tree_header = $doctype_header; $tree_header =~ s/\n" . &AddTreeLineArt(\@tree) . "\n\n"); close (OUTPUT); &UpdateFileIfChanged ($old_tree_index, $new_tree_index, 0); &OutputObjectList; } ############################################################################# # Function : ReadInterfaces # Description : This reads in the $MODULE.interfaces file. # # Arguments : none ############################################################################# sub ReadInterfaces { %Interfaces = (); if (! -f $INTERFACES_FILE) { return; } if (!open (INPUT, $INTERFACES_FILE)) { warn "Can't open $INTERFACES_FILE - skipping interfaces\n"; return; } while () { chomp; my ($object, @ifaces) = split; if (exists($KnownSymbols{$object}) && $KnownSymbols{$object} == 1) { my @knownIfaces = (); # filter out private interfaces, but leave foreign interfaces foreach my $iface (@ifaces) { if (!exists($KnownSymbols{$iface}) || $KnownSymbols{$iface} == 1) { push (@knownIfaces, $iface); } } $Interfaces{$object} = join(' ', @knownIfaces); @TRACE@("Interfaces for $object: $Interfaces{$object}\n"); } else { @TRACE@("skipping interfaces for unknown symbol: $object\n"); } } close (INPUT); } ############################################################################# # Function : ReadPrerequisites # Description : This reads in the $MODULE.prerequisites file. # # Arguments : none ############################################################################# sub ReadPrerequisites { %Prerequisites = (); if (! -f $PREREQUISITES_FILE) { return; } if (!open (INPUT, $PREREQUISITES_FILE)) { warn "Can't open $PREREQUISITES_FILE - skipping prerequisites\n"; return; } while () { chomp; my ($iface, @prereqs) = split; if (exists($KnownSymbols{$iface}) && $KnownSymbols{$iface} == 1) { my @knownPrereqs = (); # filter out private prerequisites, but leave foreign prerequisites foreach my $prereq (@prereqs) { if (!exists($KnownSymbols{$prereq}) || $KnownSymbols{$prereq} == 1) { push (@knownPrereqs, $prereq); } } $Prerequisites{$iface} = join(' ', @knownPrereqs); } } 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, @ArgFlags, @ArgNicks and @ArgBlurbs 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; my $arg_nick; my $arg_blurb; my $arg_default; my $arg_range; # Reset the args info. @ArgObjects = (); @ArgNames = (); @ArgTypes = (); @ArgFlags = (); @ArgNicks = (); @ArgBlurbs = (); @ArgDefaults = (); @ArgRanges = (); if (! -f $file) { return; } if (!open (INPUT, $file)) { warn "Can't open $file - skipping args\n"; return; } while () { if (!$in_arg) { if (m/^/) { $in_arg = 1; $arg_object = ""; $arg_name = ""; $arg_type = ""; $arg_flags = ""; $arg_nick = ""; $arg_blurb = ""; $arg_default = ""; $arg_range = ""; } } else { if (m/^(.*)<\/NAME>/) { $arg_name = $1; if ($arg_name =~ m/^(.*)::(.*)$/) { $arg_object = $1; ($arg_name = $2) =~ s/_/-/g; @TRACE@("Found arg: $arg_name\n"); } else { &LogWarning ($file, $., "Invalid argument name: $arg_name"); } } elsif (m/^(.*)<\/TYPE>/) { $arg_type = $1; } elsif (m/^(.*)<\/RANGE>/) { $arg_range = $1; } elsif (m/^(.*)<\/FLAGS>/) { $arg_flags = $1; } elsif (m/^(.*)<\/NICK>/) { $arg_nick = $1; } elsif (m/^(.*)<\/BLURB>/) { $arg_blurb = $1; if ($arg_blurb eq "(null)") { $arg_blurb = ""; &LogWarning ($file, $., "Property ${arg_object}:${arg_name} has no documentation."); } } elsif (m/^(.*)<\/DEFAULT>/) { $arg_default = $1; } elsif (m%^%) { @TRACE@("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 (@ArgRanges, $arg_range); push (@ArgFlags, $arg_flags); push (@ArgNicks, $arg_nick); push (@ArgBlurbs, $arg_blurb); push (@ArgDefaults, $arg_default); $in_arg = 0; } } } close (INPUT); } ############################################################################# # Function : AddTreeLineArt # Description : Add unicode lineart to a pre-indented string array and returns # it as as multiline string. # Arguments : @tree - array of indented strings. ############################################################################# sub AddTreeLineArt { my @tree = @{$_[0]}; my $i; my $j; my $indent; # iterate bottom up over the tree for ($i = $#tree; $i >= 0; $i--) { # count leading spaces $tree[$i] =~ /^([^ 4) { if (substr($tree[$i],$indent-4,1) eq " ") { substr($tree[$i],$indent-4,4) = "--- "; } else { substr($tree[$i],$indent-4,4) = "+-- "; } # go lines up while space and insert | for ($j = $i - 1; ($j >= 0 && substr($tree[$j],$indent-4,1) eq ' '); $j--) { substr($tree[$j],$indent-4,1) = '|'; } } } my $res = join("\n", @tree); # unicode chars for: ╰── $res =~ s%---%╰──%g; # unicde chars for: ├── $res =~ s%\+--%├──%g; # unicode char for: │ $res =~ s%\|%%g; return $res; } ############################################################################# # Function : CheckIsObject # Description : Returns 1 if the given name is a GObject 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 GObject classes. # Arguments : $name - the name to check. ############################################################################# sub CheckIsObject { my ($name) = @_; my $root = $ObjectRoots{$name}; # Let GBoxed pass as an object here to get -struct appended to the id # and prevent conflicts with sections. return (defined($root) and $root ne 'GEnum' and $root ne 'GFlags'); } ############################################################################# # Function : MakeReturnField # Description : Pads a string to $RETURN_TYPE_FIELD_WIDTH. # Arguments : $str - the string to pad. ############################################################################# sub MakeReturnField { my ($str) = @_; return $str . (' ' x ($RETURN_TYPE_FIELD_WIDTH - length ($str))); } ############################################################################# # Function : GetSymbolSourceFile # Description : Get the filename where the symbol docs where taken from. # Arguments : $symbol - the symbol name ############################################################################# sub GetSymbolSourceFile { my ($symbol) = @_; if (defined($SourceSymbolSourceFile{$symbol})) { return $SourceSymbolSourceFile{$symbol}; } elsif (defined($SymbolSourceFile{$symbol})) { return $SymbolSourceFile{$symbol}; } else { return ""; } } ############################################################################# # Function : GetSymbolSourceLine # Description : Get the file line where the symbol docs where taken from. # Arguments : $symbol - the symbol name ############################################################################# sub GetSymbolSourceLine { my ($symbol) = @_; if (defined($SourceSymbolSourceLine{$symbol})) { return $SourceSymbolSourceLine{$symbol}; } elsif (defined($SymbolSourceLine{$symbol})) { return $SymbolSourceLine{$symbol}; } else { return 0; } }