1
0
mirror of https://github.com/pgbackrest/pgbackrest.git synced 2025-01-18 04:58:51 +02:00

Migrate command-line help generation to C.

Command-line help is now generated at build time so it does not need to be committed. This reduces churn on commits that add configuration and/or update the help.

Since churn is no longer an issue, help.auto.c is bzip2 compressed to save space in the binary.

The Perl config parser (Data.pm) has been moved to doc/lib since the Perl build path is no longer required.

Likewise doc/xml/reference.xml has been moved to src/build/help/help.xml since it is required at build time.
This commit is contained in:
David Steele 2021-09-08 18:16:06 -04:00
parent def7d513cd
commit f4e1babf6b
32 changed files with 1194 additions and 5394 deletions

View File

@ -570,7 +570,7 @@ To add an option, two files need be to be modified:
- `src/build/config/config.yaml`
- `doc/xml/reference.xml`
- `src/build/config/help.xml`
These files are discussed in the following sections along with how to verify the `help` command output.
@ -657,7 +657,7 @@ repo-test-type:
At compile time, the `config.auto.h` file will be generated to contain the constants used for options in the code. For the C enums, any dashes in the option name will be removed, camel-cased and prefixed with `cfgOpt`, e.g. `repo-path` becomes `cfgOptRepoPath`.
### reference.xml
### help.xml
All options must be documented or the system will error during the build. To add an option, find the command section identified by `command id="COMMAND"` section where `COMMAND` is the name of the command (e.g. `expire`) or, if the option is used by more than one command and the definition for the option is the same for all of the commands, the `operation-general title="General Options"` section.

View File

@ -1,195 +0,0 @@
####################################################################################################################################
# Auto-Generate C Files Required for Build
####################################################################################################################################
package pgBackRestBuild::Build;
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use English '-no_match_vars';
use Exporter qw(import);
our @EXPORT = qw();
use File::Basename qw(basename);
use Storable qw(dclone);
use pgBackRestDoc::Common::Log;
use pgBackRestDoc::Common::String;
use pgBackRestTest::Common::Storage;
use pgBackRestTest::Common::StoragePosix;
use pgBackRestBuild::Build::Common;
####################################################################################################################################
# Define generator used for auto generated warning messages
####################################################################################################################################
use constant GENERATOR => 'Build.pm';
####################################################################################################################################
# buildAll - execute all build functions and generate C source code
####################################################################################################################################
sub buildAll
{
my $strBuildPath = shift;
my $rhBuild = shift;
my $strFileExt = shift;
# Storage object
my $oStorage = new pgBackRestTest::Common::Storage(
$strBuildPath, new pgBackRestTest::Common::StoragePosix({bFileSync => false, bPathSync => false}));
# List of files actually built
my @stryBuilt;
# Build and output source code
#-------------------------------------------------------------------------------------------------------------------------------
foreach my $strBuild (sort(keys(%{$rhBuild})))
{
my $strPath = $rhBuild->{$strBuild}{&BLD_PATH};
foreach my $strFile (sort(keys(%{$rhBuild->{$strBuild}{&BLD_DATA}{&BLD_FILE}})))
{
my $rhFile = $rhBuild->{$strBuild}{&BLD_DATA}{&BLD_FILE}{$strFile};
my $rhFileConstant = $rhFile->{&BLD_CONSTANT_GROUP};
my $rhFileEnum = $rhFile->{&BLD_ENUM};
my $rhFileDeclare = $rhFile->{&BLD_DECLARE};
my $rhFileData = $rhFile->{&BLD_DATA};
my $rhSource;
# Build general banner
#-------------------------------------------------------------------------------------------------------------------------------
my $strBanner = bldBanner($rhFile->{&BLD_SUMMARY}, GENERATOR);
# Build header file
#-------------------------------------------------------------------------------------------------------------------------------
if (defined($rhFileEnum) || defined($rhFileConstant) || defined($rhFileDeclare))
{
my $strHeaderDefine = uc("${strPath}/${strFile}") . '_AUTO_H';
$strHeaderDefine =~ s/\//_/g;
my $strHeader =
$strBanner .
"#ifndef ${strHeaderDefine}\n" .
"#define ${strHeaderDefine}\n";
# Iterate constant groups
foreach my $strConstantGroup (sort(keys(%{$rhFileConstant})))
{
my $rhConstantGroup = $rhFileConstant->{$strConstantGroup};
$strHeader .= "\n" . bldBanner($rhConstantGroup->{&BLD_SUMMARY} . ' constants');
# Iterate constants
foreach my $strConstant (sort(keys(%{$rhConstantGroup->{&BLD_CONSTANT}})))
{
my $rhConstant = $rhConstantGroup->{&BLD_CONSTANT}{$strConstant};
$strHeader .=
"#define ${strConstant} " . (' ' x (69 - length($strConstant) - 10)) .
$rhConstant->{&BLD_CONSTANT_VALUE} . "\n";
}
}
# Iterate enum groups
foreach my $strEnum (sort(keys(%{$rhFileEnum})))
{
my $rhEnum = $rhFileEnum->{$strEnum};
$strHeader .=
"\n" . bldBanner($rhEnum->{&BLD_SUMMARY} . ' enum');
$strHeader .=
"typedef enum\n" .
"{\n";
my $iExpectedValue = 0;
# Iterate enums
foreach my $strEnumItem (@{$rhEnum->{&BLD_LIST}})
{
$strHeader .=
" ${strEnumItem}";
if (defined($rhEnum->{&BLD_VALUE}{$strEnumItem}) && $rhEnum->{&BLD_VALUE}{$strEnumItem} != $iExpectedValue)
{
$strHeader .= ' = ' . (' ' x (61 - length($strEnumItem))) . $rhEnum->{&BLD_VALUE}{$strEnumItem};
$iExpectedValue = $rhEnum->{&BLD_VALUE}{$strEnumItem};
}
$strHeader .= ",\n";
$iExpectedValue++;
}
$strHeader .=
"} " . $rhEnum->{&BLD_NAME} . ";\n";
}
foreach my $strDeclare (sort(keys(%{$rhFileDeclare})))
{
my $rhDeclare = $rhFileDeclare->{$strDeclare};
$strHeader .= "\n" . bldBanner($rhDeclare->{&BLD_SUMMARY});
$strHeader .= $rhDeclare->{&BLD_SOURCE};
}
$strHeader .=
"\n#endif";
$rhSource->{&BLD_HEADER} = $strHeader;
}
# Build C file
#-----------------------------------------------------------------------------------------------------------------------
if (defined($rhFileData))
{
my $strCode;
foreach my $strData (sort(keys(%{$rhFileData})))
{
my $rhData = $rhFileData->{$strData};
$strCode .= "\n" . bldBanner($rhData->{&BLD_SUMMARY});
$strCode .= $rhData->{&BLD_SOURCE};
}
$rhSource->{&BLD_C} = "${strBanner}${strCode}";
}
# Output files
#-----------------------------------------------------------------------------------------------------------------------
foreach my $strFileType (sort(keys(%{$rhSource})))
{
my $strExt = $strFileType;
if (defined($strFileExt))
{
$strExt = $strFileType eq BLD_C ? $strFileExt : "${strFileExt}h";
}
# Save the file if it has not changed
my $strBuilt = "${strPath}/${strFile}.auto.${strExt}";
my $bSave = true;
my $oFile = $oStorage->openRead($strBuilt, {bIgnoreMissing => true});
if (defined($oFile) && ${$oStorage->get($oFile)} eq (trim($rhSource->{$strFileType}) . "\n"))
{
$bSave = false;
}
if ($bSave)
{
$oStorage->put($strBuilt, trim($rhSource->{$strFileType}) . "\n");
push(@stryBuilt, basename($strBuildPath) . "/${strBuilt}");
}
}
}
}
# Return list of files built
return @stryBuilt;
}
push @EXPORT, qw(buildAll);
1;

View File

@ -1,144 +0,0 @@
####################################################################################################################################
# Build Constants and Functions
####################################################################################################################################
package pgBackRestBuild::Build::Common;
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use English '-no_match_vars';
use Cwd qw(abs_path);
use Exporter qw(import);
our @EXPORT = qw();
use Storable qw(dclone);
use pgBackRestDoc::Common::Log;
use pgBackRestDoc::Common::String;
####################################################################################################################################
# Constants
####################################################################################################################################
use constant BLD_PATH => 'path';
push @EXPORT, qw(BLD_PATH);
use constant BLD_FILE => 'file';
push @EXPORT, qw(BLD_FILE);
use constant BLD_C => 'c';
push @EXPORT, qw(BLD_C);
use constant BLD_EXT => 'ext';
push @EXPORT, qw(BLD_EXT);
use constant BLD_HEADER => 'h';
push @EXPORT, qw(BLD_HEADER);
use constant BLD_CONSTANT => 'constant';
push @EXPORT, qw(BLD_CONSTANT);
use constant BLD_CONSTANT_GROUP => 'constantGroup';
push @EXPORT, qw(BLD_CONSTANT_GROUP);
use constant BLD_CONSTANT_VALUE => 'constantValue';
push @EXPORT, qw(BLD_CONSTANT_VALUE);
use constant BLD_DATA => 'data';
push @EXPORT, qw(BLD_DATA);
use constant BLD_DECLARE => 'declare';
push @EXPORT, qw(BLD_DECLARE);
use constant BLD_ENUM => 'enum';
push @EXPORT, qw(BLD_ENUM);
use constant BLD_LIST => 'list';
push @EXPORT, qw(BLD_LIST);
use constant BLD_NAME => 'name';
push @EXPORT, qw(BLD_NAME);
use constant BLD_PATH => 'path';
push @EXPORT, qw(BLD_PATH);
use constant BLD_SOURCE => 'buildSource';
push @EXPORT, qw(BLD_SOURCE);
use constant BLD_SUMMARY => 'summary';
push @EXPORT, qw(BLD_SUMMARY);
use constant BLD_VALUE => 'value';
push @EXPORT, qw(BLD_VALUE);
####################################################################################################################################
# bldAutoWarning - warning not to modify automatically generated files directly
####################################################################################################################################
sub bldAutoWarning
{
my $strGenerator = shift;
return "Automatically generated by ${strGenerator} -- do not modify directly.";
}
push @EXPORT, qw(bldAutoWarning);
####################################################################################################################################
# bldBanner - build general banner
####################################################################################################################################
sub bldBanner
{
my $strContent = shift;
my $strGenerator = shift;
my $strBanner =
qw{/} . (qw{*} x 131) . "\n" .
trim($strContent) . "\n";
if (defined($strGenerator))
{
$strBanner .=
"\n" .
bldAutoWarning($strGenerator) . "\n";
}
$strBanner .=
(qw{*} x 131) . qw{/} . "\n";
return $strBanner;
}
push @EXPORT, qw(bldBanner);
####################################################################################################################################
# Generate an enum name from a prefix and - separated name
####################################################################################################################################
sub bldEnum
{
my $strPrefix = shift;
my $strName = shift;
my $bInitCapFirst = shift;
$bInitCapFirst = defined($bInitCapFirst) ? $bInitCapFirst : true;
my $bFirst = true;
my @stryName = split('\-', $strName);
$strName = undef;
foreach my $strPart (@stryName)
{
$strName .= ($bFirst && $bInitCapFirst) || !$bFirst ? ucfirst($strPart) : $strPart;
$bFirst = false;
}
return "${strPrefix}${strName}";
}
push @EXPORT, qw(bldEnum);
####################################################################################################################################
# Quote a list of strings
####################################################################################################################################
sub bldQuoteList
{
my $ryList = shift;
my @stryQuoteList;
foreach my $strItem (@{$ryList})
{
push(@stryQuoteList, "\"${strItem}\"");
}
return @stryQuoteList;
}
push @EXPORT, qw(bldQuoteList);
1;

