From 15af4a8387c0236da36575ac14c90a7c267ca8ea Mon Sep 17 00:00:00 2001 From: Marto Date: Sun, 9 Feb 2025 19:31:27 +0100 Subject: [PATCH] initial commit --- build.zig | 91 +++++++++++++++++++++++++++++++++++++++++++++++++ build.zig.zon | 72 ++++++++++++++++++++++++++++++++++++++ src/file_op.zig | 66 +++++++++++++++++++++++++++++++++++ src/main.zig | 60 ++++++++++++++++++++++++++++++++ 4 files changed, 289 insertions(+) create mode 100644 build.zig create mode 100644 build.zig.zon create mode 100644 src/file_op.zig create mode 100644 src/main.zig diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..ae53d19 --- /dev/null +++ b/build.zig @@ -0,0 +1,91 @@ +const std = @import("std"); + +// Although this function looks imperative, note that its job is to +// declaratively construct a build graph that will be executed by an external +// runner. +pub fn build(b: *std.Build) void { + // Standard target options allows the person running `zig build` to choose + // what target to build for. Here we do not override the defaults, which + // means any target is allowed, and the default is native. Other options + // for restricting supported target set are available. + const target = b.standardTargetOptions(.{}); + + // Standard optimization options allow the person running `zig build` to select + // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not + // set a preferred release mode, allowing the user to decide how to optimize. + const optimize = b.standardOptimizeOption(.{}); + + // const lib = b.addStaticLibrary(.{ + // .name = "todo", + // // In this case the main source file is merely a path, however, in more + // // complicated build scripts, this could be a generated file. + // .root_source_file = b.path("src/root.zig"), + // .target = target, + // .optimize = optimize, + // }); + + // This declares intent for the library to be installed into the standard + // location when the user invokes the "install" step (the default step when + // running `zig build`). + // b.installArtifact(lib); + + const exe = b.addExecutable(.{ + .name = "todo", + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + }); + + // This declares intent for the executable to be installed into the + // standard location when the user invokes the "install" step (the default + // step when running `zig build`). + b.installArtifact(exe); + + // This *creates* a Run step in the build graph, to be executed when another + // step is evaluated that depends on it. The next line below will establish + // such a dependency. + const run_cmd = b.addRunArtifact(exe); + + // By making the run step depend on the install step, it will be run from the + // installation directory rather than directly from within the cache directory. + // This is not necessary, however, if the application depends on other installed + // files, this ensures they will be present and in the expected location. + run_cmd.step.dependOn(b.getInstallStep()); + + // This allows the user to pass arguments to the application in the build + // command itself, like this: `zig build run -- arg1 arg2 etc` + if (b.args) |args| { + run_cmd.addArgs(args); + } + + // This creates a build step. It will be visible in the `zig build --help` menu, + // and can be selected like this: `zig build run` + // This will evaluate the `run` step rather than the default, which is "install". + const run_step = b.step("run", "Run the app"); + run_step.dependOn(&run_cmd.step); + + // Creates a step for unit testing. This only builds the test executable + // but does not run it. + // const lib_unit_tests = b.addTest(.{ + // .root_source_file = b.path("src/root.zig"), + // .target = target, + // .optimize = optimize, + // }); + + // const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests); + + const exe_unit_tests = b.addTest(.{ + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + }); + + const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests); + + // Similar to creating the run step earlier, this exposes a `test` step to + // the `zig build --help` menu, providing a way for the user to request + // running the unit tests. + const test_step = b.step("test", "Run unit tests"); + // test_step.dependOn(&run_lib_unit_tests.step); + test_step.dependOn(&run_exe_unit_tests.step); +} diff --git a/build.zig.zon b/build.zig.zon new file mode 100644 index 0000000..d834152 --- /dev/null +++ b/build.zig.zon @@ -0,0 +1,72 @@ +.{ + // This is the default name used by packages depending on this one. For + // example, when a user runs `zig fetch --save `, this field is used + // as the key in the `dependencies` table. Although the user can choose a + // different name, most users will stick with this provided value. + // + // It is redundant to include "zig" in this name because it is already + // within the Zig package namespace. + .name = "todo", + + // This is a [Semantic Version](https://semver.org/). + // In a future version of Zig it will be used for package deduplication. + .version = "0.0.0", + + // This field is optional. + // This is currently advisory only; Zig does not yet do anything + // with this value. + //.minimum_zig_version = "0.11.0", + + // This field is optional. + // Each dependency must either provide a `url` and `hash`, or a `path`. + // `zig build --fetch` can be used to fetch all dependencies of a package, recursively. + // Once all dependencies are fetched, `zig build` no longer requires + // internet connectivity. + .dependencies = .{ + // See `zig fetch --save ` for a command-line interface for adding dependencies. + //.example = .{ + // // When updating this field to a new URL, be sure to delete the corresponding + // // `hash`, otherwise you are communicating that you expect to find the old hash at + // // the new URL. + // .url = "https://example.com/foo.tar.gz", + // + // // This is computed from the file contents of the directory of files that is + // // obtained after fetching `url` and applying the inclusion rules given by + // // `paths`. + // // + // // This field is the source of truth; packages do not come from a `url`; they + // // come from a `hash`. `url` is just one of many possible mirrors for how to + // // obtain a package matching this `hash`. + // // + // // Uses the [multihash](https://multiformats.io/multihash/) format. + // .hash = "...", + // + // // When this is provided, the package is found in a directory relative to the + // // build root. In this case the package's hash is irrelevant and therefore not + // // computed. This field and `url` are mutually exclusive. + // .path = "foo", + + // // When this is set to `true`, a package is declared to be lazily + // // fetched. This makes the dependency only get fetched if it is + // // actually used. + // .lazy = false, + //}, + }, + + // Specifies the set of files and directories that are included in this package. + // Only files and directories listed here are included in the `hash` that + // is computed for this package. Only files listed here will remain on disk + // when using the zig package manager. As a rule of thumb, one should list + // files required for compilation plus any license(s). + // Paths are relative to the build root. Use the empty string (`""`) to refer to + // the build root itself. + // A directory listed here means that all files within, recursively, are included. + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + // For example... + //"LICENSE", + //"README.md", + }, +} diff --git a/src/file_op.zig b/src/file_op.zig new file mode 100644 index 0000000..096783e --- /dev/null +++ b/src/file_op.zig @@ -0,0 +1,66 @@ +const std = @import("std"); +const fs = std.fs; + +const FileRead = struct { + file: fs.File, + tasks: std.ArrayList([]u8), +}; + +pub fn readTasksFromFile(allocator: std.mem.Allocator, filename: []const u8) !FileRead { + var tasks = std.ArrayList([]u8).init(allocator); + + var file = fs.cwd().openFile(filename, .{ .mode = .read_write }) catch |err| { + if (err == error.FileNotFound) { + std.debug.print("File not found: {s}\n", .{filename}); + // todo needs to be closed eventually + const new_file = fs.cwd().createFile(filename, .{}) catch |e| { + std.debug.print("Failed to create the file: {}", .{e}); + return e; + }; + + return FileRead{ + .file = new_file, + .tasks = tasks, + }; + } else { + std.debug.print("Failed to open the file: {s}, error: {}\n", .{ filename, err }); + return err; + } + }; + + // wrap the file reader in a buffered reader + // since it's usually faster to read a bunch of bytes at once. + var buf_reader = std.io.bufferedReader(file.reader()); + const reader = buf_reader.reader(); + + var line = std.ArrayList(u8).init(allocator); + defer line.deinit(); + + const writer = line.writer(); + + while (reader.streamUntilDelimiter(writer, '\n', null)) { + // clear the line so we can reuse it + defer line.clearRetainingCapacity(); + + const copy = try allocator.dupe(u8, line.items); + try tasks.append(copy); + } else |err| switch (err) { + error.EndOfStream => { + // end of file + if (line.items.len > 0) { + const copy = try allocator.dupe(u8, line.items); + try tasks.append(copy); + } + }, + else => return err, //propagate error + } + + return FileRead{ .file = file, .tasks = tasks }; +} + +pub fn writeTasksToFile(file_system: FileRead) !void { + for (file_system.tasks.items) |item| { + try file_system.file.writeAll(item); + try file_system.file.writeAll("\n"); + } +} diff --git a/src/main.zig b/src/main.zig new file mode 100644 index 0000000..084342c --- /dev/null +++ b/src/main.zig @@ -0,0 +1,60 @@ +const std = @import("std"); +const file_op = @import("file_op.zig"); + +pub fn main() !void { + const filename = "todo-db.txt"; + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + const allocator = gpa.allocator(); + + var file_res = file_op.readTasksFromFile(allocator, filename) catch |err| { + std.debug.print("Failed read or create file, exiting ...", .{}); + return err; + }; + + defer { + for (file_res.tasks.items) |task| { + allocator.free(task); + } + file_res.tasks.deinit(); + file_res.file.close(); + } + + std.debug.print("Reading from file:\n", .{}); + for (file_res.tasks.items) |i| { + std.debug.print("{s}\n", .{i}); + } + + try userInteraction(); + + file_op.writeTasksToFile(file_res) catch |err| { + std.debug.print("Error while writing data to file:\n{}", .{err}); + }; +} + +fn userInteraction() !void { + const stdin = std.io.getStdIn().reader(); + const stdout = std.io.getStdOut().writer(); + // var buf: [100]u8 = undefined; + + while (true) { + try stdout.print("Please enter your choice add/remove/edit/quit: ", .{}); + const bare_line = try stdin.readUntilDelimiterAlloc(std.heap.page_allocator, '\n', 8192); + defer std.heap.page_allocator.free(bare_line); + // windows legacy + const line = std.mem.trim(u8, bare_line, "\r"); + std.debug.print("You entered: {s}\n", .{line}); + + const Case = enum { add, edit, delete, quit }; + const case = std.meta.stringToEnum(Case, line) orelse return; + + switch (case) { + .add => {}, + .edit => {}, + .delete => {}, + .quit => break, + } + + //break; + } +}