Current section
Files
Jump to
Current section
Files
src/fmglee.erl
-module(fmglee).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]).
-export([new/1, s/2, d/2, f/2, try_sprintf/2, write/3, print/2, println/2, writef/2, printf/1, printlnf/1, sprintf/2, fmt/2, try_fmt/2, try_build/1, build/1]).
-export_type([fmt_error/0, fmt/0, formatter/0]).
-type fmt_error() :: {incorrect_value_type, binary(), fmt()} |
too_many_values |
not_enough_values |
{invalid_float, binary()} |
{invalid_int, binary()} |
{invalid_float_format_specifier, binary()}.
-type fmt() :: {s, binary()} | {d, integer()} | {f, float()}.
-type formatter() :: {formatter, binary(), list(fmt())}.
-spec round_float(binary(), binary()) -> {ok, binary()} | {error, fmt_error()}.
round_float(Value, N) ->
gleam@result:'try'(
begin
_pipe = gleam@int:parse(N),
gleam@result:replace_error(_pipe, {invalid_int, N})
end,
fun(N@1) -> case N@1 of
0 ->
case gleam@string:split(Value, <<"."/utf8>>) of
[Num, _] ->
{ok, Num};
_ ->
{error, {invalid_float, Value}}
end;
_ ->
case gleam@string:split(Value, <<"."/utf8>>) of
[Num@1, Remainder] ->
Parts = gleam@string:split(Remainder, <<""/utf8>>),
Len_parts = erlang:length(Parts),
Rounded = begin
_pipe@1 = case Len_parts > N@1 of
true ->
gleam@list:take(Parts, N@1);
false ->
Pad = gleam@list:repeat(
<<"0"/utf8>>,
(N@1 - Len_parts)
),
lists:append(Parts, Pad)
end,
gleam@string:join(_pipe@1, <<""/utf8>>)
end,
{ok,
<<<<Num@1/binary, "."/utf8>>/binary,
Rounded/binary>>};
_ ->
{error, {invalid_float, Value}}
end
end end
).
-spec format_delimited_float(float(), binary()) -> {ok, binary()} |
{error, fmt_error()}.
format_delimited_float(Value, Delimiter) ->
Val = gleam@float:to_string(Value),
case gleam@string:split(Val, <<"."/utf8>>) of
[Num, Remainder] ->
Intersparced = begin
_pipe = gleam@string:split(Num, <<""/utf8>>),
_pipe@1 = lists:reverse(_pipe),
_pipe@2 = gleam@list:index_map(
_pipe@1,
fun(V, I) -> case I rem 3 of
0 ->
[Delimiter, V];
_ ->
[V]
end end
),
gleam@list:flatten(_pipe@2)
end,
S = begin
_pipe@3 = case Intersparced of
[Val@1 | Vals] when Val@1 =:= Delimiter ->
Vals;
Vals@1 ->
Vals@1
end,
_pipe@4 = lists:reverse(_pipe@3),
gleam@string:join(_pipe@4, <<""/utf8>>)
end,
{ok, <<<<S/binary, "."/utf8>>/binary, Remainder/binary>>};
_ ->
{error, {invalid_float, gleam@float:to_string(Value)}}
end.
-spec new(binary()) -> formatter().
new(S) ->
{formatter, S, []}.
-spec s(formatter(), binary()) -> formatter().
s(Formatter, S) ->
{formatter,
erlang:element(2, Formatter),
[{s, S} | erlang:element(3, Formatter)]}.
-spec d(formatter(), integer()) -> formatter().
d(Formatter, D) ->
{formatter,
erlang:element(2, Formatter),
[{d, D} | erlang:element(3, Formatter)]}.
-spec f(formatter(), float()) -> formatter().
f(Formatter, F) ->
{formatter,
erlang:element(2, Formatter),
[{f, F} | erlang:element(3, Formatter)]}.
-spec process_fmt(binary(), binary(), binary(), list(fmt()), list(binary())) -> {ok,
binary()} |
{error, fmt_error()}.
process_fmt(Placeholder, Value, Str, Values, Matches) ->
case gleam@string:split_once(Str, Placeholder) of
{ok, {First, Last}} ->
do_fmt(
<<<<First/binary, Value/binary>>/binary, Last/binary>>,
Values,
Matches
);
{error, _} ->
{error, {incorrect_value_type, Placeholder, {s, Value}}}
end.
-spec do_fmt(binary(), list(fmt()), list(binary())) -> {ok, binary()} |
{error, fmt_error()}.
do_fmt(Str, Values, Matches) ->
case {Values, Matches} of
{[], []} ->
{ok, Str};
{[{s, Value} | Rest], [<<"%s"/utf8>> | Matches@1]} ->
process_fmt(<<"%s"/utf8>>, Value, Str, Rest, Matches@1);
{[{d, Value@1} | Rest@1], [<<"%d"/utf8>> | Matches@2]} ->
process_fmt(
<<"%d"/utf8>>,
gleam@int:to_string(Value@1),
Str,
Rest@1,
Matches@2
);
{[{f, Value@2} | Rest@2], [Placeholder | Matches@3]} ->
process_float_fmt(Placeholder, Value@2, Str, Rest@2, Matches@3);
{[Got | _], [Expected | _]} ->
{error, {incorrect_value_type, Expected, Got}};
{_, _} ->
{error, too_many_values}
end.
-spec try_sprintf(binary(), list(fmt())) -> {ok, binary()} |
{error, fmt_error()}.
try_sprintf(S, V) ->
_assert_subject = gleam@regex:from_string(
<<"(\\%d|\\%s|\\%[\\W]?f|%[\\W]?\\.\\df)"/utf8>>
),
{ok, Re} = case _assert_subject of
{ok, _} -> _assert_subject;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _assert_fail,
module => <<"fmglee"/utf8>>,
function => <<"try_sprintf"/utf8>>,
line => 140})
end,
Matches = begin
_pipe = gleam@regex:scan(Re, S),
gleam@list:map(_pipe, fun(M) -> erlang:element(2, M) end)
end,
Num_matches = erlang:length(Matches),
Num_values = erlang:length(V),
case {Num_matches > Num_values, Num_values > Num_matches} of
{true, false} ->
{error, not_enough_values};
{false, true} ->
{error, too_many_values};
{_, _} ->
do_fmt(S, V, Matches)
end.
-spec write(binary(), list(fmt()), fun((binary()) -> nil)) -> nil.
write(S, V, Writer) ->
_assert_subject = try_sprintf(S, V),
{ok, Str} = case _assert_subject of
{ok, _} -> _assert_subject;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _assert_fail,
module => <<"fmglee"/utf8>>,
function => <<"write"/utf8>>,
line => 112})
end,
Writer(Str).
-spec print(binary(), list(fmt())) -> nil.
print(S, V) ->
write(S, V, fun gleam@io:print/1).
-spec println(binary(), list(fmt())) -> nil.
println(S, V) ->
write(S, V, fun gleam@io:println/1).
-spec writef(formatter(), fun((binary()) -> nil)) -> nil.
writef(Formatter, Writer) ->
write(erlang:element(2, Formatter), erlang:element(3, Formatter), Writer).
-spec printf(formatter()) -> nil.
printf(Formatter) ->
writef(Formatter, fun gleam@io:print/1).
-spec printlnf(formatter()) -> nil.
printlnf(Formatter) ->
writef(Formatter, fun gleam@io:println/1).
-spec sprintf(binary(), list(fmt())) -> binary().
sprintf(S, V) ->
_assert_subject = try_sprintf(S, V),
{ok, Str} = case _assert_subject of
{ok, _} -> _assert_subject;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _assert_fail,
module => <<"fmglee"/utf8>>,
function => <<"sprintf"/utf8>>,
line => 125})
end,
Str.
-spec fmt(binary(), list(fmt())) -> binary().
fmt(S, V) ->
sprintf(S, V).
-spec try_fmt(binary(), list(fmt())) -> {ok, binary()} | {error, fmt_error()}.
try_fmt(S, V) ->
try_sprintf(S, V).
-spec process_float_fmt(
binary(),
float(),
binary(),
list(fmt()),
list(binary())
) -> {ok, binary()} | {error, fmt_error()}.
process_float_fmt(Placeholder, Value, Str, Values, Matches) ->
case gleam@string:split(Placeholder, <<""/utf8>>) of
[<<"%"/utf8>>, C, <<"f"/utf8>>] ->
gleam@result:'try'(
format_delimited_float(Value, C),
fun(Val) ->
process_fmt(Placeholder, Val, Str, Values, Matches)
end
);
[<<"%"/utf8>>, C@1, <<"."/utf8>>, N, <<"f"/utf8>>] ->
gleam@result:'try'(
format_delimited_float(Value, C@1),
fun(Val@1) ->
gleam@result:'try'(
round_float(Val@1, N),
fun(Rounded) ->
process_fmt(
Placeholder,
Rounded,
Str,
Values,
Matches
)
end
)
end
);
[<<"%"/utf8>>, <<"."/utf8>>, N@1, <<"f"/utf8>>] ->
gleam@result:'try'(
begin
_pipe = gleam@float:to_string(Value),
round_float(_pipe, N@1)
end,
fun(Val@2) ->
process_fmt(Placeholder, Val@2, Str, Values, Matches)
end
);
[<<"%"/utf8>>, <<"f"/utf8>>] ->
process_fmt(
<<"%f"/utf8>>,
gleam@float:to_string(Value),
Str,
Values,
Matches
);
_ ->
{error, {invalid_float_format_specifier, Placeholder}}
end.
-spec try_build(formatter()) -> {ok, binary()} | {error, fmt_error()}.
try_build(Formatter) ->
try_sprintf(
erlang:element(2, Formatter),
lists:reverse(erlang:element(3, Formatter))
).
-spec build(formatter()) -> binary().
build(Formatter) ->
_assert_subject = try_build(Formatter),
{ok, Str} = case _assert_subject of
{ok, _} -> _assert_subject;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _assert_fail,
module => <<"fmglee"/utf8>>,
function => <<"build"/utf8>>,
line => 338})
end,
Str.