-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(), <>)} 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, <<<>/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; _ -> <<<>/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(<>))/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())).