-module(embeds@files). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/embeds/files.gleam"). -export([default_filter/2, generate/1, get_files/1, main/0]). -export_type([options/0, source/0, flatten/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( " `embeds/files` generates Gleam modules containing constants and zero-argument\n" " functions based on the contents of files.\n" "\n" " This module is intended to be run as a CLI tool during your build process:\n" " ```sh\n" " gleam run -m embeds/files -- --help\n" " ```\n" "\n" " The functions exposed in this module can also be used directly to support\n" " advanced use cases inside custom pre-build scripts.\n" ). -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()}. -file("src/embeds/files.gleam", 84). ?DOC( " A default filter function to use inside sources.\n" " Filters out hidden files and directories.\n" ). -spec default_filter(boolean(), binary()) -> boolean(). default_filter(_, Path) -> Is_hidden = begin _pipe = filepath:base_name(Path), gleam_stdlib:string_starts_with(_pipe, <<"."/utf8>>) end, Is_hidden =:= false. -file("src/embeds/files.gleam", 416). -spec do_split_last(list(HIE), list(HIE), HIE) -> {list(HIE), HIE}. do_split_last(List, Init, Last) -> case List of [] -> {lists:reverse(Init), Last}; [First | Rest] -> do_split_last(Rest, [Last | Init], First) end. -file("src/embeds/files.gleam", 409). -spec split_last(list(HHZ)) -> {ok, {list(HHZ), HHZ}} | {error, nil}. split_last(List) -> case List of [] -> {error, nil}; [First | List@1] -> {ok, do_split_last(List@1, [], First)} end. -file("src/embeds/files.gleam", 313). -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) -> File_type = simplifile:file_info_type(Info), gleam@bool:guard( not (erlang:element(6, Src))( File_type =:= directory, Relative_path ), {ok, Acc}, fun() -> case {File_type, erlang:element(5, Src)} of {file, _} -> {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]}; {directory, none} -> gleam@result:'try'( do_get_files( Src, Relative_path, Depth + 1 ), fun(Nested_files) -> {ok, lists:append( Acc, Nested_files )} end ); {directory, {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 ). -file("src/embeds/files.gleam", 121). ?DOC( " Generate Gleam modules, containing constants for all found files, given the sources.\n" " Exported for easier integration with other build tools.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " import embeds/files\n" " import embeds/generate\n" " import gleam/option\n" " import gleam/io\n" "\n" " pub fn main() {\n" " let result = files.generate(files.Options(\n" " sources: [files.Source(\n" " src: \"./assets\",\n" " module: \"assets\",\n" " flatten: files.FlattenAll,\n" " max_depth: option.None,\n" " filter: files.default_filter,\n" " )],\n" "\n" " print: generate.default_print,\n" " header: generate.default_header,\n" " force: False,\n" " ))\n" "\n" " case result {\n" " Ok(Nil) -> Nil\n" " Error(errs) -> io.println_error(generate.describe_errors(errs))\n" " }\n" " }\n" " ```\n" ). -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'( begin embeds@internal: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 ) end, fun(Defs) -> embeds@generate:generate( lists:append(Defs), Header, Print, Force ) end ) end ). -file("src/embeds/files.gleam", 309). ?DOC( " Find all files inside a `Source`.\n" " You usually do not need to call this function yourself.\n" ). -spec get_files(source()) -> {ok, list(embeds@generate:definition())} | {error, simplifile:file_error()}. get_files(Src) -> do_get_files(Src, <<""/utf8>>, 0). -file("src/embeds/files.gleam", 272). ?DOC(" Parse a `Source` given as a command-line argument into our Gleam structure.\n"). -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. -file("src/embeds/files.gleam", 151). -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) -> Force@2 = case Force( Flags ) of {ok, Force@1} -> Force@1; _assert_fail -> erlang:error( #{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"embeds/files"/utf8>>, function => <<"generate_cmd"/utf8>>, line => 200, value => _assert_fail, start => 6634, 'end' => 6669, pattern_start => 6645, pattern_end => 6654} ) end, Flatten_bool@2 = case Flatten_bool( Flags ) of {ok, Flatten_bool@1} -> Flatten_bool@1; _assert_fail@1 -> erlang:error( #{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"embeds/files"/utf8>>, function => <<"generate_cmd"/utf8>>, line => 201, value => _assert_fail@1, start => 6672, 'end' => 6721, pattern_start => 6683, pattern_end => 6699} ) end, Flatten_int@2 = case Flatten_int( Flags ) of {ok, Flatten_int@1} -> Flatten_int@1; _assert_fail@2 -> erlang:error( #{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"embeds/files"/utf8>>, function => <<"generate_cmd"/utf8>>, line => 202, value => _assert_fail@2, start => 6724, 'end' => 6771, pattern_start => 6735, pattern_end => 6750} ) 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@2, Flatten_int@2} of {true, _} -> flatten_all; {false, 0} -> dont_flatten; {false, N} -> {flatten_first, N} end, Result = begin gleam@result:'try'( case Filter@1 of {some, Filter@2} -> case gleam@regexp: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@regexp: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}]}; _ -> embeds@internal: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@2}, generate( Options ) end ) end ) end, case Result of {ok, nil} -> nil; {error, Errs} -> gleam_stdlib:println_error( embeds@generate:describe_errors( Errs ) ) end, nil end ) end ) end ) end ) end ) end ) end ). -file("src/embeds/files.gleam", 142). ?DOC(" runs generate using command line arguments to fill out the arguments.\n"). -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())). -file("src/embeds/files.gleam", 367). -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. -file("src/embeds/files.gleam", 375). ?DOC(" Get the module and variable name for a file, based on its relative path inside a source.\n"). -spec get_file_name(source(), binary()) -> {binary(), binary()}. get_file_name(Src, Relative_path) -> {Module_parts@1, File_name@1} = case 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 of {ok, {Module_parts, File_name}} -> {Module_parts, File_name}; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"embeds/files"/utf8>>, function => <<"get_file_name"/utf8>>, line => 376, value => _assert_fail, start => 11301, 'end' => 11480, pattern_start => 11312, pattern_end => 11342}) end, Var_name = begin _pipe@3 = File_name@1, _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@1, _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@1, _pipe@9 = gleam@list:prepend(_pipe@8, erlang:element(3, Src)), gleam@string:join(_pipe@9, <<"/"/utf8>>) end ), {Module_name, Var_name}.