Skip to content

Commit 44da9be

Browse files
committed
std.tar: use doctest
Make std.tar look better in docs. Remove from public interface what is not necessary. Add comment to the public methods. Add doctest as usage examples for iterator and pipeToFileSystem.
1 parent 136f2ae commit 44da9be

File tree

3 files changed

+164
-108
lines changed

3 files changed

+164
-108
lines changed

lib/std/tar.zig

Lines changed: 161 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@
1717
//!
1818
const std = @import("std");
1919
const assert = std.debug.assert;
20+
const testing = std.testing;
2021

21-
pub const Options = struct {
22+
/// pipeToFileSystem options
23+
pub const PipeOptions = struct {
2224
/// Number of directory levels to skip when extracting files.
2325
strip_components: u32 = 0,
2426
/// How to handle the "mode" property of files from within the tar file.
@@ -82,14 +84,14 @@ pub const Options = struct {
8284
};
8385
};
8486

85-
pub const Header = struct {
87+
const Header = struct {
8688
const SIZE = 512;
87-
pub const MAX_NAME_SIZE = 100 + 1 + 155; // name(100) + separator(1) + prefix(155)
88-
pub const LINK_NAME_SIZE = 100;
89+
const MAX_NAME_SIZE = 100 + 1 + 155; // name(100) + separator(1) + prefix(155)
90+
const LINK_NAME_SIZE = 100;
8991

9092
bytes: *const [SIZE]u8,
9193

92-
pub const Kind = enum(u8) {
94+
const Kind = enum(u8) {
9395
normal_alias = 0,
9496
normal = '0',
9597
hard_link = '1',
@@ -235,74 +237,53 @@ fn nullStr(str: []const u8) []const u8 {
235237
return str;
236238
}
237239

240+
/// Options for iterator.
241+
/// Buffers should be provided by the caller.
238242
pub const IteratorOptions = struct {
239243
/// Use a buffer with length `std.fs.MAX_PATH_BYTES` to match file system capabilities.
240244
file_name_buffer: []u8,
241245
/// Use a buffer with length `std.fs.MAX_PATH_BYTES` to match file system capabilities.
242246
link_name_buffer: []u8,
247+
/// Provide this to receive detailed error messages.
248+
/// When this is provided, some errors which would otherwise be returned immediately
249+
/// will instead be added to this structure. The API user must check the errors
250+
/// in diagnostics to know whether the operation succeeded or failed.
243251
diagnostics: ?*Diagnostics = null,
244252

245-
pub const Diagnostics = Options.Diagnostics;
253+
pub const Diagnostics = PipeOptions.Diagnostics;
246254
};
247255

248256
/// Iterates over files in tar archive.
249-
/// `next` returns each file in `reader` tar archive.
250-
///
251-
/// Init iterator with tar archive reader and provided buffers:
252-
///
253-
/// var file_name_buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined;
254-
/// var link_name_buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined;
255-
///
256-
/// var iter = std.tar.iterator(archive.reader(), .{
257-
/// .file_name_buffer = &file_name_buffer,
258-
/// .link_name_buffer = &link_name_buffer,
259-
/// });
260-
///
261-
/// Iterate on each tar archive file:
262-
///
263-
/// while (try iter.next()) |file| {
264-
/// switch (file.kind) {
265-
/// .directory => {
266-
/// // try dir.makePath(file.name);
267-
/// },
268-
/// .file => {
269-
/// // try file.writeAll(writer);
270-
/// },
271-
/// .sym_link => {
272-
/// // try dir.symLink(file.link_name, file.name, .{});
273-
/// },
274-
/// }
275-
/// }
276-
///
257+
/// `next` returns each file in tar archive.
277258
pub fn iterator(reader: anytype, options: IteratorOptions) Iterator(@TypeOf(reader)) {
278259
return .{
279260
.reader = reader,
280261
.diagnostics = options.diagnostics,
281-
.header_buffer = undefined,
282262
.file_name_buffer = options.file_name_buffer,
283263
.link_name_buffer = options.link_name_buffer,
284-
.padding = 0,
285264
};
286265
}
287266

267+
/// Type of the file returned by iterator `next` method.
288268
pub const FileKind = enum {
289269
directory,
290270
sym_link,
291271
file,
292272
};
293273

294-
fn Iterator(comptime ReaderType: type) type {
274+
/// Iteartor over entries in the tar file represented by reader.
275+
pub fn Iterator(comptime ReaderType: type) type {
295276
return struct {
296277
reader: ReaderType,
297-
diagnostics: ?*Options.Diagnostics,
278+
diagnostics: ?*PipeOptions.Diagnostics = null,
298279

299280
// buffers for heeader and file attributes
300-
header_buffer: [Header.SIZE]u8,
281+
header_buffer: [Header.SIZE]u8 = undefined,
301282
file_name_buffer: []u8,
302283
link_name_buffer: []u8,
303284

304285
// bytes of padding to the end of the block
305-
padding: usize,
286+
padding: usize = 0,
306287
// not consumed bytes of file from last next iteration
307288
unread_file_bytes: u64 = 0,
308289

@@ -314,18 +295,18 @@ fn Iterator(comptime ReaderType: type) type {
314295
kind: FileKind = .file,
315296

316297
unread_bytes: *u64,
317-
reader: ReaderType,
298+
parent_reader: ReaderType,
318299

319-
pub const Reader = std.io.Reader(*Self, ReaderType.Error, read);
300+
pub const Reader = std.io.Reader(File, ReaderType.Error, File.read);
320301

321-
pub fn reader(self: *Self) Reader {
302+
pub fn reader(self: File) Reader {
322303
return .{ .context = self };
323304
}
324305

325-
pub fn read(self: *Self, dest: []u8) ReaderType.Error!usize {
326-
const buf = dest[0..@min(dest.len, self.unread_size.*)];
327-
const n = try self.reader.read(buf);
328-
self.unread_size.* -= n;
306+
pub fn read(self: File, dest: []u8) ReaderType.Error!usize {
307+
const buf = dest[0..@min(dest.len, self.unread_bytes.*)];
308+
const n = try self.parent_reader.read(buf);
309+
self.unread_bytes.* -= n;
329310
return n;
330311
}
331312

@@ -335,7 +316,7 @@ fn Iterator(comptime ReaderType: type) type {
335316

336317
while (self.unread_bytes.* > 0) {
337318
const buf = buffer[0..@min(buffer.len, self.unread_bytes.*)];
338-
try self.reader.readNoEof(buf);
319+
try self.parent_reader.readNoEof(buf);
339320
try writer.writeAll(buf);
340321
self.unread_bytes.* -= buf.len;
341322
}
@@ -367,7 +348,7 @@ fn Iterator(comptime ReaderType: type) type {
367348
return .{
368349
.name = self.file_name_buffer[0..0],
369350
.link_name = self.link_name_buffer[0..0],
370-
.reader = self.reader,
351+
.parent_reader = self.reader,
371352
.unread_bytes = &self.unread_file_bytes,
372353
};
373354
}
@@ -592,7 +573,8 @@ fn PaxIterator(comptime ReaderType: type) type {
592573
};
593574
}
594575

595-
pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: Options) !void {
576+
/// Saves tar file content to the file systems.
577+
pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: PipeOptions) !void {
596578
switch (options.mode_mode) {
597579
.ignore => {},
598580
.executable_bit_only => {
@@ -697,7 +679,7 @@ fn stripComponents(path: []const u8, count: u32) []const u8 {
697679
}
698680

699681
test "stripComponents" {
700-
const expectEqualStrings = std.testing.expectEqualStrings;
682+
const expectEqualStrings = testing.expectEqualStrings;
701683
try expectEqualStrings("a/b/c", stripComponents("a/b/c", 0));
702684
try expectEqualStrings("b/c", stripComponents("a/b/c", 1));
703685
try expectEqualStrings("c", stripComponents("a/b/c", 2));
@@ -808,24 +790,24 @@ test "PaxIterator" {
808790
var i: usize = 0;
809791
while (iter.next() catch |err| {
810792
if (case.err) |e| {
811-
try std.testing.expectEqual(e, err);
793+
try testing.expectEqual(e, err);
812794
continue;
813795
}
814796
return err;
815797
}) |attr| : (i += 1) {
816798
const exp = case.attrs[i];
817-
try std.testing.expectEqual(exp.kind, attr.kind);
799+
try testing.expectEqual(exp.kind, attr.kind);
818800
const value = attr.value(&buffer) catch |err| {
819801
if (exp.err) |e| {
820-
try std.testing.expectEqual(e, err);
802+
try testing.expectEqual(e, err);
821803
break :outer;
822804
}
823805
return err;
824806
};
825-
try std.testing.expectEqualStrings(exp.value, value);
807+
try testing.expectEqualStrings(exp.value, value);
826808
}
827-
try std.testing.expectEqual(case.attrs.len, i);
828-
try std.testing.expect(case.err == null);
809+
try testing.expectEqual(case.attrs.len, i);
810+
try testing.expect(case.err == null);
829811
}
830812
}
831813

@@ -861,9 +843,9 @@ test "header parse size" {
861843
@memcpy(bytes[124 .. 124 + case.in.len], case.in);
862844
var header = Header{ .bytes = &bytes };
863845
if (case.err) |err| {
864-
try std.testing.expectError(err, header.size());
846+
try testing.expectError(err, header.size());
865847
} else {
866-
try std.testing.expectEqual(case.want, try header.size());
848+
try testing.expectEqual(case.want, try header.size());
867849
}
868850
}
869851
}
@@ -886,15 +868,15 @@ test "header parse mode" {
886868
@memcpy(bytes[100 .. 100 + case.in.len], case.in);
887869
var header = Header{ .bytes = &bytes };
888870
if (case.err) |err| {
889-
try std.testing.expectError(err, header.mode());
871+
try testing.expectError(err, header.mode());
890872
} else {
891-
try std.testing.expectEqual(case.want, try header.mode());
873+
try testing.expectEqual(case.want, try header.mode());
892874
}
893875
}
894876
}
895877

896878
test "create file and symlink" {
897-
var root = std.testing.tmpDir(.{});
879+
var root = testing.tmpDir(.{});
898880
defer root.cleanup();
899881

900882
var file = try createDirAndFile(root.dir, "file1");
@@ -914,3 +896,120 @@ test "create file and symlink" {
914896
file = try createDirAndFile(root.dir, "g/h/i/file4");
915897
file.close();
916898
}
899+
900+
test iterator {
901+
// Example tar file is created from this tree structure:
902+
// $ tree example
903+
// example
904+
// ├── a
905+
// │   └── file
906+
// ├── b
907+
// │   └── symlink -> ../a/file
908+
// └── empty
909+
// $ cat example/a/file
910+
// content
911+
// $ tar -cf example.tar example
912+
// $ tar -tvf example.tar
913+
// example/
914+
// example/b/
915+
// example/b/symlink -> ../a/file
916+
// example/a/
917+
// example/a/file
918+
// example/empty/
919+
920+
const data = @embedFile("tar/testdata/example.tar");
921+
var fbs = std.io.fixedBufferStream(data);
922+
923+
// User provided buffers to the iterator
924+
var file_name_buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined;
925+
var link_name_buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined;
926+
// Create iterator
927+
var iter = iterator(fbs.reader(), .{
928+
.file_name_buffer = &file_name_buffer,
929+
.link_name_buffer = &link_name_buffer,
930+
});
931+
// Iterate over files in example.tar
932+
var file_no: usize = 0;
933+
while (try iter.next()) |file| : (file_no += 1) {
934+
switch (file.kind) {
935+
.directory => {
936+
switch (file_no) {
937+
0 => try testing.expectEqualStrings("example/", file.name),
938+
1 => try testing.expectEqualStrings("example/b/", file.name),
939+
3 => try testing.expectEqualStrings("example/a/", file.name),
940+
5 => try testing.expectEqualStrings("example/empty/", file.name),
941+
else => unreachable,
942+
}
943+
},
944+
.file => {
945+
try testing.expectEqualStrings("example/a/file", file.name);
946+
// Read file content
947+
var buf: [16]u8 = undefined;
948+
const n = try file.reader().readAll(&buf);
949+
try testing.expectEqualStrings("content\n", buf[0..n]);
950+
},
951+
.sym_link => {
952+
try testing.expectEqualStrings("example/b/symlink", file.name);
953+
try testing.expectEqualStrings("../a/file", file.link_name);
954+
},
955+
}
956+
}
957+
}
958+
959+
test pipeToFileSystem {
960+
// Example tar file is created from this tree structure:
961+
// $ tree example
962+
// example
963+
// ├── a
964+
// │   └── file
965+
// ├── b
966+
// │   └── symlink -> ../a/file
967+
// └── empty
968+
// $ cat example/a/file
969+
// content
970+
// $ tar -cf example.tar example
971+
// $ tar -tvf example.tar
972+
// example/
973+
// example/b/
974+
// example/b/symlink -> ../a/file
975+
// example/a/
976+
// example/a/file
977+
// example/empty/
978+
979+
const data = @embedFile("tar/testdata/example.tar");
980+
var fbs = std.io.fixedBufferStream(data);
981+
const reader = fbs.reader();
982+
983+
var tmp = testing.tmpDir(.{ .no_follow = true });
984+
defer tmp.cleanup();
985+
const dir = tmp.dir;
986+
987+
// Save tar from `reader` to the file system `dir`
988+
pipeToFileSystem(dir, reader, .{
989+
.mode_mode = .ignore,
990+
.strip_components = 1,
991+
.exclude_empty_directories = true,
992+
}) catch |err| {
993+
// Skip on platform which don't support symlinks
994+
if (err == error.UnableToCreateSymLink) return error.SkipZigTest;
995+
return err;
996+
};
997+
998+
try testing.expectError(error.FileNotFound, dir.statFile("empty"));
999+
try testing.expect((try dir.statFile("a/file")).kind == .file);
1000+
try testing.expect((try dir.statFile("b/symlink")).kind == .file); // statFile follows symlink
1001+
1002+
var buf: [32]u8 = undefined;
1003+
try testing.expectEqualSlices(
1004+
u8,
1005+
"../a/file",
1006+
normalizePath(try dir.readLink("b/symlink", &buf)),
1007+
);
1008+
}
1009+
1010+
fn normalizePath(bytes: []u8) []u8 {
1011+
const canonical_sep = std.fs.path.sep_posix;
1012+
if (std.fs.path.sep == canonical_sep) return bytes;
1013+
std.mem.replaceScalar(u8, bytes, std.fs.path.sep, canonical_sep);
1014+
return bytes;
1015+
}

0 commit comments

Comments
 (0)