#let ex(input) = {
  set align(center);
  block(
  fill: luma(97%),
  inset: 8pt,
  radius: 4pt,
  breakable: false,
  [
    #set align(left);
    #input
  ])
}

#let snippet(filename) = {[
  #let version = toml("typst.toml").package.version;
  #let snippet-code-path = "doc/example-snippets/" + filename + ".typ"
  #let snippet-image-path = "doc/compiled-snippets/" + filename + ".svg"

  #let data = xml(snippet-image-path).first();
  #let size = (
    height: float(data.attrs.height) * 1pt ,
    width: float(data.attrs.width) * 1pt,
  );

  #let content = read(snippet-code-path).replace("\r", "").replace("<<VERSION>>", version)
  #ex(raw(content, block: true, lang: "typ"))
  #ex(image(snippet-image-path, ..size))
]}

#let snippet-quiet(filename) = {[
  #let version = toml("typst.toml").package.version;
  #let snippet-code-path = "doc/example-snippets/" + filename + ".typ"
  #let content = read(snippet-code-path).replace("\r", "").replace("<<VERSION>>", version)
  #ex(raw(content, block: true, lang: "typ"))
]}

#let no-break(content) = {
  block(breakable: false, width:100%, content)
}

#let subsection(name) = {
  [== #name ]
}

#let label-text(content) = {
  label(lower(content.text.replace(" ", "-")))
}

#let section(name) = {
  [= #name #label-text(name)]
}

#let subsection(name) = {
  [== #name #label-text(name)]
}

#no-break([

= Tabut

_Powerful, Simple, Concise_

A Typst plugin for turning data into tables.

== Outline

#set list(marker: ([•], [◦]))

#let sections = (
  (name: [Examples], subs: (
    [Input Format and Creation],
    [Basic Table],
    [Table Styling],
    [Header Formatting],
    [Remove Headers],
    [Cell Expressions and Formatting],
    [Index],
    [Transpose],
    [Alignment],
    [Column Width],
    [Get Cells Only],
    [Use with Tablex],
  )),
  (name: [Data Operation Examples], subs: (
    [CSV Data],
    [Slice],
    [Sorting and Reversing],
    [Filter],
    [Aggregation using Map and Sum],
    [Grouping]
  )),
  (name: [Function Definitions], subs: (
    [`tabut`],
    [`tabut-cells`],
    [`rows-to-records`],
    [`records-from-csv`],
    [`group`],
  )),
)

#{
  let items = ();
  for s in sections {
    let s-items = ();
    for ss in s.subs {
      s-items.push(link(label-text(ss), ss));
    }
    items.push([
      #link(label-text(s.name), s.name)
      #list(..s-items)
    ])
  }
  list(..items, tight: true)
}

]) 

#pagebreak(weak: true)

#no-break([

#section([Examples])

]) #no-break([

#subsection([Input Format and Creation])

The `tabut` function takes input in "record" format, an array of dictionaries, with each dictionary representing a single "object" or "record".

In the example below, each record is a listing for an office supply product.

#snippet-quiet("example-data/supplies")

]) #no-break([

#subsection([Basic Table])

Now create a basic table from the data. 

#snippet("basic")

`funct` takes a function which generates content for a given cell corrosponding to the defined column for each record. 
`r` is the record, so `r => r.name` returns the `name` property of each record in the input data if it has one.

]) #no-break([

The philosphy of `tabut` is that the display of data should be simple and clearly defined, 
therefore each column and it's content and formatting should be defined within a single clear column defintion.
One consequence is you can comment out, remove or move, any column easily, for example:

#snippet("rearrange")

]) #no-break([

#subsection([Table Styling])

Any default Table style options can be tacked on and are passed to the final table function.

#snippet("styling")

]) #no-break([

#subsection([Header Formatting])

You can pass any content or expression into the header property.

#snippet("title")

]) #no-break([

#subsection([Remove Headers])

You can prevent from being generated with the `headers` paramater. This is useful with the `tabut-cells` function as demonstrated in it's section.

#snippet("no-headers")

]) #no-break([

#subsection([Cell Expressions and Formatting])

Just like the headers, cell contents can be modified and formatted like any content in Typst.

