Changes
3 changed files (+190/-1)
-
-
@@ -92,12 +92,17 @@ stdenvNoCC.mkDerivation (finalAttrs: {-a "delete" \ -d "Delete a timer from history" complete -c ${finalAttrs.pname} \ -n "not __fish_seen_subcommand_from $commands" \ -a "rename" \ -d "Rename timer title" complete -c ${finalAttrs.pname} \ -n "__fish_seen_subcommand_from stop" \ -a "(${finalAttrs.pname} list running -c id,title)" complete -c ${finalAttrs.pname} \ -n "__fish_seen_subcommand_from delete" \ -n "__fish_seen_subcommand_from delete rename" \ -a "(${finalAttrs.pname} list -c id,title)" complete -c ${finalAttrs.pname} \
-
-
-
@@ -0,0 +1,177 @@// Copyright 2026 Shota FUJI // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. // // SPDX-License-Identifier: MPL-2.0 const std = @import("std"); const uuid = @import("uuid"); const Options = @import("../Options.zig"); const errors = @import("../errors.zig"); const events = @import("../events.zig"); const timer = @import("../timer.zig"); pub const Error = errors.CommandLineError || events.Iterator.InitError || std.Io.Reader.LimitedAllocError || events.Envelope.AppendError || events.Manifest.SaveError || timer.TimerIterator.Error || std.mem.Allocator.Error || error{TimerNotFound}; fn printUsage(io: std.Io, file: std.Io.File) !void { var buf: [512]u8 = undefined; var writer = file.writer(io, &buf); try writer.interface.writeAll( \\timetracker rename - Rename timer title \\ \\[USAGE] \\timetracker rename [OPTIONS] <TITLE-OR-ID> <NEW-TITLE> \\ \\[PARAMETERS] \\TITLE-OR-ID Title text or ID of the timer to rename. \\NEW-TITLE New title text. \\ \\[OPTIONS] \\-h, --help Print this message to stdout and exits. \\ \\ ); try writer.flush(); } pub fn run(args: *std.process.Args.Iterator, gpa: std.mem.Allocator, io: std.Io, opts: Options) Error!void { var first_positional_arg: ?[]const u8 = null; defer if (first_positional_arg) |slice| gpa.free(slice); var second_positional_arg: ?[]const u8 = null; defer if (second_positional_arg) |slice| gpa.free(slice); while (args.next()) |arg| { if (std.mem.eql(u8, "-h", arg) or std.mem.eql(u8, "--help", arg)) { printUsage(io, std.Io.File.stdout()) catch |err| { std.log.err("Failed to print usage to stdout: {t}", .{err}); return Error.WriteToStdoutFailed; }; return; } if (first_positional_arg == null) { first_positional_arg = try gpa.dupe(u8, arg); continue; } if (second_positional_arg == null) { second_positional_arg = try gpa.dupe(u8, arg); continue; } std.log.err("You cannot set more than two positional arguments.", .{}); printUsage(io, std.Io.File.stderr()) catch return Error.WriteToStderrFailed; return Error.InvalidUsage; } if (second_positional_arg == null) { const stdin = std.Io.File.stdin(); if (!(try stdin.isTty(io))) { // TITLE-OR-ID needs to be exactly same to the target, so this will be better // fit for stdin, thus shifting. second_positional_arg = first_positional_arg; var read_buf: [128]u8 = undefined; var reader = stdin.reader(io, &read_buf); first_positional_arg = reader.interface.allocRemaining(gpa, .limited(256)) catch |err| { std.log.err("Unable to read from stdin: {t}", .{err}); return err; }; } } const title_or_id = if (first_positional_arg) |a| std.mem.trim(u8, a, " \n") else { std.log.err("TITLE-OR-ID is required", .{}); printUsage(io, std.Io.File.stderr()) catch return Error.WriteToStderrFailed; return Error.InvalidUsage; }; const new_title = if (second_positional_arg) |a| std.mem.trim(u8, a, " \n") else { std.log.err("NEW-TITLE is required", .{}); printUsage(io, std.Io.File.stderr()) catch return Error.WriteToStderrFailed; return Error.InvalidUsage; }; var manifest = try events.Manifest.open(gpa, io, &opts.app_dir); defer manifest.deinit(gpa); const maybe_id = uuid.urn.deserialize(title_or_id) catch null; var iter = try events.Iterator.init(io, &manifest); var timer_iter: timer.TimerIterator = .{ .events_iter = &iter }; defer timer_iter.deinit(gpa); while (try timer_iter.next(gpa, io)) |t| { defer t.deinit(gpa); const id, const title = switch (t) { .stopped => |stopped| .{ stopped.id, stopped.title }, .running => |running| .{ running.id, running.title }, }; const matches = matches: { if (maybe_id) |target_id| { if (id == target_id) { break :matches true; } } break :matches std.mem.eql(u8, title_or_id, title); }; if (!matches) { break; } const event_id = uuid.v4.new(io); const envelope: events.Envelope = .{ .parent = manifest.tail, .id = event_id, .event = .{ .timer_title_changed = .{ .event_id = event_id, .timer_id = id, .created_at = std.Io.Clock.real.now(io).toSeconds(), .new_title = new_title, } }, }; envelope.append(gpa, io, &manifest) catch |err| { std.log.err("Failed to save event file: {t}", .{err}); return err; }; manifest.tail = event_id; manifest.save(io) catch |err| { std.log.err("Failed to update manifest file: {t}", .{err}); return err; }; var stderr = std.Io.File.stderr(); defer stderr.close(io); var write_buf: [1024]u8 = undefined; var writer = stderr.writer(io, &write_buf); try writer.interface.print("Renamed \"{s}\" to \"{s}\"\n", .{ title, new_title }); try writer.interface.flush(); return; } std.log.err("No running timer matches to {s}", .{title_or_id}); return Error.TimerNotFound; }
-
-
-
@@ -12,6 +12,7 @@ const config = @import("config");const Options = @import("Options.zig"); const delete = @import("./commands/delete.zig"); const list = @import("./commands/list.zig"); const rename = @import("./commands/rename.zig"); const start = @import("./commands/start.zig"); const status = @import("./commands/status.zig"); const stop = @import("./commands/stop.zig");
-
@@ -41,6 +42,7 @@ fn printUsage(io: std.Io, file: std.Io.File) !void {\\start Start a new timer. \\stop Stop a running timer. \\delete Delete a timer from history. \\rename Rename timer title. \\ \\[OPTIONS] \\-h, --help Print this message to stdout and exits.
-
@@ -97,6 +99,11 @@ fn run(init: *const std.process.Init) !void {return; } if (std.mem.eql(u8, "rename", arg)) { try rename.run(&args, init.gpa, init.io, options); return; } std.log.err("Unknown option: {s}", .{arg}); printUsage(init.io, std.Io.File.stderr()) catch return CommandLineError.WriteToStderrFailed; return CommandLineError.InvalidUsage;
-