Current section
Files
Jump to
Current section
Files
src/knit.erl
-module(knit).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]).
-export([crop_left/2, crop_right/2, crop_centre/2, pad_left/3, pad_right/3, pad_centre/3, ellipsize/3, digit_separator/3, new/1, from/2, with/1, with_arg/1, margin_left/3, margin_right/3, margin_centre/3, simple_wrap/2, split/2, join/2, from_string/1, to_string/1, length/1]).
-export_type([yarn/0]).
-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(
" Start knitting with the [`new`](#new)/[`from`](#from) functions!\n"
"\n"
" > For complexity reasons, formatters are not aware of linebreaks! See [`split`](#split) for an example on how to handle multiline `String`s.\n"
"\n"
" > Except where otherwise noted; for formatters that take a `width`, the total length of the resulting `String` will never exceed `width`.\n"
).
-opaque yarn() :: {yarn, binary(), integer()}.
-file("src/knit.gleam", 25).
?DOC(
" Drop characters from the left side of a `String` if it exceeds `width`.\n"
"\n"
" - If `width` is less than 0, it will be clamped to 0.\n"
"\n"
" ## Examples:\n"
" ```gleam\n"
" knit.new(knit.crop_left(_, 5))(\"hello world\") // \"hello\"\n"
" ```\n"
).
-spec crop_left(yarn(), integer()) -> yarn().
crop_left(This, Width) ->
{yarn, Str, Len} = This,
Width@1 = gleam@int:max(Width, 0),
case Len of
Len@1 when Len@1 =< Width@1 ->
This;
Len@2 ->
{yarn, gleam@string:slice(Str, Len@2 - Width@1, Width@1), Width@1}
end.
-file("src/knit.gleam", 43).
?DOC(
" Drop characters from the right side of a `String` if it exceeds `width`.\n"
"\n"
" - If `width` is less than 0, it will be clamped to 0.\n"
"\n"
" ## Examples:\n"
" ```gleam\n"
" knit.new(knit.crop_right(_, 5))(\"hello world\") // \"world\"\n"
" ```\n"
).
-spec crop_right(yarn(), integer()) -> yarn().
crop_right(This, Width) ->
{yarn, Str, Len} = This,
Width@1 = gleam@int:max(Width, 0),
case Len of
Len@1 when Len@1 =< Width@1 ->
This;
_ ->
{yarn, gleam@string:slice(Str, 0, Width@1), Width@1}
end.
-file("src/knit.gleam", 61).
?DOC(
" Drop characters equally from both sides of a `String` if it exceeds `width`.\n"
"\n"
" - If `width` is less than 0, it will be clamped to 0.\n"
"\n"
" ## Examples:\n"
" ```gleam\n"
" knit.new(knit.crop_centre(_, 5))(\"hello world\") // \"lo wo\"\n"
" ```\n"
).
-spec crop_centre(yarn(), integer()) -> yarn().
crop_centre(This, Width) ->
{yarn, Str, Len} = This,
Width@1 = gleam@int:max(Width, 0),
case Len of
Len@1 when Len@1 =< Width@1 ->
This;
Len@2 ->
{yarn,
gleam@string:slice(Str, (Len@2 - Width@1) div 2, Width@1),
Width@1}
end.
-file("src/knit.gleam", 82).
?DOC(
" Pad the left side of a `String` with `fill` if it is less than `width`.\n"
"\n"
" - The resulting `String` will be allowed to exceed `width`.\n"
" - If `width` is less than 0, it will be clamped to 0.\n"
" - `fill` is truncated to the first character.\n"
" - If `fill` is `\"\"`, it will default to `\" \"`.\n"
"\n"
" ## Examples:\n"
" ```gleam\n"
" knit.new(knit.pad_left(_, 15, \" \"))(\"hello world\") // \" hello world\"\n"
" ```\n"
).
-spec pad_left(yarn(), integer(), binary()) -> yarn().
pad_left(This, Width, Fill) ->
Fill@1 = begin
_pipe = gleam@string:first(Fill),
gleam@result:unwrap(_pipe, <<" "/utf8>>)
end,
{yarn, Str, Len} = This,
Width@1 = gleam@int:max(Width, 0),
case Len of
Len@1 when Len@1 > Width@1 ->
This;
_ ->
{yarn, gleam@string:pad_start(Str, Width@1, Fill@1), Width@1}
end.
-file("src/knit.gleam", 104).
?DOC(
" Pad the right side of a `String` with `fill` if it is less than `width`.\n"
"\n"
" - The resulting `String` will be allowed to exceed `width`.\n"
" - If `width` is less than 0, it will be clamped to 0.\n"
" - `fill` is truncated to the first character.\n"
" - If `fill` is `\"\"`, it will default to `\" \"`.\n"
"\n"
" ## Examples:\n"
" ```gleam\n"
" knit.new(knit.pad_right(_, 15, \" \"))(\"hello world\") // \"hello world \"\n"
" ```\n"
).
-spec pad_right(yarn(), integer(), binary()) -> yarn().
pad_right(This, Width, Fill) ->
Fill@1 = begin
_pipe = gleam@string:first(Fill),
gleam@result:unwrap(_pipe, <<" "/utf8>>)
end,
{yarn, Str, Len} = This,
Width@1 = gleam@int:max(Width, 0),
case Len of
Len@1 when Len@1 > Width@1 ->
This;
_ ->
{yarn, gleam@string:pad_end(Str, Width@1, Fill@1), Width@1}
end.
-file("src/knit.gleam", 126).
?DOC(
" Pad both sides of a `String` with `fill` if it is less than `width`.\n"
"\n"
" - The resulting `String` will be allowed to exceed `width`.\n"
" - If `width` is less than 0, it will be clamped to 0.\n"
" - `fill` is truncated to the first character.\n"
" - If `fill` is `\"\"`, it will default to `\" \"`.\n"
"\n"
" ## Examples:\n"
" ```gleam\n"
" knit.new(knit.pad_centre(_, 15, \" \"))(\"hello world\") // \" hello world \"\n"
" ```\n"
).
-spec pad_centre(yarn(), integer(), binary()) -> yarn().
pad_centre(This, Width, Fill) ->
Fill@1 = begin
_pipe = gleam@string:first(Fill),
gleam@result:unwrap(_pipe, <<" "/utf8>>)
end,
{yarn, Str, Len} = This,
Width@1 = gleam@int:max(Width, 0),
case Len of
Len@1 when Len@1 > Width@1 ->
This;
Len@2 ->
Padding = Width@1 - Len@2,
Pfx = gleam@string:repeat(Fill@1, Padding div 2),
Sfx = <<Pfx/binary,
(gleam@string:repeat(Fill@1, Padding rem 2))/binary>>,
{yarn, <<<<Pfx/binary, Str/binary>>/binary, Sfx/binary>>, Width@1}
end.
-file("src/knit.gleam", 156).
?DOC(
" Ellipsize the right side of a `String` with `ellipses` if the total length exceeds `width`.\n"
"\n"
" - If `width` is less than 0, it will be clamped to 0.\n"
" - If `ellipses` is `\"\"`, it will default to `\"...\"`.\n"
" - `ellipses` is truncated to `width`.\n"
"\n"
" ## Examples:\n"
" ```gleam\n"
" knit.new(knit.ellipsize(_, 10, \"...\"))(\"hello world\") // \"hello w...\"\n"
" ```\n"
" ```gleam\n"
" knit.new(knit.ellipsize(_, 11, \"...\"))(\"hello world\") // \"hello world\"\n"
" ```\n"
).
-spec ellipsize(yarn(), integer(), binary()) -> yarn().
ellipsize(This, Width, Ellipses) ->
{yarn, Str, Len} = This,
Width@1 = gleam@int:max(Width, 0),
Ellipses@1 = begin
_pipe = case Ellipses of
<<""/utf8>> ->
<<"..."/utf8>>;
A ->
A
end,
gleam@string:slice(_pipe, 0, Width@1)
end,
Content_width = Width@1 - string:length(Ellipses@1),
case Len of
Len@1 when Len@1 =< Content_width ->
This;
_ ->
{yarn,
<<(gleam@string:slice(Str, 0, Content_width))/binary,
Ellipses@1/binary>>,
Width@1}
end.
-file("src/knit.gleam", 190).
?DOC(
" Add a `separator` every `interval` integer digits in a `String` representing a number.\n"
"\n"
" - If `interval` is less than 1, it will default to 3.\n"
" - `separator` is truncated to the first character.\n"
" - If `separator` is `\"\"`, it will default to `\",\"`.\n"
" - This function does not check that what it's processing _actually_ represents a number; this may lead to amusing behavior if used on other `String`s.\n"
"\n"
" ## Examples:\n"
" ```gleam\n"
" knit.from(int.to_string, knit.digit_separator(_, 3, \",\"))(1_000_000) // \"1,000,000\"\n"
" ```\n"
" ```gleam\n"
" knit.from(float.to_string, knit.digit_separator(_, 3, \"_\"))(1_000.0001) // \"1_000.0001\"\n"
" ```\n"
" ```gleam\n"
" knit.from(int.to_string, knit.digit_separator(_, 3, \".\"))(-100_001) // \"-100.001\"\n"
" ```\n"
).
-spec digit_separator(yarn(), integer(), binary()) -> yarn().
digit_separator(This, Interval, Separator) ->
{yarn, Str, Len} = This,
Separator@1 = begin
_pipe = gleam@string:first(Separator),
gleam@result:unwrap(_pipe, <<","/utf8>>)
end,
Interval@1 = case Interval of
A when A < 1 ->
3;
A@1 ->
A@1
end,
{Sign, Str@1} = case Str of
<<"-"/utf8, Rest/binary>> ->
{<<"-"/utf8>>, Rest};
A@2 ->
{<<""/utf8>>, A@2}
end,
{Integer, Decimal} = case gleam@string:split_once(Str@1, <<"."/utf8>>) of
{ok, {I, D}} ->
{I, <<"."/utf8, D/binary>>};
_ ->
{Str@1, <<""/utf8>>}
end,
Chunks = begin
_pipe@1 = gleam@string:reverse(Integer),
_pipe@2 = gleam@string:to_graphemes(_pipe@1),
gleam@list:sized_chunk(_pipe@2, Interval@1)
end,
Separator_count = erlang:length(Chunks) - 1,
Integer@1 = begin
_pipe@3 = Chunks,
_pipe@4 = gleam@list:map(_pipe@3, fun erlang:list_to_binary/1),
_pipe@5 = gleam@string:join(_pipe@4, Separator@1),
gleam@string:reverse(_pipe@5)
end,
{yarn,
<<<<Sign/binary, Integer@1/binary>>/binary, Decimal/binary>>,
Len + Separator_count}.
-file("src/knit.gleam", 242).
?DOC(
" Create a new formatter that accepts `String`s.\n"
"\n"
" ## Examples:\n"
" ```gleam\n"
" let pad = knit.new(knit.pad_centre(_, 15, \" \"))\n"
" pad(\"hello world\") // \" hello world \"\n"
" ```\n"
" ```gleam\n"
" let fmt = {\n"
" use yarn <- knit.new\n"
" yarn |> knit.crop_centre(9) |> knit.pad_centre(9, \" \")\n"
" }\n"
"\n"
" fmt(\"hello world\") // \"ello worl\"\n"
" fmt(\"hello\") // \" hello \"\n"
" ```\n"
).
-spec new(fun((yarn()) -> yarn())) -> fun((binary()) -> binary()).
new(Formatter) ->
fun(Str) ->
erlang:element(2, Formatter({yarn, Str, string:length(Str)}))
end.
-file("src/knit.gleam", 262).
?DOC(
" Create a new formatter that accepts any type you can convert to a `String`.\n"
"\n"
" ## Examples:\n"
" ```gleam\n"
" let fmt = knit.from(int.to_string, knit.digit_separator(_, 3, \" \"))\n"
" fmt(3_141_592) // \"3 141 592\"\n"
" ```\n"
" ```gleam\n"
" let normalise = {\n"
" use yarn <- knit.from(fn(a) { a |> float.to_precision(2) |> float.to_string })\n"
" yarn |> knit.pad_right(8, \"0\")\n"
" }\n"
"\n"
" normalise(3.1415926) // \"3.140000\"\n"
" normalise(1000.1) // \"1000.100\"\n"
" ```\n"
).
-spec from(fun((EWA) -> binary()), fun((yarn()) -> yarn())) -> fun((EWA) -> binary()).
from(From, Formatter) ->
fun(A) ->
Str = From(A),
erlang:element(2, Formatter({yarn, Str, string:length(Str)}))
end.
-file("src/knit.gleam", 297).
?DOC(
" Convert any `fn(String) -> String` into a composable formatter.\n"
"\n"
" - The formatter returned by this function will always call `string.length`, take care when calling repeatedly on large inputs.\n"
"\n"
" ## Examples:\n"
" ```gleam\n"
" let header = {\n"
" use yarn <- knit.new\n"
" yarn |> knit.with(string.uppercase) |> knit.pad_centre(32, \"-\")\n"
" }\n"
"\n"
" echo header(\"section header\") // \"---------SECTION HEADER---------\"\n"
" ```\n"
" ```gleam\n"
" let header = {\n"
" let title_case = {\n"
" use string <- knit.with\n"
" string.split(string, \" \") |> list.map(string.capitalise) |> string.join(\" \")\n"
" }\n"
" use yarn <- knit.new\n"
" title_case(yarn) |> knit.pad_centre(32, \" \")\n"
" }\n"
"\n"
" echo header(\"section 2: electric boogaloo\") // \" Section 2: Electric Boogaloo \"\n"
" ```\n"
).
-spec with(fun((binary()) -> binary())) -> fun((yarn()) -> yarn()).
with(This) ->
fun(Yarn) ->
Str = This(erlang:element(2, Yarn)),
{yarn, Str, string:length(Str)}
end.
-file("src/knit.gleam", 313).
?DOC(
" Convert any `fn(String, a) -> String` into a composable formatter.\n"
"\n"
" - The formatter returned by this function will always call `string.length`, take care when calling repeatedly on large inputs.\n"
" - If you need to pass more than one argument, try using a tuple!\n"
"\n"
" ## Examples:\n"
" ```gleam\n"
" // todo\n"
" ```\n"
).
-spec with_arg(fun((binary(), EWB) -> binary())) -> fun((yarn(), EWB) -> yarn()).
with_arg(This) ->
fun(Yarn, A) ->
Str = This(erlang:element(2, Yarn), A),
{yarn, Str, string:length(Str)}
end.
-file("src/knit.gleam", 330).
?DOC(
" Pad the left side of a `String` with `fill`, `amount` times.\n"
"\n"
" - If `amount` is less than 0, it will be clamped to 0.\n"
" - `fill` is truncated to the first character.\n"
" - If `fill` is `\"\"`, it will default to `\" \"`.\n"
"\n"
" ## Examples:\n"
" ```gleam\n"
" knit.new(knit.margin_left(_, 4, \" \"))(\"hello world\") // \" hello world\"\n"
" ```\n"
).
-spec margin_left(yarn(), integer(), binary()) -> yarn().
margin_left(This, Amount, Fill) ->
Fill@1 = begin
_pipe = gleam@string:first(Fill),
gleam@result:unwrap(_pipe, <<" "/utf8>>)
end,
{yarn, Str, Len} = This,
Amount@1 = gleam@int:max(Amount, 0),
{yarn,
<<(gleam@string:repeat(Fill@1, Amount@1))/binary, Str/binary>>,
Len + Amount@1}.
-file("src/knit.gleam", 348).
?DOC(
" Pad the right side of a `String` with `fill`, `amount` times.\n"
"\n"
" - If `amount` is less than 0, it will be clamped to 0.\n"
" - `fill` is truncated to the first character.\n"
" - If `fill` is `\"\"`, it will default to `\" \"`.\n"
"\n"
" ## Examples:\n"
" ```gleam\n"
" knit.new(knit.margin_right(_, 4, \" \"))(\"hello world\") // \"hello world \"\n"
" ```\n"
).
-spec margin_right(yarn(), integer(), binary()) -> yarn().
margin_right(This, Amount, Fill) ->
Fill@1 = begin
_pipe = gleam@string:first(Fill),
gleam@result:unwrap(_pipe, <<" "/utf8>>)
end,
{yarn, Str, Len} = This,
Amount@1 = gleam@int:max(Amount, 0),
{yarn,
<<Str/binary, (gleam@string:repeat(Fill@1, Amount@1))/binary>>,
Len + Amount@1}.
-file("src/knit.gleam", 366).
?DOC(
" Pad both sides of a `String` with `fill`, adding `amount` characters total.\n"
"\n"
" - If `amount` is less than 0, it will be clamped to 0.\n"
" - `fill` is truncated to the first character.\n"
" - If `fill` is `\"\"`, it will default to `\" \"`.\n"
"\n"
" ## Examples:\n"
" ```gleam\n"
" knit.new(knit.margin_centre(_, 4, \" \"))(\"hello world\") // \" hello world \"\n"
" ```\n"
).
-spec margin_centre(yarn(), integer(), binary()) -> yarn().
margin_centre(This, Amount, Fill) ->
Fill@1 = begin
_pipe = gleam@string:first(Fill),
gleam@result:unwrap(_pipe, <<" "/utf8>>)
end,
{yarn, Str, Len} = This,
Amount@1 = gleam@int:max(Amount, 0),
Pfx = gleam@string:repeat(Fill@1, Amount@1 div 2),
Sfx = <<Pfx/binary, (gleam@string:repeat(Fill@1, Amount@1 rem 2))/binary>>,
{yarn, <<<<Pfx/binary, Str/binary>>/binary, Sfx/binary>>, Len + Amount@1}.
-file("src/knit.gleam", 392).
?DOC(
" Simply wrap a `String` onto multiple lines at `width`.\n"
"\n"
" - This function is very simplistic and does not wrap at word boundaries or hyphenate. If you're looking for something fancier, consider the [`string_width`](https://hex.pm/packages/string_width) package!\n"
"\n"
" ## Examples:\n"
" ```gleam\n"
" knit.new(fn(yarn) { knit.simple_wrap(yarn, 16) |> knit.from_lines })(\n"
" \"this is a long sentence that maybe doesn't look so great\",\n"
" )\n"
" ```\n"
" ```text\n"
" this is a long s\n"
" entence that may\n"
" be doesn't look\n"
" so great\n"
" ```\n"
).
-spec simple_wrap(yarn(), integer()) -> list(yarn()).
simple_wrap(This, Width) ->
{yarn, Str, Len} = This,
Width@1 = gleam@int:max(Width, 0),
case Len > Width@1 of
false ->
gleam@list:wrap(This);
true ->
_pipe = gleam@string:to_graphemes(Str),
_pipe@1 = gleam@list:sized_chunk(_pipe, Width@1),
gleam@list:map(
_pipe@1,
fun(A) -> {yarn, erlang:list_to_binary(A), erlang:length(A)} end
)
end.
-file("src/knit.gleam", 431).
?DOC(
" Split a `String` by `separator`.\n"
"\n"
" This function is useful for formatting multiline `String`s correctly!\n"
"\n"
" - `separator` is truncated to the first character.\n"
" - If `separator` is `\"\"`, it will default to `\"\\n\"`.\n"
"\n"
" ## Examples:\n"
" ```gleam\n"
" let fmt = {\n"
" use yarn <- knit.new\n"
" yarn\n"
" |> knit.split(\"\\n\")\n"
" |> list.map(fn(yarn) {\n"
" knit.pad_centre(yarn, 14, \" \") |> knit.margin_centre(2, \"|\")\n"
" })\n"
" |> knit.join(\"\\n\")\n"
" }\n"
" \n"
" fmt(\"line 1\\nline 2\\nline 3\")\n"
" ```\n"
" ```text\n"
" | line 1 |\n"
" | line 2 |\n"
" | line 3 |\n"
" ```\n"
).
-spec split(yarn(), binary()) -> list(yarn()).
split(This, Separator) ->
Separator@1 = begin
_pipe = gleam@string:first(Separator),
gleam@result:unwrap(_pipe, <<"\n"/utf8>>)
end,
{yarn, Str, _} = This,
_pipe@1 = gleam@string:split(Str, Separator@1),
gleam@list:map(_pipe@1, fun(A) -> {yarn, A, string:length(A)} end).
-file("src/knit.gleam", 445).
?DOC(
" Join `String`s by `separator`.\n"
"\n"
" - `separator` is truncated to the first character.\n"
" - If `separator` is `\"\"`, it will default to `\"\\n\"`.\n"
"\n"
" ## Examples:\n"
" - See [`split`](#split)\n"
).
-spec join(list(yarn()), binary()) -> yarn().
join(This, Separator) ->
Separator@1 = begin
_pipe = gleam@string:first(Separator),
gleam@result:unwrap(_pipe, <<"\n"/utf8>>)
end,
{yarn, Str, Len} = gleam@list:fold(
This,
{yarn, <<""/utf8>>, 0},
fun(A, B) ->
{yarn,
<<<<(erlang:element(2, A))/binary, Separator@1/binary>>/binary,
(erlang:element(2, B))/binary>>,
(erlang:element(3, A) + erlang:element(3, B)) + 1}
end
),
{yarn, gleam@string:slice(Str, 1, Len - 1), Len - 1}.
-file("src/knit.gleam", 458).
?DOC(
" Convert a `String` into a `Yarn`.\n"
"\n"
" - This requires a call to `string.length`.\n"
).
-spec from_string(binary()) -> yarn().
from_string(This) ->
{yarn, This, string:length(This)}.
-file("src/knit.gleam", 463).
?DOC(" Convert a `String` into a `Yarn`.\n").
-spec to_string(yarn()) -> binary().
to_string(This) ->
erlang:element(2, This).
-file("src/knit.gleam", 470).
?DOC(
" Get the length of a `Yarn`.\n"
"\n"
" - This is a \"free\" operation, as the length is already tracked.\n"
).
-spec length(yarn()) -> integer().
length(This) ->
erlang:element(3, This).