From bb24aef0669ed4305e8556bbd87c4a0724e32a00 Mon Sep 17 00:00:00 2001
From: futrime <35801754+futrime@users.noreply.github.com>
Date: Wed, 22 Jan 2025 09:04:07 +0800
Subject: [PATCH] refactor: split external interfaces to independent
Lip.Context package
---
Lip.Context/.gitattributes | 8 ++
Lip.Context/.gitignore | 37 ++++++++
Lip.Context/IContext.cs | 14 +++
Lip.Context/IDownloader.cs | 6 ++
Lip.Context/IGit.cs | 6 ++
{Lip => Lip.Context}/IUserInteraction.cs | 10 +--
Lip.Context/Lip.Context.csproj | 14 +++
Lip.Tests/LipConfigTests.cs | 107 ++++++++++-------------
Lip.Tests/LipInitTests.cs | 50 ++++++-----
Lip.Tests/LipListTests.cs | 74 +++++++---------
Lip.Tests/PackageSpecifierTests.cs | 7 +-
Lip.Tests/PathManagerTests.cs | 41 ++++++---
Lip.Tests/RuntimeConfigTests.cs | 2 -
Lip/Lip.Config.cs | 2 +-
Lip/Lip.Init.cs | 24 +++--
Lip/Lip.List.cs | 12 ++-
Lip/Lip.cs | 20 ++---
Lip/Lip.csproj | 7 +-
Lip/PackageSpecifier.cs | 4 +
Lip/PathManager.cs | 38 +++-----
Lip/RuntimeConfig.cs | 3 -
21 files changed, 278 insertions(+), 208 deletions(-)
create mode 100644 Lip.Context/.gitattributes
create mode 100644 Lip.Context/.gitignore
create mode 100644 Lip.Context/IContext.cs
create mode 100644 Lip.Context/IDownloader.cs
create mode 100644 Lip.Context/IGit.cs
rename {Lip => Lip.Context}/IUserInteraction.cs (88%)
create mode 100644 Lip.Context/Lip.Context.csproj
diff --git a/Lip.Context/.gitattributes b/Lip.Context/.gitattributes
new file mode 100644
index 0000000..f7c7529
--- /dev/null
+++ b/Lip.Context/.gitattributes
@@ -0,0 +1,8 @@
+# Auto detect text files and perform LF normalization
+* text=auto
+
+*.cs text diff=csharp
+*.cshtml text diff=html
+*.csx text diff=csharp
+*.sln text eol=crlf
+*.csproj text eol=crlf
diff --git a/Lip.Context/.gitignore b/Lip.Context/.gitignore
new file mode 100644
index 0000000..a437a65
--- /dev/null
+++ b/Lip.Context/.gitignore
@@ -0,0 +1,37 @@
+*.swp
+*.*~
+project.lock.json
+.DS_Store
+*.pyc
+nupkg/
+
+# Visual Studio Code
+.vscode
+
+# Rider
+.idea
+
+# User-specific files
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+build/
+bld/
+[Bb]in/
+[Oo]bj/
+[Oo]ut/
+msbuild.log
+msbuild.err
+msbuild.wrn
+
+# Visual Studio 2015
+.vs/
diff --git a/Lip.Context/IContext.cs b/Lip.Context/IContext.cs
new file mode 100644
index 0000000..b5e77e4
--- /dev/null
+++ b/Lip.Context/IContext.cs
@@ -0,0 +1,14 @@
+using System.IO.Abstractions;
+using Microsoft.Extensions.Logging;
+
+namespace Lip.Context;
+
+public interface IContext
+{
+ IDownloader Downloader { get; }
+ IFileSystem FileSystem { get; }
+ IGit? Git { get; }
+ ILogger Logger { get; }
+ string RuntimeIdentifier { get; }
+ IUserInteraction UserInteraction { get; }
+}
diff --git a/Lip.Context/IDownloader.cs b/Lip.Context/IDownloader.cs
new file mode 100644
index 0000000..bedf3b6
--- /dev/null
+++ b/Lip.Context/IDownloader.cs
@@ -0,0 +1,6 @@
+namespace Lip.Context;
+
+public interface IDownloader
+{
+ Task DownloadFile(Uri url, string destinationPath);
+}
diff --git a/Lip.Context/IGit.cs b/Lip.Context/IGit.cs
new file mode 100644
index 0000000..9241b3e
--- /dev/null
+++ b/Lip.Context/IGit.cs
@@ -0,0 +1,6 @@
+namespace Lip.Context;
+
+public interface IGit
+{
+ Task Clone(string repository, string directory, int? depth = null);
+}
diff --git a/Lip/IUserInteraction.cs b/Lip.Context/IUserInteraction.cs
similarity index 88%
rename from Lip/IUserInteraction.cs
rename to Lip.Context/IUserInteraction.cs
index e54c83f..ace47e9 100644
--- a/Lip/IUserInteraction.cs
+++ b/Lip.Context/IUserInteraction.cs
@@ -1,4 +1,4 @@
-namespace Lip;
+namespace Lip.Context;
///
/// Represents a user interaction interface.
@@ -16,22 +16,22 @@ public interface IUserInteraction
/// Prompts user for text input.
///
/// The prompt message
- /// Optional default value
/// User input as string
Task PromptForInput(string format, params object[] args);
///
/// Prompts user to select from multiple options.
///
- /// The prompt message
/// Available options
+ /// The prompt message
/// Selected option
Task PromptForSelection(IEnumerable options, string format, params object[] args);
///
/// Shows progress for long-running operations.
///
- /// Progress message
+ /// Progress ID
/// Progress value (0.0-1.0)
- Task UpdateProgress(float progress, string format, params object[] args);
+ /// Progress message
+ Task UpdateProgress(string id, float progress, string format, params object[] args);
}
diff --git a/Lip.Context/Lip.Context.csproj b/Lip.Context/Lip.Context.csproj
new file mode 100644
index 0000000..82b5c18
--- /dev/null
+++ b/Lip.Context/Lip.Context.csproj
@@ -0,0 +1,14 @@
+
+
+
+ net9.0
+ enable
+ enable
+
+
+
+
+
+
+
+
diff --git a/Lip.Tests/LipConfigTests.cs b/Lip.Tests/LipConfigTests.cs
index d6bedf6..8af151e 100644
--- a/Lip.Tests/LipConfigTests.cs
+++ b/Lip.Tests/LipConfigTests.cs
@@ -1,4 +1,5 @@
using System.IO.Abstractions.TestingHelpers;
+using Lip.Context;
using Microsoft.Extensions.Logging;
using Moq;
@@ -24,10 +25,10 @@ public async Task ConfigDelete_SingleItem_ResetsToDefault()
{ s_runtimeConfigPath, new MockFileData(initialRuntimeConfig.ToBytes()) },
});
- Mock logger = new();
- Mock userInteraction = new();
+ Mock context = new();
+ context.SetupGet(c => c.FileSystem).Returns(fileSystem);
- Lip lip = new(initialRuntimeConfig, fileSystem, logger.Object, userInteraction.Object);
+ Lip lip = new(initialRuntimeConfig, context.Object);
// Act.
await lip.ConfigDelete(["color"], new Lip.ConfigDeleteArgs());
@@ -66,10 +67,10 @@ public async Task ConfigDelete_MultipleItems_ResetsToDefault()
{ s_runtimeConfigPath, new MockFileData(initialRuntimeConfig.ToBytes()) },
});
- Mock logger = new();
- Mock userInteraction = new();
+ Mock context = new();
+ context.SetupGet(c => c.FileSystem).Returns(fileSystem);
- Lip lip = new(initialRuntimeConfig, fileSystem, logger.Object, userInteraction.Object);
+ Lip lip = new(initialRuntimeConfig, context.Object);
// Act.
await lip.ConfigDelete(["color", "git"], new Lip.ConfigDeleteArgs());
@@ -97,11 +98,10 @@ public async Task ConfigDelete_EmptyKeys_ThrowsArgumentException()
{
// Arrange.
RuntimeConfig initialRuntimeConfig = new();
- MockFileSystem fileSystem = new();
- Mock logger = new();
- Mock userInteraction = new();
- Lip lip = new(initialRuntimeConfig, fileSystem, logger.Object, userInteraction.Object);
+ Mock context = new();
+
+ Lip lip = new(initialRuntimeConfig, context.Object);
// Act & Assert.
ArgumentException exception = await Assert.ThrowsAsync(
@@ -114,11 +114,10 @@ public async Task ConfigDelete_UnknownKey_ThrowsArgumentException()
{
// Arrange.
RuntimeConfig initialRuntimeConfig = new();
- MockFileSystem fileSystem = new();
- Mock logger = new();
- Mock userInteraction = new();
- Lip lip = new(initialRuntimeConfig, fileSystem, logger.Object, userInteraction.Object);
+ Mock context = new();
+
+ Lip lip = new(initialRuntimeConfig, context.Object);
// Act & Assert.
ArgumentException exception = await Assert.ThrowsAsync(
@@ -131,11 +130,10 @@ public async Task ConfigDelete_PartialUnknownItem_ThrowsArgumentException()
{
// Arrange.
RuntimeConfig initialRuntimeConfig = new();
- MockFileSystem fileSystem = new();
- Mock logger = new();
- Mock userInteraction = new();
- Lip lip = new(initialRuntimeConfig, fileSystem, logger.Object, userInteraction.Object);
+ Mock context = new();
+
+ Lip lip = new(initialRuntimeConfig, context.Object);
// Act & Assert.
ArgumentException argumentException = await Assert.ThrowsAsync(
@@ -154,10 +152,10 @@ public void ConfigGet_SingleItem_Passes()
{ s_runtimeConfigPath, new MockFileData(initialRuntimeConfig.ToBytes()) },
});
- Mock logger = new();
- Mock userInteraction = new();
+ Mock context = new();
+ context.SetupGet(c => c.FileSystem).Returns(fileSystem);
- Lip lip = new(initialRuntimeConfig, fileSystem, logger.Object, userInteraction.Object);
+ Lip lip = new(initialRuntimeConfig, context.Object);
// Act.
Dictionary result = lip.ConfigGet(["cache"], new Lip.ConfigGetArgs());
@@ -182,10 +180,10 @@ public void ConfigGet_MultipleItems_Passes()
{ s_runtimeConfigPath, new MockFileData(initialRuntimeConfig.ToBytes()) },
});
- Mock logger = new();
- Mock userInteraction = new();
+ Mock context = new();
+ context.SetupGet(c => c.FileSystem).Returns(fileSystem);
- Lip lip = new(initialRuntimeConfig, fileSystem, logger.Object, userInteraction.Object);
+ Lip lip = new(initialRuntimeConfig, context.Object);
// Act.
Dictionary result = lip.ConfigGet(
@@ -203,11 +201,10 @@ public void ConfigGet_UnknownKey_ThrowsArgumentException()
{
// Arrange.
RuntimeConfig initialRuntimeConfig = new();
- MockFileSystem fileSystem = new();
- Mock logger = new();
- Mock userInteraction = new();
- Lip lip = new(initialRuntimeConfig, fileSystem, logger.Object, userInteraction.Object);
+ Mock context = new();
+
+ Lip lip = new(initialRuntimeConfig, context.Object);
// Act & Assert.
ArgumentException exception = Assert.Throws(
@@ -220,11 +217,10 @@ public void ConfigGet_PartialUnknownItem_ThrowsArgumentException()
{
// Arrange.
RuntimeConfig initialRuntimeConfig = new();
- MockFileSystem fileSystem = new();
- Mock logger = new();
- Mock userInteraction = new();
- Lip lip = new(initialRuntimeConfig, fileSystem, logger.Object, userInteraction.Object);
+ Mock context = new();
+
+ Lip lip = new(initialRuntimeConfig, context.Object);
// Act & Assert.
ArgumentException exception = Assert.Throws(
@@ -237,11 +233,10 @@ public void ConfigGet_EmptyKeys_ThrowsArgumentException()
{
// Arrange.
RuntimeConfig initialRuntimeConfig = new();
- MockFileSystem fileSystem = new();
- Mock logger = new();
- Mock userInteraction = new();
- Lip lip = new(initialRuntimeConfig, fileSystem, logger.Object, userInteraction.Object);
+ Mock context = new();
+
+ Lip lip = new(initialRuntimeConfig, context.Object);
// Act & Assert.
ArgumentException exception = Assert.Throws(
@@ -266,11 +261,9 @@ public void ConfigList_ReturnsAllConfigurations()
ScriptShell = "/custom/shell"
};
- MockFileSystem fileSystem = new();
- Mock logger = new();
- Mock userInteraction = new();
+ Mock context = new();
- Lip lip = new(initialRuntimeConfig, fileSystem, logger.Object, userInteraction.Object);
+ Lip lip = new(initialRuntimeConfig, context.Object);
// Act.
Dictionary result = lip.ConfigList(new Lip.ConfigGetArgs());
@@ -299,11 +292,10 @@ public async Task ConfigSet_SingleItem_Passes()
{ s_runtimeConfigPath, new MockFileData(initialRuntimeConfig.ToBytes()) },
});
- Mock logger = new();
-
- Mock userInteraction = new();
+ Mock context = new();
+ context.SetupGet(c => c.FileSystem).Returns(fileSystem);
- Lip lip = new(new(), fileSystem, logger.Object, userInteraction.Object);
+ Lip lip = new(initialRuntimeConfig, context.Object);
Dictionary keyValuePairs = new()
{
@@ -342,11 +334,10 @@ public async Task ConfigSet_MultipleItems_Passes()
{ s_runtimeConfigPath, new MockFileData(initialRuntimeConfig.ToBytes()) },
});
- Mock logger = new();
-
- Mock userInteraction = new();
+ Mock context = new();
+ context.SetupGet(c => c.FileSystem).Returns(fileSystem);
- Lip lip = new(new(), fileSystem, logger.Object, userInteraction.Object);
+ Lip lip = new(initialRuntimeConfig, context.Object);
Dictionary keyValuePairs = new()
{
@@ -393,11 +384,10 @@ public async Task ConfigSet_NoItems_ThrowsArgumentException()
{ s_runtimeConfigPath, new MockFileData(initialRuntimeConfig.ToBytes()) },
});
- Mock logger = new();
+ Mock context = new();
+ context.SetupGet(c => c.FileSystem).Returns(fileSystem);
- Mock userInteraction = new();
-
- Lip lip = new(new(), fileSystem, logger.Object, userInteraction.Object);
+ Lip lip = new(initialRuntimeConfig, context.Object);
Dictionary keyValuePairs = [];
@@ -419,11 +409,10 @@ public async Task ConfigSet_UnknownItem_ThrowsArgumentException()
{ s_runtimeConfigPath, new MockFileData(initialRuntimeConfig.ToBytes()) },
});
- Mock logger = new();
-
- Mock userInteraction = new();
+ Mock context = new();
+ context.SetupGet(c => c.FileSystem).Returns(fileSystem);
- Lip lip = new(new(), fileSystem, logger.Object, userInteraction.Object);
+ Lip lip = new(initialRuntimeConfig, context.Object);
Dictionary keyValuePairs = new()
{
@@ -449,10 +438,10 @@ public async Task ConfigSet_PartialUnknownItem_ThrowsArgumentException()
{ s_runtimeConfigPath, new MockFileData(initialRuntimeConfig.ToBytes()) },
});
- Mock logger = new();
- Mock userInteraction = new();
+ Mock context = new();
+ context.SetupGet(c => c.FileSystem).Returns(fileSystem);
- Lip lip = new(new(), fileSystem, logger.Object, userInteraction.Object);
+ Lip lip = new(initialRuntimeConfig, context.Object);
Dictionary keyValuePairs = new()
{
diff --git a/Lip.Tests/LipInitTests.cs b/Lip.Tests/LipInitTests.cs
index 8e3a117..3139e79 100644
--- a/Lip.Tests/LipInitTests.cs
+++ b/Lip.Tests/LipInitTests.cs
@@ -1,4 +1,5 @@
using System.IO.Abstractions.TestingHelpers;
+using Lip.Context;
using Microsoft.Extensions.Logging;
using Moq;
@@ -17,8 +18,6 @@ public async Task Init_Interactive_Passes()
{ s_workspacePath, new MockDirectoryData() },
}, currentDirectory: s_workspacePath);
- Mock logger = new();
-
Mock userInteraction = new();
userInteraction.Setup(u => u.PromptForInput(
"Enter the tooth path (e.g. {DefaultTooth}):",
@@ -37,12 +36,14 @@ public async Task Init_Interactive_Passes()
userInteraction.Setup(u => u.Confirm("Do you want to create the following package manifest file?\n{jsonString}", It.IsAny()).Result)
.Returns(true);
- Lip lip = new(new(), fileSystem, logger.Object, userInteraction.Object);
+ Mock context = new();
+ context.SetupGet(c => c.FileSystem).Returns(fileSystem);
+ context.SetupGet(c => c.UserInteraction).Returns(userInteraction.Object);
- Lip.InitArgs args = new();
+ Lip lip = new(new(), context.Object);
// Act.
- await lip.Init(args);
+ await lip.Init(new());
// Assert.
Assert.True(fileSystem.File.Exists(Path.Join(s_workspacePath, "tooth.json")));
@@ -73,11 +74,10 @@ public async Task Init_WithDefaultValues_Passes()
{ s_workspacePath, new MockDirectoryData() },
}, currentDirectory: s_workspacePath);
- Mock logger = new();
-
- Mock userInteraction = new();
+ Mock context = new();
+ context.SetupGet(c => c.FileSystem).Returns(fileSystem);
- Lip lip = new(new(), fileSystem, logger.Object, userInteraction.Object);
+ Lip lip = new(new(), context.Object);
Lip.InitArgs args = new()
{
@@ -110,11 +110,10 @@ public async Task Init_WithInitialValues_Passes()
{ s_workspacePath, new MockDirectoryData() },
}, currentDirectory: s_workspacePath);
- Mock logger = new();
-
- Mock userInteraction = new();
+ Mock context = new();
+ context.SetupGet(c => c.FileSystem).Returns(fileSystem);
- Lip lip = new(new(), fileSystem, logger.Object, userInteraction.Object);
+ Lip lip = new(new(), context.Object);
Lip.InitArgs args = new()
{
@@ -158,13 +157,15 @@ public async Task Init_OperationCanceled_ThrowsOperationCanceledException()
{ s_workspacePath, new MockDirectoryData() },
}, currentDirectory: s_workspacePath);
- Mock logger = new();
-
Mock userInteraction = new();
userInteraction.Setup(u => u.Confirm("Do you want to create the following package manifest file?\n{jsonString}", It.IsAny()).Result)
.Returns(false);
- Lip lip = new(new(), fileSystem, logger.Object, userInteraction.Object);
+ Mock context = new();
+ context.SetupGet(c => c.FileSystem).Returns(fileSystem);
+ context.SetupGet(c => c.UserInteraction).Returns(userInteraction.Object);
+
+ Lip lip = new(new(), context.Object);
Lip.InitArgs args = new()
{
@@ -190,13 +191,15 @@ public async Task Init_ManifestFileExists_ThrowsInvalidOperationException()
{ Path.Join(s_workspacePath, "tooth.json"), new MockFileData("content") },
}, currentDirectory: s_workspacePath);
- Mock logger = new();
-
Mock userInteraction = new();
userInteraction.Setup(u => u.Confirm("Do you want to create the following package manifest file?\n{jsonString}", It.IsAny()).Result)
.Returns(false);
- Lip lip = new(new(), fileSystem, logger.Object, userInteraction.Object);
+ Mock context = new();
+ context.SetupGet(c => c.FileSystem).Returns(fileSystem);
+ context.SetupGet(c => c.UserInteraction).Returns(userInteraction.Object);
+
+ Lip lip = new(new(), context.Object);
Lip.InitArgs args = new()
{
@@ -222,13 +225,16 @@ public async Task Init_OverwritesManifestFile_Passes()
{ Path.Join(s_workspacePath, "tooth.json"), new MockFileData("content") },
}, currentDirectory: s_workspacePath);
- Mock logger = new();
-
Mock userInteraction = new();
userInteraction.Setup(u => u.Confirm("Do you want to create the following package manifest file?\n{jsonString}", It.IsAny()).Result)
.Returns(false);
- Lip lip = new(new(), fileSystem, logger.Object, userInteraction.Object);
+ Mock context = new();
+ context.SetupGet(c => c.FileSystem).Returns(fileSystem);
+ context.SetupGet(c => c.Logger).Returns(new Mock().Object);
+ context.SetupGet(c => c.UserInteraction).Returns(userInteraction.Object);
+
+ Lip lip = new(new(), context.Object);
Lip.InitArgs args = new()
{
diff --git a/Lip.Tests/LipListTests.cs b/Lip.Tests/LipListTests.cs
index 0fa6adb..7d48ce7 100644
--- a/Lip.Tests/LipListTests.cs
+++ b/Lip.Tests/LipListTests.cs
@@ -1,5 +1,6 @@
using System.IO.Abstractions.TestingHelpers;
using System.Runtime.InteropServices;
+using Lip.Context;
using Microsoft.Extensions.Logging;
using Moq;
@@ -7,11 +8,12 @@ namespace Lip.Tests;
public class LipListTests
{
- [Fact]
- public async Task List_ReturnsListItems()
+ [Theory]
+ [InlineData("win-x64")]
+ [InlineData("linux-x64")]
+ [InlineData("osx-x64")]
+ public async Task List_ReturnsListItems(string runtimeIdentifier)
{
- RuntimeConfig initialRuntimeConfig = new();
-
// Arrange.
var fileSystem = new MockFileSystem(new Dictionary
{
@@ -28,7 +30,7 @@ public async Task List_ReturnsListItems()
"variants": [
{
"label": "variant1",
- "platform": "{{RuntimeInformation.RuntimeIdentifier}}"
+ "platform": "{{runtimeIdentifier}}"
}
]
},
@@ -40,7 +42,7 @@ public async Task List_ReturnsListItems()
"variants": [
{
"label": "variant2",
- "platform": "{{RuntimeInformation.RuntimeIdentifier}}"
+ "platform": "{{runtimeIdentifier}}"
}
]
}
@@ -56,15 +58,11 @@ public async Task List_ReturnsListItems()
""") }
});
- Mock logger = new();
+ Mock context = new();
+ context.SetupGet(c => c.FileSystem).Returns(fileSystem);
+ context.SetupGet(c => c.RuntimeIdentifier).Returns(runtimeIdentifier);
- Mock userInteraction = new();
-
- Lip lip = new(
- initialRuntimeConfig,
- fileSystem,
- logger.Object,
- userInteraction.Object);
+ Lip lip = new(new(), context.Object);
// Act.
List listItems = await lip.List(new());
@@ -84,16 +82,13 @@ public async Task List_ReturnsListItems()
[Fact]
public async Task List_LockFileNotExists_ReturnsEmptyList()
{
- RuntimeConfig initialRuntimeConfig = new();
-
// Arrange.
var fileSystem = new MockFileSystem();
- Mock logger = new();
+ Mock context = new();
+ context.SetupGet(c => c.FileSystem).Returns(fileSystem);
- Mock userInteraction = new();
-
- Lip lip = new(initialRuntimeConfig, fileSystem, logger.Object, userInteraction.Object);
+ Lip lip = new(new(), context.Object);
// Act.
List listItems = await lip.List(new());
@@ -105,8 +100,6 @@ public async Task List_LockFileNotExists_ReturnsEmptyList()
[Fact]
public async Task List_MismatchedToothPath_ReturnsListItems()
{
- RuntimeConfig initialRuntimeConfig = new();
-
// Arrange.
var fileSystem = new MockFileSystem(new Dictionary
{
@@ -122,7 +115,8 @@ public async Task List_MismatchedToothPath_ReturnsListItems()
"version": "1.0.0",
"variants": [
{
- "label": "variant1"
+ "label": "variant1",
+ "platform": "win-x64"
}
]
}
@@ -138,11 +132,11 @@ public async Task List_MismatchedToothPath_ReturnsListItems()
""") }
});
- Mock logger = new();
+ Mock context = new();
+ context.SetupGet(c => c.FileSystem).Returns(fileSystem);
+ context.SetupGet(c => c.RuntimeIdentifier).Returns("win-x64");
- Mock userInteraction = new();
-
- Lip lip = new(initialRuntimeConfig, fileSystem, logger.Object, userInteraction.Object);
+ Lip lip = new(new(), context.Object);
// Act.
List listItems = await lip.List(new());
@@ -158,8 +152,6 @@ public async Task List_MismatchedToothPath_ReturnsListItems()
[Fact]
public async Task List_MismatchedVersion_ReturnsListItems()
{
- RuntimeConfig initialRuntimeConfig = new();
-
// Arrange.
var fileSystem = new MockFileSystem(new Dictionary
{
@@ -175,7 +167,8 @@ public async Task List_MismatchedVersion_ReturnsListItems()
"version": "1.0.0",
"variants": [
{
- "label": "variant1"
+ "label": "variant1",
+ "platform": "win-x64"
}
]
}
@@ -191,11 +184,11 @@ public async Task List_MismatchedVersion_ReturnsListItems()
""") }
});
- Mock logger = new();
-
- Mock userInteraction = new();
+ Mock context = new();
+ context.SetupGet(c => c.FileSystem).Returns(fileSystem);
+ context.SetupGet(c => c.RuntimeIdentifier).Returns("win-x64");
- Lip lip = new(initialRuntimeConfig, fileSystem, logger.Object, userInteraction.Object);
+ Lip lip = new(new(), context.Object);
// Act.
List listItems = await lip.List(new());
@@ -211,8 +204,6 @@ public async Task List_MismatchedVersion_ReturnsListItems()
[Fact]
public async Task List_MismatchedVariantLabel_ReturnsListItems()
{
- RuntimeConfig initialRuntimeConfig = new();
-
// Arrange.
var fileSystem = new MockFileSystem(new Dictionary
{
@@ -228,7 +219,8 @@ public async Task List_MismatchedVariantLabel_ReturnsListItems()
"version": "1.0.0",
"variants": [
{
- "label": "variant1"
+ "label": "variant1",
+ "platform": "win-x64"
}
]
}
@@ -244,11 +236,11 @@ public async Task List_MismatchedVariantLabel_ReturnsListItems()
""") }
});
- Mock logger = new();
-
- Mock userInteraction = new();
+ Mock context = new();
+ context.SetupGet(c => c.FileSystem).Returns(fileSystem);
+ context.SetupGet(c => c.RuntimeIdentifier).Returns("win-x64");
- Lip lip = new(initialRuntimeConfig, fileSystem, logger.Object, userInteraction.Object);
+ Lip lip = new(new(), context.Object);
// Act.
List listItems = await lip.List(new());
diff --git a/Lip.Tests/PackageSpecifierTests.cs b/Lip.Tests/PackageSpecifierTests.cs
index d26d59f..ebee4ed 100644
--- a/Lip.Tests/PackageSpecifierTests.cs
+++ b/Lip.Tests/PackageSpecifierTests.cs
@@ -15,6 +15,7 @@ public void Constructor_ValidValues_Passes()
};
// Assert
+ Assert.Equal("example.com/pkg#variant", packageSpecifier.Specifier);
Assert.Equal("example.com/pkg", packageSpecifier.ToothPath);
Assert.Equal("variant", packageSpecifier.VariantLabel);
}
@@ -47,12 +48,12 @@ public void Constructor_InvalidVariantLabel_Throws()
public void Parse_ValidSpecifierText_Passes()
{
// Arrange & Act
- var packageSpecifier = PackageSpecifier.Parse("example.com/pkg#variant@1.0.0");
+ var packageSpecifier = PackageSpecifierWithoutVersion.Parse("example.com/pkg#variant");
// Assert
+ Assert.Equal("example.com/pkg#variant", packageSpecifier.Specifier);
Assert.Equal("example.com/pkg", packageSpecifier.ToothPath);
Assert.Equal("variant", packageSpecifier.VariantLabel);
- Assert.Equal("1.0.0", packageSpecifier.Version.ToString());
}
[Fact]
@@ -78,6 +79,7 @@ public void Constructor_ValidValues_Passes()
};
// Assert
+ Assert.Equal("example.com/pkg#variant@1.0.0", packageSpecifier.Specifier);
Assert.Equal("example.com/pkg", packageSpecifier.ToothPath);
Assert.Equal("variant", packageSpecifier.VariantLabel);
Assert.Equal("1.0.0", packageSpecifier.Version.ToString());
@@ -90,6 +92,7 @@ public void Parse_ValidSpecifierText_Passes()
var packageSpecifier = PackageSpecifier.Parse("example.com/pkg#variant@1.0.0");
// Assert
+ Assert.Equal("example.com/pkg#variant@1.0.0", packageSpecifier.Specifier);
Assert.Equal("example.com/pkg", packageSpecifier.ToothPath);
Assert.Equal("variant", packageSpecifier.VariantLabel);
Assert.Equal("1.0.0", packageSpecifier.Version.ToString());
diff --git a/Lip.Tests/PathManagerTests.cs b/Lip.Tests/PathManagerTests.cs
index 5f80eae..e7c788b 100644
--- a/Lip.Tests/PathManagerTests.cs
+++ b/Lip.Tests/PathManagerTests.cs
@@ -135,7 +135,7 @@ public void GetPackageManifestPath_WhenCalled_ReturnsCorrectPath()
PathManager pathManager = new(fileSystem);
// Act.
- string manifestPath = pathManager.PackageManifestPath;
+ string manifestPath = pathManager.CurrentPackageManifestPath;
// Assert.
Assert.Equal(Path.Join(s_workingDir, "tooth.json"), manifestPath);
@@ -152,7 +152,7 @@ public void GetPackageLockPath_WhenCalled_ReturnsCorrectPath()
PathManager pathManager = new(fileSystem);
// Act.
- string recordPath = pathManager.PackageLockPath;
+ string recordPath = pathManager.CurrentPackageLockPath;
// Assert.
Assert.Equal(Path.Join(s_workingDir, "tooth_lock.json"), recordPath);
@@ -193,12 +193,8 @@ public void GetWorkingDir_WhenCalled_ReturnsCurrentDirectory()
[Theory]
[InlineData("https://example.com/asset?v=1", "https%3A%2F%2Fexample.com%2Fasset%3Fv%3D1")]
- [InlineData("/path/to/asset", "%2Fpath%2Fto%2Fasset")]
- [InlineData("", "")]
- [InlineData(" ", "%20")]
- [InlineData("!@#$%^&*()", "%21%40%23%24%25%5E%26%2A%28%29")]
- [InlineData("../path/test", "..%2Fpath%2Ftest")]
- [InlineData("\\special\\chars", "%5Cspecial%5Cchars")]
+ [InlineData("https://example.com/path/to/asset", "https%3A%2F%2Fexample.com%2Fpath%2Fto%2Fasset")]
+ [InlineData("https://example.com/", "https%3A%2F%2Fexample.com%2F")]
public void GetDownloadedFileCacheDir_ArbitraryString_ReturnsEscapedPath(string url, string expectedFileName)
{
// Arrange.
@@ -206,7 +202,7 @@ public void GetDownloadedFileCacheDir_ArbitraryString_ReturnsEscapedPath(string
PathManager pathManager = new(fileSystem, baseCacheDir: s_cacheDir);
// Act.
- string cachePath = pathManager.GetDownloadedFileCachePath(url);
+ string cachePath = pathManager.GetDownloadedFileCachePath(new Uri(url));
// Assert.
Assert.Equal(
@@ -237,6 +233,29 @@ public void GetGitRepoCachePath_ArbitraryString_ReturnsEscapedPath(string repoUr
repoCacheDir);
}
+ [Theory]
+ [InlineData("https://example.com/asset?v=1", "https%3A%2F%2Fexample.com%2Fasset%3Fv%3D1")]
+ [InlineData("/path/to/asset", "%2Fpath%2Fto%2Fasset")]
+ [InlineData("", "")]
+ [InlineData(" ", "%20")]
+ [InlineData("!@#$%^&*()", "%21%40%23%24%25%5E%26%2A%28%29")]
+ [InlineData("../path/test", "..%2Fpath%2Ftest")]
+ [InlineData("\\special\\chars", "%5Cspecial%5Cchars")]
+ public void GetGitRepoPackageManifestCachePath_ArbitraryString_ReturnsEscapedPath(string repoUrl, string expectedDirName)
+ {
+ // Arrange.
+ MockFileSystem fileSystem = new();
+ PathManager pathManager = new(fileSystem, baseCacheDir: s_cacheDir);
+
+ // Act.
+ string repoPackageManifestPath = pathManager.GetGitRepoPackageManifestCachePath(repoUrl);
+
+ // Assert.
+ Assert.Equal(
+ Path.Join(s_cacheDir, "git_repos", expectedDirName, "tooth.json"),
+ repoPackageManifestPath);
+ }
+
[Theory]
[InlineData("https://example.com/asset?v=1", "https%3A%2F%2Fexample.com%2Fasset%3Fv%3D1.json")]
[InlineData("/path/to/asset", "%2Fpath%2Fto%2Fasset.json")]
@@ -245,14 +264,14 @@ public void GetGitRepoCachePath_ArbitraryString_ReturnsEscapedPath(string repoUr
[InlineData("!@#$%^&*()", "%21%40%23%24%25%5E%26%2A%28%29.json")]
[InlineData("../path/test", "..%2Fpath%2Ftest.json")]
[InlineData("\\special\\chars", "%5Cspecial%5Cchars.json")]
- public void GetPackageManifestCachePath_ArbitraryString_ReturnsEscapedPath(string packageName, string expectedFileName)
+ public void GetPackageManifestCachePath_ArbitraryString_ReturnsEscapedPath(string toothPath, string expectedFileName)
{
// Arrange.
MockFileSystem fileSystem = new();
PathManager pathManager = new(fileSystem, baseCacheDir: s_cacheDir);
// Act.
- string packageCacheDir = pathManager.GetPackageManifestCachePath(packageName);
+ string packageCacheDir = pathManager.GetPackageManifestCachePath(toothPath);
// Assert.
Assert.Equal(
diff --git a/Lip.Tests/RuntimeConfigTests.cs b/Lip.Tests/RuntimeConfigTests.cs
index a5704ae..27d32a8 100644
--- a/Lip.Tests/RuntimeConfigTests.cs
+++ b/Lip.Tests/RuntimeConfigTests.cs
@@ -27,7 +27,6 @@ public void FromBytes_MinimumJson_Passes()
Assert.Equal("", runtimeConfiguration.HttpsProxy);
Assert.Equal("", runtimeConfiguration.NoProxy);
Assert.Equal("", runtimeConfiguration.Proxy);
- Assert.Equal(RuntimeInformation.RuntimeIdentifier, runtimeConfiguration.RuntimeIdentifier);
Assert.Equal(
OperatingSystem.IsWindows()
? "cmd.exe"
@@ -67,7 +66,6 @@ public void FromBytes_MaximumJson_Passes()
Assert.Equal("https_proxy", runtimeConfiguration.HttpsProxy);
Assert.Equal("noproxy", runtimeConfiguration.NoProxy);
Assert.Equal("proxy", runtimeConfiguration.Proxy);
- Assert.Equal(RuntimeInformation.RuntimeIdentifier, runtimeConfiguration.RuntimeIdentifier);
Assert.Equal("script_shell", runtimeConfiguration.ScriptShell);
}
diff --git a/Lip/Lip.Config.cs b/Lip/Lip.Config.cs
index eba53ca..dd3f51c 100644
--- a/Lip/Lip.Config.cs
+++ b/Lip/Lip.Config.cs
@@ -84,7 +84,7 @@ public async Task ConfigSet(Dictionary keyValuePairs, ConfigSetA
matchedProperty.SetValue(newRuntimeConfig, convertedValue);
}
- await CreateOrUpdateRuntimeConfigurationFile(_fileSystem, newRuntimeConfig);
+ await CreateOrUpdateRuntimeConfigurationFile(_context.FileSystem, newRuntimeConfig);
}
private async Task CreateOrUpdateRuntimeConfigurationFile(IFileSystem fileSystem, RuntimeConfig runtimeConfig)
diff --git a/Lip/Lip.Init.cs b/Lip/Lip.Init.cs
index e7cb90d..fa39abc 100644
--- a/Lip/Lip.Init.cs
+++ b/Lip/Lip.Init.cs
@@ -43,12 +43,12 @@ public async Task Init(InitArgs args)
}
else
{
- string tooth = args.InitTooth ?? await _userInteraction.PromptForInput("Enter the tooth path (e.g. {DefaultTooth}):", DefaultTooth) ?? DefaultTooth;
- string version = args.InitVersion ?? await _userInteraction.PromptForInput("Enter the package version (e.g. {DefaultVersion}):", DefaultVersion) ?? DefaultVersion;
- string? name = args.InitName ?? await _userInteraction.PromptForInput("Enter the package name:");
- string? description = args.InitDescription ?? await _userInteraction.PromptForInput("Enter the package description:");
- string? author = args.InitAuthor ?? await _userInteraction.PromptForInput("Enter the package author:");
- string? avatarUrl = args.InitAvatarUrl ?? await _userInteraction.PromptForInput("Enter the author's avatar URL:");
+ string tooth = args.InitTooth ?? await _context.UserInteraction.PromptForInput("Enter the tooth path (e.g. {DefaultTooth}):", DefaultTooth) ?? DefaultTooth;
+ string version = args.InitVersion ?? await _context.UserInteraction.PromptForInput("Enter the package version (e.g. {DefaultVersion}):", DefaultVersion) ?? DefaultVersion;
+ string? name = args.InitName ?? await _context.UserInteraction.PromptForInput("Enter the package name:");
+ string? description = args.InitDescription ?? await _context.UserInteraction.PromptForInput("Enter the package description:");
+ string? author = args.InitAuthor ?? await _context.UserInteraction.PromptForInput("Enter the package author:");
+ string? avatarUrl = args.InitAvatarUrl ?? await _context.UserInteraction.PromptForInput("Enter the author's avatar URL:");
manifest = new()
{
@@ -66,28 +66,26 @@ public async Task Init(InitArgs args)
};
string jsonString = Encoding.UTF8.GetString(manifest.ToJsonBytes());
- if (!await _userInteraction.Confirm("Do you want to create the following package manifest file?\n{jsonString}", jsonString))
+ if (!await _context.UserInteraction.Confirm("Do you want to create the following package manifest file?\n{jsonString}", jsonString))
{
throw new OperationCanceledException("Operation canceled by the user.");
}
}
// Create the manifest file path.
- string manifestPath = _pathManager.PackageManifestPath;
+ string manifestPath = _pathManager.CurrentPackageManifestPath;
// Check if the manifest file already exists.
- if (_fileSystem.File.Exists(manifestPath))
+ if (_context.FileSystem.File.Exists(manifestPath))
{
if (!args.Force)
{
throw new InvalidOperationException($"The file '{manifestPath}' already exists. Use the -f or --force option to overwrite it.");
}
- _logger.LogWarning("The file '{ManifestPath}' already exists. Overwriting it.", manifestPath);
+ _context.Logger.LogWarning("The file '{ManifestPath}' already exists. Overwriting it.", manifestPath);
}
- await _fileSystem.File.WriteAllBytesAsync(manifestPath, manifest.ToJsonBytes());
-
- _logger.LogInformation("Successfully initialized the package manifest file '{ManifestPath}'.", manifestPath);
+ await _context.FileSystem.File.WriteAllBytesAsync(manifestPath, manifest.ToJsonBytes());
}
}
diff --git a/Lip/Lip.List.cs b/Lip/Lip.List.cs
index 781acf5..d6387dc 100644
--- a/Lip/Lip.List.cs
+++ b/Lip/Lip.List.cs
@@ -1,6 +1,4 @@
-using System.Runtime.InteropServices;
-
-namespace Lip;
+namespace Lip;
public partial class Lip
{
@@ -26,7 +24,7 @@ public async Task> List(ListArgs args)
&& package.Version == l.Version
&& package.GetSpecifiedVariant(
l.VariantLabel,
- _runtimeConfig.RuntimeIdentifier) is not null;
+ _context.RuntimeIdentifier) is not null;
})
})];
@@ -35,10 +33,10 @@ public async Task> List(ListArgs args)
private async Task GetPackageLock()
{
- string packageLockFilePath = _pathManager.PackageLockPath;
+ string packageLockFilePath = _pathManager.CurrentPackageLockPath;
// If the package lock file does not exist, return an empty package lock.
- if (!_fileSystem.File.Exists(packageLockFilePath))
+ if (!_context.FileSystem.File.Exists(packageLockFilePath))
{
return new()
{
@@ -49,7 +47,7 @@ private async Task GetPackageLock()
};
}
- byte[] packageLockBytes = await _fileSystem.File.ReadAllBytesAsync(packageLockFilePath);
+ byte[] packageLockBytes = await _context.FileSystem.File.ReadAllBytesAsync(packageLockFilePath);
return PackageLock.FromJsonBytes(packageLockBytes);
}
diff --git a/Lip/Lip.cs b/Lip/Lip.cs
index 1c31386..9506b94 100644
--- a/Lip/Lip.cs
+++ b/Lip/Lip.cs
@@ -1,6 +1,4 @@
-using System.IO.Abstractions;
-using System.Runtime.InteropServices;
-using Microsoft.Extensions.Logging;
+using Lip.Context;
namespace Lip;
@@ -8,18 +6,10 @@ namespace Lip;
/// The main class of the Lip library.
///
/// The runtime configuration.
-/// The file system wrapper.
-/// The logger.
-/// The user interaction wrapper.
-public partial class Lip(
- RuntimeConfig runtimeConfig,
- IFileSystem fileSystem,
- ILogger logger,
- IUserInteraction userInteraction)
+/// The context.
+public partial class Lip(RuntimeConfig runtimeConfig, IContext context)
{
- private readonly IFileSystem _fileSystem = fileSystem;
- private readonly ILogger _logger = logger;
- private readonly IPathManager _pathManager = new PathManager(fileSystem, runtimeConfig.Cache);
+ private readonly IContext _context = context;
+ private readonly PathManager _pathManager = new(context.FileSystem, runtimeConfig.Cache);
private readonly RuntimeConfig _runtimeConfig = runtimeConfig;
- private readonly IUserInteraction _userInteraction = userInteraction;
}
diff --git a/Lip/Lip.csproj b/Lip/Lip.csproj
index c86adf8..cf18b7e 100644
--- a/Lip/Lip.csproj
+++ b/Lip/Lip.csproj
@@ -8,12 +8,13 @@
-
-
-
+
+
+
+
diff --git a/Lip/PackageSpecifier.cs b/Lip/PackageSpecifier.cs
index 4f0d064..a5c5387 100644
--- a/Lip/PackageSpecifier.cs
+++ b/Lip/PackageSpecifier.cs
@@ -4,6 +4,8 @@ namespace Lip;
public record PackageSpecifierWithoutVersion
{
+ public string Specifier => $"{ToothPath}#{VariantLabel}";
+
public required string ToothPath
{
get => _tooth;
@@ -17,6 +19,7 @@ public required string ToothPath
_tooth = value;
}
}
+
public required string VariantLabel
{
get => _variantLabel;
@@ -53,6 +56,7 @@ public static PackageSpecifierWithoutVersion Parse(string specifierText)
public record PackageSpecifier : PackageSpecifierWithoutVersion
{
+ public new string Specifier => $"{base.Specifier}@{Version}";
public required SemVersion Version { get; init; }
diff --git a/Lip/PathManager.cs b/Lip/PathManager.cs
index b75f812..e711531 100644
--- a/Lip/PathManager.cs
+++ b/Lip/PathManager.cs
@@ -2,23 +2,7 @@
namespace Lip;
-public interface IPathManager
-{
- string BaseDownloadedFileCacheDir { get; }
- string BaseCacheDir { get; }
- string BaseGitRepoCacheDir { get; }
- string BasePackageManifestCacheDir { get; }
- string PackageManifestPath { get; }
- string PackageLockPath { get; }
- string RuntimeConfigPath { get; }
- string WorkingDir { get; }
-
- string GetDownloadedFileCachePath(string url);
- string GetGitRepoCachePath(string repoUrl);
- string GetPackageManifestCachePath(string packageName);
-}
-
-public class PathManager(IFileSystem fileSystem, string? baseCacheDir = null) : IPathManager
+public class PathManager(IFileSystem fileSystem, string? baseCacheDir = null)
{
private const string DownloadedFileCacheDirName = "downloaded_files";
private const string GitRepoCacheDirName = "git_repos";
@@ -37,19 +21,19 @@ public class PathManager(IFileSystem fileSystem, string? baseCacheDir = null) :
public string BasePackageManifestCacheDir => _fileSystem.Path.Join(BaseCacheDir, PackageManifestCacheDirName);
- public string PackageManifestPath => _fileSystem.Path.Join(WorkingDir, PackageManifestFileName);
+ public string CurrentPackageManifestPath => _fileSystem.Path.Join(WorkingDir, PackageManifestFileName);
- public string PackageLockPath => _fileSystem.Path.Join(WorkingDir, PackageLockFileName);
+ public string CurrentPackageLockPath => _fileSystem.Path.Join(WorkingDir, PackageLockFileName);
public string RuntimeConfigPath => _fileSystem.Path.Join(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "lip", "liprc.json");
public string WorkingDir => _fileSystem.Directory.GetCurrentDirectory();
- public string GetDownloadedFileCachePath(string url)
+ public string GetDownloadedFileCachePath(Uri url)
{
- string assetDirName = Uri.EscapeDataString(url);
- return _fileSystem.Path.Join(BaseDownloadedFileCacheDir, assetDirName);
+ string downloadedFileName = Uri.EscapeDataString(url.AbsoluteUri);
+ return _fileSystem.Path.Join(BaseDownloadedFileCacheDir, downloadedFileName);
}
public string GetGitRepoCachePath(string repoUrl)
@@ -58,9 +42,15 @@ public string GetGitRepoCachePath(string repoUrl)
return _fileSystem.Path.Join(BaseGitRepoCacheDir, repoDirName);
}
+ public string GetGitRepoPackageManifestCachePath(string repoUrl)
+ {
+ string repoDir = GetGitRepoCachePath(repoUrl);
+ return _fileSystem.Path.Join(repoDir, PackageManifestFileName);
+ }
+
public string GetPackageManifestCachePath(string packageName)
{
- string packageDirName = Uri.EscapeDataString(packageName) + ".json";
- return _fileSystem.Path.Join(BasePackageManifestCacheDir, packageDirName);
+ string packageManifestFileName = Uri.EscapeDataString(packageName) + ".json";
+ return _fileSystem.Path.Join(BasePackageManifestCacheDir, packageManifestFileName);
}
}
diff --git a/Lip/RuntimeConfig.cs b/Lip/RuntimeConfig.cs
index a5c1a49..8bcba63 100644
--- a/Lip/RuntimeConfig.cs
+++ b/Lip/RuntimeConfig.cs
@@ -43,9 +43,6 @@ public record RuntimeConfig
[JsonPropertyName("proxy")]
public string Proxy { get; init; } = "";
- [JsonIgnore]
- public string RuntimeIdentifier { get; init; } = RuntimeInformation.RuntimeIdentifier;
-
[JsonPropertyName("script_shell")]
public string ScriptShell { get; init; } = OperatingSystem.IsWindows()
? "cmd.exe"