Packages

Rapport aims to provide a robust set of modules to generate HTML reports that both looks good in the browser and when being printed.

Current section

Files

Jump to
rapport lib rapport.ex
Raw

lib/rapport.ex

defmodule Rapport do
@moduledoc """
Rapport aims to provide a robust set of modules to generate
HTML reports that both looks good in the browser and when being printed.
"""
alias Rapport.Report
alias Rapport.Page
alias Rapport.PageNumbering
@normalize_css File.read!(Path.join(__DIR__, "base_template/normalize.css"))
@paper_css File.read!(Path.join(__DIR__, "base_template/paper.css"))
@base_template File.read!(Path.join(__DIR__, "base_template/base_template.html.eex"))
@spec add_page(Report.t(), String.t(), map) :: Report.t()
defdelegate add_page(report, page_template, fields), to: Page
@spec add_page(Report.t(), Page.t()) :: Report.t()
defdelegate add_page(report, page), to: Page
@spec add_pages(Report.t(), list(Page.t())) :: Report.t()
defdelegate add_pages(report, pages), to: Page
@spec generate_pages([Page.t()], Report.padding()) :: String.t()
@spec generate_pages([Page.t()], Report.padding(), Rapport.PageNumbering.t()) :: String.t()
defdelegate generate_pages(pages, padding), to: Page
defdelegate generate_pages(pages, padding, page_number_opts), to: Page
@spec add_page_numbers(Report.t()) :: Report.t()
@spec add_page_numbers(Report.t(), atom, any) :: Report.t()
defdelegate add_page_numbers(report, page_number_position, formatter), to: PageNumbering
@spec add_page_numbers(Report.t(), atom) :: Report.t()
defdelegate add_page_numbers(report, page_number_position), to: PageNumbering
defdelegate add_page_numbers(report), to: PageNumbering
@spec new(String.t(), map()) :: Report.t()
@doc """
Creates a new report.
An optional EEx template can be passed to the `new` function. This template
is meant to hold global things like styles, fonts etc that can be used on all
pages thats added to the report.
The `new` function sets the default paper size to `:A4`, the rotation
to `:portrait`, the page padding to 10mm and the report title to "Report".
Those defaults can easily be overridden by using `set_paper_size/2`,
`set_rotation/2`, `set_padding/2` and `set_title/2`.
Returns a `Rapport.Report` struct.
## Options
* `template` - An optional EEx template for the report.
* `fields` - A map with fields to assign to the EEx report template
"""
def new(template \\ "", fields \\ %{}) do
%Report{
title: "Report",
paper_size: :A4,
rotation: :portrait,
pages: [],
template: template,
padding: 10,
fields: fields,
page_number_opts: %PageNumbering{
add_page_numbers: false,
page_number_position: :bottom_right,
page_number_formatter: fn cnt_page, _tot_pages -> "#{cnt_page}" end
}
}
end
@spec set_title(Report.t(), String.t()) :: Report.t()
@doc """
Sets the title for a report. This is the title of the generated html report.
## Options
* `report` - The `Rapport.Report` you want to set the title for.
* `title` - The new title
"""
def set_title(%Report{} = report, title) when is_binary(title) do
Map.put(report, :title, title)
end
@spec set_paper_size(
Report.t(),
Report.paper_size()
) :: Report.t()
@doc """
Sets the paper size for the report.
It expects the paper size to be an atom and must be
`:A4`, `:A3`, `:A5`, `:half_letter`, `:letter`, `:legal`, `:junior_legal`
or `:ledger`, otherwise `ArgumentError` will be raised.
## Options
* `report` - The `Rapport.Report` that you want set the paper size for
* `paper_size` - The paper size.
"""
def set_paper_size(%Report{} = report, paper_size) do
validate_list(
paper_size,
[:A4, :A3, :A5, :half_letter, :letter, :legal, :junior_legal, :ledger],
"Invalid paper size"
)
Map.put(report, :paper_size, paper_size)
end
@spec set_rotation(Report.t(), Report.rotation()) :: Report.t()
@doc """
Sets the rotation for the report.
It expects the rotation to an atom and must be `:portrait` or `:landscape`,
otherwise `ArgumentError` will be raised.
## Options
* `report` - The `Rapport.Report` that you want set the rotation for
* `rotation` - The rotation.
"""
def set_rotation(%Report{} = report, rotation) do
validate_list(rotation, [:portrait, :landscape], "Invalid rotation")
Map.put(report, :rotation, rotation)
end
@spec set_padding(Report.t(), Report.padding()) :: Report.t()
@doc """
Sets the padding (in millimeters) for the report.
It expects the padding to be an integer and must be `10`, `15`, `20` or `25` mm,
otherwise `ArgumentError` will be raised.
## Options
* `report` - The `Rapport.Report` that you want set the padding for
* `rotation` - The padding.
"""
def set_padding(%Report{} = report, padding) when is_integer(padding) do
validate_list(padding, [10, 15, 20, 25], "Invalid padding")
Map.put(report, :padding, padding)
end
@spec generate_html(Report.t()) :: String.t()
@doc """
Generates HTML for the report.
## Options
* `report` - The `Rapport.Report` that you want to generate to HTML.
"""
def generate_html(%Report{} = report) do
paper_settings = paper_settings_css(report)
add_page_numbers? = report.page_number_opts.add_page_numbers
pages =
case add_page_numbers? do
true -> generate_pages(report.pages, report.padding, report.page_number_opts)
false -> generate_pages(report.pages, report.padding)
end
report_template = EEx.eval_string(report.template, assigns: report.fields)
assigns = [
title: report.title,
paper_settings: paper_settings,
normalize_css: @normalize_css,
paper_css: @paper_css,
pages: pages,
report_template: report_template
]
EEx.eval_string(@base_template, assigns: assigns)
end
@spec save_to_file(Report.t(), binary) :: :ok
@doc """
Convenient function to save a report to file.
## Options
* `report` - The `Rapport.Report` that you want to save to a HTML file
* `file_path` - The path to the HTML file you want to save.
"""
def save_to_file(%Report{} = report, file_path) when is_binary(file_path) do
html_report = generate_html(report)
File.write!(file_path, html_report)
end
defp paper_settings_css(%Report{} = report) do
paper_size = Atom.to_string(report.paper_size)
rotation = Atom.to_string(report.rotation)
if rotation == "portrait", do: paper_size, else: "#{paper_size} #{rotation}"
end
@spec validate_list(any, list(), String.t()) :: nil
@doc false
def validate_list(what, list, msg) do
if what not in list, do: raise(ArgumentError, message: msg)
end
end