2 Commits

Author SHA1 Message Date
Tilo
1b5ad2bc40 Upgrade csvu to Zig 0.16 (#2)
* Port source to Zig 0.16

* Declare Zig 0.16 toolchain

* ci: add pr verification
2026-05-31 18:27:09 +02:00
65d157852a feat: some optimizations 2025-10-05 21:13:54 +02:00
8 changed files with 96 additions and 51 deletions

2
.gitattributes vendored Normal file
View File

@@ -0,0 +1,2 @@
*.zig text eol=lf
*.zig.zon text eol=lf

37
.github/workflows/ci.yaml vendored Normal file
View File

@@ -0,0 +1,37 @@
name: CI
on:
pull_request:
branches:
- master
push:
branches:
- master
permissions:
contents: read
jobs:
verify:
name: Format, Test, Build
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install Zig
uses: mlugg/setup-zig@v2
with:
version: 0.16.0
- name: Check formatting
run: zig fmt --check .
- name: Run tests
run: zig build test
- name: Build
run: zig build
- name: Build release mode
run: zig build -Doptimize=ReleaseSafe

View File

@@ -51,20 +51,19 @@ jobs:
- name: Install Zig - name: Install Zig
uses: mlugg/setup-zig@v2 uses: mlugg/setup-zig@v2
with: with:
version: 0.15.0 version: 0.16.0
- name: Build for ${{ matrix.triple }} - name: Build for ${{ matrix.triple }}
run: | run: |
zig build-exe "${SRC}" \ zig build \
-Dtarget=${{ matrix.triple }} \ -Dtarget=${{ matrix.triple }} \
-O ReleaseSafe \ -Doptimize=ReleaseSafe
-femit-bin="${BINARY_NAME}${{ matrix.ext }}"
- name: Package binary - name: Package binary
run: | run: |
mkdir -p artifacts mkdir -p artifacts
zip -j artifacts/"${BINARY_NAME}-${{ github.ref_name }}-${{ matrix.triple }}.zip" \ zip -j artifacts/"${BINARY_NAME}-${{ github.ref_name }}-${{ matrix.triple }}.zip" \
"${BINARY_NAME}${{ matrix.ext }}" "zig-out/bin/${BINARY_NAME}${{ matrix.ext }}"
- name: Upload release asset - name: Upload release asset
uses: actions/upload-release-asset@v1 uses: actions/upload-release-asset@v1

View File

@@ -86,7 +86,7 @@ The csvu is a dynamic CSV utility designed to streamline data handling. It effec
Before getting started with csvu, ensure your runtime environment meets the following requirements: Before getting started with csvu, ensure your runtime environment meets the following requirements:
- **Programming Language:** Zig - **Programming Language:** Zig
- **Zig Version:** 0.14.0 or later - **Zig Version:** 0.16.0 or later
### Installation ### Installation

View File

@@ -73,6 +73,16 @@ pub fn build(b: *std.Build) void {
const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests); const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests);
const csv_unit_tests = b.addTest(.{
.root_module = b.createModule(.{
.root_source_file = b.path("src/csv.zig"),
.target = target,
.optimize = optimize,
}),
});
const run_csv_unit_tests = b.addRunArtifact(csv_unit_tests);
const exe_unit_tests = b.addTest(.{ const exe_unit_tests = b.addTest(.{
.root_module = b.createModule(.{ .root_module = b.createModule(.{
.root_source_file = b.path("src/main.zig"), .root_source_file = b.path("src/main.zig"),
@@ -88,5 +98,6 @@ pub fn build(b: *std.Build) void {
// running the unit tests. // running the unit tests.
const test_step = b.step("test", "Run unit tests"); const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&run_lib_unit_tests.step); test_step.dependOn(&run_lib_unit_tests.step);
test_step.dependOn(&run_csv_unit_tests.step);
test_step.dependOn(&run_exe_unit_tests.step); test_step.dependOn(&run_exe_unit_tests.step);
} }

View File

@@ -17,7 +17,7 @@
// This field is optional. // This field is optional.
// This is currently advisory only; Zig does not yet do anything // This is currently advisory only; Zig does not yet do anything
// with this value. // with this value.
//.minimum_zig_version = "0.11.0", .minimum_zig_version = "0.16.0",
// This field is optional. // This field is optional.
// Each dependency must either provide a `url` and `hash`, or a `path`. // Each dependency must either provide a `url` and `hash`, or a `path`.

View File

@@ -1,5 +1,4 @@
const std = @import("std"); const std = @import("std");
const term = @import("term.zig");
const CsvError = error{ const CsvError = error{
NoDelimiterFound, NoDelimiterFound,
@@ -16,7 +15,7 @@ fn contains(arr: []const u8, target: u8) bool {
} }
pub fn determineDelimiter(str: []const u8) !u8 { pub fn determineDelimiter(str: []const u8) !u8 {
var allocator = std.heap.GeneralPurposeAllocator(.{}){}; var allocator = std.heap.DebugAllocator(.{}){};
defer _ = allocator.deinit(); defer _ = allocator.deinit();
const alloc = allocator.allocator(); const alloc = allocator.allocator();
@@ -60,6 +59,7 @@ const CsvFile = struct {
header: std.ArrayList([]const u8), header: std.ArrayList([]const u8),
entries: std.ArrayList(std.ArrayList([]const u8)), entries: std.ArrayList(std.ArrayList([]const u8)),
alloc: std.mem.Allocator, alloc: std.mem.Allocator,
p_lines: std.ArrayList([]const u8),
pub fn isValid(self: CsvFile) bool { pub fn isValid(self: CsvFile) bool {
const colNum = self.header.items.len; const colNum = self.header.items.len;
@@ -73,34 +73,35 @@ const CsvFile = struct {
} }
pub fn deinit(self: *CsvFile) void { pub fn deinit(self: *CsvFile) void {
for (self.header.items) |col| self.alloc.free(col); for (self.p_lines.items) |line| {
self.alloc.free(line);
}
self.p_lines.deinit(self.alloc);
self.header.deinit(self.alloc); self.header.deinit(self.alloc);
for (self.entries.items) |entry_c| { for (self.entries.items) |entry_c| {
var entry = entry_c; var entry = entry_c;
for (entry.items) |col| self.alloc.free(col);
entry.deinit(self.alloc); entry.deinit(self.alloc);
} }
self.entries.deinit(self.alloc); self.entries.deinit(self.alloc);
} }
}; };
pub fn printTable(file: CsvFile) !void { pub fn printTable(io: std.Io, file: CsvFile) !void {
var stdout_buf: [1024]u8 = undefined; var stdout_buf: [1024]u8 = undefined;
var stdout_writer = std.fs.File.stdout().writer(&stdout_buf); var stdout_writer = std.Io.File.stdout().writerStreaming(io, &stdout_buf);
var stdout = &stdout_writer.interface; var stdout = &stdout_writer.interface;
defer { defer {
_ = stdout.flush() catch null; _ = stdout.flush() catch null;
} }
var gpa = std.heap.GeneralPurposeAllocator(.{}){}; var gpa = std.heap.DebugAllocator(.{}){};
defer { defer {
_ = gpa.deinit(); _ = gpa.deinit();
} }
const alloc = gpa.allocator(); const alloc = gpa.allocator();
const dimensions = try term.getTerminalDimensions();
const col_nums = file.header.items.len; const col_nums = file.header.items.len;
const col_sizes = try alloc.alloc(usize, col_nums); const col_sizes = try alloc.alloc(usize, col_nums);
defer alloc.free(col_sizes); defer alloc.free(col_sizes);
@@ -117,8 +118,6 @@ pub fn printTable(file: CsvFile) !void {
} }
} }
_ = dimensions;
var complete_length = col_nums + 1; var complete_length = col_nums + 1;
for (col_sizes) |col_size| { for (col_sizes) |col_size| {
complete_length += col_size; complete_length += col_size;
@@ -142,7 +141,6 @@ pub fn printTable(file: CsvFile) !void {
} }
_ = try stdout.writeAll("\n"); _ = try stdout.writeAll("\n");
_ = try stdout.flush();
for (0..complete_length) |_| { for (0..complete_length) |_| {
try stdout.print("-", .{}); try stdout.print("-", .{});
@@ -150,7 +148,7 @@ pub fn printTable(file: CsvFile) !void {
try stdout.print("\n", .{}); try stdout.print("\n", .{});
for (file.entries.items) |entry| { for (file.entries.items) |entry| {
var out_line = std.ArrayList(u8){}; var out_line: std.ArrayList(u8) = .empty;
defer out_line.deinit(alloc); defer out_line.deinit(alloc);
try out_line.appendSlice(alloc, "|"); try out_line.appendSlice(alloc, "|");
@@ -169,7 +167,6 @@ pub fn printTable(file: CsvFile) !void {
try out_line.appendSlice(alloc, "\n"); try out_line.appendSlice(alloc, "\n");
_ = try stdout.writeAll(out_line.items); _ = try stdout.writeAll(out_line.items);
_ = try stdout.flush();
} }
for (0..complete_length) |_| { for (0..complete_length) |_| {
try stdout.print("-", .{}); try stdout.print("-", .{});
@@ -177,46 +174,44 @@ pub fn printTable(file: CsvFile) !void {
try stdout.print("\n", .{}); try stdout.print("\n", .{});
} }
pub fn loadFile(filepath: []const u8, alloc: std.mem.Allocator) !CsvFile { pub fn loadFile(io: std.Io, filepath: []const u8, alloc: std.mem.Allocator) !CsvFile {
var file_buf: [4096]u8 = undefined; var file_buf: [4096]u8 = undefined;
var file = try std.fs.cwd().openFile(filepath, .{ .mode = .read_write }); var file = try std.Io.Dir.cwd().openFile(io, filepath, .{ .mode = .read_write });
defer file.close(); defer file.close(io);
var file_reader = file.reader(&file_buf); var file_reader = file.readerStreaming(io, &file_buf);
const in_stream = &file_reader.interface; const in_stream = &file_reader.interface;
var readHeader = false; var readHeader = false;
var headerList: std.ArrayList([]const u8) = undefined; var headerList: std.ArrayList([]const u8) = undefined;
var entries = std.ArrayList(std.ArrayList([]const u8)){}; var entries: std.ArrayList(std.ArrayList([]const u8)) = .empty;
var lines: std.ArrayList([]const u8) = .empty;
var delimiter: u8 = ' '; var delimiter: u8 = ' ';
while (true) { while (true) {
const line = in_stream.takeDelimiterExclusive('\n') catch |err| { const line = in_stream.takeDelimiter('\n') catch |err| switch (err) {
if (err == error.EndOfStream) { error.ReadFailed => return file_reader.err.?,
break; else => return err,
}
return err;
}; };
if (line == null) break;
if (delimiter == ' ') { if (delimiter == ' ') {
delimiter = try determineDelimiter(line); delimiter = try determineDelimiter(line.?);
} }
const del = delimiter; const del = delimiter;
var entr = std.ArrayList([]const u8){}; var entr: std.ArrayList([]const u8) = .empty;
var splitIt = std.mem.splitSequence(u8, line, &[_]u8{del}); var splitIt = std.mem.splitSequence(u8, line.?, &[_]u8{del});
while (splitIt.next()) |part| { while (splitIt.next()) |part| {
const dest = try alloc.alloc(u8, part.len); const dest = try alloc.alloc(u8, part.len);
lines.append(alloc, dest) catch unreachable;
std.mem.copyForwards(u8, dest, part); std.mem.copyForwards(u8, dest, part);
const res = std.mem.trim(u8, dest, &[_]u8{ '\n', '\t', '\r', ' ' }); const res = std.mem.trim(u8, dest, &[_]u8{ '\n', '\t', '\r', ' ' });
const res2 = try alloc.alloc(u8, res.len); _ = try entr.append(alloc, res);
std.mem.copyForwards(u8, res2, res);
alloc.free(dest);
_ = try entr.append(alloc, res2);
} }
if (!readHeader) { if (!readHeader) {
@@ -227,17 +222,18 @@ pub fn loadFile(filepath: []const u8, alloc: std.mem.Allocator) !CsvFile {
_ = try entries.append(alloc, entr); _ = try entries.append(alloc, entr);
} }
return CsvFile{ .entries = entries, .header = headerList, .alloc = alloc }; return CsvFile{ .entries = entries, .header = headerList, .alloc = alloc, .p_lines = lines };
} }
test "Determine delimiter" { test "Determine delimiter" {
const del = try determineDelimiter("this,is,a,test"); const del = try determineDelimiter("this,is,a,test");
std.testing.expect(del == ','); try std.testing.expect(del == ',');
const del2 = try determineDelimiter("th#is; is,a; test; with,many;symbols"); const del2 = try determineDelimiter("th#is; is,a; test; with,many;symbols");
std.testing.expect(del2 == ';'); try std.testing.expect(del2 == ';');
determineDelimiter("This does not have an delimiter") catch |err| { try std.testing.expectError(
try std.testing.expect(err == CsvError.NoDelimiterFound); CsvError.NoDelimiterFound,
}; determineDelimiter("This does not have an delimiter"),
);
} }

View File

@@ -1,16 +1,16 @@
const std = @import("std"); const std = @import("std");
const csv = @import("csv.zig"); const csv = @import("csv.zig");
pub fn main() !void { pub fn main(init: std.process.Init) !void {
var stdout_buf: [1024]u8 = undefined; var stdout_buf: [1024]u8 = undefined;
var stdout_writer = std.fs.File.stdout().writer(&stdout_buf); var stdout_writer = std.Io.File.stdout().writerStreaming(init.io, &stdout_buf);
var stdout = &stdout_writer.interface; var stdout = &stdout_writer.interface;
var allocator = std.heap.GeneralPurposeAllocator(.{}){}; var allocator = std.heap.DebugAllocator(.{}){};
defer _ = allocator.deinit(); defer _ = allocator.deinit();
const alloc = allocator.allocator(); const alloc = allocator.allocator();
var args = try std.process.ArgIterator.initWithAllocator(alloc); var args = try init.minimal.args.iterateAllocator(alloc);
defer args.deinit(); defer args.deinit();
_ = args.next(); _ = args.next();
@@ -27,7 +27,7 @@ pub fn main() !void {
return; return;
} }
var file = try csv.loadFile(filepath, alloc); var file = try csv.loadFile(init.io, filepath, alloc);
defer file.deinit(); defer file.deinit();
const valid = file.isValid(); const valid = file.isValid();
@@ -35,6 +35,6 @@ pub fn main() !void {
_ = try stdout.flush(); _ = try stdout.flush();
try csv.printTable(file); try csv.printTable(init.io, file);
_ = try stdout.flush(); _ = try stdout.flush();
} }