Skip to main content

src/packkit@ar.erl

-module(packkit@ar).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/packkit/ar.gleam").
-export([format/0, new/0, decode_with_limits/2, decode/1, encode/1]).
-export_type([special_kind/0, parsed_record/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(
    " Unix `ar` archive encoder and decoder.\n"
    "\n"
    " The encoder emits the BSD long-name variant (`#1/N`) so it can\n"
    " carry archive entries whose paths exceed 16 bytes or contain\n"
    " spaces.  The decoder additionally accepts the GNU long-name\n"
    " variant (a leading `//` string-table member plus `/<offset>`\n"
    " references in entry headers), which is the form produced by\n"
    " `binutils` ar and present in nearly every `.deb` / `.a` on\n"
    " Linux.  GNU symbol-table members (named `/`) are skipped\n"
    " transparently so they do not show up as user-visible entries.\n"
    "\n"
    " Only regular file entries are supported - `ar` does not represent\n"
    " directories or symbolic links.\n"
).

-type special_kind() :: regular_entry | symbol_table | string_table.

-type parsed_record() :: {parsed_record,
        binary(),
        integer(),
        gleam@option:option(integer()),
        special_kind(),
        integer(),
        integer(),
        integer(),
        integer(),
        integer()}.

-file("src/packkit/ar.gleam", 38).
?DOC(" AR archive format marker.\n").
-spec format() -> packkit@archive:archive_format().
format() ->
    packkit@archive:ar().

-file("src/packkit/ar.gleam", 43).
?DOC(" Create an empty AR archive value.\n").
-spec new() -> packkit@archive:archive().
new() ->
    packkit@archive:new(format()).

-file("src/packkit/ar.gleam", 61).
-spec reject_comment(packkit@archive:archive()) -> {ok, nil} |
    {error, packkit@error:archive_error()}.
reject_comment(Archive_value) ->
    case packkit@archive:comment(Archive_value) of
        none ->
            {ok, nil};

        {some, _} ->
            {error, {archive_comment_unsupported, <<"ar"/utf8>>}}
    end.

-file("src/packkit/ar.gleam", 260).
-spec find_gnu_terminator(bitstring(), integer(), integer()) -> integer().
find_gnu_terminator(Bytes, Pos, Len) ->
    case Pos >= Len of
        true ->
            Len;

        false ->
            case gleam_stdlib:bit_array_slice(Bytes, Pos, 1) of
                {ok, <<16#2F>>} ->
                    Pos;

                {ok, <<16#00>>} ->
                    Pos;

                {ok, <<16#0A>>} ->
                    Pos;

                _ ->
                    find_gnu_terminator(Bytes, Pos + 1, Len)
            end
    end.

-file("src/packkit/ar.gleam", 274).
-spec check_member_limit(integer(), packkit@limit:limits()) -> {ok, nil} |
    {error, packkit@error:archive_error()}.
check_member_limit(Count, Limits) ->
    case Count > packkit@limit:max_members(Limits) of
        true ->
            {error, {archive_limit_exceeded, <<"max_members"/utf8>>, Count}};

        false ->
            {ok, nil}
    end.

-file("src/packkit/ar.gleam", 535).
-spec pow_int_loop(integer(), integer(), integer()) -> integer().
pow_int_loop(Base, Exponent, Acc) ->
    case Exponent of
        0 ->
            Acc;

        _ ->
            pow_int_loop(Base, Exponent - 1, Acc * Base)
    end.

-file("src/packkit/ar.gleam", 531).
-spec pow_int(integer(), integer()) -> integer().
pow_int(Base, Exponent) ->
    pow_int_loop(Base, Exponent, 1).

-file("src/packkit/ar.gleam", 563).
-spec byte_repeat_loop(integer(), integer(), bitstring()) -> bitstring().
byte_repeat_loop(Byte, Count, Acc) ->
    case Count of
        0 ->
            Acc;

        _ ->
            byte_repeat_loop(Byte, Count - 1, <<Acc/bitstring, Byte>>)
    end.

-file("src/packkit/ar.gleam", 559).
-spec byte_repeat(integer(), integer()) -> bitstring().
byte_repeat(Byte, Count) ->
    byte_repeat_loop(Byte, Count, <<>>).

-file("src/packkit/ar.gleam", 548).
-spec right_pad(bitstring(), integer(), integer()) -> bitstring().
right_pad(Value, Width, Fill) ->
    Size = erlang:byte_size(Value),
    case Size >= Width of
        true ->
            Slice@1 = case gleam_stdlib:bit_array_slice(Value, 0, Width) of
                {ok, Slice} -> Slice;
                _assert_fail ->
                    erlang:error(#{gleam_error => let_assert,
                                message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
                                file => <<?FILEPATH/utf8>>,
                                module => <<"packkit/ar"/utf8>>,
                                function => <<"right_pad"/utf8>>,
                                line => 552,
                                value => _assert_fail,
                                start => 16047,
                                'end' => 16102,
                                pattern_start => 16058,
                                pattern_end => 16067})
            end,
            Slice@1;

        false ->
            gleam_stdlib:bit_array_concat(
                [Value, byte_repeat(Fill, Width - Size)]
            )
    end.

-file("src/packkit/ar.gleam", 480).
-spec text_field(binary(), integer()) -> bitstring().
text_field(Value, Width) ->
    Bytes = gleam_stdlib:identity(Value),
    right_pad(Bytes, Width, 16#20).

-file("src/packkit/ar.gleam", 485).
-spec decimal_field(integer(), integer()) -> bitstring().
decimal_field(Value, Width) ->
    Raw = erlang:integer_to_binary(Value),
    Raw_bits = gleam_stdlib:identity(Raw),
    right_pad(Raw_bits, Width, 16#20).

-file("src/packkit/ar.gleam", 491).
-spec checked_decimal_field(integer(), integer(), binary()) -> {ok, bitstring()} |
    {error, packkit@error:archive_error()}.
checked_decimal_field(Value, Width, Field) ->
    Raw = erlang:integer_to_binary(Value),
    Raw_size = erlang:byte_size(Raw),
    case (Value < 0) orelse (Raw_size > Width) of
        true ->
            Max_value = pow_int(10, Width) - 1,
            {error,
                {archive_field_overflow,
                    <<"ar "/utf8, Field/binary>>,
                    Value,
                    Max_value}};

        false ->
            {ok, decimal_field(Value, Width)}
    end.

-file("src/packkit/ar.gleam", 542).
-spec octal_field(integer(), integer()) -> bitstring().
octal_field(Value, Width) ->
    Raw = gleam@int:to_base8(Value),
    Raw_bits = gleam_stdlib:identity(Raw),
    right_pad(Raw_bits, Width, 16#20).

-file("src/packkit/ar.gleam", 511).
-spec checked_octal_field(integer(), integer(), binary()) -> {ok, bitstring()} |
    {error, packkit@error:archive_error()}.
checked_octal_field(Value, Width, Field) ->
    Raw = gleam@int:to_base8(Value),
    Raw_size = erlang:byte_size(Raw),
    case (Value < 0) orelse (Raw_size > Width) of
        true ->
            Max_value = pow_int(8, Width) - 1,
            {error,
                {archive_field_overflow,
                    <<"ar "/utf8, Field/binary>>,
                    Value,
                    Max_value}};

        false ->
            {ok, octal_field(Value, Width)}
    end.

-file("src/packkit/ar.gleam", 608).
-spec slice_or_error(bitstring(), integer(), integer()) -> {ok, bitstring()} |
    {error, packkit@error:archive_error()}.
slice_or_error(Block, Offset, Width) ->
    case gleam_stdlib:bit_array_slice(Block, Offset, Width) of
        {ok, Value} ->
            {ok, Value};

        {error, _} ->
            {error, {archive_invalid, <<"ar header field out of bounds"/utf8>>}}
    end.

-file("src/packkit/ar.gleam", 620).
-spec bytes_to_string(bitstring()) -> {ok, binary()} |
    {error, packkit@error:archive_error()}.
bytes_to_string(Bytes) ->
    case gleam@bit_array:to_string(Bytes) of
        {ok, Value} ->
            {ok, Value};

        {error, _} ->
            {error, {archive_invalid, <<"non-UTF-8 ar header string"/utf8>>}}
    end.

-file("src/packkit/ar.gleam", 241).
-spec gnu_string_at(bitstring(), integer()) -> {ok, binary()} |
    {error, packkit@error:archive_error()}.
gnu_string_at(Table, Offset) ->
    Size = erlang:byte_size(Table),
    case (Offset < 0) orelse (Offset >= Size) of
        true ->
            {error,
                {archive_invalid,
                    <<"ar GNU string table offset out of range"/utf8>>}};

        false ->
            Tail@1 = case gleam_stdlib:bit_array_slice(
                Table,
                Offset,
                Size - Offset
            ) of
                {ok, Tail} -> Tail;
                _assert_fail ->
                    erlang:error(#{gleam_error => let_assert,
                                message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
                                file => <<?FILEPATH/utf8>>,
                                module => <<"packkit/ar"/utf8>>,
                                function => <<"gnu_string_at"/utf8>>,
                                line => 252,
                                value => _assert_fail,
                                start => 7860,
                                'end' => 7927,
                                pattern_start => 7871,
                                pattern_end => 7879})
            end,
            End_index = find_gnu_terminator(Tail@1, 0, Size - Offset),
            Name_bits@1 = case gleam_stdlib:bit_array_slice(
                Tail@1,
                0,
                End_index
            ) of
                {ok, Name_bits} -> Name_bits;
                _assert_fail@1 ->
                    erlang:error(#{gleam_error => let_assert,
                                message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
                                file => <<?FILEPATH/utf8>>,
                                module => <<"packkit/ar"/utf8>>,
                                function => <<"gnu_string_at"/utf8>>,
                                line => 254,
                                value => _assert_fail@1,
                                start => 8000,
                                'end' => 8062,
                                pattern_start => 8011,
                                pattern_end => 8024})
            end,
            bytes_to_string(Name_bits@1)
    end.

-file("src/packkit/ar.gleam", 570).
-spec read_decimal_field(bitstring(), integer(), integer()) -> {ok, integer()} |
    {error, packkit@error:archive_error()}.
read_decimal_field(Block, Offset, Width) ->
    gleam@result:'try'(
        slice_or_error(Block, Offset, Width),
        fun(Slice) ->
            gleam@result:'try'(
                bytes_to_string(Slice),
                fun(Raw) ->
                    Trimmed = gleam@string:trim(Raw),
                    case Trimmed of
                        <<""/utf8>> ->
                            {ok, 0};

                        _ ->
                            case gleam_stdlib:parse_int(Trimmed) of
                                {ok, Value} ->
                                    {ok, Value};

                                {error, _} ->
                                    {error,
                                        {archive_invalid,
                                            <<"invalid decimal ar field"/utf8>>}}
                            end
                    end
                end
            )
        end
    ).

-file("src/packkit/ar.gleam", 589).
-spec read_octal_field(bitstring(), integer(), integer()) -> {ok, integer()} |
    {error, packkit@error:archive_error()}.
read_octal_field(Block, Offset, Width) ->
    gleam@result:'try'(
        slice_or_error(Block, Offset, Width),
        fun(Slice) ->
            gleam@result:'try'(
                bytes_to_string(Slice),
                fun(Raw) ->
                    Trimmed = gleam@string:trim(Raw),
                    case Trimmed of
                        <<""/utf8>> ->
                            {ok, 0};

                        _ ->
                            case gleam@int:base_parse(Trimmed, 8) of
                                {ok, Value} ->
                                    {ok, Value};

                                {error, _} ->
                                    {error,
                                        {archive_invalid,
                                            <<"invalid octal ar field"/utf8>>}}
                            end
                    end
                end
            )
        end
    ).

-file("src/packkit/ar.gleam", 628).
-spec entry_error_to_archive_error(packkit@entry:entry_error(), binary()) -> packkit@error:archive_error().
entry_error_to_archive_error(Err, Path) ->
    case Err of
        empty_path ->
            {archive_entry_rejected, Path, <<"empty path"/utf8>>};

        {absolute_path, _} ->
            {archive_entry_rejected, Path, <<"absolute path"/utf8>>};

        {path_traversal, _} ->
            {archive_entry_rejected, Path, <<"path traversal"/utf8>>};

        {windows_path, _} ->
            {archive_entry_rejected, Path, <<"windows path"/utf8>>};

        {empty_segment, _} ->
            {archive_entry_rejected, Path, <<"empty segment"/utf8>>};

        {dot_segment, _} ->
            {archive_entry_rejected, Path, <<"dot segment"/utf8>>};

        {contains_nul, _} ->
            {archive_entry_rejected, Path, <<"nul byte"/utf8>>}
    end.

-file("src/packkit/ar.gleam", 650).
-spec strip_trailing_slash(binary()) -> binary().
strip_trailing_slash(Value) ->
    case gleam_stdlib:string_ends_with(Value, <<"/"/utf8>>) of
        true ->
            gleam@string:drop_end(Value, 1);

        false ->
            Value
    end.

-file("src/packkit/ar.gleam", 662).
-spec strip_trailing_nul_loop(bitstring(), integer()) -> bitstring().
strip_trailing_nul_loop(Bytes, Size) ->
    case Size of
        0 ->
            <<>>;

        _ ->
            case gleam_stdlib:bit_array_slice(Bytes, Size - 1, 1) of
                {ok, <<0>>} ->
                    strip_trailing_nul_loop(Bytes, Size - 1);

                _ ->
                    Slice@1 = case gleam_stdlib:bit_array_slice(Bytes, 0, Size) of
                        {ok, Slice} -> Slice;
                        _assert_fail ->
                            erlang:error(#{gleam_error => let_assert,
                                        message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
                                        file => <<?FILEPATH/utf8>>,
                                        module => <<"packkit/ar"/utf8>>,
                                        function => <<"strip_trailing_nul_loop"/utf8>>,
                                        line => 669,
                                        value => _assert_fail,
                                        start => 19250,
                                        'end' => 19304,
                                        pattern_start => 19261,
                                        pattern_end => 19270})
                    end,
                    Slice@1
            end
    end.

-file("src/packkit/ar.gleam", 657).
-spec strip_trailing_nul(bitstring()) -> bitstring().
strip_trailing_nul(Bytes) ->
    Size = erlang:byte_size(Bytes),
    strip_trailing_nul_loop(Bytes, Size).

-file("src/packkit/ar.gleam", 212).
-spec resolve_entry_name(
    parsed_record(),
    bitstring(),
    integer(),
    gleam@option:option(bitstring())
) -> {ok, binary()} | {error, packkit@error:archive_error()}.
resolve_entry_name(Record, Bytes, After_header_offset, String_table) ->
    case {erlang:element(3, Record), erlang:element(4, Record)} of
        {N, _} when N > 0 ->
            gleam@result:'try'(
                slice_or_error(
                    Bytes,
                    After_header_offset,
                    erlang:element(3, Record)
                ),
                fun(Ext_bits) ->
                    bytes_to_string(strip_trailing_nul(Ext_bits))
                end
            );

        {_, {some, Offset}} ->
            case String_table of
                none ->
                    {error,
                        {archive_invalid,
                            <<"ar entry references missing GNU string table"/utf8>>}};

                {some, Table} ->
                    gnu_string_at(Table, Offset)
            end;

        {_, none} ->
            {ok, erlang:element(2, Record)}
    end.

-file("src/packkit/ar.gleam", 676).
-spec min(integer(), integer()) -> integer().
min(A, B) ->
    case A < B of
        true ->
            A;

        false ->
            B
    end.

-file("src/packkit/ar.gleam", 305).
-spec parse_header(bitstring()) -> {ok, parsed_record()} |
    {error, packkit@error:archive_error()}.
parse_header(Block) ->
    gleam@result:'try'(
        slice_or_error(Block, 0, 16),
        fun(Raw_name_bits) ->
            gleam@result:'try'(
                bytes_to_string(Raw_name_bits),
                fun(Raw_name) ->
                    Trimmed_name = gleam@string:trim_end(Raw_name),
                    gleam@result:'try'(
                        read_decimal_field(Block, 16, 12),
                        fun(Mtime) ->
                            gleam@result:'try'(
                                read_decimal_field(Block, 28, 6),
                                fun(Uid) ->
                                    gleam@result:'try'(
                                        read_decimal_field(Block, 34, 6),
                                        fun(Gid) ->
                                            gleam@result:'try'(
                                                read_octal_field(Block, 40, 8),
                                                fun(Mode) ->
                                                    gleam@result:'try'(
                                                        read_decimal_field(
                                                            Block,
                                                            48,
                                                            10
                                                        ),
                                                        fun(Size) ->
                                                            gleam@result:'try'(
                                                                slice_or_error(
                                                                    Block,
                                                                    58,
                                                                    2
                                                                ),
                                                                fun(Ending_bits) ->
                                                                    gleam@bool:guard(
                                                                        Ending_bits
                                                                        /= <<16#60,
                                                                            16#0A>>,
                                                                        {error,
                                                                            {archive_invalid,
                                                                                <<"ar header marker missing"/utf8>>}},
                                                                        fun() ->
                                                                            case Trimmed_name of
                                                                                <<"/"/utf8>> ->
                                                                                    {ok,
                                                                                        {parsed_record,
                                                                                            Trimmed_name,
                                                                                            0,
                                                                                            none,
                                                                                            symbol_table,
                                                                                            Mtime,
                                                                                            Uid,
                                                                                            Gid,
                                                                                            Mode,
                                                                                            Size}};

                                                                                <<"/SYM64/"/utf8>> ->
                                                                                    {ok,
                                                                                        {parsed_record,
                                                                                            Trimmed_name,
                                                                                            0,
                                                                                            none,
                                                                                            symbol_table,
                                                                                            Mtime,
                                                                                            Uid,
                                                                                            Gid,
                                                                                            Mode,
                                                                                            Size}};

                                                                                <<"//"/utf8>> ->
                                                                                    {ok,
                                                                                        {parsed_record,
                                                                                            Trimmed_name,
                                                                                            0,
                                                                                            none,
                                                                                            string_table,
                                                                                            Mtime,
                                                                                            Uid,
                                                                                            Gid,
                                                                                            Mode,
                                                                                            Size}};

                                                                                <<"ARFILENAMES/"/utf8>> ->
                                                                                    {ok,
                                                                                        {parsed_record,
                                                                                            Trimmed_name,
                                                                                            0,
                                                                                            none,
                                                                                            string_table,
                                                                                            Mtime,
                                                                                            Uid,
                                                                                            Gid,
                                                                                            Mode,
                                                                                            Size}};

                                                                                _ ->
                                                                                    case gleam_stdlib:string_starts_with(
                                                                                        Trimmed_name,
                                                                                        <<"#1/"/utf8>>
                                                                                    ) of
                                                                                        true ->
                                                                                            Len_string = gleam@string:drop_start(
                                                                                                Trimmed_name,
                                                                                                3
                                                                                            ),
                                                                                            case gleam_stdlib:parse_int(
                                                                                                Len_string
                                                                                            ) of
                                                                                                {ok,
                                                                                                    Len} ->
                                                                                                    {ok,
                                                                                                        {parsed_record,
                                                                                                            <<""/utf8>>,
                                                                                                            Len,
                                                                                                            none,
                                                                                                            regular_entry,
                                                                                                            Mtime,
                                                                                                            Uid,
                                                                                                            Gid,
                                                                                                            Mode,
                                                                                                            Size}};

                                                                                                {error,
                                                                                                    _} ->
                                                                                                    {error,
                                                                                                        {archive_invalid,
                                                                                                            <<"invalid BSD long name length in ar header"/utf8>>}}
                                                                                            end;

                                                                                        false ->
                                                                                            case gleam_stdlib:string_starts_with(
                                                                                                Trimmed_name,
                                                                                                <<"/"/utf8>>
                                                                                            ) of
                                                                                                true ->
                                                                                                    Offset_string = gleam@string:drop_start(
                                                                                                        Trimmed_name,
                                                                                                        1
                                                                                                    ),
                                                                                                    case gleam_stdlib:parse_int(
                                                                                                        Offset_string
                                                                                                    ) of
                                                                                                        {ok,
                                                                                                            Offset} ->
                                                                                                            {ok,
                                                                                                                {parsed_record,
                                                                                                                    <<""/utf8>>,
                                                                                                                    0,
                                                                                                                    {some,
                                                                                                                        Offset},
                                                                                                                    regular_entry,
                                                                                                                    Mtime,
                                                                                                                    Uid,
                                                                                                                    Gid,
                                                                                                                    Mode,
                                                                                                                    Size}};

                                                                                                        {error,
                                                                                                            _} ->
                                                                                                            {error,
                                                                                                                {archive_invalid,
                                                                                                                    <<"invalid GNU long name offset in ar header"/utf8>>}}
                                                                                                    end;

                                                                                                false ->
                                                                                                    {ok,
                                                                                                        {parsed_record,
                                                                                                            strip_trailing_slash(
                                                                                                                Trimmed_name
                                                                                                            ),
                                                                                                            0,
                                                                                                            none,
                                                                                                            regular_entry,
                                                                                                            Mtime,
                                                                                                            Uid,
                                                                                                            Gid,
                                                                                                            Mode,
                                                                                                            Size}}
                                                                                            end
                                                                                    end
                                                                            end
                                                                        end
                                                                    )
                                                                end
                                                            )
                                                        end
                                                    )
                                                end
                                            )
                                        end
                                    )
                                end
                            )
                        end
                    )
                end
            )
        end
    ).

-file("src/packkit/ar.gleam", 104).
-spec decode_loop(
    bitstring(),
    list(packkit@entry:entry()),
    integer(),
    gleam@option:option(bitstring()),
    packkit@limit:limits()
) -> {ok, list(packkit@entry:entry())} | {error, packkit@error:archive_error()}.
decode_loop(Bytes, Acc, Count, String_table, Limits) ->
    case erlang:byte_size(Bytes) of
        0 ->
            {ok, Acc};

        _ ->
            case erlang:byte_size(Bytes) < 60 of
                true ->
                    {error,
                        {archive_invalid, <<"ar entry header truncated"/utf8>>}};

                false ->
                    Header_bits@1 = case gleam_stdlib:bit_array_slice(
                        Bytes,
                        0,
                        60
                    ) of
                        {ok, Header_bits} -> Header_bits;
                        _assert_fail ->
                            erlang:error(#{gleam_error => let_assert,
                                        message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
                                        file => <<?FILEPATH/utf8>>,
                                        module => <<"packkit/ar"/utf8>>,
                                        function => <<"decode_loop"/utf8>>,
                                        line => 118,
                                        value => _assert_fail,
                                        start => 3520,
                                        'end' => 3587,
                                        pattern_start => 3531,
                                        pattern_end => 3546})
                    end,
                    gleam@result:'try'(
                        parse_header(Header_bits@1),
                        fun(Record) ->
                            After_header_offset = 60,
                            Payload_offset = After_header_offset + erlang:element(
                                3,
                                Record
                            ),
                            Payload_size = erlang:element(10, Record) - erlang:element(
                                3,
                                Record
                            ),
                            Total_size = 60 + erlang:element(10, Record),
                            Padded_size = case Total_size rem 2 of
                                0 ->
                                    Total_size;

                                _ ->
                                    Total_size + 1
                            end,
                            gleam@bool:guard(
                                erlang:byte_size(Bytes) < (Payload_offset + Payload_size),
                                {error,
                                    {archive_invalid,
                                        <<"ar entry truncated"/utf8>>}},
                                fun() ->
                                    Advance = min(
                                        Padded_size,
                                        erlang:byte_size(Bytes)
                                    ),
                                    Remaining@1 = case gleam_stdlib:bit_array_slice(
                                        Bytes,
                                        Advance,
                                        erlang:byte_size(Bytes) - Advance
                                    ) of
                                        {ok, Remaining} -> Remaining;
                                        _assert_fail@1 ->
                                            erlang:error(
                                                    #{gleam_error => let_assert,
                                                        message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
                                                        file => <<?FILEPATH/utf8>>,
                                                        module => <<"packkit/ar"/utf8>>,
                                                        function => <<"decode_loop"/utf8>>,
                                                        line => 136,
                                                        value => _assert_fail@1,
                                                        start => 4294,
                                                        'end' => 4459,
                                                        pattern_start => 4305,
                                                        pattern_end => 4318}
                                                )
                                    end,
                                    case erlang:element(5, Record) of
                                        symbol_table ->
                                            decode_loop(
                                                Remaining@1,
                                                Acc,
                                                Count,
                                                String_table,
                                                Limits
                                            );

                                        string_table ->
                                            Table_body@1 = case gleam_stdlib:bit_array_slice(
                                                Bytes,
                                                Payload_offset,
                                                Payload_size
                                            ) of
                                                {ok, Table_body} -> Table_body;
                                                _assert_fail@2 ->
                                                    erlang:error(
                                                            #{gleam_error => let_assert,
                                                                message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
                                                                file => <<?FILEPATH/utf8>>,
                                                                module => <<"packkit/ar"/utf8>>,
                                                                function => <<"decode_loop"/utf8>>,
                                                                line => 147,
                                                                value => _assert_fail@2,
                                                                start => 4634,
                                                                'end' => 4730,
                                                                pattern_start => 4645,
                                                                pattern_end => 4659}
                                                        )
                                            end,
                                            decode_loop(
                                                Remaining@1,
                                                Acc,
                                                Count,
                                                {some, Table_body@1},
                                                Limits
                                            );

                                        regular_entry ->
                                            gleam@result:'try'(
                                                check_member_limit(
                                                    Count + 1,
                                                    Limits
                                                ),
                                                fun(_) ->
                                                    gleam@result:'try'(
                                                        resolve_entry_name(
                                                            Record,
                                                            Bytes,
                                                            After_header_offset,
                                                            String_table
                                                        ),
                                                        fun(Name) ->
                                                            gleam@bool:guard(
                                                                erlang:byte_size(
                                                                    Name
                                                                )
                                                                > packkit@limit:max_entry_name_bytes(
                                                                    Limits
                                                                ),
                                                                {error,
                                                                    {archive_limit_exceeded,
                                                                        <<"max_entry_name_bytes"/utf8>>,
                                                                        erlang:byte_size(
                                                                            Name
                                                                        )}},
                                                                fun() ->
                                                                    Body@1 = case gleam_stdlib:bit_array_slice(
                                                                        Bytes,
                                                                        Payload_offset,
                                                                        Payload_size
                                                                    ) of
                                                                        {ok,
                                                                            Body} -> Body;
                                                                        _assert_fail@3 ->
                                                                            erlang:error(
                                                                                    #{gleam_error => let_assert,
                                                                                        message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
                                                                                        file => <<?FILEPATH/utf8>>,
                                                                                        module => <<"packkit/ar"/utf8>>,
                                                                                        function => <<"decode_loop"/utf8>>,
                                                                                        line => 176,
                                                                                        value => _assert_fail@3,
                                                                                        start => 5559,
                                                                                        'end' => 5649,
                                                                                        pattern_start => 5570,
                                                                                        pattern_end => 5578}
                                                                                )
                                                                    end,
                                                                    gleam@result:'try'(
                                                                        begin
                                                                            _pipe = packkit@entry:file_checked(
                                                                                Name,
                                                                                Body@1
                                                                            ),
                                                                            gleam@result:map_error(
                                                                                _pipe,
                                                                                fun(
                                                                                    _capture
                                                                                ) ->
                                                                                    entry_error_to_archive_error(
                                                                                        _capture,
                                                                                        Name
                                                                                    )
                                                                                end
                                                                            )
                                                                        end,
                                                                        fun(
                                                                            Base_entry
                                                                        ) ->
                                                                            Depth = packkit@entry:depth(
                                                                                packkit@entry:path(
                                                                                    Base_entry
                                                                                )
                                                                            ),
                                                                            gleam@bool:guard(
                                                                                Depth
                                                                                > packkit@limit:max_entry_depth(
                                                                                    Limits
                                                                                ),
                                                                                {error,
                                                                                    {archive_limit_exceeded,
                                                                                        <<"max_entry_depth"/utf8>>,
                                                                                        Depth}},
                                                                                fun(
                                                                                    
                                                                                ) ->
                                                                                    Entry_value = begin
                                                                                        _pipe@1 = Base_entry,
                                                                                        _pipe@2 = packkit@entry:with_mode(
                                                                                            _pipe@1,
                                                                                            erlang:element(
                                                                                                9,
                                                                                                Record
                                                                                            )
                                                                                        ),
                                                                                        _pipe@3 = packkit@entry:with_owner(
                                                                                            _pipe@2,
                                                                                            erlang:element(
                                                                                                7,
                                                                                                Record
                                                                                            ),
                                                                                            erlang:element(
                                                                                                8,
                                                                                                Record
                                                                                            )
                                                                                        ),
                                                                                        packkit@entry:with_modified_at(
                                                                                            _pipe@3,
                                                                                            erlang:element(
                                                                                                6,
                                                                                                Record
                                                                                            )
                                                                                        )
                                                                                    end,
                                                                                    decode_loop(
                                                                                        Remaining@1,
                                                                                        [Entry_value |
                                                                                            Acc],
                                                                                        Count
                                                                                        + 1,
                                                                                        String_table,
                                                                                        Limits
                                                                                    )
                                                                                end
                                                                            )
                                                                        end
                                                                    )
                                                                end
                                                            )
                                                        end
                                                    )
                                                end
                                            )
                                    end
                                end
                            )
                        end
                    )
            end
    end.

