From c929c6b5b91069be154e6442ee55ba22a600afc2 Mon Sep 17 00:00:00 2001 From: Jimmy Lewis Date: Sat, 1 Jun 2024 11:49:37 -0700 Subject: [PATCH] Add variable expansion in defaultDestination This implements #68, except I used [Name] instead of [LibraryName]. It felt like LibraryName should be matched with LibraryVersion and that felt verbose, so I took the shorter versions. This expansion is applied when we expand the ManifestOnDisk (which is either read from disk or from a raw JSON) into LibraryInstallationState. This is where we determine to use the defaultDestination or a library-specific destination, so it should be the only place this expansion needs to occur. --- .../Json/LibraryStateToFileConverter.cs | 40 ++++++- .../Json/LibraryStateToFileConverterTests.cs | 109 ++++++++++++++++++ 2 files changed, 145 insertions(+), 4 deletions(-) create mode 100644 test/LibraryManager.Test/Json/LibraryStateToFileConverterTests.cs diff --git a/src/LibraryManager/Json/LibraryStateToFileConverter.cs b/src/LibraryManager/Json/LibraryStateToFileConverter.cs index ef77e5807..4c75f9738 100644 --- a/src/LibraryManager/Json/LibraryStateToFileConverter.cs +++ b/src/LibraryManager/Json/LibraryStateToFileConverter.cs @@ -1,4 +1,9 @@ -using Microsoft.Web.LibraryManager.Contracts; +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Diagnostics.CodeAnalysis; +using System.Text; +using Microsoft.Web.LibraryManager.Contracts; using Microsoft.Web.LibraryManager.LibraryNaming; namespace Microsoft.Web.LibraryManager.Json @@ -22,10 +27,14 @@ public ILibraryInstallationState ConvertToLibraryInstallationState(LibraryInstal } string provider = string.IsNullOrEmpty(stateOnDisk.ProviderId) ? _defaultProvider : stateOnDisk.ProviderId; - string destination = string.IsNullOrEmpty(stateOnDisk.DestinationPath) ? _defaultDestination : stateOnDisk.DestinationPath; + + (string name, string version) = LibraryIdToNameAndVersionConverter.Instance.GetLibraryNameAndVersion(stateOnDisk.LibraryId, provider); + string destination = string.IsNullOrEmpty(stateOnDisk.DestinationPath) ? ExpandDestination(_defaultDestination, name, version) : stateOnDisk.DestinationPath; var state = new LibraryInstallationState() { + Name = name, + Version = version, IsUsingDefaultDestination = string.IsNullOrEmpty(stateOnDisk.DestinationPath), IsUsingDefaultProvider = string.IsNullOrEmpty(stateOnDisk.ProviderId), ProviderId = provider, @@ -33,11 +42,34 @@ public ILibraryInstallationState ConvertToLibraryInstallationState(LibraryInstal Files = stateOnDisk.Files }; - (state.Name, state.Version) = LibraryIdToNameAndVersionConverter.Instance.GetLibraryNameAndVersion(stateOnDisk.LibraryId, provider); - return state; } + /// + /// Expands [Name] and [Version] tokens in the DefaultDestination + /// + /// The default destination string + /// Package name + /// Package version + /// + [SuppressMessage("Globalization", "CA1307:Specify StringComparison for clarity", Justification = "Not available on net481, not needed here (caseless)")] + private string ExpandDestination(string destination, string name, string version) + { + if (!destination.Contains("[")) + { + return destination; + } + + // if the name contains a slash (either filesystem or scoped packages), + // trim that and only take the last segment. + int cutIndex = name.LastIndexOfAny(['/', '\\']); + + StringBuilder stringBuilder = new StringBuilder(destination); + stringBuilder.Replace("[Name]", cutIndex == -1 ? name : name.Substring(cutIndex + 1)); + stringBuilder.Replace("[Version]", version); + return stringBuilder.ToString(); + } + public LibraryInstallationStateOnDisk ConvertToLibraryInstallationStateOnDisk(ILibraryInstallationState state) { if (state == null) diff --git a/test/LibraryManager.Test/Json/LibraryStateToFileConverterTests.cs b/test/LibraryManager.Test/Json/LibraryStateToFileConverterTests.cs new file mode 100644 index 000000000..20b0cd23f --- /dev/null +++ b/test/LibraryManager.Test/Json/LibraryStateToFileConverterTests.cs @@ -0,0 +1,109 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.Web.LibraryManager.Contracts; +using Microsoft.Web.LibraryManager.Json; +using Microsoft.Web.LibraryManager.LibraryNaming; +using Microsoft.Web.LibraryManager.Mocks; +using Microsoft.Web.LibraryManager.Providers.Cdnjs; + +namespace Microsoft.Web.LibraryManager.Test.Json +{ + [TestClass] + public class LibraryStateToFileConverterTests + { + [TestInitialize] + public void Setup() + { + string cacheFolder = Environment.ExpandEnvironmentVariables(@"%localappdata%\Microsoft\Library\"); + string projectFolder = Path.Combine(Path.GetTempPath(), "LibraryManager"); + var hostInteraction = new HostInteraction(projectFolder, cacheFolder); + var dependencies = new Dependencies(hostInteraction, new CdnjsProviderFactory()); + IProvider provider = dependencies.GetProvider("cdnjs"); + LibraryIdToNameAndVersionConverter.Instance.EnsureInitialized(dependencies); + } + + [TestMethod] + public void ConvertToLibraryInstallationState_NullStateOnDisk() + { + LibraryStateToFileConverter converter = new LibraryStateToFileConverter("provider", "destination"); + + ILibraryInstallationState result = converter.ConvertToLibraryInstallationState(null); + + Assert.IsNull(result); + } + + [TestMethod] + public void ConvertToLibraryInstallationState_UseDefaultProviderAndDestination() + { + LibraryStateToFileConverter converter = new LibraryStateToFileConverter("defaultProvider", "defaultDestination"); + + var stateOnDisk = new LibraryInstallationStateOnDisk + { + LibraryId = "libraryId", + }; + + ILibraryInstallationState result = converter.ConvertToLibraryInstallationState(stateOnDisk); + + Assert.AreEqual("defaultProvider", result.ProviderId); + Assert.AreEqual("defaultDestination", result.DestinationPath); + } + + [TestMethod] + public void ConvertToLibraryInstallationState_OverrideProviderAndDestination() + { + LibraryStateToFileConverter converter = new LibraryStateToFileConverter("defaultProvider", "defaultDestination"); + + var stateOnDisk = new LibraryInstallationStateOnDisk + { + LibraryId = "libraryId", + ProviderId = "provider", + DestinationPath = "destination", + }; + + ILibraryInstallationState result = converter.ConvertToLibraryInstallationState(stateOnDisk); + + Assert.AreEqual("provider", result.ProviderId); + Assert.AreEqual("destination", result.DestinationPath); + } + + [TestMethod] + public void ConvertToLibraryInstallationState_ExpandTokensInDefaultDestination() + { + LibraryStateToFileConverter converter = new LibraryStateToFileConverter("defaultProvider", "lib/[Name]/[Version]"); + + var stateOnDisk = new LibraryInstallationStateOnDisk + { + LibraryId = "testLibraryId@1.0", + // it needs to be a provider that uses the versioned naming scheme + ProviderId = "cdnjs", + }; + + ILibraryInstallationState result = converter.ConvertToLibraryInstallationState(stateOnDisk); + + Assert.AreEqual("lib/testLibraryId/1.0", result.DestinationPath); + } + + [TestMethod] + [DataRow("filesystem", "c:\\path\\to\\library")] + [DataRow("filesystem", "/path/to/library")] + [DataRow("cdnjs", "@scope/library@1.0.0")] + public void ConvertToLibraryInstallationState_ExpandTokensInDefaultDestination_NamesWithSlashes(string provider, string libraryId) + { + LibraryStateToFileConverter converter = new LibraryStateToFileConverter("defaultProvider", "lib/[Name]"); + + var stateOnDisk = new LibraryInstallationStateOnDisk + { + LibraryId = libraryId, + ProviderId = provider, + }; + + ILibraryInstallationState result = converter.ConvertToLibraryInstallationState(stateOnDisk); + + Assert.AreEqual("lib/library", result.DestinationPath); + } + } +}