### ----------------------------------------------------
### -- Prettier::Table
### -- Licenses: MIT
### -- Authors: Luis F. Uceta, masukomi <masukomi@masukomi.org>
### -- File: lib/Prettier/Table.rakumod
### ----------------------------------------------------
### -- Chunk 2 of 2
 "record" with a via of metaprogramming the table's state right before the
    get-string's parameters (if defined) are applied and then set the table back
    right before prepare-lines returns.
    =end comment
    my %original;
    for %_.grep(*.value.defined).Hash.keys -> $method-name {
        my $getter = self.^find_method($method-name).candidates.first(*.arity == 1);

        # FIX: Doing this because otherwise the error:
        # Invocant of method 'end' must be a type object of type 'Any',
        # not an object instance of type 'Prettier::Table'.  Did you forget a 'multi'?
        %original{$method-name} = $method-name eq 'end' ?? self.end !! self.$getter;

        my $setter = self.^find_method($method-name).candidates.first(*.arity == 2);

        # FIX: Doing this because otherwise the error:
        # Invocant of method 'end' must be a type object of type 'Any',
        # not an object instance of type 'Prettier::Table'.  Did you forget a 'multi'?
        $method-name eq 'end' ?? self.end(%_{$method-name}) !! self.$setter(%_{$method-name});
    }

    if self.row-count() == 0 and (!$!print-empty or !$!border) {
        return [];
    }

    # Get the rows we need to print, taking into account slicing, sorting, etc.
    my @rows = self!get-rows;

    my @formatted-rows = self!format-rows(@rows);

    # Compute column widths
    self!compute-widths(@formatted-rows);

    my @lines;

    # Add title
    @lines.push(self!stringify-title($!title)) if $!title.defined and ! $!markdown;
    # Add header or top of border

    if $!header and $!title.defined {
        @lines.append(self!stringify-header(row=>'header-middle').split(/\r\n|\n/));
    } elsif $!header {
        @lines.append(self!stringify-header(row=>'header-top').split(/\r\n|\n/));
    } elsif $!border and $!hrules ∈ (ALL, FRAME) {
        @lines.push(self!stringify-hrule(row => 'top'));
    }

    # Add rows of content (after title and headers)
    loop (my $i = 0; $i < @formatted-rows.elems; $i++) {

        if $i < @formatted-rows.elems - 1 {
            my @stringified-lines = self!stringify-row(@formatted-rows[$i].Array, row-position => 'middle');
            @lines.push(@stringified-lines);
        } else {
            my @stringified-lines = self!stringify-row(@formatted-rows[$i].Array, row-position => 'bottom');
            @lines.push(@stringified-lines);
        }
    }
    # Add bottom border
    if $!border and $!hrules ~~ FRAME {
        @lines.push(self!stringify-hrule(row => 'bottom'));
    }

    =begin comment
    Setting the table's state back to how it was before the latest get-string call.
    Here we're getting the Prettier::Table setter methods related to prepare-lines's
    parameter list and setting to their previous values (if any).
    =end comment
    for %original.keys -> $method-name {
        my $setter = self.^find_method($method-name).candidates.first(*.arity == 2);
        self.$setter(%original{$method-name}) if %original{$method-name}.defined;
    }

    return @lines;
}

