Application Context Implementation #4
|
@ -10,15 +10,7 @@
|
||||||
"valuesFormatting": "parseText",
|
"valuesFormatting": "parseText",
|
||||||
"preLaunchTask": "Build Debug",
|
"preLaunchTask": "Build Debug",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "Oar",
|
|
||||||
"type": "gdb",
|
|
||||||
"request": "launch",
|
|
||||||
"target": "${workspaceFolder}/zig-out/bin/oar",
|
|
||||||
"cwd": "${workspaceRoot}",
|
|
||||||
"valuesFormatting": "parseText",
|
|
||||||
"preLaunchTask": "Build Debug",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "Test",
|
"name": "Test",
|
||||||
"type": "gdb",
|
"type": "gdb",
|
||||||
|
|
253
src/oar/main.zig
253
src/oar/main.zig
|
@ -1,253 +0,0 @@
|
||||||
const core = @import("core");
|
|
||||||
const std = @import("std");
|
|
||||||
|
|
||||||
///
|
|
||||||
///
|
|
||||||
///
|
|
||||||
pub const Archive = struct {
|
|
||||||
pub fn deinit(archive: *Archive) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn init(file_system: *const sys.FileSystem, file_path: sys.Path) {
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
///
|
|
||||||
/// An entry block of an Oar archive file.
|
|
||||||
///
|
|
||||||
/// Typically, following the block in memory is the file data it holds the meta-information for.
|
|
||||||
///
|
|
||||||
pub const Entry = extern struct {
|
|
||||||
signature: [signature_magic.len]u8 = signature_magic,
|
|
||||||
revision: u8,
|
|
||||||
path: Path,
|
|
||||||
file_size: u64,
|
|
||||||
absolute_offset: u64,
|
|
||||||
padding: [232]u8 = std.mem.zeroes([232]u8),
|
|
||||||
|
|
||||||
comptime {
|
|
||||||
const entry_size = @sizeOf(Entry);
|
|
||||||
|
|
||||||
if (entry_size != 512)
|
|
||||||
@compileError("Entry is " ++
|
|
||||||
std.fmt.comptimePrint("{d}", .{entry_size}) ++ " bytes");
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// Attempts to read the next [Entry] from `file_access`.
|
|
||||||
///
|
|
||||||
/// Returns the read [Entry], `null` if there is no more to read, or a
|
|
||||||
/// [core.io.FileAccess.Error] if it failed.
|
|
||||||
///
|
|
||||||
pub fn next(file_access: core.io.FileAccess) core.io.FileAccess.Error!?Entry {
|
|
||||||
const mem = std.mem;
|
|
||||||
var entry = mem.zeroes(Entry);
|
|
||||||
const origin = try file_access.queryCursor();
|
|
||||||
|
|
||||||
if (((try file_access.read(mem.asBytes(&entry))) != @sizeOf(Entry)) and
|
|
||||||
core.io.equals(u8, &entry.signature, &signature_magic)) {
|
|
||||||
|
|
||||||
try file_access.seek(origin);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// Magic identifier used to validate [Entry] data.
|
|
||||||
///
|
|
||||||
pub const signature_magic = [3]u8{'o', 'a', 'r'};
|
|
||||||
};
|
|
||||||
|
|
||||||
///
|
|
||||||
/// Unique identifier pointing to an entry within an archive.
|
|
||||||
///
|
|
||||||
/// A path does not do any verification that the given entry pointed to actually exists.
|
|
||||||
///
|
|
||||||
pub const Path = extern struct {
|
|
||||||
buffer: [255]u8,
|
|
||||||
length: u8,
|
|
||||||
|
|
||||||
///
|
|
||||||
/// [Error.TooLong] occurs when creating a path that is greater than the maximum path size **in
|
|
||||||
/// bytes**.
|
|
||||||
///
|
|
||||||
pub const Error = error {
|
|
||||||
TooLong,
|
|
||||||
};
|
|
||||||
|
|
||||||
///
|
|
||||||
/// An empty [Path] with a length of `0`.
|
|
||||||
///
|
|
||||||
pub const empty = std.mem.zeroes(Path);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// Returns `true` if `this_path` is equal to `that_path, otherwise `false`.
|
|
||||||
///
|
|
||||||
pub fn equals(this_path: Path, that_path: Path) bool {
|
|
||||||
return core.io.equals(u8, this_path.buffer[0 ..this_path.
|
|
||||||
length], that_path.buffer[0 .. that_path.length]);
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// Returns the hash of the text in `path`.
|
|
||||||
///
|
|
||||||
pub fn hash(path: Path) usize {
|
|
||||||
return core.io.hashBytes(path.buffer[0 .. path.length]);
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// Attempts to create a [Path] with the path components in `sequences` as a fully qualified
|
|
||||||
/// path from root.
|
|
||||||
///
|
|
||||||
/// A [Path] value is returned containing the fully qualified path from the file-system root or
|
|
||||||
/// a [Error] if it could not be created.
|
|
||||||
///
|
|
||||||
pub fn joined(sequences: []const []const u8) Error!Path {
|
|
||||||
var path = empty;
|
|
||||||
|
|
||||||
if (sequences.len != 0) {
|
|
||||||
const last_sequence_index = sequences.len - 1;
|
|
||||||
|
|
||||||
for (sequences) |sequence, index| if (sequence.len != 0) {
|
|
||||||
var components = core.io.Spliterator(u8){
|
|
||||||
.source = sequence,
|
|
||||||
.delimiter = "/",
|
|
||||||
};
|
|
||||||
|
|
||||||
while (components.next()) |component| if (component.len != 0) {
|
|
||||||
for (component) |byte| {
|
|
||||||
if (path.length == max) return error.TooLong;
|
|
||||||
|
|
||||||
path.buffer[path.length] = byte;
|
|
||||||
path.length += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (components.hasNext()) {
|
|
||||||
if (path.length == max) return error.TooLong;
|
|
||||||
|
|
||||||
path.buffer[path.length] = '/';
|
|
||||||
path.length += 1;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (index < last_sequence_index) {
|
|
||||||
if (path.length == max) return error.TooLong;
|
|
||||||
|
|
||||||
path.buffer[path.length] = '/';
|
|
||||||
path.length += 1;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// Maximum number of **bytes** in a [Path].
|
|
||||||
///
|
|
||||||
pub const max = 255;
|
|
||||||
|
|
||||||
///
|
|
||||||
/// Textual separator between components of a [Path].
|
|
||||||
///
|
|
||||||
pub const seperator = '/';
|
|
||||||
};
|
|
||||||
|
|
||||||
test "Path" {
|
|
||||||
const testing = std.testing;
|
|
||||||
const empty_path = Path.empty;
|
|
||||||
|
|
||||||
try testing.expectEqual(empty_path.length, 0);
|
|
||||||
try testing.expect(empty_path.equals(Path.empty));
|
|
||||||
|
|
||||||
const joined_component_path = try Path.joined(&.{"path", "to/my", "/file"});
|
|
||||||
const joined_normalized_path = try Path.joined(&.{"path/to/my/file"});
|
|
||||||
|
|
||||||
try testing.expectEqual(joined_component_path.length, joined_normalized_path.length);
|
|
||||||
try testing.expect(joined_component_path.equals(joined_normalized_path));
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// Starts the **O**na **Ar**chive packer utility.
|
|
||||||
///
|
|
||||||
pub fn main() u8 {
|
|
||||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
|
||||||
|
|
||||||
defer std.debug.assert(!gpa.deinit());
|
|
||||||
|
|
||||||
const allocator = gpa.allocator();
|
|
||||||
const out_writer = std.io.getStdOut().writer();
|
|
||||||
const process = std.process;
|
|
||||||
|
|
||||||
const args = process.argsAlloc(allocator) catch {
|
|
||||||
out_writer.print("Failed to allocate args memory\n", .{}) catch undefined;
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
};
|
|
||||||
|
|
||||||
defer process.argsFree(allocator, args);
|
|
||||||
|
|
||||||
if (args.len < 2) {
|
|
||||||
out_writer.print("Usage: oar [OPTION]... [FILE]...\n", .{}) catch undefined;
|
|
||||||
out_writer.print("Options and arguments\n", .{}) catch undefined;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const arg = std.mem.sliceTo(args[1], 0);
|
|
||||||
|
|
||||||
if (core.io.equals(u8, arg, "--create")) {
|
|
||||||
if (args.len < 3) {
|
|
||||||
out_writer.print("Expected output file specified after `--create`\n", .{}) catch undefined;
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
var archive = Archive.init(allocator, Path.joined(&.{args[2]})) catch {
|
|
||||||
out_writer.print("Failed to initialize archive for create\n", .{}) catch undefined;
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
};
|
|
||||||
|
|
||||||
defer archive.deinit();
|
|
||||||
|
|
||||||
for (args[3 .. ]) |input_file_path| {
|
|
||||||
const file = std.fs.cwd().openFile(input_file_path) catch {
|
|
||||||
out_writer.print("Failed to open {s}\n", .{input_file_path}) catch undefined;
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
};
|
|
||||||
|
|
||||||
defer file.close();
|
|
||||||
|
|
||||||
var entry = archive.open(Path.joined(&.{input_file_path})) catch {
|
|
||||||
out_writer.print("Failed to open {s}\n", .{input_file_path}) catch undefined;
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
};
|
|
||||||
|
|
||||||
defer archive.close(entry);
|
|
||||||
|
|
||||||
var copy_buffer = std.mem.zeroes([4096]u8);
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
const read = try file.read(©_buffer);
|
|
||||||
|
|
||||||
if (read == 0) break;
|
|
||||||
|
|
||||||
try entry.write(copy_buffer[read ..]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
out_writer.print("Unrecognized command-line option `{s}`\n", .{arg}) catch undefined;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
Loading…
Reference in New Issue