Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@ jobs:
- uses: actions/checkout@v3
- uses: mlugg/setup-zig@v2
with:
version: 0.15.0
version: 0.16.0
- run: zig build test
21 changes: 9 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,19 @@ This is a very simple ini-parser library that provides:
### Zig

```zig
pub fn main() !void {
const file = try std.fs.cwd().openFile("example.ini", .{});
defer file.close();

var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer if (gpa.deinit() != .ok) @panic("memory leaked");
pub fn main(init: std.process.Init) !void {
const io = init.io;
const file = try std.Io.Dir.cwd().openFile(io, "example.ini", .{});
defer file.close(io);

var read_buffer: [1024]u8 = undefined;
var file_reader = file.reader(&read_buffer);
var parser = ini.parse(gpa.allocator(), &file_reader.interface, ";#");
var file_reader = file.reader(io, &read_buffer);
var parser = ini.parse(init.gpa, &file_reader.interface, ";#");
defer parser.deinit();

var write_buffer: [1024]u8 = undefined;
var file_writer = std.fs.File.stdout().writer(&write_buffer);
var writer = &file_writer.interface;
var file_writer = std.Io.File.stdout().writer(io, &write_buffer);
const writer = &file_writer.interface;
defer writer.flush() catch @panic("Could not flush to stdout");

while (try parser.next()) |record| {
Expand All @@ -36,8 +34,7 @@ pub fn main() !void {
.enumeration => |value| try writer.print("{s}\n", .{value}),
}
}
}
```
}```

### C

Expand Down
40 changes: 30 additions & 10 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,29 @@ pub fn build(b: *std.Build) void {
.target = target,
});

const ini_c_header = b.addTranslateC(.{
.root_source_file = b.path("src/ini.h"),
.target = target,
.optimize = optimize,
});

const lib = b.addLibrary(.{
.name = "ini",
.root_module = b.createModule(.{
.root_source_file = b.path("src/lib.zig"),
.target = target,
.optimize = optimize,
.link_libc = true,
.imports = &.{
.{
.name = "c",
.module = ini_c_header.createModule(),
},
},
}),
});
lib.bundle_compiler_rt = true;
lib.addIncludePath(b.path("src"));
lib.linkLibC();
lib.root_module.addIncludePath(b.path("src"));
lib.installHeader(b.path("src/ini.h"), "ini.h");
b.installArtifact(lib);

Expand All @@ -30,19 +42,19 @@ pub fn build(b: *std.Build) void {
.root_module = b.createModule(.{
.optimize = optimize,
.target = target,
.link_libc = true,
}),
});
example_c.addCSourceFile(.{
example_c.root_module.addCSourceFile(.{
.file = b.path("example/example.c"),
.flags = &.{
"-Wall",
"-Wextra",
"-pedantic",
},
});
example_c.addIncludePath(b.path("src"));
example_c.linkLibrary(lib);
example_c.linkLibC();
example_c.root_module.addIncludePath(b.path("src"));
example_c.root_module.linkLibrary(lib);
example_step.dependOn(&b.addInstallArtifact(example_c, .{}).step);

const example_zig = b.addExecutable(.{
Expand All @@ -51,9 +63,11 @@ pub fn build(b: *std.Build) void {
.root_source_file = b.path("example/example.zig"),
.optimize = optimize,
.target = target,
.imports = &.{
.{ .name = "ini", .module = b.modules.get("ini").? },
},
}),
});
example_zig.root_module.addImport("ini", b.modules.get("ini").?);
example_step.dependOn(&b.addInstallArtifact(example_zig, .{}).step);

const test_step = b.step("test", "Run library tests");
Expand All @@ -71,10 +85,16 @@ pub fn build(b: *std.Build) void {
.root_source_file = b.path("src/lib-test.zig"),
.optimize = optimize,
.target = target,
.link_libc = true,
.imports = &.{
.{
.name = "c",
.module = ini_c_header.createModule(),
},
},
}),
});
binding_tests.addIncludePath(b.path("src"));
binding_tests.linkLibrary(lib);
binding_tests.linkLibC();
binding_tests.root_module.addIncludePath(b.path("src"));
binding_tests.root_module.linkLibrary(lib);
test_step.dependOn(&b.addRunArtifact(binding_tests).step);
}
2 changes: 1 addition & 1 deletion build.zig.zon
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
.name = .ini,
.fingerprint = 0x7757b668623d2460,
.version = "0.1.0",
.minimum_zig_version = "0.15.0",
.minimum_zig_version = "0.16.0",
.paths = .{
"LICENCE",
"README.md",
Expand Down
18 changes: 8 additions & 10 deletions example/example.zig
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
const std = @import("std");
const ini = @import("ini");

pub fn main() !void {
const file = try std.fs.cwd().openFile("example.ini", .{});
defer file.close();

var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer if (gpa.deinit() != .ok) @panic("memory leaked");
pub fn main(init: std.process.Init) !void {
const io = init.io;
const file = try std.Io.Dir.cwd().openFile(io, "example.ini", .{});
defer file.close(io);

var read_buffer: [1024]u8 = undefined;
var file_reader = file.reader(&read_buffer);
var parser = ini.parse(gpa.allocator(), &file_reader.interface, ";#");
var file_reader = file.reader(io, &read_buffer);
var parser = ini.parse(init.gpa, &file_reader.interface, ";#");
defer parser.deinit();

var write_buffer: [1024]u8 = undefined;
var file_writer = std.fs.File.stdout().writer(&write_buffer);
var writer = &file_writer.interface;
var file_writer = std.Io.File.stdout().writer(io, &write_buffer);
const writer = &file_writer.interface;
defer writer.flush() catch @panic("Could not flush to stdout");

while (try parser.next()) |record| {
Expand Down
38 changes: 18 additions & 20 deletions src/ini.zig
Original file line number Diff line number Diff line change
Expand Up @@ -37,39 +37,37 @@ fn insertNulTerminator(slice: []const u8) [:0]const u8 {
pub const Parser = struct {
const Self = @This();

allocator: std.mem.Allocator,
line_buffer: std.array_list.Managed(u8),
reader: *std.io.Reader,
line_writer: std.Io.Writer.Allocating,
reader: *std.Io.Reader,
comment_characters: []const u8,

pub fn deinit(self: *Self) void {
self.line_buffer.deinit();
self.line_writer.deinit();
self.* = undefined;
}

pub fn next(self: *Self) !?Record {
var write_buffer: [1024]u8 = undefined;
var old_writer_adapter = self.line_buffer.writer().adaptToNewApi(&write_buffer);
var writer = &old_writer_adapter.new_interface;
self.line_buffer.clearRetainingCapacity();
self.line_writer.clearRetainingCapacity();
while (true) {
_ = try self.reader.streamDelimiterLimit(writer, '\n', .limited(4096));
try writer.flush();
_ = try self.reader.streamDelimiterLimit(&self.line_writer.writer, '\n', .limited(4096));

var line: []const u8 = self.line_writer.written();

const discarded = self.reader.discard(.limited(1)) catch |e| blk: {
switch (e) {
error.EndOfStream => {
if (self.line_buffer.items.len == 0)
if (line.len == 0)
return null;
break :blk 0;
},
else => return e,
}
};
if (self.line_buffer.items.len == 0 and discarded == 0)
if (line.len == 0 and discarded == 0)
return null;
try self.line_buffer.append(0); // append guaranteed space for sentinel
try self.line_writer.writer.writeByte(0); // append guaranteed space for sentinel
line = self.line_writer.written();

var line: []const u8 = self.line_buffer.items;
var last_index: usize = 0;

// handle comments and escaping
Expand All @@ -81,8 +79,9 @@ pub const Parser = struct {
const previous_char = line[previous_index];

if (previous_char == '\\') {
_ = self.line_buffer.orderedRemove(previous_index);
line = self.line_buffer.items;
var buf = self.line_writer.written();
@memmove(buf[previous_index .. buf.len - 1], buf[index..buf.len]);
self.line_writer.shrinkRetainingCapacity(buf.len - 1);

last_index = index + 1;
continue;
Expand All @@ -98,7 +97,7 @@ pub const Parser = struct {
}

if (line.len == 0) {
self.line_buffer.clearRetainingCapacity();
self.line_writer.clearRetainingCapacity();
continue;
}

Expand All @@ -122,10 +121,9 @@ pub const Parser = struct {
};

/// Returns a new parser that can read the ini structure
pub fn parse(allocator: std.mem.Allocator, reader: *std.io.Reader, comment_characters: []const u8) Parser {
pub fn parse(allocator: std.mem.Allocator, reader: *std.Io.Reader, comment_characters: []const u8) Parser {
return Parser{
.allocator = allocator,
.line_buffer = std.array_list.Managed(u8).init(allocator),
.line_writer = std.Io.Writer.Allocating.init(allocator),
.reader = reader,
.comment_characters = comment_characters,
};
Expand Down
5 changes: 1 addition & 4 deletions src/lib-test.zig
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
const std = @import("std");

const c = @cImport({
@cInclude("ini.h");
});
const c = @import("c");

test "parser create/destroy" {
var buffer: c.ini_Parser = undefined;
Expand Down
70 changes: 38 additions & 32 deletions src/lib.zig
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
const std = @import("std");
const ini = @import("ini.zig");

const c = @cImport({
@cInclude("ini.h");
});
const c = @import("c");

const Record = extern struct {
type: Type,
Expand All @@ -29,12 +26,12 @@ const Record = extern struct {
};

const BufferParser = struct {
stream: std.io.Reader,
stream: std.Io.Reader,
parser: ini.Parser,
};

const FileParser = struct {
old_reader_adapter: CReader.Adapter,
reader: CReader,
parser: ini.Parser,
};

Expand Down Expand Up @@ -70,7 +67,7 @@ comptime {
export fn ini_create_buffer(parser: *IniParser, data: [*]const u8, data_length: usize, comment_characters: [*]const u8, comment_characters_length: usize) void {
parser.* = IniParser{
.buffer = .{
.stream = std.io.Reader.fixed(data[0..data_length]),
.stream = std.Io.Reader.fixed(data[0..data_length]),
.parser = undefined,
},
};
Expand All @@ -81,12 +78,12 @@ export fn ini_create_buffer(parser: *IniParser, data: [*]const u8, data_length:
export fn ini_create_file(parser: *IniParser, read_buffer: [*]u8, read_buffer_length: usize, file: *std.c.FILE, comment_characters: [*]const u8, comment_characters_length: usize) void {
parser.* = IniParser{
.file = .{
.old_reader_adapter = cReader(file).adaptToNewApi(read_buffer[0..read_buffer_length]),
.reader = CReader.init(file, read_buffer[0..read_buffer_length]),
.parser = undefined,
},
};

parser.file.parser = ini.parse(std.heap.c_allocator, &parser.file.old_reader_adapter.new_interface, comment_characters[0..comment_characters_length]);
parser.file.parser = ini.parse(std.heap.c_allocator, &parser.file.reader.interface, comment_characters[0..comment_characters_length]);
}

export fn ini_destroy(parser: *IniParser) void {
Expand All @@ -97,7 +94,7 @@ export fn ini_destroy(parser: *IniParser) void {
parser.* = undefined;
}

const ParseError = error{ OutOfMemory, StreamTooLong } || std.io.Reader.Error || std.io.Writer.Error;
const ParseError = error{ OutOfMemory, StreamTooLong } || std.Io.Reader.Error || std.Io.Writer.Error;

fn mapError(err: ParseError) IniError {
return switch (err) {
Expand Down Expand Up @@ -141,28 +138,37 @@ export fn ini_next(parser: *IniParser, record: *Record) IniError {
return .success;
}

const CReader = std.Io.GenericReader(*std.c.FILE, std.fs.File.ReadError, cReaderRead);
extern "c" fn feof(stream: *std.c.FILE) c_int;

fn cReader(c_file: *std.c.FILE) CReader {
return .{ .context = c_file };
}
const CReader = struct {
file: *std.c.FILE,
interface: std.Io.Reader,

fn cReaderRead(c_file: *std.c.FILE, bytes: []u8) std.fs.File.ReadError!usize {
const amt_read = std.c.fread(bytes.ptr, 1, bytes.len, c_file);
if (amt_read >= 0) return amt_read;
switch (@as(std.os.E, @enumFromInt(std.c._errno().*))) {
.SUCCESS => unreachable,
.INVAL => unreachable,
.FAULT => unreachable,
.AGAIN => unreachable, // this is a blocking API
.BADF => unreachable, // always a race condition
.DESTADDRREQ => unreachable, // connect was never called
.DQUOT => return error.DiskQuota,
.FBIG => return error.FileTooBig,
.IO => return error.InputOutput,
.NOSPC => return error.NoSpaceLeft,
.PERM => return error.AccessDenied,
.PIPE => return error.BrokenPipe,
else => |err| return std.os.unexpectedErrno(err),
fn init(file: *std.c.FILE, buffer: []u8) CReader {
return .{
.file = file,
.interface = .{
.vtable = &.{ .stream = stream },
.buffer = buffer,
.seek = 0,
.end = 0,
},
};
}
}

fn stream(r: *std.Io.Reader, w: *std.Io.Writer, limit: std.Io.Limit) std.Io.Reader.StreamError!usize {
const creader: *CReader = @alignCast(@fieldParentPtr("interface", r));

if (limit == .nothing) return 0;
const dest = limit.slice(try w.writableSliceGreedy(1));

const n = std.c.fread(dest.ptr, 1, dest.len, creader.file);
if (n > 0) {
w.advance(n);
return n;
}

if (feof(creader.file) != 0) return error.EndOfStream;
return error.ReadFailed;
}
};
Loading