src/gleedoc@generate.erl

-module(gleedoc@generate).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/gleedoc/generate.gleam").
-export([generate_tests/2, clean_generated/1]).
-export_type([config/0, import/0]).

-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.

-type config() :: {config, binary()}.

-type import() :: {import, binary(), list(binary())}.

-file("src/gleedoc/generate.gleam", 243).
?DOC(" Replace characters that aren't valid in function names\n").
-spec sanitize_name(binary()) -> binary().
sanitize_name(Name) ->
    _pipe = Name,
    _pipe@1 = gleam@string:replace(_pipe, <<"."/utf8>>, <<"_"/utf8>>),
    gleam@string:replace(_pipe@1, <<"-"/utf8>>, <<"_"/utf8>>).

-file("src/gleedoc/generate.gleam", 216).
-spec generate_test_function(gleedoc@parse:code_block(), integer()) -> binary().
generate_test_function(Block, Index) ->
    Target_name = gleam@option:unwrap(
        erlang:element(3, erlang:element(4, Block)),
        <<"module"/utf8>>
    ),
    Test_func_name = <<<<<<(sanitize_name(Target_name))/binary, "_"/utf8>>/binary,
            (erlang:integer_to_binary(Index + 1))/binary>>/binary,
        "_test"/utf8>>,
    Source_info = <<<<<<"// From: "/utf8,
                (erlang:element(4, erlang:element(4, Block)))/binary>>/binary,
            ":"/utf8>>/binary,
        (erlang:integer_to_binary(
            (erlang:element(5, erlang:element(4, Block)) + erlang:element(
                5,
                Block
            ))
            - 1
        ))/binary>>,
    Code = begin
        _pipe = erlang:element(3, Block),
        gleam@string:trim(_pipe)
    end,
    gleam@string:join(
        [<<""/utf8>>,
            Source_info,
            <<<<"pub fn "/utf8, Test_func_name/binary>>/binary, "() {"/utf8>>,
            <<"  "/utf8,
                (gleam@string:replace(Code, <<"\n"/utf8>>, <<"\n  "/utf8>>))/binary>>,
            <<"}"/utf8>>],
        <<"\n"/utf8>>
    ).

-file("src/gleedoc/generate.gleam", 211).
-spec generate_test_functions(list(gleedoc@parse:code_block())) -> list(binary()).
generate_test_functions(Blocks) ->
    _pipe = Blocks,
    gleam@list:index_map(_pipe, fun generate_test_function/2).

-file("src/gleedoc/generate.gleam", 140).
-spec import_to_string(import()) -> binary().
import_to_string(Imp) ->
    case erlang:element(3, Imp) of
        [] ->
            <<"import "/utf8, (erlang:element(2, Imp))/binary>>;

        Names ->
            Types = begin
                _pipe = Names,
                _pipe@1 = gleam@list:filter(
                    _pipe,
                    fun(N) ->
                        gleam_stdlib:string_starts_with(N, <<"type "/utf8>>)
                    end
                ),
                gleam@list:sort(_pipe@1, fun gleam@string:compare/2)
            end,
            Values = begin
                _pipe@2 = Names,
                _pipe@3 = gleam@list:filter(
                    _pipe@2,
                    fun(N@1) ->
                        not gleam_stdlib:string_starts_with(
                            N@1,
                            <<"type "/utf8>>
                        )
                    end
                ),
                gleam@list:sort(_pipe@3, fun gleam@string:compare/2)
            end,
            Names_str = gleam@string:join(
                lists:append(Types, Values),
                <<", "/utf8>>
            ),
            <<<<<<<<"import "/utf8, (erlang:element(2, Imp))/binary>>/binary,
                        ".{"/utf8>>/binary,
                    Names_str/binary>>/binary,
                "}"/utf8>>
    end.

-file("src/gleedoc/generate.gleam", 179).
-spec list_find_import(list(import()), binary()) -> {ok, import()} |
    {error, nil}.
list_find_import(Imports, Module) ->
    gleam@list:find(Imports, fun(Imp) -> erlang:element(2, Imp) =:= Module end).

-file("src/gleedoc/generate.gleam", 124).
-spec parse_import(binary()) -> import().
parse_import(Line) ->
    Rest = begin
        _pipe = Line,
        _pipe@1 = gleam@string:trim(_pipe),
        _pipe@2 = gleam@string:drop_start(_pipe@1, 7),
        gleam@string:trim_start(_pipe@2)
    end,
    case gleam@string:split_once(Rest, <<".{"/utf8>>) of
        {ok, {Module, Names_part}} ->
            Names = begin
                _pipe@3 = Names_part,
                _pipe@4 = gleam@string:replace(
                    _pipe@3,
                    <<"}"/utf8>>,
                    <<""/utf8>>
                ),
                _pipe@5 = gleam@string:split(_pipe@4, <<","/utf8>>),
                _pipe@6 = gleam@list:map(_pipe@5, fun gleam@string:trim/1),
                gleam@list:filter(_pipe@6, fun(S) -> S /= <<""/utf8>> end)
            end,
            {import, Module, Names};

        {error, nil} ->
            {import, Rest, []}
    end.

