Packages

Generate Gleam modules based on static assets and environment variables.

Current section

Files

Jump to
embeds src embeds@files.erl
Raw

src/embeds@files.erl

-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 => <<?FILEPATH/utf8>>,
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 => <<?FILEPATH/utf8>>,
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 => <<?FILEPATH/utf8>>,
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 => <<?FILEPATH/utf8>>,
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}.