1####################################################################################################################################
2# DOC MANIFEST MODULE
3####################################################################################################################################
4package pgBackRestDoc::Common::DocManifest;
5
6use strict;
7use warnings FATAL => qw(all);
8use Carp qw(confess);
9
10use Cwd qw(abs_path);
11use Exporter qw(import);
12    our @EXPORT = qw();
13use File::Basename qw(dirname);
14use JSON::PP;
15
16use pgBackRestDoc::Common::Log;
17use pgBackRestDoc::Common::String;
18
19####################################################################################################################################
20# File constants
21####################################################################################################################################
22use constant FILE_MANIFEST                                          => 'manifest.xml';
23
24####################################################################################################################################
25# Render constants
26####################################################################################################################################
27use constant RENDER                                                 => 'render';
28use constant RENDER_COMPACT                                         => 'compact';
29    push @EXPORT, qw(RENDER_COMPACT);
30use constant RENDER_FILE                                            => 'file';
31use constant RENDER_MENU                                            => 'menu';
32    push @EXPORT, qw(RENDER_MENU);
33use constant RENDER_PRETTY                                          => 'pretty';
34    push @EXPORT, qw(RENDER_PRETTY);
35
36use constant RENDER_TYPE                                            => 'type';
37use constant RENDER_TYPE_HTML                                       => 'html';
38    push @EXPORT, qw(RENDER_TYPE_HTML);
39use constant RENDER_TYPE_MARKDOWN                                   => 'markdown';
40    push @EXPORT, qw(RENDER_TYPE_MARKDOWN);
41use constant RENDER_TYPE_PDF                                        => 'pdf';
42    push @EXPORT, qw(RENDER_TYPE_PDF);
43
44####################################################################################################################################
45# CONSTRUCTOR
46####################################################################################################################################
47sub new
48{
49    my $class = shift;       # Class name
50
51    # Create the class hash
52    my $self = {};
53    bless $self, $class;
54
55    # Assign function parameters, defaults, and log debug info
56    (
57        my $strOperation,
58        $self->{oStorage},
59        $self->{stryRequire},
60        $self->{stryInclude},
61        $self->{stryExclude},
62        $self->{rhKeyVariableOverride},
63        my $rhVariableOverride,
64        $self->{strDocPath},
65        $self->{bDeploy},
66        $self->{bCacheOnly},
67        $self->{bPre},
68    ) =
69        logDebugParam
70        (
71            __PACKAGE__ . '->new', \@_,
72            {name => 'oStorage'},
73            {name => 'stryRequire'},
74            {name => 'stryInclude'},
75            {name => 'stryExclude'},
76            {name => 'rhKeyVariableOverride', required => false},
77            {name => 'rhVariableOverride', required => false},
78            {name => 'strDocPath', required => false},
79            {name => 'bDeploy', required => false},
80            {name => 'bCacheOnly', required => false},
81            {name => 'bPre', required => false, default => false},
82        );
83
84    # Set the bin path
85    $self->{strBinPath} = abs_path(dirname($0));
86
87    # Set the base path if it was not passed in
88    if (!defined($self->{strDocPath}))
89    {
90        $self->{strDocPath} = $self->{strBinPath};
91    }
92
93    # Set cache file names
94    $self->{strExeCacheLocal} = $self->{strDocPath} . "/output/exe.cache";
95    $self->{strExeCacheDeploy} = $self->{strDocPath} . "/resource/exe.cache";
96
97    # Load the manifest
98    $self->{oManifestXml} = new pgBackRestDoc::Common::Doc("$self->{strDocPath}/manifest.xml");
99
100    # Iterate the sources
101    $self->{oManifest} = {};
102
103    foreach my $oSource ($self->{oManifestXml}->nodeGet('source-list')->nodeList('source'))
104    {
105        my $oSourceHash = {};
106        my $strKey = $oSource->paramGet('key');
107        my $strSourceType = $oSource->paramGet('type', false);
108
109        logDebugMisc
110        (
111            $strOperation, 'load source',
112            {name => 'strKey', value => $strKey},
113            {name => 'strSourceType', value => $strSourceType}
114        );
115
116        # Skip sources in exclude list
117        if (grep(/^$strKey$/, @{$self->{stryExclude}}))
118        {
119            next;
120        }
121
122        $$oSourceHash{doc} = new pgBackRestDoc::Common::Doc("$self->{strDocPath}/xml/${strKey}.xml");
123
124        # Read variables from source
125        $self->variableListParse($$oSourceHash{doc}->nodeGet('variable-list', false), $rhVariableOverride);
126
127        ${$self->{oManifest}}{source}{$strKey} = $oSourceHash;
128        ${$self->{oManifest}}{source}{$strKey}{strSourceType} = $strSourceType;
129    }
130
131    # Iterate the renderers
132    foreach my $oRender ($self->{oManifestXml}->nodeGet('render-list')->nodeList('render'))
133    {
134        my $oRenderHash = {};
135        my $strType = $oRender->paramGet(RENDER_TYPE);
136
137        # Only one instance of each render type can be defined
138        if (defined(${$self->{oManifest}}{&RENDER}{$strType}))
139        {
140            confess &log(ERROR, "render ${strType} has already been defined");
141        }
142
143        # Get the file param
144        $${oRenderHash}{file} = $oRender->paramGet(RENDER_FILE, false);
145        $${oRenderHash}{&RENDER_COMPACT} = $oRender->paramGet(RENDER_COMPACT, false, 'n') eq 'y' ? true : false;
146        $${oRenderHash}{&RENDER_PRETTY} = $oRender->paramGet(RENDER_PRETTY, false, 'n') eq 'y' ? true : false;
147        $${oRenderHash}{&RENDER_MENU} = false;
148
149        logDebugMisc
150        (
151            $strOperation, '    load render',
152            {name => 'strType', value => $strType},
153            {name => 'strFile', value => $${oRenderHash}{file}}
154        );
155
156        # Error if file is set and render type is not pdf
157        if (defined($${oRenderHash}{file}) && $strType ne RENDER_TYPE_PDF)
158        {
159            confess &log(ERROR, 'only the pdf render type can have file set')
160        }
161
162        # Iterate the render sources
163        foreach my $oRenderOut ($oRender->nodeList('render-source'))
164        {
165            my $oRenderOutHash = {};
166            my $strKey = $oRenderOut->paramGet('key');
167            my $strSource = $oRenderOut->paramGet('source', false, $strKey);
168
169            # Skip sources in exclude list
170            if (grep(/^$strSource$/, @{$self->{stryExclude}}))
171            {
172                next;
173            }
174
175            # Skip sources not in include list
176            if (@{$self->{stryInclude}} > 0 && !grep(/^$strSource$/, @{$self->{stryInclude}}))
177            {
178                next;
179            }
180
181            # Preserve natural order
182            push(@{$${oRenderHash}{stryOrder}}, $strKey);
183
184            $$oRenderOutHash{source} = $strSource;
185
186            # Get the filename
187            if (defined($oRenderOut->paramGet('file', false)))
188            {
189                if ($strType eq RENDER_TYPE_HTML || $strType eq RENDER_TYPE_MARKDOWN)
190                {
191                    $$oRenderOutHash{file} = $oRenderOut->paramGet('file');
192                }
193                else
194                {
195                    confess &log(ERROR, "file is only valid with html or markdown render types");
196                }
197            }
198
199            # Get the menu caption
200            if (defined($oRenderOut->paramGet('menu', false)) && $strType ne RENDER_TYPE_HTML)
201            {
202                confess &log(ERROR, "menu is only valid with html render type");
203            }
204
205            if (defined($oRenderOut->paramGet('menu', false)))
206            {
207                $${oRenderHash}{&RENDER_MENU} = true;
208
209                if ($strType eq RENDER_TYPE_HTML)
210                {
211                    $$oRenderOutHash{menu} = $oRenderOut->paramGet('menu', false);
212                }
213                else
214                {
215                    confess &log(ERROR, 'only the html render type can have menu set');
216                }
217            }
218
219            logDebugMisc
220            (
221                $strOperation, '        load render source',
222                {name => 'strKey', value => $strKey},
223                {name => 'strSource', value => $strSource},
224                {name => 'strMenu', value => $${oRenderOutHash}{menu}}
225            );
226
227            $${oRenderHash}{out}{$strKey} = $oRenderOutHash;
228        }
229
230        ${$self->{oManifest}}{render}{$strType} = $oRenderHash;
231    }
232
233    # Set the doc path variable
234    $self->variableSet('doc-path', $self->{strDocPath});
235
236    # Read variables from manifest
237    $self->variableListParse($self->{oManifestXml}->nodeGet('variable-list', false), $rhVariableOverride);
238
239    # Return from function and log return values if any
240    return logDebugReturn
241    (
242        $strOperation,
243        {name => 'self', value => $self}
244    );
245}
246
247####################################################################################################################################
248# isBackRest
249#
250# Until all the backrest specific code can be abstracted, this function will identify when BackRest docs are being built.
251####################################################################################################################################
252sub isBackRest
253{
254    my $self = shift;
255
256    return($self->variableTest('project-exe', 'pgbackrest'));
257}
258
259####################################################################################################################################
260# Evaluate the if condition for a node
261####################################################################################################################################
262sub evaluateIf
263{
264    my $self = shift;
265    my $oNode = shift;
266
267    my $bIf = true;
268
269    # Evaluate if condition
270    if (defined($oNode->paramGet('if', false)))
271    {
272        my $strIf = $self->variableReplace($oNode->paramGet('if'));
273
274        # In this case we really do want to evaluate the contents and not treat it as a literal
275        $bIf = eval($strIf);
276
277        # Error if the eval failed
278        if ($@)
279        {
280            confess &log(ERROR, "unable to evaluate '${strIf}': $@");
281        }
282    }
283
284    return $bIf;
285}
286
287####################################################################################################################################
288# variableListParse
289#
290# Parse a variable list and store variables.
291####################################################################################################################################
292sub variableListParse
293{
294    my $self = shift;
295
296    # Assign function parameters, defaults, and log debug info
297    my
298    (
299        $strOperation,
300        $oVariableList,
301        $rhVariableOverride
302    ) =
303        logDebugParam
304        (
305            __PACKAGE__ . '->variableListParse', \@_,
306            {name => '$oVariableList', required => false},
307            {name => '$rhVariableOverride', required => false}
308        );
309
310    if (defined($oVariableList))
311    {
312        foreach my $oVariable ($oVariableList->nodeList('variable'))
313        {
314            if ($self->evaluateIf($oVariable))
315            {
316                my $strKey = $oVariable->paramGet('key');
317                my $strValue = $self->variableReplace($oVariable->valueGet());
318
319                if ($oVariable->paramTest('eval', 'y'))
320                {
321                    # In this case we really do want to evaluate the contents of strValue and not treat it as a literal.
322                    $strValue = eval($strValue);
323
324                    if ($@)
325                    {
326                        confess &log(ERROR, "unable to evaluate ${strKey}: $@\n" . $oVariable->valueGet());
327                    }
328                }
329
330                $self->variableSet($strKey, defined($rhVariableOverride->{$strKey}) ? $rhVariableOverride->{$strKey} : $strValue);
331
332                logDebugMisc
333                (
334                    $strOperation, '    load variable',
335                    {name => 'strKey', value => $strKey},
336                    {name => 'strValue', value => $strValue}
337                );
338            }
339        }
340    }
341
342    # Return from function and log return values if any
343    return logDebugReturn($strOperation);
344}
345
346####################################################################################################################################
347# variableReplace
348#
349# Replace variables in the string.
350####################################################################################################################################
351sub variableReplace
352{
353    my $self = shift;
354    my $strBuffer = shift;
355    my $strType = shift;
356
357    if (!defined($strBuffer))
358    {
359        return;
360    }
361
362    foreach my $strName (sort(keys(%{$self->{oVariable}})))
363    {
364        my $strValue = $self->{oVariable}{$strName};
365
366        $strBuffer =~ s/\{\[$strName\]\}/$strValue/g;
367    }
368
369    if (defined($strType) && $strType eq 'latex')
370    {
371        $strBuffer =~ s/\\\_/\_/g;
372        $strBuffer =~ s/\_/\\\_/g;
373        $strBuffer =~ s/\\\#/\#/g;
374        $strBuffer =~ s/\#/\\\#/g;
375    }
376
377    return $strBuffer;
378}
379
380####################################################################################################################################
381# variableSet
382#
383# Set a variable to be replaced later.
384####################################################################################################################################
385sub variableSet
386{
387    my $self = shift;
388    my $strKey = shift;
389    my $strValue = shift;
390    my $bForce = shift;
391
392    if (defined(${$self->{oVariable}}{$strKey}) && (!defined($bForce) || !$bForce))
393    {
394        confess &log(ERROR, "${strKey} variable is already defined");
395    }
396
397    ${$self->{oVariable}}{$strKey} = $self->variableReplace($strValue);
398}
399
400####################################################################################################################################
401# variableGet
402#
403# Get the current value of a variable.
404####################################################################################################################################
405sub variableGet
406{
407    my $self = shift;
408    my $strKey = shift;
409
410    return ${$self->{oVariable}}{$strKey};
411}
412
413####################################################################################################################################
414# variableTest
415#
416# Test that a variable is defined or has an expected value.
417####################################################################################################################################
418sub variableTest
419{
420    my $self = shift;
421    my $strKey = shift;
422    my $strExpectedValue = shift;
423
424    # Get the variable
425    my $strValue = ${$self->{oVariable}}{$strKey};
426
427    # Return false if it is not defined
428    if (!defined($strValue))
429    {
430        return false;
431    }
432
433    # Return false if it does not equal the expected value
434    if (defined($strExpectedValue) && $strValue ne $strExpectedValue)
435    {
436        return false;
437    }
438
439    return true;
440}
441
442####################################################################################################################################
443# Get list of source documents
444####################################################################################################################################
445sub sourceList
446{
447    my $self = shift;
448
449    # Assign function parameters, defaults, and log debug info
450    my ($strOperation) = logDebugParam(__PACKAGE__ . '->sourceList');
451
452    # Check that sources exist
453    my @strySource;
454
455    if (defined(${$self->{oManifest}}{source}))
456    {
457        @strySource = sort(keys(%{${$self->{oManifest}}{source}}));
458    }
459
460    # Return from function and log return values if any
461    return logDebugReturn
462    (
463        $strOperation,
464        {name => 'strySource', value => \@strySource}
465    );
466}
467
468####################################################################################################################################
469# sourceGet
470####################################################################################################################################
471sub sourceGet
472{
473    my $self = shift;
474
475    # Assign function parameters, defaults, and log debug info
476    my
477    (
478        $strOperation,
479        $strSource
480    ) =
481        logDebugParam
482        (
483            __PACKAGE__ . '->sourceGet', \@_,
484            {name => 'strSource', trace => true}
485        );
486
487    if (!defined(${$self->{oManifest}}{source}{$strSource}))
488    {
489        confess &log(ERROR, "source ${strSource} does not exist");
490    }
491
492    # Return from function and log return values if any
493    return logDebugReturn
494    (
495        $strOperation,
496        {name => 'oSource', value => ${$self->{oManifest}}{source}{$strSource}}
497    );
498}
499
500####################################################################################################################################
501# renderList
502####################################################################################################################################
503sub renderList
504{
505    my $self = shift;
506
507    # Assign function parameters, defaults, and log debug info
508    my ($strOperation) = logDebugParam(__PACKAGE__ . '->renderList');
509
510    # Check that the render output exists
511    my @stryRender;
512
513    if (defined(${$self->{oManifest}}{render}))
514    {
515        @stryRender = sort(keys(%{${$self->{oManifest}}{render}}));
516    }
517
518    # Return from function and log return values if any
519    return logDebugReturn
520    (
521        $strOperation,
522        {name => 'stryRender', value => \@stryRender}
523    );
524}
525
526####################################################################################################################################
527# renderGet
528####################################################################################################################################
529sub renderGet
530{
531    my $self = shift;
532
533    # Assign function parameters, defaults, and log debug info
534    my
535    (
536        $strOperation,
537        $strType
538    ) =
539        logDebugParam
540        (
541            __PACKAGE__ . '->renderGet', \@_,
542            {name => 'strType', trace => true}
543        );
544
545    # Check that the render exists
546    if (!defined(${$self->{oManifest}}{render}{$strType}))
547    {
548        confess &log(ERROR, "render type ${strType} does not exist");
549    }
550
551    # Return from function and log return values if any
552    return logDebugReturn
553    (
554        $strOperation,
555        {name => 'oRenderOut', value => ${$self->{oManifest}}{render}{$strType}}
556    );
557}
558
559####################################################################################################################################
560# renderOutList
561####################################################################################################################################
562sub renderOutList
563{
564    my $self = shift;
565
566    # Assign function parameters, defaults, and log debug info
567    my
568    (
569        $strOperation,
570        $strType
571    ) =
572        logDebugParam
573        (
574            __PACKAGE__ . '->renderOutList', \@_,
575            {name => 'strType'}
576        );
577
578    # Check that the render output exists
579    my @stryRenderOut;
580
581    if (defined(${$self->{oManifest}}{render}{$strType}))
582    {
583        @stryRenderOut = sort(keys(%{${$self->{oManifest}}{render}{$strType}{out}}));
584    }
585
586    # Return from function and log return values if any
587    return logDebugReturn
588    (
589        $strOperation,
590        {name => 'stryRenderOut', value => \@stryRenderOut}
591    );
592}
593
594####################################################################################################################################
595# renderOutGet
596####################################################################################################################################
597sub renderOutGet
598{
599    my $self = shift;
600
601    # Assign function parameters, defaults, and log debug info
602    my
603    (
604        $strOperation,
605        $strType,
606        $strKey,
607        $bIgnoreMissing,
608    ) =
609        logDebugParam
610        (
611            __PACKAGE__ . '->renderOutGet', \@_,
612            {name => 'strType', trace => true},
613            {name => 'strKey', trace => true},
614            {name => 'bIgnoreMissing', default => false, trace => true},
615        );
616
617    if (!defined(${$self->{oManifest}}{render}{$strType}{out}{$strKey}) && !$bIgnoreMissing)
618    {
619        confess &log(ERROR, "render out ${strKey} does not exist");
620    }
621
622    # Return from function and log return values if any
623    return logDebugReturn
624    (
625        $strOperation,
626        {name => 'oRenderOut', value => ${$self->{oManifest}}{render}{$strType}{out}{$strKey}}
627    );
628}
629
630####################################################################################################################################
631# cacheKey
632####################################################################################################################################
633sub cacheKey
634{
635    my $self = shift;
636
637    # Assign function parameters, defaults, and log debug info
638    my ($strOperation) = logDebugParam(__PACKAGE__ . '->cacheKey');
639
640    # Generate a cache key from the variable override
641    my $strVariableKey = JSON::PP->new()->canonical()->allow_nonref()->encode($self->{rhKeyVariableOverride});
642
643    if ($strVariableKey eq '{}')
644    {
645        $strVariableKey = 'default';
646    }
647
648    my $strRequire = defined($self->{stryRequire}) && @{$self->{stryRequire}} > 0 ?
649        join("\n", @{$self->{stryRequire}}) : 'all';
650
651    # Return from function and log return values if any
652    return logDebugReturn
653    (
654        $strOperation,
655        {name => 'strVariableKey', value => $strVariableKey},
656        {name => 'strRequire', value => $strRequire},
657    );
658}
659
660####################################################################################################################################
661# cacheRead
662####################################################################################################################################
663sub cacheRead
664{
665    my $self = shift;
666
667    # Assign function parameters, defaults, and log debug info
668    my ($strOperation) = logDebugParam(__PACKAGE__ . '->cacheRead');
669
670    $self->{hCache} = undef;
671
672    my $strCacheFile = $self->{bDeploy} ? $self->{strExeCacheDeploy} : $self->{strExeCacheLocal};
673
674    if (!$self->storage()->exists($strCacheFile) && !$self->{bDeploy})
675    {
676        $strCacheFile = $self->{strExeCacheDeploy};
677    }
678
679    if ($self->storage()->exists($strCacheFile))
680    {
681        my ($strCacheKey, $strRequire) = $self->cacheKey();
682        my $oJSON = JSON::PP->new()->allow_nonref();
683        $self->{hCache} = $oJSON->decode(${$self->storage()->get($strCacheFile)});
684
685        foreach my $strSource (sort(keys(%{${$self->{oManifest}}{source}})))
686        {
687            my $hSource = ${$self->{oManifest}}{source}{$strSource};
688
689            if (defined(${$self->{hCache}}{$strCacheKey}{$strRequire}{$strSource}))
690            {
691                $$hSource{hyCache} = ${$self->{hCache}}{$strCacheKey}{$strRequire}{$strSource};
692                &log(DETAIL, "cache load $strSource (key = ${strCacheKey}, require = ${strRequire})");
693            }
694        }
695    }
696
697    # Return from function and log return values if any
698    return logDebugReturn($strOperation);
699}
700
701####################################################################################################################################
702# cacheWrite
703####################################################################################################################################
704sub cacheWrite
705{
706    my $self = shift;
707
708    # Assign function parameters, defaults, and log debug info
709    my ($strOperation) = logDebugParam(__PACKAGE__ . '->cacheWrite');
710
711    my $strCacheFile = $self->{bDeploy} ? $self->{strExeCacheDeploy} : $self->{strExeCacheLocal};
712    my ($strCacheKey, $strRequire) = $self->cacheKey();
713
714    foreach my $strSource (sort(keys(%{${$self->{oManifest}}{source}})))
715    {
716        my $hSource = ${$self->{oManifest}}{source}{$strSource};
717
718        if (defined($$hSource{hyCache}))
719        {
720            ${$self->{hCache}}{$strCacheKey}{$strRequire}{$strSource} = $$hSource{hyCache};
721            &log(DETAIL, "cache load $strSource (key = ${strCacheKey}, require = ${strRequire})");
722        }
723    }
724
725    if (defined($self->{hCache}))
726    {
727        my $oJSON = JSON::PP->new()->canonical()->allow_nonref()->pretty();
728        $self->storage()->put($strCacheFile, $oJSON->encode($self->{hCache}));
729    }
730
731    # Return from function and log return values if any
732    return logDebugReturn($strOperation);
733}
734
735####################################################################################################################################
736# cacheReset
737####################################################################################################################################
738sub cacheReset
739{
740    my $self = shift;
741
742    # Assign function parameters, defaults, and log debug info
743    my
744    (
745        $strOperation,
746        $strSource
747    ) =
748        logDebugParam
749        (
750            __PACKAGE__ . '->cacheReset', \@_,
751            {name => 'strSource', trace => true}
752        );
753
754    if ($self->{bCacheOnly})
755    {
756        confess &log(ERROR, 'Cache reset disabled by --cache-only option');
757    }
758
759    &log(WARN, "Cache will be reset for source ${strSource} and rendering retried automatically");
760    delete(${$self->{oManifest}}{source}{$strSource}{hyCache});
761
762    # Return from function and log return values if any
763    return logDebugReturn($strOperation);
764}
765
766####################################################################################################################################
767# Getters
768####################################################################################################################################
769sub storage {shift->{oStorage}};
770
7711;
772