mirror of
https://github.com/nicoverbruggen/phpmon.git
synced 2026-03-25 13:40:08 +01:00
♻️ Reworked command history
This commit is contained in:
@@ -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 = "<group>"; };
|
||||
C46EBC4628DB9644007ACC74 /* RealShell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RealShell.swift; sourceTree = "<group>"; };
|
||||
C46EBC4928DB966A007ACC74 /* TestableShell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestableShell.swift; sourceTree = "<group>"; };
|
||||
C46EBC4A28DB966A007ACC74 /* TrackedShell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackedShell.swift; sourceTree = "<group>"; };
|
||||
C46FA23E246C358E00944F05 /* StringExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringExtension.swift; sourceTree = "<group>"; };
|
||||
C46FA9872822EFDC00D78807 /* PhpConfigurationFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpConfigurationFile.swift; sourceTree = "<group>"; };
|
||||
C46FA98A2822F08F00D78807 /* PhpConfigurationFileTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpConfigurationFileTest.swift; sourceTree = "<group>"; };
|
||||
@@ -1363,6 +1371,7 @@
|
||||
C4E4404527C56F4700D225E1 /* ValetSite.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValetSite.swift; sourceTree = "<group>"; };
|
||||
C4E49DE928F7643D0026AC4E /* CommandProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommandProtocol.swift; sourceTree = "<group>"; };
|
||||
C4E49DEC28F764A00026AC4E /* TestableCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestableCommand.swift; sourceTree = "<group>"; };
|
||||
C4E49DED28F764A00026AC4E /* TrackedCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackedCommand.swift; sourceTree = "<group>"; };
|
||||
C4E684082AF26B830023ED25 /* BrewTapFormulae.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrewTapFormulae.swift; sourceTree = "<group>"; };
|
||||
C4E713562570150F00007428 /* SECURITY.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = SECURITY.md; sourceTree = "<group>"; };
|
||||
C4E713572570151400007428 /* docs */ = {isa = PBXFileReference; lastKnownFileType = folder; path = docs; sourceTree = "<group>"; };
|
||||
@@ -1573,6 +1582,16 @@
|
||||
path = Container;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
03D0F9772F4DE7E800613D1E /* Monitoring */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
031A80DB2F4CF1690016F7DD /* CommandTracker.swift */,
|
||||
C46EBC4A28DB966A007ACC74 /* TrackedShell.swift */,
|
||||
C4E49DED28F764A00026AC4E /* TrackedCommand.swift */,
|
||||
);
|
||||
path = Monitoring;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
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 = "<group>";
|
||||
@@ -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 */,
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -15,7 +15,7 @@ class TestableCommand: CommandProtocol {
|
||||
|
||||
var commands: [String: String]
|
||||
|
||||
public func executeRaw(
|
||||
public func execute(
|
||||
path: String,
|
||||
arguments: [String],
|
||||
trimNewlines: Bool,
|
||||
|
||||
@@ -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<T>(description: String, _ work: () -> T) -> T {
|
||||
let trackingId = commandTracker.trackFromAnyThread(description)
|
||||
defer {
|
||||
commandTracker.completeFromAnyThread(trackingId)
|
||||
}
|
||||
return work()
|
||||
}
|
||||
|
||||
func trackedCommandAsync<T>(
|
||||
description: String,
|
||||
_ work: () async throws -> T
|
||||
) async rethrows -> T {
|
||||
let trackingId = commandTracker.trackFromAnyThread(description)
|
||||
defer {
|
||||
commandTracker.completeFromAnyThread(trackingId)
|
||||
}
|
||||
return try await work()
|
||||
}
|
||||
}
|
||||
38
phpmon/Common/Monitoring/TrackedCommand.swift
Normal file
38
phpmon/Common/Monitoring/TrackedCommand.swift
Normal file
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
65
phpmon/Common/Monitoring/TrackedShell.swift
Normal file
65
phpmon/Common/Monitoring/TrackedShell.swift
Normal file
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user