diff --git a/doc/doc.pl b/doc/doc.pl index 45b6a9edb..35e261771 100755 --- a/doc/doc.pl +++ b/doc/doc.pl @@ -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, diff --git a/doc/lib/pgBackRestDoc/Common/DocConfig.pm b/doc/lib/pgBackRestDoc/Common/DocConfig.pm index d2cb3bae7..480813136 100644 --- a/doc/lib/pgBackRestDoc/Common/DocConfig.pm +++ b/doc/lib/pgBackRestDoc/Common/DocConfig.pm @@ -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; diff --git a/doc/lib/pgBackRestDoc/Common/DocRender.pm b/doc/lib/pgBackRestDoc/Common/DocRender.pm index 1f928e265..6c0df2932 100644 --- a/doc/lib/pgBackRestDoc/Common/DocRender.pm +++ b/doc/lib/pgBackRestDoc/Common/DocRender.pm @@ -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(); diff --git a/doc/manifest.xml b/doc/manifest.xml index 22886c3b6..659f32a5c 100644 --- a/doc/manifest.xml +++ b/doc/manifest.xml @@ -86,6 +86,8 @@ + + @@ -101,8 +103,8 @@ - - + + diff --git a/doc/src/build/config/config.yaml b/doc/src/build/config/config.yaml new file mode 100644 index 000000000..e029b594d --- /dev/null +++ b/doc/src/build/config/config.yaml @@ -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: {}}} diff --git a/doc/src/build/help/help.xml b/doc/src/build/help/help.xml new file mode 100644 index 000000000..7ff990d19 --- /dev/null +++ b/doc/src/build/help/help.xml @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Get help. + + +

Three levels of help are provided. If no command is specified then general help will be displayed. If a command is specified (e.g. pgbackrest help backup) 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. pgbackrest help backup type) then a full description of the option as it applies to the command will be displayed.

+
+
+ + +

+ + + Build documentation. + + +

Build the specified documentation module.

+
+
+ + + Get version. + + +

Displays installed version.

+
+
+
+
+
diff --git a/doc/src/command/build/build.c b/doc/src/command/build/build.c new file mode 100644 index 000000000..b336ce4ec --- /dev/null +++ b/doc/src/command/build/build.c @@ -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(); +} diff --git a/doc/src/command/build/build.h b/doc/src/command/build/build.h new file mode 100644 index 000000000..e027817e1 --- /dev/null +++ b/doc/src/command/build/build.h @@ -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 diff --git a/doc/src/command/build/reference.c b/doc/src/command/build/reference.c new file mode 100644 index 000000000..d644a8aaa --- /dev/null +++ b/doc/src/command/build/reference.c @@ -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); +} diff --git a/doc/src/command/build/reference.h b/doc/src/command/build/reference.h new file mode 100644 index 000000000..d1e5bb16e --- /dev/null +++ b/doc/src/command/build/reference.h @@ -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 diff --git a/doc/src/command/help/meson.build b/doc/src/command/help/meson.build new file mode 100644 index 000000000..e38c2afea --- /dev/null +++ b/doc/src/command/help/meson.build @@ -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', + ], +) diff --git a/doc/src/config/load.c b/doc/src/config/load.c new file mode 100644 index 000000000..ee46bffda --- /dev/null +++ b/doc/src/config/load.c @@ -0,0 +1,123 @@ +/*********************************************************************************************************************************** +Configuration Load +***********************************************************************************************************************************/ +#include "build.auto.h" + +#include + +#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(); +} diff --git a/doc/src/config/load.h b/doc/src/config/load.h new file mode 100644 index 000000000..082fe1d86 --- /dev/null +++ b/doc/src/config/load.h @@ -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 diff --git a/doc/src/config/meson.build b/doc/src/config/meson.build new file mode 100644 index 000000000..76d79d4bd --- /dev/null +++ b/doc/src/config/meson.build @@ -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', + ], +) diff --git a/doc/src/main.c b/doc/src/main.c new file mode 100644 index 000000000..0a6811c35 --- /dev/null +++ b/doc/src/main.c @@ -0,0 +1,99 @@ +/*********************************************************************************************************************************** +Main +***********************************************************************************************************************************/ +#include "build.auto.h" + +#include + +#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)); +} diff --git a/doc/src/meson.build b/doc/src/meson.build new file mode 100644 index 000000000..d65fc201c --- /dev/null +++ b/doc/src/meson.build @@ -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, +) diff --git a/doc/xml/dtd/doc.dtd b/doc/xml/dtd/doc.dtd index 5b1b76650..4a17075ec 100644 --- a/doc/xml/dtd/doc.dtd +++ b/doc/xml/dtd/doc.dtd @@ -112,7 +112,7 @@ diff --git a/meson.build b/meson.build index 2e037c394..97bfd4262 100644 --- a/meson.build +++ b/meson.build @@ -213,4 +213,5 @@ configuration.set( # Include subdirs #################################################################################################################################### subdir('src') +subdir('doc/src') subdir('test/src') diff --git a/src/build/help/help.xml b/src/build/help/help.xml index f62bb7156..9550da641 100644 --- a/src/build/help/help.xml +++ b/src/build/help/help.xml @@ -1,7 +1,7 @@ - + The {[project]} Configuration Reference details all configuration options. @@ -1909,7 +1909,7 @@ - + The {[project]} Command Reference details all commands and options. diff --git a/test/ci.pl b/test/ci.pl index a5afe993a..555324300 100755 --- a/test/ci.pl +++ b/test/ci.pl @@ -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 diff --git a/test/define.yaml b/test/define.yaml index 1c7febc95..f672848a4 100644 --- a/test/define.yaml +++ b/test/define.yaml @@ -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 # diff --git a/test/lib/pgBackRestTest/Common/CoverageTest.pm b/test/lib/pgBackRestTest/Common/CoverageTest.pm index 7429c7ae0..7d2474be6 100644 --- a/test/lib/pgBackRestTest/Common/CoverageTest.pm +++ b/test/lib/pgBackRestTest/Common/CoverageTest.pm @@ -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"; diff --git a/test/src/command/test/build.c b/test/src/command/test/build.c index 6b92fd3b2..3e23524e7 100644 --- a/test/src/command/test/build.c +++ b/test/src/command/test/build.c @@ -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)) { diff --git a/test/src/module/doc/buildTest.c b/test/src/module/doc/buildTest.c new file mode 100644 index 000000000..7d3bfd95f --- /dev/null +++ b/test/src/module/doc/buildTest.c @@ -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", + "\n" + " \n" + " config description" + "\n" + "

