Wrap the C library libmagic in Zig and create an Elixir library via Zigler
- Dominant language
- No language data
- Stars
- 4
- Forks
- 1
- PR merge metrics
- No merged PRs in 30d
Description
## 1) Zig library wrapping `libmagic`
You have `libmagic` installed on your system:
- macOS: `brew install libmagic`
- Ubuntu/Debian: `apt install libmagic-dev`
This time, a simple `@cImport` does the trick to use a system library. Building this library to produce a vendored static `.a` or `.so` is complicated. However, the opaque struct `magic_t` is accepted with no problem at all. Made things really easy for a change.
The code is interesting - as a beginner - because `libmagic` wants cpointers to sentinel terminated slices. So you need to juggle between a pointer to a sentinel `[*c]const u8` and `Zig`-slices `[]const u8`. This is used in the `Magic.from_path` method.
- Zig to C: use `std.mem.Allocator.dupeZ`
- C to Zig: use `std.mem.span`
```zig
//! By convention, root.zig is the root source file when making a library.
const std = @import("std");
const m = @cImport(@cInclude("magic.h"));
pub const Magic = struct {
cookie: m.magic_t,
pub fn init() !Magic {
const cookie = m.magic_open(m.MAGIC_MIME_TYPE) orelse return error.MagicOpenFailed;
return Magic{ .cookie = cookie };
}
pub fn deinit(self: *Magic) void {
m.magic_close(self.cookie);
}
pub fn load(self: *Magic) !void {
if (m.magic_load(self.cookie, null) != 0) {
self.deinit();
return error.MagicLoadFailed;
}
}
pub fn merror(self: *Magic) []const u8 {
const err = std.mem.span(m.magic_error(self.cookie));
return err;
}
pub fn from_path(self: *Magic, allocator: std.mem.Allocator, path: []const u8) ![]const u8 {
const path_z = try allocator.dupeZ(u8, path);
defer allocator.free(path_z);
const res = m.magic_file(self.cookie, path_z.ptr);
if (res == null) {
return error.MagicFileFailed;
}
return std.mem.span(res);
}
pub fn from_buffer(self: *Magic, buffer: []const u8) ![]const u8 {
const res = m.magic_buffer(self.cookie, buffer.ptr, buffer.len);
if (res == null) {
return error.MagicBufferFailed;
}
return std.mem.span(res);
}
pub fn from_handle(self: *Magic, handle: i32) ![]const u8 {
const res = m.magic_descriptor(self.cookie, handle);
if (res == null) {
return error.MagicFdFailed;
}
return std.mem.span(res);
}
};
```
### Tests
`zig build test`
Test details
I have a "real" `.png` file, and the others are `.txt` files.
```zig
const std = @import("std");
const lib = @import("magic_zig");
const m = lib.Magic;
test "file buffer" {
const allocator = std.testing.allocator;
var magic = try m.init();
defer magic.deinit();
const file_contents = try std.fs.cwd().readFileAlloc(allocator, "src/main.zig", 1024 * 1024);
defer allocator.free(file_contents);
try magic.load();
const mime = try magic.from_buffer(file_contents);
try std.testing.expectEqualSlices(u8, "text/x-c", mime);
}
test "streamed buffer" {
const allocator = std.testing.allocator;
var magic = try m.init();
defer magic.deinit();
var file = try std.fs.cwd().openFile("src/main.zig", .{ .mode = .read_only });
defer file.close();
var read_buf: [1024]u8 = undefined;
var file_reader = file.reader(&read_buf);
const reader = &file_reader.interface;
var line = std.Io.Writer.Allocating.init(allocator);
defer line.deinit();
var file_as_list: std.ArrayList(u8) = .empty;
// read line by line
while (true) {
_ = reader.streamDelimiter(&line.writer, '\n') catch |err| {
if (err == error.EndOfStream) break else return err;
};
reader.toss(1); // consume the delimiter
try file_as_list.appendSlice(allocator, line.written());
line.clearRetainingCapacity();
}
try magic.load();
const streamed_content = try file_as_list.toOwnedSlice(allocator);
defer allocator.free(streamed_content);
const mime = try magic.from_buffer(streamed_content);
try std.testing.expectEqualSlices(u8, "text/x-c", mime);
}
test "file descriptor" {
var magic = try m.init();
defer magic.deinit();
var file = try std.fs.cwd().openFile("src/main.zig", .{ .mode = .read_only });
defer file.close();
try magic.load();
const mime = try magic.from_handle(file.handle);
try std.testing.expectEqualSlices(u8, "text/x-c", mime);
}
test "from paths" {
const allocator = std.testing.allocator;
var magic = try m.init();
defer magic.deinit();
try magic.load();
const paths = [_][]const u8{
"src/tests/icons8-globe-24.png",
"src/tests/t.png",
"src/tests/test.txt",
"src/tests/eggs-2-svgrepo-com.svg",
"src/tests/ex_module.ex",
"src/tests/test.json",
"src/tests/robots.txt.gz",
"README.md",
"src/tests/htmz.sql3",
"src/tests/simple.js",
};
const expected_mimes = [_][]const u8{
"image/png",
"text/plain",
"text/plain",
"image/svg+xml",
"text/plain",
"application/json",
"application/gzip",
"text/plain",
"application/vnd.sqlite3",
"application/javascript",
};
for (paths, 0..) |path, i| {
const mime = try magic.from_path(allocator, path);
try std.testing.expectEqualSlices(u8, expected_mimes[i], mime);
}
}
```
### Documentation
Example of docs generated by `zig build docs`and appended to github-pages:
Documentation setup in build.zig
```zig
// build.zig
// Documentation
const docs_step = b.step("docs", "Build zexplorer library docs");
const docs_obj = b.addObject(.{
.name = "zexplorer_docs",
.root_module = b.createModule(.{
.root_source_file = b.path("src/root.zig"),
.target = target,
.optimize = optimize,
}),
});
const docs = docs_obj.getEmittedDocs();
docs_step.dependOn(&b.addInstallDirectory(.{
.source_dir = docs,
.install_dir = .prefix,
.install_subdir = "docs",
}).step);
```
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.