You've already forked pgbackrest
mirror of
https://github.com/pgbackrest/pgbackrest.git
synced 2025-08-01 06:46:52 +03:00
Rename BackRestDoc Perl module to pgBackRestDoc.
This is consistent with the way BackRest and BackRest test were renamed way back in 18fd2523
.
More modules will be moving to pgBackRestDoc soon so renaming now reduces churn later.
This commit is contained in:
833
doc/lib/pgBackRestDoc/Common/Doc.pm
Normal file
833
doc/lib/pgBackRestDoc/Common/Doc.pm
Normal file
@ -0,0 +1,833 @@
|
||||
####################################################################################################################################
|
||||
# DOC MODULE
|
||||
####################################################################################################################################
|
||||
package pgBackRestDoc::Common::Doc;
|
||||
|
||||
use strict;
|
||||
use warnings FATAL => qw(all);
|
||||
use Carp qw(confess);
|
||||
use English '-no_match_vars';
|
||||
|
||||
use Cwd qw(abs_path);
|
||||
use File::Basename qw(dirname);
|
||||
use Scalar::Util qw(blessed);
|
||||
use XML::Checker::Parser;
|
||||
|
||||
use pgBackRestDoc::Common::Log;
|
||||
use pgBackRestDoc::Common::String;
|
||||
|
||||
####################################################################################################################################
|
||||
# CONSTRUCTOR
|
||||
####################################################################################################################################
|
||||
sub new
|
||||
{
|
||||
my $class = shift; # Class name
|
||||
|
||||
# Create the class hash
|
||||
my $self = {};
|
||||
bless $self, $class;
|
||||
|
||||
$self->{strClass} = $class;
|
||||
|
||||
# Assign function parameters, defaults, and log debug info
|
||||
(
|
||||
my $strOperation,
|
||||
$self->{strFileName},
|
||||
my $strSgmlSearchPath,
|
||||
) =
|
||||
logDebugParam
|
||||
(
|
||||
__PACKAGE__ . '->new', \@_,
|
||||
{name => 'strFileName', required => false},
|
||||
{name => 'strSgmlSearchPath', required => false},
|
||||
);
|
||||
|
||||
# Load the doc from a file if one has been defined
|
||||
if (defined($self->{strFileName}))
|
||||
{
|
||||
my $oParser = XML::Checker::Parser->new(ErrorContext => 2, Style => 'Tree');
|
||||
$oParser->set_sgml_search_path(
|
||||
defined($strSgmlSearchPath) ? $strSgmlSearchPath : dirname(dirname(abs_path($0))) . '/doc/xml/dtd');
|
||||
|
||||
my $oTree;
|
||||
|
||||
eval
|
||||
{
|
||||
local $XML::Checker::FAIL = sub
|
||||
{
|
||||
my $iCode = shift;
|
||||
|
||||
die XML::Checker::error_string($iCode, @_);
|
||||
};
|
||||
|
||||
$oTree = $oParser->parsefile($self->{strFileName});
|
||||
|
||||
return true;
|
||||
}
|
||||
# Report any error that stopped parsing
|
||||
or do
|
||||
{
|
||||
my $strException = $EVAL_ERROR;
|
||||
$strException =~ s/at \/.*?$//s; # remove module line number
|
||||
die "malformed xml in '$self->{strFileName}':\n" . trim($strException);
|
||||
};
|
||||
|
||||
# Parse and build the doc
|
||||
$self->{oDoc} = $self->build($self->parse(${$oTree}[0], ${$oTree}[1]));
|
||||
}
|
||||
# Else create a blank doc
|
||||
else
|
||||
{
|
||||
$self->{oDoc} = {name => 'doc', children => []};
|
||||
}
|
||||
|
||||
$self->{strName} = 'root';
|
||||
|
||||
# Return from function and log return values if any
|
||||
return logDebugReturn
|
||||
(
|
||||
$strOperation,
|
||||
{name => 'self', value => $self}
|
||||
);
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# parse
|
||||
#
|
||||
# Parse the xml doc into a more usable hash and array structure.
|
||||
####################################################################################################################################
|
||||
sub parse
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
# Assign function parameters, defaults, and log debug info
|
||||
my
|
||||
(
|
||||
$strOperation,
|
||||
$strName,
|
||||
$oyNode
|
||||
) =
|
||||
logDebugParam
|
||||
(
|
||||
__PACKAGE__ . '->parse', \@_,
|
||||
{name => 'strName', trace => true},
|
||||
{name => 'oyNode', trace => true}
|
||||
);
|
||||
|
||||
my %oOut;
|
||||
my $iIndex = 0;
|
||||
my $bText = $strName eq 'text' || $strName eq 'li' || $strName eq 'p' || $strName eq 'title' ||
|
||||
$strName eq 'summary' || $strName eq 'table-cell' || $strName eq 'table-column' || $strName eq 'list-item' ||
|
||||
$strName eq 'admonition';
|
||||
|
||||
# Store the node name
|
||||
$oOut{name} = $strName;
|
||||
|
||||
if (keys(%{$$oyNode[$iIndex]}))
|
||||
{
|
||||
$oOut{param} = $$oyNode[$iIndex];
|
||||
}
|
||||
|
||||
$iIndex++;
|
||||
|
||||
# Look for strings and children
|
||||
while (defined($$oyNode[$iIndex]))
|
||||
{
|
||||
# Process string data
|
||||
if (ref(\$$oyNode[$iIndex]) eq 'SCALAR' && $$oyNode[$iIndex] eq '0')
|
||||
{
|
||||
$iIndex++;
|
||||
my $strBuffer = $$oyNode[$iIndex++];
|
||||
|
||||
# Strip tabs, CRs, and LFs
|
||||
$strBuffer =~ s/\t|\r//g;
|
||||
|
||||
# If anything is left
|
||||
if (length($strBuffer) > 0)
|
||||
{
|
||||
# If text node then create array entries for strings
|
||||
if ($bText)
|
||||
{
|
||||
if (!defined($oOut{children}))
|
||||
{
|
||||
$oOut{children} = [];
|
||||
}
|
||||
|
||||
push(@{$oOut{children}}, $strBuffer);
|
||||
}
|
||||
# Don't allow strings mixed with children
|
||||
elsif (length(trim($strBuffer)) > 0)
|
||||
{
|
||||
if (defined($oOut{children}))
|
||||
{
|
||||
confess "text mixed with children in node ${strName} (spaces count)";
|
||||
}
|
||||
|
||||
if (defined($oOut{value}))
|
||||
{
|
||||
confess "value is already defined in node ${strName} - this shouldn't happen";
|
||||
}
|
||||
|
||||
# Don't allow text mixed with
|
||||
$oOut{value} = $strBuffer;
|
||||
}
|
||||
}
|
||||
}
|
||||
# Process a child
|
||||
else
|
||||
{
|
||||
if (defined($oOut{value}) && $bText)
|
||||
{
|
||||
confess "text mixed with children in node ${strName} before child " . $$oyNode[$iIndex++] . " (spaces count)";
|
||||
}
|
||||
|
||||
if (!defined($oOut{children}))
|
||||
{
|
||||
$oOut{children} = [];
|
||||
}
|
||||
|
||||
push(@{$oOut{children}}, $self->parse($$oyNode[$iIndex++], $$oyNode[$iIndex++]));
|
||||
}
|
||||
}
|
||||
|
||||
# Return from function and log return values if any
|
||||
return logDebugReturn
|
||||
(
|
||||
$strOperation,
|
||||
{name => 'oDoc', value => \%oOut, trace => true}
|
||||
);
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# build
|
||||
#
|
||||
# Restructure the doc to make walking it easier.
|
||||
####################################################################################################################################
|
||||
sub build
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
# Assign function parameters, defaults, and log debug info
|
||||
my
|
||||
(
|
||||
$strOperation,
|
||||
$oDoc
|
||||
) =
|
||||
logDebugParam
|
||||
(
|
||||
__PACKAGE__ . '->build', \@_,
|
||||
{name => 'oDoc', trace => true}
|
||||
);
|
||||
|
||||
# Initialize the node object
|
||||
my $oOut = {name => $$oDoc{name}, children => [], value => $$oDoc{value}};
|
||||
my $strError = "in node $$oDoc{name}";
|
||||
|
||||
# Get all params
|
||||
if (defined($$oDoc{param}))
|
||||
{
|
||||
for my $strParam (keys %{$$oDoc{param}})
|
||||
{
|
||||
$$oOut{param}{$strParam} = $$oDoc{param}{$strParam};
|
||||
}
|
||||
}
|
||||
|
||||
if ($$oDoc{name} eq 'p' || $$oDoc{name} eq 'title' || $$oDoc{name} eq 'summary' ||
|
||||
$$oDoc{name} eq 'table-cell' || $$oDoc{name} eq 'table-column' || $$oDoc{name} eq 'list-item' ||
|
||||
$$oDoc{name} eq 'admonition')
|
||||
{
|
||||
$$oOut{field}{text} = $oDoc;
|
||||
}
|
||||
elsif (defined($$oDoc{children}))
|
||||
{
|
||||
for (my $iIndex = 0; $iIndex < @{$$oDoc{children}}; $iIndex++)
|
||||
{
|
||||
my $oSub = $$oDoc{children}[$iIndex];
|
||||
my $strName = $$oSub{name};
|
||||
|
||||
if ($strName eq 'text')
|
||||
{
|
||||
$$oOut{field}{text} = $oSub;
|
||||
}
|
||||
elsif ((defined($$oSub{value}) && !defined($$oSub{param})) && $strName ne 'code-block')
|
||||
{
|
||||
$$oOut{field}{$strName} = $$oSub{value};
|
||||
}
|
||||
elsif (!defined($$oSub{children}) && !defined($$oSub{value}) && !defined($$oSub{param}))
|
||||
{
|
||||
$$oOut{field}{$strName} = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
push(@{$$oOut{children}}, $self->build($oSub));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Return from function and log return values if any
|
||||
return logDebugReturn
|
||||
(
|
||||
$strOperation,
|
||||
{name => 'oDoc', value => $oOut, trace => true}
|
||||
);
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# nodeGetById
|
||||
#
|
||||
# Return a node by name - error if more than one exists
|
||||
####################################################################################################################################
|
||||
sub nodeGetById
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
# Assign function parameters, defaults, and log debug info
|
||||
my
|
||||
(
|
||||
$strOperation,
|
||||
$strName,
|
||||
$strId,
|
||||
$bRequired,
|
||||
) =
|
||||
logDebugParam
|
||||
(
|
||||
__PACKAGE__ . 'nodeGetById', \@_,
|
||||
{name => 'strName', trace => true},
|
||||
{name => 'strId', required => false, trace => true},
|
||||
{name => 'bRequired', default => true, trace => true}
|
||||
);
|
||||
|
||||
my $oDoc = $self->{oDoc};
|
||||
my $oNode;
|
||||
|
||||
for (my $iIndex = 0; $iIndex < @{$$oDoc{children}}; $iIndex++)
|
||||
{
|
||||
if ((defined($strName) && $$oDoc{children}[$iIndex]{name} eq $strName) &&
|
||||
(!defined($strId) || $$oDoc{children}[$iIndex]{param}{id} eq $strId))
|
||||
{
|
||||
if (!defined($oNode))
|
||||
{
|
||||
$oNode = $$oDoc{children}[$iIndex];
|
||||
}
|
||||
else
|
||||
{
|
||||
confess "found more than one child ${strName} in node $$oDoc{name}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!defined($oNode) && $bRequired)
|
||||
{
|
||||
confess "unable to find child ${strName}" . (defined($strId) ? " (${strId})" : '') . " in node $$oDoc{name}";
|
||||
}
|
||||
|
||||
# Return from function and log return values if any
|
||||
return logDebugReturn
|
||||
(
|
||||
$strOperation,
|
||||
{name => 'oNodeDoc', value => $self->nodeBless($oNode), trace => true}
|
||||
);
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# nodeGet
|
||||
#
|
||||
# Return a node by name - error if more than one exists
|
||||
####################################################################################################################################
|
||||
sub nodeGet
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
return $self->nodeGetById(shift, undef, shift);
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# nodeTest
|
||||
#
|
||||
# Test that a node exists
|
||||
####################################################################################################################################
|
||||
sub nodeTest
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
return defined($self->nodeGetById(shift, undef, false));
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# nodeAdd
|
||||
#
|
||||
# Add a node to to the current doc's child list
|
||||
####################################################################################################################################
|
||||
sub nodeAdd
|
||||
{
|
||||
my $self = shift;
|
||||
my $strName = shift;
|
||||
my $strValue = shift;
|
||||
my $oParam = shift;
|
||||
my $oField = shift;
|
||||
|
||||
my $oDoc = $self->{oDoc};
|
||||
my $oNode = {name => $strName, value => $strValue, param => $oParam, field => $oField};
|
||||
|
||||
push(@{$$oDoc{children}}, $oNode);
|
||||
|
||||
return $self->nodeBless($oNode);
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# nodeBless
|
||||
#
|
||||
# Make a new Doc object from a node.
|
||||
####################################################################################################################################
|
||||
sub nodeBless
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
# Assign function parameters, defaults, and log debug info
|
||||
my
|
||||
(
|
||||
$strOperation,
|
||||
$oNode
|
||||
) =
|
||||
logDebugParam
|
||||
(
|
||||
__PACKAGE__ . '->nodeBless', \@_,
|
||||
{name => 'oNode', required => false, trace => true}
|
||||
);
|
||||
|
||||
my $oDoc;
|
||||
|
||||
if (defined($oNode))
|
||||
{
|
||||
$oDoc = {};
|
||||
bless $oDoc, $self->{strClass};
|
||||
|
||||
$oDoc->{strClass} = $self->{strClass};
|
||||
$oDoc->{strName} = $$oNode{name};
|
||||
$oDoc->{oDoc} = $oNode;
|
||||
}
|
||||
|
||||
# Return from function and log return values if any
|
||||
return logDebugReturn
|
||||
(
|
||||
$strOperation,
|
||||
{name => 'oDoc', value => $oDoc, trace => true}
|
||||
);
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# nodeList
|
||||
#
|
||||
# Get a list of nodes.
|
||||
####################################################################################################################################
|
||||
sub nodeList
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
# Assign function parameters, defaults, and log debug info
|
||||
my
|
||||
(
|
||||
$strOperation,
|
||||
$strName,
|
||||
$bRequired,
|
||||
) =
|
||||
logDebugParam
|
||||
(
|
||||
__PACKAGE__ . '->nodeList', \@_,
|
||||
{name => 'strName', required => false, trace => true},
|
||||
{name => 'bRequired', default => true, trace => true},
|
||||
);
|
||||
|
||||
my $oDoc = $self->{oDoc};
|
||||
my @oyNode;
|
||||
|
||||
if (defined($$oDoc{children}))
|
||||
{
|
||||
for (my $iIndex = 0; $iIndex < @{$$oDoc{children}}; $iIndex++)
|
||||
{
|
||||
if (!defined($strName) || $$oDoc{children}[$iIndex]{name} eq $strName)
|
||||
{
|
||||
if (ref(\$$oDoc{children}[$iIndex]) eq "SCALAR")
|
||||
{
|
||||
push(@oyNode, $$oDoc{children}[$iIndex]);
|
||||
}
|
||||
else
|
||||
{
|
||||
push(@oyNode, $self->nodeBless($$oDoc{children}[$iIndex]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (@oyNode == 0 && $bRequired)
|
||||
{
|
||||
confess 'unable to find ' . (defined($strName) ? "children named '${strName}'" : 'any children') . " in node $$oDoc{name}";
|
||||
}
|
||||
|
||||
# Return from function and log return values if any
|
||||
return logDebugReturn
|
||||
(
|
||||
$strOperation,
|
||||
{name => 'oyNode', value => \@oyNode, trace => true}
|
||||
);
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# nodeRemove
|
||||
#
|
||||
# Remove a child node.
|
||||
####################################################################################################################################
|
||||
sub nodeRemove
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
# Assign function parameters, defaults, and log debug info
|
||||
my
|
||||
(
|
||||
$strOperation,
|
||||
$oChildRemove
|
||||
) =
|
||||
logDebugParam
|
||||
(
|
||||
__PACKAGE__ . '->nodeRemove', \@_,
|
||||
{name => 'oChildRemove', required => false, trace => true}
|
||||
);
|
||||
|
||||
my $bRemove = false;
|
||||
my $oDoc = $self->{oDoc};
|
||||
|
||||
# Error if there are no children
|
||||
if (!defined($$oDoc{children}))
|
||||
{
|
||||
confess &log(ERROR, "node has no children");
|
||||
}
|
||||
|
||||
for (my $iIndex = 0; $iIndex < @{$$oDoc{children}}; $iIndex++)
|
||||
{
|
||||
if ($$oDoc{children}[$iIndex] == $oChildRemove->{oDoc})
|
||||
{
|
||||
splice(@{$$oDoc{children}}, $iIndex, 1);
|
||||
$bRemove = true;
|
||||
last;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$bRemove)
|
||||
{
|
||||
confess &log(ERROR, "child was not found in node, could not be removed");
|
||||
}
|
||||
|
||||
# Return from function and log return values if any
|
||||
return logDebugReturn($strOperation);
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# nodeReplace
|
||||
#
|
||||
# Replace a child node with one or more child nodes.
|
||||
####################################################################################################################################
|
||||
sub nodeReplace
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
# Assign function parameters, defaults, and log debug info
|
||||
my
|
||||
(
|
||||
$strOperation,
|
||||
$oChildRemove,
|
||||
$oyChildReplace,
|
||||
) =
|
||||
logDebugParam
|
||||
(
|
||||
__PACKAGE__ . '->nodeReplace', \@_,
|
||||
{name => 'oChildRemove', trace => true},
|
||||
{name => 'oChildReplace', trace => true},
|
||||
);
|
||||
|
||||
my $bReplace = false;
|
||||
my $iReplaceIdx = undef;
|
||||
my $iReplaceTotal = undef;
|
||||
my $oDoc = $self->{oDoc};
|
||||
|
||||
# Error if there are no children
|
||||
if (!defined($$oDoc{children}))
|
||||
{
|
||||
confess &log(ERROR, "node has no children");
|
||||
}
|
||||
|
||||
for (my $iIndex = 0; $iIndex < @{$$oDoc{children}}; $iIndex++)
|
||||
{
|
||||
if ($$oDoc{children}[$iIndex] == $oChildRemove->{oDoc})
|
||||
{
|
||||
splice(@{$$oDoc{children}}, $iIndex, 1);
|
||||
splice(@{$$oDoc{children}}, $iIndex, 0, @{$oyChildReplace});
|
||||
|
||||
$iReplaceIdx = $iIndex;
|
||||
$iReplaceTotal = scalar(@{$oyChildReplace});
|
||||
$bReplace = true;
|
||||
last;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$bReplace)
|
||||
{
|
||||
confess &log(ERROR, "child was not found in node, could not be replaced");
|
||||
}
|
||||
|
||||
# Return from function and log return values if any
|
||||
return logDebugReturn
|
||||
(
|
||||
$strOperation,
|
||||
{name => 'iReplaceIdx', value => $iReplaceIdx, trace => true},
|
||||
{name => 'iReplaceTotal', value => $iReplaceTotal, trace => true},
|
||||
);
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# nameGet
|
||||
####################################################################################################################################
|
||||
sub nameGet
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
# Assign function parameters, defaults, and log debug info
|
||||
my $strOperation = logDebugParam(__PACKAGE__ . '->nameGet');
|
||||
|
||||
# Return from function and log return values if any
|
||||
return logDebugReturn
|
||||
(
|
||||
$strOperation,
|
||||
{name => 'strName', value => ${$self->{oDoc}}{name}, trace => true}
|
||||
);
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# valueGet
|
||||
####################################################################################################################################
|
||||
sub valueGet
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
# Assign function parameters, defaults, and log debug info
|
||||
my $strOperation = logDebugParam(__PACKAGE__ . '->valueGet');
|
||||
|
||||
# Return from function and log return values if any
|
||||
return logDebugReturn
|
||||
(
|
||||
$strOperation,
|
||||
{name => 'strValue', value => ${$self->{oDoc}}{value}, trace => true}
|
||||
);
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# valueSet
|
||||
####################################################################################################################################
|
||||
sub valueSet
|
||||
{
|
||||
my $self = shift;
|
||||
my $strValue = shift;
|
||||
|
||||
# Assign function parameters, defaults, and log debug info
|
||||
my $strOperation = logDebugParam(__PACKAGE__ . '->valueSet');
|
||||
|
||||
# Set the value
|
||||
${$self->{oDoc}}{value} = $strValue;
|
||||
|
||||
# Return from function and log return values if any
|
||||
return logDebugReturn($strOperation);
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# paramGet
|
||||
#
|
||||
# Get a parameter from a node.
|
||||
####################################################################################################################################
|
||||
sub paramGet
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
# Assign function parameters, defaults, and log debug info
|
||||
my
|
||||
(
|
||||
$strOperation,
|
||||
$strName,
|
||||
$bRequired,
|
||||
$strDefault,
|
||||
$strType
|
||||
) =
|
||||
logDebugParam
|
||||
(
|
||||
__PACKAGE__ . '->paramGet', \@_,
|
||||
{name => 'strName', trace => true},
|
||||
{name => 'bRequired', default => true, trace => true},
|
||||
{name => 'strDefault', required => false, trace => true},
|
||||
{name => 'strType', default => 'param', trace => true}
|
||||
);
|
||||
|
||||
my $strValue = ${$self->{oDoc}}{$strType}{$strName};
|
||||
|
||||
if (!defined($strValue))
|
||||
{
|
||||
if ($bRequired)
|
||||
{
|
||||
confess "${strType} '${strName}' is required in node '$self->{strName}'";
|
||||
}
|
||||
|
||||
$strValue = $strDefault;
|
||||
}
|
||||
|
||||
# Return from function and log return values if any
|
||||
return logDebugReturn
|
||||
(
|
||||
$strOperation,
|
||||
{name => 'strValue', value => $strValue, trace => true}
|
||||
);
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# paramTest
|
||||
#
|
||||
# Test that a parameter exists or has a certain value.
|
||||
####################################################################################################################################
|
||||
sub paramTest
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
# Assign function parameters, defaults, and log debug info
|
||||
my
|
||||
(
|
||||
$strOperation,
|
||||
$strName,
|
||||
$strExpectedValue,
|
||||
$strType
|
||||
) =
|
||||
logDebugParam
|
||||
(
|
||||
__PACKAGE__ . '->paramTest', \@_,
|
||||
{name => 'strName', trace => true},
|
||||
{name => 'strExpectedValue', required => false, trace => true},
|
||||
{name => 'strType', default => 'param', trace => true}
|
||||
);
|
||||
|
||||
my $bResult = true;
|
||||
my $strValue = $self->paramGet($strName, false, undef, $strType);
|
||||
|
||||
if (!defined($strValue))
|
||||
{
|
||||
$bResult = false;
|
||||
}
|
||||
elsif (defined($strExpectedValue) && $strValue ne $strExpectedValue)
|
||||
{
|
||||
$bResult = false;
|
||||
}
|
||||
|
||||
# Return from function and log return values if any
|
||||
return logDebugReturn
|
||||
(
|
||||
$strOperation,
|
||||
{name => 'bResult', value => $bResult, trace => true}
|
||||
);
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# paramSet
|
||||
#
|
||||
# Set a parameter in a node.
|
||||
####################################################################################################################################
|
||||
sub paramSet
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
# Assign function parameters, defaults, and log debug info
|
||||
my
|
||||
(
|
||||
$strOperation,
|
||||
$strName,
|
||||
$strValue,
|
||||
$strType
|
||||
) =
|
||||
logDebugParam
|
||||
(
|
||||
__PACKAGE__ . '->paramSet', \@_,
|
||||
{name => 'strName', trace => true},
|
||||
{name => 'strValue', required => false, trace => true},
|
||||
{name => 'strType', default => 'param', trace => true}
|
||||
);
|
||||
|
||||
${$self->{oDoc}}{$strType}{$strName} = $strValue;
|
||||
|
||||
# Return from function and log return values if any
|
||||
logDebugReturn($strOperation);
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# fieldGet
|
||||
#
|
||||
# Get a field from a node.
|
||||
####################################################################################################################################
|
||||
sub fieldGet
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
return $self->paramGet(shift, shift, shift, 'field');
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# fieldTest
|
||||
#
|
||||
# Test if a field exists.
|
||||
####################################################################################################################################
|
||||
sub fieldTest
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
return $self->paramTest(shift, shift, 'field');
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# textGet
|
||||
#
|
||||
# Get a field from a node.
|
||||
####################################################################################################################################
|
||||
sub textGet
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
return $self->nodeBless($self->paramGet('text', shift, shift, 'field'));
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# textSet
|
||||
#
|
||||
# Get a field from a node.
|
||||
####################################################################################################################################
|
||||
sub textSet
|
||||
{
|
||||
my $self = shift;
|
||||
my $oText = shift;
|
||||
|
||||
if (blessed($oText) && $oText->isa('pgBackRestDoc::Common::Doc'))
|
||||
{
|
||||
$oText = $oText->{oDoc};
|
||||
}
|
||||
elsif (ref($oText) ne 'HASH')
|
||||
{
|
||||
$oText = {name => 'text', children => [$oText]};
|
||||
}
|
||||
|
||||
return $self->paramSet('text', $oText, 'field');
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# fieldSet
|
||||
#
|
||||
# Set a parameter in a node.
|
||||
####################################################################################################################################
|
||||
sub fieldSet
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
$self->paramSet(shift, shift, 'field');
|
||||
}
|
||||
|
||||
1;
|
885
doc/lib/pgBackRestDoc/Common/DocConfig.pm
Normal file
885
doc/lib/pgBackRestDoc/Common/DocConfig.pm
Normal file
@ -0,0 +1,885 @@
|
||||
####################################################################################################################################
|
||||
# DOC CONFIG MODULE
|
||||
####################################################################################################################################
|
||||
package pgBackRestDoc::Common::DocConfig;
|
||||
|
||||
use strict;
|
||||
use warnings FATAL => qw(all);
|
||||
use Carp qw(confess);
|
||||
|
||||
use Exporter qw(import);
|
||||
our @EXPORT = qw();
|
||||
use File::Basename qw(dirname);
|
||||
|
||||
use pgBackRest::Version;
|
||||
|
||||
use pgBackRestBuild::Config::Data;
|
||||
|
||||
use pgBackRestDoc::Common::Log;
|
||||
use pgBackRestDoc::Common::String;
|
||||
|
||||
####################################################################################################################################
|
||||
# Help types
|
||||
####################################################################################################################################
|
||||
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_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';
|
||||
push @EXPORT, qw(CONFIG_HELP_SECTION);
|
||||
use constant CONFIG_HELP_SUMMARY => 'summary';
|
||||
push @EXPORT, qw(CONFIG_HELP_SUMMARY);
|
||||
|
||||
use constant CONFIG_HELP_SOURCE => 'source';
|
||||
push @EXPORT, qw(CONFIG_HELP_SOURCE);
|
||||
use constant CONFIG_HELP_SOURCE_DEFAULT => 'default';
|
||||
use constant CONFIG_HELP_SOURCE_SECTION => CONFIG_HELP_SECTION;
|
||||
use constant CONFIG_HELP_SOURCE_COMMAND => CONFIG_HELP_COMMAND;
|
||||
push @EXPORT, qw(CONFIG_HELP_SOURCE_COMMAND);
|
||||
|
||||
####################################################################################################################################
|
||||
# Config Section Types
|
||||
####################################################################################################################################
|
||||
use constant CFGDEF_COMMAND => 'command';
|
||||
use constant CFGDEF_GENERAL => 'general';
|
||||
use constant CFGDEF_LOG => 'log';
|
||||
use constant CFGDEF_REPOSITORY => 'repository';
|
||||
|
||||
####################################################################################################################################
|
||||
# Option define hash
|
||||
####################################################################################################################################
|
||||
my $rhConfigDefine = cfgDefine();
|
||||
|
||||
####################################################################################################################################
|
||||
# Returns the option defines based on the command.
|
||||
####################################################################################################################################
|
||||
sub docConfigCommandDefine
|
||||
{
|
||||
my $strOption = shift;
|
||||
my $strCommand = shift;
|
||||
|
||||
if (defined($strCommand))
|
||||
{
|
||||
return defined($rhConfigDefine->{$strOption}{&CFGDEF_COMMAND}) &&
|
||||
defined($rhConfigDefine->{$strOption}{&CFGDEF_COMMAND}{$strCommand}) &&
|
||||
ref($rhConfigDefine->{$strOption}{&CFGDEF_COMMAND}{$strCommand}) eq 'HASH' ?
|
||||
$rhConfigDefine->{$strOption}{&CFGDEF_COMMAND}{$strCommand} : undef;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# Does the option have a default for this command?
|
||||
####################################################################################################################################
|
||||
sub docConfigOptionDefault
|
||||
{
|
||||
my $strOption = shift;
|
||||
my $strCommand = shift;
|
||||
|
||||
# Get the command define
|
||||
my $oCommandDefine = docConfigCommandDefine($strOption, $strCommand);
|
||||
|
||||
# Check for default in command
|
||||
my $strDefault = defined($oCommandDefine) ? $$oCommandDefine{&CFGDEF_DEFAULT} : undef;
|
||||
|
||||
# If defined return, else try to grab the global default
|
||||
return defined($strDefault) ? $strDefault : $rhConfigDefine->{$strOption}{&CFGDEF_DEFAULT};
|
||||
}
|
||||
|
||||
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
|
||||
####################################################################################################################################
|
||||
sub new
|
||||
{
|
||||
my $class = shift; # Class name
|
||||
|
||||
# Create the class hash
|
||||
my $self = {};
|
||||
bless $self, $class;
|
||||
|
||||
# Assign function parameters, defaults, and log debug info
|
||||
(
|
||||
my $strOperation,
|
||||
$self->{oDoc},
|
||||
$self->{oDocRender}
|
||||
) =
|
||||
logDebugParam
|
||||
(
|
||||
__PACKAGE__ . '->new', \@_,
|
||||
{name => 'oDoc'},
|
||||
{name => 'oDocRender', required => false}
|
||||
);
|
||||
|
||||
$self->process();
|
||||
|
||||
# Return from function and log return values if any
|
||||
return logDebugReturn
|
||||
(
|
||||
$strOperation,
|
||||
{name => 'self', value => $self}
|
||||
);
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# process
|
||||
#
|
||||
# Parse the xml doc into commands and options.
|
||||
####################################################################################################################################
|
||||
sub process
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
# Assign function parameters, defaults, and log debug info
|
||||
my $strOperation = logDebugParam(__PACKAGE__ . '->process');
|
||||
|
||||
# Iterate through all commands
|
||||
my $oDoc = $self->{oDoc};
|
||||
my $oConfigHash = {};
|
||||
|
||||
foreach my $strCommand (cfgDefineCommandList())
|
||||
{
|
||||
my $oCommandDoc = $oDoc->nodeGet('operation')->nodeGet('command-list')->nodeGetById('command', $strCommand);
|
||||
|
||||
$$oConfigHash{&CONFIG_HELP_COMMAND}{$strCommand} = {};
|
||||
my $oCommand = $$oConfigHash{&CONFIG_HELP_COMMAND}{$strCommand};
|
||||
|
||||
$$oCommand{&CONFIG_HELP_SUMMARY} = $oCommandDoc->nodeGet('summary')->textGet();
|
||||
$$oCommand{&CONFIG_HELP_DESCRIPTION} = $oCommandDoc->textGet();
|
||||
}
|
||||
|
||||
# Iterate through all options
|
||||
my $oOptionDefine = cfgDefine();
|
||||
|
||||
foreach my $strOption (sort(keys(%{$oOptionDefine})))
|
||||
{
|
||||
# Skip options that are internal only for all commands (test options)
|
||||
next if $oOptionDefine->{$strOption}{&CFGDEF_INTERNAL};
|
||||
|
||||
# Iterate through all commands
|
||||
my @stryCommandList = sort(keys(%{defined($$oOptionDefine{$strOption}{&CFGDEF_COMMAND}) ?
|
||||
$$oOptionDefine{$strOption}{&CFGDEF_COMMAND} : $$oConfigHash{&CONFIG_HELP_COMMAND}}));
|
||||
|
||||
foreach my $strCommand (@stryCommandList)
|
||||
{
|
||||
if (!defined($$oConfigHash{&CONFIG_HELP_COMMAND}{$strCommand}))
|
||||
{
|
||||
next;
|
||||
}
|
||||
|
||||
if (ref(\$$oOptionDefine{$strOption}{&CFGDEF_COMMAND}{$strCommand}) eq 'SCALAR' &&
|
||||
$$oOptionDefine{$strOption}{&CFGDEF_COMMAND}{$strCommand} == false)
|
||||
{
|
||||
next;
|
||||
}
|
||||
|
||||
# Skip options that are internal only for the current command
|
||||
next if $oOptionDefine->{$strOption}{&CFGDEF_COMMAND}{$strCommand}{&CFGDEF_INTERNAL};
|
||||
|
||||
my $oCommandDoc = $oDoc->nodeGet('operation')->nodeGet('command-list')->nodeGetById('command', $strCommand);
|
||||
|
||||
# First check if the option is documented in the command
|
||||
my $oOptionDoc;
|
||||
my $strOptionSource;
|
||||
my $oCommandOptionList = $oCommandDoc->nodeGet('option-list', false);
|
||||
|
||||
if (defined($oCommandOptionList))
|
||||
{
|
||||
$oOptionDoc = $oCommandOptionList->nodeGetById('option', $strOption, false);
|
||||
|
||||
$strOptionSource = CONFIG_HELP_SOURCE_COMMAND if (defined($oOptionDoc));
|
||||
}
|
||||
|
||||
# If the option wasn't found keep looking
|
||||
my $strSection;
|
||||
|
||||
if (!defined($oOptionDoc))
|
||||
{
|
||||
# Next see if it's documented in the section
|
||||
if (defined($$oOptionDefine{$strOption}{&CFGDEF_SECTION}))
|
||||
{
|
||||
# &log(INFO, " trying section ${strSection}");
|
||||
foreach my $oSectionNode ($oDoc->nodeGet('config')->nodeGet('config-section-list')->nodeList())
|
||||
{
|
||||
my $oOptionDocCheck = $oSectionNode->nodeGetById('config-key-list')
|
||||
->nodeGetById('config-key', $strOption, false);
|
||||
|
||||
if ($oOptionDocCheck)
|
||||
{
|
||||
if (defined($oOptionDoc))
|
||||
{
|
||||
confess 'option exists in more than one section';
|
||||
}
|
||||
|
||||
$oOptionDoc = $oOptionDocCheck;
|
||||
$strOptionSource = CONFIG_HELP_SOURCE_SECTION;
|
||||
$strSection = $oSectionNode->paramGet('id');
|
||||
}
|
||||
}
|
||||
}
|
||||
# If no section is defined then look in the default command option list
|
||||
else
|
||||
{
|
||||
$oOptionDoc = $oDoc->nodeGet('operation')->nodeGet('operation-general')->nodeGet('option-list')
|
||||
->nodeGetById('option', $strOption, false);
|
||||
|
||||
$strOptionSource = CONFIG_HELP_SOURCE_DEFAULT if (defined($oOptionDoc));
|
||||
}
|
||||
}
|
||||
|
||||
# If the option wasn't found then error
|
||||
if (!defined($oOptionDoc))
|
||||
{
|
||||
confess &log(ERROR, "unable to find option '${strOption}' for command '${strCommand}'")
|
||||
}
|
||||
|
||||
# if the option is documented in the command then it should be accessible from the command line only.
|
||||
if (!defined($strSection))
|
||||
{
|
||||
if (defined($$oOptionDefine{$strOption}{&CFGDEF_SECTION}))
|
||||
{
|
||||
&log(ERROR,
|
||||
"option ${strOption} defined in command ${strCommand} must not have " . CFGDEF_SECTION .
|
||||
" defined");
|
||||
}
|
||||
}
|
||||
|
||||
# Store the option in the command
|
||||
$$oConfigHash{&CONFIG_HELP_COMMAND}{$strCommand}{&CONFIG_HELP_OPTION}{$strOption}{&CONFIG_HELP_SOURCE} =
|
||||
$strOptionSource;
|
||||
|
||||
my $oCommandOption = $$oConfigHash{&CONFIG_HELP_COMMAND}{$strCommand}{&CONFIG_HELP_OPTION}{$strOption};
|
||||
|
||||
$$oCommandOption{&CONFIG_HELP_SUMMARY} = $oOptionDoc->nodeGet('summary')->textGet();
|
||||
$$oCommandOption{&CONFIG_HELP_DESCRIPTION} = $oOptionDoc->textGet();
|
||||
$$oCommandOption{&CONFIG_HELP_EXAMPLE} = $oOptionDoc->fieldGet('example');
|
||||
|
||||
$$oCommandOption{&CONFIG_HELP_NAME} = $oOptionDoc->paramGet('name');
|
||||
|
||||
# Generate a list of alternate names
|
||||
if (defined($rhConfigDefine->{$strOption}{&CFGDEF_NAME_ALT}))
|
||||
{
|
||||
my $rhNameAlt = {};
|
||||
|
||||
foreach my $strNameAlt (sort(keys(%{$rhConfigDefine->{$strOption}{&CFGDEF_NAME_ALT}})))
|
||||
{
|
||||
$strNameAlt =~ s/\?//g;
|
||||
|
||||
if ($strNameAlt ne $strOption)
|
||||
{
|
||||
$rhNameAlt->{$strNameAlt} = true;
|
||||
}
|
||||
}
|
||||
|
||||
my @stryNameAlt = sort(keys(%{$rhNameAlt}));
|
||||
|
||||
if (@stryNameAlt > 0)
|
||||
{
|
||||
if (@stryNameAlt != 1)
|
||||
{
|
||||
confess &log(
|
||||
ERROR, "multiple alt names are not supported for option '${strOption}': " . join(', ', @stryNameAlt));
|
||||
}
|
||||
|
||||
$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)
|
||||
{
|
||||
$$oConfigHash{&CONFIG_HELP_OPTION}{$strOption}{&CONFIG_HELP_SUMMARY} = $$oCommandOption{&CONFIG_HELP_SUMMARY};
|
||||
|
||||
my $oOption = $$oConfigHash{&CONFIG_HELP_OPTION}{$strOption};
|
||||
|
||||
if (defined($strSection))
|
||||
{
|
||||
$$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');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Store the config hash
|
||||
$self->{oConfigHash} = $oConfigHash;
|
||||
|
||||
# Return from function and log return values if any
|
||||
logDebugReturn($strOperation);
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# manGet
|
||||
#
|
||||
# Generate the man page.
|
||||
####################################################################################################################################
|
||||
sub manGet
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
# Assign function parameters, defaults, and log debug info
|
||||
my
|
||||
(
|
||||
$strOperation,
|
||||
$oManifest
|
||||
) =
|
||||
logDebugParam
|
||||
(
|
||||
__PACKAGE__ . '->manGet', \@_,
|
||||
{name => 'oManifest'}
|
||||
);
|
||||
|
||||
# Get index.xml to pull various text from
|
||||
my $oIndexDoc = ${$oManifest->sourceGet('index')}{doc};
|
||||
|
||||
# Write the header
|
||||
my $strManPage =
|
||||
"NAME\n" .
|
||||
' ' . PROJECT_NAME . ' - ' . $oManifest->variableReplace($oIndexDoc->paramGet('subtitle')) . "\n\n" .
|
||||
"SYNOPSIS\n" .
|
||||
' ' . PROJECT_EXE . ' [options] [command]';
|
||||
|
||||
# Output the description (first two paragraphs of index.xml introduction)
|
||||
my $iParaTotal = 0;
|
||||
|
||||
$strManPage .= "\n\n" .
|
||||
"DESCRIPTION";
|
||||
|
||||
foreach my $oPara ($oIndexDoc->nodeGetById('section', 'introduction')->nodeList('p'))
|
||||
{
|
||||
$strManPage .= ($iParaTotal == 0 ? "\n" : "\n\n") . ' ' .
|
||||
manGetFormatText($oManifest->variableReplace($self->{oDocRender}->processText($oPara->textGet())), 80, 2);
|
||||
|
||||
last;
|
||||
}
|
||||
|
||||
# Build command and config hashes
|
||||
my $hConfigDefine = cfgDefine();
|
||||
my $hConfig = $self->{oConfigHash};
|
||||
my $hCommandList = {};
|
||||
my $iCommandMaxLen = 0;
|
||||
my $hOptionList = {};
|
||||
my $iOptionMaxLen = 0;
|
||||
|
||||
foreach my $strCommand (sort(keys(%{$$hConfig{&CONFIG_HELP_COMMAND}})))
|
||||
{
|
||||
my $hCommand = $$hConfig{&CONFIG_HELP_COMMAND}{$strCommand};
|
||||
$iCommandMaxLen = length($strCommand) > $iCommandMaxLen ? length($strCommand) : $iCommandMaxLen;
|
||||
|
||||
$$hCommandList{$strCommand}{summary} = $$hCommand{&CONFIG_HELP_SUMMARY};
|
||||
|
||||
if (defined($$hCommand{&CONFIG_HELP_OPTION}))
|
||||
{
|
||||
foreach my $strOption (sort(keys(%{$$hCommand{&CONFIG_HELP_OPTION}})))
|
||||
{
|
||||
my $hOption = $$hCommand{&CONFIG_HELP_OPTION}{$strOption};
|
||||
|
||||
if ($$hOption{&CONFIG_HELP_SOURCE} eq CONFIG_HELP_SOURCE_COMMAND)
|
||||
{
|
||||
$iOptionMaxLen = length($strOption) > $iOptionMaxLen ? length($strOption) : $iOptionMaxLen;
|
||||
|
||||
$$hOptionList{$strCommand}{$strOption}{&CONFIG_HELP_SUMMARY} = $$hOption{&CONFIG_HELP_SUMMARY};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach my $strOption (sort(keys(%{$$hConfig{&CONFIG_HELP_OPTION}})))
|
||||
{
|
||||
my $hOption = $$hConfig{&CONFIG_HELP_OPTION}{$strOption};
|
||||
$iOptionMaxLen = length($strOption) > $iOptionMaxLen ? length($strOption) : $iOptionMaxLen;
|
||||
my $strSection = defined($$hOption{&CONFIG_HELP_SECTION}) ? $$hOption{&CONFIG_HELP_SECTION} : CFGDEF_GENERAL;
|
||||
|
||||
$$hOptionList{$strSection}{$strOption}{&CONFIG_HELP_SUMMARY} = $$hOption{&CONFIG_HELP_SUMMARY};
|
||||
}
|
||||
|
||||
# Output Commands
|
||||
$strManPage .= "\n\n" .
|
||||
'COMMANDS';
|
||||
|
||||
foreach my $strCommand (sort(keys(%{$hCommandList})))
|
||||
{
|
||||
# Construct the summary
|
||||
my $strSummary = $oManifest->variableReplace($self->{oDocRender}->processText($$hCommandList{$strCommand}{summary}));
|
||||
# $strSummary = lcfirst(substr($strSummary, 0, length($strSummary) - 1));
|
||||
|
||||
# Output the summary
|
||||
$strManPage .=
|
||||
"\n " . "${strCommand}" . (' ' x ($iCommandMaxLen - length($strCommand))) . ' ' .
|
||||
manGetFormatText($strSummary, 80, $iCommandMaxLen + 4);
|
||||
}
|
||||
|
||||
# Output options
|
||||
my $bFirst = true;
|
||||
$strManPage .= "\n\n" .
|
||||
'OPTIONS';
|
||||
|
||||
foreach my $strSection (sort(keys(%{$hOptionList})))
|
||||
{
|
||||
$strManPage .= ($bFirst ?'' : "\n") . "\n " . ucfirst($strSection) . ' Options:';
|
||||
|
||||
foreach my $strOption (sort(keys(%{$$hOptionList{$strSection}})))
|
||||
{
|
||||
my $hOption = $$hOptionList{$strSection}{$strOption};
|
||||
|
||||
# Construct the default
|
||||
my $strCommand = grep(/$strSection/i, cfgDefineCommandList()) ? $strSection : undef;
|
||||
my $strDefault = docConfigOptionDefault($strOption, $strCommand);
|
||||
|
||||
if (defined($strDefault))
|
||||
{
|
||||
if ($strOption eq CFGOPT_REPO_HOST_CMD || $strOption eq CFGOPT_PG_HOST_CMD)
|
||||
{
|
||||
$strDefault = PROJECT_EXE;
|
||||
}
|
||||
elsif ($$hConfigDefine{$strOption}{&CFGDEF_TYPE} eq &CFGDEF_TYPE_BOOLEAN)
|
||||
{
|
||||
$strDefault = $strDefault ? 'y' : 'n';
|
||||
}
|
||||
}
|
||||
#
|
||||
# use Data::Dumper; confess Dumper($$hOption{&CONFIG_HELP_SUMMARY});
|
||||
|
||||
# Construct the summary
|
||||
my $strSummary = $oManifest->variableReplace($self->{oDocRender}->processText($$hOption{&CONFIG_HELP_SUMMARY}));
|
||||
|
||||
$strSummary = $strSummary . (defined($strDefault) ? " [default=${strDefault}]" : '');
|
||||
|
||||
# Output the summary
|
||||
$strManPage .=
|
||||
"\n " . "--${strOption}" . (' ' x ($iOptionMaxLen - length($strOption))) . ' ' .
|
||||
manGetFormatText($strSummary, 80, $iOptionMaxLen + 8);
|
||||
}
|
||||
|
||||
$bFirst = false;
|
||||
}
|
||||
|
||||
# Write files, examples, and references
|
||||
$strManPage .= "\n\n" .
|
||||
"FILES\n" .
|
||||
"\n" .
|
||||
' ' . docConfigOptionDefault(CFGOPT_CONFIG) . "\n" .
|
||||
' ' . docConfigOptionDefault(CFGOPT_REPO_PATH) . "\n" .
|
||||
' ' . docConfigOptionDefault(CFGOPT_LOG_PATH) . "\n" .
|
||||
' ' . docConfigOptionDefault(CFGOPT_SPOOL_PATH) . "\n" .
|
||||
' ' . docConfigOptionDefault(CFGOPT_LOCK_PATH) . "\n" .
|
||||
"\n" .
|
||||
"EXAMPLES\n" .
|
||||
"\n" .
|
||||
" * Create a backup of the PostgreSQL `main` cluster:\n" .
|
||||
"\n" .
|
||||
' $ ' . PROJECT_EXE . ' --' . CFGOPT_STANZA . "=main backup\n" .
|
||||
"\n" .
|
||||
' The `main` cluster should be configured in `' . docConfigOptionDefault(CFGOPT_CONFIG) . "`\n" .
|
||||
"\n" .
|
||||
" * Show all available backups:\n" .
|
||||
"\n" .
|
||||
' $ ' . PROJECT_EXE . ' ' . CFGCMD_INFO . "\n" .
|
||||
"\n" .
|
||||
" * Show all available backups for a specific cluster:\n" .
|
||||
"\n" .
|
||||
' $ ' . PROJECT_EXE . ' --' . CFGOPT_STANZA . '=main ' . CFGCMD_INFO . "\n" .
|
||||
"\n" .
|
||||
" * Show backup specific options:\n" .
|
||||
"\n" .
|
||||
' $ ' . PROJECT_EXE . ' ' . CFGCMD_HELP . ' ' . CFGCMD_BACKUP . "\n" .
|
||||
"\n" .
|
||||
"SEE ALSO\n" .
|
||||
"\n" .
|
||||
' /usr/share/doc/' . PROJECT_EXE . "-doc/html/index.html\n" .
|
||||
' ' . $oManifest->variableReplace('{[backrest-url-base]}') . "\n";
|
||||
|
||||
return $strManPage;
|
||||
}
|
||||
|
||||
# Helper function for manGet() used to format text by indenting and splitting
|
||||
sub manGetFormatText
|
||||
{
|
||||
my $strLine = shift;
|
||||
my $iLength = shift;
|
||||
my $iIndentRest = shift;
|
||||
|
||||
my $strPart;
|
||||
my $strResult;
|
||||
my $bFirst = true;
|
||||
|
||||
do
|
||||
{
|
||||
my $iIndent = $bFirst ? 0 : $iIndentRest;
|
||||
|
||||
($strPart, $strLine) = stringSplit($strLine, ' ', $iLength - $iIndentRest);
|
||||
|
||||
$strResult .= ($bFirst ? '' : "\n") . (' ' x $iIndent) . trim($strPart);
|
||||
|
||||
$bFirst = false;
|
||||
}
|
||||
while (defined($strLine));
|
||||
|
||||
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}})))
|
||||
{
|
||||
$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}})))
|
||||
{
|
||||
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 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;
|
||||
|
||||
my $strSection = CONFIG_HELP_COMMAND;
|
||||
my $oOption = $$oConfigHelpData{&CONFIG_HELP_COMMAND}{$strCommand}{&CONFIG_HELP_OPTION}{$strOption};
|
||||
|
||||
if ($$oOption{&CONFIG_HELP_SOURCE} eq CONFIG_HELP_SOURCE_DEFAULT)
|
||||
{
|
||||
$strSection = CFGDEF_GENERAL;
|
||||
}
|
||||
elsif ($$oOption{&CONFIG_HELP_SOURCE} eq CONFIG_HELP_SOURCE_SECTION)
|
||||
{
|
||||
$oOption = $$oConfigHelpData{&CONFIG_HELP_OPTION}{$strOption};
|
||||
|
||||
if (defined($$oOption{&CONFIG_HELP_SECTION}) && $strSection ne $strCommand)
|
||||
{
|
||||
$strSection = $$oOption{&CONFIG_HELP_SECTION};
|
||||
}
|
||||
|
||||
if (($strSection ne CFGDEF_GENERAL && $strSection ne CFGDEF_LOG &&
|
||||
$strSection ne CFGDEF_REPOSITORY && $strSection ne CFGDEF_SECTION_STANZA) ||
|
||||
$strSection eq $strCommand)
|
||||
{
|
||||
$strSection = CONFIG_HELP_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});
|
||||
|
||||
$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_PREFIX};
|
||||
my $strOptionIndex = defined($strOptionPrefix) ?
|
||||
"${strOptionPrefix}1-" . substr($strOption, length($strOptionPrefix) + 1) : $strOption;
|
||||
|
||||
if (defined($strCommand))
|
||||
{
|
||||
if (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
|
||||
{
|
||||
$strExample = "--${strOptionIndex}=" . $$oOptionHash{&CONFIG_HELP_EXAMPLE};
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$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;
|
1144
doc/lib/pgBackRestDoc/Common/DocExecute.pm
Normal file
1144
doc/lib/pgBackRestDoc/Common/DocExecute.pm
Normal file
File diff suppressed because it is too large
Load Diff
771
doc/lib/pgBackRestDoc/Common/DocManifest.pm
Normal file
771
doc/lib/pgBackRestDoc/Common/DocManifest.pm
Normal file
@ -0,0 +1,771 @@
|
||||
####################################################################################################################################
|
||||
# DOC MANIFEST MODULE
|
||||
####################################################################################################################################
|
||||
package pgBackRestDoc::Common::DocManifest;
|
||||
|
||||
use strict;
|
||||
use warnings FATAL => qw(all);
|
||||
use Carp qw(confess);
|
||||
|
||||
use Cwd qw(abs_path);
|
||||
use Exporter qw(import);
|
||||
our @EXPORT = qw();
|
||||
use File::Basename qw(dirname);
|
||||
use JSON::PP;
|
||||
|
||||
use pgBackRestDoc::Common::Log;
|
||||
use pgBackRestDoc::Common::String;
|
||||
|
||||
####################################################################################################################################
|
||||
# File constants
|
||||
####################################################################################################################################
|
||||
use constant FILE_MANIFEST => 'manifest.xml';
|
||||
|
||||
####################################################################################################################################
|
||||
# Render constants
|
||||
####################################################################################################################################
|
||||
use constant RENDER => 'render';
|
||||
use constant RENDER_COMPACT => 'compact';
|
||||
push @EXPORT, qw(RENDER_COMPACT);
|
||||
use constant RENDER_FILE => 'file';
|
||||
use constant RENDER_MENU => 'menu';
|
||||
push @EXPORT, qw(RENDER_MENU);
|
||||
use constant RENDER_PRETTY => 'pretty';
|
||||
push @EXPORT, qw(RENDER_PRETTY);
|
||||
|
||||
use constant RENDER_TYPE => 'type';
|
||||
use constant RENDER_TYPE_HTML => 'html';
|
||||
push @EXPORT, qw(RENDER_TYPE_HTML);
|
||||
use constant RENDER_TYPE_MARKDOWN => 'markdown';
|
||||
push @EXPORT, qw(RENDER_TYPE_MARKDOWN);
|
||||
use constant RENDER_TYPE_PDF => 'pdf';
|
||||
push @EXPORT, qw(RENDER_TYPE_PDF);
|
||||
|
||||
####################################################################################################################################
|
||||
# CONSTRUCTOR
|
||||
####################################################################################################################################
|
||||
sub new
|
||||
{
|
||||
my $class = shift; # Class name
|
||||
|
||||
# Create the class hash
|
||||
my $self = {};
|
||||
bless $self, $class;
|
||||
|
||||
# Assign function parameters, defaults, and log debug info
|
||||
(
|
||||
my $strOperation,
|
||||
$self->{oStorage},
|
||||
$self->{stryRequire},
|
||||
$self->{stryInclude},
|
||||
$self->{stryExclude},
|
||||
$self->{rhKeyVariableOverride},
|
||||
my $rhVariableOverride,
|
||||
$self->{strDocPath},
|
||||
$self->{bDeploy},
|
||||
$self->{bCacheOnly},
|
||||
$self->{bPre},
|
||||
) =
|
||||
logDebugParam
|
||||
(
|
||||
__PACKAGE__ . '->new', \@_,
|
||||
{name => 'oStorage'},
|
||||
{name => 'stryRequire'},
|
||||
{name => 'stryInclude'},
|
||||
{name => 'stryExclude'},
|
||||
{name => 'rhKeyVariableOverride', required => false},
|
||||
{name => 'rhVariableOverride', required => false},
|
||||
{name => 'strDocPath', required => false},
|
||||
{name => 'bDeploy', required => false},
|
||||
{name => 'bCacheOnly', required => false},
|
||||
{name => 'bPre', required => false, default => false},
|
||||
);
|
||||
|
||||
# Set the bin path
|
||||
$self->{strBinPath} = abs_path(dirname($0));
|
||||
|
||||
# Set the base path if it was not passed in
|
||||
if (!defined($self->{strDocPath}))
|
||||
{
|
||||
$self->{strDocPath} = $self->{strBinPath};
|
||||
}
|
||||
|
||||
# Set cache file names
|
||||
$self->{strExeCacheLocal} = $self->{strDocPath} . "/output/exe.cache";
|
||||
$self->{strExeCacheDeploy} = $self->{strDocPath} . "/resource/exe.cache";
|
||||
|
||||
# Load the manifest
|
||||
$self->{oManifestXml} = new pgBackRestDoc::Common::Doc("$self->{strDocPath}/manifest.xml");
|
||||
|
||||
# Iterate the sources
|
||||
$self->{oManifest} = {};
|
||||
|
||||
foreach my $oSource ($self->{oManifestXml}->nodeGet('source-list')->nodeList('source'))
|
||||
{
|
||||
my $oSourceHash = {};
|
||||
my $strKey = $oSource->paramGet('key');
|
||||
my $strSourceType = $oSource->paramGet('type', false);
|
||||
|
||||
logDebugMisc
|
||||
(
|
||||
$strOperation, 'load source',
|
||||
{name => 'strKey', value => $strKey},
|
||||
{name => 'strSourceType', value => $strSourceType}
|
||||
);
|
||||
|
||||
# Skip sources in exclude list
|
||||
if (grep(/^$strKey$/, @{$self->{stryExclude}}))
|
||||
{
|
||||
next;
|
||||
}
|
||||
|
||||
$$oSourceHash{doc} = new pgBackRestDoc::Common::Doc("$self->{strDocPath}/xml/${strKey}.xml");
|
||||
|
||||
# Read variables from source
|
||||
$self->variableListParse($$oSourceHash{doc}->nodeGet('variable-list', false), $rhVariableOverride);
|
||||
|
||||
${$self->{oManifest}}{source}{$strKey} = $oSourceHash;
|
||||
${$self->{oManifest}}{source}{$strKey}{strSourceType} = $strSourceType;
|
||||
}
|
||||
|
||||
# Iterate the renderers
|
||||
foreach my $oRender ($self->{oManifestXml}->nodeGet('render-list')->nodeList('render'))
|
||||
{
|
||||
my $oRenderHash = {};
|
||||
my $strType = $oRender->paramGet(RENDER_TYPE);
|
||||
|
||||
# Only one instance of each render type can be defined
|
||||
if (defined(${$self->{oManifest}}{&RENDER}{$strType}))
|
||||
{
|
||||
confess &log(ERROR, "render ${strType} has already been defined");
|
||||
}
|
||||
|
||||
# Get the file param
|
||||
$${oRenderHash}{file} = $oRender->paramGet(RENDER_FILE, false);
|
||||
$${oRenderHash}{&RENDER_COMPACT} = $oRender->paramGet(RENDER_COMPACT, false, 'n') eq 'y' ? true : false;
|
||||
$${oRenderHash}{&RENDER_PRETTY} = $oRender->paramGet(RENDER_PRETTY, false, 'n') eq 'y' ? true : false;
|
||||
$${oRenderHash}{&RENDER_MENU} = false;
|
||||
|
||||
logDebugMisc
|
||||
(
|
||||
$strOperation, ' load render',
|
||||
{name => 'strType', value => $strType},
|
||||
{name => 'strFile', value => $${oRenderHash}{file}}
|
||||
);
|
||||
|
||||
# Error if file is set and render type is not pdf
|
||||
if (defined($${oRenderHash}{file}) && $strType ne RENDER_TYPE_PDF)
|
||||
{
|
||||
confess &log(ERROR, 'only the pdf render type can have file set')
|
||||
}
|
||||
|
||||
# Iterate the render sources
|
||||
foreach my $oRenderOut ($oRender->nodeList('render-source'))
|
||||
{
|
||||
my $oRenderOutHash = {};
|
||||
my $strKey = $oRenderOut->paramGet('key');
|
||||
my $strSource = $oRenderOut->paramGet('source', false, $strKey);
|
||||
|
||||
# Skip sources in exclude list
|
||||
if (grep(/^$strSource$/, @{$self->{stryExclude}}))
|
||||
{
|
||||
next;
|
||||
}
|
||||
|
||||
# Skip sources not in include list
|
||||
if (@{$self->{stryInclude}} > 0 && !grep(/^$strSource$/, @{$self->{stryInclude}}))
|
||||
{
|
||||
next;
|
||||
}
|
||||
|
||||
# Preserve natural order
|
||||
push(@{$${oRenderHash}{stryOrder}}, $strKey);
|
||||
|
||||
$$oRenderOutHash{source} = $strSource;
|
||||
|
||||
# Get the filename
|
||||
if (defined($oRenderOut->paramGet('file', false)))
|
||||
{
|
||||
if ($strType eq RENDER_TYPE_HTML || $strType eq RENDER_TYPE_MARKDOWN)
|
||||
{
|
||||
$$oRenderOutHash{file} = $oRenderOut->paramGet('file');
|
||||
}
|
||||
else
|
||||
{
|
||||
confess &log(ERROR, "file is only valid with html or markdown render types");
|
||||
}
|
||||
}
|
||||
|
||||
# Get the menu caption
|
||||
if (defined($oRenderOut->paramGet('menu', false)) && $strType ne RENDER_TYPE_HTML)
|
||||
{
|
||||
confess &log(ERROR, "menu is only valid with html render type");
|
||||
}
|
||||
|
||||
if (defined($oRenderOut->paramGet('menu', false)))
|
||||
{
|
||||
$${oRenderHash}{&RENDER_MENU} = true;
|
||||
|
||||
if ($strType eq RENDER_TYPE_HTML)
|
||||
{
|
||||
$$oRenderOutHash{menu} = $oRenderOut->paramGet('menu', false);
|
||||
}
|
||||
else
|
||||
{
|
||||
confess &log(ERROR, 'only the html render type can have menu set');
|
||||
}
|
||||
}
|
||||
|
||||
logDebugMisc
|
||||
(
|
||||
$strOperation, ' load render source',
|
||||
{name => 'strKey', value => $strKey},
|
||||
{name => 'strSource', value => $strSource},
|
||||
{name => 'strMenu', value => $${oRenderOutHash}{menu}}
|
||||
);
|
||||
|
||||
$${oRenderHash}{out}{$strKey} = $oRenderOutHash;
|
||||
}
|
||||
|
||||
${$self->{oManifest}}{render}{$strType} = $oRenderHash;
|
||||
}
|
||||
|
||||
# Set the doc path variable
|
||||
$self->variableSet('doc-path', $self->{strDocPath});
|
||||
|
||||
# Read variables from manifest
|
||||
$self->variableListParse($self->{oManifestXml}->nodeGet('variable-list', false), $rhVariableOverride);
|
||||
|
||||
# Return from function and log return values if any
|
||||
return logDebugReturn
|
||||
(
|
||||
$strOperation,
|
||||
{name => 'self', value => $self}
|
||||
);
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# isBackRest
|
||||
#
|
||||
# Until all the backrest specific code can be abstracted, this function will identify when BackRest docs are being built.
|
||||
####################################################################################################################################
|
||||
sub isBackRest
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
return($self->variableTest('project-exe', 'pgbackrest'));
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# Evaluate the if condition for a node
|
||||
####################################################################################################################################
|
||||
sub evaluateIf
|
||||
{
|
||||
my $self = shift;
|
||||
my $oNode = shift;
|
||||
|
||||
my $bIf = true;
|
||||
|
||||
# Evaluate if condition
|
||||
if (defined($oNode->paramGet('if', false)))
|
||||
{
|
||||
my $strIf = $self->variableReplace($oNode->paramGet('if'));
|
||||
|
||||
# In this case we really do want to evaluate the contents and not treat it as a literal
|
||||
$bIf = eval($strIf);
|
||||
|
||||
# Error if the eval failed
|
||||
if ($@)
|
||||
{
|
||||
confess &log(ERROR, "unable to evaluate '${strIf}': $@");
|
||||
}
|
||||
}
|
||||
|
||||
return $bIf;
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# variableListParse
|
||||
#
|
||||
# Parse a variable list and store variables.
|
||||
####################################################################################################################################
|
||||
sub variableListParse
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
# Assign function parameters, defaults, and log debug info
|
||||
my
|
||||
(
|
||||
$strOperation,
|
||||
$oVariableList,
|
||||
$rhVariableOverride
|
||||
) =
|
||||
logDebugParam
|
||||
(
|
||||
__PACKAGE__ . '->variableListParse', \@_,
|
||||
{name => '$oVariableList', required => false},
|
||||
{name => '$rhVariableOverride', required => false}
|
||||
);
|
||||
|
||||
if (defined($oVariableList))
|
||||
{
|
||||
foreach my $oVariable ($oVariableList->nodeList('variable'))
|
||||
{
|
||||
if ($self->evaluateIf($oVariable))
|
||||
{
|
||||
my $strKey = $oVariable->paramGet('key');
|
||||
my $strValue = $self->variableReplace($oVariable->valueGet());
|
||||
|
||||
if ($oVariable->paramTest('eval', 'y'))
|
||||
{
|
||||
# In this case we really do want to evaluate the contents of strValue and not treat it as a literal.
|
||||
$strValue = eval($strValue);
|
||||
|
||||
if ($@)
|
||||
{
|
||||
confess &log(ERROR, "unable to evaluate ${strKey}: $@\n" . $oVariable->valueGet());
|
||||
}
|
||||
}
|
||||
|
||||
$self->variableSet($strKey, defined($rhVariableOverride->{$strKey}) ? $rhVariableOverride->{$strKey} : $strValue);
|
||||
|
||||
logDebugMisc
|
||||
(
|
||||
$strOperation, ' load variable',
|
||||
{name => 'strKey', value => $strKey},
|
||||
{name => 'strValue', value => $strValue}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Return from function and log return values if any
|
||||
return logDebugReturn($strOperation);
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# variableReplace
|
||||
#
|
||||
# Replace variables in the string.
|
||||
####################################################################################################################################
|
||||
sub variableReplace
|
||||
{
|
||||
my $self = shift;
|
||||
my $strBuffer = shift;
|
||||
my $strType = shift;
|
||||
|
||||
if (!defined($strBuffer))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach my $strName (sort(keys(%{$self->{oVariable}})))
|
||||
{
|
||||
my $strValue = $self->{oVariable}{$strName};
|
||||
|
||||
$strBuffer =~ s/\{\[$strName\]\}/$strValue/g;
|
||||
}
|
||||
|
||||
if (defined($strType) && $strType eq 'latex')
|
||||
{
|
||||
$strBuffer =~ s/\\\_/\_/g;
|
||||
$strBuffer =~ s/\_/\\\_/g;
|
||||
$strBuffer =~ s/\\\#/\#/g;
|
||||
$strBuffer =~ s/\#/\\\#/g;
|
||||
}
|
||||
|
||||
return $strBuffer;
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# variableSet
|
||||
#
|
||||
# Set a variable to be replaced later.
|
||||
####################################################################################################################################
|
||||
sub variableSet
|
||||
{
|
||||
my $self = shift;
|
||||
my $strKey = shift;
|
||||
my $strValue = shift;
|
||||
my $bForce = shift;
|
||||
|
||||
if (defined(${$self->{oVariable}}{$strKey}) && (!defined($bForce) || !$bForce))
|
||||
{
|
||||
confess &log(ERROR, "${strKey} variable is already defined");
|
||||
}
|
||||
|
||||
${$self->{oVariable}}{$strKey} = $self->variableReplace($strValue);
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# variableGet
|
||||
#
|
||||
# Get the current value of a variable.
|
||||
####################################################################################################################################
|
||||
sub variableGet
|
||||
{
|
||||
my $self = shift;
|
||||
my $strKey = shift;
|
||||
|
||||
return ${$self->{oVariable}}{$strKey};
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# variableTest
|
||||
#
|
||||
# Test that a variable is defined or has an expected value.
|
||||
####################################################################################################################################
|
||||
sub variableTest
|
||||
{
|
||||
my $self = shift;
|
||||
my $strKey = shift;
|
||||
my $strExpectedValue = shift;
|
||||
|
||||
# Get the variable
|
||||
my $strValue = ${$self->{oVariable}}{$strKey};
|
||||
|
||||
# Return false if it is not defined
|
||||
if (!defined($strValue))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
# Return false if it does not equal the expected value
|
||||
if (defined($strExpectedValue) && $strValue ne $strExpectedValue)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# Get list of source documents
|
||||
####################################################################################################################################
|
||||
sub sourceList
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
# Assign function parameters, defaults, and log debug info
|
||||
my ($strOperation) = logDebugParam(__PACKAGE__ . '->sourceList');
|
||||
|
||||
# Check that sources exist
|
||||
my @strySource;
|
||||
|
||||
if (defined(${$self->{oManifest}}{source}))
|
||||
{
|
||||
@strySource = sort(keys(%{${$self->{oManifest}}{source}}));
|
||||
}
|
||||
|
||||
# Return from function and log return values if any
|
||||
return logDebugReturn
|
||||
(
|
||||
$strOperation,
|
||||
{name => 'strySource', value => \@strySource}
|
||||
);
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# sourceGet
|
||||
####################################################################################################################################
|
||||
sub sourceGet
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
# Assign function parameters, defaults, and log debug info
|
||||
my
|
||||
(
|
||||
$strOperation,
|
||||
$strSource
|
||||
) =
|
||||
logDebugParam
|
||||
(
|
||||
__PACKAGE__ . '->sourceGet', \@_,
|
||||
{name => 'strSource', trace => true}
|
||||
);
|
||||
|
||||
if (!defined(${$self->{oManifest}}{source}{$strSource}))
|
||||
{
|
||||
confess &log(ERROR, "source ${strSource} does not exist");
|
||||
}
|
||||
|
||||
# Return from function and log return values if any
|
||||
return logDebugReturn
|
||||
(
|
||||
$strOperation,
|
||||
{name => 'oSource', value => ${$self->{oManifest}}{source}{$strSource}}
|
||||
);
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# renderList
|
||||
####################################################################################################################################
|
||||
sub renderList
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
# Assign function parameters, defaults, and log debug info
|
||||
my ($strOperation) = logDebugParam(__PACKAGE__ . '->renderList');
|
||||
|
||||
# Check that the render output exists
|
||||
my @stryRender;
|
||||
|
||||
if (defined(${$self->{oManifest}}{render}))
|
||||
{
|
||||
@stryRender = sort(keys(%{${$self->{oManifest}}{render}}));
|
||||
}
|
||||
|
||||
# Return from function and log return values if any
|
||||
return logDebugReturn
|
||||
(
|
||||
$strOperation,
|
||||
{name => 'stryRender', value => \@stryRender}
|
||||
);
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# renderGet
|
||||
####################################################################################################################################
|
||||
sub renderGet
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
# Assign function parameters, defaults, and log debug info
|
||||
my
|
||||
(
|
||||
$strOperation,
|
||||
$strType
|
||||
) =
|
||||
logDebugParam
|
||||
(
|
||||
__PACKAGE__ . '->renderGet', \@_,
|
||||
{name => 'strType', trace => true}
|
||||
);
|
||||
|
||||
# Check that the render exists
|
||||
if (!defined(${$self->{oManifest}}{render}{$strType}))
|
||||
{
|
||||
confess &log(ERROR, "render type ${strType} does not exist");
|
||||
}
|
||||
|
||||
# Return from function and log return values if any
|
||||
return logDebugReturn
|
||||
(
|
||||
$strOperation,
|
||||
{name => 'oRenderOut', value => ${$self->{oManifest}}{render}{$strType}}
|
||||
);
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# renderOutList
|
||||
####################################################################################################################################
|
||||
sub renderOutList
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
# Assign function parameters, defaults, and log debug info
|
||||
my
|
||||
(
|
||||
$strOperation,
|
||||
$strType
|
||||
) =
|
||||
logDebugParam
|
||||
(
|
||||
__PACKAGE__ . '->renderOutList', \@_,
|
||||
{name => 'strType'}
|
||||
);
|
||||
|
||||
# Check that the render output exists
|
||||
my @stryRenderOut;
|
||||
|
||||
if (defined(${$self->{oManifest}}{render}{$strType}))
|
||||
{
|
||||
@stryRenderOut = sort(keys(%{${$self->{oManifest}}{render}{$strType}{out}}));
|
||||
}
|
||||
|
||||
# Return from function and log return values if any
|
||||
return logDebugReturn
|
||||
(
|
||||
$strOperation,
|
||||
{name => 'stryRenderOut', value => \@stryRenderOut}
|
||||
);
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# renderOutGet
|
||||
####################################################################################################################################
|
||||
sub renderOutGet
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
# Assign function parameters, defaults, and log debug info
|
||||
my
|
||||
(
|
||||
$strOperation,
|
||||
$strType,
|
||||
$strKey,
|
||||
$bIgnoreMissing,
|
||||
) =
|
||||
logDebugParam
|
||||
(
|
||||
__PACKAGE__ . '->renderOutGet', \@_,
|
||||
{name => 'strType', trace => true},
|
||||
{name => 'strKey', trace => true},
|
||||
{name => 'bIgnoreMissing', default => false, trace => true},
|
||||
);
|
||||
|
||||
if (!defined(${$self->{oManifest}}{render}{$strType}{out}{$strKey}) && !$bIgnoreMissing)
|
||||
{
|
||||
confess &log(ERROR, "render out ${strKey} does not exist");
|
||||
}
|
||||
|
||||
# Return from function and log return values if any
|
||||
return logDebugReturn
|
||||
(
|
||||
$strOperation,
|
||||
{name => 'oRenderOut', value => ${$self->{oManifest}}{render}{$strType}{out}{$strKey}}
|
||||
);
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# cacheKey
|
||||
####################################################################################################################################
|
||||
sub cacheKey
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
# Assign function parameters, defaults, and log debug info
|
||||
my ($strOperation) = logDebugParam(__PACKAGE__ . '->cacheKey');
|
||||
|
||||
# Generate a cache key from the variable override
|
||||
my $strVariableKey = JSON::PP->new()->canonical()->allow_nonref()->encode($self->{rhKeyVariableOverride});
|
||||
|
||||
if ($strVariableKey eq '{}')
|
||||
{
|
||||
$strVariableKey = 'default';
|
||||
}
|
||||
|
||||
my $strRequire = defined($self->{stryRequire}) && @{$self->{stryRequire}} > 0 ?
|
||||
join("\n", @{$self->{stryRequire}}) : 'all';
|
||||
|
||||
# Return from function and log return values if any
|
||||
return logDebugReturn
|
||||
(
|
||||
$strOperation,
|
||||
{name => 'strVariableKey', value => $strVariableKey},
|
||||
{name => 'strRequire', value => $strRequire},
|
||||
);
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# cacheRead
|
||||
####################################################################################################################################
|
||||
sub cacheRead
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
# Assign function parameters, defaults, and log debug info
|
||||
my ($strOperation) = logDebugParam(__PACKAGE__ . '->cacheRead');
|
||||
|
||||
$self->{hCache} = undef;
|
||||
|
||||
my $strCacheFile = $self->{bDeploy} ? $self->{strExeCacheDeploy} : $self->{strExeCacheLocal};
|
||||
|
||||
if (!$self->storage()->exists($strCacheFile) && !$self->{bDeploy})
|
||||
{
|
||||
$strCacheFile = $self->{strExeCacheDeploy};
|
||||
}
|
||||
|
||||
if ($self->storage()->exists($strCacheFile))
|
||||
{
|
||||
my ($strCacheKey, $strRequire) = $self->cacheKey();
|
||||
my $oJSON = JSON::PP->new()->allow_nonref();
|
||||
$self->{hCache} = $oJSON->decode(${$self->storage()->get($strCacheFile)});
|
||||
|
||||
foreach my $strSource (sort(keys(%{${$self->{oManifest}}{source}})))
|
||||
{
|
||||
my $hSource = ${$self->{oManifest}}{source}{$strSource};
|
||||
|
||||
if (defined(${$self->{hCache}}{$strCacheKey}{$strRequire}{$strSource}))
|
||||
{
|
||||
$$hSource{hyCache} = ${$self->{hCache}}{$strCacheKey}{$strRequire}{$strSource};
|
||||
&log(DETAIL, "cache load $strSource (key = ${strCacheKey}, require = ${strRequire})");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Return from function and log return values if any
|
||||
return logDebugReturn($strOperation);
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# cacheWrite
|
||||
####################################################################################################################################
|
||||
sub cacheWrite
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
# Assign function parameters, defaults, and log debug info
|
||||
my ($strOperation) = logDebugParam(__PACKAGE__ . '->cacheWrite');
|
||||
|
||||
my $strCacheFile = $self->{bDeploy} ? $self->{strExeCacheDeploy} : $self->{strExeCacheLocal};
|
||||
my ($strCacheKey, $strRequire) = $self->cacheKey();
|
||||
|
||||
foreach my $strSource (sort(keys(%{${$self->{oManifest}}{source}})))
|
||||
{
|
||||
my $hSource = ${$self->{oManifest}}{source}{$strSource};
|
||||
|
||||
if (defined($$hSource{hyCache}))
|
||||
{
|
||||
${$self->{hCache}}{$strCacheKey}{$strRequire}{$strSource} = $$hSource{hyCache};
|
||||
&log(DETAIL, "cache load $strSource (key = ${strCacheKey}, require = ${strRequire})");
|
||||
}
|
||||
}
|
||||
|
||||
if (defined($self->{hCache}))
|
||||
{
|
||||
my $oJSON = JSON::PP->new()->canonical()->allow_nonref()->pretty();
|
||||
$self->storage()->put($strCacheFile, $oJSON->encode($self->{hCache}));
|
||||
}
|
||||
|
||||
# Return from function and log return values if any
|
||||
return logDebugReturn($strOperation);
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# cacheReset
|
||||
####################################################################################################################################
|
||||
sub cacheReset
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
# Assign function parameters, defaults, and log debug info
|
||||
my
|
||||
(
|
||||
$strOperation,
|
||||
$strSource
|
||||
) =
|
||||
logDebugParam
|
||||
(
|
||||
__PACKAGE__ . '->cacheReset', \@_,
|
||||
{name => 'strSource', trace => true}
|
||||
);
|
||||
|
||||
if ($self->{bCacheOnly})
|
||||
{
|
||||
confess &log(ERROR, 'Cache reset disabled by --cache-only option');
|
||||
}
|
||||
|
||||
&log(WARN, "Cache will be reset for source ${strSource} and rendering retried automatically");
|
||||
delete(${$self->{oManifest}}{source}{$strSource}{hyCache});
|
||||
|
||||
# Return from function and log return values if any
|
||||
return logDebugReturn($strOperation);
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# Getters
|
||||
####################################################################################################################################
|
||||
sub storage {shift->{oStorage}};
|
||||
|
||||
1;
|
1054
doc/lib/pgBackRestDoc/Common/DocRender.pm
Normal file
1054
doc/lib/pgBackRestDoc/Common/DocRender.pm
Normal file
File diff suppressed because it is too large
Load Diff
258
doc/lib/pgBackRestDoc/Common/Exception.pm
Normal file
258
doc/lib/pgBackRestDoc/Common/Exception.pm
Normal file
@ -0,0 +1,258 @@
|
||||
####################################################################################################################################
|
||||
# COMMON EXCEPTION MODULE
|
||||
####################################################################################################################################
|
||||
package pgBackRestDoc::Common::Exception;
|
||||
|
||||
use strict;
|
||||
use warnings FATAL => qw(all);
|
||||
use Carp qw(confess longmess);
|
||||
|
||||
use Scalar::Util qw(blessed);
|
||||
|
||||
use Exporter qw(import);
|
||||
our @EXPORT = qw();
|
||||
|
||||
####################################################################################################################################
|
||||
# Error Definitions
|
||||
####################################################################################################################################
|
||||
use constant ERROR_MINIMUM => 25;
|
||||
push @EXPORT, qw(ERROR_MINIMUM);
|
||||
use constant ERROR_MAXIMUM => 125;
|
||||
push @EXPORT, qw(ERROR_MAXIMUM);
|
||||
|
||||
use constant ERROR_ASSERT => 25;
|
||||
push @EXPORT, qw(ERROR_ASSERT);
|
||||
use constant ERROR_CHECKSUM => 26;
|
||||
push @EXPORT, qw(ERROR_CHECKSUM);
|
||||
use constant ERROR_CONFIG => 27;
|
||||
push @EXPORT, qw(ERROR_CONFIG);
|
||||
use constant ERROR_FILE_INVALID => 28;
|
||||
push @EXPORT, qw(ERROR_FILE_INVALID);
|
||||
use constant ERROR_FORMAT => 29;
|
||||
push @EXPORT, qw(ERROR_FORMAT);
|
||||
use constant ERROR_OPTION_INVALID_VALUE => 32;
|
||||
push @EXPORT, qw(ERROR_OPTION_INVALID_VALUE);
|
||||
use constant ERROR_POSTMASTER_RUNNING => 38;
|
||||
push @EXPORT, qw(ERROR_POSTMASTER_RUNNING);
|
||||
use constant ERROR_PATH_NOT_EMPTY => 40;
|
||||
push @EXPORT, qw(ERROR_PATH_NOT_EMPTY);
|
||||
use constant ERROR_FILE_OPEN => 41;
|
||||
push @EXPORT, qw(ERROR_FILE_OPEN);
|
||||
use constant ERROR_FILE_READ => 42;
|
||||
push @EXPORT, qw(ERROR_FILE_READ);
|
||||
use constant ERROR_ARCHIVE_MISMATCH => 44;
|
||||
push @EXPORT, qw(ERROR_ARCHIVE_MISMATCH);
|
||||
use constant ERROR_ARCHIVE_DUPLICATE => 45;
|
||||
push @EXPORT, qw(ERROR_ARCHIVE_DUPLICATE);
|
||||
use constant ERROR_PATH_CREATE => 47;
|
||||
push @EXPORT, qw(ERROR_PATH_CREATE);
|
||||
use constant ERROR_LOCK_ACQUIRE => 50;
|
||||
push @EXPORT, qw(ERROR_LOCK_ACQUIRE);
|
||||
use constant ERROR_BACKUP_MISMATCH => 51;
|
||||
push @EXPORT, qw(ERROR_BACKUP_MISMATCH);
|
||||
use constant ERROR_PATH_OPEN => 53;
|
||||
push @EXPORT, qw(ERROR_PATH_OPEN);
|
||||
use constant ERROR_PATH_SYNC => 54;
|
||||
push @EXPORT, qw(ERROR_PATH_SYNC);
|
||||
use constant ERROR_FILE_MISSING => 55;
|
||||
push @EXPORT, qw(ERROR_FILE_MISSING);
|
||||
use constant ERROR_DB_CONNECT => 56;
|
||||
push @EXPORT, qw(ERROR_DB_CONNECT);
|
||||
use constant ERROR_DB_QUERY => 57;
|
||||
push @EXPORT, qw(ERROR_DB_QUERY);
|
||||
use constant ERROR_DB_MISMATCH => 58;
|
||||
push @EXPORT, qw(ERROR_DB_MISMATCH);
|
||||
use constant ERROR_PATH_REMOVE => 61;
|
||||
push @EXPORT, qw(ERROR_PATH_REMOVE);
|
||||
use constant ERROR_STOP => 62;
|
||||
push @EXPORT, qw(ERROR_STOP);
|
||||
use constant ERROR_FILE_WRITE => 64;
|
||||
push @EXPORT, qw(ERROR_FILE_WRITE);
|
||||
use constant ERROR_FEATURE_NOT_SUPPORTED => 67;
|
||||
push @EXPORT, qw(ERROR_FEATURE_NOT_SUPPORTED);
|
||||
use constant ERROR_ARCHIVE_COMMAND_INVALID => 68;
|
||||
push @EXPORT, qw(ERROR_ARCHIVE_COMMAND_INVALID);
|
||||
use constant ERROR_LINK_EXPECTED => 69;
|
||||
push @EXPORT, qw(ERROR_LINK_EXPECTED);
|
||||
use constant ERROR_LINK_DESTINATION => 70;
|
||||
push @EXPORT, qw(ERROR_LINK_DESTINATION);
|
||||
use constant ERROR_PATH_MISSING => 73;
|
||||
push @EXPORT, qw(ERROR_PATH_MISSING);
|
||||
use constant ERROR_FILE_MOVE => 74;
|
||||
push @EXPORT, qw(ERROR_FILE_MOVE);
|
||||
use constant ERROR_PATH_TYPE => 77;
|
||||
push @EXPORT, qw(ERROR_PATH_TYPE);
|
||||
use constant ERROR_DB_MISSING => 80;
|
||||
push @EXPORT, qw(ERROR_DB_MISSING);
|
||||
use constant ERROR_DB_INVALID => 81;
|
||||
push @EXPORT, qw(ERROR_DB_INVALID);
|
||||
use constant ERROR_ARCHIVE_TIMEOUT => 82;
|
||||
push @EXPORT, qw(ERROR_ARCHIVE_TIMEOUT);
|
||||
use constant ERROR_ARCHIVE_DISABLED => 87;
|
||||
push @EXPORT, qw(ERROR_ARCHIVE_DISABLED);
|
||||
use constant ERROR_FILE_OWNER => 88;
|
||||
push @EXPORT, qw(ERROR_FILE_OWNER);
|
||||
use constant ERROR_PATH_EXISTS => 92;
|
||||
push @EXPORT, qw(ERROR_PATH_EXISTS);
|
||||
use constant ERROR_FILE_EXISTS => 93;
|
||||
push @EXPORT, qw(ERROR_FILE_EXISTS);
|
||||
use constant ERROR_CRYPTO => 95;
|
||||
push @EXPORT, qw(ERROR_CRYPTO);
|
||||
use constant ERROR_INVALID => 123;
|
||||
push @EXPORT, qw(ERROR_INVALID);
|
||||
use constant ERROR_UNHANDLED => 124;
|
||||
push @EXPORT, qw(ERROR_UNHANDLED);
|
||||
use constant ERROR_UNKNOWN => 125;
|
||||
push @EXPORT, qw(ERROR_UNKNOWN);
|
||||
|
||||
####################################################################################################################################
|
||||
# CONSTRUCTOR
|
||||
####################################################################################################################################
|
||||
sub new
|
||||
{
|
||||
my $class = shift; # Class name
|
||||
my $strLevel = shift; # Log level
|
||||
my $iCode = shift; # Error code
|
||||
my $strMessage = shift; # ErrorMessage
|
||||
my $strTrace = shift; # Stack trace
|
||||
my $rExtra = shift; # Extra info used exclusively by the logging system
|
||||
my $bErrorC = shift; # Is this a C error?
|
||||
|
||||
if ($iCode < ERROR_MINIMUM || $iCode > ERROR_MAXIMUM)
|
||||
{
|
||||
$iCode = ERROR_INVALID;
|
||||
}
|
||||
|
||||
# Create the class hash
|
||||
my $self = {};
|
||||
bless $self, $class;
|
||||
|
||||
# Initialize exception
|
||||
$self->{strLevel} = $strLevel;
|
||||
$self->{iCode} = $iCode;
|
||||
$self->{strMessage} = $strMessage;
|
||||
$self->{strTrace} = $strTrace;
|
||||
$self->{rExtra} = $rExtra;
|
||||
$self->{bErrorC} = $bErrorC ? 1 : 0;
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# level
|
||||
####################################################################################################################################
|
||||
sub level
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
return $self->{strLevel};
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# CODE
|
||||
####################################################################################################################################
|
||||
sub code
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
return $self->{iCode};
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# extra
|
||||
####################################################################################################################################
|
||||
sub extra
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
return $self->{rExtra};
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# MESSAGE
|
||||
####################################################################################################################################
|
||||
sub message
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
return $self->{strMessage};
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# TRACE
|
||||
####################################################################################################################################
|
||||
sub trace
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
return $self->{strTrace};
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# isException - is this a structured exception or a default Perl exception?
|
||||
####################################################################################################################################
|
||||
sub isException
|
||||
{
|
||||
my $roException = shift;
|
||||
|
||||
# Only check if defined
|
||||
if (defined($roException) && defined($$roException))
|
||||
{
|
||||
# If a standard Exception
|
||||
if (blessed($$roException))
|
||||
{
|
||||
return $$roException->isa('pgBackRestDoc::Common::Exception') ? 1 : 0;
|
||||
}
|
||||
# Else if a specially formatted string from the C library
|
||||
elsif ($$roException =~ /^PGBRCLIB\:[0-9]+\:/)
|
||||
{
|
||||
# Split message and discard the first part used for identification
|
||||
my @stryException = split(/\:/, $$roException);
|
||||
shift(@stryException);
|
||||
|
||||
# Construct exception fields
|
||||
my $iCode = shift(@stryException) + 0;
|
||||
my $strTrace = shift(@stryException) . qw{:} . shift(@stryException);
|
||||
my $strMessage = join(':', @stryException);
|
||||
|
||||
# Create exception
|
||||
$$roException = new pgBackRestDoc::Common::Exception("ERROR", $iCode, $strMessage, $strTrace, undef, 1);
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
push @EXPORT, qw(isException);
|
||||
|
||||
####################################################################################################################################
|
||||
# exceptionCode
|
||||
#
|
||||
# Extract the error code from an exception - if a Perl exception return ERROR_UNKNOWN.
|
||||
####################################################################################################################################
|
||||
sub exceptionCode
|
||||
{
|
||||
my $oException = shift;
|
||||
|
||||
return isException(\$oException) ? $oException->code() : ERROR_UNKNOWN;
|
||||
}
|
||||
|
||||
push @EXPORT, qw(exceptionCode);
|
||||
|
||||
####################################################################################################################################
|
||||
# exceptionMessage
|
||||
#
|
||||
# Extract the error message from an exception - if a Perl exception return bare exception.
|
||||
####################################################################################################################################
|
||||
sub exceptionMessage
|
||||
{
|
||||
my $oException = shift;
|
||||
|
||||
return isException(\$oException) ? $oException->message() : $oException;
|
||||
}
|
||||
|
||||
push @EXPORT, qw(exceptionMessage);
|
||||
|
||||
1;
|
868
doc/lib/pgBackRestDoc/Common/Ini.pm
Normal file
868
doc/lib/pgBackRestDoc/Common/Ini.pm
Normal file
@ -0,0 +1,868 @@
|
||||
####################################################################################################################################
|
||||
# COMMON INI MODULE
|
||||
####################################################################################################################################
|
||||
package pgBackRestDoc::Common::Ini;
|
||||
|
||||
use strict;
|
||||
use warnings FATAL => qw(all);
|
||||
use Carp qw(confess);
|
||||
use English '-no_match_vars';
|
||||
|
||||
use Digest::SHA qw(sha1_hex);
|
||||
use Exporter qw(import);
|
||||
our @EXPORT = qw();
|
||||
use File::Basename qw(dirname);
|
||||
use JSON::PP;
|
||||
use Storable qw(dclone);
|
||||
|
||||
use pgBackRest::Version;
|
||||
|
||||
use pgBackRestDoc::Common::Exception;
|
||||
use pgBackRestDoc::Common::Log;
|
||||
use pgBackRestDoc::Common::String;
|
||||
|
||||
####################################################################################################################################
|
||||
# Boolean constants
|
||||
####################################################################################################################################
|
||||
use constant INI_TRUE => JSON::PP::true;
|
||||
push @EXPORT, qw(INI_TRUE);
|
||||
use constant INI_FALSE => JSON::PP::false;
|
||||
push @EXPORT, qw(INI_FALSE);
|
||||
|
||||
####################################################################################################################################
|
||||
# Ini control constants
|
||||
####################################################################################################################################
|
||||
use constant INI_SECTION_BACKREST => 'backrest';
|
||||
push @EXPORT, qw(INI_SECTION_BACKREST);
|
||||
|
||||
use constant INI_KEY_CHECKSUM => 'backrest-checksum';
|
||||
push @EXPORT, qw(INI_KEY_CHECKSUM);
|
||||
use constant INI_KEY_FORMAT => 'backrest-format';
|
||||
push @EXPORT, qw(INI_KEY_FORMAT);
|
||||
use constant INI_KEY_VERSION => 'backrest-version';
|
||||
push @EXPORT, qw(INI_KEY_VERSION);
|
||||
|
||||
use constant INI_SECTION_CIPHER => 'cipher';
|
||||
push @EXPORT, qw(INI_SECTION_CIPHER);
|
||||
|
||||
use constant INI_KEY_CIPHER_PASS => 'cipher-pass';
|
||||
push @EXPORT, qw(INI_KEY_CIPHER_PASS);
|
||||
|
||||
####################################################################################################################################
|
||||
# Ini file copy extension
|
||||
####################################################################################################################################
|
||||
use constant INI_COPY_EXT => '.copy';
|
||||
push @EXPORT, qw(INI_COPY_EXT);
|
||||
|
||||
####################################################################################################################################
|
||||
# Ini sort orders
|
||||
####################################################################################################################################
|
||||
use constant INI_SORT_FORWARD => 'forward';
|
||||
push @EXPORT, qw(INI_SORT_FORWARD);
|
||||
use constant INI_SORT_REVERSE => 'reverse';
|
||||
push @EXPORT, qw(INI_SORT_REVERSE);
|
||||
use constant INI_SORT_NONE => 'none';
|
||||
push @EXPORT, qw(INI_SORT_NONE);
|
||||
|
||||
####################################################################################################################################
|
||||
# new()
|
||||
####################################################################################################################################
|
||||
sub new
|
||||
{
|
||||
my $class = shift; # Class name
|
||||
|
||||
# Create the class hash
|
||||
my $self = {};
|
||||
bless $self, $class;
|
||||
|
||||
# Assign function parameters, defaults, and log debug info
|
||||
(
|
||||
my $strOperation,
|
||||
$self->{oStorage},
|
||||
$self->{strFileName},
|
||||
my $bLoad,
|
||||
my $strContent,
|
||||
$self->{iInitFormat},
|
||||
$self->{strInitVersion},
|
||||
my $bIgnoreMissing,
|
||||
$self->{strCipherPass}, # Passphrase to read/write the file
|
||||
my $strCipherPassSub, # Passphrase to read/write subsequent files
|
||||
) =
|
||||
logDebugParam
|
||||
(
|
||||
__PACKAGE__ . '->new', \@_,
|
||||
{name => 'oStorage', trace => true},
|
||||
{name => 'strFileName', trace => true},
|
||||
{name => 'bLoad', optional => true, default => true, trace => true},
|
||||
{name => 'strContent', optional => true, trace => true},
|
||||
{name => 'iInitFormat', optional => true, default => REPOSITORY_FORMAT, trace => true},
|
||||
{name => 'strInitVersion', optional => true, default => PROJECT_VERSION, trace => true},
|
||||
{name => 'bIgnoreMissing', optional => true, default => false, trace => true},
|
||||
{name => 'strCipherPass', optional => true, trace => true},
|
||||
{name => 'strCipherPassSub', optional => true, trace => true},
|
||||
);
|
||||
|
||||
# Set changed to false
|
||||
$self->{bModified} = false;
|
||||
|
||||
# Set exists to false
|
||||
$self->{bExists} = false;
|
||||
|
||||
# Load the file if requested
|
||||
if ($bLoad)
|
||||
{
|
||||
$self->load($bIgnoreMissing);
|
||||
}
|
||||
# Load from a string if provided
|
||||
elsif (defined($strContent))
|
||||
{
|
||||
$self->{oContent} = iniParse($strContent);
|
||||
$self->headerCheck();
|
||||
}
|
||||
|
||||
# Initialize if not loading the file and not loading from string or if a load was attempted and the file does not exist
|
||||
if (!$self->{bExists} && !defined($strContent))
|
||||
{
|
||||
$self->numericSet(INI_SECTION_BACKREST, INI_KEY_FORMAT, undef, $self->{iInitFormat});
|
||||
$self->set(INI_SECTION_BACKREST, INI_KEY_VERSION, undef, $self->{strInitVersion});
|
||||
|
||||
# Determine if the passphrase section should be set
|
||||
if (defined($self->{strCipherPass}) && defined($strCipherPassSub))
|
||||
{
|
||||
$self->set(INI_SECTION_CIPHER, INI_KEY_CIPHER_PASS, undef, $strCipherPassSub);
|
||||
}
|
||||
}
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# loadVersion() - load a version (main or copy) of the ini file
|
||||
####################################################################################################################################
|
||||
sub loadVersion
|
||||
{
|
||||
my $self = shift;
|
||||
my $bCopy = shift;
|
||||
my $bIgnoreError = shift;
|
||||
|
||||
# Load main
|
||||
my $rstrContent = $self->{oStorage}->get(
|
||||
$self->{oStorage}->openRead($self->{strFileName} . ($bCopy ? INI_COPY_EXT : ''),
|
||||
{bIgnoreMissing => $bIgnoreError, strCipherPass => $self->{strCipherPass}}));
|
||||
|
||||
# If the file exists then attempt to parse it
|
||||
if (defined($rstrContent))
|
||||
{
|
||||
my $rhContent = iniParse($$rstrContent, {bIgnoreInvalid => $bIgnoreError});
|
||||
|
||||
# If the content is valid then check the header
|
||||
if (defined($rhContent))
|
||||
{
|
||||
$self->{oContent} = $rhContent;
|
||||
|
||||
# If the header is invalid then undef content
|
||||
if (!$self->headerCheck({bIgnoreInvalid => $bIgnoreError}))
|
||||
{
|
||||
delete($self->{oContent});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return defined($self->{oContent});
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# load() - load the ini
|
||||
####################################################################################################################################
|
||||
sub load
|
||||
{
|
||||
my $self = shift;
|
||||
my $bIgnoreMissing = shift;
|
||||
|
||||
# If main was not loaded then try the copy
|
||||
if (!$self->loadVersion(false, true))
|
||||
{
|
||||
if (!$self->loadVersion(true, true))
|
||||
{
|
||||
return if $bIgnoreMissing;
|
||||
|
||||
confess &log(ERROR, "unable to open $self->{strFileName} or $self->{strFileName}" . INI_COPY_EXT, ERROR_FILE_MISSING);
|
||||
}
|
||||
}
|
||||
|
||||
$self->{bExists} = true;
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# headerCheck() - check that version and checksum in header are as expected
|
||||
####################################################################################################################################
|
||||
sub headerCheck
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
# Assign function parameters, defaults, and log debug info
|
||||
my
|
||||
(
|
||||
$strOperation,
|
||||
$bIgnoreInvalid,
|
||||
) =
|
||||
logDebugParam
|
||||
(
|
||||
__PACKAGE__ . '->headerCheck', \@_,
|
||||
{name => 'bIgnoreInvalid', optional => true, default => false, trace => true},
|
||||
);
|
||||
|
||||
# Eval so exceptions can be ignored on bIgnoreInvalid
|
||||
my $bValid = true;
|
||||
|
||||
eval
|
||||
{
|
||||
|
||||
# Make sure the ini is valid by testing checksum
|
||||
my $strChecksum = $self->get(INI_SECTION_BACKREST, INI_KEY_CHECKSUM, undef, false);
|
||||
my $strTestChecksum = $self->hash();
|
||||
|
||||
if (!defined($strChecksum) || $strChecksum ne $strTestChecksum)
|
||||
{
|
||||
confess &log(ERROR,
|
||||
"invalid checksum in '$self->{strFileName}', expected '${strTestChecksum}' but found " .
|
||||
(defined($strChecksum) ? "'${strChecksum}'" : '[undef]'),
|
||||
ERROR_CHECKSUM);
|
||||
}
|
||||
|
||||
# Make sure that the format is current, otherwise error
|
||||
my $iFormat = $self->get(INI_SECTION_BACKREST, INI_KEY_FORMAT, undef, false, 0);
|
||||
|
||||
if ($iFormat != $self->{iInitFormat})
|
||||
{
|
||||
confess &log(ERROR,
|
||||
"invalid format in '$self->{strFileName}', expected $self->{iInitFormat} but found ${iFormat}", ERROR_FORMAT);
|
||||
}
|
||||
|
||||
# Check if the version has changed
|
||||
if (!$self->test(INI_SECTION_BACKREST, INI_KEY_VERSION, undef, $self->{strInitVersion}))
|
||||
{
|
||||
$self->set(INI_SECTION_BACKREST, INI_KEY_VERSION, undef, $self->{strInitVersion});
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
or do
|
||||
{
|
||||
# Confess the error if it should not be ignored
|
||||
if (!$bIgnoreInvalid)
|
||||
{
|
||||
confess $EVAL_ERROR;
|
||||
}
|
||||
|
||||
# Return false when errors are ignored
|
||||
$bValid = false;
|
||||
};
|
||||
|
||||
# Return from function and log return values if any
|
||||
return logDebugReturn
|
||||
(
|
||||
$strOperation,
|
||||
{name => 'bValid', value => $bValid, trace => true}
|
||||
);
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# iniParse() - parse from standard INI format to a hash.
|
||||
####################################################################################################################################
|
||||
push @EXPORT, qw(iniParse);
|
||||
|
||||
sub iniParse
|
||||
{
|
||||
# Assign function parameters, defaults, and log debug info
|
||||
my
|
||||
(
|
||||
$strOperation,
|
||||
$strContent,
|
||||
$bRelaxed,
|
||||
$bIgnoreInvalid,
|
||||
) =
|
||||
logDebugParam
|
||||
(
|
||||
__PACKAGE__ . '::iniParse', \@_,
|
||||
{name => 'strContent', required => false, trace => true},
|
||||
{name => 'bRelaxed', optional => true, default => false, trace => true},
|
||||
{name => 'bIgnoreInvalid', optional => true, default => false, trace => true},
|
||||
);
|
||||
|
||||
# Ini content
|
||||
my $oContent = undef;
|
||||
my $strSection;
|
||||
|
||||
# Create the JSON object
|
||||
my $oJSON = JSON::PP->new()->allow_nonref();
|
||||
|
||||
# Eval so exceptions can be ignored on bIgnoreInvalid
|
||||
eval
|
||||
{
|
||||
# Read the INI file
|
||||
foreach my $strLine (split("\n", defined($strContent) ? $strContent : ''))
|
||||
{
|
||||
$strLine = trim($strLine);
|
||||
|
||||
# Skip lines that are blank or comments
|
||||
if ($strLine ne '' && $strLine !~ '^[ ]*#.*')
|
||||
{
|
||||
# Get the section
|
||||
if (index($strLine, '[') == 0)
|
||||
{
|
||||
$strSection = substr($strLine, 1, length($strLine) - 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!defined($strSection))
|
||||
{
|
||||
confess &log(ERROR, "key/value pair '${strLine}' found outside of a section", ERROR_CONFIG);
|
||||
}
|
||||
|
||||
# Get key and value
|
||||
my $iIndex = index($strLine, '=');
|
||||
|
||||
if ($iIndex == -1)
|
||||
{
|
||||
confess &log(ERROR, "unable to find '=' in '${strLine}'", ERROR_CONFIG);
|
||||
}
|
||||
|
||||
my $strKey = substr($strLine, 0, $iIndex);
|
||||
my $strValue = substr($strLine, $iIndex + 1);
|
||||
|
||||
# If relaxed then read the value directly
|
||||
if ($bRelaxed)
|
||||
{
|
||||
if (defined($oContent->{$strSection}{$strKey}))
|
||||
{
|
||||
if (ref($oContent->{$strSection}{$strKey}) ne 'ARRAY')
|
||||
{
|
||||
$oContent->{$strSection}{$strKey} = [$oContent->{$strSection}{$strKey}];
|
||||
}
|
||||
|
||||
push(@{$oContent->{$strSection}{$strKey}}, $strValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
$oContent->{$strSection}{$strKey} = $strValue;
|
||||
}
|
||||
}
|
||||
# Else read the value as stricter JSON
|
||||
else
|
||||
{
|
||||
${$oContent}{$strSection}{$strKey} = $oJSON->decode($strValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Error if the file is empty
|
||||
if (!($bRelaxed || defined($oContent)))
|
||||
{
|
||||
confess &log(ERROR, 'no key/value pairs found', ERROR_CONFIG);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
or do
|
||||
{
|
||||
# Confess the error if it should not be ignored
|
||||
if (!$bIgnoreInvalid)
|
||||
{
|
||||
confess $EVAL_ERROR;
|
||||
}
|
||||
|
||||
# Undef content when errors are ignored
|
||||
undef($oContent);
|
||||
};
|
||||
|
||||
# Return from function and log return values if any
|
||||
return logDebugReturn
|
||||
(
|
||||
$strOperation,
|
||||
{name => 'oContent', value => $oContent, trace => true}
|
||||
);
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# save() - save the file.
|
||||
####################################################################################################################################
|
||||
sub save
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
# Save only if modified
|
||||
if ($self->{bModified})
|
||||
{
|
||||
# Calculate the hash
|
||||
$self->hash();
|
||||
|
||||
# Save the file
|
||||
$self->{oStorage}->put($self->{strFileName}, iniRender($self->{oContent}), {strCipherPass => $self->{strCipherPass}});
|
||||
|
||||
if ($self->{oStorage}->can('pathSync'))
|
||||
{
|
||||
$self->{oStorage}->pathSync(dirname($self->{strFileName}));
|
||||
}
|
||||
|
||||
$self->{oStorage}->put($self->{strFileName} . INI_COPY_EXT, iniRender($self->{oContent}),
|
||||
{strCipherPass => $self->{strCipherPass}});
|
||||
|
||||
if ($self->{oStorage}->can('pathSync'))
|
||||
{
|
||||
$self->{oStorage}->pathSync(dirname($self->{strFileName}));
|
||||
}
|
||||
|
||||
$self->{bModified} = false;
|
||||
|
||||
# Indicate the file now exists
|
||||
$self->{bExists} = true;
|
||||
|
||||
# File was saved
|
||||
return true;
|
||||
}
|
||||
|
||||
# File was not saved
|
||||
return false;
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# saveCopy - save only a copy of the file.
|
||||
####################################################################################################################################
|
||||
sub saveCopy
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
if ($self->{oStorage}->exists($self->{strFileName}))
|
||||
{
|
||||
confess &log(ASSERT, "cannot save copy only when '$self->{strFileName}' exists");
|
||||
}
|
||||
|
||||
$self->hash();
|
||||
$self->{oStorage}->put($self->{strFileName} . INI_COPY_EXT, iniRender($self->{oContent}),
|
||||
{strCipherPass => $self->{strCipherPass}});
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# iniRender() - render hash to standard INI format.
|
||||
####################################################################################################################################
|
||||
push @EXPORT, qw(iniRender);
|
||||
|
||||
sub iniRender
|
||||
{
|
||||
# Assign function parameters, defaults, and log debug info
|
||||
my
|
||||
(
|
||||
$strOperation,
|
||||
$oContent,
|
||||
$bRelaxed,
|
||||
) =
|
||||
logDebugParam
|
||||
(
|
||||
__PACKAGE__ . '::iniRender', \@_,
|
||||
{name => 'oContent', trace => true},
|
||||
{name => 'bRelaxed', default => false, trace => true},
|
||||
);
|
||||
|
||||
# Open the ini file for writing
|
||||
my $strContent = '';
|
||||
my $bFirst = true;
|
||||
|
||||
# Create the JSON object canonical so that fields are alpha ordered to pass unit tests
|
||||
my $oJSON = JSON::PP->new()->canonical()->allow_nonref();
|
||||
|
||||
# Write the INI file
|
||||
foreach my $strSection (sort(keys(%$oContent)))
|
||||
{
|
||||
# Add a linefeed between sections
|
||||
if (!$bFirst)
|
||||
{
|
||||
$strContent .= "\n";
|
||||
}
|
||||
|
||||
# Write the section
|
||||
$strContent .= "[${strSection}]\n";
|
||||
|
||||
# Iterate through all keys in the section
|
||||
foreach my $strKey (sort(keys(%{$oContent->{$strSection}})))
|
||||
{
|
||||
# If the value is a hash then convert it to JSON, otherwise store as is
|
||||
my $strValue = ${$oContent}{$strSection}{$strKey};
|
||||
|
||||
# If relaxed then store as old-style config
|
||||
if ($bRelaxed)
|
||||
{
|
||||
# If the value is an array then save each element to a separate key/value pair
|
||||
if (ref($strValue) eq 'ARRAY')
|
||||
{
|
||||
foreach my $strArrayValue (@{$strValue})
|
||||
{
|
||||
$strContent .= "${strKey}=${strArrayValue}\n";
|
||||
}
|
||||
}
|
||||
# Else write a standard key/value pair
|
||||
else
|
||||
{
|
||||
$strContent .= "${strKey}=${strValue}\n";
|
||||
}
|
||||
}
|
||||
# Else write as stricter JSON
|
||||
else
|
||||
{
|
||||
# Skip the checksum for now but write all other key/value pairs
|
||||
if (!($strSection eq INI_SECTION_BACKREST && $strKey eq INI_KEY_CHECKSUM))
|
||||
{
|
||||
$strContent .= "${strKey}=" . $oJSON->encode($strValue) . "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$bFirst = false;
|
||||
}
|
||||
|
||||
# If there is a checksum write it at the end of the file. Having the checksum at the end of the file allows some major
|
||||
# performance optimizations which we won't implement in Perl, but will make the C code much more efficient.
|
||||
if (!$bRelaxed && defined($oContent->{&INI_SECTION_BACKREST}) && defined($oContent->{&INI_SECTION_BACKREST}{&INI_KEY_CHECKSUM}))
|
||||
{
|
||||
$strContent .=
|
||||
"\n[" . INI_SECTION_BACKREST . "]\n" .
|
||||
INI_KEY_CHECKSUM . '=' . $oJSON->encode($oContent->{&INI_SECTION_BACKREST}{&INI_KEY_CHECKSUM}) . "\n";
|
||||
}
|
||||
|
||||
# Return from function and log return values if any
|
||||
return logDebugReturn
|
||||
(
|
||||
$strOperation,
|
||||
{name => 'strContent', value => $strContent, trace => true}
|
||||
);
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# hash() - generate hash for the manifest.
|
||||
####################################################################################################################################
|
||||
sub hash
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
# Remove the old checksum
|
||||
delete($self->{oContent}{&INI_SECTION_BACKREST}{&INI_KEY_CHECKSUM});
|
||||
|
||||
# Set the new checksum
|
||||
$self->{oContent}{&INI_SECTION_BACKREST}{&INI_KEY_CHECKSUM} =
|
||||
sha1_hex(JSON::PP->new()->canonical()->allow_nonref()->encode($self->{oContent}));
|
||||
|
||||
return $self->{oContent}{&INI_SECTION_BACKREST}{&INI_KEY_CHECKSUM};
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# get() - get a value.
|
||||
####################################################################################################################################
|
||||
sub get
|
||||
{
|
||||
my $self = shift;
|
||||
my $strSection = shift;
|
||||
my $strKey = shift;
|
||||
my $strSubKey = shift;
|
||||
my $bRequired = shift;
|
||||
my $oDefault = shift;
|
||||
|
||||
# Parameter constraints
|
||||
if (!defined($strSection))
|
||||
{
|
||||
confess &log(ASSERT, 'strSection is required');
|
||||
}
|
||||
|
||||
if (defined($strSubKey) && !defined($strKey))
|
||||
{
|
||||
confess &log(ASSERT, "strKey is required when strSubKey '${strSubKey}' is requested");
|
||||
}
|
||||
|
||||
# Get the result
|
||||
my $oResult = $self->{oContent}->{$strSection};
|
||||
|
||||
if (defined($strKey) && defined($oResult))
|
||||
{
|
||||
$oResult = $oResult->{$strKey};
|
||||
|
||||
if (defined($strSubKey) && defined($oResult))
|
||||
{
|
||||
$oResult = $oResult->{$strSubKey};
|
||||
}
|
||||
}
|
||||
|
||||
# When result is not defined
|
||||
if (!defined($oResult))
|
||||
{
|
||||
# Error if a result is required
|
||||
if (!defined($bRequired) || $bRequired)
|
||||
{
|
||||
confess &log(ASSERT, "strSection '$strSection'" . (defined($strKey) ? ", strKey '$strKey'" : '') .
|
||||
(defined($strSubKey) ? ", strSubKey '$strSubKey'" : '') . ' is required but not defined');
|
||||
}
|
||||
|
||||
# Return default if specified
|
||||
if (defined($oDefault))
|
||||
{
|
||||
return $oDefault;
|
||||
}
|
||||
}
|
||||
|
||||
return $oResult
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# boolGet() - get a boolean value.
|
||||
####################################################################################################################################
|
||||
sub boolGet
|
||||
{
|
||||
my $self = shift;
|
||||
my $strSection = shift;
|
||||
my $strKey = shift;
|
||||
my $strSubKey = shift;
|
||||
my $bRequired = shift;
|
||||
my $bDefault = shift;
|
||||
|
||||
return $self->get(
|
||||
$strSection, $strKey, $strSubKey, $bRequired,
|
||||
defined($bDefault) ? ($bDefault ? INI_TRUE : INI_FALSE) : undef) ? true : false;
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# numericGet() - get a numeric value.
|
||||
####################################################################################################################################
|
||||
sub numericGet
|
||||
{
|
||||
my $self = shift;
|
||||
my $strSection = shift;
|
||||
my $strKey = shift;
|
||||
my $strSubKey = shift;
|
||||
my $bRequired = shift;
|
||||
my $nDefault = shift;
|
||||
|
||||
return $self->get($strSection, $strKey, $strSubKey, $bRequired, defined($nDefault) ? $nDefault + 0 : undef) + 0;
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# set - set a value.
|
||||
####################################################################################################################################
|
||||
sub set
|
||||
{
|
||||
my $self = shift;
|
||||
my $strSection = shift;
|
||||
my $strKey = shift;
|
||||
my $strSubKey = shift;
|
||||
my $oValue = shift;
|
||||
|
||||
# Parameter constraints
|
||||
if (!(defined($strSection) && defined($strKey)))
|
||||
{
|
||||
confess &log(ASSERT, 'strSection and strKey are required');
|
||||
}
|
||||
|
||||
my $oCurrentValue;
|
||||
|
||||
if (defined($strSubKey))
|
||||
{
|
||||
$oCurrentValue = \$self->{oContent}{$strSection}{$strKey}{$strSubKey};
|
||||
}
|
||||
else
|
||||
{
|
||||
$oCurrentValue = \$self->{oContent}{$strSection}{$strKey};
|
||||
}
|
||||
|
||||
if (!defined($$oCurrentValue) ||
|
||||
defined($oCurrentValue) != defined($oValue) ||
|
||||
${dclone($oCurrentValue)} ne ${dclone(\$oValue)})
|
||||
{
|
||||
$$oCurrentValue = $oValue;
|
||||
|
||||
if (!$self->{bModified})
|
||||
{
|
||||
$self->{bModified} = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# boolSet - set a boolean value.
|
||||
####################################################################################################################################
|
||||
sub boolSet
|
||||
{
|
||||
my $self = shift;
|
||||
my $strSection = shift;
|
||||
my $strKey = shift;
|
||||
my $strSubKey = shift;
|
||||
my $bValue = shift;
|
||||
|
||||
$self->set($strSection, $strKey, $strSubKey, $bValue ? INI_TRUE : INI_FALSE);
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# numericSet - set a numeric value.
|
||||
####################################################################################################################################
|
||||
sub numericSet
|
||||
{
|
||||
my $self = shift;
|
||||
my $strSection = shift;
|
||||
my $strKey = shift;
|
||||
my $strSubKey = shift;
|
||||
my $nValue = shift;
|
||||
|
||||
$self->set($strSection, $strKey, $strSubKey, defined($nValue) ? $nValue + 0 : undef);
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# remove - remove a value.
|
||||
####################################################################################################################################
|
||||
sub remove
|
||||
{
|
||||
my $self = shift;
|
||||
my $strSection = shift;
|
||||
my $strKey = shift;
|
||||
my $strSubKey = shift;
|
||||
|
||||
# Test if the value exists
|
||||
if ($self->test($strSection, $strKey, $strSubKey))
|
||||
{
|
||||
# Remove a subkey
|
||||
if (defined($strSubKey))
|
||||
{
|
||||
delete($self->{oContent}{$strSection}{$strKey}{$strSubKey});
|
||||
}
|
||||
|
||||
# Remove a key
|
||||
if (defined($strKey))
|
||||
{
|
||||
if (!defined($strSubKey))
|
||||
{
|
||||
delete($self->{oContent}{$strSection}{$strKey});
|
||||
}
|
||||
|
||||
# Remove the section if it is now empty
|
||||
if (keys(%{$self->{oContent}{$strSection}}) == 0)
|
||||
{
|
||||
delete($self->{oContent}{$strSection});
|
||||
}
|
||||
}
|
||||
|
||||
# Remove a section
|
||||
if (!defined($strKey))
|
||||
{
|
||||
delete($self->{oContent}{$strSection});
|
||||
}
|
||||
|
||||
# Record changes
|
||||
if (!$self->{bModified})
|
||||
{
|
||||
$self->{bModified} = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# keys - get the list of keys in a section.
|
||||
####################################################################################################################################
|
||||
sub keys
|
||||
{
|
||||
my $self = shift;
|
||||
my $strSection = shift;
|
||||
my $strSortOrder = shift;
|
||||
|
||||
if ($self->test($strSection))
|
||||
{
|
||||
if (!defined($strSortOrder) || $strSortOrder eq INI_SORT_FORWARD)
|
||||
{
|
||||
return (sort(keys(%{$self->get($strSection)})));
|
||||
}
|
||||
elsif ($strSortOrder eq INI_SORT_REVERSE)
|
||||
{
|
||||
return (sort {$b cmp $a} (keys(%{$self->get($strSection)})));
|
||||
}
|
||||
elsif ($strSortOrder eq INI_SORT_NONE)
|
||||
{
|
||||
return (keys(%{$self->get($strSection)}));
|
||||
}
|
||||
else
|
||||
{
|
||||
confess &log(ASSERT, "invalid strSortOrder '${strSortOrder}'");
|
||||
}
|
||||
}
|
||||
|
||||
my @stryEmptyArray;
|
||||
return @stryEmptyArray;
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# test - test a value.
|
||||
#
|
||||
# Test a value to see if it equals the supplied test value. If no test value is given, tests that the section, key, or subkey
|
||||
# is defined.
|
||||
####################################################################################################################################
|
||||
sub test
|
||||
{
|
||||
my $self = shift;
|
||||
my $strSection = shift;
|
||||
my $strValue = shift;
|
||||
my $strSubValue = shift;
|
||||
my $strTest = shift;
|
||||
|
||||
# Get the value
|
||||
my $strResult = $self->get($strSection, $strValue, $strSubValue, false);
|
||||
|
||||
# Is there a result
|
||||
if (defined($strResult))
|
||||
{
|
||||
# Is there a value to test against?
|
||||
if (defined($strTest))
|
||||
{
|
||||
# Make sure these are explicit strings or Devel::Cover thinks they are equal if one side is a boolean
|
||||
return ($strResult . '') eq ($strTest . '') ? true : false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# boolTest - test a boolean value, see test().
|
||||
####################################################################################################################################
|
||||
sub boolTest
|
||||
{
|
||||
my $self = shift;
|
||||
my $strSection = shift;
|
||||
my $strValue = shift;
|
||||
my $strSubValue = shift;
|
||||
my $bTest = shift;
|
||||
|
||||
return $self->test($strSection, $strValue, $strSubValue, defined($bTest) ? ($bTest ? INI_TRUE : INI_FALSE) : undef);
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# cipherPassSub - gets the passphrase (if it exists) used to read/write subsequent files
|
||||
####################################################################################################################################
|
||||
sub cipherPassSub
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
return $self->get(INI_SECTION_CIPHER, INI_KEY_CIPHER_PASS, undef, false);
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# Properties.
|
||||
####################################################################################################################################
|
||||
sub modified {shift->{bModified}} # Has the data been modified since last load/save?
|
||||
sub exists {shift->{bExists}} # Is the data persisted to file?
|
||||
sub cipherPass {shift->{strCipherPass}} # Return passphrase (will be undef if repo not encrypted)
|
||||
|
||||
1;
|
804
doc/lib/pgBackRestDoc/Common/Log.pm
Normal file
804
doc/lib/pgBackRestDoc/Common/Log.pm
Normal file
@ -0,0 +1,804 @@
|
||||
####################################################################################################################################
|
||||
# COMMON LOG MODULE
|
||||
####################################################################################################################################
|
||||
package pgBackRestDoc::Common::Log;
|
||||
|
||||
use strict;
|
||||
use warnings FATAL => qw(all);
|
||||
use Carp qw(confess longmess);
|
||||
use English '-no_match_vars';
|
||||
|
||||
use Exporter qw(import);
|
||||
our @EXPORT = qw();
|
||||
use Fcntl qw(:DEFAULT :flock);
|
||||
use File::Basename qw(dirname);
|
||||
use Scalar::Util qw(blessed reftype);
|
||||
use Time::HiRes qw(gettimeofday usleep);
|
||||
|
||||
use pgBackRestDoc::Common::Exception;
|
||||
use pgBackRestDoc::Common::String;
|
||||
|
||||
####################################################################################################################################
|
||||
# Boolean constants
|
||||
####################################################################################################################################
|
||||
use constant true => 1;
|
||||
push @EXPORT, qw(true);
|
||||
use constant false => 0;
|
||||
push @EXPORT, qw(false);
|
||||
|
||||
####################################################################################################################################
|
||||
# Log level constants
|
||||
####################################################################################################################################
|
||||
use constant TRACE => 'TRACE';
|
||||
push @EXPORT, qw(TRACE);
|
||||
use constant DEBUG => 'DEBUG';
|
||||
push @EXPORT, qw(DEBUG);
|
||||
use constant DETAIL => 'DETAIL';
|
||||
push @EXPORT, qw(DETAIL);
|
||||
use constant INFO => 'INFO';
|
||||
push @EXPORT, qw(INFO);
|
||||
use constant WARN => 'WARN';
|
||||
push @EXPORT, qw(WARN);
|
||||
use constant PROTOCOL => 'PROTOCOL';
|
||||
push @EXPORT, qw(PROTOCOL);
|
||||
use constant ERROR => 'ERROR';
|
||||
push @EXPORT, qw(ERROR);
|
||||
use constant ASSERT => 'ASSERT';
|
||||
push @EXPORT, qw(ASSERT);
|
||||
use constant OFF => 'OFF';
|
||||
push @EXPORT, qw(OFF);
|
||||
|
||||
####################################################################################################################################
|
||||
# Log levels ranked by severity
|
||||
####################################################################################################################################
|
||||
my %oLogLevelRank;
|
||||
|
||||
$oLogLevelRank{TRACE}{rank} = 8;
|
||||
$oLogLevelRank{DEBUG}{rank} = 7;
|
||||
$oLogLevelRank{DETAIL}{rank} = 6;
|
||||
$oLogLevelRank{INFO}{rank} = 5;
|
||||
$oLogLevelRank{WARN}{rank} = 4;
|
||||
$oLogLevelRank{PROTOCOL}{rank} = 3;
|
||||
$oLogLevelRank{ERROR}{rank} = 2;
|
||||
$oLogLevelRank{ASSERT}{rank} = 1;
|
||||
$oLogLevelRank{OFF}{rank} = 0;
|
||||
|
||||
####################################################################################################################################
|
||||
# Module globals
|
||||
####################################################################################################################################
|
||||
my $hLogFile = undef;
|
||||
my $strLogFileCache = undef;
|
||||
|
||||
my $strLogLevelFile = OFF;
|
||||
my $strLogLevelConsole = OFF;
|
||||
my $strLogLevelStdErr = WARN;
|
||||
my $bLogTimestamp = true;
|
||||
|
||||
# Size of the process id log field
|
||||
my $iLogProcessSize = 2;
|
||||
|
||||
# Flags to limit banner printing until there is actual output
|
||||
my $bLogFileExists;
|
||||
my $bLogFileFirst;
|
||||
|
||||
# Allow log to be globally enabled or disabled with logEnable() and logDisable()
|
||||
my $bLogDisable = 0;
|
||||
|
||||
# Allow errors to be logged as warnings
|
||||
my $bLogWarnOnError = 0;
|
||||
|
||||
# Store the last logged error
|
||||
my $oErrorLast;
|
||||
|
||||
####################################################################################################################################
|
||||
# logFileSet - set the file messages will be logged to
|
||||
####################################################################################################################################
|
||||
sub logFileSet
|
||||
{
|
||||
my $oStorage = shift;
|
||||
my $strFile = shift;
|
||||
my $bLogFileFirstParam = shift;
|
||||
|
||||
# Only open the log file if file logging is enabled
|
||||
if ($strLogLevelFile ne OFF)
|
||||
{
|
||||
$oStorage->pathCreate(dirname($strFile), {strMode => '0750', bIgnoreExists => true, bCreateParent => true});
|
||||
|
||||
$strFile .= '.log';
|
||||
$bLogFileExists = -e $strFile ? true : false;
|
||||
$bLogFileFirst = defined($bLogFileFirstParam) ? $bLogFileFirstParam : false;
|
||||
|
||||
if (!sysopen($hLogFile, $strFile, O_WRONLY | O_CREAT | O_APPEND, oct('0640')))
|
||||
{
|
||||
logErrorResult(ERROR_FILE_OPEN, "unable to open log file '${strFile}'", $OS_ERROR);
|
||||
}
|
||||
|
||||
# Write out anything that was cached before the file was opened
|
||||
if (defined($strLogFileCache))
|
||||
{
|
||||
logBanner();
|
||||
syswrite($hLogFile, $strLogFileCache);
|
||||
undef($strLogFileCache);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
push @EXPORT, qw(logFileSet);
|
||||
|
||||
####################################################################################################################################
|
||||
# logBanner
|
||||
#
|
||||
# Output a banner on the first log entry written to a file
|
||||
####################################################################################################################################
|
||||
sub logBanner
|
||||
{
|
||||
if ($bLogFileFirst)
|
||||
{
|
||||
if ($bLogFileExists)
|
||||
{
|
||||
syswrite($hLogFile, "\n");
|
||||
}
|
||||
|
||||
syswrite($hLogFile, "-------------------PROCESS START-------------------\n");
|
||||
}
|
||||
|
||||
$bLogFileFirst = false;
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# logLevelSet - set the log level for file and console
|
||||
####################################################################################################################################
|
||||
sub logLevelSet
|
||||
{
|
||||
my $strLevelFileParam = shift;
|
||||
my $strLevelConsoleParam = shift;
|
||||
my $strLevelStdErrParam = shift;
|
||||
my $bLogTimestampParam = shift;
|
||||
my $iLogProcessMax = shift;
|
||||
|
||||
if (defined($strLevelFileParam))
|
||||
{
|
||||
if (!defined($oLogLevelRank{uc($strLevelFileParam)}{rank}))
|
||||
{
|
||||
confess &log(ERROR, "file log level ${strLevelFileParam} does not exist");
|
||||
}
|
||||
|
||||
$strLogLevelFile = uc($strLevelFileParam);
|
||||
}
|
||||
|
||||
if (defined($strLevelConsoleParam))
|
||||
{
|
||||
if (!defined($oLogLevelRank{uc($strLevelConsoleParam)}{rank}))
|
||||
{
|
||||
confess &log(ERROR, "console log level ${strLevelConsoleParam} does not exist");
|
||||
}
|
||||
|
||||
$strLogLevelConsole = uc($strLevelConsoleParam);
|
||||
}
|
||||
|
||||
if (defined($strLevelStdErrParam))
|
||||
{
|
||||
if (!defined($oLogLevelRank{uc($strLevelStdErrParam)}{rank}))
|
||||
{
|
||||
confess &log(ERROR, "stdout log level ${strLevelStdErrParam} does not exist");
|
||||
}
|
||||
|
||||
$strLogLevelStdErr = uc($strLevelStdErrParam);
|
||||
}
|
||||
|
||||
if (defined($bLogTimestampParam))
|
||||
{
|
||||
$bLogTimestamp = $bLogTimestampParam;
|
||||
}
|
||||
|
||||
if (defined($iLogProcessMax))
|
||||
{
|
||||
$iLogProcessSize = $iLogProcessMax > 99 ? 3 : 2;
|
||||
}
|
||||
}
|
||||
|
||||
push @EXPORT, qw(logLevelSet);
|
||||
|
||||
####################################################################################################################################
|
||||
# logDisable
|
||||
####################################################################################################################################
|
||||
sub logDisable
|
||||
{
|
||||
$bLogDisable++;
|
||||
}
|
||||
|
||||
push @EXPORT, qw(logDisable);
|
||||
|
||||
####################################################################################################################################
|
||||
# logEnable
|
||||
####################################################################################################################################
|
||||
sub logEnable
|
||||
{
|
||||
$bLogDisable--;
|
||||
}
|
||||
|
||||
push @EXPORT, qw(logEnable);
|
||||
|
||||
####################################################################################################################################
|
||||
# logWarnOnErrorDisable
|
||||
####################################################################################################################################
|
||||
sub logWarnOnErrorDisable
|
||||
{
|
||||
$bLogWarnOnError--;
|
||||
}
|
||||
|
||||
push @EXPORT, qw(logWarnOnErrorDisable);
|
||||
|
||||
####################################################################################################################################
|
||||
# logWarnOnErrorEnable - when an error is thrown, log it as a warning instead
|
||||
####################################################################################################################################
|
||||
sub logWarnOnErrorEnable
|
||||
{
|
||||
$bLogWarnOnError++;
|
||||
}
|
||||
|
||||
push @EXPORT, qw(logWarnOnErrorEnable);
|
||||
|
||||
####################################################################################################################################
|
||||
# logDebugParam
|
||||
#
|
||||
# Log parameters passed to functions.
|
||||
####################################################################################################################################
|
||||
use constant DEBUG_PARAM => '()';
|
||||
|
||||
sub logDebugParam
|
||||
{
|
||||
my $strFunction = shift;
|
||||
my $oyParamRef = shift;
|
||||
|
||||
return logDebugProcess($strFunction, DEBUG_PARAM, undef, $oyParamRef, @_);
|
||||
}
|
||||
|
||||
push @EXPORT, qw(logDebugParam);
|
||||
|
||||
####################################################################################################################################
|
||||
# logDebugReturn
|
||||
#
|
||||
# Log values returned from functions.
|
||||
####################################################################################################################################
|
||||
use constant DEBUG_RETURN => '=>';
|
||||
|
||||
sub logDebugReturn
|
||||
{
|
||||
my $strFunction = shift;
|
||||
|
||||
return logDebugProcess($strFunction, DEBUG_RETURN, undef, undef, @_);
|
||||
}
|
||||
|
||||
push @EXPORT, qw(logDebugReturn);
|
||||
|
||||
####################################################################################################################################
|
||||
# logDebugMisc
|
||||
#
|
||||
# Log misc values and details during execution.
|
||||
####################################################################################################################################
|
||||
use constant DEBUG_MISC => '';
|
||||
|
||||
sub logDebugMisc
|
||||
{
|
||||
my $strFunction = shift;
|
||||
my $strDetail = shift;
|
||||
|
||||
return logDebugProcess($strFunction, DEBUG_MISC, $strDetail, undef, @_);
|
||||
}
|
||||
|
||||
push @EXPORT, qw(logDebugMisc);
|
||||
|
||||
####################################################################################################################################
|
||||
# logDebugProcess
|
||||
####################################################################################################################################
|
||||
sub logDebugProcess
|
||||
{
|
||||
my $strFunction = shift;
|
||||
my $strType = shift;
|
||||
my $strDetail = shift;
|
||||
my $oyParamRef = shift;
|
||||
|
||||
my $iIndex = 0;
|
||||
my $oParamHash = {};
|
||||
my @oyResult;
|
||||
my $bLogTrace = true;
|
||||
|
||||
if ($strType eq DEBUG_PARAM)
|
||||
{
|
||||
push @oyResult, $strFunction;
|
||||
}
|
||||
|
||||
# Process each parameter hash
|
||||
my $oParam = shift;
|
||||
my $bOptionalBlock = false;
|
||||
|
||||
# Strip the package name off strFunction if it's pgBackRest
|
||||
$strFunction =~ s/^pgBackRest[^\:]*\:\://;
|
||||
|
||||
while (defined($oParam))
|
||||
{
|
||||
my $strParamName = $$oParam{name};
|
||||
my $bParamOptional = defined($oParam->{optional}) && $oParam->{optional};
|
||||
my $bParamRequired = !defined($oParam->{required}) || $oParam->{required};
|
||||
my $oValue;
|
||||
|
||||
# Should the param be redacted?
|
||||
$oParamHash->{$strParamName}{redact} = $oParam->{redact} ? true : false;
|
||||
|
||||
# If param is optional then the optional block has been entered
|
||||
if ($bParamOptional)
|
||||
{
|
||||
if (defined($oParam->{required}))
|
||||
{
|
||||
confess &log(ASSERT, "cannot define 'required' for optional parameter '${strParamName}'");
|
||||
}
|
||||
|
||||
$bParamRequired = false;
|
||||
$bOptionalBlock = true;
|
||||
}
|
||||
|
||||
# Don't allow non-optional parameters once optional block has started
|
||||
if ($bParamOptional != $bOptionalBlock)
|
||||
{
|
||||
confess &log(ASSERT, "non-optional parameter '${strParamName}' invalid after optional parameters");
|
||||
}
|
||||
|
||||
# Push the return value into the return value array
|
||||
if ($strType eq DEBUG_PARAM)
|
||||
{
|
||||
if ($bParamOptional)
|
||||
{
|
||||
$oValue = $$oyParamRef[$iIndex]->{$strParamName};
|
||||
}
|
||||
else
|
||||
{
|
||||
$oValue = $$oyParamRef[$iIndex];
|
||||
}
|
||||
|
||||
if (defined($oValue))
|
||||
{
|
||||
push(@oyResult, $oValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
push(@oyResult, $${oParam}{default});
|
||||
$$oParamHash{$strParamName}{default} = true;
|
||||
}
|
||||
|
||||
$oValue = $oyResult[-1];
|
||||
|
||||
if (!defined($oValue) && $bParamRequired)
|
||||
{
|
||||
confess &log(ASSERT, "${strParamName} is required in ${strFunction}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ref($$oParam{value}) eq 'ARRAY')
|
||||
{
|
||||
if (defined($$oParam{ref}) && $$oParam{ref})
|
||||
{
|
||||
push(@oyResult, $$oParam{value});
|
||||
}
|
||||
else
|
||||
{
|
||||
push(@oyResult, @{$$oParam{value}});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
push(@oyResult, $$oParam{value});
|
||||
}
|
||||
|
||||
$oValue = $$oParam{value};
|
||||
}
|
||||
|
||||
if (!defined($$oParam{log}) || $$oParam{log})
|
||||
{
|
||||
# If the parameter is a hash but not blessed then represent it as a string
|
||||
# ??? This should go away once the inputs to logDebug can be changed
|
||||
if (ref($oValue) eq 'HASH' && !blessed($oValue))
|
||||
{
|
||||
$$oParamHash{$strParamName}{value} = '[hash]';
|
||||
}
|
||||
# Else log the parameter value exactly
|
||||
else
|
||||
{
|
||||
$$oParamHash{$strParamName}{value} = $oValue;
|
||||
}
|
||||
|
||||
# There are certain return values that it's wasteful to generate debug logging for
|
||||
if (!($strParamName eq 'self') &&
|
||||
(!defined($$oParam{trace}) || !$$oParam{trace}))
|
||||
{
|
||||
$bLogTrace = false;
|
||||
}
|
||||
}
|
||||
|
||||
# Get the next parameter hash
|
||||
$oParam = shift;
|
||||
|
||||
if (!$bParamOptional)
|
||||
{
|
||||
$iIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
if (defined($strDetail) && $iIndex == 0)
|
||||
{
|
||||
$bLogTrace = false;
|
||||
}
|
||||
|
||||
logDebugOut($strFunction, $strType, $strDetail, $oParamHash, $bLogTrace ? TRACE : DEBUG);
|
||||
|
||||
# If there are one or zero return values then just return a scalar (this will be undef if there are no return values)
|
||||
if (@oyResult == 1)
|
||||
{
|
||||
return $oyResult[0];
|
||||
}
|
||||
|
||||
# Else return an array containing return values
|
||||
return @oyResult;
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# logDebugBuild
|
||||
####################################################################################################################################
|
||||
sub logDebugBuild
|
||||
{
|
||||
my $strValue = shift;
|
||||
|
||||
my $rResult;
|
||||
|
||||
# Value is undefined
|
||||
if (!defined($strValue))
|
||||
{
|
||||
$rResult = \'[undef]';
|
||||
}
|
||||
# Value is not a ref, but return it as a ref for efficiency
|
||||
elsif (!ref($strValue))
|
||||
{
|
||||
$rResult = \$strValue;
|
||||
}
|
||||
# Value is a hash
|
||||
elsif (ref($strValue) eq 'HASH')
|
||||
{
|
||||
my $strValueHash;
|
||||
|
||||
for my $strSubValue (sort(keys(%{$strValue})))
|
||||
{
|
||||
$strValueHash .=
|
||||
(defined($strValueHash) ? ', ' : '{') . "${strSubValue} => " . ${logDebugBuild($strValue->{$strSubValue})};
|
||||
}
|
||||
|
||||
$rResult = \(defined($strValueHash) ? $strValueHash . '}' : '{}');
|
||||
}
|
||||
# Value is an array
|
||||
elsif (ref($strValue) eq 'ARRAY')
|
||||
{
|
||||
my $strValueArray;
|
||||
|
||||
for my $strSubValue (@{$strValue})
|
||||
{
|
||||
$strValueArray .= (defined($strValueArray) ? ', ' : '(') . ${logDebugBuild($strSubValue)};
|
||||
}
|
||||
|
||||
$rResult = \(defined($strValueArray) ? $strValueArray . ')' : '()');
|
||||
}
|
||||
# Else some other type ??? For the moment this is forced to object to not make big log changes
|
||||
else
|
||||
{
|
||||
$rResult = \('[object]');
|
||||
}
|
||||
|
||||
return $rResult;
|
||||
}
|
||||
|
||||
push @EXPORT, qw(logDebugBuild);
|
||||
|
||||
####################################################################################################################################
|
||||
# logDebugOut
|
||||
####################################################################################################################################
|
||||
use constant DEBUG_STRING_MAX_LEN => 1024;
|
||||
|
||||
sub logDebugOut
|
||||
{
|
||||
my $strFunction = shift;
|
||||
my $strType = shift;
|
||||
my $strMessage = shift;
|
||||
my $oParamHash = shift;
|
||||
my $strLevel = shift;
|
||||
|
||||
$strLevel = defined($strLevel) ? $strLevel : DEBUG;
|
||||
|
||||
if ($oLogLevelRank{$strLevel}{rank} <= $oLogLevelRank{$strLogLevelConsole}{rank} ||
|
||||
$oLogLevelRank{$strLevel}{rank} <= $oLogLevelRank{$strLogLevelFile}{rank} ||
|
||||
$oLogLevelRank{$strLevel}{rank} <= $oLogLevelRank{$strLogLevelStdErr}{rank})
|
||||
{
|
||||
if (defined($oParamHash))
|
||||
{
|
||||
my $strParamSet;
|
||||
|
||||
foreach my $strParam (sort(keys(%$oParamHash)))
|
||||
{
|
||||
if (defined($strParamSet))
|
||||
{
|
||||
$strParamSet .= ', ';
|
||||
}
|
||||
|
||||
my $strValueRef = defined($oParamHash->{$strParam}{value}) ? logDebugBuild($oParamHash->{$strParam}{value}) : undef;
|
||||
my $bDefault =
|
||||
defined($$strValueRef) && defined($$oParamHash{$strParam}{default}) ? $$oParamHash{$strParam}{default} : false;
|
||||
|
||||
$strParamSet .=
|
||||
"${strParam} = " .
|
||||
($oParamHash->{$strParam}{redact} && defined($$strValueRef) ? '<redacted>' :
|
||||
($bDefault ? '<' : '') .
|
||||
(defined($$strValueRef) ?
|
||||
($strParam =~ /^(b|is)/ ? ($$strValueRef ? 'true' : 'false'):
|
||||
(length($$strValueRef) > DEBUG_STRING_MAX_LEN ?
|
||||
substr($$strValueRef, 0, DEBUG_STRING_MAX_LEN) . ' ... <truncated>':
|
||||
$$strValueRef)) : '[undef]') .
|
||||
($bDefault ? '>' : ''));
|
||||
}
|
||||
|
||||
if (defined($strMessage))
|
||||
{
|
||||
$strMessage = $strMessage . (defined($strParamSet) ? ": ${strParamSet}" : '');
|
||||
}
|
||||
else
|
||||
{
|
||||
$strMessage = $strParamSet;
|
||||
}
|
||||
}
|
||||
|
||||
&log($strLevel, "${strFunction}${strType}" . (defined($strMessage) ? ": $strMessage" : ''));
|
||||
}
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# logException
|
||||
####################################################################################################################################
|
||||
sub logException
|
||||
{
|
||||
my $oException = shift;
|
||||
|
||||
return &log($oException->level(), $oException->message(), $oException->code(), undef, undef, undef, $oException->extra());
|
||||
}
|
||||
|
||||
push @EXPORT, qw(logException);
|
||||
|
||||
####################################################################################################################################
|
||||
# logErrorResult
|
||||
####################################################################################################################################
|
||||
sub logErrorResult
|
||||
{
|
||||
my $iCode = shift;
|
||||
my $strMessage = shift;
|
||||
my $strResult = shift;
|
||||
|
||||
confess &log(ERROR, $strMessage . (defined($strResult) ? ': ' . trim($strResult) : ''), $iCode);
|
||||
}
|
||||
|
||||
push @EXPORT, qw(logErrorResult);
|
||||
|
||||
####################################################################################################################################
|
||||
# LOG - log messages
|
||||
####################################################################################################################################
|
||||
sub log
|
||||
{
|
||||
my $strLevel = shift;
|
||||
my $strMessage = shift;
|
||||
my $iCode = shift;
|
||||
my $bSuppressLog = shift;
|
||||
my $iIndent = shift;
|
||||
my $iProcessId = shift;
|
||||
my $rExtra = shift;
|
||||
|
||||
# Set defaults
|
||||
$bSuppressLog = defined($bSuppressLog) ? $bSuppressLog : false;
|
||||
|
||||
# Initialize rExtra
|
||||
if (!defined($rExtra))
|
||||
{
|
||||
$rExtra =
|
||||
{
|
||||
bLogFile => false,
|
||||
bLogConsole => false,
|
||||
};
|
||||
}
|
||||
|
||||
# Set operational variables
|
||||
my $strMessageFormat = $strMessage;
|
||||
my $iLogLevelRank = $oLogLevelRank{$strLevel}{rank};
|
||||
|
||||
# Level rank must be valid
|
||||
if (!defined($iLogLevelRank))
|
||||
{
|
||||
confess &log(ASSERT, "log level ${strLevel} does not exist");
|
||||
}
|
||||
|
||||
# If message was undefined then set default message
|
||||
if (!defined($strMessageFormat))
|
||||
{
|
||||
$strMessageFormat = '(undefined)';
|
||||
}
|
||||
|
||||
# Set the error code
|
||||
if ($strLevel eq ASSERT)
|
||||
{
|
||||
$iCode = ERROR_ASSERT;
|
||||
}
|
||||
elsif ($strLevel eq ERROR && !defined($iCode))
|
||||
{
|
||||
$iCode = ERROR_UNKNOWN;
|
||||
}
|
||||
|
||||
$strMessageFormat = (defined($iCode) ? sprintf('[%03d]: ', $iCode) : '') . $strMessageFormat;
|
||||
|
||||
# Indent subsequent lines of the message if it has more than one line - makes the log more readable
|
||||
if (defined($iIndent))
|
||||
{
|
||||
my $strIndent = ' ' x $iIndent;
|
||||
$strMessageFormat =~ s/\n/\n${strIndent}/g;
|
||||
}
|
||||
else
|
||||
{
|
||||
# Indent subsequent message lines so they align
|
||||
$bLogTimestamp ?
|
||||
$strMessageFormat =~ s/\n/\n /g :
|
||||
$strMessageFormat =~ s/\n/\n /g
|
||||
}
|
||||
|
||||
# Indent TRACE and debug levels so they are distinct from normal messages
|
||||
if ($strLevel eq TRACE)
|
||||
{
|
||||
$strMessageFormat =~ s/\n/\n /g;
|
||||
$strMessageFormat = ' ' . $strMessageFormat;
|
||||
}
|
||||
elsif ($strLevel eq DEBUG)
|
||||
{
|
||||
$strMessageFormat =~ s/\n/\n /g;
|
||||
$strMessageFormat = ' ' . $strMessageFormat;
|
||||
}
|
||||
|
||||
# Format the message text
|
||||
my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime(time);
|
||||
|
||||
# If logging warnings as errors then change the display level and rank. These will be used to determine if the message will be
|
||||
# displayed or not.
|
||||
my $strDisplayLevel = ($bLogWarnOnError && $strLevel eq ERROR ? WARN : $strLevel);
|
||||
my $iLogDisplayLevelRank = ($bLogWarnOnError && $strLevel eq ERROR ? $oLogLevelRank{$strDisplayLevel}{rank} : $iLogLevelRank);
|
||||
|
||||
$strMessageFormat =
|
||||
($bLogTimestamp ? timestampFormat() . sprintf('.%03d ', (gettimeofday() - int(gettimeofday())) * 1000) : '') .
|
||||
sprintf('P%0*d', $iLogProcessSize, defined($iProcessId) ? $iProcessId : 0) .
|
||||
(' ' x (7 - length($strDisplayLevel))) . "${strDisplayLevel}: ${strMessageFormat}\n";
|
||||
|
||||
# Skip output if disabled
|
||||
if (!$bLogDisable)
|
||||
{
|
||||
# Output to stderr if configured log level setting rank is greater than the display level rank.
|
||||
if (!$rExtra->{bLogConsole} && $iLogDisplayLevelRank <= $oLogLevelRank{$strLogLevelStdErr}{rank})
|
||||
{
|
||||
if ($strLogLevelStdErr ne PROTOCOL)
|
||||
{
|
||||
syswrite(*STDERR, $strDisplayLevel . (defined($iCode) ? sprintf(' [%03d]: ', $iCode) : '') . ': ');
|
||||
}
|
||||
|
||||
syswrite(*STDERR, "${strMessage}\n");
|
||||
$rExtra->{bLogConsole} = true;
|
||||
}
|
||||
# Else output to stdout if configured log level setting rank is greater than the display level rank
|
||||
elsif (!$rExtra->{bLogConsole} && $iLogDisplayLevelRank <= $oLogLevelRank{$strLogLevelConsole}{rank})
|
||||
{
|
||||
if (!$bSuppressLog)
|
||||
{
|
||||
syswrite(*STDOUT, $strMessageFormat);
|
||||
|
||||
# This is here for debugging purposes - it's not clear how best to make it into a switch
|
||||
# if ($strLevel eq ASSERT || $strLevel eq ERROR)
|
||||
# {
|
||||
# my $strStackTrace = longmess() . "\n";
|
||||
# $strStackTrace =~ s/\n/\n /g;
|
||||
# syswrite(*STDOUT, $strStackTrace);
|
||||
# }
|
||||
}
|
||||
|
||||
$rExtra->{bLogConsole} = true;
|
||||
}
|
||||
|
||||
# Output to file if configured log level setting rank is greater than the display level rank or test flag is set.
|
||||
if (!$rExtra->{bLogLogFile} && $iLogDisplayLevelRank <= $oLogLevelRank{$strLogLevelFile}{rank})
|
||||
{
|
||||
if (defined($hLogFile) || (defined($strLogLevelFile) && $strLogLevelFile ne OFF))
|
||||
{
|
||||
if (!$bSuppressLog)
|
||||
{
|
||||
if (defined($hLogFile))
|
||||
{
|
||||
logBanner();
|
||||
syswrite($hLogFile, $strMessageFormat);
|
||||
}
|
||||
else
|
||||
{
|
||||
$strLogFileCache .= $strMessageFormat;
|
||||
}
|
||||
|
||||
if ($strDisplayLevel eq ASSERT ||
|
||||
($strDisplayLevel eq ERROR && ($strLogLevelFile eq DEBUG || $strLogLevelFile eq TRACE)))
|
||||
{
|
||||
my $strStackTrace = longmess() . "\n";
|
||||
$strStackTrace =~ s/\n/\n /g;
|
||||
|
||||
if (defined($hLogFile))
|
||||
{
|
||||
syswrite($hLogFile, $strStackTrace);
|
||||
}
|
||||
else
|
||||
{
|
||||
$strLogFileCache .= $strStackTrace;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$rExtra->{bLogFile} = true;
|
||||
}
|
||||
}
|
||||
|
||||
# Return a typed exception if code is defined
|
||||
if (defined($iCode))
|
||||
{
|
||||
$oErrorLast = new pgBackRestDoc::Common::Exception($strLevel, $iCode, $strMessage, longmess(), $rExtra);
|
||||
return $oErrorLast;
|
||||
}
|
||||
|
||||
# Return the message so it can be used in a confess
|
||||
return $strMessage;
|
||||
}
|
||||
|
||||
push @EXPORT, qw(log);
|
||||
|
||||
####################################################################################################################################
|
||||
# logErrorLast - get the last logged error
|
||||
####################################################################################################################################
|
||||
sub logErrorLast
|
||||
{
|
||||
return $oErrorLast;
|
||||
}
|
||||
|
||||
push @EXPORT, qw(logErrorLast);
|
||||
|
||||
####################################################################################################################################
|
||||
# logLevel - get the current log levels
|
||||
####################################################################################################################################
|
||||
sub logLevel
|
||||
{
|
||||
return ($strLogLevelFile, $strLogLevelConsole, $strLogLevelStdErr, $bLogTimestamp);
|
||||
}
|
||||
|
||||
push @EXPORT, qw(logLevel);
|
||||
|
||||
####################################################################################################################################
|
||||
# logFileCacheClear - Clear the log file cache
|
||||
####################################################################################################################################
|
||||
sub logFileCacheClear
|
||||
{
|
||||
undef($strLogFileCache);
|
||||
}
|
||||
|
||||
push @EXPORT, qw(logFileCacheClear);
|
||||
|
||||
####################################################################################################################################
|
||||
# logFileCache - Get the log file cache
|
||||
####################################################################################################################################
|
||||
sub logFileCache
|
||||
{
|
||||
return $strLogFileCache;
|
||||
}
|
||||
|
||||
push @EXPORT, qw(logFileCache);
|
||||
|
||||
1;
|
121
doc/lib/pgBackRestDoc/Common/String.pm
Normal file
121
doc/lib/pgBackRestDoc/Common/String.pm
Normal file
@ -0,0 +1,121 @@
|
||||
####################################################################################################################################
|
||||
# COMMON STRING MODULE
|
||||
####################################################################################################################################
|
||||
package pgBackRestDoc::Common::String;
|
||||
|
||||
use strict;
|
||||
use warnings FATAL => qw(all);
|
||||
use Carp qw(confess longmess);
|
||||
|
||||
use Exporter qw(import);
|
||||
our @EXPORT = qw();
|
||||
use File::Basename qw(dirname);
|
||||
|
||||
####################################################################################################################################
|
||||
# trim
|
||||
#
|
||||
# Trim whitespace.
|
||||
####################################################################################################################################
|
||||
sub trim
|
||||
{
|
||||
my $strBuffer = shift;
|
||||
|
||||
if (!defined($strBuffer))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$strBuffer =~ s/^\s+|\s+$//g;
|
||||
|
||||
return $strBuffer;
|
||||
}
|
||||
|
||||
push @EXPORT, qw(trim);
|
||||
|
||||
####################################################################################################################################
|
||||
# coalesce - return first defined parameter
|
||||
####################################################################################################################################
|
||||
sub coalesce
|
||||
{
|
||||
foreach my $strParam (@_)
|
||||
{
|
||||
if (defined($strParam))
|
||||
{
|
||||
return $strParam;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
push @EXPORT, qw(coalesce);
|
||||
|
||||
####################################################################################################################################
|
||||
# timestampFormat
|
||||
#
|
||||
# Get standard timestamp format (or formatted as specified).
|
||||
####################################################################################################################################
|
||||
sub timestampFormat
|
||||
{
|
||||
my $strFormat = shift;
|
||||
my $lTime = shift;
|
||||
|
||||
if (!defined($strFormat))
|
||||
{
|
||||
$strFormat = '%4d-%02d-%02d %02d:%02d:%02d';
|
||||
}
|
||||
|
||||
if (!defined($lTime))
|
||||
{
|
||||
$lTime = time();
|
||||
}
|
||||
|
||||
my ($iSecond, $iMinute, $iHour, $iMonthDay, $iMonth, $iYear, $iWeekDay, $iYearDay, $bIsDst) = localtime($lTime);
|
||||
|
||||
if ($strFormat eq "%4d")
|
||||
{
|
||||
return sprintf($strFormat, $iYear + 1900)
|
||||
}
|
||||
else
|
||||
{
|
||||
return sprintf($strFormat, $iYear + 1900, $iMonth + 1, $iMonthDay, $iHour, $iMinute, $iSecond);
|
||||
}
|
||||
}
|
||||
|
||||
push @EXPORT, qw(timestampFormat);
|
||||
|
||||
####################################################################################################################################
|
||||
# stringSplit
|
||||
####################################################################################################################################
|
||||
sub stringSplit
|
||||
{
|
||||
my $strString = shift;
|
||||
my $strChar = shift;
|
||||
my $iLength = shift;
|
||||
|
||||
if (length($strString) <= $iLength)
|
||||
{
|
||||
return $strString, undef;
|
||||
}
|
||||
|
||||
my $iPos = index($strString, $strChar);
|
||||
|
||||
if ($iPos == -1)
|
||||
{
|
||||
return $strString, undef;
|
||||
}
|
||||
|
||||
my $iNewPos = $iPos;
|
||||
|
||||
while ($iNewPos != -1 && $iNewPos + 1 < $iLength)
|
||||
{
|
||||
$iPos = $iNewPos;
|
||||
$iNewPos = index($strString, $strChar, $iPos + 1);
|
||||
}
|
||||
|
||||
return substr($strString, 0, $iPos + 1), substr($strString, $iPos + 1);
|
||||
}
|
||||
|
||||
push @EXPORT, qw(stringSplit);
|
||||
|
||||
1;
|
650
doc/lib/pgBackRestDoc/Custom/DocCustomRelease.pm
Normal file
650
doc/lib/pgBackRestDoc/Custom/DocCustomRelease.pm
Normal file
@ -0,0 +1,650 @@
|
||||
####################################################################################################################################
|
||||
# DOC RELEASE MODULE
|
||||
####################################################################################################################################
|
||||
package pgBackRestDoc::Custom::DocCustomRelease;
|
||||
|
||||
use strict;
|
||||
use warnings FATAL => qw(all);
|
||||
use Carp qw(confess);
|
||||
|
||||
use Cwd qw(abs_path);
|
||||
use Exporter qw(import);
|
||||
our @EXPORT = qw();
|
||||
use File::Basename qw(dirname);
|
||||
|
||||
use pgBackRest::Version;
|
||||
|
||||
use pgBackRestBuild::Config::Data;
|
||||
|
||||
use pgBackRestDoc::Common::DocRender;
|
||||
use pgBackRestDoc::Common::Log;
|
||||
use pgBackRestDoc::Common::String;
|
||||
|
||||
####################################################################################################################################
|
||||
# XML node constants
|
||||
####################################################################################################################################
|
||||
use constant XML_PARAM_ID => 'id';
|
||||
|
||||
use constant XML_CONTRIBUTOR_LIST => 'contributor-list';
|
||||
use constant XML_CONTRIBUTOR => 'contributor';
|
||||
use constant XML_CONTRIBUTOR_NAME_DISPLAY => 'contributor-name-display';
|
||||
|
||||
use constant XML_RELEASE_CORE_LIST => 'release-core-list';
|
||||
use constant XML_RELEASE_DOC_LIST => 'release-doc-list';
|
||||
use constant XML_RELEASE_TEST_LIST => 'release-test-list';
|
||||
|
||||
use constant XML_RELEASE_BUG_LIST => 'release-bug-list';
|
||||
use constant XML_RELEASE_DEVELOPMENT_LIST => 'release-development-list';
|
||||
use constant XML_RELEASE_FEATURE_LIST => 'release-feature-list';
|
||||
use constant XML_RELEASE_IMPROVEMENT_LIST => 'release-improvement-list';
|
||||
|
||||
use constant XML_RELEASE_ITEM_CONTRIBUTOR_LIST => 'release-item-contributor-list';
|
||||
|
||||
use constant XML_RELEASE_ITEM_CONTRIBUTOR => 'release-item-contributor';
|
||||
use constant XML_RELEASE_ITEM_IDEATOR => 'release-item-ideator';
|
||||
use constant XML_RELEASE_ITEM_REVIEWER => 'release-item-reviewer';
|
||||
|
||||
####################################################################################################################################
|
||||
# Contributor text constants
|
||||
####################################################################################################################################
|
||||
use constant TEXT_CONTRIBUTED => 'Contributed';
|
||||
use constant TEXT_FIXED => 'Fixed';
|
||||
use constant TEXT_FOUND => 'Reported';
|
||||
use constant TEXT_REVIEWED => 'Reviewed';
|
||||
use constant TEXT_SUGGESTED => 'Suggested';
|
||||
|
||||
####################################################################################################################################
|
||||
# CONSTRUCTOR
|
||||
####################################################################################################################################
|
||||
sub new
|
||||
{
|
||||
my $class = shift; # Class name
|
||||
|
||||
# Create the class hash
|
||||
my $self = {};
|
||||
bless $self, $class;
|
||||
|
||||
# Assign function parameters, defaults, and log debug info
|
||||
(
|
||||
my $strOperation,
|
||||
$self->{oDoc},
|
||||
$self->{bDev},
|
||||
) =
|
||||
logDebugParam
|
||||
(
|
||||
__PACKAGE__ . '->new', \@_,
|
||||
{name => 'oDoc'},
|
||||
{name => 'bDev', required => false, default => false},
|
||||
);
|
||||
|
||||
# Get contributor list
|
||||
foreach my $oContributor ($self->{oDoc}->nodeGet(XML_CONTRIBUTOR_LIST)->nodeList(XML_CONTRIBUTOR))
|
||||
{
|
||||
my $strContributorId = $oContributor->paramGet(XML_PARAM_ID);
|
||||
|
||||
if (!defined($self->{hContributor}))
|
||||
{
|
||||
$self->{hContributor} = {};
|
||||
$self->{strContributorDefault} = $strContributorId;
|
||||
}
|
||||
|
||||
${$self->{hContributor}}{$strContributorId}{name} = $oContributor->fieldGet(XML_CONTRIBUTOR_NAME_DISPLAY);
|
||||
}
|
||||
|
||||
# Return from function and log return values if any
|
||||
return logDebugReturn
|
||||
(
|
||||
$strOperation,
|
||||
{name => 'self', value => $self}
|
||||
);
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# currentStableVersion
|
||||
#
|
||||
# Return the current stable version.
|
||||
####################################################################################################################################
|
||||
sub currentStableVersion
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
my $oDoc = $self->{oDoc};
|
||||
|
||||
foreach my $oRelease ($oDoc->nodeGet('release-list')->nodeList('release'))
|
||||
{
|
||||
my $strVersion = $oRelease->paramGet('version');
|
||||
|
||||
if ($strVersion !~ /dev$/)
|
||||
{
|
||||
return $strVersion;
|
||||
}
|
||||
}
|
||||
|
||||
confess &log(ERROR, "unable to find non-development version");
|
||||
}
|
||||
|
||||
|
||||
####################################################################################################################################
|
||||
# releaseLast
|
||||
#
|
||||
# Get the last release.
|
||||
####################################################################################################################################
|
||||
sub releaseLast
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
my $oDoc = $self->{oDoc};
|
||||
|
||||
foreach my $oRelease ($oDoc->nodeGet('release-list')->nodeList('release'))
|
||||
{
|
||||
return $oRelease;
|
||||
}
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# contributorTextGet
|
||||
#
|
||||
# Get a list of contributors for an item in text format.
|
||||
####################################################################################################################################
|
||||
sub contributorTextGet
|
||||
{
|
||||
my $self = shift;
|
||||
my $oReleaseItem = shift;
|
||||
my $strItemType = shift;
|
||||
|
||||
my $strContributorText;
|
||||
my $hItemContributorType = {};
|
||||
|
||||
# Create a the list of contributors
|
||||
foreach my $strContributorType (XML_RELEASE_ITEM_IDEATOR, XML_RELEASE_ITEM_CONTRIBUTOR, XML_RELEASE_ITEM_REVIEWER)
|
||||
{
|
||||
my $stryItemContributor = [];
|
||||
|
||||
if ($oReleaseItem->nodeTest(XML_RELEASE_ITEM_CONTRIBUTOR_LIST))
|
||||
{
|
||||
foreach my $oContributor ($oReleaseItem->nodeGet(XML_RELEASE_ITEM_CONTRIBUTOR_LIST)->
|
||||
nodeList($strContributorType, false))
|
||||
{
|
||||
push @{$stryItemContributor}, $oContributor->paramGet(XML_PARAM_ID);
|
||||
}
|
||||
}
|
||||
|
||||
if (@$stryItemContributor == 0 && $strContributorType eq XML_RELEASE_ITEM_CONTRIBUTOR)
|
||||
{
|
||||
push @{$stryItemContributor}, $self->{strContributorDefault}
|
||||
}
|
||||
|
||||
$$hItemContributorType{$strContributorType} = $stryItemContributor;
|
||||
}
|
||||
|
||||
# Error if a reviewer is also a contributor
|
||||
foreach my $strReviewer (@{$$hItemContributorType{&XML_RELEASE_ITEM_REVIEWER}})
|
||||
{
|
||||
foreach my $strContributor (@{$$hItemContributorType{&XML_RELEASE_ITEM_CONTRIBUTOR}})
|
||||
{
|
||||
if ($strReviewer eq $strContributor)
|
||||
{
|
||||
confess &log(ERROR, "${strReviewer} cannot be both a contributor and a reviewer");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Error if the ideator list is the same as the contributor list
|
||||
if (join(',', @{$$hItemContributorType{&XML_RELEASE_ITEM_IDEATOR}}) eq
|
||||
join(',', @{$$hItemContributorType{&XML_RELEASE_ITEM_CONTRIBUTOR}}))
|
||||
{
|
||||
confess &log(ERROR, 'cannot have same contributor and ideator list: ' .
|
||||
join(', ', @{$$hItemContributorType{&XML_RELEASE_ITEM_CONTRIBUTOR}}));
|
||||
}
|
||||
|
||||
# Remove the default user if they are the only one in a group (to prevent the entire page from being splattered with one name)
|
||||
foreach my $strContributorType (XML_RELEASE_ITEM_IDEATOR, XML_RELEASE_ITEM_CONTRIBUTOR, XML_RELEASE_ITEM_REVIEWER)
|
||||
{
|
||||
if (@{$$hItemContributorType{$strContributorType}} == 1 &&
|
||||
@{$$hItemContributorType{$strContributorType}}[0] eq $self->{strContributorDefault})
|
||||
{
|
||||
$$hItemContributorType{$strContributorType} = [];
|
||||
}
|
||||
}
|
||||
|
||||
# Render the string
|
||||
foreach my $strContributorType (XML_RELEASE_ITEM_CONTRIBUTOR, XML_RELEASE_ITEM_REVIEWER, XML_RELEASE_ITEM_IDEATOR)
|
||||
{
|
||||
my $stryItemContributor = $$hItemContributorType{$strContributorType};
|
||||
my $strContributorTypeText;
|
||||
|
||||
foreach my $strContributor (@{$stryItemContributor})
|
||||
{
|
||||
my $hContributor = ${$self->{hContributor}}{$strContributor};
|
||||
|
||||
if (!defined($hContributor))
|
||||
{
|
||||
confess &log(ERROR, "contributor ${strContributor} does not exist");
|
||||
}
|
||||
|
||||
$strContributorTypeText .= (defined($strContributorTypeText) ? ', ' : '') . $$hContributor{name};
|
||||
}
|
||||
|
||||
if (defined($strContributorTypeText))
|
||||
{
|
||||
$strContributorTypeText = ' by ' . $strContributorTypeText . '.';
|
||||
|
||||
if ($strContributorType eq XML_RELEASE_ITEM_CONTRIBUTOR)
|
||||
{
|
||||
$strContributorTypeText = ($strItemType eq 'bug' ? TEXT_FIXED : TEXT_CONTRIBUTED) . $strContributorTypeText;
|
||||
}
|
||||
elsif ($strContributorType eq XML_RELEASE_ITEM_IDEATOR)
|
||||
{
|
||||
$strContributorTypeText = ($strItemType eq 'bug' ? TEXT_FOUND : TEXT_SUGGESTED) . $strContributorTypeText;
|
||||
}
|
||||
elsif ($strContributorType eq XML_RELEASE_ITEM_REVIEWER)
|
||||
{
|
||||
$strContributorTypeText = TEXT_REVIEWED . $strContributorTypeText;
|
||||
}
|
||||
|
||||
$strContributorText .= (defined($strContributorText) ? ' ' : '') . $strContributorTypeText;
|
||||
}
|
||||
}
|
||||
|
||||
return $strContributorText;
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# Find a commit by subject prefix. Error if the prefix appears more than once.
|
||||
####################################################################################################################################
|
||||
sub commitFindSubject
|
||||
{
|
||||
my $self = shift;
|
||||
my $rhyCommit = shift;
|
||||
my $strSubjectPrefix = shift;
|
||||
my $bRegExp = shift;
|
||||
|
||||
$bRegExp = defined($bRegExp) ? $bRegExp : true;
|
||||
my $rhResult = undef;
|
||||
|
||||
foreach my $rhCommit (@{$rhyCommit})
|
||||
{
|
||||
if (($bRegExp && $rhCommit->{subject} =~ /^$strSubjectPrefix/) ||
|
||||
(!$bRegExp && length($rhCommit->{subject}) >= length($strSubjectPrefix) &&
|
||||
substr($rhCommit->{subject}, 0, length($strSubjectPrefix)) eq $strSubjectPrefix))
|
||||
{
|
||||
if (defined($rhResult))
|
||||
{
|
||||
confess &log(ERROR, "subject prefix '${strSubjectPrefix}' already found in commit " . $rhCommit->{commit});
|
||||
}
|
||||
|
||||
$rhResult = $rhCommit;
|
||||
}
|
||||
}
|
||||
|
||||
return $rhResult;
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# Throw an error that includes a list of release commits
|
||||
####################################################################################################################################
|
||||
sub commitError
|
||||
{
|
||||
my $self = shift;
|
||||
my $strMessage = shift;
|
||||
my $rstryCommitRemaining = shift;
|
||||
my $rhyCommit = shift;
|
||||
|
||||
my $strList;
|
||||
|
||||
foreach my $strCommit (@{$rstryCommitRemaining})
|
||||
{
|
||||
$strList .=
|
||||
(defined($strList) ? "\n" : '') .
|
||||
substr($rhyCommit->{$strCommit}{date}, 0, length($rhyCommit->{$strCommit}{date}) - 15) . " $strCommit: " .
|
||||
$rhyCommit->{$strCommit}{subject};
|
||||
}
|
||||
|
||||
confess &log(ERROR, "${strMessage}:\n${strList}");
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# docGet
|
||||
#
|
||||
# Get the xml for release.
|
||||
####################################################################################################################################
|
||||
sub docGet
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
# Assign function parameters, defaults, and log debug info
|
||||
my $strOperation = logDebugParam(__PACKAGE__ . '->docGet');
|
||||
|
||||
# Load the git history
|
||||
my $oStorageDoc = new pgBackRestTest::Common::Storage(
|
||||
dirname(abs_path($0)), new pgBackRestTest::Common::StoragePosix({bFileSync => false, bPathSync => false}));
|
||||
my @hyGitLog = @{(JSON::PP->new()->allow_nonref())->decode(${$oStorageDoc->get("resource/git-history.cache")})};
|
||||
|
||||
# Get renderer
|
||||
my $oRender = new pgBackRestDoc::Common::DocRender('text');
|
||||
$oRender->tagSet('backrest', PROJECT_NAME);
|
||||
|
||||
# Create the doc
|
||||
my $oDoc = new pgBackRestDoc::Common::Doc();
|
||||
$oDoc->paramSet('title', $self->{oDoc}->paramGet('title'));
|
||||
$oDoc->paramSet('toc-number', $self->{oDoc}->paramGet('toc-number'));
|
||||
|
||||
# Set the description for use as a meta tag
|
||||
$oDoc->fieldSet('description', $self->{oDoc}->fieldGet('description'));
|
||||
|
||||
# Add the introduction
|
||||
my $oIntroSectionDoc = $oDoc->nodeAdd('section', undef, {id => 'introduction'});
|
||||
$oIntroSectionDoc->nodeAdd('title')->textSet('Introduction');
|
||||
$oIntroSectionDoc->textSet($self->{oDoc}->nodeGet('intro')->textGet());
|
||||
|
||||
# Add each release section
|
||||
my $oSection;
|
||||
my $iDevReleaseTotal = 0;
|
||||
my $iCurrentReleaseTotal = 0;
|
||||
my $iStableReleaseTotal = 0;
|
||||
my $iUnsupportedReleaseTotal = 0;
|
||||
|
||||
my @oyRelease = $self->{oDoc}->nodeGet('release-list')->nodeList('release');
|
||||
|
||||
for (my $iReleaseIdx = 0; $iReleaseIdx < @oyRelease; $iReleaseIdx++)
|
||||
{
|
||||
my $oRelease = $oyRelease[$iReleaseIdx];
|
||||
|
||||
# Get the release version and dev flag
|
||||
my $strVersion = $oRelease->paramGet('version');
|
||||
my $bReleaseDev = $strVersion =~ /dev$/ ? true : false;
|
||||
|
||||
# Get a list of commits that apply to this release
|
||||
my @rhyReleaseCommit;
|
||||
my $rhReleaseCommitRemaining;
|
||||
my @stryReleaseCommitRemaining;
|
||||
my $bReleaseCheckCommit = false;
|
||||
|
||||
if ($strVersion ge '2.01')
|
||||
{
|
||||
# Should commits in the release be checked?
|
||||
$bReleaseCheckCommit = !$bReleaseDev ? true : false;
|
||||
|
||||
# Get the begin commit
|
||||
my $rhReleaseCommitBegin = $self->commitFindSubject(\@hyGitLog, "Begin v${strVersion} development\\.");
|
||||
my $strReleaseCommitBegin = defined($rhReleaseCommitBegin) ? $rhReleaseCommitBegin->{commit} : undef;
|
||||
|
||||
# Get the end commit of the last release
|
||||
my $strReleaseLastVersion = $oyRelease[$iReleaseIdx + 1]->paramGet('version');
|
||||
my $rhReleaseLastCommitEnd = $self->commitFindSubject(\@hyGitLog, "v${strReleaseLastVersion}\\: .+");
|
||||
|
||||
if (!defined($rhReleaseLastCommitEnd))
|
||||
{
|
||||
confess &log(ERROR, "release ${strReleaseLastVersion} must have an end commit");
|
||||
}
|
||||
|
||||
my $strReleaseLastCommitEnd = $rhReleaseLastCommitEnd->{commit};
|
||||
|
||||
# Get the end commit
|
||||
my $rhReleaseCommitEnd = $self->commitFindSubject(\@hyGitLog, "v${strVersion}\\: .+");
|
||||
my $strReleaseCommitEnd = defined($rhReleaseCommitEnd) ? $rhReleaseCommitEnd->{commit} : undef;
|
||||
|
||||
if ($bReleaseCheckCommit && !defined($rhReleaseCommitEnd) && $iReleaseIdx != 0)
|
||||
{
|
||||
confess &log(ERROR, "release ${strVersion} must have an end commit");
|
||||
}
|
||||
|
||||
# Make a list of commits for this release
|
||||
while ($hyGitLog[0]->{commit} ne $strReleaseLastCommitEnd)
|
||||
{
|
||||
# Don't add begin/end commits to the list since they are already accounted for
|
||||
if ((defined($strReleaseCommitEnd) && $hyGitLog[0]->{commit} eq $strReleaseCommitEnd) ||
|
||||
(defined($strReleaseCommitBegin) && $hyGitLog[0]->{commit} eq $strReleaseCommitBegin))
|
||||
{
|
||||
shift(@hyGitLog);
|
||||
}
|
||||
# Else add the commit to this releases' list
|
||||
else
|
||||
{
|
||||
push(@stryReleaseCommitRemaining, $hyGitLog[0]->{commit});
|
||||
push(@rhyReleaseCommit, $hyGitLog[0]);
|
||||
|
||||
$rhReleaseCommitRemaining->{$hyGitLog[0]->{commit}}{date} = $hyGitLog[0]->{date};
|
||||
$rhReleaseCommitRemaining->{$hyGitLog[0]->{commit}}{subject} = $hyGitLog[0]->{subject};
|
||||
|
||||
shift(@hyGitLog);
|
||||
}
|
||||
}
|
||||
|
||||
# At least one commit is required for non-dev releases
|
||||
if ($bReleaseCheckCommit && @stryReleaseCommitRemaining == 0)
|
||||
{
|
||||
confess &log(ERROR, "no commits found for release ${strVersion}");
|
||||
}
|
||||
}
|
||||
|
||||
# Display versions in TOC?
|
||||
my $bTOC = true;
|
||||
|
||||
# Create a release section
|
||||
if ($bReleaseDev)
|
||||
{
|
||||
if ($iDevReleaseTotal > 1)
|
||||
{
|
||||
confess &log(ERROR, 'only one development release is allowed');
|
||||
}
|
||||
|
||||
$oSection = $oDoc->nodeAdd('section', undef, {id => 'development', if => "'{[dev]}' eq 'y'"});
|
||||
$oSection->nodeAdd('title')->textSet("Development Notes");
|
||||
|
||||
$iDevReleaseTotal++;
|
||||
}
|
||||
elsif ($iCurrentReleaseTotal == 0)
|
||||
{
|
||||
$oSection = $oDoc->nodeAdd('section', undef, {id => 'current'});
|
||||
$oSection->nodeAdd('title')->textSet("Current Stable Release");
|
||||
$iCurrentReleaseTotal++;
|
||||
}
|
||||
elsif ($strVersion ge '1.00')
|
||||
{
|
||||
if ($iStableReleaseTotal == 0)
|
||||
{
|
||||
$oSection = $oDoc->nodeAdd('section', undef, {id => 'supported'});
|
||||
$oSection->nodeAdd('title')->textSet("Stable Releases");
|
||||
}
|
||||
|
||||
$iStableReleaseTotal++;
|
||||
$bTOC = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($iUnsupportedReleaseTotal == 0)
|
||||
{
|
||||
$oSection = $oDoc->nodeAdd('section', undef, {id => 'unsupported'});
|
||||
$oSection->nodeAdd('title')->textSet("Pre-Stable Releases");
|
||||
}
|
||||
|
||||
$iUnsupportedReleaseTotal++;
|
||||
$bTOC = false;
|
||||
}
|
||||
|
||||
# Format the date
|
||||
my $strDate = $oRelease->paramGet('date');
|
||||
my $strDateOut = "";
|
||||
|
||||
my @stryMonth = ('January', 'February', 'March', 'April', 'May', 'June',
|
||||
'July', 'August', 'September', 'October', 'November', 'December');
|
||||
|
||||
if ($strDate =~ /^X/)
|
||||
{
|
||||
$strDateOut .= 'No Release Date Set';
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($strDate !~ /^(XXXX-XX-XX)|([0-9]{4}-[0-9]{2}-[0-9]{2})$/)
|
||||
{
|
||||
confess &log(ASSERT, "invalid date ${strDate} for release {$strVersion}");
|
||||
}
|
||||
|
||||
$strDateOut .= 'Released ' . $stryMonth[(substr($strDate, 5, 2) - 1)] . ' ' .
|
||||
(substr($strDate, 8, 2) + 0) . ', ' . substr($strDate, 0, 4);
|
||||
}
|
||||
|
||||
# Add section and titles
|
||||
my $oReleaseSection = $oSection->nodeAdd('section', undef, {id => $strVersion, toc => !$bTOC ? 'n' : undef});
|
||||
$oReleaseSection->paramSet(XML_SECTION_PARAM_ANCHOR, XML_SECTION_PARAM_ANCHOR_VALUE_NOINHERIT);
|
||||
|
||||
$oReleaseSection->nodeAdd('title')->textSet(
|
||||
"v${strVersion} " . ($bReleaseDev ? '' : 'Release ') . 'Notes');
|
||||
|
||||
$oReleaseSection->nodeAdd('subtitle')->textSet($oRelease->paramGet('title'));
|
||||
$oReleaseSection->nodeAdd('subsubtitle')->textSet($strDateOut);
|
||||
|
||||
# Add release sections
|
||||
my $bAdditionalNotes = false;
|
||||
my $bReleaseNote = false;
|
||||
|
||||
my $hSectionType =
|
||||
{
|
||||
&XML_RELEASE_CORE_LIST => {title => 'Core', type => 'core'},
|
||||
&XML_RELEASE_DOC_LIST => {title => 'Documentation', type => 'doc'},
|
||||
&XML_RELEASE_TEST_LIST => {title => 'Test Suite', type => 'test'},
|
||||
};
|
||||
|
||||
foreach my $strSectionType (XML_RELEASE_CORE_LIST, XML_RELEASE_DOC_LIST, XML_RELEASE_TEST_LIST)
|
||||
{
|
||||
if ($oRelease->nodeTest($strSectionType))
|
||||
{
|
||||
# Add release item types
|
||||
my $hItemType =
|
||||
{
|
||||
&XML_RELEASE_BUG_LIST => {title => 'Bug Fixes', type => 'bug'},
|
||||
&XML_RELEASE_FEATURE_LIST => {title => 'Features', type => 'feature'},
|
||||
&XML_RELEASE_IMPROVEMENT_LIST => {title => 'Improvements', type => 'improvement'},
|
||||
&XML_RELEASE_DEVELOPMENT_LIST => {title => 'Development', type => 'development'},
|
||||
};
|
||||
|
||||
foreach my $strItemType (
|
||||
XML_RELEASE_BUG_LIST, XML_RELEASE_FEATURE_LIST, XML_RELEASE_IMPROVEMENT_LIST, XML_RELEASE_DEVELOPMENT_LIST)
|
||||
{
|
||||
next if (!$self->{bDev} && $strItemType eq XML_RELEASE_DEVELOPMENT_LIST);
|
||||
|
||||
if ($oRelease->nodeGet($strSectionType)->nodeTest($strItemType))
|
||||
{
|
||||
if ($strSectionType ne XML_RELEASE_CORE_LIST && !$bAdditionalNotes)
|
||||
{
|
||||
$oReleaseSection->nodeAdd('subtitle')->textSet('Additional Notes');
|
||||
$bAdditionalNotes = true;
|
||||
}
|
||||
|
||||
# Add release note if present
|
||||
if (!$bReleaseNote && $oRelease->nodeGet($strSectionType)->nodeTest('p'))
|
||||
{
|
||||
$oReleaseSection->nodeAdd('p')->textSet($oRelease->nodeGet($strSectionType)->nodeGet('p')->textGet());
|
||||
$bReleaseNote = true;
|
||||
}
|
||||
|
||||
my $strTypeText =
|
||||
($strSectionType eq XML_RELEASE_CORE_LIST ? '' : $$hSectionType{$strSectionType}{title}) . ' ' .
|
||||
$$hItemType{$strItemType}{title} . ':';
|
||||
|
||||
$oReleaseSection->
|
||||
nodeAdd('p')->textSet(
|
||||
{name => 'text', children=> [{name => 'b', value => $strTypeText}]});
|
||||
|
||||
my $oList = $oReleaseSection->nodeAdd('list');
|
||||
|
||||
# Add release items
|
||||
foreach my $oReleaseFeature ($oRelease->nodeGet($strSectionType)->
|
||||
nodeGet($strItemType)->nodeList('release-item'))
|
||||
{
|
||||
my @rhyReleaseItemP = $oReleaseFeature->nodeList('p');
|
||||
my $oReleaseItemText = $rhyReleaseItemP[0]->textGet();
|
||||
|
||||
# Check release item commits
|
||||
if ($bReleaseCheckCommit && $strItemType ne XML_RELEASE_DEVELOPMENT_LIST)
|
||||
{
|
||||
my @oyCommit = $oReleaseFeature->nodeList('commit', false);
|
||||
|
||||
# If no commits found then try to use the description as the commit subject
|
||||
if (@oyCommit == 0)
|
||||
{
|
||||
my $strSubject = $oRender->processText($oReleaseItemText);
|
||||
my $rhCommit = $self->commitFindSubject(\@rhyReleaseCommit, $strSubject, false);
|
||||
|
||||
if (!defined($rhCommit))
|
||||
{
|
||||
$self->commitError(
|
||||
"unable to find commit or no subject match for release ${strVersion} item" .
|
||||
" '${strSubject}'",
|
||||
\@stryReleaseCommitRemaining, $rhReleaseCommitRemaining);
|
||||
|
||||
my $strCommit = $rhCommit->{commit};
|
||||
@stryReleaseCommitRemaining = grep(!/$strCommit/, @stryReleaseCommitRemaining);
|
||||
}
|
||||
}
|
||||
|
||||
# Check the rest of the commits to ensure they exist
|
||||
foreach my $oCommit (@oyCommit)
|
||||
{
|
||||
my $strSubject = $oCommit->paramGet('subject');
|
||||
my $rhCommit = $self->commitFindSubject(\@rhyReleaseCommit, $strSubject, false);
|
||||
|
||||
if (defined($rhCommit))
|
||||
{
|
||||
my $strCommit = $rhCommit->{commit};
|
||||
@stryReleaseCommitRemaining = grep(!/$strCommit/, @stryReleaseCommitRemaining);
|
||||
}
|
||||
else
|
||||
{
|
||||
$self->commitError(
|
||||
"unable to find release ${strVersion} commit subject '${strSubject}' in list",
|
||||
\@stryReleaseCommitRemaining, $rhReleaseCommitRemaining);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Append the rest of the text
|
||||
if (@rhyReleaseItemP > 1)
|
||||
{
|
||||
shift(@rhyReleaseItemP);
|
||||
|
||||
push(@{$oReleaseItemText->{oDoc}{children}}, ' ');
|
||||
|
||||
foreach my $rhReleaseItemP (@rhyReleaseItemP)
|
||||
{
|
||||
push(@{$oReleaseItemText->{oDoc}{children}}, @{$rhReleaseItemP->textGet()->{oDoc}{children}});
|
||||
}
|
||||
}
|
||||
|
||||
# Append contributor info
|
||||
my $strContributorText = $self->contributorTextGet($oReleaseFeature, $$hItemType{$strItemType}{type});
|
||||
|
||||
if (defined($strContributorText))
|
||||
{
|
||||
push(@{$oReleaseItemText->{oDoc}{children}}, ' (');
|
||||
push(@{$oReleaseItemText->{oDoc}{children}},
|
||||
{name => 'i', value => $strContributorText});
|
||||
push(@{$oReleaseItemText->{oDoc}{children}}, ')');
|
||||
}
|
||||
|
||||
# Add the list item
|
||||
$oList->nodeAdd('list-item')->textSet($oReleaseItemText);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Error if there are commits left over
|
||||
# if ($bReleaseCheckCommit && @stryReleaseCommitRemaining != 0)
|
||||
# {
|
||||
# $self->commitError(
|
||||
# "unassigned commits for release ${strVersion}", \@stryReleaseCommitRemaining, $rhReleaseCommitRemaining);
|
||||
# }
|
||||
}
|
||||
|
||||
# Return from function and log return values if any
|
||||
return logDebugReturn
|
||||
(
|
||||
$strOperation,
|
||||
{name => 'oDoc', value => $oDoc}
|
||||
);
|
||||
}
|
||||
|
||||
1;
|
288
doc/lib/pgBackRestDoc/Html/DocHtmlBuilder.pm
Normal file
288
doc/lib/pgBackRestDoc/Html/DocHtmlBuilder.pm
Normal file
@ -0,0 +1,288 @@
|
||||
####################################################################################################################################
|
||||
# DOC HTML BUILDER MODULE
|
||||
####################################################################################################################################
|
||||
package pgBackRestDoc::Html::DocHtmlBuilder;
|
||||
|
||||
use strict;
|
||||
use warnings FATAL => qw(all);
|
||||
use Carp qw(confess);
|
||||
|
||||
use Exporter qw(import);
|
||||
our @EXPORT = qw();
|
||||
|
||||
use pgBackRestDoc::Common::Log;
|
||||
use pgBackRestDoc::Common::String;
|
||||
use pgBackRestDoc::Html::DocHtmlElement;
|
||||
|
||||
####################################################################################################################################
|
||||
# CONSTRUCTOR
|
||||
####################################################################################################################################
|
||||
sub new
|
||||
{
|
||||
my $class = shift; # Class name
|
||||
|
||||
# Create the class hash
|
||||
my $self = {};
|
||||
bless $self, $class;
|
||||
|
||||
$self->{strClass} = $class;
|
||||
|
||||
# Assign function parameters, defaults, and log debug info
|
||||
(
|
||||
my $strOperation,
|
||||
$self->{strName},
|
||||
$self->{strTitle},
|
||||
$self->{strFavicon},
|
||||
$self->{strLogo},
|
||||
$self->{strDescription},
|
||||
$self->{bPretty},
|
||||
$self->{bCompact},
|
||||
$self->{strCss},
|
||||
) =
|
||||
logDebugParam
|
||||
(
|
||||
__PACKAGE__ . '->new', \@_,
|
||||
{name => 'strName'},
|
||||
{name => 'strTitle'},
|
||||
{name => 'strFavicon', required => false},
|
||||
{name => 'strLogo', required => false},
|
||||
{name => 'strDescription', required => false},
|
||||
{name => 'bPretty', default => false},
|
||||
{name => 'bCompact', default => false},
|
||||
{name => 'strCss', required => false},
|
||||
);
|
||||
|
||||
$self->{oBody} = new pgBackRestDoc::Html::DocHtmlElement(HTML_BODY);
|
||||
|
||||
# Return from function and log return values if any
|
||||
return logDebugReturn
|
||||
(
|
||||
$strOperation,
|
||||
{name => 'self', value => $self}
|
||||
);
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# indent
|
||||
#
|
||||
# Indent html
|
||||
####################################################################################################################################
|
||||
sub indent
|
||||
{
|
||||
my $self = shift;
|
||||
my $iDepth = shift;
|
||||
|
||||
return $self->{bPretty} ? (' ' x $iDepth) : '';
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# lf
|
||||
#
|
||||
# Add a linefeed.
|
||||
####################################################################################################################################
|
||||
sub lf
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
return $self->{bPretty} ? "\n" : '';
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# bodyGet
|
||||
#
|
||||
# Get the body element.
|
||||
####################################################################################################################################
|
||||
sub bodyGet
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
return $self->{oBody};
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# htmlRender
|
||||
#
|
||||
# Render each html element.
|
||||
####################################################################################################################################
|
||||
sub htmlRender
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
# Assign function parameters, defaults, and log debug info
|
||||
my (
|
||||
$strOperation,
|
||||
$oElement,
|
||||
$iDepth
|
||||
) =
|
||||
logDebugParam
|
||||
(
|
||||
__PACKAGE__ . '->htmlRender', \@_,
|
||||
{name => 'oElement', trace => true},
|
||||
{name => 'iDepth', trace => true}
|
||||
);
|
||||
|
||||
# Build the header
|
||||
my $strHtml =
|
||||
$self->indent($iDepth) . "<$oElement->{strType}" .
|
||||
(defined($oElement->{strClass}) ? " class=\"$oElement->{strClass}\"": '') .
|
||||
(defined($oElement->{strRef}) ? " href=\"$oElement->{strRef}\"": '') .
|
||||
(defined($oElement->{strId}) ? " id=\"$oElement->{strId}\"": '') .
|
||||
(defined($oElement->{strExtra}) ? " $oElement->{strExtra}": '') . '>';
|
||||
|
||||
if (defined($oElement->{strContent}))
|
||||
{
|
||||
if (!defined($oElement->{bPre}) || !$oElement->{bPre})
|
||||
{
|
||||
$oElement->{strContent} =~ s/\n/\<br\/>\n/g;
|
||||
$oElement->{strContent} = trim($oElement->{strContent});
|
||||
$strHtml .= $self->lf();
|
||||
}
|
||||
else
|
||||
{
|
||||
$oElement->{strContent} =~ s/\&/\&\;/g;
|
||||
}
|
||||
|
||||
$strHtml .= $oElement->{strContent};
|
||||
|
||||
if (!defined($oElement->{bPre}) || !$oElement->{bPre})
|
||||
{
|
||||
$strHtml .= $self->lf() . $self->indent($iDepth);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!($oElement->{strType} eq HTML_A && @{$oElement->{oyElement}} == 0))
|
||||
{
|
||||
$strHtml .= $self->lf();
|
||||
}
|
||||
|
||||
foreach my $oChildElement (@{$oElement->{oyElement}})
|
||||
{
|
||||
$strHtml .= $self->htmlRender($oChildElement, $iDepth + 1);
|
||||
}
|
||||
|
||||
if (!($oElement->{strType} eq HTML_A && @{$oElement->{oyElement}} == 0))
|
||||
{
|
||||
$strHtml .= $self->indent($iDepth);
|
||||
}
|
||||
}
|
||||
|
||||
$strHtml .= "</$oElement->{strType}>" . $self->lf();
|
||||
|
||||
# Return from function and log return values if any
|
||||
return logDebugReturn
|
||||
(
|
||||
$strOperation,
|
||||
{name => 'strHtml', value => $strHtml, trace => true}
|
||||
);
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# escape
|
||||
#
|
||||
# Generate the HTML.
|
||||
####################################################################################################################################
|
||||
sub escape
|
||||
{
|
||||
my $self = shift;
|
||||
my $strBuffer = shift;
|
||||
|
||||
$strBuffer =~ s/\&/\&\;/g;
|
||||
$strBuffer =~ s/\</\<\;/g;
|
||||
|
||||
return $strBuffer;
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# htmlGet
|
||||
#
|
||||
# Generate the HTML.
|
||||
####################################################################################################################################
|
||||
sub htmlGet
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
# Assign function parameters, defaults, and log debug info
|
||||
my ($strOperation) = logDebugParam(__PACKAGE__ . '->htmlGet');
|
||||
|
||||
# Build the header
|
||||
my $strHtml =
|
||||
$self->indent(0) . "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"" .
|
||||
" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">" . $self->lf() .
|
||||
$self->indent(0) . "<html xmlns=\"http://www.w3.org/1999/xhtml\">" . $self->lf() .
|
||||
$self->indent(0) . "<head>" . $self->lf() .
|
||||
$self->indent(1) . '<title>' . $self->escape($self->{strTitle}) . '</title>' . $self->lf() .
|
||||
$self->indent(1) . "<meta http-equiv=\"Content-Type\" content=\"text/html;charset=utf-8\"></meta>" . $self->lf();
|
||||
|
||||
if (!$self->{bCompact})
|
||||
{
|
||||
$strHtml .=
|
||||
# $self->indent(1) . "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"></meta>" . $self->lf() .
|
||||
$self->indent(1) .
|
||||
'<meta property="og:site_name" content="' . $self->escape($self->{strName}) . '"></meta>' . $self->lf() .
|
||||
$self->indent(1) .
|
||||
'<meta property="og:title" content="' . $self->escape($self->{strTitle}) . '"></meta>' . $self->lf() .
|
||||
$self->indent(1) . '<meta property="og:type" content="website"></meta>' . $self->lf();
|
||||
|
||||
if (defined($self->{strFavicon}))
|
||||
{
|
||||
$strHtml .=
|
||||
$self->indent(1) . "<link rel=\"icon\" href=\"$self->{strFavicon}\" type=\"image/png\"></link>" . $self->lf();
|
||||
}
|
||||
|
||||
if (defined($self->{strLogo}))
|
||||
{
|
||||
$strHtml .=
|
||||
$self->indent(1) . "<meta property=\"og:image:type\" content=\"image/png\"></meta>" . $self->lf() .
|
||||
$self->indent(1) . "<meta property=\"og:image\" content=\"{[backrest-url-base]}/$self->{strLogo}\"></meta>" .
|
||||
$self->lf();
|
||||
}
|
||||
|
||||
if (defined($self->{strDescription}))
|
||||
{
|
||||
$strHtml .=
|
||||
$self->indent(1) .
|
||||
'<meta name="description" content="' . $self->escape($self->{strDescription}) . '"></meta>' . $self->lf() .
|
||||
$self->indent(1) .
|
||||
'<meta property="og:description" content="' . $self->escape($self->{strDescription}) . '"></meta>' . $self->lf();
|
||||
}
|
||||
}
|
||||
|
||||
if (defined($self->{strCss}))
|
||||
{
|
||||
my $strCss = $self->{strCss};
|
||||
|
||||
if (!$self->{bPretty})
|
||||
{
|
||||
$strCss =~ s/^\s+//mg;
|
||||
$strCss =~ s/\n//g;
|
||||
$strCss =~ s/\/\*.*?\*\///g;
|
||||
}
|
||||
|
||||
$strHtml .=
|
||||
$self->indent(1) . '<style type="text/css">' . $self->lf() . trim($strCss) . $self->lf() .
|
||||
$self->indent(1) . '</style>' . $self->lf();
|
||||
}
|
||||
else
|
||||
{
|
||||
$strHtml .=
|
||||
$self->indent(1) . "<link rel=\"stylesheet\" href=\"default.css\" type=\"text/css\"></link>" . $self->lf();
|
||||
}
|
||||
|
||||
$strHtml .=
|
||||
$self->indent(0) . "</head>" . $self->lf() .
|
||||
$self->htmlRender($self->bodyGet(), 0);
|
||||
|
||||
# Complete the html
|
||||
$strHtml .=
|
||||
$self->indent(0) . "</html>" . $self->lf();
|
||||
|
||||
# Return from function and log return values if any
|
||||
return logDebugReturn
|
||||
(
|
||||
$strOperation,
|
||||
{name => 'strHtml', value => $strHtml, trace => true}
|
||||
);
|
||||
}
|
||||
|
||||
1;
|
158
doc/lib/pgBackRestDoc/Html/DocHtmlElement.pm
Normal file
158
doc/lib/pgBackRestDoc/Html/DocHtmlElement.pm
Normal file
@ -0,0 +1,158 @@
|
||||
####################################################################################################################################
|
||||
# DOC HTML ELEMENT MODULE
|
||||
####################################################################################################################################
|
||||
package pgBackRestDoc::Html::DocHtmlElement;
|
||||
|
||||
use strict;
|
||||
use warnings FATAL => qw(all);
|
||||
use Carp qw(confess);
|
||||
|
||||
use Exporter qw(import);
|
||||
our @EXPORT = qw();
|
||||
use Scalar::Util qw(blessed);
|
||||
|
||||
use pgBackRestDoc::Common::Log;
|
||||
|
||||
####################################################################################################################################
|
||||
# Html Element Types
|
||||
####################################################################################################################################
|
||||
use constant HTML_A => 'a';
|
||||
push @EXPORT, qw(HTML_A);
|
||||
use constant HTML_BODY => 'body';
|
||||
push @EXPORT, qw(HTML_BODY);
|
||||
use constant HTML_PRE => 'pre';
|
||||
push @EXPORT, qw(HTML_PRE);
|
||||
use constant HTML_DIV => 'div';
|
||||
push @EXPORT, qw(HTML_DIV);
|
||||
use constant HTML_SPAN => 'span';
|
||||
push @EXPORT, qw(HTML_SPAN);
|
||||
use constant HTML_TABLE => 'table';
|
||||
push @EXPORT, qw(HTML_TABLE);
|
||||
use constant HTML_TABLE_CAPTION => 'caption';
|
||||
push @EXPORT, qw(HTML_TABLE_CAPTION);
|
||||
use constant HTML_TD => 'td';
|
||||
push @EXPORT, qw(HTML_TD);
|
||||
use constant HTML_TH => 'th';
|
||||
push @EXPORT, qw(HTML_TH);
|
||||
use constant HTML_TR => 'tr';
|
||||
push @EXPORT, qw(HTML_TR);
|
||||
use constant HTML_UL => 'ul';
|
||||
push @EXPORT, qw(HTML_UL);
|
||||
use constant HTML_LI => 'li';
|
||||
push @EXPORT, qw(HTML_LI);
|
||||
|
||||
####################################################################################################################################
|
||||
# CONSTRUCTOR
|
||||
####################################################################################################################################
|
||||
sub new
|
||||
{
|
||||
my $class = shift; # Class name
|
||||
|
||||
# Create the class hash
|
||||
my $self = {};
|
||||
bless $self, $class;
|
||||
|
||||
$self->{strClass} = $class;
|
||||
|
||||
# Assign function parameters, defaults, and log debug info
|
||||
(
|
||||
my $strOperation,
|
||||
$self->{strType},
|
||||
$self->{strClass},
|
||||
my $oParam
|
||||
) =
|
||||
logDebugParam
|
||||
(
|
||||
__PACKAGE__ . '->new', \@_,
|
||||
{name => 'strType', trace => true},
|
||||
{name => 'strClass', required => false, trace => true},
|
||||
{name => 'oParam', required => false, trace => true}
|
||||
);
|
||||
|
||||
$self->{oyElement} = [];
|
||||
$self->{strContent} = $$oParam{strContent};
|
||||
$self->{strId} = $$oParam{strId};
|
||||
$self->{strRef} = $$oParam{strRef};
|
||||
$self->{strExtra} = $$oParam{strExtra};
|
||||
$self->{bPre} = $$oParam{bPre};
|
||||
|
||||
# Return from function and log return values if any
|
||||
return logDebugReturn
|
||||
(
|
||||
$strOperation,
|
||||
{name => 'self', value => $self}
|
||||
);
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# addNew
|
||||
#
|
||||
# Create a new element and add it.
|
||||
####################################################################################################################################
|
||||
sub addNew
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
# Assign function parameters, defaults, and log debug info
|
||||
my (
|
||||
$strOperation,
|
||||
$strType,
|
||||
$strClass,
|
||||
$oParam
|
||||
) =
|
||||
logDebugParam
|
||||
(
|
||||
__PACKAGE__ . '->addNew', \@_,
|
||||
{name => 'strType', trace => true},
|
||||
{name => 'strClass', required => false, trace => true},
|
||||
{name => 'oParam', required => false, trace => true}
|
||||
);
|
||||
|
||||
my $oElement = new pgBackRestDoc::Html::DocHtmlElement($strType, $strClass, $oParam);
|
||||
|
||||
$self->add($oElement);
|
||||
|
||||
# Return from function and log return values if any
|
||||
return logDebugReturn
|
||||
(
|
||||
$strOperation,
|
||||
{name => 'oElement', value => $oElement, trace => true}
|
||||
);
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# add
|
||||
#
|
||||
# Add an element.
|
||||
####################################################################################################################################
|
||||
sub add
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
# Assign function parameters, defaults, and log debug info
|
||||
my (
|
||||
$strOperation,
|
||||
$oElement
|
||||
) =
|
||||
logDebugParam
|
||||
(
|
||||
__PACKAGE__ . '->add', \@_,
|
||||
{name => 'oElement', trace => true}
|
||||
);
|
||||
|
||||
if (!(blessed($oElement) && $oElement->isa('pgBackRestDoc::Html::DocHtmlElement')))
|
||||
{
|
||||
confess &log(ASSERT, 'oElement must be a valid element object');
|
||||
}
|
||||
|
||||
push(@{$self->{oyElement}}, $oElement);
|
||||
|
||||
# Return from function and log return values if any
|
||||
return logDebugReturn
|
||||
(
|
||||
$strOperation,
|
||||
{name => 'oElement', value => $oElement, trace => true}
|
||||
);
|
||||
}
|
||||
|
||||
1;
|
699
doc/lib/pgBackRestDoc/Html/DocHtmlPage.pm
Normal file
699
doc/lib/pgBackRestDoc/Html/DocHtmlPage.pm
Normal file
@ -0,0 +1,699 @@
|
||||
####################################################################################################################################
|
||||
# DOC HTML PAGE MODULE
|
||||
####################################################################################################################################
|
||||
package pgBackRestDoc::Html::DocHtmlPage;
|
||||
use parent 'pgBackRestDoc::Common::DocExecute';
|
||||
|
||||
use strict;
|
||||
use warnings FATAL => qw(all);
|
||||
use Carp qw(confess);
|
||||
|
||||
use Data::Dumper;
|
||||
use Exporter qw(import);
|
||||
our @EXPORT = qw();
|
||||
|
||||
use pgBackRestDoc::Common::DocConfig;
|
||||
use pgBackRestDoc::Common::DocManifest;
|
||||
use pgBackRestDoc::Common::DocRender;
|
||||
use pgBackRestDoc::Html::DocHtmlBuilder;
|
||||
use pgBackRestDoc::Html::DocHtmlElement;
|
||||
use pgBackRestDoc::Common::Log;
|
||||
use pgBackRestDoc::Common::String;
|
||||
|
||||
####################################################################################################################################
|
||||
# CONSTRUCTOR
|
||||
####################################################################################################################################
|
||||
sub new
|
||||
{
|
||||
my $class = shift; # Class name
|
||||
|
||||
# Assign function parameters, defaults, and log debug info
|
||||
my
|
||||
(
|
||||
$strOperation,
|
||||
$oManifest,
|
||||
$strRenderOutKey,
|
||||
$bMenu,
|
||||
$bExe,
|
||||
$bCompact,
|
||||
$strCss,
|
||||
$bPretty,
|
||||
) =
|
||||
logDebugParam
|
||||
(
|
||||
__PACKAGE__ . '->new', \@_,
|
||||
{name => 'oManifest'},
|
||||
{name => 'strRenderOutKey'},
|
||||
{name => 'bMenu'},
|
||||
{name => 'bExe'},
|
||||
{name => 'bCompact'},
|
||||
{name => 'strCss'},
|
||||
{name => 'bPretty'},
|
||||
);
|
||||
|
||||
# Create the class hash
|
||||
my $self = $class->SUPER::new(RENDER_TYPE_HTML, $oManifest, $strRenderOutKey, $bExe);
|
||||
bless $self, $class;
|
||||
|
||||
$self->{bMenu} = $bMenu;
|
||||
$self->{bCompact} = $bCompact;
|
||||
$self->{strCss} = $strCss;
|
||||
$self->{bPretty} = $bPretty;
|
||||
|
||||
# Return from function and log return values if any
|
||||
return logDebugReturn
|
||||
(
|
||||
$strOperation,
|
||||
{name => 'self', value => $self}
|
||||
);
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# process
|
||||
#
|
||||
# Generate the site html
|
||||
####################################################################################################################################
|
||||
sub process
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
# Assign function parameters, defaults, and log debug info
|
||||
my $strOperation = logDebugParam(__PACKAGE__ . '->process');
|
||||
|
||||
# Working variables
|
||||
my $oPage = $self->{oDoc};
|
||||
my $oRender = $self->{oManifest}->renderGet(RENDER_TYPE_HTML);
|
||||
|
||||
# Initialize page
|
||||
my $strTitle = $oPage->paramGet('title');
|
||||
my $strSubTitle = $oPage->paramGet('subtitle', false);
|
||||
|
||||
my $oHtmlBuilder = new pgBackRestDoc::Html::DocHtmlBuilder(
|
||||
$self->{oManifest}->variableReplace('{[project]}' . (defined($self->{oManifest}->variableGet('project-tagline')) ?
|
||||
' - ' . $self->{oManifest}->variableGet('project-tagline') : '')),
|
||||
$self->{oManifest}->variableReplace($strTitle . (defined($strSubTitle) ? " - ${strSubTitle}" : '')),
|
||||
$self->{oManifest}->variableGet('project-favicon'),
|
||||
$self->{oManifest}->variableGet('project-logo'),
|
||||
$self->{oManifest}->variableReplace(trim($self->{oDoc}->fieldGet('description'))),
|
||||
$self->{bPretty},
|
||||
$self->{bCompact},
|
||||
$self->{bCompact} ? $self->{strCss} : undef);
|
||||
|
||||
# Generate header
|
||||
my $oPageHeader = $oHtmlBuilder->bodyGet()->addNew(HTML_DIV, 'page-header');
|
||||
|
||||
# add the logo to the header
|
||||
if (defined($self->{oManifest}->variableGet('html-logo')))
|
||||
{
|
||||
$oPageHeader->
|
||||
addNew(HTML_DIV, 'page-header-logo',
|
||||
{strContent =>"{[html-logo]}"});
|
||||
}
|
||||
|
||||
$oPageHeader->
|
||||
addNew(HTML_DIV, 'page-header-title',
|
||||
{strContent => $strTitle});
|
||||
|
||||
if (defined($strSubTitle))
|
||||
{
|
||||
$oPageHeader->
|
||||
addNew(HTML_DIV, 'page-header-subtitle',
|
||||
{strContent => $strSubTitle});
|
||||
}
|
||||
|
||||
# Generate menu
|
||||
if ($self->{bMenu})
|
||||
{
|
||||
my $oMenuBody = $oHtmlBuilder->bodyGet()->addNew(HTML_DIV, 'page-menu')->addNew(HTML_DIV, 'menu-body');
|
||||
|
||||
# Get the menu in the order listed in the manifest.xml
|
||||
foreach my $strRenderOutKey (@{${$oRender}{stryOrder}})
|
||||
{
|
||||
# Do not output the menu item for the page the user is on (e.g. on Command page, the Command menu item will not appear)
|
||||
if ($strRenderOutKey ne $self->{strRenderOutKey})
|
||||
{
|
||||
my $oRenderOut = $self->{oManifest}->renderOutGet(RENDER_TYPE_HTML, $strRenderOutKey);
|
||||
|
||||
if (defined($$oRenderOut{menu}))
|
||||
{
|
||||
$oMenuBody->addNew(HTML_DIV, 'menu')->addNew(
|
||||
HTML_A, 'menu-link',
|
||||
{strContent => $$oRenderOut{menu},
|
||||
strRef => $strRenderOutKey eq 'index' ? '{[project-url-root]}' : "${strRenderOutKey}.html"});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Generate table of contents
|
||||
my $oPageTocBody;
|
||||
|
||||
if ($self->{bToc})
|
||||
{
|
||||
my $oPageToc = $oHtmlBuilder->bodyGet()->addNew(HTML_DIV, 'page-toc');
|
||||
|
||||
$oPageToc->addNew(HTML_DIV, 'page-toc-header')->addNew(HTML_DIV, 'page-toc-title', {strContent => "Table of Contents"});
|
||||
|
||||
$oPageTocBody = $oPageToc->
|
||||
addNew(HTML_DIV, 'page-toc-body');
|
||||
}
|
||||
|
||||
# Generate body
|
||||
my $oPageBody = $oHtmlBuilder->bodyGet()->addNew(HTML_DIV, 'page-body');
|
||||
my $iSectionNo = 1;
|
||||
|
||||
# Render sections
|
||||
foreach my $oSection ($oPage->nodeList('section'))
|
||||
{
|
||||
my ($oChildSectionElement, $oChildSectionTocElement) =
|
||||
$self->sectionProcess($oSection, undef, "${iSectionNo}", 1);
|
||||
|
||||
$oPageBody->add($oChildSectionElement);
|
||||
|
||||
if (defined($oPageTocBody) && defined($oChildSectionTocElement))
|
||||
{
|
||||
$oPageTocBody->add($oChildSectionTocElement);
|
||||
}
|
||||
|
||||
$iSectionNo++;
|
||||
}
|
||||
|
||||
my $oPageFooter = $oHtmlBuilder->bodyGet()->
|
||||
addNew(HTML_DIV, 'page-footer',
|
||||
{strContent => '{[html-footer]}'});
|
||||
|
||||
# Return from function and log return values if any
|
||||
return logDebugReturn
|
||||
(
|
||||
$strOperation,
|
||||
{name => 'strHtml', value => $oHtmlBuilder->htmlGet(), trace => true}
|
||||
);
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# sectionProcess
|
||||
####################################################################################################################################
|
||||
sub sectionProcess
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
# Assign function parameters, defaults, and log debug info
|
||||
my
|
||||
(
|
||||
$strOperation,
|
||||
$oSection,
|
||||
$strAnchor,
|
||||
$strSectionNo,
|
||||
$iDepth
|
||||
) =
|
||||
logDebugParam
|
||||
(
|
||||
__PACKAGE__ . '->sectionProcess', \@_,
|
||||
{name => 'oSection'},
|
||||
{name => 'strAnchor', required => false},
|
||||
{name => 'strSectionNo'},
|
||||
{name => 'iDepth'}
|
||||
);
|
||||
|
||||
if ($oSection->paramGet('log'))
|
||||
{
|
||||
&log(INFO, (' ' x ($iDepth + 1)) . 'process section: ' . $oSection->paramGet('path'));
|
||||
}
|
||||
|
||||
if ($iDepth > 3)
|
||||
{
|
||||
confess &log(ASSERT, "section depth of ${iDepth} exceeds maximum");
|
||||
}
|
||||
|
||||
# Working variables
|
||||
$strAnchor =
|
||||
($oSection->paramTest(XML_SECTION_PARAM_ANCHOR, XML_SECTION_PARAM_ANCHOR_VALUE_NOINHERIT) ? '' :
|
||||
(defined($strAnchor) ? "${strAnchor}/" : '')) .
|
||||
$oSection->paramGet('id');
|
||||
|
||||
# Create the section toc element
|
||||
my $oSectionTocElement = new pgBackRestDoc::Html::DocHtmlElement(HTML_DIV, "section${iDepth}-toc");
|
||||
|
||||
# Create the section element
|
||||
my $oSectionElement = new pgBackRestDoc::Html::DocHtmlElement(HTML_DIV, "section${iDepth}");
|
||||
|
||||
# Add the section anchor
|
||||
$oSectionElement->addNew(HTML_A, undef, {strId => $strAnchor});
|
||||
|
||||
# Add the section title to section and toc
|
||||
my $oSectionHeaderElement = $oSectionElement->addNew(HTML_DIV, "section${iDepth}-header");
|
||||
my $strSectionTitle = $self->processText($oSection->nodeGet('title')->textGet());
|
||||
|
||||
if ($self->{bTocNumber})
|
||||
{
|
||||
$oSectionHeaderElement->addNew(HTML_DIV, "section${iDepth}-number", {strContent => $strSectionNo});
|
||||
}
|
||||
|
||||
$oSectionHeaderElement->addNew(HTML_DIV, "section${iDepth}-title", {strContent => $strSectionTitle});
|
||||
|
||||
if ($self->{bTocNumber})
|
||||
{
|
||||
$oSectionTocElement->addNew(HTML_DIV, "section${iDepth}-toc-number", {strContent => $strSectionNo});
|
||||
}
|
||||
|
||||
my $oTocSectionTitleElement = $oSectionTocElement->addNew(HTML_DIV, "section${iDepth}-toc-title");
|
||||
|
||||
$oTocSectionTitleElement->addNew(
|
||||
HTML_A, undef,
|
||||
{strContent => $strSectionTitle, strRef => "#${strAnchor}"});
|
||||
|
||||
# Add the section intro if it exists
|
||||
if (defined($oSection->textGet(false)))
|
||||
{
|
||||
$oSectionElement->
|
||||
addNew(HTML_DIV, "section-intro",
|
||||
{strContent => $self->processText($oSection->textGet())});
|
||||
}
|
||||
|
||||
# Add the section body
|
||||
my $oSectionBodyElement = $oSectionElement->addNew(HTML_DIV, "section-body");
|
||||
|
||||
# Process each child
|
||||
my $iSectionNo = 1;
|
||||
|
||||
foreach my $oChild ($oSection->nodeList())
|
||||
{
|
||||
&log(DEBUG, (' ' x ($iDepth + 2)) . 'process child ' . $oChild->nameGet());
|
||||
|
||||
# Execute a command
|
||||
if ($oChild->nameGet() eq 'execute-list')
|
||||
{
|
||||
my $bShow = $oChild->paramTest('show', 'n') ? false : true;
|
||||
my $oExecuteBodyElement;
|
||||
my $bFirst = true;
|
||||
my $strHostName = $self->{oManifest}->variableReplace($oChild->paramGet('host'));
|
||||
|
||||
if ($bShow)
|
||||
{
|
||||
my $oSectionBodyExecute = $oSectionBodyElement->addNew(HTML_DIV, "execute");
|
||||
$oSectionBodyExecute->
|
||||
addNew(HTML_DIV, "execute-title",
|
||||
{strContent => "<span class=\"host\">${strHostName}</span> <b>⇒</b> " .
|
||||
$self->processText($oChild->nodeGet('title')->textGet())});
|
||||
$oExecuteBodyElement = $oSectionBodyExecute->addNew(HTML_DIV, "execute-body");
|
||||
}
|
||||
|
||||
foreach my $oExecute ($oChild->nodeList('execute'))
|
||||
{
|
||||
my $bExeShow = !$oExecute->paramTest('show', 'n');
|
||||
my $bExeExpectedError = defined($oExecute->paramGet('err-expect', false));
|
||||
|
||||
my ($strCommand, $strOutput) = $self->execute(
|
||||
$oSection, $strHostName, $oExecute, {iIndent => $iDepth + 3, bShow => $bShow && $bExeShow});
|
||||
|
||||
if ($bShow && $bExeShow)
|
||||
{
|
||||
# Add continuation chars and proper spacing
|
||||
$strCommand =~ s/\n/\n /smg;
|
||||
|
||||
$oExecuteBodyElement->
|
||||
addNew(HTML_PRE, "execute-body-cmd",
|
||||
{strContent => $strCommand, bPre => true});
|
||||
|
||||
my $strHighLight = $self->{oManifest}->variableReplace($oExecute->fieldGet('exe-highlight', false));
|
||||
my $bHighLightFound = false;
|
||||
|
||||
if (defined($strOutput))
|
||||
{
|
||||
my $bHighLightOld;
|
||||
my $strHighLightOutput;
|
||||
|
||||
if ($oExecute->fieldTest('exe-highlight-type', 'error'))
|
||||
{
|
||||
$bExeExpectedError = true;
|
||||
}
|
||||
|
||||
foreach my $strLine (split("\n", $strOutput))
|
||||
{
|
||||
my $bHighLight = defined($strHighLight) && $strLine =~ /$strHighLight/;
|
||||
|
||||
if (defined($bHighLightOld) && $bHighLight != $bHighLightOld)
|
||||
{
|
||||
$oExecuteBodyElement->
|
||||
addNew(HTML_PRE, 'execute-body-output' .
|
||||
($bHighLightOld ? '-highlight' . ($bExeExpectedError ? '-error' : '') : ''),
|
||||
{strContent => $strHighLightOutput, bPre => true});
|
||||
|
||||
undef($strHighLightOutput);
|
||||
}
|
||||
|
||||
$strHighLightOutput .= (defined($strHighLightOutput) ? "\n" : '') . $strLine;
|
||||
$bHighLightOld = $bHighLight;
|
||||
|
||||
$bHighLightFound = $bHighLightFound ? true : $bHighLight ? true : false;
|
||||
}
|
||||
|
||||
if (defined($bHighLightOld))
|
||||
{
|
||||
$oExecuteBodyElement->
|
||||
addNew(HTML_PRE, 'execute-body-output' .
|
||||
($bHighLightOld ? '-highlight' . ($bExeExpectedError ? '-error' : '') : ''),
|
||||
{strContent => $strHighLightOutput, bPre => true});
|
||||
}
|
||||
|
||||
$bFirst = true;
|
||||
}
|
||||
|
||||
if ($self->{bExe} && $self->isRequired($oSection) && defined($strHighLight) && !$bHighLightFound)
|
||||
{
|
||||
confess &log(ERROR, "unable to find a match for highlight: ${strHighLight}");
|
||||
}
|
||||
}
|
||||
|
||||
$bFirst = false;
|
||||
}
|
||||
}
|
||||
# Add code block
|
||||
elsif ($oChild->nameGet() eq 'code-block')
|
||||
{
|
||||
my $strValue = $oChild->valueGet();
|
||||
|
||||
# Trim linefeeds from the beginning and all whitespace from the end
|
||||
$strValue =~ s/^\n+|\s+$//g;
|
||||
|
||||
# Find the line with the fewest leading spaces
|
||||
my $iSpaceMin = undef;
|
||||
|
||||
foreach my $strLine (split("\n", $strValue))
|
||||
{
|
||||
$strLine =~ s/\s+$//;
|
||||
|
||||
my $iSpaceMinTemp = length($strLine) - length(trim($strLine));
|
||||
|
||||
if (!defined($iSpaceMin) || $iSpaceMinTemp < $iSpaceMin)
|
||||
{
|
||||
$iSpaceMin = $iSpaceMinTemp;
|
||||
}
|
||||
}
|
||||
|
||||
# Replace the leading spaces
|
||||
$strValue =~ s/^( ){$iSpaceMin}//smg;
|
||||
|
||||
$oSectionBodyElement->addNew(
|
||||
HTML_PRE, 'code-block', {strContent => $strValue, bPre => true});
|
||||
}
|
||||
# Add table
|
||||
elsif ($oChild->nameGet() eq 'table')
|
||||
{
|
||||
my $oTableTitle;
|
||||
if ($oChild->nodeTest('title'))
|
||||
{
|
||||
$oTableTitle = $oChild->nodeGet('title');
|
||||
}
|
||||
|
||||
my $oTableElement = $oSectionBodyElement->addNew(HTML_TABLE, 'table');
|
||||
my @oyColumn;
|
||||
|
||||
# If there is a title element then add it as the caption for the table
|
||||
if (defined($oTableTitle))
|
||||
{
|
||||
# Print the label (e.g. Table 1:) in front of the title if one exists
|
||||
my $strTableTitle = $oTableTitle->paramTest('label') ?
|
||||
($oTableTitle->paramGet('label') . ': '. $self->processText($oTableTitle->textGet())) :
|
||||
$self->processText($oTableTitle->textGet());
|
||||
|
||||
$oTableElement->addNew(HTML_TABLE_CAPTION, 'table-caption', {strContent => $strTableTitle});
|
||||
}
|
||||
|
||||
# Build the header
|
||||
if ($oChild->nodeTest('table-header'))
|
||||
{
|
||||
my $oHeader = $oChild->nodeGet('table-header');
|
||||
@oyColumn = $oHeader->nodeList('table-column');
|
||||
|
||||
my $oHeaderRowElement = $oTableElement->addNew(HTML_TR, 'table-header-row');
|
||||
|
||||
foreach my $oColumn (@oyColumn)
|
||||
{
|
||||
# Each column can have different alignment properties - if not set, then default to align left
|
||||
my $strAlign = $oColumn->paramGet("align", false, 'left');
|
||||
my $bFill = $oColumn->paramTest('fill', 'y');
|
||||
|
||||
$oHeaderRowElement->addNew(
|
||||
HTML_TH,
|
||||
"table-header-${strAlign}" . ($bFill ? " table-header-fill" : ""),
|
||||
{strContent => $self->processText($oColumn->textGet())});
|
||||
}
|
||||
}
|
||||
|
||||
# Build the rows
|
||||
foreach my $oRow ($oChild->nodeGet('table-data')->nodeList('table-row'))
|
||||
{
|
||||
my $oRowElement = $oTableElement->addNew(HTML_TR, 'table-row');
|
||||
my @oRowCellList = $oRow->nodeList('table-cell');
|
||||
|
||||
for (my $iRowCellIdx = 0; $iRowCellIdx < @oRowCellList; $iRowCellIdx++)
|
||||
{
|
||||
my $oRowCell = $oRowCellList[$iRowCellIdx];
|
||||
|
||||
# If a header row was defined, then get the column alignment, else default to left
|
||||
my $strAlign = @oyColumn > 0 ? $oyColumn[$iRowCellIdx]->paramGet("align", false, 'left') : 'left';
|
||||
|
||||
$oRowElement->addNew(
|
||||
HTML_TD, "table-data-${strAlign}", {strContent => $self->processText($oRowCell->textGet())});
|
||||
}
|
||||
}
|
||||
}
|
||||
# Add descriptive text
|
||||
elsif ($oChild->nameGet() eq 'p')
|
||||
{
|
||||
$oSectionBodyElement->
|
||||
addNew(HTML_DIV, 'section-body-text',
|
||||
{strContent => $self->processText($oChild->textGet())});
|
||||
}
|
||||
# Add option descriptive text
|
||||
elsif ($oChild->nameGet() eq 'option-description')
|
||||
{
|
||||
my $strOption = $oChild->paramGet("key");
|
||||
my $oDescription = ${$self->{oReference}->{oConfigHash}}{&CONFIG_HELP_OPTION}{$strOption}{&CONFIG_HELP_DESCRIPTION};
|
||||
|
||||
if (!defined($oDescription))
|
||||
{
|
||||
confess &log(ERROR, "unable to find ${strOption} option in sections - try adding option?");
|
||||
}
|
||||
|
||||
$oSectionBodyElement->
|
||||
addNew(HTML_DIV, 'section-body-text',
|
||||
{strContent => $self->processText($oDescription)});
|
||||
}
|
||||
# Add cmd descriptive text
|
||||
elsif ($oChild->nameGet() eq 'cmd-description')
|
||||
{
|
||||
my $strCommand = $oChild->paramGet("key");
|
||||
my $oDescription = ${$self->{oReference}->{oConfigHash}}{&CONFIG_HELP_COMMAND}{$strCommand}{&CONFIG_HELP_DESCRIPTION};
|
||||
|
||||
if (!defined($oDescription))
|
||||
{
|
||||
confess &log(ERROR, "unable to find ${strCommand} command in sections - try adding command?");
|
||||
}
|
||||
|
||||
$oSectionBodyElement->
|
||||
addNew(HTML_DIV, 'section-body-text',
|
||||
{strContent => $self->processText($oDescription)});
|
||||
}
|
||||
# Add/remove backrest config options
|
||||
elsif ($oChild->nameGet() eq 'backrest-config')
|
||||
{
|
||||
my $oConfigElement = $self->backrestConfigProcess($oSection, $oChild, $iDepth + 3);
|
||||
|
||||
if (defined($oConfigElement))
|
||||
{
|
||||
$oSectionBodyElement->add($oConfigElement);
|
||||
}
|
||||
}
|
||||
# Add/remove postgres config options
|
||||
elsif ($oChild->nameGet() eq 'postgres-config')
|
||||
{
|
||||
my $oConfigElement = $self->postgresConfigProcess($oSection, $oChild, $iDepth + 3);
|
||||
|
||||
if (defined($oConfigElement))
|
||||
{
|
||||
$oSectionBodyElement->add($oConfigElement);
|
||||
}
|
||||
}
|
||||
# Add a list
|
||||
elsif ($oChild->nameGet() eq 'list')
|
||||
{
|
||||
my $oList = $oSectionBodyElement->addNew(HTML_UL, 'list-unordered');
|
||||
|
||||
foreach my $oListItem ($oChild->nodeList())
|
||||
{
|
||||
$oList->addNew(HTML_LI, 'list-unordered', {strContent => $self->processText($oListItem->textGet())});
|
||||
}
|
||||
}
|
||||
# Add a subtitle
|
||||
elsif ($oChild->nameGet() eq 'subtitle')
|
||||
{
|
||||
$oSectionBodyElement->
|
||||
addNew(HTML_DIV, "section${iDepth}-subtitle",
|
||||
{strContent => $self->processText($oChild->textGet())});
|
||||
}
|
||||
# Add a subsubtitle
|
||||
elsif ($oChild->nameGet() eq 'subsubtitle')
|
||||
{
|
||||
$oSectionBodyElement->
|
||||
addNew(HTML_DIV, "section${iDepth}-subsubtitle",
|
||||
{strContent => $self->processText($oChild->textGet())});
|
||||
}
|
||||
# Add a subsection
|
||||
elsif ($oChild->nameGet() eq 'section')
|
||||
{
|
||||
my ($oChildSectionElement, $oChildSectionTocElement) =
|
||||
$self->sectionProcess($oChild, $strAnchor, "${strSectionNo}.${iSectionNo}", $iDepth + 1);
|
||||
|
||||
$oSectionBodyElement->add($oChildSectionElement);
|
||||
|
||||
if (defined($oChildSectionTocElement))
|
||||
{
|
||||
$oSectionTocElement->add($oChildSectionTocElement);
|
||||
}
|
||||
|
||||
$iSectionNo++;
|
||||
}
|
||||
# Add an admonition (e.g. NOTE, WARNING, etc)
|
||||
elsif ($oChild->nameGet() eq 'admonition')
|
||||
{
|
||||
my $oAdmonition = $oSectionBodyElement->addNew(HTML_DIV, 'admonition');
|
||||
$oAdmonition->addNew(HTML_DIV, $oChild->paramGet('type'), {strContent => uc($oChild->paramGet('type')) . ": "});
|
||||
$oAdmonition->addNew(HTML_DIV, $oChild->paramGet('type') . '-text',
|
||||
{strContent => $self->processText($oChild->textGet())});
|
||||
}
|
||||
# Check if the child can be processed by a parent
|
||||
else
|
||||
{
|
||||
$self->sectionChildProcess($oSection, $oChild, $iDepth + 1);
|
||||
}
|
||||
}
|
||||
|
||||
# Return from function and log return values if any
|
||||
return logDebugReturn
|
||||
(
|
||||
$strOperation,
|
||||
{name => 'oSectionElement', value => $oSectionElement, trace => true},
|
||||
{name => 'oSectionTocElement', value => $oSection->paramTest('toc', 'n') ? undef : $oSectionTocElement, trace => true}
|
||||
);
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# backrestConfigProcess
|
||||
####################################################################################################################################
|
||||
sub backrestConfigProcess
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
# Assign function parameters, defaults, and log debug info
|
||||
my
|
||||
(
|
||||
$strOperation,
|
||||
$oSection,
|
||||
$oConfig,
|
||||
$iDepth
|
||||
) =
|
||||
logDebugParam
|
||||
(
|
||||
__PACKAGE__ . '->backrestConfigProcess', \@_,
|
||||
{name => 'oSection'},
|
||||
{name => 'oConfig'},
|
||||
{name => 'iDepth'}
|
||||
);
|
||||
|
||||
# Generate the config
|
||||
my $oConfigElement;
|
||||
my ($strFile, $strConfig, $bShow) = $self->backrestConfig($oSection, $oConfig, $iDepth);
|
||||
|
||||
if ($bShow)
|
||||
{
|
||||
my $strHostName = $self->{oManifest}->variableReplace($oConfig->paramGet('host'));
|
||||
|
||||
# Render the config
|
||||
$oConfigElement = new pgBackRestDoc::Html::DocHtmlElement(HTML_DIV, "config");
|
||||
|
||||
$oConfigElement->
|
||||
addNew(HTML_DIV, "config-title",
|
||||
{strContent => "<span class=\"host\">${strHostName}</span>:<span class=\"file\">${strFile}</span>" .
|
||||
" <b>⇒</b> " . $self->processText($oConfig->nodeGet('title')->textGet())});
|
||||
|
||||
my $oConfigBodyElement = $oConfigElement->addNew(HTML_DIV, "config-body");
|
||||
#
|
||||
# $oConfigBodyElement->
|
||||
# addNew(HTML_DIV, "config-body-title",
|
||||
# {strContent => "${strFile}:"});
|
||||
|
||||
$oConfigBodyElement->
|
||||
addNew(HTML_DIV, "config-body-output",
|
||||
{strContent => $strConfig});
|
||||
}
|
||||
|
||||
# Return from function and log return values if any
|
||||
return logDebugReturn
|
||||
(
|
||||
$strOperation,
|
||||
{name => 'oConfigElement', value => $oConfigElement, trace => true}
|
||||
);
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# postgresConfigProcess
|
||||
####################################################################################################################################
|
||||
sub postgresConfigProcess
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
# Assign function parameters, defaults, and log debug info
|
||||
my
|
||||
(
|
||||
$strOperation,
|
||||
$oSection,
|
||||
$oConfig,
|
||||
$iDepth
|
||||
) =
|
||||
logDebugParam
|
||||
(
|
||||
__PACKAGE__ . '->postgresConfigProcess', \@_,
|
||||
{name => 'oSection'},
|
||||
{name => 'oConfig'},
|
||||
{name => 'iDepth'}
|
||||
);
|
||||
|
||||
# Generate the config
|
||||
my $oConfigElement;
|
||||
my ($strFile, $strConfig, $bShow) = $self->postgresConfig($oSection, $oConfig, $iDepth);
|
||||
|
||||
if ($bShow)
|
||||
{
|
||||
# Render the config
|
||||
my $strHostName = $self->{oManifest}->variableReplace($oConfig->paramGet('host'));
|
||||
$oConfigElement = new pgBackRestDoc::Html::DocHtmlElement(HTML_DIV, "config");
|
||||
|
||||
$oConfigElement->
|
||||
addNew(HTML_DIV, "config-title",
|
||||
{strContent => "<span class=\"host\">${strHostName}</span>:<span class=\"file\">${strFile}</span>" .
|
||||
" <b>⇒</b> " . $self->processText($oConfig->nodeGet('title')->textGet())});
|
||||
|
||||
my $oConfigBodyElement = $oConfigElement->addNew(HTML_DIV, "config-body");
|
||||
|
||||
# $oConfigBodyElement->
|
||||
# addNew(HTML_DIV, "config-body-title",
|
||||
# {strContent => "append to ${strFile}:"});
|
||||
|
||||
$oConfigBodyElement->
|
||||
addNew(HTML_DIV, "config-body-output",
|
||||
{strContent => defined($strConfig) ? $strConfig : '<No PgBackRest Settings>'});
|
||||
|
||||
$oConfig->fieldSet('actual-config', $strConfig);
|
||||
}
|
||||
|
||||
# Return from function and log return values if any
|
||||
return logDebugReturn
|
||||
(
|
||||
$strOperation,
|
||||
{name => 'oConfigElement', value => $oConfigElement, trace => true}
|
||||
);
|
||||
}
|
||||
|
||||
1;
|
162
doc/lib/pgBackRestDoc/Html/DocHtmlSite.pm
Normal file
162
doc/lib/pgBackRestDoc/Html/DocHtmlSite.pm
Normal file
@ -0,0 +1,162 @@
|
||||
####################################################################################################################################
|
||||
# DOC HTML SITE MODULE
|
||||
####################################################################################################################################
|
||||
package pgBackRestDoc::Html::DocHtmlSite;
|
||||
|
||||
use strict;
|
||||
use warnings FATAL => qw(all);
|
||||
use Carp qw(confess);
|
||||
use English '-no_match_vars';
|
||||
|
||||
use Data::Dumper;
|
||||
use Exporter qw(import);
|
||||
our @EXPORT = qw();
|
||||
use File::Basename qw(dirname);
|
||||
use File::Copy;
|
||||
use POSIX qw(strftime);
|
||||
use Storable qw(dclone);
|
||||
|
||||
use pgBackRest::Version;
|
||||
|
||||
use pgBackRestTest::Common::ExecuteTest;
|
||||
|
||||
use pgBackRestDoc::Common::DocConfig;
|
||||
use pgBackRestDoc::Common::DocManifest;
|
||||
use pgBackRestDoc::Common::Exception;
|
||||
use pgBackRestDoc::Common::Log;
|
||||
use pgBackRestDoc::Common::String;
|
||||
use pgBackRestDoc::Html::DocHtmlPage;
|
||||
|
||||
####################################################################################################################################
|
||||
# CONSTRUCTOR
|
||||
####################################################################################################################################
|
||||
sub new
|
||||
{
|
||||
my $class = shift; # Class name
|
||||
|
||||
# Create the class hash
|
||||
my $self = {};
|
||||
bless $self, $class;
|
||||
|
||||
$self->{strClass} = $class;
|
||||
|
||||
# Assign function parameters, defaults, and log debug info
|
||||
(
|
||||
my $strOperation,
|
||||
$self->{oManifest},
|
||||
$self->{strXmlPath},
|
||||
$self->{strHtmlPath},
|
||||
$self->{strCssFile},
|
||||
$self->{strFaviconFile},
|
||||
$self->{strProjectLogoFile},
|
||||
$self->{bExe}
|
||||
) =
|
||||
logDebugParam
|
||||
(
|
||||
__PACKAGE__ . '->new', \@_,
|
||||
{name => 'oManifest'},
|
||||
{name => 'strXmlPath'},
|
||||
{name => 'strHtmlPath'},
|
||||
{name => 'strCssFile'},
|
||||
{name => 'strFaviconFile', required => false},
|
||||
{name => 'strProjectLogoFile', required => false},
|
||||
{name => 'bExe'}
|
||||
);
|
||||
|
||||
# Return from function and log return values if any
|
||||
return logDebugReturn
|
||||
(
|
||||
$strOperation,
|
||||
{name => 'self', value => $self}
|
||||
);
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# process
|
||||
#
|
||||
# Generate the site html
|
||||
####################################################################################################################################
|
||||
sub process
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
# Assign function parameters, defaults, and log debug info
|
||||
my $strOperation = logDebugParam(__PACKAGE__ . '->process');
|
||||
|
||||
# Get render options
|
||||
my $oRender = $self->{oManifest}->renderGet(RENDER_TYPE_HTML);
|
||||
|
||||
my $bMenu = $$oRender{&RENDER_MENU};
|
||||
my $bPretty = $$oRender{&RENDER_PRETTY};
|
||||
my $bCompact = $$oRender{&RENDER_COMPACT};
|
||||
|
||||
if (!$bCompact)
|
||||
{
|
||||
# Copy the css file
|
||||
my $strCssFileDestination = "$self->{strHtmlPath}/default.css";
|
||||
copy($self->{strCssFile}, $strCssFileDestination)
|
||||
or confess &log(ERROR, "unable to copy $self->{strCssFile} to ${strCssFileDestination}");
|
||||
|
||||
# Copy the favicon file
|
||||
if (defined($self->{strFaviconFile}))
|
||||
{
|
||||
my $strFaviconFileDestination = "$self->{strHtmlPath}/" . $self->{oManifest}->variableGet('project-favicon');
|
||||
copy($self->{strFaviconFile}, $strFaviconFileDestination)
|
||||
or confess &log(ERROR, "unable to copy $self->{strFaviconFile} to ${strFaviconFileDestination}");
|
||||
}
|
||||
|
||||
# Copy the project logo file
|
||||
if (defined($self->{strProjectLogoFile}))
|
||||
{
|
||||
my $strProjectLogoFileDestination = "$self->{strHtmlPath}/" . $self->{oManifest}->variableGet('project-logo');
|
||||
copy($self->{strProjectLogoFile}, $strProjectLogoFileDestination)
|
||||
or confess &log(ERROR, "unable to copy $self->{strProjectLogoFile} to ${strProjectLogoFileDestination}");
|
||||
}
|
||||
}
|
||||
|
||||
foreach my $strPageId ($self->{oManifest}->renderOutList(RENDER_TYPE_HTML))
|
||||
{
|
||||
&log(INFO, " render out: ${strPageId}");
|
||||
|
||||
my $strHtml;
|
||||
my $oRenderOut = $self->{oManifest}->renderOutGet(RENDER_TYPE_HTML, $strPageId);
|
||||
|
||||
eval
|
||||
{
|
||||
$strHtml = $self->{oManifest}->variableReplace(
|
||||
new pgBackRestDoc::Html::DocHtmlPage(
|
||||
$self->{oManifest}, $strPageId, $bMenu, $self->{bExe}, $bCompact,
|
||||
${$self->{oManifest}->storage()->get($self->{strCssFile})}, $bPretty)->process());
|
||||
|
||||
return true;
|
||||
}
|
||||
or do
|
||||
{
|
||||
my $oException = $@;
|
||||
|
||||
if (exceptionCode($oException) == ERROR_FILE_INVALID)
|
||||
{
|
||||
my $oRenderOut = $self->{oManifest}->renderOutGet(RENDER_TYPE_HTML, $strPageId);
|
||||
$self->{oManifest}->cacheReset($$oRenderOut{source});
|
||||
|
||||
$strHtml = $self->{oManifest}->variableReplace(
|
||||
new pgBackRestDoc::Html::DocHtmlPage(
|
||||
$self->{oManifest}, $strPageId, $bMenu, $self->{bExe}, $bCompact,
|
||||
${$self->{oManifest}->storage()->get($self->{strCssFile})}, $bPretty)->process());
|
||||
}
|
||||
else
|
||||
{
|
||||
confess $oException;
|
||||
}
|
||||
};
|
||||
|
||||
# Save the html page
|
||||
my $strFile = "$self->{strHtmlPath}/" . (defined($$oRenderOut{file}) ? $$oRenderOut{file} : "${strPageId}.html");
|
||||
$self->{oManifest}->storage()->put($strFile, $strHtml);
|
||||
}
|
||||
|
||||
# Return from function and log return values if any
|
||||
logDebugReturn($strOperation);
|
||||
}
|
||||
|
||||
1;
|
181
doc/lib/pgBackRestDoc/Latex/DocLatex.pm
Normal file
181
doc/lib/pgBackRestDoc/Latex/DocLatex.pm
Normal file
@ -0,0 +1,181 @@
|
||||
####################################################################################################################################
|
||||
# DOC LATEX MODULE
|
||||
####################################################################################################################################
|
||||
package pgBackRestDoc::Latex::DocLatex;
|
||||
|
||||
use strict;
|
||||
use warnings FATAL => qw(all);
|
||||
use Carp qw(confess);
|
||||
use English '-no_match_vars';
|
||||
|
||||
use Cwd qw(abs_path);
|
||||
use Data::Dumper;
|
||||
use Exporter qw(import);
|
||||
our @EXPORT = qw();
|
||||
use File::Basename qw(dirname basename);
|
||||
use File::Copy;
|
||||
use POSIX qw(strftime);
|
||||
use Storable qw(dclone);
|
||||
|
||||
use pgBackRest::Version;
|
||||
|
||||
use pgBackRestDoc::Common::Exception;
|
||||
use pgBackRestDoc::Common::Log;
|
||||
use pgBackRestDoc::Common::String;
|
||||
|
||||
use pgBackRestTest::Common::ExecuteTest;
|
||||
|
||||
use pgBackRestDoc::Common::DocConfig;
|
||||
use pgBackRestDoc::Common::DocManifest;
|
||||
use pgBackRestDoc::Latex::DocLatexSection;
|
||||
|
||||
####################################################################################################################################
|
||||
# CONSTRUCTOR
|
||||
####################################################################################################################################
|
||||
sub new
|
||||
{
|
||||
my $class = shift; # Class name
|
||||
|
||||
# Create the class hash
|
||||
my $self = {};
|
||||
bless $self, $class;
|
||||
|
||||
$self->{strClass} = $class;
|
||||
|
||||
# Assign function parameters, defaults, and log debug info
|
||||
(
|
||||
my $strOperation,
|
||||
$self->{oManifest},
|
||||
$self->{strXmlPath},
|
||||
$self->{strLatexPath},
|
||||
$self->{strPreambleFile},
|
||||
$self->{bExe}
|
||||
) =
|
||||
logDebugParam
|
||||
(
|
||||
__PACKAGE__ . '->new', \@_,
|
||||
{name => 'oManifest'},
|
||||
{name => 'strXmlPath'},
|
||||
{name => 'strLatexPath'},
|
||||
{name => 'strPreambleFile'},
|
||||
{name => 'bExe'}
|
||||
);
|
||||
|
||||
# Return from function and log return values if any
|
||||
return logDebugReturn
|
||||
(
|
||||
$strOperation,
|
||||
{name => 'self', value => $self}
|
||||
);
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# process
|
||||
#
|
||||
# Generate the site html
|
||||
####################################################################################################################################
|
||||
sub process
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
# Assign function parameters, defaults, and log debug info
|
||||
my $strOperation = logDebugParam(__PACKAGE__ . '->process');
|
||||
|
||||
my $oRender = $self->{oManifest}->renderGet(RENDER_TYPE_PDF);
|
||||
|
||||
my $strLogo = $self->{oManifest}->variableGet('pdf-resource-logo');
|
||||
|
||||
if (!defined($strLogo))
|
||||
{
|
||||
$strLogo = 'blank.eps';
|
||||
}
|
||||
|
||||
my ($strExt) = $strLogo =~ /(\.[^.]+)$/;
|
||||
my $strLogoPath = defined($self->{oManifest}->variableGet('pdf-resource-path')) ?
|
||||
$self->{oManifest}->variableGet('pdf-resource-path') :
|
||||
"$self->{oManifest}{strDocPath}/resource/latex/";
|
||||
|
||||
# Copy the logo
|
||||
copy($strLogoPath . $strLogo, "$self->{strLatexPath}/logo$strExt")
|
||||
or confess &log(ERROR, "unable to copy logo");
|
||||
|
||||
my $strLatex = $self->{oManifest}->variableReplace(
|
||||
${$self->{oManifest}->storage()->get($self->{strPreambleFile})}, 'latex') . "\n";
|
||||
|
||||
# ??? Temp hack for underscores in filename
|
||||
$strLatex =~ s/pgaudit\\\_doc/pgaudit\_doc/g;
|
||||
|
||||
# Process the sources in the order listed in the manifest.xml
|
||||
foreach my $strPageId (@{${$self->{oManifest}->renderGet(RENDER_TYPE_PDF)}{stryOrder}})
|
||||
{
|
||||
&log(INFO, " render out: ${strPageId}");
|
||||
|
||||
eval
|
||||
{
|
||||
my $oDocLatexSection =
|
||||
new pgBackRestDoc::Latex::DocLatexSection($self->{oManifest}, $strPageId, $self->{bExe});
|
||||
|
||||
# Save the html page
|
||||
$strLatex .= $oDocLatexSection->process();
|
||||
|
||||
return true;
|
||||
}
|
||||
or do
|
||||
{
|
||||
my $oException = $EVAL_ERROR;
|
||||
|
||||
if (exceptionCode($oException) == ERROR_FILE_INVALID)
|
||||
{
|
||||
my $oRenderOut = $self->{oManifest}->renderOutGet(RENDER_TYPE_HTML, $strPageId);
|
||||
$self->{oManifest}->cacheReset($$oRenderOut{source});
|
||||
|
||||
my $oDocLatexSection =
|
||||
new pgBackRestDoc::Latex::DocLatexSection($self->{oManifest}, $strPageId, $self->{bExe});
|
||||
|
||||
# Save the html page
|
||||
$strLatex .= $oDocLatexSection->process();
|
||||
}
|
||||
else
|
||||
{
|
||||
confess $oException;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
$strLatex .= "\n% " . ('-' x 130) . "\n% End document\n% " . ('-' x 130) . "\n\\end{document}\n";
|
||||
|
||||
# Get base name of output file to use for processing
|
||||
(my $strLatexFileBase = basename($$oRender{file})) =~ s/\.[^.]+$//;
|
||||
$strLatexFileBase = $self->{oManifest}->variableReplace($strLatexFileBase);
|
||||
|
||||
# Name of latex file to use for output and processing
|
||||
my $strLatexFileName = $self->{oManifest}->variableReplace("$self->{strLatexPath}/" . $strLatexFileBase . '.tex');
|
||||
|
||||
# Output latex and build PDF
|
||||
$self->{oManifest}->storage()->put($strLatexFileName, $strLatex);
|
||||
|
||||
executeTest("pdflatex -output-directory=$self->{strLatexPath} -shell-escape $strLatexFileName",
|
||||
{bSuppressStdErr => true});
|
||||
executeTest("pdflatex -output-directory=$self->{strLatexPath} -shell-escape $strLatexFileName",
|
||||
{bSuppressStdErr => true});
|
||||
|
||||
# Determine path of output file
|
||||
my $strLatexOutputName = $oRender->{file};
|
||||
|
||||
if ($strLatexOutputName !~ /^\//)
|
||||
{
|
||||
$strLatexOutputName = abs_path($self->{strLatexPath} . "/" . $oRender->{file});
|
||||
}
|
||||
|
||||
# Copy pdf file if is is not already in the correct place
|
||||
if ($strLatexOutputName ne "$self->{strLatexPath}/" . $strLatexFileBase . '.pdf')
|
||||
{
|
||||
copy("$self->{strLatexPath}/" . $strLatexFileBase . '.pdf', $strLatexOutputName)
|
||||
or confess &log(ERROR, "unable to copy pdf to " . $strLatexOutputName);
|
||||
}
|
||||
|
||||
# Return from function and log return values if any
|
||||
logDebugReturn($strOperation);
|
||||
}
|
||||
|
||||
1;
|
486
doc/lib/pgBackRestDoc/Latex/DocLatexSection.pm
Normal file
486
doc/lib/pgBackRestDoc/Latex/DocLatexSection.pm
Normal file
@ -0,0 +1,486 @@
|
||||
####################################################################################################################################
|
||||
# DOC LATEX SECTION MODULE
|
||||
####################################################################################################################################
|
||||
package pgBackRestDoc::Latex::DocLatexSection;
|
||||
use parent 'pgBackRestDoc::Common::DocExecute';
|
||||
|
||||
use strict;
|
||||
use warnings FATAL => qw(all);
|
||||
use Carp qw(confess);
|
||||
|
||||
use Exporter qw(import);
|
||||
our @EXPORT = qw();
|
||||
|
||||
use pgBackRestDoc::Common::DocConfig;
|
||||
use pgBackRestDoc::Common::DocManifest;
|
||||
use pgBackRestDoc::Common::Log;
|
||||
use pgBackRestDoc::Common::String;
|
||||
|
||||
####################################################################################################################################
|
||||
# CONSTRUCTOR
|
||||
####################################################################################################################################
|
||||
sub new
|
||||
{
|
||||
my $class = shift; # Class name
|
||||
|
||||
# Assign function parameters, defaults, and log debug info
|
||||
my
|
||||
(
|
||||
$strOperation,
|
||||
$oManifest,
|
||||
$strRenderOutKey,
|
||||
$bExe
|
||||
) =
|
||||
logDebugParam
|
||||
(
|
||||
__PACKAGE__ . '->new', \@_,
|
||||
{name => 'oManifest'},
|
||||
{name => 'strRenderOutKey'},
|
||||
{name => 'bExe'}
|
||||
);
|
||||
|
||||
# Create the class hash
|
||||
my $self = $class->SUPER::new('latex', $oManifest, $strRenderOutKey, $bExe);
|
||||
bless $self, $class;
|
||||
|
||||
# Return from function and log return values if any
|
||||
return logDebugReturn
|
||||
(
|
||||
$strOperation,
|
||||
{name => 'self', value => $self}
|
||||
);
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# process
|
||||
#
|
||||
# Generate the site html
|
||||
####################################################################################################################################
|
||||
sub process
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
# Assign function parameters, defaults, and log debug info
|
||||
my $strOperation = logDebugParam(__PACKAGE__ . '->process');
|
||||
|
||||
# Working variables
|
||||
my $oPage = $self->{oDoc};
|
||||
my $strLatex;
|
||||
|
||||
# Initialize page
|
||||
my $strTitle = $oPage->paramGet('title');
|
||||
my $strSubTitle = $oPage->paramGet('subtitle', false);
|
||||
|
||||
# Render sections
|
||||
foreach my $oSection ($oPage->nodeList('section'))
|
||||
{
|
||||
$strLatex .= (defined($strLatex) ? "\n" : '') . $self->sectionProcess($oSection, undef, 1);
|
||||
}
|
||||
|
||||
# Return from function and log return values if any
|
||||
return logDebugReturn
|
||||
(
|
||||
$strOperation,
|
||||
{name => 'strHtml', value => $strLatex, trace => true}
|
||||
);
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# sectionProcess
|
||||
####################################################################################################################################
|
||||
sub sectionProcess
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
# Assign function parameters, defaults, and log debug info
|
||||
my
|
||||
(
|
||||
$strOperation,
|
||||
$oSection,
|
||||
$strSection,
|
||||
$iDepth
|
||||
) =
|
||||
logDebugParam
|
||||
(
|
||||
__PACKAGE__ . '->sectionRender', \@_,
|
||||
{name => 'oSection'},
|
||||
{name => 'strSection', required => false},
|
||||
{name => 'iDepth'}
|
||||
);
|
||||
|
||||
if ($oSection->paramGet('log'))
|
||||
{
|
||||
&log(INFO, (' ' x ($iDepth + 1)) . 'process section: ' . $oSection->paramGet('path'));
|
||||
}
|
||||
|
||||
# Create section type
|
||||
my $strSectionTitle = $self->processText($oSection->nodeGet('title')->textGet());
|
||||
$strSection .= (defined($strSection) ? ', ' : '') . "'${strSectionTitle}' " . ('Sub' x ($iDepth - 1)) . "Section";
|
||||
|
||||
# Create section comment
|
||||
my $strLatex =
|
||||
"% ${strSection}\n% " . ('-' x 130) . "\n";
|
||||
|
||||
# Exclude from table of contents if requested
|
||||
if ($iDepth <= 3 && $oSection->paramTest('toc', 'n'))
|
||||
{
|
||||
$strLatex .= '\\addtocontents{toc}{\\protect\\setcounter{tocdepth}{' . ($iDepth - 1) . "}}\n";
|
||||
}
|
||||
|
||||
# Create section name
|
||||
$strLatex .= '\\';
|
||||
|
||||
if ($iDepth <= 3)
|
||||
{
|
||||
$strLatex .= ($iDepth > 1 ? ('sub' x ($iDepth - 1)) : '') . "section";
|
||||
}
|
||||
elsif ($iDepth == 4)
|
||||
{
|
||||
$strLatex .= 'paragraph';
|
||||
}
|
||||
else
|
||||
{
|
||||
confess &log(ASSERT, "section depth of ${iDepth} exceeds maximum");
|
||||
}
|
||||
|
||||
$strLatex .= "\{${strSectionTitle}\}\\label{" . $oSection->paramGet('path', false) . "}\n";
|
||||
|
||||
# Reset table of contents numbering if the section was excluded
|
||||
if ($iDepth <= 3 && $oSection->paramTest('toc', 'n'))
|
||||
{
|
||||
$strLatex .= '\\addtocontents{toc}{\\protect\\setcounter{tocdepth}{' . $iDepth . "}}\n";
|
||||
}
|
||||
|
||||
foreach my $oChild ($oSection->nodeList())
|
||||
{
|
||||
&log(DEBUG, (' ' x ($iDepth + 2)) . 'process child ' . $oChild->nameGet());
|
||||
|
||||
# Execute a command
|
||||
if ($oChild->nameGet() eq 'execute-list')
|
||||
{
|
||||
my $bShow = $oChild->paramTest('show', 'n') ? false : true;
|
||||
my $strHostName = $self->{oManifest}->variableReplace($oChild->paramGet('host'));
|
||||
|
||||
if ($bShow)
|
||||
{
|
||||
$strLatex .=
|
||||
"\n\\begin\{lstlisting\}[title=\{\\textnormal{\\textbf\{${strHostName}}} --- " .
|
||||
$self->processText($oChild->nodeGet('title')->textGet()) . "}]\n";
|
||||
}
|
||||
|
||||
foreach my $oExecute ($oChild->nodeList('execute'))
|
||||
{
|
||||
my $bExeShow = !$oExecute->paramTest('show', 'n');
|
||||
my ($strCommand, $strOutput) = $self->execute(
|
||||
$oSection, $self->{oManifest}->variableReplace($oChild->paramGet('host')), $oExecute,
|
||||
{iIndent => $iDepth + 3, bShow => $bShow && $bExeShow});
|
||||
|
||||
if ($bShow && $bExeShow)
|
||||
{
|
||||
$strLatex .= "${strCommand}\n";
|
||||
|
||||
if (defined($strOutput))
|
||||
{
|
||||
$strLatex .= "\nOutput:\n\n${strOutput}\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($bShow)
|
||||
{
|
||||
$strLatex .=
|
||||
"\\end{lstlisting}\n";
|
||||
}
|
||||
}
|
||||
# Add code block
|
||||
elsif ($oChild->nameGet() eq 'code-block')
|
||||
{
|
||||
my $strTitle = $self->{oManifest}->variableReplace($oChild->paramGet("title", false), 'latex');
|
||||
|
||||
if (defined($strTitle) && $strTitle eq '')
|
||||
{
|
||||
undef($strTitle)
|
||||
}
|
||||
|
||||
# Begin the code listing
|
||||
if (!defined($strTitle))
|
||||
{
|
||||
$strLatex .=
|
||||
"\\vspace{.75em}\n";
|
||||
}
|
||||
|
||||
$strLatex .=
|
||||
"\\begin\{lstlisting\}";
|
||||
|
||||
# Add the title if one is provided
|
||||
if (defined($strTitle))
|
||||
{
|
||||
$strLatex .= "[title=\{${strTitle}:\}]";
|
||||
}
|
||||
|
||||
# End the code listing
|
||||
$strLatex .=
|
||||
"\n" .
|
||||
trim($oChild->valueGet()) . "\n" .
|
||||
"\\end{lstlisting}\n";
|
||||
}
|
||||
# Add table
|
||||
elsif ($oChild->nameGet() eq 'table')
|
||||
{
|
||||
my $oHeader;
|
||||
my @oyColumn;
|
||||
|
||||
if ($oChild->nodeTest('table-header'))
|
||||
{
|
||||
$oHeader = $oChild->nodeGet('table-header');
|
||||
@oyColumn = $oHeader->nodeList('table-column');
|
||||
}
|
||||
|
||||
my $strWidth =
|
||||
'{' . (defined($oHeader) && $oHeader->paramTest('width') ? ($oHeader->paramGet('width') / 100) .
|
||||
'\textwidth' : '\textwidth') . '}';
|
||||
|
||||
# Build the table
|
||||
$strLatex .= "\\vspace{1em}\\newline\n\\begin{table}\n\\begin{tabularx}${strWidth}{|";
|
||||
|
||||
# Build the table header
|
||||
foreach my $oColumn (@oyColumn)
|
||||
{
|
||||
my $strAlignCode;
|
||||
my $strAlign = $oColumn->paramGet("align", false);
|
||||
|
||||
# If fill is specified then use X or the custom designed alignments in the preamble to fill and justify the columns.
|
||||
if ($oColumn->paramTest('fill') && $oColumn->paramGet('fill', false) eq 'y')
|
||||
{
|
||||
if (!defined($strAlign) || $strAlign eq 'left')
|
||||
{
|
||||
$strAlignCode = 'X';
|
||||
}
|
||||
elsif ($strAlign eq 'right')
|
||||
{
|
||||
$strAlignCode = 'R';
|
||||
}
|
||||
elsif ($strAlign eq 'center')
|
||||
{
|
||||
$strAlignCode = 'C';
|
||||
}
|
||||
else
|
||||
{
|
||||
confess &log(ERROR, "align '${strAlign}' not valid when fill=y");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!defined($strAlign) || $strAlign eq 'left')
|
||||
{
|
||||
$strAlignCode = 'l';
|
||||
}
|
||||
elsif ($strAlign eq 'center')
|
||||
{
|
||||
$strAlignCode = 'c';
|
||||
}
|
||||
elsif ($strAlign eq 'right')
|
||||
{
|
||||
$strAlignCode = 'r';
|
||||
}
|
||||
else
|
||||
{
|
||||
confess &log(ERROR, "align '${strAlign}' not valid");
|
||||
}
|
||||
}
|
||||
|
||||
# $strLatex .= 'p{' . $oColumn->paramGet("width") . '} | ';
|
||||
$strLatex .= $strAlignCode . ' | ';
|
||||
}
|
||||
|
||||
# If table-header not provided then default the column alignment and fill by using the number of columns in the 1st row
|
||||
if (!defined($oHeader))
|
||||
{
|
||||
my @oyRow = $oChild->nodeGet('table-data')->nodeList('table-row');
|
||||
foreach my $oRowCell ($oyRow[0]->nodeList('table-cell'))
|
||||
{
|
||||
$strLatex .= 'X|';
|
||||
}
|
||||
}
|
||||
|
||||
$strLatex .= "}\n";
|
||||
|
||||
my $strLine;
|
||||
|
||||
if (defined($oHeader))
|
||||
{
|
||||
$strLatex .= "\\hline";
|
||||
$strLatex .= "\\rowcolor{ltgray}\n";
|
||||
|
||||
foreach my $oColumn (@oyColumn)
|
||||
{
|
||||
$strLine .= (defined($strLine) ? ' & ' : '') . '\textbf{' . $self->processText($oColumn->textGet()) . '}';
|
||||
}
|
||||
|
||||
$strLatex .= "${strLine}\\\\";
|
||||
}
|
||||
|
||||
# Build the rows
|
||||
foreach my $oRow ($oChild->nodeGet('table-data')->nodeList('table-row'))
|
||||
{
|
||||
$strLatex .= "\\hline\n";
|
||||
undef($strLine);
|
||||
|
||||
foreach my $oRowCell ($oRow->nodeList('table-cell'))
|
||||
{
|
||||
$strLine .= (defined($strLine) ? ' & ' : '') . $self->processText($oRowCell->textGet());
|
||||
}
|
||||
|
||||
$strLatex .= "${strLine}\\\\";
|
||||
}
|
||||
|
||||
$strLatex .= "\\hline\n\\end{tabularx}\n";
|
||||
|
||||
# If there is a title for the table, add it. Ignore the label since LaTex will automatically generate numbered labels.
|
||||
# e.g. Table 1:
|
||||
if ($oChild->nodeGet("title", false))
|
||||
{
|
||||
$strLatex .= "\\caption{" . $self->processText($oChild->nodeGet("title")->textGet()) . "}\n";
|
||||
}
|
||||
|
||||
$strLatex .= "\\end{table}\n";
|
||||
}
|
||||
# Add descriptive text
|
||||
elsif ($oChild->nameGet() eq 'p')
|
||||
{
|
||||
$strLatex .= "\n" . $self->processText($oChild->textGet()) . "\n";
|
||||
}
|
||||
# Add option descriptive text
|
||||
elsif ($oChild->nameGet() eq 'option-description')
|
||||
{
|
||||
my $strOption = $oChild->paramGet("key");
|
||||
my $oDescription = ${$self->{oReference}->{oConfigHash}}{&CONFIG_HELP_OPTION}{$strOption}{&CONFIG_HELP_DESCRIPTION};
|
||||
|
||||
if (!defined($oDescription))
|
||||
{
|
||||
confess &log(ERROR, "unable to find ${strOption} option in sections - try adding option?");
|
||||
}
|
||||
|
||||
$strLatex .= "\n" . $self->processText($oDescription) . "\n";
|
||||
}
|
||||
# Add cmd descriptive text
|
||||
elsif ($oChild->nameGet() eq 'cmd-description')
|
||||
{
|
||||
my $strCommand = $oChild->paramGet("key");
|
||||
my $oDescription = ${$self->{oReference}->{oConfigHash}}{&CONFIG_HELP_COMMAND}{$strCommand}{&CONFIG_HELP_DESCRIPTION};
|
||||
|
||||
if (!defined($oDescription))
|
||||
{
|
||||
confess &log(ERROR, "unable to find ${strCommand} command in sections - try adding command?");
|
||||
}
|
||||
|
||||
$strLatex .= "\n" . $self->processText($oDescription) . "\n";
|
||||
}
|
||||
# Add a list
|
||||
elsif ($oChild->nameGet() eq 'list')
|
||||
{
|
||||
$strLatex .= "\n\\begin{itemize}";
|
||||
|
||||
foreach my $oListItem ($oChild->nodeList())
|
||||
{
|
||||
$strLatex .= "\n \\item " . $self->processText($oListItem->textGet());
|
||||
}
|
||||
|
||||
$strLatex .= "\n\\end{itemize}";
|
||||
}
|
||||
# Add/remove config options
|
||||
elsif ($oChild->nameGet() eq 'backrest-config' || $oChild->nameGet() eq 'postgres-config')
|
||||
{
|
||||
$strLatex .= $self->configProcess($oSection, $oChild, $iDepth + 3);
|
||||
}
|
||||
# Add a subsection
|
||||
elsif ($oChild->nameGet() eq 'section')
|
||||
{
|
||||
$strLatex .= "\n" . $self->sectionProcess($oChild, $strSection, $iDepth + 1);
|
||||
}
|
||||
# Add an admonition (e.g. NOTE, WARNING, etc)
|
||||
elsif ($oChild->nameGet() eq 'admonition')
|
||||
{
|
||||
$strLatex .= "\n\\begin{leftbar}";
|
||||
$strLatex .= "\n\\textit{\\textbf{" . uc($oChild->paramGet('type')) . ": }";
|
||||
$strLatex .= $self->processText($oChild->textGet()) . "}";
|
||||
$strLatex .= "\n\\end{leftbar}\n";
|
||||
}
|
||||
# Check if the child can be processed by a parent
|
||||
else
|
||||
{
|
||||
$self->sectionChildProcess($oSection, $oChild, $iDepth + 1);
|
||||
}
|
||||
}
|
||||
|
||||
# Return from function and log return values if any
|
||||
return logDebugReturn
|
||||
(
|
||||
$strOperation,
|
||||
{name => 'strSection', value => $strLatex, trace => true}
|
||||
);
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# configProcess
|
||||
####################################################################################################################################
|
||||
sub configProcess
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
# Assign function parameters, defaults, and log debug info
|
||||
my
|
||||
(
|
||||
$strOperation,
|
||||
$oSection,
|
||||
$oConfig,
|
||||
$iDepth
|
||||
) =
|
||||
logDebugParam
|
||||
(
|
||||
__PACKAGE__ . '->configProcess', \@_,
|
||||
{name => 'oSection'},
|
||||
{name => 'oConfig'},
|
||||
{name => 'iDepth'}
|
||||
);
|
||||
|
||||
# Working variables
|
||||
my $strLatex = '';
|
||||
my $strFile;
|
||||
my $strConfig;
|
||||
my $bShow = true;
|
||||
|
||||
# Generate the config
|
||||
if ($oConfig->nameGet() eq 'backrest-config')
|
||||
{
|
||||
($strFile, $strConfig, $bShow) = $self->backrestConfig($oSection, $oConfig, $iDepth);
|
||||
}
|
||||
else
|
||||
{
|
||||
($strFile, $strConfig, $bShow) = $self->postgresConfig($oSection, $oConfig, $iDepth);
|
||||
}
|
||||
|
||||
if ($bShow)
|
||||
{
|
||||
my $strHostName = $self->{oManifest}->variableReplace($oConfig->paramGet('host'));
|
||||
|
||||
# Replace _ in filename
|
||||
$strFile = $self->variableReplace($strFile);
|
||||
|
||||
# Render the config
|
||||
$strLatex =
|
||||
"\n\\begin\{lstlisting\}[title=\{\\textnormal{\\textbf\{${strHostName}}}:\\textnormal{\\texttt\{${strFile}}} --- " .
|
||||
$self->processText($oConfig->nodeGet('title')->textGet()) . "}]\n" .
|
||||
(defined($strConfig) ? $strConfig : '') .
|
||||
"\\end{lstlisting}\n";
|
||||
}
|
||||
|
||||
# Return from function and log return values if any
|
||||
return logDebugReturn
|
||||
(
|
||||
$strOperation,
|
||||
{name => 'strConfig', value => $strLatex, trace => true}
|
||||
);
|
||||
}
|
||||
|
||||
1;
|
95
doc/lib/pgBackRestDoc/Markdown/DocMarkdown.pm
Normal file
95
doc/lib/pgBackRestDoc/Markdown/DocMarkdown.pm
Normal file
@ -0,0 +1,95 @@
|
||||
####################################################################################################################################
|
||||
# DOC MARKDOWN MODULE
|
||||
####################################################################################################################################
|
||||
package pgBackRestDoc::Markdown::DocMarkdown;
|
||||
|
||||
use strict;
|
||||
use warnings FATAL => qw(all);
|
||||
use Carp qw(confess);
|
||||
|
||||
use Data::Dumper;
|
||||
use Exporter qw(import);
|
||||
our @EXPORT = qw();
|
||||
use File::Basename qw(dirname);
|
||||
use File::Copy;
|
||||
use POSIX qw(strftime);
|
||||
use Storable qw(dclone);
|
||||
|
||||
use pgBackRest::Version;
|
||||
|
||||
use pgBackRestTest::Common::ExecuteTest;
|
||||
|
||||
use pgBackRestDoc::Common::DocConfig;
|
||||
use pgBackRestDoc::Common::DocManifest;
|
||||
use pgBackRestDoc::Common::Log;
|
||||
use pgBackRestDoc::Common::String;
|
||||
use pgBackRestDoc::Markdown::DocMarkdownRender;
|
||||
|
||||
####################################################################################################################################
|
||||
# CONSTRUCTOR
|
||||
####################################################################################################################################
|
||||
sub new
|
||||
{
|
||||
my $class = shift; # Class name
|
||||
|
||||
# Create the class hash
|
||||
my $self = {};
|
||||
bless $self, $class;
|
||||
|
||||
$self->{strClass} = $class;
|
||||
|
||||
# Assign function parameters, defaults, and log debug info
|
||||
(
|
||||
my $strOperation,
|
||||
$self->{oManifest},
|
||||
$self->{strXmlPath},
|
||||
$self->{strMarkdownPath},
|
||||
$self->{bExe}
|
||||
) =
|
||||
logDebugParam
|
||||
(
|
||||
__PACKAGE__ . '->new', \@_,
|
||||
{name => 'oManifest'},
|
||||
{name => 'strXmlPath'},
|
||||
{name => 'strMarkdownPath'},
|
||||
{name => 'bExe'}
|
||||
);
|
||||
|
||||
# Return from function and log return values if any
|
||||
return logDebugReturn
|
||||
(
|
||||
$strOperation,
|
||||
{name => 'self', value => $self}
|
||||
);
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# process
|
||||
#
|
||||
# Generate the site html
|
||||
####################################################################################################################################
|
||||
sub process
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
# Assign function parameters, defaults, and log debug info
|
||||
my $strOperation = logDebugParam(__PACKAGE__ . '->process');
|
||||
|
||||
foreach my $strRenderOutId ($self->{oManifest}->renderOutList(RENDER_TYPE_MARKDOWN))
|
||||
{
|
||||
my $oRenderOut = $self->{oManifest}->renderOutGet(RENDER_TYPE_MARKDOWN, $strRenderOutId);
|
||||
my $strFile = "$self->{strMarkdownPath}/" . (defined($$oRenderOut{file}) ? $$oRenderOut{file} : "${strRenderOutId}.md");
|
||||
|
||||
&log(INFO, " render out: ${strRenderOutId}");
|
||||
|
||||
# Save the html page
|
||||
$self->{oManifest}->storage()->put(
|
||||
$strFile, $self->{oManifest}->variableReplace((new pgBackRestDoc::Markdown::DocMarkdownRender($self->{oManifest},
|
||||
$strRenderOutId, $self->{bExe}))->process()));
|
||||
}
|
||||
|
||||
# Return from function and log return values if any
|
||||
logDebugReturn($strOperation);
|
||||
}
|
||||
|
||||
1;
|
568
doc/lib/pgBackRestDoc/Markdown/DocMarkdownRender.pm
Normal file
568
doc/lib/pgBackRestDoc/Markdown/DocMarkdownRender.pm
Normal file
@ -0,0 +1,568 @@
|
||||
####################################################################################################################################
|
||||
# DOC MARKDOWN RENDER MODULE
|
||||
####################################################################################################################################
|
||||
package pgBackRestDoc::Markdown::DocMarkdownRender;
|
||||
use parent 'pgBackRestDoc::Common::DocExecute';
|
||||
|
||||
use strict;
|
||||
use warnings FATAL => qw(all);
|
||||
use Carp qw(confess);
|
||||
|
||||
use Data::Dumper;
|
||||
use Exporter qw(import);
|
||||
our @EXPORT = qw();
|
||||
use File::Basename qw(dirname);
|
||||
use File::Copy;
|
||||
use Storable qw(dclone);
|
||||
|
||||
use pgBackRestDoc::Common::DocConfig;
|
||||
use pgBackRestDoc::Common::DocManifest;
|
||||
use pgBackRestDoc::Common::Log;
|
||||
use pgBackRestDoc::Common::String;
|
||||
|
||||
####################################################################################################################################
|
||||
# CONSTRUCTOR
|
||||
####################################################################################################################################
|
||||
sub new
|
||||
{
|
||||
my $class = shift; # Class name
|
||||
|
||||
# Assign function parameters, defaults, and log debug info
|
||||
my
|
||||
(
|
||||
$strOperation,
|
||||
$oManifest,
|
||||
$strRenderOutKey,
|
||||
$bExe
|
||||
) =
|
||||
logDebugParam
|
||||
(
|
||||
__PACKAGE__ . '->new', \@_,
|
||||
{name => 'oManifest'},
|
||||
{name => 'strRenderOutKey'},
|
||||
{name => 'bExe'}
|
||||
);
|
||||
|
||||
# Create the class hash
|
||||
my $self = $class->SUPER::new(RENDER_TYPE_MARKDOWN, $oManifest, $strRenderOutKey, $bExe);
|
||||
bless $self, $class;
|
||||
|
||||
# Return from function and log return values if any
|
||||
return logDebugReturn
|
||||
(
|
||||
$strOperation,
|
||||
{name => 'self', value => $self}
|
||||
);
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# process
|
||||
#
|
||||
# Generate the site html
|
||||
####################################################################################################################################
|
||||
sub process
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
# Assign function parameters, defaults, and log debug info
|
||||
my $strOperation = logDebugParam(__PACKAGE__ . '->process');
|
||||
|
||||
# Working variables
|
||||
my $oPage = $self->{oDoc};
|
||||
|
||||
# Initialize page
|
||||
my $strMarkdown = "# " . $oPage->paramGet('title');
|
||||
|
||||
if (defined($oPage->paramGet('subtitle', false)))
|
||||
{
|
||||
$strMarkdown .= ' <br/> ' . $oPage->paramGet('subtitle') . '';
|
||||
}
|
||||
|
||||
# my $oHtmlBuilder = new pgBackRestDoc::Html::DocHtmlBuilder("{[project]} - Reliable PostgreSQL Backup",
|
||||
# $strTitle . (defined($strSubTitle) ? " - ${strSubTitle}" : ''),
|
||||
# $self->{bPretty});
|
||||
#
|
||||
# # Generate header
|
||||
# my $oPageHeader = $oHtmlBuilder->bodyGet()->addNew(HTML_DIV, 'page-header');
|
||||
#
|
||||
# $oPageHeader->
|
||||
# addNew(HTML_DIV, 'page-header-title',
|
||||
# {strContent => $strTitle});
|
||||
#
|
||||
# if (defined($strSubTitle))
|
||||
# {
|
||||
# $oPageHeader->
|
||||
# addNew(HTML_DIV, 'page-header-subtitle',
|
||||
# {strContent => $strSubTitle});
|
||||
# }
|
||||
#
|
||||
# # Generate menu
|
||||
# my $oMenuBody = $oHtmlBuilder->bodyGet()->addNew(HTML_DIV, 'page-menu')->addNew(HTML_DIV, 'menu-body');
|
||||
#
|
||||
# if ($self->{strRenderOutKey} ne 'index')
|
||||
# {
|
||||
# my $oRenderOut = $self->{oManifest}->renderOutGet(RENDER_TYPE_HTML, 'index');
|
||||
#
|
||||
# $oMenuBody->
|
||||
# addNew(HTML_DIV, 'menu')->
|
||||
# addNew(HTML_A, 'menu-link', {strContent => $$oRenderOut{menu}, strRef => '{[project-url-root]}'});
|
||||
# }
|
||||
#
|
||||
# foreach my $strRenderOutKey ($self->{oManifest}->renderOutList(RENDER_TYPE_HTML))
|
||||
# {
|
||||
# if ($strRenderOutKey ne $self->{strRenderOutKey} && $strRenderOutKey ne 'index')
|
||||
# {
|
||||
# my $oRenderOut = $self->{oManifest}->renderOutGet(RENDER_TYPE_HTML, $strRenderOutKey);
|
||||
#
|
||||
# $oMenuBody->
|
||||
# addNew(HTML_DIV, 'menu')->
|
||||
# addNew(HTML_A, 'menu-link', {strContent => $$oRenderOut{menu}, strRef => "${strRenderOutKey}.html"});
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# # Generate table of contents
|
||||
# my $oPageTocBody;
|
||||
#
|
||||
# if (!defined($oPage->paramGet('toc', false)) || $oPage->paramGet('toc') eq 'y')
|
||||
# {
|
||||
# my $oPageToc = $oHtmlBuilder->bodyGet()->addNew(HTML_DIV, 'page-toc');
|
||||
#
|
||||
# $oPageToc->
|
||||
# addNew(HTML_DIV, 'page-toc-title',
|
||||
# {strContent => "Table of Contents"});
|
||||
#
|
||||
# $oPageTocBody = $oPageToc->
|
||||
# addNew(HTML_DIV, 'page-toc-body');
|
||||
# }
|
||||
#
|
||||
# # Generate body
|
||||
# my $oPageBody = $oHtmlBuilder->bodyGet()->addNew(HTML_DIV, 'page-body');
|
||||
|
||||
# Render sections
|
||||
foreach my $oSection ($oPage->nodeList('section'))
|
||||
{
|
||||
$strMarkdown = trim($strMarkdown) . "\n\n" . $self->sectionProcess($oSection, 1);
|
||||
}
|
||||
|
||||
$strMarkdown .= "\n";
|
||||
|
||||
# Return from function and log return values if any
|
||||
return logDebugReturn
|
||||
(
|
||||
$strOperation,
|
||||
{name => 'strMarkdown', value => $strMarkdown, trace => true}
|
||||
);
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# sectionProcess
|
||||
####################################################################################################################################
|
||||
sub sectionProcess
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
# Assign function parameters, defaults, and log debug info
|
||||
my
|
||||
(
|
||||
$strOperation,
|
||||
$oSection,
|
||||
$iDepth
|
||||
) =
|
||||
logDebugParam
|
||||
(
|
||||
__PACKAGE__ . '->sectionProcess', \@_,
|
||||
{name => 'oSection'},
|
||||
{name => 'iDepth'}
|
||||
);
|
||||
|
||||
if ($oSection->paramGet('log'))
|
||||
{
|
||||
&log(INFO, (' ' x ($iDepth + 1)) . 'process section: ' . $oSection->paramGet('path'));
|
||||
}
|
||||
|
||||
if ($iDepth > 3)
|
||||
{
|
||||
confess &log(ASSERT, "section depth of ${iDepth} exceeds maximum");
|
||||
}
|
||||
|
||||
my $strMarkdown = '#' . ('#' x $iDepth) . ' ' . $self->processText($oSection->nodeGet('title')->textGet());
|
||||
|
||||
my $strLastChild = undef;
|
||||
|
||||
foreach my $oChild ($oSection->nodeList())
|
||||
{
|
||||
&log(DEBUG, (' ' x ($iDepth + 2)) . 'process child ' . $oChild->nameGet());
|
||||
|
||||
# Execute a command
|
||||
if ($oChild->nameGet() eq 'execute-list')
|
||||
{
|
||||
my $bShow = $oChild->paramTest('show', 'n') ? false : true;
|
||||
my $bFirst = true;
|
||||
my $strHostName = $self->{oManifest}->variableReplace($oChild->paramGet('host'));
|
||||
my $bOutput = false;
|
||||
|
||||
if ($bShow)
|
||||
{
|
||||
$strMarkdown .=
|
||||
"\n\n${strHostName} => " . $self->processText($oChild->nodeGet('title')->textGet()) .
|
||||
"\n```\n";
|
||||
}
|
||||
|
||||
foreach my $oExecute ($oChild->nodeList('execute'))
|
||||
{
|
||||
my $bExeShow = !$oExecute->paramTest('show', 'n');
|
||||
my $bExeExpectedError = defined($oExecute->paramGet('err-expect', false));
|
||||
|
||||
if ($bOutput)
|
||||
{
|
||||
confess &log(ERROR, "only the last command can have output");
|
||||
}
|
||||
|
||||
my ($strCommand, $strOutput) = $self->execute(
|
||||
$oSection, $strHostName, $oExecute, {iIndent => $iDepth + 3, bShow => $bShow && $bExeShow});
|
||||
|
||||
if ($bShow && $bExeShow)
|
||||
{
|
||||
# Add continuation chars and proper spacing
|
||||
$strCommand =~ s/\n/\n /smg;
|
||||
|
||||
$strMarkdown .= "${strCommand}\n";
|
||||
|
||||
my $strHighLight = $self->{oManifest}->variableReplace($oExecute->fieldGet('exe-highlight', false));
|
||||
my $bHighLightFound = false;
|
||||
|
||||
if (defined($strOutput))
|
||||
{
|
||||
$strMarkdown .= "\n--- output ---\n\n";
|
||||
|
||||
if ($oExecute->fieldTest('exe-highlight-type', 'error'))
|
||||
{
|
||||
$bExeExpectedError = true;
|
||||
}
|
||||
|
||||
foreach my $strLine (split("\n", $strOutput))
|
||||
{
|
||||
my $bHighLight = defined($strHighLight) && $strLine =~ /$strHighLight/;
|
||||
|
||||
if ($bHighLight)
|
||||
{
|
||||
$strMarkdown .= $bExeExpectedError ? "ERR" : "-->";
|
||||
}
|
||||
else
|
||||
{
|
||||
$strMarkdown .= " ";
|
||||
}
|
||||
|
||||
$strMarkdown .= " ${strLine}\n";
|
||||
|
||||
$bHighLightFound = $bHighLightFound ? true : $bHighLight ? true : false;
|
||||
}
|
||||
|
||||
$bFirst = true;
|
||||
}
|
||||
|
||||
if ($self->{bExe} && $self->isRequired($oSection) && defined($strHighLight) && !$bHighLightFound)
|
||||
{
|
||||
confess &log(ERROR, "unable to find a match for highlight: ${strHighLight}");
|
||||
}
|
||||
}
|
||||
|
||||
$bFirst = false;
|
||||
}
|
||||
|
||||
$strMarkdown .= "```";
|
||||
}
|
||||
# Add code block
|
||||
elsif ($oChild->nameGet() eq 'code-block')
|
||||
{
|
||||
if ($oChild->paramTest('title'))
|
||||
{
|
||||
if (defined($strLastChild) && $strLastChild ne 'code-block')
|
||||
{
|
||||
$strMarkdown .= "\n";
|
||||
}
|
||||
|
||||
$strMarkdown .= "\n_" . $oChild->paramGet('title') . "_:";
|
||||
}
|
||||
|
||||
$strMarkdown .= "\n```";
|
||||
|
||||
if ($oChild->paramTest('type'))
|
||||
{
|
||||
$strMarkdown .= $oChild->paramGet('type');
|
||||
}
|
||||
|
||||
$strMarkdown .= "\n" . trim($oChild->valueGet()) . "\n```";
|
||||
}
|
||||
# Add descriptive text
|
||||
elsif ($oChild->nameGet() eq 'p')
|
||||
{
|
||||
if (defined($strLastChild) && $strLastChild ne 'code-block' && $strLastChild ne 'table')
|
||||
{
|
||||
$strMarkdown .= "\n";
|
||||
}
|
||||
|
||||
$strMarkdown .= "\n" . $self->processText($oChild->textGet());
|
||||
}
|
||||
# Add option descriptive text
|
||||
elsif ($oChild->nameGet() eq 'option-description')
|
||||
{
|
||||
# my $strOption = $oChild->paramGet("key");
|
||||
# my $oDescription = ${$self->{oReference}->{oConfigHash}}{&CONFIG_HELP_OPTION}{$strOption}{&CONFIG_HELP_DESCRIPTION};
|
||||
#
|
||||
# if (!defined($oDescription))
|
||||
# {
|
||||
# confess &log(ERROR, "unable to find ${strOption} option in sections - try adding command?");
|
||||
# }
|
||||
#
|
||||
# $oSectionBodyElement->
|
||||
# addNew(HTML_DIV, 'section-body-text',
|
||||
# {strContent => $self->processText($oDescription)});
|
||||
}
|
||||
# Add/remove backrest config options
|
||||
elsif ($oChild->nameGet() eq 'backrest-config')
|
||||
{
|
||||
# my $oConfigElement = $self->backrestConfigProcess($oSection, $oChild, $iDepth + 3);
|
||||
#
|
||||
# if (defined($oConfigElement))
|
||||
# {
|
||||
# $oSectionBodyElement->add($oConfigElement);
|
||||
# }
|
||||
}
|
||||
# Add/remove postgres config options
|
||||
elsif ($oChild->nameGet() eq 'postgres-config')
|
||||
{
|
||||
# my $oConfigElement = $self->postgresConfigProcess($oSection, $oChild, $iDepth + 3);
|
||||
#
|
||||
# if (defined($oConfigElement))
|
||||
# {
|
||||
# $oSectionBodyElement->add($oConfigElement);
|
||||
# }
|
||||
}
|
||||
# Add a list
|
||||
elsif ($oChild->nameGet() eq 'list')
|
||||
{
|
||||
foreach my $oListItem ($oChild->nodeList())
|
||||
{
|
||||
$strMarkdown .= "\n\n- " . $self->processText($oListItem->textGet());
|
||||
}
|
||||
}
|
||||
# Add a subsection
|
||||
elsif ($oChild->nameGet() eq 'section')
|
||||
{
|
||||
$strMarkdown = trim($strMarkdown) . "\n\n" . $self->sectionProcess($oChild, $iDepth + 1);
|
||||
}
|
||||
elsif ($oChild->nameGet() eq 'table')
|
||||
{
|
||||
my $oTableTitle;
|
||||
if ($oChild->nodeTest('title'))
|
||||
{
|
||||
$oTableTitle = $oChild->nodeGet('title');
|
||||
}
|
||||
|
||||
my $oHeader;
|
||||
my @oyColumn;
|
||||
|
||||
if ($oChild->nodeTest('table-header'))
|
||||
{
|
||||
$oHeader = $oChild->nodeGet('table-header');
|
||||
@oyColumn = $oHeader->nodeList('table-column');
|
||||
}
|
||||
|
||||
if (defined($oTableTitle))
|
||||
{
|
||||
# Print the label (e.g. Table 1:) in front of the title if one exists
|
||||
$strMarkdown .= "\n\n**" . ($oTableTitle->paramTest('label') ?
|
||||
($oTableTitle->paramGet('label') . ': ' . $self->processText($oTableTitle->textGet())) :
|
||||
$self->processText($oTableTitle->textGet())) . "**\n\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
$strMarkdown .= "\n\n";
|
||||
}
|
||||
|
||||
my $strHeaderText = "| ";
|
||||
my $strHeaderIndicator = "| ";
|
||||
|
||||
for (my $iColCellIdx = 0; $iColCellIdx < @oyColumn; $iColCellIdx++)
|
||||
{
|
||||
my $strAlign = $oyColumn[$iColCellIdx]->paramGet("align", false, 'left');
|
||||
|
||||
$strHeaderText .= $self->processText($oyColumn[$iColCellIdx]->textGet()) .
|
||||
(($iColCellIdx < @oyColumn - 1) ? " | " : " |\n");
|
||||
$strHeaderIndicator .= ($strAlign eq 'left' || $strAlign eq 'center') ? ":---" : "---";
|
||||
$strHeaderIndicator .= ($strAlign eq 'right' || $strAlign eq 'center') ? "---:" : "";
|
||||
$strHeaderIndicator .= ($iColCellIdx < @oyColumn - 1) ? " | " : " |\n";
|
||||
}
|
||||
|
||||
# Markdown requires a table header so if not provided then create an empty header row and default the column alignment
|
||||
# left by using the number of columns in the 1st row
|
||||
if (!defined($oHeader))
|
||||
{
|
||||
my @oyRow = $oChild->nodeGet('table-data')->nodeList('table-row');
|
||||
foreach my $oRowCell ($oyRow[0]->nodeList('table-cell'))
|
||||
{
|
||||
$strHeaderText .= " | ";
|
||||
$strHeaderIndicator .= ":--- | ";
|
||||
}
|
||||
$strHeaderText .= "\n";
|
||||
$strHeaderIndicator .= "\n";
|
||||
}
|
||||
|
||||
$strMarkdown .= (defined($strHeaderText) ? $strHeaderText : '') . $strHeaderIndicator;
|
||||
|
||||
# Build the rows
|
||||
foreach my $oRow ($oChild->nodeGet('table-data')->nodeList('table-row'))
|
||||
{
|
||||
my @oRowCellList = $oRow->nodeList('table-cell');
|
||||
$strMarkdown .= "| ";
|
||||
|
||||
for (my $iRowCellIdx = 0; $iRowCellIdx < @oRowCellList; $iRowCellIdx++)
|
||||
{
|
||||
my $oRowCell = $oRowCellList[$iRowCellIdx];
|
||||
|
||||
$strMarkdown .= $self->processText($oRowCell->textGet()) .
|
||||
(($iRowCellIdx < @oRowCellList -1) ? " | " : " |\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
# Add an admonition (e.g. NOTE, WARNING, etc)
|
||||
elsif ($oChild->nameGet() eq 'admonition')
|
||||
{
|
||||
$strMarkdown .= "\n> **" . uc($oChild->paramGet('type')) . ":** " . $self->processText($oChild->textGet());
|
||||
}
|
||||
# Check if the child can be processed by a parent
|
||||
else
|
||||
{
|
||||
$self->sectionChildProcess($oSection, $oChild, $iDepth + 1);
|
||||
}
|
||||
|
||||
$strLastChild = $oChild->nameGet();
|
||||
}
|
||||
|
||||
# Return from function and log return values if any
|
||||
return logDebugReturn
|
||||
(
|
||||
$strOperation,
|
||||
{name => 'strMarkdown', value => $strMarkdown, trace => true}
|
||||
);
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# backrestConfigProcess
|
||||
####################################################################################################################################
|
||||
sub backrestConfigProcess
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
# Assign function parameters, defaults, and log debug info
|
||||
my
|
||||
(
|
||||
$strOperation,
|
||||
$oSection,
|
||||
$oConfig,
|
||||
$iDepth
|
||||
) =
|
||||
logDebugParam
|
||||
(
|
||||
__PACKAGE__ . '->backrestConfigProcess', \@_,
|
||||
{name => 'oSection'},
|
||||
{name => 'oConfig'},
|
||||
{name => 'iDepth'}
|
||||
);
|
||||
|
||||
# # Generate the config
|
||||
# my $oConfigElement;
|
||||
# my ($strFile, $strConfig, $bShow) = $self->backrestConfig($oSection, $oConfig, $iDepth);
|
||||
#
|
||||
# if ($bShow)
|
||||
# {
|
||||
# my $strHostName = $self->{oManifest}->variableReplace($oConfig->paramGet('host'));
|
||||
#
|
||||
# # Render the config
|
||||
# $oConfigElement = new pgBackRestDoc::Html::DocHtmlElement(HTML_DIV, "config");
|
||||
#
|
||||
# $oConfigElement->
|
||||
# addNew(HTML_DIV, "config-title",
|
||||
# {strContent => "<span class=\"host\">${strHostName}</span>:<span class=\"file\">${strFile}</span>" .
|
||||
# " <b>⇒</b> " . $self->processText($oConfig->nodeGet('title')->textGet())});
|
||||
#
|
||||
# my $oConfigBodyElement = $oConfigElement->addNew(HTML_DIV, "config-body");
|
||||
# #
|
||||
# # $oConfigBodyElement->
|
||||
# # addNew(HTML_DIV, "config-body-title",
|
||||
# # {strContent => "${strFile}:"});
|
||||
#
|
||||
# $oConfigBodyElement->
|
||||
# addNew(HTML_DIV, "config-body-output",
|
||||
# {strContent => $strConfig});
|
||||
# }
|
||||
#
|
||||
# # Return from function and log return values if any
|
||||
# return logDebugReturn
|
||||
# (
|
||||
# $strOperation,
|
||||
# {name => 'oConfigElement', value => $oConfigElement, trace => true}
|
||||
# );
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# postgresConfigProcess
|
||||
####################################################################################################################################
|
||||
sub postgresConfigProcess
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
# # Assign function parameters, defaults, and log debug info
|
||||
# my
|
||||
# (
|
||||
# $strOperation,
|
||||
# $oSection,
|
||||
# $oConfig,
|
||||
# $iDepth
|
||||
# ) =
|
||||
# logDebugParam
|
||||
# (
|
||||
# __PACKAGE__ . '->postgresConfigProcess', \@_,
|
||||
# {name => 'oSection'},
|
||||
# {name => 'oConfig'},
|
||||
# {name => 'iDepth'}
|
||||
# );
|
||||
#
|
||||
# # Generate the config
|
||||
# my $oConfigElement;
|
||||
# my ($strFile, $strConfig, $bShow) = $self->postgresConfig($oSection, $oConfig, $iDepth);
|
||||
#
|
||||
# if ($bShow)
|
||||
# {
|
||||
# # Render the config
|
||||
# my $strHostName = $self->{oManifest}->variableReplace($oConfig->paramGet('host'));
|
||||
# $oConfigElement = new pgBackRestDoc::Html::DocHtmlElement(HTML_DIV, "config");
|
||||
#
|
||||
# $oConfigElement->
|
||||
# addNew(HTML_DIV, "config-title",
|
||||
# {strContent => "<span class=\"host\">${strHostName}</span>:<span class=\"file\">${strFile}</span>" .
|
||||
# " <b>⇒</b> " . $self->processText($oConfig->nodeGet('title')->textGet())});
|
||||
#
|
||||
# my $oConfigBodyElement = $oConfigElement->addNew(HTML_DIV, "config-body");
|
||||
#
|
||||
# # $oConfigBodyElement->
|
||||
# # addNew(HTML_DIV, "config-body-title",
|
||||
# # {strContent => "append to ${strFile}:"});
|
||||
#
|
||||
# $oConfigBodyElement->
|
||||
# addNew(HTML_DIV, "config-body-output",
|
||||
# {strContent => defined($strConfig) ? $strConfig : '<No PgBackRest Settings>'});
|
||||
#
|
||||
# $oConfig->fieldSet('actual-config', $strConfig);
|
||||
# }
|
||||
#
|
||||
# # Return from function and log return values if any
|
||||
# return logDebugReturn
|
||||
# (
|
||||
# $strOperation,
|
||||
# {name => 'oConfigElement', value => $oConfigElement, trace => true}
|
||||
# );
|
||||
}
|
||||
|
||||
1;
|
Reference in New Issue
Block a user