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
  119. 119
  120. 120
  121. 121
// 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 config = @import("config");

const Options = @import("Options.zig");
const delete = @import("./commands/delete.zig");
const list = @import("./commands/list.zig");
const start = @import("./commands/start.zig");
const status = @import("./commands/status.zig");
const stop = @import("./commands/stop.zig");
const errors = @import("./errors.zig");

const CommandLineError = errors.CommandLineError;

test {
    _ = @import("./events.zig");
    _ = @import("./timer.zig");
}

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 - Timetracking CLI
        \\
        \\[USAGE]
        \\# CLI
        \\timetracker [OPTIONS] <COMMAND>
        \\
        \\[COMMANDS]
        \\list    List timers.
        \\status  Show a timer.
        \\start   Start a new timer.
        \\stop    Stop a running timer.
        \\delete  Delete a timer from history.
        \\
        \\[OPTIONS]
        \\-h, --help    Print this message to stdout and exits.
        \\
    );

    try writer.flush();
}

fn run(init: *const std.process.Init) !void {
    const options = Options.init(init.io, init.environ_map) catch |err| {
        std.log.err("Cannot open application directory: {t}", .{err});
        return error.CannotOpenDataDirectory;
    };
    defer options.deinit(init.io);

    var args = try init.minimal.args.iterateAllocator(init.gpa);
    defer args.deinit();

    // Skip the program name.
    _ = args.skip();

    while (args.next()) |arg| {
        if (std.mem.eql(u8, "-h", arg) or std.mem.eql(u8, "--help", arg)) {
            printUsage(init.io, std.Io.File.stdout()) catch |err| {
                std.log.err("Failed to print usage to stdout: {t}", .{err});
                return CommandLineError.WriteToStdoutFailed;
            };
            return;
        }

        if (std.mem.eql(u8, "list", arg)) {
            try list.run(&args, init.gpa, init.io, options);
            return;
        }

        if (std.mem.eql(u8, "status", arg)) {
            try status.run(&args, init.gpa, init.io, options);
            return;
        }

        if (std.mem.eql(u8, "start", arg)) {
            try start.run(&args, init.gpa, init.io, options);
            return;
        }

        if (std.mem.eql(u8, "stop", arg)) {
            try stop.run(&args, init.gpa, init.io, options);
            return;
        }

        if (std.mem.eql(u8, "delete", arg)) {
            try delete.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;
    }

    std.log.err("COMMAND is required.", .{});
    printUsage(init.io, std.Io.File.stderr()) catch return CommandLineError.WriteToStderrFailed;
    return CommandLineError.InvalidUsage;
}

pub fn main(init: std.process.Init) u8 {
    run(&init) catch |err| return switch (err) {
        CommandLineError.InvalidUsage => 3,
        error.TimerNotFound => 4,
        CommandLineError.WriteToStdoutFailed, CommandLineError.WriteToStderrFailed => 5,
        error.CannotOpenDataDirectory => 6,
        error.OutOfMemory => 9,
        else => 1,
    };

    return 0;
}