1#!/usr/bin/perl 2#################################################################################################################################### 3# doc.pl - PgBackRest Doc Builder 4#################################################################################################################################### 5 6#################################################################################################################################### 7# Perl includes 8#################################################################################################################################### 9use strict; 10use warnings FATAL => qw(all); 11use Carp qw(confess); 12use English '-no_match_vars'; 13 14$SIG{__DIE__} = sub { Carp::confess @_ }; 15 16use Cwd qw(abs_path); 17use File::Basename qw(dirname); 18use Getopt::Long qw(GetOptions); 19use Pod::Usage qw(pod2usage); 20use Storable; 21 22use lib dirname(abs_path($0)) . '/lib'; 23use lib dirname(dirname(abs_path($0))) . '/lib'; 24use lib dirname(dirname(abs_path($0))) . '/build/lib'; 25use lib dirname(dirname(abs_path($0))) . '/test/lib'; 26 27use pgBackRestTest::Common::ExecuteTest; 28use pgBackRestTest::Common::Storage; 29use pgBackRestTest::Common::StoragePosix; 30 31use pgBackRestDoc::Common::Doc; 32use pgBackRestDoc::Common::DocConfig; 33use pgBackRestDoc::Common::DocManifest; 34use pgBackRestDoc::Common::DocRender; 35use pgBackRestDoc::Common::Exception; 36use pgBackRestDoc::Common::Log; 37use pgBackRestDoc::Common::String; 38use pgBackRestDoc::Html::DocHtmlSite; 39use pgBackRestDoc::Latex::DocLatex; 40use pgBackRestDoc::Markdown::DocMarkdown; 41use pgBackRestDoc::ProjectInfo; 42 43#################################################################################################################################### 44# Usage 45#################################################################################################################################### 46 47=head1 NAME 48 49doc.pl - Generate pgBackRest documentation 50 51=head1 SYNOPSIS 52 53doc.pl [options] 54 55 General Options: 56 --help Display usage and exit 57 --version Display pgBackRest version 58 --quiet Sets log level to ERROR 59 --log-level Log level for execution (e.g. ERROR, WARN, INFO, DEBUG) 60 --deploy Write exe.cache into resource for persistence 61 --no-exe Should commands be executed when building help? (for testing only) 62 --no-cache Don't use execution cache 63 --cache-only Only use the execution cache - don't attempt to generate it 64 --pre Pre-build containers for execute elements marked pre 65 --var Override defined variable 66 --key-var Override defined variable and use in cache key 67 --doc-path Document path to render (manifest.xml should be located here) 68 --out Output types (html, pdf, markdown) 69 --out-preserve Don't clean output directory 70 --require Require only certain sections of the document (to speed testing) 71 --include Include source in generation (links will reference website) 72 --exclude Exclude source from generation (links will reference website) 73 74 Variable Options: 75 --dev Set 'dev' variable to 'y' 76 --debug Set 'debug' variable to 'y' 77=cut 78 79#################################################################################################################################### 80# Load command line parameters and config (see usage above for details) 81#################################################################################################################################### 82my $bHelp = false; 83my $bVersion = false; 84my $bQuiet = false; 85my $strLogLevel = 'info'; 86my $bNoExe = false; 87my $bNoCache = false; 88my $bCacheOnly = false; 89my $rhVariableOverride = {}; 90my $rhKeyVariableOverride = {}; 91my $strDocPath; 92my @stryOutput; 93my $bOutPreserve = false; 94my @stryRequire; 95my @stryInclude; 96my @stryExclude; 97my $bDeploy = false; 98my $bDev = false; 99my $bDebug = false; 100my $bPre = false; 101 102GetOptions ('help' => \$bHelp, 103 'version' => \$bVersion, 104 'quiet' => \$bQuiet, 105 'log-level=s' => \$strLogLevel, 106 'out=s@' => \@stryOutput, 107 'out-preserve' => \$bOutPreserve, 108 'require=s@' => \@stryRequire, 109 'include=s@' => \@stryInclude, 110 'exclude=s@' => \@stryExclude, 111 'no-exe', \$bNoExe, 112 'deploy', \$bDeploy, 113 'no-cache', \$bNoCache, 114 'dev', \$bDev, 115 'debug', \$bDebug, 116 'pre', \$bPre, 117 'cache-only', \$bCacheOnly, 118 'key-var=s%', $rhKeyVariableOverride, 119 'var=s%', $rhVariableOverride, 120 'doc-path=s', \$strDocPath) 121 or pod2usage(2); 122 123#################################################################################################################################### 124# Run in eval block to catch errors 125#################################################################################################################################### 126eval 127{ 128 # Display version and exit if requested 129 if ($bHelp || $bVersion) 130 { 131 print PROJECT_NAME . ' ' . PROJECT_VERSION . " Documentation Builder\n"; 132 133 if ($bHelp) 134 { 135 print "\n"; 136 pod2usage(); 137 } 138 139 exit 0; 140 } 141 142 # Disable cache when no exe 143 if ($bNoExe) 144 { 145 $bNoCache = true; 146 } 147 148 # Make sure options are set correctly for deploy 149 if ($bDeploy) 150 { 151 my $strError = 'cannot be specified for deploy'; 152 153 !$bNoExe 154 or confess "--no-exe ${strError}"; 155 156 !@stryRequire 157 or confess "--require ${strError}"; 158 } 159 160 # one --include must be specified when --required is 161 if (@stryRequire && @stryInclude != 1) 162 { 163 confess "one --include is required when --require is specified"; 164 } 165 166 # Set console log level 167 if ($bQuiet) 168 { 169 $strLogLevel = 'error'; 170 } 171 172 # If --dev passed then set the dev var to 'y' 173 if ($bDev) 174 { 175 $rhVariableOverride->{'dev'} = 'y'; 176 } 177 178 # If --debug passed then set the debug var to 'y' 179 if ($bDebug) 180 { 181 $rhVariableOverride->{'debug'} = 'y'; 182 } 183 184 # Doesn't make sense to pass include and exclude 185 if (@stryInclude > 0 && @stryExclude > 0) 186 { 187 confess "cannot specify both --include and --exclude"; 188 } 189 190 logLevelSet(undef, uc($strLogLevel), OFF); 191 192 # Get the base path 193 my $strBasePath = abs_path(dirname($0)); 194 195 my $oStorageDoc = new pgBackRestTest::Common::Storage( 196 $strBasePath, new pgBackRestTest::Common::StoragePosix({bFileSync => false, bPathSync => false})); 197 198 if (!defined($strDocPath)) 199 { 200 $strDocPath = $strBasePath; 201 } 202 203 my $strOutputPath = "${strDocPath}/output"; 204 205 # Create the out path if it does not exist 206 if (!-e $strOutputPath) 207 { 208 mkdir($strOutputPath) 209 or confess &log(ERROR, "unable to create path ${strOutputPath}"); 210 } 211 212 # Merge key variables into the variable list and ensure there are no duplicates 213 foreach my $strKey (sort(keys(%{$rhKeyVariableOverride}))) 214 { 215 if (defined($rhVariableOverride->{$strKey})) 216 { 217 confess &log(ERROR, "'${strKey}' cannot be passed as --var and --key-var"); 218 } 219 220 $rhVariableOverride->{$strKey} = $rhKeyVariableOverride->{$strKey}; 221 } 222 223 # Load the manifest 224 my $oManifest = new pgBackRestDoc::Common::DocManifest( 225 $oStorageDoc, \@stryRequire, \@stryInclude, \@stryExclude, $rhKeyVariableOverride, $rhVariableOverride, 226 $strDocPath, $bDeploy, $bCacheOnly, $bPre); 227 228 if (!$bNoCache) 229 { 230 $oManifest->cacheRead(); 231 } 232 233 # If no outputs were given 234 if (@stryOutput == 0) 235 { 236 @stryOutput = $oManifest->renderList(); 237 238 if ($oManifest->isBackRest()) 239 { 240 push(@stryOutput, 'man'); 241 } 242 } 243 244 # Build host containers 245 if (!$bCacheOnly && !$bNoExe) 246 { 247 foreach my $strSource ($oManifest->sourceList()) 248 { 249 if ((@stryInclude == 0 || grep(/$strSource/, @stryInclude)) && !grep(/$strSource/, @stryExclude)) 250 { 251 &log(INFO, "source $strSource"); 252 253 foreach my $oHostDefine ($oManifest->sourceGet($strSource)->{doc}->nodeList('host-define', false)) 254 { 255 if ($oManifest->evaluateIf($oHostDefine)) 256 { 257 my $strImage = $oManifest->variableReplace($oHostDefine->paramGet('image')); 258 my $strFrom = $oManifest->variableReplace($oHostDefine->paramGet('from')); 259 my $strDockerfile = "${strOutputPath}/doc-host.dockerfile"; 260 261 &log(INFO, "Build vm '${strImage}' from '${strFrom}'"); 262 263 $oStorageDoc->put( 264 $strDockerfile, 265 "FROM ${strFrom}\n\n" . trim($oManifest->variableReplace($oHostDefine->valueGet())) . "\n"); 266 executeTest("docker build -f ${strDockerfile} -t ${strImage} ${strBasePath}"); 267 } 268 } 269 } 270 } 271 } 272 273 # Render output 274 for my $strOutput (@stryOutput) 275 { 276 if (!($strOutput eq 'man' && $oManifest->isBackRest())) 277 { 278 $oManifest->renderGet($strOutput); 279 } 280 281 &log(INFO, "render ${strOutput} output"); 282 283 # Clean contents of out directory 284 if (!$bOutPreserve) 285 { 286 my $strOutputPath = $strOutput eq 'pdf' ? "${strOutputPath}/latex" : "${strOutputPath}/$strOutput"; 287 288 # Clean the current out path if it exists 289 if (-e $strOutputPath) 290 { 291 executeTest("rm -rf ${strOutputPath}/*"); 292 } 293 # Else create the html path 294 else 295 { 296 mkdir($strOutputPath) 297 or confess &log(ERROR, "unable to create path ${strOutputPath}"); 298 } 299 } 300 301 if ($strOutput eq 'markdown') 302 { 303 my $oMarkdown = 304 new pgBackRestDoc::Markdown::DocMarkdown 305 ( 306 $oManifest, 307 "${strBasePath}/xml", 308 "${strOutputPath}/markdown", 309 !$bNoExe 310 ); 311 312 $oMarkdown->process(); 313 } 314 elsif ($strOutput eq 'man' && $oManifest->isBackRest()) 315 { 316 # Generate the command-line help 317 my $oRender = new pgBackRestDoc::Common::DocRender('text', $oManifest, !$bNoExe); 318 my $oDocConfig = 319 new pgBackRestDoc::Common::DocConfig( 320 new pgBackRestDoc::Common::Doc("${strBasePath}/xml/reference.xml"), $oRender); 321 322 $oStorageDoc->pathCreate( 323 "${strBasePath}/output/man", {strMode => '0770', bIgnoreExists => true, bCreateParent => true}); 324 $oStorageDoc->put("${strBasePath}/output/man/" . lc(PROJECT_NAME) . '.1.txt', $oDocConfig->manGet($oManifest)); 325 } 326 elsif ($strOutput eq 'html') 327 { 328 my $oHtmlSite = 329 new pgBackRestDoc::Html::DocHtmlSite 330 ( 331 $oManifest, 332 "${strBasePath}/xml", 333 "${strOutputPath}/html", 334 "${strBasePath}/resource/html/default.css", 335 defined($oManifest->variableGet('project-favicon')) ? 336 "${strBasePath}/resource/html/" . $oManifest->variableGet('project-favicon') : undef, 337 defined($oManifest->variableGet('project-logo')) ? 338 "${strBasePath}/resource/" . $oManifest->variableGet('project-logo') : undef, 339 !$bNoExe 340 ); 341 342 $oHtmlSite->process(); 343 } 344 elsif ($strOutput eq 'pdf') 345 { 346 my $oLatex = 347 new pgBackRestDoc::Latex::DocLatex 348 ( 349 $oManifest, 350 "${strBasePath}/xml", 351 "${strOutputPath}/latex", 352 "${strBasePath}/resource/latex/preamble.tex", 353 !$bNoExe 354 ); 355 356 $oLatex->process(); 357 } 358 } 359 360 # Cache the manifest (mostly useful for testing rendering changes in the code) 361 if (!$bNoCache && !$bCacheOnly) 362 { 363 $oManifest->cacheWrite(); 364 } 365 366 # Exit with success 367 exit 0; 368} 369 370#################################################################################################################################### 371# Check for errors 372#################################################################################################################################### 373or do 374{ 375 # If a backrest exception then return the code 376 exit $EVAL_ERROR->code() if (isException(\$EVAL_ERROR)); 377 378 # Else output the unhandled error 379 print $EVAL_ERROR; 380 exit ERROR_UNHANDLED; 381}; 382 383# It shouldn't be possible to get here 384&log(ASSERT, 'execution reached invalid location in ' . __FILE__ . ', line ' . __LINE__); 385exit ERROR_ASSERT; 386