Data::Report(3pm)

πŸ“¦
version 1.001 Β· 6 versions
, in ubuntu noble, ubuntu resolute, ubuntu stonking

πŸŽ€ Data::Report πŸ’–

okay bestie, gather round, because we're about to get into Data::Report β€” the Perl module that's basically the main character of the reporting world. it's part of the User Contributed Perl Documentation crew, and no cap, it does a LOT. this is part 1 of 3, so hydrate and let's go ✨


🌈 NAME 🌈

Data::Report β€” a whole framework for flexible reporting, periodt. πŸ’…


✨ SYNOPSIS ✨

here's the vibe in code form, copied exactly because we do not mess with the receipts:

use Data::Report;

# Create a new reporter.
my $rep = Data::Report::->create(type => "text"); # or "html", or "csv", ...

# Define the layout.
$rep->set_layout
  ([ { name => "acct", title => "Acct",        width => 6  },
     { name => "desc", title => "Description", width => 40, align => "<" },
     { name => "deb",  title => "Debet",       width => 10, align => ">" },
     { name => "crd",  title => "Credit",      width => 10, align => ">" },
   ]);

# Start the reporter.
$rep->start;

# Add data, row by row.
$rep->add({ acct => 1234, desc => "Received", deb => "242.33"                  });
$rep->add({ acct => 5678, desc => "Paid",                      crd => "699.45" });
$rep->add({ acct => 1259, desc => "Taxes",    deb =>  "12.00", crd => "244.00" });
$rep->add({               desc => "TOTAL",    deb => "254.33", crd => "943.45" });

# Finish the reporter.
$rep->finish;

like… look at that. clean. organized. iconic. πŸ’–


πŸ’œ DESCRIPTION πŸ’œ

so Data::Report is a flexible, plugin-driven reporting framework. it makes defining reports that can pop out as text, HTML and CSV genuinely easy. and the textual ornaments β€” extra empty lines, dashed lines, cell lines β€” can be added in a way that's similar to HTML style sheets. it's giving ✨CSS but make it a report✨.

the Data::Report framework is made of three parts, and we're gonna meet all of them:

  • The plugins β€” plugins implement a specific type of report. the standard ones you get out of the box are Data::Report::Plugin::Text for textual reports, Data::Report::Plugin::Html for HTML reports, and Data::Report::Plugin::Csv for CSV (comma-separated) files.

and here's the thing, bestie β€” users can, and are actively encouraged, to develop their own plugins to handle different styles and types of reports. go off, build your own. πŸ‘‘

  • The base class β€” the base class Data::Report::Base implements the functionality that's common to all reporters, plus a bunch of utility functions the plugins can use. it's the supportive bestie holding everything together. 🫢
  • The factory β€” the actual Data::Report module is a factory 🏭 that creates a reporter for a given report type by picking the right plugin and returning an instance of it. matchmaker energy, honestly.

πŸ’… BASIC METHODS πŸ’…

quick heads up before we dive in: except for the create method, all the other methods are actually handled by the plugins and their base class. so Data::Report itself is lowkey just the front door, iykyk. πŸšͺ

🌸 create

reporter objects get created with the class method create. it takes a hash (or a hashref) of arguments to initialise the reporter object. simple as.

the actual reporter object is implemented by one of the plugin modules, and which one gets picked is decided by the type argument. standard plugins are provided for the "text", "HTML" and "CSV" report types. and if you don't say anything? the default type is "text". πŸ“

when it's hunting for a plugin to support report type "foo", the create method will first try to load a module My::Package::Foo, where My::Package is the invocant class. if that module can't be loaded, it falls back to Data::Report::Plugin::Foo. also note: unless you've subclassed it, the current class is gonna be Data::Report.

all the other initialisation arguments line up with attribute setting methods provided by the plugins. so this hypothetical call:

my $rpt = Data::Report->create(foo => 1, bar => "Hello!");

is literally identical to:

my $rpt = Data::Report->create;
$rpt->set_foo(1);
$rpt->set_bar("Hello!");

