#!/usr/bin/env perl
#
#  This file is part of WebDyne.
#
#  This software is copyright (c) 2026 by Andrew Speer <andrew.speer@isolutions.com.au>.
#
#  This is free software; you can redistribute it and/or modify it under
#  the same terms as the Perl 5 programming language system itself.
#
#  Full license text is available at:
#
#  <http://dev.perl.org/licenses/>
#


#
#  Compile and/or show compiled version of WebDyne HTML scripts
#
package main;


#  Compiler pragma, load library path
#
use strict qw(vars);
use vars   qw($VERSION);
use warnings;


#  Use the base modules
#
use WebDyne::Util;


#  External modules
#
use WebDyne::Constant;
use WebDyne::Compile;
use Getopt::Long;
use Pod::Usage;
use Data::Dumper;
use File::Spec;
use FindBin qw($RealBin $Script);


#  Local customisation
#
local $Data::Dumper::Indent=1;
local $Data::Dumper::Sortkeys=1;


#  Version Info, must be all one line for MakeMaker, CPAN.
#
$VERSION='3.005';


#  Run main
#
exit ${&main(\@ARGV) || die errdump()};


#===================================================================================================


sub main {


    #  Get argv array ref
    #
    my $argv_ar=shift();


    #  Base options will pass to compile
    #
    my %opt=(

        no_filter      => 1,    # don't run through any filters
        no_perl        => 1,    # don't run perl code,
        no_timestamp   => 1,
        no_head_insert => 1,
        manifest      => 1

        #stage5   => 1,    # default

    );


    #  Now import command line options. Command line option compilation stages are different from
    #  internal stage numbers so maps are 0=>stage0, 1=>stage2, 2=>stage3, 3=>stage4, confusing ..
    #
    GetOptions(
        \%opt, 
        my @opt=(
        'stage0|0',
        'stage1|1',
        'stage2|2',
        'stage3|3',
        'stage4|4',
        'stage5|5|final',
        'meta',
        'data',
        'manifest!',
        'head_insert|head-insert!',
        'outfile|dest|dest_fn:s',
        'all',
        'timestamp!',
        'repeat|r|num|n=s',
        'perl!',
        'filter!',
        'test',
        'version',
        'help|?',
        'man',
        'dump_opt|dump-opt|opt'
        ),
    ) || pod2usage(2);
    pod2usage(-verbose => 99, -sections => 'SYNOPSIS|OPTIONS', -exitval => 1) if $opt{'help'};
    pod2usage(-verbose => 2)                                                  if $opt{'man'};
    $opt{'version'} && do {
        print "$Script version: $VERSION\n";
        print "WebDyne version: $WebDyne::VERSION ($WebDyne::VERSION_GIT_SHA)\n";
        exit 0
    };


    #  Clunky, too hard to change module logic
    #
    map {$opt{"no_${_}"}=!($opt{$_})} map { /^([^|!:=+]+)/ } grep {/\!$/} @opt;
    #map {$opt{"no${_}"}=!($opt{$_})} qw(perl filter timestamp manifest head_insert);
    #die Dumper(\%opt);


    #  Get srce file, add to options
    #
    my $srce_fn=$opt{'test'} ? $WEBDYNE_DEFAULT_TEST_FN : $argv_ar->[0];
    $srce_fn ||
        pod2usage("$Script: no source file specified !");
    $opt{'srce'}=$srce_fn;


    #  Dump options for debugging
    #
    die Dumper(\%opt) if $opt{'dump_opt'};


    #  Loop if required as diagnostic for consistant compiles, i.e. making
    #  sure we haven't deleted an attribute from save data tree etc.
    #
    for (1 .. ($opt{'repeat'} || 1)) {

        #  Create and run compile object
        #
        my $compile_or=WebDyne::Compile->new(\%opt);
        my $data_ar=$compile_or->compile(\%opt) ||
            return err();


        #  Dump it
        #
        if ($opt{'all'}) {
            print Dumper($data_ar);
        }
        elsif ($opt{'meta'}) {
            print Dumper($data_ar->[0])
        }
        else {
            print Dumper($data_ar->[1])
        }

    }

    #print Data::Dumper::Dumper(grep {$_} $opt{'meta'} ? $data_ar->[0] : undef, $data_ar->[1]);


    #  Return success
    #
    return \0;


}

__END__

=begin markdown

# wdcompile #

# NAME #

wdcompile - parse and display internal data representation of WebDyne files

# SYNOPSIS #

`wdcompile [OPTIONS] FILE`

# Description #

The  `wdcompile`  command compiles a \.psp page with the WebDyne perl module and displays the resulting internal Perl data structure using `Data::Dumper`.

WebDyne parses HTML into an intermediate format which can then be stored to disk to speed up subsequent rendering of pages. It is dependent on HTML::TreeBuilder and HTML::Parser modules to build the tree, and the compiler produces a container of metadata plus compiled page data.

The parser may build incorrect representations on a HTML tree if the HTML is badly formed or there are errors in the WebDyne compiler itself. This utility is useful for diagnosing any such errors.

