1
0
mirror of https://github.com/pgbackrest/pgbackrest.git synced 2024-12-04 09:43:08 +02:00

Build command and configuration reference in C.

Migrate generation of these files from help.xml to the intermediate documentation format. This allows us to share a lot of code that is already in C and remove duplicated code in Perl. More duplicate code can be removed in Perl once man generation is migrated.

Also update the unit test harness to allow testing of modules in the doc directory.
This commit is contained in:
David Steele 2023-10-09 14:03:43 -04:00
parent 983cc1a9e3
commit 1eb0162208
25 changed files with 1366 additions and 421 deletions

View File

@ -220,6 +220,24 @@ eval
$rhVariableOverride->{$strKey} = $rhKeyVariableOverride->{$strKey};
}
# Build C code
my $strBuildPath = "${strBasePath}/output/build";
my $strRepoPath = dirname($strBasePath);
my $strBuildNinja = "${strBuildPath}/build.ninja";
&log(INFO, "build C helper");
if (!-e $strBuildNinja)
{
executeTest("meson setup -Dwerror=true -Dfatal-errors=true -Dbuildtype=debug ${strBuildPath} ${strRepoPath}");
}
executeTest("ninja -C ${strBuildPath} doc/src/doc-pgbackrest");
executeTest(
"${strBuildPath}/doc/src/doc-pgbackrest --repo-path=${strRepoPath}" .
($strLogLevel ne 'info' ? " --log-level=${strLogLevel}" : ''),
{bShowOutputAsync => true});
# Load the manifest
my $oManifest = new pgBackRestDoc::Common::DocManifest(
$oStorageDoc, \@stryRequire, \@stryInclude, \@stryExclude, $rhKeyVariableOverride, $rhVariableOverride,

View File

@ -19,18 +19,11 @@ use pgBackRestDoc::ProjectInfo;
####################################################################################################################################
# Help types
####################################################################################################################################
use constant CONFIG_HELP_BETA => 'beta';
use constant CONFIG_HELP_COMMAND => 'command';
push @EXPORT, qw(CONFIG_HELP_COMMAND);
use constant CONFIG_HELP_CURRENT => 'current';
use constant CONFIG_HELP_DEFAULT => 'default';
use constant CONFIG_HELP_DESCRIPTION => 'description';
push @EXPORT, qw(CONFIG_HELP_DESCRIPTION);
use constant CONFIG_HELP_EXAMPLE => 'example';
use constant CONFIG_HELP_INTERNAL => 'internal';
use constant CONFIG_HELP_NAME => 'name';
use constant CONFIG_HELP_NAME_ALT => 'name-alt';
push @EXPORT, qw(CONFIG_HELP_NAME_ALT);
use constant CONFIG_HELP_OPTION => 'option';
push @EXPORT, qw(CONFIG_HELP_OPTION);
use constant CONFIG_HELP_SECTION => 'section';
@ -97,54 +90,6 @@ sub docConfigOptionDefault
push @EXPORT, qw(docConfigOptionDefault);
####################################################################################################################################
# Get the allowed setting range for the option if it exists
####################################################################################################################################
sub docConfigOptionRange
{
my $strOption = shift;
my $strCommand = shift;
# Get the command define
my $oCommandDefine = docConfigCommandDefine($strOption, $strCommand);
# Check for default in command
if (defined($oCommandDefine) && defined($$oCommandDefine{&CFGDEF_ALLOW_RANGE}))
{
return $$oCommandDefine{&CFGDEF_ALLOW_RANGE}[0], $$oCommandDefine{&CFGDEF_ALLOW_RANGE}[1];
}
# If defined return, else try to grab the global default
return $rhConfigDefine->{$strOption}{&CFGDEF_ALLOW_RANGE}[0], $rhConfigDefine->{$strOption}{&CFGDEF_ALLOW_RANGE}[1];
}
push @EXPORT, qw(docConfigOptionRange);
####################################################################################################################################
# Get the option type
####################################################################################################################################
sub docConfigOptionType
{
my $strOption = shift;
return $rhConfigDefine->{$strOption}{&CFGDEF_TYPE};
}
push @EXPORT, qw(docConfigOptionType);
####################################################################################################################################
# Test the option type
####################################################################################################################################
sub docConfigOptionTypeTest
{
my $strOption = shift;
my $strType = shift;
return docConfigOptionType($strOption) eq $strType;
}
push @EXPORT, qw(docConfigOptionTypeTest);
####################################################################################################################################
# CONSTRUCTOR
####################################################################################################################################
@ -317,10 +262,8 @@ sub process
$$oCommandOption{&CONFIG_HELP_SUMMARY} = $oOptionDoc->nodeGet('summary')->textGet();
$$oCommandOption{&CONFIG_HELP_DESCRIPTION} = $oOptionDoc->textGet();
$$oCommandOption{&CONFIG_HELP_EXAMPLE} = $oOptionDoc->fieldGet('example');
$oCommandOption->{&CONFIG_HELP_INTERNAL} =
cfgDefineCommand()->{$strCommand}{&CFGDEF_INTERNAL} ? true : $oOptionDefine->{$strOption}{&CFGDEF_INTERNAL};
$oCommandOption->{&CONFIG_HELP_BETA} = $oOptionDefine->{$strOption}{&CFGDEF_BETA};
# If internal is defined for the option/command it overrides everthing else
if (defined($oOptionDefine->{$strOption}{&CFGDEF_COMMAND}{$strCommand}{&CFGDEF_INTERNAL}))
@ -329,31 +272,6 @@ sub process
$oOptionDefine->{$strOption}{&CFGDEF_COMMAND}{$strCommand}{&CFGDEF_INTERNAL};
}
$$oCommandOption{&CONFIG_HELP_NAME} = $oOptionDoc->paramGet('name');
# Generate a list of alternate names
if (defined($rhConfigDefine->{$strOption}{&CFGDEF_DEPRECATE}))
{
my $rhNameAlt = {};
foreach my $strNameAlt (sort(keys(%{$rhConfigDefine->{$strOption}{&CFGDEF_DEPRECATE}})))
{
$strNameAlt =~ s/\?//g;
if ($strNameAlt ne $strOption)
{
$rhNameAlt->{$strNameAlt} = true;
}
}
my @stryNameAlt = sort(keys(%{$rhNameAlt}));
if (@stryNameAlt > 0)
{
$oCommandOption->{&CONFIG_HELP_NAME_ALT} = \@stryNameAlt;
}
}
# If the option did not come from the command also store in global option list. This prevents duplication of commonly
# used options.
if ($strOptionSource ne CONFIG_HELP_SOURCE_COMMAND)
@ -367,12 +285,8 @@ sub process
$$oOption{&CONFIG_HELP_SECTION} = $strSection;
}
$$oOption{&CONFIG_HELP_NAME} = $oOptionDoc->paramGet('name');
$oOption->{&CONFIG_HELP_NAME_ALT} = $oCommandOption->{&CONFIG_HELP_NAME_ALT};
$$oOption{&CONFIG_HELP_DESCRIPTION} = $$oCommandOption{&CONFIG_HELP_DESCRIPTION};
$$oOption{&CONFIG_HELP_EXAMPLE} = $oOptionDoc->fieldGet('example');
$oOption->{&CONFIG_HELP_INTERNAL} = $oOptionDefine->{$strOption}{&CFGDEF_INTERNAL};
$oOption->{&CONFIG_HELP_BETA} = $oOptionDefine->{$strOption}{&CFGDEF_BETA};
}
}
}
@ -603,313 +517,4 @@ sub manGetFormatText
return $strResult;
}
####################################################################################################################################
# helpConfigDocGet
#
# Get the xml for configuration help.
####################################################################################################################################
sub helpConfigDocGet
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my $strOperation = logDebugParam(__PACKAGE__ . '->helpConfigDocGet');
# Build a hash of the sections
my $oConfigHash = $self->{oConfigHash};
my $oConfigDoc = $self->{oDoc}->nodeGet('config');
my $oSectionHash = {};
foreach my $strOption (sort(keys(%{$$oConfigHash{&CONFIG_HELP_OPTION}})))
{
my $oOption = $$oConfigHash{&CONFIG_HELP_OPTION}{$strOption};
if (defined($$oOption{&CONFIG_HELP_SECTION}))
{
$$oSectionHash{$$oOption{&CONFIG_HELP_SECTION}}{$strOption} = true;
}
}
my $oDoc = new pgBackRestDoc::Common::Doc();
$oDoc->paramSet('title', $oConfigDoc->paramGet('title'));
# set the description for use as a meta tag
$oDoc->fieldSet('description', $oConfigDoc->fieldGet('description'));
# Output the introduction
my $oIntroSectionDoc = $oDoc->nodeAdd('section', undef, {id => 'introduction'});
$oIntroSectionDoc->nodeAdd('title')->textSet('Introduction');
$oIntroSectionDoc->textSet($oConfigDoc->textGet());
foreach my $strSection (sort(keys(%{$oSectionHash})))
{
my $oSectionElement = $oDoc->nodeAdd('section', undef, {id => "section-${strSection}"});
my $oSectionDoc = $oConfigDoc->nodeGet('config-section-list')->nodeGetById('config-section', $strSection);
# Set the summary text for the section
$oSectionElement->textSet($oSectionDoc->textGet());
$oSectionElement->
nodeAdd('title')->textSet(
{name => 'text',
children=> [$oSectionDoc->paramGet('name') . ' Options (', {name => 'id', value => $strSection}, ')']});
foreach my $strOption (sort(keys(%{$$oSectionHash{$strSection}})))
{
# Skip internal options
next if $oConfigHash->{&CONFIG_HELP_OPTION}{$strOption}{&CONFIG_HELP_INTERNAL};
$self->helpOptionGet(undef, $strOption, $oSectionElement, $$oConfigHash{&CONFIG_HELP_OPTION}{$strOption});
}
}
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'oDoc', value => $oDoc}
);
}
####################################################################################################################################
# helpCommandDocGet
#
# Get the xml for command help.
####################################################################################################################################
sub helpCommandDocGet
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my $strOperation = logDebugParam(__PACKAGE__ . '->helpCommandDocGet');
# Working variables
my $oConfigHash = $self->{oConfigHash};
my $oOperationDoc = $self->{oDoc}->nodeGet('operation');
my $oOptionDefine = cfgDefine();
my $oDoc = new pgBackRestDoc::Common::Doc();
$oDoc->paramSet('title', $oOperationDoc->paramGet('title'));
# set the description for use as a meta tag
$oDoc->fieldSet('description', $oOperationDoc->fieldGet('description'));
# Output the introduction
my $oIntroSectionDoc = $oDoc->nodeAdd('section', undef, {id => 'introduction'});
$oIntroSectionDoc->nodeAdd('title')->textSet('Introduction');
$oIntroSectionDoc->textSet($oOperationDoc->textGet());
foreach my $strCommand (sort(keys(%{$$oConfigHash{&CONFIG_HELP_COMMAND}})))
{
# Skip internal commands
next if $oConfigHash->{&CONFIG_HELP_COMMAND}{$strCommand}{&CONFIG_HELP_INTERNAL};
my $oCommandHash = $$oConfigHash{&CONFIG_HELP_COMMAND}{$strCommand};
my $oSectionElement = $oDoc->nodeAdd('section', undef, {id => "command-${strCommand}"});
my $oCommandDoc = $oOperationDoc->nodeGet('command-list')->nodeGetById('command', $strCommand);
$oSectionElement->
nodeAdd('title')->textSet(
{name => 'text',
children=> [$oCommandDoc->paramGet('name') . ' Command (', {name => 'id', value => $strCommand}, ')']});
$oSectionElement->textSet($$oCommandHash{&CONFIG_HELP_DESCRIPTION});
# use Data::doc;
# confess Dumper($oDoc->{oDoc});
if (defined($$oCommandHash{&CONFIG_HELP_OPTION}))
{
my $oCategory = {};
foreach my $strOption (sort(keys(%{$$oCommandHash{&CONFIG_HELP_OPTION}})))
{
# Skip internal options
next if $$oCommandHash{&CONFIG_HELP_OPTION}{$strOption}{&CONFIG_HELP_INTERNAL};
# Skip secure options that can't be defined on the command line
next if ($rhConfigDefine->{$strOption}{&CFGDEF_SECURE});
my ($oOption, $strCategory) = helpCommandDocGetOptionFind($oConfigHash, $oOptionDefine, $strCommand, $strOption);
$$oCategory{$strCategory}{$strOption} = $oOption;
}
# Iterate sections
foreach my $strCategory (sort(keys(%{$oCategory})))
{
my $oOptionListElement = $oSectionElement->nodeAdd(
'section', undef, {id => "category-${strCategory}", toc => 'n'});
$oOptionListElement->
nodeAdd('title')->textSet(ucfirst($strCategory) . ' Options');
# Iterate options
foreach my $strOption (sort(keys(%{$$oCategory{$strCategory}})))
{
$self->helpOptionGet($strCommand, $strOption, $oOptionListElement,
$$oCommandHash{&CONFIG_HELP_OPTION}{$strOption});
}
}
}
}
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'oDoc', value => $oDoc}
);
}
# Helper function for helpCommandDocGet() to find options. The option may be stored with the command or in the option list depending
# on whether it's generic or command-specific
sub helpCommandDocGetOptionFind
{
my $oConfigHelpData = shift;
my $oOptionDefine = shift;
my $strCommand = shift;
my $strOption = shift;
# Get section from the option
my $strSection = $oConfigHelpData->{&CONFIG_HELP_OPTION}{$strOption}{&CONFIG_HELP_SECTION};
# Get option from the command to start
my $oOption = $$oConfigHelpData{&CONFIG_HELP_COMMAND}{$strCommand}{&CONFIG_HELP_OPTION}{$strOption};
# If the option has a section (i.e. not command-line only) then it comes from the standard option reference
if ($$oOption{&CONFIG_HELP_SOURCE} eq CONFIG_HELP_SOURCE_SECTION)
{
$oOption = $$oConfigHelpData{&CONFIG_HELP_OPTION}{$strOption};
}
# Reduce the sections that are shown in the command help. This is the same logic as help.c.
if (!defined($strSection) ||
($strSection ne 'general' && $strSection ne 'log' && $strSection ne 'repository' && $strSection ne 'stanza'))
{
$strSection = 'command';
}
return $oOption, $strSection;
}
####################################################################################################################################
# helpOptionGet
#
# Get the xml for an option.
####################################################################################################################################
sub helpOptionGet
{
my $self = shift;
my $strCommand = shift;
my $strOption = shift;
my $oParentElement = shift;
my $oOptionHash = shift;
# Create the option section
my $oOptionElement = $oParentElement->nodeAdd(
'section', undef, {id => "option-${strOption}", toc => defined($strCommand) ? 'n' : 'y'});
# Set the option section title
$oOptionElement->
nodeAdd('title')->textSet(
{name => 'text',
children=> [$$oOptionHash{&CONFIG_HELP_NAME} . ' Option (', {name => 'id', value => "--${strOption}"}, ')']});
# Add the option summary and description
$oOptionElement->
nodeAdd('p')->textSet($$oOptionHash{&CONFIG_HELP_SUMMARY});
# Add beta warning
if ($$oOptionHash{&CONFIG_HELP_BETA})
{
$oOptionElement->nodeAdd('p')->textSet({name => 'text', children => ['FOR BETA TESTING ONLY. DO NOT USE IN PRODUCTION.']});
}
$oOptionElement->
nodeAdd('p')->textSet($$oOptionHash{&CONFIG_HELP_DESCRIPTION});
# Get the default value (or required=n if there is no default)
my $strCodeBlock;
if (defined(docConfigOptionDefault($strOption, $strCommand)))
{
my $strDefault;
if ($strOption eq CFGOPT_REPO_HOST_CMD || $strOption eq CFGOPT_PG_HOST_CMD)
{
$strDefault = '[INSTALL-PATH]/' . PROJECT_EXE;
}
else
{
if (docConfigOptionTypeTest($strOption, CFGDEF_TYPE_BOOLEAN))
{
$strDefault = docConfigOptionDefault($strOption, $strCommand) ? 'y' : 'n';
}
else
{
$strDefault = docConfigOptionDefault($strOption, $strCommand);
}
}
$strCodeBlock = "default: ${strDefault}";
}
# This won't work correctly until there is some notion of dependency
# elsif (optionRequired($strOption, $strCommand))
# {
# $strCodeBlock = 'required: y';
# }
# Get the allowed range if it exists
my ($strRangeMin, $strRangeMax) = docConfigOptionRange($strOption, $strCommand);
if (defined($strRangeMin))
{
$strCodeBlock .= (defined($strCodeBlock) ? "\n" : '') . "allowed: ${strRangeMin}-${strRangeMax}";
}
# Get the example
my $strExample = '';
my $strOptionPrefix = $rhConfigDefine->{$strOption}{&CFGDEF_GROUP};
my $strOptionIndex = defined($strOptionPrefix) ?
"${strOptionPrefix}1-" . substr($strOption, length($strOptionPrefix) + 1) : $strOption;
if (defined($strCommand) && docConfigOptionTypeTest($strOption, CFGDEF_TYPE_BOOLEAN))
{
if ($$oOptionHash{&CONFIG_HELP_EXAMPLE} ne 'n' && $$oOptionHash{&CONFIG_HELP_EXAMPLE} ne 'y')
{
confess &log(ERROR, "option ${strOption} example should be boolean but value is: " .
$$oOptionHash{&CONFIG_HELP_EXAMPLE});
}
$strExample = '--' . ($$oOptionHash{&CONFIG_HELP_EXAMPLE} eq 'n' ? 'no-' : '') . $strOptionIndex;
}
else
{
if (defined($strCommand))
{
$strExample = '--';
}
$strExample .= "${strOptionIndex}=" . $$oOptionHash{&CONFIG_HELP_EXAMPLE};
}
$strCodeBlock .= (defined($strCodeBlock) ? "\n" : '') . "example: ${strExample}";
$oOptionElement->
nodeAdd('code-block')->valueSet($strCodeBlock);
# Output deprecated names
if (defined($oOptionHash->{&CONFIG_HELP_NAME_ALT}))
{
my $strCaption = 'Deprecated Name' . (@{$oOptionHash->{&CONFIG_HELP_NAME_ALT}} > 1 ? 's' : '');
$oOptionElement->
nodeAdd('p')->textSet("${strCaption}: " . join(', ', @{$oOptionHash->{&CONFIG_HELP_NAME_ALT}}));
}
}
1;

