gitbay/Issues/IssueFilter.swift
53 lines · 1931 bytes
1import Foundation
2
3/// What `issue list` is narrowed to. Renders itself to the flags the
4/// command accepts; an empty value sends no flag at all, since
5/// `--search ""` is a different query from no search.
6nonisolated struct IssueFilter: Equatable, Sendable {
7
8 enum State: String, CaseIterable, Identifiable, Sendable {
9 case open, closed, all
10 var id: String { rawValue }
11 var label: String { rawValue.capitalized }
12 }
13
14 var state: State = .open
15 var search = ""
16 var label: String?
17 var assignee: String?
18 var author: String?
19 /// A milestone title, or the literal "none" for issues without one.
20 var milestone: String?
21
22 func flags() -> [String] {
23 var flags = ["--state", state.rawValue]
24 flags += filterFlag("--search", search)
25 flags += filterFlag("--label", label)
26 flags += filterFlag("--assignee", assignee)
27 flags += filterFlag("--author", author)
28 flags += filterFlag("--milestone", milestone)
29 return flags
30 }
31
32 /// True when anything beyond the state picker is set, which is what
33 /// decides whether the empty state blames the filters.
34 var isNarrowed: Bool { flags().count > 2 }
35
36 var emptyMessage: String {
37 if isNarrowed {
38 return state == .all
39 ? "No issues match these filters."
40 : "No \(state.rawValue) issues match these filters."
41 }
42 return state == .all ? "No issues." : "No \(state.rawValue) issues."
43 }
44}
45
46/// One flag, or nothing when the value is absent or blank. Shared by
47/// both filters, so it is named for what it does rather than taking the
48/// bare name `flag` in the module namespace.
49nonisolated func filterFlag(_ name: String, _ value: String?) -> [String] {
50 guard let value else { return [] }
51 let trimmed = value.trimmingCharacters(in: .whitespaces)
52 return trimmed.isEmpty ? [] : [name, trimmed]
53}