-module(embeds@files). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]). -export([default_filter/2, generate/1, get_files/1, main/0]). -export_type([options/0, source/0, flatten/0]). -type options() :: {options, list(source()), binary(), fun((binary(), embeds@generate:data()) -> embeds@generate:code()), boolean()}. -type source() :: {source, binary(), binary(), flatten(), gleam@option:option(integer()), fun((boolean(), binary()) -> boolean())}. -type flatten() :: dont_flatten | flatten_all | {flatten_first, integer()}. -spec default_filter(boolean(), binary()) -> boolean(). default_filter(_, Path) -> Is_hidden = begin _pipe = filepath:base_name(Path), gleam@string:starts_with(_pipe, <<"."/utf8>>) end, Is_hidden =:= false. -spec do_split_last(list(JGN), list(JGN), JGN) -> {list(JGN), JGN}. do_split_last(List, Init, Last) -> case List of [] -> {lists:reverse(Init), Last}; [First | Rest] -> do_split_last(Rest, [Last | Init], First) end. -spec split_last(list(JGI)) -> {ok, {list(JGI), JGI}} | {error, nil}. split_last(List) -> case List of [] -> {error, nil}; [First | List@1] -> {ok, do_split_last(List@1, [], First)} end. -spec do_traverse_map( list(JHC), fun((JHC) -> {ok, JHE} | {error, list(JHF)}), list(JHE), list(JHF) ) -> {ok, list(JHE)} | {error, list(JHF)}. do_traverse_map(List, Body, Oks, Errs) -> case List of [] -> case Errs of [] -> {ok, lists:reverse(Oks)}; _ -> {error, lists:reverse(Errs)} end; [Head | Tail] -> case Body(Head) of {ok, Ok} -> do_traverse_map(Tail, Body, [Ok | Oks], Errs); {error, Err} -> do_traverse_map(Tail, Body, Oks, lists:append(Err, Errs)) end end. -spec traverse_map(list(JGR), fun((JGR) -> {ok, JGT} | {error, list(JGU)})) -> {ok, list(JGT)} | {error, list(JGU)}. traverse_map(List, Body) -> do_traverse_map(List, Body, [], []). -spec do_get_files(source(), binary(), integer()) -> {ok, list(embeds@generate:definition())} | {error, simplifile:file_error()}. do_get_files(Src, Nested, Depth) -> gleam@result:'try'( simplifile_erl:read_directory( filepath:join(erlang:element(2, Src), Nested) ), fun(Contents) -> gleam@list:try_fold( Contents, [], fun(Acc, Content) -> Relative_path = filepath:join(Nested, Content), Absolute_path = filepath:join( erlang:element(2, Src), Relative_path ), gleam@result:'try'( simplifile_erl:file_info(Absolute_path), fun(Info) -> Is_file = erlang:'band'( erlang:element(3, Info), 8#100000 ) > 0, Is_dir = erlang:'band'( erlang:element(3, Info), 8#40000 ) > 0, gleam@bool:guard( not (erlang:element(6, Src))( Is_dir, Relative_path ), {ok, Acc}, fun() -> case {Is_file, Is_dir, erlang:element(5, Src)} of {true, _, _} -> {Module_name, Var_name} = get_file_name( Src, Relative_path ), Definition = {definition, Absolute_path, fun() -> read_file(Absolute_path) end, erlang:element(10, Info), Module_name, Var_name}, {ok, [Definition | Acc]}; {_, true, none} -> gleam@result:'try'( do_get_files( Src, Relative_path, Depth + 1 ), fun(Nested_files) -> {ok, lists:append( Acc, Nested_files )} end ); {_, true, {some, Max_depth}} when Depth < Max_depth -> gleam@result:'try'( do_get_files( Src, Relative_path, Depth + 1 ), fun(Nested_files@1) -> {ok, lists:append( Acc, Nested_files@1 )} end ); {_, _, _} -> {ok, Acc} end end ) end ) end ) end ). -spec generate(options()) -> {ok, nil} | {error, list(embeds@generate:error())}. generate(Options) -> embeds@generate:guard_gleam_project( fun() -> {options, Sources, Header, Print, Force} = Options, gleam@result:'try'( (traverse_map(Sources, fun(Spec) -> _pipe = get_files(Spec), gleam@result:map_error( _pipe, fun(E) -> [{could_not_list_files, erlang:element(2, Spec), E}] end ) end)), fun(Defs) -> embeds@generate:generate( gleam@list:flatten(Defs), Header, Print, Force ) end ) end ). -spec get_files(source()) -> {ok, list(embeds@generate:definition())} | {error, simplifile:file_error()}. get_files(Src) -> do_get_files(Src, <<""/utf8>>, 0). -spec parse_path_spec( binary(), flatten(), gleam@option:option(integer()), fun((boolean(), binary()) -> boolean()) ) -> {ok, source()} | {error, nil}. parse_path_spec(Spec, Flatten, Max_depth, Filter) -> case gleam@string:split(Spec, <<":"/utf8>>) of [Src] -> gleam@result:map( filepath:expand(Src), fun(Src@1) -> {source, Src@1, embeds@generate:to_gleam_module_name(Src@1), Flatten, Max_depth, Filter} end ); [Module, Src@2] -> gleam@result:map( filepath:expand(Src@2), fun(Src@3) -> {source, Src@3, embeds@generate:to_gleam_module_name(Module), Flatten, Max_depth, Filter} end ); _ -> {error, nil} end. -spec generate_cmd() -> glint:command(nil). generate_cmd() -> glint:command_help( <<<<<<<<<<<<"Generate Gleam modules containing String/BitArray constants of static files.\n\n\n"/utf8, "By default, looks for a directory called assets, and generates a Gleam module called assets. The directory structure is preserved in the module structure.\n\n\n"/utf8>>/binary, "ARGS can be a list of any of the following:\n\n\n"/utf8>>/binary, "- ./path/to/files:\n\n"/utf8>>/binary, " List all files in ./path/to/files, generating a Gleam module called `files` (following the directory name).\n\n"/utf8>>/binary, "- assets:./path/to/files\n\n"/utf8>>/binary, " Rename the top-level module to `assets`\n\n"/utf8>>, fun() -> glint:flag( begin _pipe = glint:bool_flag(<<"force"/utf8>>), _pipe@1 = glint:flag_default(_pipe, false), glint:flag_help( _pipe@1, <<"Do not skip generating based on timestamps"/utf8>> ) end, fun(Force) -> glint:flag( begin _pipe@2 = glint:bool_flag(<<"flatten"/utf8>>), _pipe@3 = glint:flag_default(_pipe@2, false), glint:flag_help( _pipe@3, <<"Do not generate nested modules reflecting the directory tree"/utf8>> ) end, fun(Flatten_bool) -> glint:flag( begin _pipe@4 = glint:int_flag(<<"strip"/utf8>>), _pipe@5 = glint:flag_default(_pipe@4, 0), glint:flag_help( _pipe@5, <<"Like flatten, but only strip the first N levels from the directory tree. If both are given, flatten takes precedence."/utf8>> ) end, fun(Flatten_int) -> glint:flag( begin _pipe@6 = glint:int_flag( <<"max-depth"/utf8>> ), glint:flag_help( _pipe@6, <<"The maximum level of nesting before stopping looking for files."/utf8>> ) end, fun(Max_depth) -> glint:flag( begin _pipe@7 = glint:string_flag( <<"filter"/utf8>> ), glint:flag_help( _pipe@7, <<"A regular expression that needs to match the relative file path."/utf8>> ) end, fun(Filter) -> glint:command( fun(_, Args, Flags) -> _assert_subject = Force( Flags ), {ok, Force@1} = case _assert_subject of {ok, _} -> _assert_subject; _assert_fail -> erlang:error( #{gleam_error => let_assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail, module => <<"embeds/files"/utf8>>, function => <<"generate_cmd"/utf8>>, line => 194} ) end, _assert_subject@1 = Flatten_bool( Flags ), {ok, Flatten_bool@1} = case _assert_subject@1 of {ok, _} -> _assert_subject@1; _assert_fail@1 -> erlang:error( #{gleam_error => let_assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail@1, module => <<"embeds/files"/utf8>>, function => <<"generate_cmd"/utf8>>, line => 195} ) end, _assert_subject@2 = Flatten_int( Flags ), {ok, Flatten_int@1} = case _assert_subject@2 of {ok, _} -> _assert_subject@2; _assert_fail@2 -> erlang:error( #{gleam_error => let_assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail@2, module => <<"embeds/files"/utf8>>, function => <<"generate_cmd"/utf8>>, line => 196} ) end, Max_depth@1 = begin _pipe@8 = Max_depth( Flags ), gleam@option:from_result( _pipe@8 ) end, Filter@1 = begin _pipe@9 = Filter( Flags ), gleam@option:from_result( _pipe@9 ) end, Flatten = case {Flatten_bool@1, Flatten_int@1} of {true, _} -> flatten_all; {false, 0} -> dont_flatten; {false, N} -> {flatten_first, N} end, Result = (gleam@result:'try'( case Filter@1 of {some, Filter@2} -> case gleam@regex:from_string( Filter@2 ) of {ok, Regex} -> {ok, fun( Is_dir, Path ) -> case {default_filter( Is_dir, Path ), Is_dir} of {false, _} -> false; {true, false} -> gleam@regex:check( Regex, Path ); {true, true} -> true end end}; {error, {compile_error, Error, _}} -> {error, [{invalid_argument, <<"filter"/utf8>>, Filter@2, Error}]} end; none -> {ok, fun default_filter/2} end, fun(Filter@3) -> gleam@result:'try'( case Args of [] -> {ok, [{source, <<"./assets"/utf8>>, <<"assets"/utf8>>, Flatten, Max_depth@1, Filter@3}]}; _ -> traverse_map( Args, fun( Spec ) -> _pipe@10 = parse_path_spec( Spec, Flatten, Max_depth@1, Filter@3 ), gleam@result:replace_error( _pipe@10, [{invalid_argument, <<""/utf8>>, Spec, <<"Invalid spec string"/utf8>>}] ) end ) end, fun( Path_specs ) -> Options = {options, Path_specs, <<"//// Generated by `embeds`, manual edits are futile"/utf8>>, fun embeds@generate:default_print/2, Force@1}, generate( Options ) end ) end )), case Result of {ok, nil} -> nil; {error, Errs} -> gleam@io:println_error( embeds@generate:describe_errors( Errs ) ) end, nil end ) end ) end ) end ) end ) end ) end ). -spec main() -> nil. main() -> _pipe = glint:new(), _pipe@1 = glint:with_name(_pipe, <<"embeds/files"/utf8>>), _pipe@2 = glint:as_module(_pipe@1), _pipe@3 = glint:pretty_help(_pipe@2, glint:default_pretty_help()), _pipe@4 = glint:add(_pipe@3, [], generate_cmd()), glint:run(_pipe@4, erlang:element(4, argv:load())). -spec read_file(binary()) -> {ok, embeds@generate:data()} | {error, binary()}. read_file(Path) -> case simplifile_erl:read_bits(Path) of {ok, Bits} -> {ok, embeds@generate:bits_to_data(Bits)}; {error, Err} -> {error, simplifile:describe_error(Err)} end. -spec get_file_name(source(), binary()) -> {binary(), binary()}. get_file_name(Src, Relative_path) -> _assert_subject = begin _pipe = Relative_path, _pipe@1 = gleam@string:split(_pipe, <<"/"/utf8>>), _pipe@2 = gleam@list:filter( _pipe@1, fun(Part) -> ((Part /= <<""/utf8>>) andalso (Part /= <<"."/utf8>>)) andalso (Part /= <<".."/utf8>>) end ), split_last(_pipe@2) end, {ok, {Module_parts, File_name}} = case _assert_subject of {ok, {_, _}} -> _assert_subject; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail, module => <<"embeds/files"/utf8>>, function => <<"get_file_name"/utf8>>, line => 369}) end, Var_name = begin _pipe@3 = File_name, _pipe@4 = filepath:strip_extension(_pipe@3), embeds@generate:to_gleam_identifier(_pipe@4) end, Module_name = embeds@generate:to_gleam_module_name( case erlang:element(4, Src) of flatten_all -> erlang:element(3, Src); {flatten_first, N} -> _pipe@5 = Module_parts, _pipe@6 = gleam@list:drop(_pipe@5, N), _pipe@7 = gleam@list:prepend(_pipe@6, erlang:element(3, Src)), gleam@string:join(_pipe@7, <<"/"/utf8>>); dont_flatten -> _pipe@8 = Module_parts, _pipe@9 = gleam@list:prepend(_pipe@8, erlang:element(3, Src)), gleam@string:join(_pipe@9, <<"/"/utf8>>) end ), {Module_name, Var_name}.