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
  122. 122
  123. 123
  124. 124
  125. 125
// 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");

pub const Error = errors.CommandLineError || events.Manifest.OpenError || events.Iterator.InitError || events.Iterator.IterateError || std.mem.Allocator.Error;

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 list - Print running and stopped timers
        \\
        \\[USAGE]
        \\timetracker list [FILTER] [OPTIONS]
        \\
        \\[PARAMETERS]
        \\FILTER   Display timers only matches to this filter.
        \\         Available values: running, stopped
        \\
        \\[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 filter: ?timer.State = null;
    var print_options_builder: options.PrintOptionsBuilder = .{};

    arg_loop: 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 => {},
        }

        inline for (@typeInfo(timer.State).@"enum".fields) |field| {
            if (std.mem.eql(u8, field.name, arg)) {
                if (filter != null) {
                    std.log.err("You cannot set filter more than once", .{});
                    printUsage(io, std.Io.File.stderr()) catch return Error.WriteToStderrFailed;
                    return Error.InvalidUsage;
                }

                filter = @enumFromInt(field.value);
                continue :arg_loop;
            }
        }

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

    if (filter == .running) {
        const result = try timer.aggregate(timer.Aggregation.RunningOnly, gpa, io, &iter);
        defer result.deinit(gpa);

        for (result.running) |t| {
            timer.Timer.print(&.{ .running = t }, &writer.interface, print_opts) catch return Error.WriteToStdoutFailed;
        }
    } else {
        const result = try timer.aggregate(timer.Aggregation.Full, gpa, io, &iter);
        defer result.deinit(gpa);

        if (filter == null) {
            for (result.running) |t| {
                timer.Timer.print(&.{ .running = t }, &writer.interface, print_opts) catch return Error.WriteToStdoutFailed;
            }
        }

        for (result.stopped) |t| {
            timer.Timer.print(&.{ .stopped = t }, &writer.interface, print_opts) catch return Error.WriteToStdoutFailed;
        }
    }

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