From 08f7c2759ff1d43d9e9a9b855684bada7bbc94a4 Mon Sep 17 00:00:00 2001 From: Ryan Brandenburg Date: Mon, 16 Apr 2018 16:56:22 -0700 Subject: [PATCH 01/66] Update version number to 2.2.0 --- version.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.props b/version.props index ce0b3d1a83..4c749bb5d2 100644 --- a/version.props +++ b/version.props @@ -1,8 +1,8 @@ - 2.1.0 + 2.2.0 15.6 - preview3 + preview1 $(VersionPrefix) $(VersionPrefix)-$(VersionSuffix)-final $(VsixVersion).$(BuildNumber) From 0c471a5b1329393a9478e000d581e4836eb12449 Mon Sep 17 00:00:00 2001 From: "ASP.NET CI" Date: Mon, 23 Apr 2018 12:05:18 -0700 Subject: [PATCH 02/66] Update dependencies.props [auto-updated: dependencies] --- build/dependencies.props | 18 +++++++++--------- korebuild-lock.txt | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index 02852a5e72..fd54efdb1a 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -3,18 +3,18 @@ $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - 2.1.0-rc1-15774 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 + 2.2.0-preview1-17037 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 2.0.0 - 2.1.0-rc1-26419-02 + 2.1.0-preview3-26413-05 15.6.1 2.0.1 - 4.5.0-rc1-26419-03 - 4.5.0-rc1-26419-03 + 4.5.0-preview3-26413-02 + 4.5.0-preview3-26413-02 9.0.1 2.3.1 2.4.0-beta.1.build3945 diff --git a/korebuild-lock.txt b/korebuild-lock.txt index ce2f277c53..790ae84e6d 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.1.0-preview3-17018 -commithash:af264ca131f212b5ba8aafbc5110fc0fc510a2be +version:2.2.0-preview1-17037 +commithash:557055a86cbdc359c97d4fb1c2d23a3dc7ae731e From 783090718f6aa89634facb9998c7fdaf1d2531a1 Mon Sep 17 00:00:00 2001 From: "ASP.NET CI" Date: Sun, 29 Apr 2018 12:12:59 -0700 Subject: [PATCH 03/66] Update dependencies.props [auto-updated: dependencies] --- build/dependencies.props | 20 ++++++++++---------- korebuild-lock.txt | 4 ++-- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index fd54efdb1a..ead6b8535d 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -3,18 +3,18 @@ $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - 2.2.0-preview1-17037 - 2.2.0-preview1-34029 - 2.2.0-preview1-34029 - 2.2.0-preview1-34029 - 2.2.0-preview1-34029 - 2.2.0-preview1-34029 + 2.2.0-preview1-17042 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 2.0.0 - 2.1.0-preview3-26413-05 + 2.2.0-preview1-26424-04 15.6.1 - 2.0.1 - 4.5.0-preview3-26413-02 - 4.5.0-preview3-26413-02 + 2.0.3 + 4.5.0-preview3-26423-04 + 4.5.0-preview3-26423-04 9.0.1 2.3.1 2.4.0-beta.1.build3945 diff --git a/korebuild-lock.txt b/korebuild-lock.txt index 790ae84e6d..5a9689541e 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.2.0-preview1-17037 -commithash:557055a86cbdc359c97d4fb1c2d23a3dc7ae731e +version:2.2.0-preview1-17042 +commithash:edf0705d014293c260de763543784330514db9a3 From 3193f45bae62c2dd2b0867e6efe26cce8f3738ce Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Thu, 3 May 2018 18:01:56 -0700 Subject: [PATCH 04/66] Attempt to address flaky dotnet-watch tests --- .../FileWatcherTests.cs | 26 +++++++++++++++---- .../NoDepsAppTests.cs | 4 +-- .../Scenario/WatchableApp.cs | 6 +++++ 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/test/dotnet-watch.FunctionalTests/FileWatcherTests.cs b/test/dotnet-watch.FunctionalTests/FileWatcherTests.cs index 9f1cc132bc..60fd501ca8 100644 --- a/test/dotnet-watch.FunctionalTests/FileWatcherTests.cs +++ b/test/dotnet-watch.FunctionalTests/FileWatcherTests.cs @@ -8,12 +8,19 @@ using System.Linq; using System.Threading; using Microsoft.DotNet.Watcher.Internal; using Xunit; +using Xunit.Abstractions; namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests { public class FileWatcherTests { + public FileWatcherTests(ITestOutputHelper output) + { + _output = output; + } + private const int DefaultTimeout = 10 * 1000; // 10 sec + private readonly ITestOutputHelper _output; [Theory] [InlineData(true)] @@ -277,16 +284,24 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests [InlineData(false)] public void MultipleTriggers(bool usePolling) { + var filesChanged = new HashSet(); + + void Clear() + { + _output.WriteLine("Clear files changed list"); + filesChanged.Clear(); + } + UsingTempDirectory(dir => { using (var changedEv = new AutoResetEvent(false)) using (var watcher = FileWatcherFactory.CreateWatcher(dir, usePolling)) { - var filesChanged = new HashSet(); EventHandler handler = null; handler = (_, f) => { + _output.WriteLine("File changed: " + f); filesChanged.Add(f); try { @@ -318,7 +333,7 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests Assert.True(changedEv.WaitOne(DefaultTimeout)); var fileChanged = Assert.Single(filesChanged); Assert.Equal(testFileFullPath, fileChanged); - filesChanged.Clear(); + Clear(); changedEv.Reset(); // On Unix the file write time is in 1s increments; @@ -331,7 +346,7 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests Assert.True(changedEv.WaitOne(DefaultTimeout)); fileChanged = Assert.Single(filesChanged); Assert.Equal(testFileFullPath, fileChanged); - filesChanged.Clear(); + Clear(); changedEv.Reset(); // On Unix the file write time is in 1s increments; @@ -342,8 +357,9 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests testFileFullPath = Path.Combine(dir, "foo3"); File.WriteAllText(testFileFullPath, string.Empty); Assert.True(changedEv.WaitOne(DefaultTimeout)); - Assert.Equal(testFileFullPath, filesChanged.Single()); - filesChanged.Clear(); + fileChanged = Assert.Single(filesChanged); + Assert.Equal(testFileFullPath, fileChanged); + Clear(); changedEv.Reset(); // On Unix the file write time is in 1s increments; diff --git a/test/dotnet-watch.FunctionalTests/NoDepsAppTests.cs b/test/dotnet-watch.FunctionalTests/NoDepsAppTests.cs index ea9ffbec03..8ac2f694e7 100644 --- a/test/dotnet-watch.FunctionalTests/NoDepsAppTests.cs +++ b/test/dotnet-watch.FunctionalTests/NoDepsAppTests.cs @@ -1,11 +1,10 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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.Diagnostics; using System.IO; using System.Threading.Tasks; -using Microsoft.AspNetCore.Testing; using Xunit; using Xunit.Abstractions; @@ -47,6 +46,7 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests await _app.StartWatcherAsync(); var pid = await _app.GetProcessId(); await _app.HasExited(); // process should exit after run + await _app.IsWaitingForFileChange(); var fileToChange = Path.Combine(_app.SourceDirectory, "Program.cs"); var programCs = File.ReadAllText(fileToChange); diff --git a/test/dotnet-watch.FunctionalTests/Scenario/WatchableApp.cs b/test/dotnet-watch.FunctionalTests/Scenario/WatchableApp.cs index 16bfbacc8d..56702afac2 100644 --- a/test/dotnet-watch.FunctionalTests/Scenario/WatchableApp.cs +++ b/test/dotnet-watch.FunctionalTests/Scenario/WatchableApp.cs @@ -19,6 +19,7 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests private const string StartedMessage = "Started"; private const string ExitingMessage = "Exiting"; private const string WatchExitedMessage = "watch : Exited"; + private const string WaitingForFileChangeMessage = "watch : Waiting for a file to change"; private readonly ITestOutputHelper _logger; private string _appName; @@ -49,6 +50,11 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests await Process.GetOutputLineAsync(WatchExitedMessage, DefaultMessageTimeOut); } + public async Task IsWaitingForFileChange() + { + await Process.GetOutputLineStartsWithAsync(WaitingForFileChangeMessage, DefaultMessageTimeOut); + } + public bool UsePollingWatcher { get; set; } public async Task GetProcessId() From 34c81aa794b165cd82ac91d6ed3b6f48acf81eb2 Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Fri, 4 May 2018 11:27:22 -0700 Subject: [PATCH 05/66] Add link to dotnet-CLI in README [ci skip] --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0eb8eb3be8..438b665cd8 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,8 @@ This project is part of ASP.NET Core. You can find samples, documentation and ge ## Projects -The repository contains command-line tools for ASP.NET Core that are bundled* in the .NET Core CLI. Follow the links below for more details on each tool. +The repository contains command-line tools for ASP.NET Core that are bundled* in the [.NET Core CLI](https://github.com/dotnet/cli). +Follow the links below for more details on each tool. - [dotnet-watch](src/dotnet-watch/README.md) - [dotnet-user-secrets](src/dotnet-user-secrets/README.md) From 7cbb6220e44fb87e75e97ab65d602b35884b5ac2 Mon Sep 17 00:00:00 2001 From: "ASP.NET CI" Date: Sun, 6 May 2018 12:12:09 -0700 Subject: [PATCH 06/66] Update dependencies.props [auto-updated: dependencies] --- build/dependencies.props | 12 ++++++------ korebuild-lock.txt | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index 40c71997ba..09717e8564 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -3,12 +3,12 @@ $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - 2.2.0-preview1-17044 - 2.2.0-preview1-34089 - 2.2.0-preview1-34089 - 2.2.0-preview1-34089 - 2.2.0-preview1-34089 - 2.2.0-preview1-34089 + 2.2.0-preview1-17047 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 2.0.0 2.2.0-preview1-26424-04 15.6.1 diff --git a/korebuild-lock.txt b/korebuild-lock.txt index 843f39817d..18df6940ae 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.2.0-preview1-17044 -commithash:d9eb8a53eca84f61a7b0845c99fc43a3ec51e353 +version:2.2.0-preview1-17047 +commithash:e1957b52ddc8b62bd39c5c400322fccb5364624c From 341ba98d27741e231f8850e3534ae93f7a8de1c0 Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Mon, 7 May 2018 10:52:47 -0700 Subject: [PATCH 07/66] [dotnet-watch] Don't show false error messages (#434) --- src/dotnet-watch/DotNetWatcher.cs | 8 ++++---- .../dotnet-watch.FunctionalTests/Scenario/WatchableApp.cs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/dotnet-watch/DotNetWatcher.cs b/src/dotnet-watch/DotNetWatcher.cs index 258ba0ab1e..f4b0423511 100644 --- a/src/dotnet-watch/DotNetWatcher.cs +++ b/src/dotnet-watch/DotNetWatcher.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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; @@ -69,13 +69,13 @@ namespace Microsoft.DotNet.Watcher await Task.WhenAll(processTask, fileSetTask); - if (processTask.Result == 0) + if (processTask.Result != 0 && finishedTask == processTask) { - _reporter.Output("Exited"); + _reporter.Error($"Exited with error code {processTask.Result}"); } else { - _reporter.Error($"Exited with error code {processTask.Result}"); + _reporter.Output("Exited"); } if (finishedTask == cancelledTaskSource.Task || cancellationToken.IsCancellationRequested) diff --git a/test/dotnet-watch.FunctionalTests/Scenario/WatchableApp.cs b/test/dotnet-watch.FunctionalTests/Scenario/WatchableApp.cs index 56702afac2..4f2d575f01 100644 --- a/test/dotnet-watch.FunctionalTests/Scenario/WatchableApp.cs +++ b/test/dotnet-watch.FunctionalTests/Scenario/WatchableApp.cs @@ -47,7 +47,7 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests public async Task HasExited() { await Process.GetOutputLineAsync(ExitingMessage, DefaultMessageTimeOut); - await Process.GetOutputLineAsync(WatchExitedMessage, DefaultMessageTimeOut); + await Process.GetOutputLineStartsWithAsync(WatchExitedMessage, DefaultMessageTimeOut); } public async Task IsWaitingForFileChange() From f71dad1055c2b2b9f661ec1552ac4afa4e1f2d89 Mon Sep 17 00:00:00 2001 From: Ryan Brandenburg Date: Mon, 7 May 2018 15:10:34 -0700 Subject: [PATCH 08/66] Upgrade to netcoreapp22 --- Directory.Build.targets | 5 ++++- build/dependencies.props | 15 ++++++++------- build/repo.props | 3 ++- korebuild-lock.txt | 4 ++-- .../LaunchAnyCommand/LaunchAnyCommand.csproj | 2 +- .../WatchJavascriptFiles.csproj | 2 +- .../WatchMultipleProjects/Test/Test.csproj | 2 +- .../WatchMultipleProjects/Web/Web.csproj | 2 +- .../WatchMultipleProjects/watch.csproj | 2 +- .../dotnet-user-secrets.Tests.csproj | 2 +- .../TestProjects/GlobbingApp/GlobbingApp.csproj | 2 +- .../TestProjects/NoDepsApp/NoDepsApp.csproj | 2 +- .../dotnet-watch.FunctionalTests.csproj | 2 +- test/dotnet-watch.Tests/dotnet-watch.Tests.csproj | 2 +- 14 files changed, 26 insertions(+), 21 deletions(-) diff --git a/Directory.Build.targets b/Directory.Build.targets index eb03b2565f..73b97f2807 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -1,6 +1,9 @@ - + $(MicrosoftNETCoreApp21PackageVersion) + $(MicrosoftNETCoreApp22PackageVersion) $(NETStandardLibrary20PackageVersion) + + 99.9 diff --git a/build/dependencies.props b/build/dependencies.props index 09717e8564..f0c4a4e0c4 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -1,16 +1,17 @@ - + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - 2.2.0-preview1-17047 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 + 2.2.0-preview1-17048 + 2.2.0-preview1-34140 + 2.2.0-preview1-34140 + 2.2.0-preview1-34140 + 2.2.0-preview1-34140 + 2.2.0-preview1-34140 2.0.0 2.2.0-preview1-26424-04 + 2.2.0-preview1-26502-01 15.6.1 2.0.3 4.5.0-preview3-26423-04 diff --git a/build/repo.props b/build/repo.props index 1be3632130..015f722dd9 100644 --- a/build/repo.props +++ b/build/repo.props @@ -1,10 +1,11 @@ - + + diff --git a/korebuild-lock.txt b/korebuild-lock.txt index 18df6940ae..da5dcd1202 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.2.0-preview1-17047 -commithash:e1957b52ddc8b62bd39c5c400322fccb5364624c +version:2.2.0-preview1-17048 +commithash:de14a0ee5fb48508ee8a29c14280a2928f8dabf8 diff --git a/samples/dotnet-watch/LaunchAnyCommand/LaunchAnyCommand.csproj b/samples/dotnet-watch/LaunchAnyCommand/LaunchAnyCommand.csproj index 1dc6efedd6..1609403042 100644 --- a/samples/dotnet-watch/LaunchAnyCommand/LaunchAnyCommand.csproj +++ b/samples/dotnet-watch/LaunchAnyCommand/LaunchAnyCommand.csproj @@ -1,6 +1,6 @@ - netcoreapp2.1 + netcoreapp2.2 diff --git a/samples/dotnet-watch/WatchJavascriptFiles/WatchJavascriptFiles.csproj b/samples/dotnet-watch/WatchJavascriptFiles/WatchJavascriptFiles.csproj index 008b0322e6..0c0cac78f3 100755 --- a/samples/dotnet-watch/WatchJavascriptFiles/WatchJavascriptFiles.csproj +++ b/samples/dotnet-watch/WatchJavascriptFiles/WatchJavascriptFiles.csproj @@ -1,7 +1,7 @@ - netcoreapp2.1 + netcoreapp2.2 diff --git a/samples/dotnet-watch/WatchMultipleProjects/Test/Test.csproj b/samples/dotnet-watch/WatchMultipleProjects/Test/Test.csproj index 0ffd10766a..0559578c32 100755 --- a/samples/dotnet-watch/WatchMultipleProjects/Test/Test.csproj +++ b/samples/dotnet-watch/WatchMultipleProjects/Test/Test.csproj @@ -1,7 +1,7 @@ - netcoreapp2.1 + netcoreapp2.2 diff --git a/samples/dotnet-watch/WatchMultipleProjects/Web/Web.csproj b/samples/dotnet-watch/WatchMultipleProjects/Web/Web.csproj index ec2a93ed3a..616246b199 100755 --- a/samples/dotnet-watch/WatchMultipleProjects/Web/Web.csproj +++ b/samples/dotnet-watch/WatchMultipleProjects/Web/Web.csproj @@ -1,7 +1,7 @@ - netcoreapp2.1 + netcoreapp2.2 diff --git a/samples/dotnet-watch/WatchMultipleProjects/watch.csproj b/samples/dotnet-watch/WatchMultipleProjects/watch.csproj index d890295d39..5ca4ee2379 100644 --- a/samples/dotnet-watch/WatchMultipleProjects/watch.csproj +++ b/samples/dotnet-watch/WatchMultipleProjects/watch.csproj @@ -1,6 +1,6 @@ - netcoreapp2.1 + netcoreapp2.2 false diff --git a/test/dotnet-user-secrets.Tests/dotnet-user-secrets.Tests.csproj b/test/dotnet-user-secrets.Tests/dotnet-user-secrets.Tests.csproj index fd8be34db7..834e7ede2b 100644 --- a/test/dotnet-user-secrets.Tests/dotnet-user-secrets.Tests.csproj +++ b/test/dotnet-user-secrets.Tests/dotnet-user-secrets.Tests.csproj @@ -1,4 +1,4 @@ - + netcoreapp2.1 diff --git a/test/dotnet-watch.FunctionalTests/TestProjects/GlobbingApp/GlobbingApp.csproj b/test/dotnet-watch.FunctionalTests/TestProjects/GlobbingApp/GlobbingApp.csproj index a01efb4b2f..8d31551941 100644 --- a/test/dotnet-watch.FunctionalTests/TestProjects/GlobbingApp/GlobbingApp.csproj +++ b/test/dotnet-watch.FunctionalTests/TestProjects/GlobbingApp/GlobbingApp.csproj @@ -1,4 +1,4 @@ - + netcoreapp2.1 diff --git a/test/dotnet-watch.FunctionalTests/TestProjects/NoDepsApp/NoDepsApp.csproj b/test/dotnet-watch.FunctionalTests/TestProjects/NoDepsApp/NoDepsApp.csproj index b242bd2546..d4ddbcd217 100644 --- a/test/dotnet-watch.FunctionalTests/TestProjects/NoDepsApp/NoDepsApp.csproj +++ b/test/dotnet-watch.FunctionalTests/TestProjects/NoDepsApp/NoDepsApp.csproj @@ -1,4 +1,4 @@ - + netcoreapp2.1 diff --git a/test/dotnet-watch.FunctionalTests/dotnet-watch.FunctionalTests.csproj b/test/dotnet-watch.FunctionalTests/dotnet-watch.FunctionalTests.csproj index d7c0a3fdac..768d08ce8c 100644 --- a/test/dotnet-watch.FunctionalTests/dotnet-watch.FunctionalTests.csproj +++ b/test/dotnet-watch.FunctionalTests/dotnet-watch.FunctionalTests.csproj @@ -1,4 +1,4 @@ - + netcoreapp2.1 diff --git a/test/dotnet-watch.Tests/dotnet-watch.Tests.csproj b/test/dotnet-watch.Tests/dotnet-watch.Tests.csproj index ce5349ad3e..c5578a357b 100644 --- a/test/dotnet-watch.Tests/dotnet-watch.Tests.csproj +++ b/test/dotnet-watch.Tests/dotnet-watch.Tests.csproj @@ -1,4 +1,4 @@ - + netcoreapp2.1 From ddc478ab879b6dc17ea035f05410f1de945a167a Mon Sep 17 00:00:00 2001 From: Artak <34246760+mkArtakMSFT@users.noreply.github.com> Date: Wed, 9 May 2018 09:47:48 -0700 Subject: [PATCH 09/66] Added using block around a disposable type usage Noticed this when reviewed an already-merged PR --- .../FileWatcherTests.cs | 62 ++++++++++--------- 1 file changed, 32 insertions(+), 30 deletions(-) diff --git a/test/dotnet-watch.FunctionalTests/FileWatcherTests.cs b/test/dotnet-watch.FunctionalTests/FileWatcherTests.cs index 7cd4bd15aa..51c1895e05 100644 --- a/test/dotnet-watch.FunctionalTests/FileWatcherTests.cs +++ b/test/dotnet-watch.FunctionalTests/FileWatcherTests.cs @@ -304,42 +304,44 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests private void AssertFileChangeRaisesEvent(string directory, IFileSystemWatcher watcher) { - var semaphoreSlim = new SemaphoreSlim(0); - var expectedPath = Path.Combine(directory, Path.GetRandomFileName()); - EventHandler handler = (object _, string f) => + using (var semaphoreSlim = new SemaphoreSlim(0)) { - _output.WriteLine("File changed: " + f); + var expectedPath = Path.Combine(directory, Path.GetRandomFileName()); + EventHandler handler = (object _, string f) => + { + _output.WriteLine("File changed: " + f); + try + { + if (string.Equals(f, expectedPath, StringComparison.OrdinalIgnoreCase)) + { + semaphoreSlim.Release(); + } + } + catch (ObjectDisposedException) + { + // There's a known race condition here: + // even though we tell the watcher to stop raising events and we unsubscribe the handler + // there might be in-flight events that will still process. Since we dispose the reset + // event, this code will fail if the handler executes after Dispose happens. + } + }; + + File.AppendAllText(expectedPath, " "); + + watcher.OnFileChange += handler; try { - if (string.Equals(f, expectedPath, StringComparison.OrdinalIgnoreCase)) - { - semaphoreSlim.Release(); - } + // On Unix the file write time is in 1s increments; + // if we don't wait, there's a chance that the polling + // watcher will not detect the change + Thread.Sleep(1000); + File.AppendAllText(expectedPath, " "); + Assert.True(semaphoreSlim.Wait(DefaultTimeout), "Expected a file change event for " + expectedPath); } - catch (ObjectDisposedException) + finally { - // There's a known race condition here: - // even though we tell the watcher to stop raising events and we unsubscribe the handler - // there might be in-flight events that will still process. Since we dispose the reset - // event, this code will fail if the handler executes after Dispose happens. + watcher.OnFileChange -= handler; } - }; - - File.AppendAllText(expectedPath, " "); - - watcher.OnFileChange += handler; - try - { - // On Unix the file write time is in 1s increments; - // if we don't wait, there's a chance that the polling - // watcher will not detect the change - Thread.Sleep(1000); - File.AppendAllText(expectedPath, " "); - Assert.True(semaphoreSlim.Wait(DefaultTimeout), "Expected a file change event for " + expectedPath); - } - finally - { - watcher.OnFileChange -= handler; } } From 78236286b449f505a4073af0127dc62a0f77a5c2 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Wed, 9 May 2018 14:44:43 -0700 Subject: [PATCH 10/66] Upgrade tools to netcoreapp2.2 --- NuGetPackageVerifier.json | 72 +++++++++---------- build/dependencies.props | 18 ++--- korebuild-lock.txt | 4 +- ...NetCore.DeveloperCertificates.XPlat.csproj | 2 +- src/dotnet-dev-certs/dotnet-dev-certs.csproj | 2 +- src/dotnet-sql-cache/dotnet-sql-cache.csproj | 2 +- .../dotnet-user-secrets.csproj | 2 +- src/dotnet-watch/dotnet-watch.csproj | 2 +- .../UserSecretsTestFixture.cs | 2 +- .../dotnet-user-secrets.Tests.csproj | 2 +- .../AppWithDeps/AppWithDeps.csproj | 5 +- .../GlobbingApp/GlobbingApp.csproj | 5 +- .../KitchenSink/KitchenSink.csproj | 5 +- .../TestProjects/NoDepsApp/NoDepsApp.csproj | 5 +- .../dotnet-watch.FunctionalTests.csproj | 2 +- .../dotnet-watch.Tests.csproj | 2 +- 16 files changed, 72 insertions(+), 60 deletions(-) diff --git a/NuGetPackageVerifier.json b/NuGetPackageVerifier.json index 3e59aeb910..6503b35342 100644 --- a/NuGetPackageVerifier.json +++ b/NuGetPackageVerifier.json @@ -15,39 +15,39 @@ ], "Exclusions": { "NEUTRAL_RESOURCES_LANGUAGE": { - "tools/netcoreapp2.1/any/System.Runtime.CompilerServices.Unsafe.dll": "Assembly is built by another project but bundled in our nupkg." + "tools/netcoreapp2.2/any/System.Runtime.CompilerServices.Unsafe.dll": "Assembly is built by another project but bundled in our nupkg." }, "WRONG_PUBLICKEYTOKEN": { - "tools/netcoreapp2.1/any/System.Runtime.CompilerServices.Unsafe.dll": "Assembly is built by another project but bundled in our nupkg.", - "tools/netcoreapp2.1/any/System.Data.SqlClient.dll": "Assembly is built by another project but bundled in our nupkg.", - "tools/netcoreapp2.1/any/System.Text.Encoding.CodePages.dll": "Assembly is built by another project but bundled in our nupkg.", - "tools/netcoreapp2.1/any/runtimes/win/lib/netcoreapp2.0/System.Text.Encoding.CodePages.dll": "Assembly is built by another project but bundled in our nupkg.", - "tools/netcoreapp2.1/any/runtimes/unix/lib/netcoreapp2.1/System.Data.SqlClient.dll": "Assembly is built by another project but bundled in our nupkg.", - "tools/netcoreapp2.1/any/runtimes/win/lib/netcoreapp2.1/System.Data.SqlClient.dll": "Assembly is built by another project but bundled in our nupkg." + "tools/netcoreapp2.2/any/System.Data.SqlClient.dll": "Assembly is built by another project but bundled in our nupkg.", + "tools/netcoreapp2.2/any/runtimes/unix/lib/netcoreapp2.1/System.Data.SqlClient.dll": "Assembly is built by another project but bundled in our nupkg.", + "tools/netcoreapp2.2/any/runtimes/win/lib/netcoreapp2.1/System.Data.SqlClient.dll": "Assembly is built by another project but bundled in our nupkg.", + "tools/netcoreapp2.2/any/System.Runtime.CompilerServices.Unsafe.dll": "Assembly is built by another project but bundled in our nupkg.", + "tools/netcoreapp2.2/any/System.Text.Encoding.CodePages.dll": "Assembly is built by another project but bundled in our nupkg.", + "tools/netcoreapp2.2/any/runtimes/win/lib/netcoreapp2.0/System.Text.Encoding.CodePages.dll": "Assembly is built by another project but bundled in our nupkg." }, "ASSEMBLY_INFORMATIONAL_VERSION_MISMATCH": { - "tools/netcoreapp2.1/any/System.Runtime.CompilerServices.Unsafe.dll": "Assembly is built by another project but bundled in our nupkg.", - "tools/netcoreapp2.1/any/System.Data.SqlClient.dll": "Assembly is built by another project but bundled in our nupkg.", - "tools/netcoreapp2.1/any/System.Text.Encoding.CodePages.dll": "Assembly is built by another project but bundled in our nupkg.", - "tools/netcoreapp2.1/any/runtimes/win/lib/netcoreapp2.0/System.Text.Encoding.CodePages.dll": "Assembly is built by another project but bundled in our nupkg.", - "tools/netcoreapp2.1/any/runtimes/unix/lib/netcoreapp2.1/System.Data.SqlClient.dll": "Assembly is built by another project but bundled in our nupkg.", - "tools/netcoreapp2.1/any/runtimes/win/lib/netcoreapp2.1/System.Data.SqlClient.dll": "Assembly is built by another project but bundled in our nupkg." + "tools/netcoreapp2.2/any/System.Data.SqlClient.dll": "Assembly is built by another project but bundled in our nupkg.", + "tools/netcoreapp2.2/any/runtimes/unix/lib/netcoreapp2.1/System.Data.SqlClient.dll": "Assembly is built by another project but bundled in our nupkg.", + "tools/netcoreapp2.2/any/runtimes/win/lib/netcoreapp2.1/System.Data.SqlClient.dll": "Assembly is built by another project but bundled in our nupkg.", + "tools/netcoreapp2.2/any/System.Runtime.CompilerServices.Unsafe.dll": "Assembly is built by another project but bundled in our nupkg.", + "tools/netcoreapp2.2/any/System.Text.Encoding.CodePages.dll": "Assembly is built by another project but bundled in our nupkg.", + "tools/netcoreapp2.2/any/runtimes/win/lib/netcoreapp2.0/System.Text.Encoding.CodePages.dll": "Assembly is built by another project but bundled in our nupkg." }, "ASSEMBLY_FILE_VERSION_MISMATCH": { - "tools/netcoreapp2.1/any/System.Runtime.CompilerServices.Unsafe.dll": "Assembly is built by another project but bundled in our nupkg.", - "tools/netcoreapp2.1/any/System.Data.SqlClient.dll": "Assembly is built by another project but bundled in our nupkg.", - "tools/netcoreapp2.1/any/System.Text.Encoding.CodePages.dll": "Assembly is built by another project but bundled in our nupkg.", - "tools/netcoreapp2.1/any/runtimes/win/lib/netcoreapp2.0/System.Text.Encoding.CodePages.dll": "Assembly is built by another project but bundled in our nupkg.", - "tools/netcoreapp2.1/any/runtimes/unix/lib/netcoreapp2.1/System.Data.SqlClient.dll": "Assembly is built by another project but bundled in our nupkg.", - "tools/netcoreapp2.1/any/runtimes/win/lib/netcoreapp2.1/System.Data.SqlClient.dll": "Assembly is built by another project but bundled in our nupkg." + "tools/netcoreapp2.2/any/System.Data.SqlClient.dll": "Assembly is built by another project but bundled in our nupkg.", + "tools/netcoreapp2.2/any/runtimes/unix/lib/netcoreapp2.1/System.Data.SqlClient.dll": "Assembly is built by another project but bundled in our nupkg.", + "tools/netcoreapp2.2/any/runtimes/win/lib/netcoreapp2.1/System.Data.SqlClient.dll": "Assembly is built by another project but bundled in our nupkg.", + "tools/netcoreapp2.2/any/System.Runtime.CompilerServices.Unsafe.dll": "Assembly is built by another project but bundled in our nupkg.", + "tools/netcoreapp2.2/any/System.Text.Encoding.CodePages.dll": "Assembly is built by another project but bundled in our nupkg.", + "tools/netcoreapp2.2/any/runtimes/win/lib/netcoreapp2.0/System.Text.Encoding.CodePages.dll": "Assembly is built by another project but bundled in our nupkg." }, "ASSEMBLY_VERSION_MISMATCH": { - "tools/netcoreapp2.1/any/System.Runtime.CompilerServices.Unsafe.dll": "Assembly is built by another project but bundled in our nupkg.", - "tools/netcoreapp2.1/any/System.Data.SqlClient.dll": "Assembly is built by another project but bundled in our nupkg.", - "tools/netcoreapp2.1/any/System.Text.Encoding.CodePages.dll": "Assembly is built by another project but bundled in our nupkg.", - "tools/netcoreapp2.1/any/runtimes/win/lib/netcoreapp2.0/System.Text.Encoding.CodePages.dll": "Assembly is built by another project but bundled in our nupkg.", - "tools/netcoreapp2.1/any/runtimes/unix/lib/netcoreapp2.1/System.Data.SqlClient.dll": "Assembly is built by another project but bundled in our nupkg.", - "tools/netcoreapp2.1/any/runtimes/win/lib/netcoreapp2.1/System.Data.SqlClient.dll": "Assembly is built by another project but bundled in our nupkg." + "tools/netcoreapp2.2/any/System.Data.SqlClient.dll": "Assembly is built by another project but bundled in our nupkg.", + "tools/netcoreapp2.2/any/runtimes/unix/lib/netcoreapp2.1/System.Data.SqlClient.dll": "Assembly is built by another project but bundled in our nupkg.", + "tools/netcoreapp2.2/any/runtimes/win/lib/netcoreapp2.1/System.Data.SqlClient.dll": "Assembly is built by another project but bundled in our nupkg.", + "tools/netcoreapp2.2/any/System.Runtime.CompilerServices.Unsafe.dll": "Assembly is built by another project but bundled in our nupkg.", + "tools/netcoreapp2.2/any/System.Text.Encoding.CodePages.dll": "Assembly is built by another project but bundled in our nupkg.", + "tools/netcoreapp2.2/any/runtimes/win/lib/netcoreapp2.0/System.Text.Encoding.CodePages.dll": "Assembly is built by another project but bundled in our nupkg." } } }, @@ -57,26 +57,26 @@ ], "Exclusions": { "NEUTRAL_RESOURCES_LANGUAGE": { - "tools/netcoreapp2.1/any/System.Runtime.CompilerServices.Unsafe.dll": "Assembly is built by another project but bundled in our nupkg." + "tools/netcoreapp2.2/any/System.Runtime.CompilerServices.Unsafe.dll": "Assembly is built by another project but bundled in our nupkg." }, "SERVICING_ATTRIBUTE": { - "tools/netcoreapp2.1/any/Newtonsoft.Json.dll": "Assembly is built by another project but bundled in our nupkg." + "tools/netcoreapp2.2/any/Newtonsoft.Json.dll": "Assembly is built by another project but bundled in our nupkg." }, "WRONG_PUBLICKEYTOKEN": { - "tools/netcoreapp2.1/any/Newtonsoft.Json.dll": "Assembly is built by another project but bundled in our nupkg.", - "tools/netcoreapp2.1/any/System.Runtime.CompilerServices.Unsafe.dll": "Assembly is built by another project but bundled in our nupkg." + "tools/netcoreapp2.2/any/Newtonsoft.Json.dll": "Assembly is built by another project but bundled in our nupkg.", + "tools/netcoreapp2.2/any/System.Runtime.CompilerServices.Unsafe.dll": "Assembly is built by another project but bundled in our nupkg." }, "ASSEMBLY_INFORMATIONAL_VERSION_MISMATCH": { - "tools/netcoreapp2.1/any/Newtonsoft.Json.dll": "Assembly is built by another project but bundled in our nupkg.", - "tools/netcoreapp2.1/any/System.Runtime.CompilerServices.Unsafe.dll": "Assembly is built by another project but bundled in our nupkg." + "tools/netcoreapp2.2/any/Newtonsoft.Json.dll": "Assembly is built by another project but bundled in our nupkg.", + "tools/netcoreapp2.2/any/System.Runtime.CompilerServices.Unsafe.dll": "Assembly is built by another project but bundled in our nupkg." }, "ASSEMBLY_FILE_VERSION_MISMATCH": { - "tools/netcoreapp2.1/any/Newtonsoft.Json.dll": "Assembly is built by another project but bundled in our nupkg.", - "tools/netcoreapp2.1/any/System.Runtime.CompilerServices.Unsafe.dll": "Assembly is built by another project but bundled in our nupkg." + "tools/netcoreapp2.2/any/Newtonsoft.Json.dll": "Assembly is built by another project but bundled in our nupkg.", + "tools/netcoreapp2.2/any/System.Runtime.CompilerServices.Unsafe.dll": "Assembly is built by another project but bundled in our nupkg." }, "ASSEMBLY_VERSION_MISMATCH": { - "tools/netcoreapp2.1/any/Newtonsoft.Json.dll": "Assembly is built by another project but bundled in our nupkg.", - "tools/netcoreapp2.1/any/System.Runtime.CompilerServices.Unsafe.dll": "Assembly is built by another project but bundled in our nupkg." + "tools/netcoreapp2.2/any/Newtonsoft.Json.dll": "Assembly is built by another project but bundled in our nupkg.", + "tools/netcoreapp2.2/any/System.Runtime.CompilerServices.Unsafe.dll": "Assembly is built by another project but bundled in our nupkg." } } }, @@ -88,7 +88,7 @@ "Microsoft.AspNetCore.DeveloperCertificates.XPlat": { "Exclusions": { "DOC_MISSING": { - "lib/netcoreapp2.1/Microsoft.AspNetCore.DeveloperCertificates.XPlat.dll": "Docs not required to shipoob package" + "lib/netcoreapp2.2/Microsoft.AspNetCore.DeveloperCertificates.XPlat.dll": "Docs not required to shipoob package" } } } diff --git a/build/dependencies.props b/build/dependencies.props index f0c4a4e0c4..53d6c6353d 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -1,17 +1,17 @@ - + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - 2.2.0-preview1-17048 - 2.2.0-preview1-34140 - 2.2.0-preview1-34140 - 2.2.0-preview1-34140 - 2.2.0-preview1-34140 - 2.2.0-preview1-34140 + 2.2.0-preview1-17050 + 2.2.0-preview1-34154 + 2.2.0-preview1-34154 + 2.2.0-preview1-34154 + 2.2.0-preview1-34154 + 2.2.0-preview1-34154 2.0.0 - 2.2.0-preview1-26424-04 - 2.2.0-preview1-26502-01 + 2.1.0-rc1 + 2.2.0-preview1-26424-04 15.6.1 2.0.3 4.5.0-preview3-26423-04 diff --git a/korebuild-lock.txt b/korebuild-lock.txt index da5dcd1202..82ebcf2549 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.2.0-preview1-17048 -commithash:de14a0ee5fb48508ee8a29c14280a2928f8dabf8 +version:2.2.0-preview1-17050 +commithash:24b548c969ffdf58730a7237a4c050b12c8711ed diff --git a/src/Microsoft.AspNetCore.DeveloperCertificates.XPlat/Microsoft.AspNetCore.DeveloperCertificates.XPlat.csproj b/src/Microsoft.AspNetCore.DeveloperCertificates.XPlat/Microsoft.AspNetCore.DeveloperCertificates.XPlat.csproj index 81c90a297c..19e0fc454b 100644 --- a/src/Microsoft.AspNetCore.DeveloperCertificates.XPlat/Microsoft.AspNetCore.DeveloperCertificates.XPlat.csproj +++ b/src/Microsoft.AspNetCore.DeveloperCertificates.XPlat/Microsoft.AspNetCore.DeveloperCertificates.XPlat.csproj @@ -1,7 +1,7 @@ - netcoreapp2.1 + netcoreapp2.2 Package for the CLI first run experience. $(DefineConstants);XPLAT aspnet;cli diff --git a/src/dotnet-dev-certs/dotnet-dev-certs.csproj b/src/dotnet-dev-certs/dotnet-dev-certs.csproj index 024cc705d2..b785b60c42 100644 --- a/src/dotnet-dev-certs/dotnet-dev-certs.csproj +++ b/src/dotnet-dev-certs/dotnet-dev-certs.csproj @@ -3,7 +3,7 @@ - netcoreapp2.1 + netcoreapp2.2 exe Command line tool to generate certificates used in ASP.NET Core during development. Microsoft.AspNetCore.DeveloperCertificates.Tools diff --git a/src/dotnet-sql-cache/dotnet-sql-cache.csproj b/src/dotnet-sql-cache/dotnet-sql-cache.csproj index ad1105f2b8..3949d90be0 100644 --- a/src/dotnet-sql-cache/dotnet-sql-cache.csproj +++ b/src/dotnet-sql-cache/dotnet-sql-cache.csproj @@ -3,7 +3,7 @@ - netcoreapp2.1 + netcoreapp2.2 exe Command line tool to create tables and indexes in a Microsoft SQL Server database for distributed caching. cache;distributedcache;sqlserver diff --git a/src/dotnet-user-secrets/dotnet-user-secrets.csproj b/src/dotnet-user-secrets/dotnet-user-secrets.csproj index f431447a01..74bb54c602 100644 --- a/src/dotnet-user-secrets/dotnet-user-secrets.csproj +++ b/src/dotnet-user-secrets/dotnet-user-secrets.csproj @@ -3,7 +3,7 @@ - netcoreapp2.1 + netcoreapp2.2 exe Command line tool to manage user secrets for Microsoft.Extensions.Configuration. false diff --git a/src/dotnet-watch/dotnet-watch.csproj b/src/dotnet-watch/dotnet-watch.csproj index f20ea3ebc5..c5210a0cd2 100644 --- a/src/dotnet-watch/dotnet-watch.csproj +++ b/src/dotnet-watch/dotnet-watch.csproj @@ -3,7 +3,7 @@ - netcoreapp2.1 + netcoreapp2.2 exe Command line tool to watch for source file changes during development and restart the dotnet command. Microsoft.DotNet.Watcher.Tools diff --git a/test/dotnet-user-secrets.Tests/UserSecretsTestFixture.cs b/test/dotnet-user-secrets.Tests/UserSecretsTestFixture.cs index 14e62805bc..590787ec31 100644 --- a/test/dotnet-user-secrets.Tests/UserSecretsTestFixture.cs +++ b/test/dotnet-user-secrets.Tests/UserSecretsTestFixture.cs @@ -35,7 +35,7 @@ namespace Microsoft.Extensions.Configuration.UserSecrets.Tests private const string ProjectTemplate = @" Exe - netcoreapp2.1 + netcoreapp2.2 {0} false diff --git a/test/dotnet-user-secrets.Tests/dotnet-user-secrets.Tests.csproj b/test/dotnet-user-secrets.Tests/dotnet-user-secrets.Tests.csproj index 834e7ede2b..b52bd3eb92 100644 --- a/test/dotnet-user-secrets.Tests/dotnet-user-secrets.Tests.csproj +++ b/test/dotnet-user-secrets.Tests/dotnet-user-secrets.Tests.csproj @@ -1,7 +1,7 @@ - netcoreapp2.1 + netcoreapp2.2 Microsoft.Extensions.SecretManager.Tools.Tests diff --git a/test/dotnet-watch.FunctionalTests/TestProjects/AppWithDeps/AppWithDeps.csproj b/test/dotnet-watch.FunctionalTests/TestProjects/AppWithDeps/AppWithDeps.csproj index 0dcb552112..7a55f33ef9 100644 --- a/test/dotnet-watch.FunctionalTests/TestProjects/AppWithDeps/AppWithDeps.csproj +++ b/test/dotnet-watch.FunctionalTests/TestProjects/AppWithDeps/AppWithDeps.csproj @@ -1,9 +1,12 @@ - netcoreapp2.1 + netcoreapp2.2 exe true + + + 99.9 diff --git a/test/dotnet-watch.FunctionalTests/TestProjects/GlobbingApp/GlobbingApp.csproj b/test/dotnet-watch.FunctionalTests/TestProjects/GlobbingApp/GlobbingApp.csproj index 8d31551941..7c90a96aba 100644 --- a/test/dotnet-watch.FunctionalTests/TestProjects/GlobbingApp/GlobbingApp.csproj +++ b/test/dotnet-watch.FunctionalTests/TestProjects/GlobbingApp/GlobbingApp.csproj @@ -1,10 +1,13 @@ - netcoreapp2.1 + netcoreapp2.2 exe false true + + + 99.9 diff --git a/test/dotnet-watch.FunctionalTests/TestProjects/KitchenSink/KitchenSink.csproj b/test/dotnet-watch.FunctionalTests/TestProjects/KitchenSink/KitchenSink.csproj index 72f7d5cae4..e2875bdba1 100644 --- a/test/dotnet-watch.FunctionalTests/TestProjects/KitchenSink/KitchenSink.csproj +++ b/test/dotnet-watch.FunctionalTests/TestProjects/KitchenSink/KitchenSink.csproj @@ -9,8 +9,11 @@ Exe - netcoreapp2.1 + netcoreapp2.2 true + + + 99.9 diff --git a/test/dotnet-watch.FunctionalTests/TestProjects/NoDepsApp/NoDepsApp.csproj b/test/dotnet-watch.FunctionalTests/TestProjects/NoDepsApp/NoDepsApp.csproj index d4ddbcd217..47ca89d4f6 100644 --- a/test/dotnet-watch.FunctionalTests/TestProjects/NoDepsApp/NoDepsApp.csproj +++ b/test/dotnet-watch.FunctionalTests/TestProjects/NoDepsApp/NoDepsApp.csproj @@ -1,9 +1,12 @@ - netcoreapp2.1 + netcoreapp2.2 exe true + + + 99.9 diff --git a/test/dotnet-watch.FunctionalTests/dotnet-watch.FunctionalTests.csproj b/test/dotnet-watch.FunctionalTests/dotnet-watch.FunctionalTests.csproj index 768d08ce8c..7f92dede3c 100644 --- a/test/dotnet-watch.FunctionalTests/dotnet-watch.FunctionalTests.csproj +++ b/test/dotnet-watch.FunctionalTests/dotnet-watch.FunctionalTests.csproj @@ -1,7 +1,7 @@ - netcoreapp2.1 + netcoreapp2.2 $(DefaultItemExcludes);TestProjects\**\* Microsoft.DotNet.Watcher.Tools.FunctionalTests diff --git a/test/dotnet-watch.Tests/dotnet-watch.Tests.csproj b/test/dotnet-watch.Tests/dotnet-watch.Tests.csproj index c5578a357b..bc85a1119c 100644 --- a/test/dotnet-watch.Tests/dotnet-watch.Tests.csproj +++ b/test/dotnet-watch.Tests/dotnet-watch.Tests.csproj @@ -1,7 +1,7 @@ - netcoreapp2.1 + netcoreapp2.2 Microsoft.DotNet.Watcher.Tools.Tests From 31d7971d2e4529014ae4fbb8032934ce6b5173c1 Mon Sep 17 00:00:00 2001 From: "ASP.NET CI" Date: Sun, 13 May 2018 14:05:12 -0700 Subject: [PATCH 11/66] Update dependencies.props [auto-updated: dependencies] --- build/dependencies.props | 18 +++++++++--------- korebuild-lock.txt | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index 53d6c6353d..64cad79092 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -3,19 +3,19 @@ $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - 2.2.0-preview1-17050 - 2.2.0-preview1-34154 - 2.2.0-preview1-34154 - 2.2.0-preview1-34154 - 2.2.0-preview1-34154 - 2.2.0-preview1-34154 + 2.2.0-preview1-17051 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 2.0.0 2.1.0-rc1 - 2.2.0-preview1-26424-04 + 2.2.0-preview1-26509-06 15.6.1 2.0.3 - 4.5.0-preview3-26423-04 - 4.5.0-preview3-26423-04 + 4.6.0-preview1-26508-04 + 4.6.0-preview1-26508-04 9.0.1 2.3.1 2.4.0-beta.1.build3945 diff --git a/korebuild-lock.txt b/korebuild-lock.txt index 82ebcf2549..56263a26fc 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.2.0-preview1-17050 -commithash:24b548c969ffdf58730a7237a4c050b12c8711ed +version:2.2.0-preview1-17051 +commithash:253c3a480063bc3abaa5cde42f6e27b58457ef9b From 6272b3625eb127d5d6ca248f34facba9bd7016a4 Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Mon, 14 May 2018 14:17:27 -0700 Subject: [PATCH 12/66] Dispatch cancellation on CTRL+C to avoid deadlocks in killing the process tree --- shared/CliContext.cs | 9 ++-- src/dotnet-watch/DotNetWatcher.cs | 4 +- src/dotnet-watch/Internal/ProcessRunner.cs | 50 +++++++++++++------ src/dotnet-watch/Program.cs | 3 +- .../AwaitableProcess.cs | 25 ++++++++-- .../GlobbingAppTests.cs | 3 +- .../NoDepsAppTests.cs | 28 +++++++++++ .../Scenario/ProjectToolScenario.cs | 4 +- test/dotnet-watch.Tests/ProgramTests.cs | 22 ++++---- 9 files changed, 109 insertions(+), 39 deletions(-) diff --git a/shared/CliContext.cs b/shared/CliContext.cs index ad766a2e3b..854ea0fef6 100644 --- a/shared/CliContext.cs +++ b/shared/CliContext.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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; @@ -8,14 +8,13 @@ namespace Microsoft.Extensions.Tools.Internal public static class CliContext { /// - /// dotnet --verbose subcommand + /// dotnet -d|--diagnostics subcommand /// /// public static bool IsGlobalVerbose() { - bool globalVerbose; - bool.TryParse(Environment.GetEnvironmentVariable("DOTNET_CLI_CONTEXT_VERBOSE"), out globalVerbose); + bool.TryParse(Environment.GetEnvironmentVariable("DOTNET_CLI_CONTEXT_VERBOSE"), out bool globalVerbose); return globalVerbose; } } -} \ No newline at end of file +} diff --git a/src/dotnet-watch/DotNetWatcher.cs b/src/dotnet-watch/DotNetWatcher.cs index f4b0423511..476acfcb43 100644 --- a/src/dotnet-watch/DotNetWatcher.cs +++ b/src/dotnet-watch/DotNetWatcher.cs @@ -69,8 +69,10 @@ namespace Microsoft.DotNet.Watcher await Task.WhenAll(processTask, fileSetTask); - if (processTask.Result != 0 && finishedTask == processTask) + if (processTask.Result != 0 && finishedTask == processTask && !cancellationToken.IsCancellationRequested) { + // Only show this error message if the process exited non-zero due to a normal process exit. + // Don't show this if dotnet-watch killed the inner process due to file change or CTRL+C by the user _reporter.Error($"Exited with error code {processTask.Result}"); } else diff --git a/src/dotnet-watch/Internal/ProcessRunner.cs b/src/dotnet-watch/Internal/ProcessRunner.cs index a5f7cac8ef..bbded25611 100644 --- a/src/dotnet-watch/Internal/ProcessRunner.cs +++ b/src/dotnet-watch/Internal/ProcessRunner.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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; @@ -33,7 +33,7 @@ namespace Microsoft.DotNet.Watcher.Internal var stopwatch = new Stopwatch(); using (var process = CreateProcess(processSpec)) - using (var processState = new ProcessState(process)) + using (var processState = new ProcessState(process, _reporter)) { cancellationToken.Register(() => processState.TryKill()); @@ -97,27 +97,36 @@ namespace Microsoft.DotNet.Watcher.Internal private class ProcessState : IDisposable { + private readonly IReporter _reporter; private readonly Process _process; private readonly TaskCompletionSource _tcs = new TaskCompletionSource(); private volatile bool _disposed; - public ProcessState(Process process) + public ProcessState(Process process, IReporter reporter) { + _reporter = reporter; _process = process; _process.Exited += OnExited; Task = _tcs.Task.ContinueWith(_ => { - // We need to use two WaitForExit calls to ensure that all of the output/events are processed. Previously - // this code used Process.Exited, which could result in us missing some output due to the ordering of - // events. - // - // See the remarks here: https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.process.waitforexit#System_Diagnostics_Process_WaitForExit_System_Int32_ - if (!process.WaitForExit(Int32.MaxValue)) + try { - throw new TimeoutException(); - } + // We need to use two WaitForExit calls to ensure that all of the output/events are processed. Previously + // this code used Process.Exited, which could result in us missing some output due to the ordering of + // events. + // + // See the remarks here: https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.process.waitforexit#System_Diagnostics_Process_WaitForExit_System_Int32_ + if (!_process.WaitForExit(Int32.MaxValue)) + { + throw new TimeoutException(); + } - process.WaitForExit(); + _process.WaitForExit(); + } + catch (InvalidOperationException) + { + // suppress if this throws if no process is associated with this object anymore. + } }); } @@ -125,15 +134,26 @@ namespace Microsoft.DotNet.Watcher.Internal public void TryKill() { + if (_disposed) + { + return; + } + try { if (!_process.HasExited) { + _reporter.Verbose($"Killing process {_process.Id}"); _process.KillTree(); } } - catch - { } + catch (Exception ex) + { + _reporter.Verbose($"Error while killing process '{_process.StartInfo.FileName} {_process.StartInfo.Arguments}': {ex.Message}"); +#if DEBUG + _reporter.Verbose(ex.ToString()); +#endif + } } private void OnExited(object sender, EventArgs args) @@ -143,8 +163,8 @@ namespace Microsoft.DotNet.Watcher.Internal { if (!_disposed) { - _disposed = true; TryKill(); + _disposed = true; _process.Exited -= OnExited; _process.Dispose(); } diff --git a/src/dotnet-watch/Program.cs b/src/dotnet-watch/Program.cs index 7e8200b102..3ef376d048 100644 --- a/src/dotnet-watch/Program.cs +++ b/src/dotnet-watch/Program.cs @@ -121,7 +121,8 @@ namespace Microsoft.DotNet.Watcher _reporter.Output("Shutdown requested. Press Ctrl+C again to force exit."); } - _cts.Cancel(); + // Invoke the cancellation on the default thread pool to workaround https://github.com/dotnet/corefx/issues/29699 + ThreadPool.QueueUserWorkItem(_ => _cts.Cancel()); } private async Task MainInternalAsync( diff --git a/test/dotnet-watch.FunctionalTests/AwaitableProcess.cs b/test/dotnet-watch.FunctionalTests/AwaitableProcess.cs index 91b53133eb..86e2f90f79 100644 --- a/test/dotnet-watch.FunctionalTests/AwaitableProcess.cs +++ b/test/dotnet-watch.FunctionalTests/AwaitableProcess.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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; @@ -17,16 +17,26 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests { private Process _process; private readonly ProcessSpec _spec; + private readonly List _lines; private BufferBlock _source; private ITestOutputHelper _logger; + private TaskCompletionSource _exited; public AwaitableProcess(ProcessSpec spec, ITestOutputHelper logger) { _spec = spec; _logger = logger; _source = new BufferBlock(); + _lines = new List(); + _exited = new TaskCompletionSource(); } + public IEnumerable Output => _lines; + + public Task Exited => _exited.Task; + + public int Id => _process.Id; + public void Start() { if (_process != null) @@ -52,6 +62,11 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests } }; + foreach (var env in _spec.EnvironmentVariables) + { + _process.StartInfo.EnvironmentVariables[env.Key] = env.Value; + } + _process.OutputDataReceived += OnData; _process.ErrorDataReceived += OnData; _process.Exited += OnExit; @@ -65,13 +80,13 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests public async Task GetOutputLineAsync(string message, TimeSpan timeout) { _logger.WriteLine($"Waiting for output line [msg == '{message}']. Will wait for {timeout.TotalSeconds} sec."); - return await GetOutputLineAsync(m => message == m).TimeoutAfter(timeout); + return await GetOutputLineAsync(m => string.Equals(m, message, StringComparison.Ordinal)).TimeoutAfter(timeout); } public async Task GetOutputLineStartsWithAsync(string message, TimeSpan timeout) { _logger.WriteLine($"Waiting for output line [msg.StartsWith('{message}')]. Will wait for {timeout.TotalSeconds} sec."); - return await GetOutputLineAsync(m => m.StartsWith(message)).TimeoutAfter(timeout); + return await GetOutputLineAsync(m => m != null && m.StartsWith(message, StringComparison.Ordinal)).TimeoutAfter(timeout); } private async Task GetOutputLineAsync(Predicate predicate) @@ -81,6 +96,7 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests while (await _source.OutputAvailableAsync()) { var next = await _source.ReceiveAsync(); + _lines.Add(next); _logger.WriteLine($"{DateTime.Now}: recv: '{next}'"); if (predicate(next)) { @@ -119,6 +135,8 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests // Wait to ensure the process has exited and all output consumed _process.WaitForExit(); _source.Complete(); + _exited.TrySetResult(_process.ExitCode); + _logger.WriteLine($"Process {_process.Id} has exited"); } public void Dispose() @@ -135,6 +153,7 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests _process.ErrorDataReceived -= OnData; _process.OutputDataReceived -= OnData; _process.Exited -= OnExit; + _process.Dispose(); } } } diff --git a/test/dotnet-watch.FunctionalTests/GlobbingAppTests.cs b/test/dotnet-watch.FunctionalTests/GlobbingAppTests.cs index 71b5d068bf..a01a28e7d8 100644 --- a/test/dotnet-watch.FunctionalTests/GlobbingAppTests.cs +++ b/test/dotnet-watch.FunctionalTests/GlobbingAppTests.cs @@ -102,6 +102,7 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests await _app.PrepareAsync(); _app.Start(new [] { "--list" }); var lines = await _app.Process.GetAllOutputLines(); + var files = lines.Where(l => !l.StartsWith("watch :")); AssertEx.EqualFileList( _app.Scenario.WorkFolder, @@ -111,7 +112,7 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests "GlobbingApp/include/Foo.cs", "GlobbingApp/GlobbingApp.csproj", }, - lines); + files); } public void Dispose() diff --git a/test/dotnet-watch.FunctionalTests/NoDepsAppTests.cs b/test/dotnet-watch.FunctionalTests/NoDepsAppTests.cs index 8ac2f694e7..df2521b5d3 100644 --- a/test/dotnet-watch.FunctionalTests/NoDepsAppTests.cs +++ b/test/dotnet-watch.FunctionalTests/NoDepsAppTests.cs @@ -5,6 +5,8 @@ using System; using System.Diagnostics; using System.IO; using System.Threading.Tasks; +using Microsoft.AspNetCore.Testing; +using Microsoft.AspNetCore.Testing.xunit; using Xunit; using Xunit.Abstractions; @@ -15,10 +17,34 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests private static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(30); private readonly WatchableApp _app; + private readonly ITestOutputHelper _output; public NoDepsAppTests(ITestOutputHelper logger) { _app = new WatchableApp("NoDepsApp", logger); + _output = logger; + } + + [ConditionalFact] + [OSSkipCondition(OperatingSystems.Windows, SkipReason = "Testing SIGINT is specific to macOS/Linux")] + public async Task KillsProcessOnSigInt() + { + void SendSigInt(int pid) + { + _output.WriteLine($"kill -SIGINT {pid}"); + Process.Start("kill", $"-SIGINT {pid}"); + } + + await _app.StartWatcherAsync(new[] { "--no-exit" }); + + var childPid = await _app.GetProcessId(); + + SendSigInt(_app.Process.Id); + SendSigInt(childPid); + + await _app.Process.Exited.TimeoutAfter(TimeSpan.FromSeconds(30)); + + Assert.DoesNotContain(_app.Process.Output, l => l.StartsWith("Exited with error code")); } [Fact] @@ -33,6 +59,8 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests File.WriteAllText(fileToChange, programCs); await _app.HasRestarted(); + Assert.DoesNotContain(_app.Process.Output, l => l.StartsWith("Exited with error code")); + var pid2 = await _app.GetProcessId(); Assert.NotEqual(pid, pid2); diff --git a/test/dotnet-watch.FunctionalTests/Scenario/ProjectToolScenario.cs b/test/dotnet-watch.FunctionalTests/Scenario/ProjectToolScenario.cs index a0a14093ec..b5c064563b 100644 --- a/test/dotnet-watch.FunctionalTests/Scenario/ProjectToolScenario.cs +++ b/test/dotnet-watch.FunctionalTests/Scenario/ProjectToolScenario.cs @@ -1,8 +1,7 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; @@ -12,7 +11,6 @@ using System.Threading.Tasks; using System.Xml.Linq; using Microsoft.Extensions.CommandLineUtils; using Microsoft.Extensions.Internal; -using Microsoft.Extensions.Tools.Internal; using Xunit.Abstractions; namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests diff --git a/test/dotnet-watch.Tests/ProgramTests.cs b/test/dotnet-watch.Tests/ProgramTests.cs index 8c116b5595..a5998b25c8 100644 --- a/test/dotnet-watch.Tests/ProgramTests.cs +++ b/test/dotnet-watch.Tests/ProgramTests.cs @@ -28,23 +28,25 @@ namespace Microsoft.DotNet.Watcher.Tools.Tests { _tempDir .WithCSharpProject("testproj") - .WithTargetFrameworks("netcoreapp1.0") + .WithTargetFrameworks("netcoreapp2.2") .Dir() .WithFile("Program.cs") .Create(); - var stdout = new StringBuilder(); - _console.Out = new StringWriter(stdout); - var program = new Program(_console, _tempDir.Root) - .RunAsync(new[] { "run" }); + var output = new StringBuilder(); + _console.Error = _console.Out = new StringWriter(output); + using (var app = new Program(_console, _tempDir.Root)) + { + var run = app.RunAsync(new[] { "run" }); - await _console.CancelKeyPressSubscribed.TimeoutAfter(TimeSpan.FromSeconds(30)); - _console.ConsoleCancelKey(); + await _console.CancelKeyPressSubscribed.TimeoutAfter(TimeSpan.FromSeconds(30)); + _console.ConsoleCancelKey(); - var exitCode = await program.TimeoutAfter(TimeSpan.FromSeconds(30)); + var exitCode = await run.TimeoutAfter(TimeSpan.FromSeconds(30)); - Assert.Contains("Shutdown requested. Press Ctrl+C again to force exit.", stdout.ToString()); - Assert.Equal(0, exitCode); + Assert.Contains("Shutdown requested. Press Ctrl+C again to force exit.", output.ToString()); + Assert.Equal(0, exitCode); + } } public void Dispose() From 5dac1be2d75a3acdf20531d07044bca6d79f9196 Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Wed, 16 May 2018 16:47:34 -0700 Subject: [PATCH 13/66] Mark UserSecrets extension as a system component (#439) --- .../source.extension.vsixmanifest | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tooling/Microsoft.VisualStudio.SecretManager/source.extension.vsixmanifest b/tooling/Microsoft.VisualStudio.SecretManager/source.extension.vsixmanifest index 37bb50c5da..68b5347ce6 100644 --- a/tooling/Microsoft.VisualStudio.SecretManager/source.extension.vsixmanifest +++ b/tooling/Microsoft.VisualStudio.SecretManager/source.extension.vsixmanifest @@ -6,7 +6,7 @@ Microsoft.VisualStudio.SecretManager Enables IVsProjectSecrets for ASP.NET Core projects that use Microsoft.Extensions.Configuration.UserSecrets. - + From 2d70bb90214e1e16ac7e3b2f0cae670567f3651b Mon Sep 17 00:00:00 2001 From: "ASP.NET CI" Date: Sun, 20 May 2018 19:28:26 +0000 Subject: [PATCH 14/66] Update dependencies.props [auto-updated: dependencies] --- build/dependencies.props | 12 ++++++------ korebuild-lock.txt | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index 64cad79092..e4d7319982 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -3,12 +3,12 @@ $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - 2.2.0-preview1-17051 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 + 2.2.0-preview1-17060 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 2.0.0 2.1.0-rc1 2.2.0-preview1-26509-06 diff --git a/korebuild-lock.txt b/korebuild-lock.txt index 56263a26fc..06fc8a13e4 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.2.0-preview1-17051 -commithash:253c3a480063bc3abaa5cde42f6e27b58457ef9b +version:2.2.0-preview1-17060 +commithash:25b4b134d6f8f7b461928f0d495cfc695ccabb5b From 4fb42482d9a65bbd1a792d2cd22280dfa4faca47 Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Wed, 23 May 2018 10:51:58 -0700 Subject: [PATCH 15/66] Add myget.org aspnetcore-tools to Nuget.config --- NuGet.config | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/NuGet.config b/NuGet.config index e32bddfd51..8c30f1c84e 100644 --- a/NuGet.config +++ b/NuGet.config @@ -2,6 +2,10 @@ - + + From 83134227ab4ea17238b84c12d55a8dae6d530208 Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Thu, 24 May 2018 08:24:56 -0700 Subject: [PATCH 16/66] Add support for DOTNET_WATCH_ITERATION (#443) This environment variable can be used to determine how many types the inner command has been re-launched Resolves #387 --- build/repo.props | 3 --- src/dotnet-watch/CommandLineOptions.cs | 4 ++++ src/dotnet-watch/DotNetWatcher.cs | 6 ++++++ .../DotNetWatcherTests.cs | 19 +++++++++++++++++++ .../TestProjects/KitchenSink/Program.cs | 1 + 5 files changed, 30 insertions(+), 3 deletions(-) diff --git a/build/repo.props b/build/repo.props index 015f722dd9..9569f0ba08 100644 --- a/build/repo.props +++ b/build/repo.props @@ -2,9 +2,6 @@ - - - diff --git a/src/dotnet-watch/CommandLineOptions.cs b/src/dotnet-watch/CommandLineOptions.cs index e6e23890d5..5cbaab33e9 100644 --- a/src/dotnet-watch/CommandLineOptions.cs +++ b/src/dotnet-watch/CommandLineOptions.cs @@ -53,6 +53,10 @@ Environment variables: DOTNET_WATCH dotnet-watch sets this variable to '1' on all child processes launched. + DOTNET_WATCH_ITERATION + dotnet-watch sets this variable to '1' and increments by one each time + a file is changed and the command is restarted. + Remarks: The special option '--' is used to delimit the end of the options and the beginning of arguments that will be passed to the child dotnet process. diff --git a/src/dotnet-watch/DotNetWatcher.cs b/src/dotnet-watch/DotNetWatcher.cs index 476acfcb43..8431615c1e 100644 --- a/src/dotnet-watch/DotNetWatcher.cs +++ b/src/dotnet-watch/DotNetWatcher.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Globalization; using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.Watcher.Internal; @@ -32,8 +33,13 @@ namespace Microsoft.DotNet.Watcher cancellationToken.Register(state => ((TaskCompletionSource) state).TrySetResult(null), cancelledTaskSource); + var iteration = 1; + while (true) { + processSpec.EnvironmentVariables["DOTNET_WATCH_ITERATION"] = iteration.ToString(CultureInfo.InvariantCulture); + iteration++; + var fileSet = await fileSetFactory.CreateAsync(cancellationToken); if (fileSet == null) diff --git a/test/dotnet-watch.FunctionalTests/DotNetWatcherTests.cs b/test/dotnet-watch.FunctionalTests/DotNetWatcherTests.cs index d0dc735247..d1dd022a15 100644 --- a/test/dotnet-watch.FunctionalTests/DotNetWatcherTests.cs +++ b/test/dotnet-watch.FunctionalTests/DotNetWatcherTests.cs @@ -3,6 +3,8 @@ using System; using System.Collections.Generic; +using System.IO; +using System.Globalization; using System.Threading.Tasks; using Xunit; using Xunit.Abstractions; @@ -30,6 +32,23 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests Assert.Equal("1", envValue); } + [Fact] + public async Task RunsWithIterationEnvVariable() + { + await _app.StartWatcherAsync(); + var source = Path.Combine(_app.SourceDirectory, "Program.cs"); + const string messagePrefix = "DOTNET_WATCH_ITERATION = "; + for (var i = 1; i <= 4; i++) + { + var message = await _app.Process.GetOutputLineStartsWithAsync(messagePrefix, TimeSpan.FromMinutes(2)); + var count = int.Parse(message.Substring(messagePrefix.Length), CultureInfo.InvariantCulture); + Assert.Equal(i, count); + + File.SetLastWriteTime(source, DateTime.Now); + await _app.HasRestarted(); + } + } + public void Dispose() { _app.Dispose(); diff --git a/test/dotnet-watch.FunctionalTests/TestProjects/KitchenSink/Program.cs b/test/dotnet-watch.FunctionalTests/TestProjects/KitchenSink/Program.cs index 5251cdc1e0..f38dc8231b 100644 --- a/test/dotnet-watch.FunctionalTests/TestProjects/KitchenSink/Program.cs +++ b/test/dotnet-watch.FunctionalTests/TestProjects/KitchenSink/Program.cs @@ -13,6 +13,7 @@ namespace KitchenSink Console.WriteLine("Started"); Console.WriteLine("PID = " + Process.GetCurrentProcess().Id); Console.WriteLine("DOTNET_WATCH = " + Environment.GetEnvironmentVariable("DOTNET_WATCH")); + Console.WriteLine("DOTNET_WATCH_ITERATION = " + Environment.GetEnvironmentVariable("DOTNET_WATCH_ITERATION")); } } } From e7e9cdc5eeb1c893b6d1aa2a08b8acbe93ef0354 Mon Sep 17 00:00:00 2001 From: "Nate McMaster (automated)" Date: Fri, 25 May 2018 16:13:50 -0700 Subject: [PATCH 17/66] Update bootstrapper scripts (automated commit) [ci skip] --- run.ps1 | 25 +++++++++++++++++++------ run.sh | 33 +++++++++++++++++++++++++++++---- 2 files changed, 48 insertions(+), 10 deletions(-) diff --git a/run.ps1 b/run.ps1 index 27dcf848f8..3b27382468 100644 --- a/run.ps1 +++ b/run.ps1 @@ -26,12 +26,18 @@ The base url where build tools can be downloaded. Overrides the value from the c .PARAMETER Update Updates KoreBuild to the latest version even if a lock file is present. +.PARAMETER Reinstall +Re-installs KoreBuild + .PARAMETER ConfigFile The path to the configuration file that stores values. Defaults to korebuild.json. .PARAMETER ToolsSourceSuffix The Suffix to append to the end of the ToolsSource. Useful for query strings in blob stores. +.PARAMETER CI +Sets up CI specific settings and variables. + .PARAMETER Arguments Arguments to be passed to the command @@ -65,8 +71,10 @@ param( [string]$ToolsSource, [Alias('u')] [switch]$Update, - [string]$ConfigFile, + [switch]$Reinstall, [string]$ToolsSourceSuffix, + [string]$ConfigFile = $null, + [switch]$CI, [Parameter(ValueFromRemainingArguments = $true)] [string[]]$Arguments ) @@ -93,6 +101,10 @@ function Get-KoreBuild { $version = $version.TrimStart('version:').Trim() $korebuildPath = Join-Paths $DotNetHome ('buildtools', 'korebuild', $version) + if ($Reinstall -and (Test-Path $korebuildPath)) { + Remove-Item -Force -Recurse $korebuildPath + } + if (!(Test-Path $korebuildPath)) { Write-Host -ForegroundColor Magenta "Downloading KoreBuild $version" New-Item -ItemType Directory -Path $korebuildPath | Out-Null @@ -101,9 +113,9 @@ function Get-KoreBuild { try { $tmpfile = Join-Path ([IO.Path]::GetTempPath()) "KoreBuild-$([guid]::NewGuid()).zip" Get-RemoteFile $remotePath $tmpfile $ToolsSourceSuffix - if (Get-Command -Name 'Expand-Archive' -ErrorAction Ignore) { + if (Get-Command -Name 'Microsoft.PowerShell.Archive\Expand-Archive' -ErrorAction Ignore) { # Use built-in commands where possible as they are cross-plat compatible - Expand-Archive -Path $tmpfile -DestinationPath $korebuildPath + Microsoft.PowerShell.Archive\Expand-Archive -Path $tmpfile -DestinationPath $korebuildPath } else { # Fallback to old approach for old installations of PowerShell @@ -167,8 +179,9 @@ if (Test-Path $ConfigFile) { } } catch { - Write-Warning "$ConfigFile could not be read. Its settings will be ignored." - Write-Warning $Error[0] + Write-Host -ForegroundColor Red $Error[0] + Write-Error "$ConfigFile contains invalid JSON." + exit 1 } } @@ -188,7 +201,7 @@ $korebuildPath = Get-KoreBuild Import-Module -Force -Scope Local (Join-Path $korebuildPath 'KoreBuild.psd1') try { - Set-KoreBuildSettings -ToolsSource $ToolsSource -DotNetHome $DotNetHome -RepoPath $Path -ConfigFile $ConfigFile + Set-KoreBuildSettings -ToolsSource $ToolsSource -DotNetHome $DotNetHome -RepoPath $Path -ConfigFile $ConfigFile -CI:$CI Invoke-KoreBuildCommand $Command @Arguments } finally { diff --git a/run.sh b/run.sh index 834961fc3a..02aac15874 100755 --- a/run.sh +++ b/run.sh @@ -14,10 +14,12 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" [ -z "${DOTNET_HOME:-}" ] && DOTNET_HOME="$HOME/.dotnet" verbose=false update=false +reinstall=false repo_path="$DIR" channel='' tools_source='' tools_source_suffix='' +ci=false # # Functions @@ -38,6 +40,8 @@ __usage() { echo " -s|--tools-source|-ToolsSource The base url where build tools can be downloaded. Overrides the value from the config file." echo " --tools-source-suffix|-ToolsSourceSuffix The suffix to append to tools-source. Useful for query strings." echo " -u|--update Update to the latest KoreBuild even if the lock file is present." + echo " --reinstall Reinstall KoreBuild." + echo " --ci Apply CI specific settings and environment variables." echo "" echo "Description:" echo " This function will create a file \$DIR/korebuild-lock.txt. This lock file can be committed to source, but does not have to be." @@ -62,6 +66,10 @@ get_korebuild() { version="$(echo "${version#version:}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')" local korebuild_path="$DOTNET_HOME/buildtools/korebuild/$version" + if [ "$reinstall" = true ] && [ -d "$korebuild_path" ]; then + rm -rf "$korebuild_path" + fi + { if [ ! -d "$korebuild_path" ]; then mkdir -p "$korebuild_path" @@ -175,6 +183,12 @@ while [[ $# -gt 0 ]]; do -u|--update|-Update) update=true ;; + --reinstall|-[Rr]einstall) + reinstall=true + ;; + --ci|-[Cc][Ii]) + ci=true + ;; --verbose|-Verbose) verbose=true ;; @@ -206,17 +220,28 @@ if [ -f "$config_file" ]; then config_channel="$(jq -r 'select(.channel!=null) | .channel' "$config_file")" config_tools_source="$(jq -r 'select(.toolsSource!=null) | .toolsSource' "$config_file")" else - __warn "$config_file is invalid JSON. Its settings will be ignored." + _error "$config_file contains invalid JSON." + exit 1 fi elif __machine_has python ; then if python -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'))" >/dev/null ; then config_channel="$(python -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'));print(obj['channel'] if 'channel' in obj else '')")" config_tools_source="$(python -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'));print(obj['toolsSource'] if 'toolsSource' in obj else '')")" else - __warn "$config_file is invalid JSON. Its settings will be ignored." + _error "$config_file contains invalid JSON." + exit 1 + fi + elif __machine_has python3 ; then + if python3 -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'))" >/dev/null ; then + config_channel="$(python3 -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'));print(obj['channel'] if 'channel' in obj else '')")" + config_tools_source="$(python3 -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'));print(obj['toolsSource'] if 'toolsSource' in obj else '')")" + else + _error "$config_file contains invalid JSON." + exit 1 fi else - __warn 'Missing required command: jq or pyton. Could not parse the JSON file. Its settings will be ignored.' + _error 'Missing required command: jq or python. Could not parse the JSON file.' + exit 1 fi [ ! -z "${config_channel:-}" ] && channel="$config_channel" @@ -227,5 +252,5 @@ fi [ -z "$tools_source" ] && tools_source='https://aspnetcore.blob.core.windows.net/buildtools' get_korebuild -set_korebuildsettings "$tools_source" "$DOTNET_HOME" "$repo_path" "$config_file" +set_korebuildsettings "$tools_source" "$DOTNET_HOME" "$repo_path" "$config_file" "$ci" invoke_korebuild_command "$command" "$@" From f573ab6c8216cdac599bad06695dbe6cfa1c1259 Mon Sep 17 00:00:00 2001 From: "ASP.NET CI" Date: Sun, 27 May 2018 19:11:20 +0000 Subject: [PATCH 18/66] Update dependencies.props [auto-updated: dependencies] --- build/dependencies.props | 18 +++++++++--------- korebuild-lock.txt | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index e4d7319982..d2adad6991 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -3,19 +3,19 @@ $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - 2.2.0-preview1-17060 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 + 2.2.0-preview1-17064 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 2.0.0 2.1.0-rc1 - 2.2.0-preview1-26509-06 + 2.2.0-preview1-26526-03 15.6.1 2.0.3 - 4.6.0-preview1-26508-04 - 4.6.0-preview1-26508-04 + 4.6.0-preview1-26525-01 + 4.6.0-preview1-26525-01 9.0.1 2.3.1 2.4.0-beta.1.build3945 diff --git a/korebuild-lock.txt b/korebuild-lock.txt index 06fc8a13e4..de5df64434 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.2.0-preview1-17060 -commithash:25b4b134d6f8f7b461928f0d495cfc695ccabb5b +version:2.2.0-preview1-17064 +commithash:5380a2461b135b261646f31d1c919ab0a7b577a8 From e74907d6bf0d426678c36040f21c63bfc1913472 Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Thu, 31 May 2018 20:16:06 -0700 Subject: [PATCH 19/66] Resolves #444 - remove workaround for CTRL+C deadlocks --- src/dotnet-watch/Program.cs | 5 ++-- .../DotNetWatcherTests.cs | 4 +++- .../NoDepsAppTests.cs | 24 ------------------- 3 files changed, 5 insertions(+), 28 deletions(-) diff --git a/src/dotnet-watch/Program.cs b/src/dotnet-watch/Program.cs index 3ef376d048..25317fb6b2 100644 --- a/src/dotnet-watch/Program.cs +++ b/src/dotnet-watch/Program.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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; @@ -121,8 +121,7 @@ namespace Microsoft.DotNet.Watcher _reporter.Output("Shutdown requested. Press Ctrl+C again to force exit."); } - // Invoke the cancellation on the default thread pool to workaround https://github.com/dotnet/corefx/issues/29699 - ThreadPool.QueueUserWorkItem(_ => _cts.Cancel()); + _cts.Cancel(); } private async Task MainInternalAsync( diff --git a/test/dotnet-watch.FunctionalTests/DotNetWatcherTests.cs b/test/dotnet-watch.FunctionalTests/DotNetWatcherTests.cs index d1dd022a15..cc0a1c3fc0 100644 --- a/test/dotnet-watch.FunctionalTests/DotNetWatcherTests.cs +++ b/test/dotnet-watch.FunctionalTests/DotNetWatcherTests.cs @@ -37,6 +37,7 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests { await _app.StartWatcherAsync(); var source = Path.Combine(_app.SourceDirectory, "Program.cs"); + var contents = File.ReadAllText(source); const string messagePrefix = "DOTNET_WATCH_ITERATION = "; for (var i = 1; i <= 4; i++) { @@ -44,7 +45,8 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests var count = int.Parse(message.Substring(messagePrefix.Length), CultureInfo.InvariantCulture); Assert.Equal(i, count); - File.SetLastWriteTime(source, DateTime.Now); + File.Delete(source); + File.WriteAllText(source, contents); await _app.HasRestarted(); } } diff --git a/test/dotnet-watch.FunctionalTests/NoDepsAppTests.cs b/test/dotnet-watch.FunctionalTests/NoDepsAppTests.cs index df2521b5d3..22f46cbbe0 100644 --- a/test/dotnet-watch.FunctionalTests/NoDepsAppTests.cs +++ b/test/dotnet-watch.FunctionalTests/NoDepsAppTests.cs @@ -5,8 +5,6 @@ using System; using System.Diagnostics; using System.IO; using System.Threading.Tasks; -using Microsoft.AspNetCore.Testing; -using Microsoft.AspNetCore.Testing.xunit; using Xunit; using Xunit.Abstractions; @@ -25,28 +23,6 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests _output = logger; } - [ConditionalFact] - [OSSkipCondition(OperatingSystems.Windows, SkipReason = "Testing SIGINT is specific to macOS/Linux")] - public async Task KillsProcessOnSigInt() - { - void SendSigInt(int pid) - { - _output.WriteLine($"kill -SIGINT {pid}"); - Process.Start("kill", $"-SIGINT {pid}"); - } - - await _app.StartWatcherAsync(new[] { "--no-exit" }); - - var childPid = await _app.GetProcessId(); - - SendSigInt(_app.Process.Id); - SendSigInt(childPid); - - await _app.Process.Exited.TimeoutAfter(TimeSpan.FromSeconds(30)); - - Assert.DoesNotContain(_app.Process.Output, l => l.StartsWith("Exited with error code")); - } - [Fact] public async Task RestartProcessOnFileChange() { From fed623db5c2c10008e8aaae739606eaa914d3a89 Mon Sep 17 00:00:00 2001 From: "ASP.NET CI" Date: Sun, 3 Jun 2018 19:11:05 +0000 Subject: [PATCH 20/66] Update dependencies.props [auto-updated: dependencies] --- build/dependencies.props | 20 ++++++++++---------- korebuild-lock.txt | 4 ++-- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index d2adad6991..ce7db6101f 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -3,19 +3,19 @@ $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - 2.2.0-preview1-17064 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 + 2.2.0-preview1-17067 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 2.0.0 - 2.1.0-rc1 - 2.2.0-preview1-26526-03 + 2.1.0 + 2.2.0-preview1-26531-03 15.6.1 2.0.3 - 4.6.0-preview1-26525-01 - 4.6.0-preview1-26525-01 + 4.6.0-preview1-26531-03 + 4.6.0-preview1-26531-03 9.0.1 2.3.1 2.4.0-beta.1.build3945 diff --git a/korebuild-lock.txt b/korebuild-lock.txt index de5df64434..34bf7eb808 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.2.0-preview1-17064 -commithash:5380a2461b135b261646f31d1c919ab0a7b577a8 +version:2.2.0-preview1-17067 +commithash:2af0e2e3d02329b4f0290061ab9bd8c7ca1aa26f From 124b6ba9a193ce9753b06e0bae4725a6bd3d8342 Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Tue, 5 Jun 2018 22:31:24 -0700 Subject: [PATCH 21/66] Add certificate names for code signing --- Directory.Build.props | 3 +- NuGetPackageVerifier.json | 65 +------------------ korebuild-lock.txt | 4 +- src/dotnet-sql-cache/dotnet-sql-cache.csproj | 13 ++++ .../dotnet-user-secrets.csproj | 19 ++++++ 5 files changed, 38 insertions(+), 66 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 29d623306f..8ca8a8757b 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -14,8 +14,9 @@ $(MSBuildThisFileDirectory) $(MSBuildThisFileDirectory)build\Key.snk true - true + MicrosoftNuGet Microsoft + true true diff --git a/NuGetPackageVerifier.json b/NuGetPackageVerifier.json index 6503b35342..fb43448889 100644 --- a/NuGetPackageVerifier.json +++ b/NuGetPackageVerifier.json @@ -12,73 +12,12 @@ "dotnet-sql-cache": { "packageTypes": [ "DotnetTool" - ], - "Exclusions": { - "NEUTRAL_RESOURCES_LANGUAGE": { - "tools/netcoreapp2.2/any/System.Runtime.CompilerServices.Unsafe.dll": "Assembly is built by another project but bundled in our nupkg." - }, - "WRONG_PUBLICKEYTOKEN": { - "tools/netcoreapp2.2/any/System.Data.SqlClient.dll": "Assembly is built by another project but bundled in our nupkg.", - "tools/netcoreapp2.2/any/runtimes/unix/lib/netcoreapp2.1/System.Data.SqlClient.dll": "Assembly is built by another project but bundled in our nupkg.", - "tools/netcoreapp2.2/any/runtimes/win/lib/netcoreapp2.1/System.Data.SqlClient.dll": "Assembly is built by another project but bundled in our nupkg.", - "tools/netcoreapp2.2/any/System.Runtime.CompilerServices.Unsafe.dll": "Assembly is built by another project but bundled in our nupkg.", - "tools/netcoreapp2.2/any/System.Text.Encoding.CodePages.dll": "Assembly is built by another project but bundled in our nupkg.", - "tools/netcoreapp2.2/any/runtimes/win/lib/netcoreapp2.0/System.Text.Encoding.CodePages.dll": "Assembly is built by another project but bundled in our nupkg." - }, - "ASSEMBLY_INFORMATIONAL_VERSION_MISMATCH": { - "tools/netcoreapp2.2/any/System.Data.SqlClient.dll": "Assembly is built by another project but bundled in our nupkg.", - "tools/netcoreapp2.2/any/runtimes/unix/lib/netcoreapp2.1/System.Data.SqlClient.dll": "Assembly is built by another project but bundled in our nupkg.", - "tools/netcoreapp2.2/any/runtimes/win/lib/netcoreapp2.1/System.Data.SqlClient.dll": "Assembly is built by another project but bundled in our nupkg.", - "tools/netcoreapp2.2/any/System.Runtime.CompilerServices.Unsafe.dll": "Assembly is built by another project but bundled in our nupkg.", - "tools/netcoreapp2.2/any/System.Text.Encoding.CodePages.dll": "Assembly is built by another project but bundled in our nupkg.", - "tools/netcoreapp2.2/any/runtimes/win/lib/netcoreapp2.0/System.Text.Encoding.CodePages.dll": "Assembly is built by another project but bundled in our nupkg." - }, - "ASSEMBLY_FILE_VERSION_MISMATCH": { - "tools/netcoreapp2.2/any/System.Data.SqlClient.dll": "Assembly is built by another project but bundled in our nupkg.", - "tools/netcoreapp2.2/any/runtimes/unix/lib/netcoreapp2.1/System.Data.SqlClient.dll": "Assembly is built by another project but bundled in our nupkg.", - "tools/netcoreapp2.2/any/runtimes/win/lib/netcoreapp2.1/System.Data.SqlClient.dll": "Assembly is built by another project but bundled in our nupkg.", - "tools/netcoreapp2.2/any/System.Runtime.CompilerServices.Unsafe.dll": "Assembly is built by another project but bundled in our nupkg.", - "tools/netcoreapp2.2/any/System.Text.Encoding.CodePages.dll": "Assembly is built by another project but bundled in our nupkg.", - "tools/netcoreapp2.2/any/runtimes/win/lib/netcoreapp2.0/System.Text.Encoding.CodePages.dll": "Assembly is built by another project but bundled in our nupkg." - }, - "ASSEMBLY_VERSION_MISMATCH": { - "tools/netcoreapp2.2/any/System.Data.SqlClient.dll": "Assembly is built by another project but bundled in our nupkg.", - "tools/netcoreapp2.2/any/runtimes/unix/lib/netcoreapp2.1/System.Data.SqlClient.dll": "Assembly is built by another project but bundled in our nupkg.", - "tools/netcoreapp2.2/any/runtimes/win/lib/netcoreapp2.1/System.Data.SqlClient.dll": "Assembly is built by another project but bundled in our nupkg.", - "tools/netcoreapp2.2/any/System.Runtime.CompilerServices.Unsafe.dll": "Assembly is built by another project but bundled in our nupkg.", - "tools/netcoreapp2.2/any/System.Text.Encoding.CodePages.dll": "Assembly is built by another project but bundled in our nupkg.", - "tools/netcoreapp2.2/any/runtimes/win/lib/netcoreapp2.0/System.Text.Encoding.CodePages.dll": "Assembly is built by another project but bundled in our nupkg." - } - } + ] }, "dotnet-user-secrets": { "packageTypes": [ "DotnetTool" - ], - "Exclusions": { - "NEUTRAL_RESOURCES_LANGUAGE": { - "tools/netcoreapp2.2/any/System.Runtime.CompilerServices.Unsafe.dll": "Assembly is built by another project but bundled in our nupkg." - }, - "SERVICING_ATTRIBUTE": { - "tools/netcoreapp2.2/any/Newtonsoft.Json.dll": "Assembly is built by another project but bundled in our nupkg." - }, - "WRONG_PUBLICKEYTOKEN": { - "tools/netcoreapp2.2/any/Newtonsoft.Json.dll": "Assembly is built by another project but bundled in our nupkg.", - "tools/netcoreapp2.2/any/System.Runtime.CompilerServices.Unsafe.dll": "Assembly is built by another project but bundled in our nupkg." - }, - "ASSEMBLY_INFORMATIONAL_VERSION_MISMATCH": { - "tools/netcoreapp2.2/any/Newtonsoft.Json.dll": "Assembly is built by another project but bundled in our nupkg.", - "tools/netcoreapp2.2/any/System.Runtime.CompilerServices.Unsafe.dll": "Assembly is built by another project but bundled in our nupkg." - }, - "ASSEMBLY_FILE_VERSION_MISMATCH": { - "tools/netcoreapp2.2/any/Newtonsoft.Json.dll": "Assembly is built by another project but bundled in our nupkg.", - "tools/netcoreapp2.2/any/System.Runtime.CompilerServices.Unsafe.dll": "Assembly is built by another project but bundled in our nupkg." - }, - "ASSEMBLY_VERSION_MISMATCH": { - "tools/netcoreapp2.2/any/Newtonsoft.Json.dll": "Assembly is built by another project but bundled in our nupkg.", - "tools/netcoreapp2.2/any/System.Runtime.CompilerServices.Unsafe.dll": "Assembly is built by another project but bundled in our nupkg." - } - } + ] }, "dotnet-dev-certs": { "packageTypes": [ diff --git a/korebuild-lock.txt b/korebuild-lock.txt index 34bf7eb808..2dedb9dd4c 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.2.0-preview1-17067 -commithash:2af0e2e3d02329b4f0290061ab9bd8c7ca1aa26f +version:2.2.0-preview1-17075 +commithash:d9f07c7f313a0af1d49f003f5424b4dbbdd3e09f diff --git a/src/dotnet-sql-cache/dotnet-sql-cache.csproj b/src/dotnet-sql-cache/dotnet-sql-cache.csproj index 3949d90be0..4ab0f3425c 100644 --- a/src/dotnet-sql-cache/dotnet-sql-cache.csproj +++ b/src/dotnet-sql-cache/dotnet-sql-cache.csproj @@ -22,4 +22,17 @@ + + + + + + + + + + + + + diff --git a/src/dotnet-user-secrets/dotnet-user-secrets.csproj b/src/dotnet-user-secrets/dotnet-user-secrets.csproj index 74bb54c602..bcb37e37b2 100644 --- a/src/dotnet-user-secrets/dotnet-user-secrets.csproj +++ b/src/dotnet-user-secrets/dotnet-user-secrets.csproj @@ -25,4 +25,23 @@ + + + + + + + + + + + + + + + + + + + From 736a09e889f30e28249a6bfeacd5ec8caef9879e Mon Sep 17 00:00:00 2001 From: "ASP.NET CI" Date: Thu, 7 Jun 2018 19:31:53 +0000 Subject: [PATCH 22/66] Update dependencies.props [auto-updated: dependencies] --- build/dependencies.props | 18 +++++++++--------- korebuild-lock.txt | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index ce7db6101f..0b26eb5044 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -3,19 +3,19 @@ $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - 2.2.0-preview1-17067 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 + 2.2.0-preview1-17081 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 2.0.0 2.1.0 - 2.2.0-preview1-26531-03 + 2.2.0-preview1-26606-01 15.6.1 2.0.3 - 4.6.0-preview1-26531-03 - 4.6.0-preview1-26531-03 + 4.6.0-preview1-26605-01 + 4.6.0-preview1-26605-01 9.0.1 2.3.1 2.4.0-beta.1.build3945 diff --git a/korebuild-lock.txt b/korebuild-lock.txt index 2dedb9dd4c..9592880b2a 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.2.0-preview1-17075 -commithash:d9f07c7f313a0af1d49f003f5424b4dbbdd3e09f +version:2.2.0-preview1-17081 +commithash:73f09c256e2a54270951562ecc0ef4a953926c36 From 3033dd6b19357d50417d7e79bea4d7f1585607bd Mon Sep 17 00:00:00 2001 From: Ryan Brandenburg Date: Thu, 7 Jun 2018 15:44:17 -0700 Subject: [PATCH 23/66] Adding VSTS file --- .vsts-pipelines/builds/ci-internal.yml | 13 +++++++++++++ .vsts-pipelines/builds/ci-public.yml | 15 +++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 .vsts-pipelines/builds/ci-internal.yml create mode 100644 .vsts-pipelines/builds/ci-public.yml diff --git a/.vsts-pipelines/builds/ci-internal.yml b/.vsts-pipelines/builds/ci-internal.yml new file mode 100644 index 0000000000..d7ceb76378 --- /dev/null +++ b/.vsts-pipelines/builds/ci-internal.yml @@ -0,0 +1,13 @@ +trigger: +- dev +- release/* + +resources: + repositories: + - repository: buildtools + type: git + name: aspnet-BuildTools + ref: refs/heads/dev + +phases: +- template: .vsts-pipelines/templates/project-ci.yml@buildtools diff --git a/.vsts-pipelines/builds/ci-public.yml b/.vsts-pipelines/builds/ci-public.yml new file mode 100644 index 0000000000..b7f25723f8 --- /dev/null +++ b/.vsts-pipelines/builds/ci-public.yml @@ -0,0 +1,15 @@ +trigger: +- dev +- release/* + +# See https://github.com/aspnet/BuildTools +resources: + repositories: + - repository: buildtools + type: github + endpoint: DotNet-Bot GitHub Connection + name: aspnet/BuildTools + ref: refs/heads/dev + +phases: +- template: .vsts-pipelines/templates/project-ci.yml@buildtools From b882d9727751f58435368444270495838f236060 Mon Sep 17 00:00:00 2001 From: "ASP.NET CI" Date: Mon, 25 Jun 2018 11:11:57 -0700 Subject: [PATCH 24/66] Update dependencies.props [auto-updated: dependencies] --- build/dependencies.props | 18 +++++++++--------- korebuild-lock.txt | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index 0b26eb5044..cc06d1b63b 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -3,19 +3,19 @@ $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - 2.2.0-preview1-17081 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 + 2.2.0-preview1-17090 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 2.0.0 2.1.0 - 2.2.0-preview1-26606-01 + 2.2.0-preview1-26618-02 15.6.1 2.0.3 - 4.6.0-preview1-26605-01 - 4.6.0-preview1-26605-01 + 4.6.0-preview1-26617-01 + 4.6.0-preview1-26617-01 9.0.1 2.3.1 2.4.0-beta.1.build3945 diff --git a/korebuild-lock.txt b/korebuild-lock.txt index 9592880b2a..3e694b2ed8 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.2.0-preview1-17081 -commithash:73f09c256e2a54270951562ecc0ef4a953926c36 +version:2.2.0-preview1-17090 +commithash:b19e903e946579cd9482089bce7d917e8bacd765 From 3177be42f50495aa2295ddbe28a4b52163c8bc01 Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Thu, 28 Jun 2018 16:19:32 -0700 Subject: [PATCH 25/66] Update infrastructure for the 2.2 release --- .vsts-pipelines/builds/ci-internal.yml | 4 ++-- .vsts-pipelines/builds/ci-public.yml | 6 +++--- build/repo.props | 1 + korebuild.json | 4 ++-- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/.vsts-pipelines/builds/ci-internal.yml b/.vsts-pipelines/builds/ci-internal.yml index d7ceb76378..dc7b8a3cb9 100644 --- a/.vsts-pipelines/builds/ci-internal.yml +++ b/.vsts-pipelines/builds/ci-internal.yml @@ -1,5 +1,5 @@ trigger: -- dev +- master - release/* resources: @@ -7,7 +7,7 @@ resources: - repository: buildtools type: git name: aspnet-BuildTools - ref: refs/heads/dev + ref: refs/heads/release/2.2 phases: - template: .vsts-pipelines/templates/project-ci.yml@buildtools diff --git a/.vsts-pipelines/builds/ci-public.yml b/.vsts-pipelines/builds/ci-public.yml index b7f25723f8..f5087d9c30 100644 --- a/.vsts-pipelines/builds/ci-public.yml +++ b/.vsts-pipelines/builds/ci-public.yml @@ -1,5 +1,5 @@ trigger: -- dev +- master - release/* # See https://github.com/aspnet/BuildTools @@ -9,7 +9,7 @@ resources: type: github endpoint: DotNet-Bot GitHub Connection name: aspnet/BuildTools - ref: refs/heads/dev - + ref: refs/heads/release/2.2 + phases: - template: .vsts-pipelines/templates/project-ci.yml@buildtools diff --git a/build/repo.props b/build/repo.props index 9569f0ba08..2108954d7e 100644 --- a/build/repo.props +++ b/build/repo.props @@ -8,6 +8,7 @@ Internal.AspNetCore.Universe.Lineup + 2.2.0-* https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json diff --git a/korebuild.json b/korebuild.json index 4d59ad132a..7002d25ca1 100644 --- a/korebuild.json +++ b/korebuild.json @@ -1,6 +1,6 @@ { - "$schema": "https://raw.githubusercontent.com/aspnet/BuildTools/dev/tools/korebuild.schema.json", - "channel": "dev", + "$schema": "https://raw.githubusercontent.com/aspnet/BuildTools/release/2.2/tools/korebuild.schema.json", + "channel": "release/2.2", "toolsets": { "visualstudio": { "required": [ From cb954e15f88ffb164d2f23704bfb2a56ff7cf344 Mon Sep 17 00:00:00 2001 From: "ASP.NET CI" Date: Sun, 8 Jul 2018 12:10:46 -0700 Subject: [PATCH 26/66] Update dependencies.props [auto-updated: dependencies] --- build/dependencies.props | 18 +++++++++--------- korebuild-lock.txt | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index cc06d1b63b..3c51bc3d39 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -3,14 +3,14 @@ $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - 2.2.0-preview1-17090 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.0.0 - 2.1.0 + 2.2.0-preview1-17099 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.0.7 + 2.1.1 2.2.0-preview1-26618-02 15.6.1 2.0.3 @@ -18,7 +18,7 @@ 4.6.0-preview1-26617-01 9.0.1 2.3.1 - 2.4.0-beta.1.build3945 + 2.4.0-rc.1.build4038 diff --git a/korebuild-lock.txt b/korebuild-lock.txt index 3e694b2ed8..8b9d17825f 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.2.0-preview1-17090 -commithash:b19e903e946579cd9482089bce7d917e8bacd765 +version:2.2.0-preview1-17099 +commithash:263ed1db9866b6b419b1f5d5189a712aa218acb3 From 93c12758277030e7c368f780d03f91b2ad10c504 Mon Sep 17 00:00:00 2001 From: "ASP.NET CI" Date: Sun, 15 Jul 2018 12:10:39 -0700 Subject: [PATCH 27/66] Update dependencies.props [auto-updated: dependencies] --- build/dependencies.props | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index 3c51bc3d39..a9483aa4d5 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -4,18 +4,18 @@ 2.2.0-preview1-17099 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.0.7 - 2.1.1 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.0.9 + 2.1.2 2.2.0-preview1-26618-02 15.6.1 2.0.3 - 4.6.0-preview1-26617-01 - 4.6.0-preview1-26617-01 + 4.5.1 + 4.5.0 9.0.1 2.3.1 2.4.0-rc.1.build4038 From dcde9afc4888af1b8b1e71e50f1183c240155a47 Mon Sep 17 00:00:00 2001 From: "ASP.NET CI" Date: Sun, 22 Jul 2018 12:10:11 -0700 Subject: [PATCH 28/66] Update dependencies.props [auto-updated: dependencies] --- build/dependencies.props | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index a9483aa4d5..607267c52c 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -4,11 +4,11 @@ 2.2.0-preview1-17099 - 2.2.0-preview1-34694 - 2.2.0-preview1-34694 - 2.2.0-preview1-34694 - 2.2.0-preview1-34694 - 2.2.0-preview1-34694 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 2.0.9 2.1.2 2.2.0-preview1-26618-02 From 05b3040f85b78361b63c05ad48bb105b4f3049da Mon Sep 17 00:00:00 2001 From: "ASP.NET CI" Date: Sun, 29 Jul 2018 12:09:56 -0700 Subject: [PATCH 29/66] Update dependencies.props [auto-updated: dependencies] --- build/dependencies.props | 15 ++++++++------- korebuild-lock.txt | 4 ++-- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index 607267c52c..0f57ae33b0 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -3,12 +3,12 @@ $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - 2.2.0-preview1-17099 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 + 2.2.0-preview1-17102 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 2.0.9 2.1.2 2.2.0-preview1-26618-02 @@ -18,7 +18,8 @@ 4.5.0 9.0.1 2.3.1 - 2.4.0-rc.1.build4038 + 2.4.0 + diff --git a/korebuild-lock.txt b/korebuild-lock.txt index 8b9d17825f..28cd6a5b03 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.2.0-preview1-17099 -commithash:263ed1db9866b6b419b1f5d5189a712aa218acb3 +version:2.2.0-preview1-17102 +commithash:e7e2b5a97ca92cfc6acc4def534cb0901a6d1eb9 From 4baed363e5c96c3774627cc6eb01740c289eeff1 Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Mon, 30 Jul 2018 15:54:16 -0700 Subject: [PATCH 30/66] Close #467 - fix race conditions in tests that restart a watchable app * Ensure the test app is actually watching for file changes before attempting to trigger a restart. * Add retry if the first attempt times out --- .../DotNetWatcherTests.cs | 18 ++++++++++++++---- .../NoDepsAppTests.cs | 15 ++++++++++++--- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/test/dotnet-watch.FunctionalTests/DotNetWatcherTests.cs b/test/dotnet-watch.FunctionalTests/DotNetWatcherTests.cs index cc0a1c3fc0..65ff6416e2 100644 --- a/test/dotnet-watch.FunctionalTests/DotNetWatcherTests.cs +++ b/test/dotnet-watch.FunctionalTests/DotNetWatcherTests.cs @@ -39,15 +39,25 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests var source = Path.Combine(_app.SourceDirectory, "Program.cs"); var contents = File.ReadAllText(source); const string messagePrefix = "DOTNET_WATCH_ITERATION = "; - for (var i = 1; i <= 4; i++) + for (var i = 1; i <= 3; i++) { var message = await _app.Process.GetOutputLineStartsWithAsync(messagePrefix, TimeSpan.FromMinutes(2)); var count = int.Parse(message.Substring(messagePrefix.Length), CultureInfo.InvariantCulture); Assert.Equal(i, count); - File.Delete(source); - File.WriteAllText(source, contents); - await _app.HasRestarted(); + await _app.IsWaitingForFileChange(); + + try + { + File.SetLastWriteTime(source, DateTime.Now); + await _app.HasRestarted(); + } + catch + { + // retry + File.SetLastWriteTime(source, DateTime.Now); + await _app.HasRestarted(); + } } } diff --git a/test/dotnet-watch.FunctionalTests/NoDepsAppTests.cs b/test/dotnet-watch.FunctionalTests/NoDepsAppTests.cs index 22f46cbbe0..55d0441552 100644 --- a/test/dotnet-watch.FunctionalTests/NoDepsAppTests.cs +++ b/test/dotnet-watch.FunctionalTests/NoDepsAppTests.cs @@ -53,10 +53,19 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests await _app.IsWaitingForFileChange(); var fileToChange = Path.Combine(_app.SourceDirectory, "Program.cs"); - var programCs = File.ReadAllText(fileToChange); - File.WriteAllText(fileToChange, programCs); - await _app.HasRestarted(); + try + { + File.SetLastWriteTime(fileToChange, DateTime.Now); + await _app.HasRestarted(); + } + catch + { + // retry + File.SetLastWriteTime(fileToChange, DateTime.Now); + await _app.HasRestarted(); + } + var pid2 = await _app.GetProcessId(); Assert.NotEqual(pid, pid2); await _app.HasExited(); // process should exit after run From 89ab0cfde80b1da1e87f563220fcb9bed232d2f3 Mon Sep 17 00:00:00 2001 From: Mike Lorbetske Date: Sun, 29 Jul 2018 12:38:02 -0700 Subject: [PATCH 31/66] Initial commit of the HTTP REPL --- DotNetTools.sln | 44 ++ NuGetPackageVerifier.json | 21 + build/dependencies.props | 2 + .../AggregateDirectoryStructure.cs | 38 ++ .../Commands/BaseHttpCommand.cs | 574 ++++++++++++++++++ .../Commands/ChangeDirectoryCommand.cs | 78 +++ .../Commands/ClearCommand.cs | 62 ++ .../Commands/ConfigCommand.cs | 67 ++ .../Commands/DeleteCommand.cs | 9 + .../Commands/EchoCommand.cs | 55 ++ .../Commands/ExitCommand.cs | 29 + src/Microsoft.HttpRepl/Commands/Formatter.cs | 33 + src/Microsoft.HttpRepl/Commands/GetCommand.cs | 9 + .../Commands/HeadCommand.cs | 9 + .../Commands/HelpCommand.cs | 172 ++++++ .../Commands/ListCommand.cs | 160 +++++ .../Commands/OptionsCommand.cs | 9 + .../Commands/PatchCommand.cs | 9 + .../Commands/PostCommand.cs | 9 + .../Commands/PrefCommand.cs | 172 ++++++ src/Microsoft.HttpRepl/Commands/PutCommand.cs | 9 + src/Microsoft.HttpRepl/Commands/RunCommand.cs | 80 +++ .../Commands/SetBaseCommand.cs | 100 +++ .../Commands/SetDiagCommand.cs | 138 +++++ .../Commands/SetHeaderCommand.cs | 92 +++ .../Commands/SetSwaggerCommand.cs | 272 +++++++++ src/Microsoft.HttpRepl/Commands/TreeNode.cs | 47 ++ src/Microsoft.HttpRepl/Commands/UICommand.cs | 78 +++ .../Diagnostics/ConfigItem.cs | 9 + .../Diagnostics/DiagEndpoint.cs | 9 + .../Diagnostics/DiagEndpointMetadata.cs | 9 + .../Diagnostics/DiagItem.cs | 11 + .../Diagnostics/DiagnosticsState.cs | 13 + src/Microsoft.HttpRepl/DirectoryStructure.cs | 105 ++++ .../DirectoryStructureExtensions.cs | 57 ++ .../Formatting/JsonVisitor.cs | 111 ++++ src/Microsoft.HttpRepl/HttpState.cs | 370 +++++++++++ src/Microsoft.HttpRepl/IDirectoryStructure.cs | 24 + .../Microsoft.HttpRepl.csproj | 31 + src/Microsoft.HttpRepl/OpenApi/Either.cs | 33 + .../OpenApi/EitherConverter.cs | 32 + .../OpenApi/EndpointMetadata.cs | 17 + .../OpenApi/EndpointMetadataReader.cs | 38 ++ .../OpenApi/IEndpointMetadataReader.cs | 12 + .../OpenApiV3EndpointMetadataReader.cs | 98 +++ src/Microsoft.HttpRepl/OpenApi/Parameter.cs | 13 + src/Microsoft.HttpRepl/OpenApi/PointerUtil.cs | 218 +++++++ src/Microsoft.HttpRepl/OpenApi/Schema.cs | 125 ++++ .../SwaggerV1EndpointMetadataReader.cs | 106 ++++ .../SwaggerV2EndpointMetadataReader.cs | 87 +++ .../Preferences/IJsonConfig.cs | 29 + .../Preferences/JsonConfig.cs | 42 ++ .../Preferences/RequestConfig.cs | 46 ++ .../Preferences/RequestOrResponseConfig.cs | 44 ++ .../Preferences/ResponseConfig.cs | 42 ++ .../Preferences/WellKnownPreference.cs | 200 ++++++ src/Microsoft.HttpRepl/Program.cs | 52 ++ .../Properties/launchSettings.json | 8 + .../Suggestions/HeaderCompletion.cs | 97 +++ .../Suggestions/ServerPathCompletion.cs | 54 ++ .../Commanding/CommandHistory.cs | 75 +++ .../Commanding/CommandInputLocation.cs | 10 + .../Commanding/CommandInputProcessingIssue.cs | 15 + .../CommandInputProcessingIssueKind.cs | 11 + .../Commanding/CommandInputSpecification.cs | 44 ++ .../CommandInputSpecificationBuilder.cs | 66 ++ .../Commanding/CommandOptionSpecification.cs | 29 + .../CommandWithStructuredInputBase.cs | 193 ++++++ .../Commanding/DefaultCommandDispatcher.cs | 169 ++++++ .../Commanding/DefaultCommandInput.cs | 193 ++++++ src/Microsoft.Repl/Commanding/ICommand.cs | 21 + .../Commanding/ICommandDispatcher.cs | 24 + .../Commanding/ICommandHistory.cs | 15 + src/Microsoft.Repl/Commanding/InputElement.cs | 29 + .../ConsoleHandling/AllowedColors.cs | 27 + .../ConsoleHandling/AnsiColorExtensions.cs | 80 +++ .../ConsoleHandling/AnsiConsole.cs | 151 +++++ .../ConsoleHandling/ConsoleManager.cs | 204 +++++++ .../ConsoleHandling/IConsoleManager.cs | 28 + .../ConsoleHandling/IWritable.cs | 15 + src/Microsoft.Repl/ConsoleHandling/Point.cs | 50 ++ .../ConsoleHandling/Reporter.cs | 115 ++++ .../ConsoleHandling/Writable.cs | 54 ++ src/Microsoft.Repl/Disposable.cs | 42 ++ src/Microsoft.Repl/IShellState.cs | 22 + .../Input/AsyncKeyPressHandler.cs | 8 + src/Microsoft.Repl/Input/IInputManager.cs | 27 + src/Microsoft.Repl/Input/InputManager.cs | 344 +++++++++++ src/Microsoft.Repl/Input/KeyHandlers.cs | 239 ++++++++ src/Microsoft.Repl/Microsoft.Repl.csproj | 9 + src/Microsoft.Repl/Parsing/CoreParseResult.cs | 78 +++ src/Microsoft.Repl/Parsing/CoreParser.cs | 129 ++++ .../Parsing/ICoreParseResult.cs | 23 + src/Microsoft.Repl/Parsing/IParser.cs | 12 + .../Scripting/IScriptExecutor.cs | 11 + .../Scripting/ScriptExecutor.cs | 49 ++ src/Microsoft.Repl/Shell.cs | 30 + src/Microsoft.Repl/ShellState.cs | 31 + .../Suggestions/FileSystemCompletion.cs | 52 ++ .../Suggestions/ISuggestionManager.cs | 9 + .../Suggestions/SuggestionManager.cs | 95 +++ src/Microsoft.Repl/Utils.cs | 12 + .../JsonVisitorTests.cs | 60 ++ .../Microsoft.HttpRepl.Tests.csproj | 21 + .../Microsoft.Repl.Tests.csproj | 19 + test/Microsoft.Repl.Tests/ParserTests.cs | 26 + 106 files changed, 7485 insertions(+) create mode 100644 src/Microsoft.HttpRepl/AggregateDirectoryStructure.cs create mode 100644 src/Microsoft.HttpRepl/Commands/BaseHttpCommand.cs create mode 100644 src/Microsoft.HttpRepl/Commands/ChangeDirectoryCommand.cs create mode 100644 src/Microsoft.HttpRepl/Commands/ClearCommand.cs create mode 100644 src/Microsoft.HttpRepl/Commands/ConfigCommand.cs create mode 100644 src/Microsoft.HttpRepl/Commands/DeleteCommand.cs create mode 100644 src/Microsoft.HttpRepl/Commands/EchoCommand.cs create mode 100644 src/Microsoft.HttpRepl/Commands/ExitCommand.cs create mode 100644 src/Microsoft.HttpRepl/Commands/Formatter.cs create mode 100644 src/Microsoft.HttpRepl/Commands/GetCommand.cs create mode 100644 src/Microsoft.HttpRepl/Commands/HeadCommand.cs create mode 100644 src/Microsoft.HttpRepl/Commands/HelpCommand.cs create mode 100644 src/Microsoft.HttpRepl/Commands/ListCommand.cs create mode 100644 src/Microsoft.HttpRepl/Commands/OptionsCommand.cs create mode 100644 src/Microsoft.HttpRepl/Commands/PatchCommand.cs create mode 100644 src/Microsoft.HttpRepl/Commands/PostCommand.cs create mode 100644 src/Microsoft.HttpRepl/Commands/PrefCommand.cs create mode 100644 src/Microsoft.HttpRepl/Commands/PutCommand.cs create mode 100644 src/Microsoft.HttpRepl/Commands/RunCommand.cs create mode 100644 src/Microsoft.HttpRepl/Commands/SetBaseCommand.cs create mode 100644 src/Microsoft.HttpRepl/Commands/SetDiagCommand.cs create mode 100644 src/Microsoft.HttpRepl/Commands/SetHeaderCommand.cs create mode 100644 src/Microsoft.HttpRepl/Commands/SetSwaggerCommand.cs create mode 100644 src/Microsoft.HttpRepl/Commands/TreeNode.cs create mode 100644 src/Microsoft.HttpRepl/Commands/UICommand.cs create mode 100644 src/Microsoft.HttpRepl/Diagnostics/ConfigItem.cs create mode 100644 src/Microsoft.HttpRepl/Diagnostics/DiagEndpoint.cs create mode 100644 src/Microsoft.HttpRepl/Diagnostics/DiagEndpointMetadata.cs create mode 100644 src/Microsoft.HttpRepl/Diagnostics/DiagItem.cs create mode 100644 src/Microsoft.HttpRepl/Diagnostics/DiagnosticsState.cs create mode 100644 src/Microsoft.HttpRepl/DirectoryStructure.cs create mode 100644 src/Microsoft.HttpRepl/DirectoryStructureExtensions.cs create mode 100644 src/Microsoft.HttpRepl/Formatting/JsonVisitor.cs create mode 100644 src/Microsoft.HttpRepl/HttpState.cs create mode 100644 src/Microsoft.HttpRepl/IDirectoryStructure.cs create mode 100644 src/Microsoft.HttpRepl/Microsoft.HttpRepl.csproj create mode 100644 src/Microsoft.HttpRepl/OpenApi/Either.cs create mode 100644 src/Microsoft.HttpRepl/OpenApi/EitherConverter.cs create mode 100644 src/Microsoft.HttpRepl/OpenApi/EndpointMetadata.cs create mode 100644 src/Microsoft.HttpRepl/OpenApi/EndpointMetadataReader.cs create mode 100644 src/Microsoft.HttpRepl/OpenApi/IEndpointMetadataReader.cs create mode 100644 src/Microsoft.HttpRepl/OpenApi/OpenApiV3EndpointMetadataReader.cs create mode 100644 src/Microsoft.HttpRepl/OpenApi/Parameter.cs create mode 100644 src/Microsoft.HttpRepl/OpenApi/PointerUtil.cs create mode 100644 src/Microsoft.HttpRepl/OpenApi/Schema.cs create mode 100644 src/Microsoft.HttpRepl/OpenApi/SwaggerV1EndpointMetadataReader.cs create mode 100644 src/Microsoft.HttpRepl/OpenApi/SwaggerV2EndpointMetadataReader.cs create mode 100644 src/Microsoft.HttpRepl/Preferences/IJsonConfig.cs create mode 100644 src/Microsoft.HttpRepl/Preferences/JsonConfig.cs create mode 100644 src/Microsoft.HttpRepl/Preferences/RequestConfig.cs create mode 100644 src/Microsoft.HttpRepl/Preferences/RequestOrResponseConfig.cs create mode 100644 src/Microsoft.HttpRepl/Preferences/ResponseConfig.cs create mode 100644 src/Microsoft.HttpRepl/Preferences/WellKnownPreference.cs create mode 100644 src/Microsoft.HttpRepl/Program.cs create mode 100644 src/Microsoft.HttpRepl/Properties/launchSettings.json create mode 100644 src/Microsoft.HttpRepl/Suggestions/HeaderCompletion.cs create mode 100644 src/Microsoft.HttpRepl/Suggestions/ServerPathCompletion.cs create mode 100644 src/Microsoft.Repl/Commanding/CommandHistory.cs create mode 100644 src/Microsoft.Repl/Commanding/CommandInputLocation.cs create mode 100644 src/Microsoft.Repl/Commanding/CommandInputProcessingIssue.cs create mode 100644 src/Microsoft.Repl/Commanding/CommandInputProcessingIssueKind.cs create mode 100644 src/Microsoft.Repl/Commanding/CommandInputSpecification.cs create mode 100644 src/Microsoft.Repl/Commanding/CommandInputSpecificationBuilder.cs create mode 100644 src/Microsoft.Repl/Commanding/CommandOptionSpecification.cs create mode 100644 src/Microsoft.Repl/Commanding/CommandWithStructuredInputBase.cs create mode 100644 src/Microsoft.Repl/Commanding/DefaultCommandDispatcher.cs create mode 100644 src/Microsoft.Repl/Commanding/DefaultCommandInput.cs create mode 100644 src/Microsoft.Repl/Commanding/ICommand.cs create mode 100644 src/Microsoft.Repl/Commanding/ICommandDispatcher.cs create mode 100644 src/Microsoft.Repl/Commanding/ICommandHistory.cs create mode 100644 src/Microsoft.Repl/Commanding/InputElement.cs create mode 100644 src/Microsoft.Repl/ConsoleHandling/AllowedColors.cs create mode 100644 src/Microsoft.Repl/ConsoleHandling/AnsiColorExtensions.cs create mode 100644 src/Microsoft.Repl/ConsoleHandling/AnsiConsole.cs create mode 100644 src/Microsoft.Repl/ConsoleHandling/ConsoleManager.cs create mode 100644 src/Microsoft.Repl/ConsoleHandling/IConsoleManager.cs create mode 100644 src/Microsoft.Repl/ConsoleHandling/IWritable.cs create mode 100644 src/Microsoft.Repl/ConsoleHandling/Point.cs create mode 100644 src/Microsoft.Repl/ConsoleHandling/Reporter.cs create mode 100644 src/Microsoft.Repl/ConsoleHandling/Writable.cs create mode 100644 src/Microsoft.Repl/Disposable.cs create mode 100644 src/Microsoft.Repl/IShellState.cs create mode 100644 src/Microsoft.Repl/Input/AsyncKeyPressHandler.cs create mode 100644 src/Microsoft.Repl/Input/IInputManager.cs create mode 100644 src/Microsoft.Repl/Input/InputManager.cs create mode 100644 src/Microsoft.Repl/Input/KeyHandlers.cs create mode 100644 src/Microsoft.Repl/Microsoft.Repl.csproj create mode 100644 src/Microsoft.Repl/Parsing/CoreParseResult.cs create mode 100644 src/Microsoft.Repl/Parsing/CoreParser.cs create mode 100644 src/Microsoft.Repl/Parsing/ICoreParseResult.cs create mode 100644 src/Microsoft.Repl/Parsing/IParser.cs create mode 100644 src/Microsoft.Repl/Scripting/IScriptExecutor.cs create mode 100644 src/Microsoft.Repl/Scripting/ScriptExecutor.cs create mode 100644 src/Microsoft.Repl/Shell.cs create mode 100644 src/Microsoft.Repl/ShellState.cs create mode 100644 src/Microsoft.Repl/Suggestions/FileSystemCompletion.cs create mode 100644 src/Microsoft.Repl/Suggestions/ISuggestionManager.cs create mode 100644 src/Microsoft.Repl/Suggestions/SuggestionManager.cs create mode 100644 src/Microsoft.Repl/Utils.cs create mode 100644 test/Microsoft.HttpRepl.Tests/JsonVisitorTests.cs create mode 100644 test/Microsoft.HttpRepl.Tests/Microsoft.HttpRepl.Tests.csproj create mode 100644 test/Microsoft.Repl.Tests/Microsoft.Repl.Tests.csproj create mode 100644 test/Microsoft.Repl.Tests/ParserTests.cs diff --git a/DotNetTools.sln b/DotNetTools.sln index 82fefe40d0..d996c043b6 100644 --- a/DotNetTools.sln +++ b/DotNetTools.sln @@ -56,6 +56,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.VisualStudio.Secr EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.DeveloperCertificates.XPlat", "src\Microsoft.AspNetCore.DeveloperCertificates.XPlat\Microsoft.AspNetCore.DeveloperCertificates.XPlat.csproj", "{96E71881-1465-44F5-B4B7-DF9B370FFD02}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.HttpRepl", "src\Microsoft.HttpRepl\Microsoft.HttpRepl.csproj", "{4725BEAD-34F0-43C1-BF46-7AB16B4DE81D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Repl", "src\Microsoft.Repl\Microsoft.Repl.csproj", "{EE9A6128-3DE2-4206-A5A4-3ED935084590}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Repl.Tests", "test\Microsoft.Repl.Tests\Microsoft.Repl.Tests.csproj", "{59C2B354-3B5E-40EB-A7BC-74583A5707CA}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.HttpRepl.Tests", "test\Microsoft.HttpRepl.Tests\Microsoft.HttpRepl.Tests.csproj", "{BE7CC4CD-CD76-4211-B593-CAC84407162A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -140,6 +148,38 @@ Global {96E71881-1465-44F5-B4B7-DF9B370FFD02}.Release|Any CPU.Build.0 = Release|Any CPU {96E71881-1465-44F5-B4B7-DF9B370FFD02}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU {96E71881-1465-44F5-B4B7-DF9B370FFD02}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU + {4725BEAD-34F0-43C1-BF46-7AB16B4DE81D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4725BEAD-34F0-43C1-BF46-7AB16B4DE81D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4725BEAD-34F0-43C1-BF46-7AB16B4DE81D}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU + {4725BEAD-34F0-43C1-BF46-7AB16B4DE81D}.DebugNoVSIX|Any CPU.Build.0 = Debug|Any CPU + {4725BEAD-34F0-43C1-BF46-7AB16B4DE81D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4725BEAD-34F0-43C1-BF46-7AB16B4DE81D}.Release|Any CPU.Build.0 = Release|Any CPU + {4725BEAD-34F0-43C1-BF46-7AB16B4DE81D}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU + {4725BEAD-34F0-43C1-BF46-7AB16B4DE81D}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU + {EE9A6128-3DE2-4206-A5A4-3ED935084590}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EE9A6128-3DE2-4206-A5A4-3ED935084590}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EE9A6128-3DE2-4206-A5A4-3ED935084590}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU + {EE9A6128-3DE2-4206-A5A4-3ED935084590}.DebugNoVSIX|Any CPU.Build.0 = Debug|Any CPU + {EE9A6128-3DE2-4206-A5A4-3ED935084590}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EE9A6128-3DE2-4206-A5A4-3ED935084590}.Release|Any CPU.Build.0 = Release|Any CPU + {EE9A6128-3DE2-4206-A5A4-3ED935084590}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU + {EE9A6128-3DE2-4206-A5A4-3ED935084590}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU + {59C2B354-3B5E-40EB-A7BC-74583A5707CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {59C2B354-3B5E-40EB-A7BC-74583A5707CA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {59C2B354-3B5E-40EB-A7BC-74583A5707CA}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU + {59C2B354-3B5E-40EB-A7BC-74583A5707CA}.DebugNoVSIX|Any CPU.Build.0 = Debug|Any CPU + {59C2B354-3B5E-40EB-A7BC-74583A5707CA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {59C2B354-3B5E-40EB-A7BC-74583A5707CA}.Release|Any CPU.Build.0 = Release|Any CPU + {59C2B354-3B5E-40EB-A7BC-74583A5707CA}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU + {59C2B354-3B5E-40EB-A7BC-74583A5707CA}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU + {BE7CC4CD-CD76-4211-B593-CAC84407162A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BE7CC4CD-CD76-4211-B593-CAC84407162A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BE7CC4CD-CD76-4211-B593-CAC84407162A}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU + {BE7CC4CD-CD76-4211-B593-CAC84407162A}.DebugNoVSIX|Any CPU.Build.0 = Debug|Any CPU + {BE7CC4CD-CD76-4211-B593-CAC84407162A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BE7CC4CD-CD76-4211-B593-CAC84407162A}.Release|Any CPU.Build.0 = Release|Any CPU + {BE7CC4CD-CD76-4211-B593-CAC84407162A}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU + {BE7CC4CD-CD76-4211-B593-CAC84407162A}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -155,6 +195,10 @@ Global {5E117F2E-7152-447F-BF47-59F759EEF3A7} = {62826851-7D74-4F1E-B7D1-12553B789CD8} {965F8820-F809-4081-9090-1AEC903F291B} = {62826851-7D74-4F1E-B7D1-12553B789CD8} {96E71881-1465-44F5-B4B7-DF9B370FFD02} = {66517987-2A5A-4330-B130-207039378FD4} + {4725BEAD-34F0-43C1-BF46-7AB16B4DE81D} = {66517987-2A5A-4330-B130-207039378FD4} + {EE9A6128-3DE2-4206-A5A4-3ED935084590} = {66517987-2A5A-4330-B130-207039378FD4} + {59C2B354-3B5E-40EB-A7BC-74583A5707CA} = {F5B382BC-258F-46E1-AC3D-10E5CCD55134} + {BE7CC4CD-CD76-4211-B593-CAC84407162A} = {F5B382BC-258F-46E1-AC3D-10E5CCD55134} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {57C07F14-2EAC-44FF-A277-B9221B4B2BF7} diff --git a/NuGetPackageVerifier.json b/NuGetPackageVerifier.json index fb43448889..4167e36811 100644 --- a/NuGetPackageVerifier.json +++ b/NuGetPackageVerifier.json @@ -24,12 +24,33 @@ "DotnetTool" ] }, + "dotnet-httprepl": { + "packageTypes": [ + "DotnetTool" + ], + "Exclusions": { + "ASSEMBLY_DESCRIPTION": { + "tools/netcoreapp2.2/any/System.Net.Http.Formatting.dll": "Referenced assembly, not built as part of this process" + }, + "VERSION_INFORMATIONALVERSION": { + "tools/netcoreapp2.2/any/Newtonsoft.Json.dll": "Referenced assembly, not built as part of this process", + "tools/netcoreapp2.2/any/Newtonsoft.Json.Bson.dll": "Referenced assembly, not built as part of this process" + } + } + }, "Microsoft.AspNetCore.DeveloperCertificates.XPlat": { "Exclusions": { "DOC_MISSING": { "lib/netcoreapp2.2/Microsoft.AspNetCore.DeveloperCertificates.XPlat.dll": "Docs not required to shipoob package" } } + }, + "Microsoft.Repl": { + "Exclusions": { + "DOC_MISSING": { + "lib/netcoreapp2.2/Microsoft.Repl.dll": "Docs not required to shipoob package" + } + } } } }, diff --git a/build/dependencies.props b/build/dependencies.props index 0f57ae33b0..0c7d2bca9d 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -13,9 +13,11 @@ 2.1.2 2.2.0-preview1-26618-02 15.6.1 + 5.2.6 2.0.3 4.5.1 4.5.0 + 10.0.1 9.0.1 2.3.1 2.4.0 diff --git a/src/Microsoft.HttpRepl/AggregateDirectoryStructure.cs b/src/Microsoft.HttpRepl/AggregateDirectoryStructure.cs new file mode 100644 index 0000000000..ed763a3e8f --- /dev/null +++ b/src/Microsoft.HttpRepl/AggregateDirectoryStructure.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.HttpRepl +{ + public class AggregateDirectoryStructure : IDirectoryStructure + { + private readonly IDirectoryStructure _first; + private readonly IDirectoryStructure _second; + + public AggregateDirectoryStructure(IDirectoryStructure first, IDirectoryStructure second) + { + _first = first; + _second = second; + } + + public IEnumerable DirectoryNames + { + get + { + HashSet values = new HashSet(StringComparer.OrdinalIgnoreCase); + values.UnionWith(_first.DirectoryNames); + values.UnionWith(_second.DirectoryNames); + return values.OrderBy(x => x, StringComparer.OrdinalIgnoreCase); + } + } + + public IDirectoryStructure Parent => _first.Parent ?? _second.Parent; + + public IDirectoryStructure GetChildDirectory(string name) + { + return new AggregateDirectoryStructure(_first.GetChildDirectory(name), _second.GetChildDirectory(name)); + } + + public IRequestInfo RequestInfo => _first.RequestInfo ?? _second.RequestInfo; + } +} diff --git a/src/Microsoft.HttpRepl/Commands/BaseHttpCommand.cs b/src/Microsoft.HttpRepl/Commands/BaseHttpCommand.cs new file mode 100644 index 0000000000..3db2f39b7b --- /dev/null +++ b/src/Microsoft.HttpRepl/Commands/BaseHttpCommand.cs @@ -0,0 +1,574 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Xml.Linq; +using Microsoft.HttpRepl.Formatting; +using Microsoft.HttpRepl.Preferences; +using Microsoft.HttpRepl.Suggestions; +using Microsoft.Repl; +using Microsoft.Repl.Commanding; +using Microsoft.Repl.ConsoleHandling; +using Microsoft.Repl.Parsing; +using Microsoft.Repl.Suggestions; +using Newtonsoft.Json.Linq; + +namespace Microsoft.HttpRepl.Commands +{ + public abstract class BaseHttpCommand : CommandWithStructuredInputBase + { + private const string HeaderOption = nameof(HeaderOption); + private const string ResponseHeadersFileOption = nameof(ResponseHeadersFileOption); + private const string ResponseBodyFileOption = nameof(ResponseBodyFileOption); + private const string ResponseFileOption = nameof(ResponseFileOption); + private const string BodyFileOption = nameof(BodyFileOption); + private const string NoBodyOption = nameof(NoBodyOption); + private const string BodyContentOption = nameof(BodyContentOption); + private static readonly char[] HeaderSeparatorChars = new[] { '=', ':' }; + + private CommandInputSpecification _inputSpec; + + protected abstract string Verb { get; } + + protected abstract bool RequiresBody { get; } + + protected override CommandInputSpecification InputSpec + { + get + { + if (_inputSpec != null) + { + return _inputSpec; + } + + CommandInputSpecificationBuilder builder = CommandInputSpecification.Create(Verb) + .MaximumArgCount(1) + .WithOption(new CommandOptionSpecification(HeaderOption, requiresValue: true, forms: new[] {"--header", "-h"})) + .WithOption(new CommandOptionSpecification(ResponseFileOption, requiresValue: true, maximumOccurrences: 1, forms: new[] { "--response", })) + .WithOption(new CommandOptionSpecification(ResponseHeadersFileOption, requiresValue: true, maximumOccurrences: 1, forms: new[] { "--response:headers", })) + .WithOption(new CommandOptionSpecification(ResponseBodyFileOption, requiresValue: true, maximumOccurrences: 1, forms: new[] { "--response:body", })); + + if (RequiresBody) + { + builder = builder.WithOption(new CommandOptionSpecification(NoBodyOption, maximumOccurrences: 1, forms: "--no-body")) + .WithOption(new CommandOptionSpecification(BodyFileOption, requiresValue: true, maximumOccurrences: 1, forms: new[] {"--file", "-f"})) + .WithOption(new CommandOptionSpecification(BodyContentOption, requiresValue: true, maximumOccurrences: 1, forms: new[] {"--content", "-c"})); + } + + _inputSpec = builder.Finish(); + return _inputSpec; + } + } + + protected override async Task ExecuteAsync(IShellState shellState, HttpState programState, DefaultCommandInput commandInput, ICoreParseResult parseResult, CancellationToken cancellationToken) + { + if (programState.BaseAddress == null) + { + shellState.ConsoleManager.Error.WriteLine("'set base {url}' must be called before issuing requests".Bold().Red()); + return; + } + + Dictionary thisRequestHeaders = new Dictionary(); + + foreach (InputElement header in commandInput.Options[HeaderOption]) + { + int equalsIndex = header.Text.IndexOfAny(HeaderSeparatorChars); + + if (equalsIndex < 0) + { + shellState.ConsoleManager.Error.WriteLine("Headers must be formatted as {header}={value} or {header}:{value}".Bold().Red()); + return; + } + + thisRequestHeaders[header.Text.Substring(0, equalsIndex)] = header.Text.Substring(equalsIndex + 1); + } + + Uri effectivePath = programState.GetEffectivePath(commandInput.Arguments.Count > 0 ? commandInput.Arguments[0].Text : string.Empty); + HttpRequestMessage request = new HttpRequestMessage(new HttpMethod(Verb.ToUpperInvariant()), effectivePath); + bool noBody = false; + + if (RequiresBody) + { + string filePath = null; + string bodyContent = null; + bool deleteFile = false; + noBody = commandInput.Options[NoBodyOption].Count > 0; + + if (!noBody) + { + if (commandInput.Options[BodyFileOption].Count > 0) + { + filePath = commandInput.Options[BodyFileOption][0].Text; + + if (!File.Exists(filePath)) + { + shellState.ConsoleManager.Error.WriteLine($"Content file {filePath} does not exist".Bold().Red()); + return; + } + } + else if (commandInput.Options[BodyContentOption].Count > 0) + { + bodyContent = commandInput.Options[BodyContentOption][0].Text; + } + else + { + string defaultEditorCommand = programState.GetStringPreference(WellKnownPreference.DefaultEditorCommand); + if (defaultEditorCommand == null) + { + shellState.ConsoleManager.Error.WriteLine($"The default editor must be configured using the command `pref set {WellKnownPreference.DefaultEditorCommand} \"{{commandline}}\"`".Bold().Red()); + return; + } + + deleteFile = true; + filePath = Path.GetTempFileName(); + + if (!thisRequestHeaders.TryGetValue("content-type", out string contentType) && programState.Headers.TryGetValue("content-type", out IEnumerable contentTypes)) + { + contentType = contentTypes.FirstOrDefault(); + } + + if (contentType == null) + { + contentType = "application/json"; + } + + string exampleBody = programState.GetExampleBody(commandInput.Arguments.Count > 0 ? commandInput.Arguments[0].Text : string.Empty, contentType, Verb); + + if (!string.IsNullOrEmpty(exampleBody)) + { + File.WriteAllText(filePath, exampleBody); + } + + string defaultEditorArguments = programState.GetStringPreference(WellKnownPreference.DefaultEditorArguments) ?? ""; + string original = defaultEditorArguments; + string pathString = $"\"{filePath}\""; + + defaultEditorArguments = defaultEditorArguments.Replace("{filename}", pathString); + + if (string.Equals(defaultEditorArguments, original, StringComparison.Ordinal)) + { + defaultEditorArguments = (defaultEditorArguments + " " + pathString).Trim(); + } + + ProcessStartInfo info = new ProcessStartInfo(defaultEditorCommand, defaultEditorArguments); + + Process.Start(info)?.WaitForExit(); + } + } + + byte[] data = noBody + ? new byte[0] + : string.IsNullOrEmpty(bodyContent) + ? File.ReadAllBytes(filePath) + : Encoding.UTF8.GetBytes(bodyContent); + + HttpContent content = new ByteArrayContent(data); + request.Content = content; + + if (deleteFile) + { + File.Delete(filePath); + } + + foreach (KeyValuePair> header in programState.Headers) + { + content.Headers.TryAddWithoutValidation(header.Key, header.Value); + } + + foreach (KeyValuePair header in thisRequestHeaders) + { + content.Headers.TryAddWithoutValidation(header.Key, header.Value); + } + } + + foreach (KeyValuePair> header in programState.Headers) + { + request.Headers.TryAddWithoutValidation(header.Key, header.Value); + } + + foreach (KeyValuePair header in thisRequestHeaders) + { + request.Headers.TryAddWithoutValidation(header.Key, header.Value); + } + + string headersTarget = commandInput.Options[ResponseHeadersFileOption].FirstOrDefault()?.Text ?? commandInput.Options[ResponseFileOption].FirstOrDefault()?.Text; + string bodyTarget = commandInput.Options[ResponseBodyFileOption].FirstOrDefault()?.Text ?? commandInput.Options[ResponseFileOption].FirstOrDefault()?.Text; + + HttpResponseMessage response = await programState.Client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + await HandleResponseAsync(programState, shellState.ConsoleManager, response, programState.EchoRequest, headersTarget, bodyTarget, cancellationToken).ConfigureAwait(false); + } + + private static async Task HandleResponseAsync(HttpState programState, IConsoleManager consoleManager, HttpResponseMessage response, bool echoRequest, string headersTargetFile, string bodyTargetFile, CancellationToken cancellationToken) + { + RequestConfig requestConfig = new RequestConfig(programState); + ResponseConfig responseConfig = new ResponseConfig(programState); + string protocolInfo; + + if (echoRequest) + { + string hostString = response.RequestMessage.RequestUri.Scheme + "://" + response.RequestMessage.RequestUri.Host + (!response.RequestMessage.RequestUri.IsDefaultPort ? ":" + response.RequestMessage.RequestUri.Port : ""); + consoleManager.WriteLine($"Request to {hostString}...".SetColor(requestConfig.AddressColor)); + consoleManager.WriteLine(); + + string method = response.RequestMessage.Method.ToString().ToUpperInvariant().SetColor(requestConfig.MethodColor); + string pathAndQuery = response.RequestMessage.RequestUri.PathAndQuery.SetColor(requestConfig.AddressColor); + protocolInfo = $"{"HTTP".SetColor(requestConfig.ProtocolNameColor)}{"/".SetColor(requestConfig.ProtocolSeparatorColor)}{response.RequestMessage.Version.ToString().SetColor(requestConfig.ProtocolVersionColor)}"; + + consoleManager.WriteLine($"{method} {pathAndQuery} {protocolInfo}"); + IEnumerable>> requestHeaders = response.RequestMessage.Headers; + + if (response.RequestMessage.Content != null) + { + requestHeaders = requestHeaders.Union(response.RequestMessage.Content.Headers); + } + + foreach (KeyValuePair> header in requestHeaders.OrderBy(x => x.Key)) + { + string headerKey = header.Key.SetColor(requestConfig.HeaderKeyColor); + string headerSep = ":".SetColor(requestConfig.HeaderSeparatorColor); + string headerValue = string.Join(";".SetColor(requestConfig.HeaderValueSeparatorColor), header.Value.Select(x => x.Trim().SetColor(requestConfig.HeaderValueColor))); + consoleManager.WriteLine($"{headerKey}{headerSep} {headerValue}"); + } + + consoleManager.WriteLine(); + + if (response.RequestMessage.Content != null) + { + using (StreamWriter writer = new StreamWriter(new MemoryStream())) + { + await FormatBodyAsync(programState, consoleManager, response.RequestMessage.Content, writer, cancellationToken).ConfigureAwait(false); + } + } + + consoleManager.WriteLine(); + consoleManager.WriteLine($"Response from {hostString}...".SetColor(requestConfig.AddressColor)); + consoleManager.WriteLine(); + } + + protocolInfo = $"{"HTTP".SetColor(responseConfig.ProtocolNameColor)}{"/".SetColor(responseConfig.ProtocolSeparatorColor)}{response.Version.ToString().SetColor(responseConfig.ProtocolVersionColor)}"; + string status = ((int)response.StatusCode).ToString().SetColor(responseConfig.StatusCodeColor) + " " + response.ReasonPhrase.SetColor(responseConfig.StatusReasonPhraseColor); + + consoleManager.WriteLine($"{protocolInfo} {status}"); + + IEnumerable>> responseHeaders = response.Headers; + + if (response.Content != null) + { + responseHeaders = responseHeaders.Union(response.Content.Headers); + } + + StreamWriter headerFileWriter; + + if (headersTargetFile != null) + { + headerFileWriter = new StreamWriter(File.Create(headersTargetFile)); + } + else + { + headerFileWriter = new StreamWriter(new MemoryStream()); + } + + foreach (KeyValuePair> header in responseHeaders.OrderBy(x => x.Key)) + { + string headerKey = header.Key.SetColor(responseConfig.HeaderKeyColor); + string headerSep = ":".SetColor(responseConfig.HeaderSeparatorColor); + string headerValue = string.Join(";".SetColor(responseConfig.HeaderValueSeparatorColor), header.Value.Select(x => x.Trim().SetColor(responseConfig.HeaderValueColor))); + consoleManager.WriteLine($"{headerKey}{headerSep} {headerValue}"); + headerFileWriter.WriteLine($"{header.Key}: {string.Join(";", header.Value.Select(x => x.Trim()))}"); + } + + StreamWriter bodyFileWriter; + if (!string.Equals(headersTargetFile, bodyTargetFile, StringComparison.Ordinal)) + { + headerFileWriter.Flush(); + headerFileWriter.Close(); + headerFileWriter.Dispose(); + + if (bodyTargetFile != null) + { + bodyFileWriter = new StreamWriter(File.Create(bodyTargetFile)); + } + else + { + bodyFileWriter = new StreamWriter(new MemoryStream()); + } + } + else + { + headerFileWriter.WriteLine(); + bodyFileWriter = headerFileWriter; + } + + consoleManager.WriteLine(); + + if (response.Content != null) + { + await FormatBodyAsync(programState, consoleManager, response.Content, bodyFileWriter, cancellationToken).ConfigureAwait(false); + } + + bodyFileWriter.Flush(); + bodyFileWriter.Close(); + bodyFileWriter.Dispose(); + + consoleManager.WriteLine(); + } + + private static async Task FormatBodyAsync(HttpState programState, IConsoleManager consoleManager, HttpContent content, StreamWriter bodyFileWriter, CancellationToken cancellationToken) + { + string contentType = null; + if (content.Headers.TryGetValues("Content-Type", out IEnumerable contentTypeValues)) + { + contentType = contentTypeValues.FirstOrDefault()?.Split(';').FirstOrDefault(); + } + + contentType = contentType?.ToUpperInvariant() ?? "text/plain"; + + if (contentType.EndsWith("/JSON", StringComparison.OrdinalIgnoreCase) + || contentType.EndsWith("-JSON", StringComparison.OrdinalIgnoreCase) + || contentType.EndsWith("+JSON", StringComparison.OrdinalIgnoreCase) + || contentType.EndsWith("/JAVASCRIPT", StringComparison.OrdinalIgnoreCase) + || contentType.EndsWith("-JAVASCRIPT", StringComparison.OrdinalIgnoreCase) + || contentType.EndsWith("+JAVASCRIPT", StringComparison.OrdinalIgnoreCase)) + { + if (await FormatJsonAsync(programState, consoleManager, content, bodyFileWriter)) + { + return; + } + } + else if (contentType.EndsWith("/HTML", StringComparison.OrdinalIgnoreCase) + || contentType.EndsWith("-HTML", StringComparison.OrdinalIgnoreCase) + || contentType.EndsWith("+HTML", StringComparison.OrdinalIgnoreCase) + || contentType.EndsWith("/XML", StringComparison.OrdinalIgnoreCase) + || contentType.EndsWith("-XML", StringComparison.OrdinalIgnoreCase) + || contentType.EndsWith("+XML", StringComparison.OrdinalIgnoreCase)) + { + if (await FormatXmlAsync(consoleManager, content, bodyFileWriter)) + { + return; + } + } + + //If we don't have content length, assume streaming + if (!content.Headers.TryGetValues("Content-Length", out IEnumerable _)) + { + Memory buffer = new Memory(new char[2048]); + Stream s = await content.ReadAsStreamAsync().ConfigureAwait(false); + StreamReader reader = new StreamReader(s); + consoleManager.WriteLine("Streaming the response, press any key to stop...".Bold().Yellow()); + + while (!cancellationToken.IsCancellationRequested) + { + try + { + ValueTask readTask = reader.ReadAsync(buffer, cancellationToken); + if (await WaitForCompletionAsync(readTask, cancellationToken).ConfigureAwait(false)) + { + if (readTask.Result == 0) + { + break; + } + + string str = new string(buffer.Span.Slice(0, readTask.Result)); + consoleManager.Write(str); + bodyFileWriter.Write(str); + } + else + { + break; + } + } + catch (OperationCanceledException) + { + } + } + + return; + } + + string responseContent = await content.ReadAsStringAsync().ConfigureAwait(false); + bodyFileWriter.WriteLine(responseContent); + consoleManager.WriteLine(responseContent); + } + + private static async Task WaitForCompletionAsync(ValueTask readTask, CancellationToken cancellationToken) + { + while (!readTask.IsCompleted && !cancellationToken.IsCancellationRequested && !Console.KeyAvailable) + { + await Task.Delay(1, cancellationToken).ConfigureAwait(false); + } + + if (Console.KeyAvailable) + { + Console.ReadKey(false); + return false; + } + + return readTask.IsCompleted; + } + + private static async Task FormatXmlAsync(IWritable consoleManager, HttpContent content, StreamWriter bodyFileWriter) + { + string responseContent = await content.ReadAsStringAsync().ConfigureAwait(false); + try + { + XDocument body = XDocument.Parse(responseContent); + consoleManager.WriteLine(body.ToString()); + bodyFileWriter.WriteLine(body.ToString()); + return true; + } + catch + { + } + + return false; + } + + private static async Task FormatJsonAsync(HttpState programState, IWritable outputSink, HttpContent content, StreamWriter bodyFileWriter) + { + string responseContent = await content.ReadAsStringAsync().ConfigureAwait(false); + + try + { + JsonConfig config = new JsonConfig(programState); + string formatted = JsonVisitor.FormatAndColorize(config, responseContent); + outputSink.WriteLine(formatted); + bodyFileWriter.WriteLine(JToken.Parse(responseContent).ToString()); + return true; + } + catch + { + } + + return false; + } + + protected override string GetHelpDetails(IShellState shellState, HttpState programState, DefaultCommandInput commandInput, ICoreParseResult parseResult) + { + return $"Issues a {Verb.ToUpperInvariant()} request"; + } + + public override string GetHelpSummary(IShellState shellState, HttpState programState) + { + return $"{Verb.ToLowerInvariant()} - Issues a {Verb.ToUpperInvariant()} request"; + } + + protected override IEnumerable GetArgumentSuggestionsForText(IShellState shellState, HttpState programState, ICoreParseResult parseResult, DefaultCommandInput commandInput, string normalCompletionString) + { + List results = new List(); + + if (programState.Structure != null && programState.BaseAddress != null) + { + //If it's an absolute URI, nothing to suggest + if (Uri.TryCreate(parseResult.Sections[1], UriKind.Absolute, out Uri _)) + { + return null; + } + + string path = normalCompletionString.Replace('\\', '/'); + int searchFrom = normalCompletionString.Length - 1; + int lastSlash = path.LastIndexOf('/', searchFrom); + string prefix; + + if (lastSlash < 0) + { + path = string.Empty; + prefix = normalCompletionString; + } + else + { + path = path.Substring(0, lastSlash + 1); + prefix = normalCompletionString.Substring(lastSlash + 1); + } + + IDirectoryStructure s = programState.Structure.TraverseTo(programState.PathSections.Reverse()).TraverseTo(path); + + foreach (string child in s.DirectoryNames) + { + if (child.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) + { + results.Add(path + child); + } + } + } + + return results; + } + + protected override IEnumerable GetOptionValueCompletions(IShellState shellState, HttpState programState, string optionId, DefaultCommandInput commandInput, ICoreParseResult parseResult, string normalizedCompletionText) + { + if (string.Equals(optionId, BodyFileOption, StringComparison.Ordinal) || string.Equals(optionId, ResponseFileOption, StringComparison.OrdinalIgnoreCase) || string.Equals(optionId, ResponseBodyFileOption, StringComparison.OrdinalIgnoreCase) || string.Equals(optionId, ResponseHeadersFileOption, StringComparison.OrdinalIgnoreCase)) + { + return FileSystemCompletion.GetCompletions(normalizedCompletionText); + } + + if (string.Equals(optionId, HeaderOption, StringComparison.Ordinal)) + { + HashSet alreadySpecifiedHeaders = new HashSet(StringComparer.Ordinal); + IReadOnlyList options = commandInput.Options[HeaderOption]; + for (int i = 0; i < options.Count; ++i) + { + if (options[i] == commandInput.SelectedElement) + { + continue; + } + + string elementText = options[i].Text; + string existingHeaderName = elementText.Split(HeaderSeparatorChars)[0]; + alreadySpecifiedHeaders.Add(existingHeaderName); + } + + //Check to see if the selected element is in a header name or value + int equalsIndex = normalizedCompletionText.IndexOfAny(HeaderSeparatorChars); + string path = commandInput.Arguments.Count > 0 ? commandInput.Arguments[0].Text : string.Empty; + + if (equalsIndex < 0) + { + IEnumerable headerNameOptions = HeaderCompletion.GetCompletions(alreadySpecifiedHeaders, normalizedCompletionText); + + if (headerNameOptions == null) + { + return null; + } + + List allSuggestions = new List(); + foreach (string suggestion in headerNameOptions.Select(x => x)) + { + allSuggestions.Add(suggestion + ":"); + + IEnumerable suggestions = HeaderCompletion.GetValueCompletions(Verb, path, suggestion, string.Empty, programState); + + if (suggestions != null) + { + foreach (string valueSuggestion in suggestions) + { + allSuggestions.Add(suggestion + ":" + valueSuggestion); + } + } + } + + return allSuggestions; + } + else + { + //Didn't exit from the header name check, so must be a value + string headerName = normalizedCompletionText.Substring(0, equalsIndex); + IEnumerable suggestions = HeaderCompletion.GetValueCompletions(Verb, path, headerName, normalizedCompletionText.Substring(equalsIndex + 1), programState); + + if (suggestions == null) + { + return null; + } + + return suggestions.Select(x => normalizedCompletionText.Substring(0, equalsIndex + 1) + x); + } + } + + return null; + } + } +} diff --git a/src/Microsoft.HttpRepl/Commands/ChangeDirectoryCommand.cs b/src/Microsoft.HttpRepl/Commands/ChangeDirectoryCommand.cs new file mode 100644 index 0000000000..3a983698cf --- /dev/null +++ b/src/Microsoft.HttpRepl/Commands/ChangeDirectoryCommand.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.HttpRepl.Suggestions; +using Microsoft.Repl; +using Microsoft.Repl.Commanding; +using Microsoft.Repl.Parsing; + +namespace Microsoft.HttpRepl.Commands +{ + public class ChangeDirectoryCommand : CommandWithStructuredInputBase + { + protected override Task ExecuteAsync(IShellState shellState, HttpState programState, DefaultCommandInput commandInput, ICoreParseResult parseResult, CancellationToken cancellationToken) + { + if (commandInput.Arguments.Count == 0 || string.IsNullOrEmpty(commandInput.Arguments[0]?.Text)) + { + shellState.ConsoleManager.WriteLine($"/{string.Join("/", programState.PathSections.Reverse())}"); + } + else + { + string[] parts = commandInput.Arguments[0].Text.Replace('\\', '/').Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); + + if (commandInput.Arguments[0].Text.StartsWith("/", StringComparison.Ordinal)) + { + programState.PathSections.Clear(); + } + + foreach (string part in parts) + { + switch (part) + { + case ".": + break; + case "..": + if (programState.PathSections.Count > 0) + { + programState.PathSections.Pop(); + } + break; + default: + programState.PathSections.Push(part); + break; + } + } + + shellState.ConsoleManager.WriteLine($"/{string.Join("/", programState.PathSections.Reverse())}"); + } + + return Task.CompletedTask; + } + + protected override CommandInputSpecification InputSpec { get; } = CommandInputSpecification.Create("cd") + .MaximumArgCount(1) + .Finish(); + + protected override string GetHelpDetails(IShellState shellState, HttpState programState, DefaultCommandInput commandInput, ICoreParseResult parseResult) + { + if (commandInput.Arguments.Count == 1 && !string.IsNullOrEmpty(commandInput.Arguments[0]?.Text)) + { + return "Prints the current directory if no argument is specified, otherwise changes to the specified directory"; + } + + return "Changes to the directory " + commandInput.Arguments[0].Text; + } + + public override string GetHelpSummary(IShellState shellState, HttpState programState) + { + return "cd [directory name] - Prints the current directory if no argument is specified, otherwise changes to the specified directory"; + } + + protected override IEnumerable GetArgumentSuggestionsForText(IShellState shellState, HttpState programState, ICoreParseResult parseResult, DefaultCommandInput commandInput, string normalCompletionString) + { + return ServerPathCompletion.GetCompletions(programState, normalCompletionString); + } + } +} diff --git a/src/Microsoft.HttpRepl/Commands/ClearCommand.cs b/src/Microsoft.HttpRepl/Commands/ClearCommand.cs new file mode 100644 index 0000000000..a695dfc544 --- /dev/null +++ b/src/Microsoft.HttpRepl/Commands/ClearCommand.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Repl; +using Microsoft.Repl.Commanding; +using Microsoft.Repl.Parsing; + +namespace Microsoft.HttpRepl.Commands +{ + public class ClearCommand : ICommand + { + private static readonly string Name = "clear"; + private static readonly string AlternateName = "cls"; + + public bool? CanHandle(IShellState shellState, object programState, ICoreParseResult parseResult) + { + return parseResult.Sections.Count == 1 && (string.Equals(parseResult.Sections[0], Name, StringComparison.OrdinalIgnoreCase) || string.Equals(parseResult.Sections[0], AlternateName, StringComparison.OrdinalIgnoreCase)) + ? (bool?) true + : null; + } + + public Task ExecuteAsync(IShellState shellState, object programState, ICoreParseResult parseResult, CancellationToken cancellationToken) + { + shellState.ConsoleManager.Clear(); + shellState.CommandDispatcher.OnReady(shellState); + return Task.CompletedTask; + } + + public string GetHelpDetails(IShellState shellState, object programState, ICoreParseResult parseResult) + { + if (parseResult.Sections.Count == 1 && (string.Equals(parseResult.Sections[0], Name, StringComparison.OrdinalIgnoreCase) || string.Equals(parseResult.Sections[0], AlternateName, StringComparison.OrdinalIgnoreCase))) + { + return "Clears the shell"; + } + + return null; + } + + public string GetHelpSummary(IShellState shellState, object programState) + { + return "clear - Clears the shell"; + } + + public IEnumerable Suggest(IShellState shellState, object programState, ICoreParseResult parseResult) + { + if (parseResult.SelectedSection == 0 && + (string.IsNullOrEmpty(parseResult.Sections[parseResult.SelectedSection]) || Name.StartsWith(parseResult.Sections[0].Substring(0, parseResult.CaretPositionWithinSelectedSection), StringComparison.OrdinalIgnoreCase))) + { + return new[] { Name }; + } + + if (parseResult.SelectedSection == 0 && + (string.IsNullOrEmpty(parseResult.Sections[parseResult.SelectedSection]) || AlternateName.StartsWith(parseResult.Sections[0].Substring(0, parseResult.CaretPositionWithinSelectedSection), StringComparison.OrdinalIgnoreCase))) + { + return new[] { Name }; + } + + return null; + } + } +} diff --git a/src/Microsoft.HttpRepl/Commands/ConfigCommand.cs b/src/Microsoft.HttpRepl/Commands/ConfigCommand.cs new file mode 100644 index 0000000000..56003280ab --- /dev/null +++ b/src/Microsoft.HttpRepl/Commands/ConfigCommand.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.HttpRepl.Diagnostics; +using Microsoft.Repl; +using Microsoft.Repl.Commanding; +using Microsoft.Repl.ConsoleHandling; +using Microsoft.Repl.Parsing; + +namespace Microsoft.HttpRepl.Commands +{ + public class ConfigCommand : CommandWithStructuredInputBase + { + protected override async Task ExecuteAsync(IShellState shellState, HttpState programState, DefaultCommandInput commandInput, ICoreParseResult parseResult, CancellationToken cancellationToken) + { + if (programState.BaseAddress == null) + { + shellState.ConsoleManager.Error.WriteLine("Must be connected to a server to query configuration".Bold().Red()); + return; + } + + if (string.IsNullOrEmpty(programState.DiagnosticsState.DiagnosticsEndpoint)) + { + shellState.ConsoleManager.Error.WriteLine("Diagnostics endpoint must be set to query configuration (see set diag)".Bold().Red()); + return; + } + + string configUrl = programState.DiagnosticsState.DiagnosticItems.FirstOrDefault(x => x.DisplayName == "Configuration")?.Url; + + if (configUrl == null) + { + shellState.ConsoleManager.Error.WriteLine("Diagnostics endpoint does not expose configuration information".Bold().Red()); + return; + } + + HttpResponseMessage response = await programState.Client.GetAsync(new Uri(programState.BaseAddress, configUrl), cancellationToken).ConfigureAwait(false); + + if (!response.IsSuccessStatusCode) + { + shellState.ConsoleManager.Error.WriteLine("Unable to get configuration information from diagnostics endpoint".Bold().Red()); + return; + } + + List configItems = await response.Content.ReadAsAsync>(cancellationToken).ConfigureAwait(false); + + foreach (ConfigItem item in configItems) + { + shellState.ConsoleManager.WriteLine($"{item.Key.Cyan()}: {item.Value}"); + } + } + + protected override CommandInputSpecification InputSpec { get; } = CommandInputSpecification.Create("config").Finish(); + + protected override string GetHelpDetails(IShellState shellState, HttpState programState, DefaultCommandInput commandInput, ICoreParseResult parseResult) + { + return "config - Gets configuration information for the site if connected to a diagnostics endpoint"; + } + + public override string GetHelpSummary(IShellState shellState, HttpState programState) + { + return "config - Gets configuration information for the site if connected to a diagnostics endpoint"; + } + } +} diff --git a/src/Microsoft.HttpRepl/Commands/DeleteCommand.cs b/src/Microsoft.HttpRepl/Commands/DeleteCommand.cs new file mode 100644 index 0000000000..a861b35142 --- /dev/null +++ b/src/Microsoft.HttpRepl/Commands/DeleteCommand.cs @@ -0,0 +1,9 @@ +namespace Microsoft.HttpRepl.Commands +{ + public class DeleteCommand : BaseHttpCommand + { + protected override string Verb => "delete"; + + protected override bool RequiresBody => true; + } +} diff --git a/src/Microsoft.HttpRepl/Commands/EchoCommand.cs b/src/Microsoft.HttpRepl/Commands/EchoCommand.cs new file mode 100644 index 0000000000..19eb33788b --- /dev/null +++ b/src/Microsoft.HttpRepl/Commands/EchoCommand.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Repl; +using Microsoft.Repl.Commanding; +using Microsoft.Repl.ConsoleHandling; +using Microsoft.Repl.Parsing; + +namespace Microsoft.HttpRepl.Commands +{ + public class EchoCommand : CommandWithStructuredInputBase + { + private readonly HashSet _allowedModes = new HashSet(StringComparer.OrdinalIgnoreCase) {"on", "off"}; + + protected override bool CanHandle(IShellState shellState, HttpState programState, DefaultCommandInput commandInput) + { + if (commandInput.Arguments.Count == 0 || !_allowedModes.Contains(commandInput.Arguments[0]?.Text)) + { + shellState.ConsoleManager.Error.WriteLine("Allowed echo modes are 'on' and 'off'".Bold().Red()); + return false; + } + + return true; + } + + protected override Task ExecuteAsync(IShellState shellState, HttpState programState, DefaultCommandInput commandInput, ICoreParseResult parseResult, CancellationToken cancellationToken) + { + bool turnOn = string.Equals(commandInput.Arguments[0].Text, "on", StringComparison.OrdinalIgnoreCase); + programState.EchoRequest = turnOn; + + shellState.ConsoleManager.WriteLine("Request echoing is " + (turnOn ? "on" : "off")); + return Task.CompletedTask; + } + + protected override CommandInputSpecification InputSpec { get; } = CommandInputSpecification.Create("echo").ExactArgCount(1).Finish(); + + protected override string GetHelpDetails(IShellState shellState, HttpState programState, DefaultCommandInput commandInput, ICoreParseResult parseResult) + { + return "Turns request echoing on or off"; + } + + public override string GetHelpSummary(IShellState shellState, HttpState programState) + { + return "echo [on/off] - Turns request echoing on or off"; + } + + protected override IEnumerable GetArgumentSuggestionsForText(IShellState shellState, HttpState programState, ICoreParseResult parseResult, DefaultCommandInput commandInput, string normalCompletionString) + { + List result = _allowedModes.Where(x => x.StartsWith(normalCompletionString, StringComparison.OrdinalIgnoreCase)).ToList(); + return result.Count > 0 ? result : null; + } + } +} diff --git a/src/Microsoft.HttpRepl/Commands/ExitCommand.cs b/src/Microsoft.HttpRepl/Commands/ExitCommand.cs new file mode 100644 index 0000000000..ebc0635876 --- /dev/null +++ b/src/Microsoft.HttpRepl/Commands/ExitCommand.cs @@ -0,0 +1,29 @@ +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Repl; +using Microsoft.Repl.Commanding; +using Microsoft.Repl.Parsing; + +namespace Microsoft.HttpRepl.Commands +{ + public class ExitCommand : CommandWithStructuredInputBase + { + protected override Task ExecuteAsync(IShellState shellState, object programState, DefaultCommandInput commandInput, ICoreParseResult parseResult, CancellationToken cancellationToken) + { + shellState.IsExiting = true; + return Task.CompletedTask; + } + + protected override CommandInputSpecification InputSpec { get; } = CommandInputSpecification.Create("exit").ExactArgCount(0).Finish(); + + protected override string GetHelpDetails(IShellState shellState, object programState, DefaultCommandInput commandInput, ICoreParseResult parseResult) + { + return "Exits the shell"; + } + + public override string GetHelpSummary(IShellState shellState, object programState) + { + return "exit - Exits the shell"; + } + } +} diff --git a/src/Microsoft.HttpRepl/Commands/Formatter.cs b/src/Microsoft.HttpRepl/Commands/Formatter.cs new file mode 100644 index 0000000000..5a450e3406 --- /dev/null +++ b/src/Microsoft.HttpRepl/Commands/Formatter.cs @@ -0,0 +1,33 @@ +namespace Microsoft.HttpRepl.Commands +{ + public class Formatter + { + //private readonly List _prefix = new List(); + private int _prefix; + private int _maxDepth; + + public void RegisterEntry(int prefixLength, int depth) + { + //while (_prefix.Count < depth + 1) + //{ + // _prefix.Add(0); + //} + + if (depth > _maxDepth) + { + _maxDepth = depth; + } + + if (prefixLength > _prefix) + { + _prefix = prefixLength; + } + } + + public string Format(string prefix, string entry, int level) + { + string indent = "".PadRight(level * 4); + return (indent + prefix).PadRight(_prefix + 3 + _maxDepth * 4) + entry; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.HttpRepl/Commands/GetCommand.cs b/src/Microsoft.HttpRepl/Commands/GetCommand.cs new file mode 100644 index 0000000000..76765ce374 --- /dev/null +++ b/src/Microsoft.HttpRepl/Commands/GetCommand.cs @@ -0,0 +1,9 @@ +namespace Microsoft.HttpRepl.Commands +{ + public class GetCommand : BaseHttpCommand + { + protected override string Verb => "get"; + + protected override bool RequiresBody => false; + } +} diff --git a/src/Microsoft.HttpRepl/Commands/HeadCommand.cs b/src/Microsoft.HttpRepl/Commands/HeadCommand.cs new file mode 100644 index 0000000000..97023f5860 --- /dev/null +++ b/src/Microsoft.HttpRepl/Commands/HeadCommand.cs @@ -0,0 +1,9 @@ +namespace Microsoft.HttpRepl.Commands +{ + public class HeadCommand : BaseHttpCommand + { + protected override string Verb => "head"; + + protected override bool RequiresBody => false; + } +} diff --git a/src/Microsoft.HttpRepl/Commands/HelpCommand.cs b/src/Microsoft.HttpRepl/Commands/HelpCommand.cs new file mode 100644 index 0000000000..de909a4906 --- /dev/null +++ b/src/Microsoft.HttpRepl/Commands/HelpCommand.cs @@ -0,0 +1,172 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.HttpRepl.Suggestions; +using Microsoft.Repl; +using Microsoft.Repl.Commanding; +using Microsoft.Repl.Parsing; + +namespace Microsoft.HttpRepl.Commands +{ + public class HelpCommand : ICommand + { + private static readonly string Name = "help"; + + public bool? CanHandle(IShellState shellState, HttpState programState, ICoreParseResult parseResult) + { + return parseResult.Sections.Count > 0 && string.Equals(parseResult.Sections[0], Name) + ? (bool?)true + : null; + } + + public Task ExecuteAsync(IShellState shellState, HttpState programState, ICoreParseResult parseResult, CancellationToken cancellationToken) + { + if (shellState.CommandDispatcher is ICommandDispatcher dispatcher) + { + if (parseResult.Sections.Count == 1) + { + foreach (ICommand command in dispatcher.Commands) + { + string help = command.GetHelpSummary(shellState, programState); + + if (!string.IsNullOrEmpty(help)) + { + shellState.ConsoleManager.WriteLine(help); + } + } + } + else + { + bool anyHelp = false; + + if (parseResult.Slice(1) is ICoreParseResult continuationParseResult) + { + foreach (ICommand command in dispatcher.Commands) + { + string help = command.GetHelpDetails(shellState, programState, continuationParseResult); + + if (!string.IsNullOrEmpty(help)) + { + anyHelp = true; + shellState.ConsoleManager.WriteLine(help); + } + } + } + + if (!anyHelp) + { + //Maybe the input is an URL + if (parseResult.Sections.Count == 2) + { + IDirectoryStructure structure = programState.Structure.TraverseTo(parseResult.Sections[1]); + if (structure.DirectoryNames.Any()) + { + shellState.ConsoleManager.WriteLine("Child directories:"); + + foreach (string name in structure.DirectoryNames) + { + shellState.ConsoleManager.WriteLine(" " + name + "/"); + } + + anyHelp = true; + } + + if (structure.RequestInfo != null) + { + if (structure.RequestInfo.Methods.Count > 0) + { + if (anyHelp) + { + shellState.ConsoleManager.WriteLine(); + } + + anyHelp = true; + shellState.ConsoleManager.WriteLine("Available methods:"); + + foreach (string method in structure.RequestInfo.Methods) + { + shellState.ConsoleManager.WriteLine(" " + method.ToUpperInvariant()); + IReadOnlyList accepts = structure.RequestInfo.ContentTypesByMethod[method]; + string acceptsString = string.Join(", ", accepts.Where(x => !string.IsNullOrEmpty(x))); + if (!string.IsNullOrEmpty(acceptsString)) + { + shellState.ConsoleManager.WriteLine(" Accepts: " + acceptsString); + } + } + } + } + } + + if (!anyHelp) + { + shellState.ConsoleManager.WriteLine("Unable to locate any help information for the specified command"); + } + } + } + } + + return Task.CompletedTask; + } + + public string GetHelpDetails(IShellState shellState, HttpState programState, ICoreParseResult parseResult) + { + if (parseResult.Sections.Count > 0 && string.Equals(parseResult.Sections[0], Name, StringComparison.OrdinalIgnoreCase)) + { + if (parseResult.Sections.Count > 1) + { + return "Gets help about " + parseResult.Slice(1).CommandText; + } + else + { + return "Gets help"; + } + } + + return null; + } + + public string GetHelpSummary(IShellState shellState, HttpState programState) + { + return "help - Gets help"; + } + + public IEnumerable Suggest(IShellState shellState, HttpState programState, ICoreParseResult parseResult) + { + if (parseResult.SelectedSection == 0 && + (string.IsNullOrEmpty(parseResult.Sections[parseResult.SelectedSection]) || Name.StartsWith(parseResult.Sections[0].Substring(0, parseResult.CaretPositionWithinSelectedSection), StringComparison.OrdinalIgnoreCase))) + { + return new[] { Name }; + } + else if (parseResult.Sections.Count > 1 && string.Equals(parseResult.Sections[0], Name, StringComparison.OrdinalIgnoreCase)) + { + if (shellState.CommandDispatcher is ICommandDispatcher dispatcher + && parseResult.Slice(1) is ICoreParseResult continuationParseResult) + { + HashSet suggestions = new HashSet(StringComparer.OrdinalIgnoreCase); + + foreach (ICommand command in dispatcher.Commands) + { + IEnumerable commandSuggestions = command.Suggest(shellState, programState, continuationParseResult); + + if (commandSuggestions != null) + { + suggestions.UnionWith(commandSuggestions); + } + } + + if (continuationParseResult.SelectedSection == 0) + { + string normalizedCompletionText = continuationParseResult.Sections[0].Substring(0, continuationParseResult.CaretPositionWithinSelectedSection); + suggestions.UnionWith(ServerPathCompletion.GetCompletions(programState, normalizedCompletionText)); + } + + return suggestions.OrderBy(x => x, StringComparer.OrdinalIgnoreCase).ToList(); + } + } + + return null; + } + } +} diff --git a/src/Microsoft.HttpRepl/Commands/ListCommand.cs b/src/Microsoft.HttpRepl/Commands/ListCommand.cs new file mode 100644 index 0000000000..078a20d398 --- /dev/null +++ b/src/Microsoft.HttpRepl/Commands/ListCommand.cs @@ -0,0 +1,160 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Repl; +using Microsoft.Repl.Commanding; +using Microsoft.Repl.Parsing; + +namespace Microsoft.HttpRepl.Commands +{ + public class ListCommand : CommandWithStructuredInputBase + { + private const string RecursiveOption = nameof(RecursiveOption); + + protected override Task ExecuteAsync(IShellState shellState, HttpState programState, DefaultCommandInput commandInput, ICoreParseResult parseResult, CancellationToken cancellationToken) + { + if (programState.Structure == null || programState.BaseAddress == null) + { + return Task.CompletedTask; + } + + string path = commandInput.Arguments.Count > 0 ? commandInput.Arguments[0].Text : string.Empty; + + //If it's an absolute URI, nothing to suggest + if (Uri.TryCreate(path, UriKind.Absolute, out Uri _)) + { + return Task.CompletedTask; + } + + IDirectoryStructure s = programState.Structure.TraverseTo(programState.PathSections.Reverse()).TraverseTo(path); + + List roots = new List(); + Formatter formatter = new Formatter(); + + roots.Add(new TreeNode(formatter, ".", string.Empty)); + + if (s.Parent != null) + { + roots.Add(new TreeNode(formatter, "..", string.Empty)); + } + + int recursionDepth = 1; + + if (commandInput.Options[RecursiveOption].Count > 0) + { + if (string.IsNullOrEmpty(commandInput.Options[RecursiveOption][0]?.Text)) + { + recursionDepth = int.MaxValue; + } + else if (int.TryParse(commandInput.Options[RecursiveOption][0].Text, NumberStyles.Integer, CultureInfo.InvariantCulture, out int rd) && rd > 1) + { + recursionDepth = rd; + } + } + + foreach (string child in s.DirectoryNames) + { + IDirectoryStructure dir = s.GetChildDirectory(child); + + string methods = dir.RequestInfo != null && dir.RequestInfo.Methods.Count > 0 + ? "[" + string.Join("|", dir.RequestInfo.Methods) + "]" + : "[]"; + + TreeNode dirNode = new TreeNode(formatter, child, methods); + roots.Add(dirNode); + Recurse(dirNode, dir, recursionDepth - 1); + } + + foreach (TreeNode node in roots) + { + shellState.ConsoleManager.WriteLine(node.ToString()); + } + + return Task.CompletedTask; + } + + private static void Recurse(TreeNode parentNode, IDirectoryStructure parent, int remainingDepth) + { + if (remainingDepth <= 0) + { + return; + } + + foreach (string child in parent.DirectoryNames) + { + IDirectoryStructure dir = parent.GetChildDirectory(child); + + string methods = dir.RequestInfo != null && dir.RequestInfo.Methods.Count > 0 + ? "[" + string.Join("|", dir.RequestInfo.Methods) + "]" + : "[]"; + + TreeNode node = parentNode.AddChild(child, methods); + Recurse(node, dir, remainingDepth - 1); + } + } + + + + protected override CommandInputSpecification InputSpec { get; } = CommandInputSpecification.Create("ls") + .MaximumArgCount(1) + .WithOption(new CommandOptionSpecification(RecursiveOption, maximumOccurrences: 1, acceptsValue: true, forms: new[] {"-r", "--recursive"})) + .Finish(); + + protected override string GetHelpDetails(IShellState shellState, HttpState programState, DefaultCommandInput commandInput, ICoreParseResult parseResult) + { + return "Lists the contents of a directory"; + } + + public override string GetHelpSummary(IShellState shellState, HttpState programState) + { + return "ls - Performs a directory listing"; + } + + protected override IEnumerable GetArgumentSuggestionsForText(IShellState shellState, HttpState programState, ICoreParseResult parseResult, DefaultCommandInput commandInput, string normalCompletionString) + { + if (programState.Structure == null || programState.BaseAddress == null) + { + return null; + } + + //If it's an absolute URI, nothing to suggest + if (Uri.TryCreate(normalCompletionString, UriKind.Absolute, out Uri _)) + { + return null; + } + + string path = normalCompletionString.Replace('\\', '/'); + int searchFrom = normalCompletionString.Length - 1; + int lastSlash = path.LastIndexOf('/', searchFrom); + string prefix; + + if (lastSlash < 0) + { + path = string.Empty; + prefix = normalCompletionString; + } + else + { + path = path.Substring(0, lastSlash + 1); + prefix = normalCompletionString.Substring(lastSlash + 1); + } + + IDirectoryStructure s = programState.Structure.TraverseTo(programState.PathSections.Reverse()).TraverseTo(path); + + List results = new List(); + + foreach (string child in s.DirectoryNames) + { + if (child.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) + { + results.Add(path + child); + } + } + + return results; + } + } +} diff --git a/src/Microsoft.HttpRepl/Commands/OptionsCommand.cs b/src/Microsoft.HttpRepl/Commands/OptionsCommand.cs new file mode 100644 index 0000000000..f6a3c8903c --- /dev/null +++ b/src/Microsoft.HttpRepl/Commands/OptionsCommand.cs @@ -0,0 +1,9 @@ +namespace Microsoft.HttpRepl.Commands +{ + public class OptionsCommand : BaseHttpCommand + { + protected override string Verb => "options"; + + protected override bool RequiresBody => false; + } +} diff --git a/src/Microsoft.HttpRepl/Commands/PatchCommand.cs b/src/Microsoft.HttpRepl/Commands/PatchCommand.cs new file mode 100644 index 0000000000..c5d9c875be --- /dev/null +++ b/src/Microsoft.HttpRepl/Commands/PatchCommand.cs @@ -0,0 +1,9 @@ +namespace Microsoft.HttpRepl.Commands +{ + public class PatchCommand : BaseHttpCommand + { + protected override string Verb => "patch"; + + protected override bool RequiresBody => true; + } +} diff --git a/src/Microsoft.HttpRepl/Commands/PostCommand.cs b/src/Microsoft.HttpRepl/Commands/PostCommand.cs new file mode 100644 index 0000000000..216fe8ef1d --- /dev/null +++ b/src/Microsoft.HttpRepl/Commands/PostCommand.cs @@ -0,0 +1,9 @@ +namespace Microsoft.HttpRepl.Commands +{ + public class PostCommand : BaseHttpCommand + { + protected override string Verb => "post"; + + protected override bool RequiresBody => true; + } +} diff --git a/src/Microsoft.HttpRepl/Commands/PrefCommand.cs b/src/Microsoft.HttpRepl/Commands/PrefCommand.cs new file mode 100644 index 0000000000..7f16af09b3 --- /dev/null +++ b/src/Microsoft.HttpRepl/Commands/PrefCommand.cs @@ -0,0 +1,172 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.HttpRepl.Preferences; +using Microsoft.Repl; +using Microsoft.Repl.Commanding; +using Microsoft.Repl.ConsoleHandling; +using Microsoft.Repl.Parsing; + +namespace Microsoft.HttpRepl.Commands +{ + public class PrefCommand : CommandWithStructuredInputBase + { + private readonly HashSet _allowedSubcommands = new HashSet(StringComparer.OrdinalIgnoreCase) {"get", "set"}; + + public override string GetHelpSummary(IShellState shellState, HttpState programState) + { + return "pref [get/set] {setting} [{value}] - Allows viewing or changing preferences"; + } + + protected override bool CanHandle(IShellState shellState, HttpState programState, DefaultCommandInput commandInput) + { + if (commandInput.Arguments.Count == 0 || !_allowedSubcommands.Contains(commandInput.Arguments[0]?.Text)) + { + shellState.ConsoleManager.Error.WriteLine("Whether get or set settings must be specified"); + return false; + } + + if (!string.Equals("get", commandInput.Arguments[0].Text) && (commandInput.Arguments.Count < 2 || string.IsNullOrEmpty(commandInput.Arguments[1]?.Text))) + { + shellState.ConsoleManager.Error.WriteLine("The preference to set must be specified"); + return false; + } + + return true; + } + + protected override string GetHelpDetails(IShellState shellState, HttpState programState, DefaultCommandInput commandInput, ICoreParseResult parseResult) + { + if (commandInput.Arguments.Count == 0 || !_allowedSubcommands.Contains(commandInput.Arguments[0]?.Text)) + { + return "pref [get/set] {setting} [{value}] - Get or sets a preference to a particular value"; + } + + if (string.Equals(commandInput.Arguments[0].Text, "get", StringComparison.OrdinalIgnoreCase)) + { + return "pref get [{setting}] - Gets the value of the specified preference or lists all preferences if no preference is specified"; + } + else + { + return "pref set {setting} [{value}] - Sets (or clears if value is not specified) the value of the specified preference"; + } + } + + protected override Task ExecuteAsync(IShellState shellState, HttpState programState, DefaultCommandInput commandInput, ICoreParseResult parseResult, CancellationToken cancellationToken) + { + if (string.Equals(commandInput.Arguments[0].Text, "get", StringComparison.OrdinalIgnoreCase)) + { + return GetSetting(shellState, programState, commandInput); + } + + return SetSetting(shellState, programState, commandInput); + } + + private static Task SetSetting(IShellState shellState, HttpState programState, DefaultCommandInput commandInput) + { + string prefName = commandInput.Arguments[1].Text; + string prefValue = commandInput.Arguments.Count > 2 ? commandInput.Arguments[2]?.Text : null; + + if (string.IsNullOrEmpty(prefValue)) + { + if (!programState.DefaultPreferences.TryGetValue(prefName, out string defaultValue)) + { + programState.Preferences.Remove(prefName); + } + else + { + programState.Preferences[prefName] = defaultValue; + } + } + else + { + programState.Preferences[prefName] = prefValue; + } + + if (!programState.SavePreferences()) + { + shellState.ConsoleManager.Error.WriteLine("Error saving preferences".Bold().Red()); + } + + return Task.CompletedTask; + } + + private static Task GetSetting(IShellState shellState, HttpState programState, DefaultCommandInput commandInput) + { + string preferenceName = commandInput.Arguments.Count > 1 ? commandInput.Arguments[1]?.Text : null; + + //If there's a particular setting to get the value of + if (!string.IsNullOrEmpty(preferenceName)) + { + if (programState.Preferences.TryGetValue(preferenceName, out string value)) + { + shellState.ConsoleManager.WriteLine("Configured value: " + value); + } + else + { + shellState.ConsoleManager.Error.WriteLine((commandInput.Arguments[1].Text + " does not have a configured value").Bold().Red()); + } + } + else + { + foreach (KeyValuePair entry in programState.Preferences.OrderBy(x => x.Key)) + { + shellState.ConsoleManager.WriteLine($"{entry.Key}={entry.Value}"); + } + } + + return Task.CompletedTask; + } + + protected override CommandInputSpecification InputSpec { get; } = CommandInputSpecification.Create("pref") + .MinimumArgCount(1) + .MaximumArgCount(3) + .Finish(); + + + protected override IEnumerable GetArgumentSuggestionsForText(IShellState shellState, HttpState programState, ICoreParseResult parseResult, DefaultCommandInput commandInput, string normalCompletionString) + { + if (parseResult.SelectedSection == 1) + { + return _allowedSubcommands.Where(x => x.StartsWith(normalCompletionString, StringComparison.OrdinalIgnoreCase)); + } + + if (parseResult.SelectedSection == 2) + { + string prefix = parseResult.Sections.Count > 2 ? normalCompletionString : string.Empty; + List matchingProperties = new List(); + + foreach (string val in WellKnownPreference.Catalog.Names) + { + if (val.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) + { + matchingProperties.Add(val); + } + } + + return matchingProperties; + } + + if (parseResult.SelectedSection == 3 + && parseResult.Sections[2].StartsWith("colors.", StringComparison.OrdinalIgnoreCase)) + { + string prefix = parseResult.Sections.Count > 3 ? normalCompletionString : string.Empty; + List matchingProperties = new List(); + + foreach (string val in Enum.GetNames(typeof(AllowedColors))) + { + if (val.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) + { + matchingProperties.Add(val); + } + } + + return matchingProperties; + } + + return null; + } + } +} diff --git a/src/Microsoft.HttpRepl/Commands/PutCommand.cs b/src/Microsoft.HttpRepl/Commands/PutCommand.cs new file mode 100644 index 0000000000..a65439dfb7 --- /dev/null +++ b/src/Microsoft.HttpRepl/Commands/PutCommand.cs @@ -0,0 +1,9 @@ +namespace Microsoft.HttpRepl.Commands +{ + public class PutCommand : BaseHttpCommand + { + protected override string Verb => "put"; + + protected override bool RequiresBody => true; + } +} diff --git a/src/Microsoft.HttpRepl/Commands/RunCommand.cs b/src/Microsoft.HttpRepl/Commands/RunCommand.cs new file mode 100644 index 0000000000..6dd2f4a378 --- /dev/null +++ b/src/Microsoft.HttpRepl/Commands/RunCommand.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Repl; +using Microsoft.Repl.Commanding; +using Microsoft.Repl.Parsing; +using Microsoft.Repl.Scripting; +using Microsoft.Repl.Suggestions; + +namespace Microsoft.HttpRepl.Commands +{ + public class RunCommand : ICommand + { + private static readonly string Name = "run"; + + public bool? CanHandle(IShellState shellState, HttpState programState, ICoreParseResult parseResult) + { + return parseResult.Sections.Count > 1 && parseResult.Sections.Count < 4 && string.Equals(Name, parseResult.Sections[0], StringComparison.OrdinalIgnoreCase) + ? (bool?)true + : null; + } + + public async Task ExecuteAsync(IShellState shellState, HttpState programState, ICoreParseResult parseResult, CancellationToken cancellationToken) + { + if (!File.Exists(parseResult.Sections[1])) + { + shellState.ConsoleManager.Error.WriteLine($"Could not file script file {parseResult.Sections[1]}"); + return; + } + + bool suppressScriptLinesInHistory = true; + if (parseResult.Sections.Count == 3) + { + suppressScriptLinesInHistory = !string.Equals(parseResult.Sections[2], "+history"); + } + + string[] lines = File.ReadAllLines(parseResult.Sections[1]); + IScriptExecutor scriptExecutor = new ScriptExecutor(suppressScriptLinesInHistory); + await scriptExecutor.ExecuteScriptAsync(shellState, lines, cancellationToken).ConfigureAwait(false); + } + + public string GetHelpDetails(IShellState shellState, HttpState programState, ICoreParseResult parseResult) + { + if (parseResult.Sections.Count > 0 && string.Equals(parseResult.Sections[0], Name, StringComparison.OrdinalIgnoreCase)) + { + if (parseResult.Sections.Count == 1) + { + return "Runs the specified script"; + } + + return "Runs the script " + parseResult.Sections[1]; + } + + return null; + } + + public string GetHelpSummary(IShellState shellState, HttpState programState) + { + return "run {path to script} - Runs a script"; + } + + public IEnumerable Suggest(IShellState shellState, HttpState programState, ICoreParseResult parseResult) + { + if (parseResult.SelectedSection == 0 && + (string.IsNullOrEmpty(parseResult.Sections[parseResult.SelectedSection]) || Name.StartsWith(parseResult.Sections[0].Substring(0, parseResult.CaretPositionWithinSelectedSection), StringComparison.OrdinalIgnoreCase))) + { + return new[] { Name }; + } + + if (parseResult.SelectedSection == 1 && string.Equals(parseResult.Sections[0], Name, StringComparison.OrdinalIgnoreCase)) + { + return FileSystemCompletion.GetCompletions(parseResult.Sections[1].Substring(0, parseResult.CaretPositionWithinSelectedSection)); + } + + return null; + } + } +} diff --git a/src/Microsoft.HttpRepl/Commands/SetBaseCommand.cs b/src/Microsoft.HttpRepl/Commands/SetBaseCommand.cs new file mode 100644 index 0000000000..c346f541d4 --- /dev/null +++ b/src/Microsoft.HttpRepl/Commands/SetBaseCommand.cs @@ -0,0 +1,100 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Repl; +using Microsoft.Repl.Commanding; +using Microsoft.Repl.ConsoleHandling; +using Microsoft.Repl.Parsing; + +namespace Microsoft.HttpRepl.Commands +{ + public class SetBaseCommand : ICommand + { + private const string Name = "set"; + private const string SubCommand = "base"; + + public string Description => "Sets the base address to direct requests to."; + + public bool? CanHandle(IShellState shellState, HttpState programState, ICoreParseResult parseResult) + { + return parseResult.Sections.Count > 1 && string.Equals(parseResult.Sections[0], Name, StringComparison.OrdinalIgnoreCase) && string.Equals(parseResult.Sections[1], SubCommand, StringComparison.OrdinalIgnoreCase) + ? (bool?)true + : null; + } + + public async Task ExecuteAsync(IShellState shellState, HttpState state, ICoreParseResult parseResult, CancellationToken cancellationToken) + { + if (parseResult.Sections.Count == 2) + { + state.BaseAddress = null; + } + else if (parseResult.Sections.Count != 3 || string.IsNullOrEmpty(parseResult.Sections[2]) || !Uri.TryCreate(EnsureTrailingSlash(parseResult.Sections[2]), UriKind.Absolute, out Uri serverUri)) + { + shellState.ConsoleManager.Error.WriteLine("Must specify a server".Bold().Red()); + } + else + { + state.BaseAddress = serverUri; + } + + if (state.BaseAddress == null || !Uri.TryCreate(state.BaseAddress, "/swagger/v1/swagger.json", out Uri result)) + { + state.SwaggerStructure = null; + } + else + { + await SetSwaggerCommand.CreateDirectoryStructureForSwaggerEndpointAsync(shellState, state, result, cancellationToken).ConfigureAwait(false); + if (state.SwaggerStructure != null) + { + shellState.ConsoleManager.WriteLine("Using swagger metadata from " + result); + } + } + } + + private string EnsureTrailingSlash(string v) + { + if (!v.EndsWith("/", StringComparison.Ordinal)) + { + v += "/"; + } + + return v; + } + + public string GetHelpDetails(IShellState shellState, HttpState programState, ICoreParseResult parseResult) + { + if (parseResult.Sections.Count > 1 && string.Equals(parseResult.Sections[0], Name, StringComparison.OrdinalIgnoreCase) && string.Equals(parseResult.Sections[1], SubCommand, StringComparison.OrdinalIgnoreCase)) + { + return Description; + } + + return null; + } + + public string GetHelpSummary(IShellState shellState, HttpState programState) + { + return Description; + } + + public IEnumerable Suggest(IShellState shellState, HttpState programState, ICoreParseResult parseResult) + { + if (parseResult.Sections.Count == 0) + { + return new[] { Name }; + } + + if (parseResult.Sections.Count > 0 && parseResult.SelectedSection == 0 && Name.StartsWith(parseResult.Sections[0].Substring(0, parseResult.CaretPositionWithinSelectedSection), StringComparison.OrdinalIgnoreCase)) + { + return new[] { Name }; + } + + if (string.Equals(Name, parseResult.Sections[0], StringComparison.OrdinalIgnoreCase) && parseResult.SelectedSection == 1 && (parseResult.Sections.Count < 2 || SubCommand.StartsWith(parseResult.Sections[1].Substring(0, parseResult.CaretPositionWithinSelectedSection), StringComparison.OrdinalIgnoreCase))) + { + return new[] { SubCommand }; + } + + return null; + } + } +} diff --git a/src/Microsoft.HttpRepl/Commands/SetDiagCommand.cs b/src/Microsoft.HttpRepl/Commands/SetDiagCommand.cs new file mode 100644 index 0000000000..4e86452b7c --- /dev/null +++ b/src/Microsoft.HttpRepl/Commands/SetDiagCommand.cs @@ -0,0 +1,138 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.HttpRepl.Diagnostics; +using Microsoft.Repl; +using Microsoft.Repl.Commanding; +using Microsoft.Repl.ConsoleHandling; +using Microsoft.Repl.Parsing; + +namespace Microsoft.HttpRepl.Commands +{ + public class SetDiagCommand : ICommand + { + private static readonly string Name = "set"; + private static readonly string SubCommand = "diag"; + + public string Description => "Sets the diagnostics path to direct requests to."; + + public bool? CanHandle(IShellState shellState, HttpState programState, ICoreParseResult parseResult) + { + return parseResult.Sections.Count > 1 && string.Equals(parseResult.Sections[0], Name, StringComparison.OrdinalIgnoreCase) && string.Equals(parseResult.Sections[1], SubCommand, StringComparison.OrdinalIgnoreCase) + ? (bool?)true + : null; + } + + public async Task ExecuteAsync(IShellState shellState, HttpState programState, ICoreParseResult parseResult, CancellationToken cancellationToken) + { + if (parseResult.Sections.Count == 2) + { + programState.DiagnosticsState.DiagnosticsEndpoint = null; + programState.DiagnosticsState.DiagnosticItems = null; + programState.DiagnosticsState.DiagEndpointsStructure = null; + return; + } + + if (parseResult.Sections.Count != 3 || string.IsNullOrEmpty(parseResult.Sections[2]) || !Uri.TryCreate(parseResult.Sections[2], UriKind.Relative, out Uri _)) + { + shellState.ConsoleManager.Error.WriteLine("Must specify a relative path".Bold().Red()); + } + else + { + programState.DiagnosticsState.DiagnosticsEndpoint = parseResult.Sections[2]; + HttpResponseMessage response = await programState.Client.GetAsync(new Uri(programState.BaseAddress, programState.DiagnosticsState.DiagnosticsEndpoint), cancellationToken).ConfigureAwait(false); + + if (!response.IsSuccessStatusCode) + { + shellState.ConsoleManager.Error.WriteLine("Unable to access diagnostics endpoint".Bold().Red()); + programState.DiagnosticsState.DiagnosticsEndpoint = null; + programState.DiagnosticsState.DiagnosticItems = null; + } + else + { + programState.DiagnosticsState.DiagnosticItems = (await response.Content.ReadAsAsync>(cancellationToken).ConfigureAwait(false))?.Select(x => x.Value).ToList(); + + DiagItem endpointsItem = programState.DiagnosticsState.DiagnosticItems?.FirstOrDefault(x => string.Equals(x.DisplayName, "Endpoints", StringComparison.OrdinalIgnoreCase)); + + if (endpointsItem != null) + { + HttpResponseMessage endpointsResponse = await programState.Client.GetAsync(new Uri(programState.BaseAddress, endpointsItem.Url), cancellationToken).ConfigureAwait(false); + + if (!endpointsResponse.IsSuccessStatusCode) + { + shellState.ConsoleManager.Error.WriteLine("Unable to get endpoints information from diagnostics endpoint".Bold().Red()); + return; + } + + List endpoints = await endpointsResponse.Content.ReadAsAsync>(cancellationToken).ConfigureAwait(false); + DirectoryStructure structure = new DirectoryStructure(null); + + foreach (DiagEndpoint endpoint in endpoints) + { + if (endpoint.Url.StartsWith(endpointsItem.Url, StringComparison.OrdinalIgnoreCase) + || endpoint.Url.StartsWith("/graphql", StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + FillDirectoryInfo(structure, endpoint.Url); + } + + programState.DiagnosticsState.DiagEndpointsStructure = structure; + } + } + } + } + + private static void FillDirectoryInfo(DirectoryStructure parent, string endpoint) + { + string[] parts = endpoint.Split('/'); + + foreach (string part in parts) + { + if (!string.IsNullOrEmpty(part)) + { + parent = parent.DeclareDirectory(part); + } + } + } + + public string GetHelpSummary(IShellState shellState, HttpState programState) + { + return Description; + } + + public string GetHelpDetails(IShellState shellState, HttpState programState, ICoreParseResult parseResult) + { + if (parseResult.Sections.Count > 1 && string.Equals(parseResult.Sections[0], Name, StringComparison.OrdinalIgnoreCase) && string.Equals(parseResult.Sections[1], SubCommand, StringComparison.OrdinalIgnoreCase)) + { + return Description; + } + + return null; + } + + public IEnumerable Suggest(IShellState shellState, HttpState programState, ICoreParseResult parseResult) + { + if (parseResult.Sections.Count == 0) + { + return new[] { Name }; + } + + if (parseResult.Sections.Count > 0 && parseResult.SelectedSection == 0 && Name.StartsWith(parseResult.Sections[0].Substring(0, parseResult.CaretPositionWithinSelectedSection), StringComparison.OrdinalIgnoreCase)) + { + return new[] { Name }; + } + + if (string.Equals(Name, parseResult.Sections[0], StringComparison.OrdinalIgnoreCase) && parseResult.SelectedSection == 1 && (parseResult.Sections.Count < 2 || SubCommand.StartsWith(parseResult.Sections[1].Substring(0, parseResult.CaretPositionWithinSelectedSection), StringComparison.OrdinalIgnoreCase))) + { + return new[] { SubCommand }; + } + + return null; + } + } +} diff --git a/src/Microsoft.HttpRepl/Commands/SetHeaderCommand.cs b/src/Microsoft.HttpRepl/Commands/SetHeaderCommand.cs new file mode 100644 index 0000000000..c95d22a6be --- /dev/null +++ b/src/Microsoft.HttpRepl/Commands/SetHeaderCommand.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.HttpRepl.Suggestions; +using Microsoft.Repl; +using Microsoft.Repl.Commanding; +using Microsoft.Repl.Parsing; + +namespace Microsoft.HttpRepl.Commands +{ + public class SetHeaderCommand : ICommand + { + private static readonly string Name = "set"; + private static readonly string SubCommand = "header"; + + public string Description => "set header {name} [{{value}}] - Sets or clears a header"; + + public bool? CanHandle(IShellState shellState, HttpState programState, ICoreParseResult parseResult) + { + return parseResult.Sections.Count > 2 && string.Equals(parseResult.Sections[0], Name, StringComparison.OrdinalIgnoreCase) && string.Equals(parseResult.Sections[1], SubCommand, StringComparison.OrdinalIgnoreCase) + ? (bool?)true + : null; + } + + public Task ExecuteAsync(IShellState shellState, HttpState programState, ICoreParseResult parseResult, CancellationToken cancellationToken) + { + if (parseResult.Sections.Count == 3) + { + programState.Headers.Remove(parseResult.Sections[2]); + } + else + { + programState.Headers[parseResult.Sections[2]] = parseResult.Sections.Skip(3).ToList(); + } + + return Task.CompletedTask; + } + + public string GetHelpDetails(IShellState shellState, HttpState programState, ICoreParseResult parseResult) + { + if (parseResult.Sections.Count > 1 && string.Equals(parseResult.Sections[0], Name, StringComparison.OrdinalIgnoreCase) && string.Equals(parseResult.Sections[1], SubCommand, StringComparison.OrdinalIgnoreCase)) + { + return Description; + } + + return null; + } + + public string GetHelpSummary(IShellState shellState, HttpState programState) + { + return Description; + } + + public IEnumerable Suggest(IShellState shellState, HttpState programState, ICoreParseResult parseResult) + { + if (parseResult.Sections.Count == 0) + { + return new[] { Name }; + } + + if (parseResult.Sections.Count > 0 && parseResult.SelectedSection == 0 && Name.StartsWith(parseResult.Sections[0].Substring(0, parseResult.CaretPositionWithinSelectedSection), StringComparison.OrdinalIgnoreCase)) + { + return new[] { Name }; + } + + if (string.Equals(Name, parseResult.Sections[0], StringComparison.OrdinalIgnoreCase) && parseResult.SelectedSection == 1 && (parseResult.Sections.Count < 2 || SubCommand.StartsWith(parseResult.Sections[1].Substring(0, parseResult.CaretPositionWithinSelectedSection), StringComparison.OrdinalIgnoreCase))) + { + return new[] { SubCommand }; + } + + if (parseResult.Sections.Count > 2 + && string.Equals(Name, parseResult.Sections[0], StringComparison.OrdinalIgnoreCase) + && string.Equals(SubCommand, parseResult.Sections[1], StringComparison.OrdinalIgnoreCase) && parseResult.SelectedSection == 2) + { + string prefix = parseResult.Sections[2].Substring(0, parseResult.CaretPositionWithinSelectedSection); + return HeaderCompletion.GetCompletions(null, prefix); + } + + if (parseResult.Sections.Count > 3 + && string.Equals(Name, parseResult.Sections[0], StringComparison.OrdinalIgnoreCase) + && string.Equals(SubCommand, parseResult.Sections[1], StringComparison.OrdinalIgnoreCase) && parseResult.SelectedSection == 3) + { + string prefix = parseResult.Sections[3].Substring(0, parseResult.CaretPositionWithinSelectedSection); + return HeaderCompletion.GetValueCompletions(null, string.Empty, parseResult.Sections[2], prefix, programState); + } + + return null; + } + } +} diff --git a/src/Microsoft.HttpRepl/Commands/SetSwaggerCommand.cs b/src/Microsoft.HttpRepl/Commands/SetSwaggerCommand.cs new file mode 100644 index 0000000000..9c9a1f36cc --- /dev/null +++ b/src/Microsoft.HttpRepl/Commands/SetSwaggerCommand.cs @@ -0,0 +1,272 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.HttpRepl.OpenApi; +using Microsoft.Repl; +using Microsoft.Repl.Commanding; +using Microsoft.Repl.ConsoleHandling; +using Microsoft.Repl.Parsing; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Microsoft.HttpRepl.Commands +{ + public class SetSwaggerCommand : ICommand + { + private static readonly string Name = "set"; + private static readonly string SubCommand = "swagger"; + + public string Description => "Sets the swagger document to use for information about the current server"; + + private static void FillDirectoryInfo(DirectoryStructure parent, EndpointMetadata entry) + { + string[] parts = entry.Path.Split('/'); + + foreach (string part in parts) + { + if (!string.IsNullOrEmpty(part)) + { + parent = parent.DeclareDirectory(part); + } + } + + RequestInfo dirRequestInfo = new RequestInfo(); + + foreach (KeyValuePair>> requestInfo in entry.AvailableRequests) + { + string method = requestInfo.Key; + + foreach (KeyValuePair> parameterSetsByContentType in requestInfo.Value) + { + if (string.IsNullOrEmpty(parameterSetsByContentType.Key)) + { + dirRequestInfo.SetFallbackRequestBody(method, GetBodyString(null, parameterSetsByContentType.Value)); + } + + dirRequestInfo.SetRequestBody(method, parameterSetsByContentType.Key, GetBodyString(parameterSetsByContentType.Key, parameterSetsByContentType.Value)); + } + + dirRequestInfo.AddMethod(method); + } + + if (dirRequestInfo.Methods.Count > 0) + { + parent.RequestInfo = dirRequestInfo; + } + } + + private static string GetBodyString(string contentType, IEnumerable operation) + { + Parameter body = operation.FirstOrDefault(x => string.Equals(x.Location, "body", StringComparison.OrdinalIgnoreCase)); + + if (body != null) + { + JToken result = GenerateData(body.Schema); + return result?.ToString() ?? "{\n}"; + } + + return null; + } + + private static JToken GenerateData(Schema schema) + { + if (schema == null) + { + return null; + } + + if (schema.Example != null) + { + return JToken.FromObject(schema.Example); + } + + if (schema.Default != null) + { + return JToken.FromObject(schema.Default); + } + + if (schema.Type is null) + { + if (schema.Properties != null || schema.AdditionalProperties != null || schema.MinProperties.HasValue || schema.MaxProperties.HasValue) + { + schema.Type = "OBJECT"; + } + else if (schema.Items != null || schema.MinItems.HasValue || schema.MaxItems.HasValue) + { + schema.Type = "ARRAY"; + } + else if (schema.Minimum.HasValue || schema.Maximum.HasValue || schema.MultipleOf.HasValue) + { + schema.Type = "INTEGER"; + } + } + + switch (schema.Type?.ToUpperInvariant()) + { + case null: + case "STRING": + return ""; + case "NUMBER": + if (schema.Minimum.HasValue) + { + if (schema.Maximum.HasValue) + { + return (schema.Maximum.Value + schema.Minimum.Value) / 2; + } + + if (schema.ExclusiveMinimum) + { + return schema.Minimum.Value + 1; + } + + return schema.Minimum.Value; + } + return 1.1; + case "INTEGER": + if (schema.Minimum.HasValue) + { + if (schema.Maximum.HasValue) + { + return (int)((schema.Maximum.Value + schema.Minimum.Value) / 2); + } + + if (schema.ExclusiveMinimum) + { + return schema.Minimum.Value + 1; + } + + return schema.Minimum.Value; + } + return 0; + case "BOOLEAN": + return true; + case "ARRAY": + JArray container = new JArray(); + JToken item = GenerateData(schema.Items) ?? ""; + + int count = schema.MinItems.GetValueOrDefault(); + count = Math.Max(1, count); + + for (int i = 0; i < count; ++i) + { + container.Add(item.DeepClone()); + } + + return container; + case "OBJECT": + JObject obj = new JObject(); + foreach (KeyValuePair property in schema.Properties) + { + JToken data = GenerateData(property.Value) ?? ""; + obj[property.Key] = data; + } + return obj; + } + + return null; + } + + private static async Task> GetSwaggerDocAsync(HttpClient client, Uri uri) + { + var resp = await client.GetAsync(uri).ConfigureAwait(false); + resp.EnsureSuccessStatusCode(); + string responseString = await resp.Content.ReadAsStringAsync().ConfigureAwait(false); + JsonSerializer serializer = new JsonSerializer{ PreserveReferencesHandling = PreserveReferencesHandling.All }; + JObject responseObject = (JObject)serializer.Deserialize(new StringReader(responseString), typeof(JObject)); + EndpointMetadataReader reader = new EndpointMetadataReader(); + responseObject = await PointerUtil.ResolvePointersAsync(uri, responseObject, client).ConfigureAwait(false) as JObject; + + if (responseObject is null) + { + return new EndpointMetadata[0]; + } + + return reader.Read(responseObject); + } + + public string GetHelpSummary(IShellState shellState, HttpState programState) + { + return Description; + } + + public string GetHelpDetails(IShellState shellState, HttpState programState, ICoreParseResult parseResult) + { + if (parseResult.Sections.Count > 1 && string.Equals(parseResult.Sections[0], Name, StringComparison.OrdinalIgnoreCase) && string.Equals(parseResult.Sections[1], SubCommand, StringComparison.OrdinalIgnoreCase)) + { + return Description; + } + + return null; + } + + public IEnumerable Suggest(IShellState shellState, HttpState programState, ICoreParseResult parseResult) + { + if (parseResult.Sections.Count == 0) + { + return new[] { Name }; + } + + if (parseResult.Sections.Count > 0 && parseResult.SelectedSection == 0 && Name.StartsWith(parseResult.Sections[0].Substring(0, parseResult.CaretPositionWithinSelectedSection), StringComparison.OrdinalIgnoreCase)) + { + return new[] { Name }; + } + + if (string.Equals(Name, parseResult.Sections[0], StringComparison.OrdinalIgnoreCase) && parseResult.SelectedSection == 1 && (parseResult.Sections.Count < 2 || SubCommand.StartsWith(parseResult.Sections[1].Substring(0, parseResult.CaretPositionWithinSelectedSection), StringComparison.OrdinalIgnoreCase))) + { + return new[] { SubCommand }; + } + + return null; + } + + public bool? CanHandle(IShellState shellState, HttpState programState, ICoreParseResult parseResult) + { + return parseResult.Sections.Count > 1 && string.Equals(parseResult.Sections[0], Name, StringComparison.OrdinalIgnoreCase) && string.Equals(parseResult.Sections[1], SubCommand, StringComparison.OrdinalIgnoreCase) + ? (bool?)true + : null; + } + + public async Task ExecuteAsync(IShellState shellState, HttpState programState, ICoreParseResult parseResult, CancellationToken cancellationToken) + { + if (parseResult.Sections.Count == 2) + { + programState.SwaggerStructure = null; + return; + } + + if (parseResult.Sections.Count != 3 || string.IsNullOrEmpty(parseResult.Sections[2]) || !Uri.TryCreate(parseResult.Sections[2], UriKind.Absolute, out Uri serverUri)) + { + shellState.ConsoleManager.Error.WriteLine("Must specify a swagger document".Bold().Red()); + } + else + { + await CreateDirectoryStructureForSwaggerEndpointAsync(shellState, programState, serverUri, cancellationToken).ConfigureAwait(false); + } + } + + internal static async Task CreateDirectoryStructureForSwaggerEndpointAsync(IShellState shellState, HttpState programState, Uri serverUri, CancellationToken cancellationToken) + { + try + { + IEnumerable doc = await GetSwaggerDocAsync(programState.Client, serverUri).ConfigureAwait(false); + + DirectoryStructure d = new DirectoryStructure(null); + + foreach (EndpointMetadata entry in doc) + { + FillDirectoryInfo(d, entry); + } + + programState.SwaggerStructure = !cancellationToken.IsCancellationRequested ? d : null; + } + catch + { + programState.SwaggerStructure = null; + } + } + } +} diff --git a/src/Microsoft.HttpRepl/Commands/TreeNode.cs b/src/Microsoft.HttpRepl/Commands/TreeNode.cs new file mode 100644 index 0000000000..256d5d9dae --- /dev/null +++ b/src/Microsoft.HttpRepl/Commands/TreeNode.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; + +namespace Microsoft.HttpRepl.Commands +{ + public class TreeNode + { + private readonly int _depth; + private readonly Formatter _formatter; + private readonly string _prefix; + private readonly string _entry; + private readonly List _children = new List(); + + public TreeNode(Formatter formatter, string prefix, string entry) + : this(formatter, prefix, entry, 0) + { + } + + private TreeNode(Formatter formatter, string prefix, string entry, int depth) + { + _formatter = formatter; + formatter.RegisterEntry(prefix.Length, depth); + _prefix = prefix; + _entry = entry; + _depth = depth; + } + + public TreeNode AddChild(string prefix, string entry) + { + TreeNode child = new TreeNode(_formatter, prefix, entry, _depth + 1); + _children.Add(child); + return child; + } + + public override string ToString() + { + string self = _formatter.Format(_prefix, _entry, _depth); + + if (_children.Count == 0) + { + return self; + } + + return self + Environment.NewLine + string.Join(Environment.NewLine, _children); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.HttpRepl/Commands/UICommand.cs b/src/Microsoft.HttpRepl/Commands/UICommand.cs new file mode 100644 index 0000000000..ddf4ca4097 --- /dev/null +++ b/src/Microsoft.HttpRepl/Commands/UICommand.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Repl; +using Microsoft.Repl.Commanding; +using Microsoft.Repl.ConsoleHandling; +using Microsoft.Repl.Parsing; + +namespace Microsoft.HttpRepl.Commands +{ + public class UICommand : ICommand + { + private static readonly string Name = "ui"; + + public bool? CanHandle(IShellState shellState, HttpState programState, ICoreParseResult parseResult) + { + return parseResult.Sections.Count == 1 && string.Equals(parseResult.Sections[0], Name) + ? (bool?)true + : null; + } + + public Task ExecuteAsync(IShellState shellState, HttpState programState, ICoreParseResult parseResult, CancellationToken cancellationToken) + { + if (programState.BaseAddress == null) + { + shellState.ConsoleManager.Error.WriteLine("Must be connected to a server to launch Swagger UI".Bold().Red()); + return Task.CompletedTask; + } + + Uri uri = new Uri(programState.BaseAddress, "swagger"); + string agent = "cmd"; + string agentParam = $"/c {uri.AbsoluteUri}"; + + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + agent = "open"; + agentParam = uri.AbsoluteUri; + } + else if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + agent = "xdg-open"; + agentParam = uri.AbsoluteUri; + } + + Process.Start(new ProcessStartInfo(agent, agentParam) { CreateNoWindow = true }); + return Task.CompletedTask; + } + + public string GetHelpDetails(IShellState shellState, HttpState programState, ICoreParseResult parseResult) + { + if (parseResult.Sections.Count == 1 && string.Equals(parseResult.Sections[0], Name)) + { + return "ui - Launches the Swagger UI page (if available) in the default browser"; + } + + return null; + } + + public string GetHelpSummary(IShellState shellState, HttpState programState) + { + return "ui - Launches the Swagger UI page (if available) in the default browser"; + } + + public IEnumerable Suggest(IShellState shellState, HttpState programState, ICoreParseResult parseResult) + { + if (parseResult.SelectedSection == 0 && + (string.IsNullOrEmpty(parseResult.Sections[parseResult.SelectedSection]) || Name.StartsWith(parseResult.Sections[0].Substring(0, parseResult.CaretPositionWithinSelectedSection), StringComparison.OrdinalIgnoreCase))) + { + return new[] { Name }; + } + + return null; + } + } +} diff --git a/src/Microsoft.HttpRepl/Diagnostics/ConfigItem.cs b/src/Microsoft.HttpRepl/Diagnostics/ConfigItem.cs new file mode 100644 index 0000000000..ba16701d58 --- /dev/null +++ b/src/Microsoft.HttpRepl/Diagnostics/ConfigItem.cs @@ -0,0 +1,9 @@ +namespace Microsoft.HttpRepl.Diagnostics +{ + public class ConfigItem + { + public string Key { get; set; } + + public string Value { get; set; } + } +} diff --git a/src/Microsoft.HttpRepl/Diagnostics/DiagEndpoint.cs b/src/Microsoft.HttpRepl/Diagnostics/DiagEndpoint.cs new file mode 100644 index 0000000000..80e619dcc3 --- /dev/null +++ b/src/Microsoft.HttpRepl/Diagnostics/DiagEndpoint.cs @@ -0,0 +1,9 @@ +namespace Microsoft.HttpRepl.Diagnostics +{ + public class DiagEndpoint + { + public string DisplayName { get; set; } + public string Url { get; set; } + public DiagEndpointMetadata[] Metadata { get; set; } + } +} diff --git a/src/Microsoft.HttpRepl/Diagnostics/DiagEndpointMetadata.cs b/src/Microsoft.HttpRepl/Diagnostics/DiagEndpointMetadata.cs new file mode 100644 index 0000000000..53e5aa3918 --- /dev/null +++ b/src/Microsoft.HttpRepl/Diagnostics/DiagEndpointMetadata.cs @@ -0,0 +1,9 @@ +namespace Microsoft.HttpRepl.Diagnostics +{ + public class DiagEndpointMetadata + { + public object Item { get; set; } + public string Type { get; set; } + public string[] Interfaces { get; set; } + } +} diff --git a/src/Microsoft.HttpRepl/Diagnostics/DiagItem.cs b/src/Microsoft.HttpRepl/Diagnostics/DiagItem.cs new file mode 100644 index 0000000000..c0fbf2df9c --- /dev/null +++ b/src/Microsoft.HttpRepl/Diagnostics/DiagItem.cs @@ -0,0 +1,11 @@ +namespace Microsoft.HttpRepl.Diagnostics +{ + public class DiagItem + { + public string DisplayName { get; set; } + + public string Description { get; set; } + + public string Url { get; set; } + } +} diff --git a/src/Microsoft.HttpRepl/Diagnostics/DiagnosticsState.cs b/src/Microsoft.HttpRepl/Diagnostics/DiagnosticsState.cs new file mode 100644 index 0000000000..3fe27cce19 --- /dev/null +++ b/src/Microsoft.HttpRepl/Diagnostics/DiagnosticsState.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; + +namespace Microsoft.HttpRepl.Diagnostics +{ + public class DiagnosticsState + { + public string DiagnosticsEndpoint { get; set; } + + public IReadOnlyList DiagnosticItems { get; internal set; } + + public IDirectoryStructure DiagEndpointsStructure { get; set; } + } +} diff --git a/src/Microsoft.HttpRepl/DirectoryStructure.cs b/src/Microsoft.HttpRepl/DirectoryStructure.cs new file mode 100644 index 0000000000..f682488b9b --- /dev/null +++ b/src/Microsoft.HttpRepl/DirectoryStructure.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.HttpRepl +{ + public class DirectoryStructure : IDirectoryStructure + { + private readonly Dictionary _childDirectories = new Dictionary(StringComparer.OrdinalIgnoreCase); + + public DirectoryStructure(IDirectoryStructure parent) + { + Parent = parent; + } + + public IEnumerable DirectoryNames => _childDirectories.Keys; + + public IDirectoryStructure Parent { get; } + + public DirectoryStructure DeclareDirectory(string name) + { + if (_childDirectories.TryGetValue(name, out DirectoryStructure existing)) + { + return existing; + } + + return _childDirectories[name] = new DirectoryStructure(this); + } + + public IDirectoryStructure GetChildDirectory(string name) + { + if (_childDirectories.TryGetValue(name, out DirectoryStructure result)) + { + return result; + } + + IDirectoryStructure parameterizedTarget = _childDirectories.FirstOrDefault(x => x.Key.StartsWith('{') && x.Key.EndsWith('}')).Value; + + if (!(parameterizedTarget is null)) + { + return parameterizedTarget; + } + + return new DirectoryStructure(this); + } + + public IRequestInfo RequestInfo { get; set; } + } + + public class RequestInfo : IRequestInfo + { + private readonly HashSet _methods = new HashSet(StringComparer.OrdinalIgnoreCase); + private readonly Dictionary> _requestBodiesByMethodByContentType = new Dictionary>(StringComparer.OrdinalIgnoreCase); + private readonly Dictionary _fallbackBodyStringsByMethod = new Dictionary(StringComparer.OrdinalIgnoreCase); + private readonly Dictionary> _contentTypesByMethod = new Dictionary>(StringComparer.OrdinalIgnoreCase); + + public IReadOnlyList Methods => _methods.ToList(); + + public IReadOnlyDictionary> ContentTypesByMethod => _contentTypesByMethod; + + public string GetRequestBodyForContentType(string contentType, string method) + { + if (_requestBodiesByMethodByContentType.TryGetValue(method, out Dictionary bodiesByContentType) + && bodiesByContentType.TryGetValue(contentType, out string body)) + { + return body; + } + + if (_fallbackBodyStringsByMethod.TryGetValue(method, out body)) + { + return body; + } + + return null; + } + + public void SetRequestBody(string method, string contentType, string body) + { + if (!_requestBodiesByMethodByContentType.TryGetValue(method, out Dictionary bodiesByContentType)) + { + _requestBodiesByMethodByContentType[method] = bodiesByContentType = new Dictionary(StringComparer.OrdinalIgnoreCase); + } + + if (!_contentTypesByMethod.TryGetValue(method, out IReadOnlyList contentTypesRaw)) + { + _contentTypesByMethod[method] = contentTypesRaw = new List(); + } + + List contentTypes = (List)contentTypesRaw; + contentTypes.Add(contentType); + + bodiesByContentType[contentType] = body; + } + + public void AddMethod(string method) + { + _methods.Add(method); + } + + public void SetFallbackRequestBody(string method, string fallbackBodyString) + { + _fallbackBodyStringsByMethod[method] = fallbackBodyString; + } + } +} diff --git a/src/Microsoft.HttpRepl/DirectoryStructureExtensions.cs b/src/Microsoft.HttpRepl/DirectoryStructureExtensions.cs new file mode 100644 index 0000000000..2b7ad5341a --- /dev/null +++ b/src/Microsoft.HttpRepl/DirectoryStructureExtensions.cs @@ -0,0 +1,57 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.HttpRepl +{ + public static class DirectoryStructureExtensions + { + public static IEnumerable GetDirectoryListingAtPath(this IDirectoryStructure structure, string path) + { + return structure.TraverseTo(path).DirectoryNames; + } + + public static IDirectoryStructure TraverseTo(this IDirectoryStructure structure, string path) + { + string[] parts = path.Replace('\\', '/').Split('/'); + return structure.TraverseTo(parts); + } + + public static IDirectoryStructure TraverseTo(this IDirectoryStructure structure, IEnumerable pathParts) + { + IDirectoryStructure s = structure; + IReadOnlyList parts = pathParts.ToList(); + + if (parts.Count == 0) + { + return s; + } + + if (parts[0] == string.Empty && parts.Count > 1) + { + while (s.Parent != null) + { + s = s.Parent; + } + } + + foreach (string part in parts) + { + if (part == ".") + { + continue; + } + + if (part == "..") + { + s = s.Parent ?? s; + } + else if (!string.IsNullOrEmpty(part)) + { + s = s.GetChildDirectory(part); + } + } + + return s; + } + } +} diff --git a/src/Microsoft.HttpRepl/Formatting/JsonVisitor.cs b/src/Microsoft.HttpRepl/Formatting/JsonVisitor.cs new file mode 100644 index 0000000000..fa40ee62ab --- /dev/null +++ b/src/Microsoft.HttpRepl/Formatting/JsonVisitor.cs @@ -0,0 +1,111 @@ +using System.IO; +using System.Text; +using Microsoft.HttpRepl.Preferences; +using Microsoft.Repl.ConsoleHandling; +using Newtonsoft.Json; + +namespace Microsoft.HttpRepl.Formatting +{ + public static class JsonVisitor + { + public static string FormatAndColorize(IJsonConfig config, string jsonData) + { + if (jsonData == null) + { + return string.Empty; + } + + StringBuilder result = new StringBuilder(); + JsonTextReader reader = new JsonTextReader(new StringReader(jsonData)); + bool isValuePosition = false; + bool isTerminalValue = false; + bool isFirstToken = true; + + while (reader.Read()) + { + if (!isValuePosition) + { + //If we're about to write an end object/array, we shouldn't have a comma + if (reader.TokenType != JsonToken.EndArray && reader.TokenType != JsonToken.EndObject + && isTerminalValue) + { + result.Append(",".SetColor(config.CommaColor)); + } + + if (!isFirstToken) + { + result.AppendLine(); + } + } + + isFirstToken = false; + + if (!isValuePosition) + { + result.Append("".PadLeft(reader.Depth * config.IndentSize)); + } + + isTerminalValue = false; + isValuePosition = false; + JsonToken type = reader.TokenType; + + switch (type) + { + case JsonToken.StartObject: + result.Append("{".SetColor(config.ObjectBraceColor)); + break; + case JsonToken.EndObject: + result.Append("}".SetColor(config.ObjectBraceColor)); + isTerminalValue = true; + break; + case JsonToken.StartArray: + result.Append("[".SetColor(config.ArrayBraceColor)); + break; + case JsonToken.EndArray: + result.Append("]".SetColor(config.ArrayBraceColor)); + isTerminalValue = true; + break; + case JsonToken.PropertyName: + result.Append((reader.QuoteChar.ToString() + reader.Value + reader.QuoteChar).SetColor(config.NameColor) + ": ".SetColor(config.NameSeparatorColor)); + isValuePosition = true; + break; + case JsonToken.Boolean: + result.Append(reader.Value.ToString().ToLowerInvariant().SetColor(config.BoolColor)); + isTerminalValue = true; + break; + case JsonToken.Integer: + case JsonToken.Float: + result.Append(reader.Value.ToString().ToLowerInvariant().SetColor(config.NumericColor)); + isTerminalValue = true; + break; + case JsonToken.Null: + result.Append("null".SetColor(config.NullColor)); + isTerminalValue = true; + break; + case JsonToken.Comment: + result.Append(("//" + reader.Value).SetColor(config.NumericColor)); + break; + case JsonToken.String: + result.Append((reader.QuoteChar.ToString() + reader.Value + reader.QuoteChar.ToString()).SetColor(config.StringColor)); + isTerminalValue = true; + break; + case JsonToken.Raw: + case JsonToken.Date: + case JsonToken.Bytes: + case JsonToken.Undefined: + case JsonToken.None: + result.Append(reader.Value.ToString().SetColor(config.DefaultColor)); + isTerminalValue = true; + break; + case JsonToken.EndConstructor: + case JsonToken.StartConstructor: + default: + result.Append(reader.Value.ToString().SetColor(config.DefaultColor)); + break; + } + } + + return result.ToString(); + } + } +} diff --git a/src/Microsoft.HttpRepl/HttpState.cs b/src/Microsoft.HttpRepl/HttpState.cs new file mode 100644 index 0000000000..55cfdd6188 --- /dev/null +++ b/src/Microsoft.HttpRepl/HttpState.cs @@ -0,0 +1,370 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Runtime.InteropServices; +using Microsoft.HttpRepl.Diagnostics; +using Microsoft.HttpRepl.Preferences; + +namespace Microsoft.HttpRepl +{ + public class HttpState + { + private string _userProfileDir; + private string _prefsFilePath; + + public HttpClient Client { get; } + + public Stack PathSections { get; } + + public IDirectoryStructure SwaggerStructure { get; set; } + + public IDirectoryStructure Structure => DiagnosticsState.DiagEndpointsStructure == null + ? SwaggerStructure + : SwaggerStructure == null + ? DiagnosticsState.DiagEndpointsStructure + : new AggregateDirectoryStructure(SwaggerStructure, DiagnosticsState.DiagEndpointsStructure); + + public Uri BaseAddress { get; set; } + + public bool EchoRequest { get; set; } + + public Dictionary Preferences { get; } + + public IReadOnlyDictionary DefaultPreferences { get; } + + public string UserProfileDir + { + get + { + if (_userProfileDir == null) + { + bool isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + + string profileDir = Environment.GetEnvironmentVariable(isWindows + ? "USERPROFILE" + : "HOME"); + + _userProfileDir = profileDir; + } + + return _userProfileDir; + } + } + + public string PrefsFilePath => _prefsFilePath ?? (_prefsFilePath = Path.Combine(UserProfileDir, ".httpreplprefs")); + + public Dictionary> Headers { get; } + + public DiagnosticsState DiagnosticsState { get; } + + public HttpState() + { + Client = new HttpClient(); + PathSections = new Stack(); + Preferences = new Dictionary(); + DefaultPreferences = CreateDefaultPreferencs(); + Headers = new Dictionary>(StringComparer.OrdinalIgnoreCase) + { + { "User-Agent", new[] { "HTTP-REPL" } } + }; + Preferences = new Dictionary(DefaultPreferences); + LoadPreferences(); + DiagnosticsState = new DiagnosticsState(); + } + + public string GetPrompt() + { + return $"{GetEffectivePath(new string[0], false, out int _)?.ToString() ?? "(Disconnected)"}~ "; + } + + private void LoadPreferences() + { + if (File.Exists(PrefsFilePath)) + { + string[] prefsFile = File.ReadAllLines(PrefsFilePath); + + foreach (string line in prefsFile) + { + int equalsIndex = line.IndexOf('='); + + if (equalsIndex < 0) + { + continue; + } + + Preferences[line.Substring(0, equalsIndex)] = line.Substring(equalsIndex + 1); + } + } + } + + private IReadOnlyDictionary CreateDefaultPreferencs() + { + return new Dictionary + { + { WellKnownPreference.ProtocolColor, "BoldGreen" }, + { WellKnownPreference.StatusColor, "BoldYellow" }, + + { WellKnownPreference.JsonArrayBraceColor, "BoldCyan" }, + { WellKnownPreference.JsonCommaColor, "BoldYellow" }, + { WellKnownPreference.JsonNameColor, "BoldMagenta" }, + { WellKnownPreference.JsonNameSeparatorColor, "BoldWhite" }, + { WellKnownPreference.JsonObjectBraceColor, "Cyan" }, + { WellKnownPreference.JsonColor, "Green" } + }; + } + + public bool SavePreferences() + { + List lines = new List(); + foreach (KeyValuePair entry in Preferences.OrderBy(x => x.Key)) + { + //If the value didn't exist in the defaults or the value's different, include it in the user's preferences file + if (!DefaultPreferences.TryGetValue(entry.Key, out string value) || !string.Equals(value, entry.Value, StringComparison.Ordinal)) + { + lines.Add($"{entry.Key}={entry.Value}"); + } + } + + try + { + File.WriteAllLines(PrefsFilePath, lines); + return true; + } + catch + { + return false; + } + } + + public string GetExampleBody(string path, string contentType, string method) + { + Uri effectivePath = GetEffectivePath(path); + string rootRelativePath = effectivePath.LocalPath.Substring(BaseAddress.LocalPath.Length).TrimStart('/'); + IDirectoryStructure structure = SwaggerStructure?.TraverseTo(rootRelativePath); + return structure?.RequestInfo?.GetRequestBodyForContentType(contentType, method); + } + + public IEnumerable GetApplicableContentTypes(string method, string path) + { + Uri effectivePath = GetEffectivePath(path); + string rootRelativePath = effectivePath.LocalPath.Substring(BaseAddress.LocalPath.Length).TrimStart('/'); + IDirectoryStructure structure = SwaggerStructure?.TraverseTo(rootRelativePath); + IReadOnlyDictionary> contentTypesByMethod = structure?.RequestInfo?.ContentTypesByMethod; + + if (contentTypesByMethod != null) + { + if (method is null) + { + return contentTypesByMethod.Values.SelectMany(x => x).Distinct(StringComparer.OrdinalIgnoreCase); + } + + if (contentTypesByMethod.TryGetValue(method, out IReadOnlyList contentTypes)) + { + return contentTypes; + } + } + + return null; + } + + public Uri GetEffectivePath(string commandSpecifiedPath) + { + if (Uri.TryCreate(commandSpecifiedPath, UriKind.Absolute, out Uri absoluteUri)) + { + return absoluteUri; + } + + UriBuilder builder = new UriBuilder(BaseAddress); + string path = string.Join('/', PathSections.Reverse()); + string[] parts = path.Split('?'); + string query = null; + string query2 = null; + + if (parts.Length > 1) + { + path = parts[0]; + query = string.Join('?', parts.Skip(1)); + } + + builder.Path += path; + + if (commandSpecifiedPath.Length > 0) + { + if (commandSpecifiedPath[0] != '/') + { + string argPath = commandSpecifiedPath; + if (builder.Path.Length > 0 && builder.Path[builder.Path.Length - 1] != '/') + { + argPath = "/" + argPath; + } + + int queryIndex = argPath.IndexOf('?'); + path = argPath; + + if (queryIndex > -1) + { + query2 = argPath.Substring(queryIndex + 1); + path = argPath.Substring(0, queryIndex); + } + + builder.Path += path; + } + else + { + int queryIndex = commandSpecifiedPath.IndexOf('?'); + path = commandSpecifiedPath; + + if (queryIndex > -1) + { + query2 = commandSpecifiedPath.Substring(queryIndex + 1); + path = commandSpecifiedPath.Substring(0, queryIndex); + } + + builder.Path = path; + } + } + else + { + + int queryIndex = commandSpecifiedPath.IndexOf('?'); + path = commandSpecifiedPath; + + if (queryIndex > -1) + { + query2 = commandSpecifiedPath.Substring(queryIndex + 1); + path = commandSpecifiedPath.Substring(0, queryIndex); + } + + builder.Path += path; + } + + if (query != null) + { + if (!string.IsNullOrEmpty(builder.Query)) + { + query = "&" + query; + } + + builder.Query += query; + } + + if (query2 != null) + { + if (!string.IsNullOrEmpty(builder.Query)) + { + query2 = "&" + query2; + } + + builder.Query += query2; + } + + return builder.Uri; + } + + public Uri GetEffectivePath(IReadOnlyList sections, bool requiresBody, out int filePathIndex) + { + filePathIndex = 1; + + if (BaseAddress == null) + { + return null; + } + + UriBuilder builder = new UriBuilder(BaseAddress); + string path = string.Join('/', PathSections.Reverse()); + string[] parts = path.Split('?'); + string query = null; + string query2 = null; + + if (parts.Length > 1) + { + path = parts[0]; + query = string.Join('?', parts.Skip(1)); + } + + builder.Path += path; + + if (sections.Count > 1) + { + if (!requiresBody || !File.Exists(sections[1])) + { + if (sections[1].Length > 0) + { + if (sections[1][0] != '/') + { + string argPath = sections[1]; + if (builder.Path.Length > 0 && builder.Path[builder.Path.Length - 1] != '/') + { + argPath = "/" + argPath; + } + + int queryIndex = argPath.IndexOf('?'); + path = argPath; + + if (queryIndex > -1) + { + query2 = argPath.Substring(queryIndex + 1); + path = argPath.Substring(0, queryIndex); + } + + builder.Path += path; + } + else + { + int queryIndex = sections[1].IndexOf('?'); + path = sections[1]; + + if (queryIndex > -1) + { + query2 = sections[1].Substring(queryIndex + 1); + path = sections[1].Substring(0, queryIndex); + } + + builder.Path = path; + } + } + else + { + + int queryIndex = sections[1].IndexOf('?'); + path = sections[1]; + + if (queryIndex > -1) + { + query2 = sections[1].Substring(queryIndex + 1); + path = sections[1].Substring(0, queryIndex); + } + + builder.Path += path; + } + + filePathIndex = 2; + } + } + + if (query != null) + { + if (!string.IsNullOrEmpty(builder.Query)) + { + query = "&" + query; + } + + builder.Query += query; + } + + if (query2 != null) + { + if (!string.IsNullOrEmpty(builder.Query)) + { + query2 = "&" + query2; + } + + builder.Query += query2; + } + + return builder.Uri; + } + } +} diff --git a/src/Microsoft.HttpRepl/IDirectoryStructure.cs b/src/Microsoft.HttpRepl/IDirectoryStructure.cs new file mode 100644 index 0000000000..5c02e8e476 --- /dev/null +++ b/src/Microsoft.HttpRepl/IDirectoryStructure.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; + +namespace Microsoft.HttpRepl +{ + public interface IDirectoryStructure + { + IEnumerable DirectoryNames { get; } + + IDirectoryStructure Parent { get; } + + IDirectoryStructure GetChildDirectory(string name); + + IRequestInfo RequestInfo { get; } + } + + public interface IRequestInfo + { + IReadOnlyDictionary> ContentTypesByMethod { get; } + + IReadOnlyList Methods { get; } + + string GetRequestBodyForContentType(string contentType, string method); + } +} diff --git a/src/Microsoft.HttpRepl/Microsoft.HttpRepl.csproj b/src/Microsoft.HttpRepl/Microsoft.HttpRepl.csproj new file mode 100644 index 0000000000..cec7ac17d5 --- /dev/null +++ b/src/Microsoft.HttpRepl/Microsoft.HttpRepl.csproj @@ -0,0 +1,31 @@ + + + + Exe + netcoreapp2.2 + true + dotnet-httprepl + latest + Command line tool to for making HTTP calls and viewing their results. + dotnet;http;httprepl + + + true + + + + + + + + + + + + + + + + + + diff --git a/src/Microsoft.HttpRepl/OpenApi/Either.cs b/src/Microsoft.HttpRepl/OpenApi/Either.cs new file mode 100644 index 0000000000..f1e7dfacb3 --- /dev/null +++ b/src/Microsoft.HttpRepl/OpenApi/Either.cs @@ -0,0 +1,33 @@ +namespace Microsoft.HttpRepl.OpenApi +{ + public class Either + { + public Either(TOption1 option1) + { + Option1 = option1; + IsOption1 = true; + } + + public Either(TOption2 option2) + { + Option2 = option2; + IsOption1 = false; + } + + public bool IsOption1 { get; } + + public TOption1 Option1 { get; } + + public TOption2 Option2 { get; } + + public static implicit operator Either(TOption1 value) + { + return new Either(value); + } + + public static implicit operator Either(TOption2 value) + { + return new Either(value); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.HttpRepl/OpenApi/EitherConverter.cs b/src/Microsoft.HttpRepl/OpenApi/EitherConverter.cs new file mode 100644 index 0000000000..b89a85c9ef --- /dev/null +++ b/src/Microsoft.HttpRepl/OpenApi/EitherConverter.cs @@ -0,0 +1,32 @@ +using System; +using Newtonsoft.Json; + +namespace Microsoft.HttpRepl.OpenApi +{ + public class EitherConverter : JsonConverter + { + public override bool CanConvert(Type objectType) + { + return typeof(TOption1).IsAssignableFrom(objectType) || typeof(TOption2).IsAssignableFrom(objectType) || typeof(EitherConverter) == objectType; + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + try + { + TOption1 option1 = serializer.Deserialize(reader); + return new Either(option1); + } + catch + { + TOption2 option2 = serializer.Deserialize(reader); + return new Either(option2); + } + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.HttpRepl/OpenApi/EndpointMetadata.cs b/src/Microsoft.HttpRepl/OpenApi/EndpointMetadata.cs new file mode 100644 index 0000000000..e46387b3e0 --- /dev/null +++ b/src/Microsoft.HttpRepl/OpenApi/EndpointMetadata.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; + +namespace Microsoft.HttpRepl.OpenApi +{ + public class EndpointMetadata + { + public EndpointMetadata(string path, IReadOnlyDictionary>> requestsByMethodAndContentType) + { + Path = path; + AvailableRequests = requestsByMethodAndContentType ?? new Dictionary>>(); + } + + public string Path { get; } + + public IReadOnlyDictionary>> AvailableRequests { get; } + } +} \ No newline at end of file diff --git a/src/Microsoft.HttpRepl/OpenApi/EndpointMetadataReader.cs b/src/Microsoft.HttpRepl/OpenApi/EndpointMetadataReader.cs new file mode 100644 index 0000000000..8635d6fbee --- /dev/null +++ b/src/Microsoft.HttpRepl/OpenApi/EndpointMetadataReader.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using Newtonsoft.Json.Linq; + +namespace Microsoft.HttpRepl.OpenApi +{ + public class EndpointMetadataReader + { + private readonly List _readers = new List + { + new OpenApiV3EndpointMetadataReader(), + new SwaggerV2EndpointMetadataReader(), + new SwaggerV1EndpointMetadataReader() + }; + + public void RegisterReader(IEndpointMetadataReader reader) + { + _readers.Add(reader); + } + + public IEnumerable Read(JObject document) + { + foreach (IEndpointMetadataReader reader in _readers) + { + if (reader.CanHandle(document)) + { + IEnumerable result = reader.ReadMetadata(document); + + if (result != null) + { + return result; + } + } + } + + return null; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.HttpRepl/OpenApi/IEndpointMetadataReader.cs b/src/Microsoft.HttpRepl/OpenApi/IEndpointMetadataReader.cs new file mode 100644 index 0000000000..287740c7af --- /dev/null +++ b/src/Microsoft.HttpRepl/OpenApi/IEndpointMetadataReader.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using Newtonsoft.Json.Linq; + +namespace Microsoft.HttpRepl.OpenApi +{ + public interface IEndpointMetadataReader + { + bool CanHandle(JObject document); + + IEnumerable ReadMetadata(JObject document); + } +} \ No newline at end of file diff --git a/src/Microsoft.HttpRepl/OpenApi/OpenApiV3EndpointMetadataReader.cs b/src/Microsoft.HttpRepl/OpenApi/OpenApiV3EndpointMetadataReader.cs new file mode 100644 index 0000000000..5fa968efc6 --- /dev/null +++ b/src/Microsoft.HttpRepl/OpenApi/OpenApiV3EndpointMetadataReader.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json.Linq; + +namespace Microsoft.HttpRepl.OpenApi +{ + public class OpenApiV3EndpointMetadataReader : IEndpointMetadataReader + { + public bool CanHandle(JObject document) + { + return (document["openapi"]?.ToString() ?? "").StartsWith("3.", StringComparison.Ordinal); + } + + public IEnumerable ReadMetadata(JObject document) + { + List metadata = new List(); + + if (document["paths"] is JObject paths) + { + foreach (JProperty path in paths.Properties()) + { + if (!(path.Value is JObject pathBody)) + { + continue; + } + + Dictionary>> requestMethods = new Dictionary>>(StringComparer.OrdinalIgnoreCase); + + foreach (JProperty method in pathBody.Properties()) + { + List parameters = new List(); + + if (method.Value is JObject methodBody) + { + if (methodBody["parameters"] is JArray parametersArray) + { + foreach (JObject parameterObj in parametersArray.OfType()) + { + Parameter p = parameterObj.ToObject(); + p.Location = parameterObj["in"]?.ToString(); + + if (!(parameterObj["schema"] is JObject schemaObject)) + { + schemaObject = null; + } + + p.Schema = schemaObject?.ToObject() ?? parameterObj.ToObject(); + parameters.Add(p); + } + } + + if (methodBody["requestBody"] is JObject bodyObject) + { + if (!(bodyObject["content"] is JObject contentTypeLookup)) + { + continue; + } + + foreach (JProperty contentTypeEntry in contentTypeLookup.Properties()) + { + List parametersByContentType = new List(parameters); + Parameter p = bodyObject.ToObject(); + p.Location = "body"; + p.IsRequired = bodyObject["required"]?.ToObject() ?? false; + + if (!(bodyObject["schema"] is JObject schemaObject)) + { + schemaObject = null; + } + + p.Schema = schemaObject?.ToObject() ?? bodyObject.ToObject(); + parametersByContentType.Add(p); + + Dictionary> bucketByMethod; + if (!requestMethods.TryGetValue(method.Name, out IReadOnlyDictionary> bucketByMethodRaw)) + { + requestMethods[method.Name] = bucketByMethodRaw = new Dictionary>(StringComparer.OrdinalIgnoreCase) + { + { "", parametersByContentType } + }; + } + + bucketByMethod = (Dictionary>)bucketByMethodRaw; + bucketByMethod[contentTypeEntry.Name] = parametersByContentType; + } + } + } + } + + metadata.Add(new EndpointMetadata(path.Name, requestMethods)); + } + } + + return metadata; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.HttpRepl/OpenApi/Parameter.cs b/src/Microsoft.HttpRepl/OpenApi/Parameter.cs new file mode 100644 index 0000000000..faf21e5f9a --- /dev/null +++ b/src/Microsoft.HttpRepl/OpenApi/Parameter.cs @@ -0,0 +1,13 @@ +namespace Microsoft.HttpRepl.OpenApi +{ + public class Parameter + { + public string Name { get; set; } + + public string Location { get; set; } + + public bool IsRequired { get; set; } + + public Schema Schema { get; set; } + } +} \ No newline at end of file diff --git a/src/Microsoft.HttpRepl/OpenApi/PointerUtil.cs b/src/Microsoft.HttpRepl/OpenApi/PointerUtil.cs new file mode 100644 index 0000000000..b01730b116 --- /dev/null +++ b/src/Microsoft.HttpRepl/OpenApi/PointerUtil.cs @@ -0,0 +1,218 @@ +using System; +using System.Globalization; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; +using Newtonsoft.Json.Linq; + +namespace Microsoft.HttpRepl.OpenApi +{ + public static class PointerUtil + { + public static Task ResolvePointersAsync(Uri loadLocation, JToken root, HttpClient client) + { + return ResolvePointersAsync(loadLocation, root, root, client); + } + + private static async Task ResolvePointersAsync(Uri loadLocation, JToken root, JToken toResolve, HttpClient client) + { + if (toResolve is JArray arr) + { + for (int i = 0; i < arr.Count; ++i) + { + arr[i] = await ResolvePointersAsync(loadLocation, root, arr[i], client).ConfigureAwait(false); + } + } + else if (toResolve is JObject obj) + { + if (obj["$ref"] is JValue refVal && refVal.Type == JTokenType.String) + { + if (!Uri.TryCreate((string)refVal.Value, UriKind.RelativeOrAbsolute, out Uri loadTarget)) + { + //TODO: Error resolving pointer (pointer must be a valid URI) + return new JValue((object)null); + } + + if (!loadTarget.IsAbsoluteUri) + { + if (!Uri.TryCreate(loadLocation, loadTarget, out loadTarget)) + { + //TODO: Error resolving pointer (could not combine with base path) + return new JValue((object)null); + } + } + + //Check to see if we're changing source documents, if we are, get it + if (!string.Equals(loadLocation.Host, loadTarget.Host, StringComparison.OrdinalIgnoreCase) || !string.Equals(loadLocation.AbsolutePath, loadTarget.AbsolutePath, StringComparison.OrdinalIgnoreCase)) + { + HttpResponseMessage responseMessage = await client.GetAsync(loadTarget).ConfigureAwait(false); + + if (!responseMessage.IsSuccessStatusCode) + { + //TODO: Error resolving pointer (could not get referenced document) + return new JValue((object)null); + } + + string responseString = await responseMessage.Content.ReadAsStringAsync().ConfigureAwait(false); + JToken newRoot; + + try + { + newRoot = JToken.Parse(responseString); + } + catch + { + //TODO: Error resolving pointer (referenced document is not valid JSON) + return new JValue((object)null); + } + + return await ResolvePointersAsync(loadTarget, newRoot, newRoot, client).ConfigureAwait(false); + } + + //We're in the right document, grab the bookmark (fragment) of the URI and get the element at that path + string fragment = loadTarget.Fragment; + + if (fragment.StartsWith('#')) + { + fragment = fragment.Substring(1); + } + + string[] parts = fragment.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); + JToken cursor = root; + + for (int i = 0; i < parts.Length; ++i) + { + if (cursor is JArray ca) + { + if (!int.TryParse(parts[i], NumberStyles.Integer, CultureInfo.InvariantCulture, out int index)) + { + //TODO: Error resolving pointer, array index is non-integral + return new JValue((object)null); + } + + if (index < 0 || index >= ca.Count) + { + //TODO: Error resolving pointer, array index is out of bounds + return new JValue((object)null); + } + + JToken val = ca[index]; + if (val is JObject vo && vo.TryGetValue("$ref", out JToken vor) && vor is JValue vorv && vorv.Type == JTokenType.String) + { + cursor = await ResolvePointersAsync(loadLocation, root, val, client).ConfigureAwait(false); + } + else + { + cursor = val; + } + } + else if (cursor is JObject co) + { + if (!co.TryGetValue(parts[i], out JToken val)) + { + //TODO: Error resolving pointer, no such property on object + return new JValue((object)null); + } + + if (val is JObject vo && vo.TryGetValue("$ref", out JToken vor) && vor is JValue vorv && vorv.Type == JTokenType.String) + { + cursor = await ResolvePointersAsync(loadLocation, root, val, client).ConfigureAwait(false); + } + else + { + cursor = val; + } + } + else + { + //TODO: Error resolving pointer, cannot index into literal + return new JValue((object)null); + } + } + + cursor = await ResolvePointersAsync(loadLocation, root, cursor, client); + return cursor.DeepClone(); + } + + foreach (JProperty property in obj.Properties().ToList()) + { + obj[property.Name] = await ResolvePointersAsync(loadLocation, root, property.Value, client).ConfigureAwait(false); + } + } + + return toResolve; + } + + //public static async Task ResolvePointerAsync(this JToken root, HttpClient client, string pointer) + //{ + // if (!pointer.StartsWith("#/", StringComparison.Ordinal)) + // { + // HttpResponseMessage response = await client.GetAsync(pointer).ConfigureAwait(false); + + // if (!response.IsSuccessStatusCode) + // { + // //TODO: Failed to resolve pointer message + // return new JValue((object)null); + // } + + // string responseString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + + // try + // { + // root = JToken.Parse(responseString); + // int hashIndex = pointer.IndexOf("#"); + + // if (hashIndex < 0) + // { + // return root; + // } + + // pointer = pointer.Substring(hashIndex); + // } + // catch (Exception ex) + // { + // //TODO: Failed to deserialize pointer message + // return new JValue((object)null); + // } + // } + + // string[] pointerParts = pointer.Split('/'); + + // for (int i = 1; !(root is null) && i < pointerParts.Length; ++i) + // { + // if (root is JArray arr) + // { + // if (!int.TryParse(pointerParts[i], System.Globalization.NumberStyles.Integer, System.Globalization.CultureInfo.InvariantCulture, out int result) || result < 0 || result >= arr.Count) + // { + // //TODO: Failed to resolve pointer part message (non-integer index to array or index out of range) + // return null; + // } + + // root = arr[result]; + // } + // else if (root is JObject obj) + // { + // root = obj[pointerParts[i]]; + + // if (root is null) + // { + // //TODO: Failed to resolve pointer part message (no such path in object) + // } + + // JToken nestedRef = root["$ref"]; + // if (nestedRef is JValue value && value.Type == JTokenType.String) + // { + // root = await ResolvePointerAsync(root, ) + // } + // } + // else + // { + // //TODO: Failed to resolve pointer part message (pathing into literal) + // return null; + // } + // } + + // return root; + //} + } +} \ No newline at end of file diff --git a/src/Microsoft.HttpRepl/OpenApi/Schema.cs b/src/Microsoft.HttpRepl/OpenApi/Schema.cs new file mode 100644 index 0000000000..028c0b13ca --- /dev/null +++ b/src/Microsoft.HttpRepl/OpenApi/Schema.cs @@ -0,0 +1,125 @@ +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Microsoft.HttpRepl.OpenApi +{ + public class Schema + { + public void PrepareForUsage(JToken document) + { + AdditionalProperties?.Option1?.PrepareForUsage(document); + + if (AllOf != null) + { + for (int i = 0; i < AllOf.Length; ++i) + { + AllOf[i].PrepareForUsage(document); + } + } + + if (AnyOf != null) + { + for (int i = 0; i < AnyOf.Length; ++i) + { + AnyOf[i].PrepareForUsage(document); + } + } + + if (OneOf != null) + { + for (int i = 0; i < OneOf.Length; ++i) + { + OneOf[i].PrepareForUsage(document); + } + } + + if (Properties != null) + { + IReadOnlyList keys = Properties.Keys.ToList(); + for (int i = 0; i < keys.Count; ++i) + { + Properties[keys[i]]?.PrepareForUsage(document); + } + } + + Items?.PrepareForUsage(document); + Not?.PrepareForUsage(document); + + if (Required?.Option1 != null) + { + if (Properties != null) + { + foreach (string propertyName in Required.Option1) + { + if (Properties.TryGetValue(propertyName, out Schema value)) + { + value.Required = true; + } + } + } + + Required = false; + } + } + + [JsonConverter(typeof(EitherConverter))] + public Either AdditionalProperties { get; set; } + + public Schema[] AllOf { get; set; } + + public Schema[] AnyOf { get; set; } + + public object Default { get; set; } + + public string Description { get; set; } + + public object[] Enum { get; set; } + + public object Example { get; set; } + + public bool ExclusiveMaximum { get; set; } + + public bool ExclusiveMinimum { get; set; } + + public string Format { get; set; } + + public Schema Items { get; set; } + + public double? Maximum { get; set; } + + public double? Minimum { get; set; } + + public int? MaxItems { get; set; } + + public int? MinItems { get; set; } + + public int? MaxLength { get; set; } + + public int? MinLength { get; set; } + + public int? MaxProperties { get; set; } + + public int? MinProperties { get; set; } + + public double? MultipleOf { get; set; } + + public Schema Not { get; set; } + + public Schema[] OneOf { get; set; } + + public string Pattern { get; set; } + + public Dictionary Properties { get; set; } + + [JsonConverter(typeof(EitherConverter))] + public Either Required { get; set; } + + public string Title { get; set; } + + public string Type { get; set; } + + public bool UniqueItems { get; set; } + } +} \ No newline at end of file diff --git a/src/Microsoft.HttpRepl/OpenApi/SwaggerV1EndpointMetadataReader.cs b/src/Microsoft.HttpRepl/OpenApi/SwaggerV1EndpointMetadataReader.cs new file mode 100644 index 0000000000..7f4fef4fb1 --- /dev/null +++ b/src/Microsoft.HttpRepl/OpenApi/SwaggerV1EndpointMetadataReader.cs @@ -0,0 +1,106 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json.Linq; + +namespace Microsoft.HttpRepl.OpenApi +{ + public class SwaggerV1EndpointMetadataReader : IEndpointMetadataReader + { + public bool CanHandle(JObject document) + { + return (document["swaggerVersion"]?.ToString() ?? "").StartsWith("1.", StringComparison.Ordinal); + } + + public IEnumerable ReadMetadata(JObject document) + { + List metadata = new List(); + + if (!(document["consumes"] is JArray globalConsumes)) + { + globalConsumes = new JArray(); + } + + if (document["apis"] is JObject obj) + { + foreach (JProperty property in obj.Properties()) + { + string path = obj["path"]?.ToString(); + + if (path is null) + { + continue; + } + + Dictionary>> requestMethods = new Dictionary>>(StringComparer.Ordinal); + + if (obj["operations"] is JArray operations) + { + foreach (JObject operationObject in operations.OfType()) + { + string method = operationObject["method"]?.ToString(); + List parameters = new List(); + + if (operationObject["parameters"] is JArray parametersArray) + { + foreach (JObject parameterObj in parametersArray.OfType()) + { + Parameter p = parameterObj.ToObject(); + p.Location = parameterObj["paramType"]?.ToString(); + p.IsRequired = parameterObj["required"]?.ToObject() ?? false; + + string type = parameterObj["type"]?.ToString(); + + if (type is null) + { + continue; + } + + switch (type.ToUpperInvariant()) + { + case "INTEGER": + case "NUMBER": + case "STRING": + case "BOOLEAN": + p.Schema = new Schema { Type = type }; + break; + case "FILE": + break; + default: + if (document["models"]?[type] is JObject schemaObject) + { + //TODO: Handle subtypes (https://github.com/OAI/OpenAPI-Specification/blob/master/versions/1.2.md#527-model-object) + p.Schema = schemaObject.ToObject(); + } + break; + } + + parameters.Add(p); + } + } + + if (!(operationObject["consumes"] is JArray consumes)) + { + consumes = globalConsumes; + } + + Dictionary> parametersByContentType = new Dictionary>(StringComparer.OrdinalIgnoreCase) + { + { "", parameters } + }; + + foreach (JValue value in consumes.OfType().Where(x => x.Type == JTokenType.String)) + { + parametersByContentType[value.ToString()] = parameters; + } + } + } + + metadata.Add(new EndpointMetadata(path, requestMethods)); + } + } + + return metadata; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.HttpRepl/OpenApi/SwaggerV2EndpointMetadataReader.cs b/src/Microsoft.HttpRepl/OpenApi/SwaggerV2EndpointMetadataReader.cs new file mode 100644 index 0000000000..5e7ecfe6aa --- /dev/null +++ b/src/Microsoft.HttpRepl/OpenApi/SwaggerV2EndpointMetadataReader.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json.Linq; + +namespace Microsoft.HttpRepl.OpenApi +{ + public class SwaggerV2EndpointMetadataReader : IEndpointMetadataReader + { + public bool CanHandle(JObject document) + { + return (document["swagger"]?.ToString() ?? "").StartsWith("2.", StringComparison.Ordinal); + } + + public IEnumerable ReadMetadata(JObject document) + { + List metadata = new List(); + + if (!(document["consumes"] is JArray globalConsumes)) + { + globalConsumes = new JArray(); + } + + if (document["paths"] is JObject obj) + { + foreach (JProperty property in obj.Properties()) + { + if (!(property.Value is JObject requestMethodInfos)) + { + continue; + } + + Dictionary>> requestMethods = new Dictionary>>(StringComparer.Ordinal); + + foreach (JProperty methodInfo in requestMethodInfos.Properties()) + { + List parameters = new List(); + + if (methodInfo.Value is JObject methodInfoDescription) + { + if (methodInfoDescription["parameters"] is JArray parametersArray) + { + foreach (JObject parameterObj in parametersArray.OfType()) + { + //TODO: Resolve refs here + + Parameter p = parameterObj.ToObject(); + p.Location = parameterObj["in"]?.ToString(); + p.IsRequired = parameterObj["required"]?.ToObject() ?? false; + + if (!(parameterObj["schema"] is JObject schemaObject)) + { + schemaObject = null; + } + + p.Schema = schemaObject?.ToObject() ?? parameterObj.ToObject(); + parameters.Add(p); + } + } + + if (!(methodInfoDescription["consumes"] is JArray consumes)) + { + consumes = globalConsumes; + } + + Dictionary> parametersByContentType = new Dictionary>(StringComparer.OrdinalIgnoreCase) + { + { "", parameters } + }; + + foreach (JValue value in consumes.OfType().Where(x => x.Type == JTokenType.String)) + { + parametersByContentType[value.ToString()] = parameters; + } + + requestMethods[methodInfo.Name] = parametersByContentType; + } + } + + metadata.Add(new EndpointMetadata(property.Name, requestMethods)); + } + } + + return metadata; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.HttpRepl/Preferences/IJsonConfig.cs b/src/Microsoft.HttpRepl/Preferences/IJsonConfig.cs new file mode 100644 index 0000000000..21918e44ef --- /dev/null +++ b/src/Microsoft.HttpRepl/Preferences/IJsonConfig.cs @@ -0,0 +1,29 @@ +using Microsoft.Repl.ConsoleHandling; + +namespace Microsoft.HttpRepl.Preferences +{ + public interface IJsonConfig + { + int IndentSize { get; } + + AllowedColors DefaultColor { get; } + + AllowedColors ArrayBraceColor { get; } + + AllowedColors ObjectBraceColor { get; } + + AllowedColors CommaColor { get; } + + AllowedColors NameColor { get; } + + AllowedColors NameSeparatorColor { get; } + + AllowedColors BoolColor { get; } + + AllowedColors NumericColor { get; } + + AllowedColors StringColor { get; } + + AllowedColors NullColor { get; } + } +} \ No newline at end of file diff --git a/src/Microsoft.HttpRepl/Preferences/JsonConfig.cs b/src/Microsoft.HttpRepl/Preferences/JsonConfig.cs new file mode 100644 index 0000000000..a1902e245e --- /dev/null +++ b/src/Microsoft.HttpRepl/Preferences/JsonConfig.cs @@ -0,0 +1,42 @@ +using Microsoft.Repl.ConsoleHandling; + +namespace Microsoft.HttpRepl.Preferences +{ + public class JsonConfig : IJsonConfig + { + private readonly HttpState _state; + + public int IndentSize => _state.GetIntPreference(WellKnownPreference.JsonIndentSize, 2); + + public AllowedColors DefaultColor => _state.GetColorPreference(WellKnownPreference.JsonColor); + + private AllowedColors DefaultBraceColor => _state.GetColorPreference(WellKnownPreference.JsonBraceColor, DefaultSyntaxColor); + + private AllowedColors DefaultSyntaxColor => _state.GetColorPreference(WellKnownPreference.JsonSyntaxColor, DefaultColor); + + private AllowedColors DefaultLiteralColor => _state.GetColorPreference(WellKnownPreference.JsonLiteralColor, DefaultColor); + + public AllowedColors ArrayBraceColor => _state.GetColorPreference(WellKnownPreference.JsonArrayBraceColor, DefaultBraceColor); + + public AllowedColors ObjectBraceColor => _state.GetColorPreference(WellKnownPreference.JsonObjectBraceColor, DefaultBraceColor); + + public AllowedColors CommaColor => _state.GetColorPreference(WellKnownPreference.JsonCommaColor, DefaultSyntaxColor); + + public AllowedColors NameColor => _state.GetColorPreference(WellKnownPreference.JsonNameColor, StringColor); + + public AllowedColors NameSeparatorColor => _state.GetColorPreference(WellKnownPreference.JsonNameSeparatorColor, DefaultSyntaxColor); + + public AllowedColors BoolColor => _state.GetColorPreference(WellKnownPreference.JsonBoolColor, DefaultLiteralColor); + + public AllowedColors NumericColor => _state.GetColorPreference(WellKnownPreference.JsonNumericColor, DefaultLiteralColor); + + public AllowedColors StringColor => _state.GetColorPreference(WellKnownPreference.JsonStringColor, DefaultLiteralColor); + + public AllowedColors NullColor => _state.GetColorPreference(WellKnownPreference.JsonNullColor, DefaultLiteralColor); + + public JsonConfig(HttpState state) + { + _state = state; + } + } +} diff --git a/src/Microsoft.HttpRepl/Preferences/RequestConfig.cs b/src/Microsoft.HttpRepl/Preferences/RequestConfig.cs new file mode 100644 index 0000000000..a3f9b55259 --- /dev/null +++ b/src/Microsoft.HttpRepl/Preferences/RequestConfig.cs @@ -0,0 +1,46 @@ +using Microsoft.Repl.ConsoleHandling; + +namespace Microsoft.HttpRepl.Preferences +{ + public class RequestConfig : RequestOrResponseConfig + { + public RequestConfig(HttpState state) + : base(state) + { + } + + public override AllowedColors BodyColor => State.GetColorPreference(WellKnownPreference.RequestBodyColor, base.BodyColor); + + public override AllowedColors SchemeColor => State.GetColorPreference(WellKnownPreference.RequestSchemeColor, base.SchemeColor); + + public override AllowedColors HeaderKeyColor => State.GetColorPreference(WellKnownPreference.RequestHeaderKeyColor, base.HeaderKeyColor); + + public override AllowedColors HeaderSeparatorColor => State.GetColorPreference(WellKnownPreference.RequestHeaderSeparatorColor, base.HeaderSeparatorColor); + + public override AllowedColors HeaderValueSeparatorColor => State.GetColorPreference(WellKnownPreference.RequestHeaderValueSeparatorColor, base.HeaderValueSeparatorColor); + + public override AllowedColors HeaderValueColor => State.GetColorPreference(WellKnownPreference.RequestHeaderValueColor, base.HeaderValueColor); + + public override AllowedColors HeaderColor => State.GetColorPreference(WellKnownPreference.RequestHeaderColor, base.HeaderColor); + + public override AllowedColors GeneralColor => State.GetColorPreference(WellKnownPreference.RequestColor, base.GeneralColor); + + public override AllowedColors ProtocolColor => State.GetColorPreference(WellKnownPreference.RequestProtocolColor, base.ProtocolColor); + + public override AllowedColors ProtocolNameColor => State.GetColorPreference(WellKnownPreference.RequestProtocolNameColor, base.ProtocolNameColor); + + public override AllowedColors ProtocolVersionColor => State.GetColorPreference(WellKnownPreference.RequestProtocolVersionColor, base.ProtocolVersionColor); + + public override AllowedColors ProtocolSeparatorColor => State.GetColorPreference(WellKnownPreference.RequestProtocolSeparatorColor, base.ProtocolSeparatorColor); + + public override AllowedColors StatusColor => State.GetColorPreference(WellKnownPreference.RequestStatusColor, base.StatusColor); + + public override AllowedColors StatusCodeColor => State.GetColorPreference(WellKnownPreference.RequestStatusCodeColor, base.StatusCodeColor); + + public override AllowedColors StatusReasonPhraseColor => State.GetColorPreference(WellKnownPreference.RequestStatusReaseonPhraseColor, base.StatusReasonPhraseColor); + + public AllowedColors MethodColor => State.GetColorPreference(WellKnownPreference.RequestMethodColor, GeneralColor); + + public AllowedColors AddressColor => State.GetColorPreference(WellKnownPreference.RequestAddressColor, GeneralColor); + } +} diff --git a/src/Microsoft.HttpRepl/Preferences/RequestOrResponseConfig.cs b/src/Microsoft.HttpRepl/Preferences/RequestOrResponseConfig.cs new file mode 100644 index 0000000000..c509a89f5e --- /dev/null +++ b/src/Microsoft.HttpRepl/Preferences/RequestOrResponseConfig.cs @@ -0,0 +1,44 @@ +using Microsoft.Repl.ConsoleHandling; + +namespace Microsoft.HttpRepl.Preferences +{ + public abstract class RequestOrResponseConfig + { + protected HttpState State { get; } + + protected RequestOrResponseConfig(HttpState state) + { + State = state; + } + + public virtual AllowedColors BodyColor => State.GetColorPreference(WellKnownPreference.BodyColor, GeneralColor); + + public virtual AllowedColors SchemeColor => State.GetColorPreference(WellKnownPreference.SchemeColor, GeneralColor); + + public virtual AllowedColors HeaderKeyColor => State.GetColorPreference(WellKnownPreference.HeaderKeyColor, HeaderColor); + + public virtual AllowedColors HeaderSeparatorColor => State.GetColorPreference(WellKnownPreference.HeaderSeparatorColor, HeaderColor); + + public virtual AllowedColors HeaderValueSeparatorColor => State.GetColorPreference(WellKnownPreference.HeaderValueSeparatorColor, HeaderSeparatorColor); + + public virtual AllowedColors HeaderValueColor => State.GetColorPreference(WellKnownPreference.HeaderValueColor, HeaderColor); + + public virtual AllowedColors HeaderColor => State.GetColorPreference(WellKnownPreference.HeaderColor, GeneralColor); + + public virtual AllowedColors GeneralColor => State.GetColorPreference(WellKnownPreference.RequestOrResponseColor); + + public virtual AllowedColors ProtocolColor => State.GetColorPreference(WellKnownPreference.ProtocolColor, GeneralColor); + + public virtual AllowedColors ProtocolNameColor => State.GetColorPreference(WellKnownPreference.ProtocolNameColor, ProtocolColor); + + public virtual AllowedColors ProtocolVersionColor => State.GetColorPreference(WellKnownPreference.ProtocolVersionColor, ProtocolColor); + + public virtual AllowedColors ProtocolSeparatorColor => State.GetColorPreference(WellKnownPreference.ProtocolSeparatorColor, ProtocolColor); + + public virtual AllowedColors StatusColor => State.GetColorPreference(WellKnownPreference.StatusColor, GeneralColor); + + public virtual AllowedColors StatusCodeColor => State.GetColorPreference(WellKnownPreference.StatusCodeColor, StatusColor); + + public virtual AllowedColors StatusReasonPhraseColor => State.GetColorPreference(WellKnownPreference.StatusReaseonPhraseColor, StatusColor); + } +} diff --git a/src/Microsoft.HttpRepl/Preferences/ResponseConfig.cs b/src/Microsoft.HttpRepl/Preferences/ResponseConfig.cs new file mode 100644 index 0000000000..123a86ff79 --- /dev/null +++ b/src/Microsoft.HttpRepl/Preferences/ResponseConfig.cs @@ -0,0 +1,42 @@ +using Microsoft.Repl.ConsoleHandling; + +namespace Microsoft.HttpRepl.Preferences +{ + public class ResponseConfig : RequestOrResponseConfig + { + public ResponseConfig(HttpState state) + : base(state) + { + } + + public override AllowedColors BodyColor => State.GetColorPreference(WellKnownPreference.ResponseBodyColor, base.BodyColor); + + public override AllowedColors SchemeColor => State.GetColorPreference(WellKnownPreference.ResponseSchemeColor, base.SchemeColor); + + public override AllowedColors HeaderKeyColor => State.GetColorPreference(WellKnownPreference.ResponseHeaderKeyColor, base.HeaderKeyColor); + + public override AllowedColors HeaderSeparatorColor => State.GetColorPreference(WellKnownPreference.ResponseHeaderSeparatorColor, base.HeaderSeparatorColor); + + public override AllowedColors HeaderValueSeparatorColor => State.GetColorPreference(WellKnownPreference.ResponseHeaderValueSeparatorColor, base.HeaderValueSeparatorColor); + + public override AllowedColors HeaderValueColor => State.GetColorPreference(WellKnownPreference.ResponseHeaderValueColor, base.HeaderValueColor); + + public override AllowedColors HeaderColor => State.GetColorPreference(WellKnownPreference.ResponseHeaderColor, base.HeaderColor); + + public override AllowedColors GeneralColor => State.GetColorPreference(WellKnownPreference.ResponseColor, base.GeneralColor); + + public override AllowedColors ProtocolColor => State.GetColorPreference(WellKnownPreference.ResponseProtocolColor, base.ProtocolColor); + + public override AllowedColors ProtocolNameColor => State.GetColorPreference(WellKnownPreference.ResponseProtocolNameColor, base.ProtocolNameColor); + + public override AllowedColors ProtocolVersionColor => State.GetColorPreference(WellKnownPreference.ResponseProtocolVersionColor, base.ProtocolVersionColor); + + public override AllowedColors ProtocolSeparatorColor => State.GetColorPreference(WellKnownPreference.ResponseProtocolSeparatorColor, base.ProtocolSeparatorColor); + + public override AllowedColors StatusColor => State.GetColorPreference(WellKnownPreference.ResponseStatusColor, base.StatusColor); + + public override AllowedColors StatusCodeColor => State.GetColorPreference(WellKnownPreference.ResponseStatusCodeColor, base.StatusCodeColor); + + public override AllowedColors StatusReasonPhraseColor => State.GetColorPreference(WellKnownPreference.ResponseStatusReaseonPhraseColor, base.StatusReasonPhraseColor); + } +} diff --git a/src/Microsoft.HttpRepl/Preferences/WellKnownPreference.cs b/src/Microsoft.HttpRepl/Preferences/WellKnownPreference.cs new file mode 100644 index 0000000000..a6c16a6da7 --- /dev/null +++ b/src/Microsoft.HttpRepl/Preferences/WellKnownPreference.cs @@ -0,0 +1,200 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using Microsoft.Repl.ConsoleHandling; + +namespace Microsoft.HttpRepl.Preferences +{ + public static class WellKnownPreference + { + public static class Catalog + { + private static IReadOnlyList _names; + + public static IReadOnlyList Names + { + get + { + if (_names != null) + { + return _names; + } + + List matchingProperties = new List(); + + foreach (PropertyInfo property in typeof(WellKnownPreference).GetProperties(BindingFlags.Public | BindingFlags.Static)) + { + if (property.PropertyType == typeof(string) && property.GetMethod != null && property.GetValue(null) is string val) + { + matchingProperties.Add(val); + } + } + + return _names = matchingProperties; + } + } + } + + #region JSON + public static string JsonArrayBraceColor { get; } = "colors.json.arrayBrace"; + + public static string JsonObjectBraceColor { get; } = "colors.json.objectBrace"; + + public static string JsonNameColor { get; } = "colors.json.name"; + + public static string JsonNameSeparatorColor { get; } = "colors.json.nameSeparator"; + + public static string JsonIndentSize { get; } = "formatting.json.indentSize"; + + public static string JsonCommaColor { get; } = "colors.json.comma"; + + public static string JsonLiteralColor { get; } = "colors.json.literal"; + + public static string JsonNullColor { get; } = "colors.json.null"; + + public static string JsonBoolColor { get; } = "colors.json.bool"; + + public static string JsonNumericColor { get; } = "colors.json.numeric"; + + public static string JsonStringColor { get; } = "colors.json.string"; + + public static string JsonColor { get; } = "colors.json"; + + public static string JsonSyntaxColor { get; } = "colors.json.syntax"; + + public static string JsonBraceColor { get; } = "colors.json.brace"; + #endregion JSON + + public static string RequestColor { get; } = "colors.request"; + + public static string RequestBodyColor { get; } = "colors.request.body"; + + public static string RequestSchemeColor { get; } = "colors.request.scheme"; + + public static string RequestHeaderKeyColor { get; } = "colors.request.header.key"; + + public static string RequestHeaderSeparatorColor { get; } = "colors.request.header.separator"; + + public static string RequestHeaderValueSeparatorColor { get; } = "colors.request.header.valueSeparator"; + + public static string RequestHeaderValueColor { get; } = "colors.request.header.value"; + + public static string RequestHeaderColor { get; } = "colors.request.header"; + + public static string RequestProtocolColor { get; } = "colors.request.protocol"; + + public static string RequestProtocolNameColor { get; } = "colors.request.protocol.name"; + + public static string RequestProtocolSeparatorColor { get; } = "colors.request.protocol.separator"; + + public static string RequestProtocolVersionColor { get; } = "colors.request.protocol.version"; + + public static string RequestStatusColor { get; } = "colors.request.status"; + + public static string RequestStatusCodeColor { get; } = "colors.request.status.code"; + + public static string RequestStatusReaseonPhraseColor { get; } = "colors.request.status.reasonPhrase"; + + public static string RequestMethodColor { get; } = "colors.request.method"; + + public static string RequestAddressColor { get; } = "colors.request.address"; + + + public static string ResponseColor { get; } = "colors.response"; + + public static string ResponseBodyColor { get; } = "colors.response.body"; + + public static string ResponseSchemeColor { get; } = "colors.response.scheme"; + + public static string ResponseHeaderKeyColor { get; } = "colors.response.header.key"; + + public static string ResponseHeaderSeparatorColor { get; } = "colors.response.header.separator"; + + public static string ResponseHeaderValueSeparatorColor { get; } = "colors.response.header.valueSeparator"; + + public static string ResponseHeaderValueColor { get; } = "colors.response.header.value"; + + public static string ResponseHeaderColor { get; } = "colors.response.header"; + + public static string ResponseProtocolColor { get; } = "colors.response.protocol"; + + public static string ResponseProtocolNameColor { get; } = "colors.response.protocol.name"; + + public static string ResponseProtocolSeparatorColor { get; } = "colors.response.protocol.separator"; + + public static string ResponseProtocolVersionColor { get; } = "colors.response.protocol.version"; + + public static string ResponseStatusColor { get; } = "colors.response.status"; + + public static string ResponseStatusCodeColor { get; } = "colors.response.status.code"; + + public static string ResponseStatusReaseonPhraseColor { get; } = "colors.response.status.reasonPhrase"; + + + public static string RequestOrResponseColor { get; } = "colors.requestOrResponse"; + + public static string BodyColor { get; } = "colors.body"; + + public static string SchemeColor { get; } = "colors.scheme"; + + public static string HeaderKeyColor { get; } = "colors.header.key"; + + public static string HeaderSeparatorColor { get; } = "colors.header.separator"; + + public static string HeaderValueSeparatorColor { get; } = "colors.header.valueSeparator"; + + public static string HeaderValueColor { get; } = "colors.header.value"; + + public static string HeaderColor { get; } = "colors.header"; + + public static string ProtocolColor { get; } = "colors.protocol"; + + public static string ProtocolNameColor { get; } = "colors.protocol.name"; + + public static string ProtocolSeparatorColor { get; } = "colors.protocol.separator"; + + public static string ProtocolVersionColor { get; } = "colors.protocol.version"; + + public static string StatusColor { get; } = "colors.status"; + + public static string StatusCodeColor { get; } = "colors.status.code"; + + public static string StatusReaseonPhraseColor { get; } = "colors.status.reasonPhrase"; + + + public static string DefaultEditorCommand { get; } = "editor.command.default"; + + public static string DefaultEditorArguments { get; } = "editor.command.default.arguments"; + + + public static AllowedColors GetColorPreference(this HttpState programState, string preference, AllowedColors defaultvalue = AllowedColors.None) + { + if (!programState.Preferences.TryGetValue(preference, out string preferenceValueString) || !Enum.TryParse(preferenceValueString, true, out AllowedColors result)) + { + result = defaultvalue; + } + + return result; + } + + public static int GetIntPreference(this HttpState programState, string preference, int defaultValue = 0) + { + if (!programState.Preferences.TryGetValue(preference, out string preferenceValueString) || !int.TryParse(preferenceValueString, out int result)) + { + result = defaultValue; + } + + return result; + } + + public static string GetStringPreference(this HttpState programState, string preference, string defaultValue = null) + { + if (!programState.Preferences.TryGetValue(preference, out string result)) + { + result = defaultValue; + } + + return result; + } + } +} diff --git a/src/Microsoft.HttpRepl/Program.cs b/src/Microsoft.HttpRepl/Program.cs new file mode 100644 index 0000000000..f64fcf377a --- /dev/null +++ b/src/Microsoft.HttpRepl/Program.cs @@ -0,0 +1,52 @@ +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Repl; +using Microsoft.Repl.Commanding; +using Microsoft.Repl.Parsing; +using Microsoft.HttpRepl.Commands; + +namespace Microsoft.HttpRepl +{ + class Program + { + static async Task Main(string[] args) + { + var state = new HttpState(); + var dispatcher = DefaultCommandDispatcher.Create(state.GetPrompt, state); + + dispatcher.AddCommand(new ChangeDirectoryCommand()); + dispatcher.AddCommand(new ClearCommand()); + //dispatcher.AddCommand(new ConfigCommand()); + dispatcher.AddCommand(new DeleteCommand()); + dispatcher.AddCommand(new EchoCommand()); + dispatcher.AddCommand(new ExitCommand()); + dispatcher.AddCommand(new HeadCommand()); + dispatcher.AddCommand(new HelpCommand()); + dispatcher.AddCommand(new GetCommand()); + dispatcher.AddCommand(new ListCommand()); + dispatcher.AddCommand(new OptionsCommand()); + dispatcher.AddCommand(new PatchCommand()); + dispatcher.AddCommand(new PrefCommand()); + dispatcher.AddCommand(new PostCommand()); + dispatcher.AddCommand(new PutCommand()); + dispatcher.AddCommand(new RunCommand()); + dispatcher.AddCommand(new SetBaseCommand()); + dispatcher.AddCommand(new SetDiagCommand()); + dispatcher.AddCommand(new SetHeaderCommand()); + dispatcher.AddCommand(new SetSwaggerCommand()); + dispatcher.AddCommand(new UICommand()); + + CancellationTokenSource source = new CancellationTokenSource(); + var shell = new Shell(dispatcher); + shell.ShellState.ConsoleManager.AddBreakHandler(() => source.Cancel()); + if (args.Length > 0) + { + shell.ShellState.CommandDispatcher.OnReady(shell.ShellState); + shell.ShellState.InputManager.SetInput(shell.ShellState, $"set base \"{args[0]}\""); + await shell.ShellState.CommandDispatcher.ExecuteCommandAsync(shell.ShellState, CancellationToken.None).ConfigureAwait(false); + } + Task result = shell.RunAsync(source.Token); + await result.ConfigureAwait(false); + } + } +} diff --git a/src/Microsoft.HttpRepl/Properties/launchSettings.json b/src/Microsoft.HttpRepl/Properties/launchSettings.json new file mode 100644 index 0000000000..dcce8f72a3 --- /dev/null +++ b/src/Microsoft.HttpRepl/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "Microsoft.HttpRepl": { + "commandName": "Project", + "commandLineArgs": "http://localhost" + } + } +} \ No newline at end of file diff --git a/src/Microsoft.HttpRepl/Suggestions/HeaderCompletion.cs b/src/Microsoft.HttpRepl/Suggestions/HeaderCompletion.cs new file mode 100644 index 0000000000..05372d7bec --- /dev/null +++ b/src/Microsoft.HttpRepl/Suggestions/HeaderCompletion.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.HttpRepl.Suggestions +{ + public class HeaderCompletion + { + private static readonly IEnumerable CommonHeaders = new[] + { + "A-IM", + "Accept", + "Accept-Charset", + "Accept-Encoding", + "Accept-Language", + "Accept-Datetime", + "Access-Control-Request-Method", + "Access-Control-Request-Headers", + "Authorization", + "Cache-Control", + "Connection", + "Content-Length", + "Content-MD5", + "Content-Type", + "Cookie", + "Date", + "Expect", + "Forwarded", + "From", + "Host", + "If-Match", + "If-Modified-Since", + "If-None-Match", + "If-Range", + "If-Unmodified-Since", + "Max-Forwards", + "Origin", + "Pragma", + "Proxy-Authentication", + "Range", + "Referer", + "TE", + "User-Agent", + "Upgrade", + "Via", + "Warning", + //Non-standard + "Upgrade-Insecure-Requests", + "X-Requested-With", + "DNT", + "X-Forwarded-For", + "X-Forwarded-Host", + "X-Forwarded-Proto", + "Front-End-Https", + "X-Http-Method-Override", + "X-ATT-DeviceId", + "X-Wap-Profile", + "Proxy-Connection", + "X-UIDH", + "X-Csrf-Token", + "X-Request-ID", + "X-Correlation-ID" + }; + + private static readonly IReadOnlyList DefaultContentTypesList = null; + + public static IEnumerable GetCompletions(IReadOnlyCollection existingHeaders, string prefix) + { + List result = CommonHeaders.Where(x => x.StartsWith(prefix, StringComparison.OrdinalIgnoreCase) && !(existingHeaders?.Contains(x) ?? false)).ToList(); + + if (result.Count > 0) + { + return result; + } + + return null; + } + + public static IEnumerable GetValueCompletions(string method, string path, string header, string prefix, HttpState programState) + { + switch (header.ToUpperInvariant()) + { + case "CONTENT-TYPE": + IEnumerable results = programState.GetApplicableContentTypes(method, path) ?? DefaultContentTypesList; + + if (results is null) + { + return null; + } + + return results.Where(x => !string.IsNullOrEmpty(x) && x.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)); + default: + return null; + } + } + } +} diff --git a/src/Microsoft.HttpRepl/Suggestions/ServerPathCompletion.cs b/src/Microsoft.HttpRepl/Suggestions/ServerPathCompletion.cs new file mode 100644 index 0000000000..a50df8ae8d --- /dev/null +++ b/src/Microsoft.HttpRepl/Suggestions/ServerPathCompletion.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.HttpRepl.Suggestions +{ + public static class ServerPathCompletion + { + public static IEnumerable GetCompletions(HttpState programState, string normalCompletionString) + { + + //If it's an absolute URI, nothing to suggest + if (Uri.TryCreate(normalCompletionString, UriKind.Absolute, out Uri _)) + { + return null; + } + + string path = normalCompletionString.Replace('\\', '/'); + int searchFrom = normalCompletionString.Length - 1; + int lastSlash = path.LastIndexOf('/', searchFrom); + string prefix; + + if (lastSlash < 0) + { + path = string.Empty; + prefix = normalCompletionString; + } + else + { + path = path.Substring(0, lastSlash + 1); + prefix = normalCompletionString.Substring(lastSlash + 1); + } + + IDirectoryStructure s = programState.Structure.TraverseTo(programState.PathSections.Reverse()).TraverseTo(path); + + if (s?.DirectoryNames == null) + { + return null; + } + + List results = new List(); + + foreach (string child in s.DirectoryNames) + { + if (child.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) + { + results.Add(path + child); + } + } + + return results; + } + } +} diff --git a/src/Microsoft.Repl/Commanding/CommandHistory.cs b/src/Microsoft.Repl/Commanding/CommandHistory.cs new file mode 100644 index 0000000000..83c7206a9d --- /dev/null +++ b/src/Microsoft.Repl/Commanding/CommandHistory.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; + +namespace Microsoft.Repl.Commanding +{ + public class CommandHistory : ICommandHistory + { + private readonly int _maxEntries; + private readonly List _commandLines = new List(); + private int _currentCommand = -1; + private int _suspensionDepth; + + public CommandHistory(int maxEntries = 50) + { + _maxEntries = maxEntries; + } + + public void AddCommand(string command) + { + if (_suspensionDepth > 0) + { + return; + } + + _commandLines.Add(command); + if (_commandLines.Count > _maxEntries) + { + _commandLines.RemoveAt(0); + } + _currentCommand = -1; + } + + public string GetNextCommand() + { + if (_commandLines.Count == 0) + { + return string.Empty; + } + + if (_currentCommand == -1 || _currentCommand >= _commandLines.Count - 1) + { + _currentCommand = -1; + return string.Empty; + } + + return _commandLines[++_currentCommand]; + } + + public string GetPreviousCommand() + { + if (_commandLines.Count == 0) + { + return string.Empty; + } + + if (_currentCommand == -1) + { + _currentCommand = _commandLines.Count; + } + + if (_currentCommand > 0) + { + return _commandLines[--_currentCommand]; + } + + return _commandLines[0]; + } + + public IDisposable SuspendHistory() + { + ++_suspensionDepth; + return new Disposable(() => --_suspensionDepth); + } + } +} diff --git a/src/Microsoft.Repl/Commanding/CommandInputLocation.cs b/src/Microsoft.Repl/Commanding/CommandInputLocation.cs new file mode 100644 index 0000000000..1612384b61 --- /dev/null +++ b/src/Microsoft.Repl/Commanding/CommandInputLocation.cs @@ -0,0 +1,10 @@ +namespace Microsoft.Repl.Commanding +{ + public enum CommandInputLocation + { + CommandName, + Argument, + OptionName, + OptionValue + } +} \ No newline at end of file diff --git a/src/Microsoft.Repl/Commanding/CommandInputProcessingIssue.cs b/src/Microsoft.Repl/Commanding/CommandInputProcessingIssue.cs new file mode 100644 index 0000000000..501b4357da --- /dev/null +++ b/src/Microsoft.Repl/Commanding/CommandInputProcessingIssue.cs @@ -0,0 +1,15 @@ +namespace Microsoft.Repl.Commanding +{ + public class CommandInputProcessingIssue + { + public CommandInputProcessingIssueKind Kind { get; } + + public string Text { get; } + + public CommandInputProcessingIssue(CommandInputProcessingIssueKind kind, string text) + { + Kind = kind; + Text = text; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.Repl/Commanding/CommandInputProcessingIssueKind.cs b/src/Microsoft.Repl/Commanding/CommandInputProcessingIssueKind.cs new file mode 100644 index 0000000000..cd69dccb85 --- /dev/null +++ b/src/Microsoft.Repl/Commanding/CommandInputProcessingIssueKind.cs @@ -0,0 +1,11 @@ +namespace Microsoft.Repl.Commanding +{ + public enum CommandInputProcessingIssueKind + { + CommandMismatch, + ArgumentCountOutOfRange, + UnknownOption, + OptionUseCountOutOfRange, + MissingRequiredOptionInput, + } +} \ No newline at end of file diff --git a/src/Microsoft.Repl/Commanding/CommandInputSpecification.cs b/src/Microsoft.Repl/Commanding/CommandInputSpecification.cs new file mode 100644 index 0000000000..370b9f3d34 --- /dev/null +++ b/src/Microsoft.Repl/Commanding/CommandInputSpecification.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; + +namespace Microsoft.Repl.Commanding +{ + public class CommandInputSpecification + { + public IReadOnlyList CommandName { get; } + + public char OptionPreamble { get; } + + public int MinimumArguments { get; } + + public int MaximumArguments { get; } + + public IReadOnlyList Options { get; } + + public CommandInputSpecification(IReadOnlyList name, char optionPreamble, IReadOnlyList options, int minimumArgs, int maximumArgs) + { + CommandName = name; + OptionPreamble = optionPreamble; + MinimumArguments = minimumArgs; + MaximumArguments = maximumArgs; + + if (MinimumArguments < 0) + { + MinimumArguments = 0; + } + + if (MaximumArguments < MinimumArguments) + { + MaximumArguments = MinimumArguments; + } + + Options = options; + } + + public static CommandInputSpecificationBuilder Create(string baseName, params string[] additionalNameParts) + { + List nameParts = new List {baseName}; + nameParts.AddRange(additionalNameParts); + return new CommandInputSpecificationBuilder(nameParts); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.Repl/Commanding/CommandInputSpecificationBuilder.cs b/src/Microsoft.Repl/Commanding/CommandInputSpecificationBuilder.cs new file mode 100644 index 0000000000..5ad5edf345 --- /dev/null +++ b/src/Microsoft.Repl/Commanding/CommandInputSpecificationBuilder.cs @@ -0,0 +1,66 @@ +using System.Collections.Generic; + +namespace Microsoft.Repl.Commanding +{ + public class CommandInputSpecificationBuilder + { + private readonly IReadOnlyList _name; + private char _optionPreamble; + private int _minimumArgs; + private int _maximumArgs; + private readonly List _options = new List(); + + public CommandInputSpecificationBuilder(IReadOnlyList name) + { + _name = name; + _optionPreamble = '-'; + } + + public CommandInputSpecificationBuilder WithOptionPreamble(char optionChar) + { + _optionPreamble = optionChar; + return this; + } + + public CommandInputSpecificationBuilder ExactArgCount(int count) + { + _minimumArgs = count; + _maximumArgs = count; + return this; + } + + public CommandInputSpecificationBuilder MinimumArgCount(int count) + { + _minimumArgs = count; + if (_maximumArgs < count) + { + _maximumArgs = count; + } + + return this; + } + + public CommandInputSpecificationBuilder MaximumArgCount(int count) + { + _maximumArgs = count; + + if (_minimumArgs > count) + { + _minimumArgs = count; + } + + return this; + } + + public CommandInputSpecificationBuilder WithOption(CommandOptionSpecification option) + { + _options.Add(option); + return this; + } + + public CommandInputSpecification Finish() + { + return new CommandInputSpecification(_name, _optionPreamble, _options, _minimumArgs, _maximumArgs); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.Repl/Commanding/CommandOptionSpecification.cs b/src/Microsoft.Repl/Commanding/CommandOptionSpecification.cs new file mode 100644 index 0000000000..d925e5b2c9 --- /dev/null +++ b/src/Microsoft.Repl/Commanding/CommandOptionSpecification.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; + +namespace Microsoft.Repl.Commanding +{ + public class CommandOptionSpecification + { + public string Id { get; } + + public IReadOnlyList Forms { get; } + + public int MaximumOccurrences { get; } + + public int MinimumOccurrences { get; } + + public bool AcceptsValue { get; } + + public bool RequiresValue { get; } + + public CommandOptionSpecification(string id, bool acceptsValue = false, bool requiresValue = false, int minimumOccurrences = 0, int maximumOccurrences = int.MaxValue, params string[] forms) + { + Id = id; + Forms = forms; + MinimumOccurrences = minimumOccurrences; + MaximumOccurrences = maximumOccurrences > minimumOccurrences ? maximumOccurrences : minimumOccurrences; + RequiresValue = requiresValue; + AcceptsValue = RequiresValue || acceptsValue; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.Repl/Commanding/CommandWithStructuredInputBase.cs b/src/Microsoft.Repl/Commanding/CommandWithStructuredInputBase.cs new file mode 100644 index 0000000000..38443486e8 --- /dev/null +++ b/src/Microsoft.Repl/Commanding/CommandWithStructuredInputBase.cs @@ -0,0 +1,193 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Repl.Parsing; + +namespace Microsoft.Repl.Commanding +{ + public abstract class CommandWithStructuredInputBase : ICommand + where TParseResult : ICoreParseResult + { + public abstract string GetHelpSummary(IShellState shellState, TProgramState programState); + + public string GetHelpDetails(IShellState shellState, TProgramState programState, TParseResult parseResult) + { + if (!DefaultCommandInput.TryProcess(InputSpec, parseResult, out DefaultCommandInput commandInput, out IReadOnlyList processingIssues) + && processingIssues.Any(x => x.Kind == CommandInputProcessingIssueKind.CommandMismatch)) + { + //If this is the right command, just not the right syntax, report the usage errors + return null; + } + + return GetHelpDetails(shellState, programState, commandInput, parseResult); + } + + protected abstract string GetHelpDetails(IShellState shellState, TProgramState programState, DefaultCommandInput commandInput, TParseResult parseResult); + + public IEnumerable Suggest(IShellState shellState, TProgramState programState, TParseResult parseResult) + { + DefaultCommandInput.TryProcess(InputSpec, parseResult, out DefaultCommandInput commandInput, out IReadOnlyList _); + + string normalCompletionString = parseResult.SelectedSection == parseResult.Sections.Count + ? string.Empty + : parseResult.Sections[parseResult.SelectedSection].Substring(0, parseResult.CaretPositionWithinSelectedSection); + + //If we're completing in a name position, offer completion for the command name + if (parseResult.SelectedSection < InputSpec.CommandName.Count) + { + for (int i = 0; i < parseResult.SelectedSection; ++i) + { + if (!string.Equals(InputSpec.CommandName[i], parseResult.Sections[i], StringComparison.OrdinalIgnoreCase)) + { + return null; + } + } + + if (InputSpec.CommandName[parseResult.SelectedSection].StartsWith(normalCompletionString, StringComparison.OrdinalIgnoreCase)) + { + return new[] {InputSpec.CommandName[parseResult.SelectedSection]}; + } + } + + if (commandInput is null) + { + return null; + } + + if (normalCompletionString.StartsWith(InputSpec.OptionPreamble)) + { + return GetOptionCompletions(commandInput, normalCompletionString); + } + + IEnumerable completions = Enumerable.Empty(); + CommandInputLocation? inputLocation = commandInput.SelectedElement?.Location; + + if (inputLocation != CommandInputLocation.OptionValue && commandInput.Arguments.Count < InputSpec.MaximumArguments) + { + IEnumerable results = GetArgumentSuggestionsForText(shellState, programState, parseResult, commandInput, normalCompletionString); + + if (results != null) + { + completions = results; + } + } + + switch (inputLocation) + { + case CommandInputLocation.OptionName: + { + IEnumerable results = GetOptionCompletions(commandInput, normalCompletionString); + + if (results != null) + { + completions = completions.Union(results); + } + + break; + } + case CommandInputLocation.OptionValue: + { + IEnumerable results = GetOptionValueCompletions(shellState, programState, commandInput.SelectedElement.Owner.NormalizedText, commandInput, parseResult, normalCompletionString); + + if (results != null) + { + completions = completions.Union(results); + } + + break; + } + case CommandInputLocation.Argument: + { + IEnumerable argumentResults = GetArgumentSuggestionsForText(shellState, programState, parseResult, commandInput, normalCompletionString); + + if (argumentResults != null) + { + completions = completions.Union(argumentResults); + } + + if (string.IsNullOrEmpty(normalCompletionString)) + { + IEnumerable results = GetOptionCompletions(commandInput, normalCompletionString); + + if (results != null) + { + completions = completions.Union(results); + } + } + + break; + } + } + + return completions; + } + + protected virtual IEnumerable GetOptionValueCompletions(IShellState shellState, TProgramState programState, string optionId, DefaultCommandInput commandInput, TParseResult parseResult, string normalizedCompletionText) + { + return null; + } + + protected virtual IEnumerable GetArgumentSuggestionsForText(IShellState shellState, TProgramState programState, TParseResult parseResult, DefaultCommandInput commandInput, string normalCompletionString) + { + return null; + } + + private IEnumerable GetOptionCompletions(DefaultCommandInput commandInput, string normalCompletionString) + { + return InputSpec.Options.Where(x => commandInput.Options[x.Id].Count < x.MaximumOccurrences) + .SelectMany(x => x.Forms) + .Where(x => x.StartsWith(normalCompletionString, StringComparison.OrdinalIgnoreCase)); + } + + public bool? CanHandle(IShellState shellState, TProgramState programState, TParseResult parseResult) + { + if (!DefaultCommandInput.TryProcess(InputSpec, parseResult, out DefaultCommandInput commandInput, out IReadOnlyList processingIssues)) + { + //If this is the right command, just not the right syntax, report the usage errors + if (processingIssues.All(x => x.Kind != CommandInputProcessingIssueKind.CommandMismatch)) + { + foreach (CommandInputProcessingIssue issue in processingIssues) + { + shellState.ConsoleManager.Error.WriteLine(GetStringForIssue(issue)); + } + + string help = GetHelpDetails(shellState, programState, parseResult); + shellState.ConsoleManager.WriteLine(help); + return false; + } + + //If there was a mismatch in the command name, this isn't our input to handle + return null; + } + + return CanHandle(shellState, programState, commandInput); + } + + protected virtual bool CanHandle(IShellState shellState, TProgramState programState, DefaultCommandInput commandInput) + { + return true; + } + + protected virtual string GetStringForIssue(CommandInputProcessingIssue issue) + { + //TODO: Make this nicer + return issue.Kind + " -- " + issue.Text; + } + + public Task ExecuteAsync(IShellState shellState, TProgramState programState, TParseResult parseResult, CancellationToken cancellationToken) + { + if (!DefaultCommandInput.TryProcess(InputSpec, parseResult, out DefaultCommandInput commandInput, out IReadOnlyList _)) + { + return Task.CompletedTask; + } + + return ExecuteAsync(shellState, programState, commandInput, parseResult, cancellationToken); + } + + protected abstract Task ExecuteAsync(IShellState shellState, TProgramState programState, DefaultCommandInput commandInput, TParseResult parseResult, CancellationToken cancellationToken); + + protected abstract CommandInputSpecification InputSpec { get; } + } +} \ No newline at end of file diff --git a/src/Microsoft.Repl/Commanding/DefaultCommandDispatcher.cs b/src/Microsoft.Repl/Commanding/DefaultCommandDispatcher.cs new file mode 100644 index 0000000000..932e8cdb1a --- /dev/null +++ b/src/Microsoft.Repl/Commanding/DefaultCommandDispatcher.cs @@ -0,0 +1,169 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Repl.ConsoleHandling; +using Microsoft.Repl.Parsing; + +namespace Microsoft.Repl.Commanding +{ + public static class DefaultCommandDispatcher + { + public static DefaultCommandDispatcher Create(Func getPrompt, TProgramState programState) + { + return new DefaultCommandDispatcher(getPrompt, programState); + } + + public static DefaultCommandDispatcher Create(Action onReady, TProgramState programState) + { + return new DefaultCommandDispatcher(onReady, programState); + } + + public static DefaultCommandDispatcher Create(Func getPrompt, TProgramState programState, IParser parser) + where TParseResult : ICoreParseResult + { + return new DefaultCommandDispatcher(getPrompt, programState, parser); + } + + public static DefaultCommandDispatcher Create(Action onReady, TProgramState programState, IParser parser) + where TParseResult : ICoreParseResult + { + return new DefaultCommandDispatcher(onReady, programState, parser); + } + } + + public class DefaultCommandDispatcher : DefaultCommandDispatcher + { + public DefaultCommandDispatcher(Func getPrompt, TProgramState programState) + : base(getPrompt, programState, new CoreParser()) + { + } + + public DefaultCommandDispatcher(Action onReady, TProgramState programState) + : base(onReady, programState, new CoreParser()) + { + } + } + + public class DefaultCommandDispatcher : ICommandDispatcher + where TParseResult : ICoreParseResult + { + private readonly Action _onReady; + private readonly TProgramState _programState; + private readonly IParser _parser; + private readonly HashSet> _commands = new HashSet>(); + private bool _isReady; + + public DefaultCommandDispatcher(Func getPrompt, TProgramState programState, IParser parser) + : this(s => s.ConsoleManager.Write(getPrompt()), programState, parser) + { + } + + public DefaultCommandDispatcher(Action onReady, TProgramState programState, IParser parser) + { + _onReady = onReady; + _programState = programState; + _parser = parser; + } + + public void AddCommand(ICommand command) + { + _commands.Add(command); + } + + public IEnumerable> Commands => _commands; + + public IParser Parser => _parser; + + public IReadOnlyList CollectSuggesetions(IShellState shellState) + { + string line = shellState.InputManager.GetCurrentBuffer(); + TParseResult parseResult = _parser.Parse(line, shellState.ConsoleManager.CaretPosition); + HashSet suggestions = new HashSet(StringComparer.OrdinalIgnoreCase); + + foreach (ICommand command in _commands) + { + IEnumerable commandSuggestions = command.Suggest(shellState, _programState, parseResult); + + if (commandSuggestions != null) + { + suggestions.UnionWith(commandSuggestions); + } + } + + return suggestions.OrderBy(x => x, StringComparer.OrdinalIgnoreCase).ToList(); + } + + public async Task ExecuteCommandAsync(IShellState shellState, CancellationToken cancellationToken) + { + _isReady = false; + shellState.ConsoleManager.WriteLine(); + string commandText = shellState.InputManager.GetCurrentBuffer(); + + if (!string.IsNullOrWhiteSpace(commandText)) + { + shellState.CommandHistory.AddCommand(shellState.InputManager.GetCurrentBuffer()); + + try + { + await ExecuteCommandInternalAsync(shellState, cancellationToken).ConfigureAwait(false); + } + catch (Exception ex) + { + shellState.ConsoleManager.Error.WriteLine(ex.ToString().Bold().Red()); + } + + if (cancellationToken.IsCancellationRequested) + { + shellState.ConsoleManager.Error.WriteLine("Execution was cancelled".Bold().Red()); + } + } + + if (!_isReady) + { + shellState.ConsoleManager.WriteLine(); + OnReady(shellState); + } + + shellState.InputManager.ResetInput(); + } + + private async Task ExecuteCommandInternalAsync(IShellState shellState, CancellationToken cancellationToken) + { + string line = shellState.InputManager.GetCurrentBuffer(); + TParseResult parseResult = _parser.Parse(line, shellState.ConsoleManager.CaretPosition); + + if (!string.IsNullOrWhiteSpace(parseResult.CommandText)) + { + foreach (ICommand command in _commands) + { + bool? result = command.CanHandle(shellState, _programState, parseResult); + + if (result.HasValue) + { + if (result.Value) + { + await command.ExecuteAsync(shellState, _programState, parseResult, cancellationToken); + } + + //If the handler returned non-null, the input would be directed to it, but it's not valid input + return; + } + } + + shellState.ConsoleManager.Error.WriteLine("No matching command found".Red().Bold()); + } + } + + public void OnReady(IShellState shellState) + { + if (!_isReady) + { + _onReady(shellState); + shellState.ConsoleManager.ResetCommandStart(); + _isReady = true; + } + } + } +} diff --git a/src/Microsoft.Repl/Commanding/DefaultCommandInput.cs b/src/Microsoft.Repl/Commanding/DefaultCommandInput.cs new file mode 100644 index 0000000000..0d3b97932e --- /dev/null +++ b/src/Microsoft.Repl/Commanding/DefaultCommandInput.cs @@ -0,0 +1,193 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Repl.Parsing; + +namespace Microsoft.Repl.Commanding +{ + public class DefaultCommandInput + where TParseResult : ICoreParseResult + { + public DefaultCommandInput(IReadOnlyList commandName, IReadOnlyList arguments, IReadOnlyDictionary> options, InputElement selectedElement) + { + CommandName = commandName; + Arguments = arguments; + Options = options; + SelectedElement = selectedElement; + } + + public static bool TryProcess(CommandInputSpecification spec, TParseResult parseResult, out DefaultCommandInput result, out IReadOnlyList processingIssues) + { + List issues = new List(); + List commandNameElements = new List(); + + if (spec.CommandName.Count > parseResult.Sections.Count) + { + issues.Add(new CommandInputProcessingIssue(CommandInputProcessingIssueKind.CommandMismatch, spec.CommandName[parseResult.Sections.Count])); + } + + for (int i = 0; i < spec.CommandName.Count && i < parseResult.Sections.Count; ++i) + { + if (!string.Equals(spec.CommandName[i], parseResult.Sections[i], StringComparison.OrdinalIgnoreCase)) + { + issues.Add(new CommandInputProcessingIssue(CommandInputProcessingIssueKind.CommandMismatch, parseResult.Sections[i])); + } + + commandNameElements.Add(new InputElement(CommandInputLocation.CommandName, parseResult.Sections[i], spec.CommandName[i], i)); + } + + //If we have a command name mismatch, no point in continuing + if (issues.Count > 0) + { + result = null; + processingIssues = issues; + return false; + } + + List arguments = new List(); + Dictionary options = new Dictionary(); + InputElement currentOption = null; + CommandOptionSpecification currentOptionSpec = null; + InputElement selectedElement = null; + + for (int i = spec.CommandName.Count; i < parseResult.Sections.Count; ++i) + { + //If we're not looking at an option name + if (!parseResult.Sections[i].StartsWith(spec.OptionPreamble) || parseResult.IsQuotedSection(i)) + { + if (currentOption is null) + { + InputElement currentElement = new InputElement(CommandInputLocation.Argument, parseResult.Sections[i], parseResult.Sections[i], i); + + if (i == parseResult.SelectedSection) + { + selectedElement = currentElement; + } + + arguments.Add(currentElement); + } + else + { + //If the option isn't a defined one or it is and indicates that it accepts a value, add the section as an option value, + // otherwise add it as an argument + if (currentOptionSpec?.AcceptsValue ?? true) + { + InputElement currentElement = new InputElement(currentOption, CommandInputLocation.OptionValue, parseResult.Sections[i], parseResult.Sections[i], i); + + if (i == parseResult.SelectedSection) + { + selectedElement = currentElement; + } + + options[currentOption] = currentElement; + currentOption = null; + currentOptionSpec = null; + } + else + { + InputElement currentElement = new InputElement(CommandInputLocation.Argument, parseResult.Sections[i], parseResult.Sections[i], i); + + if (i == parseResult.SelectedSection) + { + selectedElement = currentElement; + } + + arguments.Add(currentElement); + } + } + } + //If we are looking at an option name + else + { + //Otherwise, check to see whether the previous option had a required argument before committing it + if (!(currentOption is null)) + { + options[currentOption] = null; + + if (currentOptionSpec?.RequiresValue ?? false) + { + issues.Add(new CommandInputProcessingIssue(CommandInputProcessingIssueKind.MissingRequiredOptionInput, currentOption.Text)); + } + } + + CommandOptionSpecification optionSpec = spec.Options.FirstOrDefault(x => x.Forms.Any(y => string.Equals(y, parseResult.Sections[i], StringComparison.Ordinal))); + + if (optionSpec is null) + { + issues.Add(new CommandInputProcessingIssue(CommandInputProcessingIssueKind.UnknownOption, parseResult.Sections[i])); + } + + currentOption = new InputElement(CommandInputLocation.OptionName, parseResult.Sections[i], optionSpec?.Id, i); + + if (i == parseResult.SelectedSection) + { + selectedElement = currentOption; + } + + currentOptionSpec = optionSpec; + } + } + + //Clear any option in progress + if (!(currentOption is null)) + { + options[currentOption] = null; + + if (currentOptionSpec?.RequiresValue ?? false) + { + issues.Add(new CommandInputProcessingIssue(CommandInputProcessingIssueKind.MissingRequiredOptionInput, currentOption.Text)); + } + } + + //Check to make sure our argument count is in range, if not add an issue + if (arguments.Count > spec.MaximumArguments || arguments.Count < spec.MinimumArguments) + { + issues.Add(new CommandInputProcessingIssue(CommandInputProcessingIssueKind.ArgumentCountOutOfRange, arguments.Count.ToString())); + } + + //Build up the dictionary of options by normal form, then validate counts for every option in the spec + Dictionary> optionsByNormalForm = new Dictionary>(StringComparer.Ordinal); + + foreach (KeyValuePair entry in options) + { + if (entry.Key.NormalizedText is null) + { + continue; + } + + if (!optionsByNormalForm.TryGetValue(entry.Key.NormalizedText, out IReadOnlyList rawBucket)) + { + optionsByNormalForm[entry.Key.NormalizedText] = rawBucket = new List(); + } + + List bucket = (List) rawBucket; + bucket.Add(entry.Value); + } + + foreach (CommandOptionSpecification optionSpec in spec.Options) + { + if (!optionsByNormalForm.TryGetValue(optionSpec.Id, out IReadOnlyList values)) + { + optionsByNormalForm[optionSpec.Id] = values = new List(); + } + + if (values.Count < optionSpec.MinimumOccurrences || values.Count > optionSpec.MaximumOccurrences) + { + issues.Add(new CommandInputProcessingIssue(CommandInputProcessingIssueKind.OptionUseCountOutOfRange, values.Count.ToString())); + } + } + + result = new DefaultCommandInput(commandNameElements, arguments, optionsByNormalForm, selectedElement); + processingIssues = issues; + return issues.Count == 0; + } + + public InputElement SelectedElement { get; } + + public IReadOnlyList CommandName { get; } + + public IReadOnlyList Arguments { get; } + + public IReadOnlyDictionary> Options { get; } + } +} \ No newline at end of file diff --git a/src/Microsoft.Repl/Commanding/ICommand.cs b/src/Microsoft.Repl/Commanding/ICommand.cs new file mode 100644 index 0000000000..be575437e2 --- /dev/null +++ b/src/Microsoft.Repl/Commanding/ICommand.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Repl.Parsing; + +namespace Microsoft.Repl.Commanding +{ + public interface ICommand + where TParseResult : ICoreParseResult + { + string GetHelpSummary(IShellState shellState, TProgramState programState); + + string GetHelpDetails(IShellState shellState, TProgramState programState, TParseResult parseResult); + + IEnumerable Suggest(IShellState shellState, TProgramState programState, TParseResult parseResult); + + bool? CanHandle(IShellState shellState, TProgramState programState, TParseResult parseResult); + + Task ExecuteAsync(IShellState shellState, TProgramState programState, TParseResult parseResult, CancellationToken cancellationToken); + } +} diff --git a/src/Microsoft.Repl/Commanding/ICommandDispatcher.cs b/src/Microsoft.Repl/Commanding/ICommandDispatcher.cs new file mode 100644 index 0000000000..c175490833 --- /dev/null +++ b/src/Microsoft.Repl/Commanding/ICommandDispatcher.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Repl.Parsing; + +namespace Microsoft.Repl.Commanding +{ + public interface ICommandDispatcher + { + IParser Parser { get; } + + IReadOnlyList CollectSuggesetions(IShellState shellState); + + void OnReady(IShellState shellState); + + Task ExecuteCommandAsync(IShellState shellState, CancellationToken cancellationToken); + } + + public interface ICommandDispatcher : ICommandDispatcher + where TParseResult : ICoreParseResult + { + IEnumerable> Commands { get; } + } +} diff --git a/src/Microsoft.Repl/Commanding/ICommandHistory.cs b/src/Microsoft.Repl/Commanding/ICommandHistory.cs new file mode 100644 index 0000000000..8e25db8906 --- /dev/null +++ b/src/Microsoft.Repl/Commanding/ICommandHistory.cs @@ -0,0 +1,15 @@ +using System; + +namespace Microsoft.Repl.Commanding +{ + public interface ICommandHistory + { + string GetPreviousCommand(); + + string GetNextCommand(); + + void AddCommand(string command); + + IDisposable SuspendHistory(); + } +} diff --git a/src/Microsoft.Repl/Commanding/InputElement.cs b/src/Microsoft.Repl/Commanding/InputElement.cs new file mode 100644 index 0000000000..c02c7f0f23 --- /dev/null +++ b/src/Microsoft.Repl/Commanding/InputElement.cs @@ -0,0 +1,29 @@ +namespace Microsoft.Repl.Commanding +{ + public class InputElement + { + public CommandInputLocation Location { get; } + + public string Text { get; } + + public string NormalizedText { get; } + + public InputElement Owner { get; } + + public int ParseResultSectionIndex { get; } + + public InputElement(CommandInputLocation location, string text, string normalizedText, int sectionIndex) + : this(null, location, text, normalizedText, sectionIndex) + { + } + + public InputElement(InputElement owner, CommandInputLocation location, string text, string normalizedText, int sectionIndex) + { + Owner = owner; + Location = location; + Text = text; + NormalizedText = normalizedText; + ParseResultSectionIndex = sectionIndex; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.Repl/ConsoleHandling/AllowedColors.cs b/src/Microsoft.Repl/ConsoleHandling/AllowedColors.cs new file mode 100644 index 0000000000..a71dc2c8a4 --- /dev/null +++ b/src/Microsoft.Repl/ConsoleHandling/AllowedColors.cs @@ -0,0 +1,27 @@ +using System; + +namespace Microsoft.Repl.ConsoleHandling +{ + [Flags] + public enum AllowedColors + { + Black = 0x00, + BoldBlack = Bold | Black, + Red = 0x01, + BoldRed = Bold | Red, + Green = 0x02, + BoldGreen = Bold | Green, + Yellow = 0x03, + BoldYellow = Bold | Yellow, + Blue = 0x04, + BoldBlue = Bold | Blue, + Magenta = 0x05, + BoldMagenta = Bold | Magenta, + Cyan = 0x06, + BoldCyan = Bold | Cyan, + White = 0x07, + BoldWhite = White | Bold, + Bold = 0x100, + None = 0x99 + } +} \ No newline at end of file diff --git a/src/Microsoft.Repl/ConsoleHandling/AnsiColorExtensions.cs b/src/Microsoft.Repl/ConsoleHandling/AnsiColorExtensions.cs new file mode 100644 index 0000000000..cab5bc6f05 --- /dev/null +++ b/src/Microsoft.Repl/ConsoleHandling/AnsiColorExtensions.cs @@ -0,0 +1,80 @@ +namespace Microsoft.Repl.ConsoleHandling +{ + public static class AnsiColorExtensions + { + public static string Black(this string text) + { + return "\x1B[30m" + text + "\x1B[39m"; + } + + public static string Red(this string text) + { + return "\x1B[31m" + text + "\x1B[39m"; + } + public static string Green(this string text) + { + return "\x1B[32m" + text + "\x1B[39m"; + } + + public static string Yellow(this string text) + { + return "\x1B[33m" + text + "\x1B[39m"; + } + + public static string Blue(this string text) + { + return "\x1B[34m" + text + "\x1B[39m"; + } + + public static string Magenta(this string text) + { + return "\x1B[35m" + text + "\x1B[39m"; + } + + public static string Cyan(this string text) + { + return "\x1B[36m" + text + "\x1B[39m"; + } + + public static string White(this string text) + { + return "\x1B[37m" + text + "\x1B[39m"; + } + + public static string Bold(this string text) + { + return "\x1B[1m" + text + "\x1B[22m"; + } + + public static string SetColor(this string text, AllowedColors color) + { + if (color.HasFlag(AllowedColors.Bold)) + { + text = text.Bold(); + color = color & ~AllowedColors.Bold; + } + + switch (color) + { + case AllowedColors.Black: + return text.Black(); + case AllowedColors.Red: + return text.Red(); + case AllowedColors.Green: + return text.Green(); + case AllowedColors.Yellow: + return text.Yellow(); + case AllowedColors.Blue: + return text.Blue(); + case AllowedColors.Magenta: + return text.Magenta(); + case AllowedColors.Cyan: + return text.Cyan(); + case AllowedColors.White: + return text.White(); + default: + return text; + } + } + } +} diff --git a/src/Microsoft.Repl/ConsoleHandling/AnsiConsole.cs b/src/Microsoft.Repl/ConsoleHandling/AnsiConsole.cs new file mode 100644 index 0000000000..24944f8871 --- /dev/null +++ b/src/Microsoft.Repl/ConsoleHandling/AnsiConsole.cs @@ -0,0 +1,151 @@ +using System; +using System.IO; + +namespace Microsoft.Repl.ConsoleHandling +{ + public class AnsiConsole + { + private AnsiConsole(TextWriter writer) + { + Writer = writer; + + OriginalForegroundColor = Console.ForegroundColor; + } + + private int _boldRecursion; + + public static AnsiConsole GetOutput() + { + return new AnsiConsole(Console.Out); + } + + public static AnsiConsole GetError() + { + return new AnsiConsole(Console.Error); + } + + public TextWriter Writer { get; } + + public ConsoleColor OriginalForegroundColor { get; } + + private void SetColor(ConsoleColor color) + { + const int light = 0x08; + int c = (int)color; + + Console.ForegroundColor = + c < 0 ? color : // unknown, just use it + _boldRecursion > 0 ? (ConsoleColor)(c | light) : // ensure color is light + (ConsoleColor)(c & ~light); // ensure color is dark + } + + private void SetBold(bool bold) + { + _boldRecursion += bold ? 1 : -1; + if (_boldRecursion > 1 || (_boldRecursion == 1 && !bold)) + { + return; + } + + // switches on _boldRecursion to handle boldness + SetColor(Console.ForegroundColor); + } + + public void WriteLine(string message) + { + Write(message); + Writer.WriteLine(); + } + + public void Write(char message) + { + Writer.Write(message); + } + + public void Write(string message) + { + if (message is null) + { + return; + } + + var escapeScan = 0; + for (; ; ) + { + var escapeIndex = message.IndexOf("\x1b[", escapeScan, StringComparison.Ordinal); + if (escapeIndex == -1) + { + var text = message.Substring(escapeScan); + Writer.Write(text); + break; + } + else + { + var startIndex = escapeIndex + 2; + var endIndex = startIndex; + while (endIndex != message.Length && + message[endIndex] >= 0x20 && + message[endIndex] <= 0x3f) + { + endIndex += 1; + } + + var text = message.Substring(escapeScan, escapeIndex - escapeScan); + Writer.Write(text); + if (endIndex == message.Length) + { + break; + } + + switch (message[endIndex]) + { + case 'm': + if (int.TryParse(message.Substring(startIndex, endIndex - startIndex), out int value)) + { + switch (value) + { + case 1: + SetBold(true); + break; + case 22: + SetBold(false); + break; + case 30: + SetColor(ConsoleColor.Black); + break; + case 31: + SetColor(ConsoleColor.Red); + break; + case 32: + SetColor(ConsoleColor.Green); + break; + case 33: + SetColor(ConsoleColor.Yellow); + break; + case 34: + SetColor(ConsoleColor.Blue); + break; + case 35: + SetColor(ConsoleColor.Magenta); + break; + case 36: + SetColor(ConsoleColor.Cyan); + break; + case 37: + SetColor(ConsoleColor.Gray); + break; + case 39: + Console.ForegroundColor = OriginalForegroundColor; + break; + } + } + break; + } + + escapeScan = endIndex + 1; + } + } + } + } + +} diff --git a/src/Microsoft.Repl/ConsoleHandling/ConsoleManager.cs b/src/Microsoft.Repl/ConsoleHandling/ConsoleManager.cs new file mode 100644 index 0000000000..a15c1fd420 --- /dev/null +++ b/src/Microsoft.Repl/ConsoleHandling/ConsoleManager.cs @@ -0,0 +1,204 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; + +namespace Microsoft.Repl.ConsoleHandling +{ + public class ConsoleManager : IConsoleManager + { + private readonly List _breakHandlers = new List(); + + public Point Caret => new Point(Console.CursorLeft, Console.CursorTop); + + public Point CommandStart => new Point(Caret.X - CaretPosition % Console.BufferWidth, Caret.Y - CaretPosition / Console.BufferWidth); + + public int CaretPosition { get; private set; } + + public bool IsKeyAvailable => Console.KeyAvailable; + + public bool IsCaretVisible + { + get => Reporter.Output.IsCaretVisible; + set => Reporter.Output.IsCaretVisible = value; + } + + public ConsoleManager() + { + Error = new Writable(CaretUpdateScope, Reporter.Error); + Console.CancelKeyPress += OnCancelKeyPress; + } + + public void Clear() + { + using (CaretUpdateScope()) + { + Console.Clear(); + ResetCommandStart(); + } + } + + public void MoveCaret(int positions) + { + using (CaretUpdateScope()) + { + if (positions == 0) + { + return; + } + + while (positions < 0 && CaretPosition > 0) + { + if (-positions > Console.BufferWidth) + { + if (Console.CursorTop == 0) + { + Console.CursorLeft = 0; + positions = 0; + } + else + { + positions += Console.BufferWidth; + --Console.CursorTop; + } + } + else + { + int remaining = Console.CursorLeft + positions; + + if (remaining >= 0) + { + Console.CursorLeft = remaining; + } + else if (Console.CursorTop == 0) + { + Console.CursorLeft = 0; + } + else + { + --Console.CursorTop; + Console.CursorLeft = Console.BufferWidth + remaining; + } + + positions = 0; + } + } + + while (positions > 0) + { + if (positions > Console.BufferWidth) + { + positions -= Console.BufferWidth; + ++Console.CursorTop; + } + else + { + int spaceLeftOnLine = Console.BufferWidth - Console.CursorLeft - 1; + if (positions > spaceLeftOnLine) + { + ++Console.CursorTop; + Console.CursorLeft = positions - spaceLeftOnLine - 1; + } + else + { + Console.CursorLeft += positions; + } + + positions = 0; + } + } + } + } + + public ConsoleKeyInfo ReadKey(CancellationToken cancellationToken) + { + while (!Console.KeyAvailable && !cancellationToken.IsCancellationRequested) + { + Thread.Sleep(2); + } + + if (cancellationToken.IsCancellationRequested) + { + return default(ConsoleKeyInfo); + } + else + { + return Console.ReadKey(true); + } + } + + public void ResetCommandStart() + { + CaretPosition = 0; + } + + public void Write(char c) + { + using (CaretUpdateScope()) + { + Reporter.Output.Write(c); + } + } + + public void Write(string s) + { + using (CaretUpdateScope()) + { + Reporter.Output.Write(s); + } + } + + public void WriteLine() + { + using (CaretUpdateScope()) + { + Reporter.Output.WriteLine(); + } + } + + public void WriteLine(string s) + { + if (s is null) + { + return; + } + + using (CaretUpdateScope()) + { + Reporter.Output.WriteLine(s); + } + } + + public IDisposable AddBreakHandler(Action handler) + { + Disposable result = new Disposable(() => ReleaseBreakHandler(handler)); + _breakHandlers.Add(handler); + return result; + } + + private IDisposable CaretUpdateScope() + { + Point currentCaret = Caret; + return new Disposable(() => + { + int y = Caret.Y - currentCaret.Y; + int x = Caret.X - currentCaret.X; + CaretPosition += y * Console.BufferWidth + x; + }); + } + + private void OnCancelKeyPress(object sender, ConsoleCancelEventArgs e) + { + e.Cancel = true; + Action handler = _breakHandlers.LastOrDefault(); + handler?.Invoke(); + } + + private void ReleaseBreakHandler(Action handler) + { + _breakHandlers.Remove(handler); + } + + public IWritable Error { get; } + } +} diff --git a/src/Microsoft.Repl/ConsoleHandling/IConsoleManager.cs b/src/Microsoft.Repl/ConsoleHandling/IConsoleManager.cs new file mode 100644 index 0000000000..9ab7b26a15 --- /dev/null +++ b/src/Microsoft.Repl/ConsoleHandling/IConsoleManager.cs @@ -0,0 +1,28 @@ +using System; +using System.Threading; + +namespace Microsoft.Repl.ConsoleHandling +{ + public interface IConsoleManager : IWritable + { + Point Caret { get; } + + Point CommandStart { get; } + + int CaretPosition { get; } + + IWritable Error { get; } + + bool IsKeyAvailable { get; } + + void Clear(); + + void MoveCaret(int positions); + + ConsoleKeyInfo ReadKey(CancellationToken cancellationToken); + + void ResetCommandStart(); + + IDisposable AddBreakHandler(Action onBreak); + } +} diff --git a/src/Microsoft.Repl/ConsoleHandling/IWritable.cs b/src/Microsoft.Repl/ConsoleHandling/IWritable.cs new file mode 100644 index 0000000000..f18fa123a1 --- /dev/null +++ b/src/Microsoft.Repl/ConsoleHandling/IWritable.cs @@ -0,0 +1,15 @@ +namespace Microsoft.Repl.ConsoleHandling +{ + public interface IWritable + { + void Write(char c); + + void Write(string s); + + void WriteLine(); + + void WriteLine(string s); + + bool IsCaretVisible { get; set; } + } +} diff --git a/src/Microsoft.Repl/ConsoleHandling/Point.cs b/src/Microsoft.Repl/ConsoleHandling/Point.cs new file mode 100644 index 0000000000..4eeb68989d --- /dev/null +++ b/src/Microsoft.Repl/ConsoleHandling/Point.cs @@ -0,0 +1,50 @@ +namespace Microsoft.Repl.ConsoleHandling +{ + public struct Point + { + public readonly int X; + + public readonly int Y; + + public Point(int x, int y) + { + X = x; + Y = y; + } + + public static bool operator >(Point left, Point right) + { + return left.Y > right.Y || (left.Y == right.Y && right.X > left.X); + } + + public static bool operator <(Point left, Point right) + { + return left.Y < right.Y || (left.Y == right.Y && right.X < left.X); + } + + public static bool operator ==(Point left, Point right) + { + return left.X == right.X && left.Y == right.Y; + } + + public static bool operator !=(Point left, Point right) + { + return left.X != right.X || left.Y != right.Y; + } + + public override bool Equals(object obj) + { + return obj is Point other && other.X == X && other.Y == Y; + } + + public override int GetHashCode() + { + return X ^ Y; + } + + public override string ToString() + { + return $"(X={X}, Y={Y})"; + } + } +} diff --git a/src/Microsoft.Repl/ConsoleHandling/Reporter.cs b/src/Microsoft.Repl/ConsoleHandling/Reporter.cs new file mode 100644 index 0000000000..5b10d2be02 --- /dev/null +++ b/src/Microsoft.Repl/ConsoleHandling/Reporter.cs @@ -0,0 +1,115 @@ +using System; + +namespace Microsoft.Repl.ConsoleHandling +{ + public class Reporter : IWritable + { + private static readonly Reporter NullReporter = new Reporter(null); + private static readonly object Sync = new object(); + + private readonly AnsiConsole _console; + + static Reporter() + { + Reset(); + } + + private Reporter(AnsiConsole console) + { + _console = console; + } + + public static Reporter Output { get; private set; } + public static Reporter Error { get; private set; } + public static Reporter Verbose { get; private set; } + + /// + /// Resets the Reporters to write to the current Console Out/Error. + /// + public static void Reset() + { + lock (Sync) + { + Output = new Reporter(AnsiConsole.GetOutput()); + Error = new Reporter(AnsiConsole.GetError()); + Verbose = IsVerbose ? + new Reporter(AnsiConsole.GetOutput()) : + NullReporter; + } + } + + public void WriteLine(string message) + { + if (message is null) + { + return; + } + + lock (Sync) + { + if (ShouldPassAnsiCodesThrough) + { + _console?.Writer?.WriteLine(message); + } + else + { + _console?.WriteLine(message); + } + } + } + + public void WriteLine() + { + lock (Sync) + { + _console?.Writer?.WriteLine(); + } + } + + public void Write(char message) + { + lock (Sync) + { + if (ShouldPassAnsiCodesThrough) + { + _console?.Writer?.Write(message); + } + else + { + _console?.Write(message); + } + } + } + + public void Write(string message) + { + lock (Sync) + { + if (ShouldPassAnsiCodesThrough) + { + _console?.Writer?.Write(message); + } + else + { + _console?.Write(message); + } + } + } + + private static bool IsVerbose => bool.TryParse(Environment.GetEnvironmentVariable("DOTNET_CLI_CONTEXT_VERBOSE") ?? "false", out bool value) && value; + + private bool ShouldPassAnsiCodesThrough => bool.TryParse(Environment.GetEnvironmentVariable("DOTNET_CLI_CONTEXT_ANSI_PASS_THRU") ?? "false", out bool value) && value; + + private bool _isCaretVisible = true; + + public bool IsCaretVisible + { + get => _isCaretVisible; + set + { + Console.CursorVisible = value; + _isCaretVisible = value; + } + } + } +} diff --git a/src/Microsoft.Repl/ConsoleHandling/Writable.cs b/src/Microsoft.Repl/ConsoleHandling/Writable.cs new file mode 100644 index 0000000000..b12c2ad4e7 --- /dev/null +++ b/src/Microsoft.Repl/ConsoleHandling/Writable.cs @@ -0,0 +1,54 @@ +using System; + +namespace Microsoft.Repl.ConsoleHandling +{ + internal class Writable : IWritable + { + private readonly Func _caretUpdater; + private readonly Reporter _reporter; + + public Writable(Func caretUpdater, Reporter reporter) + { + _caretUpdater = caretUpdater; + _reporter = reporter; + } + + public bool IsCaretVisible + { + get => _reporter.IsCaretVisible; + set => _reporter.IsCaretVisible = value; + } + + public void Write(char c) + { + using (_caretUpdater()) + { + _reporter.Write(c); + } + } + + public void Write(string s) + { + using (_caretUpdater()) + { + _reporter.Write(s); + } + } + + public void WriteLine() + { + using (_caretUpdater()) + { + _reporter.WriteLine(); + } + } + + public void WriteLine(string s) + { + using (_caretUpdater()) + { + _reporter.WriteLine(s); + } + } + } +} diff --git a/src/Microsoft.Repl/Disposable.cs b/src/Microsoft.Repl/Disposable.cs new file mode 100644 index 0000000000..78002fb1d9 --- /dev/null +++ b/src/Microsoft.Repl/Disposable.cs @@ -0,0 +1,42 @@ +using System; + +namespace Microsoft.Repl +{ + public class Disposable : IDisposable + { + private Action _onDispose; + + public Disposable(Action onDispose) + { + _onDispose = onDispose; + } + public virtual void Dispose() + { + _onDispose?.Invoke(); + _onDispose = null; + } + } + + public class Disposable : Disposable + where T : class + { + public Disposable(T value, Action onDispose) + : base (onDispose) + { + Value = value; + } + + public T Value { get; private set; } + + public override void Dispose() + { + if (Value is IDisposable d) + { + d.Dispose(); + Value = null; + } + + base.Dispose(); + } + } +} diff --git a/src/Microsoft.Repl/IShellState.cs b/src/Microsoft.Repl/IShellState.cs new file mode 100644 index 0000000000..2ec1cb1514 --- /dev/null +++ b/src/Microsoft.Repl/IShellState.cs @@ -0,0 +1,22 @@ +using Microsoft.Repl.Commanding; +using Microsoft.Repl.ConsoleHandling; +using Microsoft.Repl.Input; +using Microsoft.Repl.Suggestions; + +namespace Microsoft.Repl +{ + public interface IShellState + { + IInputManager InputManager { get; } + + ICommandHistory CommandHistory { get; } + + IConsoleManager ConsoleManager { get; } + + ICommandDispatcher CommandDispatcher { get; } + + ISuggestionManager SuggestionManager { get; } + + bool IsExiting { get; set; } + } +} diff --git a/src/Microsoft.Repl/Input/AsyncKeyPressHandler.cs b/src/Microsoft.Repl/Input/AsyncKeyPressHandler.cs new file mode 100644 index 0000000000..616a749611 --- /dev/null +++ b/src/Microsoft.Repl/Input/AsyncKeyPressHandler.cs @@ -0,0 +1,8 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Repl.Input +{ + public delegate Task AsyncKeyPressHandler(ConsoleKeyInfo keyInfo, IShellState state, CancellationToken cancellationToken); +} diff --git a/src/Microsoft.Repl/Input/IInputManager.cs b/src/Microsoft.Repl/Input/IInputManager.cs new file mode 100644 index 0000000000..100dd22bea --- /dev/null +++ b/src/Microsoft.Repl/Input/IInputManager.cs @@ -0,0 +1,27 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Repl.Input +{ + public interface IInputManager + { + bool IsOverwriteMode { get; set; } + + IInputManager RegisterKeyHandler(ConsoleKey key, AsyncKeyPressHandler handler); + + void ResetInput(); + + Task StartAsync(IShellState state, CancellationToken cancellationToken); + + void SetInput(IShellState state, string input); + + string GetCurrentBuffer(); + + void RemovePreviousCharacter(IShellState state); + + void RemoveCurrentCharacter(IShellState state); + + void Clear(IShellState state); + } +} diff --git a/src/Microsoft.Repl/Input/InputManager.cs b/src/Microsoft.Repl/Input/InputManager.cs new file mode 100644 index 0000000000..a635ccd37a --- /dev/null +++ b/src/Microsoft.Repl/Input/InputManager.cs @@ -0,0 +1,344 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Repl.Input +{ + public class InputManager : IInputManager + { + private readonly Dictionary _handlers = new Dictionary(); + private readonly List _inputBuffer = new List(); + + public bool IsOverwriteMode { get; set; } + + public void Clear(IShellState state) + { + SetInput(state, string.Empty); + } + + public string GetCurrentBuffer() + { + return _inputBuffer.Stringify(); + } + + public IInputManager RegisterKeyHandler(ConsoleKey key, AsyncKeyPressHandler handler) + { + if (handler == null) + { + _handlers.Remove(key); + } + else + { + _handlers[key] = handler; + } + + return this; + } + + public void RemoveCurrentCharacter(IShellState state) + { + int caret = state.ConsoleManager.CaretPosition; + + if (caret == _inputBuffer.Count) + { + return; + } + + List update = _inputBuffer.ToList(); + update.RemoveAt(caret); + state.ConsoleManager.IsCaretVisible = false; + SetInput(state, update); + state.ConsoleManager.MoveCaret(caret - state.ConsoleManager.CaretPosition); + state.ConsoleManager.IsCaretVisible = true; + } + + public void RemovePreviousCharacter(IShellState state) + { + int caret = state.ConsoleManager.CaretPosition; + if (caret == 0) + { + return; + } + + List update = _inputBuffer.ToList(); + update.RemoveAt(caret - 1); + state.ConsoleManager.IsCaretVisible = false; + SetInput(state, update, false); + state.ConsoleManager.MoveCaret(caret - state.ConsoleManager.CaretPosition - 1); + state.ConsoleManager.IsCaretVisible = true; + } + + public void SetInput(IShellState state, string input) + { + SetInput(state, input.ToCharArray()); + } + + public void ResetInput() + { + _inputBuffer.Clear(); + } + + private string _ttyState; + + private void StashEchoState() + { + _ttyState = System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.OSX) + ? GetTtyState() + : null; + + if (!string.IsNullOrEmpty(_ttyState)) + { + //"gfmt1:cflag=4300:iflag=6b02:lflag=200005c7:oflag=3:discard=f:dsusp=19:eof=4:eol=ff:eol2=ff:erase=7f:intr=3:kill=15:lnext=16:min=1:quit=1c:reprint=12:start=11:status=14:stop=13:susp=1a:time=0:werase=17:ispeed=38400:ospeed=38400\n" + ProcessStartInfo psi = new ProcessStartInfo("stty", "gfmt1:erase=08:werase=08 -echo"); + Process p = Process.Start(psi); + p?.WaitForExit(); + } + } + + private static string GetTtyState() + { + ProcessStartInfo psi = new ProcessStartInfo("stty", "-g") + { + RedirectStandardOutput = true + }; + Process p = Process.Start(psi); + p?.WaitForExit(); + string result = p?.StandardOutput.ReadToEnd(); + return result; + } + + private void RestoreTtyState() + { + if (!string.IsNullOrEmpty(_ttyState)) + { + ProcessStartInfo psi = new ProcessStartInfo("stty", _ttyState); + Process p = Process.Start(psi); + p?.WaitForExit(); + } + } + + private void SetInput(IShellState state, IReadOnlyList input, bool moveCaret = true) + { + bool oldCaretVisibility = state.ConsoleManager.IsCaretVisible; + state.ConsoleManager.IsCaretVisible = false; + int lastCommonPosition = 0; + + for (; lastCommonPosition < input.Count && lastCommonPosition < _inputBuffer.Count && _inputBuffer[lastCommonPosition] == input[lastCommonPosition]; ++lastCommonPosition) + { + } + + state.ConsoleManager.MoveCaret(-state.ConsoleManager.CaretPosition + lastCommonPosition); + string str = new string(input.Skip(lastCommonPosition).ToArray()); + int trailing = _inputBuffer.Count - input.Count; + + if (trailing > 0) + { + str = str.PadRight(trailing + str.Length); + } + + state.ConsoleManager.Write(str); + + if (trailing > 0 && moveCaret) + { + state.ConsoleManager.MoveCaret(-trailing); + } + + _inputBuffer.Clear(); + _inputBuffer.AddRange(input); + + if (oldCaretVisibility) + { + state.ConsoleManager.IsCaretVisible = true; + } + } + + public async Task StartAsync(IShellState state, CancellationToken cancellationToken) + { + StashEchoState(); + + try + { + List presses = null; + while (!state.IsExiting && !cancellationToken.IsCancellationRequested) + { + ConsoleKeyInfo keyPress = state.ConsoleManager.ReadKey(cancellationToken); + + if (_handlers.TryGetValue(keyPress.Key, out AsyncKeyPressHandler handler)) + { + using (CancellationTokenSource source = new CancellationTokenSource()) + using (state.ConsoleManager.AddBreakHandler(() => source.Cancel())) + { + if (presses != null) + { + FlushInput(state, ref presses); + } + + await handler(keyPress, state, source.Token).ConfigureAwait(false); + } + } + else if (!string.IsNullOrEmpty(_ttyState) && keyPress.Modifiers == ConsoleModifiers.Control) + { + if (presses != null) + { + FlushInput(state, ref presses); + } + + if (keyPress.Key == ConsoleKey.A) + { + state.ConsoleManager.MoveCaret(-state.ConsoleManager.CaretPosition); + } + else if (keyPress.Key == ConsoleKey.E) + { + state.ConsoleManager.MoveCaret(_inputBuffer.Count - state.ConsoleManager.CaretPosition); + } + } + else if (!string.IsNullOrEmpty(_ttyState) && keyPress.Modifiers == ConsoleModifiers.Alt) + { + if (presses != null) + { + FlushInput(state, ref presses); + } + + //Move back a word + if (keyPress.Key == ConsoleKey.B) + { + int i = state.ConsoleManager.CaretPosition - 1; + + if (i < 0) + { + continue; + } + + bool letterMode = char.IsLetterOrDigit(_inputBuffer[i]); + + for (; i > 0 && (char.IsLetterOrDigit(_inputBuffer[i]) == letterMode); --i) + { + } + + if (letterMode && i > 0) + { + ++i; + } + + if (i > -1) + { + state.ConsoleManager.MoveCaret(i - state.ConsoleManager.CaretPosition); + } + } + //Move forward a word + else if (keyPress.Key == ConsoleKey.F) + { + int i = state.ConsoleManager.CaretPosition + 1; + + if (i >= _inputBuffer.Count) + { + continue; + } + + bool letterMode = char.IsLetterOrDigit(_inputBuffer[i]); + + for (; i < _inputBuffer.Count && (char.IsLetterOrDigit(_inputBuffer[i]) == letterMode); ++i) + { + } + + if (letterMode && i < _inputBuffer.Count - 1 && i > 0) + { + --i; + } + + state.ConsoleManager.MoveCaret(i - state.ConsoleManager.CaretPosition); + } + } + else if (!keyPress.Modifiers.HasFlag(ConsoleModifiers.Alt) && !keyPress.Modifiers.HasFlag(ConsoleModifiers.Control)) + { + if (state.ConsoleManager.IsKeyAvailable) + { + if (presses == null) + { + presses = new List(); + } + + presses.Add(keyPress); + continue; + } + + if (presses != null) + { + presses.Add(keyPress); + FlushInput(state, ref presses); + continue; + } + + if (state.ConsoleManager.CaretPosition == _inputBuffer.Count) + { + _inputBuffer.Add(keyPress.KeyChar); + state.ConsoleManager.Write(keyPress.KeyChar); + } + else if (IsOverwriteMode) + { + _inputBuffer[state.ConsoleManager.CaretPosition] = keyPress.KeyChar; + state.ConsoleManager.Write(keyPress.KeyChar); + } + else + { + state.ConsoleManager.IsCaretVisible = false; + _inputBuffer.Insert(state.ConsoleManager.CaretPosition, keyPress.KeyChar); + int currentCaretPosition = state.ConsoleManager.CaretPosition; + string s = new string(_inputBuffer.ToArray(), state.ConsoleManager.CaretPosition, _inputBuffer.Count - state.ConsoleManager.CaretPosition); + state.ConsoleManager.Write(s); + state.ConsoleManager.MoveCaret(currentCaretPosition - state.ConsoleManager.CaretPosition + 1); + state.ConsoleManager.IsCaretVisible = true; + } + } + } + } + finally + { + RestoreTtyState(); + } + } + + private void FlushInput(IShellState state, ref List presses) + { + string str = new string(presses.Select(x => x.KeyChar).ToArray()); + + if (state.ConsoleManager.CaretPosition == _inputBuffer.Count) + { + _inputBuffer.AddRange(str); + state.ConsoleManager.Write(str); + } + else if (IsOverwriteMode) + { + for (int i = 0; i < str.Length; ++i) + { + if (state.ConsoleManager.CaretPosition + i < _inputBuffer.Count) + { + _inputBuffer[state.ConsoleManager.CaretPosition + i] = str[i]; + } + else + { + _inputBuffer.AddRange(str.Skip(i)); + break; + } + } + + state.ConsoleManager.Write(str); + } + else + { + state.ConsoleManager.IsCaretVisible = false; + _inputBuffer.InsertRange(state.ConsoleManager.CaretPosition, str); + int currentCaretPosition = state.ConsoleManager.CaretPosition; + string s = new string(_inputBuffer.ToArray(), state.ConsoleManager.CaretPosition, _inputBuffer.Count - state.ConsoleManager.CaretPosition); + state.ConsoleManager.Write(s); + state.ConsoleManager.MoveCaret(currentCaretPosition - state.ConsoleManager.CaretPosition + str.Length); + state.ConsoleManager.IsCaretVisible = true; + } + + presses = null; + } + } +} diff --git a/src/Microsoft.Repl/Input/KeyHandlers.cs b/src/Microsoft.Repl/Input/KeyHandlers.cs new file mode 100644 index 0000000000..7b23f08769 --- /dev/null +++ b/src/Microsoft.Repl/Input/KeyHandlers.cs @@ -0,0 +1,239 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Repl.Parsing; + +namespace Microsoft.Repl.Input +{ + public static class KeyHandlers + { + public static void RegisterDefaultKeyHandlers(IInputManager inputManager) + { + //Navigation in line + inputManager.RegisterKeyHandler(ConsoleKey.LeftArrow, LeftArrow); + inputManager.RegisterKeyHandler(ConsoleKey.RightArrow, RightArrow); + inputManager.RegisterKeyHandler(ConsoleKey.Home, Home); + inputManager.RegisterKeyHandler(ConsoleKey.End, End); + + //Command history + inputManager.RegisterKeyHandler(ConsoleKey.UpArrow, UpArrow); + inputManager.RegisterKeyHandler(ConsoleKey.DownArrow, DownArrow); + + //Completion + inputManager.RegisterKeyHandler(ConsoleKey.Tab, Tab); + + //Input manipulation + inputManager.RegisterKeyHandler(ConsoleKey.Escape, Escape); + inputManager.RegisterKeyHandler(ConsoleKey.Delete, Delete); + inputManager.RegisterKeyHandler(ConsoleKey.Backspace, Backspace); + + //Insert/Overwrite mode + inputManager.RegisterKeyHandler(ConsoleKey.Insert, Insert); + + //Execute command + inputManager.RegisterKeyHandler(ConsoleKey.Enter, Enter); + + //Map non-printable keys that aren't handled by default + inputManager.RegisterKeyHandler(ConsoleKey.F1, Unhandled); + inputManager.RegisterKeyHandler(ConsoleKey.F2, Unhandled); + inputManager.RegisterKeyHandler(ConsoleKey.F3, Unhandled); + inputManager.RegisterKeyHandler(ConsoleKey.F4, Unhandled); + inputManager.RegisterKeyHandler(ConsoleKey.F5, Unhandled); + inputManager.RegisterKeyHandler(ConsoleKey.F6, Unhandled); + inputManager.RegisterKeyHandler(ConsoleKey.F7, Unhandled); + inputManager.RegisterKeyHandler(ConsoleKey.F8, Unhandled); + inputManager.RegisterKeyHandler(ConsoleKey.F9, Unhandled); + inputManager.RegisterKeyHandler(ConsoleKey.F10, Unhandled); + inputManager.RegisterKeyHandler(ConsoleKey.F11, Unhandled); + inputManager.RegisterKeyHandler(ConsoleKey.F12, Unhandled); + inputManager.RegisterKeyHandler(ConsoleKey.F13, Unhandled); + inputManager.RegisterKeyHandler(ConsoleKey.F14, Unhandled); + inputManager.RegisterKeyHandler(ConsoleKey.F15, Unhandled); + inputManager.RegisterKeyHandler(ConsoleKey.F16, Unhandled); + inputManager.RegisterKeyHandler(ConsoleKey.F17, Unhandled); + inputManager.RegisterKeyHandler(ConsoleKey.F18, Unhandled); + inputManager.RegisterKeyHandler(ConsoleKey.F19, Unhandled); + inputManager.RegisterKeyHandler(ConsoleKey.F20, Unhandled); + inputManager.RegisterKeyHandler(ConsoleKey.F21, Unhandled); + inputManager.RegisterKeyHandler(ConsoleKey.F22, Unhandled); + inputManager.RegisterKeyHandler(ConsoleKey.F23, Unhandled); + inputManager.RegisterKeyHandler(ConsoleKey.F24, Unhandled); + + inputManager.RegisterKeyHandler(ConsoleKey.Applications, Unhandled); + inputManager.RegisterKeyHandler(ConsoleKey.Attention, Unhandled); + inputManager.RegisterKeyHandler(ConsoleKey.BrowserBack, Unhandled); + inputManager.RegisterKeyHandler(ConsoleKey.BrowserFavorites, Unhandled); + inputManager.RegisterKeyHandler(ConsoleKey.BrowserForward, Unhandled); + inputManager.RegisterKeyHandler(ConsoleKey.BrowserHome, Unhandled); + inputManager.RegisterKeyHandler(ConsoleKey.BrowserRefresh, Unhandled); + inputManager.RegisterKeyHandler(ConsoleKey.BrowserSearch, Unhandled); + inputManager.RegisterKeyHandler(ConsoleKey.BrowserStop, Unhandled); + inputManager.RegisterKeyHandler(ConsoleKey.Clear, Unhandled); + inputManager.RegisterKeyHandler(ConsoleKey.CrSel, Unhandled); + + inputManager.RegisterKeyHandler(ConsoleKey.EraseEndOfFile, Unhandled); + inputManager.RegisterKeyHandler(ConsoleKey.Execute, Unhandled); + inputManager.RegisterKeyHandler(ConsoleKey.ExSel, Unhandled); + inputManager.RegisterKeyHandler(ConsoleKey.Help, Unhandled); + inputManager.RegisterKeyHandler(ConsoleKey.LaunchApp1, Unhandled); + inputManager.RegisterKeyHandler(ConsoleKey.LaunchApp2, Unhandled); + inputManager.RegisterKeyHandler(ConsoleKey.LaunchMail, Unhandled); + inputManager.RegisterKeyHandler(ConsoleKey.LaunchMediaSelect, Unhandled); + inputManager.RegisterKeyHandler(ConsoleKey.LeftWindows, Unhandled); + inputManager.RegisterKeyHandler(ConsoleKey.MediaNext, Unhandled); + inputManager.RegisterKeyHandler(ConsoleKey.MediaPlay, Unhandled); + inputManager.RegisterKeyHandler(ConsoleKey.MediaPrevious, Unhandled); + inputManager.RegisterKeyHandler(ConsoleKey.MediaStop, Unhandled); + inputManager.RegisterKeyHandler(ConsoleKey.NoName, Unhandled); + + inputManager.RegisterKeyHandler(ConsoleKey.Pa1, Unhandled); + inputManager.RegisterKeyHandler(ConsoleKey.Packet, Unhandled); + inputManager.RegisterKeyHandler(ConsoleKey.PageDown, Unhandled); + inputManager.RegisterKeyHandler(ConsoleKey.PageUp, Unhandled); + inputManager.RegisterKeyHandler(ConsoleKey.Pause, Unhandled); + inputManager.RegisterKeyHandler(ConsoleKey.Play, Unhandled); + inputManager.RegisterKeyHandler(ConsoleKey.Print, Unhandled); + inputManager.RegisterKeyHandler(ConsoleKey.PrintScreen, Unhandled); + inputManager.RegisterKeyHandler(ConsoleKey.Process, Unhandled); + inputManager.RegisterKeyHandler(ConsoleKey.RightWindows, Unhandled); + inputManager.RegisterKeyHandler(ConsoleKey.Select, Unhandled); + inputManager.RegisterKeyHandler(ConsoleKey.Separator, Unhandled); + inputManager.RegisterKeyHandler(ConsoleKey.Sleep, Unhandled); + inputManager.RegisterKeyHandler(ConsoleKey.VolumeDown, Unhandled); + inputManager.RegisterKeyHandler(ConsoleKey.VolumeMute, Unhandled); + inputManager.RegisterKeyHandler(ConsoleKey.VolumeUp, Unhandled); + inputManager.RegisterKeyHandler(ConsoleKey.Zoom, Unhandled); + } + + private static Task End(ConsoleKeyInfo keyInfo, IShellState state, CancellationToken cancellationToken) + { + state.ConsoleManager.MoveCaret(state.InputManager.GetCurrentBuffer().Length - state.ConsoleManager.CaretPosition); + return Task.CompletedTask; + } + + public static Task Home(ConsoleKeyInfo keyInfo, IShellState state, CancellationToken cancellationToken) + { + state.ConsoleManager.MoveCaret(-state.ConsoleManager.CaretPosition); + return Task.CompletedTask; + } + + public static Task LeftArrow(ConsoleKeyInfo keyInfo, IShellState state, CancellationToken cancellationToken) + { + if (state.ConsoleManager.CaretPosition > 0) + { + if (!keyInfo.Modifiers.HasFlag(ConsoleModifiers.Control)) + { + state.ConsoleManager.MoveCaret(-1); + } + else + { + string line = state.InputManager.GetCurrentBuffer(); + ICoreParseResult parseResult = state.CommandDispatcher.Parser.Parse(line, state.ConsoleManager.CaretPosition); + int targetSection = parseResult.SelectedSection - (parseResult.CaretPositionWithinSelectedSection > 0 ? 0 : 1); + + if (targetSection < 0) + { + targetSection = 0; + } + + int desiredPosition = parseResult.SectionStartLookup[targetSection]; + state.ConsoleManager.MoveCaret(desiredPosition - state.ConsoleManager.CaretPosition); + } + } + + return Task.CompletedTask; + } + + public static Task RightArrow(ConsoleKeyInfo keyInfo, IShellState state, CancellationToken cancellationToken) + { + string line = state.InputManager.GetCurrentBuffer(); + + if (state.ConsoleManager.CaretPosition < line.Length) + { + if (!keyInfo.Modifiers.HasFlag(ConsoleModifiers.Control)) + { + state.ConsoleManager.MoveCaret(1); + } + else + { + ICoreParseResult parseResult = state.CommandDispatcher.Parser.Parse(line, state.ConsoleManager.CaretPosition); + int targetSection = parseResult.SelectedSection + 1; + + if (targetSection >= parseResult.Sections.Count) + { + state.ConsoleManager.MoveCaret(line.Length - state.ConsoleManager.CaretPosition); + } + else + { + int desiredPosition = parseResult.SectionStartLookup[targetSection]; + state.ConsoleManager.MoveCaret(desiredPosition - state.ConsoleManager.CaretPosition); + } + } + } + + return Task.CompletedTask; + } + + public static Task UpArrow(ConsoleKeyInfo keyInfo, IShellState state, CancellationToken cancellationToken) + { + string line = state.CommandHistory.GetPreviousCommand(); + state.InputManager.SetInput(state, line); + return Task.CompletedTask; + } + + public static Task DownArrow(ConsoleKeyInfo keyInfo, IShellState state, CancellationToken cancellationToken) + { + string line = state.CommandHistory.GetNextCommand(); + state.InputManager.SetInput(state, line); + return Task.CompletedTask; + } + + public static Task Enter(ConsoleKeyInfo keyInfo, IShellState state, CancellationToken cancellationToken) + { + return state.CommandDispatcher.ExecuteCommandAsync(state, cancellationToken); + } + + public static Task Backspace(ConsoleKeyInfo keyInfo, IShellState state, CancellationToken cancellationToken) + { + state.InputManager.RemovePreviousCharacter(state); + return Task.CompletedTask; + } + + public static Task Unhandled(ConsoleKeyInfo keyInfo, IShellState state, CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + + public static Task Escape(ConsoleKeyInfo keyInfo, IShellState state, CancellationToken cancellationToken) + { + state.InputManager.SetInput(state, string.Empty); + return Task.CompletedTask; + } + + public static Task Tab(ConsoleKeyInfo keyInfo, IShellState state, CancellationToken cancellationToken) + { + if (keyInfo.Modifiers.HasFlag(ConsoleModifiers.Shift)) + { + state.SuggestionManager.PreviousSuggestion(state); + } + else + { + state.SuggestionManager.NextSuggestion(state); + } + + return Task.CompletedTask; + } + + public static Task Delete(ConsoleKeyInfo keyInfo, IShellState state, CancellationToken cancellationToken) + { + state.InputManager.RemoveCurrentCharacter(state); + return Task.CompletedTask; + } + + public static Task Insert(ConsoleKeyInfo keyInfo, IShellState state, CancellationToken cancellationToken) + { + state.InputManager.IsOverwriteMode = !state.InputManager.IsOverwriteMode; + return Task.CompletedTask; + } + } +} diff --git a/src/Microsoft.Repl/Microsoft.Repl.csproj b/src/Microsoft.Repl/Microsoft.Repl.csproj new file mode 100644 index 0000000000..63af949be4 --- /dev/null +++ b/src/Microsoft.Repl/Microsoft.Repl.csproj @@ -0,0 +1,9 @@ + + + + netcoreapp2.2 + A framework for creating REPLs in .NET Core. + dotnet;repl + + + diff --git a/src/Microsoft.Repl/Parsing/CoreParseResult.cs b/src/Microsoft.Repl/Parsing/CoreParseResult.cs new file mode 100644 index 0000000000..8f345813f6 --- /dev/null +++ b/src/Microsoft.Repl/Parsing/CoreParseResult.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.Repl.Parsing +{ + public class CoreParseResult : ICoreParseResult + { + public CoreParseResult(int caretPositionWithinCommandText, int caretPositionWithinSelectedSection, string commandText, IReadOnlyList sections, int selectedSection, IReadOnlyDictionary sectionStartLookup, HashSet quotedSections) + { + CaretPositionWithinCommandText = caretPositionWithinCommandText; + CaretPositionWithinSelectedSection = caretPositionWithinSelectedSection; + CommandText = commandText; + Sections = sections; + SelectedSection = selectedSection; + SectionStartLookup = sectionStartLookup; + _quotedSections = quotedSections; + } + + public int CaretPositionWithinCommandText { get; } + + public int CaretPositionWithinSelectedSection { get; } + + public string CommandText { get; } + + public IReadOnlyList Sections { get; } + + public int SelectedSection { get; } + + public IReadOnlyDictionary SectionStartLookup { get; } + + private readonly HashSet _quotedSections; + + public bool IsQuotedSection(int index) + { + return _quotedSections.Contains(index); + } + + public virtual ICoreParseResult Slice(int numberOfLeadingSectionsToRemove) + { + if (numberOfLeadingSectionsToRemove == 0) + { + return this; + } + + if (numberOfLeadingSectionsToRemove >= Sections.Count) + { + return new CoreParseResult(0, 0, string.Empty, new[] { string.Empty }, 0, new Dictionary { { 0, 0 } }, new HashSet()); + } + + string commandText = CommandText.Substring(SectionStartLookup[numberOfLeadingSectionsToRemove]); + int caretPositionWithinCommandText = CaretPositionWithinCommandText - SectionStartLookup[numberOfLeadingSectionsToRemove]; + + if (caretPositionWithinCommandText < 0) + { + caretPositionWithinCommandText = 0; + } + + Dictionary sectionStartLookup = new Dictionary(); + List sections = new List(); + for (int i = 0; i < Sections.Count - numberOfLeadingSectionsToRemove; ++i) + { + sectionStartLookup[i] = SectionStartLookup[numberOfLeadingSectionsToRemove + i] - SectionStartLookup[numberOfLeadingSectionsToRemove]; + sections.Add(Sections[numberOfLeadingSectionsToRemove + i]); + } + + int selectedSection = SelectedSection - numberOfLeadingSectionsToRemove; + + if (selectedSection < 0) + { + selectedSection = 0; + } + + HashSet quotedSections = new HashSet(_quotedSections.Where(x => x > 0).Select(x => x - 1)); + return new CoreParseResult(caretPositionWithinCommandText, CaretPositionWithinSelectedSection, commandText, sections, selectedSection, sectionStartLookup, quotedSections); + } + } +} diff --git a/src/Microsoft.Repl/Parsing/CoreParser.cs b/src/Microsoft.Repl/Parsing/CoreParser.cs new file mode 100644 index 0000000000..35be4867a5 --- /dev/null +++ b/src/Microsoft.Repl/Parsing/CoreParser.cs @@ -0,0 +1,129 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.Repl.Parsing +{ + public class CoreParser : IParser + { + public ICoreParseResult Parse(string commandText, int caretPosition) + { + List sections = commandText.Split(' ').ToList(); + Dictionary sectionStartLookup = new Dictionary(); + HashSet quotedSections = new HashSet(); + int runningIndex = 0; + int selectedSection = -1; + int caretPositionWithinSelectedSection = 0; + bool isInQuotedSection = false; + + for (int i = 0; i < sections.Count; ++i) + { + int thisSectionLength = sections[i].Length; + bool isLastSection = i == sections.Count - 1; + + //If currently in a quoted section, combine with the previous section, check to see if this section closes the quotes + if (isInQuotedSection) + { + //Combine with the previous section + sections[i - 1] += " " + sections[i]; + sections.RemoveAt(i--); + + //Check for the closing quote + int sectionLength = sections[i].Length; + if (sections[i][sectionLength - 1] == '"') + { + if (sectionLength > 1 && sections[i][sectionLength - 2] != '\\') + { + isInQuotedSection = false; + } + } + } + //Not in a quoted section, check to see if we're starting one + else + { + sectionStartLookup[i] = runningIndex; + + if (sections[i].Length > 0) + { + if (sections[i][0] == '"') + { + isInQuotedSection = true; + } + } + } + + //Update the running index, adding one for all but the last element to account for the spaces between the sections + runningIndex += thisSectionLength + (isLastSection ? 0 : 1); + + //If the selected section hasn't been determined yet, and the end of the text is past the caret, set the selected + // section to the current section and set the initial value for the caret position within the selected section. + // Note that the caret position within the selected section, unlike the other positions, accounts for escape + // sequences and must be fixed up when escape sequences are removed + if (selectedSection == -1 && runningIndex > caretPosition) + { + selectedSection = i; + caretPositionWithinSelectedSection = caretPosition - sectionStartLookup[i]; + } + } + + //Unescape the sections + // Note that this isn't combined with the above loop to avoid additional complexity in the quoted section case + for (int i = 0; i < sections.Count; ++i) + { + string s = sections[i]; + + //Trim quotes if needed + if (s.Length > 1) + { + if (s[0] == s[s.Length - 1] && s[0] == '"') + { + s = s.Substring(1, s.Length - 2); + quotedSections.Add(i); + + //Fix up the caret position in the text + if (selectedSection == i) + { + //If the caret was on the closing quote, back up to the last character of the section + if (caretPositionWithinSelectedSection == s.Length - 1) + { + caretPositionWithinSelectedSection -= 2; + } + //If the caret was after the opening quote, back up one + else if (caretPositionWithinSelectedSection > 0) + { + --caretPositionWithinSelectedSection; + } + } + } + } + + for (int j = 0; j < s.Length - 1; ++j) + { + if (s[j] == '\\') + { + if (s[j + 1] == '\\' || s[j + 1] == '"') + { + s = s.Substring(0, j) + s.Substring(j + 1); + + //If we're changing the selected section, and we're removing a character + // from before the caret position, back the caret position up to account for it + if (selectedSection == i && j < caretPositionWithinSelectedSection) + { + --caretPositionWithinSelectedSection; + } + } + } + } + + sections[i] = s; + } + + if (selectedSection == -1) + { + selectedSection = sections.Count - 1; + caretPositionWithinSelectedSection = sections[selectedSection].Length; + } + + return new CoreParseResult(caretPosition, caretPositionWithinSelectedSection, commandText, sections, selectedSection, sectionStartLookup, quotedSections); + } + } +} diff --git a/src/Microsoft.Repl/Parsing/ICoreParseResult.cs b/src/Microsoft.Repl/Parsing/ICoreParseResult.cs new file mode 100644 index 0000000000..20ac2c7b4e --- /dev/null +++ b/src/Microsoft.Repl/Parsing/ICoreParseResult.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; + +namespace Microsoft.Repl.Parsing +{ + public interface ICoreParseResult + { + int CaretPositionWithinCommandText { get; } + + int CaretPositionWithinSelectedSection { get; } + + string CommandText { get; } + + IReadOnlyList Sections { get; } + + bool IsQuotedSection(int index); + + int SelectedSection { get; } + + IReadOnlyDictionary SectionStartLookup { get; } + + ICoreParseResult Slice(int numberOfLeadingSectionsToRemove); + } +} diff --git a/src/Microsoft.Repl/Parsing/IParser.cs b/src/Microsoft.Repl/Parsing/IParser.cs new file mode 100644 index 0000000000..c909f7d94f --- /dev/null +++ b/src/Microsoft.Repl/Parsing/IParser.cs @@ -0,0 +1,12 @@ +namespace Microsoft.Repl.Parsing +{ + public interface IParser + { + ICoreParseResult Parse(string commandText, int caretPosition); + } + + public interface IParser : IParser + { + new TParseResult Parse(string commandText, int caretPosition); + } +} diff --git a/src/Microsoft.Repl/Scripting/IScriptExecutor.cs b/src/Microsoft.Repl/Scripting/IScriptExecutor.cs new file mode 100644 index 0000000000..7d832d8306 --- /dev/null +++ b/src/Microsoft.Repl/Scripting/IScriptExecutor.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Repl.Scripting +{ + public interface IScriptExecutor + { + Task ExecuteScriptAsync(IShellState shellState, IEnumerable commandTexts, CancellationToken cancellationToken); + } +} diff --git a/src/Microsoft.Repl/Scripting/ScriptExecutor.cs b/src/Microsoft.Repl/Scripting/ScriptExecutor.cs new file mode 100644 index 0000000000..3ec4bf5d21 --- /dev/null +++ b/src/Microsoft.Repl/Scripting/ScriptExecutor.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Repl.Commanding; +using Microsoft.Repl.Parsing; + +namespace Microsoft.Repl.Scripting +{ + public class ScriptExecutor : IScriptExecutor + where TParseResult : ICoreParseResult + { + private readonly bool _hideScriptLinesFromHistory; + + public ScriptExecutor(bool hideScriptLinesFromHistory = true) + { + _hideScriptLinesFromHistory = hideScriptLinesFromHistory; + } + + public async Task ExecuteScriptAsync(IShellState shellState, IEnumerable commandTexts, CancellationToken cancellationToken) + { + if (shellState.CommandDispatcher is ICommandDispatcher dispatcher) + { + IDisposable suppressor = _hideScriptLinesFromHistory ? shellState.CommandHistory.SuspendHistory() : null; + + using (suppressor) + { + foreach (string commandText in commandTexts) + { + if (string.IsNullOrWhiteSpace(commandText)) + { + continue; + } + + if (cancellationToken.IsCancellationRequested) + { + break; + } + + dispatcher.OnReady(shellState); + shellState.ConsoleManager.ResetCommandStart(); + shellState.InputManager.SetInput(shellState, commandText); + await dispatcher.ExecuteCommandAsync(shellState, cancellationToken).ConfigureAwait(false); + } + } + } + } + } +} diff --git a/src/Microsoft.Repl/Shell.cs b/src/Microsoft.Repl/Shell.cs new file mode 100644 index 0000000000..49f450eae0 --- /dev/null +++ b/src/Microsoft.Repl/Shell.cs @@ -0,0 +1,30 @@ +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Repl.Commanding; +using Microsoft.Repl.Input; +using Microsoft.Repl.Suggestions; + +namespace Microsoft.Repl +{ + public class Shell + { + public Shell(IShellState shellState) + { + KeyHandlers.RegisterDefaultKeyHandlers(shellState.InputManager); + ShellState = shellState; + } + + public Shell(ICommandDispatcher dispatcher, ISuggestionManager suggestionManager = null) + : this(new ShellState(dispatcher, suggestionManager)) + { + } + + public IShellState ShellState { get; } + + public Task RunAsync(CancellationToken cancellationToken) + { + ShellState.CommandDispatcher.OnReady(ShellState); + return ShellState.InputManager.StartAsync(ShellState, cancellationToken); + } + } +} diff --git a/src/Microsoft.Repl/ShellState.cs b/src/Microsoft.Repl/ShellState.cs new file mode 100644 index 0000000000..ef0ccf2a64 --- /dev/null +++ b/src/Microsoft.Repl/ShellState.cs @@ -0,0 +1,31 @@ +using Microsoft.Repl.Commanding; +using Microsoft.Repl.ConsoleHandling; +using Microsoft.Repl.Input; +using Microsoft.Repl.Suggestions; + +namespace Microsoft.Repl +{ + public class ShellState : IShellState + { + public ShellState(ICommandDispatcher commandDispatcher, ISuggestionManager suggestionManager = null, IInputManager inputManager = null, ICommandHistory commandHistory = null, IConsoleManager consoleManager = null) + { + InputManager = inputManager ?? new InputManager(); + CommandHistory = commandHistory ?? new CommandHistory(); + ConsoleManager = consoleManager ?? new ConsoleManager(); + CommandDispatcher = commandDispatcher; + SuggestionManager = suggestionManager ?? new SuggestionManager(); + } + + public IInputManager InputManager { get; } + + public ICommandHistory CommandHistory { get; } + + public IConsoleManager ConsoleManager { get; } + + public ICommandDispatcher CommandDispatcher { get; } + + public bool IsExiting { get; set; } + + public ISuggestionManager SuggestionManager { get; } + } +} diff --git a/src/Microsoft.Repl/Suggestions/FileSystemCompletion.cs b/src/Microsoft.Repl/Suggestions/FileSystemCompletion.cs new file mode 100644 index 0000000000..71237fcd27 --- /dev/null +++ b/src/Microsoft.Repl/Suggestions/FileSystemCompletion.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace Microsoft.Repl.Suggestions +{ + public static class FileSystemCompletion + { + public static IEnumerable GetCompletions(string prefix) + { + if (prefix.StartsWith('\"')) + { + prefix = prefix.Substring(1); + + int lastQuote = prefix.LastIndexOf('\"'); + + if (lastQuote > -1) + { + prefix = prefix.Remove(lastQuote, 1); + } + + while (prefix.EndsWith($"{Path.DirectorySeparatorChar}{Path.DirectorySeparatorChar}")) + { + prefix = prefix.Substring(0, prefix.Length - 1); + } + } + + int lastPathIndex = prefix.LastIndexOfAny(new[] { '\\', '/' }); + if (lastPathIndex < 0) + { + return null; + } + + string dir = prefix.Substring(0, lastPathIndex + 1); + + if (dir.IndexOfAny(Path.GetInvalidPathChars()) > -1) + { + return null; + } + + string partPrefix = prefix.Substring(lastPathIndex + 1); + if (Directory.Exists(dir)) + { + return Directory.EnumerateDirectories(dir).Where(x => Path.GetFileName(x).StartsWith(partPrefix, StringComparison.OrdinalIgnoreCase)) + .Union(Directory.EnumerateFiles(dir).Where(x => Path.GetFileName(x).StartsWith(partPrefix, StringComparison.OrdinalIgnoreCase))).Select(x => x.IndexOf(' ') > -1 ? $"\"{x}\"" : x); + } + + return null; + } + } +} diff --git a/src/Microsoft.Repl/Suggestions/ISuggestionManager.cs b/src/Microsoft.Repl/Suggestions/ISuggestionManager.cs new file mode 100644 index 0000000000..f7e11a06fb --- /dev/null +++ b/src/Microsoft.Repl/Suggestions/ISuggestionManager.cs @@ -0,0 +1,9 @@ +namespace Microsoft.Repl.Suggestions +{ + public interface ISuggestionManager + { + void NextSuggestion(IShellState shellState); + + void PreviousSuggestion(IShellState shellState); + } +} diff --git a/src/Microsoft.Repl/Suggestions/SuggestionManager.cs b/src/Microsoft.Repl/Suggestions/SuggestionManager.cs new file mode 100644 index 0000000000..1fd9e861da --- /dev/null +++ b/src/Microsoft.Repl/Suggestions/SuggestionManager.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Generic; +using Microsoft.Repl.Parsing; + +namespace Microsoft.Repl.Suggestions +{ + public class SuggestionManager : ISuggestionManager + { + private int _currentSuggestion; + private IReadOnlyList _suggestions; + private ICoreParseResult _expected; + + public void NextSuggestion(IShellState shellState) + { + string line = shellState.InputManager.GetCurrentBuffer(); + ICoreParseResult parseResult = shellState.CommandDispatcher.Parser.Parse(line, shellState.ConsoleManager.CaretPosition); + string currentSuggestion; + + //Check to see if we're continuing before querying for suggestions again + if (_expected != null + && string.Equals(_expected.CommandText, parseResult.CommandText, StringComparison.Ordinal) + && _expected.SelectedSection == parseResult.SelectedSection + && _expected.CaretPositionWithinSelectedSection == parseResult.CaretPositionWithinSelectedSection) + { + if (_suggestions == null || _suggestions.Count == 0) + { + return; + } + + _currentSuggestion = (_currentSuggestion + 1) % _suggestions.Count; + currentSuggestion = _suggestions[_currentSuggestion]; + } + else + { + _currentSuggestion = 0; + _suggestions = shellState.CommandDispatcher.CollectSuggesetions(shellState); + + if (_suggestions == null || _suggestions.Count == 0) + { + return; + } + + currentSuggestion = _suggestions[0]; + } + + //We now have a suggestion, take the command text leading up to the section being suggested for, + // concatenate that and the suggestion text, turn it in to keys, submit it to the input manager, + // reset the caret, store the parse result of the new text as what's expected for a continuation + string newText = parseResult.CommandText.Substring(0, parseResult.SectionStartLookup[parseResult.SelectedSection]) + currentSuggestion; + _expected = shellState.CommandDispatcher.Parser.Parse(newText, newText.Length); + shellState.InputManager.SetInput(shellState, newText); + } + + public void PreviousSuggestion(IShellState shellState) + { + string line = shellState.InputManager.GetCurrentBuffer(); + ICoreParseResult parseResult = shellState.CommandDispatcher.Parser.Parse(line, shellState.ConsoleManager.CaretPosition); + string currentSuggestion; + + //Check to see if we're continuing before querying for suggestions again + if (_expected != null + && string.Equals(_expected.CommandText, parseResult.CommandText, StringComparison.Ordinal) + && _expected.SelectedSection == parseResult.SelectedSection + && _expected.CaretPositionWithinSelectedSection == parseResult.CaretPositionWithinSelectedSection) + { + if (_suggestions == null || _suggestions.Count == 0) + { + return; + } + + _currentSuggestion = (_currentSuggestion - 1 + _suggestions.Count) % _suggestions.Count; + currentSuggestion = _suggestions[_currentSuggestion]; + } + else + { + _suggestions = shellState.CommandDispatcher.CollectSuggesetions(shellState); + _currentSuggestion = _suggestions.Count - 1; + + if (_suggestions == null || _suggestions.Count == 0) + { + return; + } + + currentSuggestion = _suggestions[_suggestions.Count - 1]; + } + + //We now have a suggestion, take the command text leading up to the section being suggested for, + // concatenate that and the suggestion text, turn it in to keys, submit it to the input manager, + // reset the caret, store the parse result of the new text as what's expected for a continuation + string newText = parseResult.CommandText.Substring(0, parseResult.SectionStartLookup[parseResult.SelectedSection]) + currentSuggestion; + _expected = shellState.CommandDispatcher.Parser.Parse(newText, newText.Length); + shellState.InputManager.SetInput(shellState, newText); + } + } +} diff --git a/src/Microsoft.Repl/Utils.cs b/src/Microsoft.Repl/Utils.cs new file mode 100644 index 0000000000..37ffcd97fb --- /dev/null +++ b/src/Microsoft.Repl/Utils.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; + +namespace Microsoft.Repl +{ + public static class Utils + { + public static string Stringify(this IReadOnlyList keys) + { + return string.Join("", keys); + } + } +} diff --git a/test/Microsoft.HttpRepl.Tests/JsonVisitorTests.cs b/test/Microsoft.HttpRepl.Tests/JsonVisitorTests.cs new file mode 100644 index 0000000000..30155b022f --- /dev/null +++ b/test/Microsoft.HttpRepl.Tests/JsonVisitorTests.cs @@ -0,0 +1,60 @@ +using Microsoft.HttpRepl.Formatting; +using Microsoft.HttpRepl.Preferences; +using Microsoft.Repl.ConsoleHandling; +using Xunit; + +namespace Microsoft.HttpRepl.Tests +{ + public class JsonVisitorTests + { + [Fact] + public void JsonVisitor_ObjectWithComments() + { + string testData = @"[ + { + //Object 1 + ""property"": ""value"", + ""and"": ""again"" + }, + { + //Object 2 + }, + [ + //An array + ], + null, + 1, + 3.2, + ""test"", + false +]"; + + string formatted = JsonVisitor.FormatAndColorize(new MockJsonConfig(), testData); + } + + private class MockJsonConfig : IJsonConfig + { + public int IndentSize => 2; + + public AllowedColors DefaultColor => AllowedColors.None; + + public AllowedColors ArrayBraceColor => AllowedColors.None; + + public AllowedColors ObjectBraceColor => AllowedColors.None; + + public AllowedColors CommaColor => AllowedColors.None; + + public AllowedColors NameColor => AllowedColors.None; + + public AllowedColors NameSeparatorColor => AllowedColors.None; + + public AllowedColors BoolColor => AllowedColors.None; + + public AllowedColors NumericColor => AllowedColors.None; + + public AllowedColors StringColor => AllowedColors.None; + + public AllowedColors NullColor => AllowedColors.None; + } + } +} diff --git a/test/Microsoft.HttpRepl.Tests/Microsoft.HttpRepl.Tests.csproj b/test/Microsoft.HttpRepl.Tests/Microsoft.HttpRepl.Tests.csproj new file mode 100644 index 0000000000..7a79d0121e --- /dev/null +++ b/test/Microsoft.HttpRepl.Tests/Microsoft.HttpRepl.Tests.csproj @@ -0,0 +1,21 @@ + + + + netcoreapp2.2 + + false + + + + + + + + + + + + + + + diff --git a/test/Microsoft.Repl.Tests/Microsoft.Repl.Tests.csproj b/test/Microsoft.Repl.Tests/Microsoft.Repl.Tests.csproj new file mode 100644 index 0000000000..52fbd0de16 --- /dev/null +++ b/test/Microsoft.Repl.Tests/Microsoft.Repl.Tests.csproj @@ -0,0 +1,19 @@ + + + + netcoreapp2.2 + + false + + + + + + + + + + + + + diff --git a/test/Microsoft.Repl.Tests/ParserTests.cs b/test/Microsoft.Repl.Tests/ParserTests.cs new file mode 100644 index 0000000000..422123db85 --- /dev/null +++ b/test/Microsoft.Repl.Tests/ParserTests.cs @@ -0,0 +1,26 @@ +using Microsoft.Repl.Parsing; +using Xunit; + +namespace Microsoft.Repl.Tests +{ + public class ParserTests + { + [Fact] + public void ParserTests_Basic() + { + string testString = "\"this is a test\" of \"Escape\\\\Sequences\\\"\""; + + CoreParser parser = new CoreParser(); + ICoreParseResult result = parser.Parse(testString, 29); + + Assert.Equal(3, result.Sections.Count); + Assert.Equal(2, result.SelectedSection); + Assert.Equal(0, result.SectionStartLookup[0]); + Assert.Equal(17, result.SectionStartLookup[1]); + Assert.Equal(20, result.SectionStartLookup[2]); + Assert.Equal(7, result.CaretPositionWithinSelectedSection); + Assert.Equal(29, result.CaretPositionWithinCommandText); + Assert.Equal(testString, result.CommandText); + } + } +} From 7f8f210a24fcebdd661037fd6cf6b1ed4bc1d06c Mon Sep 17 00:00:00 2001 From: Mike Lorbetske Date: Wed, 1 Aug 2018 12:35:27 -0700 Subject: [PATCH 32/66] Add license headers to all files Bump version of JSON.NET to 11.0.2 --- build/dependencies.props | 4 ++-- .../AggregateDirectoryStructure.cs | 5 ++++- .../Commands/BaseHttpCommand.cs | 5 ++++- .../Commands/ChangeDirectoryCommand.cs | 5 ++++- src/Microsoft.HttpRepl/Commands/ClearCommand.cs | 5 ++++- src/Microsoft.HttpRepl/Commands/ConfigCommand.cs | 5 ++++- src/Microsoft.HttpRepl/Commands/DeleteCommand.cs | 5 ++++- src/Microsoft.HttpRepl/Commands/EchoCommand.cs | 5 ++++- src/Microsoft.HttpRepl/Commands/ExitCommand.cs | 5 ++++- src/Microsoft.HttpRepl/Commands/Formatter.cs | 13 +++++-------- src/Microsoft.HttpRepl/Commands/GetCommand.cs | 5 ++++- src/Microsoft.HttpRepl/Commands/HeadCommand.cs | 5 ++++- src/Microsoft.HttpRepl/Commands/HelpCommand.cs | 5 ++++- src/Microsoft.HttpRepl/Commands/ListCommand.cs | 5 ++++- .../Commands/OptionsCommand.cs | 5 ++++- src/Microsoft.HttpRepl/Commands/PatchCommand.cs | 5 ++++- src/Microsoft.HttpRepl/Commands/PostCommand.cs | 5 ++++- src/Microsoft.HttpRepl/Commands/PrefCommand.cs | 5 ++++- src/Microsoft.HttpRepl/Commands/PutCommand.cs | 5 ++++- src/Microsoft.HttpRepl/Commands/RunCommand.cs | 5 ++++- .../Commands/SetBaseCommand.cs | 5 ++++- .../Commands/SetDiagCommand.cs | 5 ++++- .../Commands/SetHeaderCommand.cs | 5 ++++- .../Commands/SetSwaggerCommand.cs | 5 ++++- src/Microsoft.HttpRepl/Commands/TreeNode.cs | 7 +++++-- src/Microsoft.HttpRepl/Commands/UICommand.cs | 5 ++++- src/Microsoft.HttpRepl/Diagnostics/ConfigItem.cs | 5 ++++- .../Diagnostics/DiagEndpoint.cs | 5 ++++- .../Diagnostics/DiagEndpointMetadata.cs | 5 ++++- src/Microsoft.HttpRepl/Diagnostics/DiagItem.cs | 5 ++++- .../Diagnostics/DiagnosticsState.cs | 5 ++++- src/Microsoft.HttpRepl/DirectoryStructure.cs | 5 ++++- .../DirectoryStructureExtensions.cs | 5 ++++- src/Microsoft.HttpRepl/Formatting/JsonVisitor.cs | 5 ++++- src/Microsoft.HttpRepl/HttpState.cs | 5 ++++- src/Microsoft.HttpRepl/IDirectoryStructure.cs | 14 ++++---------- src/Microsoft.HttpRepl/IRequestInfo.cs | 16 ++++++++++++++++ src/Microsoft.HttpRepl/OpenApi/Either.cs | 5 ++++- .../OpenApi/EitherConverter.cs | 5 ++++- .../OpenApi/EndpointMetadata.cs | 5 ++++- .../OpenApi/EndpointMetadataReader.cs | 5 ++++- .../OpenApi/IEndpointMetadataReader.cs | 5 ++++- .../OpenApi/OpenApiV3EndpointMetadataReader.cs | 5 ++++- src/Microsoft.HttpRepl/OpenApi/Parameter.cs | 5 ++++- src/Microsoft.HttpRepl/OpenApi/PointerUtil.cs | 5 ++++- src/Microsoft.HttpRepl/OpenApi/Schema.cs | 5 ++++- .../OpenApi/SwaggerV1EndpointMetadataReader.cs | 5 ++++- .../OpenApi/SwaggerV2EndpointMetadataReader.cs | 5 ++++- .../Preferences/IJsonConfig.cs | 7 +++++-- src/Microsoft.HttpRepl/Preferences/JsonConfig.cs | 5 ++++- .../Preferences/RequestConfig.cs | 5 ++++- .../Preferences/RequestOrResponseConfig.cs | 5 ++++- .../Preferences/ResponseConfig.cs | 5 ++++- .../Preferences/WellKnownPreference.cs | 5 ++++- src/Microsoft.HttpRepl/Program.cs | 5 ++++- .../Suggestions/HeaderCompletion.cs | 5 ++++- .../Suggestions/ServerPathCompletion.cs | 5 ++++- src/Microsoft.Repl/Commanding/CommandHistory.cs | 5 ++++- .../Commanding/CommandInputLocation.cs | 7 +++++-- .../Commanding/CommandInputProcessingIssue.cs | 7 +++++-- .../CommandInputProcessingIssueKind.cs | 7 +++++-- .../Commanding/CommandInputSpecification.cs | 7 +++++-- .../CommandInputSpecificationBuilder.cs | 7 +++++-- .../Commanding/CommandOptionSpecification.cs | 7 +++++-- .../Commanding/CommandWithStructuredInputBase.cs | 7 +++++-- .../Commanding/DefaultCommandDispatcher.cs | 5 ++++- .../Commanding/DefaultCommandInput.cs | 7 +++++-- src/Microsoft.Repl/Commanding/ICommand.cs | 5 ++++- .../Commanding/ICommandDispatcher.cs | 5 ++++- src/Microsoft.Repl/Commanding/ICommandHistory.cs | 5 ++++- src/Microsoft.Repl/Commanding/InputElement.cs | 7 +++++-- .../ConsoleHandling/AllowedColors.cs | 7 +++++-- .../ConsoleHandling/AnsiColorExtensions.cs | 5 ++++- .../ConsoleHandling/AnsiConsole.cs | 5 ++++- .../ConsoleHandling/ConsoleManager.cs | 5 ++++- .../ConsoleHandling/IConsoleManager.cs | 5 ++++- src/Microsoft.Repl/ConsoleHandling/IWritable.cs | 5 ++++- src/Microsoft.Repl/ConsoleHandling/Point.cs | 5 ++++- src/Microsoft.Repl/ConsoleHandling/Reporter.cs | 5 ++++- src/Microsoft.Repl/ConsoleHandling/Writable.cs | 5 ++++- src/Microsoft.Repl/Disposable.cs | 5 ++++- src/Microsoft.Repl/IShellState.cs | 5 ++++- src/Microsoft.Repl/Input/AsyncKeyPressHandler.cs | 5 ++++- src/Microsoft.Repl/Input/IInputManager.cs | 5 ++++- src/Microsoft.Repl/Input/InputManager.cs | 5 ++++- src/Microsoft.Repl/Input/KeyHandlers.cs | 5 ++++- src/Microsoft.Repl/Parsing/CoreParseResult.cs | 5 ++++- src/Microsoft.Repl/Parsing/CoreParser.cs | 5 ++++- src/Microsoft.Repl/Parsing/ICoreParseResult.cs | 5 ++++- src/Microsoft.Repl/Parsing/IParser.cs | 5 ++++- src/Microsoft.Repl/Scripting/IScriptExecutor.cs | 5 ++++- src/Microsoft.Repl/Scripting/ScriptExecutor.cs | 5 ++++- src/Microsoft.Repl/Shell.cs | 5 ++++- src/Microsoft.Repl/ShellState.cs | 5 ++++- .../Suggestions/FileSystemCompletion.cs | 5 ++++- .../Suggestions/ISuggestionManager.cs | 5 ++++- .../Suggestions/SuggestionManager.cs | 5 ++++- src/Microsoft.Repl/Utils.cs | 5 ++++- 98 files changed, 415 insertions(+), 126 deletions(-) create mode 100644 src/Microsoft.HttpRepl/IRequestInfo.cs diff --git a/build/dependencies.props b/build/dependencies.props index 0c7d2bca9d..ee0b0b8ccf 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -1,4 +1,4 @@ - + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) @@ -17,7 +17,7 @@ 2.0.3 4.5.1 4.5.0 - 10.0.1 + 11.0.2 9.0.1 2.3.1 2.4.0 diff --git a/src/Microsoft.HttpRepl/AggregateDirectoryStructure.cs b/src/Microsoft.HttpRepl/AggregateDirectoryStructure.cs index ed763a3e8f..19dcb289f8 100644 --- a/src/Microsoft.HttpRepl/AggregateDirectoryStructure.cs +++ b/src/Microsoft.HttpRepl/AggregateDirectoryStructure.cs @@ -1,4 +1,7 @@ -using System; +// 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.Collections.Generic; using System.Linq; diff --git a/src/Microsoft.HttpRepl/Commands/BaseHttpCommand.cs b/src/Microsoft.HttpRepl/Commands/BaseHttpCommand.cs index 3db2f39b7b..374544790b 100644 --- a/src/Microsoft.HttpRepl/Commands/BaseHttpCommand.cs +++ b/src/Microsoft.HttpRepl/Commands/BaseHttpCommand.cs @@ -1,4 +1,7 @@ -using System; +// 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.Collections.Generic; using System.Diagnostics; using System.IO; diff --git a/src/Microsoft.HttpRepl/Commands/ChangeDirectoryCommand.cs b/src/Microsoft.HttpRepl/Commands/ChangeDirectoryCommand.cs index 3a983698cf..c2baaf0de5 100644 --- a/src/Microsoft.HttpRepl/Commands/ChangeDirectoryCommand.cs +++ b/src/Microsoft.HttpRepl/Commands/ChangeDirectoryCommand.cs @@ -1,4 +1,7 @@ -using System; +// 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.Collections.Generic; using System.Linq; using System.Threading; diff --git a/src/Microsoft.HttpRepl/Commands/ClearCommand.cs b/src/Microsoft.HttpRepl/Commands/ClearCommand.cs index a695dfc544..4244946c95 100644 --- a/src/Microsoft.HttpRepl/Commands/ClearCommand.cs +++ b/src/Microsoft.HttpRepl/Commands/ClearCommand.cs @@ -1,4 +1,7 @@ -using System; +// 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.Collections.Generic; using System.Threading; using System.Threading.Tasks; diff --git a/src/Microsoft.HttpRepl/Commands/ConfigCommand.cs b/src/Microsoft.HttpRepl/Commands/ConfigCommand.cs index 56003280ab..773bee0f04 100644 --- a/src/Microsoft.HttpRepl/Commands/ConfigCommand.cs +++ b/src/Microsoft.HttpRepl/Commands/ConfigCommand.cs @@ -1,4 +1,7 @@ -using System; +// 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.Collections.Generic; using System.Linq; using System.Net.Http; diff --git a/src/Microsoft.HttpRepl/Commands/DeleteCommand.cs b/src/Microsoft.HttpRepl/Commands/DeleteCommand.cs index a861b35142..7c9b789db3 100644 --- a/src/Microsoft.HttpRepl/Commands/DeleteCommand.cs +++ b/src/Microsoft.HttpRepl/Commands/DeleteCommand.cs @@ -1,4 +1,7 @@ -namespace Microsoft.HttpRepl.Commands +// 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. + +namespace Microsoft.HttpRepl.Commands { public class DeleteCommand : BaseHttpCommand { diff --git a/src/Microsoft.HttpRepl/Commands/EchoCommand.cs b/src/Microsoft.HttpRepl/Commands/EchoCommand.cs index 19eb33788b..691eb5679f 100644 --- a/src/Microsoft.HttpRepl/Commands/EchoCommand.cs +++ b/src/Microsoft.HttpRepl/Commands/EchoCommand.cs @@ -1,4 +1,7 @@ -using System; +// 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.Collections.Generic; using System.Linq; using System.Threading; diff --git a/src/Microsoft.HttpRepl/Commands/ExitCommand.cs b/src/Microsoft.HttpRepl/Commands/ExitCommand.cs index ebc0635876..af76ad95df 100644 --- a/src/Microsoft.HttpRepl/Commands/ExitCommand.cs +++ b/src/Microsoft.HttpRepl/Commands/ExitCommand.cs @@ -1,4 +1,7 @@ -using System.Threading; +// 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.Threading; using System.Threading.Tasks; using Microsoft.Repl; using Microsoft.Repl.Commanding; diff --git a/src/Microsoft.HttpRepl/Commands/Formatter.cs b/src/Microsoft.HttpRepl/Commands/Formatter.cs index 5a450e3406..d1783dc357 100644 --- a/src/Microsoft.HttpRepl/Commands/Formatter.cs +++ b/src/Microsoft.HttpRepl/Commands/Formatter.cs @@ -1,18 +1,15 @@ -namespace Microsoft.HttpRepl.Commands +// 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. + +namespace Microsoft.HttpRepl.Commands { public class Formatter { - //private readonly List _prefix = new List(); private int _prefix; private int _maxDepth; public void RegisterEntry(int prefixLength, int depth) { - //while (_prefix.Count < depth + 1) - //{ - // _prefix.Add(0); - //} - if (depth > _maxDepth) { _maxDepth = depth; @@ -30,4 +27,4 @@ return (indent + prefix).PadRight(_prefix + 3 + _maxDepth * 4) + entry; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.HttpRepl/Commands/GetCommand.cs b/src/Microsoft.HttpRepl/Commands/GetCommand.cs index 76765ce374..e5441cecd1 100644 --- a/src/Microsoft.HttpRepl/Commands/GetCommand.cs +++ b/src/Microsoft.HttpRepl/Commands/GetCommand.cs @@ -1,4 +1,7 @@ -namespace Microsoft.HttpRepl.Commands +// 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. + +namespace Microsoft.HttpRepl.Commands { public class GetCommand : BaseHttpCommand { diff --git a/src/Microsoft.HttpRepl/Commands/HeadCommand.cs b/src/Microsoft.HttpRepl/Commands/HeadCommand.cs index 97023f5860..fbd3de4971 100644 --- a/src/Microsoft.HttpRepl/Commands/HeadCommand.cs +++ b/src/Microsoft.HttpRepl/Commands/HeadCommand.cs @@ -1,4 +1,7 @@ -namespace Microsoft.HttpRepl.Commands +// 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. + +namespace Microsoft.HttpRepl.Commands { public class HeadCommand : BaseHttpCommand { diff --git a/src/Microsoft.HttpRepl/Commands/HelpCommand.cs b/src/Microsoft.HttpRepl/Commands/HelpCommand.cs index de909a4906..f205fc1243 100644 --- a/src/Microsoft.HttpRepl/Commands/HelpCommand.cs +++ b/src/Microsoft.HttpRepl/Commands/HelpCommand.cs @@ -1,4 +1,7 @@ -using System; +// 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.Collections.Generic; using System.Linq; using System.Threading; diff --git a/src/Microsoft.HttpRepl/Commands/ListCommand.cs b/src/Microsoft.HttpRepl/Commands/ListCommand.cs index 078a20d398..b1aa0718a3 100644 --- a/src/Microsoft.HttpRepl/Commands/ListCommand.cs +++ b/src/Microsoft.HttpRepl/Commands/ListCommand.cs @@ -1,4 +1,7 @@ -using System; +// 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.Collections.Generic; using System.Globalization; using System.Linq; diff --git a/src/Microsoft.HttpRepl/Commands/OptionsCommand.cs b/src/Microsoft.HttpRepl/Commands/OptionsCommand.cs index f6a3c8903c..5da5cadfaf 100644 --- a/src/Microsoft.HttpRepl/Commands/OptionsCommand.cs +++ b/src/Microsoft.HttpRepl/Commands/OptionsCommand.cs @@ -1,4 +1,7 @@ -namespace Microsoft.HttpRepl.Commands +// 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. + +namespace Microsoft.HttpRepl.Commands { public class OptionsCommand : BaseHttpCommand { diff --git a/src/Microsoft.HttpRepl/Commands/PatchCommand.cs b/src/Microsoft.HttpRepl/Commands/PatchCommand.cs index c5d9c875be..bfb9cc9192 100644 --- a/src/Microsoft.HttpRepl/Commands/PatchCommand.cs +++ b/src/Microsoft.HttpRepl/Commands/PatchCommand.cs @@ -1,4 +1,7 @@ -namespace Microsoft.HttpRepl.Commands +// 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. + +namespace Microsoft.HttpRepl.Commands { public class PatchCommand : BaseHttpCommand { diff --git a/src/Microsoft.HttpRepl/Commands/PostCommand.cs b/src/Microsoft.HttpRepl/Commands/PostCommand.cs index 216fe8ef1d..61edb6d4c8 100644 --- a/src/Microsoft.HttpRepl/Commands/PostCommand.cs +++ b/src/Microsoft.HttpRepl/Commands/PostCommand.cs @@ -1,4 +1,7 @@ -namespace Microsoft.HttpRepl.Commands +// 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. + +namespace Microsoft.HttpRepl.Commands { public class PostCommand : BaseHttpCommand { diff --git a/src/Microsoft.HttpRepl/Commands/PrefCommand.cs b/src/Microsoft.HttpRepl/Commands/PrefCommand.cs index 7f16af09b3..2a64d2f017 100644 --- a/src/Microsoft.HttpRepl/Commands/PrefCommand.cs +++ b/src/Microsoft.HttpRepl/Commands/PrefCommand.cs @@ -1,4 +1,7 @@ -using System; +// 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.Collections.Generic; using System.Linq; using System.Threading; diff --git a/src/Microsoft.HttpRepl/Commands/PutCommand.cs b/src/Microsoft.HttpRepl/Commands/PutCommand.cs index a65439dfb7..4f37e1776c 100644 --- a/src/Microsoft.HttpRepl/Commands/PutCommand.cs +++ b/src/Microsoft.HttpRepl/Commands/PutCommand.cs @@ -1,4 +1,7 @@ -namespace Microsoft.HttpRepl.Commands +// 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. + +namespace Microsoft.HttpRepl.Commands { public class PutCommand : BaseHttpCommand { diff --git a/src/Microsoft.HttpRepl/Commands/RunCommand.cs b/src/Microsoft.HttpRepl/Commands/RunCommand.cs index 6dd2f4a378..3228dbccf1 100644 --- a/src/Microsoft.HttpRepl/Commands/RunCommand.cs +++ b/src/Microsoft.HttpRepl/Commands/RunCommand.cs @@ -1,4 +1,7 @@ -using System; +// 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.Collections.Generic; using System.IO; using System.Threading; diff --git a/src/Microsoft.HttpRepl/Commands/SetBaseCommand.cs b/src/Microsoft.HttpRepl/Commands/SetBaseCommand.cs index c346f541d4..77e5f0adde 100644 --- a/src/Microsoft.HttpRepl/Commands/SetBaseCommand.cs +++ b/src/Microsoft.HttpRepl/Commands/SetBaseCommand.cs @@ -1,4 +1,7 @@ -using System; +// 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.Collections.Generic; using System.Threading; using System.Threading.Tasks; diff --git a/src/Microsoft.HttpRepl/Commands/SetDiagCommand.cs b/src/Microsoft.HttpRepl/Commands/SetDiagCommand.cs index 4e86452b7c..535d1a2026 100644 --- a/src/Microsoft.HttpRepl/Commands/SetDiagCommand.cs +++ b/src/Microsoft.HttpRepl/Commands/SetDiagCommand.cs @@ -1,4 +1,7 @@ -using System; +// 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.Collections.Generic; using System.Linq; using System.Net.Http; diff --git a/src/Microsoft.HttpRepl/Commands/SetHeaderCommand.cs b/src/Microsoft.HttpRepl/Commands/SetHeaderCommand.cs index c95d22a6be..3db62f47b3 100644 --- a/src/Microsoft.HttpRepl/Commands/SetHeaderCommand.cs +++ b/src/Microsoft.HttpRepl/Commands/SetHeaderCommand.cs @@ -1,4 +1,7 @@ -using System; +// 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.Collections.Generic; using System.Linq; using System.Threading; diff --git a/src/Microsoft.HttpRepl/Commands/SetSwaggerCommand.cs b/src/Microsoft.HttpRepl/Commands/SetSwaggerCommand.cs index 9c9a1f36cc..be99891d5e 100644 --- a/src/Microsoft.HttpRepl/Commands/SetSwaggerCommand.cs +++ b/src/Microsoft.HttpRepl/Commands/SetSwaggerCommand.cs @@ -1,4 +1,7 @@ -using System; +// 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.Collections.Generic; using System.IO; using System.Linq; diff --git a/src/Microsoft.HttpRepl/Commands/TreeNode.cs b/src/Microsoft.HttpRepl/Commands/TreeNode.cs index 256d5d9dae..1ae5db3cbc 100644 --- a/src/Microsoft.HttpRepl/Commands/TreeNode.cs +++ b/src/Microsoft.HttpRepl/Commands/TreeNode.cs @@ -1,4 +1,7 @@ -using System; +// 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.Collections.Generic; namespace Microsoft.HttpRepl.Commands @@ -44,4 +47,4 @@ namespace Microsoft.HttpRepl.Commands return self + Environment.NewLine + string.Join(Environment.NewLine, _children); } } -} \ No newline at end of file +} diff --git a/src/Microsoft.HttpRepl/Commands/UICommand.cs b/src/Microsoft.HttpRepl/Commands/UICommand.cs index ddf4ca4097..0eb02826f9 100644 --- a/src/Microsoft.HttpRepl/Commands/UICommand.cs +++ b/src/Microsoft.HttpRepl/Commands/UICommand.cs @@ -1,4 +1,7 @@ -using System; +// 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.Collections.Generic; using System.Diagnostics; using System.Runtime.InteropServices; diff --git a/src/Microsoft.HttpRepl/Diagnostics/ConfigItem.cs b/src/Microsoft.HttpRepl/Diagnostics/ConfigItem.cs index ba16701d58..8e5c2943e4 100644 --- a/src/Microsoft.HttpRepl/Diagnostics/ConfigItem.cs +++ b/src/Microsoft.HttpRepl/Diagnostics/ConfigItem.cs @@ -1,4 +1,7 @@ -namespace Microsoft.HttpRepl.Diagnostics +// 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. + +namespace Microsoft.HttpRepl.Diagnostics { public class ConfigItem { diff --git a/src/Microsoft.HttpRepl/Diagnostics/DiagEndpoint.cs b/src/Microsoft.HttpRepl/Diagnostics/DiagEndpoint.cs index 80e619dcc3..a38e50bada 100644 --- a/src/Microsoft.HttpRepl/Diagnostics/DiagEndpoint.cs +++ b/src/Microsoft.HttpRepl/Diagnostics/DiagEndpoint.cs @@ -1,4 +1,7 @@ -namespace Microsoft.HttpRepl.Diagnostics +// 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. + +namespace Microsoft.HttpRepl.Diagnostics { public class DiagEndpoint { diff --git a/src/Microsoft.HttpRepl/Diagnostics/DiagEndpointMetadata.cs b/src/Microsoft.HttpRepl/Diagnostics/DiagEndpointMetadata.cs index 53e5aa3918..5989daf094 100644 --- a/src/Microsoft.HttpRepl/Diagnostics/DiagEndpointMetadata.cs +++ b/src/Microsoft.HttpRepl/Diagnostics/DiagEndpointMetadata.cs @@ -1,4 +1,7 @@ -namespace Microsoft.HttpRepl.Diagnostics +// 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. + +namespace Microsoft.HttpRepl.Diagnostics { public class DiagEndpointMetadata { diff --git a/src/Microsoft.HttpRepl/Diagnostics/DiagItem.cs b/src/Microsoft.HttpRepl/Diagnostics/DiagItem.cs index c0fbf2df9c..a9d0692cfd 100644 --- a/src/Microsoft.HttpRepl/Diagnostics/DiagItem.cs +++ b/src/Microsoft.HttpRepl/Diagnostics/DiagItem.cs @@ -1,4 +1,7 @@ -namespace Microsoft.HttpRepl.Diagnostics +// 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. + +namespace Microsoft.HttpRepl.Diagnostics { public class DiagItem { diff --git a/src/Microsoft.HttpRepl/Diagnostics/DiagnosticsState.cs b/src/Microsoft.HttpRepl/Diagnostics/DiagnosticsState.cs index 3fe27cce19..90033d9062 100644 --- a/src/Microsoft.HttpRepl/Diagnostics/DiagnosticsState.cs +++ b/src/Microsoft.HttpRepl/Diagnostics/DiagnosticsState.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// 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.Collections.Generic; namespace Microsoft.HttpRepl.Diagnostics { diff --git a/src/Microsoft.HttpRepl/DirectoryStructure.cs b/src/Microsoft.HttpRepl/DirectoryStructure.cs index f682488b9b..8787f962fc 100644 --- a/src/Microsoft.HttpRepl/DirectoryStructure.cs +++ b/src/Microsoft.HttpRepl/DirectoryStructure.cs @@ -1,4 +1,7 @@ -using System; +// 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.Collections.Generic; using System.Linq; diff --git a/src/Microsoft.HttpRepl/DirectoryStructureExtensions.cs b/src/Microsoft.HttpRepl/DirectoryStructureExtensions.cs index 2b7ad5341a..822fa41e6f 100644 --- a/src/Microsoft.HttpRepl/DirectoryStructureExtensions.cs +++ b/src/Microsoft.HttpRepl/DirectoryStructureExtensions.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// 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.Collections.Generic; using System.Linq; namespace Microsoft.HttpRepl diff --git a/src/Microsoft.HttpRepl/Formatting/JsonVisitor.cs b/src/Microsoft.HttpRepl/Formatting/JsonVisitor.cs index fa40ee62ab..2538e2fdf2 100644 --- a/src/Microsoft.HttpRepl/Formatting/JsonVisitor.cs +++ b/src/Microsoft.HttpRepl/Formatting/JsonVisitor.cs @@ -1,4 +1,7 @@ -using System.IO; +// 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.IO; using System.Text; using Microsoft.HttpRepl.Preferences; using Microsoft.Repl.ConsoleHandling; diff --git a/src/Microsoft.HttpRepl/HttpState.cs b/src/Microsoft.HttpRepl/HttpState.cs index 55cfdd6188..d012a8fc45 100644 --- a/src/Microsoft.HttpRepl/HttpState.cs +++ b/src/Microsoft.HttpRepl/HttpState.cs @@ -1,4 +1,7 @@ -using System; +// 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.Collections.Generic; using System.IO; using System.Linq; diff --git a/src/Microsoft.HttpRepl/IDirectoryStructure.cs b/src/Microsoft.HttpRepl/IDirectoryStructure.cs index 5c02e8e476..e3641e3ca8 100644 --- a/src/Microsoft.HttpRepl/IDirectoryStructure.cs +++ b/src/Microsoft.HttpRepl/IDirectoryStructure.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// 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.Collections.Generic; namespace Microsoft.HttpRepl { @@ -12,13 +15,4 @@ namespace Microsoft.HttpRepl IRequestInfo RequestInfo { get; } } - - public interface IRequestInfo - { - IReadOnlyDictionary> ContentTypesByMethod { get; } - - IReadOnlyList Methods { get; } - - string GetRequestBodyForContentType(string contentType, string method); - } } diff --git a/src/Microsoft.HttpRepl/IRequestInfo.cs b/src/Microsoft.HttpRepl/IRequestInfo.cs new file mode 100644 index 0000000000..24054f6142 --- /dev/null +++ b/src/Microsoft.HttpRepl/IRequestInfo.cs @@ -0,0 +1,16 @@ +// 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.Collections.Generic; + +namespace Microsoft.HttpRepl +{ + public interface IRequestInfo + { + IReadOnlyDictionary> ContentTypesByMethod { get; } + + IReadOnlyList Methods { get; } + + string GetRequestBodyForContentType(string contentType, string method); + } +} diff --git a/src/Microsoft.HttpRepl/OpenApi/Either.cs b/src/Microsoft.HttpRepl/OpenApi/Either.cs index f1e7dfacb3..8e3069de89 100644 --- a/src/Microsoft.HttpRepl/OpenApi/Either.cs +++ b/src/Microsoft.HttpRepl/OpenApi/Either.cs @@ -1,3 +1,6 @@ +// 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. + namespace Microsoft.HttpRepl.OpenApi { public class Either @@ -30,4 +33,4 @@ namespace Microsoft.HttpRepl.OpenApi return new Either(value); } } -} \ No newline at end of file +} diff --git a/src/Microsoft.HttpRepl/OpenApi/EitherConverter.cs b/src/Microsoft.HttpRepl/OpenApi/EitherConverter.cs index b89a85c9ef..a7197abc8b 100644 --- a/src/Microsoft.HttpRepl/OpenApi/EitherConverter.cs +++ b/src/Microsoft.HttpRepl/OpenApi/EitherConverter.cs @@ -1,3 +1,6 @@ +// 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 Newtonsoft.Json; @@ -29,4 +32,4 @@ namespace Microsoft.HttpRepl.OpenApi throw new NotImplementedException(); } } -} \ No newline at end of file +} diff --git a/src/Microsoft.HttpRepl/OpenApi/EndpointMetadata.cs b/src/Microsoft.HttpRepl/OpenApi/EndpointMetadata.cs index e46387b3e0..27bb4d1b35 100644 --- a/src/Microsoft.HttpRepl/OpenApi/EndpointMetadata.cs +++ b/src/Microsoft.HttpRepl/OpenApi/EndpointMetadata.cs @@ -1,3 +1,6 @@ +// 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.Collections.Generic; namespace Microsoft.HttpRepl.OpenApi @@ -14,4 +17,4 @@ namespace Microsoft.HttpRepl.OpenApi public IReadOnlyDictionary>> AvailableRequests { get; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.HttpRepl/OpenApi/EndpointMetadataReader.cs b/src/Microsoft.HttpRepl/OpenApi/EndpointMetadataReader.cs index 8635d6fbee..243b8fa92e 100644 --- a/src/Microsoft.HttpRepl/OpenApi/EndpointMetadataReader.cs +++ b/src/Microsoft.HttpRepl/OpenApi/EndpointMetadataReader.cs @@ -1,3 +1,6 @@ +// 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.Collections.Generic; using Newtonsoft.Json.Linq; @@ -35,4 +38,4 @@ namespace Microsoft.HttpRepl.OpenApi return null; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.HttpRepl/OpenApi/IEndpointMetadataReader.cs b/src/Microsoft.HttpRepl/OpenApi/IEndpointMetadataReader.cs index 287740c7af..385f9dfce0 100644 --- a/src/Microsoft.HttpRepl/OpenApi/IEndpointMetadataReader.cs +++ b/src/Microsoft.HttpRepl/OpenApi/IEndpointMetadataReader.cs @@ -1,3 +1,6 @@ +// 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.Collections.Generic; using Newtonsoft.Json.Linq; @@ -9,4 +12,4 @@ namespace Microsoft.HttpRepl.OpenApi IEnumerable ReadMetadata(JObject document); } -} \ No newline at end of file +} diff --git a/src/Microsoft.HttpRepl/OpenApi/OpenApiV3EndpointMetadataReader.cs b/src/Microsoft.HttpRepl/OpenApi/OpenApiV3EndpointMetadataReader.cs index 5fa968efc6..c009951608 100644 --- a/src/Microsoft.HttpRepl/OpenApi/OpenApiV3EndpointMetadataReader.cs +++ b/src/Microsoft.HttpRepl/OpenApi/OpenApiV3EndpointMetadataReader.cs @@ -1,3 +1,6 @@ +// 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.Collections.Generic; using System.Linq; @@ -95,4 +98,4 @@ namespace Microsoft.HttpRepl.OpenApi return metadata; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.HttpRepl/OpenApi/Parameter.cs b/src/Microsoft.HttpRepl/OpenApi/Parameter.cs index faf21e5f9a..0a44f489c5 100644 --- a/src/Microsoft.HttpRepl/OpenApi/Parameter.cs +++ b/src/Microsoft.HttpRepl/OpenApi/Parameter.cs @@ -1,3 +1,6 @@ +// 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. + namespace Microsoft.HttpRepl.OpenApi { public class Parameter @@ -10,4 +13,4 @@ namespace Microsoft.HttpRepl.OpenApi public Schema Schema { get; set; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.HttpRepl/OpenApi/PointerUtil.cs b/src/Microsoft.HttpRepl/OpenApi/PointerUtil.cs index b01730b116..db36c082bf 100644 --- a/src/Microsoft.HttpRepl/OpenApi/PointerUtil.cs +++ b/src/Microsoft.HttpRepl/OpenApi/PointerUtil.cs @@ -1,3 +1,6 @@ +// 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.Globalization; using System.Linq; @@ -215,4 +218,4 @@ namespace Microsoft.HttpRepl.OpenApi // return root; //} } -} \ No newline at end of file +} diff --git a/src/Microsoft.HttpRepl/OpenApi/Schema.cs b/src/Microsoft.HttpRepl/OpenApi/Schema.cs index 028c0b13ca..c84bbdda0f 100644 --- a/src/Microsoft.HttpRepl/OpenApi/Schema.cs +++ b/src/Microsoft.HttpRepl/OpenApi/Schema.cs @@ -1,3 +1,6 @@ +// 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.Collections.Generic; using System.Linq; using Newtonsoft.Json; @@ -122,4 +125,4 @@ namespace Microsoft.HttpRepl.OpenApi public bool UniqueItems { get; set; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.HttpRepl/OpenApi/SwaggerV1EndpointMetadataReader.cs b/src/Microsoft.HttpRepl/OpenApi/SwaggerV1EndpointMetadataReader.cs index 7f4fef4fb1..d62fc8c3b1 100644 --- a/src/Microsoft.HttpRepl/OpenApi/SwaggerV1EndpointMetadataReader.cs +++ b/src/Microsoft.HttpRepl/OpenApi/SwaggerV1EndpointMetadataReader.cs @@ -1,3 +1,6 @@ +// 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.Collections.Generic; using System.Linq; @@ -103,4 +106,4 @@ namespace Microsoft.HttpRepl.OpenApi return metadata; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.HttpRepl/OpenApi/SwaggerV2EndpointMetadataReader.cs b/src/Microsoft.HttpRepl/OpenApi/SwaggerV2EndpointMetadataReader.cs index 5e7ecfe6aa..66b16d2ed5 100644 --- a/src/Microsoft.HttpRepl/OpenApi/SwaggerV2EndpointMetadataReader.cs +++ b/src/Microsoft.HttpRepl/OpenApi/SwaggerV2EndpointMetadataReader.cs @@ -1,3 +1,6 @@ +// 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.Collections.Generic; using System.Linq; @@ -84,4 +87,4 @@ namespace Microsoft.HttpRepl.OpenApi return metadata; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.HttpRepl/Preferences/IJsonConfig.cs b/src/Microsoft.HttpRepl/Preferences/IJsonConfig.cs index 21918e44ef..96d36ad7af 100644 --- a/src/Microsoft.HttpRepl/Preferences/IJsonConfig.cs +++ b/src/Microsoft.HttpRepl/Preferences/IJsonConfig.cs @@ -1,4 +1,7 @@ -using Microsoft.Repl.ConsoleHandling; +// 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 Microsoft.Repl.ConsoleHandling; namespace Microsoft.HttpRepl.Preferences { @@ -26,4 +29,4 @@ namespace Microsoft.HttpRepl.Preferences AllowedColors NullColor { get; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.HttpRepl/Preferences/JsonConfig.cs b/src/Microsoft.HttpRepl/Preferences/JsonConfig.cs index a1902e245e..67ce8aa2ed 100644 --- a/src/Microsoft.HttpRepl/Preferences/JsonConfig.cs +++ b/src/Microsoft.HttpRepl/Preferences/JsonConfig.cs @@ -1,4 +1,7 @@ -using Microsoft.Repl.ConsoleHandling; +// 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 Microsoft.Repl.ConsoleHandling; namespace Microsoft.HttpRepl.Preferences { diff --git a/src/Microsoft.HttpRepl/Preferences/RequestConfig.cs b/src/Microsoft.HttpRepl/Preferences/RequestConfig.cs index a3f9b55259..ae2b41e1fc 100644 --- a/src/Microsoft.HttpRepl/Preferences/RequestConfig.cs +++ b/src/Microsoft.HttpRepl/Preferences/RequestConfig.cs @@ -1,4 +1,7 @@ -using Microsoft.Repl.ConsoleHandling; +// 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 Microsoft.Repl.ConsoleHandling; namespace Microsoft.HttpRepl.Preferences { diff --git a/src/Microsoft.HttpRepl/Preferences/RequestOrResponseConfig.cs b/src/Microsoft.HttpRepl/Preferences/RequestOrResponseConfig.cs index c509a89f5e..c4385ad032 100644 --- a/src/Microsoft.HttpRepl/Preferences/RequestOrResponseConfig.cs +++ b/src/Microsoft.HttpRepl/Preferences/RequestOrResponseConfig.cs @@ -1,4 +1,7 @@ -using Microsoft.Repl.ConsoleHandling; +// 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 Microsoft.Repl.ConsoleHandling; namespace Microsoft.HttpRepl.Preferences { diff --git a/src/Microsoft.HttpRepl/Preferences/ResponseConfig.cs b/src/Microsoft.HttpRepl/Preferences/ResponseConfig.cs index 123a86ff79..d3a6c9c26f 100644 --- a/src/Microsoft.HttpRepl/Preferences/ResponseConfig.cs +++ b/src/Microsoft.HttpRepl/Preferences/ResponseConfig.cs @@ -1,4 +1,7 @@ -using Microsoft.Repl.ConsoleHandling; +// 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 Microsoft.Repl.ConsoleHandling; namespace Microsoft.HttpRepl.Preferences { diff --git a/src/Microsoft.HttpRepl/Preferences/WellKnownPreference.cs b/src/Microsoft.HttpRepl/Preferences/WellKnownPreference.cs index a6c16a6da7..1cd17a04ce 100644 --- a/src/Microsoft.HttpRepl/Preferences/WellKnownPreference.cs +++ b/src/Microsoft.HttpRepl/Preferences/WellKnownPreference.cs @@ -1,4 +1,7 @@ -using System; +// 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.Collections.Generic; using System.Reflection; using Microsoft.Repl.ConsoleHandling; diff --git a/src/Microsoft.HttpRepl/Program.cs b/src/Microsoft.HttpRepl/Program.cs index f64fcf377a..a0e1c9addf 100644 --- a/src/Microsoft.HttpRepl/Program.cs +++ b/src/Microsoft.HttpRepl/Program.cs @@ -1,4 +1,7 @@ -using System.Threading; +// 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.Threading; using System.Threading.Tasks; using Microsoft.Repl; using Microsoft.Repl.Commanding; diff --git a/src/Microsoft.HttpRepl/Suggestions/HeaderCompletion.cs b/src/Microsoft.HttpRepl/Suggestions/HeaderCompletion.cs index 05372d7bec..74ceab2507 100644 --- a/src/Microsoft.HttpRepl/Suggestions/HeaderCompletion.cs +++ b/src/Microsoft.HttpRepl/Suggestions/HeaderCompletion.cs @@ -1,4 +1,7 @@ -using System; +// 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.Collections.Generic; using System.Linq; diff --git a/src/Microsoft.HttpRepl/Suggestions/ServerPathCompletion.cs b/src/Microsoft.HttpRepl/Suggestions/ServerPathCompletion.cs index a50df8ae8d..2311b714b3 100644 --- a/src/Microsoft.HttpRepl/Suggestions/ServerPathCompletion.cs +++ b/src/Microsoft.HttpRepl/Suggestions/ServerPathCompletion.cs @@ -1,4 +1,7 @@ -using System; +// 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.Collections.Generic; using System.Linq; diff --git a/src/Microsoft.Repl/Commanding/CommandHistory.cs b/src/Microsoft.Repl/Commanding/CommandHistory.cs index 83c7206a9d..27489df0d1 100644 --- a/src/Microsoft.Repl/Commanding/CommandHistory.cs +++ b/src/Microsoft.Repl/Commanding/CommandHistory.cs @@ -1,4 +1,7 @@ -using System; +// 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.Collections.Generic; namespace Microsoft.Repl.Commanding diff --git a/src/Microsoft.Repl/Commanding/CommandInputLocation.cs b/src/Microsoft.Repl/Commanding/CommandInputLocation.cs index 1612384b61..e1ed19a50c 100644 --- a/src/Microsoft.Repl/Commanding/CommandInputLocation.cs +++ b/src/Microsoft.Repl/Commanding/CommandInputLocation.cs @@ -1,4 +1,7 @@ -namespace Microsoft.Repl.Commanding +// 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. + +namespace Microsoft.Repl.Commanding { public enum CommandInputLocation { @@ -7,4 +10,4 @@ OptionName, OptionValue } -} \ No newline at end of file +} diff --git a/src/Microsoft.Repl/Commanding/CommandInputProcessingIssue.cs b/src/Microsoft.Repl/Commanding/CommandInputProcessingIssue.cs index 501b4357da..f294718ff0 100644 --- a/src/Microsoft.Repl/Commanding/CommandInputProcessingIssue.cs +++ b/src/Microsoft.Repl/Commanding/CommandInputProcessingIssue.cs @@ -1,4 +1,7 @@ -namespace Microsoft.Repl.Commanding +// 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. + +namespace Microsoft.Repl.Commanding { public class CommandInputProcessingIssue { @@ -12,4 +15,4 @@ Text = text; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.Repl/Commanding/CommandInputProcessingIssueKind.cs b/src/Microsoft.Repl/Commanding/CommandInputProcessingIssueKind.cs index cd69dccb85..dfc8a5f563 100644 --- a/src/Microsoft.Repl/Commanding/CommandInputProcessingIssueKind.cs +++ b/src/Microsoft.Repl/Commanding/CommandInputProcessingIssueKind.cs @@ -1,4 +1,7 @@ -namespace Microsoft.Repl.Commanding +// 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. + +namespace Microsoft.Repl.Commanding { public enum CommandInputProcessingIssueKind { @@ -8,4 +11,4 @@ OptionUseCountOutOfRange, MissingRequiredOptionInput, } -} \ No newline at end of file +} diff --git a/src/Microsoft.Repl/Commanding/CommandInputSpecification.cs b/src/Microsoft.Repl/Commanding/CommandInputSpecification.cs index 370b9f3d34..0e24c3fffa 100644 --- a/src/Microsoft.Repl/Commanding/CommandInputSpecification.cs +++ b/src/Microsoft.Repl/Commanding/CommandInputSpecification.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// 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.Collections.Generic; namespace Microsoft.Repl.Commanding { @@ -41,4 +44,4 @@ namespace Microsoft.Repl.Commanding return new CommandInputSpecificationBuilder(nameParts); } } -} \ No newline at end of file +} diff --git a/src/Microsoft.Repl/Commanding/CommandInputSpecificationBuilder.cs b/src/Microsoft.Repl/Commanding/CommandInputSpecificationBuilder.cs index 5ad5edf345..019782d0b8 100644 --- a/src/Microsoft.Repl/Commanding/CommandInputSpecificationBuilder.cs +++ b/src/Microsoft.Repl/Commanding/CommandInputSpecificationBuilder.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// 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.Collections.Generic; namespace Microsoft.Repl.Commanding { @@ -63,4 +66,4 @@ namespace Microsoft.Repl.Commanding return new CommandInputSpecification(_name, _optionPreamble, _options, _minimumArgs, _maximumArgs); } } -} \ No newline at end of file +} diff --git a/src/Microsoft.Repl/Commanding/CommandOptionSpecification.cs b/src/Microsoft.Repl/Commanding/CommandOptionSpecification.cs index d925e5b2c9..6f7e4469fe 100644 --- a/src/Microsoft.Repl/Commanding/CommandOptionSpecification.cs +++ b/src/Microsoft.Repl/Commanding/CommandOptionSpecification.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// 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.Collections.Generic; namespace Microsoft.Repl.Commanding { @@ -26,4 +29,4 @@ namespace Microsoft.Repl.Commanding AcceptsValue = RequiresValue || acceptsValue; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.Repl/Commanding/CommandWithStructuredInputBase.cs b/src/Microsoft.Repl/Commanding/CommandWithStructuredInputBase.cs index 38443486e8..1957205913 100644 --- a/src/Microsoft.Repl/Commanding/CommandWithStructuredInputBase.cs +++ b/src/Microsoft.Repl/Commanding/CommandWithStructuredInputBase.cs @@ -1,4 +1,7 @@ -using System; +// 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.Collections.Generic; using System.Linq; using System.Threading; @@ -190,4 +193,4 @@ namespace Microsoft.Repl.Commanding protected abstract CommandInputSpecification InputSpec { get; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.Repl/Commanding/DefaultCommandDispatcher.cs b/src/Microsoft.Repl/Commanding/DefaultCommandDispatcher.cs index 932e8cdb1a..2b85c2bc57 100644 --- a/src/Microsoft.Repl/Commanding/DefaultCommandDispatcher.cs +++ b/src/Microsoft.Repl/Commanding/DefaultCommandDispatcher.cs @@ -1,4 +1,7 @@ -using System; +// 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.Collections.Generic; using System.Linq; using System.Threading; diff --git a/src/Microsoft.Repl/Commanding/DefaultCommandInput.cs b/src/Microsoft.Repl/Commanding/DefaultCommandInput.cs index 0d3b97932e..ead59738b3 100644 --- a/src/Microsoft.Repl/Commanding/DefaultCommandInput.cs +++ b/src/Microsoft.Repl/Commanding/DefaultCommandInput.cs @@ -1,4 +1,7 @@ -using System; +// 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.Collections.Generic; using System.Linq; using Microsoft.Repl.Parsing; @@ -190,4 +193,4 @@ namespace Microsoft.Repl.Commanding public IReadOnlyDictionary> Options { get; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.Repl/Commanding/ICommand.cs b/src/Microsoft.Repl/Commanding/ICommand.cs index be575437e2..3b754924f0 100644 --- a/src/Microsoft.Repl/Commanding/ICommand.cs +++ b/src/Microsoft.Repl/Commanding/ICommand.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// 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.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.Repl.Parsing; diff --git a/src/Microsoft.Repl/Commanding/ICommandDispatcher.cs b/src/Microsoft.Repl/Commanding/ICommandDispatcher.cs index c175490833..d2d9000123 100644 --- a/src/Microsoft.Repl/Commanding/ICommandDispatcher.cs +++ b/src/Microsoft.Repl/Commanding/ICommandDispatcher.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// 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.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.Repl.Parsing; diff --git a/src/Microsoft.Repl/Commanding/ICommandHistory.cs b/src/Microsoft.Repl/Commanding/ICommandHistory.cs index 8e25db8906..cd506fb2b3 100644 --- a/src/Microsoft.Repl/Commanding/ICommandHistory.cs +++ b/src/Microsoft.Repl/Commanding/ICommandHistory.cs @@ -1,4 +1,7 @@ -using System; +// 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; namespace Microsoft.Repl.Commanding { diff --git a/src/Microsoft.Repl/Commanding/InputElement.cs b/src/Microsoft.Repl/Commanding/InputElement.cs index c02c7f0f23..1f0c7cd764 100644 --- a/src/Microsoft.Repl/Commanding/InputElement.cs +++ b/src/Microsoft.Repl/Commanding/InputElement.cs @@ -1,4 +1,7 @@ -namespace Microsoft.Repl.Commanding +// 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. + +namespace Microsoft.Repl.Commanding { public class InputElement { @@ -26,4 +29,4 @@ ParseResultSectionIndex = sectionIndex; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.Repl/ConsoleHandling/AllowedColors.cs b/src/Microsoft.Repl/ConsoleHandling/AllowedColors.cs index a71dc2c8a4..fd9245db28 100644 --- a/src/Microsoft.Repl/ConsoleHandling/AllowedColors.cs +++ b/src/Microsoft.Repl/ConsoleHandling/AllowedColors.cs @@ -1,4 +1,7 @@ -using System; +// 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; namespace Microsoft.Repl.ConsoleHandling { @@ -24,4 +27,4 @@ namespace Microsoft.Repl.ConsoleHandling Bold = 0x100, None = 0x99 } -} \ No newline at end of file +} diff --git a/src/Microsoft.Repl/ConsoleHandling/AnsiColorExtensions.cs b/src/Microsoft.Repl/ConsoleHandling/AnsiColorExtensions.cs index cab5bc6f05..8d535bf15f 100644 --- a/src/Microsoft.Repl/ConsoleHandling/AnsiColorExtensions.cs +++ b/src/Microsoft.Repl/ConsoleHandling/AnsiColorExtensions.cs @@ -1,4 +1,7 @@ -namespace Microsoft.Repl.ConsoleHandling +// 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. + +namespace Microsoft.Repl.ConsoleHandling { public static class AnsiColorExtensions { diff --git a/src/Microsoft.Repl/ConsoleHandling/AnsiConsole.cs b/src/Microsoft.Repl/ConsoleHandling/AnsiConsole.cs index 24944f8871..205c7713b5 100644 --- a/src/Microsoft.Repl/ConsoleHandling/AnsiConsole.cs +++ b/src/Microsoft.Repl/ConsoleHandling/AnsiConsole.cs @@ -1,4 +1,7 @@ -using System; +// 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; namespace Microsoft.Repl.ConsoleHandling diff --git a/src/Microsoft.Repl/ConsoleHandling/ConsoleManager.cs b/src/Microsoft.Repl/ConsoleHandling/ConsoleManager.cs index a15c1fd420..ad4231ad5e 100644 --- a/src/Microsoft.Repl/ConsoleHandling/ConsoleManager.cs +++ b/src/Microsoft.Repl/ConsoleHandling/ConsoleManager.cs @@ -1,4 +1,7 @@ -using System; +// 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.Collections.Generic; using System.Linq; using System.Threading; diff --git a/src/Microsoft.Repl/ConsoleHandling/IConsoleManager.cs b/src/Microsoft.Repl/ConsoleHandling/IConsoleManager.cs index 9ab7b26a15..ca93dbe88d 100644 --- a/src/Microsoft.Repl/ConsoleHandling/IConsoleManager.cs +++ b/src/Microsoft.Repl/ConsoleHandling/IConsoleManager.cs @@ -1,4 +1,7 @@ -using System; +// 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.Threading; namespace Microsoft.Repl.ConsoleHandling diff --git a/src/Microsoft.Repl/ConsoleHandling/IWritable.cs b/src/Microsoft.Repl/ConsoleHandling/IWritable.cs index f18fa123a1..fcb5441a69 100644 --- a/src/Microsoft.Repl/ConsoleHandling/IWritable.cs +++ b/src/Microsoft.Repl/ConsoleHandling/IWritable.cs @@ -1,4 +1,7 @@ -namespace Microsoft.Repl.ConsoleHandling +// 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. + +namespace Microsoft.Repl.ConsoleHandling { public interface IWritable { diff --git a/src/Microsoft.Repl/ConsoleHandling/Point.cs b/src/Microsoft.Repl/ConsoleHandling/Point.cs index 4eeb68989d..e705eada63 100644 --- a/src/Microsoft.Repl/ConsoleHandling/Point.cs +++ b/src/Microsoft.Repl/ConsoleHandling/Point.cs @@ -1,4 +1,7 @@ -namespace Microsoft.Repl.ConsoleHandling +// 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. + +namespace Microsoft.Repl.ConsoleHandling { public struct Point { diff --git a/src/Microsoft.Repl/ConsoleHandling/Reporter.cs b/src/Microsoft.Repl/ConsoleHandling/Reporter.cs index 5b10d2be02..a7da8d0060 100644 --- a/src/Microsoft.Repl/ConsoleHandling/Reporter.cs +++ b/src/Microsoft.Repl/ConsoleHandling/Reporter.cs @@ -1,4 +1,7 @@ -using System; +// 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; namespace Microsoft.Repl.ConsoleHandling { diff --git a/src/Microsoft.Repl/ConsoleHandling/Writable.cs b/src/Microsoft.Repl/ConsoleHandling/Writable.cs index b12c2ad4e7..1b32e939f4 100644 --- a/src/Microsoft.Repl/ConsoleHandling/Writable.cs +++ b/src/Microsoft.Repl/ConsoleHandling/Writable.cs @@ -1,4 +1,7 @@ -using System; +// 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; namespace Microsoft.Repl.ConsoleHandling { diff --git a/src/Microsoft.Repl/Disposable.cs b/src/Microsoft.Repl/Disposable.cs index 78002fb1d9..40b892ace1 100644 --- a/src/Microsoft.Repl/Disposable.cs +++ b/src/Microsoft.Repl/Disposable.cs @@ -1,4 +1,7 @@ -using System; +// 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; namespace Microsoft.Repl { diff --git a/src/Microsoft.Repl/IShellState.cs b/src/Microsoft.Repl/IShellState.cs index 2ec1cb1514..1941fa65f9 100644 --- a/src/Microsoft.Repl/IShellState.cs +++ b/src/Microsoft.Repl/IShellState.cs @@ -1,4 +1,7 @@ -using Microsoft.Repl.Commanding; +// 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 Microsoft.Repl.Commanding; using Microsoft.Repl.ConsoleHandling; using Microsoft.Repl.Input; using Microsoft.Repl.Suggestions; diff --git a/src/Microsoft.Repl/Input/AsyncKeyPressHandler.cs b/src/Microsoft.Repl/Input/AsyncKeyPressHandler.cs index 616a749611..4ac3793979 100644 --- a/src/Microsoft.Repl/Input/AsyncKeyPressHandler.cs +++ b/src/Microsoft.Repl/Input/AsyncKeyPressHandler.cs @@ -1,4 +1,7 @@ -using System; +// 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.Threading; using System.Threading.Tasks; diff --git a/src/Microsoft.Repl/Input/IInputManager.cs b/src/Microsoft.Repl/Input/IInputManager.cs index 100dd22bea..b67f3e3936 100644 --- a/src/Microsoft.Repl/Input/IInputManager.cs +++ b/src/Microsoft.Repl/Input/IInputManager.cs @@ -1,4 +1,7 @@ -using System; +// 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.Threading; using System.Threading.Tasks; diff --git a/src/Microsoft.Repl/Input/InputManager.cs b/src/Microsoft.Repl/Input/InputManager.cs index a635ccd37a..e1d6055683 100644 --- a/src/Microsoft.Repl/Input/InputManager.cs +++ b/src/Microsoft.Repl/Input/InputManager.cs @@ -1,4 +1,7 @@ -using System; +// 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.Collections.Generic; using System.Diagnostics; using System.Linq; diff --git a/src/Microsoft.Repl/Input/KeyHandlers.cs b/src/Microsoft.Repl/Input/KeyHandlers.cs index 7b23f08769..192c55319c 100644 --- a/src/Microsoft.Repl/Input/KeyHandlers.cs +++ b/src/Microsoft.Repl/Input/KeyHandlers.cs @@ -1,4 +1,7 @@ -using System; +// 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.Threading; using System.Threading.Tasks; using Microsoft.Repl.Parsing; diff --git a/src/Microsoft.Repl/Parsing/CoreParseResult.cs b/src/Microsoft.Repl/Parsing/CoreParseResult.cs index 8f345813f6..1655e4a36a 100644 --- a/src/Microsoft.Repl/Parsing/CoreParseResult.cs +++ b/src/Microsoft.Repl/Parsing/CoreParseResult.cs @@ -1,4 +1,7 @@ -using System; +// 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.Collections.Generic; using System.Linq; diff --git a/src/Microsoft.Repl/Parsing/CoreParser.cs b/src/Microsoft.Repl/Parsing/CoreParser.cs index 35be4867a5..7075f764a0 100644 --- a/src/Microsoft.Repl/Parsing/CoreParser.cs +++ b/src/Microsoft.Repl/Parsing/CoreParser.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// 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.Collections.Generic; using System.Linq; namespace Microsoft.Repl.Parsing diff --git a/src/Microsoft.Repl/Parsing/ICoreParseResult.cs b/src/Microsoft.Repl/Parsing/ICoreParseResult.cs index 20ac2c7b4e..55bf5b2264 100644 --- a/src/Microsoft.Repl/Parsing/ICoreParseResult.cs +++ b/src/Microsoft.Repl/Parsing/ICoreParseResult.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// 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.Collections.Generic; namespace Microsoft.Repl.Parsing { diff --git a/src/Microsoft.Repl/Parsing/IParser.cs b/src/Microsoft.Repl/Parsing/IParser.cs index c909f7d94f..86ce9ed940 100644 --- a/src/Microsoft.Repl/Parsing/IParser.cs +++ b/src/Microsoft.Repl/Parsing/IParser.cs @@ -1,4 +1,7 @@ -namespace Microsoft.Repl.Parsing +// 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. + +namespace Microsoft.Repl.Parsing { public interface IParser { diff --git a/src/Microsoft.Repl/Scripting/IScriptExecutor.cs b/src/Microsoft.Repl/Scripting/IScriptExecutor.cs index 7d832d8306..fcd5be2b53 100644 --- a/src/Microsoft.Repl/Scripting/IScriptExecutor.cs +++ b/src/Microsoft.Repl/Scripting/IScriptExecutor.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// 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.Collections.Generic; using System.Threading; using System.Threading.Tasks; diff --git a/src/Microsoft.Repl/Scripting/ScriptExecutor.cs b/src/Microsoft.Repl/Scripting/ScriptExecutor.cs index 3ec4bf5d21..6fafa6cbe4 100644 --- a/src/Microsoft.Repl/Scripting/ScriptExecutor.cs +++ b/src/Microsoft.Repl/Scripting/ScriptExecutor.cs @@ -1,4 +1,7 @@ -using System; +// 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.Collections.Generic; using System.Threading; using System.Threading.Tasks; diff --git a/src/Microsoft.Repl/Shell.cs b/src/Microsoft.Repl/Shell.cs index 49f450eae0..8d6cddb035 100644 --- a/src/Microsoft.Repl/Shell.cs +++ b/src/Microsoft.Repl/Shell.cs @@ -1,4 +1,7 @@ -using System.Threading; +// 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.Threading; using System.Threading.Tasks; using Microsoft.Repl.Commanding; using Microsoft.Repl.Input; diff --git a/src/Microsoft.Repl/ShellState.cs b/src/Microsoft.Repl/ShellState.cs index ef0ccf2a64..cb94bc63a2 100644 --- a/src/Microsoft.Repl/ShellState.cs +++ b/src/Microsoft.Repl/ShellState.cs @@ -1,4 +1,7 @@ -using Microsoft.Repl.Commanding; +// 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 Microsoft.Repl.Commanding; using Microsoft.Repl.ConsoleHandling; using Microsoft.Repl.Input; using Microsoft.Repl.Suggestions; diff --git a/src/Microsoft.Repl/Suggestions/FileSystemCompletion.cs b/src/Microsoft.Repl/Suggestions/FileSystemCompletion.cs index 71237fcd27..5b852a9b59 100644 --- a/src/Microsoft.Repl/Suggestions/FileSystemCompletion.cs +++ b/src/Microsoft.Repl/Suggestions/FileSystemCompletion.cs @@ -1,4 +1,7 @@ -using System; +// 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.Collections.Generic; using System.IO; using System.Linq; diff --git a/src/Microsoft.Repl/Suggestions/ISuggestionManager.cs b/src/Microsoft.Repl/Suggestions/ISuggestionManager.cs index f7e11a06fb..51e09c3a1c 100644 --- a/src/Microsoft.Repl/Suggestions/ISuggestionManager.cs +++ b/src/Microsoft.Repl/Suggestions/ISuggestionManager.cs @@ -1,4 +1,7 @@ -namespace Microsoft.Repl.Suggestions +// 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. + +namespace Microsoft.Repl.Suggestions { public interface ISuggestionManager { diff --git a/src/Microsoft.Repl/Suggestions/SuggestionManager.cs b/src/Microsoft.Repl/Suggestions/SuggestionManager.cs index 1fd9e861da..ba68ee9d46 100644 --- a/src/Microsoft.Repl/Suggestions/SuggestionManager.cs +++ b/src/Microsoft.Repl/Suggestions/SuggestionManager.cs @@ -1,4 +1,7 @@ -using System; +// 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.Collections.Generic; using Microsoft.Repl.Parsing; diff --git a/src/Microsoft.Repl/Utils.cs b/src/Microsoft.Repl/Utils.cs index 37ffcd97fb..eaf8160222 100644 --- a/src/Microsoft.Repl/Utils.cs +++ b/src/Microsoft.Repl/Utils.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// 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.Collections.Generic; namespace Microsoft.Repl { From 7487da155f9272f46890d354be2dacf8912308c9 Mon Sep 17 00:00:00 2001 From: Mike Lorbetske Date: Wed, 1 Aug 2018 12:42:16 -0700 Subject: [PATCH 33/66] Switch signing certificate for Newtonsoft.JSON to Microsoft3rdPartyAppComponentDual --- src/Microsoft.HttpRepl/Microsoft.HttpRepl.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.HttpRepl/Microsoft.HttpRepl.csproj b/src/Microsoft.HttpRepl/Microsoft.HttpRepl.csproj index cec7ac17d5..cf53e4b62e 100644 --- a/src/Microsoft.HttpRepl/Microsoft.HttpRepl.csproj +++ b/src/Microsoft.HttpRepl/Microsoft.HttpRepl.csproj @@ -23,8 +23,8 @@ - - + + From af5d8a824479f8a708447b8467e94b3282a75634 Mon Sep 17 00:00:00 2001 From: Mike Lorbetske Date: Fri, 3 Aug 2018 09:18:39 -0700 Subject: [PATCH 34/66] Address most code review comments Pagination hasn't been dealt with yet mac keybindings may be able to be simplified, pending some additional verification Fix 3rd party signing cert Bind a few common bash/zsh mappings Check for output redirection Make package version variable name consistent Add --help option for the command line Remove regions Remove old pointer resolution code Make command not found message more friendly Fix issue where base address was required for requests to absolute URIs Add options to suppress response formatting and streaming Turn off packing for the REPL project --- Directory.Build.props | 3 +- build/dependencies.props | 2 +- .../Commands/BaseHttpCommand.cs | 63 ++++++++-------- .../Commands/HelpCommand.cs | 23 +++--- .../Microsoft.HttpRepl.csproj | 4 +- src/Microsoft.HttpRepl/OpenApi/PointerUtil.cs | 72 ------------------- .../Preferences/WellKnownPreference.cs | 2 - src/Microsoft.HttpRepl/Program.cs | 24 +++++++ .../Commanding/DefaultCommandDispatcher.cs | 1 + src/Microsoft.Repl/Input/IInputManager.cs | 2 + src/Microsoft.Repl/Input/InputManager.cs | 34 +++++++-- src/Microsoft.Repl/Input/KeyHandlers.cs | 6 ++ src/Microsoft.Repl/Microsoft.Repl.csproj | 1 + .../Microsoft.HttpRepl.Tests.csproj | 2 +- 14 files changed, 119 insertions(+), 120 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 8ca8a8757b..9921cb358e 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,4 +1,4 @@ - + @@ -16,6 +16,7 @@ true MicrosoftNuGet Microsoft + Microsoft3rdPartyAppComponentDual true true diff --git a/build/dependencies.props b/build/dependencies.props index ee0b0b8ccf..bffcc722e2 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -17,7 +17,7 @@ 2.0.3 4.5.1 4.5.0 - 11.0.2 + 11.0.2 9.0.1 2.3.1 2.4.0 diff --git a/src/Microsoft.HttpRepl/Commands/BaseHttpCommand.cs b/src/Microsoft.HttpRepl/Commands/BaseHttpCommand.cs index 374544790b..ee9b4bcf0e 100644 --- a/src/Microsoft.HttpRepl/Commands/BaseHttpCommand.cs +++ b/src/Microsoft.HttpRepl/Commands/BaseHttpCommand.cs @@ -31,6 +31,8 @@ namespace Microsoft.HttpRepl.Commands private const string ResponseFileOption = nameof(ResponseFileOption); private const string BodyFileOption = nameof(BodyFileOption); private const string NoBodyOption = nameof(NoBodyOption); + private const string NoFormattingOption = nameof(NoFormattingOption); + private const string NoStreamingOption = nameof(NoStreamingOption); private const string BodyContentOption = nameof(BodyContentOption); private static readonly char[] HeaderSeparatorChars = new[] { '=', ':' }; @@ -54,7 +56,9 @@ namespace Microsoft.HttpRepl.Commands .WithOption(new CommandOptionSpecification(HeaderOption, requiresValue: true, forms: new[] {"--header", "-h"})) .WithOption(new CommandOptionSpecification(ResponseFileOption, requiresValue: true, maximumOccurrences: 1, forms: new[] { "--response", })) .WithOption(new CommandOptionSpecification(ResponseHeadersFileOption, requiresValue: true, maximumOccurrences: 1, forms: new[] { "--response:headers", })) - .WithOption(new CommandOptionSpecification(ResponseBodyFileOption, requiresValue: true, maximumOccurrences: 1, forms: new[] { "--response:body", })); + .WithOption(new CommandOptionSpecification(ResponseBodyFileOption, requiresValue: true, maximumOccurrences: 1, forms: new[] { "--response:body", })) + .WithOption(new CommandOptionSpecification(NoFormattingOption, maximumOccurrences: 1, forms: new[] { "--no-formatting", "-F" })) + .WithOption(new CommandOptionSpecification(NoStreamingOption, maximumOccurrences: 1, forms: new[] { "--no-streaming", "-S" })); if (RequiresBody) { @@ -70,9 +74,9 @@ namespace Microsoft.HttpRepl.Commands protected override async Task ExecuteAsync(IShellState shellState, HttpState programState, DefaultCommandInput commandInput, ICoreParseResult parseResult, CancellationToken cancellationToken) { - if (programState.BaseAddress == null) + if (programState.BaseAddress == null && (commandInput.Arguments.Count == 0 || !Uri.TryCreate(commandInput.Arguments[0].Text, UriKind.Absolute, out Uri _))) { - shellState.ConsoleManager.Error.WriteLine("'set base {url}' must be called before issuing requests".Bold().Red()); + shellState.ConsoleManager.Error.WriteLine("'set base {url}' must be called before issuing requests to a relative path".Bold().Red()); return; } @@ -203,10 +207,10 @@ namespace Microsoft.HttpRepl.Commands string bodyTarget = commandInput.Options[ResponseBodyFileOption].FirstOrDefault()?.Text ?? commandInput.Options[ResponseFileOption].FirstOrDefault()?.Text; HttpResponseMessage response = await programState.Client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); - await HandleResponseAsync(programState, shellState.ConsoleManager, response, programState.EchoRequest, headersTarget, bodyTarget, cancellationToken).ConfigureAwait(false); + await HandleResponseAsync(programState, commandInput, shellState.ConsoleManager, response, programState.EchoRequest, headersTarget, bodyTarget, cancellationToken).ConfigureAwait(false); } - private static async Task HandleResponseAsync(HttpState programState, IConsoleManager consoleManager, HttpResponseMessage response, bool echoRequest, string headersTargetFile, string bodyTargetFile, CancellationToken cancellationToken) + private static async Task HandleResponseAsync(HttpState programState, DefaultCommandInput commandInput, IConsoleManager consoleManager, HttpResponseMessage response, bool echoRequest, string headersTargetFile, string bodyTargetFile, CancellationToken cancellationToken) { RequestConfig requestConfig = new RequestConfig(programState); ResponseConfig responseConfig = new ResponseConfig(programState); @@ -244,7 +248,7 @@ namespace Microsoft.HttpRepl.Commands { using (StreamWriter writer = new StreamWriter(new MemoryStream())) { - await FormatBodyAsync(programState, consoleManager, response.RequestMessage.Content, writer, cancellationToken).ConfigureAwait(false); + await FormatBodyAsync(commandInput, programState, consoleManager, response.RequestMessage.Content, writer, cancellationToken).ConfigureAwait(false); } } @@ -311,7 +315,7 @@ namespace Microsoft.HttpRepl.Commands if (response.Content != null) { - await FormatBodyAsync(programState, consoleManager, response.Content, bodyFileWriter, cancellationToken).ConfigureAwait(false); + await FormatBodyAsync(commandInput, programState, consoleManager, response.Content, bodyFileWriter, cancellationToken).ConfigureAwait(false); } bodyFileWriter.Flush(); @@ -321,7 +325,7 @@ namespace Microsoft.HttpRepl.Commands consoleManager.WriteLine(); } - private static async Task FormatBodyAsync(HttpState programState, IConsoleManager consoleManager, HttpContent content, StreamWriter bodyFileWriter, CancellationToken cancellationToken) + private static async Task FormatBodyAsync(DefaultCommandInput commandInput, HttpState programState, IConsoleManager consoleManager, HttpContent content, StreamWriter bodyFileWriter, CancellationToken cancellationToken) { string contentType = null; if (content.Headers.TryGetValues("Content-Type", out IEnumerable contentTypeValues)) @@ -331,33 +335,36 @@ namespace Microsoft.HttpRepl.Commands contentType = contentType?.ToUpperInvariant() ?? "text/plain"; - if (contentType.EndsWith("/JSON", StringComparison.OrdinalIgnoreCase) - || contentType.EndsWith("-JSON", StringComparison.OrdinalIgnoreCase) - || contentType.EndsWith("+JSON", StringComparison.OrdinalIgnoreCase) - || contentType.EndsWith("/JAVASCRIPT", StringComparison.OrdinalIgnoreCase) - || contentType.EndsWith("-JAVASCRIPT", StringComparison.OrdinalIgnoreCase) - || contentType.EndsWith("+JAVASCRIPT", StringComparison.OrdinalIgnoreCase)) + if (commandInput.Options[NoFormattingOption].Count == 0) { - if (await FormatJsonAsync(programState, consoleManager, content, bodyFileWriter)) + if (contentType.EndsWith("/JSON", StringComparison.OrdinalIgnoreCase) + || contentType.EndsWith("-JSON", StringComparison.OrdinalIgnoreCase) + || contentType.EndsWith("+JSON", StringComparison.OrdinalIgnoreCase) + || contentType.EndsWith("/JAVASCRIPT", StringComparison.OrdinalIgnoreCase) + || contentType.EndsWith("-JAVASCRIPT", StringComparison.OrdinalIgnoreCase) + || contentType.EndsWith("+JAVASCRIPT", StringComparison.OrdinalIgnoreCase)) { - return; + if (await FormatJsonAsync(programState, consoleManager, content, bodyFileWriter)) + { + return; + } } - } - else if (contentType.EndsWith("/HTML", StringComparison.OrdinalIgnoreCase) - || contentType.EndsWith("-HTML", StringComparison.OrdinalIgnoreCase) - || contentType.EndsWith("+HTML", StringComparison.OrdinalIgnoreCase) - || contentType.EndsWith("/XML", StringComparison.OrdinalIgnoreCase) - || contentType.EndsWith("-XML", StringComparison.OrdinalIgnoreCase) - || contentType.EndsWith("+XML", StringComparison.OrdinalIgnoreCase)) - { - if (await FormatXmlAsync(consoleManager, content, bodyFileWriter)) + else if (contentType.EndsWith("/HTML", StringComparison.OrdinalIgnoreCase) + || contentType.EndsWith("-HTML", StringComparison.OrdinalIgnoreCase) + || contentType.EndsWith("+HTML", StringComparison.OrdinalIgnoreCase) + || contentType.EndsWith("/XML", StringComparison.OrdinalIgnoreCase) + || contentType.EndsWith("-XML", StringComparison.OrdinalIgnoreCase) + || contentType.EndsWith("+XML", StringComparison.OrdinalIgnoreCase)) { - return; + if (await FormatXmlAsync(consoleManager, content, bodyFileWriter)) + { + return; + } } } - //If we don't have content length, assume streaming - if (!content.Headers.TryGetValues("Content-Length", out IEnumerable _)) + //Unless the user has explicitly specified to not stream the response, if we don't have content length, assume streaming + if (commandInput.Options[NoStreamingOption].Count == 0 && !content.Headers.TryGetValues("Content-Length", out IEnumerable _)) { Memory buffer = new Memory(new char[2048]); Stream s = await content.ReadAsStreamAsync().ConfigureAwait(false); diff --git a/src/Microsoft.HttpRepl/Commands/HelpCommand.cs b/src/Microsoft.HttpRepl/Commands/HelpCommand.cs index f205fc1243..646dd4c544 100644 --- a/src/Microsoft.HttpRepl/Commands/HelpCommand.cs +++ b/src/Microsoft.HttpRepl/Commands/HelpCommand.cs @@ -30,15 +30,7 @@ namespace Microsoft.HttpRepl.Commands { if (parseResult.Sections.Count == 1) { - foreach (ICommand command in dispatcher.Commands) - { - string help = command.GetHelpSummary(shellState, programState); - - if (!string.IsNullOrEmpty(help)) - { - shellState.ConsoleManager.WriteLine(help); - } - } + CoreGetHelp(shellState, dispatcher, programState); } else { @@ -171,5 +163,18 @@ namespace Microsoft.HttpRepl.Commands return null; } + + public void CoreGetHelp(IShellState shellState, ICommandDispatcher dispatcher, HttpState programState) + { + foreach (ICommand command in dispatcher.Commands) + { + string help = command.GetHelpSummary(shellState, programState); + + if (!string.IsNullOrEmpty(help)) + { + shellState.ConsoleManager.WriteLine(help); + } + } + } } } diff --git a/src/Microsoft.HttpRepl/Microsoft.HttpRepl.csproj b/src/Microsoft.HttpRepl/Microsoft.HttpRepl.csproj index cf53e4b62e..2dce88c68b 100644 --- a/src/Microsoft.HttpRepl/Microsoft.HttpRepl.csproj +++ b/src/Microsoft.HttpRepl/Microsoft.HttpRepl.csproj @@ -23,8 +23,8 @@ - - + + diff --git a/src/Microsoft.HttpRepl/OpenApi/PointerUtil.cs b/src/Microsoft.HttpRepl/OpenApi/PointerUtil.cs index db36c082bf..c8566095b7 100644 --- a/src/Microsoft.HttpRepl/OpenApi/PointerUtil.cs +++ b/src/Microsoft.HttpRepl/OpenApi/PointerUtil.cs @@ -145,77 +145,5 @@ namespace Microsoft.HttpRepl.OpenApi return toResolve; } - - //public static async Task ResolvePointerAsync(this JToken root, HttpClient client, string pointer) - //{ - // if (!pointer.StartsWith("#/", StringComparison.Ordinal)) - // { - // HttpResponseMessage response = await client.GetAsync(pointer).ConfigureAwait(false); - - // if (!response.IsSuccessStatusCode) - // { - // //TODO: Failed to resolve pointer message - // return new JValue((object)null); - // } - - // string responseString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - - // try - // { - // root = JToken.Parse(responseString); - // int hashIndex = pointer.IndexOf("#"); - - // if (hashIndex < 0) - // { - // return root; - // } - - // pointer = pointer.Substring(hashIndex); - // } - // catch (Exception ex) - // { - // //TODO: Failed to deserialize pointer message - // return new JValue((object)null); - // } - // } - - // string[] pointerParts = pointer.Split('/'); - - // for (int i = 1; !(root is null) && i < pointerParts.Length; ++i) - // { - // if (root is JArray arr) - // { - // if (!int.TryParse(pointerParts[i], System.Globalization.NumberStyles.Integer, System.Globalization.CultureInfo.InvariantCulture, out int result) || result < 0 || result >= arr.Count) - // { - // //TODO: Failed to resolve pointer part message (non-integer index to array or index out of range) - // return null; - // } - - // root = arr[result]; - // } - // else if (root is JObject obj) - // { - // root = obj[pointerParts[i]]; - - // if (root is null) - // { - // //TODO: Failed to resolve pointer part message (no such path in object) - // } - - // JToken nestedRef = root["$ref"]; - // if (nestedRef is JValue value && value.Type == JTokenType.String) - // { - // root = await ResolvePointerAsync(root, ) - // } - // } - // else - // { - // //TODO: Failed to resolve pointer part message (pathing into literal) - // return null; - // } - // } - - // return root; - //} } } diff --git a/src/Microsoft.HttpRepl/Preferences/WellKnownPreference.cs b/src/Microsoft.HttpRepl/Preferences/WellKnownPreference.cs index 1cd17a04ce..424c0bb9a4 100644 --- a/src/Microsoft.HttpRepl/Preferences/WellKnownPreference.cs +++ b/src/Microsoft.HttpRepl/Preferences/WellKnownPreference.cs @@ -38,7 +38,6 @@ namespace Microsoft.HttpRepl.Preferences } } - #region JSON public static string JsonArrayBraceColor { get; } = "colors.json.arrayBrace"; public static string JsonObjectBraceColor { get; } = "colors.json.objectBrace"; @@ -66,7 +65,6 @@ namespace Microsoft.HttpRepl.Preferences public static string JsonSyntaxColor { get; } = "colors.json.syntax"; public static string JsonBraceColor { get; } = "colors.json.brace"; - #endregion JSON public static string RequestColor { get; } = "colors.request"; diff --git a/src/Microsoft.HttpRepl/Program.cs b/src/Microsoft.HttpRepl/Program.cs index a0e1c9addf..6509e7a38c 100644 --- a/src/Microsoft.HttpRepl/Program.cs +++ b/src/Microsoft.HttpRepl/Program.cs @@ -1,10 +1,12 @@ // 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.Threading; using System.Threading.Tasks; using Microsoft.Repl; using Microsoft.Repl.Commanding; +using Microsoft.Repl.ConsoleHandling; using Microsoft.Repl.Parsing; using Microsoft.HttpRepl.Commands; @@ -14,6 +16,12 @@ namespace Microsoft.HttpRepl { static async Task Main(string[] args) { + if(Console.IsOutputRedirected) + { + Reporter.Error.WriteLine("Cannot start the REPL when output is being redirected".Bold().Red()); + return; + } + var state = new HttpState(); var dispatcher = DefaultCommandDispatcher.Create(state.GetPrompt, state); @@ -44,6 +52,22 @@ namespace Microsoft.HttpRepl shell.ShellState.ConsoleManager.AddBreakHandler(() => source.Cancel()); if (args.Length > 0) { + if (string.Equals(args[0], "--help", StringComparison.OrdinalIgnoreCase)) + { + shell.ShellState.ConsoleManager.WriteLine("Usage: dotnet httprepl [] [options]"); + shell.ShellState.ConsoleManager.WriteLine(); + shell.ShellState.ConsoleManager.WriteLine("Arguments:"); + shell.ShellState.ConsoleManager.WriteLine(" - The initial base address for the REPL."); + shell.ShellState.ConsoleManager.WriteLine(); + shell.ShellState.ConsoleManager.WriteLine("Options:"); + shell.ShellState.ConsoleManager.WriteLine(" --help - Show help information."); + + shell.ShellState.ConsoleManager.WriteLine(); + shell.ShellState.ConsoleManager.WriteLine("REPL Commands:"); + new HelpCommand().CoreGetHelp(shell.ShellState, (ICommandDispatcher)shell.ShellState.CommandDispatcher, state); + return; + } + shell.ShellState.CommandDispatcher.OnReady(shell.ShellState); shell.ShellState.InputManager.SetInput(shell.ShellState, $"set base \"{args[0]}\""); await shell.ShellState.CommandDispatcher.ExecuteCommandAsync(shell.ShellState, CancellationToken.None).ConfigureAwait(false); diff --git a/src/Microsoft.Repl/Commanding/DefaultCommandDispatcher.cs b/src/Microsoft.Repl/Commanding/DefaultCommandDispatcher.cs index 2b85c2bc57..22be314827 100644 --- a/src/Microsoft.Repl/Commanding/DefaultCommandDispatcher.cs +++ b/src/Microsoft.Repl/Commanding/DefaultCommandDispatcher.cs @@ -156,6 +156,7 @@ namespace Microsoft.Repl.Commanding } shellState.ConsoleManager.Error.WriteLine("No matching command found".Red().Bold()); + shellState.ConsoleManager.Error.WriteLine("Execute 'help' to se available commands".Red().Bold()); } } diff --git a/src/Microsoft.Repl/Input/IInputManager.cs b/src/Microsoft.Repl/Input/IInputManager.cs index b67f3e3936..e8e98b2d54 100644 --- a/src/Microsoft.Repl/Input/IInputManager.cs +++ b/src/Microsoft.Repl/Input/IInputManager.cs @@ -13,6 +13,8 @@ namespace Microsoft.Repl.Input IInputManager RegisterKeyHandler(ConsoleKey key, AsyncKeyPressHandler handler); + IInputManager RegisterKeyHandler(ConsoleKey key, ConsoleModifiers modifiers, AsyncKeyPressHandler handler); + void ResetInput(); Task StartAsync(IShellState state, CancellationToken cancellationToken); diff --git a/src/Microsoft.Repl/Input/InputManager.cs b/src/Microsoft.Repl/Input/InputManager.cs index e1d6055683..cd1157c6ff 100644 --- a/src/Microsoft.Repl/Input/InputManager.cs +++ b/src/Microsoft.Repl/Input/InputManager.cs @@ -12,7 +12,7 @@ namespace Microsoft.Repl.Input { public class InputManager : IInputManager { - private readonly Dictionary _handlers = new Dictionary(); + private readonly Dictionary> _handlers = new Dictionary>(); private readonly List _inputBuffer = new List(); public bool IsOverwriteMode { get; set; } @@ -29,13 +29,37 @@ namespace Microsoft.Repl.Input public IInputManager RegisterKeyHandler(ConsoleKey key, AsyncKeyPressHandler handler) { + if (!_handlers.TryGetValue(key, out Dictionary handlers)) + { + _handlers[key] = handlers = new Dictionary(); + } + if (handler == null) { - _handlers.Remove(key); + handlers.Remove(default(ConsoleModifiers)); } else { - _handlers[key] = handler; + handlers[default(ConsoleModifiers)] = handler; + } + + return this; + } + + public IInputManager RegisterKeyHandler(ConsoleKey key, ConsoleModifiers modifiers, AsyncKeyPressHandler handler) + { + if (!_handlers.TryGetValue(key, out Dictionary handlers)) + { + _handlers[key] = handlers = new Dictionary(); + } + + if (handler == null) + { + handlers.Remove(modifiers); + } + else + { + handlers[modifiers] = handler; } return this; @@ -169,7 +193,7 @@ namespace Microsoft.Repl.Input { ConsoleKeyInfo keyPress = state.ConsoleManager.ReadKey(cancellationToken); - if (_handlers.TryGetValue(keyPress.Key, out AsyncKeyPressHandler handler)) + if (_handlers.TryGetValue(keyPress.Key, out Dictionary handlerLookup) && handlerLookup.TryGetValue(keyPress.Modifiers, out AsyncKeyPressHandler handler)) { using (CancellationTokenSource source = new CancellationTokenSource()) using (state.ConsoleManager.AddBreakHandler(() => source.Cancel())) @@ -189,6 +213,7 @@ namespace Microsoft.Repl.Input FlushInput(state, ref presses); } + //TODO: Verify on a mac whether these are still needed if (keyPress.Key == ConsoleKey.A) { state.ConsoleManager.MoveCaret(-state.ConsoleManager.CaretPosition); @@ -198,6 +223,7 @@ namespace Microsoft.Repl.Input state.ConsoleManager.MoveCaret(_inputBuffer.Count - state.ConsoleManager.CaretPosition); } } + //TODO: Register these like regular commands else if (!string.IsNullOrEmpty(_ttyState) && keyPress.Modifiers == ConsoleModifiers.Alt) { if (presses != null) diff --git a/src/Microsoft.Repl/Input/KeyHandlers.cs b/src/Microsoft.Repl/Input/KeyHandlers.cs index 192c55319c..750db8d2f6 100644 --- a/src/Microsoft.Repl/Input/KeyHandlers.cs +++ b/src/Microsoft.Repl/Input/KeyHandlers.cs @@ -14,9 +14,13 @@ namespace Microsoft.Repl.Input { //Navigation in line inputManager.RegisterKeyHandler(ConsoleKey.LeftArrow, LeftArrow); + inputManager.RegisterKeyHandler(ConsoleKey.LeftArrow, ConsoleModifiers.Control, LeftArrow); inputManager.RegisterKeyHandler(ConsoleKey.RightArrow, RightArrow); + inputManager.RegisterKeyHandler(ConsoleKey.RightArrow, ConsoleModifiers.Control, RightArrow); inputManager.RegisterKeyHandler(ConsoleKey.Home, Home); + inputManager.RegisterKeyHandler(ConsoleKey.A, ConsoleModifiers.Control, Home); inputManager.RegisterKeyHandler(ConsoleKey.End, End); + inputManager.RegisterKeyHandler(ConsoleKey.E, ConsoleModifiers.Control, End); //Command history inputManager.RegisterKeyHandler(ConsoleKey.UpArrow, UpArrow); @@ -24,9 +28,11 @@ namespace Microsoft.Repl.Input //Completion inputManager.RegisterKeyHandler(ConsoleKey.Tab, Tab); + inputManager.RegisterKeyHandler(ConsoleKey.Tab, ConsoleModifiers.Shift, Tab); //Input manipulation inputManager.RegisterKeyHandler(ConsoleKey.Escape, Escape); + inputManager.RegisterKeyHandler(ConsoleKey.U, ConsoleModifiers.Control, Escape); inputManager.RegisterKeyHandler(ConsoleKey.Delete, Delete); inputManager.RegisterKeyHandler(ConsoleKey.Backspace, Backspace); diff --git a/src/Microsoft.Repl/Microsoft.Repl.csproj b/src/Microsoft.Repl/Microsoft.Repl.csproj index 63af949be4..cb7311eeeb 100644 --- a/src/Microsoft.Repl/Microsoft.Repl.csproj +++ b/src/Microsoft.Repl/Microsoft.Repl.csproj @@ -4,6 +4,7 @@ netcoreapp2.2 A framework for creating REPLs in .NET Core. dotnet;repl + false diff --git a/test/Microsoft.HttpRepl.Tests/Microsoft.HttpRepl.Tests.csproj b/test/Microsoft.HttpRepl.Tests/Microsoft.HttpRepl.Tests.csproj index 7a79d0121e..a50dd1df30 100644 --- a/test/Microsoft.HttpRepl.Tests/Microsoft.HttpRepl.Tests.csproj +++ b/test/Microsoft.HttpRepl.Tests/Microsoft.HttpRepl.Tests.csproj @@ -10,7 +10,7 @@ - + From 8a9b407761436362c16b1de2b6d00e324273a6b9 Mon Sep 17 00:00:00 2001 From: Mike Lorbetske Date: Fri, 3 Aug 2018 12:27:02 -0700 Subject: [PATCH 35/66] Use netcoreapp2.1 instead of 2.2 --- src/Microsoft.HttpRepl/Microsoft.HttpRepl.csproj | 4 ++-- src/Microsoft.Repl/Microsoft.Repl.csproj | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.HttpRepl/Microsoft.HttpRepl.csproj b/src/Microsoft.HttpRepl/Microsoft.HttpRepl.csproj index 2dce88c68b..99b86075c3 100644 --- a/src/Microsoft.HttpRepl/Microsoft.HttpRepl.csproj +++ b/src/Microsoft.HttpRepl/Microsoft.HttpRepl.csproj @@ -1,8 +1,8 @@ - + Exe - netcoreapp2.2 + netcoreapp2.1 true dotnet-httprepl latest diff --git a/src/Microsoft.Repl/Microsoft.Repl.csproj b/src/Microsoft.Repl/Microsoft.Repl.csproj index cb7311eeeb..d6cc6e83e6 100644 --- a/src/Microsoft.Repl/Microsoft.Repl.csproj +++ b/src/Microsoft.Repl/Microsoft.Repl.csproj @@ -1,7 +1,7 @@ - + - netcoreapp2.2 + netcoreapp2.1 A framework for creating REPLs in .NET Core. dotnet;repl false From 43c74cfa5360be63faaaf361e5d4385e2796e138 Mon Sep 17 00:00:00 2001 From: Mike Lorbetske Date: Fri, 3 Aug 2018 20:58:07 -0700 Subject: [PATCH 36/66] Respond to much of the feedback from review Default to requerying swagger for each exec Auto-set the content-type if not yet set for requests based on swagger Fix swagger UI command Make error and warning colors configurable Allow aliases to be specified in structured input based commands Have delete not require a body Show allowed methods for . and .. Show verbs on the announced path after CD Issue a request on set base and warn if there's a socket error --- .../Commands/BaseHttpCommand.cs | 98 ++++++++++--------- .../Commands/ChangeDirectoryCommand.cs | 8 +- .../Commands/ConfigCommand.cs | 8 +- .../Commands/DeleteCommand.cs | 2 +- .../Commands/EchoCommand.cs | 2 +- .../Commands/HelpCommand.cs | 16 ++- .../Commands/ListCommand.cs | 33 +++++-- .../Commands/PrefCommand.cs | 4 +- .../Commands/SetBaseCommand.cs | 13 ++- .../Commands/SetDiagCommand.cs | 6 +- .../Commands/SetSwaggerCommand.cs | 4 +- src/Microsoft.HttpRepl/Commands/UICommand.cs | 4 +- src/Microsoft.HttpRepl/HttpState.cs | 7 ++ .../Preferences/WellKnownPreference.cs | 7 +- src/Microsoft.HttpRepl/Program.cs | 8 +- .../Commanding/CommandInputSpecification.cs | 4 +- .../CommandInputSpecificationBuilder.cs | 13 ++- .../CommandWithStructuredInputBase.cs | 27 ++++- .../Commanding/DefaultCommandInput.cs | 54 +++++++--- 19 files changed, 219 insertions(+), 99 deletions(-) diff --git a/src/Microsoft.HttpRepl/Commands/BaseHttpCommand.cs b/src/Microsoft.HttpRepl/Commands/BaseHttpCommand.cs index ee9b4bcf0e..9a5eba8227 100644 --- a/src/Microsoft.HttpRepl/Commands/BaseHttpCommand.cs +++ b/src/Microsoft.HttpRepl/Commands/BaseHttpCommand.cs @@ -32,7 +32,7 @@ namespace Microsoft.HttpRepl.Commands private const string BodyFileOption = nameof(BodyFileOption); private const string NoBodyOption = nameof(NoBodyOption); private const string NoFormattingOption = nameof(NoFormattingOption); - private const string NoStreamingOption = nameof(NoStreamingOption); + private const string StreamingOption = nameof(StreamingOption); private const string BodyContentOption = nameof(BodyContentOption); private static readonly char[] HeaderSeparatorChars = new[] { '=', ':' }; @@ -58,7 +58,7 @@ namespace Microsoft.HttpRepl.Commands .WithOption(new CommandOptionSpecification(ResponseHeadersFileOption, requiresValue: true, maximumOccurrences: 1, forms: new[] { "--response:headers", })) .WithOption(new CommandOptionSpecification(ResponseBodyFileOption, requiresValue: true, maximumOccurrences: 1, forms: new[] { "--response:body", })) .WithOption(new CommandOptionSpecification(NoFormattingOption, maximumOccurrences: 1, forms: new[] { "--no-formatting", "-F" })) - .WithOption(new CommandOptionSpecification(NoStreamingOption, maximumOccurrences: 1, forms: new[] { "--no-streaming", "-S" })); + .WithOption(new CommandOptionSpecification(StreamingOption, maximumOccurrences: 1, forms: new[] { "--streaming", "-s" })); if (RequiresBody) { @@ -76,10 +76,20 @@ namespace Microsoft.HttpRepl.Commands { if (programState.BaseAddress == null && (commandInput.Arguments.Count == 0 || !Uri.TryCreate(commandInput.Arguments[0].Text, UriKind.Absolute, out Uri _))) { - shellState.ConsoleManager.Error.WriteLine("'set base {url}' must be called before issuing requests to a relative path".Bold().Red()); + shellState.ConsoleManager.Error.WriteLine("'set base {url}' must be called before issuing requests to a relative path".SetColor(programState.ErrorColor)); return; } + if (programState.SwaggerEndpoint != null) + { + string swaggerRequeryBehaviorSetting = programState.GetStringPreference(WellKnownPreference.SwaggerRequeryBehavior, "auto"); + + if (swaggerRequeryBehaviorSetting.StartsWith("auto", StringComparison.OrdinalIgnoreCase)) + { + await SetSwaggerCommand.CreateDirectoryStructureForSwaggerEndpointAsync(shellState, programState, programState.SwaggerEndpoint, cancellationToken).ConfigureAwait(false); + } + } + Dictionary thisRequestHeaders = new Dictionary(); foreach (InputElement header in commandInput.Options[HeaderOption]) @@ -88,7 +98,7 @@ namespace Microsoft.HttpRepl.Commands if (equalsIndex < 0) { - shellState.ConsoleManager.Error.WriteLine("Headers must be formatted as {header}={value} or {header}:{value}".Bold().Red()); + shellState.ConsoleManager.Error.WriteLine("Headers must be formatted as {header}={value} or {header}:{value}".SetColor(programState.ErrorColor)); return; } @@ -114,7 +124,7 @@ namespace Microsoft.HttpRepl.Commands if (!File.Exists(filePath)) { - shellState.ConsoleManager.Error.WriteLine($"Content file {filePath} does not exist".Bold().Red()); + shellState.ConsoleManager.Error.WriteLine($"Content file {filePath} does not exist".SetColor(programState.ErrorColor)); return; } } @@ -127,7 +137,7 @@ namespace Microsoft.HttpRepl.Commands string defaultEditorCommand = programState.GetStringPreference(WellKnownPreference.DefaultEditorCommand); if (defaultEditorCommand == null) { - shellState.ConsoleManager.Error.WriteLine($"The default editor must be configured using the command `pref set {WellKnownPreference.DefaultEditorCommand} \"{{commandline}}\"`".Bold().Red()); + shellState.ConsoleManager.Error.WriteLine($"The default editor must be configured using the command `pref set {WellKnownPreference.DefaultEditorCommand} \"{{commandline}}\"`".SetColor(programState.ErrorColor)); return; } @@ -145,6 +155,7 @@ namespace Microsoft.HttpRepl.Commands } string exampleBody = programState.GetExampleBody(commandInput.Arguments.Count > 0 ? commandInput.Arguments[0].Text : string.Empty, contentType, Verb); + request.Headers.TryAddWithoutValidation("Content-Type", contentType); if (!string.IsNullOrEmpty(exampleBody)) { @@ -224,7 +235,7 @@ namespace Microsoft.HttpRepl.Commands string method = response.RequestMessage.Method.ToString().ToUpperInvariant().SetColor(requestConfig.MethodColor); string pathAndQuery = response.RequestMessage.RequestUri.PathAndQuery.SetColor(requestConfig.AddressColor); - protocolInfo = $"{"HTTP".SetColor(requestConfig.ProtocolNameColor)}{"/".SetColor(requestConfig.ProtocolSeparatorColor)}{response.RequestMessage.Version.ToString().SetColor(requestConfig.ProtocolVersionColor)}"; + protocolInfo = $"{"HTTP".SetColor(requestConfig.ProtocolNameColor)}{"/".SetColor(requestConfig.ProtocolSeparatorColor)}{response.Version.ToString().SetColor(requestConfig.ProtocolVersionColor)}"; consoleManager.WriteLine($"{method} {pathAndQuery} {protocolInfo}"); IEnumerable>> requestHeaders = response.RequestMessage.Headers; @@ -327,6 +338,42 @@ namespace Microsoft.HttpRepl.Commands private static async Task FormatBodyAsync(DefaultCommandInput commandInput, HttpState programState, IConsoleManager consoleManager, HttpContent content, StreamWriter bodyFileWriter, CancellationToken cancellationToken) { + if (commandInput.Options[StreamingOption].Count > 0) + { + Memory buffer = new Memory(new char[2048]); + Stream s = await content.ReadAsStreamAsync().ConfigureAwait(false); + StreamReader reader = new StreamReader(s); + consoleManager.WriteLine("Streaming the response, press any key to stop...".SetColor(programState.WarningColor)); + + while (!cancellationToken.IsCancellationRequested) + { + try + { + ValueTask readTask = reader.ReadAsync(buffer, cancellationToken); + if (await WaitForCompletionAsync(readTask, cancellationToken).ConfigureAwait(false)) + { + if (readTask.Result == 0) + { + break; + } + + string str = new string(buffer.Span.Slice(0, readTask.Result)); + consoleManager.Write(str); + bodyFileWriter.Write(str); + } + else + { + break; + } + } + catch (OperationCanceledException) + { + } + } + + return; + } + string contentType = null; if (content.Headers.TryGetValues("Content-Type", out IEnumerable contentTypeValues)) { @@ -363,43 +410,6 @@ namespace Microsoft.HttpRepl.Commands } } - //Unless the user has explicitly specified to not stream the response, if we don't have content length, assume streaming - if (commandInput.Options[NoStreamingOption].Count == 0 && !content.Headers.TryGetValues("Content-Length", out IEnumerable _)) - { - Memory buffer = new Memory(new char[2048]); - Stream s = await content.ReadAsStreamAsync().ConfigureAwait(false); - StreamReader reader = new StreamReader(s); - consoleManager.WriteLine("Streaming the response, press any key to stop...".Bold().Yellow()); - - while (!cancellationToken.IsCancellationRequested) - { - try - { - ValueTask readTask = reader.ReadAsync(buffer, cancellationToken); - if (await WaitForCompletionAsync(readTask, cancellationToken).ConfigureAwait(false)) - { - if (readTask.Result == 0) - { - break; - } - - string str = new string(buffer.Span.Slice(0, readTask.Result)); - consoleManager.Write(str); - bodyFileWriter.Write(str); - } - else - { - break; - } - } - catch (OperationCanceledException) - { - } - } - - return; - } - string responseContent = await content.ReadAsStringAsync().ConfigureAwait(false); bodyFileWriter.WriteLine(responseContent); consoleManager.WriteLine(responseContent); diff --git a/src/Microsoft.HttpRepl/Commands/ChangeDirectoryCommand.cs b/src/Microsoft.HttpRepl/Commands/ChangeDirectoryCommand.cs index c2baaf0de5..a18aa2df9f 100644 --- a/src/Microsoft.HttpRepl/Commands/ChangeDirectoryCommand.cs +++ b/src/Microsoft.HttpRepl/Commands/ChangeDirectoryCommand.cs @@ -48,7 +48,13 @@ namespace Microsoft.HttpRepl.Commands } } - shellState.ConsoleManager.WriteLine($"/{string.Join("/", programState.PathSections.Reverse())}"); + IDirectoryStructure s = programState.Structure.TraverseTo(programState.PathSections.Reverse()); + + string thisDirMethod = s.RequestInfo != null && s.RequestInfo.Methods.Count > 0 + ? "[" + string.Join("|", s.RequestInfo.Methods) + "]" + : "[]"; + + shellState.ConsoleManager.WriteLine($"/{string.Join("/", programState.PathSections.Reverse())} {thisDirMethod}"); } return Task.CompletedTask; diff --git a/src/Microsoft.HttpRepl/Commands/ConfigCommand.cs b/src/Microsoft.HttpRepl/Commands/ConfigCommand.cs index 773bee0f04..938936303f 100644 --- a/src/Microsoft.HttpRepl/Commands/ConfigCommand.cs +++ b/src/Microsoft.HttpRepl/Commands/ConfigCommand.cs @@ -21,13 +21,13 @@ namespace Microsoft.HttpRepl.Commands { if (programState.BaseAddress == null) { - shellState.ConsoleManager.Error.WriteLine("Must be connected to a server to query configuration".Bold().Red()); + shellState.ConsoleManager.Error.WriteLine("Must be connected to a server to query configuration".SetColor(programState.ErrorColor)); return; } if (string.IsNullOrEmpty(programState.DiagnosticsState.DiagnosticsEndpoint)) { - shellState.ConsoleManager.Error.WriteLine("Diagnostics endpoint must be set to query configuration (see set diag)".Bold().Red()); + shellState.ConsoleManager.Error.WriteLine("Diagnostics endpoint must be set to query configuration (see set diag)".SetColor(programState.ErrorColor)); return; } @@ -35,7 +35,7 @@ namespace Microsoft.HttpRepl.Commands if (configUrl == null) { - shellState.ConsoleManager.Error.WriteLine("Diagnostics endpoint does not expose configuration information".Bold().Red()); + shellState.ConsoleManager.Error.WriteLine("Diagnostics endpoint does not expose configuration information".SetColor(programState.ErrorColor)); return; } @@ -43,7 +43,7 @@ namespace Microsoft.HttpRepl.Commands if (!response.IsSuccessStatusCode) { - shellState.ConsoleManager.Error.WriteLine("Unable to get configuration information from diagnostics endpoint".Bold().Red()); + shellState.ConsoleManager.Error.WriteLine("Unable to get configuration information from diagnostics endpoint".SetColor(programState.ErrorColor)); return; } diff --git a/src/Microsoft.HttpRepl/Commands/DeleteCommand.cs b/src/Microsoft.HttpRepl/Commands/DeleteCommand.cs index 7c9b789db3..aedef544c0 100644 --- a/src/Microsoft.HttpRepl/Commands/DeleteCommand.cs +++ b/src/Microsoft.HttpRepl/Commands/DeleteCommand.cs @@ -7,6 +7,6 @@ namespace Microsoft.HttpRepl.Commands { protected override string Verb => "delete"; - protected override bool RequiresBody => true; + protected override bool RequiresBody => false; } } diff --git a/src/Microsoft.HttpRepl/Commands/EchoCommand.cs b/src/Microsoft.HttpRepl/Commands/EchoCommand.cs index 691eb5679f..e81ff810d4 100644 --- a/src/Microsoft.HttpRepl/Commands/EchoCommand.cs +++ b/src/Microsoft.HttpRepl/Commands/EchoCommand.cs @@ -21,7 +21,7 @@ namespace Microsoft.HttpRepl.Commands { if (commandInput.Arguments.Count == 0 || !_allowedModes.Contains(commandInput.Arguments[0]?.Text)) { - shellState.ConsoleManager.Error.WriteLine("Allowed echo modes are 'on' and 'off'".Bold().Red()); + shellState.ConsoleManager.Error.WriteLine("Allowed echo modes are 'on' and 'off'".SetColor(programState.ErrorColor)); return false; } diff --git a/src/Microsoft.HttpRepl/Commands/HelpCommand.cs b/src/Microsoft.HttpRepl/Commands/HelpCommand.cs index 646dd4c544..6677bde928 100644 --- a/src/Microsoft.HttpRepl/Commands/HelpCommand.cs +++ b/src/Microsoft.HttpRepl/Commands/HelpCommand.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.HttpRepl.Preferences; using Microsoft.HttpRepl.Suggestions; using Microsoft.Repl; using Microsoft.Repl.Commanding; @@ -24,7 +25,7 @@ namespace Microsoft.HttpRepl.Commands : null; } - public Task ExecuteAsync(IShellState shellState, HttpState programState, ICoreParseResult parseResult, CancellationToken cancellationToken) + public async Task ExecuteAsync(IShellState shellState, HttpState programState, ICoreParseResult parseResult, CancellationToken cancellationToken) { if (shellState.CommandDispatcher is ICommandDispatcher dispatcher) { @@ -55,6 +56,17 @@ namespace Microsoft.HttpRepl.Commands //Maybe the input is an URL if (parseResult.Sections.Count == 2) { + + if (programState.SwaggerEndpoint != null) + { + string swaggerRequeryBehaviorSetting = programState.GetStringPreference(WellKnownPreference.SwaggerRequeryBehavior, "auto"); + + if (swaggerRequeryBehaviorSetting.StartsWith("auto", StringComparison.OrdinalIgnoreCase)) + { + await SetSwaggerCommand.CreateDirectoryStructureForSwaggerEndpointAsync(shellState, programState, programState.SwaggerEndpoint, cancellationToken).ConfigureAwait(false); + } + } + IDirectoryStructure structure = programState.Structure.TraverseTo(parseResult.Sections[1]); if (structure.DirectoryNames.Any()) { @@ -101,8 +113,6 @@ namespace Microsoft.HttpRepl.Commands } } } - - return Task.CompletedTask; } public string GetHelpDetails(IShellState shellState, HttpState programState, ICoreParseResult parseResult) diff --git a/src/Microsoft.HttpRepl/Commands/ListCommand.cs b/src/Microsoft.HttpRepl/Commands/ListCommand.cs index b1aa0718a3..b0641f186b 100644 --- a/src/Microsoft.HttpRepl/Commands/ListCommand.cs +++ b/src/Microsoft.HttpRepl/Commands/ListCommand.cs @@ -7,6 +7,7 @@ using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.HttpRepl.Preferences; using Microsoft.Repl; using Microsoft.Repl.Commanding; using Microsoft.Repl.Parsing; @@ -17,11 +18,21 @@ namespace Microsoft.HttpRepl.Commands { private const string RecursiveOption = nameof(RecursiveOption); - protected override Task ExecuteAsync(IShellState shellState, HttpState programState, DefaultCommandInput commandInput, ICoreParseResult parseResult, CancellationToken cancellationToken) + protected override async Task ExecuteAsync(IShellState shellState, HttpState programState, DefaultCommandInput commandInput, ICoreParseResult parseResult, CancellationToken cancellationToken) { + if (programState.SwaggerEndpoint != null) + { + string swaggerRequeryBehaviorSetting = programState.GetStringPreference(WellKnownPreference.SwaggerRequeryBehavior, "auto"); + + if (swaggerRequeryBehaviorSetting.StartsWith("auto", StringComparison.OrdinalIgnoreCase)) + { + await SetSwaggerCommand.CreateDirectoryStructureForSwaggerEndpointAsync(shellState, programState, programState.SwaggerEndpoint, cancellationToken).ConfigureAwait(false); + } + } + if (programState.Structure == null || programState.BaseAddress == null) { - return Task.CompletedTask; + return; } string path = commandInput.Arguments.Count > 0 ? commandInput.Arguments[0].Text : string.Empty; @@ -29,19 +40,27 @@ namespace Microsoft.HttpRepl.Commands //If it's an absolute URI, nothing to suggest if (Uri.TryCreate(path, UriKind.Absolute, out Uri _)) { - return Task.CompletedTask; + return; } IDirectoryStructure s = programState.Structure.TraverseTo(programState.PathSections.Reverse()).TraverseTo(path); + string thisDirMethod = s.RequestInfo != null && s.RequestInfo.Methods.Count > 0 + ? "[" + string.Join("|", s.RequestInfo.Methods) + "]" + : "[]"; + List roots = new List(); Formatter formatter = new Formatter(); - roots.Add(new TreeNode(formatter, ".", string.Empty)); + roots.Add(new TreeNode(formatter, ".", thisDirMethod)); if (s.Parent != null) { - roots.Add(new TreeNode(formatter, "..", string.Empty)); + string parentDirMethod = s.Parent.RequestInfo != null && s.Parent.RequestInfo.Methods.Count > 0 + ? "[" + string.Join("|", s.Parent.RequestInfo.Methods) + "]" + : "[]"; + + roots.Add(new TreeNode(formatter, "..", parentDirMethod)); } int recursionDepth = 1; @@ -75,8 +94,6 @@ namespace Microsoft.HttpRepl.Commands { shellState.ConsoleManager.WriteLine(node.ToString()); } - - return Task.CompletedTask; } private static void Recurse(TreeNode parentNode, IDirectoryStructure parent, int remainingDepth) @@ -101,7 +118,7 @@ namespace Microsoft.HttpRepl.Commands - protected override CommandInputSpecification InputSpec { get; } = CommandInputSpecification.Create("ls") + protected override CommandInputSpecification InputSpec { get; } = CommandInputSpecification.Create("ls").AlternateName("dir") .MaximumArgCount(1) .WithOption(new CommandOptionSpecification(RecursiveOption, maximumOccurrences: 1, acceptsValue: true, forms: new[] {"-r", "--recursive"})) .Finish(); diff --git a/src/Microsoft.HttpRepl/Commands/PrefCommand.cs b/src/Microsoft.HttpRepl/Commands/PrefCommand.cs index 2a64d2f017..d4d5d19b4a 100644 --- a/src/Microsoft.HttpRepl/Commands/PrefCommand.cs +++ b/src/Microsoft.HttpRepl/Commands/PrefCommand.cs @@ -90,7 +90,7 @@ namespace Microsoft.HttpRepl.Commands if (!programState.SavePreferences()) { - shellState.ConsoleManager.Error.WriteLine("Error saving preferences".Bold().Red()); + shellState.ConsoleManager.Error.WriteLine("Error saving preferences".SetColor(programState.ErrorColor)); } return Task.CompletedTask; @@ -109,7 +109,7 @@ namespace Microsoft.HttpRepl.Commands } else { - shellState.ConsoleManager.Error.WriteLine((commandInput.Arguments[1].Text + " does not have a configured value").Bold().Red()); + shellState.ConsoleManager.Error.WriteLine((commandInput.Arguments[1].Text + " does not have a configured value").SetColor(programState.ErrorColor)); } } else diff --git a/src/Microsoft.HttpRepl/Commands/SetBaseCommand.cs b/src/Microsoft.HttpRepl/Commands/SetBaseCommand.cs index 77e5f0adde..76166d5c53 100644 --- a/src/Microsoft.HttpRepl/Commands/SetBaseCommand.cs +++ b/src/Microsoft.HttpRepl/Commands/SetBaseCommand.cs @@ -3,6 +3,8 @@ using System; using System.Collections.Generic; +using System.Net.Http; +using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; using Microsoft.Repl; @@ -34,11 +36,20 @@ namespace Microsoft.HttpRepl.Commands } else if (parseResult.Sections.Count != 3 || string.IsNullOrEmpty(parseResult.Sections[2]) || !Uri.TryCreate(EnsureTrailingSlash(parseResult.Sections[2]), UriKind.Absolute, out Uri serverUri)) { - shellState.ConsoleManager.Error.WriteLine("Must specify a server".Bold().Red()); + shellState.ConsoleManager.Error.WriteLine("Must specify a server".SetColor(state.ErrorColor)); } else { state.BaseAddress = serverUri; + try + { + await state.Client.SendAsync(new HttpRequestMessage(HttpMethod.Head, serverUri)).ConfigureAwait(false); + } + catch (Exception ex) when (ex.InnerException is SocketException se) + { + shellState.ConsoleManager.Error.WriteLine($"Warning: HEAD request to the specified address was unsuccessful ({se.Message})".SetColor(state.WarningColor)); + } + catch { } } if (state.BaseAddress == null || !Uri.TryCreate(state.BaseAddress, "/swagger/v1/swagger.json", out Uri result)) diff --git a/src/Microsoft.HttpRepl/Commands/SetDiagCommand.cs b/src/Microsoft.HttpRepl/Commands/SetDiagCommand.cs index 535d1a2026..4adb208de3 100644 --- a/src/Microsoft.HttpRepl/Commands/SetDiagCommand.cs +++ b/src/Microsoft.HttpRepl/Commands/SetDiagCommand.cs @@ -41,7 +41,7 @@ namespace Microsoft.HttpRepl.Commands if (parseResult.Sections.Count != 3 || string.IsNullOrEmpty(parseResult.Sections[2]) || !Uri.TryCreate(parseResult.Sections[2], UriKind.Relative, out Uri _)) { - shellState.ConsoleManager.Error.WriteLine("Must specify a relative path".Bold().Red()); + shellState.ConsoleManager.Error.WriteLine("Must specify a relative path".SetColor(programState.ErrorColor)); } else { @@ -50,7 +50,7 @@ namespace Microsoft.HttpRepl.Commands if (!response.IsSuccessStatusCode) { - shellState.ConsoleManager.Error.WriteLine("Unable to access diagnostics endpoint".Bold().Red()); + shellState.ConsoleManager.Error.WriteLine("Unable to access diagnostics endpoint".SetColor(programState.ErrorColor)); programState.DiagnosticsState.DiagnosticsEndpoint = null; programState.DiagnosticsState.DiagnosticItems = null; } @@ -66,7 +66,7 @@ namespace Microsoft.HttpRepl.Commands if (!endpointsResponse.IsSuccessStatusCode) { - shellState.ConsoleManager.Error.WriteLine("Unable to get endpoints information from diagnostics endpoint".Bold().Red()); + shellState.ConsoleManager.Error.WriteLine("Unable to get endpoints information from diagnostics endpoint".SetColor(programState.ErrorColor)); return; } diff --git a/src/Microsoft.HttpRepl/Commands/SetSwaggerCommand.cs b/src/Microsoft.HttpRepl/Commands/SetSwaggerCommand.cs index be99891d5e..41c1564a99 100644 --- a/src/Microsoft.HttpRepl/Commands/SetSwaggerCommand.cs +++ b/src/Microsoft.HttpRepl/Commands/SetSwaggerCommand.cs @@ -243,7 +243,7 @@ namespace Microsoft.HttpRepl.Commands if (parseResult.Sections.Count != 3 || string.IsNullOrEmpty(parseResult.Sections[2]) || !Uri.TryCreate(parseResult.Sections[2], UriKind.Absolute, out Uri serverUri)) { - shellState.ConsoleManager.Error.WriteLine("Must specify a swagger document".Bold().Red()); + shellState.ConsoleManager.Error.WriteLine("Must specify a swagger document".SetColor(programState.ErrorColor)); } else { @@ -253,6 +253,8 @@ namespace Microsoft.HttpRepl.Commands internal static async Task CreateDirectoryStructureForSwaggerEndpointAsync(IShellState shellState, HttpState programState, Uri serverUri, CancellationToken cancellationToken) { + programState.SwaggerEndpoint = serverUri; + try { IEnumerable doc = await GetSwaggerDocAsync(programState.Client, serverUri).ConfigureAwait(false); diff --git a/src/Microsoft.HttpRepl/Commands/UICommand.cs b/src/Microsoft.HttpRepl/Commands/UICommand.cs index 0eb02826f9..ac03d53d29 100644 --- a/src/Microsoft.HttpRepl/Commands/UICommand.cs +++ b/src/Microsoft.HttpRepl/Commands/UICommand.cs @@ -29,13 +29,13 @@ namespace Microsoft.HttpRepl.Commands { if (programState.BaseAddress == null) { - shellState.ConsoleManager.Error.WriteLine("Must be connected to a server to launch Swagger UI".Bold().Red()); + shellState.ConsoleManager.Error.WriteLine("Must be connected to a server to launch Swagger UI".SetColor(programState.ErrorColor)); return Task.CompletedTask; } Uri uri = new Uri(programState.BaseAddress, "swagger"); string agent = "cmd"; - string agentParam = $"/c {uri.AbsoluteUri}"; + string agentParam = $"/c start {uri.AbsoluteUri}"; if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { diff --git a/src/Microsoft.HttpRepl/HttpState.cs b/src/Microsoft.HttpRepl/HttpState.cs index d012a8fc45..9104e4e02c 100644 --- a/src/Microsoft.HttpRepl/HttpState.cs +++ b/src/Microsoft.HttpRepl/HttpState.cs @@ -9,6 +9,7 @@ using System.Net.Http; using System.Runtime.InteropServices; using Microsoft.HttpRepl.Diagnostics; using Microsoft.HttpRepl.Preferences; +using Microsoft.Repl.ConsoleHandling; namespace Microsoft.HttpRepl { @@ -19,6 +20,10 @@ namespace Microsoft.HttpRepl public HttpClient Client { get; } + public AllowedColors ErrorColor => this.GetColorPreference(WellKnownPreference.ErrorColor, AllowedColors.BoldRed); + + public AllowedColors WarningColor => this.GetColorPreference(WellKnownPreference.WarningColor, AllowedColors.BoldYellow); + public Stack PathSections { get; } public IDirectoryStructure SwaggerStructure { get; set; } @@ -62,6 +67,8 @@ namespace Microsoft.HttpRepl public DiagnosticsState DiagnosticsState { get; } + public Uri SwaggerEndpoint { get; set; } + public HttpState() { Client = new HttpClient(); diff --git a/src/Microsoft.HttpRepl/Preferences/WellKnownPreference.cs b/src/Microsoft.HttpRepl/Preferences/WellKnownPreference.cs index 424c0bb9a4..a0456bb99f 100644 --- a/src/Microsoft.HttpRepl/Preferences/WellKnownPreference.cs +++ b/src/Microsoft.HttpRepl/Preferences/WellKnownPreference.cs @@ -131,9 +131,12 @@ namespace Microsoft.HttpRepl.Preferences public static string ResponseStatusReaseonPhraseColor { get; } = "colors.response.status.reasonPhrase"; - public static string RequestOrResponseColor { get; } = "colors.requestOrResponse"; + public static string ErrorColor { get; } = "colors.error"; + + public static string WarningColor { get; } = "colors.warning"; + public static string BodyColor { get; } = "colors.body"; public static string SchemeColor { get; } = "colors.scheme"; @@ -167,6 +170,8 @@ namespace Microsoft.HttpRepl.Preferences public static string DefaultEditorArguments { get; } = "editor.command.default.arguments"; + public static string SwaggerRequeryBehavior { get; } = "swagger.requery"; + public static AllowedColors GetColorPreference(this HttpState programState, string preference, AllowedColors defaultvalue = AllowedColors.None) { diff --git a/src/Microsoft.HttpRepl/Program.cs b/src/Microsoft.HttpRepl/Program.cs index 6509e7a38c..e41a36ff21 100644 --- a/src/Microsoft.HttpRepl/Program.cs +++ b/src/Microsoft.HttpRepl/Program.cs @@ -16,15 +16,15 @@ namespace Microsoft.HttpRepl { static async Task Main(string[] args) { - if(Console.IsOutputRedirected) + var state = new HttpState(); + + if (Console.IsOutputRedirected) { - Reporter.Error.WriteLine("Cannot start the REPL when output is being redirected".Bold().Red()); + Reporter.Error.WriteLine("Cannot start the REPL when output is being redirected".SetColor(state.ErrorColor)); return; } - var state = new HttpState(); var dispatcher = DefaultCommandDispatcher.Create(state.GetPrompt, state); - dispatcher.AddCommand(new ChangeDirectoryCommand()); dispatcher.AddCommand(new ClearCommand()); //dispatcher.AddCommand(new ConfigCommand()); diff --git a/src/Microsoft.Repl/Commanding/CommandInputSpecification.cs b/src/Microsoft.Repl/Commanding/CommandInputSpecification.cs index 0e24c3fffa..05a14e8641 100644 --- a/src/Microsoft.Repl/Commanding/CommandInputSpecification.cs +++ b/src/Microsoft.Repl/Commanding/CommandInputSpecification.cs @@ -7,7 +7,7 @@ namespace Microsoft.Repl.Commanding { public class CommandInputSpecification { - public IReadOnlyList CommandName { get; } + public IReadOnlyList> CommandName { get; } public char OptionPreamble { get; } @@ -17,7 +17,7 @@ namespace Microsoft.Repl.Commanding public IReadOnlyList Options { get; } - public CommandInputSpecification(IReadOnlyList name, char optionPreamble, IReadOnlyList options, int minimumArgs, int maximumArgs) + public CommandInputSpecification(IReadOnlyList> name, char optionPreamble, IReadOnlyList options, int minimumArgs, int maximumArgs) { CommandName = name; OptionPreamble = optionPreamble; diff --git a/src/Microsoft.Repl/Commanding/CommandInputSpecificationBuilder.cs b/src/Microsoft.Repl/Commanding/CommandInputSpecificationBuilder.cs index 019782d0b8..7b5b529e3c 100644 --- a/src/Microsoft.Repl/Commanding/CommandInputSpecificationBuilder.cs +++ b/src/Microsoft.Repl/Commanding/CommandInputSpecificationBuilder.cs @@ -1,13 +1,14 @@ // 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.Collections.Generic; namespace Microsoft.Repl.Commanding { public class CommandInputSpecificationBuilder { - private readonly IReadOnlyList _name; + private readonly List> _name; private char _optionPreamble; private int _minimumArgs; private int _maximumArgs; @@ -15,7 +16,7 @@ namespace Microsoft.Repl.Commanding public CommandInputSpecificationBuilder(IReadOnlyList name) { - _name = name; + _name = new List> { name }; _optionPreamble = '-'; } @@ -65,5 +66,13 @@ namespace Microsoft.Repl.Commanding { return new CommandInputSpecification(_name, _optionPreamble, _options, _minimumArgs, _maximumArgs); } + + public CommandInputSpecificationBuilder AlternateName(string baseName, params string[] additionalNameParts) + { + List nameParts = new List { baseName }; + nameParts.AddRange(additionalNameParts); + _name.Add(nameParts); + return this; + } } } diff --git a/src/Microsoft.Repl/Commanding/CommandWithStructuredInputBase.cs b/src/Microsoft.Repl/Commanding/CommandWithStructuredInputBase.cs index 1957205913..a7e07b3cee 100644 --- a/src/Microsoft.Repl/Commanding/CommandWithStructuredInputBase.cs +++ b/src/Microsoft.Repl/Commanding/CommandWithStructuredInputBase.cs @@ -40,17 +40,34 @@ namespace Microsoft.Repl.Commanding //If we're completing in a name position, offer completion for the command name if (parseResult.SelectedSection < InputSpec.CommandName.Count) { - for (int i = 0; i < parseResult.SelectedSection; ++i) + IReadOnlyList commandName = null; + for (int j = 0; j < InputSpec.CommandName.Count; ++j) { - if (!string.Equals(InputSpec.CommandName[i], parseResult.Sections[i], StringComparison.OrdinalIgnoreCase)) + bool success = true; + for (int i = 0; i < parseResult.SelectedSection; ++i) { - return null; + if (!string.Equals(InputSpec.CommandName[j][i], parseResult.Sections[i], StringComparison.OrdinalIgnoreCase)) + { + success = false; + break; + } + } + + if (success) + { + commandName = InputSpec.CommandName[j]; + break; } } - if (InputSpec.CommandName[parseResult.SelectedSection].StartsWith(normalCompletionString, StringComparison.OrdinalIgnoreCase)) + if (commandName is null) { - return new[] {InputSpec.CommandName[parseResult.SelectedSection]}; + return null; + } + + if (commandName[parseResult.SelectedSection].StartsWith(normalCompletionString, StringComparison.OrdinalIgnoreCase)) + { + return new[] {commandName[parseResult.SelectedSection]}; } } diff --git a/src/Microsoft.Repl/Commanding/DefaultCommandInput.cs b/src/Microsoft.Repl/Commanding/DefaultCommandInput.cs index ead59738b3..8ddaa031dc 100644 --- a/src/Microsoft.Repl/Commanding/DefaultCommandInput.cs +++ b/src/Microsoft.Repl/Commanding/DefaultCommandInput.cs @@ -21,26 +21,19 @@ namespace Microsoft.Repl.Commanding public static bool TryProcess(CommandInputSpecification spec, TParseResult parseResult, out DefaultCommandInput result, out IReadOnlyList processingIssues) { - List issues = new List(); - List commandNameElements = new List(); + List issues = null; + List commandNameElements = null; - if (spec.CommandName.Count > parseResult.Sections.Count) + foreach (IReadOnlyList commandName in spec.CommandName) { - issues.Add(new CommandInputProcessingIssue(CommandInputProcessingIssueKind.CommandMismatch, spec.CommandName[parseResult.Sections.Count])); - } - - for (int i = 0; i < spec.CommandName.Count && i < parseResult.Sections.Count; ++i) - { - if (!string.Equals(spec.CommandName[i], parseResult.Sections[i], StringComparison.OrdinalIgnoreCase)) + if (TryProcessCommandName(commandName, parseResult, out List nameElements, out issues)) { - issues.Add(new CommandInputProcessingIssue(CommandInputProcessingIssueKind.CommandMismatch, parseResult.Sections[i])); + commandNameElements = nameElements; + break; } - - commandNameElements.Add(new InputElement(CommandInputLocation.CommandName, parseResult.Sections[i], spec.CommandName[i], i)); } - //If we have a command name mismatch, no point in continuing - if (issues.Count > 0) + if (commandNameElements is null) { result = null; processingIssues = issues; @@ -185,6 +178,39 @@ namespace Microsoft.Repl.Commanding return issues.Count == 0; } + private static bool TryProcessCommandName(IReadOnlyList commandName, TParseResult parseResult, out List nameElements, out List processingIssues) + { + List issues = new List(); + List commandNameElements = new List(); + + if (commandName.Count > parseResult.Sections.Count) + { + issues.Add(new CommandInputProcessingIssue(CommandInputProcessingIssueKind.CommandMismatch, commandName[parseResult.Sections.Count])); + } + + for (int i = 0; i < commandName.Count && i < parseResult.Sections.Count; ++i) + { + if (!string.Equals(commandName[i], parseResult.Sections[i], StringComparison.OrdinalIgnoreCase)) + { + issues.Add(new CommandInputProcessingIssue(CommandInputProcessingIssueKind.CommandMismatch, parseResult.Sections[i])); + } + + commandNameElements.Add(new InputElement(CommandInputLocation.CommandName, parseResult.Sections[i], commandName[i], i)); + } + + processingIssues = issues; + + //If we have a command name mismatch, no point in continuing + if (issues.Count > 0) + { + nameElements = null; + return false; + } + + nameElements = commandNameElements; + return true; + } + public InputElement SelectedElement { get; } public IReadOnlyList CommandName { get; } From 6d106bee9290f79ae0ee5be895dc46ab565d5fb0 Mon Sep 17 00:00:00 2001 From: "ASP.NET CI" Date: Sun, 5 Aug 2018 19:11:25 +0000 Subject: [PATCH 37/66] Update dependencies.props [auto-updated: dependencies] --- build/dependencies.props | 12 ++++++------ korebuild-lock.txt | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index 0f57ae33b0..83d1dc944d 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -3,12 +3,12 @@ $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - 2.2.0-preview1-17102 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 + 2.2.0-preview1-20180731.1 + 2.2.0-preview1-34882 + 2.2.0-preview1-34882 + 2.2.0-preview1-34882 + 2.2.0-preview1-34882 + 2.2.0-preview1-34882 2.0.9 2.1.2 2.2.0-preview1-26618-02 diff --git a/korebuild-lock.txt b/korebuild-lock.txt index 28cd6a5b03..b6efc7cfcb 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.2.0-preview1-17102 -commithash:e7e2b5a97ca92cfc6acc4def534cb0901a6d1eb9 +version:2.2.0-preview1-20180731.1 +commithash:29fde58465439f4bb9df40830635ed758e063daf From b242704bda1dbef522d6be69f7b1d4b2aa2b258e Mon Sep 17 00:00:00 2001 From: Mike Lorbetske Date: Sun, 5 Aug 2018 13:55:29 -0700 Subject: [PATCH 38/66] Remove unnecessary exclusions from NuGetPackageVerifier.json --- NuGetPackageVerifier.json | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/NuGetPackageVerifier.json b/NuGetPackageVerifier.json index 4167e36811..fb43448889 100644 --- a/NuGetPackageVerifier.json +++ b/NuGetPackageVerifier.json @@ -24,33 +24,12 @@ "DotnetTool" ] }, - "dotnet-httprepl": { - "packageTypes": [ - "DotnetTool" - ], - "Exclusions": { - "ASSEMBLY_DESCRIPTION": { - "tools/netcoreapp2.2/any/System.Net.Http.Formatting.dll": "Referenced assembly, not built as part of this process" - }, - "VERSION_INFORMATIONALVERSION": { - "tools/netcoreapp2.2/any/Newtonsoft.Json.dll": "Referenced assembly, not built as part of this process", - "tools/netcoreapp2.2/any/Newtonsoft.Json.Bson.dll": "Referenced assembly, not built as part of this process" - } - } - }, "Microsoft.AspNetCore.DeveloperCertificates.XPlat": { "Exclusions": { "DOC_MISSING": { "lib/netcoreapp2.2/Microsoft.AspNetCore.DeveloperCertificates.XPlat.dll": "Docs not required to shipoob package" } } - }, - "Microsoft.Repl": { - "Exclusions": { - "DOC_MISSING": { - "lib/netcoreapp2.2/Microsoft.Repl.dll": "Docs not required to shipoob package" - } - } } } }, From eb591fb29dfc87c9815eba024038788c731afcbc Mon Sep 17 00:00:00 2001 From: Mike Lorbetske Date: Sun, 5 Aug 2018 14:13:08 -0700 Subject: [PATCH 39/66] Attempt to fix exclusions in NuGetPackageVerifier.json again --- NuGetPackageVerifier.json | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/NuGetPackageVerifier.json b/NuGetPackageVerifier.json index fb43448889..63b866c8ed 100644 --- a/NuGetPackageVerifier.json +++ b/NuGetPackageVerifier.json @@ -24,6 +24,20 @@ "DotnetTool" ] }, + "dotnet-httprepl": { + "packageTypes": [ + "DotnetTool" + ], + "Exclusions": { + "ASSEMBLY_DESCRIPTION": { + "tools/netcoreapp2.1/any/System.Net.Http.Formatting.dll": "Referenced assembly, not built as part of this process" + }, + "VERSION_INFORMATIONALVERSION": { + "tools/netcoreapp2.1/any/Newtonsoft.Json.dll": "Referenced assembly, not built as part of this process", + "tools/netcoreapp2.1/any/Newtonsoft.Json.Bson.dll": "Referenced assembly, not built as part of this process" + } + } + }, "Microsoft.AspNetCore.DeveloperCertificates.XPlat": { "Exclusions": { "DOC_MISSING": { From dbad34cdef766fc06877cb28d862eeff0c55377a Mon Sep 17 00:00:00 2001 From: "ASP.NET CI" Date: Mon, 6 Aug 2018 20:33:37 +0000 Subject: [PATCH 40/66] Update dependencies.props [auto-updated: dependencies] --- build/dependencies.props | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index 83d1dc944d..b6107e7202 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -4,11 +4,11 @@ 2.2.0-preview1-20180731.1 - 2.2.0-preview1-34882 - 2.2.0-preview1-34882 - 2.2.0-preview1-34882 - 2.2.0-preview1-34882 - 2.2.0-preview1-34882 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 2.0.9 2.1.2 2.2.0-preview1-26618-02 From b53d50f6f0fb79f5c4794f090d6c80382a7e9d98 Mon Sep 17 00:00:00 2001 From: Mike Lorbetske Date: Tue, 7 Aug 2018 16:16:33 -0700 Subject: [PATCH 41/66] Fix incorrectly resolved cross-document pointers --- src/Microsoft.HttpRepl/OpenApi/PointerUtil.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.HttpRepl/OpenApi/PointerUtil.cs b/src/Microsoft.HttpRepl/OpenApi/PointerUtil.cs index c8566095b7..5bff058b95 100644 --- a/src/Microsoft.HttpRepl/OpenApi/PointerUtil.cs +++ b/src/Microsoft.HttpRepl/OpenApi/PointerUtil.cs @@ -19,6 +19,8 @@ namespace Microsoft.HttpRepl.OpenApi private static async Task ResolvePointersAsync(Uri loadLocation, JToken root, JToken toResolve, HttpClient client) { + JToken cursor = root; + if (toResolve is JArray arr) { for (int i = 0; i < arr.Count; ++i) @@ -69,7 +71,7 @@ namespace Microsoft.HttpRepl.OpenApi return new JValue((object)null); } - return await ResolvePointersAsync(loadTarget, newRoot, newRoot, client).ConfigureAwait(false); + cursor = await ResolvePointersAsync(loadTarget, newRoot, newRoot, client).ConfigureAwait(false); } //We're in the right document, grab the bookmark (fragment) of the URI and get the element at that path @@ -81,7 +83,6 @@ namespace Microsoft.HttpRepl.OpenApi } string[] parts = fragment.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); - JToken cursor = root; for (int i = 0; i < parts.Length; ++i) { From a9bdf718e532b87bac5ec6f5532e75c1f0b84036 Mon Sep 17 00:00:00 2001 From: "ASP.NET CI" Date: Sun, 12 Aug 2018 19:10:29 +0000 Subject: [PATCH 42/66] Update dependencies.props [auto-updated: dependencies] --- build/dependencies.props | 12 ++++++------ korebuild-lock.txt | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index b6107e7202..54c03037c6 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -3,12 +3,12 @@ $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - 2.2.0-preview1-20180731.1 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 + 2.2.0-preview1-20180807.2 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 2.0.9 2.1.2 2.2.0-preview1-26618-02 diff --git a/korebuild-lock.txt b/korebuild-lock.txt index b6efc7cfcb..29a57027f1 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.2.0-preview1-20180731.1 -commithash:29fde58465439f4bb9df40830635ed758e063daf +version:2.2.0-preview1-20180807.2 +commithash:11495dbd236104434e08cb1152fcb58cf2a20923 From 734ddf9c1007181e4c6dcd7ca49e2f512b2cd4ae Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Wed, 15 Aug 2018 10:35:12 -0700 Subject: [PATCH 43/66] Restrict build to only use VS 2017 --- korebuild.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/korebuild.json b/korebuild.json index 7002d25ca1..6cff0cf67e 100644 --- a/korebuild.json +++ b/korebuild.json @@ -7,7 +7,7 @@ "windows" ], "includePrerelease": true, - "minVersion": "15.0.26730.03", + "versionRange": "[15.0.26730.03, 16.0)", "requiredWorkloads": [ "Microsoft.VisualStudio.Component.VSSDK" ] From b3f6477a49e1ebdd6d0f8a8933cacd7b0b916b88 Mon Sep 17 00:00:00 2001 From: "ASP.NET CI" Date: Tue, 21 Aug 2018 13:33:49 -0700 Subject: [PATCH 44/66] Update package branding for 2.2.0-preview2 --- build/dependencies.props | 2 +- version.props | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index 54c03037c6..c3fb6054b3 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -20,6 +20,6 @@ 2.3.1 2.4.0 - + diff --git a/version.props b/version.props index 4c749bb5d2..91190966fa 100644 --- a/version.props +++ b/version.props @@ -2,7 +2,7 @@ 2.2.0 15.6 - preview1 + preview2 $(VersionPrefix) $(VersionPrefix)-$(VersionSuffix)-final $(VsixVersion).$(BuildNumber) From 64c572c8a58a088626d61326ba77f453e5f7199b Mon Sep 17 00:00:00 2001 From: "ASP.NET CI" Date: Sun, 2 Sep 2018 12:09:51 -0700 Subject: [PATCH 45/66] Update dependencies.props [auto-updated: dependencies] --- build/dependencies.props | 16 ++++++++-------- korebuild-lock.txt | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index 029957e745..cf1be76701 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -1,23 +1,23 @@ - + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - 2.2.0-preview1-20180807.2 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 + 2.2.0-preview1-20180821.1 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 2.0.9 2.1.2 2.2.0-preview1-26618-02 15.6.1 5.2.6 2.0.3 + 11.0.2 4.5.1 4.5.0 - 11.0.2 9.0.1 2.3.1 2.4.0 diff --git a/korebuild-lock.txt b/korebuild-lock.txt index 29a57027f1..524a2323d0 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.2.0-preview1-20180807.2 -commithash:11495dbd236104434e08cb1152fcb58cf2a20923 +version:2.2.0-preview1-20180821.1 +commithash:c8d0cc52cd1abb697be24e288ffd54f8fae8bf17 From 64a2c3ab46274613935a7a2a87c76b4b320d14b0 Mon Sep 17 00:00:00 2001 From: "ASP.NET CI" Date: Wed, 5 Sep 2018 16:34:06 -0700 Subject: [PATCH 46/66] Update branding to 2.2.0-preview3 --- version.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.props b/version.props index 91190966fa..07833713f2 100644 --- a/version.props +++ b/version.props @@ -2,7 +2,7 @@ 2.2.0 15.6 - preview2 + preview3 $(VersionPrefix) $(VersionPrefix)-$(VersionSuffix)-final $(VsixVersion).$(BuildNumber) From aa93edcee29acea037694d2a71ad02232836f69c Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Fri, 7 Sep 2018 10:01:52 -0700 Subject: [PATCH 47/66] Remove Microsoft.DotNet.GlobalTools.Sdk These targets are now part of Microsoft.NET.Sdk --- NuGet.config | 6 +----- build/dependencies.props | 2 +- korebuild-lock.txt | 4 ++-- src/Microsoft.HttpRepl/Microsoft.HttpRepl.csproj | 7 +++---- src/dotnet-dev-certs/dotnet-dev-certs.csproj | 4 +--- src/dotnet-sql-cache/dotnet-sql-cache.csproj | 7 ++----- src/dotnet-user-secrets/dotnet-user-secrets.csproj | 7 ++----- src/dotnet-watch/dotnet-watch.csproj | 7 ++----- 8 files changed, 14 insertions(+), 30 deletions(-) diff --git a/NuGet.config b/NuGet.config index 8c30f1c84e..e32bddfd51 100644 --- a/NuGet.config +++ b/NuGet.config @@ -2,10 +2,6 @@ - - + diff --git a/build/dependencies.props b/build/dependencies.props index cf1be76701..fbf093bdf8 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -3,7 +3,7 @@ $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - 2.2.0-preview1-20180821.1 + 2.2.0-preview1-20180907.3 2.2.0-preview2-35143 2.2.0-preview2-35143 2.2.0-preview2-35143 diff --git a/korebuild-lock.txt b/korebuild-lock.txt index 524a2323d0..be535d1f04 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.2.0-preview1-20180821.1 -commithash:c8d0cc52cd1abb697be24e288ffd54f8fae8bf17 +version:2.2.0-preview1-20180907.3 +commithash:2315030c7f3de2af0f16cf64502321937b6c4315 diff --git a/src/Microsoft.HttpRepl/Microsoft.HttpRepl.csproj b/src/Microsoft.HttpRepl/Microsoft.HttpRepl.csproj index 99b86075c3..72f3e8fc1e 100644 --- a/src/Microsoft.HttpRepl/Microsoft.HttpRepl.csproj +++ b/src/Microsoft.HttpRepl/Microsoft.HttpRepl.csproj @@ -1,4 +1,4 @@ - + Exe @@ -8,9 +8,8 @@ latest Command line tool to for making HTTP calls and viewing their results. dotnet;http;httprepl - - true + win-x64;win-x86 @@ -20,7 +19,7 @@ - + diff --git a/src/dotnet-dev-certs/dotnet-dev-certs.csproj b/src/dotnet-dev-certs/dotnet-dev-certs.csproj index b785b60c42..343025749a 100644 --- a/src/dotnet-dev-certs/dotnet-dev-certs.csproj +++ b/src/dotnet-dev-certs/dotnet-dev-certs.csproj @@ -1,6 +1,5 @@ - netcoreapp2.2 @@ -9,9 +8,8 @@ Microsoft.AspNetCore.DeveloperCertificates.Tools dotnet;developercertificates true - - true + win-x64;win-x86 diff --git a/src/dotnet-sql-cache/dotnet-sql-cache.csproj b/src/dotnet-sql-cache/dotnet-sql-cache.csproj index 4ab0f3425c..3f9ce05717 100644 --- a/src/dotnet-sql-cache/dotnet-sql-cache.csproj +++ b/src/dotnet-sql-cache/dotnet-sql-cache.csproj @@ -1,6 +1,4 @@ - - - + netcoreapp2.2 @@ -8,9 +6,8 @@ Command line tool to create tables and indexes in a Microsoft SQL Server database for distributed caching. cache;distributedcache;sqlserver true - - true + win-x64;win-x86 diff --git a/src/dotnet-user-secrets/dotnet-user-secrets.csproj b/src/dotnet-user-secrets/dotnet-user-secrets.csproj index bcb37e37b2..ae77d9bffd 100644 --- a/src/dotnet-user-secrets/dotnet-user-secrets.csproj +++ b/src/dotnet-user-secrets/dotnet-user-secrets.csproj @@ -1,6 +1,4 @@ - - - + netcoreapp2.2 @@ -10,9 +8,8 @@ Microsoft.Extensions.SecretManager.Tools configuration;secrets;usersecrets true - - true + win-x64;win-x86 diff --git a/src/dotnet-watch/dotnet-watch.csproj b/src/dotnet-watch/dotnet-watch.csproj index c5210a0cd2..d5012267f8 100644 --- a/src/dotnet-watch/dotnet-watch.csproj +++ b/src/dotnet-watch/dotnet-watch.csproj @@ -1,6 +1,4 @@ - - - + netcoreapp2.2 @@ -9,9 +7,8 @@ Microsoft.DotNet.Watcher.Tools dotnet;watch true - - true + win-x64;win-x86 From 4bcfbe4d404a3e3fed76a548618ca6d698547ced Mon Sep 17 00:00:00 2001 From: Glenn Condron Date: Fri, 7 Sep 2018 15:21:42 -0700 Subject: [PATCH 48/66] [REPL] Help improvements for repl (#478) * Beginning of help additions. Fixed a nullref. * Standardized help with options displayed as appropriate. --- .../Commands/BaseHttpCommand.cs | 15 +- .../Commands/ChangeDirectoryCommand.cs | 15 +- .../Commands/ConfigCommand.cs | 2 +- .../Commands/EchoCommand.cs | 10 +- .../Commands/ExitCommand.cs | 11 +- .../Commands/HelpCommand.cs | 134 +++++++++++++----- .../Commands/ListCommand.cs | 13 +- .../Commands/PrefCommand.cs | 80 ++++++++++- src/Microsoft.HttpRepl/Commands/RunCommand.cs | 15 +- .../Commands/SetBaseCommand.cs | 8 +- .../Commands/SetHeaderCommand.cs | 16 ++- .../CommandWithStructuredInputBase.cs | 2 +- 12 files changed, 250 insertions(+), 71 deletions(-) diff --git a/src/Microsoft.HttpRepl/Commands/BaseHttpCommand.cs b/src/Microsoft.HttpRepl/Commands/BaseHttpCommand.cs index 9a5eba8227..a8ad52fc24 100644 --- a/src/Microsoft.HttpRepl/Commands/BaseHttpCommand.cs +++ b/src/Microsoft.HttpRepl/Commands/BaseHttpCommand.cs @@ -42,7 +42,7 @@ namespace Microsoft.HttpRepl.Commands protected abstract bool RequiresBody { get; } - protected override CommandInputSpecification InputSpec + public override CommandInputSpecification InputSpec { get { @@ -469,7 +469,18 @@ namespace Microsoft.HttpRepl.Commands protected override string GetHelpDetails(IShellState shellState, HttpState programState, DefaultCommandInput commandInput, ICoreParseResult parseResult) { - return $"Issues a {Verb.ToUpperInvariant()} request"; + var helpText = new StringBuilder(); + helpText.Append("Usage: ".Bold()); + helpText.AppendLine($"{Verb.ToUpperInvariant()} [Options]"); + helpText.AppendLine(); + helpText.AppendLine($"Issues a {Verb.ToUpperInvariant()} request."); + + if (RequiresBody) + { + helpText.AppendLine("Your default editor will be opened with a sample body if no options are provided."); + } + + return helpText.ToString(); } public override string GetHelpSummary(IShellState shellState, HttpState programState) diff --git a/src/Microsoft.HttpRepl/Commands/ChangeDirectoryCommand.cs b/src/Microsoft.HttpRepl/Commands/ChangeDirectoryCommand.cs index a18aa2df9f..4fd011d182 100644 --- a/src/Microsoft.HttpRepl/Commands/ChangeDirectoryCommand.cs +++ b/src/Microsoft.HttpRepl/Commands/ChangeDirectoryCommand.cs @@ -4,11 +4,13 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.HttpRepl.Suggestions; using Microsoft.Repl; using Microsoft.Repl.Commanding; +using Microsoft.Repl.ConsoleHandling; using Microsoft.Repl.Parsing; namespace Microsoft.HttpRepl.Commands @@ -60,18 +62,19 @@ namespace Microsoft.HttpRepl.Commands return Task.CompletedTask; } - protected override CommandInputSpecification InputSpec { get; } = CommandInputSpecification.Create("cd") + public override CommandInputSpecification InputSpec { get; } = CommandInputSpecification.Create("cd") .MaximumArgCount(1) .Finish(); protected override string GetHelpDetails(IShellState shellState, HttpState programState, DefaultCommandInput commandInput, ICoreParseResult parseResult) { - if (commandInput.Arguments.Count == 1 && !string.IsNullOrEmpty(commandInput.Arguments[0]?.Text)) - { - return "Prints the current directory if no argument is specified, otherwise changes to the specified directory"; - } + var help = new StringBuilder(); + help.Append("Usage:".Bold()); + help.AppendLine("cd [directory]"); + help.AppendLine(); + help.AppendLine("Prints the current directory if no argument is specified, otherwise changes to the specified directory"); - return "Changes to the directory " + commandInput.Arguments[0].Text; + return help.ToString(); } public override string GetHelpSummary(IShellState shellState, HttpState programState) diff --git a/src/Microsoft.HttpRepl/Commands/ConfigCommand.cs b/src/Microsoft.HttpRepl/Commands/ConfigCommand.cs index 938936303f..9ad71ea15d 100644 --- a/src/Microsoft.HttpRepl/Commands/ConfigCommand.cs +++ b/src/Microsoft.HttpRepl/Commands/ConfigCommand.cs @@ -55,7 +55,7 @@ namespace Microsoft.HttpRepl.Commands } } - protected override CommandInputSpecification InputSpec { get; } = CommandInputSpecification.Create("config").Finish(); + public override CommandInputSpecification InputSpec { get; } = CommandInputSpecification.Create("config").Finish(); protected override string GetHelpDetails(IShellState shellState, HttpState programState, DefaultCommandInput commandInput, ICoreParseResult parseResult) { diff --git a/src/Microsoft.HttpRepl/Commands/EchoCommand.cs b/src/Microsoft.HttpRepl/Commands/EchoCommand.cs index e81ff810d4..e4c0ace75a 100644 --- a/src/Microsoft.HttpRepl/Commands/EchoCommand.cs +++ b/src/Microsoft.HttpRepl/Commands/EchoCommand.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.Repl; @@ -37,11 +38,16 @@ namespace Microsoft.HttpRepl.Commands return Task.CompletedTask; } - protected override CommandInputSpecification InputSpec { get; } = CommandInputSpecification.Create("echo").ExactArgCount(1).Finish(); + public override CommandInputSpecification InputSpec { get; } = CommandInputSpecification.Create("echo").ExactArgCount(1).Finish(); protected override string GetHelpDetails(IShellState shellState, HttpState programState, DefaultCommandInput commandInput, ICoreParseResult parseResult) { - return "Turns request echoing on or off"; + var helpText = new StringBuilder(); + helpText.Append("Usage: ".Bold()); + helpText.AppendLine($"echo [on|off]"); + helpText.AppendLine(); + helpText.AppendLine($"Turns request echoing on or off. When request echoing is on we will display a text representation of requests made by the CLI."); + return helpText.ToString(); } public override string GetHelpSummary(IShellState shellState, HttpState programState) diff --git a/src/Microsoft.HttpRepl/Commands/ExitCommand.cs b/src/Microsoft.HttpRepl/Commands/ExitCommand.cs index af76ad95df..ea6bf74b68 100644 --- a/src/Microsoft.HttpRepl/Commands/ExitCommand.cs +++ b/src/Microsoft.HttpRepl/Commands/ExitCommand.cs @@ -1,10 +1,12 @@ // 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.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.Repl; using Microsoft.Repl.Commanding; +using Microsoft.Repl.ConsoleHandling; using Microsoft.Repl.Parsing; namespace Microsoft.HttpRepl.Commands @@ -17,11 +19,16 @@ namespace Microsoft.HttpRepl.Commands return Task.CompletedTask; } - protected override CommandInputSpecification InputSpec { get; } = CommandInputSpecification.Create("exit").ExactArgCount(0).Finish(); + public override CommandInputSpecification InputSpec { get; } = CommandInputSpecification.Create("exit").ExactArgCount(0).Finish(); protected override string GetHelpDetails(IShellState shellState, object programState, DefaultCommandInput commandInput, ICoreParseResult parseResult) { - return "Exits the shell"; + var helpText = new StringBuilder(); + helpText.Append("Usage: ".Bold()); + helpText.AppendLine($"exit"); + helpText.AppendLine(); + helpText.AppendLine($"Exits the shell"); + return helpText.ToString(); } public override string GetHelpSummary(IShellState shellState, object programState) diff --git a/src/Microsoft.HttpRepl/Commands/HelpCommand.cs b/src/Microsoft.HttpRepl/Commands/HelpCommand.cs index 6677bde928..26e4468e8a 100644 --- a/src/Microsoft.HttpRepl/Commands/HelpCommand.cs +++ b/src/Microsoft.HttpRepl/Commands/HelpCommand.cs @@ -10,6 +10,7 @@ using Microsoft.HttpRepl.Preferences; using Microsoft.HttpRepl.Suggestions; using Microsoft.Repl; using Microsoft.Repl.Commanding; +using Microsoft.Repl.ConsoleHandling; using Microsoft.Repl.Parsing; namespace Microsoft.HttpRepl.Commands @@ -46,7 +47,30 @@ namespace Microsoft.HttpRepl.Commands if (!string.IsNullOrEmpty(help)) { anyHelp = true; + shellState.ConsoleManager.WriteLine(); shellState.ConsoleManager.WriteLine(help); + + var structuredCommand = command as CommandWithStructuredInputBase; + if (structuredCommand != null && structuredCommand.InputSpec.Options.Any()) + { + shellState.ConsoleManager.WriteLine(); + shellState.ConsoleManager.WriteLine("Options:".Bold()); + foreach (var option in structuredCommand.InputSpec.Options) + { + var optionText = string.Empty; + foreach (var form in option.Forms) + { + if (!string.IsNullOrEmpty(optionText)) + { + optionText += "|"; + } + optionText += form; + } + shellState.ConsoleManager.WriteLine($" {optionText}"); + } + } + + break; } } } @@ -67,39 +91,42 @@ namespace Microsoft.HttpRepl.Commands } } - IDirectoryStructure structure = programState.Structure.TraverseTo(parseResult.Sections[1]); - if (structure.DirectoryNames.Any()) + //Structure is null because, for example, SwaggerEndpoint exists but is not reachable. + if (programState.Structure != null) { - shellState.ConsoleManager.WriteLine("Child directories:"); - - foreach (string name in structure.DirectoryNames) + IDirectoryStructure structure = programState.Structure.TraverseTo(parseResult.Sections[1]); + if (structure.DirectoryNames.Any()) { - shellState.ConsoleManager.WriteLine(" " + name + "/"); + shellState.ConsoleManager.WriteLine("Child directories:"); + + foreach (string name in structure.DirectoryNames) + { + shellState.ConsoleManager.WriteLine(" " + name + "/"); + } + anyHelp = true; } - anyHelp = true; - } - - if (structure.RequestInfo != null) - { - if (structure.RequestInfo.Methods.Count > 0) + if (structure.RequestInfo != null) { - if (anyHelp) + if (structure.RequestInfo.Methods.Count > 0) { - shellState.ConsoleManager.WriteLine(); - } - - anyHelp = true; - shellState.ConsoleManager.WriteLine("Available methods:"); - - foreach (string method in structure.RequestInfo.Methods) - { - shellState.ConsoleManager.WriteLine(" " + method.ToUpperInvariant()); - IReadOnlyList accepts = structure.RequestInfo.ContentTypesByMethod[method]; - string acceptsString = string.Join(", ", accepts.Where(x => !string.IsNullOrEmpty(x))); - if (!string.IsNullOrEmpty(acceptsString)) + if (anyHelp) { - shellState.ConsoleManager.WriteLine(" Accepts: " + acceptsString); + shellState.ConsoleManager.WriteLine(); + } + + anyHelp = true; + shellState.ConsoleManager.WriteLine("Available methods:"); + + foreach (string method in structure.RequestInfo.Methods) + { + shellState.ConsoleManager.WriteLine(" " + method.ToUpperInvariant()); + IReadOnlyList accepts = structure.RequestInfo.ContentTypesByMethod[method]; + string acceptsString = string.Join(", ", accepts.Where(x => !string.IsNullOrEmpty(x))); + if (!string.IsNullOrEmpty(acceptsString)) + { + shellState.ConsoleManager.WriteLine(" Accepts: " + acceptsString); + } } } } @@ -176,15 +203,54 @@ namespace Microsoft.HttpRepl.Commands public void CoreGetHelp(IShellState shellState, ICommandDispatcher dispatcher, HttpState programState) { - foreach (ICommand command in dispatcher.Commands) - { - string help = command.GetHelpSummary(shellState, programState); + shellState.ConsoleManager.WriteLine(); + shellState.ConsoleManager.WriteLine("HTTP Commands:".Bold().Cyan()); + shellState.ConsoleManager.WriteLine("Use these commands to execute requests against your application."); + shellState.ConsoleManager.WriteLine(); - if (!string.IsNullOrEmpty(help)) - { - shellState.ConsoleManager.WriteLine(help); - } - } + const int navCommandColumn = -15; + + shellState.ConsoleManager.WriteLine($"{"GET",navCommandColumn}{"Issues a GET request."}"); + shellState.ConsoleManager.WriteLine($"{"POST",navCommandColumn}{"Issues a POST request."}"); + shellState.ConsoleManager.WriteLine($"{"PUT",navCommandColumn}{"Issues a PUT request."}"); + shellState.ConsoleManager.WriteLine($"{"DELETE",navCommandColumn}{"Issues a DELETE request."}"); + shellState.ConsoleManager.WriteLine($"{"PATCH",navCommandColumn}{"Issues a PATCH request."}"); + shellState.ConsoleManager.WriteLine($"{"HEAD",navCommandColumn}{"Issues a HEAD request."}"); + shellState.ConsoleManager.WriteLine($"{"OPTIONS",navCommandColumn}{"Issues an OPTIONS request."}"); + shellState.ConsoleManager.WriteLine(); + shellState.ConsoleManager.WriteLine($"{"set header",navCommandColumn}{"Sets or clears a header for all requests. e.g. `set header content-type:application/json`"}"); + shellState.ConsoleManager.WriteLine(); + + shellState.ConsoleManager.WriteLine(); + shellState.ConsoleManager.WriteLine("Navigation Commands:".Bold().Cyan()); + shellState.ConsoleManager.WriteLine("The REPL allows you to navigate your URL space and focus on specific APIS that you are working on."); + shellState.ConsoleManager.WriteLine(); + + shellState.ConsoleManager.WriteLine($"{"set base",navCommandColumn}{"Set the base URI. e.g. `set base http://locahost:5000`"}"); + shellState.ConsoleManager.WriteLine($"{"set swagger",navCommandColumn}{"Set the URI, relative to your base if set, of the Swagger document for this API. e.g. `set swagger /swagger/v1/swagger.json`"}"); + shellState.ConsoleManager.WriteLine($"{"ls",navCommandColumn}{"Show all endpoints for the current path."}"); + shellState.ConsoleManager.WriteLine($"{"cd",navCommandColumn}{"Append the given directory to the currently selected path, or move up a path when using `cd ..`."}"); + + shellState.ConsoleManager.WriteLine(); + shellState.ConsoleManager.WriteLine("Shell Commands:".Bold().Cyan()); + shellState.ConsoleManager.WriteLine("Use these commands to interact with the REPL shell."); + shellState.ConsoleManager.WriteLine(); + + shellState.ConsoleManager.WriteLine($"{"clear",navCommandColumn}{"Removes all text from the shell."}"); + shellState.ConsoleManager.WriteLine($"{"echo [on/off]",navCommandColumn}{"Turns request echoing on or off, show the request that was mode when using request commands."}"); + shellState.ConsoleManager.WriteLine($"{"exit",navCommandColumn}{"Exit the shell."}"); + + shellState.ConsoleManager.WriteLine(); + shellState.ConsoleManager.WriteLine("REPL Customization Commands:".Bold().Cyan()); + shellState.ConsoleManager.WriteLine("Use these commands to customize the REPL behavior.."); + shellState.ConsoleManager.WriteLine(); + + shellState.ConsoleManager.WriteLine($"{"pref [get/set]",navCommandColumn}{"Allows viewing or changing preferences, e.g. 'pref set editor.command.default 'C:\\Program Files\\Microsoft VS Code\\Code.exe'`"}"); + shellState.ConsoleManager.WriteLine($"{"run",navCommandColumn}{"Runs the script at the given path. A script is a set of commands that can be typed with one command per line."}"); + shellState.ConsoleManager.WriteLine($"{"ui",navCommandColumn}{"Displays the swagger UI page, if available, in the default browser."}"); + shellState.ConsoleManager.WriteLine(); + shellState.ConsoleManager.WriteLine("Use help to learn more details about individual commands. e.g. `help get`".Bold().Cyan()); + shellState.ConsoleManager.WriteLine(); } } } diff --git a/src/Microsoft.HttpRepl/Commands/ListCommand.cs b/src/Microsoft.HttpRepl/Commands/ListCommand.cs index b0641f186b..ff604e9fe3 100644 --- a/src/Microsoft.HttpRepl/Commands/ListCommand.cs +++ b/src/Microsoft.HttpRepl/Commands/ListCommand.cs @@ -5,11 +5,13 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.HttpRepl.Preferences; using Microsoft.Repl; using Microsoft.Repl.Commanding; +using Microsoft.Repl.ConsoleHandling; using Microsoft.Repl.Parsing; namespace Microsoft.HttpRepl.Commands @@ -118,19 +120,24 @@ namespace Microsoft.HttpRepl.Commands - protected override CommandInputSpecification InputSpec { get; } = CommandInputSpecification.Create("ls").AlternateName("dir") + public override CommandInputSpecification InputSpec { get; } = CommandInputSpecification.Create("ls").AlternateName("dir") .MaximumArgCount(1) .WithOption(new CommandOptionSpecification(RecursiveOption, maximumOccurrences: 1, acceptsValue: true, forms: new[] {"-r", "--recursive"})) .Finish(); protected override string GetHelpDetails(IShellState shellState, HttpState programState, DefaultCommandInput commandInput, ICoreParseResult parseResult) { - return "Lists the contents of a directory"; + var helpText = new StringBuilder(); + helpText.Append("Usage: ".Bold()); + helpText.AppendLine($"ls [Options]"); + helpText.AppendLine(); + helpText.AppendLine($"Displays the known routes at the current location. Requires a Swagger document to be set."); + return helpText.ToString(); } public override string GetHelpSummary(IShellState shellState, HttpState programState) { - return "ls - Performs a directory listing"; + return "ls - List known routes for the current location"; } protected override IEnumerable GetArgumentSuggestionsForText(IShellState shellState, HttpState programState, ICoreParseResult parseResult, DefaultCommandInput commandInput, string normalCompletionString) diff --git a/src/Microsoft.HttpRepl/Commands/PrefCommand.cs b/src/Microsoft.HttpRepl/Commands/PrefCommand.cs index d4d5d19b4a..02df9eef47 100644 --- a/src/Microsoft.HttpRepl/Commands/PrefCommand.cs +++ b/src/Microsoft.HttpRepl/Commands/PrefCommand.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.HttpRepl.Preferences; @@ -42,19 +43,86 @@ namespace Microsoft.HttpRepl.Commands protected override string GetHelpDetails(IShellState shellState, HttpState programState, DefaultCommandInput commandInput, ICoreParseResult parseResult) { + var helpText = new StringBuilder(); + helpText.Append("Usage: ".Bold()); + if (commandInput.Arguments.Count == 0 || !_allowedSubcommands.Contains(commandInput.Arguments[0]?.Text)) { - return "pref [get/set] {setting} [{value}] - Get or sets a preference to a particular value"; + helpText.AppendLine("pref [get/set] {setting} [{value}] - Get or sets a preference to a particular value"); } - - if (string.Equals(commandInput.Arguments[0].Text, "get", StringComparison.OrdinalIgnoreCase)) + else if (string.Equals(commandInput.Arguments[0].Text, "get", StringComparison.OrdinalIgnoreCase)) { - return "pref get [{setting}] - Gets the value of the specified preference or lists all preferences if no preference is specified"; + helpText.AppendLine("pref get [{setting}] - Gets the value of the specified preference or lists all preferences if no preference is specified"); } else { - return "pref set {setting} [{value}] - Sets (or clears if value is not specified) the value of the specified preference"; + helpText.AppendLine("pref set {setting} [{value}] - Sets (or clears if value is not specified) the value of the specified preference"); } + + helpText.AppendLine(); + helpText.AppendLine("Current Default Preferences:"); + foreach (var pref in programState.DefaultPreferences) + { + var val = pref.Value; + if (pref.Key.Contains("colors")) + { + val = GetColor(val); + } + helpText.AppendLine($"{pref.Key,-50}{val}"); + } + helpText.AppendLine(); + helpText.AppendLine("Current Preferences:"); + foreach (var pref in programState.Preferences) + { + var val = pref.Value; + if (pref.Key.Contains("colors")) + { + val = GetColor(val); + } + helpText.AppendLine($"{pref.Key,-50}{val}"); + } + + return helpText.ToString(); + } + + private static string GetColor(string value) + { + if (value.Contains("Bold")) + { + value = value.Bold(); + } + + if (value.Contains("Yellow")) + { + value = value.Yellow(); + } + + if (value.Contains("Cyan")) + { + value = value.Cyan(); + } + + if (value.Contains("Magenta")) + { + value = value.Magenta(); + } + + if (value.Contains("Green")) + { + value = value.Green(); + } + + if (value.Contains("White")) + { + value = value.White(); + } + + if (value.Contains("Black")) + { + value = value.Black(); + } + + return value; } protected override Task ExecuteAsync(IShellState shellState, HttpState programState, DefaultCommandInput commandInput, ICoreParseResult parseResult, CancellationToken cancellationToken) @@ -123,7 +191,7 @@ namespace Microsoft.HttpRepl.Commands return Task.CompletedTask; } - protected override CommandInputSpecification InputSpec { get; } = CommandInputSpecification.Create("pref") + public override CommandInputSpecification InputSpec { get; } = CommandInputSpecification.Create("pref") .MinimumArgCount(1) .MaximumArgCount(3) .Finish(); diff --git a/src/Microsoft.HttpRepl/Commands/RunCommand.cs b/src/Microsoft.HttpRepl/Commands/RunCommand.cs index 3228dbccf1..09f4649812 100644 --- a/src/Microsoft.HttpRepl/Commands/RunCommand.cs +++ b/src/Microsoft.HttpRepl/Commands/RunCommand.cs @@ -4,10 +4,12 @@ using System; using System.Collections.Generic; using System.IO; +using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.Repl; using Microsoft.Repl.Commanding; +using Microsoft.Repl.ConsoleHandling; using Microsoft.Repl.Parsing; using Microsoft.Repl.Scripting; using Microsoft.Repl.Suggestions; @@ -48,12 +50,13 @@ namespace Microsoft.HttpRepl.Commands { if (parseResult.Sections.Count > 0 && string.Equals(parseResult.Sections[0], Name, StringComparison.OrdinalIgnoreCase)) { - if (parseResult.Sections.Count == 1) - { - return "Runs the specified script"; - } - - return "Runs the script " + parseResult.Sections[1]; + var helpText = new StringBuilder(); + helpText.Append("Usage: ".Bold()); + helpText.AppendLine("run {path to script}"); + helpText.AppendLine(); + helpText.AppendLine("Runs the specified script."); + helpText.AppendLine("A script is a text file containing one CLI command per line. Each line will be run as if it was typed into the CLI."); + return helpText.ToString(); } return null; diff --git a/src/Microsoft.HttpRepl/Commands/SetBaseCommand.cs b/src/Microsoft.HttpRepl/Commands/SetBaseCommand.cs index 76166d5c53..aa12ec819e 100644 --- a/src/Microsoft.HttpRepl/Commands/SetBaseCommand.cs +++ b/src/Microsoft.HttpRepl/Commands/SetBaseCommand.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Net.Http; using System.Net.Sockets; +using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.Repl; @@ -80,7 +81,12 @@ namespace Microsoft.HttpRepl.Commands { if (parseResult.Sections.Count > 1 && string.Equals(parseResult.Sections[0], Name, StringComparison.OrdinalIgnoreCase) && string.Equals(parseResult.Sections[1], SubCommand, StringComparison.OrdinalIgnoreCase)) { - return Description; + var helpText = new StringBuilder(); + helpText.Append("Usage: ".Bold()); + helpText.AppendLine($"set base [uri]"); + helpText.AppendLine(); + helpText.AppendLine(Description); + return helpText.ToString(); } return null; diff --git a/src/Microsoft.HttpRepl/Commands/SetHeaderCommand.cs b/src/Microsoft.HttpRepl/Commands/SetHeaderCommand.cs index 3db62f47b3..7c46906358 100644 --- a/src/Microsoft.HttpRepl/Commands/SetHeaderCommand.cs +++ b/src/Microsoft.HttpRepl/Commands/SetHeaderCommand.cs @@ -4,11 +4,13 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.HttpRepl.Suggestions; using Microsoft.Repl; using Microsoft.Repl.Commanding; +using Microsoft.Repl.ConsoleHandling; using Microsoft.Repl.Parsing; namespace Microsoft.HttpRepl.Commands @@ -18,7 +20,7 @@ namespace Microsoft.HttpRepl.Commands private static readonly string Name = "set"; private static readonly string SubCommand = "header"; - public string Description => "set header {name} [{{value}}] - Sets or clears a header"; + public string Description => "set header {name} [value] - Sets or clears a header"; public bool? CanHandle(IShellState shellState, HttpState programState, ICoreParseResult parseResult) { @@ -43,12 +45,12 @@ namespace Microsoft.HttpRepl.Commands public string GetHelpDetails(IShellState shellState, HttpState programState, ICoreParseResult parseResult) { - if (parseResult.Sections.Count > 1 && string.Equals(parseResult.Sections[0], Name, StringComparison.OrdinalIgnoreCase) && string.Equals(parseResult.Sections[1], SubCommand, StringComparison.OrdinalIgnoreCase)) - { - return Description; - } - - return null; + var helpText = new StringBuilder(); + helpText.Append("Usage: ".Bold()); + helpText.AppendLine("set header {name} [value]"); + helpText.AppendLine(); + helpText.AppendLine("Sets or clears a header. When [value] is empty the header is cleared."); + return Description; } public string GetHelpSummary(IShellState shellState, HttpState programState) diff --git a/src/Microsoft.Repl/Commanding/CommandWithStructuredInputBase.cs b/src/Microsoft.Repl/Commanding/CommandWithStructuredInputBase.cs index a7e07b3cee..d087d4cea6 100644 --- a/src/Microsoft.Repl/Commanding/CommandWithStructuredInputBase.cs +++ b/src/Microsoft.Repl/Commanding/CommandWithStructuredInputBase.cs @@ -208,6 +208,6 @@ namespace Microsoft.Repl.Commanding protected abstract Task ExecuteAsync(IShellState shellState, TProgramState programState, DefaultCommandInput commandInput, TParseResult parseResult, CancellationToken cancellationToken); - protected abstract CommandInputSpecification InputSpec { get; } + public abstract CommandInputSpecification InputSpec { get; } } } From e975b87b91a16233e55426ca232d62df4267bd54 Mon Sep 17 00:00:00 2001 From: "ASP.NET CI" Date: Sun, 9 Sep 2018 12:10:33 -0700 Subject: [PATCH 49/66] Update dependencies.props [auto-updated: dependencies] --- build/dependencies.props | 18 +++++++++--------- korebuild-lock.txt | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index fbf093bdf8..4f54bb78c9 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -3,20 +3,20 @@ $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - 2.2.0-preview1-20180907.3 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 + 2.2.0-preview1-20180907.8 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 2.0.9 - 2.1.2 - 2.2.0-preview1-26618-02 + 2.1.3 + 2.2.0-preview2-26905-02 15.6.1 5.2.6 2.0.3 11.0.2 - 4.5.1 + 4.6.0-preview2-26905-02 4.5.0 9.0.1 2.3.1 diff --git a/korebuild-lock.txt b/korebuild-lock.txt index be535d1f04..552300b0ce 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.2.0-preview1-20180907.3 -commithash:2315030c7f3de2af0f16cf64502321937b6c4315 +version:2.2.0-preview1-20180907.8 +commithash:078918eb5c1f176ee1da351c584fb4a4d7491aa0 From 7517263bd4d089680757272dc665d4e94d51950b Mon Sep 17 00:00:00 2001 From: "ASP.NET CI" Date: Sun, 16 Sep 2018 12:09:26 -0700 Subject: [PATCH 50/66] Update dependencies.props [auto-updated: dependencies] --- build/dependencies.props | 12 ++++++------ korebuild-lock.txt | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index 4f54bb78c9..deb0947864 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -3,12 +3,12 @@ $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - 2.2.0-preview1-20180907.8 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 + 2.2.0-preview1-20180911.1 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 2.0.9 2.1.3 2.2.0-preview2-26905-02 diff --git a/korebuild-lock.txt b/korebuild-lock.txt index 552300b0ce..1090ad6a92 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.2.0-preview1-20180907.8 -commithash:078918eb5c1f176ee1da351c584fb4a4d7491aa0 +version:2.2.0-preview1-20180911.1 +commithash:ddfecdfc6e8e4859db5a0daea578070b862aac65 From 6c39b06a5988e0b17c685024b0ba26cefe6b3208 Mon Sep 17 00:00:00 2001 From: "ASP.NET CI" Date: Sun, 23 Sep 2018 19:11:11 +0000 Subject: [PATCH 51/66] Update dependencies.props [auto-updated: dependencies] --- build/dependencies.props | 12 ++++++------ korebuild-lock.txt | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index deb0947864..53c1c40ecb 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -3,12 +3,12 @@ $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - 2.2.0-preview1-20180911.1 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 + 2.2.0-preview1-20180918.1 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 2.0.9 2.1.3 2.2.0-preview2-26905-02 diff --git a/korebuild-lock.txt b/korebuild-lock.txt index 1090ad6a92..8491de70e6 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.2.0-preview1-20180911.1 -commithash:ddfecdfc6e8e4859db5a0daea578070b862aac65 +version:2.2.0-preview1-20180918.1 +commithash:ad5e3fc53442741a0dd49bce437d2ac72f4b5800 From c601038484606dab00000ed9bf7581a5659b2215 Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Wed, 26 Sep 2018 14:24:22 -0700 Subject: [PATCH 52/66] Remove duplicate PackageReferences --- test/Microsoft.Repl.Tests/Microsoft.Repl.Tests.csproj | 8 -------- 1 file changed, 8 deletions(-) diff --git a/test/Microsoft.Repl.Tests/Microsoft.Repl.Tests.csproj b/test/Microsoft.Repl.Tests/Microsoft.Repl.Tests.csproj index 52fbd0de16..31a83d818e 100644 --- a/test/Microsoft.Repl.Tests/Microsoft.Repl.Tests.csproj +++ b/test/Microsoft.Repl.Tests/Microsoft.Repl.Tests.csproj @@ -2,16 +2,8 @@ netcoreapp2.2 - - false - - - - - - From 0157e38a23bbe7ef827a34c221bc3cd268f242a7 Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Wed, 26 Sep 2018 15:05:40 -0700 Subject: [PATCH 53/66] Fix the path for SignedPackageFile and include the VSIX .dll in signing --- build/VSIX.targets | 1 + .../Microsoft.HttpRepl.csproj | 8 ++++---- .../dotnet-user-secrets.csproj | 20 +++++++++---------- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/build/VSIX.targets b/build/VSIX.targets index 687fd72b24..058c4dd0fd 100644 --- a/build/VSIX.targets +++ b/build/VSIX.targets @@ -41,6 +41,7 @@ + diff --git a/src/Microsoft.HttpRepl/Microsoft.HttpRepl.csproj b/src/Microsoft.HttpRepl/Microsoft.HttpRepl.csproj index 72f3e8fc1e..821e7398b5 100644 --- a/src/Microsoft.HttpRepl/Microsoft.HttpRepl.csproj +++ b/src/Microsoft.HttpRepl/Microsoft.HttpRepl.csproj @@ -21,10 +21,10 @@ - - - - + + + + diff --git a/src/dotnet-user-secrets/dotnet-user-secrets.csproj b/src/dotnet-user-secrets/dotnet-user-secrets.csproj index ae77d9bffd..5bcc7e7f57 100644 --- a/src/dotnet-user-secrets/dotnet-user-secrets.csproj +++ b/src/dotnet-user-secrets/dotnet-user-secrets.csproj @@ -24,18 +24,18 @@ - - - - - - - - - + + + + + + + + + - + From 4baa07ccae9665416044bee78cc98cd9bebef2b3 Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Thu, 27 Sep 2018 12:53:38 -0700 Subject: [PATCH 54/66] Update code signing properties and configuration --- Directory.Build.props | 4 ---- build/VSIX.targets | 2 +- .../Microsoft.HttpRepl.csproj | 2 +- src/dotnet-sql-cache/dotnet-sql-cache.csproj | 18 +++++++------- .../dotnet-user-secrets.csproj | 24 +++++++++---------- 5 files changed, 22 insertions(+), 28 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 9921cb358e..80d6cc1543 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -14,10 +14,6 @@ $(MSBuildThisFileDirectory) $(MSBuildThisFileDirectory)build\Key.snk true - MicrosoftNuGet - Microsoft - Microsoft3rdPartyAppComponentDual - true true diff --git a/build/VSIX.targets b/build/VSIX.targets index 058c4dd0fd..af61b649a4 100644 --- a/build/VSIX.targets +++ b/build/VSIX.targets @@ -41,7 +41,7 @@ - + diff --git a/src/Microsoft.HttpRepl/Microsoft.HttpRepl.csproj b/src/Microsoft.HttpRepl/Microsoft.HttpRepl.csproj index 821e7398b5..d2552ef1e4 100644 --- a/src/Microsoft.HttpRepl/Microsoft.HttpRepl.csproj +++ b/src/Microsoft.HttpRepl/Microsoft.HttpRepl.csproj @@ -24,7 +24,7 @@ - + diff --git a/src/dotnet-sql-cache/dotnet-sql-cache.csproj b/src/dotnet-sql-cache/dotnet-sql-cache.csproj index 3f9ce05717..59017c22ff 100644 --- a/src/dotnet-sql-cache/dotnet-sql-cache.csproj +++ b/src/dotnet-sql-cache/dotnet-sql-cache.csproj @@ -21,15 +21,15 @@ - - - - - - - - - + + + + + + + + + diff --git a/src/dotnet-user-secrets/dotnet-user-secrets.csproj b/src/dotnet-user-secrets/dotnet-user-secrets.csproj index 5bcc7e7f57..15ce1c1d5a 100644 --- a/src/dotnet-user-secrets/dotnet-user-secrets.csproj +++ b/src/dotnet-user-secrets/dotnet-user-secrets.csproj @@ -23,22 +23,20 @@ - - - - - - - - - - - - - + + + + + + + + + + + From b88c6fcd192130778ba654219b41e4e8cc0270e2 Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Fri, 28 Sep 2018 17:10:32 -0700 Subject: [PATCH 55/66] automated: bulk infrastructure updates. Update bootstrapper scripts and remove unnecessary signing properties --- run.ps1 | 6 +++--- run.sh | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/run.ps1 b/run.ps1 index 3b27382468..34604c7175 100644 --- a/run.ps1 +++ b/run.ps1 @@ -52,8 +52,8 @@ in the file are overridden by command line parameters. Example config file: ```json { - "$schema": "https://raw.githubusercontent.com/aspnet/BuildTools/dev/tools/korebuild.schema.json", - "channel": "dev", + "$schema": "https://raw.githubusercontent.com/aspnet/BuildTools/master/tools/korebuild.schema.json", + "channel": "master", "toolsSource": "https://aspnetcore.blob.core.windows.net/buildtools" } ``` @@ -192,7 +192,7 @@ if (!$DotNetHome) { else { Join-Path $PSScriptRoot '.dotnet'} } -if (!$Channel) { $Channel = 'dev' } +if (!$Channel) { $Channel = 'master' } if (!$ToolsSource) { $ToolsSource = 'https://aspnetcore.blob.core.windows.net/buildtools' } # Execute diff --git a/run.sh b/run.sh index 02aac15874..4c1fed5646 100755 --- a/run.sh +++ b/run.sh @@ -220,7 +220,7 @@ if [ -f "$config_file" ]; then config_channel="$(jq -r 'select(.channel!=null) | .channel' "$config_file")" config_tools_source="$(jq -r 'select(.toolsSource!=null) | .toolsSource' "$config_file")" else - _error "$config_file contains invalid JSON." + __error "$config_file contains invalid JSON." exit 1 fi elif __machine_has python ; then @@ -228,7 +228,7 @@ if [ -f "$config_file" ]; then config_channel="$(python -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'));print(obj['channel'] if 'channel' in obj else '')")" config_tools_source="$(python -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'));print(obj['toolsSource'] if 'toolsSource' in obj else '')")" else - _error "$config_file contains invalid JSON." + __error "$config_file contains invalid JSON." exit 1 fi elif __machine_has python3 ; then @@ -236,11 +236,11 @@ if [ -f "$config_file" ]; then config_channel="$(python3 -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'));print(obj['channel'] if 'channel' in obj else '')")" config_tools_source="$(python3 -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'));print(obj['toolsSource'] if 'toolsSource' in obj else '')")" else - _error "$config_file contains invalid JSON." + __error "$config_file contains invalid JSON." exit 1 fi else - _error 'Missing required command: jq or python. Could not parse the JSON file.' + __error 'Missing required command: jq or python. Could not parse the JSON file.' exit 1 fi @@ -248,7 +248,7 @@ if [ -f "$config_file" ]; then [ ! -z "${config_tools_source:-}" ] && tools_source="$config_tools_source" fi -[ -z "$channel" ] && channel='dev' +[ -z "$channel" ] && channel='master' [ -z "$tools_source" ] && tools_source='https://aspnetcore.blob.core.windows.net/buildtools' get_korebuild From 37bdb72c5d35a5712c46e8f4af2cf5bf3efae9bb Mon Sep 17 00:00:00 2001 From: "ASP.NET CI" Date: Sun, 30 Sep 2018 12:11:26 -0700 Subject: [PATCH 56/66] Update dependencies.props [auto-updated: dependencies] --- build/dependencies.props | 16 ++++++++-------- korebuild-lock.txt | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index 53c1c40ecb..1b90e37de1 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -3,20 +3,20 @@ $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - 2.2.0-preview1-20180918.1 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 + 2.2.0-preview1-20180928.5 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 2.0.9 2.1.3 - 2.2.0-preview2-26905-02 + 2.2.0-preview3-26927-02 15.6.1 5.2.6 2.0.3 11.0.2 - 4.6.0-preview2-26905-02 + 4.6.0-preview3-26927-02 4.5.0 9.0.1 2.3.1 diff --git a/korebuild-lock.txt b/korebuild-lock.txt index 8491de70e6..0507680073 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.2.0-preview1-20180918.1 -commithash:ad5e3fc53442741a0dd49bce437d2ac72f4b5800 +version:2.2.0-preview1-20180928.5 +commithash:43faa29f679f47b88689d645b39e6be5e0055d70 From 4c49d5ce80f4515ac0549016822db04bbd647bb2 Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Tue, 2 Oct 2018 09:17:04 -0700 Subject: [PATCH 57/66] Resolve flaky tests (#497) 1. Prevent an ObjectDisposedException in dotnet-watch on slower machines 2. Fix flakiness caused by PID reuse 3. Fix flakiness in tests that await the restart of dotnet-watch. The `.TimeoutAfter` method doesn't cancel the long-running task. This left 2 readers running on dotnet-watch output which caused indeterminate test outcome. --- .../Internal/FileWatcher/DotnetFileWatcher.cs | 43 ++++++++++++++----- .../AwaitableProcess.cs | 27 +++++++----- .../DotNetWatcherTests.cs | 6 ++- .../GlobbingAppTests.cs | 5 ++- .../NoDepsAppTests.cs | 3 -- 5 files changed, 58 insertions(+), 26 deletions(-) diff --git a/src/dotnet-watch/Internal/FileWatcher/DotnetFileWatcher.cs b/src/dotnet-watch/Internal/FileWatcher/DotnetFileWatcher.cs index d1103c41e6..0372327819 100644 --- a/src/dotnet-watch/Internal/FileWatcher/DotnetFileWatcher.cs +++ b/src/dotnet-watch/Internal/FileWatcher/DotnetFileWatcher.cs @@ -10,6 +10,8 @@ namespace Microsoft.DotNet.Watcher.Internal { internal class DotnetFileWatcher : IFileSystemWatcher { + private volatile bool _disposed; + private readonly Func _watcherFactory; private FileSystemWatcher _fileSystemWatcher; @@ -46,6 +48,11 @@ namespace Microsoft.DotNet.Watcher.Internal private void WatcherErrorHandler(object sender, ErrorEventArgs e) { + if (_disposed) + { + return; + } + var exception = e.GetException(); // Win32Exception may be triggered when setting EnableRaisingEvents on a file system type @@ -62,6 +69,11 @@ namespace Microsoft.DotNet.Watcher.Internal private void WatcherRenameHandler(object sender, RenamedEventArgs e) { + if (_disposed) + { + return; + } + NotifyChange(e.OldFullPath); NotifyChange(e.FullPath); @@ -79,6 +91,11 @@ namespace Microsoft.DotNet.Watcher.Internal private void WatcherChangeHandler(object sender, FileSystemEventArgs e) { + if (_disposed) + { + return; + } + NotifyChange(e.FullPath); } @@ -98,15 +115,7 @@ namespace Microsoft.DotNet.Watcher.Internal { enableEvents = _fileSystemWatcher.EnableRaisingEvents; - _fileSystemWatcher.EnableRaisingEvents = false; - - _fileSystemWatcher.Created -= WatcherChangeHandler; - _fileSystemWatcher.Deleted -= WatcherChangeHandler; - _fileSystemWatcher.Changed -= WatcherChangeHandler; - _fileSystemWatcher.Renamed -= WatcherRenameHandler; - _fileSystemWatcher.Error -= WatcherErrorHandler; - - _fileSystemWatcher.Dispose(); + DisposeInnerWatcher(); } _fileSystemWatcher = _watcherFactory(BasePath); @@ -122,6 +131,19 @@ namespace Microsoft.DotNet.Watcher.Internal } } + private void DisposeInnerWatcher() + { + _fileSystemWatcher.EnableRaisingEvents = false; + + _fileSystemWatcher.Created -= WatcherChangeHandler; + _fileSystemWatcher.Deleted -= WatcherChangeHandler; + _fileSystemWatcher.Changed -= WatcherChangeHandler; + _fileSystemWatcher.Renamed -= WatcherRenameHandler; + _fileSystemWatcher.Error -= WatcherErrorHandler; + + _fileSystemWatcher.Dispose(); + } + public bool EnableRaisingEvents { get => _fileSystemWatcher.EnableRaisingEvents; @@ -130,7 +152,8 @@ namespace Microsoft.DotNet.Watcher.Internal public void Dispose() { - _fileSystemWatcher.Dispose(); + _disposed = true; + DisposeInnerWatcher(); } } } diff --git a/test/dotnet-watch.FunctionalTests/AwaitableProcess.cs b/test/dotnet-watch.FunctionalTests/AwaitableProcess.cs index 86e2f90f79..3e22d53245 100644 --- a/test/dotnet-watch.FunctionalTests/AwaitableProcess.cs +++ b/test/dotnet-watch.FunctionalTests/AwaitableProcess.cs @@ -4,9 +4,9 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Threading; using System.Threading.Tasks; using System.Threading.Tasks.Dataflow; -using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Internal; using Microsoft.Extensions.CommandLineUtils; using Xunit.Abstractions; @@ -80,25 +80,30 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests public async Task GetOutputLineAsync(string message, TimeSpan timeout) { _logger.WriteLine($"Waiting for output line [msg == '{message}']. Will wait for {timeout.TotalSeconds} sec."); - return await GetOutputLineAsync(m => string.Equals(m, message, StringComparison.Ordinal)).TimeoutAfter(timeout); + var cts = new CancellationTokenSource(); + cts.CancelAfter(timeout); + return await GetOutputLineAsync($"[msg == '{message}']", m => string.Equals(m, message, StringComparison.Ordinal), cts.Token); } public async Task GetOutputLineStartsWithAsync(string message, TimeSpan timeout) { _logger.WriteLine($"Waiting for output line [msg.StartsWith('{message}')]. Will wait for {timeout.TotalSeconds} sec."); - return await GetOutputLineAsync(m => m != null && m.StartsWith(message, StringComparison.Ordinal)).TimeoutAfter(timeout); + var cts = new CancellationTokenSource(); + cts.CancelAfter(timeout); + return await GetOutputLineAsync($"[msg.StartsWith('{message}')]", m => m != null && m.StartsWith(message, StringComparison.Ordinal), cts.Token); } - private async Task GetOutputLineAsync(Predicate predicate) + private async Task GetOutputLineAsync(string predicateName, Predicate predicate, CancellationToken cancellationToken) { while (!_source.Completion.IsCompleted) { - while (await _source.OutputAvailableAsync()) + while (await _source.OutputAvailableAsync(cancellationToken)) { - var next = await _source.ReceiveAsync(); + var next = await _source.ReceiveAsync(cancellationToken); _lines.Add(next); - _logger.WriteLine($"{DateTime.Now}: recv: '{next}'"); - if (predicate(next)) + var match = predicate(next); + _logger.WriteLine($"{DateTime.Now}: recv: '{next}'. {(match ? "Matches" : "Does not match")} condition '{predicateName}'."); + if (match) { return next; } @@ -108,14 +113,14 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests return null; } - public async Task> GetAllOutputLines() + public async Task> GetAllOutputLinesAsync(CancellationToken cancellationToken) { var lines = new List(); while (!_source.Completion.IsCompleted) { - while (await _source.OutputAvailableAsync()) + while (await _source.OutputAvailableAsync(cancellationToken)) { - var next = await _source.ReceiveAsync(); + var next = await _source.ReceiveAsync(cancellationToken); _logger.WriteLine($"{DateTime.Now}: recv: '{next}'"); lines.Add(next); } diff --git a/test/dotnet-watch.FunctionalTests/DotNetWatcherTests.cs b/test/dotnet-watch.FunctionalTests/DotNetWatcherTests.cs index 65ff6416e2..d31f650aa6 100644 --- a/test/dotnet-watch.FunctionalTests/DotNetWatcherTests.cs +++ b/test/dotnet-watch.FunctionalTests/DotNetWatcherTests.cs @@ -13,10 +13,12 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests { public class DotNetWatcherTests : IDisposable { + private readonly ITestOutputHelper _logger; private readonly KitchenSinkApp _app; public DotNetWatcherTests(ITestOutputHelper logger) { + _logger = logger; _app = new KitchenSinkApp(logger); } @@ -52,8 +54,10 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests File.SetLastWriteTime(source, DateTime.Now); await _app.HasRestarted(); } - catch + catch (Exception ex) { + _logger.WriteLine("Retrying. First attempt to restart app failed: " + ex.Message); + // retry File.SetLastWriteTime(source, DateTime.Now); await _app.HasRestarted(); diff --git a/test/dotnet-watch.FunctionalTests/GlobbingAppTests.cs b/test/dotnet-watch.FunctionalTests/GlobbingAppTests.cs index a01a28e7d8..3658261049 100644 --- a/test/dotnet-watch.FunctionalTests/GlobbingAppTests.cs +++ b/test/dotnet-watch.FunctionalTests/GlobbingAppTests.cs @@ -4,6 +4,7 @@ using System; using System.IO; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.Watcher.Tools.Tests; using Xunit; @@ -101,7 +102,9 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests { await _app.PrepareAsync(); _app.Start(new [] { "--list" }); - var lines = await _app.Process.GetAllOutputLines(); + var cts = new CancellationTokenSource(); + cts.CancelAfter(TimeSpan.FromSeconds(30)); + var lines = await _app.Process.GetAllOutputLinesAsync(cts.Token); var files = lines.Where(l => !l.StartsWith("watch :")); AssertEx.EqualFileList( diff --git a/test/dotnet-watch.FunctionalTests/NoDepsAppTests.cs b/test/dotnet-watch.FunctionalTests/NoDepsAppTests.cs index 55d0441552..d1eead9048 100644 --- a/test/dotnet-watch.FunctionalTests/NoDepsAppTests.cs +++ b/test/dotnet-watch.FunctionalTests/NoDepsAppTests.cs @@ -39,9 +39,6 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests var pid2 = await _app.GetProcessId(); Assert.NotEqual(pid, pid2); - - // first app should have shut down - Assert.Throws(() => Process.GetProcessById(pid)); } [Fact] From b473a0d76ce0c255962d97e22d45904c6b62fe5d Mon Sep 17 00:00:00 2001 From: "ASP.NET CI" Date: Sun, 7 Oct 2018 19:11:55 +0000 Subject: [PATCH 58/66] Update dependencies.props [auto-updated: dependencies] --- build/dependencies.props | 16 ++++++++-------- korebuild-lock.txt | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index 1b90e37de1..825e82f2e3 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -3,20 +3,20 @@ $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - 2.2.0-preview1-20180928.5 - 2.2.0-preview3-35359 - 2.2.0-preview3-35359 - 2.2.0-preview3-35359 - 2.2.0-preview3-35359 - 2.2.0-preview3-35359 + 2.2.0-preview2-20181004.6 + 2.2.0-preview3-35425 + 2.2.0-preview3-35425 + 2.2.0-preview3-35425 + 2.2.0-preview3-35425 + 2.2.0-preview3-35425 2.0.9 2.1.3 - 2.2.0-preview3-26927-02 + 2.2.0-preview3-27001-02 15.6.1 5.2.6 2.0.3 11.0.2 - 4.6.0-preview3-26927-02 + 4.6.0-preview3-27001-02 4.5.0 9.0.1 2.3.1 diff --git a/korebuild-lock.txt b/korebuild-lock.txt index 0507680073..3e92dd5543 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.2.0-preview1-20180928.5 -commithash:43faa29f679f47b88689d645b39e6be5e0055d70 +version:2.2.0-preview2-20181004.6 +commithash:c04c4b2f5018632647f96210ab01876661302dac From 98f7829b5c5f5738f15f3863205547e35901bbe1 Mon Sep 17 00:00:00 2001 From: "ASP.NET CI" Date: Tue, 16 Oct 2018 12:48:13 -0700 Subject: [PATCH 59/66] Update package branding for 2.2 RTM --- version.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.props b/version.props index 07833713f2..0d0c8979e6 100644 --- a/version.props +++ b/version.props @@ -2,7 +2,7 @@ 2.2.0 15.6 - preview3 + rtm $(VersionPrefix) $(VersionPrefix)-$(VersionSuffix)-final $(VsixVersion).$(BuildNumber) From e75dc563279c3ec622e4dd9dc3ad22fabbc111e7 Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Tue, 16 Oct 2018 14:29:10 -0700 Subject: [PATCH 60/66] Sign Newtonsoft.Json with AssemblySigning3rdPartyCertName --- src/dotnet-user-secrets/dotnet-user-secrets.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dotnet-user-secrets/dotnet-user-secrets.csproj b/src/dotnet-user-secrets/dotnet-user-secrets.csproj index 15ce1c1d5a..133f303006 100644 --- a/src/dotnet-user-secrets/dotnet-user-secrets.csproj +++ b/src/dotnet-user-secrets/dotnet-user-secrets.csproj @@ -24,7 +24,7 @@ - + From 572aadf149fad6f19691e922926c11bdb2a74fb1 Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Wed, 17 Oct 2018 13:15:14 -0700 Subject: [PATCH 61/66] Remove duplicate package references (#501) --- test/Microsoft.HttpRepl.Tests/Microsoft.HttpRepl.Tests.csproj | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/Microsoft.HttpRepl.Tests/Microsoft.HttpRepl.Tests.csproj b/test/Microsoft.HttpRepl.Tests/Microsoft.HttpRepl.Tests.csproj index a50dd1df30..981a7e8bdd 100644 --- a/test/Microsoft.HttpRepl.Tests/Microsoft.HttpRepl.Tests.csproj +++ b/test/Microsoft.HttpRepl.Tests/Microsoft.HttpRepl.Tests.csproj @@ -7,9 +7,6 @@ - - - From 01f9af21c533df2b3961b7900ca0b656ee38a588 Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Wed, 24 Oct 2018 13:02:25 -0700 Subject: [PATCH 62/66] Temporarily stop producing a package for dotnet-httprepl (#505) This package isn't quite ship-shape yet, so we're delaying this from shipping with 2.2 RTM. Setting IsPackable=false so we avoid accidentally building a 2.2.0 RTM version of this package along with the rest of the 2.2.0 RTM tools in this repo, like dotnet-watch. --- NuGetPackageVerifier.json | 14 -------------- src/Microsoft.HttpRepl/Microsoft.HttpRepl.csproj | 3 +++ 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/NuGetPackageVerifier.json b/NuGetPackageVerifier.json index 63b866c8ed..fb43448889 100644 --- a/NuGetPackageVerifier.json +++ b/NuGetPackageVerifier.json @@ -24,20 +24,6 @@ "DotnetTool" ] }, - "dotnet-httprepl": { - "packageTypes": [ - "DotnetTool" - ], - "Exclusions": { - "ASSEMBLY_DESCRIPTION": { - "tools/netcoreapp2.1/any/System.Net.Http.Formatting.dll": "Referenced assembly, not built as part of this process" - }, - "VERSION_INFORMATIONALVERSION": { - "tools/netcoreapp2.1/any/Newtonsoft.Json.dll": "Referenced assembly, not built as part of this process", - "tools/netcoreapp2.1/any/Newtonsoft.Json.Bson.dll": "Referenced assembly, not built as part of this process" - } - } - }, "Microsoft.AspNetCore.DeveloperCertificates.XPlat": { "Exclusions": { "DOC_MISSING": { diff --git a/src/Microsoft.HttpRepl/Microsoft.HttpRepl.csproj b/src/Microsoft.HttpRepl/Microsoft.HttpRepl.csproj index d2552ef1e4..d367892325 100644 --- a/src/Microsoft.HttpRepl/Microsoft.HttpRepl.csproj +++ b/src/Microsoft.HttpRepl/Microsoft.HttpRepl.csproj @@ -10,6 +10,9 @@ dotnet;http;httprepl win-x64;win-x86 + + + false From c6130df988524145848dd347a9006929c1374c8b Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Sat, 27 Oct 2018 01:47:18 -0700 Subject: [PATCH 63/66] Upgrade dependencies and build tools --- build/dependencies.props | 16 ++++++++-------- korebuild-lock.txt | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index add83e237b..3be7610a3d 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -3,19 +3,19 @@ $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - 2.2.0-preview2-20181004.6 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 + 2.2.0-preview2-20181026.4 + 2.2.0-rtm-35542 + 2.2.0-rtm-35542 + 2.2.0-rtm-35542 + 2.2.0-rtm-35545 + 2.2.0-rtm-35542 2.1.3 - 2.2.0-preview3-27001-02 + 2.2.0-rtm-27023-02 15.9.0 5.2.6 2.0.3 11.0.2 - 4.6.0-preview3-27001-02 + 4.6.0-rtm-27023-03 4.5.0 9.0.1 2.4.0 diff --git a/korebuild-lock.txt b/korebuild-lock.txt index 3e92dd5543..a60356d3e0 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.2.0-preview2-20181004.6 -commithash:c04c4b2f5018632647f96210ab01876661302dac +version:2.2.0-preview2-20181026.4 +commithash:f05a283e6c1eb66ef29a32526f75f8b567a986c9 From fbca8be4042533db160704324e4f095efe77583d Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Thu, 1 Nov 2018 10:01:42 -0700 Subject: [PATCH 64/66] Disable building the VSIX in 2.2 by default (#508) We don't release the UserSecrets VSIX from this branch --- build/VSIX.targets | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build/VSIX.targets b/build/VSIX.targets index ccafe5bf76..a761a9bc13 100644 --- a/build/VSIX.targets +++ b/build/VSIX.targets @@ -1,6 +1,7 @@ - true + + false $(RestoreDependsOn);RestoreVSIX $(PackageDependsOn);PackageVSIX $(GetArtifactInfoDependsOn);GetVSIXArtifactInfo From 22a32500ac53283bb0db681ec1f22a95091eb717 Mon Sep 17 00:00:00 2001 From: Mike Lorbetske Date: Wed, 31 Oct 2018 12:40:16 -0700 Subject: [PATCH 65/66] Fix several issues Fix #502 Fix #492 Fix #491 Fix #486 Improves #489 --- .../Commands/BaseHttpCommand.cs | 30 +++++++------ .../Commands/HelpCommand.cs | 2 +- .../Commands/SetBaseCommand.cs | 17 +++++++- .../Commands/SetSwaggerCommand.cs | 2 +- src/Microsoft.HttpRepl/DirectoryStructure.cs | 11 ++++- src/Microsoft.HttpRepl/HttpState.cs | 4 +- src/Microsoft.HttpRepl/IRequestInfo.cs | 2 +- .../Commanding/DefaultCommandDispatcher.cs | 2 +- .../Commanding/ICommandDispatcher.cs | 2 +- .../ConsoleHandling/ConsoleManager.cs | 42 +++++++++++-------- src/Microsoft.Repl/Input/InputManager.cs | 26 ++++++++---- .../Suggestions/SuggestionManager.cs | 4 +- 12 files changed, 93 insertions(+), 51 deletions(-) diff --git a/src/Microsoft.HttpRepl/Commands/BaseHttpCommand.cs b/src/Microsoft.HttpRepl/Commands/BaseHttpCommand.cs index a8ad52fc24..30191306e4 100644 --- a/src/Microsoft.HttpRepl/Commands/BaseHttpCommand.cs +++ b/src/Microsoft.HttpRepl/Commands/BaseHttpCommand.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using System.IO; using System.Linq; using System.Net.Http; +using System.Net.Http.Headers; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -116,8 +117,18 @@ namespace Microsoft.HttpRepl.Commands bool deleteFile = false; noBody = commandInput.Options[NoBodyOption].Count > 0; + if (!thisRequestHeaders.TryGetValue("content-type", out string contentType) && programState.Headers.TryGetValue("content-type", out IEnumerable contentTypes)) + { + contentType = contentTypes.FirstOrDefault(); + } + if (!noBody) { + if (string.IsNullOrEmpty(contentType)) + { + contentType = "application/json"; + } + if (commandInput.Options[BodyFileOption].Count > 0) { filePath = commandInput.Options[BodyFileOption][0].Text; @@ -144,18 +155,7 @@ namespace Microsoft.HttpRepl.Commands deleteFile = true; filePath = Path.GetTempFileName(); - if (!thisRequestHeaders.TryGetValue("content-type", out string contentType) && programState.Headers.TryGetValue("content-type", out IEnumerable contentTypes)) - { - contentType = contentTypes.FirstOrDefault(); - } - - if (contentType == null) - { - contentType = "application/json"; - } - - string exampleBody = programState.GetExampleBody(commandInput.Arguments.Count > 0 ? commandInput.Arguments[0].Text : string.Empty, contentType, Verb); - request.Headers.TryAddWithoutValidation("Content-Type", contentType); + string exampleBody = programState.GetExampleBody(commandInput.Arguments.Count > 0 ? commandInput.Arguments[0].Text : string.Empty, ref contentType, Verb); if (!string.IsNullOrEmpty(exampleBody)) { @@ -179,6 +179,11 @@ namespace Microsoft.HttpRepl.Commands } } + if (string.IsNullOrEmpty(contentType)) + { + contentType = "application/json"; + } + byte[] data = noBody ? new byte[0] : string.IsNullOrEmpty(bodyContent) @@ -186,6 +191,7 @@ namespace Microsoft.HttpRepl.Commands : Encoding.UTF8.GetBytes(bodyContent); HttpContent content = new ByteArrayContent(data); + content.Headers.ContentType = new MediaTypeHeaderValue(contentType); request.Content = content; if (deleteFile) diff --git a/src/Microsoft.HttpRepl/Commands/HelpCommand.cs b/src/Microsoft.HttpRepl/Commands/HelpCommand.cs index 26e4468e8a..17dbef4d1f 100644 --- a/src/Microsoft.HttpRepl/Commands/HelpCommand.cs +++ b/src/Microsoft.HttpRepl/Commands/HelpCommand.cs @@ -218,7 +218,7 @@ namespace Microsoft.HttpRepl.Commands shellState.ConsoleManager.WriteLine($"{"HEAD",navCommandColumn}{"Issues a HEAD request."}"); shellState.ConsoleManager.WriteLine($"{"OPTIONS",navCommandColumn}{"Issues an OPTIONS request."}"); shellState.ConsoleManager.WriteLine(); - shellState.ConsoleManager.WriteLine($"{"set header",navCommandColumn}{"Sets or clears a header for all requests. e.g. `set header content-type:application/json`"}"); + shellState.ConsoleManager.WriteLine($"{"set header",navCommandColumn}{"Sets or clears a header for all requests. e.g. `set header content-type application/json`"}"); shellState.ConsoleManager.WriteLine(); shellState.ConsoleManager.WriteLine(); diff --git a/src/Microsoft.HttpRepl/Commands/SetBaseCommand.cs b/src/Microsoft.HttpRepl/Commands/SetBaseCommand.cs index aa12ec819e..70e829a891 100644 --- a/src/Microsoft.HttpRepl/Commands/SetBaseCommand.cs +++ b/src/Microsoft.HttpRepl/Commands/SetBaseCommand.cs @@ -53,7 +53,7 @@ namespace Microsoft.HttpRepl.Commands catch { } } - if (state.BaseAddress == null || !Uri.TryCreate(state.BaseAddress, "/swagger/v1/swagger.json", out Uri result)) + if (state.BaseAddress == null || !Uri.TryCreate(state.BaseAddress, "swagger.json", out Uri result)) { state.SwaggerStructure = null; } @@ -64,6 +64,21 @@ namespace Microsoft.HttpRepl.Commands { shellState.ConsoleManager.WriteLine("Using swagger metadata from " + result); } + else + { + if (state.BaseAddress == null || !Uri.TryCreate(state.BaseAddress, "swagger/v1/swagger.json", out result)) + { + state.SwaggerStructure = null; + } + else + { + await SetSwaggerCommand.CreateDirectoryStructureForSwaggerEndpointAsync(shellState, state, result, cancellationToken).ConfigureAwait(false); + if (state.SwaggerStructure != null) + { + shellState.ConsoleManager.WriteLine("Using swagger metadata from " + result); + } + } + } } } diff --git a/src/Microsoft.HttpRepl/Commands/SetSwaggerCommand.cs b/src/Microsoft.HttpRepl/Commands/SetSwaggerCommand.cs index 41c1564a99..53d3743d6d 100644 --- a/src/Microsoft.HttpRepl/Commands/SetSwaggerCommand.cs +++ b/src/Microsoft.HttpRepl/Commands/SetSwaggerCommand.cs @@ -47,7 +47,7 @@ namespace Microsoft.HttpRepl.Commands { if (string.IsNullOrEmpty(parameterSetsByContentType.Key)) { - dirRequestInfo.SetFallbackRequestBody(method, GetBodyString(null, parameterSetsByContentType.Value)); + dirRequestInfo.SetFallbackRequestBody(method, parameterSetsByContentType.Key, GetBodyString(null, parameterSetsByContentType.Value)); } dirRequestInfo.SetRequestBody(method, parameterSetsByContentType.Key, GetBodyString(parameterSetsByContentType.Key, parameterSetsByContentType.Value)); diff --git a/src/Microsoft.HttpRepl/DirectoryStructure.cs b/src/Microsoft.HttpRepl/DirectoryStructure.cs index 8787f962fc..7587327de0 100644 --- a/src/Microsoft.HttpRepl/DirectoryStructure.cs +++ b/src/Microsoft.HttpRepl/DirectoryStructure.cs @@ -55,13 +55,14 @@ namespace Microsoft.HttpRepl private readonly HashSet _methods = new HashSet(StringComparer.OrdinalIgnoreCase); private readonly Dictionary> _requestBodiesByMethodByContentType = new Dictionary>(StringComparer.OrdinalIgnoreCase); private readonly Dictionary _fallbackBodyStringsByMethod = new Dictionary(StringComparer.OrdinalIgnoreCase); + private readonly Dictionary _fallbackContentTypeStringsByMethod = new Dictionary(StringComparer.OrdinalIgnoreCase); private readonly Dictionary> _contentTypesByMethod = new Dictionary>(StringComparer.OrdinalIgnoreCase); public IReadOnlyList Methods => _methods.ToList(); public IReadOnlyDictionary> ContentTypesByMethod => _contentTypesByMethod; - public string GetRequestBodyForContentType(string contentType, string method) + public string GetRequestBodyForContentType(ref string contentType, string method) { if (_requestBodiesByMethodByContentType.TryGetValue(method, out Dictionary bodiesByContentType) && bodiesByContentType.TryGetValue(contentType, out string body)) @@ -71,6 +72,11 @@ namespace Microsoft.HttpRepl if (_fallbackBodyStringsByMethod.TryGetValue(method, out body)) { + if (_fallbackContentTypeStringsByMethod.TryGetValue(method, out string newContentType)) + { + contentType = newContentType; + } + return body; } @@ -100,9 +106,10 @@ namespace Microsoft.HttpRepl _methods.Add(method); } - public void SetFallbackRequestBody(string method, string fallbackBodyString) + public void SetFallbackRequestBody(string method, string contentType, string fallbackBodyString) { _fallbackBodyStringsByMethod[method] = fallbackBodyString; + _fallbackContentTypeStringsByMethod[method] = contentType; } } } diff --git a/src/Microsoft.HttpRepl/HttpState.cs b/src/Microsoft.HttpRepl/HttpState.cs index 9104e4e02c..4ac8e09ed4 100644 --- a/src/Microsoft.HttpRepl/HttpState.cs +++ b/src/Microsoft.HttpRepl/HttpState.cs @@ -148,12 +148,12 @@ namespace Microsoft.HttpRepl } } - public string GetExampleBody(string path, string contentType, string method) + public string GetExampleBody(string path, ref string contentType, string method) { Uri effectivePath = GetEffectivePath(path); string rootRelativePath = effectivePath.LocalPath.Substring(BaseAddress.LocalPath.Length).TrimStart('/'); IDirectoryStructure structure = SwaggerStructure?.TraverseTo(rootRelativePath); - return structure?.RequestInfo?.GetRequestBodyForContentType(contentType, method); + return structure?.RequestInfo?.GetRequestBodyForContentType(ref contentType, method); } public IEnumerable GetApplicableContentTypes(string method, string path) diff --git a/src/Microsoft.HttpRepl/IRequestInfo.cs b/src/Microsoft.HttpRepl/IRequestInfo.cs index 24054f6142..d420d36d2a 100644 --- a/src/Microsoft.HttpRepl/IRequestInfo.cs +++ b/src/Microsoft.HttpRepl/IRequestInfo.cs @@ -11,6 +11,6 @@ namespace Microsoft.HttpRepl IReadOnlyList Methods { get; } - string GetRequestBodyForContentType(string contentType, string method); + string GetRequestBodyForContentType(ref string contentType, string method); } } diff --git a/src/Microsoft.Repl/Commanding/DefaultCommandDispatcher.cs b/src/Microsoft.Repl/Commanding/DefaultCommandDispatcher.cs index 22be314827..bdd9426868 100644 --- a/src/Microsoft.Repl/Commanding/DefaultCommandDispatcher.cs +++ b/src/Microsoft.Repl/Commanding/DefaultCommandDispatcher.cs @@ -79,7 +79,7 @@ namespace Microsoft.Repl.Commanding public IParser Parser => _parser; - public IReadOnlyList CollectSuggesetions(IShellState shellState) + public IReadOnlyList CollectSuggestions(IShellState shellState) { string line = shellState.InputManager.GetCurrentBuffer(); TParseResult parseResult = _parser.Parse(line, shellState.ConsoleManager.CaretPosition); diff --git a/src/Microsoft.Repl/Commanding/ICommandDispatcher.cs b/src/Microsoft.Repl/Commanding/ICommandDispatcher.cs index d2d9000123..b02dd830a7 100644 --- a/src/Microsoft.Repl/Commanding/ICommandDispatcher.cs +++ b/src/Microsoft.Repl/Commanding/ICommandDispatcher.cs @@ -12,7 +12,7 @@ namespace Microsoft.Repl.Commanding { IParser Parser { get; } - IReadOnlyList CollectSuggesetions(IShellState shellState); + IReadOnlyList CollectSuggestions(IShellState shellState); void OnReady(IShellState shellState); diff --git a/src/Microsoft.Repl/ConsoleHandling/ConsoleManager.cs b/src/Microsoft.Repl/ConsoleHandling/ConsoleManager.cs index ad4231ad5e..b6d471ad52 100644 --- a/src/Microsoft.Repl/ConsoleHandling/ConsoleManager.cs +++ b/src/Microsoft.Repl/ConsoleHandling/ConsoleManager.cs @@ -50,37 +50,41 @@ namespace Microsoft.Repl.ConsoleHandling return; } + int bufferWidth = Console.BufferWidth; + int cursorTop = Console.CursorTop; + int cursorLeft = Console.CursorLeft; + while (positions < 0 && CaretPosition > 0) { - if (-positions > Console.BufferWidth) + if (-positions > bufferWidth) { - if (Console.CursorTop == 0) + if (cursorTop == 0) { - Console.CursorLeft = 0; + cursorLeft = 0; positions = 0; } else { - positions += Console.BufferWidth; - --Console.CursorTop; + positions += bufferWidth; + --cursorTop; } } else { - int remaining = Console.CursorLeft + positions; + int remaining = cursorLeft + positions; if (remaining >= 0) { - Console.CursorLeft = remaining; + cursorLeft = remaining; } - else if (Console.CursorTop == 0) + else if (cursorTop == 0) { - Console.CursorLeft = 0; + cursorLeft = 0; } else { - --Console.CursorTop; - Console.CursorLeft = Console.BufferWidth + remaining; + --cursorTop; + cursorLeft = bufferWidth + remaining; } positions = 0; @@ -89,27 +93,29 @@ namespace Microsoft.Repl.ConsoleHandling while (positions > 0) { - if (positions > Console.BufferWidth) + if (positions > bufferWidth) { - positions -= Console.BufferWidth; - ++Console.CursorTop; + positions -= bufferWidth; + ++cursorTop; } else { - int spaceLeftOnLine = Console.BufferWidth - Console.CursorLeft - 1; + int spaceLeftOnLine = bufferWidth - cursorLeft - 1; if (positions > spaceLeftOnLine) { - ++Console.CursorTop; - Console.CursorLeft = positions - spaceLeftOnLine - 1; + ++cursorTop; + cursorLeft = positions - spaceLeftOnLine - 1; } else { - Console.CursorLeft += positions; + cursorLeft += positions; } positions = 0; } } + + Console.SetCursorPosition(cursorLeft, cursorTop); } } diff --git a/src/Microsoft.Repl/Input/InputManager.cs b/src/Microsoft.Repl/Input/InputManager.cs index cd1157c6ff..b35633ad4e 100644 --- a/src/Microsoft.Repl/Input/InputManager.cs +++ b/src/Microsoft.Repl/Input/InputManager.cs @@ -112,14 +112,22 @@ namespace Microsoft.Repl.Input private void StashEchoState() { - _ttyState = System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.OSX) - ? GetTtyState() - : null; - - if (!string.IsNullOrEmpty(_ttyState)) + string sttyFlags = null; + if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.OSX)) { - //"gfmt1:cflag=4300:iflag=6b02:lflag=200005c7:oflag=3:discard=f:dsusp=19:eof=4:eol=ff:eol2=ff:erase=7f:intr=3:kill=15:lnext=16:min=1:quit=1c:reprint=12:start=11:status=14:stop=13:susp=1a:time=0:werase=17:ispeed=38400:ospeed=38400\n" - ProcessStartInfo psi = new ProcessStartInfo("stty", "gfmt1:erase=08:werase=08 -echo"); + _ttyState = GetTtyState(); + sttyFlags = "gfmt1:erase=08:werase=08 -echo"; + } + //If it's any of the ubuntu variants on 18.x, stty tweaks are required + else if (System.Runtime.InteropServices.RuntimeInformation.OSDescription.IndexOf("buntu", StringComparison.OrdinalIgnoreCase) > -1) + { + _ttyState = GetTtyState(); + sttyFlags = "erase 0x08 werase 0x08 -echo"; + } + + if (!string.IsNullOrEmpty(sttyFlags)) + { + ProcessStartInfo psi = new ProcessStartInfo("stty", sttyFlags); Process p = Process.Start(psi); p?.WaitForExit(); } @@ -133,7 +141,7 @@ namespace Microsoft.Repl.Input }; Process p = Process.Start(psi); p?.WaitForExit(); - string result = p?.StandardOutput.ReadToEnd(); + string result = p?.StandardOutput.ReadToEnd().Trim(); return result; } @@ -331,7 +339,7 @@ namespace Microsoft.Repl.Input } private void FlushInput(IShellState state, ref List presses) - { + { string str = new string(presses.Select(x => x.KeyChar).ToArray()); if (state.ConsoleManager.CaretPosition == _inputBuffer.Count) diff --git a/src/Microsoft.Repl/Suggestions/SuggestionManager.cs b/src/Microsoft.Repl/Suggestions/SuggestionManager.cs index ba68ee9d46..5080e6ae6f 100644 --- a/src/Microsoft.Repl/Suggestions/SuggestionManager.cs +++ b/src/Microsoft.Repl/Suggestions/SuggestionManager.cs @@ -36,7 +36,7 @@ namespace Microsoft.Repl.Suggestions else { _currentSuggestion = 0; - _suggestions = shellState.CommandDispatcher.CollectSuggesetions(shellState); + _suggestions = shellState.CommandDispatcher.CollectSuggestions(shellState); if (_suggestions == null || _suggestions.Count == 0) { @@ -76,7 +76,7 @@ namespace Microsoft.Repl.Suggestions } else { - _suggestions = shellState.CommandDispatcher.CollectSuggesetions(shellState); + _suggestions = shellState.CommandDispatcher.CollectSuggestions(shellState); _currentSuggestion = _suggestions.Count - 1; if (_suggestions == null || _suggestions.Count == 0) From d745b8c161d42665ceb51597888062336fec7764 Mon Sep 17 00:00:00 2001 From: Mike Lorbetske Date: Wed, 31 Oct 2018 12:45:45 -0700 Subject: [PATCH 66/66] Fix #485 --- src/Microsoft.HttpRepl/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.HttpRepl/Program.cs b/src/Microsoft.HttpRepl/Program.cs index e41a36ff21..308996ff2c 100644 --- a/src/Microsoft.HttpRepl/Program.cs +++ b/src/Microsoft.HttpRepl/Program.cs @@ -52,7 +52,7 @@ namespace Microsoft.HttpRepl shell.ShellState.ConsoleManager.AddBreakHandler(() => source.Cancel()); if (args.Length > 0) { - if (string.Equals(args[0], "--help", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(args[0], "--help", StringComparison.OrdinalIgnoreCase) || string.Equals(args[0], "-h", StringComparison.OrdinalIgnoreCase)) { shell.ShellState.ConsoleManager.WriteLine("Usage: dotnet httprepl [] [options]"); shell.ShellState.ConsoleManager.WriteLine();