|
| 1 | +import ArgumentParser |
| 2 | +import Foundation |
| 3 | +import when |
| 4 | + |
| 5 | +/// `plan hours "..."` |
| 6 | +/// |
| 7 | +/// List spent/planned hours |
| 8 | +struct Hours: ParsableCommand { |
| 9 | + static var configuration = CommandConfiguration( |
| 10 | + abstract: "List spent/planned hours" |
| 11 | + ) |
| 12 | + |
| 13 | + @OptionGroup |
| 14 | + var opts: SharedOptions |
| 15 | + |
| 16 | + @Argument(help: "Date expression") |
| 17 | + var expression: String |
| 18 | + |
| 19 | + mutating func run() throws { |
| 20 | + let parser = DateParser(rules: EN.all + Common.all) |
| 21 | + var userDate: Date |
| 22 | + do { |
| 23 | + let result = try parser.parse(text: expression, base: Date()) |
| 24 | + userDate = result.date |
| 25 | + } catch { |
| 26 | + StdErr.print("Can't parse date") |
| 27 | + throw ExitCode.failure |
| 28 | + } |
| 29 | + |
| 30 | + let today = FCalendar.current.startOfDay(for: userDate) |
| 31 | + let start = FCalendar.current.date(byAdding: .day, value: 0, to: today)! |
| 32 | + let end = FCalendar.current.date(byAdding: .day, value: 1, to: today)! |
| 33 | + |
| 34 | + let orders = opts.sortBy.isEmpty ? [Order.Default] : opts.sortBy |
| 35 | + |
| 36 | + let eventSelector = EventSelector.Combined(selectors: [ |
| 37 | + // first sort |
| 38 | + EventSelector.Sorted(orders: orders), |
| 39 | + // then choose all |
| 40 | + EventSelector.All(), |
| 41 | + ] |
| 42 | + ) |
| 43 | + |
| 44 | + let events = Plan().events( |
| 45 | + start: start, end: end, opts: opts, |
| 46 | + selector: eventSelector, |
| 47 | + transformer: EventTransformer(rules: Loader.readConfig()?.iconize ?? []) |
| 48 | + ) |
| 49 | + let groups = Dictionary(grouping: events, by: { $0.title.full }) |
| 50 | + .mapValues { $0.reduce(into: 0) { $0 += $1.schedule.duration } } |
| 51 | + .mapValues { String(format: "%.2f", Double($0) / 60) } |
| 52 | + for group in groups { |
| 53 | + StdOut.print(group.key + ": " + group.value) |
| 54 | + } |
| 55 | + } |
| 56 | +} |
0 commit comments