Skip to main content

src/mesv@format.erl

-module(mesv@format).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src\\mesv\\format.gleam").
-export([build/1, set_row_sep/2, set_col_sep/2, set_headers/2, set_escaper/2, set_escape_all/2, format/2]).
-export_type([formatter/1]).

-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.

?MODULEDOC(
    " The module containing the functions for building the `Formatter`, and for using a\n"
    " `Formatter(a)` to transform `List(a)` into a `String`, which can be directly written to file.\n"
    " \n"
    " ## Examples\n"
    " A basic example of formatting data\n"
    " ```gleam\n"
    " import gleam/int\n"
    " import mesv/format\n"
    " \n"
    " const data: List(#(String, Int, Bool)) = [\n"
    "   #(\"Adam\", 20, True),\n"
    "   #(\"Beatrice\", 25, True),\n"
    "   #(\"Colin\", 2, False),\n"
    " ]\n"
    " \n"
    " pub fn main() -> Nil {\n"
    "   let formatter =\n"
    "     // First create a formatter\n"
    "     format.build(fn(val: #(String, Int, Bool)) -> List(String) {\n"
    "       let #(name, age, adult) = val\n"
    "       [\n"
    "         name,\n"
    "         int.to_string(age),\n"
    "         case adult {\n"
    "           True -> \"true\"\n"
    "           False -> \"false\"\n"
    "         },\n"
    "       ]\n"
    "     })\n"
    " \n"
    "   // Then, use that formatter on the data you want to format\n"
    "   let formatted_data = format.format(formatter, data)\n"
    " \n"
    "   // By default, the formatter uses the comma as a column separator,\n"
    "   // newline as the row separator, and doublequotes for escaping cells\n"
    "   assert formatted_data == \"Adam,20,true\\nBeatrice,25,true\\nColin,2,false\"\n"
    " }\n"
    " ```\n"
    " A cool party trick to impress your friends - computing data *just in time*\n"
    " when converting to string, minimizing the memory required!\n"
    " ```gleam\n"
    " // [...]\n"
    " const data: List(#(String, Int)) = [\n"
    "   #(\"Alex\", 20),\n"
    "   #(\"Betty\", 25),\n"
    "   #(\"Conrad\", 2),\n"
    " ]\n"
    " \n"
    " pub fn main() -> Nil {\n"
    "   let formatted_data =\n"
    "     format.build(fn(val: #(String, Int)) -> List(String) {\n"
    "       let #(name, age) = val\n"
    "       [\n"
    "         name,\n"
    "         int.to_string(age),\n"
    "         bool.to_string(age >= 18),\n"
    "       ]\n"
    "     })\n"
    "     |> format.format(data)\n"
    " \n"
    "   assert formatted_data == \"Alex,20,True\\nBetty,25,True\\nConrad,2,False\"\n"
    " }\n"
    " ```\n"
    " \n"
).

-opaque formatter(DKW) :: {formatter,
        binary(),
        binary(),
        binary(),
        boolean(),
        gleam@option:option(list(binary())),
        fun((DKW) -> list(binary()))}.

-file("src\\mesv\\format.gleam", 93).
?DOC(
    " Function for directly building a `Formatter` that outputs the specified\n"
    " elements in an exact order.\n"
).
-spec build(fun((DKX) -> list(binary()))) -> formatter(DKX).
build(F) ->
    {formatter, <<","/utf8>>, <<"\n"/utf8>>, <<"\""/utf8>>, false, none, F}.

-file("src\\mesv\\format.gleam", 109).
?DOC(
    " Function to set a specific row separator, instead of the default newline (`\\n`)\n"
    " \n"
    " If the row separator chosen is longer than a single character, it might cause problems\n"
    " with performance later during parsing.\n"
).
-spec set_row_sep(formatter(DLA), binary()) -> formatter(DLA).
set_row_sep(Formatter, New_row_separator) ->
    {formatter,
        erlang:element(2, Formatter),
        New_row_separator,
        erlang:element(4, Formatter),
        erlang:element(5, Formatter),
        erlang:element(6, Formatter),
        erlang:element(7, Formatter)}.

-file("src\\mesv\\format.gleam", 121).
?DOC(
    " Function to set a specific column separator, instead of the default comma (`,`)\n"
    " \n"
    " If the column separator chosen is longer than a single character, it might cause problems\n"
    " with performance later during parsing.\n"
).
-spec set_col_sep(formatter(DLD), binary()) -> formatter(DLD).
set_col_sep(Formatter, New_column_separator) ->
    {formatter,
        New_column_separator,
        erlang:element(3, Formatter),
        erlang:element(4, Formatter),
        erlang:element(5, Formatter),
        erlang:element(6, Formatter),
        erlang:element(7, Formatter)}.

-file("src\\mesv\\format.gleam", 133).
?DOC(
    " Function to manually set column headers in a particular order.\n"
    " \n"
    " By default, no headers will be written to output String, and the first row will\n"
    " directly be the formatted data.\n"
).
-spec set_headers(formatter(DLG), list(binary())) -> formatter(DLG).
set_headers(Formatter, New_headers) ->
    {formatter,
        erlang:element(2, Formatter),
        erlang:element(3, Formatter),
        erlang:element(4, Formatter),
        erlang:element(5, Formatter),
        {some, New_headers},
        erlang:element(7, Formatter)}.