View File

@ -1,490 +0,0 @@
####################################################################################################################################
# Auto-Generate Command and Option Help Pack
####################################################################################################################################
package pgBackRestBuild::Config::BuildHelp;
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use English '-no_match_vars';
use Cwd qw(abs_path);
use Exporter qw(import);
our @EXPORT = qw();
use File::Basename qw(dirname);
use pgBackRestDoc::Common::DocConfig;
use pgBackRestDoc::Common::DocRender;
use pgBackRestDoc::Common::Log;
use pgBackRestDoc::Common::String;
use pgBackRestDoc::ProjectInfo;
use pgBackRestBuild::Build::Common;
use pgBackRestBuild::Config::Data;
####################################################################################################################################
# Constants
####################################################################################################################################
use constant BLDLCL_FILE_DEFINE => 'help';
use constant BLDLCL_DATA_COMMAND => '01-command';
####################################################################################################################################
# Definitions for data to build
####################################################################################################################################
my $strSummary = 'Help Definition Pack';
my $rhBuild =
{
&BLD_FILE =>
{
&BLDLCL_FILE_DEFINE =>
{
&BLD_SUMMARY => $strSummary,
&BLD_DATA =>
{
&BLDLCL_DATA_COMMAND =>
{
&BLD_SUMMARY => 'Command help',
},
},
},
},
};
####################################################################################################################################
# Format pack tag
####################################################################################################################################
use constant PCK_TYPE_ARRAY => 1;
use constant PCK_TYPE_BOOL => 2;
use constant PCK_TYPE_OBJ => 5;
use constant PCK_TYPE_STR => 7;
# Pack an unsigned 64-bit integer to base-128 varint encoding and output to hex. This is a simplified version of
# pckWriteUInt64Internal() so see that function for more information.
sub packIntFormat
{
my $iValue = shift;
my $strResult = '';
while ($iValue >= 0x80)
{
# Encode the lower order 7 bits, adding the continuation bit to indicate there is more data
$strResult .= sprintf(" 0x%02X,", ($iValue & 0x7f) | 0x80);
# Shift the value to remove bits that have been encoded
$iValue >>= 7;
}
return $strResult . sprintf(" 0x%02X,", $iValue);
}
# Write pack field tag and data. This is a cut down version of pckWriteTag() so see that function for more information.
sub packTagFormat
{
my $strName = shift;
my $iType = shift;
my $iDelta = shift;
my $xData = shift;
my $iIndent = shift;
my $strIndent = ' ' x $iIndent;
# Pack delta bits and determine value for various pack. See pckWriteTag() for more detailed information.
my $iValue = undef;
my $iBits = undef;
if ($iType == PCK_TYPE_STR || $iType == PCK_TYPE_BOOL)
{
$iBits = $iDelta & 0x3;
$iDelta >>= 2;
if ($iDelta != 0)
{
$iBits |= 0x4;
}
if ($iType == PCK_TYPE_STR)
{
$iBits |= 0x8;
$iValue = length($xData);
}
else
{
$iBits |= $xData ? 0x8 : 0;
undef($xData);
}
}
elsif ($iType == PCK_TYPE_ARRAY || $iType == PCK_TYPE_OBJ)
{
$iBits |= $iDelta & 0x7;
$iDelta >>= 3;
if ($iDelta != 0)
{
$iBits |= 0x8;
}
}
# Output pack type and bits
my $strResult = sprintf("${strIndent}0x%02X,", ($iType << 4) | $iBits);
# Output additional id delta when present
if ($iDelta > 0)
{
$strResult .= packIntFormat($iDelta);
}
# Output value when present
if (defined($iValue))
{
$strResult .= packIntFormat($iValue);
}
# Output pack name
$strResult .= " // ${strName}";
# Output data in hex format
if (defined($xData) && length($xData) > 0)
{
$strResult .= "\n${strIndent} ";
my $iLength = length($strIndent) + 4;
my $bLastLF = false;
my $bFirst = true;
# Loop through all chars
foreach my $iChar (unpack("W*", $xData))
{
# Encode char to hex
my $strOut = sprintf("0x%02X,", $iChar);
# Break on linefeeds to prevent diffs within a paragraph of text from cascading through all the data
if ($bLastLF && $iChar != 0xA)
{
$strResult .= "\n${strIndent} ";
$iLength = length($strIndent) + 4;
$bFirst = true;
}
$bLastLF = $iChar == 0xA;
# If this hex would exceed the line length then break and write on the next line
if ($iLength + length($strOut) + 1 > 132)
{
$strResult .= "\n${strIndent} ${strOut}";
$iLength = length($strIndent) + 4 + length($strOut);
}
# Else append the hex
else
{
$strResult .= ($bFirst ? '' : ' ') . "${strOut}";
$iLength += length($strOut) + ($bFirst ? 0 : 1);
$bFirst = false;
}
}
}
return $strResult . "\n";
}
####################################################################################################################################
# Build help data
####################################################################################################################################
sub buildConfigHelp
{
# Load help data
#-------------------------------------------------------------------------------------------------------------------------------
require pgBackRestDoc::Common::Doc;
require pgBackRestDoc::Common::DocManifest;
my $strDocPath = abs_path(dirname($0) . '/../doc');
my $oStorageDoc = new pgBackRestTest::Common::Storage(
$strDocPath, new pgBackRestTest::Common::StoragePosix({bFileSync => false, bPathSync => false}));
my @stryEmpty = [];
my $oManifest = new pgBackRestDoc::Common::DocManifest(
$oStorageDoc, \@stryEmpty, \@stryEmpty, \@stryEmpty, \@stryEmpty, undef, $strDocPath, false, false);
my $oDocRender = new pgBackRestDoc::Common::DocRender('text', $oManifest, false);
my $oDocConfig =
new pgBackRestDoc::Common::DocConfig(
new pgBackRestDoc::Common::Doc("${strDocPath}/xml/reference.xml"), $oDocRender);
my $hConfigHelp = $oDocConfig->{oConfigHash};
# Build command help
#-------------------------------------------------------------------------------------------------------------------------------
my $rhCommandDefine = cfgDefineCommand();
my $strBuildSource =
"static const unsigned char helpDataPack[] =\n" .
"{\n" .
" // Commands\n" .
" // " . (qw{-} x 125) . "\n";
$strBuildSource .= packTagFormat("Commands begin", PCK_TYPE_ARRAY, 0, undef, 4);
foreach my $strCommand (sort(keys(%{$rhCommandDefine})))
{
my $rhCommand = $rhCommandDefine->{$strCommand};
my $iDelta = 0;
# Get command help
my $rhCommandHelp = $hConfigHelp->{&CONFIG_HELP_COMMAND}{$strCommand};
if (!defined($rhCommandHelp))
{
confess "no help for command ${strCommand}"
}
# Build command data
$strBuildSource .=
"\n" .
" // ${strCommand} command\n" .
" // " . (qw{-} x 121) . "\n";
if ($rhCommand->{&CFGDEF_INTERNAL})
{
$strBuildSource .= packTagFormat("Internal", PCK_TYPE_BOOL, 0, true, 8);
}
else
{
$iDelta++;
}
my $strSummary = trim($oManifest->variableReplace($oDocRender->processText($rhCommandHelp->{&CONFIG_HELP_SUMMARY})));
if (length($strSummary) > 72)
{
confess("summary for command '${strCommand}' may not be greater than 72 characters");
}
$strBuildSource .= packTagFormat("Summary", PCK_TYPE_STR, $iDelta, $strSummary, 8);
$iDelta = 0;
$strBuildSource .= packTagFormat(
"Description", PCK_TYPE_STR, 0,
trim($oManifest->variableReplace($oDocRender->processText($rhCommandHelp->{&CONFIG_HELP_DESCRIPTION}))), 8);
};
$strBuildSource .=
"\n" .
" 0x00, // Commands end\n";
# Build option help
#-------------------------------------------------------------------------------------------------------------------------------
my $rhConfigDefine = cfgDefine();
$strBuildSource .=
"\n" .
" // Options\n" .
" // " . (qw{-} x 125) . "\n";
$strBuildSource .= packTagFormat("Options begin", PCK_TYPE_ARRAY, 0, undef, 4);
my $iDelta = 0;
foreach my $strOption (sort(keys(%{$rhConfigDefine})))
{
my $bFirst = true;
my $bInternal = false;
# Build option data
my $rhOption = $rhConfigDefine->{$strOption};
# Get option help
my $rhOptionHelp = $hConfigHelp->{&CONFIG_HELP_OPTION}{$strOption};
# Build command data
$strBuildSource .=
"\n" .
" // ${strOption} option\n" .
" // " . (qw{-} x 121) . "\n";
# Internal
if ($rhOption->{&CFGDEF_INTERNAL})
{
$strBuildSource .= packTagFormat("Internal", PCK_TYPE_BOOL, $iDelta, true, 8);
$iDelta = 0;
$bInternal = true;
}
else
{
$iDelta++;
}
if (defined($rhOptionHelp))
{
# Section
my $strSection = $rhOptionHelp->{&CONFIG_HELP_SECTION};
if (defined($strSection))
{
if (length($strSection) > 72)
{
confess("section for option '${strOption}' may not be greater than 72 characters");
}
$strBuildSource .= packTagFormat("Section", PCK_TYPE_STR, $iDelta, $strSection, 8);
$iDelta = 0;
}
else
{
$iDelta++;
}
# Summary
my $strSummary = trim($oManifest->variableReplace($oDocRender->processText($rhOptionHelp->{&CONFIG_HELP_SUMMARY})));
if (length($strSummary) > 72)
{
confess("summary for option '${strOption}' may not be greater than 72 characters");
}
$strBuildSource .= packTagFormat("Summary", PCK_TYPE_STR, $iDelta, $strSummary, 8);
$iDelta = 0;
# Description
$strBuildSource .= packTagFormat(
"Description", PCK_TYPE_STR, $iDelta,
trim($oManifest->variableReplace($oDocRender->processText($rhOptionHelp->{&CONFIG_HELP_DESCRIPTION}))), 8);
$bFirst = false;
}
else
{
$iDelta += 3;
}
# Output deprecated names
my $stryDeprecatedName = $rhOptionHelp->{&CONFIG_HELP_NAME_ALT};
if (defined($stryDeprecatedName))
{
$strBuildSource .=
($bFirst ? '' : "\n") .
packTagFormat("Deprecated names begin", PCK_TYPE_ARRAY, $iDelta, undef, 8);
$iDelta = 0;
foreach my $strDeprecatedName (@{$stryDeprecatedName})
{
$strBuildSource .= packTagFormat($strDeprecatedName, PCK_TYPE_STR, 0, $strDeprecatedName, 12);
}
$strBuildSource .=
" 0x00, // Deprecated names end\n";
$bFirst = false;
}
else
{
$iDelta++;
}
# Command overrides
my $strBuildSourceCommands;
my $iCommandId = 0;
my $iLastCommandId = 0;
foreach my $strCommand (sort(keys(%{$rhCommandDefine})))
{
my $rhCommand = $rhOption->{&CFGDEF_COMMAND}{$strCommand};
my $iDeltaCommand = 0;
my $strBuildSourceCommand;
if (defined($rhCommand))
{
if ($bInternal && defined($rhCommand->{&CFGDEF_INTERNAL}) && !$rhCommand->{&CFGDEF_INTERNAL})
{
confess("option '${strOption}' is internal but command '${strCommand}' override is not");
}
# Internal
if (defined($rhCommand->{&CFGDEF_INTERNAL}) && $bInternal != $rhCommand->{&CFGDEF_INTERNAL})
{
$strBuildSourceCommand .=
packTagFormat("Internal", PCK_TYPE_BOOL, $iDeltaCommand, true, 16);
$iDeltaCommand = 0;
}
else
{
$iDeltaCommand++;
}
my $rhCommandHelp = $hConfigHelp->{&CONFIG_HELP_COMMAND}{$strCommand}{&CONFIG_HELP_OPTION}{$strOption};
if (defined($rhCommandHelp->{&CONFIG_HELP_SOURCE}) &&
$rhCommandHelp->{&CONFIG_HELP_SOURCE} eq CONFIG_HELP_SOURCE_COMMAND)
{
# Summary
my $strSummary = trim(
$oManifest->variableReplace($oDocRender->processText($rhCommandHelp->{&CONFIG_HELP_SUMMARY})));
if (length($strSummary) > 72)
{
confess("summary for command '${strCommand}' option '${strOption}' may not be greater than 72 characters");
}
$strBuildSourceCommand .=
packTagFormat("Summary", PCK_TYPE_STR, $iDeltaCommand, $strSummary, 16);
$iDeltaCommand = 0;
# Description
$strBuildSourceCommand .= packTagFormat(
"Description", PCK_TYPE_STR, $iDeltaCommand,
trim($oManifest->variableReplace($oDocRender->processText($rhCommandHelp->{&CONFIG_HELP_DESCRIPTION}))),
16);
}
if (defined($strBuildSourceCommand))
{
$strBuildSourceCommands .=
"\n" .
packTagFormat(
"Command ${strCommand} override begin", PCK_TYPE_OBJ, $iCommandId - $iLastCommandId, undef, 12) .
$strBuildSourceCommand .
" 0x00, // Command ${strCommand} override end\n";
$iLastCommandId = $iCommandId + 1;
}
}
$iCommandId++;
}
if (defined($strBuildSourceCommands))
{
$strBuildSource .=
($bFirst ? '' : "\n") .
packTagFormat("Command overrides begin", PCK_TYPE_ARRAY, $iDelta, undef, 8) .
$strBuildSourceCommands . "\n" .
" 0x00, // Command overrides end\n";
$iDelta = 0;
$bFirst = false;
}
else
{
$iDelta++;
}
}
$strBuildSource .=
"\n" .
" 0x00, // Options end\n";
$strBuildSource .=
"\n" .
" 0x00, // Pack end\n" .
"};\n";
$rhBuild->{&BLD_FILE}{&BLDLCL_FILE_DEFINE}{&BLD_DATA}{&BLDLCL_DATA_COMMAND}{&BLD_SOURCE} = $strBuildSource;
return $rhBuild;
}
push @EXPORT, qw(buildConfigHelp);
1;

