{"id":13710424,"url":"https://github.com/karlseguin/zul","last_synced_at":"2025-04-05T05:04:43.021Z","repository":{"id":207574962,"uuid":"719571956","full_name":"karlseguin/zul","owner":"karlseguin","description":"zig utility library","archived":false,"fork":false,"pushed_at":"2024-10-04T15:00:08.000Z","size":342,"stargazers_count":146,"open_issues_count":0,"forks_count":5,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-10-29T16:01:07.690Z","etag":null,"topics":["uuidv4","uuidv7","zig","zig-package"],"latest_commit_sha":null,"homepage":"https://www.goblgobl.com/zul/","language":"Zig","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/karlseguin.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-11-16T13:04:09.000Z","updated_at":"2024-10-24T10:03:44.000Z","dependencies_parsed_at":"2024-03-23T02:39:37.407Z","dependency_job_id":"d99f2ee7-b00f-49b7-ae4c-42785c9d9ddf","html_url":"https://github.com/karlseguin/zul","commit_stats":null,"previous_names":["karlseguin/zul"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/karlseguin%2Fzul","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/karlseguin%2Fzul/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/karlseguin%2Fzul/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/karlseguin%2Fzul/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/karlseguin","download_url":"https://codeload.github.com/karlseguin/zul/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247289424,"owners_count":20914464,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["uuidv4","uuidv7","zig","zig-package"],"created_at":"2024-08-02T23:00:55.934Z","updated_at":"2025-04-05T05:04:43.002Z","avatar_url":"https://github.com/karlseguin.png","language":"Zig","funding_links":[],"categories":["Zig","Language Essentials"],"sub_categories":["Date, Time and Timezones"],"readme":"# Zig Utility Library\nThe purpose of this library is to enhance Zig's standard library. Much of zul wraps Zig's std to provide simpler APIs for common tasks (e.g. reading lines from a file). In other cases, new functionality has been added (e.g. a UUID type).\n\nBesides Zig's standard library, there are no dependencies. Most functionality is contained within its own file and can be copy and pasted into an existing library or project.\n\nFull documentation is available at: [https://www.goblgobl.com/zul/](https://www.goblgobl.com/zul/).\n\n(This readme is auto-generated from [docs/src/readme.njk](https://github.com/karlseguin/zul/blob/master/docs/src/readme.njk))\n\n## Usage\nIn your build.zig.zon add a reference to Zul:\n\n```zig\n.{\n  .name = \"my-app\",\n  .paths = .{\"\"},\n  .version = \"0.0.0\",\n  .dependencies = .{\n    .zul = .{\n      .url = \"https://github.com/karlseguin/zul/archive/master.tar.gz\",\n      .hash = \"$INSERT_HASH_HERE\"\n    },\n  },\n}\n```\n\nTo get the hash, run:\n\n```bash\nzig fetch https://github.com/karlseguin/zul/archive/master.tar.gz\n```\n\nInstead of `master` you can use a specific commit/tag.\n\nNext, in your `build.zig`, you should already have an executable, something like:\n\n```zig\nconst exe = b.addExecutable(.{\n    .name = \"my-app\",\n    .root_source_file = b.path(\"src/main.zig\"),\n    .target = target,\n    .optimize = optimize,\n});\n```\n\nAdd the following line:\n\n```zig\nexe.root_module.addImport(\"zul\", b.dependency(\"zul\", .{}).module(\"zul\"));\n```\n\nYou can now `const zul = @import(\"zul\");` in your project.\n\n## [zul.benchmark.run](https://www.goblgobl.com/zul/benchmark/)\nSimple benchmarking function.\n\n```zig\nconst HAYSTACK = \"abcdefghijklmnopqrstvuwxyz0123456789\";\n\npub fn main() !void {\n\t(try zul.benchmark.run(indexOfScalar, .{})).print(\"indexOfScalar\");\n\t(try zul.benchmark.run(lastIndexOfScalar, .{})).print(\"lastIndexOfScalar\");\n}\n\nfn indexOfScalar(_: Allocator, _: *std.time.Timer) !void {\n\tconst i = std.mem.indexOfScalar(u8, HAYSTACK, '9').?;\n\tif (i != 35) {\n\t\t@panic(\"fail\");\n\t}\n}\n\nfn lastIndexOfScalar(_: Allocator, _: *std.time.Timer) !void {\n\tconst i = std.mem.lastIndexOfScalar(u8, HAYSTACK, 'a').?;\n\tif (i != 0) {\n\t\t@panic(\"fail\");\n\t}\n}\n\n// indexOfScalar\n//   49882322 iterations   59.45ns per iterations\n//   worst: 167ns  median: 42ns    stddev: 20.66ns\n//\n// lastIndexOfScalar\n//   20993066 iterations   142.15ns per iterations\n//   worst: 292ns  median: 125ns   stddev: 23.13ns\n```\n\n## [zul.CommandLineArgs](https://www.goblgobl.com/zul/command_line_args/)\nA simple command line parser.\n\n```zig\nvar args = try zul.CommandLineArgs.parse(allocator);\ndefer args.deinit();\n\nif (args.contains(\"version\")) {\n\t//todo: print the version\n\tos.exit(0);\n}\n\n// Values retrieved from args.get are valid until args.deinit()\n// is called. Dupe the value if needed.\nconst host = args.get(\"host\") orelse \"127.0.0.1\";\n...\n```\n\n## [zul.DateTime](https://www.goblgobl.com/zul/datetime/)\nSimple (no leap seconds, UTC-only), DateTime, Date and Time types.\n\n```zig\n// Currently only supports RFC3339\nconst dt = try zul.DateTime.parse(\"2028-11-05T23:29:10Z\", .rfc3339);\nconst next_week = try dt.add(7, .days);\nstd.debug.assert(next_week.order(dt) == .gt);\n\n// 1857079750000 == 2028-11-05T23:29:10Z\nstd.debug.print(\"{d} == {s}\", .{dt.unix(.milliseconds), dt});\n```\n\n## [zul.fs.readDir](https://www.goblgobl.com/zul/fs/readdir/)\nIterates, non-recursively, through a directory.\n\n```zig\n// Parameters:\n// 1- Absolute or relative directory path\nvar it = try zul.fs.readDir(\"/tmp/dir\");\ndefer it.deinit();\n\n// can iterate through the files\nwhile (try it.next()) |entry| {\n\tstd.debug.print(\"{s} {any}\\n\", .{entry.name, entry.kind});\n}\n\n// reset the iterator\nit.reset();\n\n// or can collect them into a slice, optionally sorted:\nconst sorted_entries = try it.all(allocator, .dir_first);\nfor (sorted_entries) |entry| {\n\tstd.debug.print(\"{s} {any}\\n\", .{entry.name, entry.kind});\n}\n```\n\n## [zul.fs.readJson](https://www.goblgobl.com/zul/fs/readjson/)\nReads and parses a JSON file.\n\n```zig\n// Parameters:\n// 1- The type to parse the JSON data into\n// 2- An allocator\n// 3- Absolute or relative path\n// 4- std.json.ParseOptions\nconst managed_user = try zul.fs.readJson(User, allocator, \"/tmp/data.json\", .{});\n\n// readJson returns a zul.Managed(T)\n// managed_user.value is valid until managed_user.deinit() is called\ndefer managed_user.deinit();\nconst user = managed_user.value;\n```\n\n## [zul.fs.readLines](https://www.goblgobl.com/zul/fs/readlines/)\nIterate over the lines in a file.\n\n```zig\n// create a buffer large enough to hold the longest valid line\nvar line_buffer: [1024]u8 = undefined;\n\n// Parameters:\n// 1- an absolute or relative path to the file\n// 2- the line buffer\n// 3- options (here we're using the default)\nvar it = try zul.fs.readLines(\"/tmp/data.txt\", \u0026line_buffer, .{});\ndefer it.deinit();\n\nwhile (try it.next()) |line| {\n\t// line is only valid until the next call to\n\t// it.next() or it.deinit()\n\tstd.debug.print(\"line: {s}\\n\", .{line});\n}\n```\n\n## [zul.http.Client](https://www.goblgobl.com/zul/http/client/)\nA wrapper around std.http.Client to make it easier to create requests and consume responses.\n\n```zig\n// The client is thread-safe\nvar client = zul.http.Client.init(allocator);\ndefer client.deinit();\n\n// Not thread safe, method defaults to .GET\nvar req = try client.request(\"https://api.github.com/search/topics\");\ndefer req.deinit();\n\n// Set the querystring, can also be set in the URL passed to client.request\n// or a mix of setting in client.request and programmatically via req.query\ntry req.query(\"q\", \"zig\");\n\ntry req.header(\"Authorization\", \"Your Token\");\n\n// The lifetime of res is tied to req\nvar res = try req.getResponse(.{});\nif (res.status != 200) {\n\t// TODO: handle error\n\treturn;\n}\n\n// On success, this is a zul.Managed(SearchResult), its lifetime is detached\n// from the req, allowing it to outlive req.\nconst managed = try res.json(SearchResult, allocator, .{});\n\n// Parsing the JSON and creating SearchResult [probably] required some allocations.\n// Internally an arena was created to manage this from the allocator passed to\n// res.json.\ndefer managed.deinit();\n\nconst search_result = managed.value;\n```\n\n## [zul.JsonString](https://www.goblgobl.com/zul/json_string/)\nAllows the embedding of already-encoded JSON strings into objects in order to avoid double encoded values.\n\n```zig\nconst an_encoded_json_value = \"{\\\"over\\\": 9000}\";\nconst str = try std.json.stringifyAlloc(allocator, .{\n\t.name = \"goku\",\n\t.power = zul.jsonString(an_encoded_json_value),\n}, .{});\n```\n\n## [zul.pool](https://www.goblgobl.com/zul/pool/)\nA thread-safe object pool which will dynamically grow when empty and revert to the configured size.\n\n```zig\n// create a pool for our Expensive class.\n// Our Expensive class takes a special initializing context, here an usize which\n// we set to 10_000. This is just to pass data from the pool into Expensive.init\nvar pool = try zul.pool.Growing(Expensive, usize).init(allocator, 10_000, .{.count = 100});\ndefer pool.deinit();\n\n// acquire will either pick an item from the pool\n// if the pool is empty, it'll create a new one (hence, \"Growing\")\nvar exp1 = try pool.acquire();\ndefer pool.release(exp1);\n\n...\n\n// pooled object must have 3 functions\nconst Expensive = struct {\n\t// an init function\n\tpub fn init(allocator: Allocator, size: usize) !Expensive {\n\t\treturn .{\n\t\t\t// ...\n\t\t};\n\t}\n\n\t// a deinit method\n\tpub fn deinit(self: *Expensive) void {\n\t\t// ...\n\t}\n\n\t// a reset method, called when the item is released back to the pool\n\tpub fn reset(self: *Expensive) void {\n\t\t// ...\n\t}\n};\n```\n\n## [zul.Scheduler](https://www.goblgobl.com/zul/scheduler/)\nEphemeral thread-based task scheduler used to run tasks at a specific time.\n\n```zig\n// Where multiple types of tasks can be scheduled using the same schedule,\n// a tagged union is ideal.\nconst Task = union(enum) {\n\tsay: []const u8,\n\n\t// Whether T is a tagged union (as here) or another type, a public\n\t// run function must exist\n\tpub fn run(task: Task, ctx: void, at: i64) void {\n\t\t// the original time the task was scheduled for\n\t\t_ = at;\n\n\t\t// application-specific context that will be passed to each task\n\t\t_ ctx;\n\n\t\tswitch (task) {\n\t\t\t.say =\u003e |msg| {std.debug.print(\"{s}\\n\", .{msg}),\n\t\t}\n\t}\n}\n\n...\n\n// This example doesn't use a app-context, so we specify its\n// type as void\nvar s = zul.Scheduler(Task, void).init(allocator);\ndefer s.deinit();\n\n// Starts the scheduler, launching a new thread\n// We pass our context. Since we have a null context\n// we pass a null value, i.e. {}\ntry s.start({});\n\n// will run the say task in 5 seconds\ntry s.scheduleIn(.{.say = \"world\"}, std.time.ms_per_s * 5);\n\n// will run the say task in 100 milliseconds\ntry s.schedule(.{.say = \"hello\"},  std.time.milliTimestamp() + 100);\n```\n\n## [zul.sort](https://www.goblgobl.com/zul/sort/)\nHelpers for sorting strings and integers\n\n```zig\n// sorting strings based on their bytes\nvar values = [_][]const u8{\"ABC\", \"abc\", \"Dog\", \"Cat\", \"horse\", \"chicken\"};\nzul.sort.strings(\u0026values, .asc);\n\n// sort ASCII strings, ignoring case\nzul.sort.asciiIgnoreCase(\u0026values, .desc);\n\n// sort integers or floats\nvar numbers = [_]i32{10, -20, 33, 0, 2, 6};\nzul.sort.numbers(i32, \u0026numbers, .asc);\n```\n\n## [zul.StringBuilder](https://www.goblgobl.com/zul/string_builder/)\nEfficiently create/concat strings or binary data, optionally using a thread-safe pool with pre-allocated static buffers.\n\n```zig\n// StringBuilder can be used to efficiently concatenate strings\n// But it can also be used to craft binary payloads.\nvar sb = zul.StringBuilder.init(allocator);\ndefer sb.deinit();\n\n// We're going to generate a 4-byte length-prefixed message.\n// We don't know the length yet, so we'll skip 4 bytes\n// We get back a \"view\" which will let us backfill the length\nvar view = try sb.skip(4);\n\n// Writes a single byte\ntry sb.writeByte(10);\n\n// Writes a []const u8\ntry sb.write(\"hello\");\n\n// Using our view, which points to where the view was taken,\n// fill in the length.\nview.writeU32Big(@intCast(sb.len() - 4));\n\nstd.debug.print(\"{any}\\n\", .{sb.string()});\n// []u8{0, 0, 0, 6, 10, 'h', 'e', 'l', 'l', 'o'}\n```\n\n## [zul.testing](https://www.goblgobl.com/zul/testing/)\nHelpers for writing tests.\n\n```zig\nconst t = zul.testing;\n\ntest \"memcpy\" {\n\t// clear's the arena allocator\n\tdefer t.reset();\n\n\t// In addition to exposing std.testing.allocator as zul.testing.allocator\n\t// zul.testing.arena is an ArenaAllocator. An ArenaAllocator can\n\t// make managing test-specific allocations a lot simpler.\n\t// Just stick a `defer zul.testing.reset()` atop your test.\n\tvar buf = try t.arena.allocator().alloc(u8, 5);\n\n\t// unlike std.testing.expectEqual, zul's expectEqual\n\t// will coerce expected to actual's type, so this is valid:\n\ttry t.expectEqual(5, buf.len);\n\n\t@memcpy(buf[0..5], \"hello\");\n\n\t// zul's expectEqual also works with strings.\n\ttry t.expectEqual(\"hello\", buf);\n}\n```\n\n## [zul.ThreadPool](https://www.goblgobl.com/zul/thread_pool/)\nLightweight thread pool with back-pressure and zero allocations after initialization.\n\n```zig\nvar tp = try zul.ThreadPool(someTask).init(allocator, .{.count = 4, .backlog = 500});\ndefer tp.deinit(allocator);\n\n// This will block if the threadpool has 500 pending jobs\n// where 500 is the configured backlog\ntp.spawn(.{1, true});\n\n\nfn someTask(i: i32, allow: bool) void {\n\t// process\n}\n```\n\n## [zul.UUID](https://www.goblgobl.com/zul/uuid/)\nParse and generate version 4 and version 7 UUIDs.\n\n```zig\n// v4() returns a zul.UUID\nconst uuid1 = zul.UUID.v4();\n\n// toHex() returns a [36]u8\nconst hex = uuid1.toHex(.lower);\n\n// returns a zul.UUID (or an error)\nconst uuid2 = try zul.UUID.parse(\"761e3a9d-4f92-4e0d-9d67-054425c2b5c3\");\nstd.debug.print(\"{any}\\n\", uuid1.eql(uuid2));\n\n// create a UUIDv7\nconst uuid3 = zul.UUID.v7();\n\n// zul.UUID can be JSON serialized\ntry std.json.stringify(.{.id = uuid3}, .{}, writer);\n```\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkarlseguin%2Fzul","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkarlseguin%2Fzul","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkarlseguin%2Fzul/lists"}