The parser interprets files in 3 main stages \(and some lesser intermediate stages that do not impact data structure but are used in the test suites): a full HTML::TreeBuilder representation of all tags with no optimisation, an intermediate partial optimisation stage that re-renders tags with no dynamic components \(e.g. no &lt;perl&gt; or similar tags) back into static HTML and a final fully optimised file that should only contain a data structure for dynamic components of the page.

The final data structure contains various artifacts used by the render engine such as a manifest section, and notation of line numbers for dynamic sections to aid in error display and source file tracebacks should an error occur in any dynamic code at render time.

By default the command prints the compiled page data section only. `--meta` prints only the metadata section, and `--all` prints the full two-element container.

# Options #

* **--help**

    Show brief help message.

* **--meta**

    Show only the metadata section of the compiled container.

* **--data**

    Show only the compiled page data section. This is the default output mode.

* **--all**

    Show the full compiled container, including both metadata and compiled page data.

* **--test**

    Use WebDyne's built-in default test page as the source file.

* **--manifest**

    Enable or disable population of the source filename in the metadata manifest section. Manifest output is enabled by default.

* **--timestamp**

    Enable or disable compile timestamps in metadata. Timestamps are disabled by default.

* **--perl**

    Enable or disable execution of Perl in \__PERL__ sections at compile time. Perl execution is disabled by default.

* **--filter**

    Enable or disable compile-time filter stages nominated in the file. Filters are disabled by default.

* **--head_insert**

    Enable or disable WebDyne head insertion while compiling from the command line, including configured `WEBDYNE_HEAD_INSERT` content and related `start_html` parameters. This is disabled by default so diagnostic compile output is not changed by runtime head additions normally intended for Apache, PSGI, or PAGI request handling.

* **--outfile**

    Intended to specify a destination file for the compiled Storable cache. In the current `wdcompile` script this option is parsed but not wired through to the compiler's `dest` parameter, so it does not currently write an output file.

* **--repeat=NUM**

    Specify the number of times to repeat the compile. Used for consistency testing.

* **--stage0**

    Stop after parsing into the initial container representation.

* **--stage1**

    Stop after compile-time Perl processing.

* **--stage2**

    Stop after compile-time filter processing.

* **--stage3**

    Stop after the first optimisation pass.

* **--stage4**

    Stop after the second optimisation pass.

* **--stage5**

    Stop at the final compiled container representation. This is the default.

* **--dump_opt**

    Dump the parsed option hash for debugging and exit.

* **--man**

    Display the full manual.

* **--version**

    Display the script version and WebDyne version, then exit.

# Examples #

```sh
# Reference file saved as time.psp
#
<start_html>
The current server time is: <? localtime() ?>

```

```sh
# Show the final compiled data structure of time.psp
#
$ wdcompile time.psp

$VAR1 = [
  '<!DOCTYPE html><html lang="en"><head><title>Untitled Document</title><meta charset="UTF-8"><meta content="width=device-width, initial-scale=1.0" name="viewport"></head>
<body><p>The current server time is: ',
  [
    'perl',
    {
      'inline' => 1,
      'perl' => ' localtime() '
    },
    undef,
    undef,
    2,
    2,
    \'time.psp'
  ],
  '</p></body></html>'
];

```

```sh
# Show the initial compiled data structure (stage 0) of the time.psp file including metadata
#
$ wdcompile --all --stage0 time.psp

$VAR1 = [
  {
    'manifest' => [
      'time.psp'
    ]
  },
  [
    'html',
    {
      'lang' => 'en'
    },
    [
      [
        'head',
        undef,
        [
          [
            'title',
            undef,
            [
              'Untitled Document'
            ],
            undef,
            1,
            1,
            \$VAR1->[0]{'manifest'}[0]
            ...

```

# Notes #

The  `wdcompile`  command will attempt to build the HTML tree as faithfully as possible from the command line environment, but may not be able to interpret all HTML files, especially those with malformed HTML tags. It is reliant on HTML::TreeBuilder and HTML::Parser. If you are sure your HTML is compliant \(all tags closed, all attributes double quoted
 etc.) and wdcompile is still producing a malformed tree you can submit a bug report with the smallest possible HTML file needed to replicate the issue.

# Author #

Andrew Speer <andrew.speer@isolutions.com.au>

# LICENSE and COPYRIGHT

This file is part of WebDyne.

This software is copyright (c) 2026 by Andrew Speer <andrew.speer@isolutions.com.au>.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

Full license text is available at:

<http://dev.perl.org/licenses/>


=end markdown


=head1 wdcompile


=head1 NAME

wdcompile - parse and display internal data representation of WebDyne files


=head1 SYNOPSIS

C<wdcompile [OPTIONS] FILE>


=head1 Description

The  C<wdcompile>  command compiles a .psp page with the WebDyne perl module and displays the resulting internal Perl data structure using C<Data::Dumper>.

WebDyne parses HTML into an intermediate format which can then be stored to disk to speed up subsequent rendering of pages. It is dependent on HTML::TreeBuilder and HTML::Parser modules to build the tree, and the compiler produces a container of metadata plus compiled page data.