and you can mix and match any combination you want, at your convenience. your report, your rules. πŸ’β€β™€οΈ

🌸 start

this method is basically the "okay we're SO ready" signal β€” it says all the setup is done and starts the reporter. important: no output is generated until the add method is called. so don't panic if it's quiet.

start takes no arguments.

could this method have been eliminated by just auto-starting the reporter on the first call to add? sure. but it turns out that an explicit start makes the API much cleaner and makes it way easier to catch mistakes. so it stays. 🎯

🌸 add

this method adds a new entry to the report. it takes one single argument: a hash ref of column names and their corresponding values. any columns you leave out just get left blank β€” no drama. πŸ•³οΈ

on top of the column names and values, you can also toss in the special key _style to designate a particular style for this entry. what that actually means depends on whichever plugin implements the reporter. for example, the standard HTML reporter plugin prefixes the given style with "r_" to form the class name for the row. and the style name should be a simple name β€” letters, digits and underscores, starting with a letter. keep it cute, keep it simple. ✨

Example

$rpt->add({ date   => "2006-04-31",
            amount => 1000,
            descr  => "First payment",
            _style => "plain" });

🌸 finish

this method says report generation is complete. and once you've done that, you can call start again to kick off a whole new report. recycling queen. ♻️

finish takes no arguments.

🌸 close

this one's a convenience method. if the output stream was set up by the reporter itself (peep "set_output", below), then the stream gets closed. otherwise, this method is a total no-op β€” it just chills. πŸ§˜β€β™€οΈ

close takes no arguments.

πŸ’– ATTRIBUTE HANDLING METHODS πŸ’–

okay so this is the part where you make your report actually look like something, bestie. layouts, styles, headings, widths β€” all the aesthetic decisions live here. let's go. πŸŽ€


get_type πŸ¦‹

The reporter type.

set_layout πŸ‘‘

This is the most important attribute, no exaggeration β€” it literally defines the report layout.

It takes one argument: an array reference. Every element of that array is a hash reference that corresponds to one column in the report. The order of elements defines the order of the columns in the report, but bestie please also check out set_fields below. ✨

The following keys are possible in the hash reference:

  • name β€” the name of this column. Keep it a simple name: letters, digits and underscores, starting with a letter.

The standard HTML reporter plugin uses the column name to form a class name for each cell by prefixing with c_. Likewise, the classes for the table headings will be formed by prefixing the column names with h_. See ADVANCED EXAMPLES, below. πŸ’…

  • title β€” the title of this column. This title is placed in the column heading.
  • width β€” the width of this column. Relevant for textual reporters only.

By default, if a value does not fit in the given width, it will be spread over multiple rows in a pseudo-elegant way. See also the truncate key, below.

  • align β€” the alignment of this column. This can be either "<" for left-aligned columns, or ">" to indicate a right-aligned column.
  • truncate β€” if true, the values in this column will be truncated to fit the width of the column. Relevant for textual reporters only.

set_style 🌸

This one lets you set an arbitrary style (a string) whose meaning depends on the implementing plugin. Like, an HTML plugin could totally use this as the name of the style sheet to use.

The name should be a simple name, containing letters, digits and underscores, starting with a letter.

get_style 🧚

Returns the style, or default if none.

set_output πŸ“¬

Designates the destination for the report. The argument can be:

  • a SCALAR reference β€” all output will be appended to the designated scalar.
  • an ARRAY reference β€” all output lines will be pushed onto the array.
  • a SCALAR β€” a file will be created with the given name, and all output will be written to this file. To close the file, use the close method described above.
  • anything else β€” anything else will be considered to be a file handle, and treated as such.

set_stylist πŸ‘‘

The stylist is a powerful method to control the appearance of the report at the row and cell level. The basic idea is taken from HTML style sheets. By using a stylist, it is possible to add extra spaces and lines to rows and cells in a declarative way. iconic behaviour, honestly.

When used, the stylist should be a reference to a possibly anonymous subroutine with three arguments: the reporter object, the style of a row (as specified with _style in the add method), and the name of a column as defined in the layout. For table headings, the row name _head is used.