config text

" + "\n" + " \n" + " \n" + "

general section

" + "\n" + " \n" + " \n" + " Buffer size option summary.\n" + "

Buffer size option description.

\n" + " 128KiB\n" + " 256KiB\n" + "
\n" + "\n" + " \n" + " Internal option summary.\n" + "

Internal option description

\n" + "
\n" + "\n" + " \n" + " Secure option summary.\n" + "

Secure option description

\n" + "
\n" + "\n" + " \n" + " Stanza option summary.\n" + "

Stanza option description

\n" + "
\n" + "
\n" + "
\n" + "
\n" + "
\n" + "\n" + " \n" + " command description" + "

command text

" + "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n" + " \n" + " \n" + " backup command summary.\n" + "

Backup command description.

\n" + "\n" + " \n" + " \n" + "\n" + " \n" + " \n" + "
\n" + "\n" + " \n" + " Check command summary.\n" + "

Check command description.

\n" + "
\n" + "\n" + " \n" + " Restore command summary.\n" + "

Restore command description.

\n" + "
\n" + "
\n" + "
\n" + "
\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", + "\n" + "\n" + "" + // {uncrustify_off - indentation} + "config description" + "
" + "Introduction" + "

config text

" + "
" + "
" + "General Options (<id>general</id>)" + "

general section

" + "
" + "Buffer Size Option (<id>--buffer-size</id>)" + "

Buffer size option summary.

" + "

FOR BETA TESTING ONLY. DO NOT USE IN PRODUCTION.

" + "

Buffer size option description.

" + "default: 1024\n" + "example: buffer-size=128KiB\n" + "example: buffer-size=256KiB" + "
" + "
" + "Config Option (<id>--config</id>)" + "

config option summary.

" + "

config option description.

" + "
" + "
" + "Secure Option (<id>--secure</id>)" + "

Secure option summary.

" + "

Secure option description

" + "default: n" + "
" + "
" + // {uncrustify_on} + "
\n"); + + // ------------------------------------------------------------------------------------------------------------------------- + TEST_TITLE("command.xml"); + + TEST_STORAGE_GET( + storageTest, + "doc/output/xml/command.xml", + "\n" + "\n" + "" + // {uncrustify_off - indentation} + "command description" + "
Introduction" + "

command text

" + "
" + "
" + "Backup Command (<id>backup</id>)" + "

Backup command description.

" + "
" + "Command Options" + "
" + "Force Backup Option (<id>--force</id>)" + "

Force option command backup summary.

" + "

Force option command backup description.

" + "default: y\n" + "example: --no-force --force" + "

Deprecated Name: frc

" + "
" + "
" + "Repo Compress Level Backup Option (<id>--repo-compress-level</id>)" + "

Repo compress level option command backup summary.

" + "

Repo compress level option command backup description.

" + "default: 9\n" + "allowed: 0-9\n" + "example: --repo1-compress-level=4" + "
" + "
" + "
" + "General Options" + "
" + "Buffer Size Option (<id>--buffer-size</id>)" + "

Buffer size option summary.

" + "

FOR BETA TESTING ONLY. DO NOT USE IN PRODUCTION.

" + "

Buffer size option description.

" + "default: 1024\n" + "example: --buffer-size=128KiB --buffer-size=256KiB" + "
" + "
" + "
" + "
" + "Check Command (<id>check</id>)" + "

Check command description.

" + "
" + "General Options" + "
" + "Buffer Size Option (<id>--buffer-size</id>)" + "

Buffer size option summary.

" + "

FOR BETA TESTING ONLY. DO NOT USE IN PRODUCTION.

" + "

Buffer size option description.

" + "default: 1024\n" + "example: --buffer-size=128KiB --buffer-size=256KiB" + "
" + "
" + "Config Option (<id>--config</id>)" + "

config option summary.

" + "

config option description.

" + "
" + "
" + "
" + "Stanza Options" + "
" + "Stanza Option (<id>--stanza</id>)" + "

Stanza option summary.

" + "

Stanza option description

" + "default: demo" + "

Deprecated Names: stanza1, stanza2

" + "
" + "
" + "
" + // {uncrustify_on} + "
\n"); + } + + FUNCTION_HARNESS_RETURN_VOID(); +} diff --git a/test/src/module/test/testTest.c b/test/src/module/test/testTest.c index a503c5cb4..23c1078c0 100644 --- a/test/src/module/test/testTest.c +++ b/test/src/module/test/testTest.c @@ -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"