mirror of
git://git.proxmox.com/git/pve-docs.git
synced 2025-01-06 13:17:48 +03:00
7de8bba68d
To allow linking from to a chapter/section not included in a manpage allow the manpage link resolver to just return text in a case the link target text is in fact no manpage. If the link is a valid one in general will be checked in a lot of other places, so here we won't run into a regression where a wrong, non-existing, link does not get detected by the build system. The particular case I run into problems with this is when linking from the pveceph chapter to the pve-package-repos, to point to the (cheph) repos. pve-package-repos is no where included or itself a manpage, so this checks fails. Even if we say it would make sense to have it as a manpage, which was my initial solution, we run then into issues as we have a link to a outside reference, located in pve-bibliography, which then we probably do not want as manpage. So in the case where a valid link, which just has no manpage counter part, is passed to the respective link resolver I just return the link text as is. An alternative could be to get the link object and put it in parentheses after the link text, but I'm not to sure about this so let's for now go the easier route. Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com> Signed-off-by: Alwin Antreich <a.antreich@proxmox.com>
635 lines
14 KiB
Perl
635 lines
14 KiB
Perl
#!/usr/bin/perl
|
|
|
|
use strict;
|
|
use warnings;
|
|
use Getopt::Long;
|
|
use File::Path;
|
|
use File::Basename;
|
|
use IO::File;
|
|
use Cwd;
|
|
|
|
use JSON;
|
|
|
|
my $verbose;
|
|
my $keep_artifacts;
|
|
|
|
my $release = '@RELEASE@';
|
|
|
|
my $clicmd = shift or
|
|
die "no command specified\n";
|
|
|
|
my $data_str = "";
|
|
while (<main::DATA>) { $data_str .= $_; }
|
|
|
|
my $fileinfo = decode_json($data_str);
|
|
|
|
my $tmpprefix = '.asciidoc-pve-tmp'.$$.'_';
|
|
|
|
my $adoc_source_dir = "/usr/share/pve-doc-generator";
|
|
|
|
# inside pve-docs source dir?
|
|
if (-f "asciidoc-pve.in" && -f "pve-admin-guide.adoc") {
|
|
$adoc_source_dir = getcwd();
|
|
}
|
|
|
|
my $prepared_files = {};
|
|
|
|
my $man_target = 'man';
|
|
my $env_stack = [];
|
|
my $env_skip = 0;
|
|
|
|
my $online_help_links = {
|
|
'pve_service_daemons' => {
|
|
link => '/pve-docs/index.html#_service_daemons',
|
|
title => 'Service Daemons',
|
|
},
|
|
'pve_documentation_index' => {
|
|
link => '/pve-docs/index.html',
|
|
title => 'Proxmox VE Documentation Index',
|
|
},
|
|
'pve_admin_guide' => {
|
|
link => '/pve-docs/pve-admin-guide.html',
|
|
title => 'Proxmox VE Administration Guide',
|
|
},
|
|
};
|
|
|
|
sub debug {
|
|
my $msg = shift;
|
|
|
|
return if !$verbose;
|
|
|
|
print STDERR "asciidoc-pve: $msg\n";
|
|
}
|
|
|
|
sub push_environment {
|
|
my ($env, $skip) = @_;
|
|
|
|
$skip = 1 if $env_skip;
|
|
$skip = 0 if !defined($skip);
|
|
|
|
push @$env_stack, [$env, $skip];
|
|
|
|
$env_skip = $skip;
|
|
}
|
|
|
|
sub pop_environment {
|
|
my ($env) = @_;
|
|
|
|
my $last_stack_entry = pop @$env_stack;
|
|
die "unable to pop env '$env'" if !defined($last_stack_entry);
|
|
|
|
my ($last_env, $skip) = @$last_stack_entry;
|
|
die "environment missmatch (${last_env} != $env)\n" if $last_env ne $env;
|
|
|
|
if (!scalar(@$env_stack)) {
|
|
$env_skip = 0;
|
|
} else {
|
|
my (undef, $skip) = @{$env_stack->[-1]};
|
|
$env_skip = $skip;
|
|
}
|
|
}
|
|
|
|
my $files_for_cleanup = [];
|
|
|
|
sub cleanup {
|
|
|
|
return if $keep_artifacts;
|
|
|
|
foreach my $file (@$files_for_cleanup) {
|
|
unlink $file;
|
|
}
|
|
}
|
|
|
|
sub replace_wiki_xref {
|
|
my ($blockid, $text) = @_;
|
|
|
|
my $link = $fileinfo->{blockid_target}->{wiki}->{$blockid};
|
|
my $reftext = $fileinfo->{reftext}->{wiki}->{$blockid};
|
|
|
|
die "unable to resolve wiki link (xref:$blockid)\n"
|
|
if !defined($link);
|
|
|
|
$text = $reftext if !length($text);
|
|
|
|
die "xref: no text for wiki link '$blockid'\n" if !$text;
|
|
|
|
return "$link\[$text\]";
|
|
}
|
|
|
|
sub replace_default_xref {
|
|
my ($blockid, $text) = @_;
|
|
|
|
my $link = $fileinfo->{blockid_target}->{default}->{$blockid};
|
|
my $reftext = $fileinfo->{reftext}->{default}->{$blockid};
|
|
|
|
die "unable to resolve chapter link (xref:$blockid)\n"
|
|
if !defined($link);
|
|
|
|
$text = $reftext if !length($text);
|
|
|
|
die "xref: no text for chapter link '$blockid'\n" if !$text;
|
|
|
|
return "$link\[$text\]";
|
|
}
|
|
|
|
sub replace_man_xref {
|
|
my ($blockid, $text) = @_;
|
|
|
|
my $link = $fileinfo->{blockid_target}->{manvolnum}->{$blockid};
|
|
my $reftext = $fileinfo->{reftext}->{manvolnum}->{$blockid};
|
|
|
|
die "unable to resolve man page link (xref:$blockid)\n"
|
|
if !defined($link);
|
|
|
|
$text = $reftext if !length($text);
|
|
|
|
die "xref: no text for man page link '$blockid'\n" if !$text;
|
|
|
|
my $section = $fileinfo->{mansection}->{manvolnum}->{$link};
|
|
if (!defined($section)) {
|
|
warn "link '$blockid' target '$link' is not a manual page, ignoring\n";
|
|
return "$text";
|
|
}
|
|
|
|
|
|
if ($man_target eq 'html') {
|
|
my $target = $link;
|
|
$target =~ s/\.adoc//;
|
|
$target .= ".$section";
|
|
return "link:${target}.html#${blockid}\[$text\]";
|
|
} elsif ($man_target eq 'man') {
|
|
my $command = $link;
|
|
$command =~ s/\.adoc//;
|
|
return "\*${text}\* (man \*${command}\*($section))";
|
|
} else {
|
|
die "internal error"
|
|
}
|
|
}
|
|
|
|
sub replace_xref {
|
|
my ($env, $blockid, $text) = @_;
|
|
|
|
if ($env eq 'wiki') {
|
|
return replace_wiki_xref($blockid, $text);
|
|
} elsif ($env eq 'manvolnum') {
|
|
if (($man_target eq 'man') || ($man_target eq 'html')) {
|
|
return replace_man_xref($blockid, $text);
|
|
} elsif ($man_target eq 'wiki') {
|
|
return replace_wiki_xref($blockid, $text);
|
|
} else {
|
|
die "internal error"
|
|
}
|
|
} elsif ($env eq 'default') {
|
|
return replace_default_xref($blockid, $text);
|
|
} else {
|
|
die "internal error";
|
|
}
|
|
}
|
|
|
|
sub prepare_adoc_file {
|
|
my ($target_env, $filename, $attributes) = @_;
|
|
|
|
return $prepared_files->{$filename} if defined($prepared_files->{$filename});
|
|
|
|
debug("prepare $filename");
|
|
|
|
my $dirname = dirname($filename);
|
|
my $basename = basename($filename);
|
|
|
|
my $outfilename = "$dirname/${tmpprefix}$basename";
|
|
|
|
$prepared_files->{$filename} = $outfilename;
|
|
|
|
my $fh = IO::File->new("$filename", "r") or
|
|
die "unable to open file '$filename' - $!\n";
|
|
|
|
my $outfh = IO::File->new("$outfilename", "w") or
|
|
die "unable to open temporary file '$outfilename'\n";
|
|
|
|
push @$files_for_cleanup, $outfilename;
|
|
|
|
while (defined (my $line = <$fh>)) {
|
|
chomp $line;
|
|
if ($line =~ m/^if(n?)def::(\S+)\[(.*)\]\s*$/) {
|
|
my ($not, $env, $text) = ($1, $2, $3);
|
|
die "unsuported ifdef usage - implement me" if $text;
|
|
|
|
my $skip = !exists($attributes->{$env}) ? 1 : 0;
|
|
$skip = ($skip ? 0 : 1 ) if $not;
|
|
|
|
push_environment($env, $skip);
|
|
next;
|
|
} elsif ($line =~ m/^endif::(\S+)\[(.*)\]\s*$/) {
|
|
my ($env, $text) = ($1, $2);
|
|
die "unsuported ifdef usage - implement me" if $text;
|
|
pop_environment($env);
|
|
next;
|
|
}
|
|
|
|
next if $env_skip;
|
|
|
|
if ($line =~ m/^include::(\S+)(\[.*\]\s*)$/) {
|
|
my ($fn, $rest) = ($1, $2);
|
|
debug("include $fn");
|
|
my $new_fn = prepare_adoc_file($target_env, $fn, $attributes);
|
|
|
|
print $outfh "include::${new_fn}$rest\n";
|
|
next;
|
|
}
|
|
|
|
if ($line =~ m/xref:\S+?\[[^\]]*$/) {
|
|
die "possible xref spanning multiple lines in '$filename':\n(line $.): $line\n";
|
|
}
|
|
if ($line =~ m/<<((?!\>\>).)*$/) {
|
|
die "possible xref spanning multiple lines in '$filename':\n(line $.): $line\n";
|
|
}
|
|
# fix xrefs
|
|
$line =~ s/xref:([^\s\[\]]+)\[([^\]]*)\]/replace_xref(${target_env},$1,$2)/ge;
|
|
|
|
$line =~ s/<<([^\s,\[\]]+)(?:,(.*?))?>>/replace_xref(${target_env},$1,$2)/ge;
|
|
|
|
print $outfh $line . "\n";
|
|
}
|
|
|
|
return $outfilename;
|
|
}
|
|
|
|
sub compile_asciidoc {
|
|
my ($env) = @_;
|
|
|
|
my $outfile;
|
|
|
|
GetOptions ("outfile=s" => \$outfile,
|
|
"keep-artifacts" => \$keep_artifacts,
|
|
"verbose" => \$verbose) or
|
|
die("Error in command line arguments\n");
|
|
|
|
my $infile = shift(@ARGV) or
|
|
die "no input file specified\n";
|
|
|
|
scalar(@ARGV) == 0 or
|
|
die "too many arguments...\n";
|
|
|
|
my $outfilemap = $fileinfo->{outfile}->{$env}->{$infile} ||
|
|
die "no output file mapping for '$infile' ($env)";
|
|
|
|
if ($man_target eq 'html') {
|
|
$outfilemap .= '.html';
|
|
} elsif ($man_target eq 'wiki') {
|
|
$outfilemap .= '-plain.html';
|
|
}
|
|
|
|
if (defined($outfile)) {
|
|
die "wrong output file name '$outfile != $outfilemap' ($env)"
|
|
if $outfile ne $outfilemap;
|
|
} else {
|
|
$outfile = $outfilemap;
|
|
}
|
|
|
|
defined($fileinfo->{titles}->{$env}) ||
|
|
die "unknown environment '$env'";
|
|
|
|
my $title = $fileinfo->{titles}->{$env}->{$infile} or
|
|
die "unable to get title for '$infile'$env\n";
|
|
|
|
debug("compile $title");
|
|
|
|
my $leveloffset = 0;
|
|
|
|
my $doctype = $fileinfo->{doctype}->{$env}->{$infile};
|
|
|
|
die "unable to get document type for '$infile'\n"
|
|
if !defined($doctype);
|
|
|
|
$leveloffset = - $doctype;
|
|
|
|
my $date;
|
|
if (defined($ENV{SOURCE_DATE_EPOCH})) {
|
|
$date = `date -d "\@$ENV{SOURCE_DATE_EPOCH}"`;
|
|
} else {
|
|
$date = `date`;
|
|
}
|
|
chomp $date;
|
|
|
|
my $attributes = {
|
|
$env => undef,
|
|
leveloffset => $leveloffset,
|
|
revnumber => $release,
|
|
revdate => $date,
|
|
'footer-style' => 'revdate',
|
|
};
|
|
|
|
my $mansection = $fileinfo->{mansection}->{$env}->{$infile};
|
|
|
|
if ($env eq 'wiki') {
|
|
} elsif ($env eq 'manvolnum') {
|
|
die "undefined man section" if !defined($mansection);
|
|
$attributes->{manvolnum} = $mansection;
|
|
} elsif ($env eq 'default') {
|
|
die "$infile: wrong doctype\n" if $doctype != 0;
|
|
$attributes->{toc} = undef;
|
|
}
|
|
|
|
if (!defined($outfile)) {
|
|
$outfile = $infile;
|
|
$outfile =~ s/\.adoc$//;
|
|
if ($env eq 'manvolnum') {
|
|
if (($man_target eq 'html') || ($man_target eq 'wiki')) {
|
|
$outfile .= ".$mansection.html";
|
|
} else {
|
|
$outfile .= ".$mansection";
|
|
}
|
|
} else {
|
|
$outfile .= ".html";
|
|
}
|
|
}
|
|
|
|
if (($env eq 'manvolnum') && ($man_target eq 'man')) {
|
|
|
|
# asciidoc /etc/asciidoc/docbook-xsl/manpage.xsl skip REFERENCES
|
|
# section like footnotes, so we cannot use a2x.
|
|
# We use xmlto instead.
|
|
|
|
my $cmd = ['asciidoc', '-dmanpage', '-bdocbook',
|
|
'-f', "$adoc_source_dir/asciidoc/asciidoc-pve.conf",
|
|
'-a', 'docinfo1'];
|
|
|
|
foreach my $key (keys %$attributes) {
|
|
my $value = $attributes->{$key};
|
|
if (defined($value)) {
|
|
push @$cmd, '-a', "$key=$value";
|
|
} else {
|
|
push @$cmd, '-a', $key;
|
|
}
|
|
}
|
|
|
|
push @$cmd, '--verbose' if $verbose;
|
|
|
|
my $tmpxmlfile = "${outfile}.xml.tmp";
|
|
|
|
push @$cmd, '--out-file', $tmpxmlfile;
|
|
|
|
push @$files_for_cleanup, $tmpxmlfile;
|
|
|
|
my $new_infile = prepare_adoc_file($env, $infile, $attributes);
|
|
|
|
push @$cmd, $new_infile;
|
|
|
|
debug("run " . join(' ', @$cmd));
|
|
|
|
system(@$cmd) == 0 or
|
|
die "aciidoc error";
|
|
|
|
$cmd = ['xmlto', 'man', $tmpxmlfile];
|
|
|
|
push @$cmd, '-v' if $verbose;
|
|
|
|
debug("run " . join(' ', @$cmd));
|
|
|
|
system(@$cmd) == 0 or
|
|
die "xmlto error";
|
|
|
|
} else {
|
|
|
|
$attributes->{icons} = undef;
|
|
$attributes->{'data-uri'} = undef;
|
|
|
|
my $cmd = ['asciidoc',
|
|
'-f', "$adoc_source_dir/asciidoc/asciidoc-pve.conf",
|
|
];
|
|
|
|
if (($env eq 'wiki') ||
|
|
(($env eq 'manvolnum') && ($man_target eq 'wiki'))) {
|
|
|
|
push @$cmd, '-b', "$adoc_source_dir/asciidoc/mediawiki";
|
|
} else {
|
|
push @$cmd, '-b', "$adoc_source_dir/asciidoc/pve-html";
|
|
}
|
|
|
|
foreach my $key (keys %$attributes) {
|
|
my $value = $attributes->{$key};
|
|
if (defined($value)) {
|
|
push @$cmd, '-a', "$key=$value";
|
|
} else {
|
|
push @$cmd, '-a', $key;
|
|
}
|
|
}
|
|
|
|
push @$cmd, '--verbose' if $verbose;
|
|
|
|
push @$cmd, '--out-file', $outfile;
|
|
|
|
my $new_infile = prepare_adoc_file($env, $infile, $attributes);
|
|
|
|
push @$cmd, $new_infile;
|
|
|
|
debug("run " . join(' ', @$cmd));
|
|
|
|
system(@$cmd) == 0 or
|
|
die "aciidoc error";
|
|
}
|
|
}
|
|
|
|
sub get_links {
|
|
|
|
my $data = {};
|
|
|
|
foreach my $blockid (sort keys %{$fileinfo->{blockid_target}->{default}}) {
|
|
my $link = $fileinfo->{blockid_target}->{default}->{$blockid};
|
|
my $reftitle = $fileinfo->{reftitle}->{default}->{$blockid};
|
|
my $reftext = $fileinfo->{reftext}->{default}->{$blockid};
|
|
die "internal error" if $link !~ m/^link:/;
|
|
$link =~ s/^link://;
|
|
|
|
my $file = $fileinfo->{blockid}->{default}->{$blockid};
|
|
die "internal error - no filename" if ! defined($file);
|
|
my $title = $fileinfo->{titles}->{default}->{$file} ||
|
|
die "internal error - no title";
|
|
|
|
$data->{$blockid}->{title} = $title;
|
|
$data->{$blockid}->{link} = $link;
|
|
my $subtitle = $reftitle || $reftext;
|
|
$data->{$blockid}->{subtitle} = $subtitle
|
|
if $subtitle && ($title ne $subtitle);
|
|
}
|
|
|
|
return $data;
|
|
}
|
|
|
|
sub scan_extjs_file {
|
|
my ($filename, $res_data) = @_;
|
|
|
|
my $fh = IO::File->new($filename, "r") ||
|
|
die "unable to open '$filename' - $!\n";
|
|
|
|
debug("scan-extjs $filename");
|
|
|
|
while(defined(my $line = <$fh>)) {
|
|
if ($line =~ m/\s+onlineHelp:\s*[\'\"](.*?)[\'\"]/) {
|
|
my $blockid = $1;
|
|
my $link = $fileinfo->{blockid_target}->{default}->{$blockid};
|
|
die "undefined blockid '$blockid' ($filename, line $.)\n"
|
|
if !(defined($link) || defined($online_help_links->{$blockid}));
|
|
|
|
$res_data->{$blockid} = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($clicmd eq 'compile-wiki') {
|
|
|
|
eval { compile_asciidoc('wiki'); };
|
|
my $err = $@;
|
|
|
|
cleanup();
|
|
|
|
die $err if $err;
|
|
|
|
} elsif ($clicmd eq 'compile-chapter') {
|
|
|
|
eval { compile_asciidoc('default'); };
|
|
my $err = $@;
|
|
|
|
cleanup();
|
|
|
|
die $err if $err;
|
|
|
|
} elsif ($clicmd eq 'compile-man-html') {
|
|
|
|
$man_target = 'html';
|
|
|
|
eval { compile_asciidoc('manvolnum'); };
|
|
my $err = $@;
|
|
|
|
cleanup();
|
|
|
|
die $err if $err;
|
|
|
|
} elsif ($clicmd eq 'compile-man-wiki') {
|
|
|
|
$man_target = 'wiki';
|
|
|
|
eval { compile_asciidoc('manvolnum'); };
|
|
my $err = $@;
|
|
|
|
cleanup();
|
|
|
|
die $err if $err;
|
|
|
|
} elsif ($clicmd eq 'compile-man') {
|
|
|
|
eval { compile_asciidoc('manvolnum'); };
|
|
my $err = $@;
|
|
|
|
cleanup();
|
|
|
|
die $err if $err;
|
|
|
|
} elsif ($clicmd eq 'print-links') {
|
|
|
|
my $outfile;
|
|
|
|
GetOptions("outfile=s" => \$outfile,
|
|
"verbose" => \$verbose) or
|
|
die("Error in command line arguments\n");
|
|
|
|
scalar(@ARGV) == 0 or
|
|
die "too many arguments...\n";
|
|
|
|
my $data = get_links();
|
|
|
|
my $res = to_json($data, { pretty => 1, canonical => 1 } );
|
|
|
|
if (defined($outfile)) {
|
|
my $outfh = IO::File->new("$outfile", "w") or
|
|
die "unable to open temporary file '$outfile'\n";
|
|
|
|
print $outfh $res;
|
|
|
|
} else {
|
|
|
|
print $res;
|
|
}
|
|
|
|
} elsif ($clicmd eq 'scan-extjs') {
|
|
|
|
GetOptions("verbose" => \$verbose) or
|
|
die("Error in command line arguments\n");
|
|
|
|
my $link_hash = {};
|
|
my $scanned_files = {};
|
|
while (my $filename = shift) {
|
|
die "got strange file name '$filename'\n"
|
|
if $filename !~ m/\.js$/;
|
|
next if $scanned_files->{$filename};
|
|
|
|
scan_extjs_file($filename, $link_hash);
|
|
$scanned_files->{$filename} = 1;
|
|
}
|
|
|
|
my $data = get_links();
|
|
|
|
my $res_data = {};
|
|
|
|
foreach my $blockid (keys %$link_hash) {
|
|
$res_data->{$blockid} = $data->{$blockid} || $online_help_links->{$blockid} ||
|
|
die "internal error - no data for '$blockid'";
|
|
}
|
|
|
|
my $data_str = to_json($res_data, { pretty => 1, canonical => 1 });
|
|
chomp $data_str;
|
|
|
|
print "var pveOnlineHelpInfo = ${data_str};\n";
|
|
|
|
} elsif ($clicmd eq 'chapter-table') {
|
|
|
|
print '[width="100%",options="header"]' . "\n";
|
|
print "|====\n";
|
|
print "|Title|Link\n";
|
|
|
|
my $filelist = $fileinfo->{outfile}->{default};
|
|
foreach my $sourcefile (sort keys %$filelist) {
|
|
my $target = $filelist->{$sourcefile};
|
|
next if $target eq 'pve-admin-guide.html';
|
|
my $title = $fileinfo->{titles}->{default}->{$sourcefile} ||
|
|
die "not title for '$sourcefile'";
|
|
print "|$title|link:$target\[\]\n";
|
|
}
|
|
|
|
print "|====\n";
|
|
|
|
} elsif ($clicmd =~ m/^man([158])page-table$/) {
|
|
|
|
my $section = $1;
|
|
print '[width="100%",cols="5*d",options="header"]' . "\n";
|
|
print "|====\n";
|
|
print "|Name 3+|Title|Link\n";
|
|
|
|
my $filelist = $fileinfo->{outfile}->{manvolnum};
|
|
foreach my $manpage (sort keys %$filelist) {
|
|
next if $section ne $fileinfo->{mansection}->{manvolnum}->{$manpage};
|
|
my $mantitle = $fileinfo->{titles}->{manvolnum}->{$manpage} ||
|
|
die "not manual title for '$manpage'";
|
|
my $title = $fileinfo->{titles}->{default}->{$manpage} ||
|
|
die "not title for '$manpage'";
|
|
|
|
# hack - remove command name prefix from titles
|
|
$title =~ s/^[a-z]+\s*-\s*//;
|
|
|
|
my $target = $filelist->{$manpage};
|
|
print "|$mantitle 3+|$title|link:$target.html\[$target\]\n";
|
|
}
|
|
|
|
print "|====\n";
|
|
|
|
} else {
|
|
|
|
die "unknown command '$clicmd'\n";
|
|
|
|
}
|
|
|
|
|
|
exit 0;
|
|
|
|
__END__
|