View File

@ -204,22 +204,7 @@ sub new
new pgBackRestDoc::Common::DocConfig(${$self->{oManifest}->sourceGet('help')}{doc}, $self);
}
if (defined($$oRenderOut{source}) && $$oRenderOut{source} eq 'help' && $self->{oManifest}->isBackRest())
{
if ($self->{strRenderOutKey} eq 'configuration')
{
$self->{oDoc} = $self->{oReference}->helpConfigDocGet();
}
elsif ($self->{strRenderOutKey} eq 'command')
{
$self->{oDoc} = $self->{oReference}->helpCommandDocGet();
}
else
{
confess &log(ERROR, "cannot render $self->{strRenderOutKey} from source $$oRenderOut{source}");
}
}
elsif (defined($$oRenderOut{source}) && $$oRenderOut{source} eq 'release' && $self->{oManifest}->isBackRest())
if (defined($$oRenderOut{source}) && $$oRenderOut{source} eq 'release' && $self->{oManifest}->isBackRest())
{
require pgBackRestDoc::Custom::DocCustomRelease;
pgBackRestDoc::Custom::DocCustomRelease->import();

View File

@ -86,6 +86,8 @@
<source key="index"/>
<source key="user-guide-index"/>
<source key="user-guide"/>
<source key="command" file="output/xml/command.xml"/>
<source key="configuration" file="output/xml/configuration.xml"/>
<source key="help" type="custom" file="../src/build/help/help.xml"/>
<source key="release" type="custom"/>
<source key="faq"/>
@ -101,8 +103,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="help" menu="Configuration"/>
<render-source key="command" source="help" menu="Commands"/>
<render-source key="configuration" source="configuration" menu="Configuration"/>
<render-source key="command" source="command" menu="Commands"/>
<render-source key="faq" menu="FAQ"/>
<render-source key="metric" menu="Metrics"/>
</render>

View File

@ -0,0 +1,97 @@
####################################################################################################################################
# Configuration Definition
####################################################################################################################################
####################################################################################################################################
# Commands
####################################################################################################################################
command:
help:
log-level-default: DEBUG
parameter-allowed: true
build:
parameter-allowed: true
noop:
internal: true
version:
log-level-default: DEBUG
####################################################################################################################################
# Option groups that are not used but must be present for modules to compile
####################################################################################################################################
optionGroup:
pg: {}
repo: {}
####################################################################################################################################
# Options
####################################################################################################################################
option:
# General options
#---------------------------------------------------------------------------------------------------------------------------------
buffer-size:
type: size
internal: true
default: 64KiB
allow-list:
- 16KiB
- 32KiB
- 64KiB
- 128KiB
- 256KiB
- 512KiB
- 1MiB
neutral-umask:
type: boolean
internal: true
default: true
repo-path:
type: string
default: pgbackrest
command:
build: {}
# Logging options
#---------------------------------------------------------------------------------------------------------------------------------
log-level:
type: string-id
default: info
allow-list:
- off
- error
- warn
- info
- detail
- debug
- trace
log-timestamp:
type: boolean
default: true
negate: true
command: log-level
# Options that are not used but must be present for modules to compile. All must have a default or not be required.
#---------------------------------------------------------------------------------------------------------------------------------
beta: {type: boolean, default: false, command: {noop: {}}}
compress-level-network: {type: string, required: false, command: {noop: {}}}
config: {
type: string, internal: true, default: CFGOPTDEF_CONFIG_PATH "/" PROJECT_CONFIG_FILE, default-literal: true, negate: true}
config-path: {type: string, required: false, command: {noop: {}}}
config-include-path: {
type: string, default: CFGOPTDEF_CONFIG_PATH "/" PROJECT_CONFIG_INCLUDE_PATH, default-literal: true, command: {noop: {}}}
job-retry: {type: string, required: false, deprecate: {job-retry-old: {}}, command: {noop: {}}}
job-retry-interval: {type: string, required: false, command: {noop: {}}}
log-level-file: {type: string, required: false, command: {noop: {}}}
log-level-stderr: {type: string, required: false, command: {noop: {}}}
pg: {type: string, required: false, command: {noop: {}}}
pg-path: {type: string, required: false, command: {noop: {}}}
repo-type: {type: string, required: false, command: {noop: {}}}
repo: {type: string, required: false, command: {noop: {}}}
spool-path: {type: string, required: false, command: {noop: {}}}
stanza: {type: string, required: false, command: {noop: {}}}

122
doc/src/build/help/help.xml Normal file
View File

@ -0,0 +1,122 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE doc SYSTEM "doc.dtd">
<doc>
<config><config-section-list></config-section-list></config>
<operation>
<operation-general>
<option-list>
<option id="beta"><summary></summary><text><p></p></text></option>
<option id="config"><summary></summary><text><p></p></text></option>
<option id="config-path"><summary></summary><text><p></p></text></option>
<option id="config-include-path"><summary></summary><text><p></p></text></option>
<option id="pg"><summary></summary><text><p></p></text></option>
<option id="pg-path"><summary></summary><text><p></p></text></option>
<option id="compress-level-network"><summary></summary><text><p></p></text></option>
<option id="job-retry"><summary></summary><text><p></p></text></option>
<option id="job-retry-interval"><summary></summary><text><p></p></text></option>
<option id="log-level-file"><summary></summary><text><p></p></text></option>
<option id="log-level-stderr"><summary></summary><text><p></p></text></option>
<option id="repo"><summary></summary><text><p></p></text></option>
<option id="repo-type"><summary></summary><text><p></p></text></option>
<option id="spool-path"><summary></summary><text><p></p></text></option>
<option id="stanza"><summary></summary><text><p></p></text></option>
<option id="buffer-size">
<summary>Buffer size for I/O operations.</summary>
<text>
<p>Buffer size used for copy, compress, encrypt, and other operations. The number of buffers used depends on options and each operation may use additional memory, e.g. <id>gz</id> compression may use an additional 256KiB of memory.</p>
<p>Allowed values are <id>16KiB</id>, <id>32KiB</id>, <id>64KiB</id>, <id>128KiB</id>, <id>256KiB</id>, <id>512KiB</id>, and <id>1MiB</id>.</p>
</text>
<example>1MiB</example>
</option>
<option id="log-level">
<summary>Level for harness logging.</summary>
<text>
<p>Log level for the test harness (as opposed to the test being run).</p>
<p>The following log levels are supported:</p>
<list>
<list-item><id>off</id> - No logging at all (not recommended)</list-item>
<list-item><id>error</id> - Log only errors</list-item>
<list-item><id>warn</id> - Log warnings and errors</list-item>
<list-item><id>info</id> - Log info, warnings, and errors</list-item>
<list-item><id>detail</id> - Log detail, info, warnings, and errors</list-item>
<list-item><id>debug</id> - Log debug, detail, info, warnings, and errors</list-item>
<list-item><id>trace</id> - Log trace (very verbose debugging), debug, info, warnings, and errors</list-item>
</list>
</text>
<example>error</example>
</option>
<option id="log-timestamp">
<summary>Enable timestamp in logging.</summary>
<text>
<p>Enables the timestamp in console and file logging. This option is disabled in special situations such as generating documentation.</p>
</text>
<example>n</example>
</option>
<option id="neutral-umask">
<summary>Use a neutral umask.</summary>
<text>
<p>Sets the umask to 0000 so modes are created in a sensible way. The default directory mode is 0750 and default file mode is 0640. The lock and log directories set the directory and file mode to 0770 and 0660 respectively.</p>
<p>To use the executing user's umask instead specify <setting>neutral-umask=n</setting> in the config file or <setting>--no-neutral-umask</setting> on the command line.</p>
</text>
<example>n</example>
</option>
<option id="repo-path">
<summary>Path to code repository.</summary>
<text>
<p>Path to the original code repository.</p>
</text>
<example>/path/to/pgbackrest</example>
</option>
</option-list>
</operation-general>
<command-list>
<command id="help">
<summary>Get help.</summary>
<text>
<p>Three levels of help are provided. If no command is specified then general help will be displayed. If a command is specified (e.g. <cmd>pgbackrest help backup</cmd>) then a full description of the command will be displayed along with a list of valid options. If an option is specified in addition to a command (e.g. <cmd>pgbackrest help backup type</cmd>) then a full description of the option as it applies to the command will be displayed.</p>
</text>
</command>
<!-- Noop command holds options that must be defined but we don't need -->
<command id="noop"><summary></summary><text><p></p></text></command>
<command id="build">
<summary>Build documentation.</summary>
<text>
<p>Build the specified documentation module.</p>
</text>
</command>
<command id="version">
<summary>Get version.</summary>
<text>
<p>Displays installed <backrest/> version.</p>
</text>
</command>
</command-list>
</operation>
</doc>

View File

@ -0,0 +1,36 @@
/***********************************************************************************************************************************
Build Command
***********************************************************************************************************************************/
#include "build.auto.h"
#include "command/build/reference.h"
#include "common/debug.h"
#include "common/log.h"
#include "storage/posix/storage.h"
#include "version.h"
/**********************************************************************************************************************************/
void
cmdBuild(const String *const pathRepo)
{
FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_LOG_PARAM(STRING, pathRepo);
FUNCTION_LOG_END();
MEM_CONTEXT_TEMP_BEGIN()
{
Storage *const storageRepo = storagePosixNewP(pathRepo, .write = true);
const BldCfg bldCfg = bldCfgParse(storageRepo);
const BldHlp bldHlp = bldHlpParse(storageRepo, bldCfg, true);
storagePutP(
storageNewWriteP(storageRepo, STRDEF("doc/output/xml/command.xml")),
xmlDocumentBuf(referenceCommandRender(&bldCfg, &bldHlp)));
storagePutP(
storageNewWriteP(storageRepo, STRDEF("doc/output/xml/configuration.xml")),
xmlDocumentBuf(referenceConfigurationRender(&bldCfg, &bldHlp)));
}
MEM_CONTEXT_TEMP_END();
FUNCTION_LOG_RETURN_VOID();
}

View File

@ -0,0 +1,14 @@
/***********************************************************************************************************************************
Build Documentation
***********************************************************************************************************************************/
#ifndef DOC_COMMAND_BUILD_H
#define DOC_COMMAND_BUILD_H
#include "common/type/string.h"
/***********************************************************************************************************************************
Functions
***********************************************************************************************************************************/
void cmdBuild(const String *pathRepo);
#endif

View File

@ -0,0 +1,339 @@
/***********************************************************************************************************************************
Build Command
***********************************************************************************************************************************/
#include "build.auto.h"
#include "build/config/parse.h"
#include "build/help/parse.h"
#include "command/build/build.h"
#include "common/debug.h"
#include "common/log.h"
#include "storage/posix/storage.h"
#include "storage/storage.h"
#include "version.h"
/***********************************************************************************************************************************
Build option
***********************************************************************************************************************************/
static void
referenceOptionRender(
XmlNode *const xmlSection, const BldCfgOptionCommand *const optCmdCfg, const BldCfgOption *const optCfg,
const BldHlpOption *const optHlp)
{
FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_LOG_PARAM(XML_NODE, xmlSection);
FUNCTION_LOG_PARAM_P(VOID, optCmdCfg);
FUNCTION_LOG_PARAM_P(VOID, optCfg);
FUNCTION_LOG_PARAM_P(VOID, optHlp);
FUNCTION_LOG_END();
MEM_CONTEXT_TEMP_BEGIN()
{
XmlNode *const xmlOption = xmlNodeAdd(xmlSection, STRDEF("section"));
XmlNode *const xmlOptionTitle = xmlNodeAdd(xmlOption, STRDEF("title"));
xmlNodeAttributeSet(xmlOption, STRDEF("id"), strNewFmt("option-%s", strZ(optHlp->name)));
xmlNodeContentSet(xmlOptionTitle, strNewFmt("%s Option (", strZ(optHlp->title)));
xmlNodeContentSet(xmlNodeAdd(xmlOptionTitle, STRDEF("id")), strNewFmt("--%s", strZ(optHlp->name)));
xmlNodeContentSet(xmlOptionTitle, STRDEF(")"));
xmlNodeChildAdd(xmlNodeAdd(xmlOption, STRDEF("p")), optHlp->summary);
if (optCfg->beta)
xmlNodeContentSet(xmlNodeAdd(xmlOption, STRDEF("p")), STRDEF("FOR BETA TESTING ONLY. DO NOT USE IN PRODUCTION."));
xmlNodeChildAdd(xmlNodeAdd(xmlOption, STRDEF("p")), optHlp->description);
// Add default value
StringList *const blockList = strLstNew();
const String *const defaultValue =
optCmdCfg != NULL && optCmdCfg->defaultValue != NULL ? optCmdCfg->defaultValue : optCfg->defaultValue;
if (defaultValue != NULL)
{
if (strEq(optCfg->type, OPT_TYPE_BOOLEAN_STR))
strLstAddFmt(blockList, "default: %s", strEqZ(defaultValue, "true") ? "y" : "n");
else
strLstAddFmt(blockList, "default: %s", strZ(defaultValue));
}
// Add allow range
if (optCfg->allowRangeMin != NULL)
{
ASSERT(optCfg->allowRangeMax != NULL);
strLstAddFmt(blockList, "allowed: %s-%s", strZ(optCfg->allowRangeMin), strZ(optCfg->allowRangeMax));
}
// Add examples
if (optHlp->exampleList != NULL)
{
String *output = strCatZ(strNew(), "example: ");
const String *const option =
optCfg->group != NULL ?
strNewFmt("%s1%s", strZ(optCfg->group), strZ(strSub(optCfg->name, strSize(optCfg->group)))) : optCfg->name;
for (unsigned int exampleIdx = 0; exampleIdx < strLstSize(optHlp->exampleList); exampleIdx++)
{
const String *const example = strLstGet(optHlp->exampleList, exampleIdx);
// If Command line example
if (optCmdCfg != NULL)
{
if (exampleIdx != 0)
strCatZ(output, " ");
strCatZ(output, "--");
if (strEq(optCfg->type, OPT_TYPE_BOOLEAN_STR) && strEqZ(example, "n"))
strCatZ(output, "no-");
}
// Else configuration file example
else if (exampleIdx != 0)
{
strLstAdd(blockList, output);
output = strCatZ(strNew(), "example: ");
}
strCat(output, option);
if (optCmdCfg == NULL || !strEq(optCfg->type, OPT_TYPE_BOOLEAN_STR))
strCatFmt(output, "=%s", strZ(example));
}
strLstAdd(blockList, output);
}
if (!strLstEmpty(blockList))
xmlNodeContentSet(xmlNodeAdd(xmlOption, STRDEF("code-block")), strLstJoin(blockList, "\n"));
// Add deprecated names
if (optCfg->deprecateList != NULL)
{
String *const deprecateStr = strNew();
for (unsigned int deprecateIdx = 0; deprecateIdx < lstSize(optCfg->deprecateList); deprecateIdx++)
{
const BldCfgOptionDeprecate *const deprecate = lstGet(optCfg->deprecateList, deprecateIdx);
if (!strEq(deprecate->name, optCfg->name))
strCatFmt(deprecateStr, "%s %s", !strEmpty(deprecateStr) ? "," : "", strZ(deprecate->name));
}
if (!strEmpty(deprecateStr))
{
xmlNodeContentSet(
xmlNodeAdd(xmlOption, STRDEF("p")),
strNewFmt("Deprecated Name%s:%s", lstSize(optCfg->deprecateList) > 1 ? "s" : "", strZ(deprecateStr)));
}
}
}
MEM_CONTEXT_TEMP_END();
FUNCTION_LOG_RETURN_VOID();
}
/**********************************************************************************************************************************/
XmlDocument *
referenceConfigurationRender(const BldCfg *const bldCfg, const BldHlp *const bldHlp)
{
FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_LOG_PARAM_P(VOID, bldCfg);
FUNCTION_LOG_PARAM_P(VOID, bldHlp);
FUNCTION_LOG_END();
XmlDocument *const result = xmlDocumentNewP(STRDEF("doc"), .dtdName = STRDEF("doc"), .dtdFile = STRDEF("doc.dtd"));
MEM_CONTEXT_TEMP_BEGIN()
{
XmlNode *const xmlRoot = xmlDocumentRoot(result);
// Set attributes in root node
xmlNodeAttributeSet(xmlRoot, STRDEF("title"), STRDEF("{[project]}"));
xmlNodeAttributeSet(xmlRoot, STRDEF("subtitle"), bldHlp->optTitle);
xmlNodeAttributeSet(xmlRoot, STRDEF("toc"), STRDEF("y"));
// Set description
xmlNodeContentSet(xmlNodeAdd(xmlRoot, STRDEF("description")), bldHlp->optDescription);
// Set introduction
XmlNode *const xmlIntro = xmlNodeAdd(xmlRoot, STRDEF("section"));
xmlNodeAttributeSet(xmlIntro, STRDEF("id"), STRDEF("introduction"));
xmlNodeContentSet(xmlNodeAdd(xmlIntro, STRDEF("title")), STRDEF("Introduction"));
xmlNodeChildAdd(xmlNodeAdd(xmlIntro, STRDEF("text")), bldHlp->optIntroduction);
for (unsigned int sctIdx = 0; sctIdx < lstSize(bldHlp->sctList); sctIdx++)
{
const BldHlpSection *const section = lstGet(bldHlp->sctList, sctIdx);
XmlNode *const xmlSection = xmlNodeAdd(xmlRoot, STRDEF("section"));
XmlNode *const xmlSectionTitle = xmlNodeAdd(xmlSection, STRDEF("title"));
xmlNodeAttributeSet(xmlSection, STRDEF("id"), strNewFmt("section-%s", strZ(section->id)));
xmlNodeContentSet(xmlSectionTitle, strNewFmt("%s Options (", strZ(section->name)));
xmlNodeContentSet(xmlNodeAdd(xmlSectionTitle, STRDEF("id")), section->id);
xmlNodeContentSet(xmlSectionTitle, STRDEF(")"));
xmlNodeChildAdd(xmlNodeAdd(xmlSection, STRDEF("text")), section->introduction);
for (unsigned int optIdx = 0; optIdx < lstSize(bldHlp->optList); optIdx++)
{
const BldHlpOption *const optHlp = lstGet(bldHlp->optList, optIdx);
const BldCfgOption *const optCfg = lstFind(bldCfg->optList, &optHlp->name);
ASSERT(optCfg != NULL);
if (!strEq(optHlp->section == NULL ? STRDEF("general") : optHlp->section, section->id))
continue;
// Skip if option is internal
if (optCfg->internal)
continue;
referenceOptionRender(xmlSection, NULL, optCfg, optHlp);
}
}
}
MEM_CONTEXT_TEMP_END();
FUNCTION_LOG_RETURN(XML_DOCUMENT, result);
}
/**********************************************************************************************************************************/
// Helper to remap section names for the command reference
static String *
referenceCommandSection(const String *const section)
{
FUNCTION_TEST_BEGIN();
FUNCTION_TEST_PARAM(STRING, section);
FUNCTION_TEST_END();
if (section == NULL)
FUNCTION_TEST_RETURN(STRING, strNewZ("general"));
if (!strEqZ(section, "general") && !strEqZ(section, "log") && !strEqZ(section, "repository") && !strEqZ(section, "stanza"))
FUNCTION_TEST_RETURN(STRING, strNewZ("command"));
FUNCTION_TEST_RETURN(STRING, strDup(section));
}
XmlDocument *
referenceCommandRender(const BldCfg *const bldCfg, const BldHlp *const bldHlp)
{
FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_LOG_PARAM_P(VOID, bldCfg);
FUNCTION_LOG_PARAM_P(VOID, bldHlp);
FUNCTION_LOG_END();
XmlDocument *const result = xmlDocumentNewP(STRDEF("doc"), .dtdName = STRDEF("doc"), .dtdFile = STRDEF("doc.dtd"));
MEM_CONTEXT_TEMP_BEGIN()
{
XmlNode *const xmlRoot = xmlDocumentRoot(result);
// Set attributes in root node
xmlNodeAttributeSet(xmlRoot, STRDEF("title"), STRDEF("{[project]}"));
xmlNodeAttributeSet(xmlRoot, STRDEF("subtitle"), bldHlp->cmdTitle);
xmlNodeAttributeSet(xmlRoot, STRDEF("toc"), STRDEF("y"));
// Set description
xmlNodeContentSet(xmlNodeAdd(xmlRoot, STRDEF("description")), bldHlp->cmdDescription);
// Set introduction
XmlNode *const xmlIntro = xmlNodeAdd(xmlRoot, STRDEF("section"));
xmlNodeAttributeSet(xmlIntro, STRDEF("id"), STRDEF("introduction"));
xmlNodeContentSet(xmlNodeAdd(xmlIntro, STRDEF("title")), STRDEF("Introduction"));
xmlNodeChildAdd(xmlNodeAdd(xmlIntro, STRDEF("text")), bldHlp->cmdIntroduction);
for (unsigned int cmdIdx = 0; cmdIdx < lstSize(bldHlp->cmdList); cmdIdx++)
{
const BldHlpCommand *const cmdHlp = lstGet(bldHlp->cmdList, cmdIdx);
const BldCfgCommand *const cmdCfg = lstFind(bldCfg->cmdList, &cmdHlp->name);
ASSERT(cmdCfg != NULL);
// Skip internal commands
if (cmdCfg->internal)
continue;
XmlNode *const xmlSection = xmlNodeAdd(xmlRoot, STRDEF("section"));
XmlNode *const xmlSectionTitle = xmlNodeAdd(xmlSection, STRDEF("title"));
xmlNodeAttributeSet(xmlSection, STRDEF("id"), strNewFmt("command-%s", strZ(cmdHlp->name)));
xmlNodeContentSet(xmlSectionTitle, strNewFmt("%s Command (", strZ(cmdHlp->title)));
xmlNodeContentSet(xmlNodeAdd(xmlSectionTitle, STRDEF("id")), cmdHlp->name);
xmlNodeContentSet(xmlSectionTitle, STRDEF(")"));
xmlNodeChildAdd(xmlNodeAdd(xmlSection, STRDEF("text")), cmdHlp->description);
// Build option list for command
StringList *const sctList = strLstNew();
List *const optCfgList = lstNewP(sizeof(BldCfgOption));
List *const optHlpList = lstNewP(sizeof(BldHlpOption));
for (unsigned int optIdx = 0; optIdx < lstSize(bldCfg->optList); optIdx++)
{
const BldCfgOption *const optCfg = lstGet(bldCfg->optList, optIdx);
// Skip internal options
if (optCfg->internal || optCfg->secure)
continue;
// Skip options that are not valid, internal, or do not have the main role for this command
const BldCfgOptionCommand *const optCmdCfg = lstFind(optCfg->cmdList, &cmdCfg->name);
if (optCmdCfg == NULL || optCmdCfg->internal || !strLstExists(optCmdCfg->roleList, STRDEF("main")))
continue;
// Get help from command list or general list
BldHlpOption optHlp = {0};
if (cmdHlp->optList != NULL && lstFind(cmdHlp->optList, &optCfg->name) != NULL)
{
optHlp = *((const BldHlpOption *)lstFind(cmdHlp->optList, &optCfg->name));
optHlp.section = strNewZ("command");
}
else
{
ASSERT(lstFind(bldHlp->optList, &optCfg->name) != NULL);
optHlp = *((const BldHlpOption *)lstFind(bldHlp->optList, &optCfg->name));
}
// Remap section name
optHlp.section = referenceCommandSection(optHlp.section);
// Add sections
strLstAddIfMissing(sctList, optHlp.section);
// Add options
lstAdd(optCfgList, optCfg);
lstAdd(optHlpList, &optHlp);
}
strLstSort(sctList, sortOrderAsc);
for (unsigned int sctIdx = 0; sctIdx < strLstSize(sctList); sctIdx++)
{
const String *const section = strLstGet(sctList, sctIdx);
XmlNode *const xmlCommandCategory = xmlNodeAdd(xmlSection, STRDEF("section"));
XmlNode *const xmlCommandCategoryTitle = xmlNodeAdd(xmlCommandCategory, STRDEF("title"));
xmlNodeAttributeSet(xmlCommandCategory, STRDEF("id"), strNewFmt("category-%s", strZ(section)));
xmlNodeAttributeSet(xmlCommandCategory, STRDEF("toc"), STRDEF("n"));
xmlNodeContentSet(xmlCommandCategoryTitle, strNewFmt("%s Options", strZ(strFirstUpper(strDup(section)))));
for (unsigned int optIdx = 0; optIdx < lstSize(optHlpList); optIdx++)
{
const BldHlpOption *const optHlp = lstGet(optHlpList, optIdx);
const BldCfgOption *const optCfg = lstGet(optCfgList, optIdx);
ASSERT(optCfg != NULL);
const BldCfgOptionCommand *const optCmdCfg = lstFind(optCfg->cmdList, &cmdCfg->name);
ASSERT(optCmdCfg != NULL);
if (!strEq(optHlp->section, section))
continue;
referenceOptionRender(xmlCommandCategory, optCmdCfg, optCfg, optHlp);
}
}
}
}
MEM_CONTEXT_TEMP_END();
FUNCTION_LOG_RETURN(XML_DOCUMENT, result);
}

View File

@ -0,0 +1,20 @@
/***********************************************************************************************************************************
Build Command and Configuration Reference
***********************************************************************************************************************************/
#ifndef DOC_COMMAND_BUILD_REFERENCE_H
#define DOC_COMMAND_BUILD_REFERENCE_H
#include "build/config/parse.h"
#include "build/help/parse.h"
#include "common/type/xml.h"
/***********************************************************************************************************************************
Functions
***********************************************************************************************************************************/
// Build command reference
XmlDocument *referenceCommandRender(const BldCfg *bldCfg, const BldHlp *bldHlp);
// Build configuration reference
XmlDocument *referenceConfigurationRender(const BldCfg *bldCfg, const BldHlp *bldHlp);
#endif

View File

@ -0,0 +1,17 @@
####################################################################################################################################
# Generate help
####################################################################################################################################
doc_help_auto_c_inc = custom_target(
'doc_help.auto.c.inc',
output: 'help.auto.c.inc',
depend_files: [
'../../build/config/config.yaml',
'../../build/help/help.xml',
],
command: [
build_code,
'help',
'@CURRENT_SOURCE_DIR@/../../..',
'@BUILD_ROOT@/doc',
],
)

123
doc/src/config/load.c Normal file
View File

@ -0,0 +1,123 @@
/***********************************************************************************************************************************
Configuration Load
***********************************************************************************************************************************/
#include "build.auto.h"
#include <unistd.h>
#include "command/command.h"
#include "common/debug.h"
#include "common/io/io.h"
#include "common/log.h"
#include "config/config.intern.h"
#include "storage/posix/storage.h"
/***********************************************************************************************************************************
Load log settings
***********************************************************************************************************************************/
static void
cfgLoadLogSetting(void)
{
FUNCTION_LOG_VOID(logLevelTrace);
// Initialize logging
LogLevel logLevelConsole = logLevelOff;
bool logTimestamp = true;
if (cfgOptionValid(cfgOptLogLevel))
logLevelConsole = logLevelEnum(cfgOptionStrId(cfgOptLogLevel));
if (cfgOptionValid(cfgOptLogTimestamp))
logTimestamp = cfgOptionBool(cfgOptLogTimestamp);
logInit(logLevelConsole, logLevelOff, logLevelOff, logTimestamp, 0, 1, false);
FUNCTION_LOG_RETURN_VOID();
}
/***********************************************************************************************************************************
Update options that have complex rules
***********************************************************************************************************************************/
static void
cfgLoadUpdateOption(void)
{
FUNCTION_LOG_VOID(logLevelTrace);
// Get current working dir
char currentWorkDir[1024];
THROW_ON_SYS_ERROR(getcwd(currentWorkDir, sizeof(currentWorkDir)) == NULL, FormatError, "unable to get cwd");
// Invalidate config option so it does not show up in option list
cfgOptionInvalidate(cfgOptConfig);
// If repo-path is relative then make it absolute
const String *const repoPath = cfgOptionStr(cfgOptRepoPath);
if (!strBeginsWithZ(repoPath, "/"))
cfgOptionSet(cfgOptRepoPath, cfgOptionSource(cfgOptRepoPath), VARSTR(strNewFmt("%s/%s", currentWorkDir, strZ(repoPath))));
FUNCTION_LOG_RETURN_VOID();
}
/**********************************************************************************************************************************/
void
cfgLoad(unsigned int argListSize, const char *argList[])
{
FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_LOG_PARAM(UINT, argListSize);
FUNCTION_LOG_PARAM(CHARPY, argList);
FUNCTION_LOG_END();
MEM_CONTEXT_TEMP_BEGIN()
{
// Make a copy of the arguments so they can be manipulated
StringList *const argListNew = strLstNew();
for (unsigned int argListIdx = 0; argListIdx < argListSize; argListIdx++)
strLstAddZ(argListNew, argList[argListIdx]);
// Explicitly set --no-config so a stray config file will not be loaded
strLstAddZ(argListNew, "--no-" CFGOPT_CONFIG);
// Parse config from command line
TRY_BEGIN()
{
cfgParseP(storagePosixNewP(FSLASH_STR), strLstSize(argListNew), strLstPtr(argListNew));
}
CATCH(CommandRequiredError)
{
strLstAddZ(argListNew, CFGCMD_BUILD);
cfgParseP(storagePosixNewP(FSLASH_STR), strLstSize(argListNew), strLstPtr(argListNew));
}
TRY_END();
// Error on noop command. This command is required to hold options that must be declared but are unused by test.
if (cfgCommand() == cfgCmdNoop)
THROW(CommandInvalidError, "invalid command '" CFGCMD_NOOP "'");
// If a command is set
if (cfgCommand() != cfgCmdNone && cfgCommand() != cfgCmdHelp && cfgCommand() != cfgCmdVersion)
{
// Load the log settings
if (!cfgCommandHelp())
cfgLoadLogSetting();
// Neutralize the umask to make the repository file/path modes more consistent
if (cfgOptionValid(cfgOptNeutralUmask) && cfgOptionBool(cfgOptNeutralUmask))
umask(0000);
// Set IO buffer size
if (cfgOptionValid(cfgOptBufferSize))
ioBufferSizeSet(cfgOptionUInt(cfgOptBufferSize));
// Update options that have complex rules
cfgLoadUpdateOption();
// Begin the command
cmdBegin();
}
}
MEM_CONTEXT_TEMP_END();
FUNCTION_LOG_RETURN_VOID();
}

13
doc/src/config/load.h Normal file
View File

@ -0,0 +1,13 @@
/***********************************************************************************************************************************
Documentation Configuration Load
***********************************************************************************************************************************/
#ifndef DOC_CONFIG_LOAD_H
#define DOC_CONFIG_LOAD_H
/***********************************************************************************************************************************
Functions
***********************************************************************************************************************************/
// Load the configuration
void cfgLoad(unsigned int argListSize, const char *argList[]);
#endif

View File

@ -0,0 +1,16 @@
####################################################################################################################################
# Generate config
####################################################################################################################################
doc_parse_auto_c_inc = custom_target(
'doc_parse.auto.c.inc',
output : 'parse.auto.c.inc',
depend_files: [
'../build/config/config.yaml',
],
command : [
build_code,
'config',
'@CURRENT_SOURCE_DIR@/../..',
'@BUILD_ROOT@/doc',
],
)

99
doc/src/main.c Normal file
View File

@ -0,0 +1,99 @@
/***********************************************************************************************************************************
Main
***********************************************************************************************************************************/
#include "build.auto.h"
#include <stdio.h>
#include "command/build/build.h"
#include "command/command.h"
#include "command/exit.h"
#include "command/help/help.h"
#include "common/debug.h"
#include "common/log.h"
#include "common/macro.h"
#include "common/memContext.h"
#include "common/stat.h"
#include "config/load.h"
#include "config/parse.h"
#include "storage/posix/storage.h"
#include "version.h"
/***********************************************************************************************************************************
Include automatically generated help data
***********************************************************************************************************************************/
#include "command/help/help.auto.c.inc"
int
main(int argListSize, const char *argList[])
{
// Set stack trace and mem context error cleanup handlers
static const ErrorHandlerFunction errorHandlerList[] = {stackTraceClean, memContextClean};
errorHandlerSet(errorHandlerList, LENGTH_OF(errorHandlerList));
FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_LOG_PARAM(INT, argListSize);
FUNCTION_LOG_PARAM(CHARPY, argList);
FUNCTION_LOG_END();
// Initialize command with the start time
cmdInit();
// Initialize statistics collector
statInit();
// Initialize exit handler
exitInit();
// Process commands
volatile int result = 0;
volatile bool error = false;
TRY_BEGIN()
{
// Load the configuration
// -------------------------------------------------------------------------------------------------------------------------
cfgLoad((unsigned int)argListSize, argList);
// Display help
// -------------------------------------------------------------------------------------------------------------------------
if (cfgCommandHelp())
{
cmdHelp(BUF(helpData, sizeof(helpData)));
}
else
{
switch (cfgCommand())
{
// Build
// -----------------------------------------------------------------------------------------------------------------
case cfgCmdBuild:
cmdBuild(cfgOptionStr(cfgOptRepoPath));
break;
// Display version
// -----------------------------------------------------------------------------------------------------------------
case cfgCmdVersion:
printf(PROJECT_NAME " Documentation " PROJECT_VERSION "\n");
fflush(stdout);
break;
// Error on commands that should have already been handled
// -----------------------------------------------------------------------------------------------------------------
case cfgCmdHelp:
case cfgCmdNone:
case cfgCmdNoop:
THROW_FMT(AssertError, "'%s' command should have been handled", cfgCommandName());
break;
}
}
}
CATCH_FATAL()
{
error = true;
result = exitSafe(result, true, 0);
}
TRY_END();
FUNCTION_LOG_RETURN(INT, error ? result : exitSafe(result, false, 0));
}

75
doc/src/meson.build Normal file
View File

@ -0,0 +1,75 @@
####################################################################################################################################
# Error on release builds since we do not want anyone using meson for production yet
####################################################################################################################################
if not get_option('force-release') and get_option('buildtype') != 'debug' and get_option('buildtype') != 'debugoptimized'
error('meson is currently not supported for release builds')
endif
####################################################################################################################################
# Write configuration
####################################################################################################################################
configure_file(output: 'build.auto.h', configuration: configuration)
####################################################################################################################################
# Build config target
####################################################################################################################################
# build parse.auto.c.inc
subdir('config')
####################################################################################################################################
# Build help target
####################################################################################################################################
# build help.auto.c.inc
subdir('command/help')
####################################################################################################################################
# test target
####################################################################################################################################
src_doc = [
'../../src/build/common/render.c',
'../../src/build/common/string.c',
'../../src/build/common/xml.c',
'../../src/build/common/yaml.c',
'../../src/build/config/parse.c',
'../../src/build/help/parse.c',
'../../src/command/command.c',
'../../src/command/exit.c',
'../../src/command/help/help.c',
'../../src/common/compress/bz2/common.c',
'../../src/common/compress/bz2/decompress.c',
'../../src/common/ini.c',
'../../src/common/io/fd.c',
'../../src/common/io/fdRead.c',
'../../src/common/io/fdWrite.c',
'../../src/common/lock.c',
'../../src/common/stat.c',
'../../src/common/type/json.c',
'../../src/config/config.c',
'../../src/config/parse.c',
'command/build/build.c',
'command/build/reference.c',
'config/load.c',
'main.c',
]
executable(
'doc-pgbackrest',
src_common,
src_doc,
doc_help_auto_c_inc,
doc_parse_auto_c_inc,
include_directories: include_directories('.', '../../src'),
c_args: [
# Use large buffer sizes to capture large error/log outputs
'-DERROR_MESSAGE_BUFFER_SIZE=262144',
'-DLOG_BUFFER_SIZE=262144',
arg_unity,
],
dependencies: [
lib_backtrace,
lib_bz2,
lib_xml,
lib_yaml,
],
build_by_default: false,
)

View File

@ -112,7 +112,7 @@
<!ATTLIST cmd-description key CDATA #REQUIRED>
<!ELEMENT section (title?,
((p|list|table|host-add|execute-list|backrest-config|postgres-config|cmd-description|
((p|text|list|table|host-add|execute-list|backrest-config|postgres-config|cmd-description|
option-description|code-block|block|section|admonition)*))>
<!ATTLIST section id CDATA #REQUIRED>
<!ATTLIST section if CDATA "">

View File

@ -213,4 +213,5 @@ configuration.set(
# Include subdirs
####################################################################################################################################
subdir('src')
subdir('doc/src')
subdir('test/src')

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE doc SYSTEM "doc.dtd">
<doc title="{[project]} Command &amp; Configuration Reference">
<config title="{[project]} Configuration Reference">
<config title="Configuration Reference">
<description>The {[project]} Configuration Reference details all configuration options.</description>
<text>
@ -1909,7 +1909,7 @@
</config-section-list>
</config>
<operation title="{[project]} Command Reference">
<operation title="Command Reference">
<description>The {[project]} Command Reference details all commands and options.</description>
<text>

View File

@ -131,7 +131,7 @@ eval
processBegin('install common packages');
processExec('sudo apt-get -qq update', {bSuppressStdErr => true, bSuppressError => true});
processExec(
'sudo DEBIAN_FRONTEND=noninteractive apt-get install -y libxml-checker-perl libyaml-perl', {bSuppressStdErr => true});
'sudo DEBIAN_FRONTEND=noninteractive apt-get install -y meson libxml-checker-perl libyaml-perl', {bSuppressStdErr => true});
processEnd();
if (!$bNoTempFs)
@ -182,7 +182,7 @@ eval
{
# Build list of packages that need to be installed
my $strPackage =
"make gcc ccache meson python3-pip git rsync zlib1g-dev libssl-dev libxml2-dev libpq-dev libyaml-dev pkg-config" .
"make gcc ccache python3-pip git rsync zlib1g-dev libssl-dev libxml2-dev libpq-dev libyaml-dev pkg-config" .
" uncrustify libssh2-1-dev";
# Add lcov when testing coverage

View File

@ -973,6 +973,18 @@ unit:
- command/server/ping
- command/server/server
# ********************************************************************************************************************************
- name: doc
test:
# ----------------------------------------------------------------------------------------------------------------------------
- name: build
total: 1
coverage:
- doc/command/build/build
- doc/command/build/reference
# **********************************************************************************************************************************
# Integration tests
#

View File

@ -129,6 +129,10 @@ sub coverageExtract
{
$strModuleName =~ s/^test/src/mg;
}
elsif ($strModuleName =~ /^doc/mg)
{
$strModuleName =~ s/^doc/doc\/src/mg;
}
my $strModuleOutName = $strModuleName;
my $bTest = false;
@ -150,6 +154,10 @@ sub coverageExtract
{
$strModulePath .= 'test/src/module/' . substr(${strModuleOutName}, 5);
}
elsif (${strModuleOutName} =~ /^doc\//)
{
$strModulePath .= "${strModuleOutName}";
}
else
{
$strModulePath .= "src/${strModuleOutName}";
@ -161,7 +169,7 @@ sub coverageExtract
my $strModuleSourceFile = $strModulePath . '.c' . ($bInc ? '.inc' : '');
executeTest(
"${strLCovExe}" . ($bTest ? ' --rc lcov_branch_coverage=0' : '') . " --extract=${strLCovOut} */${strModuleName}.c" .
"${strLCovExe}" . ($bTest ? ' --rc lcov_branch_coverage=0' : '') . " --extract=${strLCovOut} *${strModuleName}.c" .
($bInc ? '.inc' : '') . " --o=${strLCovOutTmp}");
# Combine with prior run if there was one
@ -315,6 +323,7 @@ sub coverageValidateAndGenerate
{
my $strCoverageFile = $strCodeModule;
$strCoverageFile =~ s/^test/src/mg;
$strCoverageFile =~ s/^doc/doc\/src/mg;
$strCoverageFile =~ s/^module/test/mg;
$strCoverageFile = "${strTestResultCoveragePath}/raw/${strCoverageFile}.lcov";

View File

@ -214,6 +214,8 @@ cmdBldPathModule(const String *const moduleName)
{
if (strBeginsWithZ(moduleName, "test/"))
strCatFmt(result, "test/src%s", strZ(strSub(moduleName, 4)));
else if (strBeginsWithZ(moduleName, "doc/"))
strCatFmt(result, "doc/src%s", strZ(strSub(moduleName, 3)));
else
strCatFmt(result, "src/%s", strZ(moduleName));
}
@ -534,10 +536,11 @@ testBldUnit(TestBuild *const this)
" include_directories(\n"
" '.',\n"
" '%s/src',\n"
" '%s/doc/src',\n"
" '%s/test/src',\n"
" ),\n"
" dependencies: [\n",
strZ(pathRepoRel), strZ(pathRepoRel));
strZ(pathRepoRel), strZ(pathRepoRel), strZ(pathRepoRel));
if (testBldBackTrace(this))
{

View File

@ -0,0 +1,331 @@
/***********************************************************************************************************************************
Test Documentation Build
***********************************************************************************************************************************/
#include "storage/posix/storage.h"
#include "common/harnessStorage.h"
/***********************************************************************************************************************************
Test Run
***********************************************************************************************************************************/
static void
testRun(void)
{
FUNCTION_HARNESS_VOID();
// Create default storage object for testing
const Storage *const storageTest = storagePosixNewP(TEST_PATH_STR, .write = true);
// *****************************************************************************************************************************
if (testBegin("cmdBuild()"))
{
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("referenceCommandSection()");
TEST_RESULT_STR_Z(referenceCommandSection(NULL), "general", "null section remap");
TEST_RESULT_STR_Z(referenceCommandSection(STRDEF("general")), "general", "general section no remap");
TEST_RESULT_STR_Z(referenceCommandSection(STRDEF("log")), "log", "log section no remap");
TEST_RESULT_STR_Z(referenceCommandSection(STRDEF("repository")), "repository", "repository section no remap");
TEST_RESULT_STR_Z(referenceCommandSection(STRDEF("stanza")), "stanza", "stanza section no remap");
TEST_RESULT_STR_Z(referenceCommandSection(STRDEF("other")), "command", "other section remap");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("parse and render");
HRN_STORAGE_PUT_Z(
storageTest, "src/build/config/config.yaml",
"command:\n"
" backup: {}\n"
"\n"
" check: {}\n"
"\n"
" restore:\n"
" internal: true\n"
"\n"
"optionGroup:\n"
" pg: {}\n"
" repo: {}\n"
"\n"
"option:\n"
" config:\n"
" type: string\n"
" command:\n"
" backup:\n"
" internal: true\n"
" check: {}\n"
"\n"
" buffer-size:\n"
" section: global\n"
" beta: true\n"
" type: integer\n"
" default: 1024\n"
" allow-list: [512, 1024, 2048, 4096]\n"
"\n"
" internal:\n"
" section: global\n"
" internal: true\n"
" type: integer\n"
" default: 11\n"
"\n"
" secure:\n"
" section: global\n"
" type: boolean\n"
" secure: true\n"
" default: false\n"
"\n"
" repo-compress-level:\n"
" group: repo\n"
" type: integer\n"
" default: 9\n"
" allow-range: [0, 9]\n"
" command:\n"
" backup: {}\n"
" deprecate:\n"
" repo-compress-level: {}\n"
"\n"
" force:\n"
" type: boolean\n"
" default: true\n"
" command:\n"
" backup:\n"
" default: true\n"
" deprecate:\n"
" frc: {}\n"
"\n"
" stanza:\n"
" type: string\n"
" default: demo\n"
" required: false\n"
" deprecate:\n"
" stanza: {}\n"
" stanza1: {}\n"
" stanza2: {}\n"
" command:\n"
" backup:\n"
" command-role:\n"
" local: {}\n"
" remote: {}\n"
" check: {}\n"
"\n");
HRN_STORAGE_PUT_Z(
storageTest, "src/build/help/help.xml",
"<doc>\n"
" <config title=\"Config Title\">\n"
" <description>config description</description>"
"\n"
" <text><p>config text</p></text>"
"\n"
" <config-section-list>\n"
" <config-section id=\"general\" name=\"General\">\n"
" <text><p>general section</p></text>"
"\n"
" <config-key-list>\n"
" <config-key id=\"buffer-size\" name=\"Buffer Size\">\n"
" <summary>Buffer size option summary.</summary>\n"
" <text><p>Buffer size option description.</p></text>\n"
" <example>128KiB</example>\n"
" <example>256KiB</example>\n"
" </config-key>\n"
"\n"
" <config-key id=\"internal\" name=\"Internal\">\n"
" <summary>Internal option summary.</summary>\n"
" <text><p>Internal option description</p></text>\n"
" </config-key>\n"
"\n"
" <config-key id=\"secure\" name=\"Secure\">\n"
" <summary>Secure option summary.</summary>\n"
" <text><p>Secure option description</p></text>\n"
" </config-key>\n"
"\n"
" <config-key id=\"stanza\" section=\"stanza\" name=\"Stanza\">\n"
" <summary>Stanza option summary.</summary>\n"
" <text><p>Stanza option description</p></text>\n"
" </config-key>\n"
" </config-key-list>\n"
" </config-section>\n"
" </config-section-list>\n"
" </config>\n"
"\n"
" <operation title=\"Command Title\">\n"
" <description>command description</description>"
" <text><p>command text</p></text>"
"\n"
" <operation-general>\n"
" <option-list>\n"
" <option id=\"config\" name=\"Config\">\n"
" <summary>config option summary.</summary>\n"
" <text><p>config option description.</p></text>\n"
" </option>\n"
" </option-list>\n"
" </operation-general>\n"
"\n"
" <command-list>\n"
" <command id=\"backup\" name=\"Backup\">\n"
" <summary>backup command summary.</summary>\n"
" <text><p>Backup command description.</p></text>\n"
"\n"
" <option-list>\n"
" <option id=\"force\" name=\"Force Backup\">\n"
" <summary>Force option command backup summary.</summary>\n"
" <text><p>Force option command backup description.</p></text>\n"
" <example>n</example>"
" <example>y</example>"
" </option>\n"
"\n"
" <option id=\"repo-compress-level\" name=\"Repo Compress Level Backup\">\n"
" <summary>Repo compress level option command backup summary.</summary>\n"
" <text><p>Repo compress level option command backup description.</p></text>\n"
" <example>4</example>"
" </option>\n"
" </option-list>\n"
" </command>\n"
"\n"
" <command id=\"check\" name=\"Check\">\n"
" <summary>Check command summary.</summary>\n"
" <text><p>Check command description.</p></text>\n"
" </command>\n"
"\n"
" <command id=\"restore\" name=\"Restore\">\n"
" <summary>Restore command summary.</summary>\n"
" <text><p>Restore command description.</p></text>\n"
" </command>\n"
" </command-list>\n"
" </operation>\n"
"</doc>\n");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("build documentation");
TEST_RESULT_VOID(cmdBuild(TEST_PATH_STR), "write files");
TEST_STORAGE_EXISTS(storageTest, "doc/output/xml/configuration.xml");
TEST_STORAGE_EXISTS(storageTest, "doc/output/xml/command.xml");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("configuration.xml");
TEST_STORAGE_GET(
storageTest,
"doc/output/xml/configuration.xml",
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<!DOCTYPE doc SYSTEM \"doc.dtd\">\n"
"<doc title=\"{[project]}\" subtitle=\"Config Title\" toc=\"y\">"
// {uncrustify_off - indentation}
"<description>config description</description>"
"<section id=\"introduction\">"
"<title>Introduction</title>"
"<text><p>config text</p></text>"
"</section>"
"<section id=\"section-general\">"
"<title>General Options (<id>general</id>)</title>"
"<text><p>general section</p></text>"
"<section id=\"option-buffer-size\">"
"<title>Buffer Size Option (<id>--buffer-size</id>)</title>"
"<p>Buffer size option summary.</p>"
"<p>FOR BETA TESTING ONLY. DO NOT USE IN PRODUCTION.</p>"
"<p><p>Buffer size option description.</p></p>"
"<code-block>default: 1024\n"
"example: buffer-size=128KiB\n"
"example: buffer-size=256KiB</code-block>"
"</section>"
"<section id=\"option-config\">"
"<title>Config Option (<id>--config</id>)</title>"
"<p>config option summary.</p>"
"<p><p>config option description.</p></p>"
"</section>"
"<section id=\"option-secure\">"
"<title>Secure Option (<id>--secure</id>)</title>"
"<p>Secure option summary.</p>"
"<p><p>Secure option description</p></p>"
"<code-block>default: n</code-block>"
"</section>"
"</section>"
// {uncrustify_on}
"</doc>\n");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("command.xml");
TEST_STORAGE_GET(
storageTest,
"doc/output/xml/command.xml",
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<!DOCTYPE doc SYSTEM \"doc.dtd\">\n"
"<doc title=\"{[project]}\" subtitle=\"Command Title\" toc=\"y\">"
// {uncrustify_off - indentation}
"<description>command description</description>"
"<section id=\"introduction\"><title>Introduction</title>"
"<text><p>command text</p></text>"
"</section>"
"<section id=\"command-backup\">"
"<title>Backup Command (<id>backup</id>)</title>"
"<text><p>Backup command description.</p></text>"
"<section id=\"category-command\" toc=\"n\">"
"<title>Command Options</title>"
"<section id=\"option-force\">"
"<title>Force Backup Option (<id>--force</id>)</title>"
"<p>Force option command backup summary.</p>"
"<p><p>Force option command backup description.</p></p>"
"<code-block>default: y\n"
"example: --no-force --force</code-block>"
"<p>Deprecated Name: frc</p>"
"</section>"
"<section id=\"option-repo-compress-level\">"
"<title>Repo Compress Level Backup Option (<id>--repo-compress-level</id>)</title>"
"<p>Repo compress level option command backup summary.</p>"
"<p><p>Repo compress level option command backup description.</p></p>"
"<code-block>default: 9\n"
"allowed: 0-9\n"
"example: --repo1-compress-level=4</code-block>"
"</section>"
"</section>"
"<section id=\"category-general\" toc=\"n\">"
"<title>General Options</title>"
"<section id=\"option-buffer-size\">"
"<title>Buffer Size Option (<id>--buffer-size</id>)</title>"
"<p>Buffer size option summary.</p>"
"<p>FOR BETA TESTING ONLY. DO NOT USE IN PRODUCTION.</p>"
"<p><p>Buffer size option description.</p></p>"
"<code-block>default: 1024\n"
"example: --buffer-size=128KiB --buffer-size=256KiB</code-block>"
"</section>"
"</section>"
"</section>"
"<section id=\"command-check\">"
"<title>Check Command (<id>check</id>)</title>"
"<text><p>Check command description.</p></text>"
"<section id=\"category-general\" toc=\"n\">"
"<title>General Options</title>"
"<section id=\"option-buffer-size\">"
"<title>Buffer Size Option (<id>--buffer-size</id>)</title>"
"<p>Buffer size option summary.</p>"
"<p>FOR BETA TESTING ONLY. DO NOT USE IN PRODUCTION.</p>"
"<p><p>Buffer size option description.</p></p>"
"<code-block>default: 1024\n"
"example: --buffer-size=128KiB --buffer-size=256KiB</code-block>"
"</section>"
"<section id=\"option-config\">"
"<title>Config Option (<id>--config</id>)</title>"
"<p>config option summary.</p>"
"<p><p>config option description.</p></p>"
"</section>"
"</section>"
"<section id=\"category-stanza\" toc=\"n\">"
"<title>Stanza Options</title>"
"<section id=\"option-stanza\">"
"<title>Stanza Option (<id>--stanza</id>)</title>"
"<p>Stanza option summary.</p>"
"<p><p>Stanza option description</p></p>"
"<code-block>default: demo</code-block>"
"<p>Deprecated Names: stanza1, stanza2</p>"
"</section>"
"</section>"
"</section>"
// {uncrustify_on}
"</doc>\n");
}
FUNCTION_HARNESS_RETURN_VOID();
}

View File

@ -206,6 +206,7 @@ testRun(void)
" include:\n"
" - common/error/error\n"
" - test/common/include\n"
" - doc/command/build/build\n"
"\n"
"integration:\n"
" - name: mock\n"
@ -258,6 +259,8 @@ testRun(void)
"test/src/module/common/stackTraceTest.c",
"test/src/test.c");
HRN_STORAGE_PUT_EMPTY(storageTest, "repo/doc/src/command/build/build.c");
TEST_RESULT_VOID(
cmdTest(
STRDEF(TEST_PATH "/repo"), storagePathP(storageTest, STRDEF("test")), STRDEF("none"), 3,
@ -307,6 +310,7 @@ testRun(void)
" include_directories(\n"
" '.',\n"
" '../../../repo/src',\n"
" '../../../repo/doc/src',\n"
" '../../../repo/test/src',\n"
" ),\n"
" dependencies: [\n"
@ -423,6 +427,7 @@ testRun(void)
" include_directories(\n"
" '.',\n"
" '../../../repo/src',\n"
" '../../../repo/doc/src',\n"
" '../../../repo/test/src',\n"
" ),\n"
" dependencies: [\n"
@ -634,6 +639,7 @@ testRun(void)
" include_directories(\n"
" '.',\n"
" '../../../repo/src',\n"
" '../../../repo/doc/src',\n"
" '../../../repo/test/src',\n"
" ),\n"
" dependencies: [\n"
@ -688,7 +694,8 @@ testRun(void)
STRDEF(
"#include \"test/src/common/harnessShim.c\"\n"
"#include \"test/src/common/harnessError.c\"\n"
"#include \"../../../repo/test/src/common/include.c\""));
"#include \"../../../repo/test/src/common/include.c\"\n"
"#include \"../../../repo/doc/src/command/build/build.c\""));
strReplace(
testCDup, STRDEF("{[C_TEST_INCLUDE]}"), STRDEF("#include \"../../../repo/test/src/module/test/shimTest.c\""));
strReplace(
@ -806,6 +813,7 @@ testRun(void)
" include_directories(\n"
" '.',\n"
" '../../../repo/src',\n"
" '../../../repo/doc/src',\n"
" '../../../repo/test/src',\n"
" ),\n"
" dependencies: [\n"