-module(embeds@generate). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/embeds/generate.gleam"). -export([default_print/2, describe_error/1, describe_errors/1, guard_gleam_project/1, bits_to_data/1, data_to_bits/1, result_to_const/1, skip_or_const/1, result_to_fn/1, skip_or_fn/1, to_gleam_identifier/1, to_gleam_module_name/1, generate_module_file/3, generate_modules/6, generate/4]). -export_type([error/0, data/0, code/0, definition/0]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. ?MODULEDOC( " This module exposes the internal/shared code for both `embeds/files` as\n" " well as `embeds/env`.\n" "\n" " It exposes some common type aliases and functions, and provides a\n" " lower-level API to generate module files from a set of `Definitions`.\n" "\n" " I recommend starting with the `embeds/files` and/or `embeds/env` modules\n" " first before diving into this one!\n" ). -type error() :: not_inside_gleam_project | {invalid_argument, binary(), binary(), binary()} | {could_not_list_files, binary(), simplifile:file_error()} | {could_not_read, binary(), binary()} | {duplicate_var_name, binary(), binary()} | {printer_error, binary(), binary()} | {write_error, binary(), simplifile:file_error()}. -type data() :: {text, binary()} | {binary, bitstring()}. -type code() :: skip | {generate_error, binary()} | {constant, binary()} | {function_body, binary()}. -type definition() :: {definition, binary(), fun(() -> {ok, data()} | {error, binary()}), integer(), binary(), binary()}. -file("src/embeds/generate.gleam", 24). ?DOC(" convert data to code, by simply calling string.inspect/bit_array.inspect.\n"). -spec default_print(binary(), data()) -> code(). default_print(_, Data) -> Code = case Data of {text, Text} -> gleam@string:inspect(Text); {binary, Bits} -> gleam@bit_array:inspect(Bits) end, {constant, Code}. -file("src/embeds/generate.gleam", 56). ?DOC(" Turn an `Error` into a human-readable string.\n"). -spec describe_error(error()) -> binary(). describe_error(Err) -> case Err of not_inside_gleam_project -> <<"generate needs to be called inside the root directory of your Gleam project!"/utf8>>; {invalid_argument, Name, Value, Reason} -> <<<<<<<<<<"Invalid argument "/utf8, Name/binary>>/binary, " ("/utf8>>/binary, Value/binary>>/binary, "): "/utf8>>/binary, Reason/binary>>; {could_not_list_files, Path, E} -> <<<<<<"Could not list files from "/utf8, Path/binary>>/binary, ": "/utf8>>/binary, (simplifile:describe_error(E))/binary>>; {could_not_read, Path@1, E@1} -> <<<<<<"Could not read "/utf8, Path@1/binary>>/binary, ": "/utf8>>/binary, E@1/binary>>; {duplicate_var_name, A, B} -> <<<<<<<<"I have found 2 source definitions "/utf8, A/binary>>/binary, " and "/utf8>>/binary, B/binary>>/binary, " - both would produce the same constant in the same module!"/utf8>>; {printer_error, Path@2, E@2} -> <<<<<<"The printer for "/utf8, Path@2/binary>>/binary, " returned an error: "/utf8>>/binary, E@2/binary>>; {write_error, Path@3, E@3} -> <<<<<<"Error writing module "/utf8, Path@3/binary>>/binary, ": "/utf8>>/binary, (simplifile:describe_error(E@3))/binary>> end. -file("src/embeds/generate.gleam", 83). ?DOC(" Turn multiple erros into a human-readable string.\n"). -spec describe_errors(list(error())) -> binary(). describe_errors(Errs) -> gleam@string:join(gleam@list:map(Errs, fun describe_error/1), <<"\n"/utf8>>). -file("src/embeds/generate.gleam", 91). ?DOC( " enforce that the current working directory is a gleam project\n" " (by checking a gleam.toml exists).\n" " Automatically called by `generate`, but you may wish to exit earlier.\n" " Designed to be used with `use`.\n" ). -spec guard_gleam_project(fun(() -> {ok, GQE} | {error, list(error())})) -> {ok, GQE} | {error, list(error())}. guard_gleam_project(F) -> case simplifile_erl:is_file(<<"gleam.toml"/utf8>>) of {ok, true} -> F(); _ -> {error, [not_inside_gleam_project]} end. -file("src/embeds/generate.gleam", 113). ?DOC( " Try to convert a BitArray to `Text` first , returning `Binary` if the\n" " conversion fails.\n" ). -spec bits_to_data(bitstring()) -> data(). bits_to_data(Bits) -> case gleam@bit_array:to_string(Bits) of {ok, Text} -> {text, Text}; {error, _} -> {binary, Bits} end. -file("src/embeds/generate.gleam", 121). ?DOC(" Convert some data back to a BitArray.\n"). -spec data_to_bits(data()) -> bitstring(). data_to_bits(Data) -> case Data of {text, Text} -> gleam_stdlib:identity(Text); {binary, Bits} -> Bits end. -file("src/embeds/generate.gleam", 159). ?DOC(" Helper function to convert a Result into either a `Constant` or `GenerateError`.\n"). -spec result_to_const({ok, binary()} | {error, binary()}) -> code(). result_to_const(Result) -> case Result of {ok, Code} -> {constant, Code}; {error, Err} -> {generate_error, Err} end. -file("src/embeds/generate.gleam", 167). ?DOC(" Helper function to convert a Result into either a `Constant` or skip generating on error.\n"). -spec skip_or_const({ok, binary()} | {error, nil}) -> code(). skip_or_const(Result) -> case Result of {ok, Code} -> {constant, Code}; {error, nil} -> skip end. -file("src/embeds/generate.gleam", 175). ?DOC(" Helper function to convert a Result into either a `FunctionBody` or `GenerateError`.\n"). -spec result_to_fn({ok, binary()} | {error, binary()}) -> code(). result_to_fn(Result) -> case Result of {ok, Code} -> {function_body, Code}; {error, Err} -> {generate_error, Err} end. -file("src/embeds/generate.gleam", 183). ?DOC(" Helper function to convert a Result into either a `FunctionBody` or skip generating on error.\n"). -spec skip_or_fn({ok, binary()} | {error, nil}) -> code(). skip_or_fn(Result) -> case Result of {ok, Code} -> {function_body, Code}; {error, nil} -> skip end. -file("src/embeds/generate.gleam", 241). ?DOC( " Ensures the given string is a valid Gleam identifier by replacing invalid\n" " characters by underscores.\n" "\n" " Empty strings return \"empty\", numbers will be prefixed with \"n\".\n" "\n" " If the resulting variable name would be a reserved keyword, an additional\n" " underscore will be added at the end.\n" ). -spec to_gleam_identifier(binary()) -> binary(). to_gleam_identifier(Str) -> Re@1 = case gleam@regexp:from_string(<<"[^a-zA-Z0-9]+"/utf8>>) of {ok, Re} -> Re; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"embeds/generate"/utf8>>, function => <<"to_gleam_identifier"/utf8>>, line => 242, value => _assert_fail, start => 7800, 'end' => 7854, pattern_start => 7811, pattern_end => 7817}) end, Var_name = begin _pipe = Str, _pipe@1 = gleam_regexp_ffi:replace(Re@1, _pipe, <<" "/utf8>>), _pipe@2 = gleam@string:trim(_pipe@1), _pipe@3 = gleam@string:replace(_pipe@2, <<" "/utf8>>, <<"_"/utf8>>), string:lowercase(_pipe@3) end, case Var_name of <<"0"/utf8, _/binary>> -> <<"n"/utf8, Var_name/binary>>; <<"1"/utf8, _/binary>> -> <<"n"/utf8, Var_name/binary>>; <<"2"/utf8, _/binary>> -> <<"n"/utf8, Var_name/binary>>; <<"3"/utf8, _/binary>> -> <<"n"/utf8, Var_name/binary>>; <<"4"/utf8, _/binary>> -> <<"n"/utf8, Var_name/binary>>; <<"5"/utf8, _/binary>> -> <<"n"/utf8, Var_name/binary>>; <<"6"/utf8, _/binary>> -> <<"n"/utf8, Var_name/binary>>; <<"7"/utf8, _/binary>> -> <<"n"/utf8, Var_name/binary>>; <<"8"/utf8, _/binary>> -> <<"n"/utf8, Var_name/binary>>; <<"9"/utf8, _/binary>> -> <<"n"/utf8, Var_name/binary>>; <<""/utf8>> -> <<"empty"/utf8>>; <<"as"/utf8>> -> <>; <<"assert"/utf8>> -> <>; <<"auto"/utf8>> -> <>; <<"case"/utf8>> -> <>; <<"const"/utf8>> -> <>; <<"delegate"/utf8>> -> <>; <<"derive"/utf8>> -> <>; <<"echo"/utf8>> -> <>; <<"else"/utf8>> -> <>; <<"fn"/utf8>> -> <>; <<"if"/utf8>> -> <>; <<"implement"/utf8>> -> <>; <<"import"/utf8>> -> <>; <<"let"/utf8>> -> <>; <<"macro"/utf8>> -> <>; <<"opaque"/utf8>> -> <>; <<"panic"/utf8>> -> <>; <<"pub"/utf8>> -> <>; <<"test"/utf8>> -> <>; <<"todo"/utf8>> -> <>; <<"type"/utf8>> -> <>; <<"use"/utf8>> -> <>; _ -> Var_name end. -file("src/embeds/generate.gleam", 308). ?DOC( " Ensures a path is a valid Gleam module path, by making sure that every piece\n" " is a valid Gleam identifier.\n" "\n" " Unix `/` and Windows `\\\\` are both treated as module separators.\n" "\n" " **NOTE**:The top-level module is not allowed to be called `gleam`, but this\n" " function does not enforce this!\n" ). -spec to_gleam_module_name(binary()) -> binary(). to_gleam_module_name(Path) -> Module = gleam@string:join( begin gleam@list:filter_map( begin _pipe = Path, _pipe@1 = gleam@string:replace( _pipe, <<"\\"/utf8>>, <<"/"/utf8>> ), gleam@string:split(_pipe@1, <<"/"/utf8>>) end, fun(Part) -> case Part of <<"."/utf8>> -> {error, nil}; <<".."/utf8>> -> {error, nil}; <<""/utf8>> -> {error, nil}; _ -> {ok, to_gleam_identifier(Part)} end end ) end, <<"/"/utf8>> ), case Module of <<""/utf8>> -> <<"assets"/utf8>>; _ -> Module end. -file("src/embeds/generate.gleam", 333). ?DOC(" Turn a Gleam module name into the path to its source file.\n"). -spec module_file(binary()) -> binary(). module_file(Module) -> filepath:join(<<"src"/utf8>>, <>). -file("src/embeds/generate.gleam", 417). ?DOC(false). -spec generate_module_file( list(definition()), binary(), fun((binary(), data()) -> code()) ) -> {ok, binary()} | {error, list(error())}. generate_module_file(Definitions, Header, Print) -> gleam@result:'try'( begin embeds@internal:traverse_map( Definitions, fun(Definition) -> embeds@internal:'try'( begin _pipe = (erlang:element(3, Definition))(), gleam@result:map_error( _pipe, fun(E) -> {could_not_read, erlang:element(2, Definition), E} end ) end, fun(Data) -> Code = Print(erlang:element(2, Definition), Data), case Code of skip -> {error, []}; {generate_error, Err} -> {error, [{printer_error, erlang:element(2, Definition), Err}]}; {constant, Code@1} -> {ok, <<<<<<<<<<<<"/// "/utf8, (erlang:element( 2, Definition ))/binary>>/binary, "\n"/utf8>>/binary, "pub const "/utf8>>/binary, (erlang:element( 6, Definition ))/binary>>/binary, " = "/utf8>>/binary, Code@1/binary>>}; {function_body, Code@2} -> {ok, <<<<<<<<<<<<<<"/// "/utf8, (erlang:element( 2, Definition ))/binary>>/binary, "\n"/utf8>>/binary, "pub fn "/utf8>>/binary, (erlang:element( 6, Definition ))/binary>>/binary, "() {\n"/utf8>>/binary, Code@2/binary>>/binary, "\n}"/utf8>>} end end ) end ) end, fun(Definitions@1) -> case {Definitions@1, Header} of {[], _} -> {ok, <<""/utf8>>}; {_, <<""/utf8>>} -> {ok, gleam@string:join(Definitions@1, <<"\n\n"/utf8>>)}; {_, _} -> {ok, <<<
>/binary, (gleam@string:join(Definitions@1, <<"\n\n"/utf8>>))/binary>>} end end ). -file("src/embeds/generate.gleam", 468). ?DOC(" Actual implementation for `generate_modules`\n"). -spec get_mtime(binary()) -> {ok, integer()} | {error, simplifile:file_error()}. get_mtime(Path) -> gleam@result:map( simplifile_erl:file_info(Path), fun(Info) -> erlang:element(10, Info) end ). -file("src/embeds/generate.gleam", 474). ?DOC(" Actual implementation for `generate_modules`\n"). -spec write(binary(), binary()) -> {ok, nil} | {error, simplifile:file_error()}. write(Path, Contents) -> gleam@result:'try'( begin _pipe = Path, _pipe@1 = filepath:directory_name(_pipe), simplifile:create_directory_all(_pipe@1) end, fun(_) -> simplifile:write(Path, Contents) end ). -file("src/embeds/generate.gleam", 496). -spec do_gather_by(list(GRP), fun((GRP) -> GRR), list({GRR, list(GRP)})) -> list({GRR, list(GRP)}). do_gather_by(List, To_key, Acc) -> case List of [] -> lists:reverse(Acc); [Head | Tail] -> Key = To_key(Head), {Matching, Remaining} = gleam@list:partition( Tail, fun(Other) -> To_key(Other) =:= Key end ), do_gather_by(Remaining, To_key, [{Key, [Head | Matching]} | Acc]) end. -file("src/embeds/generate.gleam", 489). ?DOC(" Like dict.group >> dict.to_list, but stable wrt. order.\n"). -spec gather_by(list(GRK), fun((GRK) -> GRM)) -> list({GRM, list(GRK)}). gather_by(List, To_key) -> do_gather_by(List, To_key, []). -file("src/embeds/generate.gleam", 514). ?DOC( " Make sure all items in a list are unique using a key-generating function, or\n" " return the first tuple of elements that have matching keys if they are not.\n" ). -spec enforce_unique_by(list(GRW), fun((GRW) -> any())) -> {ok, list(GRW)} | {error, {GRW, GRW}}. enforce_unique_by(List, To_key) -> gleam@list:try_fold( List, [], fun(Acc, Elem) -> Key = To_key(Elem), case gleam@list:find( Acc, fun(Existing) -> To_key(Existing) =:= Key end ) of {ok, Existing@1} -> {error, {Existing@1, Elem}}; {error, nil} -> {ok, [Elem | Acc]} end end ). -file("src/embeds/generate.gleam", 354). ?DOC(false). -spec generate_modules( list(definition()), binary(), fun((binary(), data()) -> code()), boolean(), fun((binary()) -> {ok, integer()} | {error, simplifile:file_error()}), fun((binary(), binary()) -> {ok, nil} | {error, simplifile:file_error()}) ) -> {ok, nil} | {error, list(error())}. generate_modules(Defs, Header, Print, Force, Get_mtime, Write) -> gleam@result:map( begin gleam@result:'try'( begin _pipe = Defs, _pipe@1 = gather_by( _pipe, fun(File) -> erlang:element(5, File) end ), embeds@internal:traverse_map( _pipe@1, fun(Pair) -> case enforce_unique_by( erlang:element(2, Pair), fun(File@1) -> erlang:element(6, File@1) end ) of {ok, Files} -> Files@1 = begin _pipe@2 = Files, gleam@list:sort( _pipe@2, fun(A, B) -> gleam@string:compare( erlang:element(6, A), erlang:element(6, B) ) end ) end, {ok, {erlang:element(1, Pair), Files@1}}; {error, Conflict} -> {error, [{duplicate_var_name, erlang:element( 2, (erlang:element(1, Conflict)) ), erlang:element( 2, (erlang:element(2, Conflict)) )}]} end end ) end, fun(Module_specs) -> embeds@internal:traverse_map( Module_specs, fun(_use0) -> {Module_name, Defs@1} = _use0, Module_file = module_file(Module_name), Needs_update = case Get_mtime(Module_file) of {ok, Module_ts} -> gleam@list:any( Defs@1, fun(_use0@1) -> {definition, _, _, File_ts, _, _} = _use0@1, (File_ts =< 0) orelse (File_ts >= Module_ts) end ); {error, _} -> true end, gleam@bool:guard( not Force andalso not Needs_update, {error, []}, fun() -> gleam@result:'try'( generate_module_file( Defs@1, Header, Print ), fun(Contents) -> embeds@internal:'try'( begin _pipe@3 = Write( Module_file, Contents ), gleam@result:map_error( _pipe@3, fun(E) -> {write_error, Module_file, E} end ) end, fun(_) -> {ok, nil} end ) end ) end ) end ) end ) end, fun(_) -> nil end ). -file("src/embeds/generate.gleam", 342). ?DOC(" Generates module files based on a list of Definitions.\n"). -spec generate( list(definition()), binary(), fun((binary(), data()) -> code()), boolean() ) -> {ok, nil} | {error, list(error())}. generate(Defs, Header, Print, Force) -> guard_gleam_project( fun() -> generate_modules( Defs, Header, Print, Force, fun get_mtime/1, fun write/2 ) end ).