=begin pod

=head1 NAME

Form - Raku implementation of Perl-style fixed-width text formatting

=head1 SYNOPSIS

=begin code :lang<raku>
use Form;

my @items  = ('Widget A', 'Widget B');
my @prices = (9.99, 24.50);

print form
    'Item                   Price',
    '----------------------------',
    '{[[[[[[[[[[[[[[[[[[[} {]].[}',
    @items,                @prices;

# Item                   Price
# ----------------------------
# Widget A                9.99
# Widget B               24.50
=end code

=head1 DESCRIPTION

C<Form> provides a C<form()> function for creating fixed-width formatted text.
It takes alternating I<format strings> and I<data arguments>, fills each field in
the format with the corresponding data, and returns the completed text as a C<Str>.

=begin code :lang<raku>
my $text = form $format1, $datum1, $datum2,
                $format2, $datum3;
=end code

Like all Raku subroutines, C<form> can be called in scalar context (returns a string)
or void context (dies loudly, since the result would be lost).

=head1 CONCEPTS

=head2 Format

A string used as a template. It contains zero or more I<fields> — fixed-width
slots enclosed in braces — usually separated by literal characters.

=head2 Field

A fixed-width slot within a format string, enclosed in C<{ }>. The characters
inside determine the type, width, and justification of the data interpolated there.
Fields are designed to look like a stylised picture of the finished result:

=begin code
{<<<<<<<}    Justify text to the left
{>>>>>>>}               Justify text to the right
{>>>>>>>}                    Centre the text
{<<<<>>>}    Fully  justify  text  to  both  margins
=end code

=head2 Data

A scalar string, number, or array supplied after each format string. C<form> pulls
from each data argument in turn to fill the fields in the preceding format.

=head2 Line field vs Block field

A B<line field> interpolates only as much data as fits on a single line, then stops.
A B<block field> interpolates all its data over as many output lines as necessary.

=head2 Column

One character-width of horizontal space in the output.

=head1 FIELD TYPES

All fields are enclosed in C<{ }>. The characters inside the braces select the
field type and width; the opening C<{> and closing C<}> each contribute B<one>
column to the field width.

=head2 Text Fields

=head3 Left-justified

Data is padded with spaces on the right.

=begin code
{<<<<}     line field  — one line only; data truncated if wider than the field
{[[[[}     block field — data wraps across as many lines as needed
=end code

=begin code :lang<raku>
print form '{[[[[[[[[[}', 'The quick brown fox';
# The
# quick
# brown
# fox
=end code

=head3 Right-justified

Data is padded with spaces on the left.

=begin code
{>>>>}     line field
{]]]]]}    block field
=end code

=head3 Centred

Data is padded on both sides; any odd padding goes on the right.
Two equivalent syntaxes are supported:

=begin code
{>>><<<}   or   {|||||||}    line field
{]]][[[ }  or   {IIIIIII}    block field
=end code

=head3 Fully justified

Whitespace in the data is stretched to fill the field width evenly.
The final line of a block field is always left-justified.

=begin code
{<<<>>>>}    line field
{[[[]]]]]}   block field
=end code

=begin code :lang<raku>
print form '{<<<<<<<<<<<>>>>>>>>>>>>}',
           'A fellow of infinite jest';
# A  fellow  of  infinite
=end code

=head3 Verbatim

Data is copied exactly as-is, without any re-justification or word-wrapping.
Newlines in the data produce new lines in the output.

