Current section

Files

Jump to
birdie src birdie.erl
Raw

src/birdie.erl

-module(birdie).
-compile([no_auto_import, nowarn_ignored, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-export([snap/2, main/0]).
-export_type([error/0, new/0, accepted/0, snapshot/1, snapshot_info/0, outcome/0, info_line/0, split/0, answer/0, review_mode/0, review_choice/0]).
-type error() :: snapshot_with_empty_title | {cannot_create_snapshots_folder, simplifile:file_error()} | {cannot_read_accepted_snapshot, simplifile:file_error(), binary()} | {cannot_read_new_snapshot, simplifile:file_error(), binary()} | {cannot_save_new_snapshot, simplifile:file_error(), binary(), binary()} | {cannot_read_snapshots, simplifile:file_error(), binary()} | {cannot_reject_snapshot, simplifile:file_error(), binary()} | {cannot_accept_snapshot, simplifile:file_error(), binary()} | cannot_read_user_input | {corrupted_snapshot, binary()} | {cannot_find_project_root, simplifile:file_error()} | {cannot_create_referenced_file, binary(), simplifile:file_error()} | {cannot_read_referenced_file, binary(), simplifile:file_error()} | {cannot_mark_snapshot_as_referenced, simplifile:file_error()} | {stale_snapshots_found, list(binary())} | {cannot_delete_stale_snapshot, simplifile:file_error()} | missing_referenced_file | {cannot_read_test_directory, simplifile:file_error()} | {cannot_read_test_file, simplifile:file_error(), binary()} | {cannot_figure_out_project_name, simplifile:file_error()} | {analysis_error, list(birdie@internal@analyser:error())} | {cannot_migrate_birdie_snapshot_directory, simplifile:file_error(), binary(), binary()}.
-type new() :: any().
-type accepted() :: any().
-type snapshot(LUK) :: {snapshot, binary(), binary(), gleam@option:option(snapshot_info())} | {gleam_phantom, LUK}.
-type snapshot_info() :: {snapshot_info, binary(), binary()}.
-type outcome() :: {new_snapshot_created, snapshot(new()), binary()} | {different, snapshot(accepted()), snapshot(new())} | same.
-type info_line() :: {info_line_with_title, binary(), split(), binary()} | {info_line_with_no_title, binary(), split()}.
-type split() :: do_not_split | split_words | truncate.
-type answer() :: yes | no.
-type review_mode() :: show_diff | hide_diff.
-type review_choice() :: accept_snapshot | reject_snapshot | skip_snapshot | toggle_diff_view.
-file("src/birdie.gleam", 158).
-spec get_temp_directory() -> binary().
get_temp_directory() ->
Temp = begin
gleam@result:lazy_or(envoy_ffi:get(~"TMPDIR"), fun() ->
gleam@result:lazy_or(envoy_ffi:get(~"TEMP"), fun() ->
envoy_ffi:get(~"TMP")
end)
end)
end,
case Temp of
{ok, Temp@1} ->
Temp@1;
{error, _} ->
case birdie_ffi:is_windows() of
true ->
~"C:\\TMP";
false ->
~"/tmp"
end
end.
-file("src/birdie.gleam", 150).
-spec referenced_file_path() -> {ok, binary()} | {error, error()}.
referenced_file_path() ->
gleam@result:'try'(begin
_pipe = birdie@internal@project:name(),
gleam@result:map_error(_pipe, fun(_value) ->
{cannot_figure_out_project_name, _value}
end)
end, fun(Name) ->
{ok, filepath:join(get_temp_directory(), <<Name/binary, "_referenced.txt"/utf8>>)}
end).
-file("src/birdie.gleam", 132).
-spec global_referenced_file() -> {ok, binary()} | {error, error()}.
-doc(~" Returns the path to the referenced file, initialising it to be empty only
the first time this function is called.
").
global_referenced_file() ->
global_value:create_with_unique_name(~"birdie.referenced_file", fun() ->
gleam@result:'try'(referenced_file_path(), fun(Referenced_file) ->
case simplifile:create_file(Referenced_file) of
{ok, _} ->
{ok, Referenced_file};
{error, eexist} ->
_pipe = simplifile:write(Referenced_file, ~""),
_pipe@1 = gleam@result:replace(_pipe, Referenced_file),
gleam@result:map_error(_pipe@1, fun(_capture) ->
{cannot_create_referenced_file, Referenced_file, _capture}
end);
{error, Reason} ->
{error, {cannot_create_referenced_file, Referenced_file, Reason}}
end
end)
end).
-file("src/birdie.gleam", 216).
-spec legacy_snapshot_folder_name() -> {ok, binary()} | {error, error()}.
-doc(~" This returns the name of the snapshot folder that was used before `1.6.0`.").
legacy_snapshot_folder_name() ->
global_value:create_with_unique_name(~"birdie.legacy_snapshot_folder", fun() ->
Result = gleam@result:map_error(birdie@internal@project:find_root(), fun(_value) ->
{cannot_find_project_root, _value}
end),
gleam@result:'try'(Result, fun(Project_root) ->
{ok, filepath:join(Project_root, ~"birdie_snapshots")}
end)
end).
-file("src/birdie.gleam", 204).
-spec snapshot_folder_name() -> {ok, binary()} | {error, error()}.
snapshot_folder_name() ->
global_value:create_with_unique_name(~"birdie.snapshot_folder_name", fun() ->
Result = gleam@result:map_error(birdie@internal@project:find_root(), fun(_value) ->
{cannot_find_project_root, _value}
end),
gleam@result:'try'(Result, fun(Project_root) ->
_pipe = Project_root,
_pipe@1 = filepath:join(_pipe, ~"test"),
_pipe@2 = filepath:join(_pipe@1, ~"birdie_snapshots"),
{ok, _pipe@2}
end)
end).
-file("src/birdie.gleam", 182).
-spec snapshot_folder() -> {ok, binary()} | {error, error()}.
-doc(~" Finds the snapshots folder at the root of the project the command is run
into. If it's not present the folder is created automatically.
").
snapshot_folder() ->
global_value:create_with_unique_name(~"birdie.snapshot_folder", fun() ->
gleam@result:'try'(snapshot_folder_name(), fun(Snapshot_folder) ->
gleam@result:'try'(legacy_snapshot_folder_name(), fun(Legacy_snapshot_folder) ->
case simplifile_erl:is_directory(Snapshot_folder) of
{ok, true} ->
{ok, Snapshot_folder};
{ok, false} ->
case simplifile_erl:is_directory(Legacy_snapshot_folder) of
{ok, true} ->
{ok, Legacy_snapshot_folder};
{ok, false} ->
case simplifile_erl:create_directory(Snapshot_folder) of
{ok, _} ->
{ok, Snapshot_folder};
{error, Error} ->
{error, {cannot_create_snapshots_folder, Error}}
end;
{error, enoent} ->
case simplifile_erl:create_directory(Snapshot_folder) of
{ok, _} ->
{ok, Snapshot_folder};
{error, Error} ->
{error, {cannot_create_snapshots_folder, Error}}
end;
{error, Error@1} ->
{error, {cannot_create_snapshots_folder, Error@1}}
end;
{error, enoent} ->
case simplifile_erl:is_directory(Legacy_snapshot_folder) of
{ok, true} ->
{ok, Legacy_snapshot_folder};
{ok, false} ->
case simplifile_erl:create_directory(Snapshot_folder) of
{ok, _} ->
{ok, Snapshot_folder};
{error, Error} ->
{error, {cannot_create_snapshots_folder, Error}}
end;
{error, enoent} ->
case simplifile_erl:create_directory(Snapshot_folder) of
{ok, _} ->
{ok, Snapshot_folder};
{error, Error} ->
{error, {cannot_create_snapshots_folder, Error}}
end;
{error, Error@1} ->
{error, {cannot_create_snapshots_folder, Error@1}}
end;
{error, Error@2} ->
{error, {cannot_create_snapshots_folder, Error@2}}
end
end)
end)
end).
-file("src/birdie.gleam", 680).
-spec to_diagnostic(error()) -> list(birdie@internal@diagnostic:diagnostic()).
to_diagnostic(Error) ->
Error_diagnostic = fun(Title, Text) ->
[{diagnostic, erro, Title, none, Text, none}]
end,
case Error of
snapshot_with_empty_title ->
Error_diagnostic(~"snapshot with empty title", ~"A snapshot cannot have an empty title.");
{cannot_create_snapshots_folder, Reason} ->
Error_diagnostic(~"cannot create snapshot folder", <<<<"An unexpected error happened: "/utf8, (simplifile:describe_error(Reason))/binary>>/binary, "."/utf8>>);
{cannot_read_accepted_snapshot, Reason@1, Source} ->
Error_diagnostic(~"cannot read accepted snapshot", <<<<<<<<"An unexpected error happened trying to read "/utf8, (gleam_community@ansi:italic(<<<<"\""/utf8, Source/binary>>/binary, "\":"/utf8>>))/binary>>/binary, " "/utf8>>/binary, (simplifile:describe_error(Reason@1))/binary>>/binary, "."/utf8>>);
{cannot_read_new_snapshot, Reason@2, Source@1} ->
Error_diagnostic(~"cannot read new snapshot", <<<<<<"An unexpected error happened trying to read "/utf8, (gleam_community@ansi:italic(<<<<"\""/utf8, Source@1/binary>>/binary, "\": "/utf8>>))/binary>>/binary, (simplifile:describe_error(Reason@2))/binary>>/binary, "."/utf8>>);
{cannot_save_new_snapshot, Reason@3, Title, Destination} ->
Error_diagnostic(~"cannot save new snapshot", <<<<<<<<<<"An unexpected error happened trying to save "/utf8, (gleam_community@ansi:italic(<<<<"\""/utf8, Title/binary>>/binary, "\""/utf8>>))/binary>>/binary, " to "/utf8>>/binary, (gleam_community@ansi:italic(<<<<"\""/utf8, Destination/binary>>/binary, "\": "/utf8>>))/binary>>/binary, (simplifile:describe_error(Reason@3))/binary>>/binary, "."/utf8>>);
{cannot_read_snapshots, Reason@4, _} ->
Error_diagnostic(~"cannot read snapshots folder", <<<<"An unexpected error happened trying to read the snapshots folder: "/utf8, (simplifile:describe_error(Reason@4))/binary>>/binary, "."/utf8>>);
{cannot_reject_snapshot, Reason@5, Snapshot} ->
Error_diagnostic(~"cannot reject snapshot", <<<<<<"An unexpected error happened trying to reject "/utf8, (gleam_community@ansi:italic(<<<<"\""/utf8, Snapshot/binary>>/binary, "\": "/utf8>>))/binary>>/binary, (simplifile:describe_error(Reason@5))/binary>>/binary, "."/utf8>>);
{cannot_accept_snapshot, Reason@6, Snapshot@1} ->
Error_diagnostic(~"cannot accept snapshot", <<<<<<"An unexpected error happened trying to accept "/utf8, (gleam_community@ansi:italic(<<<<"\""/utf8, Snapshot@1/binary>>/binary, "\": "/utf8>>))/binary>>/binary, (simplifile:describe_error(Reason@6))/binary>>/binary, "."/utf8>>);
cannot_read_user_input ->
Error_diagnostic(~"cannot read user input", ~"");
{corrupted_snapshot, Source@2} ->
[{diagnostic, erro, ~"corrupted snapshot", none, <<<<<<"It looks like "/utf8, (gleam_community@ansi:italic(<<<<"\""/utf8, Source@2/binary>>/binary, "\" "/utf8>>))/binary>>/binary, "is not a valid snapshot.\n"/utf8>>/binary, "This might happen when someone modifies its content."/utf8>>, {some, ~"try deleting the snapshot and recreating it."}}];
{cannot_create_referenced_file, File, eacces} ->
[{diagnostic, erro, ~"missing permission to create reference file", none, <<<<<<"I don't have the required permission to create the file used to track\n"/utf8, <<<<"stale snapshots at: `"/utf8, File/binary>>/binary, "`.\n"/utf8>>/binary>>/binary, "This usually happens when the current user doesn't have a write\n"/utf8>>/binary, "permission for the system's temporary directory."/utf8>>, {some, <<"you can set the $TEMP environment variable to make me use a\n"/utf8, "different directory to write the reference file in."/utf8>>}}];
{cannot_read_referenced_file, File@1, eacces} ->
[{diagnostic, erro, ~"missing permission to read reference file", none, <<<<<<"I don't have the required permission to read the file used to track\n"/utf8, <<<<"stale snapshots at: `"/utf8, File@1/binary>>/binary, "`.\n"/utf8>>/binary>>/binary, "This usually happens when the current user doesn't have a read\n"/utf8>>/binary, "permission for the system's temporary directory."/utf8>>, {some, <<"you can set the $TEMP environment variable to make me use a\n"/utf8, "different directory to write the reference file in."/utf8>>}}];
{cannot_create_referenced_file, _, Reason@7} ->
Error_diagnostic(~"cannot create reference file", <<<<"An unexpected error happened trying to create the file used to track stale snapshot: "/utf8, (simplifile:describe_error(Reason@7))/binary>>/binary, "."/utf8>>);
{cannot_read_referenced_file, _, Reason@8} ->
Error_diagnostic(~"cannot read reference file", <<<<"An unexpected error happened trying to read the file used to track stale snapshot: "/utf8, (simplifile:describe_error(Reason@8))/binary>>/binary, "."/utf8>>);
{cannot_mark_snapshot_as_referenced, Reason@9} ->
Error_diagnostic(~"cannot mark snapshot as referenced", <<<<"An unexpected error happened trying to mark a snapshot as referenced: "/utf8, (simplifile:describe_error(Reason@9))/binary>>/binary, "."/utf8>>);
{cannot_find_project_root, Reason@10} ->
Error_diagnostic(~"cannot find project root", <<<<"An unexpected error happened trying to locate the project's root: "/utf8, (simplifile:describe_error(Reason@10))/binary>>/binary, "."/utf8>>);
missing_referenced_file ->
[{diagnostic, erro, ~"missing stale snapshot file", none, ~"I couldn't find any information about stale snapshots.", {some, ~"remember you have to run `gleam test` first, so I can find any stale snapshot."}}];
{stale_snapshots_found, Stale_snapshots} ->
Titles = begin
_pipe = gleam@list:map(Stale_snapshots, fun(Snapshot@2) ->
<<" - "/utf8, (filepath:strip_extension(Snapshot@2))/binary>>
end),
gleam@string:join(_pipe, ~"\n")
end,
Text = <<<<<<<<"I found the following stale snapshots:\n\n"/utf8, Titles/binary>>/binary, "\n\n"/utf8>>/binary, "These snapshots were not referenced by any snapshot test during the "/utf8>>/binary, "last `gleam test`\n"/utf8>>,
[{diagnostic, erro, ~"stale snapshot found", none, Text, {some, ~"run `gleam run -m birdie stale delete` to delete them"}}];
{cannot_delete_stale_snapshot, Reason@11} ->
Error_diagnostic(~"cannot delete stale snapshot", <<<<"An unexpected error happened trying to delete a stale snapshot: "/utf8, (simplifile:describe_error(Reason@11))/binary>>/binary, "."/utf8>>);
{cannot_read_test_directory, Reason@12} ->
Error_diagnostic(~"cannot read test directroy", <<<<"An unexpected error happened trying to read the constents of the test directory: "/utf8, (simplifile:describe_error(Reason@12))/binary>>/binary, "."/utf8>>);
{cannot_figure_out_project_name, Reason@13} ->
Error_diagnostic(~"cannot figure out project's name", <<<<"An unexpected error happened trying to figure out the project's name: "/utf8, (simplifile:describe_error(Reason@13))/binary>>/binary, "."/utf8>>);
{cannot_read_test_file, Reason@14, File@2} ->
Error_diagnostic(~"cannot read test file", <<<<<<"An unexpected error happened trying to read "/utf8, (gleam_community@ansi:italic(<<<<"\""/utf8, File@2/binary>>/binary, "\": "/utf8>>))/binary>>/binary, (simplifile:describe_error(Reason@14))/binary>>/binary, "."/utf8>>);
{cannot_migrate_birdie_snapshot_directory, Reason@15, From, To} ->
Error_diagnostic(~"cannot migrate snapshot directory", <<<<<<<<"An unexpected error happened when trying to migrate\n"/utf8, (gleam_community@ansi:italic(<<<<"\""/utf8, From/binary>>/binary, "\" to "/utf8>>))/binary>>/binary, (gleam_community@ansi:italic(<<<<"\""/utf8, To/binary>>/binary, "\"\n"/utf8>>))/binary>>/binary, "The error is: "/utf8>>/binary, (simplifile:describe_error(Reason@15))/binary>>);
{analysis_error, Errors} ->
gleam@list:map(Errors, fun birdie@internal@analyser:error_to_diagnostic/1)
end.
-file("src/birdie.gleam", 941).
-spec snapshot_default_lines(snapshot(any())) -> list(info_line()).
snapshot_default_lines(Snapshot) ->
{snapshot, Title, _, Info} = Snapshot,
case Info of
none ->
[{info_line_with_title, Title, split_words, ~"title"}];
{some, {snapshot_info, File, Test_function_name}} ->
[{info_line_with_title, Title, split_words, ~"title"}, {info_line_with_title, File, truncate, ~"file"}, {info_line_with_title, Test_function_name, truncate, ~"name"}]
end.
-file("src/birdie.gleam", 355).
-spec to_diff_lines(snapshot(accepted()), snapshot(new())) -> list(birdie@internal@diff:diff_line()).
to_diff_lines(Accepted, New) ->
{snapshot, _, Accepted_content, _} = Accepted,
{snapshot, _, New_content, _} = New,
birdie@internal@diff:histogram(Accepted_content, New_content).
-file("src/birdie.gleam", 1088).
-spec pretty_diff_line(birdie@internal@diff:diff_line(), integer(), fun((binary()) -> binary())) -> binary().
pretty_diff_line(Diff_line, Padding, Shared_line_style) ->
{diff_line, Number, Line, Kind} = Diff_line,
{Pretty_number, Pretty_line, Separator} = case Kind of
shared ->
{begin
_pipe = erlang:integer_to_binary(Number),
_pipe@1 = gleam@string:pad_start(_pipe, Padding - 1, ~" "),
gleam_community@ansi:dim(_pipe@1)
end, Shared_line_style(Line), ~" │ "};
new ->
{begin
_pipe@2 = erlang:integer_to_binary(Number),
_pipe@3 = gleam@string:pad_start(_pipe@2, Padding - 1, ~" "),
_pipe@4 = gleam_community@ansi:green(_pipe@3),
gleam_community@ansi:bold(_pipe@4)
end, gleam_community@ansi:green(Line), gleam_community@ansi:green(~" + ")};
old ->
Number@1 = begin
_pipe@5 = <<" "/utf8, (erlang:integer_to_binary(Number))/binary>>,
gleam@string:pad_end(_pipe@5, Padding - 1, ~" ")
end,
{gleam_community@ansi:red(Number@1), gleam_community@ansi:red(Line), gleam_community@ansi:red(~" - ")}
end,
<<<<Pretty_number/binary, Separator/binary>>/binary, Pretty_line/binary>>.
-file("src/birdie.gleam", 1144).
-spec do_to_lines(list(binary()), binary(), integer(), list(binary()), integer()) -> list(binary()).
do_to_lines(Lines, Line, Line_length, Words, Max_length) ->
case Words of
[] ->
case Line =:= ~"" of
true ->
lists:reverse(Lines);
false ->
lists:reverse([Line | Lines])
end;
[Word | Rest] ->
Word_length = string:length(Word),
New_line_length = (Word_length + Line_length) + 1,
case New_line_length > Max_length of
true ->
do_to_lines([Line | Lines], ~"", 0, Words, Max_length);
false ->
New_line = case Line of
~"" ->
Word;
_ ->
<<<<Line/binary, " "/utf8>>/binary, Word/binary>>
end,
do_to_lines(Lines, New_line, New_line_length, Rest, Max_length)
end
end.
-file("src/birdie.gleam", 1137).
-spec to_lines(binary(), integer()) -> list(binary()).
to_lines(String, Max_length) ->
gleam@list:flat_map(gleam@string:split(String, ~"\n"), fun(Line) ->
Words = gleam@string:split(Line, ~" "),
do_to_lines([], ~"", 0, Words, Max_length)
end).
-file("src/birdie.gleam", 1126).
-spec truncate(binary(), integer()) -> binary().
truncate(String, Max_length) ->
case string:length(String) > Max_length of
false ->
String;
true ->
_pipe = gleam@string:to_graphemes(String),
_pipe@1 = gleam@list:take(_pipe, Max_length - 3),
_pipe@2 = gleam@string:join(_pipe@1, ~""),
gleam@string:append(_pipe@2, ~"...")
end.
-file("src/birdie.gleam", 1065).
-spec pretty_info_line(info_line(), integer()) -> binary().
pretty_info_line(Line, Width) ->
{Prefix, Prefix_length} = case Line of
{info_line_with_no_title, _, _} ->
{~" ", 2};
{info_line_with_title, _, _, Title} ->
{<<" "/utf8, (gleam_community@ansi:blue(<<Title/binary, ": "/utf8>>))/binary>>, string:length(Title) + 4}
end,
case erlang:element(3, Line) of
truncate ->
<<Prefix/binary, (truncate(erlang:element(2, Line), Width - Prefix_length))/binary>>;
do_not_split ->
<<Prefix/binary, (erlang:element(2, Line))/binary>>;
split_words ->
case to_lines(erlang:element(2, Line), Width - Prefix_length) of
[] ->
Prefix;
[Line@1 | Lines] ->
gleam@list:fold(Lines, <<Prefix/binary, Line@1/binary>>, fun(Acc, Line@2) ->
<<<<<<Acc/binary, "\n"/utf8>>/binary, (gleam@string:repeat(~" ", Prefix_length))/binary>>/binary, Line@2/binary>>
end)
end
end.
-file("src/birdie.gleam", 1020).
-spec count_digits_loop(integer(), integer()) -> integer().
count_digits_loop(Number, Digits) ->
case Number < 10 of
true ->
1 + Digits;
false ->
count_digits_loop(Number div 10, 1 + Digits)
end.
-file("src/birdie.gleam", 1016).
-spec count_digits(integer()) -> integer().
count_digits(Number) ->
count_digits_loop(gleam@int:absolute_value(Number), 0).
-file("src/birdie.gleam", 1725).
-spec terminal_width() -> integer().
terminal_width() ->
case term_size_ffi:terminal_size() of
{ok, {_, Columns}} ->
Columns;
{error, _} ->
80
end.
-file("src/birdie.gleam", 1027).
-spec pretty_box(binary(), list(birdie@internal@diff:diff_line()), list(info_line()), fun((binary()) -> binary())) -> binary().
pretty_box(Title, Content_lines, Info_lines, Shared_line_style) ->
Width = terminal_width(),
Lines_count = erlang:length(Content_lines) + 1,
Padding = (count_digits(Lines_count) * 2) + 5,
Title_length = string:length(Title),
Title_line_right = gleam@string:repeat(~"─", (Width - 5) - Title_length),
Title_line = <<<<<<"── "/utf8, Title/binary>>/binary, " ─"/utf8>>/binary, Title_line_right/binary>>,
Info_lines@1 = begin
_pipe = gleam@list:map(Info_lines, fun(_capture) ->
pretty_info_line(_capture, Width)
end),
gleam@string:join(_pipe, ~"\n")
end,
Content = begin
_pipe@1 = gleam@list:map(Content_lines, fun(_capture) ->
pretty_diff_line(_capture, Padding, Shared_line_style)
end),
gleam@string:join(_pipe@1, ~"\n")
end,
Left_padding_line = gleam@string:repeat(~"─", Padding),
Right_padding_line = gleam@string:repeat(~"─", (Width - Padding) - 1),
Open_line = <<<<Left_padding_line/binary, "┬"/utf8>>/binary, Right_padding_line/binary>>,
Closed_line = <<<<Left_padding_line/binary, "┴"/utf8>>/binary, Right_padding_line/binary>>,
_pipe@2 = [Title_line, ~"", Info_lines@1, ~"", Open_line, Content, Closed_line],
gleam@string:join(_pipe@2, ~"\n").
-file("src/birdie.gleam", 973).
-spec diff_snapshot_box(snapshot(accepted()), snapshot(new()), list(info_line())) -> binary().
diff_snapshot_box(Accepted, New, Additional_info_lines) ->
pretty_box(~"mismatched snapshots", to_diff_lines(Accepted, New), begin
_pipe = [snapshot_default_lines(Accepted), Additional_info_lines, [{info_line_with_no_title, ~"", do_not_split}, {info_line_with_no_title, gleam_community@ansi:red(~"- old snapshot"), do_not_split}, {info_line_with_no_title, gleam_community@ansi:green(~"+ new snapshot"), do_not_split}]],
lists:append(_pipe)
end, fun(Shared_line) ->
gleam_community@ansi:dim(Shared_line)
end).
-file("src/birdie.gleam", 953).
-spec new_snapshot_box(snapshot(new()), list(info_line())) -> binary().
new_snapshot_box(Snapshot, Additional_info_lines) ->
{snapshot, _, Content, _} = Snapshot,
Content@1 = begin
_pipe = gleam@string:split(Content, ~"\n"),
gleam@list:index_map(_pipe, fun(Line, I) ->
{diff_line, I + 1, Line, new}
end)
end,
pretty_box(~"new snapshot", Content@1, lists:append([snapshot_default_lines(Snapshot), Additional_info_lines]), fun(Shared_line) ->
Shared_line
end).
-file("src/birdie.gleam", 439).
-spec escape_title(binary()) -> binary().
escape_title(Title) ->
_pipe = gleam@string:replace(Title, ~"\n", ~"\\n"),
gleam@string:replace(_pipe, ~"\\", ~"\\\\").
-file("src/birdie.gleam", 465).
-spec serialise(snapshot(new())) -> binary().
serialise(Snapshot) ->
{snapshot, Title, Content, Info} = Snapshot,
Info_lines = case Info of
none ->
[];
{some, {snapshot_info, File, Test_function_name}} ->
[<<"file: "/utf8, File/binary>>, <<"test_name: "/utf8, Test_function_name/binary>>]
end,
_pipe = [[~"---", <<"version: "/utf8, "2.0.2"/utf8>>, <<"title: "/utf8, (escape_title(Title))/binary>>], Info_lines, [~"---", Content]],
_pipe@1 = lists:append(_pipe),
_pipe@2 = gleam@string:join(_pipe@1, ~"\n"),
gleam@string:append(_pipe@2, ~"\n").
-file("src/birdie.gleam", 499).
-spec save(snapshot(new()), binary()) -> {ok, nil} | {error, error()}.
-doc(~" Save a new snapshot to a given path.
").
save(Snapshot, Destination) ->
case gleam_stdlib:string_ends_with(Destination, ~".new") of
false ->
erlang:error(#{
gleam_error => panic,
message => ~"Looks like I've messed up something, all new snapshots should have the `.new` extension",
file => ~"src/birdie.gleam",
module => ~"birdie",
function => ~"save",
line => 505
});
true ->
_pipe = simplifile:write(Destination, serialise(Snapshot)),
gleam@result:map_error(_pipe, fun(_capture) ->
{cannot_save_new_snapshot, _capture, erlang:element(2, Snapshot), Destination}
end)
end.
-file("src/birdie.gleam", 458).
-spec trim_end_once(binary(), binary()) -> binary().
trim_end_once(String, Substring) ->
case gleam_stdlib:string_ends_with(String, Substring) of
true ->
gleam@string:drop_end(String, string:length(Substring));
false ->
String
end.
-file("src/birdie.gleam", 450).
-spec trim_content(binary(), binary()) -> binary().
-doc(~" Birdie started adding newlines to the end of files starting from `1.4.0`,
so if we're reading a snapshot created from `1.4.0` onwards then we want to
make sure to remove that newline!
").
trim_content(Content, Version) ->
case birdie@internal@version:parse(Version) of
{ok, Version@1} ->
case birdie@internal@version:compare(Version@1, birdie@internal@version:new(1, 4, 0)) of
gt ->
trim_end_once(Content, ~"\n");
eq ->
trim_end_once(Content, ~"\n");
lt ->
Content
end;
_value ->
erlang:error(#{
gleam_error => let_assert,
message => ~"corrupt birdie version",
file => ~"src/birdie.gleam",
module => ~"birdie",
function => ~"trim_content",
line => 451,
value => _value,
start => 14424,
'end' => 14471,
pattern_start => 14435,
pattern_end => 14446
})
end.
-file("src/birdie.gleam", 432).
-spec unescape_title(binary()) -> binary().
unescape_title(Title) ->
_pipe = gleam@string:split(Title, ~"\\\\"),
_pipe@1 = gleam@list:map(_pipe, fun(_capture) ->
gleam@string:replace(_capture, ~"\\n", ~"\n")
end),
gleam@string:join(_pipe@1, ~"\\").
-file("src/birdie.gleam", 366).
-spec split_n(binary(), integer(), binary()) -> {ok, {list(binary()), binary()}} | {error, nil}.
split_n(String, N, Separator) ->
case N =< 0 of
true ->
{ok, {[], String}};
false ->
gleam@result:'try'(gleam@string:split_once(String, Separator), fun(_use0) ->
{Line, Rest} = _use0,
gleam@result:'try'(split_n(Rest, N - 1, Separator), fun(_use0@1) ->
{Lines, Rest@1} = _use0@1,
{ok, {[Line | Lines], Rest@1}}
end)
end)
end.
-file("src/birdie.gleam", 381).
-spec deserialise(binary()) -> {ok, snapshot(any())} | {error, nil}.
deserialise(Raw) ->
case split_n(Raw, 4, ~"\n") of
{ok, {[~"---", <<"version: "/utf8, Version/binary>>, <<"title: "/utf8, Title/binary>>, ~"---"], Content}} ->
{ok, {snapshot, begin
_pipe = unescape_title(Title),
gleam@string:trim(_pipe)
end, trim_content(Content, Version), none}};
{ok, {[~"---\r", <<"version: "/utf8, Version/binary>>, <<"title: "/utf8, Title/binary>>, ~"---\r"], Content}} ->
{ok, {snapshot, begin
_pipe = unescape_title(Title),
gleam@string:trim(_pipe)
end, trim_content(Content, Version), none}};
{ok, _} ->
case split_n(Raw, 6, ~"\n") of
{ok, {[~"---", <<"version: "/utf8, Version@1/binary>>, <<"title: "/utf8, Title@1/binary>>, <<"file: "/utf8, File/binary>>, <<"test_name: "/utf8, Test_name/binary>>, ~"---"], Content@1}} ->
{ok, {snapshot, begin
_pipe@1 = unescape_title(Title@1),
gleam@string:trim(_pipe@1)
end, trim_content(Content@1, Version@1), {some, {snapshot_info, gleam@string:trim(File), gleam@string:trim(Test_name)}}}};
{ok, {[~"---\r", <<"version: "/utf8, Version@1/binary>>, <<"title: "/utf8, Title@1/binary>>, <<"file: "/utf8, File/binary>>, <<"test_name: "/utf8, Test_name/binary>>, ~"---\r"], Content@1}} ->
{ok, {snapshot, begin
_pipe@1 = unescape_title(Title@1),
gleam@string:trim(_pipe@1)
end, trim_content(Content@1, Version@1), {some, {snapshot_info, gleam@string:trim(File), gleam@string:trim(Test_name)}}}};
{ok, _} ->
{error, nil};
{error, _} ->
{error, nil}
end;
{error, _} ->
case split_n(Raw, 6, ~"\n") of
{ok, {[~"---", <<"version: "/utf8, Version@1/binary>>, <<"title: "/utf8, Title@1/binary>>, <<"file: "/utf8, File/binary>>, <<"test_name: "/utf8, Test_name/binary>>, ~"---"], Content@1}} ->
{ok, {snapshot, begin
_pipe@1 = unescape_title(Title@1),
gleam@string:trim(_pipe@1)
end, trim_content(Content@1, Version@1), {some, {snapshot_info, gleam@string:trim(File), gleam@string:trim(Test_name)}}}};
{ok, {[~"---\r", <<"version: "/utf8, Version@1/binary>>, <<"title: "/utf8, Title@1/binary>>, <<"file: "/utf8, File/binary>>, <<"test_name: "/utf8, Test_name/binary>>, ~"---\r"], Content@1}} ->
{ok, {snapshot, begin
_pipe@1 = unescape_title(Title@1),
gleam@string:trim(_pipe@1)
end, trim_content(Content@1, Version@1), {some, {snapshot_info, gleam@string:trim(File), gleam@string:trim(Test_name)}}}};
{ok, _} ->
{error, nil};
{error, _} ->
{error, nil}
end
end.
-file("src/birdie.gleam", 519).
-spec read_accepted(binary()) -> {ok, gleam@option:option(snapshot(accepted()))} | {error, error()}.
-doc(~" Read an accepted snapshot which might be missing.
").
read_accepted(Source) ->
case simplifile:read(Source) of
{ok, Content} ->
case deserialise(Content) of
{ok, Snapshot} ->
{ok, {some, Snapshot}};
{error, nil} ->
{error, {corrupted_snapshot, Source}}
end;
{error, enoent} ->
{ok, none};
{error, Reason} ->
{error, {cannot_read_accepted_snapshot, Reason, Source}}
end.
-file("src/birdie.gleam", 673).
-spec to_accepted_path(binary()) -> binary().
-doc(~" Turns a new snapshot path into the path of the corresponding accepted
snapshot.
").
to_accepted_path(File) ->
<<<<(filepath:strip_extension(File))/binary, "."/utf8>>/binary, "accepted"/utf8>>.
-file("src/birdie.gleam", 653).
-spec file_name(binary()) -> binary().
-doc(~" Turns a snapshot's title into a file name stripping it of all dangerous
characters (or at least those I could think ok 😁).
").
file_name(Title) ->
_pipe = gleam@string:replace(Title, ~"/", ~" "),
_pipe@1 = gleam@string:replace(_pipe, ~"\\", ~" "),
_pipe@2 = gleam@string:replace(_pipe@1, ~"\n", ~" "),
_pipe@3 = gleam@string:replace(_pipe@2, ~"\t", ~" "),
_pipe@4 = gleam@string:replace(_pipe@3, ~"\r", ~" "),
_pipe@5 = gleam@string:replace(_pipe@4, ~".", ~" "),
_pipe@6 = gleam@string:replace(_pipe@5, ~":", ~" "),
justin:snake_case(_pipe@6).
-file("src/birdie.gleam", 666).
-spec new_destination(snapshot(new()), binary()) -> binary().
-doc(~" Returns the path where a new snapshot should be saved.
").
new_destination(Snapshot, Folder) ->
<<<<(filepath:join(Folder, file_name(erlang:element(2, Snapshot))))/binary, "."/utf8>>/binary, "new"/utf8>>.
-file("src/birdie.gleam", 346).
-spec validate_snapshot_title(binary()) -> {ok, nil} | {error, error()}.
validate_snapshot_title(Title) ->
case gleam@string:trim(Title) of
~"" ->
{error, snapshot_with_empty_title};
_ ->
{ok, nil}
end.
-file("src/birdie.gleam", 279).
-spec do_snap(binary(), binary()) -> {ok, outcome()} | {error, error()}.
do_snap(Content, Title) ->
gleam@result:'try'(validate_snapshot_title(Title), fun(_) ->
gleam@result:'try'(snapshot_folder(), fun(Folder) ->
New = {snapshot, Title, Content, none},
New_snapshot_path = new_destination(New, Folder),
Accepted_snapshot_path = to_accepted_path(New_snapshot_path),
gleam@result:'try'(read_accepted(Accepted_snapshot_path), fun(Accepted) ->
case Accepted of
none ->
gleam@result:'try'(save(New, New_snapshot_path), fun(_) ->
{ok, {new_snapshot_created, New, New_snapshot_path}}
end);
{some, Accepted@1} ->
gleam@result:'try'(global_referenced_file(), fun(Referenced_file) ->
gleam@result:'try'(begin
_pipe = simplifile:append(Referenced_file, <<(filepath:base_name(Accepted_snapshot_path))/binary, "\n"/utf8>>),
gleam@result:map_error(_pipe, fun(_value) ->
{cannot_mark_snapshot_as_referenced, _value}
end)
end, fun(_) ->
case erlang:element(3, Accepted@1) =:= erlang:element(3, New) of
true ->
_ = simplifile_erl:delete(New_snapshot_path),
{ok, same};
false ->
gleam@result:'try'(save(New, New_snapshot_path), fun(_) ->
{ok, {different, Accepted@1, New}}
end)
end
end)
end)
end
end)
end)
end).
-file("src/birdie.gleam", 241).
-spec snap(binary(), binary()) -> nil.
-doc(~" Performs a snapshot test with the given title, saving the content to a new
snapshot file. All your snapshots will be stored in a folder called
`birdie_snapshots` in the project's root.
The test will fail if there already is an accepted snapshot with the same
title and a different content.
The test will also fail if there's no accepted snapshot with the same title
to make sure you will review new snapshots as well.
> 🚨 A snapshot is saved to a file named after its title, so all titles
> should be unique! Otherwise you'd end up comparing unrelated snapshots.
> 🐦‍⬛ To review all your snapshots interactively you can run
> `gleam run -m birdie`.
>
> To get an help text and all the available options you can run
> `gleam run -m birdie help`.
").
snap(Content, Title) ->
case do_snap(Content, Title) of
{ok, same} ->
nil;
{ok, {new_snapshot_created, Snapshot, _}} ->
Hint_message = gleam_community@ansi:yellow(~"run `gleam run -m birdie` to review the snapshots"),
Hint = {info_line_with_title, Hint_message, do_not_split, ~"hint"},
Box = new_snapshot_box(Snapshot, [Hint]),
gleam_stdlib:println_error(<<<<"\n\n"/utf8, Box/binary>>/binary, "\n"/utf8>>),
erlang:error(#{
gleam_error => panic,
message => ~"Birdie snapshot test failed",
file => ~"src/birdie.gleam",
module => ~"birdie",
function => ~"snap",
line => 251
});
{ok, {different, Accepted, New}} ->
Hint_message@1 = gleam_community@ansi:yellow(~"run `gleam run -m birdie` to review the snapshots"),
Hint@1 = {info_line_with_title, Hint_message@1, do_not_split, ~"hint"},
Box@1 = diff_snapshot_box(Accepted, New, [Hint@1]),
gleam_stdlib:println_error(<<<<"\n\n"/utf8, Box@1/binary>>/binary, "\n"/utf8>>),
erlang:error(#{
gleam_error => panic,
message => ~"Birdie snapshot test failed",
file => ~"src/birdie.gleam",
module => ~"birdie",
function => ~"snap",
line => 260
});
{error, Error} ->
erlang:error(#{
gleam_error => panic,
message => <<"Birdie snapshot test failed\n"/utf8, (begin
_pipe = to_diagnostic(Error),
_pipe@1 = gleam@list:map(_pipe, fun birdie@internal@diagnostic:to_string/1),
gleam@string:join(_pipe@1, ~"\n\n")
end)/binary>>,
file => ~"src/birdie.gleam",
module => ~"birdie",
function => ~"snap",
line => 264
})
end.
-file("src/birdie.gleam", 539).
-spec read_new(binary()) -> {ok, snapshot(new())} | {error, error()}.
-doc(~" Read a new snapshot.
> ℹ️ Notice the different return type compared to `read_accepted`: when we
> try to read a new snapshot we are sure it's there (because we've listed
> the directory or something else) so if it's not present that's an error
> and we don't return an `Ok(None)`.
").
read_new(Source) ->
case simplifile:read(Source) of
{ok, Content} ->
gleam@result:replace_error(deserialise(Content), {corrupted_snapshot, Source});
{error, Reason} ->
{error, {cannot_read_new_snapshot, Reason, Source}}
end.
-file("src/birdie.gleam", 550).
-spec list_new_snapshots(binary()) -> {ok, list(binary())} | {error, error()}.
-doc(~" List all the new snapshots in a folder. Every file is automatically
prepended with the folder so you get the full path of each file.
").
list_new_snapshots(Folder) ->
case simplifile_erl:read_directory(Folder) of
{error, Reason} ->
{error, {cannot_read_snapshots, Reason, Folder}};
{ok, Files} ->
{ok, begin
gleam@list:filter_map(Files, fun(File) ->
case filepath:extension(File) of
{ok, Extension} when Extension =:= ~"new" ->
{ok, filepath:join(Folder, File)};
_ ->
{error, nil}
end
end)
end}
end.
-file("src/birdie.gleam", 570).
-spec list_accepted_snapshots(binary()) -> {ok, list(binary())} | {error, error()}.
-doc(~" List all the accepted snapshots in a folder. Every file is automatically
prepended with the folder so you get the full path of each file.
").
list_accepted_snapshots(Folder) ->
case simplifile_erl:read_directory(Folder) of
{error, Reason} ->
{error, {cannot_read_snapshots, Reason, Folder}};
{ok, Files} ->
{ok, begin
gleam@list:filter_map(Files, fun(File) ->
case filepath:extension(File) of
{ok, Extension} when Extension =:= ~"accepted" ->
{ok, filepath:join(Folder, File)};
_ ->
{error, nil}
end
end)
end}
end.
-file("src/birdie.gleam", 1379).
-spec get_info_for_snapshot(birdie@internal@analyser:analyser(), binary()) -> {ok, snapshot_info()} | {error, nil}.
-doc(~" If there's a _single_ snapshot with the given title, this return information
about it.
If there's no snapshot, or there's multiple ones then that's an error! We
can't reliably return information about because it's either missing, or
there's multiple snapshots sharing the same title and it's impossible to
know which one we're referring to.").
get_info_for_snapshot(Analyser, Title) ->
case birdie@internal@analyser:get_snapshot_tests(Analyser, Title) of
[] ->
{error, nil};
[_, _ | _] ->
{error, nil};
[{Uri, {snapshot_test, _, _, _, Test_function_name, _}}] ->
{ok, {snapshot_info, erlang:element(6, Uri), Test_function_name}}
end.
-file("src/birdie.gleam", 587).
-spec accept_snapshot(binary(), birdie@internal@analyser:analyser()) -> {ok, nil} | {error, error()}.
accept_snapshot(New_snapshot_path, Analyser) ->
gleam@result:'try'(read_new(New_snapshot_path), fun(Snapshot) ->
{snapshot, Title, Content, _} = Snapshot,
Accepted_snapshot_path = to_accepted_path(New_snapshot_path),
gleam@result:'try'(referenced_file_path(), fun(Referenced_file) ->
gleam@result:'try'(case simplifile_erl:is_file(Referenced_file) of
{ok, _} ->
{ok, nil};
{error, _} ->
_pipe = simplifile:create_file(Referenced_file),
gleam@result:map_error(_pipe, fun(_capture) ->
{cannot_create_referenced_file, Referenced_file, _capture}
end)
end, fun(_) ->
gleam@result:'try'(begin
_pipe@1 = simplifile:append(Referenced_file, <<(filepath:base_name(Accepted_snapshot_path))/binary, "\n"/utf8>>),
gleam@result:map_error(_pipe@1, fun(_value) ->
{cannot_mark_snapshot_as_referenced, _value}
end)
end, fun(_) ->
case get_info_for_snapshot(Analyser, Title) of
{ok, Info} ->
gleam@result:'try'(begin
_pipe@2 = simplifile_erl:delete(New_snapshot_path),
gleam@result:map_error(_pipe@2, fun(_capture) ->
{cannot_accept_snapshot, _capture, New_snapshot_path}
end)
end, fun(_) ->
_pipe@3 = {snapshot, Title, Content, {some, Info}},
_pipe@4 = serialise(_pipe@3),
_pipe@5 = simplifile:write(Accepted_snapshot_path, _pipe@4),
gleam@result:map_error(_pipe@5, fun(_capture) ->
{cannot_accept_snapshot, _capture, Accepted_snapshot_path}
end)
end);
{error, _} ->
_pipe@3 = simplifile_erl:rename_file(New_snapshot_path, Accepted_snapshot_path),
gleam@result:map_error(_pipe@3, fun(_capture) ->
{cannot_accept_snapshot, _capture, New_snapshot_path}
end)
end
end)
end)
end)
end).
-file("src/birdie.gleam", 643).
-spec reject_snapshot(binary()) -> {ok, nil} | {error, error()}.
reject_snapshot(New_snapshot_path) ->
_pipe = simplifile_erl:delete(New_snapshot_path),
gleam@result:map_error(_pipe, fun(_capture) ->
{cannot_reject_snapshot, _capture, New_snapshot_path}
end).
-file("src/birdie.gleam", 995).
-spec regular_snapshot_box(snapshot(new()), list(info_line())) -> binary().
regular_snapshot_box(New, Additional_info_lines) ->
{snapshot, _, Content, _} = New,
Content@1 = begin
_pipe = gleam@string:split(Content, ~"\n"),
gleam@list:index_map(_pipe, fun(Line, I) ->
{diff_line, I + 1, Line, shared}
end)
end,
pretty_box(~"mismatched snapshots", Content@1, begin
_pipe@1 = [snapshot_default_lines(New), Additional_info_lines],
lists:append(_pipe@1)
end, fun(Shared_line) ->
Shared_line
end).
-file("src/birdie.gleam", 1737).
-spec replace_first(list(LYF), LYF, LYF) -> list(LYF).
-doc(~" Replaces the first occurrence of an element in the list with the given
replacement.
").
replace_first(List, Item, Replacement) ->
case List of
[] ->
[];
[First | Rest] when First =:= Item ->
[Replacement | Rest];
[First@1 | Rest@1] ->
[First@1 | replace_first(Rest@1, Item, Replacement)]
end.
-file("src/birdie.gleam", 1250).
-spec ask_yes_or_no(binary()) -> answer().
ask_yes_or_no(Prompt) ->
case birdie_ffi:get_line(<<Prompt/binary, " [Y/n] "/utf8>>) of
{error, _} ->
no;
{ok, Line} ->
case begin
_pipe = string:lowercase(Line),
gleam@string:trim(_pipe)
end of
~"yes" ->
yes;
~"y" ->
yes;
~"" ->
yes;
_ ->
no
end
end.
-file("src/birdie.gleam", 1652).
-spec stale_snapshots_file_names() -> {ok, list(binary())} | {error, error()}.
stale_snapshots_file_names() ->
gleam@result:'try'(snapshot_folder(), fun(Snapshots_folder) ->
gleam@result:'try'(referenced_file_path(), fun(Referenced_file) ->
case simplifile:read(Referenced_file) of
{error, enoent} ->
{error, missing_referenced_file};
{error, Reason} ->
{error, {cannot_read_referenced_file, Referenced_file, Reason}};
{ok, Non_stale_snapshots} ->
Existing_accepted_snapshots = begin
_pipe = simplifile:get_files(Snapshots_folder),
_pipe@1 = gleam@result:unwrap(_pipe, []),
gleam@list:fold(_pipe@1, gleam@set:new(), fun(Files, File) ->
case filepath:extension(File) =:= {ok, ~"accepted"} of
true ->
gleam@set:insert(Files, filepath:base_name(File));
false ->
Files
end
end)
end,
Non_stale_snapshots@1 = gleam@string:split(Non_stale_snapshots, ~"\n"),
_pipe@2 = Existing_accepted_snapshots,
_pipe@3 = gleam@set:drop(_pipe@2, Non_stale_snapshots@1),
_pipe@4 = gleam@set:to_list(_pipe@3),
{ok, _pipe@4}
end
end)
end).
-file("src/birdie.gleam", 1696).
-spec delete_stale() -> {ok, nil} | {error, error()}.
delete_stale() ->
gleam_stdlib:println(~"Checking stale snapshots..."),
gleam@result:'try'(snapshot_folder(), fun(Snapshots_folder) ->
gleam@result:'try'(stale_snapshots_file_names(), fun(Stale_snapshots) ->
_pipe = gleam@list:try_each(Stale_snapshots, fun(Stale_snapshot) ->
_pipe@1 = filepath:join(Snapshots_folder, Stale_snapshot),
simplifile_erl:delete(_pipe@1)
end),
gleam@result:map_error(_pipe, fun(_capture) ->
{cannot_delete_stale_snapshot, _capture}
end)
end)
end).
-file("src/birdie.gleam", 1708).
-spec report_status({ok, nil} | {error, error()}) -> nil.
report_status(Result) ->
case Result of
{ok, nil} ->
gleam_stdlib:println(gleam_community@ansi:green(~"🐦‍⬛ Done!")),
erlang:halt(0);
{error, Error} ->
_pipe = to_diagnostic(Error),
_pipe@1 = gleam@list:map(_pipe, fun birdie@internal@diagnostic:to_string/1),
_pipe@2 = gleam@string:join(_pipe@1, ~"\n\n"),
gleam_stdlib:println_error(_pipe@2),
erlang:halt(1)
end.
-file("src/birdie.gleam", 1687).
-spec check_stale() -> {ok, nil} | {error, error()}.
check_stale() ->
gleam_stdlib:println(~"Checking stale snapshots..."),
gleam@result:'try'(stale_snapshots_file_names(), fun(Stale_snapshots) ->
case Stale_snapshots of
[] ->
{ok, nil};
[_ | _] ->
{error, {stale_snapshots_found, Stale_snapshots}}
end
end).
-file("src/birdie.gleam", 1344).
-spec update_accepted_snapshots(binary(), birdie@internal@analyser:analyser()) -> {ok, nil} | {error, error()}.
update_accepted_snapshots(Snapshots_folder, Analyser) ->
gleam@result:'try'(list_accepted_snapshots(Snapshots_folder), fun(Accepted_snapshots) ->
gleam@list:try_each(Accepted_snapshots, fun(Accepted_snapshot) ->
gleam@result:'try'(read_accepted(Accepted_snapshot), fun(Snapshot) ->
case Snapshot of
none ->
{ok, nil};
{some, {snapshot, Title, _, Existing_info} = Snapshot@1} ->
case {get_info_for_snapshot(Analyser, Title), Existing_info} of
{{ok, New_info}, {some, Existing_info@1}} when New_info =/= Existing_info@1 ->
_pipe = {snapshot, erlang:element(2, Snapshot@1), erlang:element(3, Snapshot@1), {some, New_info}},
_pipe@1 = serialise(_pipe),
_pipe@2 = simplifile:write(Accepted_snapshot, _pipe@1),
gleam@result:map_error(_pipe@2, fun(_capture) ->
{cannot_accept_snapshot, _capture, Accepted_snapshot}
end);
{{ok, Info}, none} ->
_pipe@3 = {snapshot, erlang:element(2, Snapshot@1), erlang:element(3, Snapshot@1), {some, Info}},
_pipe@4 = serialise(_pipe@3),
_pipe@5 = simplifile:write(Accepted_snapshot, _pipe@4),
gleam@result:map_error(_pipe@5, fun(_capture) ->
{cannot_accept_snapshot, _capture, Accepted_snapshot}
end);
{_, _} ->
{ok, nil}
end
end
end)
end)
end).
-file("src/birdie.gleam", 1640).
-spec filepath_to_uri(binary()) -> gleam@uri:uri().
filepath_to_uri(Path) ->
{uri, {some, ~"file"}, none, none, none, Path, none, none}.
-file("src/birdie.gleam", 1605).
-spec analyse_test_directory() -> {ok, birdie@internal@analyser:analyser()} | {error, error()}.
-doc(~" This finds the current Gleam project's test directory and analyses all the
modules inside to find snapshot tests and information related to them.
This could fail under different circumstances:
- If the file system operations (like reading) fail, should technically
never happen in a normal scenario
- OR if the test directory contains snapshots with duplicate titles!
This is something that could happen and we need to show a nice error
message.").
analyse_test_directory() ->
gleam@result:'try'(begin
_pipe = birdie@internal@project:find_root(),
gleam@result:map_error(_pipe, fun(_value) ->
{cannot_find_project_root, _value}
end)
end, fun(Root) ->
gleam@result:'try'(begin
_pipe@1 = filepath:join(Root, ~"test"),
_pipe@2 = simplifile:get_files(_pipe@1),
gleam@result:map_error(_pipe@2, fun(_value@1) ->
{cannot_read_test_directory, _value@1}
end)
end, fun(Files) ->
gleam@result:'try'(gleam@list:try_fold(Files, birdie@internal@analyser:new(), fun(Analyser, File) ->
Is_gleam_file = filepath:extension(File) =:= {ok, ~"gleam"},
gleam@bool:guard(not Is_gleam_file, {ok, Analyser}, fun() ->
gleam@result:'try'(begin
_pipe@3 = simplifile:read(File),
gleam@result:map_error(_pipe@3, fun(_capture) ->
{cannot_read_test_file, _capture, File}
end)
end, fun(Source) ->
Path = filepath_to_uri(File),
{ok, birdie@internal@analyser:analyse(Analyser, {module, Path, Source})}
end)
end)
end), fun(Analyser) ->
case birdie@internal@analyser:errors(Analyser) of
[] ->
{ok, Analyser};
[_ | _] = Errors ->
{error, {analysis_error, Errors}}
end
end)
end)
end).
-file("src/birdie.gleam", 1580).
-spec reject_all() -> {ok, nil} | {error, error()}.
reject_all() ->
gleam_stdlib:println(~"Looking for new snapshots..."),
gleam@result:'try'(snapshot_folder(), fun(Snapshots_folder) ->
gleam@result:'try'(list_new_snapshots(Snapshots_folder), fun(New_snapshots) ->
gleam@result:'try'(analyse_test_directory(), fun(Analyser) ->
gleam@result:'try'(update_accepted_snapshots(Snapshots_folder, Analyser), fun(_) ->
case erlang:length(New_snapshots) of
0 ->
gleam_stdlib:println(~"No new snapshots to reject.");
1 ->
gleam_stdlib:println(~"Rejecting one new snapshot.");
N ->
gleam_stdlib:println(<<<<"Rejecting "/utf8, (erlang:integer_to_binary(N))/binary>>/binary, " new snapshots."/utf8>>)
end,
gleam@list:try_each(New_snapshots, fun reject_snapshot/1)
end)
end)
end)
end).
-file("src/birdie.gleam", 1563).
-spec accept_all() -> {ok, nil} | {error, error()}.
accept_all() ->
gleam_stdlib:println(~"Looking for new snapshots..."),
gleam@result:'try'(snapshot_folder(), fun(Snapshots_folder) ->
gleam@result:'try'(list_new_snapshots(Snapshots_folder), fun(New_snapshots) ->
gleam@result:'try'(analyse_test_directory(), fun(Analyser) ->
gleam@result:'try'(update_accepted_snapshots(Snapshots_folder, Analyser), fun(_) ->
case erlang:length(New_snapshots) of
0 ->
gleam_stdlib:println(~"No new snapshots to accept.");
1 ->
gleam_stdlib:println(~"Accepting one new snapshot.");
N ->
gleam_stdlib:println(<<<<"Accepting "/utf8, (erlang:integer_to_binary(N))/binary>>/binary, " new snapshots."/utf8>>)
end,
gleam@list:try_each(New_snapshots, fun(_capture) ->
accept_snapshot(_capture, Analyser)
end)
end)
end)
end)
end).
-file("src/birdie.gleam", 1751).
-spec clear() -> nil.
-doc(~" Clear the screen.
").
clear() ->
gleam_stdlib:print(~"\x{1b}c"),
gleam_stdlib:print(~"\x{1b}[H\x{1b}[J").
-file("src/birdie.gleam", 1495).
-spec toggle_mode(review_mode()) -> review_mode().
toggle_mode(Mode) ->
case Mode of
show_diff ->
hide_diff;
hide_diff ->
show_diff
end.
-file("src/birdie.gleam", 1758).
-spec cursor_up(integer()) -> nil.
-doc(~" Move the cursor up a given number of lines.
").
cursor_up(N) ->
gleam_stdlib:print(<<<<"\x{1b}["/utf8, (erlang:integer_to_binary(N))/binary>>/binary, "A"/utf8>>).
-file("src/birdie.gleam", 1764).
-spec clear_line() -> nil.
-doc(~" Clear the line the cursor is currently on.
").
clear_line() ->
gleam_stdlib:print(~"\x{1b}[2K").
-file("src/birdie.gleam", 1515).
-spec ask_choice(review_mode()) -> {ok, review_choice()} | {error, error()}.
-doc(~" Asks the user to make a choice: it first prints a reminder of the options
and waits for the user to choose one.
Will prompt again if the choice is not amongst the possible options.
").
ask_choice(Mode) ->
Diff_message = case Mode of
hide_diff ->
~" show diff ";
show_diff ->
~" hide diff "
end,
gleam_stdlib:println(<<<<<<<<<<(gleam_community@ansi:bold(gleam_community@ansi:green(~" a")))/binary, " accept "/utf8>>/binary, (gleam_community@ansi:dim(~"accept the new snapshot\n"))/binary>>/binary, <<<<(gleam_community@ansi:bold(gleam_community@ansi:red(~" r")))/binary, " reject "/utf8>>/binary, (gleam_community@ansi:dim(~"reject the new snapshot\n"))/binary>>/binary>>/binary, <<<<(gleam_community@ansi:bold(gleam_community@ansi:yellow(~" s")))/binary, " skip "/utf8>>/binary, (gleam_community@ansi:dim(~"skip the snapshot for now\n"))/binary>>/binary>>/binary, <<<<(gleam_community@ansi:bold(gleam_community@ansi:cyan(~" d")))/binary, Diff_message/binary>>/binary, (gleam_community@ansi:dim(~"toggle snapshot diff\n"))/binary>>/binary>>),
clear_line(),
case gleam@result:map(birdie_ffi:get_line(~"> "), fun gleam@string:trim/1) of
{ok, ~"a"} ->
{ok, accept_snapshot};
{ok, ~"r"} ->
{ok, reject_snapshot};
{ok, ~"s"} ->
{ok, skip_snapshot};
{ok, ~"d"} ->
{ok, toggle_diff_view};
{ok, _} ->
cursor_up(6),
ask_choice(Mode);
{error, _} ->
{error, cannot_read_user_input}
end.
-file("src/birdie.gleam", 1421).
-spec review_loop(list(binary()), birdie@internal@analyser:analyser(), integer(), integer(), review_mode()) -> {ok, nil} | {error, error()}.
-doc(~" Reviews all the new snapshots one by one.").
review_loop(New_snapshot_paths, Analyser, Current, Out_of, Mode) ->
case New_snapshot_paths of
[] ->
{ok, nil};
[New_snapshot_path | Rest] ->
clear(),
gleam@result:'try'(read_new(New_snapshot_path), fun(New_snapshot) ->
New_snapshot@1 = {snapshot, erlang:element(2, New_snapshot), erlang:element(3, New_snapshot), begin
_pipe = get_info_for_snapshot(Analyser, erlang:element(2, New_snapshot)),
gleam@option:from_result(_pipe)
end},
Accepted_snapshot_path = to_accepted_path(New_snapshot_path),
gleam@result:'try'(read_accepted(Accepted_snapshot_path), fun(Accepted_snapshot) ->
Progress = <<<<<<(gleam_community@ansi:dim(~"Reviewing "))/binary, (gleam_community@ansi:bold(gleam_community@ansi:yellow(rank:ordinalise(Current))))/binary>>/binary, (gleam_community@ansi:dim(~" out of "))/binary>>/binary, (gleam_community@ansi:bold(gleam_community@ansi:yellow(erlang:integer_to_binary(Out_of))))/binary>>,
Box = case {Accepted_snapshot, Mode} of
{none, _} ->
new_snapshot_box(New_snapshot@1, []);
{{some, Accepted_snapshot@1}, show_diff} ->
diff_snapshot_box(Accepted_snapshot@1, New_snapshot@1, []);
{{some, _}, hide_diff} ->
regular_snapshot_box(New_snapshot@1, [])
end,
gleam_stdlib:println(<<<<<<Progress/binary, "\n\n"/utf8>>/binary, Box/binary>>/binary, "\n"/utf8>>),
gleam@result:'try'(ask_choice(Mode), fun(Choice) ->
case Choice of
accept_snapshot ->
gleam@result:'try'(accept_snapshot(New_snapshot_path, Analyser), fun(_) ->
review_loop(Rest, Analyser, Current + 1, Out_of, Mode)
end);
reject_snapshot ->
gleam@result:'try'(reject_snapshot(New_snapshot_path), fun(_) ->
review_loop(Rest, Analyser, Current + 1, Out_of, Mode)
end);
skip_snapshot ->
review_loop(Rest, Analyser, Current + 1, Out_of, Mode);
toggle_diff_view ->
Mode@1 = toggle_mode(Mode),
review_loop(New_snapshot_paths, Analyser, Current, Out_of, Mode@1)
end
end)
end)
end)
end.
-file("src/birdie.gleam", 1390).
-spec do_review(binary(), birdie@internal@analyser:analyser()) -> {ok, nil} | {error, error()}.
do_review(Snapshots_folder, Analyser) ->
gleam@result:'try'(list_new_snapshots(Snapshots_folder), fun(New_snapshots) ->
case erlang:length(New_snapshots) of
0 ->
gleam_stdlib:println(~"No new snapshots to review."),
{ok, nil};
N ->
Result = review_loop(New_snapshots, Analyser, 1, N, show_diff),
clear(),
gleam@result:'try'(Result, fun(_) ->
gleam_stdlib:println(case N of
1 ->
~"Reviewed one snapshot";
N@1 ->
<<<<"Reviewed "/utf8, (erlang:integer_to_binary(N@1))/binary>>/binary, " snapshots"/utf8>>
end),
{ok, nil}
end)
end
end).
-file("src/birdie.gleam", 1332).
-spec review() -> {ok, nil} | {error, error()}.
review() ->
gleam@result:'try'(snapshot_folder(), fun(Snapshots_folder) ->
gleam@result:'try'(analyse_test_directory(), fun(Analyser) ->
gleam@result:'try'(update_accepted_snapshots(Snapshots_folder, Analyser), fun(_) ->
gleam@result:'try'(do_review(Snapshots_folder, Analyser), fun(_) ->
{ok, nil}
end)
end)
end)
end).
-file("src/birdie.gleam", 1295).
-spec migrate_from_old_directory() -> {ok, nil} | {error, error()}.
migrate_from_old_directory() ->
gleam@result:'try'(snapshot_folder_name(), fun(Snapshot_folder) ->
gleam@result:'try'(legacy_snapshot_folder_name(), fun(Legacy_snapshot_folder) ->
case simplifile_erl:is_directory(Legacy_snapshot_folder) of
{error, enoent} ->
{ok, nil};
{ok, false} ->
{ok, nil};
{error, Reason} ->
{error, {cannot_read_snapshots, Reason, Legacy_snapshot_folder}};
{ok, true} ->
_pipe = {diagnostic, warn, ~"moved snapshots directory", none, ~"Starting from 1.6 birdie is using the `test/birdie_snapshots` directory to
store snapshot tests, so `birdie_snapshots` was moved there.", none},
_pipe@1 = birdie@internal@diagnostic:to_string(_pipe),
_pipe@2 = gleam@string:append(_pipe@1, ~"\n"),
gleam_stdlib:println(_pipe@2),
_pipe@3 = simplifile_erl:rename_file(Legacy_snapshot_folder, Snapshot_folder),
gleam@result:map_error(_pipe@3, fun(_capture) ->
{cannot_migrate_birdie_snapshot_directory, _capture, Legacy_snapshot_folder, Snapshot_folder}
end)
end
end)
end).
-file("src/birdie.gleam", 1266).
-spec run_command(birdie@internal@cli:command()) -> nil.
run_command(Command) ->
case migrate_from_old_directory() of
{error, Diagnostic} ->
report_status({error, Diagnostic});
{ok, _} ->
case Command of
review ->
report_status(review());
accept ->
report_status(accept_all());
reject ->
report_status(reject_all());
{stale, check_stale} ->
report_status(check_stale());
{stale, delete_stale} ->
report_status(delete_stale());
help ->
gleam_stdlib:println(birdie@internal@cli:help_text(~"2.0.2", help, full_command));
{with_help_option, Command@1, Explained} ->
gleam_stdlib:println(birdie@internal@cli:help_text(~"2.0.2", Command@1, Explained))
end
end.
-file("src/birdie.gleam", 1195).
-spec parse_and_run(list(binary())) -> nil.
parse_and_run(Args) ->
case birdie@internal@cli:parse(Args) of
{ok, Command} ->
run_command(Command);
{error, {unknown_option, Command@1, Option}} ->
_pipe = birdie@internal@cli:unknown_option_error(~"2.0.2", Command@1, Option),
gleam_stdlib:println(_pipe),
erlang:halt(1);
{error, {unknown_subcommand, Command@2, Subcommand}} ->
_pipe@1 = birdie@internal@cli:unknown_subcommand_error(~"2.0.2", Command@2, Subcommand),
gleam_stdlib:println(_pipe@1),
erlang:halt(1);
{error, {missing_subcommand, Command@3}} ->
_pipe@2 = birdie@internal@cli:missing_subcommand_error(~"2.0.2", Command@3),
gleam_stdlib:println(_pipe@2),
erlang:halt(1);
{error, {unexpected_argument, Command@4, Argument}} ->
_pipe@3 = birdie@internal@cli:unexpected_argument_error(~"2.0.2", Command@4, Argument),
gleam_stdlib:println(_pipe@3),
erlang:halt(1);
{error, {unknown_command, Command@5}} ->
case birdie@internal@cli:similar_command(Command@5) of
{error, nil} ->
_pipe@4 = birdie@internal@cli:unknown_command_error(Command@5, true),
gleam_stdlib:println(_pipe@4),
erlang:halt(1);
{ok, New_command} ->
_pipe@5 = birdie@internal@cli:unknown_command_error(Command@5, false),
gleam_stdlib:println(_pipe@5),
Prompt = <<<<"I think you misspelled `"/utf8, New_command/binary>>/binary, "`, would you like me to run it instead?"/utf8>>,
case ask_yes_or_no(Prompt) of
no ->
gleam_stdlib:println(<<"\n"/utf8, (birdie@internal@cli:main_help_text())/binary>>),
erlang:halt(1);
yes ->
_pipe@6 = replace_first(Args, Command@5, New_command),
parse_and_run(_pipe@6)
end
end
end.
-file("src/birdie.gleam", 1191).
-spec main() -> nil.
-doc(~" Reviews the snapshots in the project's folder.
This function will behave differently depending on the command line
arguments provided to the program.
To have a look at all the available options you can run
`gleam run -m birdie help`.
> 🐦‍⬛ The recommended workflow is to first run your gleeunit tests with
> `gleam test` and then review any new/failing snapshot manually running
> `gleam run -m birdie`.
>
> And don't forget to commit your snapshots! Those should be treated as code
> and checked with the vcs you're using.
").
main() ->
parse_and_run(erlang:element(4, argv:load())).