-file("src\\mesv\\format.gleam", 143).
?DOC(
    " Function to set custom escaper (character that wraps the value if its'\n"
    " string contains row or column separators, or the escaper itself)\n"
).
-spec set_escaper(formatter(DLK), binary()) -> formatter(DLK).
set_escaper(Formatter, New_escaper) ->
    {formatter,
        erlang:element(2, Formatter),
        erlang:element(3, Formatter),
        New_escaper,
        erlang:element(5, Formatter),
        erlang:element(6, Formatter),
        erlang:element(7, Formatter)}.

-file("src\\mesv\\format.gleam", 154).
?DOC(
    " Function to specify whether to wrap each value in an escaper, regardles of necessity.\n"
    " \n"
    " By default false.\n"
).
-spec set_escape_all(formatter(DLN), boolean()) -> formatter(DLN).
set_escape_all(Parser, Escape_all) ->
    {formatter,
        erlang:element(2, Parser),
        erlang:element(3, Parser),
        erlang:element(4, Parser),
        Escape_all,
        erlang:element(6, Parser),
        erlang:element(7, Parser)}.

-file("src\\mesv\\format.gleam", 165).
?DOC(
    " Internal helper function for creating a function that checks if a specific element needs\n"
    " to be escaped (wrapped in escaper, which by default is `\"`) before being written to file.\n"
    " \n"
    " It's a curried function because I like functional programming, and because it *should*\n"
    " give some performance improvements if I create such a function before any looping instead\n"
    " of constructing one for each iteration.\n"
).
-spec needs_escaping(list(binary())) -> fun((binary()) -> boolean()).
needs_escaping(Prohibited) ->
    fun(El) -> _pipe = Prohibited,
        gleam@list:any(_pipe, fun(S) -> gleam_stdlib:contains_string(El, S) end) end.

-file("src\\mesv\\format.gleam", 178).
?DOC(
    " Internal helper function for creating a function for 'escaping' an element (for each `rule`,\n"
    " replacing the first element in the tuple with the second).\n"
    " \n"
    " It's a curried function because I like functional programming, and because it *should*\n"
    " give some performance improvements if I create such a function before any looping instead\n"
    " of constructing one for each iteration.\n"
).
-spec escape(list({binary(), binary()})) -> fun((binary()) -> binary()).
escape(Rules) ->
    fun(El) -> _pipe = Rules,
        _pipe@1 = gleam@list:map(
            _pipe,
            fun(Rule) ->
                fun(_capture) ->
                    gleam@string:replace(
                        _capture,
                        erlang:element(1, Rule),
                        erlang:element(2, Rule)
                    )
                end
            end
        ),
        gleam@list:fold(_pipe@1, El, fun(Acc, Rule@1) -> Rule@1(Acc) end) end.

-file("src\\mesv\\format.gleam", 197).
?DOC(
    " Internal helper function for creating a function that wraps a String in the specified\n"
    " 'escaper' String.\n"
    " \n"
    " It's a curried function because I like functional programming, and because it *should*\n"
    " give some performance improvements if I create such a function before any looping instead\n"
    " of constructing one for each iteration.\n"
).
-spec wrap(binary()) -> fun((binary()) -> binary()).
wrap(In) ->
    fun(El) -> <<<<In/binary, El/binary>>/binary, In/binary>> end.

-file("src\\mesv\\format.gleam", 207).
?DOC(
    " Execution function that takes in a `Formatter(a)` as well as a `List(a)`,\n"
    " and encodes it into a String.\n"
    " \n"
    " All of the configuration options need to be set when building the `Formatter`,\n"
    " so this function is very simple to understand.\n"
).
-spec format(formatter(DLS), list(DLS)) -> binary().
format(Formatter, Elements) ->
    {formatter,
        Column_separator,
        Row_separator,
        Escaper,
        Escape_all,
        Maybe_headers,
        To_string} = Formatter,
    Rules = [{Escaper, <<Escaper/binary, Escaper/binary>>}],
    Escapeify = fun(El) -> _pipe = El,
        _pipe@1 = gleam@string:trim(_pipe),
        _pipe@2 = (escape(Rules))(_pipe@1),
        (wrap(Escaper))(_pipe@2) end,
    To_escape = needs_escaping(
        [Column_separator, Row_separator, Escaper, <<"\n"/utf8>>, <<"\r"/utf8>>]
    ),
    Ensafeify = fun(Val) -> case Escape_all orelse To_escape(Val) of
            true ->
                Escapeify(Val);

            false ->
                Val
        end end,
    _pipe@5 = case Maybe_headers of
        {some, Headers} ->
            [Headers |
                begin
                    _pipe@3 = Elements,
                    gleam@list:map(_pipe@3, To_string)
                end];

        none ->
            _pipe@4 = Elements,
            gleam@list:map(_pipe@4, To_string)
    end,
    _pipe@9 = gleam@list:map(_pipe@5, fun(Values) -> _pipe@6 = Values,
            _pipe@7 = gleam@list:map(_pipe@6, fun gleam@string:trim/1),
            _pipe@8 = gleam@list:map(_pipe@7, Ensafeify),
            gleam@string:join(_pipe@8, Column_separator) end),
    gleam@string:join(_pipe@9, Row_separator).