View File

@ -317,7 +317,7 @@ eval
my $oRender = new pgBackRestDoc::Common::DocRender('text', $oManifest, !$bNoExe);
my $oDocConfig =
new pgBackRestDoc::Common::DocConfig(
new pgBackRestDoc::Common::Doc("${strBasePath}/xml/reference.xml"), $oRender);
new pgBackRestDoc::Common::Doc("${strBasePath}/../src/build/help/help.xml"), $oRender);
$oStorageDoc->pathCreate(
"${strBasePath}/output/man", {strMode => '0770', bIgnoreExists => true, bCreateParent => true});

View File

@ -11,10 +11,9 @@ use Exporter qw(import);
our @EXPORT = qw();
use File::Basename qw(dirname);
use pgBackRestBuild::Config::Data;
use pgBackRestDoc::Common::Log;
use pgBackRestDoc::Common::String;
use pgBackRestDoc::Custom::DocConfigData;
use pgBackRestDoc::ProjectInfo;
####################################################################################################################################

View File

@ -15,8 +15,6 @@ use Exporter qw(import);
use File::Basename qw(dirname);
use Storable qw(dclone);
use pgBackRestBuild::Config::Data;
use pgBackRestTest::Common::ExecuteTest;
use pgBackRestTest::Common::HostTest;
use pgBackRestTest::Common::HostGroupTest;
@ -26,6 +24,7 @@ use pgBackRestDoc::Common::Exception;
use pgBackRestDoc::Common::Ini;
use pgBackRestDoc::Common::Log;
use pgBackRestDoc::Common::String;
use pgBackRestDoc::Custom::DocConfigData;
use pgBackRestDoc::ProjectInfo;
####################################################################################################################################

View File

@ -119,7 +119,16 @@ sub new
next;
}
$$oSourceHash{doc} = new pgBackRestDoc::Common::Doc("$self->{strDocPath}/xml/${strKey}.xml");
# Help is in src/build/help
if ($strKey eq 'help')
{
$oSourceHash->{doc} = new pgBackRestDoc::Common::Doc("$self->{strDocPath}/../src/build/help/${strKey}.xml");
}
# Else should be in doc/xml
else
{
$$oSourceHash{doc} = new pgBackRestDoc::Common::Doc("$self->{strDocPath}/xml/${strKey}.xml");
}
# Read variables from source
$self->variableListParse($$oSourceHash{doc}->nodeGet('variable-list', false), $rhVariableOverride);

View File