-file("src/packkit/ar.gleam", 78).
?DOC(" Decode an `ar` byte stream using explicit limits.\n").
-spec decode_with_limits(bitstring(), packkit@limit:limits()) -> {ok,
        packkit@archive:archive()} |
    {error, packkit@error:archive_error()}.
decode_with_limits(Bytes, Limits) ->
    gleam@bool:guard(
        erlang:byte_size(Bytes) > packkit@limit:max_input_bytes(Limits),
        {error,
            {archive_limit_exceeded,
                <<"max_input_bytes"/utf8>>,
                erlang:byte_size(Bytes)}},
        fun() ->
            gleam@result:'try'(
                slice_or_error(Bytes, 0, 8),
                fun(Header_bits) ->
                    gleam@result:'try'(
                        bytes_to_string(Header_bits),
                        fun(Header_string) ->
                            gleam@bool:guard(
                                Header_string /= <<"!<arch>\n"/utf8>>,
                                {error,
                                    {archive_invalid,
                                        <<"missing !<arch> magic"/utf8>>}},
                                fun() ->
                                    Body_size = erlang:byte_size(Bytes) - 8,
                                    Rest@1 = case gleam_stdlib:bit_array_slice(
                                        Bytes,
                                        8,
                                        Body_size
                                    ) of
                                        {ok, Rest} -> Rest;
                                        _assert_fail ->
                                            erlang:error(
                                                    #{gleam_error => let_assert,
                                                        message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
                                                        file => <<?FILEPATH/utf8>>,
                                                        module => <<"packkit/ar"/utf8>>,
                                                        function => <<"decode_with_limits"/utf8>>,
                                                        line => 98,
                                                        value => _assert_fail,
                                                        start => 2872,
                                                        'end' => 2939,
                                                        pattern_start => 2883,
                                                        pattern_end => 2891}
                                                )
                                    end,
                                    _pipe = decode_loop(
                                        Rest@1,
                                        [],
                                        0,
                                        none,
                                        Limits
                                    ),
                                    _pipe@1 = gleam@result:map(
                                        _pipe,
                                        fun lists:reverse/1
                                    ),
                                    gleam@result:map(
                                        _pipe@1,
                                        fun(_capture) ->
                                            packkit@archive:from_entries(
                                                format(),
                                                _capture
                                            )
                                        end
                                    )
                                end
                            )
                        end
                    )
                end
            )
        end
    ).

