-module(packkit@entry).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/packkit/entry.gleam").
-export([kind/1, is_file/1, is_directory/1, is_symlink/1, is_hardlink/1, path/1, body/1, link_target/1, metadata/1, with_owner_checked/3, with_owner/3, with_modified_at_checked/2, with_modified_at/2, to_string/1, depth/1, mode/1, user_id/1, group_id/1, modified_at_unix/1, path_checked/1, path_unchecked/1, file_checked/2, file/2, directory_checked/1, directory/1, symlink_checked/2, symlink/2, hardlink_checked/2, hardlink/2, with_mode_checked/2, with_mode/2]).
-export_type([entry_path/0, entry_kind/0, metadata/0, entry/0, entry_error/0, metadata_error/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.
-opaque entry_path() :: {entry_path, binary(), list(binary())}.
-type entry_kind() :: file | directory | symlink | hardlink.
-opaque metadata() :: {metadata, integer(), integer(), integer(), integer()}.
-opaque entry() :: {entry,
entry_kind(),
entry_path(),
bitstring(),
gleam@option:option(binary()),
metadata()}.
-type entry_error() :: empty_path |
{absolute_path, binary()} |
{path_traversal, binary()} |
{windows_path, binary()} |
{empty_segment, binary()} |
{dot_segment, binary()} |
{contains_nul, binary()}.
-type metadata_error() :: {mode_out_of_range, integer()} |
{owner_out_of_range, integer()} |
{modified_at_out_of_range, integer()}.
-file("src/packkit/entry.gleam", 227).
?DOC(" Read the logical entry kind.\n").
-spec kind(entry()) -> entry_kind().
kind(Entry) ->
erlang:element(2, Entry).
-file("src/packkit/entry.gleam", 233).
?DOC(
" Short-circuit predicate for `kind(entry) == File`. Saves callers\n"
" from importing `EntryKind` just to filter a list of entries.\n"
).
-spec is_file(entry()) -> boolean().
is_file(Entry) ->
erlang:element(2, Entry) =:= file.
-file("src/packkit/entry.gleam", 238).
?DOC(" Short-circuit predicate for `kind(entry) == Directory`.\n").
-spec is_directory(entry()) -> boolean().
is_directory(Entry) ->
erlang:element(2, Entry) =:= directory.
-file("src/packkit/entry.gleam", 243).
?DOC(" Short-circuit predicate for `kind(entry) == Symlink`.\n").
-spec is_symlink(entry()) -> boolean().
is_symlink(Entry) ->
erlang:element(2, Entry) =:= symlink.
-file("src/packkit/entry.gleam", 248).
?DOC(" Short-circuit predicate for `kind(entry) == Hardlink`.\n").
-spec is_hardlink(entry()) -> boolean().
is_hardlink(Entry) ->
erlang:element(2, Entry) =:= hardlink.
-file("src/packkit/entry.gleam", 253).
?DOC(" Read the validated entry path.\n").
-spec path(entry()) -> entry_path().
path(Entry) ->
erlang:element(3, Entry).
-file("src/packkit/entry.gleam", 258).
?DOC(" Read the raw byte body for file-like entries.\n").
-spec body(entry()) -> bitstring().
body(Entry) ->
erlang:element(4, Entry).
-file("src/packkit/entry.gleam", 263).
?DOC(" Read the optional link target.\n").
-spec link_target(entry()) -> gleam@option:option(binary()).
link_target(Entry) ->
erlang:element(5, Entry).
-file("src/packkit/entry.gleam", 268).
?DOC(" Read the metadata bundle.\n").
-spec metadata(entry()) -> metadata().
metadata(Entry) ->
erlang:element(6, Entry).
-file("src/packkit/entry.gleam", 339).
?DOC(
" Override the entry owner identifiers after validating they are\n"
" non-negative. Format-side overflow (e.g. tar's 21-bit field) is\n"
" still surfaced at encode time as `ArchiveFieldOverflow`.\n"
).
-spec with_owner_checked(entry(), integer(), integer()) -> {ok, entry()} |
{error, metadata_error()}.
with_owner_checked(Entry, User_id, Group_id) ->
gleam@bool:guard(
User_id < 0,
{error, {owner_out_of_range, User_id}},
fun() ->
gleam@bool:guard(
Group_id < 0,
{error, {owner_out_of_range, Group_id}},
fun() ->
{metadata, Mode, _, _, Modified_at_unix} = erlang:element(
6,
Entry
),
{ok,
{entry,
erlang:element(2, Entry),
erlang:element(3, Entry),
erlang:element(4, Entry),
erlang:element(5, Entry),
{metadata,
Mode,
User_id,
Group_id,
Modified_at_unix}}}
end
)
end
).
-file("src/packkit/entry.gleam", 324).
?DOC(
" Override the entry owner identifiers. Negative values panic;\n"
" see [with_owner_checked] for the validated variant. No upper\n"
" bound is enforced here — format-specific encoders (tar, ar, cpio\n"
" newc) each apply their own narrower field-width checks at encode\n"
" time, so a value that's valid for one format and oversized for\n"
" another can still be expressed as an `Entry`.\n"
).
-spec with_owner(entry(), integer(), integer()) -> entry().
with_owner(Entry, User_id, Group_id) ->
case with_owner_checked(Entry, User_id, Group_id) of
{ok, E} ->
E;
{error, _} ->
erlang:error(#{gleam_error => panic,
message => <<"packkit/entry.with_owner: uid/gid must be non-negative"/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"packkit/entry"/utf8>>,
function => <<"with_owner"/utf8>>,
line => 332})
end.
-file("src/packkit/entry.gleam", 383).
?DOC(
" Override the last-modified timestamp after validating it is\n"
" non-negative. The format-specific upper bound is enforced at\n"
" encode time as `ArchiveFieldOverflow`.\n"
).
-spec with_modified_at_checked(entry(), integer()) -> {ok, entry()} |
{error, metadata_error()}.
with_modified_at_checked(Entry, Unix_seconds) ->
gleam@bool:guard(
Unix_seconds < 0,
{error, {modified_at_out_of_range, Unix_seconds}},
fun() ->
{metadata, Mode, User_id, Group_id, _} = erlang:element(6, Entry),
{ok,
{entry,
erlang:element(2, Entry),
erlang:element(3, Entry),
erlang:element(4, Entry),
erlang:element(5, Entry),
{metadata, Mode, User_id, Group_id, Unix_seconds}}}
end
).
-file("src/packkit/entry.gleam", 372).
?DOC(
" Override the last-modified timestamp. Negative values panic;\n"
" see [with_modified_at_checked] for the validated variant. As\n"
" with [with_owner], the format-specific upper bound (gzip 32-bit,\n"
" tar 11-octal-digit, …) is enforced at encode time.\n"
).
-spec with_modified_at(entry(), integer()) -> entry().
with_modified_at(Entry, Unix_seconds) ->
case with_modified_at_checked(Entry, Unix_seconds) of
{ok, E} ->
E;
{error, _} ->
erlang:error(#{gleam_error => panic,
message => <<"packkit/entry.with_modified_at: unix_seconds must be non-negative"/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"packkit/entry"/utf8>>,
function => <<"with_modified_at"/utf8>>,
line => 376})
end.
-file("src/packkit/entry.gleam", 408).
?DOC(" Convert an `EntryPath` back to its canonical string form.\n").
-spec to_string(entry_path()) -> binary().
to_string(Path) ->
erlang:element(2, Path).
-file("src/packkit/entry.gleam", 413).
?DOC(" Number of segments in the path.\n").
-spec depth(entry_path()) -> integer().
depth(Path) ->
erlang:length(erlang:element(3, Path)).
-file("src/packkit/entry.gleam", 418).
?DOC(" File mode stored in metadata.\n").
-spec mode(metadata()) -> integer().
mode(Metadata) ->
erlang:element(2, Metadata).
-file("src/packkit/entry.gleam", 423).
?DOC(" User identifier stored in metadata.\n").
-spec user_id(metadata()) -> integer().
user_id(Metadata) ->
erlang:element(3, Metadata).
-file("src/packkit/entry.gleam", 428).
?DOC(" Group identifier stored in metadata.\n").
-spec group_id(metadata()) -> integer().
group_id(Metadata) ->
erlang:element(4, Metadata).
-file("src/packkit/entry.gleam", 433).
?DOC(" Last-modified Unix timestamp stored in metadata.\n").
-spec modified_at_unix(metadata()) -> integer().
modified_at_unix(Metadata) ->
erlang:element(5, Metadata).
-file("src/packkit/entry.gleam", 437).
-spec file_metadata() -> metadata().
file_metadata() ->
{metadata, 420, 0, 0, 0}.
-file("src/packkit/entry.gleam", 441).
-spec directory_metadata() -> metadata().
directory_metadata() ->
{metadata, 493, 0, 0, 0}.
-file("src/packkit/entry.gleam", 445).
-spec link_metadata() -> metadata().
link_metadata() ->
{metadata, 511, 0, 0, 0}.
-file("src/packkit/entry.gleam", 449).
-spec contains_empty_segment(list(binary())) -> boolean().
contains_empty_segment(Segments) ->
case Segments of
[] ->
false;
[Segment | Rest] ->
(Segment =:= <<""/utf8>>) orelse contains_empty_segment(Rest)
end.
-file("src/packkit/entry.gleam", 456).
-spec contains_dot_segment(list(binary())) -> boolean().
contains_dot_segment(Segments) ->
case Segments of
[] ->
false;
[Segment | Rest] ->
(Segment =:= <<"."/utf8>>) orelse contains_dot_segment(Rest)
end.
-file("src/packkit/entry.gleam", 463).
-spec contains_traversal_segment(list(binary())) -> boolean().
contains_traversal_segment(Segments) ->
case Segments of
[] ->
false;
[Segment | Rest] ->
(Segment =:= <<".."/utf8>>) orelse contains_traversal_segment(Rest)
end.
-file("src/packkit/entry.gleam", 66).
?DOC(" Validate a relative archive path.\n").
-spec path_checked(binary()) -> {ok, entry_path()} | {error, entry_error()}.
path_checked(Value) ->
gleam@bool:guard(
Value =:= <<""/utf8>>,
{error, empty_path},
fun() ->
gleam@bool:guard(
gleam_stdlib:contains_string(Value, <<"\x{0000}"/utf8>>),
{error, {contains_nul, Value}},
fun() ->
gleam@bool:guard(
gleam_stdlib:string_starts_with(Value, <<"/"/utf8>>),
{error, {absolute_path, Value}},
fun() ->
gleam@bool:guard(
gleam_stdlib:contains_string(
Value,
<<"\\"/utf8>>
),
{error, {windows_path, Value}},
fun() ->
Segments = gleam@string:split(
Value,
<<"/"/utf8>>
),
gleam@bool:guard(
contains_empty_segment(Segments),
{error, {empty_segment, Value}},
fun() ->
gleam@bool:guard(
contains_dot_segment(Segments),
{error, {dot_segment, Value}},
fun() ->
gleam@bool:guard(
contains_traversal_segment(
Segments
),
{error,
{path_traversal,
Value}},
fun() ->
{ok,
{entry_path,
Value,
Segments}}
end
)
end
)
end
)
end
)
end
)
end
)
end
).
-file("src/packkit/entry.gleam", 101).
?DOC(
" Panicking counterpart of `path_checked`. The getter on `Entry`\n"
" claims the short name; this constructor takes the explicit suffix.\n"
).
-spec path_unchecked(binary()) -> entry_path().
path_unchecked(Value) ->
case path_checked(Value) of
{ok, Path} ->
Path;
{error, _} ->
erlang:error(#{gleam_error => panic,
message => <<"packkit/entry.path_unchecked: entry path must be a safe relative path"/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"packkit/entry"/utf8>>,
function => <<"path_unchecked"/utf8>>,
line => 105})
end.
-file("src/packkit/entry.gleam", 110).
?DOC(" Build a regular file entry after path validation.\n").
-spec file_checked(binary(), bitstring()) -> {ok, entry()} |
{error, entry_error()}.
file_checked(Path, Body) ->
case path_checked(Path) of
{ok, Safe_path} ->
{ok, {entry, file, Safe_path, Body, none, file_metadata()}};
{error, Error} ->
{error, Error}
end.
-file("src/packkit/entry.gleam", 128).
?DOC(" Panicking counterpart of `file_checked`.\n").
-spec file(binary(), bitstring()) -> entry().
file(Path, Body) ->
case file_checked(Path, Body) of
{ok, Entry} ->
Entry;
{error, _} ->
erlang:error(#{gleam_error => panic,
message => <<"packkit/entry.file: entry path must be a safe relative path"/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"packkit/entry"/utf8>>,
function => <<"file"/utf8>>,
line => 132})
end.
-file("src/packkit/entry.gleam", 137).
?DOC(" Build a directory entry after path validation.\n").
-spec directory_checked(binary()) -> {ok, entry()} | {error, entry_error()}.
directory_checked(Path) ->
case path_checked(Path) of
{ok, Safe_path} ->
{ok,
{entry, directory, Safe_path, <<>>, none, directory_metadata()}};
{error, Error} ->
{error, Error}
end.
-file("src/packkit/entry.gleam", 152).
?DOC(" Panicking counterpart of `directory_checked`.\n").
-spec directory(binary()) -> entry().
directory(Path) ->
case directory_checked(Path) of
{ok, Entry} ->
Entry;
{error, _} ->
erlang:error(#{gleam_error => panic,
message => <<"packkit/entry.directory: entry path must be a safe relative path"/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"packkit/entry"/utf8>>,
function => <<"directory"/utf8>>,
line => 156})
end.
-file("src/packkit/entry.gleam", 162).
?DOC(
" Build a symbolic-link entry. The entry path is strictly validated.\n"
" The link target is preserved as metadata but must not contain NUL.\n"
).
-spec symlink_checked(binary(), binary()) -> {ok, entry()} |
{error, entry_error()}.
symlink_checked(Path, Target) ->
gleam@bool:guard(
gleam_stdlib:contains_string(Target, <<"\x{0000}"/utf8>>),
{error, {contains_nul, Target}},
fun() -> case path_checked(Path) of
{ok, Safe_path} ->
{ok,
{entry,
symlink,
Safe_path,
<<>>,
{some, Target},
link_metadata()}};
{error, Error} ->
{error, Error}
end end
).
-file("src/packkit/entry.gleam", 185).
?DOC(" Panicking counterpart of `symlink_checked`.\n").
-spec symlink(binary(), binary()) -> entry().
symlink(Path, Target) ->
case symlink_checked(Path, Target) of
{ok, Entry} ->
Entry;
{error, _} ->
erlang:error(#{gleam_error => panic,
message => <<"packkit/entry.symlink: invalid archive path or link target"/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"packkit/entry"/utf8>>,
function => <<"symlink"/utf8>>,
line => 189})
end.
-file("src/packkit/entry.gleam", 195).
?DOC(
" Build a hard-link entry. The entry path is strictly validated.\n"
" The link target is preserved as metadata but must not contain NUL.\n"
).
-spec hardlink_checked(binary(), binary()) -> {ok, entry()} |
{error, entry_error()}.
hardlink_checked(Path, Target) ->
gleam@bool:guard(
gleam_stdlib:contains_string(Target, <<"\x{0000}"/utf8>>),
{error, {contains_nul, Target}},
fun() -> case path_checked(Path) of
{ok, Safe_path} ->
{ok,
{entry,
hardlink,
Safe_path,
<<>>,
{some, Target},
link_metadata()}};
{error, Error} ->
{error, Error}
end end
).
-file("src/packkit/entry.gleam", 218).
?DOC(" Panicking counterpart of `hardlink_checked`.\n").
-spec hardlink(binary(), binary()) -> entry().
hardlink(Path, Target) ->
case hardlink_checked(Path, Target) of
{ok, Entry} ->
Entry;
{error, _} ->
erlang:error(#{gleam_error => panic,
message => <<"packkit/entry.hardlink: invalid archive path or link target"/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"packkit/entry"/utf8>>,
function => <<"hardlink"/utf8>>,
line => 222})
end.
-file("src/packkit/entry.gleam", 290).
?DOC(
" Override the entry mode after validating it fits the widest mode\n"
" field any of our archive families serialise it through.\n"
).
-spec with_mode_checked(entry(), integer()) -> {ok, entry()} |
{error, metadata_error()}.
with_mode_checked(Entry, Mode) ->
gleam@bool:guard(
(Mode < 0) orelse (Mode > 16#FFFF),
{error, {mode_out_of_range, Mode}},
fun() ->
{metadata, _, User_id, Group_id, Modified_at_unix} = erlang:element(
6,
Entry
),
{ok,
{entry,
erlang:element(2, Entry),
erlang:element(3, Entry),
erlang:element(4, Entry),
erlang:element(5, Entry),
{metadata, Mode, User_id, Group_id, Modified_at_unix}}}
end
).
-file("src/packkit/entry.gleam", 280).
?DOC(
" Override the entry mode. Out-of-range values panic; use\n"
" [with_mode_checked] for caller-controlled error handling.\n"
).
-spec with_mode(entry(), integer()) -> entry().
with_mode(Entry, Mode) ->
case with_mode_checked(Entry, Mode) of
{ok, E} ->
E;
{error, _} ->
erlang:error(#{gleam_error => panic,
message => <<"packkit/entry.with_mode: mode must be in the inclusive range 0..0xFFFF"/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"packkit/entry"/utf8>>,
function => <<"with_mode"/utf8>>,
line => 284})
end.