Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ jobs:
dotnet-version: |
6.0.x
7.0.x
- name: dotnet-suggest
run: dotnet tool install -g dotnet-suggest
- name: Restore dependencies
run: dotnet restore
- name: Build
Expand Down
2 changes: 1 addition & 1 deletion CommandDotNet.DocExamples/DocExamplesDefaultTestConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public class DocExamplesDefaultTestConfig : IDefaultTestConfig
public TestConfig Default => new()
{
AppInfoOverride = new AppInfo(
false, false, false,
false, false, false, false,
typeof(DocExamplesDefaultTestConfig).Assembly,
"doc-examples.dll", "doc-examples.dll", "1.1.1.1")
};
Expand Down
48 changes: 48 additions & 0 deletions CommandDotNet.Example/Commands/DotnetSuggest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using System;
using System.CommandLine.Suggest;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using CommandDotNet.Builders;
using CommandDotNet.DotnetSuggest;

namespace CommandDotNet.Example.Commands;

[Command(Description = "(Un)registers this sample app with `dotnet suggest` to provide auto complete")]
public class DotnetSuggest
{
public void Register(IConsole console, IEnvironment environment)
{
var registered = DotnetTools.EnsureRegisteredWithDotnetSuggest(environment, out var results, console);
var appInfo = AppInfo.Instance;
console.WriteLine(registered
? results is null
? appInfo.IsGlobalTool
? "Already registered. Global tools are registered by default."
: "Already registered"
: $"Succeeded:{Environment.NewLine}{results.ToString(new Indent(depth: 1), skipOutputs: true)}"
: $"Failed:{Environment.NewLine}{results!.ToString(new Indent(depth: 1), skipOutputs: true)}");
}

public async Task Unregister(IConsole console)
{
if (AppInfo.Instance.IsGlobalTool)
{
console.WriteLine("This is a global tool. Global tools are registered by default and cannot be unregistered.");
}

var path = new FileSuggestionRegistration().RegistrationConfigurationFilePath;
var lines = await File.ReadAllLinesAsync(path);
var newLines = lines.Where(l => !l.StartsWith(AppInfo.Instance.FilePath)).ToArray();

if (lines.Length == newLines.Length)
{
console.WriteLine("Not registered with dotnet-suggest");
}
else
{
await File.WriteAllLinesAsync(path, newLines);
console.WriteLine("Unregistered with dotnet-suggest");
}
}
}
3 changes: 3 additions & 0 deletions CommandDotNet.Example/Examples.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,5 +55,8 @@ public void StartSession(

[Subcommand]
public Commands.Prompts Prompts { get; set; } = null!;

[Subcommand]
public Commands.DotnetSuggest DotnetSuggest { get; set; } = null!;
}
}
1 change: 1 addition & 0 deletions CommandDotNet.Example/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public static AppRunner GetAppRunner(NameValueCollection? appConfigSettings = nu
appConfigSettings ??= new NameValueCollection();
return new AppRunner<Examples>(appNameForTests is null ? null : new AppSettings{Help = {UsageAppName = appNameForTests}})
.UseDefaultMiddleware()
.UseSuggestDirective_Experimental()
.UseCommandLogger()
.UseNameCasing(Case.KebabCase)
.UsePrompter()
Expand Down
21 changes: 16 additions & 5 deletions CommandDotNet.TestTools/TestEnvironment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using CommandDotNet.Extensions;
using CommandDotNet.Tokens;

namespace CommandDotNet.TestTools
Expand All @@ -11,7 +12,7 @@ namespace CommandDotNet.TestTools
public class TestEnvironment : IEnvironment
{
public string[]? CommandLineArgs;
public Dictionary<string, string?> EnvVar = new();
public Dictionary<EnvironmentVariableTarget, Dictionary<string, string?>> EnvVarByTarget = new();
public Action<int>? OnExit;
public Action<(string? message, Exception? exception)>? OnFailFast;
public Func<string, string>? OnExpandEnvironmentVariables;
Expand Down Expand Up @@ -56,14 +57,24 @@ OnExpandEnvironmentVariables is null
? Environment.ExpandEnvironmentVariables(name)
: OnExpandEnvironmentVariables(name);

public string? GetEnvironmentVariable(string variable, EnvironmentVariableTarget? target = null) =>
EnvVar.GetValueOrDefault(variable);
public string? GetEnvironmentVariable(string variable, EnvironmentVariableTarget? target = null) =>
target is not null
? EnvVarByTarget.GetValueOrDefault(target.Value)?.GetValueOrDefault(variable)
: (EnvVarByTarget.GetValueOrDefault(EnvironmentVariableTarget.Process)
?? EnvVarByTarget.GetValueOrDefault(EnvironmentVariableTarget.User)
?? EnvVarByTarget.GetValueOrDefault(EnvironmentVariableTarget.Machine))
?.GetValueOrDefault(variable);

public IDictionary GetEnvironmentVariables() => EnvVar;
public IDictionary GetEnvironmentVariables() => EnvVarByTarget
.SelectMany(d => d.Value)
.ToDictionary(d => d.Key, d => d.Value);

public void SetEnvironmentVariables(string variable, string? value, EnvironmentVariableTarget? target)
{
EnvVar[variable] = value;
target ??= EnvironmentVariableTarget.Process;
var vars = EnvVarByTarget.GetOrAdd(target.Value,
key => new Dictionary<string, string?>());
vars[variable] = value;
}
}
}
2 changes: 1 addition & 1 deletion CommandDotNet.Tests/CmdNetDefaultTestConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public class CmdNetDefaultTestConfig : IDefaultTestConfig
{
OnError = {Print = {ConsoleOutput = true, CommandContext = true}},
AppInfoOverride = new AppInfo(
false, false, false,
false, false, false, false,
typeof(CmdNetDefaultTestConfig).Assembly,
"testhost.dll", "testhost.dll", "1.1.1.1")
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ public void Do()
}

