diff --git a/Directory.Packages.props b/Directory.Packages.props index 6ef9afe..1e8aa77 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -4,7 +4,6 @@ - diff --git a/src/ModuleCore/Git/GitManager.cs b/src/ModuleCore/Git/GitManager.cs deleted file mode 100644 index 0d5af37..0000000 --- a/src/ModuleCore/Git/GitManager.cs +++ /dev/null @@ -1,162 +0,0 @@ -namespace ModuleCore.Git; - -// TODO: better name for this -public class GitManager -{ - private static readonly Lazy GitManagerInstance = new(() => new GitManager()); - public static GitManager Instance => GitManagerInstance.Value; - - /// - /// Always returns a new clean instance of GitManager - /// - internal static GitManager InternalFreshInstance => new(); - - /// - /// Simply .ToString() - /// - private static readonly string DirectorySeparator = Path.DirectorySeparatorChar.ToString(); - - private readonly InternalDirectory _repositories; - private readonly Lock _readWriteLock = new(); - - private GitManager() - { - Console.WriteLine($"{nameof(GitManager)} init"); - // Initialise the root container - _repositories = new InternalDirectory() - { - Name = DirectorySeparator, - InternalPath = DirectorySeparator - }; - } - - /// - /// Registers a git repository based on an absolute location. If is null or empty, - /// the registration will use the folder name for the git repo at the top level. - /// - /// - /// - /// The normalised string the repository was registered against - public string RegisterRepo(string absoluteRepositoryLocation, string registrationName) - { - // Depending on the caller, it might be possible that they've scripted automatic repo registration. Because I - // don't really want to account to all the subtle ways that can be parallised, I just naively lock on every - // registration attempt. This method should be quick regardless, and I could use ConcurrentDictionary except - // that means every instance of InternalDirectory would need it and yeah nah fuck that I can just lock at the - // top level - lock (_readWriteLock) - { - var normalisedName = NormaliseNamePath(string.IsNullOrWhiteSpace(registrationName) - ? new DirectoryInfo(absoluteRepositoryLocation).Name - : registrationName); - - // Regardless of if we get a name or not, the fully qualified version for us - // starts with a / - var directorySegmentsFromName = NameToSegments(normalisedName); - - var added = _repositories.Add(absoluteRepositoryLocation, directorySegmentsFromName); - - // Not sure about this, the Add should throw any exceptions on duplicate/failures but for now I'll leave this - // here - if (added == null) - { - throw new Exception("Failed to register location"); - } - - return normalisedName; - } - } - - /// - /// Takes a name and returns it as a queue of its parts, starting with a root of - /// - /// - /// - private Queue NameToSegments(string name) - { - var segments = name.Split(DirectorySeparator); - - return segments.Length == 1 - ? new Queue([DirectorySeparator, name]) - : new Queue([DirectorySeparator, ..segments]); - } - - /// - /// Normalises the path separators in the given string to use Path.DirectorySeparatorChar - /// - /// - /// - private string NormaliseNamePath(string name) - { - // Feels a bit hacky, but this will actually normalise a path to a valid form. So if the input is - // some/directory/paths, Path.GetRelativePath will normalise it to some\directory\paths, relative to ./ - // which is kind of handy but I also just wish there was a Path method that would do this for me. I know that - // the whole point of Path is that it's based on a file system, but file systems can also be arbitrary and not - // always be drive rooted. - // Either way, this works and saves me having to reimplement a worse method when it's more important that users - // are able to use file paths in whatever form they prefer, which means we leverage the internal implementation - // in a weird way. - return Path.GetRelativePath("./", name); - } - - private class InternalDirectory - { - /// - /// Name of the folder this - /// - public string Name { get; set; } = null!; - - public Dictionary Children { get; set; } = []; - - internal string InternalPath { get; set; } - - /// - /// If not null, this is the absolute location of a registered git repository - /// - public string? FullRepositoryPath { get; set; } - - /// - /// - /// - /// - /// - /// - /// - internal InternalDirectory? Add(string absoluteRepositoryLocation, Queue directorySegmentsFromName) - { - var topStack = directorySegmentsFromName.Dequeue(); - - if (topStack != Name) - { - // logically it shouldn't be possible to have a value on top of the stack that _doesn't_ exist, but - // just incase we throw as this should only happen if an Add is attempted on the root and the queue was - // not correctly rooted to / - throw new Exception($"Directory segment does not seem to exist: {topStack}"); - } - - // We're at the end of the directory segments so we can safely say we're at the end of the tree so - // we add it to the relevant dictionary - if (directorySegmentsFromName.Count == 0) - { - FullRepositoryPath = absoluteRepositoryLocation; - return this; - } - - var nextSegment = directorySegmentsFromName.Peek(); - - // Attempt to get the next level of the directory. If we don't have a key entry, create one - if (!Children.TryGetValue(nextSegment, out var nextChild)) - { - nextChild = new InternalDirectory() - { - Name = nextSegment, - InternalPath = Path.Combine(InternalPath, nextSegment) - }; - Children.Add(nextSegment, nextChild); - } - - // add the next - return nextChild.Add(absoluteRepositoryLocation, directorySegmentsFromName); - } - } -} \ No newline at end of file diff --git a/src/ModuleCore/ModuleCore.csproj b/src/ModuleCore/ModuleCore.csproj index 2f41110..4ef1a45 100644 --- a/src/ModuleCore/ModuleCore.csproj +++ b/src/ModuleCore/ModuleCore.csproj @@ -7,10 +7,4 @@ latestmajor - - - <_Parameter1>ModuleTests - - - diff --git a/src/PowershellHarness/Program.cs b/src/PowershellHarness/Program.cs index 4bbd0be..077a9b7 100644 --- a/src/PowershellHarness/Program.cs +++ b/src/PowershellHarness/Program.cs @@ -2,7 +2,6 @@ using System.Management.Automation.Runspaces; using System.Text; using PowershellModule.Calendar; -using PowershellModule.Git; namespace PowershellHarness; @@ -37,26 +36,8 @@ class Program var host = new CustomHost(Console.WindowWidth); var runspace = InitialisePowershellHost(host); - host.UI.SetNextPromptChoice(3); + // InvokeCommand(runspace, GetCalendarCommand.FullName); - InvokeCommand(runspace, "New-PSDrive", [ - CreateCommand("name", "git-test"), - CreateCommand("PSProvider", "GitRepo"), - CreateCommand("Root", "\\"), - ]); - - InvokeCommand(runspace, "Set-Location", - [ - // Technically this can just be the command but this is a bit easier - CreateCommand("path", "git-test:/") - ]); - - InvokeCommand(runspace, "Get-Location"); - } - - private static void CalendarTestCommands(Runspace runspace) - { - InvokeCommand(runspace, GetCalendarCommand.FullName); foreach (var day in Enum.GetValues()) { InvokeCommand(runspace, GetCalendarCommand.FullName, [ @@ -67,15 +48,11 @@ class Program ]); } - InvokeCommand(runspace, GetCalendarCommand.FullName, [ - new CommandParameter(nameof(GetCalendarCommand.MarkedDaySymbol), "🤫"), - new CommandParameter(nameof(GetCalendarCommand.StartOfWeek), DayOfWeek.Saturday) - ]); - InvokeCommand(runspace, GetCalendarCommand.FullName, [new CommandParameter(nameof(GetCalendarCommand.StartOfWeek), "Sunday")]); - } - - private static void TestGitProvider(Runspace runspace) - { + // InvokeCommand(runspace, GetCalendarCommand.FullName, [ + // new CommandParameter(nameof(GetCalendarCommand.MarkedDaySymbol), "🤫"), + // new CommandParameter(nameof(GetCalendarCommand.StartOfWeek), DayOfWeek.Saturday) + // ]); + // InvokeCommand(runspace, GetCalendarCommand.FullName, [new CommandParameter(nameof(GetCalendarCommand.StartOfWeek), "Sunday")]); } private static CommandParameter CreateCommand(string name, string? argument = null) @@ -120,10 +97,6 @@ class Program var getCalendarCommand = new SessionStateCmdletEntry(GetCalendarCommand.FullName, typeof(GetCalendarCommand), null); initialSessionState.Commands.Add(getCalendarCommand); - - var gitProvider = new SessionStateProviderEntry(GitProvider.Name, typeof(GitProvider), null); - initialSessionState.Providers.Add(gitProvider); - // Create a runspace from the state, open and return it var runspace = RunspaceFactory.CreateRunspace(host, initialSessionState); @@ -141,29 +114,16 @@ class Program // and this is just a debug harness so it doesn't really matter for now using var pipeline = runspace.CreatePipeline(); // using var powershell = PowerShell.Create(runspace); - - // StringBuilder to store the output of this command including any output results (but not errors yet) - // this is just a rudimentary test and the pwsh debug profile should be used instead as it loads the module - // in a full powershell window with debugger attached. Just no automatic command running sadly. - var sb = new StringBuilder(); - - sb.Append(command); var cmd = new Command(command); if (parameters is not null) { - var param = parameters.ToList(); - sb.Append(' ') - .AppendJoin(' ', param.Select(x => $"-{x.Name} {x.Value}")); - - foreach (var commandParameter in param) + foreach (var commandParameter in parameters) { cmd.Parameters.Add(commandParameter); } } - sb.AppendLine(); - pipeline.Commands.Add(cmd); // powershell.Commands.AddCommand(cmd); @@ -173,11 +133,8 @@ class Program // var results = powershell.Invoke(); foreach (var result in results) { - sb.AppendLine(result.ToString()); - // Console.WriteLine(result); + Console.Write(result); } - - Console.WriteLine(sb); } catch (Exception ex) { diff --git a/src/PowershellModule/Git/GitProvider.cs b/src/PowershellModule/Git/GitProvider.cs deleted file mode 100644 index e340c2b..0000000 --- a/src/PowershellModule/Git/GitProvider.cs +++ /dev/null @@ -1,86 +0,0 @@ -using System; -using System.Management.Automation; -using System.Management.Automation.Provider; - -namespace PowershellModule.Git; - -// https://learn.microsoft.com/en-us/powershell/scripting/developer/provider/accessdbprovidersample02?view=powershell-7.6 -[CmdletProvider(Name, ProviderCapabilities.None)] -public class GitProvider : NavigationCmdletProvider -{ - public const string Name = "GitRepo"; - - public GitProvider() - { - } - - protected override PSDriveInfo NewDrive(PSDriveInfo drive) - { - // This is a bit of a hack to ensure that no drive is registered with a root location. - // It seems to be how PSDrives are registered for Alias providers, and we technically don't have any concept - // of a normal file system - if (drive.Root != string.Empty) - { - throw new Exception("Drive root must be an empty string"); - } - - return base.NewDrive(drive); - } - - protected override void NewItem(string path, string itemTypeName, object newItemValue) - { - //base.NewItem(path, itemTypeName, newItemValue); - } - - protected override PSDriveInfo RemoveDrive(PSDriveInfo drive) - { - return base.RemoveDrive(drive); - } - - protected override void GetChildNames(string path, ReturnContainers returnContainers) - { - // TODO: get all configured repos based on a path from the underlying GitPsDriveInfo then - // output their repository names - } - - protected override void GetChildItems(string path, bool recurse) - { - // TODO: get all configured repos based on a path from the underlying GitPsDriveInfo then - // output their repository locations and any other meta data I store - } - - // I think this is needed to be able to cd into it - protected override bool IsItemContainer(string path) - { - return true; - } - - // I think this is needed to be able to cd into it - protected override bool ItemExists(string path) - { - return true; - } - - protected override string MakePath(string parent, string child) - { - // Test to see what happens if set the location if the path matches a pretend repo registered with the name test - // The result of the test is that yes, this does work, but there's also a shit load of calls to this method as - // it traverses the entire directory path. - // This seems to just be internal powershell behaviour so as long as this is quick the first time, everything - // afterwards just always happens. But probably happens more seeing as I'm changing the location of the path - // in this current SessionState and probably doesn't happen if this were a normal path traversal - // where I don't set the location. - if (child == "test") - { - SessionState.Path.SetLocation("F:/Repos/PowershellModule/src/PowershellModule/Git"); - return "F:/Repos/PowershellModule/src/PowershellModule/Git"; - } - - return base.MakePath(parent, child); - } - - protected override bool IsValidPath(string path) - { - throw new System.NotImplementedException(); - } -} \ No newline at end of file diff --git a/src/PowershellModule/Git/GitPsDriveInfo.cs b/src/PowershellModule/Git/GitPsDriveInfo.cs deleted file mode 100644 index 2566267..0000000 --- a/src/PowershellModule/Git/GitPsDriveInfo.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Management.Automation; - -namespace PowershellModule.Git; - -public class GitPsDriveInfo : PSDriveInfo -{ - protected GitPsDriveInfo(PSDriveInfo driveInfo) - : base(driveInfo) - { - - } -} \ No newline at end of file diff --git a/src/PowershellModule/Git/NewGitRepoCommand.cs b/src/PowershellModule/Git/NewGitRepoCommand.cs deleted file mode 100644 index 2b4bf6a..0000000 --- a/src/PowershellModule/Git/NewGitRepoCommand.cs +++ /dev/null @@ -1,114 +0,0 @@ -using System; -using System.Diagnostics; -using System.IO; -using System.Management.Automation; -using ModuleCore.Git; - -namespace PowershellModule.Git; - -[Cmdlet(VerbsCommon.New, Noun)] -public class NewGitRepoCommand : PSCmdlet -{ - private const string Noun = "GitRepo"; - - [Parameter( - Position = 0, - ValueFromPipeline = true, - HelpMessage = "Reference name for the repo")] - public string? Name { get; set; } - - public NewGitRepoCommand() - { - Console.WriteLine($"{nameof(NewGitRepoCommand)} init"); - } - - protected override void BeginProcessing() - { - var pwd = this.SessionState.Path.CurrentLocation.Path; - WriteObject("Checking if current directory is a git repository..."); - - var repoFolfder = IsGitRepo(pwd); - - if (repoFolfder is not null) - { - GitManager.Instance.RegisterRepo(repoFolfder.Directory, Name ?? repoFolfder.Folder); - } - else - { - // Not sure how we'd hit this path, but in case we do, show some sort of error. - // TODO: I should probably have IsGitRepo throw instead so I can get the location it tried in the stack track - WriteError(new ErrorRecord( - new Exception("Unable to register repo - failed to parse git repo location"), - "git-parse-failed", - ErrorCategory.InvalidData, - null - ) - ); - } - - base.BeginProcessing(); - } - - private ParsedGitFolderDetails? IsGitRepo(string path) - { - var ps = new ProcessStartInfo("git", - ["rev-parse", "--show-toplevel"]) - { - RedirectStandardOutput = true, - RedirectStandardError = true, - WorkingDirectory = path - }; - - // If the user doesn't have git on their path, this will throw an exception that I don't have to do anything - // special with, it'll be unhandled and powershell will handle it - var gitProcess = Process.Start(ps); - - // This probably shouldn't be possible? Not really sure of the conditions where the process could be started - // but return null, but I'm going to consider that unrecoverable error territory - if (gitProcess is null) - { - throw new Exception("git failed to start"); - } - - gitProcess.WaitForExit(); - - if (!gitProcess.StandardOutput.EndOfStream) - { - var directory = gitProcess.StandardOutput.ReadToEnd(); - // Gotta trim what we get as it might already have a newline character at the end - var dirInfo = new DirectoryInfo(directory.Trim()); - - var repoFolderInfo = new ParsedGitFolderDetails - { - Directory = dirInfo.FullName, - Folder = dirInfo.Name, - }; - - return repoFolderInfo; - } - - if (!gitProcess.StandardError.EndOfStream) - { - var errorAsException = new Exception(gitProcess.StandardError.ReadToEnd()); - WriteError(new ErrorRecord(errorAsException, "git-not-found", ErrorCategory.FromStdErr, null)); - } - - return null; - } - - /// - /// The directory details of the directory returned from git rev-parse --show-toplevel - /// - private class ParsedGitFolderDetails - { - /// - /// The full path to the top level folder containing a git repository - /// - public string Directory { get; set; } = null!; - - /// - /// The last folder name of the directory - /// - public string Folder { get; set; } = null!; - } -} \ No newline at end of file diff --git a/src/PowershellModule/Git/SetGitRepoCommand.cs b/src/PowershellModule/Git/SetGitRepoCommand.cs deleted file mode 100644 index 9d6a7f1..0000000 --- a/src/PowershellModule/Git/SetGitRepoCommand.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Management.Automation; -using ModuleCore.Git; - -namespace PowershellModule.Git; - -[Cmdlet(VerbsCommon.Set, Noun)] -public class SetGitRepoCommand : PSCmdlet -{ - private const string Noun = "GitRepo"; - - public SetGitRepoCommand() - { - Console.WriteLine($"{nameof(NewGitRepoCommand)} init"); - var a = GitManager.Instance; - } - - protected override void BeginProcessing() - { - SessionState.Path.SetLocation("F:/Repos/PowershellModule/src/PowershellModule/Git"); - base.BeginProcessing(); - } -} \ No newline at end of file diff --git a/src/PowershellModule/PostBuild.ps1 b/src/PowershellModule/PostBuild.ps1 deleted file mode 100644 index d4c0725..0000000 --- a/src/PowershellModule/PostBuild.ps1 +++ /dev/null @@ -1,27 +0,0 @@ -<# - .SYNOPSIS - Removes all non-core files from build output -#> -param( - [Parameter(ValueFromPipeline = $true, Position = 0, Mandatory = $true)] - $targetDir -) - -$nativeSqliteDllLocation = "$( $targetDir )runtimes\win-x64\native\e_sqlite3.dll"; - -if ($false -eq (Test-Path $nativeSqliteDllLocation)) -{ - Write-Error "POST BUILD FAILED: Unable to locate $nativeSqliteDllLocation" -} - -Write-Host "Copying '$nativeSqliteDllLocation' to '$targetDir'" -Copy-Item -Path $nativeSqliteDllLocation -Destination $targetDir - -# Because we use CopyLocalLockFileAssemblies Files that are needed for the module -$allowList = @( - "ModuleCore*" - "PowershellModule*" - "*SQLite*" -) -Write-Host "Removing all non-module required files from '$targetDir'" -Get-ChildItem -Path $targetDir -exclude $allowList | Remove-Item -Recurse \ No newline at end of file diff --git a/src/PowershellModule/PowershellModule.csproj b/src/PowershellModule/PowershellModule.csproj index 95ccbdc..c7f5801 100644 --- a/src/PowershellModule/PowershellModule.csproj +++ b/src/PowershellModule/PowershellModule.csproj @@ -5,22 +5,16 @@ PowershellModule latestmajor enable - true All - - - - - diff --git a/tests/ModuleTests/Git/AddRegistrationTests.cs b/tests/ModuleTests/Git/AddRegistrationTests.cs deleted file mode 100644 index 6de31d6..0000000 --- a/tests/ModuleTests/Git/AddRegistrationTests.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System.Text; -using ModuleCore.Calendar; -using ModuleCore.Git; -using ModuleTests.Git.TestData; - -namespace ModuleTests.Git; - -public class AddRegistrationTests -{ - private static readonly VerifySettings Settings; - - static AddRegistrationTests() - { - Settings = new VerifySettings(); - var testBaseDirectory = Path.Join(TestConstants.SnapshotFolderName, nameof(AddRegistrationTests)); - - Settings.UseDirectory(testBaseDirectory); - Settings.DisableDiff(); - } - - [Theory] - [ClassData(typeof(AddRegistrationTestData))] - public Task BasicRepoRegistration((int testId, string path) testData) - { - Settings.UseFileName($"{nameof(BasicRepoRegistration)}_{testData.testId}"); - - var gitManager = GitManager.InternalFreshInstance; - - var repoRegistration = gitManager.RegisterRepo("Test:/some/test/repo", testData.path); - var sb = new StringBuilder(); - sb.AppendLine($"Attempted to register: {testData.path}") - .AppendLine($"Registration result: {repoRegistration}"); - - return Verify(sb, Settings); - } - - [Fact] - public void RepoRegistrationWithEmptyName() - { - Settings.UseFileName(nameof(RepoRegistrationWithEmptyName)); - - var gitManager = GitManager.InternalFreshInstance; - var testRepoAbsolutePath = "Test:/some/test/repo"; - - var emptyName = gitManager.RegisterRepo(testRepoAbsolutePath, ""); - - Assert.Equal("repo", emptyName); - } - - [Fact] - public void RepoRegistrationWithNullName() - { - Settings.UseFileName(nameof(RepoRegistrationWithNullName)); - - var gitManager = GitManager.InternalFreshInstance; - var testRepoAbsolutePath = "Test:/some/test/repo"; - - // Name is technically not-nullable, but string is a reference type so null can be passed in so we should test - // it regardless - var nullName = gitManager.RegisterRepo(testRepoAbsolutePath, null!); - - Assert.Equal("repo", nullName); - } - - [Fact] - public void RepoRegistrationWithWhitespaceName() - { - Settings.UseFileName(nameof(RepoRegistrationWithWhitespaceName)); - - var gitManager = GitManager.InternalFreshInstance; - var testRepoAbsolutePath = "Test:/some/test/repo"; - - var whitespaceName = gitManager.RegisterRepo(testRepoAbsolutePath, " "); - - Assert.Equal("repo", whitespaceName); - } -} \ No newline at end of file diff --git a/tests/ModuleTests/Git/Snapshots/AddRegistrationTests/BasicRepoRegistration_0.verified.txt b/tests/ModuleTests/Git/Snapshots/AddRegistrationTests/BasicRepoRegistration_0.verified.txt deleted file mode 100644 index 3183455..0000000 --- a/tests/ModuleTests/Git/Snapshots/AddRegistrationTests/BasicRepoRegistration_0.verified.txt +++ /dev/null @@ -1,2 +0,0 @@ -Attempted to register: test -Registration result: test diff --git a/tests/ModuleTests/Git/Snapshots/AddRegistrationTests/BasicRepoRegistration_1.verified.txt b/tests/ModuleTests/Git/Snapshots/AddRegistrationTests/BasicRepoRegistration_1.verified.txt deleted file mode 100644 index e9b945d..0000000 --- a/tests/ModuleTests/Git/Snapshots/AddRegistrationTests/BasicRepoRegistration_1.verified.txt +++ /dev/null @@ -1,2 +0,0 @@ -Attempted to register: test\path -Registration result: test\path diff --git a/tests/ModuleTests/Git/Snapshots/AddRegistrationTests/BasicRepoRegistration_2.verified.txt b/tests/ModuleTests/Git/Snapshots/AddRegistrationTests/BasicRepoRegistration_2.verified.txt deleted file mode 100644 index 3c0b092..0000000 --- a/tests/ModuleTests/Git/Snapshots/AddRegistrationTests/BasicRepoRegistration_2.verified.txt +++ /dev/null @@ -1,2 +0,0 @@ -Attempted to register: other/path -Registration result: other\path diff --git a/tests/ModuleTests/Git/TestData/AddRegistrationTestData.cs b/tests/ModuleTests/Git/TestData/AddRegistrationTestData.cs deleted file mode 100644 index 3668ba2..0000000 --- a/tests/ModuleTests/Git/TestData/AddRegistrationTestData.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Linq; - -namespace ModuleTests.Git.TestData; - -public class AddRegistrationTestData : TestDataEnumerator<(int testId, string path)> -{ - public AddRegistrationTestData() - { - Data = new List() - { - "test", - $"test{Path.DirectorySeparatorChar}path", - $"other{Path.AltDirectorySeparatorChar}path" - } - .Select((x, i) => (i, x)) - .ToList(); - } -} \ No newline at end of file