@ -193,14 +193,14 @@ sub new
my $oRenderOut =
$self->{oManifest}->renderOutGet($self->{strType} eq 'latex' ? 'pdf' : $self->{strType}, $self->{strRenderOutKey});
# If these are the backrest docs then load the reference
# If these are the backrest docs then load the help
if ($self->{oManifest}->isBackRest())
{
$self->{oReference} =
new pgBackRestDoc::Common::DocConfig(${$self->{oManifest}->sourceGet('reference')}{doc}, $self);
new pgBackRestDoc::Common::DocConfig(${$self->{oManifest}->sourceGet('help')}{doc}, $self);
}
if (defined($$oRenderOut{source}) && $$oRenderOut{source} eq 'reference' && $self->{oManifest}->isBackRest())
if (defined($$oRenderOut{source}) && $$oRenderOut{source} eq 'help' && $self->{oManifest}->isBackRest())
{
if ($self->{strRenderOutKey} eq 'configuration')
{
@ -843,7 +843,7 @@ sub processTag
$strBuffer .= $strStart;
# Admonitions in the reference materials are tags of the text element rather than field elements of the document so special
# Admonitions in the help materials are tags of the text element rather than field elements of the document so special
# handling is required
if ($strTag eq 'admonition')
{

View File

@ -3,7 +3,7 @@
#
# The configuration is defined in src/build/config/config.yaml, which also contains the documentation.
####################################################################################################################################
package pgBackRestBuild::Config::Data;
package pgBackRestDoc::Custom::DocConfigData;
use strict;
use warnings FATAL => qw(all);

View File

@ -12,11 +12,10 @@ use Exporter qw(import);
our @EXPORT = qw();
use File::Basename qw(dirname);
use pgBackRestBuild::Config::Data;
use pgBackRestDoc::Common::DocRender;
use pgBackRestDoc::Common::Log;
use pgBackRestDoc::Common::String;
use pgBackRestDoc::Custom::DocConfigData;
use pgBackRestDoc::ProjectInfo;
####################################################################################################################################

View File

@ -83,7 +83,7 @@
<source key="index"/>
<source key="user-guide-index"/>
<source key="user-guide"/>
<source key="reference" type="custom"/>
<source key="help" type="custom"/>
<source key="release" type="custom"/>
<source key="faq"/>
<source key="metric"/>
@ -98,8 +98,8 @@
<render-source key="user-guide-index" menu="User Guides"/>
<render-source key="user-guide"/>
<render-source key="release" menu="Releases"/>
<render-source key="configuration" source="reference" menu="Configuration"/>
<render-source key="command" source="reference" menu="Commands"/>
<render-source key="configuration" source="help" menu="Configuration"/>
<render-source key="command" source="help" menu="Commands"/>
<render-source key="faq" menu="FAQ"/>
<render-source key="metric" menu="Metrics"/>
</render>

View File

@ -631,7 +631,7 @@ run 8/1 ------------- L2285 no current backups
<p>To add an option, two files need be to be modified:</p>
<list>
<list-item><file>src/build/config/config.yaml</file></list-item>
<list-item><file>doc/xml/reference.xml</file></list-item>
<list-item><file>src/build/help/help.xml</file></list-item>
</list>
<p>These files are discussed in the following sections along with how to verify the <code>help</code> command output.</p>
@ -720,8 +720,8 @@ run 8/1 ------------- L2285 no current backups
<p>At compile time, the <file>config.auto.h</file> file will be generated to contain the constants used for options in the code. For the C enums, any dashes in the option name will be removed, camel-cased and prefixed with <code>cfgOpt</code>, e.g. <code>repo-path</code> becomes <code>cfgOptRepoPath</code>.</p>
</section>
<section id="reference-file">
<title>reference.xml</title>
<section id="help-file">
<title>help.xml</title>
<p>All options must be documented or the system will error during the build. To add an option, find the command section identified by <code>command id="COMMAND"</code> section where <id>COMMAND</id> is the name of the command (e.g. <cmd>expire</cmd>) or, if the option is used by more than one command and the definition for the option is the same for all of the commands, the <code>operation-general title="General Options"</code> section.</p>

1
src/.gitignore vendored
View File

@ -1,5 +1,6 @@
/.build
autom4te.cache
/command/help/help.auto.c
/config.log
/config.status
/Makefile

View File

@ -6,6 +6,8 @@
# List of required source files. main.c should always be listed last and the rest in alpha order.
####################################################################################################################################
SRCS_BUILD = \
common/compress/bz2/common.c \
common/compress/bz2/compress.c \
common/debug.c \
common/encode.c \
common/error.c \
@ -13,6 +15,8 @@ SRCS_BUILD = \
common/io/filter/filter.c \
common/io/filter/group.c \
common/io/filter/sink.c \
common/io/bufferRead.c \
common/io/bufferWrite.c \
common/io/io.c \
common/io/read.c \
common/io/write.c \
@ -32,6 +36,7 @@ SRCS_BUILD = \
common/type/stringList.c \
common/type/variant.c \
common/type/variantList.c \
common/type/xml.c \
common/user.c \
common/wait.c \
storage/posix/read.c \
@ -81,8 +86,6 @@ SRCS = \
command/verify/protocol.c \
command/verify/verify.c \
common/compress/helper.c \
common/compress/bz2/common.c \
common/compress/bz2/compress.c \
common/compress/bz2/decompress.c \
common/compress/gz/common.c \
common/compress/gz/compress.c \
@ -100,8 +103,6 @@ SRCS = \
common/exit.c \
common/fork.c \
common/ini.c \
common/io/bufferRead.c \
common/io/bufferWrite.c \
common/io/client.c \
common/io/fd.c \
common/io/fdRead.c \
@ -125,7 +126,6 @@ SRCS = \
common/stat.c \
common/type/json.c \
common/type/mcv.c \
common/type/xml.c \
config/config.c \
config/exec.c \
config/load.c \
@ -183,7 +183,7 @@ SRCS = \
####################################################################################################################################
CC = @CC@
CFLAGS = $(CFLAGS_EXTRA) @CFLAGS@
CPPFLAGS = @CPPFLAGS@ -I@srcdir@
CPPFLAGS = @CPPFLAGS@ -I. -I@srcdir@
LDFLAGS = $(LDFLAGS_EXTRA) @LDFLAGS@
LIBS = @LIBS@
LIBS_BUILD = @LIBS_BUILD@
@ -237,6 +237,23 @@ build-error: $(OBJS_BUILD_ERROR) build/error/error.yaml
$(CC) -o build-error $(OBJS_BUILD_ERROR) $(LDFLAGS) $(LIBS) $(LIBS_BUILD)
./build-error $(VPATH)
####################################################################################################################################
# Compile and link help generator
####################################################################################################################################
SRCS_BUILD_HELP = \
build/common/render.c \
build/common/yaml.c \
build/config/parse.c \
build/help/parse.c \
build/help/render.c \
build/help/main.c
OBJS_BUILD_HELP = $(patsubst %.c,$(BUILDDIR)/%.o,$(SRCS_BUILD) $(SRCS_BUILD_HELP))
build-help: $(OBJS_BUILD_HELP) build/config/config.yaml build/help/help.xml
$(CC) -o build-help $(OBJS_BUILD_HELP) $(LDFLAGS) $(LIBS) $(LIBS_BUILD)
./build-help $(VPATH)
####################################################################################################################################
# Installation. DESTDIR can be used to modify the install location.
####################################################################################################################################
@ -258,7 +275,7 @@ uninstall:
# Clean build files and executable created by make
clean:
rm -rf $(BUILDDIR)
rm -f pgbackrest build-config
rm -f pgbackrest build-config build-error build-help command/help/help.auto.c
.PHONY = clean-all
@ -270,6 +287,7 @@ clean-all: clean
# Special per-object flags
####################################################################################################################################
$(BUILDDIR)/postgres/interface/page.o: CFLAGS += @CFLAGS_PAGE_CHECKSUM@
$(BUILDDIR)/main.o: build-help
####################################################################################################################################
# Compile and generate dependencies

View File

@ -53,6 +53,7 @@ Parse command list
typedef struct BldCfgCommandRaw
{
const String *const name; // See BldCfgCommand for comments
bool internal;
bool logFile;
const String *logLevelDefault;
bool lockRequired;
@ -138,6 +139,7 @@ bldCfgParseCommandList(Yaml *const yaml)
if (strEqZ(cmdDef.value, "internal"))
{
cmdRaw.internal = yamlBoolParse(cmdDefVal);
}
else if (strEqZ(cmdDef.value, "lock-type"))
{
@ -189,6 +191,7 @@ bldCfgParseCommandList(Yaml *const yaml)
&(BldCfgCommand)
{
.name = strDup(cmdRaw.name),
.internal = cmdRaw.internal,
.logFile = cmdRaw.logFile,
.logLevelDefault = strDup(cmdRaw.logLevelDefault),
.lockRequired = cmdRaw.lockRequired,
@ -275,6 +278,7 @@ typedef struct BldCfgOptionDeprecateRaw
typedef struct BldCfgOptionCommandRaw
{
const String *name; // See BldCfgOptionCommand for comments
const Variant *internal;
const Variant *required;
const String *defaultValue;
const BldCfgOptionDependRaw *depend;
@ -287,6 +291,7 @@ typedef struct BldCfgOptionRaw
const String *name; // See BldCfgOption for comments
const String *type;
const String *section;
bool internal;
const Variant *required;
const Variant *negate;
bool reset;
@ -646,6 +651,7 @@ bldCfgParseOptionCommandList(Yaml *const yaml, const List *const optList)
}
else if (strEqZ(optCmdDef.value, "internal"))
{
optCmdRaw.internal = varNewBool(yamlBoolParse(optCmdDefVal));
}
else if (strEqZ(optCmdDef.value, "required"))
{
@ -669,6 +675,7 @@ bldCfgParseOptionCommandList(Yaml *const yaml, const List *const optList)
&(BldCfgOptionCommandRaw)
{
.name = strDup(optCmdRaw.name),
.internal = varDup(optCmdRaw.internal),
.required = varDup(optCmdRaw.required),
.defaultValue = strDup(optCmdRaw.defaultValue),
.depend = optCmdRaw.depend,
@ -798,6 +805,7 @@ bldCfgParseOptionList(Yaml *const yaml, const List *const cmdList, const List *c
}
else if (strEqZ(optDef.value, "internal"))
{
optRaw.internal = yamlBoolParse(optDefVal);
}
else if (strEqZ(optDef.value, "negate"))
{
@ -889,6 +897,7 @@ bldCfgParseOptionList(Yaml *const yaml, const List *const cmdList, const List *c
.name = strDup(optRaw->name),
.type = strDup(optRaw->type),
.section = strDup(optRaw->section),
.internal = optRaw->internal,
.required = varBool(optRaw->required),
.negate = varBool(optRaw->negate),
.reset = optRaw->reset,
@ -931,6 +940,10 @@ bldCfgParseOptionList(Yaml *const yaml, const List *const cmdList, const List *c
if (optCmd.required == NULL)
optCmd.required = optRaw->required;
// Default internal to option internal if not defined
if (optCmd.internal == NULL)
optCmd.internal = varNewBool(optRaw->internal);
// Default command role list if not defined
if (optCmd.roleList == NULL)
{
@ -963,6 +976,7 @@ bldCfgParseOptionList(Yaml *const yaml, const List *const cmdList, const List *c
&(BldCfgOptionCommand)
{
.name = strDup(optCmd.name),
.internal = varBool(optCmd.internal),
.required = varBool(optCmd.required),
.defaultValue = strDup(optCmd.defaultValue),
.depend = bldCfgParseDependReconcile(optCmd.depend, result),

View File

@ -62,6 +62,7 @@ Types
typedef struct BldCfgCommand
{
const String *const name; // Name
const bool internal; // Is the command internal?
const bool logFile; // Does the command write automatically to a log file?
const String *const logLevelDefault; // Default log level
const bool lockRequired; // Is a lock required
@ -94,6 +95,7 @@ typedef struct BldCfgOptionDeprecate
typedef struct BldCfgOptionCommand
{
const String *const name; // Name
const bool internal; // Is the option internal?
const bool required; // Is the option required?
const String *const defaultValue; // Default value, if any
const BldCfgOptionDepend *const depend; // Dependency, if any
@ -106,6 +108,7 @@ struct BldCfgOption
const String *const name; // Name
const String *const type; // Option type, e.g. integer
const String *const section; // Option section, i.e. stanza or global
const bool internal; // Is the option internal?
const bool required; // Is the option required?
const bool negate; // Can the option be negated?
const bool reset; // Can the option be reset?

36
src/build/help/main.c Normal file
View File

@ -0,0 +1,36 @@
/***********************************************************************************************************************************
Auto-Generate Help
***********************************************************************************************************************************/
#include <unistd.h>
#include "common/log.h"
#include "storage/posix/storage.h"
#include "build/config/parse.h"
#include "build/help/parse.h"
#include "build/help/render.h"
int
main(int argListSize, const char *argList[])
{
// Check parameters
CHECK(argListSize <= 2);
// Initialize logging
logInit(logLevelWarn, logLevelError, logLevelOff, false, 0, 1, false);
// Get current working directory
char currentWorkDir[1024];
THROW_ON_SYS_ERROR(getcwd(currentWorkDir, sizeof(currentWorkDir)) == NULL, FormatError, "unable to get cwd");
// Get repo path (cwd if it was not passed)
const String *pathRepo = argListSize >= 2 ? strPath(STR(argList[1])) : strPath(STR(currentWorkDir));
// Render config
const Storage *const storageRepo = storagePosixNewP(pathRepo);
const Storage *const storageBuild = storagePosixNewP(STR(currentWorkDir), .write = true);
const BldCfg bldCfg = bldCfgParse(storageRepo);
bldHlpRender(storageBuild, bldCfg, bldHlpParse(storageRepo, bldCfg));
return 0;
}

191
src/build/help/parse.c Normal file
View File

@ -0,0 +1,191 @@
/***********************************************************************************************************************************
Parse Help Xml
***********************************************************************************************************************************/
#include "build.auto.h"
#include "common/type/xml.h"
#include "storage/storage.h"
#include "build/config/parse.h"
#include "build/help/parse.h"
/***********************************************************************************************************************************
Parse option help
***********************************************************************************************************************************/
// Helper to parse options
static void
bldHlpParseOption(XmlNodeList *const xmlOptList, List *const optList, const String *const sectionDefault)
{
ASSERT(xmlOptList != NULL);
ASSERT(optList != NULL);
for (unsigned int optIdx = 0; optIdx < xmlNodeLstSize(xmlOptList); optIdx++)
{
const XmlNode *const xmlOpt = xmlNodeLstGet(xmlOptList, optIdx);
// Get section or use default
const String *section = xmlNodeAttribute(xmlOpt, STRDEF("section"));
if (section == NULL)
section = sectionDefault;
// Add option to list
MEM_CONTEXT_BEGIN(lstMemContext(optList))
{
lstAdd(
optList,
&(BldHlpOption)
{
.name = xmlNodeAttribute(xmlOpt, STRDEF("id")),
.section = strDup(section),
.summary = xmlNodeChild(xmlOpt, STRDEF("summary"), true),
.description = xmlNodeChild(xmlOpt, STRDEF("text"), true),
});
}
MEM_CONTEXT_END();
}
lstSort(optList, sortOrderAsc);
}
static List *
bldHlpParseOptionList(XmlNode *const xml)
{
List *const result = lstNewP(sizeof(BldHlpOption), .comparator = lstComparatorStr);
// Parse config options
const XmlNodeList *xmlSectionList = xmlNodeChildList(
xmlNodeChild(xmlNodeChild(xml, STRDEF("config"), true), STRDEF("config-section-list"), true), STRDEF("config-section"));
for (unsigned int sectionIdx = 0; sectionIdx < xmlNodeLstSize(xmlSectionList); sectionIdx++)
{
const XmlNode *const xmlSection = xmlNodeLstGet(xmlSectionList, sectionIdx);
bldHlpParseOption(
xmlNodeChildList(xmlNodeChild(xmlSection, STRDEF("config-key-list"), true), STRDEF("config-key")), result,
xmlNodeAttribute(xmlSection, STRDEF("id")));
}
// Parse command-line only options
bldHlpParseOption(
xmlNodeChildList(
xmlNodeChild(
xmlNodeChild(xmlNodeChild(xml, STRDEF("operation"), true), STRDEF("operation-general"), true),
STRDEF("option-list"), true),
STRDEF("option")),
result, NULL);
return result;
}
/***********************************************************************************************************************************
Parse command help
***********************************************************************************************************************************/
static List *
bldHlpParseCommandList(XmlNode *const xml)
{
List *const result = lstNewP(sizeof(BldHlpCommand), .comparator = lstComparatorStr);
// Parse commands
const XmlNodeList *xmlCmdList = xmlNodeChildList(xml, STRDEF("command"));
for (unsigned int cmdIdx = 0; cmdIdx < xmlNodeLstSize(xmlCmdList); cmdIdx++)
{
const XmlNode *const xmlCmd = xmlNodeLstGet(xmlCmdList, cmdIdx);
// Parse option list if any
List *cmdOptList = NULL;
const XmlNode *const xmlCmdOptListParent = xmlNodeChild(xmlCmd, STRDEF("option-list"), false);
if (xmlCmdOptListParent != NULL)
{
cmdOptList = lstNewP(sizeof(BldHlpOption), .comparator = lstComparatorStr);
bldHlpParseOption(xmlNodeChildList(xmlCmdOptListParent, STRDEF("option")), cmdOptList, NULL);
}
// Add command to list
MEM_CONTEXT_BEGIN(lstMemContext(result))
{
lstAdd(
result,
&(BldHlpCommand)
{
.name = xmlNodeAttribute(xmlCmd, STRDEF("id")),
.summary = xmlNodeChild(xmlCmd, STRDEF("summary"), true),
.description = xmlNodeChild(xmlCmd, STRDEF("text"), true),
.optList = lstMove(cmdOptList, memContextCurrent()),
});
}
MEM_CONTEXT_END();
}
lstSort(result, sortOrderAsc);
return result;
}
/***********************************************************************************************************************************
Reconcile help
***********************************************************************************************************************************/
static void
bldHlpValidate(const BldHlp bldHlp, const BldCfg bldCfg)
{
// Validate command help
for (unsigned int cmdIdx = 0; cmdIdx < lstSize(bldCfg.cmdList); cmdIdx++)
{
const BldCfgCommand *const cmd = lstGet(bldCfg.cmdList, cmdIdx);
const BldHlpCommand *const cmdHlp = lstFind(bldHlp.cmdList, &cmd->name);
if (cmdHlp == NULL)
THROW_FMT(FormatError, "command '%s' must have help", strZ(cmd->name));
}
// Validate option help
for (unsigned int optIdx = 0; optIdx < lstSize(bldCfg.optList); optIdx++)
{
const BldCfgOption *const opt = lstGet(bldCfg.optList, optIdx);
const BldHlpOption *const optHlp = lstFind(bldHlp.optList, &opt->name);
// If help was not found in general command-line or config options then check command overrides
if (optHlp == NULL)
{
for (unsigned int optCmdListIdx = 0; optCmdListIdx < lstSize(opt->cmdList); optCmdListIdx++)
{
const BldCfgOptionCommand *const optCmd = lstGet(opt->cmdList, optCmdListIdx);
const BldHlpCommand *const cmdHlp = lstFind(bldHlp.cmdList, &optCmd->name);
CHECK(cmdHlp != NULL);
// Only options with a command role of main require help
if (!strLstExists(optCmd->roleList, CMD_ROLE_MAIN_STR))
continue;
const BldHlpOption *const cmdOptHlp = cmdHlp->optList != NULL ? lstFind(cmdHlp->optList, &opt->name) : NULL;
if (cmdOptHlp == NULL)
THROW_FMT(FormatError, "option '%s' must have help for command '%s'", strZ(opt->name), strZ(optCmd->name));
}
}
}
}
/**********************************************************************************************************************************/
BldHlp
bldHlpParse(const Storage *const storageRepo, const BldCfg bldCfg)
{
// Initialize xml
XmlNode *const xml = xmlDocumentRoot(
xmlDocumentNewBuf(storageGetP(storageNewReadP(storageRepo, STRDEF("src/build/help/help.xml")))));
// Parse help
BldHlp result =
{
.cmdList = bldHlpParseCommandList(
xmlNodeChild(xmlNodeChild(xml, STRDEF("operation"), true), STRDEF("command-list"), true)),
.optList = bldHlpParseOptionList(xml)
};
bldHlpValidate(result, bldCfg);
return result;
}

41
src/build/help/parse.h Normal file
View File

@ -0,0 +1,41 @@
/***********************************************************************************************************************************
Parse Help Xml
***********************************************************************************************************************************/
#ifndef BUILD_HELP_PARSE_H
#define BUILD_HELP_PARSE_H
#include "build/config/parse.h"
#include "common/type/xml.h"
/***********************************************************************************************************************************
Types
***********************************************************************************************************************************/
typedef struct BldHlpCommand
{
const String *name; // Name
const XmlNode *summary; // Summary
const XmlNode *description; // Description
const List *optList; // Option list
} BldHlpCommand;
typedef struct BldHlpOption
{
const String *name; // Name
const String *section; // Section
const XmlNode *summary; // Summary
const XmlNode *description; // Description
} BldHlpOption;
typedef struct BldHlp
{
const List *cmdList; // Command list
const List *optList; // Option list
} BldHlp;
/***********************************************************************************************************************************
Functions
***********************************************************************************************************************************/
// Parse help.xml
BldHlp bldHlpParse(const Storage *const storageRepo, const BldCfg bldCfg);
#endif

336
src/build/help/render.c Normal file
View File

@ -0,0 +1,336 @@
/***********************************************************************************************************************************
Render Help
***********************************************************************************************************************************/
#include "build.auto.h"
#include <stdio.h>
#include <libxml/parser.h>
#include <libxml/tree.h>
#include "common/compress/bz2/compress.h"
#include "common/io/bufferRead.h"
#include "common/io/bufferWrite.h"
#include "common/log.h"
#include "common/type/pack.h"
#include "build/common/render.h"
#include "build/config/parse.h"
#include "build/help/parse.h"
/***********************************************************************************************************************************
Render xml as text
***********************************************************************************************************************************/
static String *
bldHlpRenderReplace(const String *const string, const char *const replace, const char *const with)
{
String *result = strNew();
StringList *const stringList = strLstNewSplitZ(string, replace);
for (unsigned int stringIdx = 0; stringIdx < strLstSize(stringList); stringIdx++)
{
if (stringIdx != 0)
strCatZ(result, with);
strCat(result, strLstGet(stringList, stringIdx));
}
return result;
}
static String *
bldHlpRenderXmlNode(const xmlNodePtr xml)
{
String *const result = strNew();
for (xmlNodePtr currentNode = xml->children; currentNode != NULL; currentNode = currentNode->next)
{
const String *const name = STR((char *)currentNode->name);
if (currentNode->type == XML_ELEMENT_NODE)
{
if (strEq(name, STRDEF("admonition")))
{
strCatZ(result, "NOTE: ");
strCat(result, bldHlpRenderXmlNode(currentNode));
strCatZ(result, "\n\n");
}
else if (strEq(name, STRDEF("backrest")))
strCatZ(result, "pgBackRest");
else if (strEq(name, STRDEF("list")))
{
strCat(result, bldHlpRenderXmlNode(currentNode));
strCatChr(result, '\n');
}
else if (strEq(name, STRDEF("list-item")))
{
strCatZ(result, "* ");
strCat(result, bldHlpRenderXmlNode(currentNode));
strCatChr(result, '\n');
}
else if (strEq(name, STRDEF("p")))
{
strCat(result, bldHlpRenderXmlNode(currentNode));
strCatZ(result, "\n\n");
}
else if (strEq(name, STRDEF("postgres")))
strCatZ(result, "PostgreSQL");
else if (
strEq(name, STRDEF("id")) || strEq(name, STRDEF("br-option")) || strEq(name, STRDEF("cmd")) ||
strEq(name, STRDEF("link")) || strEq(name, STRDEF("setting")) || strEq(name, STRDEF("pg-setting")) ||
strEq(name, STRDEF("code")) || strEq(name, STRDEF("i")) || strEq(name, STRDEF("file")) ||
strEq(name, STRDEF("path")) || strEq(name, STRDEF("b")) || strEq(name, STRDEF("host")) ||
strEq(name, STRDEF("exe")) || strEq(name, STRDEF("proper")))
{
strCat(result, bldHlpRenderXmlNode(currentNode));
}
else
THROW_FMT(FormatError, "unknown tag '%s'", strZ(name));
}
else
{
xmlChar *content = xmlNodeGetContent(currentNode);
String *text = strNewZ((char *)content);
xmlFree(content);
if (strchr(strZ(text), '\n') != NULL)
{
if (!strEmpty(strTrim(strDup(text))))
THROW_FMT(FormatError, "text '%s' is invalid", strZ(text));
continue;
}
text = bldHlpRenderReplace(text, "{[dash]}", "-");
strCat(result, text);
}
}
return result;
}
static String *
bldHlpRenderXml(const XmlNode *const xml)
{
return strTrim(bldHlpRenderXmlNode(*(const xmlNodePtr *)xml));
}
/***********************************************************************************************************************************
Render help to a pack
***********************************************************************************************************************************/
static PackWrite *
bldHlpRenderHelpAutoCPack(const BldCfg bldCfg, const BldHlp bldHlp)
{
PackWrite *const pack = pckWriteNewBuf(bufNew(65 * 1024));
// Command help
// -----------------------------------------------------------------------------------------------------------------------------
pckWriteArrayBeginP(pack);
for (unsigned int cmdIdx = 0; cmdIdx < lstSize(bldCfg.cmdList); cmdIdx++)
{
const BldCfgCommand *const cmd = lstGet(bldCfg.cmdList, cmdIdx);
const BldHlpCommand *const cmdHlp = lstFind(bldHlp.cmdList, &cmd->name);
CHECK(cmdHlp != NULL);
pckWriteBoolP(pack, cmd->internal);
pckWriteStrP(pack, bldHlpRenderXml(cmdHlp->summary));
pckWriteStrP(pack, bldHlpRenderXml(cmdHlp->description));
}
pckWriteArrayEndP(pack);
// Option help
// -----------------------------------------------------------------------------------------------------------------------------
pckWriteArrayBeginP(pack);
for (unsigned int optIdx = 0; optIdx < lstSize(bldCfg.optList); optIdx++)
{
const BldCfgOption *const opt = lstGet(bldCfg.optList, optIdx);
const BldHlpOption *const optHlp = lstFind(bldHlp.optList, &opt->name);
// Internal
pckWriteBoolP(pack, opt->internal);
// Section
if (optHlp != NULL)
pckWriteStrP(pack, optHlp->section);
else
pckWriteNullP(pack);
// Summary
if (optHlp != NULL)
pckWriteStrP(pack, bldHlpRenderXml(optHlp->summary));
else
pckWriteNullP(pack);
// Description
if (optHlp != NULL)
pckWriteStrP(pack, bldHlpRenderXml(optHlp->description));
else
pckWriteNullP(pack);
// Deprecations
StringList *const deprecateList = strLstNew();
if (opt->deprecateList != NULL)
{
for (unsigned int deprecateIdx = 0; deprecateIdx < lstSize(opt->deprecateList); deprecateIdx++)
{
const BldCfgOptionDeprecate *const deprecate = lstGet(opt->deprecateList, deprecateIdx);
if (!strEq(deprecate->name, opt->name))
strLstAdd(deprecateList, deprecate->name);
}
}
if (!strLstEmpty(deprecateList))
{
pckWriteArrayBeginP(pack);
for (unsigned int deprecateIdx = 0; deprecateIdx < strLstSize(deprecateList); deprecateIdx++)
pckWriteStrP(pack, strLstGet(deprecateList, deprecateIdx));
pckWriteArrayEndP(pack);
}
else
pckWriteNullP(pack);
// Command overrides
bool found = false;
for (unsigned int cmdIdx = 0; cmdIdx < lstSize(bldCfg.cmdList); cmdIdx++)
{
const BldCfgCommand *const cmd = lstGet(bldCfg.cmdList, cmdIdx);
const BldCfgCommand *const optCmd = lstFind(opt->cmdList, &cmd->name);
if (optCmd != NULL)
{
const BldHlpCommand *const cmdHlp = lstFind(bldHlp.cmdList, &cmd->name);
CHECK(cmdHlp != NULL);
const BldHlpOption *const cmdOptHlp = cmdHlp->optList != NULL ? lstFind(cmdHlp->optList, &opt->name) : NULL;
if (opt->internal != optCmd->internal || cmdOptHlp != NULL)
{
if (!found)
{
pckWriteArrayBeginP(pack);
found = true;
}
pckWriteObjBeginP(pack, .id = cmdIdx + 1);
if (opt->internal != optCmd->internal)
pckWriteBoolP(pack, optCmd->internal);
else
pckWriteNullP(pack);
if (cmdOptHlp != NULL)
{
pckWriteStrP(pack, bldHlpRenderXml(cmdOptHlp->summary));
pckWriteStrP(pack, bldHlpRenderXml(cmdOptHlp->description));
}
pckWriteObjEndP(pack);
}
}
}
if (found)
pckWriteArrayEndP(pack);
else
pckWriteNullP(pack);
}
pckWriteArrayEndP(pack);
pckWriteEnd(pack);
return pack;
}
/***********************************************************************************************************************************
Compress pack to a buffer
***********************************************************************************************************************************/
static Buffer *
bldHlpRenderHelpAutoCCmp(const BldCfg bldCfg, const BldHlp bldHlp)
{
// Get pack buffer
const Buffer *const packBuf = pckWriteBuf(bldHlpRenderHelpAutoCPack(bldCfg, bldHlp));
Buffer *const result = bufNew(bufSize(packBuf));
// Open source/destination
IoRead *const source = ioBufferReadNew(packBuf);
IoWrite *const destination = ioBufferWriteNew(result);
ioFilterGroupAdd(ioWriteFilterGroup(destination), bz2CompressNew(9));
ioReadOpen(source);
ioWriteOpen(destination);
// Copy data from source to destination
Buffer *read = bufNew(bufUsed(packBuf) + 1);
ioRead(source, read);
ASSERT(ioReadEof(source));
ioWrite(destination, read);
ioWriteClose(destination);
// Return compressed buffer
return result;
}
/***********************************************************************************************************************************
Output buffer to a file as a byte array
***********************************************************************************************************************************/
static void
bldHlpRenderHelpAutoC(const Storage *const storageRepo, const BldCfg bldCfg, const BldHlp bldHlp)
{
// Convert pack to bytes
String *const help = strNewFmt(
"%s"
"static const unsigned char helpData[] =\n"
"{\n",
strZ(bldHeader("help", "Help Data")));
const Buffer *const buffer = bldHlpRenderHelpAutoCCmp(bldCfg, bldHlp);
bool first = true;
size_t lineSize = 0;
char byteZ[4];
for (unsigned int bufferIdx = 0; bufferIdx < bufUsed(buffer); bufferIdx++)
{
snprintf(byteZ, sizeof(byteZ), "%u", bufPtrConst(buffer)[bufferIdx]);
if (strlen(byteZ) + 1 + (first ? 0 : 1) + lineSize > 128)
{
strCatChr(help, '\n');
first = true;
}
if (first)
{
strCatFmt(help, " ");
lineSize = 0;
}
strCatFmt(help, "%s%s,", first ? "" : " ", byteZ);
lineSize += strlen(byteZ) + 1 + (first ? 0 : 1);
first = false;
}
strCatZ(help, "\n};\n");
// Write to storage
bldPut(storageRepo, "command/help/help.auto.c", BUFSTR(help));
}
/**********************************************************************************************************************************/
void
bldHlpRender(const Storage *const storageRepo, const BldCfg bldCfg, const BldHlp bldHlp)
{
bldHlpRenderHelpAutoC(storageRepo, bldCfg, bldHlp);
}

16
src/build/help/render.h Normal file
View File

@ -0,0 +1,16 @@
/***********************************************************************************************************************************
Render Help
***********************************************************************************************************************************/
#ifndef BUILD_HELP_RENDER_H
#define BUILD_HELP_RENDER_H
#include "build/config/parse.h"
#include "build/help/parse.h"
/***********************************************************************************************************************************
Functions
***********************************************************************************************************************************/
// Render help
void bldHlpRender(const Storage *const storageRepo, const BldCfg bldCfg, const BldHlp bldHlp);
#endif

File diff suppressed because it is too large Load Diff

View File

@ -8,19 +8,17 @@ Help Command
#include <sys/types.h>
#include <unistd.h>
#include "common/compress/bz2/decompress.h"
#include "common/debug.h"
#include "common/io/bufferRead.h"
#include "common/io/fdWrite.h"
#include "common/io/io.h"
#include "common/memContext.h"
#include "common/type/pack.h"
#include "config/config.intern.h"
#include "config/parse.h"
#include "version.h"
/***********************************************************************************************************************************
Include automatically generated help data pack
***********************************************************************************************************************************/
#include "command/help/help.auto.c"
/***********************************************************************************************************************************
Define the console width - use a fixed with of 80 since this should be safe on virtually all consoles
***********************************************************************************************************************************/
@ -234,16 +232,27 @@ typedef struct HelpOptionData
} HelpOptionData;
static String *
helpRender(void)
helpRender(const Buffer *const helpData)
{
FUNCTION_LOG_VOID(logLevelDebug);
FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_LOG_PARAM(BUFFER, helpData);
FUNCTION_LOG_END();
String *result = strNewZ(PROJECT_NAME " " PROJECT_VERSION);
MEM_CONTEXT_TEMP_BEGIN()
{
// Set a small buffer size to minimize memory usage
ioBufferSizeSet(8192);
// Read pack from compressed buffer
IoRead *const helpRead = ioBufferReadNew(helpData);
ioFilterGroupAdd(ioReadFilterGroup(helpRead), bz2DecompressNew());
ioReadOpen(helpRead);
PackRead *pckHelp = pckReadNew(helpRead);
// Unpack command data
PackRead *pckHelp = pckReadNewBuf(BUF(helpDataPack, sizeof(helpDataPack)));
HelpCommandData *commandData = memNew(sizeof(HelpCommandData) * CFG_COMMAND_TOTAL);
pckReadArrayBeginP(pckHelp);
@ -544,13 +553,15 @@ helpRender(void)
/**********************************************************************************************************************************/
void
cmdHelp(void)
cmdHelp(const Buffer *const helpData)
{
FUNCTION_LOG_VOID(logLevelDebug);
FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_LOG_PARAM(BUFFER, helpData);
FUNCTION_LOG_END();
MEM_CONTEXT_TEMP_BEGIN()
{
ioFdWriteOneStr(STDOUT_FILENO, helpRender());
ioFdWriteOneStr(STDOUT_FILENO, helpRender(helpData));
}
MEM_CONTEXT_TEMP_END();

View File

@ -4,10 +4,12 @@ Help Command
#ifndef COMMAND_HELP_HELP_H
#define COMMAND_HELP_HELP_H
#include "common/type/buffer.h"
/***********************************************************************************************************************************
Functions
***********************************************************************************************************************************/
// Render help and output to stdout
void cmdHelp(void);
void cmdHelp(const Buffer *const helpData);
#endif

View File

@ -42,6 +42,11 @@ Main
#include "storage/helper.h"
#include "version.h"
/***********************************************************************************************************************************
Include automatically generated help data
***********************************************************************************************************************************/
#include "command/help/help.auto.c"
int
main(int argListSize, const char *argList[])
{
@ -82,7 +87,7 @@ main(int argListSize, const char *argList[])
// -------------------------------------------------------------------------------------------------------------------------
if (cfgCommandHelp())
{
cmdHelp();
cmdHelp(BUF(helpData, sizeof(helpData)));
}
// Local role
// -------------------------------------------------------------------------------------------------------------------------

View File

@ -1,20 +1,4 @@
# File types for source files in the project
build/lib/pgBackRestBuild/Build.pm:
class: build
type: perl
build/lib/pgBackRestBuild/Build/Common.pm:
class: build
type: perl
build/lib/pgBackRestBuild/Config/BuildHelp.pm:
class: build
type: perl
build/lib/pgBackRestBuild/Config/Data.pm:
class: build
type: perl
doc/doc.pl:
class: doc/core
type: perl
@ -55,6 +39,10 @@ doc/lib/pgBackRestDoc/Common/String.pm:
class: doc/core
type: perl
doc/lib/pgBackRestDoc/Custom/DocConfigData.pm:
class: doc/core
type: perl
doc/lib/pgBackRestDoc/Custom/DocCustomRelease.pm:
class: doc/core
type: perl
@ -147,10 +135,6 @@ doc/xml/metric.xml:
class: doc/source
type: xml
doc/xml/reference.xml:
class: doc/source
type: xml
doc/xml/release.xml:
class: doc/source
type: xml
@ -251,6 +235,30 @@ src/build/error/render.h:
class: build
type: c/h
src/build/help/help.xml:
class: build
type: make
src/build/help/main.c:
class: build
type: c
src/build/help/parse.c:
class: build
type: c
src/build/help/parse.h:
class: build
type: c/h
src/build/help/render.c:
class: build
type: c
src/build/help/render.h:
class: build
type: c/h
src/build/install-sh:
class: build
type: make
@ -407,10 +415,6 @@ src/command/expire/expire.h:
class: core
type: c/h
src/command/help/help.auto.c:
class: core/auto
type: c
src/command/help/help.c:
class: core
type: c
@ -2067,6 +2071,10 @@ test/src/module/build/errorTest.c:
class: test/module
type: c
test/src/module/build/helpTest.c:
class: test/module
type: c
test/src/module/command/archiveCommonTest.c:
class: test/module
type: c

View File

@ -631,6 +631,14 @@ unit:
- build/error/parse
- build/error/render
# ----------------------------------------------------------------------------------------------------------------------------
- name: help
total: 2
coverage:
- build/help/parse
- build/help/render
# ********************************************************************************************************************************
- name: info
@ -778,6 +786,9 @@ unit:
- command/help/help
- command/help/help.auto: noCode
include:
- build/help/render
# ----------------------------------------------------------------------------------------------------------------------------
- name: info
total: 3

View File

@ -0,0 +1,408 @@
/***********************************************************************************************************************************
Test Build Help
***********************************************************************************************************************************/
#include "storage/posix/storage.h"
#include "common/harnessPack.h"
#include "common/harnessStorage.h"
/***********************************************************************************************************************************
Test Run
***********************************************************************************************************************************/
void
testRun(void)
{
FUNCTION_HARNESS_VOID();
// Create default storage object for testing
Storage *storageTest = storagePosixNewP(TEST_PATH_STR, .write = true);
// *****************************************************************************************************************************
if (testBegin("bldHlpRenderXml()"))
{
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("format errors");
TEST_ERROR(
bldHlpRenderXml(xmlDocumentRoot(xmlDocumentNewBuf(BUFSTRDEF("<doc><bogus/></doc>")))), FormatError,
"unknown tag 'bogus'");
TEST_ERROR(
bldHlpRenderXml(xmlDocumentRoot(xmlDocumentNewBuf(BUFSTRDEF("<doc>bogus\n</doc>")))), FormatError,
"text 'bogus\n' is invalid");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("xml to text");
TEST_RESULT_STR_Z(
bldHlpRenderXml(xmlDocumentRoot(xmlDocumentNewBuf(BUFSTRDEF(
"<doc>"
"<p><backrest/> <postgres/> {[dash]} "
"<b><br-option><cmd><code><exe><file><host><i><id><link><path><pg-setting><proper><setting>"
"info"
"</setting></proper></pg-setting></path></link></id></i></host></file></exe></code></cmd></br-option></b></p>\n"
"\n"
"<admonition>think about it</admonition>\n"
"\n"
"<p>List:</p>\n"
"\n"
"<list>\n"
"<list-item>item1</list-item>\n"
"<list-item>item2</list-item>\n"
"</list>\n"
"\n"
"<p>last para</p>"
"</doc>")))),
"pgBackRest PostgreSQL - info\n"
"\n"
"NOTE: think about it\n"
"\n"
"List:\n"
"\n"
"* item1\n"
"* item2\n"
"\n"
"last para",
"render");
}
// *****************************************************************************************************************************
if (testBegin("bldHlpParse() and bldHlpRender()"))
{
TEST_TITLE("error on missing command");
HRN_STORAGE_PUT_Z(
storageTest, "src/build/config/config.yaml",
"command:\n"
" backup: {}\n"
"\n"
"optionGroup:\n"
" pg: {}\n"
"\n"
"option:\n"
" buffer:\n"
" section: general\n"
" type: size\n"
"\n"
" pg:\n"
" type: string\n"
" command-role:\n"
" local: {}"
"\n"
" stanza:\n"
" type: string\n"
"\n"
" xfer:\n"
" type: bool\n"
"\n");
BldCfg bldCfgErr = bldCfgParse(storageTest);
HRN_STORAGE_PUT_Z(
storageTest, "src/build/help/help.xml",
"<doc>\n"
" <config>\n"
" <config-section-list>\n"
" </config-section-list>\n"
" </config>\n"
"\n"
" <operation>\n"
" <operation-general>\n"
" <option-list>\n"
" </option-list>\n"
" </operation-general>\n"
"\n"
" <command-list>\n"
" </command-list>\n"
" </operation>\n"
"</doc>\n");
TEST_ERROR(bldHlpParse(storageTest, bldCfgErr), FormatError, "command 'backup' must have help");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("error on missing config option");
HRN_STORAGE_PUT_Z(
storageTest, "src/build/help/help.xml",
"<doc>\n"
" <config>\n"
" <config-section-list>\n"
" </config-section-list>\n"
" </config>\n"
"\n"
" <operation>\n"
" <operation-general>\n"
" <option-list>\n"
" </option-list>\n"
" </operation-general>\n"
"\n"
" <command-list>\n"
" <command id=\"backup\" name=\"Backup\">\n"
" <summary>Backup.</summary>\n"
"\n"
" <text>\n"
" <p>Backup.</p>\n"
" </text>\n"
" </command>\n"
" </command-list>\n"
" </operation>\n"
"</doc>\n");
TEST_ERROR(bldHlpParse(storageTest, bldCfgErr), FormatError, "option 'buffer' must have help for command 'backup'");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("error on missing command-line option");
HRN_STORAGE_PUT_Z(
storageTest, "src/build/help/help.xml",
"<doc>\n"
" <config>\n"
" <config-section-list>\n"
" <config-section id=\"general\" name=\"General\">\n"
" <config-key-list>\n"
" <config-key id=\"buffer\" name=\"Buffer\">\n"
" <summary>Buffer.</summary>\n"
"\n"
" <text>\n"
" <p>Buffer.</p>\n"
" </text>\n"
" </config-key>\n"
" </config-key-list>\n"
" </config-section>\n"
" </config-section-list>\n"
" </config>\n"
"\n"
" <operation>\n"
" <operation-general>\n"
" <option-list>\n"
" </option-list>\n"
" </operation-general>\n"
"\n"
" <command-list>\n"
" <command id=\"backup\" name=\"Backup\">\n"
" <summary>Backup.</summary>\n"
"\n"
" <text>\n"
" <p>Backup.</p>\n"
" </text>\n"
" </command>\n"
" </command-list>\n"
" </operation>\n"
"</doc>\n");
TEST_ERROR(bldHlpParse(storageTest, bldCfgErr), FormatError, "option 'stanza' must have help for command 'backup'");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("parse and render");
HRN_STORAGE_PUT_Z(
storageTest, "src/build/config/config.yaml",
"command:\n"
" backup:\n"
" command-role:\n"
" async: {}\n"
" local: {}\n"
" remote: {}\n"
"\n"
" check: {}\n"
"\n"
" restore:\n"
" internal: true\n"
" command-role:\n"
" local: {}\n"
" remote: {}\n"
"\n"
"optionGroup:\n"
" pg: {}\n"
" repo: {}\n"
"\n"
"option:\n"
" config:\n"
" type: string\n"
" required: false\n"
" command:\n"
" backup:\n"
" internal: true\n"
" restore: {}\n"
"\n"
" buffer-size:\n"
" section: global\n"
" type: integer\n"
" default: 1024\n"
" allow-list: [512, 1024, 2048, 4096]\n"
"\n"
" force:\n"
" type: boolean\n"
" command:\n"
" check: {}\n"
" restore: {}\n"
"\n"
" stanza:\n"
" type: string\n"
" required: false\n"
" deprecate:\n"
" stanza: {}\n"
" stanza1: {}\n"
" stanza2: {}\n"
"\n");
BldCfg bldCfg = bldCfgParse(storageTest);
HRN_STORAGE_PUT_Z(
storageTest, "src/build/help/help.xml",
"<doc>\n"
" <config>\n"
" <config-section-list>\n"
" <config-section id=\"general\" name=\"General\">\n"
" <config-key-list>\n"
" <config-key id=\"buffer-size\" name=\"Buffer Size\">\n"
" <summary>Buffer size for file operations.</summary>\n"
"\n"
" <text>\n"
" <p>Buffer.</p>\n"
" </text>\n"
" </config-key>\n"
"\n"
" <config-key id=\"stanza\" name=\"Stanza\">\n"
" <summary>Defines the stanza.</summary>\n"
"\n"
" <text>\n"
" <p>Stanza.</p>\n"
" </text>\n"
" </config-key>\n"
" </config-key-list>\n"
" </config-section>\n"
" </config-section-list>\n"
" </config>\n"
"\n"
" <operation>\n"
" <operation-general>\n"
" <option-list>\n"
" <option id=\"config\" section=\"stanza\" name=\"Config\">\n"
" <summary><backrest/> configuration file.</summary>\n"
"\n"
" <text>\n"
" <p>Use this option to specify a different configuration file than the default.</p>\n"
" </text>\n"
" </option>\n"
" </option-list>\n"
" </operation-general>\n"
"\n"
" <command-list>\n"
" <command id=\"backup\" name=\"Backup\">\n"
" <summary>Backup cluster.</summary>\n"
"\n"
" <text>\n"
" <p><backrest/> backup.</p>\n"
" </text>\n"
" </command>\n"
"\n"
" <command id=\"check\" name=\"Check\">\n"
" <summary>Check cluster.</summary>\n"
"\n"
" <text>\n"
" <p><backrest/> check.</p>\n"
" </text>\n"
"\n"
" <option-list>\n"
" <option id=\"force\" name=\"Force\">\n"
" <summary>Force delete.</summary>\n"
"\n"
" <text>\n"
" <p>Longer description.</p>\n"
" </text>\n"
" </option>\n"
" </option-list>\n"
" </command>\n"
"\n"
" <command id=\"restore\" name=\"Restore\">\n"
" <summary>Restore cluster.</summary>\n"
"\n"
" <text>\n"
" <p><backrest/> restore.</p>\n"
" </text>\n"
"\n"
" <option-list>\n"
" <option id=\"force\" name=\"Force\">\n"
" <summary>Force delete.</summary>\n"
"\n"
" <text>\n"
" <p>Longer description.</p>\n"
" </text>\n"
" </option>\n"
" </option-list>\n"
" </command>\n"
" </command-list>\n"
" </operation>\n"
"</doc>\n");
TEST_RESULT_STR_Z(
hrnPackToStr(pckReadNewBuf(pckWriteBuf(bldHlpRenderHelpAutoCPack(bldCfg, bldHlpParse(storageTest, bldCfg))))),
"1:array:"
"["
// backup command
"2:str:Backup cluster."
", 3:str:pgBackRest backup."
// check command
", 5:str:Check cluster."
", 6:str:pgBackRest check."
// restore command
", 7:bool:true"
", 8:str:Restore cluster."
", 9:str:pgBackRest restore."
"]"
", 2:array:"
"["
// buffer-size option
"2:str:general"
", 3:str:Buffer size for file operations."
", 4:str:Buffer."
// config option
", 8:str:stanza"
", 9:str:pgBackRest configuration file."
", 10:str:Use this option to specify a different configuration file than the default."
", 12:array:"
"["
// backup command override
"1:obj:"
"{"
"1:bool:true"
"}"
"]"
// force option
", 18:array:"
"["
// check command override
"2:obj:"
"{"
"2:str:Force delete."
", 3:str:Longer description."
"}"
// restore command override
", 3:obj:"
"{"
"2:str:Force delete."
", 3:str:Longer description."
"}"
"]"
// stanza option
", 20:str:general"
", 21:str:Defines the stanza."
", 22:str:Stanza."
", 23:array:"
"["
"1:str:stanza1"
", 2:str:stanza2"
"]"
"]",
"parse and render");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("check help file");
TEST_RESULT_VOID(bldHlpRender(storageTest, bldCfg, bldHlpParse(storageTest, bldCfg)), "write file");
TEST_STORAGE_EXISTS(storageTest, "command/help/help.auto.c");
}
FUNCTION_HARNESS_RETURN_VOID();
}

View File

@ -37,6 +37,10 @@ testRun(void)
{
FUNCTION_HARNESS_VOID();
// Create help data
const BldCfg bldCfg = bldCfgParse(storagePosixNewP(HRN_PATH_REPO_STR));
const Buffer *const helpData = bldHlpRenderHelpAutoCCmp(bldCfg, bldHlpParse(storagePosixNewP(HRN_PATH_REPO_STR), bldCfg));
// Program name a version are used multiple times
const char *helpVersion = PROJECT_NAME " " PROJECT_VERSION;
@ -136,13 +140,13 @@ testRun(void)
argList = strLstNew();
strLstAddZ(argList, "/path/to/pgbackrest");
TEST_RESULT_VOID(testCfgLoad(argList), "help from empty command line");
TEST_RESULT_STR_Z(helpRender(), generalHelp, "check text");
TEST_RESULT_STR_Z(helpRender(helpData), generalHelp, "check text");
argList = strLstNew();
strLstAddZ(argList, "/path/to/pgbackrest");
strLstAddZ(argList, "help");
TEST_RESULT_VOID(testCfgLoad(argList), "help from help command");
TEST_RESULT_STR_Z(helpRender(), generalHelp, "check text");
TEST_RESULT_STR_Z(helpRender(helpData), generalHelp, "check text");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("version command");
@ -161,7 +165,7 @@ testRun(void)
strLstAddZ(argList, "help");
strLstAddZ(argList, "version");
TEST_RESULT_VOID(testCfgLoad(argList), "help for version command");
TEST_RESULT_STR_Z(helpRender(), commandHelp, "check text");
TEST_RESULT_STR_Z(helpRender(helpData), commandHelp, "check text");
// This test is broken up into multiple strings because C99 does not require compilers to support const strings > 4095 bytes
// -------------------------------------------------------------------------------------------------------------------------
@ -321,7 +325,7 @@ testRun(void)
hrnCfgArgRawZ(argList, cfgOptDbInclude, "db1");
hrnCfgArgRawZ(argList, cfgOptDbInclude, "db2");
TEST_RESULT_VOID(testCfgLoad(argList), "help for restore command");
TEST_RESULT_STR_Z(helpRender(), commandHelp, "check text");
TEST_RESULT_STR_Z(helpRender(helpData), commandHelp, "check text");
hrnCfgEnvRemoveRaw(cfgOptRepoCipherPass);
// -------------------------------------------------------------------------------------------------------------------------
@ -334,7 +338,7 @@ testRun(void)
strLstAddZ(argList, "buffer-size");
strLstAddZ(argList, "buffer-size");
TEST_RESULT_VOID(testCfgLoad(argList), "parse too many options");
TEST_ERROR(helpRender(), ParamInvalidError, "only one option allowed for option help");
TEST_ERROR(helpRender(helpData), ParamInvalidError, "only one option allowed for option help");
argList = strLstNew();
strLstAddZ(argList, "/path/to/pgbackrest");
@ -342,7 +346,7 @@ testRun(void)
strLstAddZ(argList, "archive-push");
strLstAddZ(argList, BOGUS_STR);
TEST_RESULT_VOID(testCfgLoad(argList), "parse bogus option");
TEST_ERROR(helpRender(), OptionInvalidError, "option 'BOGUS' is not valid for command 'archive-push'");
TEST_ERROR(helpRender(helpData), OptionInvalidError, "option 'BOGUS' is not valid for command 'archive-push'");
argList = strLstNew();
strLstAddZ(argList, "/path/to/pgbackrest");
@ -350,7 +354,7 @@ testRun(void)
strLstAddZ(argList, CFGCMD_ARCHIVE_PUSH);
strLstAddZ(argList, CFGOPT_PROCESS);
TEST_RESULT_VOID(testCfgLoad(argList), "parse option invalid for command");
TEST_ERROR(helpRender(), OptionInvalidError, "option 'process' is not valid for command 'archive-push'");
TEST_ERROR(helpRender(helpData), OptionInvalidError, "option 'process' is not valid for command 'archive-push'");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("default and current option value");
@ -378,12 +382,13 @@ testRun(void)
strLstAddZ(argList, "archive-push");
strLstAddZ(argList, "buffer-size");
TEST_RESULT_VOID(testCfgLoad(argList), "help for archive-push command, buffer-size option");
TEST_RESULT_STR(helpRender(), strNewFmt("%s\ndefault: 1048576\n", optionHelp), "check text");
TEST_RESULT_STR(helpRender(helpData), strNewFmt("%s\ndefault: 1048576\n", optionHelp), "check text");
// Set a current value
hrnCfgArgRawZ(argList, cfgOptBufferSize, "32768");
TEST_RESULT_VOID(testCfgLoad(argList), "help for archive-push command, buffer-size option");
TEST_RESULT_STR(helpRender(), strNewFmt("%s\ncurrent: 32768\ndefault: 1048576\n", optionHelp), "check text, current value");
TEST_RESULT_STR(
helpRender(helpData), strNewFmt("%s\ncurrent: 32768\ndefault: 1048576\n", optionHelp), "check text, current value");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("deprecated host option names");
@ -411,7 +416,7 @@ testRun(void)
strLstAddZ(argList, "archive-push");
strLstAddZ(argList, "repo1-s3-host");
TEST_RESULT_VOID(testCfgLoad(argList), "help for archive-push command, repo1-s3-host option");
TEST_RESULT_STR_Z(helpRender(), optionHelp, "check text");
TEST_RESULT_STR_Z(helpRender(helpData), optionHelp, "check text");
optionHelp = strZ(strNewFmt(
HELP_OPTION
@ -424,7 +429,7 @@ testRun(void)
hrnCfgArgRawZ(argList, cfgOptRepoType, "s3");
strLstAddZ(argList, "--repo1-s3-host=s3-host");
TEST_RESULT_VOID(testCfgLoad(argList), "help for archive-push command, repo1-s3-host option");
TEST_RESULT_STR_Z(helpRender(), optionHelp, "check text, current value");
TEST_RESULT_STR_Z(helpRender(helpData), optionHelp, "check text, current value");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("cipher pass redacted");
@ -447,7 +452,7 @@ testRun(void)
strLstAddZ(argList, "archive-push");
strLstAddZ(argList, "repo-cipher-pass");
TEST_RESULT_VOID(testCfgLoad(argList), "help for archive-push command, repo1-s3-host option");
TEST_RESULT_STR_Z(helpRender(), optionHelp, "check text");
TEST_RESULT_STR_Z(helpRender(helpData), optionHelp, "check text");
hrnCfgEnvRemoveRaw(cfgOptRepoCipherPass);
// -------------------------------------------------------------------------------------------------------------------------
@ -474,7 +479,7 @@ testRun(void)
strLstAddZ(argList, "backup");
strLstAddZ(argList, "repo-hardlink");
TEST_RESULT_VOID(testCfgLoad(argList), "help for backup command, repo-hardlink option");
TEST_RESULT_STR_Z(helpRender(), optionHelp, "check text");
TEST_RESULT_STR_Z(helpRender(helpData), optionHelp, "check text");
argList = strLstNew();
strLstAddZ(argList, "/path/to/pgbackrest");
@ -482,7 +487,7 @@ testRun(void)
strLstAddZ(argList, "backup");
strLstAddZ(argList, "hardlink");
TEST_RESULT_VOID(testCfgLoad(argList), "help for backup command, deprecated hardlink option");
TEST_RESULT_STR_Z(helpRender(), optionHelp, "check text");
TEST_RESULT_STR_Z(helpRender(helpData), optionHelp, "check text");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("check admonition");
@ -518,7 +523,7 @@ testRun(void)
strLstAddZ(argList, "backup");
strLstAddZ(argList, "repo-retention-archive");
TEST_RESULT_VOID(testCfgLoad(argList), "help for backup command, repo-retention-archive option");
TEST_RESULT_STR_Z(helpRender(), optionHelp, "check admonition text");
TEST_RESULT_STR_Z(helpRender(helpData), optionHelp, "check admonition text");
}
// *****************************************************************************************************************************
@ -534,7 +539,7 @@ testRun(void)
THROW_ON_SYS_ERROR(freopen(TEST_PATH "/stdout.help", "w", stdout) == NULL, FileWriteError, "unable to reopen stdout");
// Not in a test wrapper to avoid writing to stdout
cmdHelp();
cmdHelp(helpData);
// Restore normal stdout
dup2(stdoutSave, STDOUT_FILENO);

View File

@ -34,10 +34,6 @@ use pgBackRestDoc::Common::Log;
use pgBackRestDoc::Common::String;
use pgBackRestDoc::ProjectInfo;
use pgBackRestBuild::Build;
use pgBackRestBuild::Build::Common;
use pgBackRestBuild::Config::BuildHelp;
use pgBackRestTest::Common::BuildTest;
use pgBackRestTest::Common::CodeCountTest;
use pgBackRestTest::Common::ContainerTest;
@ -534,32 +530,8 @@ eval
{
&log(INFO, "autogenerate code");
# Auto-generate C files
#-----------------------------------------------------------------------------------------------------------------------
my $rhBuild =
{
'configHelp' =>
{
&BLD_DATA => buildConfigHelp(),
&BLD_PATH => 'command/help',
},
};
my @stryBuilt = buildAll("${strBackRestBase}/src", $rhBuild);
# Error when checking that files have already been generated but they change
if ($bGenCheck && @stryBuilt)
{
confess &log(
ERROR,
'unexpected autogeneration of C code: ' . join(', ', @stryBuilt) . ":\n" .
trim(executeTest("git -C ${strBackRestBase} diff")));
}
&log(INFO, " autogenerated C code: " . (@stryBuilt ? join(', ', @stryBuilt) : 'no changes'));
# Build code
executeTest("make -C ${strBuildPath} build-config build-error");
executeTest("make -C ${strBuildPath} build-config build-error build-help");
if ($bGenOnly)
{