#snippet("format")

]) #no-break([

You can have the cell content function do calculations on a record property.

#snippet("calculation")

]) #no-break([

Or even combine multiple record properties, go wild.

#snippet("combine")

]) #no-break([

#subsection([Index])

`tabut` automatically adds an `_index` property to each record.

#snippet("index")

You can also prevent the `index` property being generated by setting it to `none`, 
or you can also set an alternate name of the index property as shown below.

#snippet("index-alternate")

]) #no-break([

#subsection([Transpose])

This was annoying to implement, and I don't know when you'd actually use this, but here.

#snippet("transpose")

]) #no-break([

#subsection([Alignment])

#snippet("align")

You can also define Alignment manually as in the the standard Table Function.

#snippet("align-manual")

]) #no-break([

#subsection([Column Width])

#snippet("width")

You can also define Columns manually as in the the standard Table Function.

#snippet("width-manual")

]) #no-break([

#subsection([Get Cells Only])

#snippet("only-cells")

]) #no-break([

#subsection([Use with Tablex])

#snippet("tablex")

]) 

#pagebreak(weak: true)

#no-break([

#section([Data Operation Examples])

While technically seperate from table display, the following are examples of how to perform operations on data before it is displayed with `tabut`.

Since `tabut` assumes an "array of dictionaries" format, then most data operations can be performed easily with Typst's native array functions. `tabut` also provides several functions to provide additional functionality.

]) #no-break([

#subsection([CSV Data])

By default, imported CSV gives a "rows" or "array of arrays" data format, which can not be directly used by `tabut`.
To convert, `tabut` includes a function `rows-to-records` demonstrated below.

#snippet-quiet("import-csv-raw")

Imported CSV data are all strings, so it's usefull to convert them to `int` or `float` when possible.

#snippet-quiet("import-csv")

`tabut` includes a function, `records-from-csv`, to automatically perform this process.

#snippet-quiet("import-csv-easy")

]) #no-break([

#subsection([Slice])

#snippet("slice")

]) #no-break([

#subsection([Sorting and Reversing])

#snippet("sort")

]) #no-break([

#subsection([Filter])

#snippet("filter")

]) #no-break([

#subsection([Aggregation using Map and Sum])

#snippet("aggregation")

]) #no-break([

#subsection([Grouping])

#snippet("group")

#snippet("group-aggregation")

])

#pagebreak(weak: true)

#no-break([

#section([Function Definitions])

]) #no-break([

#subsection([`tabut`])

Takes data and column definitions and outputs a table.