method !stringify-hrule( Str :$row = 'middle', Positional :$alignments?) {
    return "" unless $!border;
    # FIXME won't work if no field names
    my @aligns = $alignments // \
                  ("c" x @!field-names.elems).split("",:skip-empty);

    my ($lpad, $rpad) = self!get-padding-widths;
    my @bits;

    if ! $!markdown and $!vrules ∈  (ALL, FRAME).one {
        @bits.push($!left-junction-char)       if $row eq 'middle' or $row eq 'header-bottom' or $row eq 'header-middle';
        @bits.push($!top-left-corner-char)     if $row eq 'top' or $row eq 'header-top' ;
        @bits.push($!bottom-left-corner-char)  if $row eq 'bottom';
    } elsif $!markdown {
        @bits.push($!left-junction-char) if $row eq 'middle' or $row eq 'header-bottom'
    }
    else {
        @bits.push($!horizontal-char)
    }

    unless @!field-names {
        # FIXME this can't be right with new complex junction possibilities
        @bits.push($!junction-char);
        return @bits.join
    }

    for (@!field-names Z @!widths Z @aligns) -> ($field, $width, $align) {
 if @!fields and $field ∉  @!fields {
} else {
 }

        next if @!fields and $field ∉  @!fields;
        my $padded_width = $width + $lpad + $rpad;

        if $!markdown {
            given $align {
                when 'l' {
                    @bits.push(':' ~ ( $!horizontal-char x ($padded_width - 1)) );
                }
                when 'r' {
                    @bits.push( ($!horizontal-char x ($padded_width - 1)) ~ ':' );
                }
                default {
                    @bits.push($!horizontal-char x $padded_width);
                }
            }

        } else {
            @bits.push($!horizontal-char x $padded_width);
        }


        if $!vrules {
            @bits.push($!junction-char)         if $row eq 'middle' or $row eq 'header-bottom';
            @bits.push($!top-junction-char)     if $row eq 'top'    or $row eq 'header-top' or $row eq 'header-middle';
            @bits.push($!bottom-junction-char)  if $row eq 'bottom';
        }
        else {
            @bits.push($!horizontal-char)
        }
    }
    if $!vrules {
        # replace the last junction-char with whatever's appropriate
        @bits[*-1] = $!right-junction-char       if $row eq 'middle' or $row eq 'header-bottom' or $row eq 'header-middle';
        @bits[*-1] = $!bottom-right-corner-char  if $row eq 'bottom';
        @bits[*-1] = $!top-right-corner-char     if $row eq 'top' or $row eq 'header-top';
    }

    if $!vrules ~~ FRAME { # markdown is ALL
        @bits.pop;
        @bits.push($!right-junction-char)       if $row eq 'middle' or $row eq 'header-middle' or $row eq 'header-bottom';
        @bits.push($!top-right-corner-char)     if $row eq 'top' or $row eq 'header-top' ;
        @bits.push($!bottom-right-corner-char)  if $row eq 'bottom';
    }

    @bits.join('');
}

method !stringify-title( $title is copy --> Str ) {
    my @lines;
    my ($lpad, $rpad) = self!get-padding-widths;
    if $!border {
        if $!vrules == ALL {
            $!vrules = FRAME;
            @lines.push(self!stringify-hrule(row => 'header-top'));
            $!vrules = ALL;
        }
        elsif $!vrules == FRAME {
            @lines.push(self!stringify-hrule(row => 'header-top'));
        }
    }
    my @bits;
    my $endpoint = $!vrules == (ALL, FRAME).one ?? $!vertical-char !! " ";
    @bits.push($endpoint);
    $title = (' ' x $lpad, $title, ' ' x $rpad).join;
    my $hrule = self!stringify-hrule(row => 'header-bottom');

    @bits.push(self!justify($title, $hrule.chars - 2, 'c'));
    @bits.push($endpoint);
    @lines.push(@bits.join);
    return @lines.join("\n");
}