-file("src/gleedoc/generate.gleam", 158).
-spec merge_imports(list(binary())) -> list(binary()).
merge_imports(Imports) ->
    _pipe = Imports,
    _pipe@1 = gleam@list:map(_pipe, fun parse_import/1),
    _pipe@2 = gleam@list:fold(
        _pipe@1,
        [],
        fun(Acc, Imp) -> case list_find_import(Acc, erlang:element(2, Imp)) of
                {ok, Existing} ->
                    Merged_names = gleam@list:unique(
                        lists:append(
                            erlang:element(3, Existing),
                            erlang:element(3, Imp)
                        )
                    ),
                    gleam@list:map(
                        Acc,
                        fun(Existing_imp) ->
                            case erlang:element(2, Existing_imp) =:= erlang:element(
                                2,
                                Imp
                            ) of
                                true ->
                                    {import,
                                        erlang:element(2, Existing_imp),
                                        Merged_names};

                                false ->
                                    Existing_imp
                            end
                        end
                    );

                {error, nil} ->
                    [Imp | Acc]
            end end
    ),
    _pipe@3 = gleam@list:map(_pipe@2, fun import_to_string/1),
    gleam@list:sort(_pipe@3, fun gleam@string:compare/2).

-file("src/gleedoc/generate.gleam", 186).
-spec unique_imports(binary(), binary()) -> list(binary()).
unique_imports(File, Module_name) ->
    case simplifile:read(File) of
        {ok, Source} ->
            Names = begin
                _pipe = gleedoc@scan:public_names(File, Source),
                gleam@result:unwrap(_pipe, [])
            end,
            Target_import = case Names of
                [] ->
                    <<"import "/utf8, Module_name/binary>>;

                _ ->
                    <<<<<<<<"import "/utf8, Module_name/binary>>/binary,
                                ".{"/utf8>>/binary,
                            (gleam@string:join(Names, <<", "/utf8>>))/binary>>/binary,
                        "}"/utf8>>
            end,
            Source_imports = begin
                _pipe@1 = gleedoc@scan:module_imports(File, Source),
                gleam@result:unwrap(_pipe@1, [])
            end,
            [Target_import | Source_imports];

        {error, _} ->
            [<<"import "/utf8, Module_name/binary>>]
    end.

-file("src/gleedoc/generate.gleam", 102).
-spec module_name_from_file(binary()) -> binary().
module_name_from_file(File) ->
    _pipe = gleam@string:split_once(File, <<"/"/utf8>>),
    _pipe@1 = gleam@result:map(_pipe, fun(Pair) -> erlang:element(2, Pair) end),
    _pipe@2 = gleam@result:unwrap(_pipe@1, File),
    gleam@string:replace(_pipe@2, <<".gleam"/utf8>>, <<""/utf8>>).

-file("src/gleedoc/generate.gleam", 109).
-spec test_file_name(binary()) -> binary().
test_file_name(Source_file) ->
    Name = begin
        _pipe = gleam@string:split_once(Source_file, <<"/"/utf8>>),
        _pipe@1 = gleam@result:map(
            _pipe,
            fun(Pair) -> erlang:element(2, Pair) end
        ),
        _pipe@2 = gleam@result:unwrap(_pipe@1, Source_file),
        _pipe@3 = gleam@string:replace(_pipe@2, <<".gleam"/utf8>>, <<""/utf8>>),
        gleam@string:replace(_pipe@3, <<"/"/utf8>>, <<"_"/utf8>>)
    end,
    <<Name/binary, "_gleedoc_test.gleam"/utf8>>.

-file("src/gleedoc/generate.gleam", 95).
-spec find_group(list({binary(), list(gleedoc@parse:code_block())}), binary()) -> {ok,
        {binary(), list(gleedoc@parse:code_block())}} |
    {error, nil}.
find_group(Groups, File) ->
    gleam@list:find(Groups, fun(Pair) -> erlang:element(1, Pair) =:= File end).

-file("src/gleedoc/generate.gleam", 76).
-spec group_by_file(list(gleedoc@parse:code_block())) -> list({binary(),
    list(gleedoc@parse:code_block())}).
