From f85d51290b29308ac92db687a9465802f8a091d7 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Tue, 24 Feb 2026 15:10:27 +0100 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Reworked=20command=20histo?= =?UTF-8?q?ry?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 40 ++++++++++-- phpmon/Common/Command/CommandProtocol.swift | 50 -------------- phpmon/Common/Command/RealCommand.swift | 10 +-- phpmon/Common/Command/TestableCommand.swift | 2 +- .../CommandTracker.swift | 39 ++--------- phpmon/Common/Monitoring/TrackedCommand.swift | 38 +++++++++++ phpmon/Common/Monitoring/TrackedShell.swift | 65 +++++++++++++++++++ phpmon/Common/Shell/RealShell.swift | 17 +++-- phpmon/Common/Shell/ShellProtocol.swift | 54 +-------------- phpmon/Common/Shell/TestableShell.swift | 35 ++-------- phpmon/Container/Container.swift | 6 +- .../UI/CommandHistoryView.swift | 2 +- 12 files changed, 166 insertions(+), 192 deletions(-) rename phpmon/Common/{Command => Monitoring}/CommandTracker.swift (63%) create mode 100644 phpmon/Common/Monitoring/TrackedCommand.swift create mode 100644 phpmon/Common/Monitoring/TrackedShell.swift diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 198649fa..c0d65338 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -189,6 +189,8 @@ 03CC1FF52E3D23130050FC18 /* ZshRunCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03CC1FF32E3D230B0050FC18 /* ZshRunCommand.swift */; }; 03CC1FF62E3D23130050FC18 /* ZshRunCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03CC1FF32E3D230B0050FC18 /* ZshRunCommand.swift */; }; 03CC1FF72E3D23130050FC18 /* ZshRunCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03CC1FF32E3D230B0050FC18 /* ZshRunCommand.swift */; }; + 03D0F9752F4DE6FE00613D1E /* TestableCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E49DEC28F764A00026AC4E /* TestableCommand.swift */; }; + 03D0F9762F4DE6FE00613D1E /* TestableShell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46EBC4928DB966A007ACC74 /* TestableShell.swift */; }; 03D846252EB6344E006EFE3C /* DomainListVC+Window.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03D846242EB6344A006EFE3C /* DomainListVC+Window.swift */; }; 03D846262EB6344E006EFE3C /* DomainListVC+Window.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03D846242EB6344A006EFE3C /* DomainListVC+Window.swift */; }; 03D846272EB6344E006EFE3C /* DomainListVC+Window.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03D846242EB6344A006EFE3C /* DomainListVC+Window.swift */; }; @@ -487,8 +489,8 @@ C46EBC4528DB95F0007ACC74 /* ShellProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46EBC4328DB95F0007ACC74 /* ShellProtocol.swift */; }; C46EBC4728DB9644007ACC74 /* RealShell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46EBC4628DB9644007ACC74 /* RealShell.swift */; }; C46EBC4828DB9644007ACC74 /* RealShell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46EBC4628DB9644007ACC74 /* RealShell.swift */; }; - C46EBC4A28DB966A007ACC74 /* TestableShell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46EBC4928DB966A007ACC74 /* TestableShell.swift */; }; C46EBC4B28DB966A007ACC74 /* TestableShell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46EBC4928DB966A007ACC74 /* TestableShell.swift */; }; + C46EBC4C28DB95F0007ACC74 /* TrackedShell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46EBC4A28DB966A007ACC74 /* TrackedShell.swift */; }; C46FA23F246C358E00944F05 /* StringExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46FA23E246C358E00944F05 /* StringExtension.swift */; }; C46FA9882822EFDC00D78807 /* PhpConfigurationFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46FA9872822EFDC00D78807 /* PhpConfigurationFile.swift */; }; C46FA9892822EFDC00D78807 /* PhpConfigurationFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46FA9872822EFDC00D78807 /* PhpConfigurationFile.swift */; }; @@ -547,9 +549,13 @@ C471E7F628F9BAC80021E251 /* PhpHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D936C827E3EB6100BD69FE /* PhpHelper.swift */; }; C471E7F728F9BACB0021E251 /* PhpSwitcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D9ADBE277610E1007277F4 /* PhpSwitcher.swift */; }; C471E7F828F9BACB0021E251 /* InternalSwitcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D9ADC7277611A0007277F4 /* InternalSwitcher.swift */; }; + C471E7F928F9BA600021E251 /* TrackedShell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46EBC4A28DB966A007ACC74 /* TrackedShell.swift */; }; C471E7F928F9BACB0021E251 /* PhpSwitcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D9ADBE277610E1007277F4 /* PhpSwitcher.swift */; }; + C471E7FA28F9BA8F0021E251 /* TrackedShell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46EBC4A28DB966A007ACC74 /* TrackedShell.swift */; }; C471E7FA28F9BACB0021E251 /* InternalSwitcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D9ADC7277611A0007277F4 /* InternalSwitcher.swift */; }; + C471E7FB28F9BAA30021E251 /* TrackedCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E49DED28F764A00026AC4E /* TrackedCommand.swift */; }; C471E7FB28F9BACE0021E251 /* HomebrewService.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F30B02278E16BA00755FCE /* HomebrewService.swift */; }; + C471E7FC28F9BAA30021E251 /* TrackedCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E49DED28F764A00026AC4E /* TrackedCommand.swift */; }; C471E7FC28F9BACE0021E251 /* HomebrewDecodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C412E5FB25700D5300A1FB67 /* HomebrewDecodable.swift */; }; C471E7FD28F9BACE0021E251 /* HomebrewService.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F30B02278E16BA00755FCE /* HomebrewService.swift */; }; C471E7FE28F9BACE0021E251 /* HomebrewDecodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C412E5FB25700D5300A1FB67 /* HomebrewDecodable.swift */; }; @@ -968,8 +974,9 @@ C4E4404727C56F4700D225E1 /* ValetSite.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E4404527C56F4700D225E1 /* ValetSite.swift */; }; C4E49DEA28F7643D0026AC4E /* CommandProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E49DE928F7643D0026AC4E /* CommandProtocol.swift */; }; C4E49DEB28F7643D0026AC4E /* CommandProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E49DE928F7643D0026AC4E /* CommandProtocol.swift */; }; - C4E49DED28F764A00026AC4E /* TestableCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E49DEC28F764A00026AC4E /* TestableCommand.swift */; }; C4E49DEE28F764A00026AC4E /* TestableCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E49DEC28F764A00026AC4E /* TestableCommand.swift */; }; + C4E49DF028F764A00026AC4E /* TrackedCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E49DED28F764A00026AC4E /* TrackedCommand.swift */; }; + C4E49DF128F764A00026AC4E /* TrackedCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E49DED28F764A00026AC4E /* TrackedCommand.swift */; }; C4E684092AF26B830023ED25 /* BrewTapFormulae.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E684082AF26B830023ED25 /* BrewTapFormulae.swift */; }; C4E6840A2AF26B830023ED25 /* BrewTapFormulae.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E684082AF26B830023ED25 /* BrewTapFormulae.swift */; }; C4E6840B2AF26B830023ED25 /* BrewTapFormulae.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E684082AF26B830023ED25 /* BrewTapFormulae.swift */; }; @@ -1267,6 +1274,7 @@ C46EBC4328DB95F0007ACC74 /* ShellProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShellProtocol.swift; sourceTree = ""; }; C46EBC4628DB9644007ACC74 /* RealShell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RealShell.swift; sourceTree = ""; }; C46EBC4928DB966A007ACC74 /* TestableShell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestableShell.swift; sourceTree = ""; }; + C46EBC4A28DB966A007ACC74 /* TrackedShell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackedShell.swift; sourceTree = ""; }; C46FA23E246C358E00944F05 /* StringExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringExtension.swift; sourceTree = ""; }; C46FA9872822EFDC00D78807 /* PhpConfigurationFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpConfigurationFile.swift; sourceTree = ""; }; C46FA98A2822F08F00D78807 /* PhpConfigurationFileTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpConfigurationFileTest.swift; sourceTree = ""; }; @@ -1363,6 +1371,7 @@ C4E4404527C56F4700D225E1 /* ValetSite.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValetSite.swift; sourceTree = ""; }; C4E49DE928F7643D0026AC4E /* CommandProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommandProtocol.swift; sourceTree = ""; }; C4E49DEC28F764A00026AC4E /* TestableCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestableCommand.swift; sourceTree = ""; }; + C4E49DED28F764A00026AC4E /* TrackedCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackedCommand.swift; sourceTree = ""; }; C4E684082AF26B830023ED25 /* BrewTapFormulae.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrewTapFormulae.swift; sourceTree = ""; }; C4E713562570150F00007428 /* SECURITY.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = SECURITY.md; sourceTree = ""; }; C4E713572570151400007428 /* docs */ = {isa = PBXFileReference; lastKnownFileType = folder; path = docs; sourceTree = ""; }; @@ -1573,6 +1582,16 @@ path = Container; sourceTree = ""; }; + 03D0F9772F4DE7E800613D1E /* Monitoring */ = { + isa = PBXGroup; + children = ( + 031A80DB2F4CF1690016F7DD /* CommandTracker.swift */, + C46EBC4A28DB966A007ACC74 /* TrackedShell.swift */, + C4E49DED28F764A00026AC4E /* TrackedCommand.swift */, + ); + path = Monitoring; + sourceTree = ""; + }; 03D53E902E8AE089001B1671 /* Testables */ = { isa = PBXGroup; children = ( @@ -2285,6 +2304,7 @@ C4F787A628EF811000790735 /* Shell */, C4C8900128F0E27900CE5E97 /* Filesystem */, C4E49DE528F763E20026AC4E /* Command */, + 03D0F9772F4DE7E800613D1E /* Monitoring */, C40C7F2127721F7300DDDCDC /* Core */, 54B20EDF263AA22C00D3250E /* PHP */, C44CCD4327AFE93300CE40E5 /* Errors */, @@ -2452,7 +2472,6 @@ C4B5853D2770FE3900DA4FBE /* RealCommand.swift */, C4E49DEC28F764A00026AC4E /* TestableCommand.swift */, C4E49DE928F7643D0026AC4E /* CommandProtocol.swift */, - 031A80DB2F4CF1690016F7DD /* CommandTracker.swift */, ); path = Command; sourceTree = ""; @@ -2921,7 +2940,7 @@ C4F2E43A2752F7D00020E974 /* PhpInstallation.swift in Sources */, C45B914E295608E300F4EC78 /* ValetServicesManager.swift in Sources */, C4D5576429C77CC5001A44CD /* PhpVersionManagerWindowController.swift in Sources */, - C4E49DED28F764A00026AC4E /* TestableCommand.swift in Sources */, + C4E49DEE28F764A00026AC4E /* TestableCommand.swift in Sources */, C41E871A2763D42300161EE0 /* DomainListVC+ContextMenu.swift in Sources */, 037F44222EDB92EC002EBF75 /* HomebrewWatchManager.swift in Sources */, C40C7F2827721FF600DDDCDC /* Valet+Alerts.swift in Sources */, @@ -2969,7 +2988,7 @@ C40175B82903108900763A68 /* ValetInteractor.swift in Sources */, C4ACE9E129F84EDD00110766 /* PhpGuard.swift in Sources */, C4F361612836BFD9003598CC /* MainMenu+Actions.swift in Sources */, - C46EBC4A28DB966A007ACC74 /* TestableShell.swift in Sources */, + C46EBC4B28DB966A007ACC74 /* TestableShell.swift in Sources */, C44C198D276E3A1C0072762D /* TerminalProgressWindowController.swift in Sources */, 0379C49F2ED71D050035D7EA /* Startup+Launch.swift in Sources */, 54D9E0B827E4F51E003B9AD9 /* KeyCombo.swift in Sources */, @@ -2986,6 +3005,7 @@ C40934A2298EEB2C00D25014 /* CaskFile.swift in Sources */, C495F5AF28A42E080087F70A /* EnvironmentCheck.swift in Sources */, C46EBC4428DB95F0007ACC74 /* ShellProtocol.swift in Sources */, + C46EBC4C28DB95F0007ACC74 /* TrackedShell.swift in Sources */, C41C1B4922B00A9800E7CF16 /* MenuBarImageGenerator.swift in Sources */, C4F30B03278E16BA00755FCE /* HomebrewService.swift in Sources */, 54D9E0B427E4F51E003B9AD9 /* Key.swift in Sources */, @@ -3104,6 +3124,7 @@ 039C29182E8AA314007F5FAB /* TestableWebApi.swift in Sources */, C47015022C46D6910069AAE7 /* NVAlertExtension.swift in Sources */, C4E49DEA28F7643D0026AC4E /* CommandProtocol.swift in Sources */, + C4E49DF028F764A00026AC4E /* TrackedCommand.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3295,6 +3316,7 @@ 031E2B6B2B1525A7007C29E1 /* BrewPhpExtension.swift in Sources */, C471E82728F9BB310021E251 /* BrewDiagnostics.swift in Sources */, C471E7DB28F9BA8F0021E251 /* RealShell.swift in Sources */, + C471E7FA28F9BA8F0021E251 /* TrackedShell.swift in Sources */, C471E7FF28F9BAD10021E251 /* Xdebug.swift in Sources */, 037F441D2EDB9195002EBF75 /* ConfigWatchManager.swift in Sources */, C409349F298EE8E900D25014 /* AppUpdater.swift in Sources */, @@ -3310,6 +3332,7 @@ C42106682AFA9FF400DF3732 /* PhpVersionManagerView+Actions.swift in Sources */, C4B79EC829CA474200A483EE /* FakeCommand.swift in Sources */, C471E7DE28F9BAA30021E251 /* CommandProtocol.swift in Sources */, + C471E7FB28F9BAA30021E251 /* TrackedCommand.swift in Sources */, C471E82928F9BB330021E251 /* Valet.swift in Sources */, C471E80728F9BAD40021E251 /* PhpConfigurationFile.swift in Sources */, 0329A9A32E92A69000A62A12 /* WarningManager+Evaluations.swift in Sources */, @@ -3554,10 +3577,12 @@ C471E7ED28F9BAC30021E251 /* Process.swift in Sources */, C471E81128F9BAE80021E251 /* NSMenuItemExtension.swift in Sources */, C471E7CC28F9BA5B0021E251 /* TestableShell.swift in Sources */, + C471E7F928F9BA600021E251 /* TrackedShell.swift in Sources */, C4E6840C2AF26B830023ED25 /* BrewTapFormulae.swift in Sources */, C471E80C28F9BAE80021E251 /* NSWindowExtension.swift in Sources */, C471E7CA28F9BA480021E251 /* TestableFileSystem.swift in Sources */, C471E7DD28F9BAA30021E251 /* CommandProtocol.swift in Sources */, + C471E7FC28F9BAA30021E251 /* TrackedCommand.swift in Sources */, C471E7D128F9BA630021E251 /* RealFileSystem.swift in Sources */, 033D459B2B0D4EC600070080 /* InstallPhpExtensionCommand.swift in Sources */, C471E82B28F9BB340021E251 /* Valet.swift in Sources */, @@ -3674,6 +3699,7 @@ C4D5576529C77CC5001A44CD /* PhpVersionManagerWindowController.swift in Sources */, C4BF90C127C57C220054E78C /* MainMenu+FixMyValet.swift in Sources */, C4E49DEB28F7643D0026AC4E /* CommandProtocol.swift in Sources */, + C4E49DF128F764A00026AC4E /* TrackedCommand.swift in Sources */, C4F2E4382752F08D0020E974 /* BrewDiagnostics.swift in Sources */, C485707428BF454E00539B36 /* ServicesView.swift in Sources */, 03B947DE2F43692500B6F899 /* TestURL.swift in Sources */, @@ -3731,7 +3757,7 @@ C40FE738282ABA4F00A302C2 /* AppVersion.swift in Sources */, 03B675EC2EBA30D800EE04A9 /* NSImageExtension.swift in Sources */, C415D3E92770F692005EF286 /* AppDelegate+InterApp.swift in Sources */, - C4E49DEE28F764A00026AC4E /* TestableCommand.swift in Sources */, + 03D0F9752F4DE6FE00613D1E /* TestableCommand.swift in Sources */, C4611E612AEAD3110010BE24 /* ByteLimitView.swift in Sources */, C40175B92903108900763A68 /* ValetInteractor.swift in Sources */, 03ACC6452ECCAB190070D4CD /* RealWebApiTest.swift in Sources */, @@ -3851,7 +3877,7 @@ C40C7F1F2772136000DDDCDC /* PhpEnvironments.swift in Sources */, C464ADB0275A7A6A003FCD53 /* DomainListVC.swift in Sources */, C43A8A1A25D9CD1000591B77 /* Utility.swift in Sources */, - C46EBC4B28DB966A007ACC74 /* TestableShell.swift in Sources */, + 03D0F9762F4DE6FE00613D1E /* TestableShell.swift in Sources */, C40FE73B282ABB2E00A302C2 /* AppVersionTest.swift in Sources */, C490E3BD29BCA375006D2DE6 /* Measurements.swift in Sources */, C4F780C625D80B75000DBC97 /* XibLoadable.swift in Sources */, diff --git a/phpmon/Common/Command/CommandProtocol.swift b/phpmon/Common/Command/CommandProtocol.swift index e784b8e6..c92b6959 100644 --- a/phpmon/Common/Command/CommandProtocol.swift +++ b/phpmon/Common/Command/CommandProtocol.swift @@ -9,21 +9,6 @@ import Foundation protocol CommandProtocol { - /** - Immediately executes a command, without tracking. - - - Parameter path: The path of the command or program to invoke. - - Parameter arguments: A list of arguments that are passed on. - - Parameter trimNewlines: Removes empty new line output. - - Parameter withStandardError: Outputs standard error output to the same string output as well. - */ - func executeRaw( - path: String, - arguments: [String], - trimNewlines: Bool, - withStandardError: Bool - ) -> String - /** Immediately executes a command. @@ -55,20 +40,6 @@ protocol CommandProtocol { } extension CommandProtocol { - func execute( - path: String, - arguments: [String], - trimNewlines: Bool, - withStandardError: Bool - ) -> String { - executeRaw( - path: path, - arguments: arguments, - trimNewlines: trimNewlines, - withStandardError: withStandardError - ) - } - func execute( path: String, arguments: [String], @@ -83,24 +54,3 @@ extension CommandProtocol { } } - -protocol TrackedCommandProtocol: CommandProtocol, CommandTrackingProvider {} - -extension TrackedCommandProtocol { - func execute( - path: String, - arguments: [String], - trimNewlines: Bool, - withStandardError: Bool - ) -> String { - let commandDescription = "\(path) \(arguments.joined(separator: " "))" - return trackedCommand(description: commandDescription) { - executeRaw( - path: path, - arguments: arguments, - trimNewlines: trimNewlines, - withStandardError: withStandardError - ) - } - } -} diff --git a/phpmon/Common/Command/RealCommand.swift b/phpmon/Common/Command/RealCommand.swift index 2139f185..b97b9c97 100644 --- a/phpmon/Common/Command/RealCommand.swift +++ b/phpmon/Common/Command/RealCommand.swift @@ -7,14 +7,10 @@ import Cocoa -public class RealCommand: TrackedCommandProtocol { - let commandTracker: CommandTracker +public class RealCommand: CommandProtocol { + init() {} - init(commandTracker: CommandTracker) { - self.commandTracker = commandTracker - } - - public func executeRaw( + public func execute( path: String, arguments: [String], trimNewlines: Bool, diff --git a/phpmon/Common/Command/TestableCommand.swift b/phpmon/Common/Command/TestableCommand.swift index b4b61e4a..aa912fc2 100644 --- a/phpmon/Common/Command/TestableCommand.swift +++ b/phpmon/Common/Command/TestableCommand.swift @@ -15,7 +15,7 @@ class TestableCommand: CommandProtocol { var commands: [String: String] - public func executeRaw( + public func execute( path: String, arguments: [String], trimNewlines: Bool, diff --git a/phpmon/Common/Command/CommandTracker.swift b/phpmon/Common/Monitoring/CommandTracker.swift similarity index 63% rename from phpmon/Common/Command/CommandTracker.swift rename to phpmon/Common/Monitoring/CommandTracker.swift index 942737e5..c0a1ecc0 100644 --- a/phpmon/Common/Command/CommandTracker.swift +++ b/phpmon/Common/Monitoring/CommandTracker.swift @@ -13,9 +13,9 @@ class CommandTracker: ObservableObject { nonisolated init() {} private let maxStoredCommands = 200 - @Published private(set) var commands: [TrackedCommand] = [] + @Published private(set) var commands: [LoggedCommand] = [] - var activeCommands: [TrackedCommand] { + var activeCommands: [LoggedCommand] { commands.filter { !$0.isCompleted } } @@ -25,7 +25,7 @@ class CommandTracker: ObservableObject { @discardableResult func track(_ command: String, id: UUID = UUID()) -> UUID { - let tracked = TrackedCommand(id: id, command: command, startedAt: Date()) + let tracked = LoggedCommand(id: id, command: command, startedAt: Date()) commands.append(tracked) if commands.count > maxStoredCommands { commands.removeFirst(commands.count - maxStoredCommands) @@ -54,9 +54,9 @@ class CommandTracker: ObservableObject { } } -// MARK: - Tracked Command +// MARK: - Logged Command -struct TrackedCommand: Identifiable { +struct LoggedCommand: Identifiable { let id: UUID let command: String let startedAt: Date @@ -72,7 +72,7 @@ struct TrackedCommand: Identifiable { if duration < 0.001 { let micros = max(1, Int(duration * 1_000_000)) - return "Completed in \(micros) μs" + return "Completed in \(micros) us" } let ms = max(1, Int(duration * 1000)) @@ -83,30 +83,3 @@ struct TrackedCommand: Identifiable { return "Running for \(ms) ms" } } - -// MARK: - Command Tracking - -protocol CommandTrackingProvider { - var commandTracker: CommandTracker { get } -} - -extension CommandTrackingProvider { - func trackedCommand(description: String, _ work: () -> T) -> T { - let trackingId = commandTracker.trackFromAnyThread(description) - defer { - commandTracker.completeFromAnyThread(trackingId) - } - return work() - } - - func trackedCommandAsync( - description: String, - _ work: () async throws -> T - ) async rethrows -> T { - let trackingId = commandTracker.trackFromAnyThread(description) - defer { - commandTracker.completeFromAnyThread(trackingId) - } - return try await work() - } -} diff --git a/phpmon/Common/Monitoring/TrackedCommand.swift b/phpmon/Common/Monitoring/TrackedCommand.swift new file mode 100644 index 00000000..98515e6a --- /dev/null +++ b/phpmon/Common/Monitoring/TrackedCommand.swift @@ -0,0 +1,38 @@ +// +// TrackedCommand.swift +// PHP Monitor +// +// Copyright © 2025 Nico Verbruggen. All rights reserved. +// + +import Foundation + +final class TrackedCommand: CommandProtocol { + private let command: CommandProtocol + private let commandTracker: CommandTracker + + init(command: CommandProtocol, commandTracker: CommandTracker) { + self.command = command + self.commandTracker = commandTracker + } + + func execute( + path: String, + arguments: [String], + trimNewlines: Bool, + withStandardError: Bool + ) -> String { + let commandDescription = "\(path) \(arguments.joined(separator: " "))" + let trackingId = commandTracker.trackFromAnyThread(commandDescription) + defer { + commandTracker.completeFromAnyThread(trackingId) + } + + return command.execute( + path: path, + arguments: arguments, + trimNewlines: trimNewlines, + withStandardError: withStandardError + ) + } +} diff --git a/phpmon/Common/Monitoring/TrackedShell.swift b/phpmon/Common/Monitoring/TrackedShell.swift new file mode 100644 index 00000000..cb4ee00e --- /dev/null +++ b/phpmon/Common/Monitoring/TrackedShell.swift @@ -0,0 +1,65 @@ +// +// TrackedShell.swift +// PHP Monitor +// +// Copyright © 2025 Nico Verbruggen. All rights reserved. +// + +import Foundation + +final class TrackedShell: ShellProtocol { + private let shell: ShellProtocol + private let commandTracker: CommandTracker + + init(shell: ShellProtocol, commandTracker: CommandTracker) { + self.shell = shell + self.commandTracker = commandTracker + } + + var PATH: String { + shell.PATH + } + + func sync(_ command: String) -> ShellOutput { + let trackingId = commandTracker.trackFromAnyThread(command) + defer { + commandTracker.completeFromAnyThread(trackingId) + } + return shell.sync(command) + } + + @discardableResult + func pipe(_ command: String) async -> ShellOutput { + let trackingId = commandTracker.trackFromAnyThread(command) + defer { + commandTracker.completeFromAnyThread(trackingId) + } + return await shell.pipe(command) + } + + @discardableResult + func pipe(_ command: String, timeout: TimeInterval) async -> ShellOutput { + let trackingId = commandTracker.trackFromAnyThread(command) + defer { + commandTracker.completeFromAnyThread(trackingId) + } + return await shell.pipe(command, timeout: timeout) + } + + @discardableResult + func attach( + _ command: String, + didReceiveOutput: @escaping (String, ShellStream) -> Void, + withTimeout timeout: TimeInterval + ) async throws -> (Process, ShellOutput) { + let trackingId = commandTracker.trackFromAnyThread(command) + defer { + commandTracker.completeFromAnyThread(trackingId) + } + return try await shell.attach(command, didReceiveOutput: didReceiveOutput, withTimeout: timeout) + } + + func reloadEnvPath() { + shell.reloadEnvPath() + } +} diff --git a/phpmon/Common/Shell/RealShell.swift b/phpmon/Common/Shell/RealShell.swift index 6eca8964..467cfad7 100644 --- a/phpmon/Common/Shell/RealShell.swift +++ b/phpmon/Common/Shell/RealShell.swift @@ -9,15 +9,12 @@ import Foundation @preconcurrency import Dispatch -class RealShell: TrackedShellProtocol, @unchecked Sendable { - init(binPath: String, commandTracker: CommandTracker) { +class RealShell: ShellProtocol, @unchecked Sendable { + init(binPath: String) { self.binPath = binPath - self.commandTracker = commandTracker self._PATH = RealShell.getPath() self._exports = [:] } - - let commandTracker: CommandTracker private(set) var binPath: String /** @@ -157,7 +154,8 @@ class RealShell: TrackedShellProtocol, @unchecked Sendable { // MARK: - Shellable Protocol - func syncRaw(_ command: String) -> ShellOutput { + @discardableResult + func sync(_ command: String) -> ShellOutput { let process = getShellProcess(for: command) let outputPipe = Pipe() @@ -188,7 +186,7 @@ class RealShell: TrackedShellProtocol, @unchecked Sendable { } @discardableResult - func pipeRaw(_ command: String) async -> ShellOutput { + func pipe(_ command: String) async -> ShellOutput { let process = getShellProcess(for: command) let outputPipe = Pipe() @@ -224,7 +222,7 @@ class RealShell: TrackedShellProtocol, @unchecked Sendable { } @discardableResult - func pipeRaw(_ command: String, timeout: TimeInterval) async -> ShellOutput { + func pipe(_ command: String, timeout: TimeInterval) async -> ShellOutput { let process = getShellProcess(for: command) let outputPipe = Pipe() @@ -290,7 +288,7 @@ class RealShell: TrackedShellProtocol, @unchecked Sendable { } @discardableResult - func attachRaw( + func attach( _ command: String, didReceiveOutput: @escaping (String, ShellStream) -> Void, withTimeout timeout: TimeInterval = 5.0 @@ -382,6 +380,7 @@ class RealShell: TrackedShellProtocol, @unchecked Sendable { }) } + func reloadEnvPath() { // Instead of replacing the entire shell instance, we simply re-fetch the PATH self.PATH = RealShell.getPath() diff --git a/phpmon/Common/Shell/ShellProtocol.swift b/phpmon/Common/Shell/ShellProtocol.swift index c273e017..2e414fdd 100644 --- a/phpmon/Common/Shell/ShellProtocol.swift +++ b/phpmon/Common/Shell/ShellProtocol.swift @@ -15,7 +15,7 @@ protocol ShellProtocol { var PATH: String { get } /** - Run a command synchronously without tracking. Use with caution! + Run a command synchronously. Use with caution! Common usage: ``` @@ -24,9 +24,6 @@ protocol ShellProtocol { @return The shell output. If the command times out, returns empty output. */ - @discardableResult - func syncRaw(_ command: String) -> ShellOutput - /** Run a command synchronously. Use with caution! @@ -50,9 +47,6 @@ protocol ShellProtocol { @return The shell output. If the command times out, returns empty output. */ - @discardableResult - func pipeRaw(_ command: String) async -> ShellOutput - @discardableResult func pipe(_ command: String) async -> ShellOutput @@ -65,9 +59,6 @@ protocol ShellProtocol { @return The shell output. If the command times out, returns empty output. */ - @discardableResult - func pipeRaw(_ command: String, timeout: TimeInterval) async -> ShellOutput - @discardableResult func pipe(_ command: String, timeout: TimeInterval) async -> ShellOutput @@ -82,13 +73,6 @@ protocol ShellProtocol { @return A tuple, containing the `Process` and `ShellOutput` objects. */ - @discardableResult - func attachRaw( - _ command: String, - didReceiveOutput: @escaping (String, ShellStream) -> Void, - withTimeout timeout: TimeInterval - ) async throws -> (Process, ShellOutput) - @discardableResult func attach( _ command: String, @@ -102,42 +86,6 @@ protocol ShellProtocol { func reloadEnvPath() } -protocol TrackedShellProtocol: ShellProtocol, CommandTrackingProvider {} - -extension TrackedShellProtocol { - @discardableResult - func sync(_ command: String) -> ShellOutput { - trackedCommand(description: command) { - syncRaw(command) - } - } - - @discardableResult - func pipe(_ command: String) async -> ShellOutput { - await trackedCommandAsync(description: command) { - await pipeRaw(command) - } - } - - @discardableResult - func pipe(_ command: String, timeout: TimeInterval) async -> ShellOutput { - await trackedCommandAsync(description: command) { - await pipeRaw(command, timeout: timeout) - } - } - - @discardableResult - func attach( - _ command: String, - didReceiveOutput: @escaping (String, ShellStream) -> Void, - withTimeout timeout: TimeInterval - ) async throws -> (Process, ShellOutput) { - try await trackedCommandAsync(description: command) { - try await attachRaw(command, didReceiveOutput: didReceiveOutput, withTimeout: timeout) - } - } -} - enum ShellStream: Codable { case stdOut, stdErr, stdIn } diff --git a/phpmon/Common/Shell/TestableShell.swift b/phpmon/Common/Shell/TestableShell.swift index 0a8dc927..9e32492b 100644 --- a/phpmon/Common/Shell/TestableShell.swift +++ b/phpmon/Common/Shell/TestableShell.swift @@ -20,7 +20,7 @@ public class TestableShell: ShellProtocol { var expectations: [String: BatchFakeShellOutput] = [:] @discardableResult - func syncRaw(_ command: String) -> ShellOutput { + func sync(_ command: String) -> ShellOutput { // This assertion will only fire during test builds assert(expectations.keys.contains(command), "No response declared for command: \(command)") @@ -32,18 +32,18 @@ public class TestableShell: ShellProtocol { } @discardableResult - func pipeRaw(_ command: String) async -> ShellOutput { - await pipeRaw(command, timeout: 60) + func pipe(_ command: String) async -> ShellOutput { + await pipe(command, timeout: 60) } @discardableResult - func pipeRaw(_ command: String, timeout: TimeInterval) async -> ShellOutput { - let (_, output) = try! await self.attachRaw(command, didReceiveOutput: { _, _ in }, withTimeout: timeout) + func pipe(_ command: String, timeout: TimeInterval) async -> ShellOutput { + let (_, output) = try! await self.attach(command, didReceiveOutput: { _, _ in }, withTimeout: timeout) return output } @discardableResult - func attachRaw( + func attach( _ command: String, didReceiveOutput: @escaping (String, ShellStream) -> Void, withTimeout timeout: TimeInterval @@ -73,29 +73,6 @@ public class TestableShell: ShellProtocol { // does nothing } - @discardableResult - func sync(_ command: String) -> ShellOutput { - syncRaw(command) - } - - @discardableResult - func pipe(_ command: String) async -> ShellOutput { - await pipeRaw(command) - } - - @discardableResult - func pipe(_ command: String, timeout: TimeInterval) async -> ShellOutput { - await pipeRaw(command, timeout: timeout) - } - - @discardableResult - func attach( - _ command: String, - didReceiveOutput: @escaping (String, ShellStream) -> Void, - withTimeout timeout: TimeInterval - ) async throws -> (Process, ShellOutput) { - try await attachRaw(command, didReceiveOutput: didReceiveOutput, withTimeout: timeout) - } } struct FakeShellOutput: Codable { diff --git a/phpmon/Container/Container.swift b/phpmon/Container/Container.swift index 03c39ef4..88b002d7 100644 --- a/phpmon/Container/Container.swift +++ b/phpmon/Container/Container.swift @@ -69,8 +69,10 @@ class Container: @unchecked Sendable { self.filesystem = RealFileSystem(container: self) self.paths = Paths(container: self) self.commandTracker = CommandTracker() - self.shell = RealShell(binPath: paths.binPath, commandTracker: commandTracker) - self.command = RealCommand(commandTracker: commandTracker) + let realShell = RealShell(binPath: paths.binPath) + self.shell = TrackedShell(shell: realShell, commandTracker: commandTracker) + let realCommand = RealCommand() + self.command = TrackedCommand(command: realCommand, commandTracker: commandTracker) self.webApi = RealWebApi(container: self) if coreOnly { diff --git a/phpmon/Modules/Command History/UI/CommandHistoryView.swift b/phpmon/Modules/Command History/UI/CommandHistoryView.swift index 9ed47f37..e926b699 100644 --- a/phpmon/Modules/Command History/UI/CommandHistoryView.swift +++ b/phpmon/Modules/Command History/UI/CommandHistoryView.swift @@ -34,7 +34,7 @@ struct CommandHistoryView: View { Spacer() } } else { - ForEach(commandTracker.commands) { command in + ForEach(commandTracker.commands) { command in HStack(alignment: .top, spacing: 10) { VStack(alignment: .leading, spacing: 4) { HStack(alignment: .firstTextBaseline, spacing: 8) {