The stylist routine will be repeatedly called by the reporter to obtain formatting properties for rows and cells. It should return either nothing, or a hash reference with properties.

  • When called with only the row argument, it should return the properties for this row.
  • When called with row equal to "*" and a column name, it should return the properties for the given column.
  • When called with a row and a column name, it should return the properties for the given row/column (cell).

All appropriate properties are merged to form the final set of properties to apply. ✨

The following row properties are recognised. Between parentheses the backends that support them.

  • skip_before β€” (Text) Produce an empty line before printing the current row.
  • skip_after β€” (Text) Produce an empty line after printing the current row, but only if other data follows.
  • line_before β€” (Text) Draw a line of dashes before printing the current row.
  • line_after β€” (Text) Draw a line of dashes after printing the current row.
  • cancel_skip β€” (Text) Cancel the effect of a pending skip_after
  • ignore β€” (All) Ignore this row. Useful for CSV backends where only the raw data matters, and not the totals and such.

The following cell properties are recognised. Between parentheses the backends that support them.

  • indent β€” (Text) Indent the contents of this cell with the given amount.
  • wrap_indent β€” (Text) Indent wrapped contents of this cell with the given amount.
  • truncate β€” (Text) If true, truncate the contents of this cell to fit the column width.
  • line_before β€” (Text) Draw a line in the cell before printing the current row. The value of this property indicates the symbol to use to draw the line. If it is 1, dashes are used.
  • line_after β€” (Text) Draw a line in the cell after printing the current row. The value of this property indicates the symbol to use to draw the line. If it is 1, dashes are used.
  • raw_html β€” (Html) Do not escape special HTML characters, allowing pre-prepared HTML code to be placed in the output. Use with care. ⚠️
  • ignore β€” (All) Ignore this column. Note that to prevent surprising results, the column must be ignored in all applicable styles, including the special style _head that controls the heading.
  • class β€” (Html) Class name to be used for this cell. Default class name is h_CNAME for table headings and c_CNAME for table rows, where CNAME is the name of the column.

Example:

$rep->set_stylist(sub {
  my ($rep, $row, $col) = @_;

  unless ( $col ) {
      return { line_after => 1 } if $row eq "total";
      return;
  }
  return { line_after => 1 } if $col eq "amount";
  return;
});

Each reporter provides a standard (dummy) stylist called _std_stylist. Overriding this method is equivalent to using set_stylist.

get_stylist πŸ’œ

Returns the current stylist, if any.

set_topheading πŸŽ€

Headings consist of two parts: the top heading, and the standard heading. By default, the top heading is empty, and the standard heading has the names of the columns with a separator line (dependent on the plugin used).

This method can be used to designate a subroutine that will provide the top heading of the report.

Example:

$rpt->set_topheading(sub {
  my $self = shift;
  $self->_print("Title line 1\n");
  $self->_print("Title line 2\n");
  $self->_print("\n");
});

Note the use of the reporter provided _print method to produce output. πŸ“

When subclassing a reporter, a method _top_heading can be defined to provide the top heading. This is equivalent to an explicit call to set_topheading, but doesn't need to be repeatedly and explicitly executed for each new reporter.

get_topheading 🌈

Returns the current top heading routine, if any.

set_heading ⭐

This method can be used to designate a subroutine that provides the standard heading of the report.

In normal cases using this method is not necessary, since setting the top heading will be sufficient.

Each reporter plugin provides a standard heading, implemented in a method called _std_header. This is the default value for the heading attribute. A user-defined heading can use

$self->SUPER::_std_header;

to still get the original standard heading produced.

Example:

$rpt->set_heading(sub {
  my $self = shift;
  $self->_print("Title line 1\n");
  $self->_print("Title line 2\n");
  $self->_print("\n");
  $self->SUPER::_std_heading;
  $self->_print("\n");
});

Note the use of the reporter provided _print method to produce output. πŸ’…

