diff --git a/Sources/AppBundle/command/impl/TestCommand.swift b/Sources/AppBundle/command/impl/TestCommand.swift index 728db073..5b33c60c 100644 --- a/Sources/AppBundle/command/impl/TestCommand.swift +++ b/Sources/AppBundle/command/impl/TestCommand.swift @@ -21,7 +21,7 @@ struct TestCommand: Command { return .fail } - let (infixOperator, negated) = args.infixOperator.val.structured + let infixOperator = args.infixOperator.val let rhs = args.rhs.val let lhsType = lhs.kind.rawValue let incompatibleLhsAndOperatorMsg = """ @@ -44,7 +44,7 @@ struct TestCommand: Command { } return switch result { - case .success(let result): result != negated ? ._true : ._false // xor + case .success(let result): result ? ._true : ._false case .failure(let err): .fail(io.err(err)) } } diff --git a/Sources/AppBundleTests/command/TestCommandTest.swift b/Sources/AppBundleTests/command/TestCommandTest.swift index c93900c4..3050bec7 100644 --- a/Sources/AppBundleTests/command/TestCommandTest.swift +++ b/Sources/AppBundleTests/command/TestCommandTest.swift @@ -8,20 +8,20 @@ final class TestCommandTest: XCTestCase { func testParse() { testParseCommandSucc( - "test %{app-bundle-id} .= foo", + "test %{app-bundle-id} = foo", TestCmdArgs(rawArgs: []) .copy(\.lhs, .initialized(.app(.appBundleId))) .copy(\.infixOperator, .initialized(.equals)) .copy(\.rhs, .initialized("foo")), ) - testParseCommandFail("test %{foo} .= foo", msg: "ERROR: Can\'t parse \'foo\'.\n Possible values: (window-id|window-is-fullscreen|window-title|window-layout|window-parent-container-layout|workspace|workspace-is-focused|workspace-is-visible|workspace-root-container-layout|app-bundle-id|app-name|app-pid|app-exec-path|app-bundle-path|monitor-id|monitor-appkit-nsscreen-screens-id|monitor-name|monitor-is-main)", exitCode: 2) - testParseCommandFail("test foo .= foo", msg: "ERROR: Left hand side must be a single interpolation variable", exitCode: 2) - testParseCommandFail("test foo%{app-bundle-id} .= foo", msg: "ERROR: Left hand side must be a single interpolation variable", exitCode: 2) + testParseCommandFail("test %{foo} = foo", msg: "ERROR: Can\'t parse \'foo\'.\n Possible values: (window-id|window-is-fullscreen|window-title|window-layout|window-parent-container-layout|workspace|workspace-is-focused|workspace-is-visible|workspace-root-container-layout|app-bundle-id|app-name|app-pid|app-exec-path|app-bundle-path|monitor-id|monitor-appkit-nsscreen-screens-id|monitor-name|monitor-is-main)", exitCode: 2) + testParseCommandFail("test foo = foo", msg: "ERROR: Left hand side must be a single interpolation variable", exitCode: 2) + testParseCommandFail("test foo%{app-bundle-id} = foo", msg: "ERROR: Left hand side must be a single interpolation variable", exitCode: 2) testParseCommandFail("test", msg: "ERROR: Argument \'\' is mandatory\nERROR: Argument \'\' is mandatory\nERROR: Argument \'\' is mandatory", exitCode: 2) - testParseCommandFail("test foo .= %{app-bundle-id}", msg: "ERROR: Left hand side must be a single interpolation variable\nERROR: Right hand side doesn\'t allow interpolation variables", exitCode: 2) - testParseCommandFail("test %{app-bundle-id} .= %{app-bundle-id}", msg: "ERROR: Right hand side doesn\'t allow interpolation variables", exitCode: 2) - testParseCommandFail("test %{newline} .= foo", msg: "ERROR: Can\'t parse \'newline\'.\n Possible values: (window-id|window-is-fullscreen|window-title|window-layout|window-parent-container-layout|workspace|workspace-is-focused|workspace-is-visible|workspace-root-container-layout|app-bundle-id|app-name|app-pid|app-exec-path|app-bundle-path|monitor-id|monitor-appkit-nsscreen-screens-id|monitor-name|monitor-is-main)", exitCode: 2) - testParseCommandFail("test %{window-id} .= %{invalid}", msg: "ERROR: Right hand side doesn\'t allow interpolation variables", exitCode: 2) + testParseCommandFail("test foo = %{app-bundle-id}", msg: "ERROR: Left hand side must be a single interpolation variable\nERROR: Right hand side doesn\'t allow interpolation variables", exitCode: 2) + testParseCommandFail("test %{app-bundle-id} = %{app-bundle-id}", msg: "ERROR: Right hand side doesn\'t allow interpolation variables", exitCode: 2) + testParseCommandFail("test %{newline} = foo", msg: "ERROR: Can\'t parse \'newline\'.\n Possible values: (window-id|window-is-fullscreen|window-title|window-layout|window-parent-container-layout|workspace|workspace-is-focused|workspace-is-visible|workspace-root-container-layout|app-bundle-id|app-name|app-pid|app-exec-path|app-bundle-path|monitor-id|monitor-appkit-nsscreen-screens-id|monitor-name|monitor-is-main)", exitCode: 2) + testParseCommandFail("test %{window-id} = %{invalid}", msg: "ERROR: Right hand side doesn\'t allow interpolation variables", exitCode: 2) } func testExec() async throws { @@ -30,23 +30,23 @@ final class TestCommandTest: XCTestCase { } assertEquals( - try await parseCommand("test %{window-id} .= 1").cmdOrDie.run(.defaultEnv, .emptyStdin), + try await parseCommand("test %{window-id} = 1").cmdOrDie.run(.defaultEnv, .emptyStdin), CmdResult(stdout: [], stderr: [], exitCode: Int32ExitCode(rawValue: 0)), ) assertEquals( - try await parseCommand("test %{window-id} /= 1").cmdOrDie.run(.defaultEnv, .emptyStdin), + try await parseCommand("test %{window-id} = 2").cmdOrDie.run(.defaultEnv, .emptyStdin), CmdResult(stdout: [], stderr: [], exitCode: Int32ExitCode(rawValue: 1)), ) assertEquals( - try await parseCommand("test %{workspace-is-focused} .= foo").cmdOrDie.run(.defaultEnv, .emptyStdin), + try await parseCommand("test %{workspace-is-focused} = foo").cmdOrDie.run(.defaultEnv, .emptyStdin), CmdResult(stdout: [], stderr: ["Can\'t convert String \'foo\' to Bool"], exitCode: Int32ExitCode(rawValue: 2)), ) assertEquals( - try await parseCommand("test %{workspace-is-focused} .~ foo").cmdOrDie.run(.defaultEnv, .emptyStdin), - CmdResult(stdout: [], stderr: ["Interpolation variable: \'workspace-is-focused\' has a type of Bool. The Bool type is not compatible with \'.~\' operator."], exitCode: Int32ExitCode(rawValue: 2)), + try await parseCommand("test %{workspace-is-focused} ~= foo").cmdOrDie.run(.defaultEnv, .emptyStdin), + CmdResult(stdout: [], stderr: ["Interpolation variable: \'workspace-is-focused\' has a type of Bool. The Bool type is not compatible with \'~=\' operator."], exitCode: Int32ExitCode(rawValue: 2)), ) } @@ -54,7 +54,7 @@ final class TestCommandTest: XCTestCase { assertEquals(Workspace.get(byName: name).focusWorkspace(), true) assertEquals( - try await parseCommand("test %{window-id} .= 1").cmdOrDie.run(.defaultEnv, .emptyStdin), + try await parseCommand("test %{window-id} = 1").cmdOrDie.run(.defaultEnv, .emptyStdin), CmdResult(stdout: [], stderr: ["Unknown interpolation variable \'window-id\'. Possible values:\n workspace\n workspace-is-focused\n workspace-is-visible\n workspace-root-container-layout\n monitor-id\n monitor-appkit-nsscreen-screens-id\n monitor-name\n monitor-is-main\n right-padding\n newline\n tab", "No window is focused"], exitCode: Int32ExitCode(rawValue: 2)), ) } diff --git a/Sources/AppBundleTests/config/ConfigTest.swift b/Sources/AppBundleTests/config/ConfigTest.swift index 1021f5af..9e15b1e1 100644 --- a/Sources/AppBundleTests/config/ConfigTest.swift +++ b/Sources/AppBundleTests/config/ConfigTest.swift @@ -345,7 +345,7 @@ final class ConfigTest: XCTestCase { run = ['move-node-to-workspace S', 'layout h_tiles'], }, { # 6 - if = 'test %{app-bundle-id} .= org.alacritty', + if = 'test %{app-bundle-id} = org.alacritty', run = ['move-node-to-workspace T'], }, ] diff --git a/Sources/AppBundleTests/tree/OnWindowDetectedTest.swift b/Sources/AppBundleTests/tree/OnWindowDetectedTest.swift index d9f4f073..f526abf0 100644 --- a/Sources/AppBundleTests/tree/OnWindowDetectedTest.swift +++ b/Sources/AppBundleTests/tree/OnWindowDetectedTest.swift @@ -14,7 +14,7 @@ final class OnWindowDetectedTest: XCTestCase { assertEquals(focus.windowOrNil?.windowId, 1) let callback = WindowDetectedCallback( - matcher: .command(parseCommand("test %{window-id} .= 2").cmdOrDie), + matcher: .command(parseCommand("test %{window-id} = 2").cmdOrDie), rawRun: [], ) diff --git a/Sources/Common/cmdArgs/impl/TestCmdArgs.swift b/Sources/Common/cmdArgs/impl/TestCmdArgs.swift index 2b6882b1..4deab3c0 100644 --- a/Sources/Common/cmdArgs/impl/TestCmdArgs.swift +++ b/Sources/Common/cmdArgs/impl/TestCmdArgs.swift @@ -54,24 +54,6 @@ func parseTestCmdArgs(_ args: StrArrSlice) -> ParsedCmd { } public enum InfixOperator: String, CaseIterable, Equatable, Sendable { - case equals = ".=" - case notEquals = "/=" - - case matchesRegex = ".~" - case notMatchesRegex = "/~" - - public enum Reduced: Sendable, Equatable { - case equals - case matchesRegex - } - - public var structured: (Reduced, negated: Bool) { - switch self { - case .equals: (.equals, negated: false) - case .notEquals: (.equals, negated: true) - - case .matchesRegex: (.matchesRegex, negated: false) - case .notMatchesRegex: (.matchesRegex, negated: true) - } - } + case equals = "=" + case matchesRegex = "~=" } diff --git a/Sources/Common/cmdHelpGenerated.swift b/Sources/Common/cmdHelpGenerated.swift index 37c77c5a..4d7d555a 100644 --- a/Sources/Common/cmdHelpGenerated.swift +++ b/Sources/Common/cmdHelpGenerated.swift @@ -147,10 +147,8 @@ let swap_help_generated = """ (left|down|up|right|dfs-next|dfs-prev) """ let test_help_generated = """ - USAGE: test [-h|--help] .= - OR: test [-h|--help] /= - OR: test [-h|--help] .~ - OR: test [-h|--help] /~ + USAGE: test [-h|--help] = + OR: test [-h|--help] ~= """ let trigger_binding_help_generated = """ USAGE: trigger-binding [-h|--help] --mode diff --git a/docs/aerospace-test.adoc b/docs/aerospace-test.adoc index 1ed5d12f..b20c7d74 100644 --- a/docs/aerospace-test.adoc +++ b/docs/aerospace-test.adoc @@ -9,10 +9,8 @@ include::util/man-attributes.adoc[] == Synopsis [verse] // tag::synopsis[] -aerospace test [-h|--help] .= -aerospace test [-h|--help] /= -aerospace test [-h|--help] .~ -aerospace test [-h|--help] /~ +aerospace test [-h|--help] = +aerospace test [-h|--help] ~= // end::synopsis[] @@ -26,22 +24,15 @@ The `test` utility evaluates the expression and, if it evaluates to true, return The utility is inspired by the builtin `/bin/test` utility. -` .= `:: Tests that `` equals to `` -` /= `:: Tests that `` does NOT equal to `` -` .~ `:: Tests that `` contains case insensitive `` regex. The operator fails if `` is not a string -` /~ `:: Tests that `` does NOT contain case insensitive `` regex. The operator fails if `` is not a string - -The choice of symbols is dictated by the shell expansion rules that "helpfully" expand arguments, if they start either with an exclamation mark or with an equals character. -The dot prefix is used as a safe and neutral character. -The slash denotes negation, following a common convention in functional programming (mnemonics: it "crosses out" the operator). -The tilde is widely used in scripting languages to indicate regex matching. +` = `:: Tests that `` equals to `` +` ~= `:: Tests that `` contains case insensitive `` regex. The command fails if `` is not a string // =========================================================== Examples include::./util/conditional-examples-header.adoc[] -Test if the application ID equals to Chrome: `aerospace test %{app-bundle-id} .= com.google.Chrome` +Test if the application ID equals to Chrome: `aerospace test %{app-bundle-id} = com.google.Chrome` -Test if the application name contains "chrome" (case insensitive): `aerospace test %{app-name} .~ chrome` +Test if the application name contains "chrome" (case insensitive): `aerospace test %{app-name} ~= chrome` // =========================================================== Arguments include::./util/conditional-arguments-header.adoc[] diff --git a/docs/guide.adoc b/docs/guide.adoc index 450e2ff9..2554bc7c 100644 --- a/docs/guide.adoc +++ b/docs/guide.adoc @@ -495,7 +495,7 @@ You can use `on-window-detected` config option to run commands every time a new ---- on-window-detected = [ { - if = 'test %{app-bundle-id} .= com.apple.systempreferences', + if = 'test %{app-bundle-id} = com.apple.systempreferences', check-further-callbacks = true, run = ['layout floating', 'move-node-to-workspace S'], # The callback itself }, @@ -516,7 +516,7 @@ Additionally, if you want to keep processing the list of callbacks when the `if` ---- on-window-detected = [ { - if = 'test %{app-bundle-id} .= com.apple.systempreferences', + if = 'test %{app-bundle-id} = com.apple.systempreferences', run = ['layout floating', 'move-node-to-workspace S'], # The next callbacks will be considered even if this one is successful. @@ -543,15 +543,15 @@ Examples of automations: ---- on-window-detected = [ { - if = 'test %{app-bundle-id} .= org.alacritty', + if = 'test %{app-bundle-id} = org.alacritty', run = 'move-node-to-workspace T', # mnemonics T - Terminal }, { - if = 'test %{app-bundle-id} .= com.google.Chrome', + if = 'test %{app-bundle-id} = com.google.Chrome', run = 'move-node-to-workspace W', # mnemonics W - Web browser }, { - if = 'test %{app-bundle-id} .= com.jetbrains.intellij', + if = 'test %{app-bundle-id} = com.jetbrains.intellij', run = 'move-node-to-workspace I', # mnemonics I - IDE } ] @@ -741,7 +741,7 @@ See xref:./commands.adoc#test[the test helper command documentation] for more in ---- on-window-detected = [ { - if = 'test %{app-bundle-id} .= com.apple.systempreferences', # ✅ Do this + if = 'test %{app-bundle-id} = com.apple.systempreferences', # ✅ Do this run = ['layout floating', 'move-node-to-workspace S'], }, ]