The parser may build incorrect representations on a HTML tree if the HTML is badly formed or there are errors in the WebDyne compiler itself. This utility is useful for diagnosing any such errors.

The parser interprets files in 3 main stages (and some lesser intermediate stages that do not impact data structure but are used in the test suites): a full HTML::TreeBuilder representation of all tags with no optimisation, an intermediate partial optimisation stage that re-renders tags with no dynamic components (e.g. no E<lt>perlE<gt> or similar tags) back into static HTML and a final fully optimised file that should only contain a data structure for dynamic components of the page.

The final data structure contains various artifacts used by the render engine such as a manifest section, and notation of line numbers for dynamic sections to aid in error display and source file tracebacks should an error occur in any dynamic code at render time.

By default the command prints the compiled page data section only. C<--meta> prints only the metadata section, and C<--all> prints the full two-element container.


=head1 Options

=over

=item *

B<--help>

Show brief help message.



=item *

B<--meta>

Show only the metadata section of the compiled container.



=item *

B<--data>

Show only the compiled page data section. This is the default output mode.



=item *

B<--all>

Show the full compiled container, including both metadata and compiled page data.



=item *

B<--test>

Use WebDyne's built-in default test page as the source file.



=item *

B<--manifest>

Enable or disable population of the source filename in the metadata manifest section. Manifest output is enabled by default.



=item *

B<--timestamp>

Enable or disable compile timestamps in metadata. Timestamps are disabled by default.



=item *

B<--perl>

Enable or disable execution of Perl in __PERL__ sections at compile time. Perl execution is disabled by default.



=item *

B<--filter>

Enable or disable compile-time filter stages nominated in the file. Filters are disabled by default.



=item *

B<--head_insert>

Enable or disable WebDyne head insertion while compiling from the command line, including configured C<WEBDYNE_HEAD_INSERT> content and related C<start_html> parameters. This is disabled by default so diagnostic compile output is not changed by runtime head additions normally intended for Apache, PSGI, or PAGI request handling.



=item *

B<--outfile>

Intended to specify a destination file for the compiled Storable cache. In the current C<wdcompile> script this option is parsed but not wired through to the compiler's C<dest> parameter, so it does not currently write an output file.



=item *

B<--repeat=NUM>

Specify the number of times to repeat the compile. Used for consistency testing.



=item *

B<--stage0>

Stop after parsing into the initial container representation.



=item *

B<--stage1>

Stop after compile-time Perl processing.



=item *

B<--stage2>

Stop after compile-time filter processing.



=item *

B<--stage3>

Stop after the first optimisation pass.



=item *

B<--stage4>

Stop after the second optimisation pass.



=item *

B<--stage5>

Stop at the final compiled container representation. This is the default.



=item *

B<--dump_opt>

Dump the parsed option hash for debugging and exit.



=item *

B<--man>

Display the full manual.



=item *

B<--version>

Display the script version and WebDyne version, then exit.



=back


=head1 Examples


 # Reference file saved as time.psp
 #
 <start_html>
 The current server time is: <? localtime() ?>


 # Show the final compiled data structure of time.psp
 #
 $ wdcompile time.psp
 
 $VAR1 = [
   '<!DOCTYPE html><html lang="en"><head><title>Untitled Document</title><meta charset="UTF-8"><meta content="width=device-width, initial-scale=1.0" name="viewport"></head>
 <body><p>The current server time is: ',
   [
     'perl',
     {
       'inline' => 1,
       'perl' => ' localtime() '
     },
     undef,
     undef,
     2,
     2,
     \'time.psp'
   ],
   '</p></body></html>'
 ];


 # Show the initial compiled data structure (stage 0) of the time.psp file including metadata
 #
 $ wdcompile --all --stage0 time.psp
 
 $VAR1 = [
   {
     'manifest' => [
       'time.psp'
     ]
   },
   [
     'html',
     {
       'lang' => 'en'
     },
     [
       [
         'head',
         undef,
         [
           [
             'title',
             undef,
             [
               'Untitled Document'
             ],
             undef,
             1,
             1,
             \$VAR1->[0]{'manifest'}[0]
             ...


=head1 Notes

The  C<wdcompile>  command will attempt to build the HTML tree as faithfully as possible from the command line environment, but may not be able to interpret all HTML files, especially those with malformed HTML tags. It is reliant on HTML::TreeBuilder and HTML::Parser. If you are sure your HTML is compliant (all tags closed, all attributes double quoted
 etc.) and wdcompile is still producing a malformed tree you can submit a bug report with the smallest possible HTML file needed to replicate the issue.


=head1 Author

Andrew Speer L<mailto:andrew.speer@isolutions.com.au>


=head1 LICENSE and COPYRIGHT

This file is part of WebDyne.

This software is copyright (c) 2026 by Andrew Speer E<lt>andrew.speer@isolutions.com.auE<gt>.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.

Full license text is available at:

E<lt>L<http://dev.perl.org/licenses/>;>

=cut
