-module(gftp@list). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/gftp/list.gleam"). -export([describe_error/1, parse_mlsd/1, parse_mlst/1, parse_list/1]). -export_type([parse_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. ?MODULEDOC( " Parsers for FTP directory listing formats: `LIST` (POSIX and DOS), `MLSD`, and `MLST`.\n" "\n" " ## LIST parsing\n" "\n" " The `LIST` output format is not standardized and depends on the FTP server.\n" " This parser supports both POSIX and DOS formats, trying POSIX first.\n" " If you find a format that doesn't parse correctly, please report an issue\n" " at .\n" "\n" " ```gleam\n" " import gftp/list\n" " import gftp/list/file\n" "\n" " let line = \"-rw-r--r-- 1 user group 1234 Nov 5 13:46 example.txt\"\n" " let assert Ok(f) = list.parse_list(line)\n" " let name = file.name(f) // \"example.txt\"\n" " let size = file.size(f) // 1234\n" " ```\n" "\n" " ## MLSD/MLST parsing (RFC 3659)\n" "\n" " Machine-readable listing formats provide structured, standardized output:\n" "\n" " ```gleam\n" " import gftp/list\n" " import gftp/list/file\n" "\n" " let line = \"type=file;size=1234;modify=20200105134600; example.txt\"\n" " let assert Ok(f) = list.parse_mlsd(line)\n" " let name = file.name(f) // \"example.txt\"\n" " ```\n" "\n" " ## Usage with gftp\n" "\n" " ```gleam\n" " import gftp\n" " import gftp/list\n" " import gleam/list as gleam_list\n" " import gleam/option.{None}\n" "\n" " let assert Ok(lines) = gftp.list(client, None)\n" " let assert Ok(files) = gleam_list.try_map(lines, list.parse_list)\n" " ```\n" ). -type parse_error() :: syntax_error | invalid_date | bad_size. -file("src/gftp/list.gleam", 67). ?DOC(" Collapse multiple consecutive whitespace characters into a single space.\n"). -spec normalize_whitespace(binary()) -> binary(). normalize_whitespace(Input) -> Re@1 = case gleam@regexp:from_string(<<"\\s+"/utf8>>) of {ok, Re} -> Re; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"gftp/list"/utf8>>, function => <<"normalize_whitespace"/utf8>>, line => 68, value => _assert_fail, start => 2230, 'end' => 2276, pattern_start => 2241, pattern_end => 2247}) end, gleam_regexp_ffi:replace(Re@1, Input, <<" "/utf8>>). -file("src/gftp/list.gleam", 84). ?DOC(" Describe the given `ParseError` in human-readable form.\n"). -spec describe_error(parse_error()) -> binary(). describe_error(Error) -> case Error of syntax_error -> <<"the line does not conform to expected format."/utf8>>; invalid_date -> <<"the date field could not be parsed."/utf8>>; bad_size -> <<"the size field is not a valid unsigned integer."/utf8>> end. -file("src/gftp/list.gleam", 138). ?DOC(" Parse the permissions string from a `MLSD` or `MLST` line, which is typically a three-digit octal number representing the permissions for the owner, group, and others in a POSIX file system. Each digit can be from 0 to 7, where the bits represent read (4), write (2), and execute (1) permissions.\n"). -spec parse_mlsd_mlst_permissions(binary()) -> {ok, gftp@list@permission:file_permissions()} | {error, parse_error()}. parse_mlsd_mlst_permissions(Pex_str) -> Tokens = begin _pipe = Pex_str, _pipe@1 = gleam@string:split(_pipe, <<""/utf8>>), gleam@list:map(_pipe@1, fun(Token) -> _pipe@2 = Token, _pipe@3 = gleam@int:base_parse(_pipe@2, 8), gleam@result:unwrap(_pipe@3, 0) end) end, case Tokens of [User, Group, Other] -> {ok, {file_permissions, gftp@list@permission:from_int(User), gftp@list@permission:from_int(Group), gftp@list@permission:from_int(Other)}}; [_, User, Group, Other] -> {ok, {file_permissions, gftp@list@permission:from_int(User), gftp@list@permission:from_int(Group), gftp@list@permission:from_int(Other)}}; _ -> {error, syntax_error} end. -file("src/gftp/list.gleam", 230). ?DOC(" Parse the file type token from a POSIX LIST output line, which is typically the first character of the line and can be `-` for regular files, `d` for directories, and `l` for symbolic links (although we can't retrieve the target of the symlink in this case).\n"). -spec parse_list_file_type(binary()) -> {ok, gftp@list@file_type:file_type()} | {error, parse_error()}. parse_list_file_type(Token) -> case Token of <<"-"/utf8>> -> {ok, file}; <<"d"/utf8>> -> {ok, directory}; <<"l"/utf8>> -> {ok, {symlink, <<""/utf8>>}}; _ -> {error, syntax_error} end. -file("src/gftp/list.gleam", 241). ?DOC(" Parse a group permissions set made of three characters (e.g. `rwx`, `r-x`, `rw-`, etc.) from a POSIX LIST output line, where the characters represent read (`r`), write (`w`), and execute (`x`) permissions, and `-` represents no permission. Additionally, `s` and `t` can be used to indicate setuid/setgid and sticky bits, respectively.\n"). -spec parse_list_permissions_in_range(integer(), binary()) -> gftp@list@permission:posix_permission(). parse_list_permissions_in_range(Start, Token) -> _pipe = Token, _pipe@1 = gleam@string:slice(_pipe, Start, 3), _pipe@2 = gleam@string:split(_pipe@1, <<""/utf8>>), _pipe@3 = gleam@list:fold(_pipe@2, 0, fun(Acc, Char) -> case Char of <<"r"/utf8>> -> Acc + 4; <<"w"/utf8>> -> Acc + 2; <<"x"/utf8>> -> Acc + 1; <<"s"/utf8>> -> Acc + 1; <<"t"/utf8>> -> Acc + 1; <<"-"/utf8>> -> Acc; _ -> Acc end end), gftp@list@permission:from_int(_pipe@3). -file("src/gftp/list.gleam", 265). ?DOC( " Parse the permissions string from a POSIX LIST output line, \n" " which is typically a 10-character string where the first character \n" " represents the file type and the next 9 characters represent the \n" " permissions for the owner, group, and others in a POSIX file system. \n" " The permissions are represented as `r` for read, `w` for write, `x` for execute, and `-` for no permission. \n" " Additionally, `s` and `t` can be used to indicate setuid/setgid and sticky bits, respectively.\n" ). -spec parse_list_permissions(binary()) -> gftp@list@permission:file_permissions(). parse_list_permissions(Token) -> {file_permissions, parse_list_permissions_in_range(0, Token), parse_list_permissions_in_range(3, Token), parse_list_permissions_in_range(6, Token)}. -file("src/gftp/list.gleam", 328). ?DOC( " Parse the file name and symlink target (if applicable) from a POSIX LIST output line, which is typically the last token of the line.\n" " If the file type is a symbolic link, the token may contain the file name followed by `->` and the target of the symlink (e.g. `symlink -> target`).\n" ). -spec parse_list_name_and_link(gftp@list@file_type:file_type(), binary()) -> {ok, {binary(), gleam@option:option(binary())}} | {error, parse_error()}. parse_list_name_and_link(File_type, Token) -> Get_name_and_link = fun(Parts) -> case Parts of [Name, Target] -> {ok, {Name, {some, Target}}}; [Name@1] -> {ok, {Name@1, none}}; _ -> {error, syntax_error} end end, case File_type of {symlink, _} -> _pipe = Token, _pipe@1 = gleam@string:split(_pipe, <<" -> "/utf8>>), Get_name_and_link(_pipe@1); _ -> {ok, {Token, none}} end. -file("src/gftp/list.gleam", 351). ?DOC( " Resolve the file type for a POSIX LIST entry, filling in the symlink target\n" " when both the type indicator is `l` and the name contained ` -> target`.\n" ). -spec resolve_posix_file_type( gftp@list@file_type:file_type(), gleam@option:option(binary()) ) -> gftp@list@file_type:file_type(). resolve_posix_file_type(Ft, Symlink_path) -> case {Ft, Symlink_path} of {{symlink, _}, {some, Target}} -> {symlink, Target}; {{symlink, _}, none} -> file; {_, _} -> Ft end. -file("src/gftp/list.gleam", 363). ?DOC(" Optionally set a field on a File when the value is Some.\n"). -spec maybe_set( gftp@list@file:file(), gleam@option:option(MDF), fun((gftp@list@file:file(), MDF) -> gftp@list@file:file()) ) -> gftp@list@file:file(). maybe_set(F, Value, Setter) -> case Value of {some, V} -> Setter(F, V); none -> F end. -file("src/gftp/list.gleam", 371). ?DOC(" Try to parse a string as an Int, returning None on failure.\n"). -spec try_parse_int(binary()) -> gleam@option:option(integer()). try_parse_int(S) -> _pipe = S, _pipe@1 = gleam@string:trim(_pipe), _pipe@2 = gleam_stdlib:parse_int(_pipe@1), gleam@option:from_result(_pipe@2). -file("src/gftp/list.gleam", 479). ?DOC(" Try to a string as a datetime in the provided format, returning a Timestamp on success or a ParseError on failure.\n"). -spec parse_datetime_with_format(binary(), binary()) -> {ok, gleam@time@timestamp:timestamp()} | {error, parse_error()}. parse_datetime_with_format(S, Format) -> Fmt = {custom_naive, Format}, _pipe = S, _pipe@1 = gleam@string:trim(_pipe), _pipe@2 = normalize_whitespace(_pipe@1), _pipe@3 = tempo@naive_datetime:parse(_pipe@2, Fmt), _pipe@4 = gleam@result:map(_pipe@3, fun tempo@naive_datetime:as_utc/1), _pipe@5 = gleam@result:map(_pipe@4, fun tempo@datetime:to_timestamp/1), gleam@result:map_error(_pipe@5, fun(_) -> invalid_date end). -file("src/gftp/list.gleam", 133). ?DOC(" Parse MLSD and MLST date formats, which is in the form of `%Y%m%d%H%M%S`\n"). -spec parse_mlsd_mlst_time(binary()) -> {ok, gleam@time@timestamp:timestamp()} | {error, parse_error()}. parse_mlsd_mlst_time(Date_str) -> parse_datetime_with_format(Date_str, <<"YYYYMMDDHHmmss"/utf8>>). -file("src/gftp/list.gleam", 159). ?DOC(" Parse a single token in a `key=value` pair from a `MLSD` or `MLST` line, updating the given `File` data structure with the extracted metadata on success, or returning a `ParseError` on failure.\n"). -spec parse_mlsd_mlst_token(binary(), binary(), gftp@list@file:file()) -> {ok, gftp@list@file:file()} | {error, parse_error()}. parse_mlsd_mlst_token(Key, Value, File) -> case string:lowercase(Key) of <<"type"/utf8>> -> case string:lowercase(Value) of <<"file"/utf8>> -> {ok, gftp@list@file:with_file_type(File, file)}; <<"link"/utf8>> -> {ok, gftp@list@file:with_file_type(File, file)}; <<"dir"/utf8>> -> {ok, gftp@list@file:with_file_type(File, directory)}; <<"cdir"/utf8>> -> {ok, gftp@list@file:with_file_type(File, directory)}; <<"pdir"/utf8>> -> {ok, gftp@list@file:with_file_type(File, directory)}; _ -> {error, syntax_error} end; <<"size"/utf8>> -> _pipe = Value, _pipe@1 = gleam_stdlib:parse_int(_pipe), _pipe@2 = gleam@result:map( _pipe@1, fun(Size) -> gftp@list@file:with_size(File, Size) end ), gleam@result:map_error(_pipe@2, fun(_) -> bad_size end); <<"modify"/utf8>> -> _pipe@3 = Value, _pipe@4 = parse_mlsd_mlst_time(_pipe@3), gleam@result:map( _pipe@4, fun(Modified) -> gftp@list@file:with_modified(File, Modified) end ); <<"unix.uid"/utf8>> -> _pipe@5 = Value, _pipe@6 = gleam_stdlib:parse_int(_pipe@5), _pipe@7 = gleam@result:map( _pipe@6, fun(Uid) -> gftp@list@file:with_uid(File, Uid) end ), gleam@result:map_error(_pipe@7, fun(_) -> syntax_error end); <<"unix.gid"/utf8>> -> _pipe@8 = Value, _pipe@9 = gleam_stdlib:parse_int(_pipe@8), _pipe@10 = gleam@result:map( _pipe@9, fun(Uid@1) -> gftp@list@file:with_gid(File, Uid@1) end ), gleam@result:map_error(_pipe@10, fun(_) -> syntax_error end); <<"unix.mode"/utf8>> -> _pipe@11 = Value, _pipe@12 = parse_mlsd_mlst_permissions(_pipe@11), _pipe@13 = gleam@result:map( _pipe@12, fun(Permissions) -> gftp@list@file:with_permissions(File, Permissions) end ), gleam@result:map_error(_pipe@13, fun(_) -> syntax_error end); _ -> {ok, File} end. -file("src/gftp/list.gleam", 199). ?DOC(" Parse MLSD and MLST tokens, split by `;`\n"). -spec parse_mlsd_mlst_tokens(list(binary()), gftp@list@file:file()) -> {ok, gftp@list@file:file()} | {error, parse_error()}. parse_mlsd_mlst_tokens(Tokens, File) -> case Tokens of [] -> {ok, File}; [Filename] -> parse_mlsd_mlst_tokens( [], gftp@list@file:with_name(File, gleam@string:trim(Filename)) ); [Token | Rest] -> case gleam@string:split(Token, <<"="/utf8>>) of [Key, Value] -> case parse_mlsd_mlst_token(Key, Value, File) of {ok, Updated_file} -> parse_mlsd_mlst_tokens(Rest, Updated_file); {error, Error} -> {error, Error} end; _ -> {error, syntax_error} end end. -file("src/gftp/list.gleam", 219). ?DOC( " Parse any line from a `MLSD` or `MLST` command output,\n" " returning a `File` data structure with the extracted metadata on success, or a `ParseError` on failure.\n" ). -spec parse_mlsd_mlst_line(binary()) -> {ok, gftp@list@file:file()} | {error, parse_error()}. parse_mlsd_mlst_line(Line) -> case gleam@string:trim(Line) of <<""/utf8>> -> {error, syntax_error}; Trimmed -> _pipe = Trimmed, _pipe@1 = gleam@string:split(_pipe, <<";"/utf8>>), parse_mlsd_mlst_tokens(_pipe@1, gftp@list@file:empty()) end. -file("src/gftp/list.gleam", 98). ?DOC( " Parse a line from `MLSD` command output into a `File`.\n" "\n" " ```gleam\n" " let line = \"type=file;size=1024;modify=20200105134600; readme.txt\"\n" " let assert Ok(f) = list.parse_mlsd(line)\n" " ```\n" ). -spec parse_mlsd(binary()) -> {ok, gftp@list@file:file()} | {error, parse_error()}. parse_mlsd(Line) -> parse_mlsd_mlst_line(Line). -file("src/gftp/list.gleam", 108). ?DOC( " Parse a line from `MLST` command output into a `File`.\n" "\n" " ```gleam\n" " let line = \"type=file;size=2048;modify=20210315120000; document.pdf\"\n" " let assert Ok(f) = list.parse_mlst(line)\n" " ```\n" ). -spec parse_mlst(binary()) -> {ok, gftp@list@file:file()} | {error, parse_error()}. parse_mlst(Line) -> parse_mlsd_mlst_line(Line). -file("src/gftp/list.gleam", 309). ?DOC( " If `parsed` is more than ~6 months in the future relative to `now`,\n" " re-parse using the previous year.\n" ). -spec adjust_year_if_future( gleam@time@timestamp:timestamp(), gleam@time@timestamp:timestamp(), integer(), binary() ) -> {ok, gleam@time@timestamp:timestamp()} | {error, parse_error()}. adjust_year_if_future(Parsed, Now, Current_year, Normalized) -> Six_months_in_seconds = (180 * 24) * 3600, Diff = gleam@time@timestamp:difference(Parsed, Now), case gleam@time@duration:compare( Diff, gleam@time@duration:seconds(Six_months_in_seconds) ) of gt -> _pipe = (<<<<(erlang:integer_to_binary(Current_year - 1))/binary, " "/utf8>>/binary, Normalized/binary>>), parse_datetime_with_format(_pipe, <<"YYYY MMM D HH:mm"/utf8>>); _ -> {ok, Parsed} end. -file("src/gftp/list.gleam", 275). ?DOC( " Parse the last modification time from a POSIX LIST output line, which can be in one of two formats: `MMM D YYYY` (e.g. `Nov 5 2020`)\n" " or `MMM D HH:mm` (e.g. `Nov 5 13:46`). The parser will first try to parse the date using the year format, and if that fails, it will try to parse it using the time format.\n" ). -spec parse_list_lstime(binary()) -> {ok, gleam@time@timestamp:timestamp()} | {error, parse_error()}. parse_list_lstime(Token) -> Normalized = begin _pipe = Token, _pipe@1 = gleam@string:trim(_pipe), normalize_whitespace(_pipe@1) end, Year_result = begin _pipe@2 = (<>), parse_datetime_with_format(_pipe@2, <<"MMM D YYYY HH:mm"/utf8>>) end, Now = gleam@time@timestamp:system_time(), Current_year = begin _pipe@3 = Now, _pipe@4 = gleam@time@timestamp:to_calendar( _pipe@3, gleam@time@duration:seconds(0) ), (fun(Res) -> erlang:element(2, (erlang:element(1, Res))) end)(_pipe@4) end, Time_result = begin _pipe@5 = (<<<<(erlang:integer_to_binary(Current_year))/binary, " "/utf8>>/binary, Normalized/binary>>), _pipe@6 = parse_datetime_with_format( _pipe@5, <<"YYYY MMM D HH:mm"/utf8>> ), gleam@result:'try'( _pipe@6, fun(Parsed) -> adjust_year_if_future(Parsed, Now, Current_year, Normalized) end ) end, _pipe@7 = Year_result, _pipe@8 = gleam@result:'or'(_pipe@7, Time_result), gleam@result:map_error(_pipe@8, fun(_) -> invalid_date end). -file("src/gftp/list.gleam", 383). ?DOC( " Parse a POSIX LIST output line and if it is valid, return a [`File`] instance, otherwise return a [`ParseError`].\n" "\n" " POSIX syntax has the following syntax:\n" "\n" " ```text\n" " {FILE_TYPE}{PERMISSIONS} {LINK_COUNT} {USER} {GROUP} {FILE_SIZE} {MODIFIED_TIME} {FILENAME}\n" " -rw-r--r-- 1 user group 1234 Nov 5 13:46 example.txt\n" " ```\n" ). -spec parse_list_posix(binary(), gleam@regexp:regexp()) -> {ok, gftp@list@file:file()} | {error, parse_error()}. parse_list_posix(Line, Re) -> case gftp@internal@utils:re_matches(Re, Line) of {ok, [{some, Ft_str}, {some, Perm_str}, _, {some, User}, {some, Group}, {some, Size_str}, {some, Modified_str}, {some, Name_str}]} -> gleam@result:'try'( parse_list_file_type(Ft_str), fun(Ft) -> gleam@result:'try'( begin _pipe = Size_str, _pipe@1 = gleam@string:trim(_pipe), _pipe@2 = gleam_stdlib:parse_int(_pipe@1), gleam@result:replace_error(_pipe@2, bad_size) end, fun(Size) -> gleam@result:'try'( begin _pipe@3 = Modified_str, _pipe@4 = parse_list_lstime(_pipe@3), gleam@result:replace_error( _pipe@4, invalid_date ) end, fun(Modified) -> gleam@result:'try'( parse_list_name_and_link(Ft, Name_str), fun(_use0) -> {Name, Symlink_path} = _use0, _pipe@5 = gftp@list@file:empty(), _pipe@6 = gftp@list@file:with_name( _pipe@5, Name ), _pipe@7 = gftp@list@file:with_file_type( _pipe@6, resolve_posix_file_type( Ft, Symlink_path ) ), _pipe@8 = gftp@list@file:with_size( _pipe@7, Size ), _pipe@9 = gftp@list@file:with_modified( _pipe@8, Modified ), _pipe@10 = gftp@list@file:with_permissions( _pipe@9, parse_list_permissions(Perm_str) ), _pipe@11 = maybe_set( _pipe@10, try_parse_int(User), fun gftp@list@file:with_uid/2 ), _pipe@12 = maybe_set( _pipe@11, try_parse_int(Group), fun gftp@list@file:with_gid/2 ), {ok, _pipe@12} end ) end ) end ) end ); _ -> {error, syntax_error} end. -file("src/gftp/list.gleam", 472). ?DOC( " Parse a date and time in the format used by DOS LIST output, \n" " which is typically `MM-DD-YY hh:mmA` (e.g. `10-19-20 03:19PM`), or with a space before the AM/PM (e.g. `10-19-20 03:19 PM`).\n" ). -spec parse_list_dos_time(binary()) -> {ok, gleam@time@timestamp:timestamp()} | {error, parse_error()}. parse_list_dos_time(Token) -> _pipe = Token, _pipe@1 = parse_datetime_with_format(_pipe, <<"MM-DD-YY hh:mmA"/utf8>>), gleam@result:'or'( _pipe@1, parse_datetime_with_format(Token, <<"MM-DD-YY hh:mm A"/utf8>>) ). -file("src/gftp/list.gleam", 435). ?DOC( " Try to parse a \"LIST\" output command line in DOS format.\n" " Returns [`ParseError`] if syntax is not DOS compliant.\n" " DOS syntax has the following syntax:\n" "\n" " ```text\n" " {DATE} {TIME} { | SIZE} {FILENAME}\n" " 10-19-20 03:19PM pub\n" " 04-08-14 03:09PM 403 readme.txt\n" " ```\n" ). -spec parse_list_dos(binary(), gleam@regexp:regexp()) -> {ok, gftp@list@file:file()} | {error, parse_error()}. parse_list_dos(Line, Re) -> case gftp@internal@utils:re_matches(Re, Line) of {ok, [{some, Modified}, File_type, Size, {some, Name}]} -> gleam@result:'try'( begin _pipe = Modified, _pipe@1 = parse_list_dos_time(_pipe), gleam@result:replace_error(_pipe@1, invalid_date) end, fun(Modified@1) -> File_type@1 = case gleam@option:is_some(File_type) of true -> directory; false -> file end, gleam@result:'try'(case Size of none -> {ok, 0}; {some, Size_str} -> _pipe@2 = Size_str, _pipe@3 = gleam@string:trim(_pipe@2), _pipe@4 = gleam@string:replace( _pipe@3, <<","/utf8>>, <<""/utf8>> ), _pipe@5 = gleam_stdlib:parse_int(_pipe@4), gleam@result:replace_error(_pipe@5, bad_size) end, fun(Size@1) -> _pipe@6 = gftp@list@file:empty(), _pipe@7 = gftp@list@file:with_name(_pipe@6, Name), _pipe@8 = gftp@list@file:with_file_type( _pipe@7, File_type@1 ), _pipe@9 = gftp@list@file:with_size(_pipe@8, Size@1), _pipe@10 = gftp@list@file:with_modified( _pipe@9, Modified@1 ), {ok, _pipe@10} end) end ); _ -> {error, syntax_error} end. -file("src/gftp/list.gleam", 123). ?DOC( " Parse a `LIST` output line (POSIX or DOS format) into a `File`.\n" "\n" " Tries POSIX format first, then falls back to DOS format.\n" "\n" " ```gleam\n" " // POSIX format\n" " let assert Ok(f) = list.parse_list(\"-rw-r--r-- 1 user group 1234 Nov 5 13:46 example.txt\")\n" "\n" " // DOS format\n" " let assert Ok(f) = list.parse_list(\"10-19-20 03:19PM 403 readme.txt\")\n" " ```\n" ). -spec parse_list(binary()) -> {ok, gftp@list@file:file()} | {error, parse_error()}. parse_list(Line) -> Posix_re@1 = case gleam@regexp:from_string( <<"^([\\-ld])([\\-rwxsStT]{9})\\s+(\\d+)\\s+([^ ]+)\\s+([^ ]+)\\s+(\\d+)\\s+([^ ]+\\s+\\d{1,2}\\s+(?:\\d{1,2}:\\d{1,2}|\\d{4}))\\s+(.+)$"/utf8>> ) of {ok, Posix_re} -> Posix_re; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"gftp/list"/utf8>>, function => <<"parse_list"/utf8>>, line => 124, value => _assert_fail, start => 3932, 'end' => 3994, pattern_start => 3943, pattern_end => 3955}) end, Dos_re@1 = case gleam@regexp:from_string( <<"^(\\d{2}\\-\\d{2}\\-\\d{2}\\s+\\d{2}:\\d{2}\\s*[AP]M)\\s+()?([\\d,]*)\\s+(.+)$"/utf8>> ) of {ok, Dos_re} -> Dos_re; _assert_fail@1 -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"gftp/list"/utf8>>, function => <<"parse_list"/utf8>>, line => 125, value => _assert_fail@1, start => 3997, 'end' => 4055, pattern_start => 4008, pattern_end => 4018}) end, _pipe = Line, _pipe@1 = parse_list_posix(_pipe, Posix_re@1), gleam@result:'or'(_pipe@1, parse_list_dos(Line, Dos_re@1)).