=begin code
{''''''''}    line field  — first line only
{"""""""""    block field — all lines preserved
=end code

=begin code :lang<raku>
print form '{""}', "line one\nline two";
# line one
# line two
=end code

=head2 Numeric Fields

A numeric field aligns a decimal number around a fixed-position decimal marker.
The integer part is right-justified; the fractional part is left-justified and
zero-padded to the declared number of decimal places.

=begin code
{>>>.<}     line field  — 3 integer columns, dot, 2 fractional columns
{]]].[}     block field - same
=end code

=head3 Field width and decimal places

The opening C<{> contributes B<one> position to the integer width, and the closing
C<}> contributes B<one> position to the fractional width. This makes the minimum
fractional width 1 (one decimal place) i.e. <0.0>, because the C<}> still provides
one column:

=begin code
{]].}      1 decimal place   ( 0 × [ + } - 00.0   )
{]].[}     2 decimal places  ( 1 × [ + } - 00.00  )
{]].[[}    3 decimal places  ( 2 × [ + } - 00.000 )
=end code

A bare trailing decimal such as C<0.> is not possible by design. This permits the butting
of neighboring numeric columns with 1 (or even 0) space as the gap, i.e. C<{].} {].}> for
C<0.1 0.2>.

=head3 Rounding and zero-padding

Numbers are B<rounded> to the declared number of decimal places and B<zero-padded>.
Any C<Real> subtype — C<Int>, C<Rat>, or C<Num> — is accepted:

=begin code :lang<raku>
say form '{]].[}', 5;        #    5.00   (Int  — zero-padded)
say form '{]].[}', 1.5;      #    1.50   (Rat  — zero-padded)
say form '{]].[}', 3.145;    #    3.15   (Rat  — rounded up)
say form '{]].[}', 3.144;    #    3.14   (Rat  — rounded down)
say form '{]].[}', 5e0;      #    5.00   (Num  — zero-padded)
=end code

=head3 Custom decimal marker

Any character that is not C<[>, C<]>, C<< < >>, C<< > >>, or C<+> may be used as
the decimal marker. The same marker is also recognised in string input data:

=begin code :lang<raku>
say form '{]]],[}', 1.23;       #    1,23   (comma marker)
say form '{]]],[}', '1,23';     #    1,23   (comma in input accepted too)
say form '{]]:}',   7.5;        #    7:5    (colon marker, 1dp)
=end code

=head3 Thousands separators

Include a separator character at the desired grouping position inside the integer
part of the field; C<form> infers the grouping pattern from its position. Five
major conventions are supported:

=begin code :lang<raku>
# Brittanic  — groups of 3, comma separator
say form '{],]]],]]].[}', 1234567.89;    #  1,234,567.89

# Continental  — groups of 3, period separator, comma decimal
say form '{].]]].]]],[}', 1234567.89;    #  1.234.567,89

# Subcontinental  — group of 3 then groups of 2
say form '{]],]],]]].[[}', 1234567.89;   # 12,34,567.890

# Hyperspatial  — space separator
say form '{] ]]] ]]].[}', 1234567.89;    #  1 234 567.89

# Asiatic  — groups of 4
say form '{]]]],]]]].[}', 1234567.89;    #   123,4567.89

# Swiss Army (apostrophe separator)
say form "{]']]]']]].[}", 1234567.89;    #  1'234'567.89
=end code

=head3 Overflow and invalid data

When the integer part is too wide for the available columns, the entire field
fills with C<#> characters. When data cannot be parsed as a number, the field
fills with C<?> characters:

=begin code :lang<raku>
say form '{]].[}', 9999.9;    # ###.##
say form '{]].[}', 'hello';   # ???.??
=end code

Negative numbers work naturally; the minus sign occupies one integer column:

=begin code :lang<raku>
say form '{]]].[}', -3.14;    #   -3.14
=end code

=head3 Sign-reserved fields

By default the minus sign shifts the digits right, so positive and negative
numbers do not align column-for-column. B<Sign-reserved> fields allocate a
fixed column for the sign so the digits stay aligned regardless of sign:

=begin code
{-]].[}      leading sign  — one sign column before the integer part
{]].[-}      trailing sign — one sign column after the fractional part
{(]].[)}     paren sign    — opening paren before, closing paren after
=end code

=begin code :lang<raku>
say form '{-]].[}',   3.14;    #    3.14   (space in sign column)
say form '{-]].[}',  -3.14;    # -  3.14   (minus in sign column)
say form '{]].[-}',   3.14;    #   3.14    (trailing space)
say form '{]].[-}',  -3.14;    #   3.14-   (trailing minus)
say form '{(]].[)}',  3.14;    #    3.14   (spaces around)
say form '{(]].[)}', -3.14;    # (  3.14)  (parens around)
=end code

The sign column is included in the total field width. On overflow the sign
column is preserved and the numeric columns fill with C<#>:

=begin code :lang<raku>
say form '{-]].[}',  9999.9;    #  ###.##
say form '{-]].[}', -9999.9;    # -###.##
=end code

=head2 Vertical Alignment

When a format row mixes block fields that produce different numbers of output
lines, C<form> pads the shorter columns with blank lines to match the tallest.
An alignment modifier before or after the field type controls where the data
appears within that padding.

=head3 Top (default)

Data appears at the top; blank lines fill below. This is the default — no
special character is needed.

=head3 Middle

Prefix the field type with C<=> (or suffix, or both). Data is centred
vertically; odd padding goes below.

=begin code
{=[[[[[[[}    or    {[[[[[[[=}    middle-aligned block field
=end code

=head3 Bottom

Prefix the field type with C<_> (or suffix, or both). Data is pushed to the
bottom of the padded column.

=begin code
{_[[[[[[[}    or    {[[[[[[[_}    bottom-aligned block field
=end code

=head1 DATA ARGUMENTS

Scalar values (C<Str>, C<Int>, C<Rat>, C<Num>, …) and C<Array>s may be passed
as data. Arrays are not flattened — each array is bound to its corresponding field
and its elements are consumed one per output row:

=begin code :lang<raku>
my @names  = <Alice Bob Carol>;
my @scores = (98, 74, 85);

print form '{<<<<<<<<<} {]].[}',
           @names,      @scores;
# Alice       98.00
# Bob         74.00
# Carol       85.00
=end code

Each array element provides one datum; block fields consume one element per row
and may wrap that element over multiple lines.

=head1 MULTIPLE FORMAT STRINGS

A single call to C<form> may contain any number of format / data pairs.
C<form> processes them in order and concatenates the results into one string:

=begin code :lang<raku>
print form
    'Invoice: {<<<<<}',    $id,
    'Date:    {<<<<<<<<}', $date,
    '----------------------------',
    '{[[[[[[[[[[[[} {]]].[[}',
    @descriptions, @amounts;
=end code

Literal strings (format strings with no fields) contribute their text directly,
one line per call.

=head1 INTERLEAVE

The C<:interleave> adverb lets you write the entire template as a single
multi-line string and supply all data arguments after it, rather than
alternating format / data pairs:

=begin code :lang<raku>
print form :interleave,
    q:to/END/,
Invoice: {<<<<<}
Date:    {<<<<<<<<}
--------------------
{[[[[[[[[[[[[[[[[[} {]]]].[}
--------------------
END
    $id, $date,
    @descriptions, @amounts;
=end code

C<form> splits the template on newlines and processes each line in order,
consuming data arguments left-to-right across all rows. Lines with no fields
are output as literals.

=head1 FIELD REFERENCE TABLE

    Field type            Line field        Block field
    ──────────────────    ──────────────    ──────────────
    left-justified        {<<<<<<<}         {[[[[[[[}
    right-justified       {>>>>>>>}         {]]]]]]]}`
    centred               {>>><<<}          {]]][[[ }
    centred (alt)         {|||||||}         {IIIIIII}
    fully justified       {<<<>>>>}         {[[[]]]]}`
    verbatim              {'''''''}         {"""""""}

    numeric               {>>>.<<}          {]]].[[}
    euronumeric           {>>>,<<}          {]]],[{}
    comma'd (Brittanic)   {>,>>>,>>>.<<}    {],]]],]]].[{}
    space'd (Hyperspatial){> >>> >>>.<<}    {] ]]] ]]].[{}
    subcontinental        {>>,>>,>>>.<<}    {]],]],]]].[{}
    eurocomma (Continen.) {>.>>>.>>>,<<}    {].]]].]]],[{}
    Swiss Army apostrophe {>'>>>'>>.<<}     {]']]]']]].[{}
    Asiatic               {>>,>>>>>.<<}     {]],]]]]].[{}

    signed leading        {->>.<<}          {-]].[}
    signed trailing       {>>.<-}           {]].[-}
    signed paren          {(>>.<)}          {(]].[)}

    left/middled          {=<<<<<<<}        {=[[[[[[[}
    right/middled         {=>>>>>>>}        {=]]]]]]]}`
    left/bottomed         {_<<<<<<<}        {_[[[[[[[}
    right/bottomed        {_>>>>>>>}        {_]]]]]]]}`

=head1 AUTHORS

Matthew Walton, Stephen Roe

Source: L<https://github.com/raku-community-modules/Form>

=head1 COPYRIGHT AND LICENSE

Copyright 2009 - 2012 Matthew Walton

Copyright 2013 - 2026 Raku Community

This library is free software; you can redistribute it and/or modify it under
the Artistic License 2.0.

=begin comment
────────────────────────────────────────────────────────
Rendering this file as Markdown
────────────────────────────────────────────────────────

Via mi6 (generates README.md):
  In dist.ini, under [ReadmeFromPod]:
    enable   = true              # change from false to enable
    filename = docs/Form.rakudoc # already points here

  Then run:  mi6 build

Manually (any output path):
  raku --doc=Markdown docs/Form.rakudoc > README.md

Note: requires Pod::To::Markdown to be installed:
  zef install Pod::To::Markdown
────────────────────────────────────────────────────────
=end comment

=end pod