group_by_file(Blocks) ->
    _pipe = Blocks,
    _pipe@1 = gleam@list:fold(
        _pipe,
        [],
        fun(Groups, Block) ->
            File = erlang:element(4, erlang:element(4, Block)),
            case find_group(Groups, File) of
                {ok, {_, Existing}} ->
                    gleam@list:map(
                        Groups,
                        fun(Pair) -> case erlang:element(1, Pair) =:= File of
                                true ->
                                    {File, [Block | Existing]};

                                false ->
                                    Pair
                            end end
                    );

                {error, nil} ->
                    [{File, [Block]} | Groups]
            end
        end
    ),
    gleam@list:map(
        _pipe@1,
        fun(Pair@1) ->
            {erlang:element(1, Pair@1),
                lists:reverse(erlang:element(2, Pair@1))}
        end
    ).

-file("src/gleedoc/generate.gleam", 20).
?DOC(" Generate test files from extracted code blocks.\n").
-spec generate_tests(list(gleedoc@parse:code_block()), config()) -> {ok,
        list(binary())} |
    {error, snag:snag()}.
generate_tests(Blocks, Config) ->
    By_file = group_by_file(Blocks),
    _pipe = By_file,
    gleam@list:try_map(
        _pipe,
        fun(Pair) ->
            {File, File_blocks} = Pair,
            Test_file_name = test_file_name(File),
            Output_dir = <<(erlang:element(2, Config))/binary, "/gleedoc"/utf8>>,
            Test_path = <<<<Output_dir/binary, "/"/utf8>>/binary,
                Test_file_name/binary>>,
            Module_name = module_name_from_file(File),
            gleam@result:'try'(
                begin
                    _pipe@1 = simplifile:create_directory_all(Output_dir),
                    gleam@result:map_error(
                        _pipe@1,
                        fun(Err) ->
                            snag:new(
                                <<<<<<"Failed to create directory: "/utf8,
                                            Output_dir/binary>>/binary,
                                        " - "/utf8>>/binary,
                                    (gleam@string:inspect(Err))/binary>>
                            )
                        end
                    )
                end,
                fun(_) ->
                    Block_imports = begin
                        _pipe@2 = File_blocks,
                        gleam@list:flat_map(
                            _pipe@2,
                            fun(B) -> erlang:element(6, B) end
                        )
                    end,
                    Auto_imports = unique_imports(File, Module_name),
                    All_imports = merge_imports(
                        lists:append(Block_imports, Auto_imports)
                    ),
                    Test_functions = generate_test_functions(File_blocks),
                    Content = gleam@string:join(
                        begin
                            _pipe@3 = [<<"// Generated by gleedoc - do not edit manually"/utf8>>,
                                <<""/utf8>> |
                                All_imports],
                            lists:append(_pipe@3, Test_functions)
                        end,
                        <<"\n"/utf8>>
                    ),
                    gleam@result:'try'(
                        begin
                            _pipe@4 = simplifile:write(Test_path, Content),
                            gleam@result:map_error(
                                _pipe@4,
                                fun(Err@1) ->
                                    snag:new(
                                        <<<<<<"Failed to write test file: "/utf8,
                                                    Test_path/binary>>/binary,
                                                " - "/utf8>>/binary,
                                            (gleam@string:inspect(Err@1))/binary>>
                                    )
                                end
                            )
                        end,
                        fun(_) -> {ok, Test_path} end
                    )
                end
            )
        end
    ).

-file("src/gleedoc/generate.gleam", 250).
?DOC(" Clean up generated test files.\n").
-spec clean_generated(binary()) -> {ok, nil} | {error, snag:snag()}.
clean_generated(Output_dir) ->
    Dir = <<Output_dir/binary, "/gleedoc"/utf8>>,
    case simplifile_erl:read_directory(Dir) of
        {ok, Files} ->
            _pipe = Files,
            _pipe@1 = gleam@list:filter(
                _pipe,
                fun(F) ->
                    gleam_stdlib:string_ends_with(
                        F,
                        <<"_gleedoc_test.gleam"/utf8>>
                    )
                end
            ),
            gleam@list:each(
                _pipe@1,
                fun(F@1) ->
                    Path = <<<<Dir/binary, "/"/utf8>>/binary, F@1/binary>>,
                    _ = simplifile_erl:delete(Path),
                    nil
                end
            ),
            {ok, nil};

        {error, Err} ->
            case gleam@string:inspect(Err) of
                <<"Enoent"/utf8>> ->
                    {ok, nil};

                _ ->
                    {error,
                        snag:new(
                            <<"Failed to clean generated files: "/utf8,
                                (gleam@string:inspect(Err))/binary>>
                        )}
            end
    end.