private static AppInfo BuildAppInfo(string version) => new(
false, false, false,
false, false, false, false,
typeof(NewReleaseAlertOnGitHubTests).Assembly, "blah", version);

public static string BuildGitHubApiResponse(string version) =>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using System;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
using FluentAssertions;
using Xunit;

namespace CommandDotNet.Tests.FeatureTests.SuggestDirective;

public class DotNetSuggestSync
{
private const string RepoRoot = "https://raw.githubusercontent.com/dotnet/command-line-api/main/src";

[Fact(Skip = "unskip to run")]
public async Task Sync()
{
var client = new HttpClient();
await SyncFile(client, "DotnetProfileDirectory.cs");
await SyncFile(client, "FileSuggestionRegistration.cs",
text =>
{
var fileField = "private readonly string _registrationConfigurationFilePath;";
text.Should().Contain(fileField);
text = text.Replace(fileField,
$"{fileField}{Environment.NewLine} " +
"public string RegistrationConfigurationFilePath => _registrationConfigurationFilePath;");
text = text.Replace(" : ISuggestionRegistration", "");
return text;
});
await SyncFile(client, "RegistrationPair.cs");
}

private static async Task SyncFile(HttpClient client, string fileName, Func<string, string>? alter = null)
{
var source = $"https://raw.githubusercontent.com/dotnet/command-line-api/main/src/System.CommandLine.Suggest/{fileName}";
var fileContent = await client.GetStringAsync(source);
if (alter is not null)
{
fileContent = alter(fileContent);
}

fileContent = fileContent.Replace("namespace System.CommandLine.Suggest",
@"#pragma warning disable CS8600
#pragma warning disable CS8603
#pragma warning disable CS8625
// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
// ReSharper disable CheckNamespace

namespace System.CommandLine.Suggest");

fileContent = $@"// copied from: {source}
// via: {nameof(DotNetSuggestSync)} test class

{fileContent}";

await File.WriteAllTextAsync($"../../../../CommandDotNet/DotNetSuggest/System.CommandLine.Suggest/{fileName}", fileContent);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
using System;
using System.Collections.Generic;
using System.CommandLine.Suggest;
using System.IO;
using System.Linq;
using CommandDotNet.Builders;
using CommandDotNet.DotnetSuggest;
using CommandDotNet.TestTools;
using CommandDotNet.TestTools.Scenarios;
using FluentAssertions;
using Xunit;
using Xunit.Abstractions;

namespace CommandDotNet.Tests.FeatureTests.SuggestDirective;

public class SuggestDirectiveRegistrationTests
{
private readonly ITestOutputHelper _output;
private readonly string _filePath = Path.Join(nameof(SuggestDirectiveRegistrationTests), "suggest-test.exe");

public SuggestDirectiveRegistrationTests(ITestOutputHelper output)
{
_output = output;
Ambient.Output = output;
}

[Theory]
[InlineData("[suggest]", RegistrationStrategy.None, null, false, false)]
[InlineData("", RegistrationStrategy.None, null, false, false)]
[InlineData("[suggest]", RegistrationStrategy.EnsureOnEveryRun, null, false, false)]
[InlineData("", RegistrationStrategy.EnsureOnEveryRun, null, false, true)]
[InlineData("[suggest]", RegistrationStrategy.UseRegistrationDirective, "sug", false, false)]
[InlineData("", RegistrationStrategy.UseRegistrationDirective, "sug", false, false)]
[InlineData("[sug]", RegistrationStrategy.UseRegistrationDirective, "sug", false, true)]
[InlineData("[sug]", RegistrationStrategy.None, "sug", false, false)]
[InlineData("", RegistrationStrategy.EnsureOnEveryRun, null, true, false)]
[InlineData("[sug]", RegistrationStrategy.UseRegistrationDirective, "sug", true, false)]
public void Suggest_can_register_with_Dotnet_Suggest(string args, RegistrationStrategy strategy, string? directive, bool isGlobal, bool shouldRegister)
{
#region ensure test cases are not misconfigured and summarize a few of the rules
if (strategy == RegistrationStrategy.None)
{
shouldRegister.Should().Be(false, "should never register unless ensureRegisteredWithDotnetSuggest=true");
}
if(args.StartsWith("[suggest]"))
{
shouldRegister.Should().Be(false, "should never register when providing suggestions");
}
if(isGlobal)
{
shouldRegister.Should().Be(false, "should never register when app is global tool");
}
#endregion

var result = new AppRunner<App>()
.UseDefaultMiddleware()
.UseCommandLogger()
.UseSuggestDirective_Experimental(strategy, directive!)
.UseTestEnv(new ())
.Verify(new Scenario
{
When = {Args = args},
Then = { Output = args.StartsWith("[sug") ? null : "lala" }
},
config: TestConfig.Default.Where(a => a.AppInfoOverride = BuildAppInfo(isGlobal)));

result.ExitCode.Should().Be(0);

ConfirmPathEnvVar(shouldRegister, result);

ConfirmRegistration(shouldRegister);
}

private void ConfirmPathEnvVar(bool shouldRegister, AppRunnerResult result)
{
var testEnvironment = (TestEnvironment) result.CommandContext.Environment;
var userEnvVars = testEnvironment.EnvVarByTarget.GetValueOrDefault(EnvironmentVariableTarget.User);
if (shouldRegister)
{
userEnvVars.Should().NotBeNull();
var pathEnvVar = userEnvVars!.GetValueOrDefault("PATH");
pathEnvVar.Should().NotBeNull();
pathEnvVar.Should().Contain(_filePath);
}
else
{
userEnvVars?.GetValueOrDefault("PATH")?.Should().NotContain(_filePath);
}
}

private static void ConfirmRegistration(bool shouldRegister)
{
var path = new FileSuggestionRegistration().RegistrationConfigurationFilePath;
if (File.Exists(path))
{
var lines = File.ReadAllLines(path);

// _output.WriteLine($"contents of {path}");
// lines.ForEach(l => _output.WriteLine(l));
//
// contents of /Users/{user}/.dotnet-suggest-registration.txt
// SuggestDirectiveRegistrationTests/suggest-test.exe

var cleanedLines = lines.Where(l => !l.StartsWith(nameof(SuggestDirectiveRegistrationTests))).ToArray();
File.WriteAllLines(path, cleanedLines);

if (shouldRegister)
{
lines.Length.Should().NotBe(cleanedLines.Length);
}
else
{
lines.Length.Should().Be(cleanedLines.Length);
}
}
}

private AppInfo BuildAppInfo(bool isGlobalTool)
{
return new(
false, false, false, isGlobalTool, GetType().Assembly,
_filePath, Path.GetFileName(_filePath));
}

public class App
{
[DefaultCommand]
public void Do(IConsole console) => console.WriteLine("lala");
}
}
Loading