When subclassing a reporter, the method _std_heading can be overridden to provide a customized top heading. This is equivalent to an explicit call to set_topheading, but doesn't need to be repeatedly and explicitly executed for each new reporter.

get_heading 🫢

Returns the current standard heading routine, if any.

set_fields πŸ‘‘

This method can be used to define what columns (fields) should be included in the report and the order they should appear. It takes an array reference with the names of the desired columns.

Example:

$rpt->set_fields([qw(descr amount date)]);

get_fields πŸ“

Returns the current set of selected columns.

set_width πŸ“

This method defines the width for one or more columns. It takes a hash reference with column names and widths. The width may be an absolute number, a relative number (to increase or decrease the width), or a percentage.

Example:

$rpt->set_width({ amount => 10, desc => '80%' });

get_widths ✨

Returns a hash with all column names and widths.

✨ ADVANCED EXAMPLES ✨

okay bestie this is where it gets delicious. πŸ’… We're gonna subclass Data::Report and give it a whole plugin for the type "text". And look β€” notice how we're overriding _top_heading and _std_stylist to hand this reporter its own special little defaults. Main character customization energy fr fr.

package POC::Report;

use base qw(Data::Report);

package POC::Report::Text;

use base qw(Data::Report::Plugin::Text);

sub _top_heading {
    my $self = shift;
    $self->_print("Title line 1\n");
    $self->_print("Title line 2\n");
    $self->_print("\n");
}

sub _std_stylist {
    my ($rep, $row, $col) = @_;

    if ( $col ) {
        return { line_after => "=" }
          if $row eq "special" && $col =~ /^(deb|crd)$/;
    }
    else {
        return { line_after => 1 } if $row eq "total";
    }
    return;
}

And you use it like this, bestie:

my $rep = POC::Report::->create(type => "text");

$rep->set_layout
  ([ { name => "acct", title => "Acct",   width => 6  },
     { name => "desc", title => "Report", width => 40, align => "<" },
     { name => "deb",  title => "Debet",  width => 10, align => "<" },
     { name => "crd",  title => "Credit", width => 10, align => ">" },
   ]);

$rep->start;

$rep->add({ acct => "one", desc => "two", deb => "three", crd => "four", _style => "normal" });
$rep->add({ acct => "one", desc => "two", deb => "three", crd => "four", _style => "normal" });
$rep->add({ acct => "one", desc => "two", deb => "three", crd => "four", _style => "special"});
$rep->add({ acct => "one", desc => "two", deb => "three", crd => "four", _style => "total"  });

$rep->finish;

and the output? iconic. ✨

Title line 1
Title line 2

Acct                                      Report  Debet           Credit
------------------------------------------------------------------------
one                                          two  three             four
one                                          two  three             four
one                                          two  three             four
                                                  ==========  ==========
one                                          two  three             four
------------------------------------------------------------------------

Now πŸŽ€ same vibe, but make it HTML. Watch this:

package POC::Report;

use base qw(Data::Report);

package POC::Report::Html;

use base qw(Data::Report::Plugin::Html);

sub start {
    my $self = shift;
    $self->{_title1} = shift;
    $self->{_title2} = shift;
    $self->{_title3} = shift;
    $self->SUPER::start;
}

sub _top_heading {
    my $self = shift;
    $self->_print("<html>\n",
                  "<head>\n",
                  "<title>", $self->_html($self->{_title1}), "</title>\n",
                  '<link rel="stylesheet" href="css/', $self->get_style, '.css">', "\n",
                  "</head>\n",
                  "<body>\n",
                  "<p class=\"title\">", $self->_html($self->{_title1}), "</p>\n",
                  "<p class=\"subtitle\">", $self->_html($self->{_title2}), "<br>\n",
                  $self->_html($self->{_title3}), "</p>\n");
}

sub finish {
    my $self = shift;
    $self->SUPER::finish;
    $self->_print("</body>\n</html>\n");
}

Real talk: it defines an alternative start method, and that's the one you use to pass in the extra parameters for your title fields. 🫢