-file("src/packkit/ar.gleam", 71).
?DOC(" Decode an `ar` byte stream using default limits.\n").
-spec decode(bitstring()) -> {ok, packkit@archive:archive()} |
    {error, packkit@error:archive_error()}.
decode(Bytes) ->
    decode_with_limits(Bytes, packkit@limit:default()).

-file("src/packkit/ar.gleam", 409).
-spec encode_entry(packkit@entry:entry()) -> {ok, bitstring()} |
    {error, packkit@error:archive_error()}.
encode_entry(Value) ->
    Kind = packkit@entry:kind(Value),
    gleam@bool:guard(
        Kind /= file,
        {error,
            {archive_entry_rejected,
                packkit@entry:to_string(packkit@entry:path(Value)),
                <<"ar only supports regular file entries"/utf8>>}},
        fun() ->
            Path = packkit@entry:to_string(packkit@entry:path(Value)),
            Metadata = packkit@entry:metadata(Value),
            Body = packkit@entry:body(Value),
            Body_size = erlang:byte_size(Body),
            Name_bytes = gleam_stdlib:identity(Path),
            Name_size = erlang:byte_size(Name_bytes),
            Needs_long_name = ((Name_size > 16) orelse gleam_stdlib:contains_string(
                Path,
                <<" "/utf8>>
            ))
            orelse gleam_stdlib:contains_string(Path, <<"/"/utf8>>),
            {Name_field, Name_extension_bytes, Total_size} = case Needs_long_name of
                true ->
                    Extension_size = Name_size,
                    Label = <<"#1/"/utf8,
                        (erlang:integer_to_binary(Extension_size))/binary>>,
                    {text_field(Label, 16),
                        Name_bytes,
                        Body_size + Extension_size};

                false ->
                    {text_field(Path, 16), <<>>, Body_size}
            end,
            gleam@result:'try'(
                checked_decimal_field(
                    packkit@entry:modified_at_unix(Metadata),
                    12,
                    <<"mtime"/utf8>>
                ),
                fun(Mtime_field) ->
                    gleam@result:'try'(
                        checked_decimal_field(
                            packkit@entry:user_id(Metadata),
                            6,
                            <<"uid"/utf8>>
                        ),
                        fun(Uid_field) ->
                            gleam@result:'try'(
                                checked_decimal_field(
                                    packkit@entry:group_id(Metadata),
                                    6,
                                    <<"gid"/utf8>>
                                ),
                                fun(Gid_field) ->
                                    gleam@result:'try'(
                                        checked_octal_field(
                                            packkit@entry:mode(Metadata),
                                            8,
                                            <<"mode"/utf8>>
                                        ),
                                        fun(Mode_field) ->
                                            gleam@result:'try'(
                                                checked_decimal_field(
                                                    Total_size,
                                                    10,
                                                    <<"size"/utf8>>
                                                ),
                                                fun(Size_field) ->
                                                    Header = gleam_stdlib:bit_array_concat(
                                                        [Name_field,
                                                            Mtime_field,
                                                            Uid_field,
                                                            Gid_field,
                                                            Mode_field,
                                                            Size_field,
                                                            <<16#60, 16#0A>>]
                                                    ),
                                                    Combined = gleam_stdlib:bit_array_concat(
                                                        [Header,
                                                            Name_extension_bytes,
                                                            Body]
                                                    ),
                                                    Padded = case Total_size rem 2 of
                                                        0 ->
                                                            Combined;

                                                        _ ->
                                                            gleam_stdlib:bit_array_concat(
                                                                [Combined,
                                                                    <<16#0A>>]
                                                            )
                                                    end,
                                                    {ok, Padded}
                                                end
                                            )
                                        end
                                    )
                                end
                            )
                        end
                    )
                end
            )
        end
    ).

-file("src/packkit/ar.gleam", 48).
?DOC(" Encode the logical archive to an `ar` byte stream.\n").
-spec encode(packkit@archive:archive()) -> {ok, bitstring()} |
    {error, packkit@error:archive_error()}.
encode(Archive_value) ->
    gleam@result:'try'(
        reject_comment(Archive_value),
        fun(_) -> _pipe = Archive_value,
            _pipe@1 = packkit@archive:entries(_pipe),
            _pipe@2 = gleam@list:try_map(_pipe@1, fun encode_entry/1),
            gleam@result:map(
                _pipe@2,
                fun(Records) ->
                    _pipe@3 = [gleam_stdlib:identity(<<"!<arch>\n"/utf8>>),
                        gleam_stdlib:bit_array_concat(Records)],
                    gleam_stdlib:bit_array_concat(_pipe@3)
                end
            ) end
    ).