common: port more functions and fix one
[gtk-doc.git] / gtkdoc-fixxref.in
blobfaeb7f7d65457034bca1d4a6ac78af96a58c6234
1 #!@PERL@ -w
2 # -*- cperl -*-
4 # gtk-doc - GTK DocBook documentation generator.
5 # Copyright (C) 1998  Damon Chaplin
6 #               2007-2016  Stefan Sauer
8 # This program is free software; you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 2 of the License, or
11 # (at your option) any later version.
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 # GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with this program; if not, write to the Free Software
20 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
23 #############################################################################
24 # Script      : gtkdoc-fixxref
25 # Description : This fixes cross-references in the HTML documentation.
26 #############################################################################
28 use strict;
29 use bytes;
30 use Getopt::Long;
32 push @INC, '@PACKAGE_DATA_DIR@';
33 require "gtkdoc-common.pl";
35 # Options
37 # name of documentation module
38 my $MODULE;
39 my $MODULE_DIR;
40 my $HTML_DIR = "";
41 my @EXTRA_DIRS;
42 my $PRINT_VERSION;
43 my $PRINT_HELP;
44 my $SRC_LANG;
46 # This contains all the entities and their relative URLs.
47 my %Links;
49 # failing link targets we don't warn about even once
50 my %NoLinks = (
51     'char' => 1,
52     'double' => 1,
53     'float' => 1,
54     'int' => 1,
55     'long' => 1,
56     'main' => 1,
57     'signed' => 1,
58     'unsigned' => 1,
59     'va-list' => 1,
60     'void' => 1,
61     'GBoxed' => 1,
62     'GEnum' => 1,
63     'GFlags' => 1,
64     'GInterface' => 1
67 # Cache of dirs we already scanned for index files
68 my %DirCache;
70 Run() unless caller; # Run program unless loaded as a module
73 sub Run {
74     my %optctl = ('module' => \$MODULE,
75                   'module-dir' => \$MODULE_DIR,
76                   'html-dir' => \$HTML_DIR,
77                   'extra-dir' => \@EXTRA_DIRS,
78                   'version' => \$PRINT_VERSION,
79                   'help' => \$PRINT_HELP,
80                   'src-lang' => \$SRC_LANG);
82     GetOptions(\%optctl, "module=s", "module-dir=s", "html-dir:s", "extra-dir=s@",
83               "src-lang=s", "version", "help");
85     if ($PRINT_VERSION) {
86         print "@VERSION@\n";
87         exit 0;
88     }
90     if ($PRINT_HELP) {
91             print <<EOF;
92 gtkdoc-fixxref version @VERSION@ - fix cross references in html files
94 --module=MODULE_NAME    Name of the doc module being parsed
95 --module-dir=MODULE_DIR The directory which contains the generated HTML
96 --html-dir=HTML_DIR     The directory where gtk-doc generated documentation is
97                         installed
98 --extra-dir=EXTRA_DIR   Directories to recursively scan for indices (index.sgml)
99                         in addition to HTML_DIR
100                         May be used more than once for multiple directories
101 --src-lang=SRC_LANG     Programing language used for syntax highlighting. The
102                         available languages depend on the source source
103                         highlighter you use.
104 --version               Print the version of this program
105 --help                  Print this help
107         exit 0;
108     }
110     if (!$SRC_LANG) {
111         $SRC_LANG="c"
112     }
114     my $path_prefix="";
115     if ($HTML_DIR =~ m%(.*?)/share/gtk-doc/html%) {
116         $path_prefix=$1;
117         @TRACE@("Path prefix: $path_prefix");
118     }
120     if (!defined $MODULE_DIR) {
121         $MODULE_DIR="$HTML_DIR/$MODULE";
122     }
124     my $dir;
126     # We scan the directory containing GLib and any directories in GNOME2_PATH
127     # first, but these will be overriden by any later scans.
128     $dir = `@PKG_CONFIG@ --variable=prefix glib-2.0`;
129     $dir =~ s/\s+$//;
130     $dir = $dir . "/share/gtk-doc/html";
131     if (-d $dir) {
132         # Some predefined link targets to get links into type hierarchies as these
133         # have no targets. These are always absolute for now.
134         $Links{'GBoxed'} = "$dir/gobject/gobject-Boxed-Types.html";
135         $Links{'GEnum'} = "$dir/gobject/gobject-Enumeration-and-Flag-Types.html";
136         $Links{'GFlags'} = "$dir/gobject/gobject-Enumeration-and-Flag-Types.html";
137         $Links{'GInterface'} = "$dir/gobject/GTypeModule.html";
139         if ($dir ne $HTML_DIR) {
140             @TRACE@("Scanning GLib directory: $dir");
141             if ($dir !~ m%^\Q$path_prefix\E/%) {
142                 &ScanIndices ($dir, 1);
143             } else {
144                 &ScanIndices ($dir, 0);
145             }
146         }
147     }
149     if (defined ($ENV{"GNOME2_PATH"})) {
150         foreach $dir (split (/:/, $ENV{"GNOME2_PATH"})) {
151             $dir = $dir . "/share/gtk-doc/html";
152             if (-d $dir && $dir ne $HTML_DIR) {
153                 @TRACE@("Scanning GNOME2_PATH directory: $dir");
154                 if ($dir !~ m%^\Q$path_prefix\E/%) {
155                     &ScanIndices ($dir, 1);
156                 } else {
157                     &ScanIndices ($dir, 0);
158                 }
159             }
160             # ubuntu started to compress this as index.sgml.gz :/
161             # https://bugs.launchpad.net/ubuntu/+source/gtk-doc/+bug/77138
162         }
163     }
165     @TRACE@("Scanning HTML_DIR directory: $HTML_DIR");
166     &ScanIndices ($HTML_DIR, 0);
167     @TRACE@("Scanning HTML_DIR directory: $MODULE_DIR");
168     &ScanIndices ($MODULE_DIR, 0);
170     # check all extra dirs, but skip already scanned dirs or subdirs of those
171     foreach my $dir (@EXTRA_DIRS) {
172         my $vdir;
174         $dir =~ s#/$##;
175         @TRACE@("Scanning EXTRA_DIR directory: $dir");
177         # If the --extra-dir option is not relative and is not sharing the same
178         # prefix as the target directory of the docs, we need to use absolute
179         # directories for the links
180         if ($dir !~m/^\.\./ &&  $dir !~ m%\Q$path_prefix\E/%) {
181             &ScanIndices ($dir, 1);
182         } else {
183             &ScanIndices ($dir, 0);
184         }
185     }
187     &ReadSections ();
189     &FixCrossReferences ($MODULE_DIR);
193 sub ScanIndices {
194     my ($scan_dir, $use_absolute_links) = @_;
196     if (exists $DirCache{$scan_dir}) {
197         return;
198     }
199     $DirCache{$scan_dir} = 1;
201     @TRACE@("Scanning source directory: $scan_dir absolute: $use_absolute_links");
203     # This array holds any subdirectories found.
204     my (@subdirs) = ();
206     opendir (HTMLDIR, $scan_dir) || return;
207     my $file;
208     foreach $file (readdir (HTMLDIR)) {
209         if ($file eq '.' || $file eq '..') {
210             next;
211         } elsif (-d "$scan_dir/$file") {
212             push (@subdirs, $file);
213             next;
214         }
215         if ($file =~ m/\.devhelp2$/) {
216             # if devhelp-file is good don't read index.sgml
217             &ReadDevhelp ("$scan_dir/$file", $use_absolute_links);
218         }
219         elsif (($file eq "index.sgml.gz") && ! (-e "$scan_dir/index.sgml")) {
220             # debian/ubuntu started to compress this as index.sgml.gz :/
221             print <<EOF;
222 Please fix https://bugs.launchpad.net/ubuntu/+source/gtk-doc/+bug/77138 . For now run:
223 gunzip $scan_dir/$file
225         }
226         elsif (($file =~ m/(.*?)\.devhelp2.gz$/) && ! (-e "$scan_dir/$1.devhelp2")) {
227             # debian/ubuntu started to compress this as *devhelp2.gz :/
228             print <<EOF;
229 Please fix https://bugs.launchpad.net/ubuntu/+source/gtk-doc/+bug/1466210 . For now run:
230 gunzip $scan_dir/$file
232         }
233         # we could consider supporting: use IO::Zlib;
234     }
235     closedir (HTMLDIR);
237     # Now recursively scan the subdirectories.
238     my $dir;
239     foreach $dir (sort(@subdirs)) {
240         &ScanIndices ("$scan_dir/$dir", $use_absolute_links);
241     }
245 sub ReadDevhelp {
246     my ($file, $use_absolute_links) = @_;
248     # Determine the absolute directory, to be added to links in $file
249     # if we need to use an absolute link.
250     # $file will be something like /prefix/gnome/share/gtk-doc/html/gtk/$file
251     # We want the part up to 'html/.*' since the links in $file include
252     # the rest.
253     my $dir = "../";
254     if ($use_absolute_links) {
255         # For uninstalled index.sgml files we'd need to map the path to where it
256         # will be installed to
257         if ($file !~ /\.\//) {
258             $file =~ /(.*\/)(.*?)\/.*?\.devhelp2/;
259             $dir = "$1$2/";
260         }
261     } else {
262         if ($file =~ /(.*\/)(.*?)\/.*?\.devhelp2/) {
263             $dir .= "$2/";
264         } else {
265             $dir = "";
266         }
267     }
269     @TRACE@("Scanning index file=$file, absolute=$use_absolute_links, dir=$dir");
271     open (INDEXFILE, $file)
272         || die "Can't open $file: $!";
273     while (<INDEXFILE>) {
274         if (m/ link="([^#]*)#([^"]*)"/) {
275             @TRACE@("Found id: $2 href: $1#$2");
276             $Links{$2} = "$dir$1#$2";
277         }
278     }
279     close (INDEXFILE);
283 sub ReadSections {
284     if (!defined($MODULE)) {
285         return;
286     }
288     open (INPUT, "$MODULE-sections.txt")
289             || die "Can't open $MODULE-sections.txt: $!";
290     my $subsection = "";
291     while (<INPUT>) {
292         if (m/^#/) {
293             next;
295         } elsif (m/^<SECTION>/) {
296             $subsection = "";
297         } elsif (m/^<SUBSECTION\s*(.*)>/i) {
298             $subsection = $1;
299         } elsif (m/^<SUBSECTION>/) {
300             next;
301         } elsif (m/^<TITLE>(.*)<\/TITLE>/) {
302             next;
303         } elsif (m/^<FILE>(.*)<\/FILE>/) {
304             next;
305         } elsif (m/^<INCLUDE>(.*)<\/INCLUDE>/) {
306             next;
307         } elsif (m/^<\/SECTION>/) {
308             next;
309         } elsif (m/^(\S+)/) {
310             my $symbol=$1;
312             if ($subsection eq "Standard" || $subsection eq "Private") {
313                 $NoLinks{CreateValidSGMLID($symbol)} = 1;
314             }
315         }
316     }
317     close (INPUT);
321 sub FixCrossReferences {
322     my ($scan_dir) = @_;
324     opendir (HTMLDIR, $scan_dir)
325         || die "Can't open HTML directory $scan_dir: $!";
326     my $file;
327     foreach $file (readdir (HTMLDIR)) {
328         if ($file eq '.' || $file eq '..') {
329             next;
330         } elsif ($file =~ m/.html?$/) {
331             &FixHTMLFile ("$scan_dir/$file");
332         }
333     }
334     closedir (HTMLDIR);
338 sub FixHTMLFile {
339     my ($file) = @_;
340     @TRACE@("Fixing file: $file");
342     open (HTMLFILE, $file)
343         || die "Can't open $file: $!";
344     undef $/;
345     my $entire_file = <HTMLFILE>;
346     close (HTMLFILE);
348     if ("@HIGHLIGHT@" ne "") {
349         # FIXME: ideally we'd pass a clue about the example language to the highligher
350         # unfortunately the "language" attribute is not appearing in the html output
351         # we could patch the customization to have <code class="xxx"> inside of <pre>
352         if ("@HIGHLIGHT@" =~ m%/vim$%) {
353             $entire_file =~ s%<div class=\"(example-contents|informalexample)\"><pre class=\"programlisting\">(.*?)</pre></div>%&HighlightSourceVim($1,$2);%gse;
354         }
355         else {
356             $entire_file =~ s%<div class=\"(example-contents|informalexample)\"><pre class=\"programlisting\">(.*?)</pre></div>%&HighlightSource($1,$2);%gse;
357         }
358         # this just broke existing GTKDOCLINK tags
359         # &lt;GTKDOCLINK HREF=&quot;GST-PAD-SINK:CAPS&quot;&gt;GST_PAD_SINK&lt;/GTKDOCLINK&gt;
360         $entire_file =~ s%\&lt;GTKDOCLINK\s+HREF=\&quot;(.*?)\&quot;\&gt;(.*?)\&lt;/GTKDOCLINK\&gt;%\<GTKDOCLINK\ HREF=\"$1\"\>$2\</GTKDOCLINK\>%gs;
362         # from the highlighter we get all the functions marked up
363         # now we could turn them into GTKDOCLINK items
364         $entire_file =~ s%(<span class=\"function\">)(.*?)(</span>)%&MakeGtkDocLink($1,$2,$3);%gse;
365         # we could also try the first item in stuff marked up as 'normal'
366         $entire_file =~ s%(<span class=\"normal\">\s*)(.+?)((\s+.+?)?\s*</span>)%&MakeGtkDocLink($1,$2,$3);%gse;
367     }
369     my @lines = split(/\n/, $entire_file);
370     for (my $i=0; $i<$#lines; $i++) {
371         $lines[$i] =~ s%<GTKDOCLINK\s+HREF="([^"]*)"\s*>(.*?)</GTKDOCLINK\s*>% &MakeXRef($file,$i+1,$1,$2); %ge;
372         if ($lines[$i] =~ m/GTKDOCLINK/) {
373             @TRACE@("make xref failed for line: ".$lines[$i]);
374         }
375     }
376     $entire_file = join("\n",@lines);
378     open (NEWFILE, ">$file.new")
379         || die "Can't open $file: $!";
380     print NEWFILE $entire_file;
381     close (NEWFILE);
383     unlink ($file)
384         || die "Can't delete $file: $!";
385     rename ("$file.new", $file)
386         || die "Can't rename $file.new: $!";
389 sub MakeXRef {
390     my ($file, $line, $id, $text) = @_;
392     my $href = $Links{$id};
394     # this is a workaround for some inconsistency we have with CreateValidSGMLID
395     if (!$href && $id =~ m/:/) {
396         my $tid = $id;
397         $tid =~ s/:/--/g;
398         $href = $Links{$tid};
399     }
400     # poor mans plural support
401     if (!$href && $id =~ m/s$/) {
402         my $tid = $id;
403         $tid =~ s/s$//g;
404         $href = $Links{$tid};
405         if (!$href && defined $Links{"$tid-struct"}) {
406             $href = $Links{"$tid-struct"};
407         }
408     }
409     if (!$href && defined $Links{"$id-struct"}) {
410         $href = $Links{"$id-struct"};
411     }
413     if ($href) {
414         # if it is a link to same module, remove path to make it work
415         # uninstalled
416         if (defined($MODULE) && $href =~ m%^\.\./$MODULE/(.*)$%) {
417             $href=$1;
418             @TRACE@("  Fixing link to uninstalled doc: $id, $href, $text");
419         } else {
420             @TRACE@("  Fixing link: $id, $href, $text");
421         }
422         return "<a href=\"$href\">$text</a>";
423     } else {
424         my $warn = 1;
425         @TRACE@("  no link for: $id, $text");
427         # don't warn multiple times and also skip blacklisted (ctypes)
428         $warn = 0 if exists $NoLinks{$id};
429         # if it's a function, don't warn if it does not contain a "_"
430         # (transformed to "-")
431         # - gnome coding style would use '_'
432         # - will avoid wrong warnings for ansi c functions
433         $warn = 0 if ($text =~ m/ class=\"function\"/ && $id !~ m/-/);
434         # if it's a 'return value', don't warn (implicitly created link)
435         $warn = 0 if ($text =~ m/ class=\"returnvalue\"/);
436         # if it's a 'type', don't warn if it starts with lowercase
437         # - gnome coding style would use CamelCase
438         $warn = 0 if ($text =~ m/ class=\"type\"/ && ($id =~ m/^[a-z]/));
439         # don't warn for self links
440         $warn = 0 if ($text eq $id);
442         if ($warn == 1) {
443           &LogWarning ($file, $line, "no link for: '$id' -> ($text).");
444           $NoLinks{$id} = 1;
445         }
446         return $text;
447     }
451 sub MakeGtkDocLink {
452     my ($pre,$symbol,$post) = @_;
454     my $id=CreateValidSGMLID($symbol);
456     # these are implicitely created links in highlighed sources
457     # we don't want warnings for those if the links cannot be resolved.
458     $NoLinks{$id} = 1;
460     #return "<span class=\"$type\"><GTKDOCLINK HREF=\"$id\">$symbol</GTKDOCLINK></span>";
461     return "$pre<GTKDOCLINK HREF=\"$id\">$symbol</GTKDOCLINK>$post";
465 sub HighlightSource {
466     my ($type, $source) = @_;
468     # chop of leading and trailing empty lines
469     $source =~ s/^\s*\n+//gs;
470     $source =~ s/[\s\n]+$//gs;
471     # cut common indent
472     $source =~ m/^(\s*)/;
473     $source =~ s/^$1//gms;
474     # avoid double entity replacement
475     $source =~ s/&lt;/</g;
476     $source =~ s/&gt;/>/g;
477     $source =~ s/&amp;/&/g;
479     # write source to a temp file
480     # FIXME: use .c for now to hint the language to the highlighter
481     my $temp_source_file="$MODULE_DIR/_temp_src.$$.c";
482     open (NEWFILE, ">$temp_source_file") || die "Can't open $temp_source_file: $!";
483     print NEWFILE $source;
484     close (NEWFILE);
486     @TRACE@(" running @HIGHLIGHT@ @HIGHLIGHT_OPTIONS@$temp_source_file ");
488     # format source
489     my $highlighted_source=`@HIGHLIGHT@ @HIGHLIGHT_OPTIONS@$temp_source_file`;
490     if ("@HIGHLIGHT@" =~ m%/source-highlight$%) {
491         $highlighted_source =~ s%^<\!-- .*? -->%%gs;
492         $highlighted_source =~ s%<pre><tt>(.*?)</tt></pre>%$1%gs;
493     }
494     elsif ("@HIGHLIGHT@" =~ m%/highlight$%) {
495         # need to rewrite the stylesheet classes
496         $highlighted_source =~ s%<span class="gtkdoc com">%<span class="comment">%gs;
497         $highlighted_source =~ s%<span class="gtkdoc dir">%<span class="preproc">%gs;
498         $highlighted_source =~ s%<span class="gtkdoc kwd">%<span class="function">%gs;
499         $highlighted_source =~ s%<span class="gtkdoc kwa">%<span class="keyword">%gs;
500         $highlighted_source =~ s%<span class="gtkdoc line">%<span class="linenum">%gs;
501         $highlighted_source =~ s%<span class="gtkdoc num">%<span class="number">%gs;
502         $highlighted_source =~ s%<span class="gtkdoc str">%<span class="string">%gs;
503         $highlighted_source =~ s%<span class="gtkdoc sym">%<span class="symbol">%gs;
504         # maybe also do
505         # $highlighted_source =~ s%</span>(.+)<span%</span><span class="normal">$1</span><span%gs;
506     }
507     # remove temp file
508     unlink ($temp_source_file)
509         || die "Can't delete $temp_source_file: $!";
511     return &HighlightSourcePostprocess($type, $highlighted_source);
515 sub HighlightSourceVim {
516     my ($type, $source) = @_;
518     # chop of leading and trailing empty lines
519     $source =~ s/^\s*\n+//gs;
520     $source =~ s/[\s\n]+$//gs;
521     # cut common indent
522     $source =~ m/^(\s*)/;
523     $source =~ s/^$1//gms;
524     # avoid double entity replacement
525     $source =~ s/&lt;/</g;
526     $source =~ s/&gt;/>/g;
527     $source =~ s/&amp;/&/g;
529     # write source to a temp file
530     my $temp_source_file="$MODULE_DIR/_temp_src.$$.h";
531     open (NEWFILE, ">$temp_source_file") || die "Can't open $temp_source_file: $!";
532     print NEWFILE $source;
533     close (NEWFILE);
535     # format source
536     system "echo 'let html_number_lines=0|let html_use_css=1|let html_use_xhtml=1|e $temp_source_file|syn on|set syntax=$SRC_LANG|run! syntax/2html.vim|w! $temp_source_file.html|qa!' | @HIGHLIGHT@ -n -e -u NONE -T xterm >/dev/null";
538     my $highlighted_source;
539     {
540         local $/;
541         open (NEWFILE, "<$temp_source_file.html");
542         $highlighted_source = <NEWFILE>;
543         close (NEWFILE);
544     }
545     $highlighted_source =~ s#.*<pre\b[^>]*>\n##s;
546     $highlighted_source =~ s#</pre>.*##s;
548     # need to rewrite the stylesheet classes
549     # FIXME: Vim has somewhat different syntax groups
550     $highlighted_source =~ s%<span class="Comment">%<span class="comment">%gs;
551     $highlighted_source =~ s%<span class="PreProc">%<span class="preproc">%gs;
552     $highlighted_source =~ s%<span class="Statement">%<span class="keyword">%gs;
553     $highlighted_source =~ s%<span class="Identifier">%<span class="function">%gs;
554     $highlighted_source =~ s%<span class="Constant">%<span class="number">%gs;
555     $highlighted_source =~ s%<span class="Special">%<span class="symbol">%gs;
556     $highlighted_source =~ s%<span class="Type">%<span class="type">%gs;
558     # remove temp files
559     unlink ($temp_source_file)
560         || die "Can't delete $temp_source_file: $!";
561     unlink ("$temp_source_file.html")
562         || die "Can't delete $temp_source_file.html: $!";
564     return &HighlightSourcePostprocess($type, $highlighted_source);
568 sub HighlightSourcePostprocess {
569     my ($type, $highlighted_source) = @_;
571     # chop of leading and trailing empty lines
572     $highlighted_source =~ s/^[\s\n]+//gs;
573     $highlighted_source =~ s/[\s\n]+$//gs;
575     # turn common urls in comments into links
576     $highlighted_source =~ s%<span class="url">(.*?)</span>%<span class="url"><a href="$1">$1</a></span>%gs;
578     # we do own line-numbering
579     my $source_lines="";
580     my $line_count = () = $highlighted_source =~ /\n/gs;
581     for (my $i=1; $i < ($line_count+2); $i++) {
582         $source_lines.="$i\n";
583     }
584     $source_lines =~ s/\n\Z//;
586     return <<END_OF_HTML
587 <div class="$type">
588   <table class="listing_frame" border="0" cellpadding="0" cellspacing="0">
589     <tbody>
590       <tr>
591         <td class="listing_lines" align="right"><pre>$source_lines</pre></td>
592         <td class="listing_code"><pre class="programlisting">$highlighted_source</pre></td>
593       </tr>
594     </tbody>
595   </table>
596 </div>
597 END_OF_HTML