#ex(```typc
tabut(
  data-raw, 
  colDefs, 
  columns: auto,
  align: auto,
  index: "_index",
  transpose: false,
  headers: true,
  ..tableArgs
) -> content
```)

=== Parameters
/ `data-raw`: This is the raw data that will be used to generate the table. The data is expected to be in an array of dictionaries, where each dictionary represents a single record or object.

/ `colDefs`: These are the column definitions. An array of dictionaries, each representing column definition. Must include the properties `header` and a `func`. `header` expects content, and specifies the label of the column. `func` expects a function, the function takes a record dictionary as input and returns the value to be displayed in the cell corresponding to that record and column. There are also two optional properties; `align` sets the alignment of the content within the cells of the column, `width` sets the width of the column.

/ `columns`: (optional, default: `auto`) Specifies the column widths. If set to `auto`, the function automatically generates column widths by each column's column definition. Otherwise functions exactly the `columns` paramater of the standard Typst `table` function. Unlike the `tabut-cells` setting this to `none` will break.

/ `align`: (optional, default: `auto`) Specifies the column alignment. If set to `auto`, the function automatically generates column alignment by each column's column definition. If set to `none` no `align` property is added to the output arg. Otherwise functions exactly the `align` paramater of the standard Typst `table` function.

/ `index`: (optional, default: `"_index"`) Specifies the property name for the index of each record. By default, an `_index` property is automatically added to each record. If set to `none`, no index property is added.

/ `transpose`: (optional, default: `false`) If set to `true`, transposes the table, swapping rows and columns.

/ `headers`: (optional, default: `true`) Determines whether headers should be included in the output. If set to `false`, headers are not generated.

/ `tableArgs`: (optional) Any additional arguments are passed to the `table` function, can be used for styling or anything else.

]) #no-break([

#subsection([`tabut-cells`])

The `tabut-cells` function functions as `tabut`, but returns `arguments` for use in either the standard `table` function or other tools such as `tablex`. If you just want the array of cells, use the `pos` function on the returned value, ex `tabut-cells(...).pos`.

`tabut-cells`  is particularly useful when you need to generate only the cell contents of a table or when these cells need to be passed to another function for further processing or customization.

=== Function Signature
#ex(```typc
tabut-cells(
  data-raw, 
  colDefs, 
  columns: auto,
  align: auto,
  index: "_index",
  transpose: false,
  headers: true,
) -> arguments
```)

=== Parameters
/ `data-raw`: This is the raw data that will be used to generate the table. The data is expected to be in an array of dictionaries, where each dictionary represents a single record or object.

/ `colDefs`: These are the column definitions. An array of dictionaries, each representing column definition. Must include the properties `header` and a `func`. `header` expects content, and specifies the label of the column. `func` expects a function, the function takes a record dictionary as input and returns the value to be displayed in the cell corresponding to that record and column. There are also two optional properties; `align` sets the alignment of the content within the cells of the column, `width` sets the width of the column.

/ `columns`: (optional, default: `auto`) Specifies the column widths. If set to `auto`, the function automatically generates column widths by each column's column definition. If set to `none` no `column` property is added to the output arg. Otherwise functions exactly the `columns` paramater of the standard typst `table` function.

/ `align`: (optional, default: `auto`) Specifies the column alignment. If set to `auto`, the function automatically generates column alignment by each column's column definition. If set to `none` no `align` property is added to the output arg. Otherwise functions exactly the `align` paramater of the standard typst `table` function.

/ `index`: (optional, default: `"_index"`) Specifies the property name for the index of each record. By default, an `_index` property is automatically added to each record. If set to `none`, no index property is added.

/ `transpose`: (optional, default: `false`) If set to `true`, transposes the table, swapping rows and columns.

/ `headers`: (optional, default: `true`) Determines whether headers should be included in the output. If set to `false`, headers are not generated.

]) #no-break([

#subsection([`records-from-csv`])

Automatically converts a CSV data into an array of records.

#ex(```typc
records-from-csv(
  data
) -> array
```)

=== Parameters
/ `data`: The CSV data that needs to be converted, this can be obtained using the native `csv` function, like `records-from-csv(csv(file-path))`.

This function simplifies the process of converting CSV data into a format compatible with `tabut`. It reads the CSV data, extracts the headers, and converts each row into a dictionary, using the headers as keys.

It also automatically converts data into floats or integers when possible.

]) #no-break([

#subsection([`rows-to-records`])

Converts rows of data into an array of records based on specified headers.

This function is useful for converting data in a "rows" format (commonly found in CSV files) into an array of dictionaries format, which is required for `tabut` and allows easy data processing using the built in array functions.

#ex(```typc
rows-to-records(
  headers, 
  rows, 
  default: none
) -> array
```)

=== Parameters
/ `headers`: An array representing the headers of the table. Each item in this array corresponds to a column header.

/ `rows`: An array of arrays, each representing a row of data. Each sub-array contains the cell data for a corresponding row.

/ `default`: (optional, default: `none`) A default value to use when a cell is empty or there is an error.

]) #no-break([

#subsection([`group`])

Groups data based on a specified function and returns an array of grouped records.

#ex(```typc
group(
  data, 
  function
) -> array
```)

=== Parameters
/ `data`: An array of dictionaries. Each dictionary represents a single record or object.

/ `function`: A function that takes a record as input and returns a value based on which the grouping is to be performed.

This function iterates over each record in the `data`, applies the `function` to determine the grouping value, and organizes the records into groups based on this value. Each group record is represented as a dictionary with two properties: `value` (the result of the grouping function) and `group` (an array of records belonging to this group). 

In the context of `tabut`, the `group` function is particularly useful for creating summary tables where records need to be categorized and aggregated based on certain criteria, such as calculating total or average values for each group.

])



