Split event loop into its own file
This commit is contained in:
		
							parent
							
								
									6fb6e63280
								
							
						
					
					
						commit
						94b1d8f67e
					
				@ -1,7 +0,0 @@
 | 
			
		||||
 | 
			
		||||
///
 | 
			
		||||
/// Returns `true` if `value` did not return `Error`, otherwise `false`.
 | 
			
		||||
///
 | 
			
		||||
pub fn isOk(comptime Error: type, value: Error!void) bool {
 | 
			
		||||
    return if (value) |_| true else |_| false;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										470
									
								
								src/io.zig
									
									
									
									
									
								
							
							
						
						
									
										470
									
								
								src/io.zig
									
									
									
									
									
								
							@ -1,476 +1,6 @@
 | 
			
		||||
const stack = @import("./stack.zig");
 | 
			
		||||
const std = @import("std");
 | 
			
		||||
 | 
			
		||||
///
 | 
			
		||||
///
 | 
			
		||||
///
 | 
			
		||||
pub const Path = struct {
 | 
			
		||||
    length: u16,
 | 
			
		||||
    buffer: [max]u8,
 | 
			
		||||
 | 
			
		||||
    ///
 | 
			
		||||
    ///
 | 
			
		||||
    ///
 | 
			
		||||
    pub const empty = std.mem.zeroes(Path);
 | 
			
		||||
 | 
			
		||||
    ///
 | 
			
		||||
    ///
 | 
			
		||||
    ///
 | 
			
		||||
    pub fn equalsText(path: Path, text: []const u8) bool {
 | 
			
		||||
        return std.mem.eql(u8, path.buffer[0 .. path.length], text);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ///
 | 
			
		||||
    /// The maximum possible byte-length of a [Path].
 | 
			
		||||
    ///
 | 
			
		||||
    /// Note that paths are encoded using UTF-8, meaning that a character may be bigger than one
 | 
			
		||||
    /// byte. Because of this, it is not safe to asume that a path may hold `2048` individual
 | 
			
		||||
    /// characters.
 | 
			
		||||
    ///
 | 
			
		||||
    pub const max = 2048;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
///
 | 
			
		||||
/// Universal resource identifier (URI) that operates atop the operating system to provide a
 | 
			
		||||
/// platform-agnostic interface for local and networked I/O access.
 | 
			
		||||
///
 | 
			
		||||
/// For more information, see [https://en.wikipedia.org/wiki/URI].
 | 
			
		||||
///
 | 
			
		||||
pub const Uri = struct {
 | 
			
		||||
    buffer: [max]u8,
 | 
			
		||||
    scheme_len: u16,
 | 
			
		||||
    user_info_range: Range,
 | 
			
		||||
    host_range: Range,
 | 
			
		||||
    port_number: u16,
 | 
			
		||||
    path_range: Range,
 | 
			
		||||
 | 
			
		||||
    ///
 | 
			
		||||
    /// Errors that may occur during parsing of a URI from URI-compatible source encoding.
 | 
			
		||||
    ///
 | 
			
		||||
    /// [ParseError.TooLong] occurs when the provided source data is bigger than the max allowed
 | 
			
		||||
    /// data representation in [max].
 | 
			
		||||
    ///
 | 
			
		||||
    /// [ParseError.UnexpectedToken] occurs when the internal tokenization process encounters a
 | 
			
		||||
    /// URI component token in the wrong order.
 | 
			
		||||
    ///
 | 
			
		||||
    /// [ParseError.InvalidEncoding] occurs when the source encoding being parsed is not properly
 | 
			
		||||
    /// encoded in its own format (malformed UTF-8, for example).
 | 
			
		||||
    ///
 | 
			
		||||
    pub const ParseError = error {
 | 
			
		||||
        TooLong,
 | 
			
		||||
        UnexpectedToken,
 | 
			
		||||
        InvalidEncoding,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const Range = struct {
 | 
			
		||||
        off: u16,
 | 
			
		||||
        len: u16,
 | 
			
		||||
 | 
			
		||||
        const none = std.mem.zeroes(Range);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    ///
 | 
			
		||||
    /// Represents an individual component of a URI sequence.
 | 
			
		||||
    ///
 | 
			
		||||
    pub const Token = union(enum) {
 | 
			
		||||
        scheme: []const u8,
 | 
			
		||||
        user_info: []const u8,
 | 
			
		||||
        host: []const u8,
 | 
			
		||||
        port: []const u8,
 | 
			
		||||
        path: []const u8,
 | 
			
		||||
        query: []const u8,
 | 
			
		||||
        fragment: []const u8,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    ///
 | 
			
		||||
    /// Tokenizes the data in [Tokenizer.utf8_sequence] into URI tokens.
 | 
			
		||||
    ///
 | 
			
		||||
    /// See [Component] for more information on the supported URI tokens.
 | 
			
		||||
    ///
 | 
			
		||||
    pub const Tokenizer = struct {
 | 
			
		||||
        cursor: usize = 0,
 | 
			
		||||
        utf8_sequence: []const u8,
 | 
			
		||||
 | 
			
		||||
        ///
 | 
			
		||||
        /// Extracts the next [Token] in sequence from `tokenizer` and returns it or `null` if
 | 
			
		||||
        /// there are no more tokens to be extracted.
 | 
			
		||||
        ///
 | 
			
		||||
        pub fn next(tokenizer: *Tokenizer) ?Token {
 | 
			
		||||
            while (tokenizer.cursor < tokenizer.utf8_sequence.len)
 | 
			
		||||
                switch (tokenizer.utf8_sequence[tokenizer.cursor]) {
 | 
			
		||||
 | 
			
		||||
                'A' ... 'Z', 'a' ... 'z' => {
 | 
			
		||||
                    const begin = tokenizer.cursor;
 | 
			
		||||
 | 
			
		||||
                    tokenizer.cursor += 1;
 | 
			
		||||
 | 
			
		||||
                    var is_scheme = (begin == 0);
 | 
			
		||||
 | 
			
		||||
                    while (tokenizer.cursor < tokenizer.utf8_sequence.len)
 | 
			
		||||
                        switch (tokenizer.utf8_sequence[tokenizer.cursor]) {
 | 
			
		||||
                        '+', '.', '-', '0' ... '9', 'A' ... 'Z', 'a' ... 'z' =>
 | 
			
		||||
                           tokenizer.cursor += 1,
 | 
			
		||||
 | 
			
		||||
                        ':' => {
 | 
			
		||||
                            if (is_scheme) {
 | 
			
		||||
                                defer tokenizer.cursor += 1;
 | 
			
		||||
 | 
			
		||||
                                return Token{.scheme =
 | 
			
		||||
                                    tokenizer.utf8_sequence[begin .. (tokenizer.cursor - begin)]};
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                            tokenizer.cursor += 1;
 | 
			
		||||
                        },
 | 
			
		||||
 | 
			
		||||
                        '#', '?' => break,
 | 
			
		||||
 | 
			
		||||
                        else => {
 | 
			
		||||
                            tokenizer.cursor += 1;
 | 
			
		||||
                            is_scheme = false;
 | 
			
		||||
                        },
 | 
			
		||||
                    };
 | 
			
		||||
 | 
			
		||||
                    return Token{.path =
 | 
			
		||||
                        tokenizer.utf8_sequence[begin .. (tokenizer.cursor - begin)]};
 | 
			
		||||
                },
 | 
			
		||||
 | 
			
		||||
                '@' => {
 | 
			
		||||
                    tokenizer.cursor += 1;
 | 
			
		||||
 | 
			
		||||
                    const begin = tokenizer.cursor;
 | 
			
		||||
 | 
			
		||||
                    while (tokenizer.cursor < tokenizer.utf8_sequence.len)
 | 
			
		||||
                        switch (tokenizer.utf8_sequence[tokenizer.cursor]) {
 | 
			
		||||
 | 
			
		||||
                        '/', ':' => break,
 | 
			
		||||
                        else => tokenizer.cursor += 1,
 | 
			
		||||
                    };
 | 
			
		||||
 | 
			
		||||
                    return Token{.host =
 | 
			
		||||
                        tokenizer.utf8_sequence[begin .. (tokenizer.cursor - begin)]};
 | 
			
		||||
                },
 | 
			
		||||
 | 
			
		||||
                ':' => {
 | 
			
		||||
                    tokenizer.cursor += 1;
 | 
			
		||||
 | 
			
		||||
                    const begin = tokenizer.cursor;
 | 
			
		||||
 | 
			
		||||
                    while (tokenizer.cursor < tokenizer.utf8_sequence.len)
 | 
			
		||||
                        switch (tokenizer.utf8_sequence[tokenizer.cursor]) {
 | 
			
		||||
 | 
			
		||||
                        '/' => break,
 | 
			
		||||
                        else => tokenizer.cursor += 1,
 | 
			
		||||
                    };
 | 
			
		||||
 | 
			
		||||
                    return Token{
 | 
			
		||||
                        .port = tokenizer.utf8_sequence[begin .. (tokenizer.cursor - begin)]};
 | 
			
		||||
                },
 | 
			
		||||
 | 
			
		||||
                '/' => {
 | 
			
		||||
                    tokenizer.cursor += 1;
 | 
			
		||||
 | 
			
		||||
                    if (tokenizer.utf8_sequence[tokenizer.cursor] == '/') {
 | 
			
		||||
                        tokenizer.cursor += 1;
 | 
			
		||||
 | 
			
		||||
                        const begin = tokenizer.cursor;
 | 
			
		||||
 | 
			
		||||
                        while (tokenizer.cursor < tokenizer.utf8_sequence.len)
 | 
			
		||||
                            switch (tokenizer.utf8_sequence[tokenizer.cursor]) {
 | 
			
		||||
 | 
			
		||||
                            '@' => return Token{.user_info =
 | 
			
		||||
                                tokenizer.utf8_sequence[begin .. (tokenizer.cursor - begin)]},
 | 
			
		||||
 | 
			
		||||
                            ':', '/' => break,
 | 
			
		||||
                            else => tokenizer.cursor += 1,
 | 
			
		||||
                        };
 | 
			
		||||
 | 
			
		||||
                        return Token{
 | 
			
		||||
                            .host = tokenizer.utf8_sequence[begin .. (tokenizer.cursor - begin)]};
 | 
			
		||||
                    } else {
 | 
			
		||||
                        const begin = (tokenizer.cursor - 1);
 | 
			
		||||
 | 
			
		||||
                        tokenizer.cursor += 1;
 | 
			
		||||
 | 
			
		||||
                        while (tokenizer.cursor < tokenizer.utf8_sequence.len)
 | 
			
		||||
                            switch (tokenizer.utf8_sequence[tokenizer.cursor]) {
 | 
			
		||||
 | 
			
		||||
                            '?', '#' => break,
 | 
			
		||||
                            else => tokenizer.cursor += 1,
 | 
			
		||||
                        };
 | 
			
		||||
 | 
			
		||||
                        return Token{
 | 
			
		||||
                            .path = tokenizer.utf8_sequence[begin .. (tokenizer.cursor - begin)]};
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
 | 
			
		||||
                '?' => {
 | 
			
		||||
                    tokenizer.cursor += 1;
 | 
			
		||||
 | 
			
		||||
                    const begin = tokenizer.cursor;
 | 
			
		||||
 | 
			
		||||
                    while (tokenizer.cursor < tokenizer.utf8_sequence.len)
 | 
			
		||||
                        switch (tokenizer.utf8_sequence[tokenizer.cursor]) {
 | 
			
		||||
 | 
			
		||||
                        '#' => {
 | 
			
		||||
                            tokenizer.cursor -= 1;
 | 
			
		||||
 | 
			
		||||
                            break;
 | 
			
		||||
                        },
 | 
			
		||||
 | 
			
		||||
                        else => tokenizer.cursor += 1,
 | 
			
		||||
                    };
 | 
			
		||||
 | 
			
		||||
                    return Token{
 | 
			
		||||
                        .query = tokenizer.utf8_sequence[begin .. (tokenizer.cursor - begin)]};
 | 
			
		||||
                },
 | 
			
		||||
 | 
			
		||||
                '#' => {
 | 
			
		||||
                    tokenizer.cursor += 1;
 | 
			
		||||
 | 
			
		||||
                    const begin = tokenizer.cursor;
 | 
			
		||||
 | 
			
		||||
                    while (tokenizer.cursor < tokenizer.utf8_sequence.len) tokenizer.cursor += 1;
 | 
			
		||||
 | 
			
		||||
                    return Token{
 | 
			
		||||
                        .fragment = tokenizer.utf8_sequence[begin .. (tokenizer.cursor - begin)]};
 | 
			
		||||
                },
 | 
			
		||||
 | 
			
		||||
                else => {
 | 
			
		||||
                    const begin = tokenizer.cursor;
 | 
			
		||||
 | 
			
		||||
                    tokenizer.cursor += 1;
 | 
			
		||||
 | 
			
		||||
                    while (tokenizer.cursor < tokenizer.utf8_sequence.len)
 | 
			
		||||
                        switch (tokenizer.utf8_sequence[tokenizer.cursor]) {
 | 
			
		||||
 | 
			
		||||
                        '#', '?' => break,
 | 
			
		||||
                        else => tokenizer.cursor += 1,
 | 
			
		||||
                    };
 | 
			
		||||
 | 
			
		||||
                    return Token{
 | 
			
		||||
                        .path = tokenizer.utf8_sequence[begin .. (tokenizer.cursor - begin)]};
 | 
			
		||||
                },
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ///
 | 
			
		||||
        /// A more constrained variant of [next] that accepts a `expected_token` argument to
 | 
			
		||||
        /// validate the component type of a [Token] before it is returned.
 | 
			
		||||
        ///
 | 
			
		||||
        /// If the [Component] of the extracted [Token] is not identical to `expected_token`,
 | 
			
		||||
        /// it will be discarded and `null` is returned instead.
 | 
			
		||||
        ///
 | 
			
		||||
        pub fn nextExpect(tokenizer: *Tokenizer, expected_token: std.meta.Tag(Token)) ?Token {
 | 
			
		||||
            if (tokenizer.next()) |token| {
 | 
			
		||||
                if (token == expected_token) return token;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    ///
 | 
			
		||||
    ///
 | 
			
		||||
    ///
 | 
			
		||||
    pub const empty = std.mem.zeroes(Uri);
 | 
			
		||||
 | 
			
		||||
    ///
 | 
			
		||||
    /// The maximum possible byte-length of a [URI].
 | 
			
		||||
    ///
 | 
			
		||||
    /// Note that a URI character may be encoded using multiple bytes, meaning that `2048` is not
 | 
			
		||||
    /// identical in meaning to `2048` URI characters.
 | 
			
		||||
    ///
 | 
			
		||||
    pub const max = 2048;
 | 
			
		||||
 | 
			
		||||
    ///
 | 
			
		||||
    /// Returns `true` if `uri_scheme` matches the scheme contained in `uri`, otherwise `false`.
 | 
			
		||||
    ///
 | 
			
		||||
    pub fn isScheme(uri: Uri, uri_scheme: []const u8) bool {
 | 
			
		||||
        return std.mem.eql(u8, uri.buffer[0 .. uri.scheme_len], uri_scheme);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ///
 | 
			
		||||
    /// Attempts to parse and return a [URI] value out of `utf8_sequence`, otherwise returning
 | 
			
		||||
    /// [ParseError] if `utf8_sequence` is invalid in any way.
 | 
			
		||||
    ///
 | 
			
		||||
    /// [ParseError.InvalidEncoding] occurs if the data encoded in `utf8_sequence` cannot be
 | 
			
		||||
    /// validated as UTF-8 or it contains an invalid ASCII decimal number encoding for its URL port.
 | 
			
		||||
    ///
 | 
			
		||||
    /// See [ParseError] for more details on the other errors that may be returned.
 | 
			
		||||
    ///
 | 
			
		||||
    pub fn parse(utf8_sequence: []const u8) ParseError!Uri {
 | 
			
		||||
        if (!std.unicode.utf8ValidateSlice(utf8_sequence)) return error.InvalidEncoding;
 | 
			
		||||
 | 
			
		||||
        var uri = Uri.empty;
 | 
			
		||||
 | 
			
		||||
        if (utf8_sequence.len != 0) {
 | 
			
		||||
            if (utf8_sequence.len > max) return error.TooLong;
 | 
			
		||||
 | 
			
		||||
            var tokenizer = Tokenizer{.utf8_sequence = utf8_sequence};
 | 
			
		||||
            const scheme_token = tokenizer.nextExpect(.scheme) orelse return error.UnexpectedToken;
 | 
			
		||||
            var uri_buffer = stack.Unmanaged(u8){.buffer = &uri.buffer};
 | 
			
		||||
            const uri_writer = uri_buffer.asWriter();
 | 
			
		||||
            const assert = std.debug.assert;
 | 
			
		||||
 | 
			
		||||
            // These write operations will never fail because the uri_buffer will be known to be big
 | 
			
		||||
            // enough by this point.
 | 
			
		||||
            assert(uri_writer.write(scheme_token.scheme) == scheme_token.scheme.len);
 | 
			
		||||
            assert(uri_writer.writeByte(':'));
 | 
			
		||||
 | 
			
		||||
            // Downcast is safe because utf8_sequence can't be greater than u16 max.
 | 
			
		||||
            uri.scheme_len = @intCast(u16, scheme_token.scheme.len);
 | 
			
		||||
 | 
			
		||||
            var last_token = scheme_token;
 | 
			
		||||
 | 
			
		||||
            while (tokenizer.next()) |scheme_specific_token| {
 | 
			
		||||
                switch (scheme_specific_token) {
 | 
			
		||||
                    .scheme => return error.UnexpectedToken,
 | 
			
		||||
 | 
			
		||||
                    .user_info => |user_info| {
 | 
			
		||||
                        if (last_token != .scheme) return error.UnexpectedToken;
 | 
			
		||||
 | 
			
		||||
                        const delimiter = "//";
 | 
			
		||||
 | 
			
		||||
                        assert(uri_writer.write(delimiter) == delimiter.len);
 | 
			
		||||
 | 
			
		||||
                        uri.user_info_range = .{
 | 
			
		||||
                            .off = @intCast(u16, uri_buffer.filled),
 | 
			
		||||
                            .len = @intCast(u16, user_info.len),
 | 
			
		||||
                        };
 | 
			
		||||
 | 
			
		||||
                        assert(uri_writer.write(user_info) == user_info.len);
 | 
			
		||||
                        assert(uri_writer.writeByte('@'));
 | 
			
		||||
                    },
 | 
			
		||||
 | 
			
		||||
                    .host => |host| {
 | 
			
		||||
                        switch (last_token) {
 | 
			
		||||
                            .scheme => {
 | 
			
		||||
                                const delimiter = "//";
 | 
			
		||||
 | 
			
		||||
                                assert(uri_writer.write(delimiter) == delimiter.len);
 | 
			
		||||
                            },
 | 
			
		||||
 | 
			
		||||
                            .user_info => {},
 | 
			
		||||
                            else => return error.UnexpectedToken,
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        assert(uri_writer.write(host) == host.len);
 | 
			
		||||
                    },
 | 
			
		||||
 | 
			
		||||
                    .port => |port| {
 | 
			
		||||
                        if (last_token != .host) return error.UnexpectedToken;
 | 
			
		||||
 | 
			
		||||
                        const port_radix = 10;
 | 
			
		||||
 | 
			
		||||
                        uri.port_number = std.fmt.parseInt(u16, port, port_radix) catch
 | 
			
		||||
                            return error.InvalidEncoding;
 | 
			
		||||
 | 
			
		||||
                        assert(uri_writer.writeByte(':'));
 | 
			
		||||
                        assert(uri_writer.write(port) == port.len);
 | 
			
		||||
                    },
 | 
			
		||||
 | 
			
		||||
                    .path => |path| {
 | 
			
		||||
                        if ((last_token != .scheme) and (last_token != .host) and
 | 
			
		||||
                            (last_token != .port)) return error.UnexpectedToken;
 | 
			
		||||
 | 
			
		||||
                        uri.path_range = .{
 | 
			
		||||
                            .off = @intCast(u16, uri_buffer.filled),
 | 
			
		||||
                            .len = @intCast(u16, path.len),
 | 
			
		||||
                        };
 | 
			
		||||
 | 
			
		||||
                        assert(uri_writer.write(path) == path.len);
 | 
			
		||||
                    },
 | 
			
		||||
 | 
			
		||||
                    .query => |query| {
 | 
			
		||||
                        if ((last_token != .scheme) and (last_token != .host) and
 | 
			
		||||
                            (last_token != .port) and (last_token != .path))
 | 
			
		||||
                            return error.UnexpectedToken;
 | 
			
		||||
 | 
			
		||||
                        assert(uri_writer.writeByte('?'));
 | 
			
		||||
 | 
			
		||||
                        uri.path_range = .{
 | 
			
		||||
                            .off = @intCast(u16, uri_buffer.filled),
 | 
			
		||||
                            .len = @intCast(u16, query.len),
 | 
			
		||||
                        };
 | 
			
		||||
 | 
			
		||||
                        assert(uri_writer.write(query) == query.len);
 | 
			
		||||
                    },
 | 
			
		||||
 | 
			
		||||
                    .fragment => |fragment| {
 | 
			
		||||
                        if ((last_token != .scheme) or (last_token != .host) or
 | 
			
		||||
                            (last_token != .port) or (last_token != .path) or
 | 
			
		||||
                            (last_token != .query)) return error.UnexpectedToken;
 | 
			
		||||
 | 
			
		||||
                        assert(uri_writer.writeByte('#'));
 | 
			
		||||
 | 
			
		||||
                        uri.path_range = .{
 | 
			
		||||
                            .off = @intCast(u16, uri_buffer.filled),
 | 
			
		||||
                            .len = @intCast(u16, fragment.len),
 | 
			
		||||
                        };
 | 
			
		||||
 | 
			
		||||
                        assert(uri_writer.write(fragment) == fragment.len);
 | 
			
		||||
                    },
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                last_token = scheme_specific_token;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return uri;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ///
 | 
			
		||||
    /// Creates and returns a [Path] value from the path component of `uri`.
 | 
			
		||||
    ///
 | 
			
		||||
    pub fn toPath(uri: Uri) Path {
 | 
			
		||||
        var path = Path{
 | 
			
		||||
            .length = uri.path_range.len,
 | 
			
		||||
            .buffer = std.mem.zeroes([Path.max]u8),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        std.mem.copy(u8, path.buffer[0 ..], uri.buffer[uri.path_range.off .. uri.path_range.len]);
 | 
			
		||||
 | 
			
		||||
        return path;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ///
 | 
			
		||||
    /// Writes the path component of `uri` to `path_writer`, returning `true` if all bytes used to
 | 
			
		||||
    /// encode the path were successfully written, otherwise `false` if it was partially completed
 | 
			
		||||
    /// or not at all.
 | 
			
		||||
    ///
 | 
			
		||||
    pub fn writePath(uri: Uri, path_writer: Writer) bool {
 | 
			
		||||
        return (path_writer.write(uri.buffer[uri.path_range.off ..
 | 
			
		||||
            uri.path_range.len]) == uri.path_range.len);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
test "uri" {
 | 
			
		||||
    const testing = @import("std").testing;
 | 
			
		||||
    const empty_uri = Uri.empty;
 | 
			
		||||
 | 
			
		||||
    try testing.expect(empty_uri.isScheme(""));
 | 
			
		||||
    try testing.expect(empty_uri.toPath().equalsText(""));
 | 
			
		||||
 | 
			
		||||
    const scheme_only_uri = try Uri.parse("uri:");
 | 
			
		||||
 | 
			
		||||
    try testing.expect(scheme_only_uri.isScheme("uri"));
 | 
			
		||||
    try testing.expect(scheme_only_uri.toPath().equalsText(""));
 | 
			
		||||
 | 
			
		||||
    const absolute_file_path = "/path/to/file";
 | 
			
		||||
    const absolute_file_uri = try Uri.parse("file:" ++ absolute_file_path);
 | 
			
		||||
 | 
			
		||||
    try testing.expect(absolute_file_uri.isScheme("file"));
 | 
			
		||||
    try testing.expect(absolute_file_uri.toPath().equalsText(absolute_file_path));
 | 
			
		||||
 | 
			
		||||
    const relative_file_path = "path/to/file";
 | 
			
		||||
    const relative_file_uri = try Uri.parse("file:" ++ relative_file_path);
 | 
			
		||||
 | 
			
		||||
    try testing.expect(relative_file_uri.isScheme("file"));
 | 
			
		||||
    try testing.expect(relative_file_uri.toPath().equalsText(relative_file_path));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
///
 | 
			
		||||
/// Opaque interface to a "writable" resource, such as a block device, memory buffer, or network
 | 
			
		||||
/// socket.
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										59
									
								
								src/main.zig
									
									
									
									
									
								
							
							
						
						
									
										59
									
								
								src/main.zig
									
									
									
									
									
								
							@ -2,27 +2,10 @@ const ext = @cImport({
 | 
			
		||||
    @cInclude("SDL2/SDL.h");
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const errors = @import("./errors.zig");
 | 
			
		||||
const io = @import("./io.zig");
 | 
			
		||||
const stack = @import("./stack.zig");
 | 
			
		||||
const std = @import("std");
 | 
			
		||||
 | 
			
		||||
const Request = struct {
 | 
			
		||||
    next: ?*Request = null,
 | 
			
		||||
    frame: anyframe,
 | 
			
		||||
 | 
			
		||||
    message: union(enum) {
 | 
			
		||||
        close: struct {
 | 
			
		||||
            file: *ext.SDL_RWops,
 | 
			
		||||
            is_closed: *bool,
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        open_readable: struct {
 | 
			
		||||
            uri: *const io.Uri,
 | 
			
		||||
            file: ?*ext.SDL_RWops,
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
const sys = @import("./sys.zig");
 | 
			
		||||
 | 
			
		||||
///
 | 
			
		||||
/// Entry point.
 | 
			
		||||
@ -73,7 +56,7 @@ pub fn main() anyerror!void {
 | 
			
		||||
 | 
			
		||||
    defer ext.SDL_DestroyRenderer(renderer);
 | 
			
		||||
 | 
			
		||||
    var request_chain = @as(?*Request, null);
 | 
			
		||||
    var event_loop = sys.EventLoop{};
 | 
			
		||||
    var is_running = true;
 | 
			
		||||
 | 
			
		||||
    while (is_running) {
 | 
			
		||||
@ -97,43 +80,7 @@ pub fn main() anyerror!void {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ext.SDL_RenderPresent(renderer);
 | 
			
		||||
 | 
			
		||||
        while (request_chain) |request_head| {
 | 
			
		||||
            const request = request_head;
 | 
			
		||||
 | 
			
		||||
            request_chain = request_head.next;
 | 
			
		||||
 | 
			
		||||
            switch (request.message) {
 | 
			
		||||
                .close => |*close| close.is_closed.* = (ext.SDL_RWclose(close.file) == 0),
 | 
			
		||||
 | 
			
		||||
                .open_readable => |*open_readable| {
 | 
			
		||||
                    if (open_readable.uri.isScheme("data")) {
 | 
			
		||||
                        var path = stack.Fixed(u8, 4096).init();
 | 
			
		||||
 | 
			
		||||
                        // These can never fail as the sum of the potential bytes written will
 | 
			
		||||
                        // always be less than 4096.
 | 
			
		||||
                        path.pushAll("./") catch unreachable;
 | 
			
		||||
                        std.debug.assert(open_readable.uri.writePath(path.asWriter()));
 | 
			
		||||
 | 
			
		||||
                        open_readable.file = ext.SDL_RWFromFile(&path.buffer, "r");
 | 
			
		||||
                    } else if (open_readable.uri.isScheme("user")) {
 | 
			
		||||
                        var path = stack.Fixed(u8, 4096).init();
 | 
			
		||||
                        const isOk = errors.isOk;
 | 
			
		||||
 | 
			
		||||
                        // Cannot guarantee that the sum of potential bytes written will always be
 | 
			
		||||
                        // less than path max.
 | 
			
		||||
                        if (isOk(stack.FinitePushError, path.pushAll(pref_path)) and
 | 
			
		||||
                            open_readable.uri.writePath(path.asWriter())) {
 | 
			
		||||
 | 
			
		||||
                            open_readable.file = ext.SDL_RWFromFile(&path.buffer, "r");
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            resume request.frame;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        event_loop.tick();
 | 
			
		||||
        ext.SDL_Delay(1);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										58
									
								
								src/mem.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								src/mem.zig
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,58 @@
 | 
			
		||||
const std = @import("std");
 | 
			
		||||
 | 
			
		||||
///
 | 
			
		||||
/// State machine for lazily computing all components of [Spliterator.source] that match the pattern
 | 
			
		||||
/// in [Spliterator.delimiter].
 | 
			
		||||
///
 | 
			
		||||
pub fn Spliterator(comptime Element: type) type {
 | 
			
		||||
    return struct {
 | 
			
		||||
        source: []const Element,
 | 
			
		||||
        delimiter: []const Element,
 | 
			
		||||
 | 
			
		||||
        const Self = @This();
 | 
			
		||||
 | 
			
		||||
        ///
 | 
			
		||||
        /// Returns `true` if there is more data to be processed, otherwise `false`.
 | 
			
		||||
        ///
 | 
			
		||||
        /// Note that [Spliterator.next] implicitly calls this function to determine if it should
 | 
			
		||||
        /// return another slice or `null`.
 | 
			
		||||
        ///
 | 
			
		||||
        pub fn hasNext(self: Self) bool {
 | 
			
		||||
            return (self.source.len != 0);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ///
 | 
			
		||||
        /// Iterates on `self` and returns the next view of [Spliterator.source] that matches
 | 
			
		||||
        /// [Spliterator.delimiter], or `null` if there is no more data to be processed.
 | 
			
		||||
        ///
 | 
			
		||||
        pub fn next(self: *Self) ?[]const Element {
 | 
			
		||||
            if (self.delimiter.len == 0) {
 | 
			
		||||
                defer self.source = self.source[self.source.len .. 0];
 | 
			
		||||
 | 
			
		||||
                return self.source;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            while (self.hasNext()) {
 | 
			
		||||
                var cursor = @as(usize, 0);
 | 
			
		||||
                var window = self.source[cursor .. (self.source - cursor)];
 | 
			
		||||
 | 
			
		||||
                defer self.source = window;
 | 
			
		||||
 | 
			
		||||
                if (std.mem.eql(Element, window, self.delimiter))
 | 
			
		||||
                    return self.source[cursor .. self.delimiter.len];
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
///
 | 
			
		||||
/// Searches the slice of `Data` referenced by `data` for the first instance of `sought_datum`,
 | 
			
		||||
/// returning its index or `null` if it could not be found.
 | 
			
		||||
///
 | 
			
		||||
pub fn findFirst(comptime Data: type, data: []const Data, sought_datum: Data) ?usize {
 | 
			
		||||
    for (data) |datum, index| if (datum == sought_datum) return index;
 | 
			
		||||
 | 
			
		||||
    return null;
 | 
			
		||||
}
 | 
			
		||||
@ -57,16 +57,6 @@ pub fn Fixed(comptime Element: type, comptime capacity: usize) type {
 | 
			
		||||
            return self.filled;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ///
 | 
			
		||||
        /// Creates and returns a [Self] value.
 | 
			
		||||
        ///
 | 
			
		||||
        pub fn init() Self {
 | 
			
		||||
            return Self{
 | 
			
		||||
                .filled = 0,
 | 
			
		||||
                .buffer = undefined,
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ///
 | 
			
		||||
        /// Attempts to pop the tail-end of `self`, returning the element value or `null` if the
 | 
			
		||||
        /// stack is empty.
 | 
			
		||||
@ -108,7 +98,7 @@ pub fn Fixed(comptime Element: type, comptime capacity: usize) type {
 | 
			
		||||
 | 
			
		||||
pub fn Unmanaged(comptime Element: type) type {
 | 
			
		||||
    return struct {
 | 
			
		||||
        filled: usize = 0,
 | 
			
		||||
        filled: usize,
 | 
			
		||||
        buffer: []Element,
 | 
			
		||||
 | 
			
		||||
        const Self = @This();
 | 
			
		||||
@ -147,16 +137,6 @@ pub fn Unmanaged(comptime Element: type) type {
 | 
			
		||||
            return self.filled;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ///
 | 
			
		||||
        /// Creates and returns a [Self] value wrapping `buffer` as its writable memory buffer.
 | 
			
		||||
        ///
 | 
			
		||||
        pub fn init(buffer: []Element) Self {
 | 
			
		||||
            return Self{
 | 
			
		||||
                .filled = 0,
 | 
			
		||||
                .buffer = buffer,
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ///
 | 
			
		||||
        /// Attempts to pop the tail-end of `self`, returning the element value or `null` if the
 | 
			
		||||
        /// stack is empty.
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										318
									
								
								src/sys.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										318
									
								
								src/sys.zig
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,318 @@
 | 
			
		||||
const ext = @cImport({
 | 
			
		||||
    @cInclude("SDL2/SDL.h");
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const mem = @import("./mem.zig");
 | 
			
		||||
const stack = @import("./stack.zig");
 | 
			
		||||
const std = @import("std");
 | 
			
		||||
 | 
			
		||||
///
 | 
			
		||||
///
 | 
			
		||||
///
 | 
			
		||||
pub const EventLoop = packed struct {
 | 
			
		||||
    current_request: ?*Request = null,
 | 
			
		||||
 | 
			
		||||
    ///
 | 
			
		||||
    /// With files typically being backed by a block device, they can produce a variety of errors -
 | 
			
		||||
    /// from physical to virtual errors - these are all encapsulated by the API as general
 | 
			
		||||
    /// [Error.Inaccessible] errors.
 | 
			
		||||
    ///
 | 
			
		||||
    pub const FileError = error {
 | 
			
		||||
        Inaccessible,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    ///
 | 
			
		||||
    /// [OpenError.NotFound] is used as a catch-all for any hardware or software-specific reason for
 | 
			
		||||
    /// failing to open a given file. This includes file-system restrictions surrounding a specific
 | 
			
		||||
    /// file as well as it simply not existing.
 | 
			
		||||
    ///
 | 
			
		||||
    /// [OpenError.OutOfFiles] occurs when there are no more resources available to open further
 | 
			
		||||
    /// files. As a result, some open files must be closed before more may be opened.
 | 
			
		||||
    ///
 | 
			
		||||
    pub const OpenError = error {
 | 
			
		||||
        NotFound,
 | 
			
		||||
        OutOfFiles,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    ///
 | 
			
		||||
    /// Indicates what kind of access the consumer logic has to a file.
 | 
			
		||||
    ///
 | 
			
		||||
    /// [OpenMode.read] is for reading from an existing file from the start.
 | 
			
		||||
    ///
 | 
			
		||||
    /// [OpenMode.overwrite] is for deleting the contents of a file, or creating an empty one if no
 | 
			
		||||
    /// such file exists, and writing to it from the start.
 | 
			
		||||
    ///
 | 
			
		||||
    /// [OpenMode.append] is for writing additional contents to a file, creating an empty one if no
 | 
			
		||||
    /// such file exists, on the end of whatever it already contains.
 | 
			
		||||
    ///
 | 
			
		||||
    pub const OpenMode = enum {
 | 
			
		||||
        read,
 | 
			
		||||
        overwrite,
 | 
			
		||||
        append,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const Request = struct {
 | 
			
		||||
        next: ?*Request = null,
 | 
			
		||||
        frame: anyframe,
 | 
			
		||||
 | 
			
		||||
        message: union(enum) {
 | 
			
		||||
            close: struct {
 | 
			
		||||
                file: *ext.SDL_RWops,
 | 
			
		||||
            },
 | 
			
		||||
 | 
			
		||||
            open: struct {
 | 
			
		||||
                path: *const Path,
 | 
			
		||||
                file: OpenError!*ext.SDL_RWops,
 | 
			
		||||
                mode: OpenMode,
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const max_files = 512;
 | 
			
		||||
 | 
			
		||||
    ///
 | 
			
		||||
    /// Asynchronously closes `file_access` via `event_loop`.
 | 
			
		||||
    ///
 | 
			
		||||
    /// *Note* that `file_access` must have been opened by `event_loop` for it to be closed by it,
 | 
			
		||||
    /// otherwise it will cause undefined behavior.
 | 
			
		||||
    ///
 | 
			
		||||
    pub fn close(event_loop: *EventLoop, file_access: *FileAccess) void {
 | 
			
		||||
        var request = Request{
 | 
			
		||||
            .frame = @frame(),
 | 
			
		||||
            .message = .{.close = @ptrCast(*ext.SDL_RWops, file_access)},
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        suspend {
 | 
			
		||||
            if (event_loop.current_request) |current_request| {
 | 
			
		||||
                current_request.next = &request;
 | 
			
		||||
            } else {
 | 
			
		||||
                event_loop.current_request = &request;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ///
 | 
			
		||||
    /// Asynchronously attempts to open access to a file at `path` via `event_loop`, with `mode` as
 | 
			
		||||
    /// the preferences for how it should be opened.
 | 
			
		||||
    ///
 | 
			
		||||
    /// A reference to a [FileAccess] representing the bound file is returned if the operation was
 | 
			
		||||
    /// successful, otherwise an [OpenError] if the file could not be opened.
 | 
			
		||||
    ///
 | 
			
		||||
    /// *Note* that, regardless of platform, files will always be treated as containing binary data.
 | 
			
		||||
    ///
 | 
			
		||||
    pub fn open(event_loop: *EventLoop, path: Path, mode: OpenMode) OpenError!*FileAccess {
 | 
			
		||||
        var request = Request{
 | 
			
		||||
            .frame = @frame(),
 | 
			
		||||
 | 
			
		||||
            .message = .{
 | 
			
		||||
                .open = .{
 | 
			
		||||
                    .path = &path,
 | 
			
		||||
                    .file = error.OutOfFiles,
 | 
			
		||||
                    .mode = mode,
 | 
			
		||||
                },
 | 
			
		||||
            },
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        suspend {
 | 
			
		||||
            if (event_loop.current_request) |current_request| {
 | 
			
		||||
                current_request.next = &request;
 | 
			
		||||
            } else {
 | 
			
		||||
                event_loop.current_request = &request;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return @ptrCast(*FileAccess, try request.message.open.file);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ///
 | 
			
		||||
    ///
 | 
			
		||||
    ///
 | 
			
		||||
    pub fn tick(event_loop: *EventLoop) void {
 | 
			
		||||
        while (event_loop.current_request) |request| {
 | 
			
		||||
            switch (request.message) {
 | 
			
		||||
                .close => |*close| {
 | 
			
		||||
                    // Swallow file close errors.
 | 
			
		||||
                    _ = ext.SDL_RWclose(close.file);
 | 
			
		||||
                },
 | 
			
		||||
 | 
			
		||||
                .open => |*open| {
 | 
			
		||||
                    open.file = ext.SDL_RWFromFile(&open.path.buffer, switch (open.mode) {
 | 
			
		||||
                        .read => "rb",
 | 
			
		||||
                        .overwrite => "wb",
 | 
			
		||||
                        .append => "ab",
 | 
			
		||||
                    }) orelse error.NotFound;
 | 
			
		||||
                },
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            resume request.frame;
 | 
			
		||||
 | 
			
		||||
            event_loop.current_request = request.next;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub const FileAccess = opaque {
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
///
 | 
			
		||||
/// Platform-agnostic mechanism for accessing files on any of the virtual file-systems supported by
 | 
			
		||||
/// Ona.
 | 
			
		||||
///
 | 
			
		||||
pub const Path = struct {
 | 
			
		||||
    locator: Locator,
 | 
			
		||||
    length: u16,
 | 
			
		||||
    buffer: [max]u8,
 | 
			
		||||
 | 
			
		||||
    ///
 | 
			
		||||
    /// Virtual file-system locators supported by Ona.
 | 
			
		||||
    ///
 | 
			
		||||
    pub const Locator = enum(u8) {
 | 
			
		||||
        relative,
 | 
			
		||||
        data,
 | 
			
		||||
        user,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    ///
 | 
			
		||||
    /// Errors that may occur during parsing of an incorrectly formatted [Path] URI.
 | 
			
		||||
    ///
 | 
			
		||||
    pub const ParseError = (JoinError || error {
 | 
			
		||||
        BadLocator,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    ///
 | 
			
		||||
    ///
 | 
			
		||||
    ///
 | 
			
		||||
    pub const JoinError = error {
 | 
			
		||||
        TooLong,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    ///
 | 
			
		||||
    /// Returns `true` if the length of `path` is empty, otherwise `false`.
 | 
			
		||||
    ///
 | 
			
		||||
    pub fn isEmpty(path: Path) bool {
 | 
			
		||||
        return (path.length == 0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ///
 | 
			
		||||
    /// Attempts to lazily join `components` into a new [Path] value derived from `path`, returning
 | 
			
		||||
    /// it when `components` has no more data or a [JoinError] if the operation failed.
 | 
			
		||||
    ///
 | 
			
		||||
    /// Any duplicate path components, such as trailing ASCII forward-slashes (`/`) or periods
 | 
			
		||||
    /// (`.`), will be normalized to a more concise internal representation.
 | 
			
		||||
    ///
 | 
			
		||||
    /// *Note* that `components` may be mutated during execution of the operation.
 | 
			
		||||
    ///
 | 
			
		||||
    pub fn joinSpliterator(path: Path, components: *mem.Spliterator) JoinError!Path {
 | 
			
		||||
        var joined_path = path;
 | 
			
		||||
 | 
			
		||||
        var path_buffer = stack.Unmanaged{
 | 
			
		||||
            .buffer = &joined_path.buffer,
 | 
			
		||||
            .filled = if (joined_path.length == 0) joined_path.length else (joined_path.length - 1),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        if (components.next()) |component| switch (component.len) {
 | 
			
		||||
            0 => if (joined_path.isEmpty()) path_buffer.push('/') catch return error.TooLong,
 | 
			
		||||
 | 
			
		||||
            1 => if ((component[0] == '.') and joined_path.isEmpty())
 | 
			
		||||
                    path_buffer.push("./") catch return error.TooLong,
 | 
			
		||||
 | 
			
		||||
            else => {
 | 
			
		||||
                if (!joined_path.isEmpty()) path_buffer.push('/') catch return error.TooLong;
 | 
			
		||||
 | 
			
		||||
                path_buffer.pushAll(component) catch return error.TooLong;
 | 
			
		||||
 | 
			
		||||
                if (components.hasNext()) path_buffer.push('/') catch return error.TooLong;
 | 
			
		||||
            },
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        while (components.next()) |component|
 | 
			
		||||
            if ((component.len != 0) or (!((component.len == 1) and (component[0] == '.')))) {
 | 
			
		||||
 | 
			
		||||
            if (!joined_path.isEmpty()) path_buffer.push('/') catch return error.TooLong;
 | 
			
		||||
 | 
			
		||||
            path_buffer.pushAll(component) catch return error.TooLong;
 | 
			
		||||
 | 
			
		||||
            if (components.hasNext()) path_buffer.push('/') catch return error.TooLong;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // No space left over for the null terminator.
 | 
			
		||||
        if (path_buffer.filled >= max) return error.TooLong;
 | 
			
		||||
 | 
			
		||||
        joined_path.length = path_buffer.filled;
 | 
			
		||||
 | 
			
		||||
        return joined_path;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ///
 | 
			
		||||
    /// Returns `true` if `this` is equal to `that`, otherwise `false`.
 | 
			
		||||
    ///
 | 
			
		||||
    pub fn equals(this: Path, that: Path) bool {
 | 
			
		||||
        return (this.locator == that.locator) and
 | 
			
		||||
            std.mem.eql(this.buffer[0 .. this.length], that.buffer[0 .. that.length]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ///
 | 
			
		||||
    /// Creates and returns an empty [Path] value rooted at the location of `locator`.
 | 
			
		||||
    ///
 | 
			
		||||
    pub fn from(locator: Locator) Path {
 | 
			
		||||
        return .{
 | 
			
		||||
            .locator = locator,
 | 
			
		||||
            .length = 0,
 | 
			
		||||
            .buffer = std.mem.zeroes([max]u8),
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ///
 | 
			
		||||
    /// The maximum possible byte-length of a [Path].
 | 
			
		||||
    ///
 | 
			
		||||
    /// Note that paths are encoded using UTF-8, meaning that a character may be bigger than one
 | 
			
		||||
    /// byte. Because of this, it is not safe to asume that a path may hold [max] individual
 | 
			
		||||
    /// characters.
 | 
			
		||||
    ///
 | 
			
		||||
    pub const max = 1000;
 | 
			
		||||
 | 
			
		||||
    ///
 | 
			
		||||
    /// Attempts to parse the data in `sequence`, returning the [Path] value or an error from
 | 
			
		||||
    /// [ParseError] if it failed to parse.
 | 
			
		||||
    ///
 | 
			
		||||
    /// The rules surrounding the encoding of `sequence` are loose, with the only fundamental
 | 
			
		||||
    /// requirements being:
 | 
			
		||||
    ///
 | 
			
		||||
    ///   * It starts with a locator key followed by an ASCII colon (`data:`, `user:`, etc.)
 | 
			
		||||
    ///     followed by the rest of the path.
 | 
			
		||||
    ///
 | 
			
		||||
    ///   * Each component of the path is separated by an ASCII forward-slash (`/`).
 | 
			
		||||
    ///
 | 
			
		||||
    ///   * A path that begins with an ASCII forward-slash ('/') after the locator key is considered
 | 
			
		||||
    ///     to be relative to the root of the specified locator key instead of relative to the path
 | 
			
		||||
    ///     caller.
 | 
			
		||||
    ///
 | 
			
		||||
    /// Additional encoding rules are enforced via underlying file-system being used. For example,
 | 
			
		||||
    /// Microsoft Windows is case-insensitive while Unix and Linux systems are not. Additionally,
 | 
			
		||||
    /// Windows has far more reserved characters and sequences which cannot be used when interfacing
 | 
			
		||||
    /// with files compared to Linux and Unix systems.
 | 
			
		||||
    ///
 | 
			
		||||
    /// See [ParseError] for more information on the kinds of errors that may be returned.
 | 
			
		||||
    ///
 | 
			
		||||
    pub fn parse(sequence: []const u8) ParseError!Path {
 | 
			
		||||
        if (sequence.len == 0) return Path.from(.relative);
 | 
			
		||||
 | 
			
		||||
        if (mem.forwardFind(u8, sequence, ':')) |locator_path_delimiter_index| {
 | 
			
		||||
            var locator = std.meta.stringToEnum(Locator,
 | 
			
		||||
                sequence[0 .. locator_path_delimiter_index]) orelse return error.BadLocator;
 | 
			
		||||
 | 
			
		||||
            const components_index = (locator_path_delimiter_index + 1);
 | 
			
		||||
 | 
			
		||||
            return Path.from(locator).joinSpliterator(&.{
 | 
			
		||||
                .source = sequence[components_index .. (sequence.len - components_index)],
 | 
			
		||||
                .delimiter = "/",
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return Path.from(.relative).joinSpliterator(&.{
 | 
			
		||||
            .source = sequence,
 | 
			
		||||
            .delimiter = "/",
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user