#!@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 (-e $MAIN_SGML_FILE) {
open(INPUT, "<$MAIN_SGML_FILE") || die "Can't open $MAIN_SGML_FILE";
$doctype_header = "";
while () {
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>/) {
$filename = $1;
if (! defined $templates{$filename}) {
if (&ReadTemplateFile ("$TMPL_DIR/$filename", 1)) {
&MergeSourceDocumentation;
$templates{$filename}=$.;
}
} else {
&LogWarning ($file, $., "Double $filename 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>/) {
if ($in_section) {
$section_includes = $1;
} else {
if (defined $DEFAULT_INCLUDES) {
&LogWarning ($file, $., "Default 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 .= " \n";
} else {
$book_top.="\n";
$book_bottom .= " &$section_id;\n";
}
if (defined ($SourceSymbolDocs{"$TMPL_DIR/$filename:Include"})) {
if ($section_includes) {
&LogWarning ($file, $., "Section 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 = <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>/) {
$KnownSymbols{"$TMPL_DIR/$1:Long_Description"} = 1;
$KnownSymbols{"$TMPL_DIR/$1:Short_Description"} = 1;
next;
} elsif (m/^(.*)<\/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 .= "Since: $Since{$symbol}";
}
if (exists $StabilityLevel{$symbol}) {
my $stability = $StabilityLevel{$symbol};
$AnnotationsUsed{$stability} = 1;
$desc .= "Stability Level: $stability";
}
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 .= <code search
edit documentation
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 .= <code search
edit documentation
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 = "#define$symbol";
my $desc;
my @fields = ParseMacroDeclaration($declaration, \&CreateValidSGML);
my $title = $symbol . (@fields ? "()" : "");
$desc = "\n$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 .= "";
# We used to output this, but is messes up our UpdateFileIfChanged code
# since it changes every day (and it is only used in the man pages):
# ""
if ($OUTPUT_FORMAT eq "xml") {
print OUTPUT $doctype_header;
}
print OUTPUT <$title3\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 IndexIndex 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;
$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/</g;
# Allow ">" 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 "$1>";
}
}
# 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 . "" . $elem . ">";
}
#############################################################################
# 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 “$name” 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 “$name” $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 => "" . $tag . ">",
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"} = "" . $tag . ">";
$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/</g;
}
if ($element{"!"}) {
$markup .= "";
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/</g;
$markup .= "" . $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 . "" . $tag . ">\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 .= "".$tag.">\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%^$declaration_type>%) {
@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;
}
}