method !stringify-header(Str :$row = 'header-top') {
    my @bits;
    my ($lpad, $rpad) = self!get-padding-widths();

    if $!border {
        if $!hrules ∈ (ALL, FRAME).one {
            @bits.push(self!stringify-hrule(row=>$row));
            @bits.push("\n")
        }
        if $!vrules ∈ (ALL, FRAME).one {
            @bits.push($!vertical-char)
        }
        else {
            @bits.append(" ")
        }
    }

    # for tables with no data or field names
    unless @!field-names {
        if $!vrules ∈ (ALL, FRAME).one {
            @bits.push($!vertical-char)
        }
        else {
            @bits.push(" ")
        }
    }

    my @column_alignments = [];
    for (@!field-names Z @!widths) -> ($field, $width) {
        if @!fields and $field ∉  @!fields {
            next
        }
        my $fieldname = do given $!header-style {
            when 'cap'   { $field.tc }
            when 'title' { $field.tc }
            when 'upper' { $field.uc }
            when 'lower' { $field.lc }
            default      { $field    }
        }

        @bits.push(" " x $lpad ~ self!justify($fieldname, $width, $!align{$field} // 'c') ~ " " x $rpad);
        @column_alignments.push($!align{$field} // 'c');
        if $!border {
            if $!vrules ~~ ALL {
                @bits.push($!vertical-char)
            }
            else {
                @bits.push(" ")
            }
        }
    }

    # if vrules is FRAME, then we just appended a space at the end of the last
    # field, when we really wanted a vertical character
    if $!border and $!vrules ~~ FRAME {
        @bits.pop;
        @bits.push($!vertical-char);
    }

    if $!border and $!hrules !~~ NONE {
        @bits.push("\n");
        my $hrule-row = ($row eq 'header-middle' or $row eq 'header-top') ?? 'header-bottom' !! $row;
        @bits.push(self!stringify-hrule(row=>$hrule-row, alignments => @column_alignments));
    }
    return @bits.join('')
}

method !stringify-row( @row, Str :$row-position = 'middle' --> Str ) {
    for 0..^@row.elems Z @!field-names Z @row Z @!widths -> ($index, $field, $value, $width) {
        my $guaranteed_value = Nil ~~ $value ?? '' !! $value;
        my @lines = $guaranteed_value.split(/\r\n|\n/);
        my @new-lines;
        for @lines <-> $line {
            if self!str-block-width($line) > $width {
                $line = wrap-text($line, :$width, :hard-wrap);
            }
            @new-lines.push($line)
        }
        @lines = @new-lines;
        @row[$index] = @lines.join("\n");
    }

    my $row-height = 0;
    for @row -> $c {
        my $h = self!get-size($c)<height>;
        $row-height = $h if $h > $row-height;
    }

    my @bits;
    my ($lpad, $rpad) = self!get-padding-widths;
    for ^$row-height -> $y {
        @bits.push([]);
        if $!border {
            my $char := $!vrules ∈  (ALL, FRAME) ?? self.vertical-char !! " ";
            @bits[$y].push($char);
        }
    }

    for @!field-names.flat Z @row.flat Z @!widths.flat -> ($field, $value, $width) {
        my $valign = $!valign{$field};
        my $guaranteed_value = Nil ~~ $value ?? '' !! $value;
        my @lines = $guaranteed_value.split(/\r\n|\n/);
        my $height-diff = $row-height - @lines.elems;
        if $height-diff {
            given $valign {
                when "m" { @lines = |("" xx $height-diff div 2), |@lines, |("" xx ($height-diff - $height-diff div 2)) }
                when "d" { @lines = |("" xx $height-diff), |@lines }
                default  { @lines = |@lines, |("" xx $height-diff) }
            }
        }

        my $y = 0;
        for @lines -> $l {
            next if @!fields and $field ∉ @!fields;
            @bits[$y].push(" " x $lpad ~ self!justify($l, $width, $!align{$field} // 'c') ~ " " x $rpad);
            if $!border {
                my $char := $!vrules ~~ ALL ?? self.vertical-char !! " ";
                @bits[$y].push($char);
            }
            $y += 1;
        }
    }

    # if vrules is FRAME, then we just appended a space at the end
    # of the last field, when we really want a vertical character.
    for ^$row-height -> $y {
        if $!border and $!vrules ~~ FRAME {
            @bits.pop;
            @bits[$y].push(self.vertical-char)
        }
    }

    if $!border and $!hrules ~~ ALL {
        @bits[$row-height - 1].push("\n");
        @bits[$row-height - 1].push(self!stringify-hrule(row => $row-position))
    }

    for ^$row-height -> $y {
        @bits[$y] = @bits[$y].map({ $_ !=== Any ?? $_ !! '' }).join
    }
    return @bits.join("\n")
}

method !justify( Str $text, Int $width, Str $align --> Str ) {

    my $excess = $width - self!str-block-width($text);

    sub center-align( $text, $excess ) {
        if $excess mod 2 {
            if self!str-block-width($text) mod 2 {
                return (' ' x ($excess div 2), $text, ' ' x ($excess div 2 + 1)).join
            }
            else {
                return (' ' x ($excess div 2 + 1), $text, ' ' x ($excess div 2)).join
            }
        }
        else {
            return (' ' x ($excess div 2), $text, ' ' x ($excess div 2)).join
        }
    }

    return do given $align {
        when 'l' { ($text, ' ' x $excess).join  }
        when 'r' { (' ' x $excess, $text).join  }
        default  { center-align($text, $excess) }
    }

}

method !get-size( $text --> Hash ) {
    return %(width => 0, height => 0) if Nil ~~ $text;

    my @lines = $text.split(/\r\n|\n/);
    my $height = @lines.elems;
    my $width = @lines.map({ self!str-block-width($^line) }).max;
    return %( :$width, :$height );
}

method !str-block-width( Str $text ) {
    return $text.chars
}

#
# Miscellaneous methods
#

=head2 Miscellaneous Methods

#| Return the number of rows.
method row-count( --> Int ) {
    @!rows.elems
}

#| Return the number of columns.
method col-count( --> Int ) {
    return @!field-names.elems if @!field-names;
    return @!rows[0].elems if @!rows;
    return 0;
}

method elems( ::?CLASS:D: ) {
    @!rows.elems
}

#| Return a sliced-off new Prettier::Table. The indices must between 0 and the
#| table's number of rows (exclusive). Alternatively, the postcircumfix operator
#| [] can be used.
method slice( *@indices --> Prettier::Table ) {
    # all indices must be within the valid range.
    unless @indices.all ∈ 0..^@!rows.elems {
        X::OutOfRange.new(
            what => "Indices",
            got => @indices.join(', '),
            range => "0..{@!rows.elems - 1}"
        ).throw;
    }

    my $new-table = Prettier::Table.new;
    $new-table.field-names(self.field-names);

    # copy self's attribute values into the sliced table.
    # This is a "here be dragons" situation.
    for self.^attributes(:local) -> $attr {
        # @indices dictates which rows to include so skip setting this attribute.
        next if $attr eq '@!rows';
        $attr.set_value($new-table, $attr.get_value(self))
    }

    # get them rows
    for @!rows[@indices] -> $row {
        $new-table.add-row($row);
    }

    return $new-table;
}

# COMMENTED OUT because of rakudo bug
# https://github.com/rakudo/rakudo/issues/5079
#
# Getting this to work was tricky.
# See https://stackoverflow.com/a/60061569
# multi sub postcircumfix:<[ ]> ( Prettier::Table $n, $index, *@indices ) is default is export {
#     die("death in postcircumfix");
#     #$n.slice(|$index, |@indices)
# }

#
# Miscellaneous private methods
#

method !compute-table-width( --> Int ) {
    my Int $table-width = $!vrules ~~ (FRAME, ALL).one ?? 2 !! 0;
    my Int $per-column-padding = self!get-padding-widths.sum;
    for @!field-names.kv -> $index, $fieldname {
        if !@!fields or (@!fields and $fieldname ∈  @!fields) {
            $table-width += @!widths[$index] + $per-column-padding
        }
    }
    return $table-width;
}

method !compute-widths( @rows --> Nil ) {
    my @widths = $!header
        ?? @!field-names.map(-> $field { self!get-size($field)<width> })
        !! 0 xx @!field-names.elems;

    for @rows -> $row {
        for $row.flat.kv -> $index, $value {
            my $fieldname = @!field-names[$index];
            @widths[$index] = %!max-width{ $fieldname }:exists
            ?? max(@widths[$index], min(self!get-size($value)<width>, %!max-width{$fieldname}))
            !! max(@widths[$index], self!get-size($value)<width>);

            # %!min-width must be set somewhere.
            if self.min-width{$fieldname}:exists {
                @widths[$index] = max(@widths[$index], self.min-width{$fieldname});
            }
        }
    }

    @!widths = @widths;

    if $!max-table-width {
        my $table-width = self!compute-table-width;
        if $table-width > $!max-table-width {
            # get hash with minimum widths for fields
            my %min-width = self.min-width;
            # space for vrules
            my $nonshrinkable = $!vrules ∈ (FRAME, ALL) ?? 2 !! 0;
            # space for vrules between columns
            $nonshrinkable += @!field-names.elems - 1;
            # space for padding in each column
            my $per-col-padding = self!get-padding-widths.sum;
            $nonshrinkable += @widths.elems * $per-col-padding;
            # min space for each column
            $nonshrinkable += %min-width.values.sum;
            my $scale = ($!max-table-width - $nonshrinkable) / ($table-width - $nonshrinkable);
            sub calculate-new-with($fieldname, $old-width) {
                my $width = %min-width{$fieldname};
                # scale according to recalculated table width
                my $scaled-part = (($old-width - $width) * $scale).floor.Int;
                return (1, $width + $scaled-part).max
            }

            # TODO: More idiomatic way of doing this?
            @widths = (@!field-names Z @widths).map({calculate-new-with(|$_)});
            @!widths = @widths;
        }
    }

    # are we under min-table-width or title's width?
    if $!min-table-width or $!title {
        my $title-width = 0;
        if $!title {
            $title-width = $!title.chars + self!get-padding-widths.sum;
            $title-width += 2 if $!vrules ∈ (FRAME, ALL);
        }

        my $min-table-width = $!min-table-width // 0;
        my $min-width = ($title-width, $min-table-width).max;
        my $table-width = self!compute-table-width;
        if $table-width < $min-width {
            # grow widths proportionally
            my $scale = 1.0 * $min-width / $table-width;
            @widths = @widths.map({ ($^w * $scale).ceiling });
            @!widths = @widths;
        }
    }
}

method !get-padding-widths( --> List ) {
    my $lpad = $!left-padding-width.defined ?? $!left-padding-width  !! $!padding-width;
    my $rpad = $!right-padding-width.defined ?? $!right-padding-width !! $!padding-width;
    return $lpad, $rpad;
}

method !get-rows( --> Array ) {
    my @rows = $!old-sort-slice ?? @!rows[ $!start ..^ $!end ] !! @!rows;
    @rows = @rows.map(*.Array);

    with $!sort-by {
        my $sort-index = @!field-names.grep({ $_ eq $!sort-by }, :k).first;

        # add field to sort by at front of each row.
        @rows = @rows.map(-> $row { $row.unshift($row[$sort-index]) });

        # sort rows.
        @rows = @rows.sort(&!sort-key);
        @rows = @rows.reverse if $!reverse-sort;

        @rows = @rows.map(*[1..*].Array);
    }

    unless $!old-sort-slice {
        @rows = @rows[ $!start ..^ $!end ];
    }

    return @rows;
}

method !format-value( $field, $value is copy ) {
    if $value.isa(Int) and $!int-format{$field}:exists {
        my $format = '%' ~ $!int-format{$field};
        $value = sprintf $format, $value;
    }
    elsif $value.isa(Rat) and $!float-format{$field}:exists {
        my $format = '%' ~ $!float-format{$field};
        $value = sprintf $format, $value;
    }
    elsif $value.isa(Num) and $!float-format{$field}:exists {
        my $format = '%' ~ $!float-format{$field};
        $value = sprintf $format, $value;
    }
    return $value;
}

method !format-row( @row --> List ) {
    (@!field-names Z @row).map(-> ($field, $value) { self!format-value($field, $value) }).list
}

method !format-rows( @rows --> List ) {
    @rows.map(-> $row { self!format-row($row) }).list
}

#
# Documentation
#

=begin pod

=head1 Name

C<Prettier::Table>, a simple Raku module to make it quick and easy to represent
tabular data in visually appealing ASCII tables.

By default it will generate tables using ASCII Box Drawing characters as show in
the examples below. But you can also generate L<GFM Markdown tables|https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/organizing-information-with-tables>, and MS Word Friendly tables by calling C<$my_table.set-style('MARKDOWN')> or C<$my_table.set-style('MSWORD-FRIENDLY')> Check out C<demo.raku> to see this in action.


This is a fork of L<Luis F Uceta's Prettier::Table|https://gitlab.com/uzluisf/raku-pretty-table> which is itself a port of
the L<Kane Blueriver's PTable library for Python|https://github.com/kxxoling/PTable>.

=head1 Synopsis

B<Example 1>:

=begin code
use Prettier::Table;

my $table = Prettier::Table.new:
    title => "Australian Cities",
    field-names => ["City name", "Area", "Population", "Annual Rainfall"],
    sort-by => 'Area',
    align => %('City name' => 'l'),
;

given $table {
    .add-row: ["Adelaide",  1295,  1158259,  600.5 ];
    .add-row: ["Brisbane",  5905,  1857594,  1146.4];
    .add-row: ["Darwin",    112,   120900,   1714.7];
    .add-row: ["Hobart",    1357,  205556,   619.5 ];
    .add-row: ["Sydney",    2058,  4336374,  1214.8];
    .add-row: ["Melbourne", 1566,  3806092,  646.9 ];
    .add-row: ["Perth",     5386,  1554769,  869.4 ];
}

say $table;
=end code

Output:

<img alt="actual rendering" src="https://github.com/masukomi/Prettier-Table/blob/images/images/australian_cities.png?raw=true" />

(GitHub displays the raw text incorrectly)


=begin code

    ┌─────────────────────────────────────────────────┐
    │                Australian Cities                │
    ├───────────┬──────┬────────────┬─────────────────┤
    │ City name │ Area │ Population │ Annual Rainfall │
    ├───────────┼──────┼────────────┼─────────────────┤
    │ Darwin    │ 112  │   120900   │      1714.7     │
    │ Adelaide  │ 1295 │  1158259   │      600.5      │
    │ Hobart    │ 1357 │   205556   │      619.5      │
    │ Melbourne │ 1566 │  3806092   │      646.9      │
    │ Sydney    │ 2058 │  4336374   │      1214.8     │
    │ Perth     │ 5386 │  1554769   │      869.4      │
    │ Brisbane  │ 5905 │  1857594   │      1146.4     │
    └───────────┴──────┴────────────┴─────────────────┘
=end code


B<Example 2>:

=begin code
use Prettier::Table;

my $table = Prettier::Table.new;

given $table {
    .add-column('Planet', ['Earth', 'Mercury', 'Venus', 'Mars', 'Jupiter', 'Saturn', 'Uranus', 'Neptune']);
    .add-column('Position', [3, 1, 2, 4, 5, 6, 7, 8])
    .add-column('Known Satellites', [1, 0, 0, 2, 79, 82, 27, 14]);
    .add-column('Orbital period (days)', [365.256, 87.969, 224.701, 686.971, 4332.59, 10_759.22, 30_688.5, 60_182.0]);
    .add-column('Surface gravity (m/s)', [9.806, 3.7, 8.87, 3.721, 24.79, 10.44, 8.69, 11.15]);
}

$table.title('Planets in the Solar System');
$table.align(%(:Planet<l>));
$table.float-format(%('Orbital period (days)' => '-10.3f', 'Surface gravity (m/s)' => '-5.3f'));
$table.sort-by('Position');
# If you wish to change any of the characters used in the border
# you could do something like this.
# $table.junction-char('*');

put $table;
=end code

Output:

<img alt="actual rendering" src="https://github.com/masukomi/Prettier-Table/blob/images/images/planets_of_the_solar_system.png?raw=true" />

(GitHub displays the raw text incorrectly)
=begin code

    ┌────────────────────────────────────────────────────────────────────┐
    │                    Planets in the Solar System                     │
    ├─────────┬──────────┬───────────────────────┬───────────────────────┤
    │  Planet │ Position │ Orbital period (days) │ Surface gravity (m/s) │
    ├─────────┼──────────┼───────────────────────┼───────────────────────┤
    │  Earth  │    3     │        365.256        │         9.806         │
    │ Mercury │    1     │         87.969        │          3.7          │
    │  Venus  │    2     │        224.701        │          8.87         │
    │   Mars  │    4     │        686.971        │         3.721         │
    │ Jupiter │    5     │        4332.59        │         24.79         │
    │  Saturn │    6     │        10759.22       │         10.44         │
    │  Uranus │    7     │        30688.5        │          8.69         │
    │ Neptune │    8     │         60182         │         11.15         │
    └─────────┴──────────┴───────────────────────┴───────────────────────┘
=end code

=head1 Installation

Using zef:

=code zef install Prettier::Table

From source:

=begin code
$ git clone
$ cd raku-pretty-table
$ zef install .
=end code

=head1 Quickstart

C<Prettier::Table> supports two kinds of usage:

=head2 As a module

=begin code
use Prettier::Table;
my $x = Prettier::Table.new;
=end code

Check out the attributes in C<Prettier::Table> to see the full list of things that can be set / configured. Most notably the
C<*-char> attributes, used to control the look of the border.
Additionally, the named parameters in the C<get-string> method.

=head2 AUTHORS

=item L<Luis F Uceta's Prettier::Table|https://gitlab.com/uzluisf/raku-pretty-table>
=item L<masukomi|https://masukomi.org>

=head2 LICENSE
=para
MIT. See LICENSE file.

=end pod

#
# Pod related
#

# This subroutine is adapted to the way this module's Pod is structured so
# I wouldn't bet on its usefulness elsewhere. The reason I decided to do this is:
# 1) I didn't want all the documentation in the named pod at the top of the module
# 2) Even if I placed the named pod at the bottom, the way a module's Pod is
# parsed by default meant that declarator blocks wouldn't follow after the named pod.
# 3) Pod::EOD (https://github.com/hoelzro/p6-pod-eod) allows you to move the
# declarator blocks to the end of the module, however it leaves the headers
# and paragraphs introducing those code sections behind.
sub move-declarations-to-end($pod) {
     # get the named pod
     my $named-pod = $pod.cache
        .grep(* ~~ Pod::Block::Named && *.?name !=:= Nil)
        .grep(*.name eq 'pod').Array;

     # A code section is just a heading and paragraph followed by declarator blocks.
     my @code-sections = $pod.cache.grep( -> $part {
             $part ~~ Pod::Heading
         || ($part.?name !=:= Nil && $part.name eq 'para')
         ||  $part.?WHEREFORE !=:= Nil
     }).eager;

     $pod[0..*] = |$named-pod, |@code-sections;
}

DOC INIT {
    move-declarations-to-end($=pod);
}