And the method _html? That's a lil convenience method the framework hands you for free. It takes its argument and returns it with the sensitive characters escaped into HTML entities. so cute, so safe. πŸ’–

Use it like so:

package main;

my $rep = POC::Report::->create(type => "html");

$rep->set_layout
  ([ { name => "acct", title => "Acct",   width => 6  },
     { name => "desc", title => "Report", width => 40, align => "<" },
     { name => "deb",  title => "Debet",  width => 10, align => "<" },
     { name => "crd",  title => "Credit", width => 10, align => ">" },
   ]);

$rep->start(qw(Title_One Title_Two Title_Three_Left&Right));

$rep->add({ acct => "one", desc => "two", deb => "three", crd => "four", _style => "normal" });
$rep->add({ acct => "one", desc => "two", deb => "three", crd => "four", _style => "normal" });
$rep->add({ acct => "one", desc => "two", deb => "three", crd => "four", _style => "normal" });
$rep->add({ acct => "one", desc => "two", deb => "three", crd => "four", _style => "total"  });

$rep->finish;

and the output will look like this 🌈:

<html>
<head>
<title>Title_One</title>
<link rel="stylesheet" href="css/default.css">
</head>
<body>
<p class="title">Title_One</p>
<p class="subtitle">Title_Two<br>
Title_Three_Left&amp;Right</p>
<table class="main">
<tr class="head">
<th align="left" class="h_acct">Acct</th>
<th align="left" class="h_desc">Report</th>
<th align="right" class="h_deb">Debet</th>
<th align="right" class="h_crd">Credit</th>
</tr>
<tr class="r_normal">
<td align="left" class="c_acct">one</td>
<td align="left" class="c_desc">two</td>
<td align="right" class="c_deb">three</td>
<td align="right" class="c_crd">four</td>
</tr>
<tr class="r_normal">
<td align="left" class="c_acct">one</td>
<td align="left" class="c_desc">two</td>
<td align="right" class="c_deb">three</td>
<td align="right" class="c_crd">four</td>
</tr>
<tr class="r_normal">
<td align="left" class="c_acct">one</td>
<td align="left" class="c_desc">two</td>
<td align="right" class="c_deb">three</td>
<td align="right" class="c_crd">four</td>
</tr>
<tr class="r_total">
<td align="left" class="c_acct">one</td>
<td align="left" class="c_desc">two</td>
<td align="right" class="c_deb">three</td>
<td align="right" class="c_crd">four</td>
</tr>
</table>
</body>
</html>

Also bestie, go peek at the examples in t/09poc*.t. they're right there. πŸ‘€


πŸ‘‘ AUTHOR πŸ‘‘

Johan Vromans, "<jvromans at squirrel.nl>"

the literal icon who wrote all this. respect. 🫑


πŸ’€ BUGS πŸ’€

okay so, disclaimer time, no cap: this module got derived from actual working code that the author turned into a generic CPAN module. During that glow-up, some features may have become unstable 😭 β€” but that'll be cured in time, fr. Also it is possible that revisions of the API will be necessary when new functionality gets added. So like… vibe check your versions, bestie.

Please report any bugs or feature requests to "bug-data-report at rt.cpan.org", or through the web interface at http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Data-Report. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes. group chat behavior but make it a bug tracker. 🫢


πŸŽ€ SUPPORT πŸŽ€

Development of this module takes place on GitHub: https://github.com/sciurius/perl-Data-Report.

After installing, you can find documentation for this module with the perldoc command.

perldoc Data::Report

Please report any bugs or feature requests using the issue tracker on GitHub.


🌸 ACKNOWLEDGEMENTS 🌸

(no content here in the original β€” just the heading, sitting there, being mysterious. πŸ‘»)


Copyright 2006,2008,2020 Squirrel Consultancy, all rights reserved.

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


and that's the whole page bestie, we made it πŸ¦‹βœ¨ this was Data::Report(3pm), shipped as part of perl v5.36.0, dated 2022-10-14 β€” go forth and make pretty reports, you absolute legend. πŸ’œπŸŽ€πŸ”₯

πŸ“¦ where this page comes from