timetracker
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13
  14. 14
  15. 15
  16. 16
  17. 17
  18. 18
  19. 19
  20. 20
  21. 21
  22. 22
  23. 23
  24. 24
  25. 25
  26. 26
  27. 27
  28. 28
  29. 29
  30. 30
  31. 31
  32. 32
  33. 33
  34. 34
  35. 35
  36. 36
  37. 37
  38. 38
  39. 39
  40. 40
  41. 41
  42. 42
  43. 43
  44. 44
  45. 45
  46. 46
  47. 47
  48. 48
  49. 49
  50. 50
  51. 51
  52. 52
  53. 53
  54. 54
  55. 55
  56. 56
  57. 57
  58. 58
  59. 59
  60. 60
  61. 61
  62. 62
  63. 63
  64. 64
  65. 65
  66. 66
  67. 67
  68. 68
  69. 69
  70. 70
  71. 71
  72. 72
  73. 73
  74. 74
  75. 75
  76. 76
  77. 77
  78. 78
  79. 79
  80. 80
  81. 81
  82. 82
  83. 83
  84. 84
  85. 85
  86. 86
  87. 87
  88. 88
  89. 89
  90. 90
  91. 91
  92. 92
  93. 93
  94. 94
  95. 95
  96. 96
  97. 97
  98. 98
  99. 99
  100. 100
  101. 101
  102. 102
  103. 103
  104. 104
  105. 105
  106. 106
  107. 107
  108. 108
  109. 109
  110. 110
  111. 111
  112. 112
  113. 113
  114. 114
  115. 115
  116. 116
  117. 117
  118. 118
// 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");

const options = @import("./options.zig");

const Error = errors.CommandLineError ||
    events.Iterator.InitError || events.Iterator.IterateError || timer.FindError || std.mem.Allocator.Error || error{
    TimerNotFound,
};

fn printUsage(io: std.Io, file: std.Io.File) !void {
    var buf: [1024]u8 = undefined;
    var writer = file.writer(io, &buf);

    try writer.interface.writeAll(
        \\timetracker status - Print current status of a timer
        \\
        \\[USAGE]
        \\timetracker status [ID] [OPTIONS]
        \\
        \\[PARAMETERS]
        \\ID   An ID of the timer to print. If ID is not set,
        \\     this command prints the most recent updated timer.
        \\
        \\[OPTIONS]
        \\-h, --help   Print this message to stdout and exits.
        \\
        \\
    );

    try options.writePrintOptionsUsage(&writer.interface);

    try writer.flush();
}

pub fn run(args: *std.process.Args.Iterator, gpa: std.mem.Allocator, io: std.Io, opts: Options) Error!void {
    var maybe_id: ?[]const u8 = null;
    defer if (maybe_id) |slice| gpa.free(slice);

    var print_options_builder: options.PrintOptionsBuilder = .{};

    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;
        }

        switch (print_options_builder.handle(arg, args) catch |err| {
            switch (err) {
                errors.CommandLineError.InvalidUsage => {
                    printUsage(io, std.Io.File.stderr()) catch return Error.WriteToStderrFailed;
                    return err;
                },
                else => return err,
            }
        }) {
            .handled => continue,
            .unhandled => {},
        }

        if (maybe_id == null) {
            maybe_id = try gpa.dupe(u8, arg);
            continue;
        } else {
            std.log.err("You can't pass positional parameter more than once (ID_OR_TITLE is already set.)", .{});
            printUsage(io, std.Io.File.stderr()) catch return Error.WriteToStderrFailed;
            return errors.CommandLineError.InvalidUsage;
        }

        std.log.err("Unknown option: {s}", .{arg});
        return Error.InvalidUsage;
    }

    const print_opts = print_options_builder.build();

    var stdout = std.Io.File.stdout();
    defer stdout.close(io);

    var write_buf: [1024]u8 = undefined;
    var writer = stdout.writer(io, &write_buf);

    var manifest = try events.Manifest.open(gpa, io, &opts.app_dir);
    defer manifest.deinit(gpa);

    var iter = try events.Iterator.init(io, &manifest);

    const result = if (maybe_id) |id| try timer.findById(gpa, io, &iter, uuid.urn.deserialize(id) catch |err| {
        std.log.err("ID is not UUID: {t}", .{err});
        printUsage(io, std.Io.File.stderr()) catch return Error.WriteToStderrFailed;
        return errors.CommandLineError.InvalidUsage;
    }) else try timer.getLatest(gpa, io, &iter);

    const found = result orelse {
        std.log.err("Timer not found.", .{});
        return Error.TimerNotFound;
    };
    defer found.deinit(gpa);

    found.print(&writer.interface, print_opts) catch return Error.WriteToStdoutFailed;

    writer.flush() catch return Error.WriteToStdoutFailed;
}