-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 = <>, {yarn, <<<>/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, <<<>/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, <>, 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 = <>, {yarn, <<<>/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).