From 3da1831fda4eb3afd2bcae10293253d8df66fd72 Mon Sep 17 00:00:00 2001 From: Nikita Bobko Date: Thu, 9 Apr 2026 17:35:57 +0200 Subject: [PATCH] Introduce ExitCode --- Sources/AppBundle/command/CmdIo.swift | 12 ++--- Sources/AppBundle/command/Command.swift | 17 +++---- .../command/impl/BalanceSizesCommand.swift | 6 +-- .../CloseAllWindowsButCurrentCommand.swift | 8 ++-- .../AppBundle/command/impl/CloseCommand.swift | 8 ++-- .../command/impl/ConfigCommand.swift | 4 +- .../command/impl/DebugWindowsCommand.swift | 12 ++--- .../command/impl/EnableCommand.swift | 6 +-- .../command/impl/ExecAndForgetCommand.swift | 4 +- .../impl/FlattenWorkspaceTreeCommand.swift | 6 +-- .../impl/FocusBackAndForthCommand.swift | 4 +- .../AppBundle/command/impl/FocusCommand.swift | 42 +++++++++--------- .../command/impl/FocusMonitorCommand.swift | 6 +-- .../command/impl/FullscreenCommand.swift | 8 ++-- .../command/impl/JoinWithCommand.swift | 6 +-- .../command/impl/LayoutCommand.swift | 22 +++++----- .../command/impl/ListAppsCommand.swift | 2 +- .../command/impl/ListExecEnvVarsCommand.swift | 4 +- .../command/impl/ListModesCommand.swift | 4 +- .../command/impl/ListMonitorsCommand.swift | 2 +- .../command/impl/ListWindowsCommand.swift | 4 +- .../command/impl/ListWorkspacesCommand.swift | 4 +- .../impl/MacosNativeFullscreenCommand.swift | 8 ++-- .../impl/MacosNativeMinimizeCommand.swift | 6 +-- .../AppBundle/command/impl/ModeCommand.swift | 4 +- .../AppBundle/command/impl/MoveCommand.swift | 44 +++++++++---------- .../command/impl/MoveMouseCommand.swift | 16 +++---- .../impl/MoveNodeToMonitorCommand.swift | 4 +- .../impl/MoveNodeToWorkspaceCommand.swift | 10 ++--- .../impl/MoveWorkspaceToMonitorCommand.swift | 8 ++-- .../command/impl/ReloadConfigCommand.swift | 4 +- .../command/impl/ResizeCommand.swift | 12 ++--- .../AppBundle/command/impl/SplitCommand.swift | 10 ++--- .../command/impl/SummonWorkspaceCommand.swift | 6 +-- .../AppBundle/command/impl/SwapCommand.swift | 16 +++---- .../command/impl/TriggerBindingCommand.swift | 6 +-- .../command/impl/VolumeCommand.swift | 4 +- .../impl/WorkspaceBackAndForthCommand.swift | 4 +- .../command/impl/WorkspaceCommand.swift | 10 ++--- Sources/AppBundle/server.swift | 2 +- .../command/FocusCommandTest.swift | 12 ++--- .../command/ListModesTest.swift | 10 ++--- .../command/MoveCommandTest.swift | 12 ++--- Sources/Common/cmdArgs/ExitCode.swift | 36 +++++++++++++++ .../cmdArgs/impl/TriggerBindingCmdArgs.swift | 1 + Sources/Common/cmdArgs/parseCmdArgs.swift | 1 + 46 files changed, 239 insertions(+), 198 deletions(-) create mode 100644 Sources/Common/cmdArgs/ExitCode.swift diff --git a/Sources/AppBundle/command/CmdIo.swift b/Sources/AppBundle/command/CmdIo.swift index a19e8741..8bdb03a4 100644 --- a/Sources/AppBundle/command/CmdIo.swift +++ b/Sources/AppBundle/command/CmdIo.swift @@ -1,3 +1,5 @@ +import Common + struct CmdStdin: ~Copyable { private var input: String = "" init(_ input: String) { @@ -19,11 +21,11 @@ final class CmdIo { init(stdin: consuming CmdStdin) { self.stdin = stdin } - @discardableResult func out(_ msg: String) -> Bool { stdout.append(msg); return true } - @discardableResult func err(_ msg: String) -> Bool { stderr.append(msg); return false } - @discardableResult func out(_ msg: [String]) -> Bool { stdout += msg; return true } + @discardableResult func out(_ msg: String, _ ret: T = BinaryExitCode.succ) -> T { stdout.append(msg); return ret } + @discardableResult func err(_ msg: String, _ ret: T = BinaryExitCode.fail) -> T { stderr.append(msg); return ret } + @discardableResult func out(_ msg: [String], _ ret: T = BinaryExitCode.succ) -> T { stdout += msg; return ret } // periphery:ignore - @discardableResult func err(_ msg: [String]) -> Bool { stderr += msg; return false } + @discardableResult func err(_ msg: [String], _ ret: T = BinaryExitCode.fail) -> T { stderr += msg; return ret } func readStdin() -> String { stdin.readAll() } } @@ -31,5 +33,5 @@ final class CmdIo { struct CmdResult { let stdout: [String] let stderr: [String] - let exitCode: Int32 + let exitCode: Int32ExitCode } diff --git a/Sources/AppBundle/command/Command.swift b/Sources/AppBundle/command/Command.swift index 5d786be6..3221cfdd 100644 --- a/Sources/AppBundle/command/Command.swift +++ b/Sources/AppBundle/command/Command.swift @@ -2,10 +2,11 @@ import AppKit import Common protocol Command: AeroAny, Equatable, Sendable { - associatedtype T where T: CmdArgs + associatedtype T: CmdArgs + var args: T { get } @MainActor - func run(_ env: CmdEnv, _ io: CmdIo) async throws -> Bool + func run(_ env: CmdEnv, _ io: CmdIo) async throws -> T.ExitCodeType /// We should reset closedWindowsCache when the command can potentiall change the tree var shouldResetClosedWindowsCache: Bool { get } @@ -42,20 +43,20 @@ extension Command { // 4. Tray icon buttons extension [Command] { @MainActor - func runCmdSeq(_ env: CmdEnv, _ io: sending CmdIo) async throws -> Bool { - var isSucc = true + func runCmdSeq(_ env: CmdEnv, _ io: sending CmdIo) async throws -> Int32ExitCode { + var exitCode = Int32ExitCode(rawValue: 0) for command in self { - isSucc = try await command.run(env, io) + exitCode = Int32ExitCode(rawValue: (try await command.run(env, io)).rawValue) if command.shouldResetClosedWindowsCache { resetClosedWindowsCache() } refreshModel() } - return isSucc + return exitCode } @MainActor func runCmdSeq(_ env: CmdEnv, _ stdin: consuming CmdStdin) async throws -> CmdResult { let io: CmdIo = CmdIo(stdin: stdin) - let isSucc = try await runCmdSeq(env, io) - return CmdResult(stdout: io.stdout, stderr: io.stderr, exitCode: isSucc ? 0 : 1) + let exitCode = try await runCmdSeq(env, io) + return CmdResult(stdout: io.stdout, stderr: io.stderr, exitCode: exitCode) } } diff --git a/Sources/AppBundle/command/impl/BalanceSizesCommand.swift b/Sources/AppBundle/command/impl/BalanceSizesCommand.swift index 2b2a581f..0916afe0 100644 --- a/Sources/AppBundle/command/impl/BalanceSizesCommand.swift +++ b/Sources/AppBundle/command/impl/BalanceSizesCommand.swift @@ -6,10 +6,10 @@ struct BalanceSizesCommand: Command { let args: BalanceSizesCmdArgs /*conforms*/ let shouldResetClosedWindowsCache = false - func run(_ env: CmdEnv, _ io: CmdIo) -> Bool { - guard let target = args.resolveTargetOrReportError(env, io) else { return false } + func run(_ env: CmdEnv, _ io: CmdIo) -> BinaryExitCode { + guard let target = args.resolveTargetOrReportError(env, io) else { return .fail } balance(target.workspace.rootTilingContainer) - return true + return .succ } } diff --git a/Sources/AppBundle/command/impl/CloseAllWindowsButCurrentCommand.swift b/Sources/AppBundle/command/impl/CloseAllWindowsButCurrentCommand.swift index 06e74edb..10ec78cf 100644 --- a/Sources/AppBundle/command/impl/CloseAllWindowsButCurrentCommand.swift +++ b/Sources/AppBundle/command/impl/CloseAllWindowsButCurrentCommand.swift @@ -5,17 +5,17 @@ struct CloseAllWindowsButCurrentCommand: Command { let args: CloseAllWindowsButCurrentCmdArgs /*conforms*/ let shouldResetClosedWindowsCache = false - func run(_ env: CmdEnv, _ io: CmdIo) async throws -> Bool { - guard let target = args.resolveTargetOrReportError(env, io) else { return false } + func run(_ env: CmdEnv, _ io: CmdIo) async throws -> BinaryExitCode { + guard let target = args.resolveTargetOrReportError(env, io) else { return .fail } guard let focused = target.windowOrNil else { return io.err("Empty workspace") } guard let workspace = focused.nodeWorkspace else { return io.err("Focused window '\(focused.windowId)' doesn't belong to workspace") } - var result = true + var result = BinaryExitCode.succ for window in workspace.allLeafWindowsRecursive where window != focused { - result = try await CloseCommand(args: args.closeArgs).run(env.copy(\.windowId, window.windowId), io) && result + result = try await CloseCommand(args: args.closeArgs).run(env.copy(\.windowId, window.windowId), io).and(result) } return result } diff --git a/Sources/AppBundle/command/impl/CloseCommand.swift b/Sources/AppBundle/command/impl/CloseCommand.swift index 537f1794..89b197e5 100644 --- a/Sources/AppBundle/command/impl/CloseCommand.swift +++ b/Sources/AppBundle/command/impl/CloseCommand.swift @@ -5,9 +5,9 @@ struct CloseCommand: Command { let args: CloseCmdArgs /*conforms*/ let shouldResetClosedWindowsCache = false - func run(_ env: CmdEnv, _ io: CmdIo) async throws -> Bool { + func run(_ env: CmdEnv, _ io: CmdIo) async throws -> BinaryExitCode { try await allowOnlyCancellationError { @MainActor @Sendable in - guard let target = args.resolveTargetOrReportError(env, io) else { return false } + guard let target = args.resolveTargetOrReportError(env, io) else { return .fail } guard let window = target.windowOrNil else { return io.err("Empty workspace") } @@ -20,13 +20,13 @@ struct CloseCommand: Command { (window as! MacWindow).garbageCollect(skipClosedWindowsCache: true) } } - return true + return .succ } else { return io.err("Failed to quit '\(window.app.name ?? "Unknown app")'") } } else { window.closeAxWindow() - return true + return .succ } } } diff --git a/Sources/AppBundle/command/impl/ConfigCommand.swift b/Sources/AppBundle/command/impl/ConfigCommand.swift index 190d22c2..bafc3ffc 100644 --- a/Sources/AppBundle/command/impl/ConfigCommand.swift +++ b/Sources/AppBundle/command/impl/ConfigCommand.swift @@ -5,7 +5,7 @@ struct ConfigCommand: Command { let args: ConfigCmdArgs /*conforms*/ let shouldResetClosedWindowsCache = false - func run(_ env: CmdEnv, _ io: CmdIo) -> Bool { + func run(_ env: CmdEnv, _ io: CmdIo) -> BinaryExitCode { switch args.mode { case .getKey(let key): return getKey(io, args: args, key: key) @@ -37,7 +37,7 @@ extension String { } } -@MainActor private func getKey(_ io: CmdIo, args: ConfigCmdArgs, key: String) -> Bool { +@MainActor private func getKey(_ io: CmdIo, args: ConfigCmdArgs, key: String) -> BinaryExitCode { let keyPath: [String] switch key.toKeyPath() { case .success(let _keyPath): keyPath = _keyPath diff --git a/Sources/AppBundle/command/impl/DebugWindowsCommand.swift b/Sources/AppBundle/command/impl/DebugWindowsCommand.swift index 2262098f..5054c111 100644 --- a/Sources/AppBundle/command/impl/DebugWindowsCommand.swift +++ b/Sources/AppBundle/command/impl/DebugWindowsCommand.swift @@ -23,14 +23,14 @@ struct DebugWindowsCommand: Command { let args: DebugWindowsCmdArgs /*conforms*/ let shouldResetClosedWindowsCache = false - func run(_ env: CmdEnv, _ io: CmdIo) async throws -> Bool { + func run(_ env: CmdEnv, _ io: CmdIo) async throws -> BinaryExitCode { if let windowId = args.windowId { guard let window = Window.get(byId: windowId) else { return io.err("Can't find window with the specified window-id: \(windowId)") } io.out(try await dumpWindowDebugInfo(window) + "\n") io.out(disclaimer) - return true + return .succ } switch debugWindowsState { case .recording: @@ -39,7 +39,7 @@ struct DebugWindowsCommand: Command { io.out("\n" + disclaimer + "\n") io.out("Debug session finished" + "\n") debugWindowsLog = [:] - return true + return .succ case .notRecording: debugWindowsState = .recording debugWindowsLog = [:] @@ -51,11 +51,11 @@ struct DebugWindowsCommand: Command { """, ) // Make sure that the Terminal window that started the recording is recorded first - guard let target = args.resolveTargetOrReportError(env, io) else { return false } + guard let target = args.resolveTargetOrReportError(env, io) else { return .fail } if let window = target.windowOrNil { try await debugWindowsIfRecording(window) } - return true + return .succ case .recordingAborted: io.out( """ @@ -65,7 +65,7 @@ struct DebugWindowsCommand: Command { ) debugWindowsState = .notRecording debugWindowsLog = [:] - return false + return .fail } } } diff --git a/Sources/AppBundle/command/impl/EnableCommand.swift b/Sources/AppBundle/command/impl/EnableCommand.swift index c50aea2d..ef8d2e4f 100644 --- a/Sources/AppBundle/command/impl/EnableCommand.swift +++ b/Sources/AppBundle/command/impl/EnableCommand.swift @@ -5,7 +5,7 @@ struct EnableCommand: Command { let args: EnableCmdArgs /*conforms*/ let shouldResetClosedWindowsCache = false - func run(_ env: CmdEnv, _ io: CmdIo) async throws -> Bool { + func run(_ env: CmdEnv, _ io: CmdIo) async throws -> BinaryExitCode { let prevState = TrayMenuModel.shared.isEnabled let newState: Bool = switch args.targetState.val { case .on: true @@ -17,7 +17,7 @@ struct EnableCommand: Command { io.out((newState ? "Already enabled" : "Already disabled") + "Tip: use --fail-if-noop to exit with non-zero code") } - return !args.failIfNoop + return .from(bool: !args.failIfNoop) } TrayMenuModel.shared.isEnabled = newState @@ -31,6 +31,6 @@ struct EnableCommand: Command { } else { try await activateMode(nil) } - return true + return .succ } } diff --git a/Sources/AppBundle/command/impl/ExecAndForgetCommand.swift b/Sources/AppBundle/command/impl/ExecAndForgetCommand.swift index f2c0bf94..8843b0be 100644 --- a/Sources/AppBundle/command/impl/ExecAndForgetCommand.swift +++ b/Sources/AppBundle/command/impl/ExecAndForgetCommand.swift @@ -5,13 +5,13 @@ struct ExecAndForgetCommand: Command { let args: ExecAndForgetCmdArgs /*conforms*/ let shouldResetClosedWindowsCache = false - func run(_ env: CmdEnv, _ io: CmdIo) -> Bool { + func run(_ env: CmdEnv, _ io: CmdIo) -> BinaryExitCode { // todo shall exec-and-forget fork exec session? // It doesn't throw if exit code is non-zero let process = Process() process.environment = config.execConfig.envVariables + env.asMap process.executableURL = URL(filePath: "/bin/bash") process.arguments = ["-c", args.bashScript] - return Result { try process.run() }.isSuccess + return .from(bool: Result { try process.run() }.isSuccess) } } diff --git a/Sources/AppBundle/command/impl/FlattenWorkspaceTreeCommand.swift b/Sources/AppBundle/command/impl/FlattenWorkspaceTreeCommand.swift index 9d2f593c..42c58996 100644 --- a/Sources/AppBundle/command/impl/FlattenWorkspaceTreeCommand.swift +++ b/Sources/AppBundle/command/impl/FlattenWorkspaceTreeCommand.swift @@ -5,13 +5,13 @@ struct FlattenWorkspaceTreeCommand: Command { let args: FlattenWorkspaceTreeCmdArgs /*conforms*/ let shouldResetClosedWindowsCache: Bool = true - func run(_ env: CmdEnv, _ io: CmdIo) -> Bool { - guard let target = args.resolveTargetOrReportError(env, io) else { return false } + func run(_ env: CmdEnv, _ io: CmdIo) -> BinaryExitCode { + guard let target = args.resolveTargetOrReportError(env, io) else { return .fail } let workspace = target.workspace let windows = workspace.rootTilingContainer.allLeafWindowsRecursive for window in windows { window.bind(to: workspace.rootTilingContainer, adaptiveWeight: 1, index: INDEX_BIND_LAST) } - return true + return .succ } } diff --git a/Sources/AppBundle/command/impl/FocusBackAndForthCommand.swift b/Sources/AppBundle/command/impl/FocusBackAndForthCommand.swift index ddf74b5c..2c022370 100644 --- a/Sources/AppBundle/command/impl/FocusBackAndForthCommand.swift +++ b/Sources/AppBundle/command/impl/FocusBackAndForthCommand.swift @@ -5,9 +5,9 @@ struct FocusBackAndForthCommand: Command { let args: FocusBackAndForthCmdArgs /*conforms*/ let shouldResetClosedWindowsCache = false - func run(_ env: CmdEnv, _ io: CmdIo) -> Bool { + func run(_ env: CmdEnv, _ io: CmdIo) -> BinaryExitCode { switch prevFocus { - case let prevFocus?: setFocus(to: prevFocus) + case let prevFocus?: .from(bool: setFocus(to: prevFocus)) case nil: io.err("Prev window has been closed") } } diff --git a/Sources/AppBundle/command/impl/FocusCommand.swift b/Sources/AppBundle/command/impl/FocusCommand.swift index b1d4b2cc..2ed64b0c 100644 --- a/Sources/AppBundle/command/impl/FocusCommand.swift +++ b/Sources/AppBundle/command/impl/FocusCommand.swift @@ -5,8 +5,8 @@ struct FocusCommand: Command { let args: FocusCmdArgs /*conforms*/ let shouldResetClosedWindowsCache = false - func run(_ env: CmdEnv, _ io: CmdIo) async throws -> Bool { - guard let target = args.resolveTargetOrReportError(env, io) else { return false } + func run(_ env: CmdEnv, _ io: CmdIo) async throws -> BinaryExitCode { + guard let target = args.resolveTargetOrReportError(env, io) else { return .fail } // todo bug: floating windows break mru let floatingWindows = args.floatingAsTiling ? try await makeFloatingWindowsSeenAsTiling(workspace: target.workspace) : [] defer { @@ -20,27 +20,27 @@ struct FocusCommand: Command { let window = target.windowOrNil if let (parent, ownIndex) = window?.closestParent(hasChildrenInDirection: direction, withLayout: nil) { guard let windowToFocus = parent.children[ownIndex + direction.focusOffset] - .findLeafWindowRecursive(snappedTo: direction.opposite) else { return false } - return windowToFocus.focusWindow() + .findLeafWindowRecursive(snappedTo: direction.opposite) else { return .fail } + return .from(bool: windowToFocus.focusWindow()) } else { return hitWorkspaceBoundaries(target, io, args, direction) } case .windowId(let windowId): if let windowToFocus = Window.get(byId: windowId) { - return windowToFocus.focusWindow() + return .from(bool: windowToFocus.focusWindow()) } else { return io.err("Can't find window with ID \(windowId)") } case .dfsIndex(let dfsIndex): if let windowToFocus = target.workspace.rootTilingContainer.allLeafWindowsRecursive.getOrNil(atIndex: Int(dfsIndex)) { - return windowToFocus.focusWindow() + return .from(bool: windowToFocus.focusWindow()) } else { return io.err("Can't find window with DFS index \(dfsIndex)") } case .dfsRelative(let nextPrev): let windows = target.workspace.rootTilingContainer.allLeafWindowsRecursive guard let currentIndex = windows.firstIndex(where: { $0 == target.windowOrNil }) else { - return false + return .fail } var targetIndex = switch nextPrev { case .dfsNext: currentIndex + 1 @@ -48,13 +48,13 @@ struct FocusCommand: Command { } if !(0 ..< windows.count).contains(targetIndex) { switch args.boundariesAction { - case .stop: return true - case .fail: return false + case .stop: return .succ + case .fail: return .fail case .wrapAroundTheWorkspace: targetIndex = (targetIndex + windows.count) % windows.count case .wrapAroundAllMonitors: return dieT("Must be discarded by args parser") } } - return windows[targetIndex].focusWindow() + return .from(bool: windows[targetIndex].focusWindow()) } } } @@ -64,12 +64,12 @@ struct FocusCommand: Command { _ io: CmdIo, _ args: FocusCmdArgs, _ direction: CardinalDirection, -) -> Bool { +) -> BinaryExitCode { switch args.boundaries { case .workspace: return switch args.boundariesAction { - case .stop: true - case .fail: false + case .stop: .succ + case .fail: .fail case .wrapAroundTheWorkspace: wrapAroundTheWorkspace(target, io, direction) case .wrapAroundAllMonitors: dieT("Must be discarded by args parser") } @@ -80,9 +80,9 @@ struct FocusCommand: Command { } if let targetMonitor = monitors.getOrNil(atIndex: index) { - return targetMonitor.activeWorkspace.focusWorkspace() + return .from(bool: targetMonitor.activeWorkspace.focusWorkspace()) } else { - guard let wrapped = monitors.get(wrappingIndex: index) else { return false } + guard let wrapped = monitors.get(wrappingIndex: index) else { return .fail } return hitAllMonitorsOuterFrameBoundaries(target, io, args, direction, wrapped) } } @@ -94,25 +94,25 @@ struct FocusCommand: Command { _ args: FocusCmdArgs, _ direction: CardinalDirection, _ wrappedMonitor: Monitor, -) -> Bool { +) -> BinaryExitCode { switch args.boundariesAction { case .stop: - return true + return .succ case .fail: - return false + return .fail case .wrapAroundTheWorkspace: return wrapAroundTheWorkspace(target, io, direction) case .wrapAroundAllMonitors: wrappedMonitor.activeWorkspace.findLeafWindowRecursive(snappedTo: direction.opposite)?.markAsMostRecentChild() - return wrappedMonitor.activeWorkspace.focusWorkspace() + return .from(bool: wrappedMonitor.activeWorkspace.focusWorkspace()) } } -@MainActor private func wrapAroundTheWorkspace(_ target: LiveFocus, _ io: CmdIo, _ direction: CardinalDirection) -> Bool { +@MainActor private func wrapAroundTheWorkspace(_ target: LiveFocus, _ io: CmdIo, _ direction: CardinalDirection) -> BinaryExitCode { guard let windowToFocus = target.workspace.findLeafWindowRecursive(snappedTo: direction.opposite) else { return io.err(noWindowIsFocused) } - return windowToFocus.focusWindow() + return .from(bool: windowToFocus.focusWindow()) } @MainActor private func makeFloatingWindowsSeenAsTiling(workspace: Workspace) async throws -> [FloatingWindowData] { diff --git a/Sources/AppBundle/command/impl/FocusMonitorCommand.swift b/Sources/AppBundle/command/impl/FocusMonitorCommand.swift index 7005f022..295ab550 100644 --- a/Sources/AppBundle/command/impl/FocusMonitorCommand.swift +++ b/Sources/AppBundle/command/impl/FocusMonitorCommand.swift @@ -5,10 +5,10 @@ struct FocusMonitorCommand: Command { let args: FocusMonitorCmdArgs /*conforms*/ let shouldResetClosedWindowsCache = false - func run(_ env: CmdEnv, _ io: CmdIo) -> Bool { - guard let target = args.resolveTargetOrReportError(env, io) else { return false } + func run(_ env: CmdEnv, _ io: CmdIo) -> BinaryExitCode { + guard let target = args.resolveTargetOrReportError(env, io) else { return .fail } return switch args.target.val.resolve(target.workspace.workspaceMonitor, wrapAround: args.wrapAround) { - case .success(let targetMonitor): targetMonitor.activeWorkspace.focusWorkspace() + case .success(let targetMonitor): .from(bool: targetMonitor.activeWorkspace.focusWorkspace()) case .failure(let msg): io.err(msg) } } diff --git a/Sources/AppBundle/command/impl/FullscreenCommand.swift b/Sources/AppBundle/command/impl/FullscreenCommand.swift index e4e45061..aa6350fb 100644 --- a/Sources/AppBundle/command/impl/FullscreenCommand.swift +++ b/Sources/AppBundle/command/impl/FullscreenCommand.swift @@ -5,8 +5,8 @@ struct FullscreenCommand: Command { let args: FullscreenCmdArgs /*conforms*/ let shouldResetClosedWindowsCache = false - func run(_ env: CmdEnv, _ io: CmdIo) -> Bool { - guard let target = args.resolveTargetOrReportError(env, io) else { return false } + func run(_ env: CmdEnv, _ io: CmdIo) -> BinaryExitCode { + guard let target = args.resolveTargetOrReportError(env, io) else { return .fail } guard let window = target.windowOrNil else { return io.err(noWindowIsFocused) } @@ -18,14 +18,14 @@ struct FullscreenCommand: Command { if newState == window.isFullscreen { io.err((newState ? "Already fullscreen. " : "Already not fullscreen. ") + "Tip: use --fail-if-noop to exit with non-zero code") - return !args.failIfNoop + return .from(bool: !args.failIfNoop) } window.isFullscreen = newState window.noOuterGapsInFullscreen = args.noOuterGaps // Focus on its own workspace window.markAsMostRecentChild() - return true + return .succ } } diff --git a/Sources/AppBundle/command/impl/JoinWithCommand.swift b/Sources/AppBundle/command/impl/JoinWithCommand.swift index 61272cf7..ff28ae88 100644 --- a/Sources/AppBundle/command/impl/JoinWithCommand.swift +++ b/Sources/AppBundle/command/impl/JoinWithCommand.swift @@ -5,9 +5,9 @@ struct JoinWithCommand: Command { let args: JoinWithCmdArgs /*conforms*/ let shouldResetClosedWindowsCache = true - func run(_ env: CmdEnv, _ io: CmdIo) -> Bool { + func run(_ env: CmdEnv, _ io: CmdIo) -> BinaryExitCode { let direction = args.direction.val - guard let target = args.resolveTargetOrReportError(env, io) else { return false } + guard let target = args.resolveTargetOrReportError(env, io) else { return .fail } guard let currentWindow = target.windowOrNil else { return io.err(noWindowIsFocused) } @@ -27,6 +27,6 @@ struct JoinWithCommand: Command { joinWithTarget.bind(to: newParent, adaptiveWeight: WEIGHT_AUTO, index: 0) currentWindow.bind(to: newParent, adaptiveWeight: WEIGHT_AUTO, index: direction.isPositive ? 0 : INDEX_BIND_LAST) - return true + return .succ } } diff --git a/Sources/AppBundle/command/impl/LayoutCommand.swift b/Sources/AppBundle/command/impl/LayoutCommand.swift index 40a08e09..34d91c45 100644 --- a/Sources/AppBundle/command/impl/LayoutCommand.swift +++ b/Sources/AppBundle/command/impl/LayoutCommand.swift @@ -5,14 +5,14 @@ struct LayoutCommand: Command { let args: LayoutCmdArgs /*conforms*/ let shouldResetClosedWindowsCache = true - func run(_ env: CmdEnv, _ io: CmdIo) async throws -> Bool { - guard let target = args.resolveTargetOrReportError(env, io) else { return false } + func run(_ env: CmdEnv, _ io: CmdIo) async throws -> BinaryExitCode { + guard let target = args.resolveTargetOrReportError(env, io) else { return .fail } guard let window = target.windowOrNil else { return io.err(noWindowIsFocused) } let targetDescription = args.toggleBetween.val.first(where: { !window.matchesDescription($0) }) ?? args.toggleBetween.val.first.orDie() - if window.matchesDescription(targetDescription) { return false } + if window.matchesDescription(targetDescription) { return .fail } switch targetDescription { case .h_accordion: return changeTilingLayout(io, targetLayout: .accordion, targetOrientation: .h, window: window) @@ -31,37 +31,37 @@ struct LayoutCommand: Command { case .vertical: return changeTilingLayout(io, targetLayout: nil, targetOrientation: .v, window: window) case .tiling: - guard let parent = window.parent else { return false } + guard let parent = window.parent else { return .fail } switch parent.cases { case .macosPopupWindowsContainer: - return false // Impossible + return .fail // Impossible case .macosMinimizedWindowsContainer, .macosFullscreenWindowsContainer, .macosHiddenAppsWindowsContainer: return io.err("Can't change layout for macOS minimized, fullscreen windows or windows or hidden apps. This behavior is subject to change") case .tilingContainer: - return true // Nothing to do + return .succ // Nothing to do case .workspace(let workspace): window.lastFloatingSize = try await window.getAxSize() ?? window.lastFloatingSize try await window.relayoutWindow(on: workspace, forceTile: true) - return true + return .succ } case .floating: let workspace = target.workspace window.bindAsFloatingWindow(to: workspace) if let size = window.lastFloatingSize { window.setAxFrame(nil, size) } - return true + return .succ } } } -@MainActor private func changeTilingLayout(_ io: CmdIo, targetLayout: Layout?, targetOrientation: Orientation?, window: Window) -> Bool { - guard let parent = window.parent else { return false } +@MainActor private func changeTilingLayout(_ io: CmdIo, targetLayout: Layout?, targetOrientation: Orientation?, window: Window) -> BinaryExitCode { + guard let parent = window.parent else { return .fail } switch parent.cases { case .tilingContainer(let parent): let targetOrientation = targetOrientation ?? parent.orientation let targetLayout = targetLayout ?? parent.layout parent.layout = targetLayout parent.changeOrientation(targetOrientation) - return true + return .succ case .workspace, .macosMinimizedWindowsContainer, .macosFullscreenWindowsContainer, .macosPopupWindowsContainer, .macosHiddenAppsWindowsContainer: return io.err("The window is non-tiling") diff --git a/Sources/AppBundle/command/impl/ListAppsCommand.swift b/Sources/AppBundle/command/impl/ListAppsCommand.swift index 534b75d2..8948fdf8 100644 --- a/Sources/AppBundle/command/impl/ListAppsCommand.swift +++ b/Sources/AppBundle/command/impl/ListAppsCommand.swift @@ -5,7 +5,7 @@ struct ListAppsCommand: Command { let args: ListAppsCmdArgs /*conforms*/ let shouldResetClosedWindowsCache = false - func run(_ env: CmdEnv, _ io: CmdIo) -> Bool { + func run(_ env: CmdEnv, _ io: CmdIo) -> BinaryExitCode { var result = Array(MacApp.allAppsMap.values) if let hidden = args.macosHidden { result = result.filter { $0.nsApp.isHidden == hidden } diff --git a/Sources/AppBundle/command/impl/ListExecEnvVarsCommand.swift b/Sources/AppBundle/command/impl/ListExecEnvVarsCommand.swift index 71f019ae..f857414c 100644 --- a/Sources/AppBundle/command/impl/ListExecEnvVarsCommand.swift +++ b/Sources/AppBundle/command/impl/ListExecEnvVarsCommand.swift @@ -5,10 +5,10 @@ struct ListExecEnvVarsCommand: Command { let args: ListExecEnvVarsCmdArgs /*conforms*/ let shouldResetClosedWindowsCache = false - func run(_ env: CmdEnv, _ io: CmdIo) -> Bool { + func run(_ env: CmdEnv, _ io: CmdIo) -> BinaryExitCode { for (key, value) in config.execConfig.envVariables { io.out("\(key)=\(value)") } - return true + return .succ } } diff --git a/Sources/AppBundle/command/impl/ListModesCommand.swift b/Sources/AppBundle/command/impl/ListModesCommand.swift index e3eefc95..757b8fdf 100644 --- a/Sources/AppBundle/command/impl/ListModesCommand.swift +++ b/Sources/AppBundle/command/impl/ListModesCommand.swift @@ -5,13 +5,13 @@ struct ListModesCommand: Command { let args: ListModesCmdArgs /*conforms*/ let shouldResetClosedWindowsCache = false - func run(_ env: CmdEnv, _ io: CmdIo) -> Bool { + func run(_ env: CmdEnv, _ io: CmdIo) -> BinaryExitCode { let modes: [String] = args.current ? [activeMode ?? mainModeId] : config.modes.keys.sorted() return switch true { case args.outputOnlyCount: io.out("\(modes.count)") case args.json: - JSONEncoder.aeroSpaceDefault.encodeToString(modes.map { ["mode-id": $0] }).map(io.out) + JSONEncoder.aeroSpaceDefault.encodeToString(modes.map { ["mode-id": $0] }).map { io.out($0) } ?? io.err("Failed to encode JSON") default: io.out(modes) diff --git a/Sources/AppBundle/command/impl/ListMonitorsCommand.swift b/Sources/AppBundle/command/impl/ListMonitorsCommand.swift index cbf6bd58..b1ea9880 100644 --- a/Sources/AppBundle/command/impl/ListMonitorsCommand.swift +++ b/Sources/AppBundle/command/impl/ListMonitorsCommand.swift @@ -5,7 +5,7 @@ struct ListMonitorsCommand: Command { let args: ListMonitorsCmdArgs /*conforms*/ let shouldResetClosedWindowsCache = false - func run(_ env: CmdEnv, _ io: CmdIo) -> Bool { + func run(_ env: CmdEnv, _ io: CmdIo) -> BinaryExitCode { let focus = focus var result = sortedMonitors if let focused = args.focused { diff --git a/Sources/AppBundle/command/impl/ListWindowsCommand.swift b/Sources/AppBundle/command/impl/ListWindowsCommand.swift index c940ae99..82babc50 100644 --- a/Sources/AppBundle/command/impl/ListWindowsCommand.swift +++ b/Sources/AppBundle/command/impl/ListWindowsCommand.swift @@ -5,7 +5,7 @@ struct ListWindowsCommand: Command { let args: ListWindowsCmdArgs /*conforms*/ let shouldResetClosedWindowsCache = false - func run(_ env: CmdEnv, _ io: CmdIo) async throws -> Bool { + func run(_ env: CmdEnv, _ io: CmdIo) async throws -> BinaryExitCode { let focus = focus var windows: [Window] = [] @@ -28,7 +28,7 @@ struct ListWindowsCommand: Command { .toSet() if !args.filteringOptions.monitors.isEmpty { let monitors: Set = args.filteringOptions.monitors.resolveMonitors(io) - if monitors.isEmpty { return false } + if monitors.isEmpty { return .fail } workspaces = workspaces.filter { monitors.contains($0.workspaceMonitor.rect.topLeftCorner) } } windows = workspaces.flatMap(\.allLeafWindowsRecursive) diff --git a/Sources/AppBundle/command/impl/ListWorkspacesCommand.swift b/Sources/AppBundle/command/impl/ListWorkspacesCommand.swift index c4f3418b..301f4d65 100644 --- a/Sources/AppBundle/command/impl/ListWorkspacesCommand.swift +++ b/Sources/AppBundle/command/impl/ListWorkspacesCommand.swift @@ -5,14 +5,14 @@ struct ListWorkspacesCommand: Command { let args: ListWorkspacesCmdArgs /*conforms*/ let shouldResetClosedWindowsCache = false - func run(_ env: CmdEnv, _ io: CmdIo) -> Bool { + func run(_ env: CmdEnv, _ io: CmdIo) -> BinaryExitCode { var result: [Workspace] = Workspace.all if let visible = args.filteringOptions.visible { result = result.filter { $0.isVisible == visible } } if !args.filteringOptions.onMonitors.isEmpty { let monitors: Set = args.filteringOptions.onMonitors.resolveMonitors(io) - if monitors.isEmpty { return false } + if monitors.isEmpty { return .fail } result = result.filter { monitors.contains($0.workspaceMonitor.rect.topLeftCorner) } } if let empty = args.filteringOptions.empty { diff --git a/Sources/AppBundle/command/impl/MacosNativeFullscreenCommand.swift b/Sources/AppBundle/command/impl/MacosNativeFullscreenCommand.swift index 1b4a6a99..2fbf2a40 100644 --- a/Sources/AppBundle/command/impl/MacosNativeFullscreenCommand.swift +++ b/Sources/AppBundle/command/impl/MacosNativeFullscreenCommand.swift @@ -10,8 +10,8 @@ struct MacosNativeFullscreenCommand: Command { let args: MacosNativeFullscreenCmdArgs /*conforms*/ let shouldResetClosedWindowsCache = false - func run(_ env: CmdEnv, _ io: CmdIo) async throws -> Bool { - guard let target = args.resolveTargetOrReportError(env, io) else { return false } + func run(_ env: CmdEnv, _ io: CmdIo) async throws -> BinaryExitCode { + guard let target = args.resolveTargetOrReportError(env, io) else { return .fail } guard let window = target.windowOrNil else { return io.err(noWindowIsFocused) } @@ -26,7 +26,7 @@ struct MacosNativeFullscreenCommand: Command { io.err((newState ? "Already fullscreen. " : "Already not fullscreen. ") + "Tip: use --fail-if-noop to exit with non-zero exit code") } - return !args.failIfNoop + return .from(bool: !args.failIfNoop) } window.asMacWindow().setNativeFullscreen(newState) guard let workspace = window.visualWorkspace else { @@ -42,6 +42,6 @@ struct MacosNativeFullscreenCommand: Command { try await window.relayoutWindow(on: workspace) } } - return true + return .succ } } diff --git a/Sources/AppBundle/command/impl/MacosNativeMinimizeCommand.swift b/Sources/AppBundle/command/impl/MacosNativeMinimizeCommand.swift index fe8ca61c..c17b87a8 100644 --- a/Sources/AppBundle/command/impl/MacosNativeMinimizeCommand.swift +++ b/Sources/AppBundle/command/impl/MacosNativeMinimizeCommand.swift @@ -6,10 +6,10 @@ struct MacosNativeMinimizeCommand: Command { let args: MacosNativeMinimizeCmdArgs /*conforms*/ let shouldResetClosedWindowsCache = false - func run(_ env: CmdEnv, _ io: CmdIo) async throws -> Bool { + func run(_ env: CmdEnv, _ io: CmdIo) async throws -> BinaryExitCode { // resolveTargetOrReportError on already minimized windows will always fail // It would be easier if minimized windows were part of the workspace in tree hierarchy - guard let target = args.resolveTargetOrReportError(env, io) else { return false } + guard let target = args.resolveTargetOrReportError(env, io) else { return .fail } guard let window = target.windowOrNil else { return io.err(noWindowIsFocused) } @@ -17,7 +17,7 @@ struct MacosNativeMinimizeCommand: Command { window.asMacWindow().setNativeMinimized(newState) if newState { // minimize window.bind(to: macosMinimizedWindowsContainer, adaptiveWeight: 1, index: INDEX_BIND_LAST) - return true + return .succ } else { // unminimize return io.err("The command is uncapable of unminimizing windows yet. Sorry") // dead code. should never be possible, see the comment above } diff --git a/Sources/AppBundle/command/impl/ModeCommand.swift b/Sources/AppBundle/command/impl/ModeCommand.swift index 9d2b4f60..1c1193d4 100644 --- a/Sources/AppBundle/command/impl/ModeCommand.swift +++ b/Sources/AppBundle/command/impl/ModeCommand.swift @@ -5,8 +5,8 @@ struct ModeCommand: Command { let args: ModeCmdArgs /*conforms*/ let shouldResetClosedWindowsCache = false - func run(_ env: CmdEnv, _ io: CmdIo) async throws -> Bool { + func run(_ env: CmdEnv, _ io: CmdIo) async throws -> BinaryExitCode { try await activateMode(args.targetMode.val) - return true + return .succ } } diff --git a/Sources/AppBundle/command/impl/MoveCommand.swift b/Sources/AppBundle/command/impl/MoveCommand.swift index 1cb0aa7e..77d1077b 100644 --- a/Sources/AppBundle/command/impl/MoveCommand.swift +++ b/Sources/AppBundle/command/impl/MoveCommand.swift @@ -5,13 +5,13 @@ struct MoveCommand: Command { let args: MoveCmdArgs /*conforms*/ let shouldResetClosedWindowsCache = true - func run(_ env: CmdEnv, _ io: CmdIo) -> Bool { + func run(_ env: CmdEnv, _ io: CmdIo) -> BinaryExitCode { let direction = args.direction.val - guard let target = args.resolveTargetOrReportError(env, io) else { return false } + guard let target = args.resolveTargetOrReportError(env, io) else { return .fail } guard let currentWindow = target.windowOrNil else { return io.err(noWindowIsFocused) } - guard let parent = currentWindow.parent else { return false } + guard let parent = currentWindow.parent else { return .fail } switch parent.cases { case .tilingContainer(let parent): let indexOfCurrent = currentWindow.ownIndex.orDie() @@ -23,7 +23,7 @@ struct MoveCommand: Command { case .window: // "swap windows" let prevBinding = currentWindow.unbindFromParent() currentWindow.bind(to: parent, adaptiveWeight: prevBinding.adaptiveWeight, index: indexOfSiblingTarget) - return true + return .succ } } else { return moveOut(window: currentWindow, direction: direction, io, args, env) @@ -33,7 +33,7 @@ struct MoveCommand: Command { case .macosMinimizedWindowsContainer, .macosFullscreenWindowsContainer, .macosHiddenAppsWindowsContainer: return io.err(moveOutMacosUnconventionalWindow) case .macosPopupWindowsContainer: - return false // Impossible + return .fail // Impossible } } } @@ -45,15 +45,15 @@ struct MoveCommand: Command { _ args: MoveCmdArgs, _ direction: CardinalDirection, _ env: CmdEnv, -) -> Bool { +) -> BinaryExitCode { switch args.boundaries { case .workspace: switch args.boundariesAction { - case .stop: return true - case .fail: return false + case .stop: return .succ + case .fail: return .fail case .createImplicitContainer: createImplicitContainerAndMoveWindow(window, workspace, direction) - return true + return .succ } case .allMonitorsOuterFrame: guard let (monitors, index) = window.nodeMonitor?.findRelativeMonitor(inDirection: direction) else { @@ -77,13 +77,13 @@ struct MoveCommand: Command { _ workspace: Workspace, _ args: MoveCmdArgs, _ direction: CardinalDirection, -) -> Bool { +) -> BinaryExitCode { switch args.boundariesAction { - case .stop: return true - case .fail: return false + case .stop: return .succ + case .fail: return .fail case .createImplicitContainer: createImplicitContainerAndMoveWindow(window, workspace, direction) - return true + return .succ } } @@ -95,7 +95,7 @@ private let moveOutMacosUnconventionalWindow = "moving macOS fullscreen, minimiz _ io: CmdIo, _ args: MoveCmdArgs, _ env: CmdEnv, -) -> Bool { +) -> BinaryExitCode { let innerMostChild = window.parents.first(where: { return switch $0.parent?.cases { case .tilingContainer(let parent): parent.orientation == direction.orientation @@ -104,20 +104,20 @@ private let moveOutMacosUnconventionalWindow = "moving macOS fullscreen, minimiz .macosHiddenAppsWindowsContainer, .macosPopupWindowsContainer: true } }) as? TilingContainer - guard let innerMostChild else { return false } - guard let parent = innerMostChild.parent else { return false } + guard let innerMostChild else { return .fail } + guard let parent = innerMostChild.parent else { return .fail } switch parent.cases { case .tilingContainer(let parent): check(parent.orientation == direction.orientation) - guard let ownIndex = innerMostChild.ownIndex else { return false } + guard let ownIndex = innerMostChild.ownIndex else { return .fail } window.bind(to: parent, adaptiveWeight: WEIGHT_AUTO, index: ownIndex + direction.insertionOffset) - return true + return .succ case .workspace(let parent): return hitWorkspaceBoundaries(window, parent, io, args, direction, env) case .macosMinimizedWindowsContainer, .macosFullscreenWindowsContainer, .macosHiddenAppsWindowsContainer: return io.err(moveOutMacosUnconventionalWindow) case .macosPopupWindowsContainer: - return false // Impossible + return .fail // Impossible } } @@ -135,20 +135,20 @@ private let moveOutMacosUnconventionalWindow = "moving macOS fullscreen, minimiz window.bind(to: workspace.rootTilingContainer, adaptiveWeight: WEIGHT_AUTO, index: direction.insertionOffset) } -@MainActor private func deepMoveIn(window: Window, into container: TilingContainer, moveDirection: CardinalDirection) -> Bool { +@MainActor private func deepMoveIn(window: Window, into container: TilingContainer, moveDirection: CardinalDirection) -> BinaryExitCode { let deepTarget = container.tilingTreeNodeCasesOrDie().findDeepMoveInTargetRecursive(moveDirection.orientation) switch deepTarget { case .tilingContainer(let deepTarget): window.bind(to: deepTarget, adaptiveWeight: WEIGHT_AUTO, index: 0) case .window(let deepTarget): - guard let parent = deepTarget.parent as? TilingContainer else { return false } + guard let parent = deepTarget.parent as? TilingContainer else { return .fail } window.bind( to: parent, adaptiveWeight: WEIGHT_AUTO, index: deepTarget.ownIndex.orDie() + 1, ) } - return true + return .succ } extension TilingTreeNodeCases { diff --git a/Sources/AppBundle/command/impl/MoveMouseCommand.swift b/Sources/AppBundle/command/impl/MoveMouseCommand.swift index 9e95f3b9..d030a207 100644 --- a/Sources/AppBundle/command/impl/MoveMouseCommand.swift +++ b/Sources/AppBundle/command/impl/MoveMouseCommand.swift @@ -5,21 +5,21 @@ struct MoveMouseCommand: Command { let args: MoveMouseCmdArgs /*conforms*/ let shouldResetClosedWindowsCache = false - func run(_ env: CmdEnv, _ io: CmdIo) async throws -> Bool { + func run(_ env: CmdEnv, _ io: CmdIo) async throws -> BinaryExitCode { let mouse = mouseLocation - guard let target = args.resolveTargetOrReportError(env, io) else { return false } + guard let target = args.resolveTargetOrReportError(env, io) else { return .fail } switch args.mouseTarget.val { case .windowLazyCenter: - guard let rect = try await windowSubjectRectOrReportError(target, io) else { return false } + guard let rect = try await windowSubjectRectOrReportError(target, io) else { return .fail } if rect.contains(mouse) { if !args.failIfNoop { io.err("The mouse already belongs to the window. Tip: use --fail-if-noop to exit with non-zero code") } - return !args.failIfNoop + return .from(bool: !args.failIfNoop) } return moveMouse(io, rect.center) case .windowForceCenter: - guard let rect = try await windowSubjectRectOrReportError(target, io) else { return false } + guard let rect = try await windowSubjectRectOrReportError(target, io) else { return .fail } return moveMouse(io, rect.center) case .monitorLazyCenter: let rect = target.workspace.workspaceMonitor.rect @@ -27,7 +27,7 @@ struct MoveMouseCommand: Command { if !args.failIfNoop { io.err("The mouse already belongs to the monitor. Tip: use --fail-if-noop to exit with non-zero code") } - return !args.failIfNoop + return .from(bool: !args.failIfNoop) } return moveMouse(io, rect.center) case .monitorForceCenter: @@ -36,7 +36,7 @@ struct MoveMouseCommand: Command { } } -private func moveMouse(_ io: CmdIo, _ point: CGPoint) -> Bool { +private func moveMouse(_ io: CmdIo, _ point: CGPoint) -> BinaryExitCode { let event = CGEvent( mouseEventSource: nil, mouseType: CGEventType.mouseMoved, @@ -47,7 +47,7 @@ private func moveMouse(_ io: CmdIo, _ point: CGPoint) -> Bool { case nil: return io.err("Failed to move mouse") case let event?: event.post(tap: CGEventTapLocation.cghidEventTap) - return true + return .succ } } diff --git a/Sources/AppBundle/command/impl/MoveNodeToMonitorCommand.swift b/Sources/AppBundle/command/impl/MoveNodeToMonitorCommand.swift index edd671b0..6c853476 100644 --- a/Sources/AppBundle/command/impl/MoveNodeToMonitorCommand.swift +++ b/Sources/AppBundle/command/impl/MoveNodeToMonitorCommand.swift @@ -5,8 +5,8 @@ struct MoveNodeToMonitorCommand: Command { let args: MoveNodeToMonitorCmdArgs /*conforms*/ let shouldResetClosedWindowsCache = true - func run(_ env: CmdEnv, _ io: CmdIo) -> Bool { - guard let target = args.resolveTargetOrReportError(env, io) else { return false } + func run(_ env: CmdEnv, _ io: CmdIo) -> BinaryExitCode { + guard let target = args.resolveTargetOrReportError(env, io) else { return .fail } guard let window = target.windowOrNil else { return io.err(noWindowIsFocused) } diff --git a/Sources/AppBundle/command/impl/MoveNodeToWorkspaceCommand.swift b/Sources/AppBundle/command/impl/MoveNodeToWorkspaceCommand.swift index 5b2663fe..417f5e10 100644 --- a/Sources/AppBundle/command/impl/MoveNodeToWorkspaceCommand.swift +++ b/Sources/AppBundle/command/impl/MoveNodeToWorkspaceCommand.swift @@ -4,8 +4,8 @@ struct MoveNodeToWorkspaceCommand: Command { let args: MoveNodeToWorkspaceCmdArgs /*conforms*/ let shouldResetClosedWindowsCache: Bool = true - func run(_ env: CmdEnv, _ io: CmdIo) -> Bool { - guard let target = args.resolveTargetOrReportError(env, io) else { return false } + func run(_ env: CmdEnv, _ io: CmdIo) -> BinaryExitCode { + guard let target = args.resolveTargetOrReportError(env, io) else { return .fail } guard let window = target.windowOrNil else { return io.err(noWindowIsFocused) } let subjectWs = window.nodeWorkspace let targetWorkspace: Workspace @@ -29,14 +29,14 @@ struct MoveNodeToWorkspaceCommand: Command { } @MainActor -func moveWindowToWorkspace(_ window: Window, _ targetWorkspace: Workspace, _ io: CmdIo, focusFollowsWindow: Bool, failIfNoop: Bool, index: Int = INDEX_BIND_LAST) -> Bool { +func moveWindowToWorkspace(_ window: Window, _ targetWorkspace: Workspace, _ io: CmdIo, focusFollowsWindow: Bool, failIfNoop: Bool, index: Int = INDEX_BIND_LAST) -> BinaryExitCode { if window.nodeWorkspace == targetWorkspace { if !failIfNoop { io.err("Window '\(window.windowId)' already belongs to workspace '\(targetWorkspace.name)'. Tip: use --fail-if-noop to exit with non-zero code") } - return !failIfNoop + return .from(bool: !failIfNoop) } let targetContainer: NonLeafTreeNodeObject = window.isFloating ? targetWorkspace : targetWorkspace.rootTilingContainer window.bind(to: targetContainer, adaptiveWeight: WEIGHT_AUTO, index: index) - return focusFollowsWindow ? window.focusWindow() : true + return .from(bool: focusFollowsWindow ? window.focusWindow() : true) } diff --git a/Sources/AppBundle/command/impl/MoveWorkspaceToMonitorCommand.swift b/Sources/AppBundle/command/impl/MoveWorkspaceToMonitorCommand.swift index e02da8d6..6284a648 100644 --- a/Sources/AppBundle/command/impl/MoveWorkspaceToMonitorCommand.swift +++ b/Sources/AppBundle/command/impl/MoveWorkspaceToMonitorCommand.swift @@ -5,15 +5,15 @@ struct MoveWorkspaceToMonitorCommand: Command { let args: MoveWorkspaceToMonitorCmdArgs /*conforms*/ let shouldResetClosedWindowsCache = true - func run(_ env: CmdEnv, _ io: CmdIo) -> Bool { - guard let target = args.resolveTargetOrReportError(env, io) else { return false } + func run(_ env: CmdEnv, _ io: CmdIo) -> BinaryExitCode { + guard let target = args.resolveTargetOrReportError(env, io) else { return .fail } let focusedWorkspace = target.workspace let prevMonitor = focusedWorkspace.workspaceMonitor switch args.target.val.resolve(target.workspace.workspaceMonitor, wrapAround: args.wrapAround) { case .success(let targetMonitor): if targetMonitor.monitorId_oneBased == prevMonitor.monitorId_oneBased { - return true + return .succ } if targetMonitor.setActiveWorkspace(focusedWorkspace) { let stubWorkspace = getStubWorkspace(for: prevMonitor) @@ -21,7 +21,7 @@ struct MoveWorkspaceToMonitorCommand: Command { prevMonitor.setActiveWorkspace(stubWorkspace), "getStubWorkspace generated incompatible stub workspace (\(stubWorkspace)) for the monitor (\(prevMonitor)", ) - return true + return .succ } else { return io.err( "Can't move workspace '\(focusedWorkspace.name)' to monitor '\(targetMonitor.name)'. workspace-to-monitor-force-assignment doesn't allow it", diff --git a/Sources/AppBundle/command/impl/ReloadConfigCommand.swift b/Sources/AppBundle/command/impl/ReloadConfigCommand.swift index f54b111b..bc1e9eed 100644 --- a/Sources/AppBundle/command/impl/ReloadConfigCommand.swift +++ b/Sources/AppBundle/command/impl/ReloadConfigCommand.swift @@ -5,13 +5,13 @@ struct ReloadConfigCommand: Command { let args: ReloadConfigCmdArgs /*conforms*/ let shouldResetClosedWindowsCache = false - func run(_ env: CmdEnv, _ io: CmdIo) async throws -> Bool { + func run(_ env: CmdEnv, _ io: CmdIo) async throws -> BinaryExitCode { var stdout = "" let isOk = try await reloadConfig(args: args, stdout: &stdout) if !stdout.isEmpty { io.out(stdout) } - return isOk + return .from(bool: isOk) } } diff --git a/Sources/AppBundle/command/impl/ResizeCommand.swift b/Sources/AppBundle/command/impl/ResizeCommand.swift index 51b42a59..348ad42a 100644 --- a/Sources/AppBundle/command/impl/ResizeCommand.swift +++ b/Sources/AppBundle/command/impl/ResizeCommand.swift @@ -5,8 +5,8 @@ struct ResizeCommand: Command { // todo cover with tests let args: ResizeCmdArgs /*conforms*/ let shouldResetClosedWindowsCache = true - func run(_ env: CmdEnv, _ io: CmdIo) -> Bool { - guard let target = args.resolveTargetOrReportError(env, io) else { return false } + func run(_ env: CmdEnv, _ io: CmdIo) -> BinaryExitCode { + guard let target = args.resolveTargetOrReportError(env, io) else { return .fail } let candidates = target.windowOrNil?.parentsWithSelf .filter { ($0.parent as? TilingContainer)?.layout == .tiles } @@ -34,20 +34,20 @@ struct ResizeCommand: Command { // todo cover with tests parent = node?.parent as? TilingContainer } guard let parent else { return io.err("resize command doesn't support floating windows yet https://github.com/nikitabobko/AeroSpace/issues/9") } - guard let orientation else { return false } - guard let node else { return false } + guard let orientation else { return .fail } + guard let node else { return .fail } let diff: CGFloat = switch args.units.val { case .set(let unit): CGFloat(unit) - node.getWeight(orientation) case .add(let unit): CGFloat(unit) case .subtract(let unit): -CGFloat(unit) } - guard let childDiff = diff.div(parent.children.count - 1) else { return false } + guard let childDiff = diff.div(parent.children.count - 1) else { return .fail } parent.children.lazy .filter { $0 != node } .forEach { $0.setWeight(parent.orientation, $0.getWeight(parent.orientation) - childDiff) } node.setWeight(orientation, node.getWeight(orientation) + diff) - return true + return .succ } } diff --git a/Sources/AppBundle/command/impl/SplitCommand.swift b/Sources/AppBundle/command/impl/SplitCommand.swift index a35aa9b1..81f38288 100644 --- a/Sources/AppBundle/command/impl/SplitCommand.swift +++ b/Sources/AppBundle/command/impl/SplitCommand.swift @@ -5,15 +5,15 @@ struct SplitCommand: Command { let args: SplitCmdArgs /*conforms*/ let shouldResetClosedWindowsCache = true - func run(_ env: CmdEnv, _ io: CmdIo) -> Bool { + func run(_ env: CmdEnv, _ io: CmdIo) -> BinaryExitCode { if config.enableNormalizationFlattenContainers { return io.err("'split' has no effect when 'enable-normalization-flatten-containers' normalization enabled. My recommendation: keep the normalizations enabled, and prefer 'join-with' over 'split'.") } - guard let target = args.resolveTargetOrReportError(env, io) else { return false } + guard let target = args.resolveTargetOrReportError(env, io) else { return .fail } guard let window = target.windowOrNil else { return io.err(noWindowIsFocused) } - guard let parent = window.parent else { return false } + guard let parent = window.parent else { return .fail } switch parent.cases { case .workspace: // Nothing to do for floating and macOS native fullscreen windows @@ -37,11 +37,11 @@ struct SplitCommand: Command { ) window.bind(to: newParent, adaptiveWeight: WEIGHT_AUTO, index: 0) } - return true + return .succ case .macosMinimizedWindowsContainer, .macosFullscreenWindowsContainer, .macosHiddenAppsWindowsContainer: return io.err("Can't split macos fullscreen, minimized windows and windows of hidden apps. This behavior may change in the future") case .macosPopupWindowsContainer: - return false // Impossible + return .fail // Impossible } } } diff --git a/Sources/AppBundle/command/impl/SummonWorkspaceCommand.swift b/Sources/AppBundle/command/impl/SummonWorkspaceCommand.swift index aad3f2c5..7141606d 100644 --- a/Sources/AppBundle/command/impl/SummonWorkspaceCommand.swift +++ b/Sources/AppBundle/command/impl/SummonWorkspaceCommand.swift @@ -5,14 +5,14 @@ struct SummonWorkspaceCommand: Command { let args: SummonWorkspaceCmdArgs /*conforms*/ let shouldResetClosedWindowsCache = true - func run(_ env: CmdEnv, _ io: CmdIo) -> Bool { + func run(_ env: CmdEnv, _ io: CmdIo) -> BinaryExitCode { let workspace = Workspace.get(byName: args.target.val.raw) let monitor = focus.workspace.workspaceMonitor if monitor.activeWorkspace == workspace { if !args.failIfNoop { io.err("Workspace '\(workspace.name)' is already visible on the focused monitor. Tip: use --fail-if-noop to exit with non-zero code") } - return !args.failIfNoop + return .from(bool: !args.failIfNoop) } let prevMonitor = workspace.isVisible ? workspace.workspaceMonitor : nil if monitor.setActiveWorkspace(workspace) { @@ -23,7 +23,7 @@ struct SummonWorkspaceCommand: Command { "getStubWorkspace generated incompatible stub workspace (\(stubWorkspace)) for the monitor (\(prevMonitor)", ) } - return workspace.focusWorkspace() + return .from(bool: workspace.focusWorkspace()) } else { return io.err("Can't move workspace '\(workspace.name)' to monitor '\(monitor.name)'. workspace-to-monitor-force-assignment doesn't allow it") } diff --git a/Sources/AppBundle/command/impl/SwapCommand.swift b/Sources/AppBundle/command/impl/SwapCommand.swift index 7020bf5b..fe6e834e 100644 --- a/Sources/AppBundle/command/impl/SwapCommand.swift +++ b/Sources/AppBundle/command/impl/SwapCommand.swift @@ -5,9 +5,9 @@ struct SwapCommand: Command { let args: SwapCmdArgs /*conforms*/ let shouldResetClosedWindowsCache: Bool = true - func run(_ env: CmdEnv, _ io: CmdIo) async throws -> Bool { + func run(_ env: CmdEnv, _ io: CmdIo) async throws -> BinaryExitCode { guard let target = args.resolveTargetOrReportError(env, io) else { - return false + return .fail } guard let currentWindow = target.windowOrNil else { @@ -23,12 +23,12 @@ struct SwapCommand: Command { case nil where args.wrapAround: targetWindow = target.workspace.findLeafWindowRecursive(snappedTo: direction.opposite) case nil: - return false + return .fail } case .dfsRelative(let nextPrev): let windows = target.workspace.rootTilingContainer.allLeafWindowsRecursive guard let currentIndex = windows.firstIndex(where: { $0 == target.windowOrNil }) else { - return false + return .fail } var targetIndex = switch nextPrev { case .dfsNext: currentIndex + 1 @@ -36,7 +36,7 @@ struct SwapCommand: Command { } if !(0 ..< windows.count).contains(targetIndex) { if !args.wrapAround { - return false + return .fail } targetIndex = (targetIndex + windows.count) % windows.count } @@ -44,14 +44,14 @@ struct SwapCommand: Command { } guard let targetWindow else { - return false + return .fail } swapWindows(currentWindow, targetWindow) if args.swapFocus { - return targetWindow.focusWindow() + return .from(bool: targetWindow.focusWindow()) } - return true + return .succ } } diff --git a/Sources/AppBundle/command/impl/TriggerBindingCommand.swift b/Sources/AppBundle/command/impl/TriggerBindingCommand.swift index e0317f97..93e892a5 100644 --- a/Sources/AppBundle/command/impl/TriggerBindingCommand.swift +++ b/Sources/AppBundle/command/impl/TriggerBindingCommand.swift @@ -5,17 +5,17 @@ struct TriggerBindingCommand: Command { let args: TriggerBindingCmdArgs /*conforms*/ let shouldResetClosedWindowsCache = false - func run(_ env: CmdEnv, _ io: CmdIo) async throws -> Bool { + func run(_ env: CmdEnv, _ io: CmdIo) async throws -> Int32ExitCode { return if let mode = config.modes[args.mode] { if let binding = mode.bindings.values.first(where: { $0.descriptionWithKeyNotation == args.binding.val }) { // refreshSession is not needed since commands are already run in refreshSession try await binding.commands.runCmdSeq(env, io) } else { - io.err("Binding '\(args.binding.val)' is not presented in mode '\(args.mode)'") + io.err("Binding '\(args.binding.val)' is not presented in mode '\(args.mode)'", .fail) } } else { io.err("Mode '\(args.mode)' doesn't exist. " + - "Available modes: \(config.modes.keys.joined(separator: ","))") + "Available modes: \(config.modes.keys.joined(separator: ","))", .fail) } } } diff --git a/Sources/AppBundle/command/impl/VolumeCommand.swift b/Sources/AppBundle/command/impl/VolumeCommand.swift index c7c719e0..f1cb858c 100644 --- a/Sources/AppBundle/command/impl/VolumeCommand.swift +++ b/Sources/AppBundle/command/impl/VolumeCommand.swift @@ -6,7 +6,7 @@ struct VolumeCommand: Command { let args: VolumeCmdArgs /*conforms*/ let shouldResetClosedWindowsCache = false - func run(_ env: CmdEnv, _ io: CmdIo) -> Bool { + func run(_ env: CmdEnv, _ io: CmdIo) -> BinaryExitCode { switch args.action.val { case .up: Sound.output.increaseVolume(by: 0.0625, autoMuteUnmute: true) @@ -24,6 +24,6 @@ struct VolumeCommand: Command { if args.gui, let volume = try? Sound.output.readVolume() { VolumePanel.shared.update(with: Sound.output.isMuted ? 0 : volume) } - return true + return .succ } } diff --git a/Sources/AppBundle/command/impl/WorkspaceBackAndForthCommand.swift b/Sources/AppBundle/command/impl/WorkspaceBackAndForthCommand.swift index 781398bc..0fa55877 100644 --- a/Sources/AppBundle/command/impl/WorkspaceBackAndForthCommand.swift +++ b/Sources/AppBundle/command/impl/WorkspaceBackAndForthCommand.swift @@ -5,7 +5,7 @@ struct WorkspaceBackAndForthCommand: Command { let args: WorkspaceBackAndForthCmdArgs /*conforms*/ let shouldResetClosedWindowsCache = false - func run(_ env: CmdEnv, _ io: CmdIo) -> Bool { - return prevFocusedWorkspace?.focusWorkspace() != nil + func run(_ env: CmdEnv, _ io: CmdIo) -> BinaryExitCode { + return .from(bool: prevFocusedWorkspace?.focusWorkspace() != nil) } } diff --git a/Sources/AppBundle/command/impl/WorkspaceCommand.swift b/Sources/AppBundle/command/impl/WorkspaceCommand.swift index 67e5117c..fdcdca1e 100644 --- a/Sources/AppBundle/command/impl/WorkspaceCommand.swift +++ b/Sources/AppBundle/command/impl/WorkspaceCommand.swift @@ -6,8 +6,8 @@ struct WorkspaceCommand: Command { let args: WorkspaceCmdArgs /*conforms*/ let shouldResetClosedWindowsCache = false - func run(_ env: CmdEnv, _ io: CmdIo) -> Bool { // todo refactor - guard let target = args.resolveTargetOrReportError(env, io) else { return false } + func run(_ env: CmdEnv, _ io: CmdIo) -> BinaryExitCode { // todo refactor + guard let target = args.resolveTargetOrReportError(env, io) else { return .fail } let focusedWs = target.workspace let workspaceName: String switch args.target.val { @@ -19,7 +19,7 @@ struct WorkspaceCommand: Command { stdin: args.useStdin ? io.readStdin() : nil, target: target, ) - guard let workspace else { return false } + guard let workspace else { return .fail } workspaceName = workspace.name case .direct(let name): workspaceName = name.raw @@ -31,9 +31,9 @@ struct WorkspaceCommand: Command { if !args.failIfNoop { io.err("Workspace '\(workspaceName)' is already focused. Tip: use --fail-if-noop to exit with non-zero code") } - return !args.failIfNoop + return .from(bool: !args.failIfNoop) } else { - return Workspace.get(byName: workspaceName).focusWorkspace() + return .from(bool: Workspace.get(byName: workspaceName).focusWorkspace()) } } } diff --git a/Sources/AppBundle/server.swift b/Sources/AppBundle/server.swift index fa8e78bc..b1d88cd2 100644 --- a/Sources/AppBundle/server.swift +++ b/Sources/AppBundle/server.swift @@ -102,7 +102,7 @@ private func newConnection(_ connection: NWConnection) async { // todo add exit ) let cmdResult = try await command.run(env, CmdStdin(request.stdin)) return ServerAnswer( - exitCode: cmdResult.exitCode, + exitCode: cmdResult.exitCode.rawValue, stdout: cmdResult.stdout.joined(separator: "\n"), stderr: cmdResult.stderr.joined(separator: "\n"), serverVersionAndHash: serverVersionAndHash, diff --git a/Sources/AppBundleTests/command/FocusCommandTest.swift b/Sources/AppBundleTests/command/FocusCommandTest.swift index c8241f68..61e85a5d 100644 --- a/Sources/AppBundleTests/command/FocusCommandTest.swift +++ b/Sources/AppBundleTests/command/FocusCommandTest.swift @@ -223,29 +223,29 @@ final class FocusCommandTest: XCTestCase { var args = FocusCmdArgs(rawArgs: [], cardinalOrDfsDirection: .dfsRelative(.dfsPrev)) args.rawBoundariesAction = .stop - assertEquals(try await FocusCommand(args: args).run(.defaultEnv, .emptyStdin).exitCode, 0) + assertEquals(try await FocusCommand(args: args).run(.defaultEnv, .emptyStdin).exitCode.rawValue, 0) assertEquals(focus.windowOrNil?.windowId, 1) args.rawBoundariesAction = .fail - assertEquals(try await FocusCommand(args: args).run(.defaultEnv, .emptyStdin).exitCode, 1) + assertEquals(try await FocusCommand(args: args).run(.defaultEnv, .emptyStdin).exitCode.rawValue, 1) assertEquals(focus.windowOrNil?.windowId, 1) args.rawBoundariesAction = .wrapAroundTheWorkspace - assertEquals(try await FocusCommand(args: args).run(.defaultEnv, .emptyStdin).exitCode, 0) + assertEquals(try await FocusCommand(args: args).run(.defaultEnv, .emptyStdin).exitCode.rawValue, 0) assertEquals(focus.windowOrNil?.windowId, 2) args.cardinalOrDfsDirection = .dfsRelative(.dfsNext) args.rawBoundariesAction = .stop - assertEquals(try await FocusCommand(args: args).run(.defaultEnv, .emptyStdin).exitCode, 0) + assertEquals(try await FocusCommand(args: args).run(.defaultEnv, .emptyStdin).exitCode.rawValue, 0) assertEquals(focus.windowOrNil?.windowId, 2) args.rawBoundariesAction = .fail - assertEquals(try await FocusCommand(args: args).run(.defaultEnv, .emptyStdin).exitCode, 1) + assertEquals(try await FocusCommand(args: args).run(.defaultEnv, .emptyStdin).exitCode.rawValue, 1) assertEquals(focus.windowOrNil?.windowId, 2) args.rawBoundariesAction = .wrapAroundTheWorkspace - assertEquals(try await FocusCommand(args: args).run(.defaultEnv, .emptyStdin).exitCode, 0) + assertEquals(try await FocusCommand(args: args).run(.defaultEnv, .emptyStdin).exitCode.rawValue, 0) assertEquals(focus.windowOrNil?.windowId, 1) } } diff --git a/Sources/AppBundleTests/command/ListModesTest.swift b/Sources/AppBundleTests/command/ListModesTest.swift index 2e80f231..2e8e6445 100644 --- a/Sources/AppBundleTests/command/ListModesTest.swift +++ b/Sources/AppBundleTests/command/ListModesTest.swift @@ -25,17 +25,17 @@ final class ListModesTest: XCTestCase { ] let defaultResult = try await ListModesCommand(args: ListModesCmdArgs(rawArgs: [])).run(.defaultEnv, .emptyStdin) - assertEquals(defaultResult.exitCode, 0) + assertEquals(defaultResult.exitCode.rawValue, 0) assertEquals(defaultResult.stdout, ["main", "resize", "service"]) assertEquals(defaultResult.stderr, []) let currentResult = try await ListModesCommand(args: ListModesCmdArgs(rawArgs: []).copy(\.current, true)).run(.defaultEnv, .emptyStdin) - assertEquals(currentResult.exitCode, 0) + assertEquals(currentResult.exitCode.rawValue, 0) assertEquals(currentResult.stdout, ["main"]) assertEquals(currentResult.stderr, []) let countResult = try await ListModesCommand(args: ListModesCmdArgs(rawArgs: []).copy(\.outputOnlyCount, true)).run(.defaultEnv, .emptyStdin) - assertEquals(countResult.exitCode, 0) + assertEquals(countResult.exitCode.rawValue, 0) assertEquals(countResult.stdout, ["3"]) assertEquals(countResult.stderr, []) @@ -45,7 +45,7 @@ final class ListModesTest: XCTestCase { ["mode-id": "resize"], ["mode-id": "service"], ]) - assertEquals(jsonResult.exitCode, 0) + assertEquals(jsonResult.exitCode.rawValue, 0) assertEquals(jsonResult.stdout, [expectedJson]) assertEquals(jsonResult.stderr, []) @@ -53,7 +53,7 @@ final class ListModesTest: XCTestCase { let expectedCurrentJson = JSONEncoder.aeroSpaceDefault.encodeToString([ ["mode-id": "main"], ]) - assertEquals(currentJsonResult.exitCode, 0) + assertEquals(currentJsonResult.exitCode.rawValue, 0) assertEquals(currentJsonResult.stdout, [expectedCurrentJson]) assertEquals(currentJsonResult.stderr, []) } diff --git a/Sources/AppBundleTests/command/MoveCommandTest.swift b/Sources/AppBundleTests/command/MoveCommandTest.swift index 92c7f7fc..400bee40 100644 --- a/Sources/AppBundleTests/command/MoveCommandTest.swift +++ b/Sources/AppBundleTests/command/MoveCommandTest.swift @@ -122,7 +122,7 @@ final class MoveCommandTest: XCTestCase { ]), ]), ) - assertEquals(result.exitCode, 0) + assertEquals(result.exitCode.rawValue, 0) } func testStop_onRootNode() async throws { @@ -140,7 +140,7 @@ final class MoveCommandTest: XCTestCase { .h_tiles([.window(1), .window(2), .window(3)]), ]), ) - assertEquals(result.exitCode, 0) + assertEquals(result.exitCode.rawValue, 0) } func testStop_onRootNode_withOppositeOrientation() async throws { @@ -158,7 +158,7 @@ final class MoveCommandTest: XCTestCase { .h_tiles([.window(1), .window(2), .window(3)]), ]), ) - assertEquals(result.exitCode, 0) + assertEquals(result.exitCode.rawValue, 0) } func testStop_onRootNode_whenNoBoundary() async throws { @@ -176,7 +176,7 @@ final class MoveCommandTest: XCTestCase { .h_tiles([.window(2), .window(1), .window(3)]), ]), ) - assertEquals(result.exitCode, 0) + assertEquals(result.exitCode.rawValue, 0) } func testStop_onInnerNode() async throws { @@ -196,7 +196,7 @@ final class MoveCommandTest: XCTestCase { .h_tiles([.window(1), .v_tiles([.window(3)]), .window(2)]), ]), ) - assertEquals(result.exitCode, 0) + assertEquals(result.exitCode.rawValue, 0) } func testFail() async throws { @@ -214,7 +214,7 @@ final class MoveCommandTest: XCTestCase { .h_tiles([.window(1), .window(2), .window(3)]), ]), ) - assertEquals(result.exitCode, 1) + assertEquals(result.exitCode.rawValue, 1) } func testMoveOut() async throws { diff --git a/Sources/Common/cmdArgs/ExitCode.swift b/Sources/Common/cmdArgs/ExitCode.swift new file mode 100644 index 00000000..873ac181 --- /dev/null +++ b/Sources/Common/cmdArgs/ExitCode.swift @@ -0,0 +1,36 @@ +public protocol ExitCode: RawRepresentable, AeroAny, Sendable { + static var fail: Self { get } +} + +public let EXIT_CODE_ZERO: Int32 = 0 + +// Some big enough number which is not occupied by any other ExitCode +// The only exit code which is guaranteed to denote a error +public let EXIT_CODE_UNCLASSIFIED_ERROR: Int32 = 10 + +public struct Int32ExitCode: ExitCode, Equatable { + public var rawValue: Int32 + public init(rawValue: Int32) { self.rawValue = rawValue } + public static let fail = Int32ExitCode(rawValue: EXIT_CODE_UNCLASSIFIED_ERROR) +} + +public enum BinaryExitCode: Int32, ExitCode { + case succ = 0 + case fail = 1 + public static func from(bool: Bool) -> Self { bool ? .succ : .fail } + + public func and(_ other: @autoclosure () -> Self) -> Self { + switch rawValue { + case EXIT_CODE_ZERO: other() + default: self + } + } + + // periphery:ignore + public func or(_ other: @autoclosure () -> Self) -> Self { + switch rawValue { + case EXIT_CODE_ZERO: self + default: other() + } + } +} diff --git a/Sources/Common/cmdArgs/impl/TriggerBindingCmdArgs.swift b/Sources/Common/cmdArgs/impl/TriggerBindingCmdArgs.swift index b448e2ba..635fb31a 100644 --- a/Sources/Common/cmdArgs/impl/TriggerBindingCmdArgs.swift +++ b/Sources/Common/cmdArgs/impl/TriggerBindingCmdArgs.swift @@ -9,6 +9,7 @@ public struct TriggerBindingCmdArgs: CmdArgs { ], posArgs: [newMandatoryPosArgParser(\.binding, consumeStrCliArg, placeholder: "")], ) + public typealias ExitCodeType = Int32ExitCode public var _mode: String? = nil public var binding: Lateinit = .uninitialized diff --git a/Sources/Common/cmdArgs/parseCmdArgs.swift b/Sources/Common/cmdArgs/parseCmdArgs.swift index 4dd4e840..76777b72 100644 --- a/Sources/Common/cmdArgs/parseCmdArgs.swift +++ b/Sources/Common/cmdArgs/parseCmdArgs.swift @@ -11,6 +11,7 @@ public func parseCmdArgs(_ args: StrArrSlice) -> ParsedCmd { } public protocol CmdArgs: ConvenienceCopyable, Equatable, CustomStringConvertible, AeroAny, Sendable { + associatedtype ExitCodeType: ExitCode = BinaryExitCode static var parser: CmdParser { get } var commonState: CmdArgsCommonState { get set } } -- 2.51.2