Repository: aspnet/DotNetTools Branch: master Commit: da47b81a162a Files: 282 Total size: 724.4 KB Directory structure: gitextract_sumg99vk/ ├── .appveyor.yml ├── .editorconfig ├── .gitattributes ├── .gitignore ├── .travis.yml ├── .vsts-pipelines/ │ └── builds/ │ ├── ci-internal.yml │ └── ci-public.yml ├── CONTRIBUTING.md ├── Directory.Build.props ├── Directory.Build.targets ├── DotNetTools.sln ├── LICENSE.txt ├── NuGet.config ├── NuGetPackageVerifier.json ├── README.md ├── build/ │ ├── Key.snk │ ├── VSIX.props │ ├── VSIX.targets │ ├── dependencies.props │ ├── repo.props │ ├── repo.targets │ └── sources.props ├── build.cmd ├── build.sh ├── korebuild-lock.txt ├── korebuild.json ├── run.cmd ├── run.ps1 ├── run.sh ├── samples/ │ └── dotnet-watch/ │ ├── Directory.Build.props │ ├── Directory.Build.targets │ ├── LaunchAnyCommand/ │ │ ├── LaunchAnyCommand.csproj │ │ ├── README.md │ │ ├── package.json │ │ └── say-hello.js │ ├── README.md │ ├── WatchJavascriptFiles/ │ │ ├── Program.cs │ │ ├── README.md │ │ ├── WatchJavascriptFiles.csproj │ │ └── wwwroot/ │ │ └── app.js │ └── WatchMultipleProjects/ │ ├── README.md │ ├── Test/ │ │ ├── Test.csproj │ │ └── UnitTest1.cs │ ├── Web/ │ │ ├── Program.cs │ │ └── Web.csproj │ └── watch.csproj ├── shared/ │ ├── CliContext.cs │ ├── CommandLineApplicationExtensions.cs │ ├── ConsoleReporter.cs │ ├── DebugHelper.cs │ ├── Ensure.cs │ ├── IConsole.cs │ ├── IReporter.cs │ ├── NullReporter.cs │ └── PhysicalConsole.cs ├── src/ │ ├── Directory.Build.props │ ├── Directory.Build.targets │ ├── Microsoft.AspNetCore.DeveloperCertificates.XPlat/ │ │ ├── CertificateGenerator.cs │ │ └── Microsoft.AspNetCore.DeveloperCertificates.XPlat.csproj │ ├── Microsoft.HttpRepl/ │ │ ├── AggregateDirectoryStructure.cs │ │ ├── Commands/ │ │ │ ├── BaseHttpCommand.cs │ │ │ ├── ChangeDirectoryCommand.cs │ │ │ ├── ClearCommand.cs │ │ │ ├── ConfigCommand.cs │ │ │ ├── DeleteCommand.cs │ │ │ ├── EchoCommand.cs │ │ │ ├── ExitCommand.cs │ │ │ ├── Formatter.cs │ │ │ ├── GetCommand.cs │ │ │ ├── HeadCommand.cs │ │ │ ├── HelpCommand.cs │ │ │ ├── ListCommand.cs │ │ │ ├── OptionsCommand.cs │ │ │ ├── PatchCommand.cs │ │ │ ├── PostCommand.cs │ │ │ ├── PrefCommand.cs │ │ │ ├── PutCommand.cs │ │ │ ├── RunCommand.cs │ │ │ ├── SetBaseCommand.cs │ │ │ ├── SetDiagCommand.cs │ │ │ ├── SetHeaderCommand.cs │ │ │ ├── SetSwaggerCommand.cs │ │ │ ├── TreeNode.cs │ │ │ └── UICommand.cs │ │ ├── Diagnostics/ │ │ │ ├── ConfigItem.cs │ │ │ ├── DiagEndpoint.cs │ │ │ ├── DiagEndpointMetadata.cs │ │ │ ├── DiagItem.cs │ │ │ └── DiagnosticsState.cs │ │ ├── DirectoryStructure.cs │ │ ├── DirectoryStructureExtensions.cs │ │ ├── Formatting/ │ │ │ └── JsonVisitor.cs │ │ ├── HttpState.cs │ │ ├── IDirectoryStructure.cs │ │ ├── IRequestInfo.cs │ │ ├── Microsoft.HttpRepl.csproj │ │ ├── OpenApi/ │ │ │ ├── Either.cs │ │ │ ├── EitherConverter.cs │ │ │ ├── EndpointMetadata.cs │ │ │ ├── EndpointMetadataReader.cs │ │ │ ├── IEndpointMetadataReader.cs │ │ │ ├── OpenApiV3EndpointMetadataReader.cs │ │ │ ├── Parameter.cs │ │ │ ├── PointerUtil.cs │ │ │ ├── Schema.cs │ │ │ ├── SwaggerV1EndpointMetadataReader.cs │ │ │ └── SwaggerV2EndpointMetadataReader.cs │ │ ├── Preferences/ │ │ │ ├── IJsonConfig.cs │ │ │ ├── JsonConfig.cs │ │ │ ├── RequestConfig.cs │ │ │ ├── RequestOrResponseConfig.cs │ │ │ ├── ResponseConfig.cs │ │ │ └── WellKnownPreference.cs │ │ ├── Program.cs │ │ ├── Properties/ │ │ │ └── launchSettings.json │ │ └── Suggestions/ │ │ ├── HeaderCompletion.cs │ │ └── ServerPathCompletion.cs │ ├── Microsoft.Repl/ │ │ ├── Commanding/ │ │ │ ├── CommandHistory.cs │ │ │ ├── CommandInputLocation.cs │ │ │ ├── CommandInputProcessingIssue.cs │ │ │ ├── CommandInputProcessingIssueKind.cs │ │ │ ├── CommandInputSpecification.cs │ │ │ ├── CommandInputSpecificationBuilder.cs │ │ │ ├── CommandOptionSpecification.cs │ │ │ ├── CommandWithStructuredInputBase.cs │ │ │ ├── DefaultCommandDispatcher.cs │ │ │ ├── DefaultCommandInput.cs │ │ │ ├── ICommand.cs │ │ │ ├── ICommandDispatcher.cs │ │ │ ├── ICommandHistory.cs │ │ │ └── InputElement.cs │ │ ├── ConsoleHandling/ │ │ │ ├── AllowedColors.cs │ │ │ ├── AnsiColorExtensions.cs │ │ │ ├── AnsiConsole.cs │ │ │ ├── ConsoleManager.cs │ │ │ ├── IConsoleManager.cs │ │ │ ├── IWritable.cs │ │ │ ├── Point.cs │ │ │ ├── Reporter.cs │ │ │ └── Writable.cs │ │ ├── Disposable.cs │ │ ├── IShellState.cs │ │ ├── Input/ │ │ │ ├── AsyncKeyPressHandler.cs │ │ │ ├── IInputManager.cs │ │ │ ├── InputManager.cs │ │ │ └── KeyHandlers.cs │ │ ├── Microsoft.Repl.csproj │ │ ├── Parsing/ │ │ │ ├── CoreParseResult.cs │ │ │ ├── CoreParser.cs │ │ │ ├── ICoreParseResult.cs │ │ │ └── IParser.cs │ │ ├── Scripting/ │ │ │ ├── IScriptExecutor.cs │ │ │ └── ScriptExecutor.cs │ │ ├── Shell.cs │ │ ├── ShellState.cs │ │ ├── Suggestions/ │ │ │ ├── FileSystemCompletion.cs │ │ │ ├── ISuggestionManager.cs │ │ │ └── SuggestionManager.cs │ │ └── Utils.cs │ ├── dotnet-dev-certs/ │ │ ├── Program.cs │ │ ├── README.md │ │ └── dotnet-dev-certs.csproj │ ├── dotnet-sql-cache/ │ │ ├── Program.cs │ │ ├── README.md │ │ ├── SqlQueries.cs │ │ └── dotnet-sql-cache.csproj │ ├── dotnet-user-secrets/ │ │ ├── CommandLineOptions.cs │ │ ├── Internal/ │ │ │ ├── ClearCommand.cs │ │ │ ├── CommandContext.cs │ │ │ ├── ICommand.cs │ │ │ ├── InitCommand.cs │ │ │ ├── ListCommand.cs │ │ │ ├── MsBuildProjectFinder.cs │ │ │ ├── ProjectIdResolver.cs │ │ │ ├── ReadableJsonConfigurationSource.cs │ │ │ ├── RemoveCommand.cs │ │ │ ├── SecretsStore.cs │ │ │ └── SetCommand.cs │ │ ├── Program.cs │ │ ├── Properties/ │ │ │ ├── AssemblyInfo.cs │ │ │ └── Resources.Designer.cs │ │ ├── README.md │ │ ├── Resources.resx │ │ ├── assets/ │ │ │ └── SecretManager.targets │ │ └── dotnet-user-secrets.csproj │ └── dotnet-watch/ │ ├── CommandLineOptions.cs │ ├── DotNetWatcher.cs │ ├── IFileSet.cs │ ├── IFileSetFactory.cs │ ├── Internal/ │ │ ├── FileSet.cs │ │ ├── FileSetWatcher.cs │ │ ├── FileWatcher/ │ │ │ ├── DotnetFileWatcher.cs │ │ │ ├── FileWatcherFactory.cs │ │ │ ├── IFileSystemWatcher.cs │ │ │ └── PollingFileWatcher.cs │ │ ├── FileWatcher.cs │ │ ├── MsBuildFileSetFactory.cs │ │ ├── MsBuildProjectFinder.cs │ │ ├── OutputCapture.cs │ │ ├── OutputSink.cs │ │ └── ProcessRunner.cs │ ├── PrefixConsoleReporter.cs │ ├── ProcessSpec.cs │ ├── Program.cs │ ├── Properties/ │ │ ├── AssemblyInfo.cs │ │ └── Resources.Designer.cs │ ├── README.md │ ├── Resources.resx │ ├── assets/ │ │ └── DotNetWatch.targets │ └── dotnet-watch.csproj ├── test/ │ ├── Directory.Build.props │ ├── Microsoft.HttpRepl.Tests/ │ │ ├── JsonVisitorTests.cs │ │ └── Microsoft.HttpRepl.Tests.csproj │ ├── Microsoft.Repl.Tests/ │ │ ├── Microsoft.Repl.Tests.csproj │ │ └── ParserTests.cs │ ├── Shared/ │ │ ├── TestConsole.cs │ │ └── TestReporter.cs │ ├── dotnet-user-secrets.Tests/ │ │ ├── InitCommandTest.cs │ │ ├── MsBuildProjectFinderTest.cs │ │ ├── SecretManagerTests.cs │ │ ├── SetCommandTest.cs │ │ ├── TemporaryFileProvider.cs │ │ ├── UserSecretsTestFixture.cs │ │ └── dotnet-user-secrets.Tests.csproj │ ├── dotnet-watch.FunctionalTests/ │ │ ├── AppWithDepsTests.cs │ │ ├── AwaitableProcess.cs │ │ ├── DotNetWatcherTests.cs │ │ ├── FileWatcherTests.cs │ │ ├── GlobbingAppTests.cs │ │ ├── NoDepsAppTests.cs │ │ ├── Scenario/ │ │ │ ├── ProjectToolScenario.cs │ │ │ └── WatchableApp.cs │ │ ├── TestProjects/ │ │ │ ├── AppWithDeps/ │ │ │ │ ├── AppWithDeps.csproj │ │ │ │ └── Program.cs │ │ │ ├── Dependency/ │ │ │ │ ├── Dependency.csproj │ │ │ │ └── Foo.cs │ │ │ ├── GlobbingApp/ │ │ │ │ ├── GlobbingApp.csproj │ │ │ │ ├── Program.cs │ │ │ │ ├── exclude/ │ │ │ │ │ └── Baz.cs │ │ │ │ └── include/ │ │ │ │ └── Foo.cs │ │ │ ├── KitchenSink/ │ │ │ │ ├── KitchenSink.csproj │ │ │ │ └── Program.cs │ │ │ └── NoDepsApp/ │ │ │ ├── NoDepsApp.csproj │ │ │ └── Program.cs │ │ └── dotnet-watch.FunctionalTests.csproj │ └── dotnet-watch.Tests/ │ ├── AssertEx.cs │ ├── CommandLineOptionsTests.cs │ ├── ConsoleReporterTests.cs │ ├── MsBuildFileSetFactoryTest.cs │ ├── ProgramTests.cs │ ├── Utilities/ │ │ ├── TemporaryCSharpProject.cs │ │ ├── TemporaryDirectory.cs │ │ └── TestProjectGraph.cs │ └── dotnet-watch.Tests.csproj ├── tooling/ │ ├── Microsoft.VisualStudio.SecretManager/ │ │ ├── Microsoft.VisualStudio.SecretManager.csproj │ │ ├── ProjectLocalSecretsManager.cs │ │ ├── Properties/ │ │ │ └── AssemblyInfo.cs │ │ ├── Resources.Designer.cs │ │ ├── Resources.resx │ │ ├── SecretManagerFactory.cs │ │ ├── SecretStore.cs │ │ ├── Sources/ │ │ │ ├── ConfigurationPath.cs │ │ │ ├── JsonConfigurationFileParser.cs │ │ │ └── PathHelper.cs │ │ └── source.extension.vsixmanifest │ └── Microsoft.VisualStudio.SecretManager.TestExtension/ │ ├── Key.snk │ ├── Microsoft.VisualStudio.SecretManager.TestExtension.csproj │ ├── NotifyPropertyChanged.cs │ ├── ProjectViewModel.cs │ ├── Properties/ │ │ └── AssemblyInfo.cs │ ├── RelayCommand.cs │ ├── SecretManagerTestCommand.cs │ ├── SecretManagerTestControl.xaml │ ├── SecretManagerTestControl.xaml.cs │ ├── SecretManagerTestPackage.cs │ ├── SecretManagerTestPackage.vsct │ ├── SecretManagerTestWindow.cs │ ├── SecretManagerViewModel.cs │ ├── VSPackage.resx │ ├── app.config │ └── source.extension.vsixmanifest └── version.props ================================================ FILE CONTENTS ================================================ ================================================ FILE: .appveyor.yml ================================================ init: - git config --global core.autocrlf true branches: only: - master - /^release\/.*$/ - /^(.*\/)?ci-.*$/ build_script: - ps: .\run.ps1 default-build clone_depth: 1 environment: global: DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true DOTNET_CLI_TELEMETRY_OPTOUT: 1 test: 'off' deploy: 'off' os: Visual Studio 2017 ================================================ FILE: .editorconfig ================================================ # EditorConfig is awesome:http://EditorConfig.org # top-most EditorConfig file root = true [*] indent_style = space charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true [*.cs] indent_size = 4 dotnet_sort_system_directives_first = true:warning # Xml files [*.{csproj,config,props,targets,ruleset,config,resx,xml}] indent_size = 2 [*.{json, yml}] indent_size = 2 [*.{ps1,sh}] indent_size = 4 ================================================ FILE: .gitattributes ================================================ *.doc diff=astextplain *.DOC diff=astextplain *.docx diff=astextplain *.DOCX diff=astextplain *.dot diff=astextplain *.DOT diff=astextplain *.pdf diff=astextplain *.PDF diff=astextplain *.rtf diff=astextplain *.RTF diff=astextplain *.jpg binary *.png binary *.gif binary *.cs text=auto diff=csharp *.vb text=auto *.resx text=auto *.c text=auto *.cpp text=auto *.cxx text=auto *.h text=auto *.hxx text=auto *.py text=auto *.rb text=auto *.java text=auto *.html text=auto *.htm text=auto *.css text=auto *.scss text=auto *.sass text=auto *.less text=auto *.js text=auto *.lisp text=auto *.clj text=auto *.sql text=auto *.php text=auto *.lua text=auto *.m text=auto *.asm text=auto *.erl text=auto *.fs text=auto *.fsx text=auto *.hs text=auto *.csproj text=auto *.vbproj text=auto *.fsproj text=auto *.dbproj text=auto *.sln text=auto eol=crlf *.sh eol=lf ================================================ FILE: .gitignore ================================================ [Oo]bj/ [Bb]in/ TestResults/ .nuget/ _ReSharper.*/ packages/ artifacts/ PublishProfiles/ *.user *.suo *.cache *.docstates _ReSharper.* nuget.exe *net45.csproj *net451.csproj *k10.csproj *.psess *.vsp *.pidb *.userprefs *DS_Store *.ncrunchsolution *.*sdf *.ipch *.sln.ide project.lock.json .testPublish/ .build/ /.vs/ .vscode/ *.nuget.props *.nuget.targets .idea/ .dotnet/ global.json *.binlog ================================================ FILE: .travis.yml ================================================ language: csharp sudo: false dist: trusty env: global: - DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true - DOTNET_CLI_TELEMETRY_OPTOUT: 1 mono: none os: - linux - osx osx_image: xcode8.2 addons: apt: packages: - libunwind8 branches: only: - master - /^release\/.*$/ - /^(.*\/)?ci-.*$/ before_install: - if test "$TRAVIS_OS_NAME" == "osx"; then brew update; brew install openssl; ln -s /usr/local/opt/openssl/lib/libcrypto.1.0.0.dylib /usr/local/lib/; ln -s /usr/local/opt/openssl/lib/libssl.1.0.0.dylib /usr/local/lib/; fi script: - ./build.sh ================================================ FILE: .vsts-pipelines/builds/ci-internal.yml ================================================ trigger: - master - release/* resources: repositories: - repository: buildtools type: git name: aspnet-BuildTools ref: refs/heads/master phases: - template: .vsts-pipelines/templates/project-ci.yml@buildtools ================================================ FILE: .vsts-pipelines/builds/ci-public.yml ================================================ trigger: - master - release/* # See https://github.com/aspnet/BuildTools resources: repositories: - repository: buildtools type: github endpoint: DotNet-Bot GitHub Connection name: aspnet/BuildTools ref: refs/heads/master phases: - template: .vsts-pipelines/templates/project-ci.yml@buildtools ================================================ FILE: CONTRIBUTING.md ================================================ Contributing ====== Information on contributing to this repo is in the [Contributing Guide](https://github.com/aspnet/Home/blob/master/CONTRIBUTING.md) in the Home repo. ================================================ FILE: Directory.Build.props ================================================ Microsoft .NET https://github.com/aspnet/DotNetTools git $(MSBuildThisFileDirectory) $(MSBuildThisFileDirectory)build\Key.snk true true false $(MSBuildThisFileDirectory)obj\$(MSBuildProjectName)\ $(MSBuildThisFileDirectory)bin\$(MSBuildProjectName)\ ================================================ FILE: Directory.Build.targets ================================================ $(MicrosoftNETCoreApp30PackageVersion) $(NETStandardLibrary20PackageVersion) 99.9 ================================================ FILE: DotNetTools.sln ================================================ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.27120.0 MinimumVisualStudioVersion = 15.0.26730.03 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{66517987-2A5A-4330-B130-207039378FD4}" ProjectSection(SolutionItems) = preProject src\Directory.Build.props = src\Directory.Build.props EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-watch", "src\dotnet-watch\dotnet-watch.csproj", "{8A8CEABC-AC47-43FF-A5DF-69224F7E1F46}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8321E0D1-9A47-4D2F-AED8-3AE636D44E35}" ProjectSection(SolutionItems) = preProject .appveyor.yml = .appveyor.yml .editorconfig = .editorconfig .gitattributes = .gitattributes .gitignore = .gitignore .travis.yml = .travis.yml build.cmd = build.cmd build.ps1 = build.ps1 build.sh = build.sh CONTRIBUTING.md = CONTRIBUTING.md build\dependencies.props = build\dependencies.props Directory.Build.props = Directory.Build.props Directory.Build.targets = Directory.Build.targets LICENSE.txt = LICENSE.txt NuGet.config = NuGet.config NuGetPackageVerifier.json = NuGetPackageVerifier.json README.md = README.md build\sources.props = build\sources.props version.props = version.props EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{F5B382BC-258F-46E1-AC3D-10E5CCD55134}" ProjectSection(SolutionItems) = preProject test\Directory.Build.props = test\Directory.Build.props EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-watch.FunctionalTests", "test\dotnet-watch.FunctionalTests\dotnet-watch.FunctionalTests.csproj", "{16BADE2F-1184-4518-8A70-B68A19D0805B}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-user-secrets", "src\dotnet-user-secrets\dotnet-user-secrets.csproj", "{8730E848-CA0F-4E0A-9A2F-BC22AD0B2C4E}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-user-secrets.Tests", "test\dotnet-user-secrets.Tests\dotnet-user-secrets.Tests.csproj", "{7B331122-83B1-4F08-A119-DC846959844C}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-watch.Tests", "test\dotnet-watch.Tests\dotnet-watch.Tests.csproj", "{8A2E6961-6B12-4A8E-8215-3E7301D52EAC}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-sql-cache", "src\dotnet-sql-cache\dotnet-sql-cache.csproj", "{53F3B53D-303A-4DAA-9C38-4F55195FA5B9}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-dev-certs", "src\dotnet-dev-certs\dotnet-dev-certs.csproj", "{4FED5119-EE5C-4753-88A4-D61BDEB4D6C8}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.VisualStudio.SecretManager", "tooling\Microsoft.VisualStudio.SecretManager\Microsoft.VisualStudio.SecretManager.csproj", "{5E117F2E-7152-447F-BF47-59F759EEF3A7}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tooling", "tooling", "{62826851-7D74-4F1E-B7D1-12553B789CD8}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.VisualStudio.SecretManager.TestExtension", "tooling\Microsoft.VisualStudio.SecretManager.TestExtension\Microsoft.VisualStudio.SecretManager.TestExtension.csproj", "{965F8820-F809-4081-9090-1AEC903F291B}" 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 DebugNoVSIX|Any CPU = DebugNoVSIX|Any CPU Release|Any CPU = Release|Any CPU ReleaseNoVSIX|Any CPU = ReleaseNoVSIX|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {8A8CEABC-AC47-43FF-A5DF-69224F7E1F46}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8A8CEABC-AC47-43FF-A5DF-69224F7E1F46}.Debug|Any CPU.Build.0 = Debug|Any CPU {8A8CEABC-AC47-43FF-A5DF-69224F7E1F46}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU {8A8CEABC-AC47-43FF-A5DF-69224F7E1F46}.DebugNoVSIX|Any CPU.Build.0 = Debug|Any CPU {8A8CEABC-AC47-43FF-A5DF-69224F7E1F46}.Release|Any CPU.ActiveCfg = Release|Any CPU {8A8CEABC-AC47-43FF-A5DF-69224F7E1F46}.Release|Any CPU.Build.0 = Release|Any CPU {8A8CEABC-AC47-43FF-A5DF-69224F7E1F46}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU {8A8CEABC-AC47-43FF-A5DF-69224F7E1F46}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU {16BADE2F-1184-4518-8A70-B68A19D0805B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {16BADE2F-1184-4518-8A70-B68A19D0805B}.Debug|Any CPU.Build.0 = Debug|Any CPU {16BADE2F-1184-4518-8A70-B68A19D0805B}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU {16BADE2F-1184-4518-8A70-B68A19D0805B}.DebugNoVSIX|Any CPU.Build.0 = Debug|Any CPU {16BADE2F-1184-4518-8A70-B68A19D0805B}.Release|Any CPU.ActiveCfg = Release|Any CPU {16BADE2F-1184-4518-8A70-B68A19D0805B}.Release|Any CPU.Build.0 = Release|Any CPU {16BADE2F-1184-4518-8A70-B68A19D0805B}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU {16BADE2F-1184-4518-8A70-B68A19D0805B}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU {8730E848-CA0F-4E0A-9A2F-BC22AD0B2C4E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8730E848-CA0F-4E0A-9A2F-BC22AD0B2C4E}.Debug|Any CPU.Build.0 = Debug|Any CPU {8730E848-CA0F-4E0A-9A2F-BC22AD0B2C4E}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU {8730E848-CA0F-4E0A-9A2F-BC22AD0B2C4E}.DebugNoVSIX|Any CPU.Build.0 = Debug|Any CPU {8730E848-CA0F-4E0A-9A2F-BC22AD0B2C4E}.Release|Any CPU.ActiveCfg = Release|Any CPU {8730E848-CA0F-4E0A-9A2F-BC22AD0B2C4E}.Release|Any CPU.Build.0 = Release|Any CPU {8730E848-CA0F-4E0A-9A2F-BC22AD0B2C4E}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU {8730E848-CA0F-4E0A-9A2F-BC22AD0B2C4E}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU {7B331122-83B1-4F08-A119-DC846959844C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7B331122-83B1-4F08-A119-DC846959844C}.Debug|Any CPU.Build.0 = Debug|Any CPU {7B331122-83B1-4F08-A119-DC846959844C}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU {7B331122-83B1-4F08-A119-DC846959844C}.DebugNoVSIX|Any CPU.Build.0 = Debug|Any CPU {7B331122-83B1-4F08-A119-DC846959844C}.Release|Any CPU.ActiveCfg = Release|Any CPU {7B331122-83B1-4F08-A119-DC846959844C}.Release|Any CPU.Build.0 = Release|Any CPU {7B331122-83B1-4F08-A119-DC846959844C}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU {7B331122-83B1-4F08-A119-DC846959844C}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU {8A2E6961-6B12-4A8E-8215-3E7301D52EAC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8A2E6961-6B12-4A8E-8215-3E7301D52EAC}.Debug|Any CPU.Build.0 = Debug|Any CPU {8A2E6961-6B12-4A8E-8215-3E7301D52EAC}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU {8A2E6961-6B12-4A8E-8215-3E7301D52EAC}.DebugNoVSIX|Any CPU.Build.0 = Debug|Any CPU {8A2E6961-6B12-4A8E-8215-3E7301D52EAC}.Release|Any CPU.ActiveCfg = Release|Any CPU {8A2E6961-6B12-4A8E-8215-3E7301D52EAC}.Release|Any CPU.Build.0 = Release|Any CPU {8A2E6961-6B12-4A8E-8215-3E7301D52EAC}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU {8A2E6961-6B12-4A8E-8215-3E7301D52EAC}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU {53F3B53D-303A-4DAA-9C38-4F55195FA5B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {53F3B53D-303A-4DAA-9C38-4F55195FA5B9}.Debug|Any CPU.Build.0 = Debug|Any CPU {53F3B53D-303A-4DAA-9C38-4F55195FA5B9}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU {53F3B53D-303A-4DAA-9C38-4F55195FA5B9}.DebugNoVSIX|Any CPU.Build.0 = Debug|Any CPU {53F3B53D-303A-4DAA-9C38-4F55195FA5B9}.Release|Any CPU.ActiveCfg = Release|Any CPU {53F3B53D-303A-4DAA-9C38-4F55195FA5B9}.Release|Any CPU.Build.0 = Release|Any CPU {53F3B53D-303A-4DAA-9C38-4F55195FA5B9}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU {53F3B53D-303A-4DAA-9C38-4F55195FA5B9}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU {4FED5119-EE5C-4753-88A4-D61BDEB4D6C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4FED5119-EE5C-4753-88A4-D61BDEB4D6C8}.Debug|Any CPU.Build.0 = Debug|Any CPU {4FED5119-EE5C-4753-88A4-D61BDEB4D6C8}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU {4FED5119-EE5C-4753-88A4-D61BDEB4D6C8}.DebugNoVSIX|Any CPU.Build.0 = Debug|Any CPU {4FED5119-EE5C-4753-88A4-D61BDEB4D6C8}.Release|Any CPU.ActiveCfg = Release|Any CPU {4FED5119-EE5C-4753-88A4-D61BDEB4D6C8}.Release|Any CPU.Build.0 = Release|Any CPU {4FED5119-EE5C-4753-88A4-D61BDEB4D6C8}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU {4FED5119-EE5C-4753-88A4-D61BDEB4D6C8}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU {5E117F2E-7152-447F-BF47-59F759EEF3A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5E117F2E-7152-447F-BF47-59F759EEF3A7}.Debug|Any CPU.Build.0 = Debug|Any CPU {5E117F2E-7152-447F-BF47-59F759EEF3A7}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU {5E117F2E-7152-447F-BF47-59F759EEF3A7}.Release|Any CPU.ActiveCfg = Release|Any CPU {5E117F2E-7152-447F-BF47-59F759EEF3A7}.Release|Any CPU.Build.0 = Release|Any CPU {5E117F2E-7152-447F-BF47-59F759EEF3A7}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU {965F8820-F809-4081-9090-1AEC903F291B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {965F8820-F809-4081-9090-1AEC903F291B}.Debug|Any CPU.Build.0 = Debug|Any CPU {965F8820-F809-4081-9090-1AEC903F291B}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU {965F8820-F809-4081-9090-1AEC903F291B}.Release|Any CPU.ActiveCfg = Release|Any CPU {965F8820-F809-4081-9090-1AEC903F291B}.Release|Any CPU.Build.0 = Release|Any CPU {965F8820-F809-4081-9090-1AEC903F291B}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU {96E71881-1465-44F5-B4B7-DF9B370FFD02}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {96E71881-1465-44F5-B4B7-DF9B370FFD02}.Debug|Any CPU.Build.0 = Debug|Any CPU {96E71881-1465-44F5-B4B7-DF9B370FFD02}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU {96E71881-1465-44F5-B4B7-DF9B370FFD02}.DebugNoVSIX|Any CPU.Build.0 = Debug|Any CPU {96E71881-1465-44F5-B4B7-DF9B370FFD02}.Release|Any CPU.ActiveCfg = Release|Any CPU {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 EndGlobalSection GlobalSection(NestedProjects) = preSolution {8A8CEABC-AC47-43FF-A5DF-69224F7E1F46} = {66517987-2A5A-4330-B130-207039378FD4} {16BADE2F-1184-4518-8A70-B68A19D0805B} = {F5B382BC-258F-46E1-AC3D-10E5CCD55134} {8730E848-CA0F-4E0A-9A2F-BC22AD0B2C4E} = {66517987-2A5A-4330-B130-207039378FD4} {7B331122-83B1-4F08-A119-DC846959844C} = {F5B382BC-258F-46E1-AC3D-10E5CCD55134} {8A2E6961-6B12-4A8E-8215-3E7301D52EAC} = {F5B382BC-258F-46E1-AC3D-10E5CCD55134} {53F3B53D-303A-4DAA-9C38-4F55195FA5B9} = {66517987-2A5A-4330-B130-207039378FD4} {4FED5119-EE5C-4753-88A4-D61BDEB4D6C8} = {66517987-2A5A-4330-B130-207039378FD4} {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} EndGlobalSection EndGlobal ================================================ FILE: LICENSE.txt ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright (c) .NET Foundation and Contributors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: NuGet.config ================================================  ================================================ FILE: NuGetPackageVerifier.json ================================================ { "adx": { "rules": [ "AdxVerificationCompositeRule" ], "packages": { "dotnet-watch": { "packageTypes": [ "DotnetTool" ] }, "dotnet-sql-cache": { "packageTypes": [ "DotnetTool" ] }, "dotnet-user-secrets": { "packageTypes": [ "DotnetTool" ] }, "dotnet-dev-certs": { "packageTypes": [ "DotnetTool" ] }, "Microsoft.AspNetCore.DeveloperCertificates.XPlat": { "Exclusions": { "DOC_MISSING": { "lib/netcoreapp3.0/Microsoft.AspNetCore.DeveloperCertificates.XPlat.dll": "Docs not required to shipoob package" } } } } }, "Default": { "rules": [ "DefaultCompositeRule" ] } } ================================================ FILE: README.md ================================================ DotNetTools [Archived] ====================== **This GitHub project has been archived.** Ongoing development on this project can be found in . This project is part of ASP.NET Core. You can find samples, documentation and getting started instructions for ASP.NET Core at . ## Projects 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) - [dotnet-sql-cache](src/dotnet-sql-cache/README.md) - [dotnet-dev-certs](src/dotnet-dev-certs/README.md) *\*This applies to .NET Core CLI 2.1.300-preview2 and up. For earlier versions of the CLI, these tools must be installed separately.* *For 2.0 CLI and earlier, see for details.* *For 2.1.300-preview1 CLI, see for details.* ## Usage The command line tools can be invoked as a subcommand of `dotnet`. ```sh dotnet watch dotnet user-secrets dotnet sql-cache dotnet dev-certs ``` Add `--help` to see more details. For example, ``` dotnet watch --help ``` ================================================ FILE: build/VSIX.props ================================================ ================================================ FILE: build/VSIX.targets ================================================ false $(RestoreDependsOn);RestoreVSIX $(PackageDependsOn);PackageVSIX $(GetArtifactInfoDependsOn);GetVSIXArtifactInfo Microsoft.VisualStudio.SecretManager $(RepositoryRoot)tooling\$(VSIXName)\$(VSIXName).csproj $(BuildDir)$(VSIXName).vsix $(BuildDir)$(VSIXName).json $(BuildDir)$(VSIXName).pdb shipoob VsixPackage $(PackageVersion) $(VSIXArtifactCategory) $(VSIXName) VsixPackageManifestFile $(VSIXArtifactCategory) $(VSIXName).vsix $(VSIXName) SymbolsFile $(VSIXArtifactCategory) $(VSIXName).vsix full $(LogOutputDir)vsix-restore.rsp $(LogOutputDir)vsix.log $(LogOutputDir)vsix-build.rsp ================================================ FILE: build/dependencies.props ================================================  $(MSBuildAllProjects);$(MSBuildThisFileFullPath) 3.0.0-alpha1-20181026.5 3.0.0-alpha1-10657 3.0.0-alpha1-10657 3.0.0-alpha1-10657 3.0.0-alpha1-10664 3.0.0-alpha1-10657 3.0.0-preview1-26907-05 15.9.0 5.2.6 2.0.3 11.0.2 4.6.0-preview1-26907-04 4.6.0-preview1-26907-04 9.0.1 2.4.0 2.4.0 ================================================ FILE: build/repo.props ================================================ Internal.AspNetCore.Universe.Lineup https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json ================================================ FILE: build/repo.targets ================================================ Configuration=$(Configuration)NoVSIX ================================================ FILE: build/sources.props ================================================ $(DotNetRestoreSources) $(RestoreSources); https://dotnet.myget.org/F/dotnet-core/api/v3/index.json; https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json; https://dotnet.myget.org/F/aspnetcore-tools/api/v3/index.json; https://vside.myget.org/F/vssdk/api/v3/index.json; https://vside.myget.org/F/devcore/api/v3/index.json $(RestoreSources); https://api.nuget.org/v3/index.json; ================================================ FILE: build.cmd ================================================ @ECHO OFF PowerShell -NoProfile -NoLogo -ExecutionPolicy unrestricted -Command "[System.Threading.Thread]::CurrentThread.CurrentCulture = ''; [System.Threading.Thread]::CurrentThread.CurrentUICulture = '';& '%~dp0run.ps1' default-build %*; exit $LASTEXITCODE" ================================================ FILE: build.sh ================================================ #!/usr/bin/env bash set -euo pipefail DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" # Call "sync" between "chmod" and execution to prevent "text file busy" error in Docker (aufs) chmod +x "$DIR/run.sh"; sync "$DIR/run.sh" default-build "$@" ================================================ FILE: korebuild-lock.txt ================================================ version:3.0.0-alpha1-20181026.5 commithash:0c0410ef8b17d5177a05b50cb66f9ad89ce15e32 ================================================ FILE: korebuild.json ================================================ { "$schema": "https://raw.githubusercontent.com/aspnet/BuildTools/master/tools/korebuild.schema.json", "channel": "master", "toolsets": { "visualstudio": { "required": [ "windows" ], "includePrerelease": true, "versionRange": "[15.0.26730.03, 16.0)", "requiredWorkloads": [ "Microsoft.VisualStudio.Component.VSSDK" ] } } } ================================================ FILE: run.cmd ================================================ @ECHO OFF PowerShell -NoProfile -NoLogo -ExecutionPolicy unrestricted -Command "[System.Threading.Thread]::CurrentThread.CurrentCulture = ''; [System.Threading.Thread]::CurrentThread.CurrentUICulture = '';& '%~dp0run.ps1' %*; exit $LASTEXITCODE" ================================================ FILE: run.ps1 ================================================ #!/usr/bin/env powershell #requires -version 4 <# .SYNOPSIS Executes KoreBuild commands. .DESCRIPTION Downloads korebuild if required. Then executes the KoreBuild command. To see available commands, execute with `-Command help`. .PARAMETER Command The KoreBuild command to run. .PARAMETER Path The folder to build. Defaults to the folder containing this script. .PARAMETER Channel The channel of KoreBuild to download. Overrides the value from the config file. .PARAMETER DotNetHome The directory where .NET Core tools will be stored. .PARAMETER ToolsSource The base url where build tools can be downloaded. Overrides the value from the config file. .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 .NOTES This function will create a file $PSScriptRoot/korebuild-lock.txt. This lock file can be committed to source, but does not have to be. When the lockfile is not present, KoreBuild will create one using latest available version from $Channel. The $ConfigFile is expected to be an JSON file. It is optional, and the configuration values in it are optional as well. Any options set in the file are overridden by command line parameters. .EXAMPLE Example config file: ```json { "$schema": "https://raw.githubusercontent.com/aspnet/BuildTools/master/tools/korebuild.schema.json", "channel": "master", "toolsSource": "https://aspnetcore.blob.core.windows.net/buildtools" } ``` #> [CmdletBinding(PositionalBinding = $false)] param( [Parameter(Mandatory = $true, Position = 0)] [string]$Command, [string]$Path = $PSScriptRoot, [Alias('c')] [string]$Channel, [Alias('d')] [string]$DotNetHome, [Alias('s')] [string]$ToolsSource, [Alias('u')] [switch]$Update, [switch]$Reinstall, [string]$ToolsSourceSuffix, [string]$ConfigFile = $null, [switch]$CI, [Parameter(ValueFromRemainingArguments = $true)] [string[]]$Arguments ) Set-StrictMode -Version 2 $ErrorActionPreference = 'Stop' # # Functions # function Get-KoreBuild { $lockFile = Join-Path $Path 'korebuild-lock.txt' if (!(Test-Path $lockFile) -or $Update) { Get-RemoteFile "$ToolsSource/korebuild/channels/$Channel/latest.txt" $lockFile $ToolsSourceSuffix } $version = Get-Content $lockFile | Where-Object { $_ -like 'version:*' } | Select-Object -first 1 if (!$version) { Write-Error "Failed to parse version from $lockFile. Expected a line that begins with 'version:'" } $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 $remotePath = "$ToolsSource/korebuild/artifacts/$version/korebuild.$version.zip" try { $tmpfile = Join-Path ([IO.Path]::GetTempPath()) "KoreBuild-$([guid]::NewGuid()).zip" Get-RemoteFile $remotePath $tmpfile $ToolsSourceSuffix if (Get-Command -Name 'Microsoft.PowerShell.Archive\Expand-Archive' -ErrorAction Ignore) { # Use built-in commands where possible as they are cross-plat compatible Microsoft.PowerShell.Archive\Expand-Archive -Path $tmpfile -DestinationPath $korebuildPath } else { # Fallback to old approach for old installations of PowerShell Add-Type -AssemblyName System.IO.Compression.FileSystem [System.IO.Compression.ZipFile]::ExtractToDirectory($tmpfile, $korebuildPath) } } catch { Remove-Item -Recurse -Force $korebuildPath -ErrorAction Ignore throw } finally { Remove-Item $tmpfile -ErrorAction Ignore } } return $korebuildPath } function Join-Paths([string]$path, [string[]]$childPaths) { $childPaths | ForEach-Object { $path = Join-Path $path $_ } return $path } function Get-RemoteFile([string]$RemotePath, [string]$LocalPath, [string]$RemoteSuffix) { if ($RemotePath -notlike 'http*') { Copy-Item $RemotePath $LocalPath return } $retries = 10 while ($retries -gt 0) { $retries -= 1 try { Invoke-WebRequest -UseBasicParsing -Uri $($RemotePath + $RemoteSuffix) -OutFile $LocalPath return } catch { Write-Verbose "Request failed. $retries retries remaining" } } Write-Error "Download failed: '$RemotePath'." } # # Main # # Load configuration or set defaults $Path = Resolve-Path $Path if (!$ConfigFile) { $ConfigFile = Join-Path $Path 'korebuild.json' } if (Test-Path $ConfigFile) { try { $config = Get-Content -Raw -Encoding UTF8 -Path $ConfigFile | ConvertFrom-Json if ($config) { if (!($Channel) -and (Get-Member -Name 'channel' -InputObject $config)) { [string] $Channel = $config.channel } if (!($ToolsSource) -and (Get-Member -Name 'toolsSource' -InputObject $config)) { [string] $ToolsSource = $config.toolsSource} } } catch { Write-Host -ForegroundColor Red $Error[0] Write-Error "$ConfigFile contains invalid JSON." exit 1 } } if (!$DotNetHome) { $DotNetHome = if ($env:DOTNET_HOME) { $env:DOTNET_HOME } ` elseif ($CI) { Join-Path $PSScriptRoot '.dotnet' } ` elseif ($env:USERPROFILE) { Join-Path $env:USERPROFILE '.dotnet'} ` elseif ($env:HOME) {Join-Path $env:HOME '.dotnet'}` else { Join-Path $PSScriptRoot '.dotnet'} } if (!$Channel) { $Channel = 'master' } if (!$ToolsSource) { $ToolsSource = 'https://aspnetcore.blob.core.windows.net/buildtools' } # Execute $korebuildPath = Get-KoreBuild Import-Module -Force -Scope Local (Join-Path $korebuildPath 'KoreBuild.psd1') try { Set-KoreBuildSettings -ToolsSource $ToolsSource -DotNetHome $DotNetHome -RepoPath $Path -ConfigFile $ConfigFile -CI:$CI Invoke-KoreBuildCommand $Command @Arguments } finally { Remove-Module 'KoreBuild' -ErrorAction Ignore } ================================================ FILE: run.sh ================================================ #!/usr/bin/env bash set -euo pipefail # # variables # RESET="\033[0m" RED="\033[0;31m" YELLOW="\033[0;33m" MAGENTA="\033[0;95m" DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" verbose=false update=false reinstall=false repo_path="$DIR" channel='' tools_source='' tools_source_suffix='' ci=false # # Functions # __usage() { echo "Usage: $(basename "${BASH_SOURCE[0]}") command [options] [[--] ...]" echo "" echo "Arguments:" echo " command The command to be run." echo " ... Arguments passed to the command. Variable number of arguments allowed." echo "" echo "Options:" echo " --verbose Show verbose output." echo " -c|--channel The channel of KoreBuild to download. Overrides the value from the config file.." echo " --config-file The path to the configuration file that stores values. Defaults to korebuild.json." echo " -d|--dotnet-home The directory where .NET Core tools will be stored. Defaults to '\$DOTNET_HOME' or '\$HOME/.dotnet." echo " --path The directory to build. Defaults to the directory containing the script." 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." echo " When the lockfile is not present, KoreBuild will create one using latest available version from \$channel." if [[ "${1:-}" != '--no-exit' ]]; then exit 2 fi } get_korebuild() { local version local lock_file="$repo_path/korebuild-lock.txt" if [ ! -f "$lock_file" ] || [ "$update" = true ]; then __get_remote_file "$tools_source/korebuild/channels/$channel/latest.txt" "$lock_file" "$tools_source_suffix" fi version="$(grep 'version:*' -m 1 "$lock_file")" if [[ "$version" == '' ]]; then __error "Failed to parse version from $lock_file. Expected a line that begins with 'version:'" return 1 fi 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" local remote_path="$tools_source/korebuild/artifacts/$version/korebuild.$version.zip" tmpfile="$(mktemp)" echo -e "${MAGENTA}Downloading KoreBuild ${version}${RESET}" if __get_remote_file "$remote_path" "$tmpfile" "$tools_source_suffix"; then unzip -q -d "$korebuild_path" "$tmpfile" fi rm "$tmpfile" || true fi source "$korebuild_path/KoreBuild.sh" } || { if [ -d "$korebuild_path" ]; then echo "Cleaning up after failed installation" rm -rf "$korebuild_path" || true fi return 1 } } __error() { echo -e "${RED}error: $*${RESET}" 1>&2 } __warn() { echo -e "${YELLOW}warning: $*${RESET}" } __machine_has() { hash "$1" > /dev/null 2>&1 return $? } __get_remote_file() { local remote_path=$1 local local_path=$2 local remote_path_suffix=$3 if [[ "$remote_path" != 'http'* ]]; then cp "$remote_path" "$local_path" return 0 fi local failed=false if __machine_has wget; then wget --tries 10 --quiet -O "$local_path" "${remote_path}${remote_path_suffix}" || failed=true else failed=true fi if [ "$failed" = true ] && __machine_has curl; then failed=false curl --retry 10 -sSL -f --create-dirs -o "$local_path" "${remote_path}${remote_path_suffix}" || failed=true fi if [ "$failed" = true ]; then __error "Download failed: $remote_path" 1>&2 return 1 fi } # # main # command="${1:-}" shift while [[ $# -gt 0 ]]; do case $1 in -\?|-h|--help) __usage --no-exit exit 0 ;; -c|--channel|-Channel) shift channel="${1:-}" [ -z "$channel" ] && __usage ;; --config-file|-ConfigFile) shift config_file="${1:-}" [ -z "$config_file" ] && __usage if [ ! -f "$config_file" ]; then __error "Invalid value for --config-file. $config_file does not exist." exit 1 fi ;; -d|--dotnet-home|-DotNetHome) shift DOTNET_HOME="${1:-}" [ -z "$DOTNET_HOME" ] && __usage ;; --path|-Path) shift repo_path="${1:-}" [ -z "$repo_path" ] && __usage ;; -s|--tools-source|-ToolsSource) shift tools_source="${1:-}" [ -z "$tools_source" ] && __usage ;; --tools-source-suffix|-ToolsSourceSuffix) shift tools_source_suffix="${1:-}" [ -z "$tools_source_suffix" ] && __usage ;; -u|--update|-Update) update=true ;; --reinstall|-[Rr]einstall) reinstall=true ;; --ci|-[Cc][Ii]) ci=true if [[ -z "${DOTNET_HOME:-}" ]]; then DOTNET_HOME="$DIR/.dotnet" fi ;; --verbose|-Verbose) verbose=true ;; --) shift break ;; *) break ;; esac shift done if ! __machine_has unzip; then __error 'Missing required command: unzip' exit 1 fi if ! __machine_has curl && ! __machine_has wget; then __error 'Missing required command. Either wget or curl is required.' exit 1 fi [ -z "${config_file:-}" ] && config_file="$repo_path/korebuild.json" if [ -f "$config_file" ]; then if __machine_has jq ; then if jq '.' "$config_file" >/dev/null ; 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." 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 __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 __error 'Missing required command: jq or python. Could not parse the JSON file.' exit 1 fi [ ! -z "${config_channel:-}" ] && channel="$config_channel" [ ! -z "${config_tools_source:-}" ] && tools_source="$config_tools_source" fi [ -z "${DOTNET_HOME:-}" ] && DOTNET_HOME="$HOME/.dotnet" [ -z "$channel" ] && channel='master' [ -z "$tools_source" ] && tools_source='https://aspnetcore.blob.core.windows.net/buildtools' get_korebuild set_korebuildsettings "$tools_source" "$DOTNET_HOME" "$repo_path" "$config_file" "$ci" invoke_korebuild_command "$command" "$@" ================================================ FILE: samples/dotnet-watch/Directory.Build.props ================================================ ================================================ FILE: samples/dotnet-watch/Directory.Build.targets ================================================ ================================================ FILE: samples/dotnet-watch/LaunchAnyCommand/LaunchAnyCommand.csproj ================================================ netcoreapp3.0 ================================================ FILE: samples/dotnet-watch/LaunchAnyCommand/README.md ================================================ Launch any command with dotnet-watch ==================================== ## Prerequisites 1. Install .NET Core command line. 2. Install NodeJS. ## Usage Open a terminal to the directory containing this project. ``` dotnet watch msbuild /t:RunMyNpmCommand ``` Changing the .csproj file, or the say-hello.js file will cause dotnet-watch to re-run the 'RunMyNpmCommand' target in MyApp.csproj. ================================================ FILE: samples/dotnet-watch/LaunchAnyCommand/package.json ================================================ { "name": "any-command", "version": "0.0.0", "private": true, "scripts": { "custom": "node say-hello.js" } } ================================================ FILE: samples/dotnet-watch/LaunchAnyCommand/say-hello.js ================================================ console.log("Hello from Javascript"); ================================================ FILE: samples/dotnet-watch/README.md ================================================ dotnet-watch samples ==================== The samples in this folder show some ways to customize dotnet-watch. For full details on available settings and configuration, see the [README for the dotnet-watch](../../src/dotnet-watch/README.md) project. ================================================ FILE: samples/dotnet-watch/WatchJavascriptFiles/Program.cs ================================================ // 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 Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; namespace WatchJavascriptFiles { public class Program { public static void Main(string[] args) { var host = new WebHostBuilder() .UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) .Configure(app => app.Run(async (context) => { await context.Response.WriteAsync("Hello World!"); })) .Build(); host.Run(); } } } ================================================ FILE: samples/dotnet-watch/WatchJavascriptFiles/README.md ================================================ Watch JavaScript files with dotnet-watch ======================================== ## Prerequisites Install .NET Core command line. ## Usage Open a terminal to the directory containing this project. ``` dotnet watch run ``` Changing the .csproj file, or \*.js file in wwwroot, or any \*.cs file will cause dotnet-watch to restart the website. ================================================ FILE: samples/dotnet-watch/WatchJavascriptFiles/WatchJavascriptFiles.csproj ================================================ netcoreapp3.0 ================================================ FILE: samples/dotnet-watch/WatchJavascriptFiles/wwwroot/app.js ================================================ document.title = "My awesome website"; ================================================ FILE: samples/dotnet-watch/WatchMultipleProjects/README.md ================================================ Watch multiple projects with dotnet-watch ========================================= ## Prerequisites Install .NET Core command line. ## Usage Open a terminal to the directory containing this project. ``` dotnet watch msbuild /t:TestAndRun ``` The "TestAndRun" target in watch.proj will execute "dotnet test" on Test.csproj and then launch the website by calling "dotnet run" on Web.csproj. Changing any \*.cs file in Test/ or Web/, any \*.csproj file, or watch.proj, will cause dotnet-watch to relaunch the "TestAndRun" target from watch.proj. ================================================ FILE: samples/dotnet-watch/WatchMultipleProjects/Test/Test.csproj ================================================ netcoreapp3.0 ================================================ FILE: samples/dotnet-watch/WatchMultipleProjects/Test/UnitTest1.cs ================================================ // 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 Xunit; namespace Test { public class UnitTest1 { [Fact] public void Test1() { Assert.True(true); } } } ================================================ FILE: samples/dotnet-watch/WatchMultipleProjects/Web/Program.cs ================================================ // 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 Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; namespace Web { public class Program { public static void Main(string[] args) { var host = new WebHostBuilder() .UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) .Configure(app => app.Run(async (context) => { await context.Response.WriteAsync("Hello World!"); })) .Build(); host.Run(); } } } ================================================ FILE: samples/dotnet-watch/WatchMultipleProjects/Web/Web.csproj ================================================ netcoreapp3.0 ================================================ FILE: samples/dotnet-watch/WatchMultipleProjects/watch.csproj ================================================ netcoreapp3.0 false ================================================ FILE: shared/CliContext.cs ================================================ // 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.Extensions.Tools.Internal { public static class CliContext { /// /// dotnet -d|--diagnostics subcommand /// /// public static bool IsGlobalVerbose() { bool.TryParse(Environment.GetEnvironmentVariable("DOTNET_CLI_CONTEXT_VERBOSE"), out bool globalVerbose); return globalVerbose; } } } ================================================ FILE: shared/CommandLineApplicationExtensions.cs ================================================ // 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.Reflection; namespace Microsoft.Extensions.CommandLineUtils { internal static class CommandLineApplicationExtensions { public static CommandOption HelpOption(this CommandLineApplication app) => app.HelpOption("-?|-h|--help"); public static CommandOption VerboseOption(this CommandLineApplication app) => app.Option("-v|--verbose", "Show verbose output", CommandOptionType.NoValue, inherited: true); public static void OnExecute(this CommandLineApplication app, Action action) => app.OnExecute(() => { action(); return 0; }); public static void VersionOptionFromAssemblyAttributes(this CommandLineApplication app, Assembly assembly) => app.VersionOption("--version", GetInformationalVersion(assembly)); private static string GetInformationalVersion(Assembly assembly) { var attribute = assembly.GetCustomAttribute(); var versionAttribute = attribute == null ? assembly.GetName().Version.ToString() : attribute.InformationalVersion; return versionAttribute; } } } ================================================ FILE: shared/ConsoleReporter.cs ================================================ // 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.Extensions.Tools.Internal { public class ConsoleReporter : IReporter { private object _writeLock = new object(); public ConsoleReporter(IConsole console) : this(console, verbose: false, quiet: false) { } public ConsoleReporter(IConsole console, bool verbose, bool quiet) { Ensure.NotNull(console, nameof(console)); Console = console; IsVerbose = verbose; IsQuiet = quiet; } protected IConsole Console { get; } public bool IsVerbose { get; set; } public bool IsQuiet { get; set; } protected virtual void WriteLine(TextWriter writer, string message, ConsoleColor? color) { lock (_writeLock) { if (color.HasValue) { Console.ForegroundColor = color.Value; } writer.WriteLine(message); if (color.HasValue) { Console.ResetColor(); } } } public virtual void Error(string message) => WriteLine(Console.Error, message, ConsoleColor.Red); public virtual void Warn(string message) => WriteLine(Console.Out, message, ConsoleColor.Yellow); public virtual void Output(string message) { if (IsQuiet) { return; } WriteLine(Console.Out, message, color: null); } public virtual void Verbose(string message) { if (!IsVerbose) { return; } WriteLine(Console.Out, message, ConsoleColor.DarkGray); } } } ================================================ FILE: shared/DebugHelper.cs ================================================ // 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.Linq; namespace Microsoft.Extensions.Tools.Internal { public static class DebugHelper { [Conditional("DEBUG")] public static void HandleDebugSwitch(ref string[] args) { if (args.Length > 0 && string.Equals("--debug", args[0], StringComparison.OrdinalIgnoreCase)) { args = args.Skip(1).ToArray(); Console.WriteLine("Waiting for debugger to attach. Press ENTER to continue"); Console.WriteLine($"Process ID: {Process.GetCurrentProcess().Id}"); Console.ReadLine(); } } } } ================================================ FILE: shared/Ensure.cs ================================================ // 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.Extensions.Tools.Internal { internal static class Ensure { public static T NotNull(T obj, string paramName) where T : class { if (obj == null) { throw new ArgumentNullException(paramName); } return obj; } public static string NotNullOrEmpty(string obj, string paramName) { if (string.IsNullOrEmpty(obj)) { throw new ArgumentException("Value cannot be null or an empty string.", paramName); } return obj; } } } ================================================ FILE: shared/IConsole.cs ================================================ // 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.Extensions.Tools.Internal { public interface IConsole { event ConsoleCancelEventHandler CancelKeyPress; TextWriter Out { get; } TextWriter Error { get; } TextReader In { get; } bool IsInputRedirected { get; } bool IsOutputRedirected { get; } bool IsErrorRedirected { get; } ConsoleColor ForegroundColor { get; set; } void ResetColor(); } } ================================================ FILE: shared/IReporter.cs ================================================ // 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.Extensions.Tools.Internal { public interface IReporter { void Verbose(string message); void Output(string message); void Warn(string message); void Error(string message); } } ================================================ FILE: shared/NullReporter.cs ================================================ // 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.Extensions.Tools.Internal { public class NullReporter : IReporter { private NullReporter() { } public static IReporter Singleton { get; } = new NullReporter(); public void Verbose(string message) { } public void Output(string message) { } public void Warn(string message) { } public void Error(string message) { } } } ================================================ FILE: shared/PhysicalConsole.cs ================================================ // 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.Extensions.Tools.Internal { public class PhysicalConsole : IConsole { private PhysicalConsole() { Console.CancelKeyPress += (o, e) => { CancelKeyPress?.Invoke(o, e); }; } public static IConsole Singleton { get; } = new PhysicalConsole(); public event ConsoleCancelEventHandler CancelKeyPress; public TextWriter Error => Console.Error; public TextReader In => Console.In; public TextWriter Out => Console.Out; public bool IsInputRedirected => Console.IsInputRedirected; public bool IsOutputRedirected => Console.IsOutputRedirected; public bool IsErrorRedirected => Console.IsErrorRedirected; public ConsoleColor ForegroundColor { get => Console.ForegroundColor; set => Console.ForegroundColor = value; } public void ResetColor() => Console.ResetColor(); } } ================================================ FILE: src/Directory.Build.props ================================================ ================================================ FILE: src/Directory.Build.targets ================================================ ================================================ FILE: src/Microsoft.AspNetCore.DeveloperCertificates.XPlat/CertificateGenerator.cs ================================================ using System; using Microsoft.AspNetCore.Certificates.Generation; namespace Microsoft.AspNetCore.DeveloperCertificates.XPlat { public static class CertificateGenerator { public static void GenerateAspNetHttpsCertificate() { var manager = new CertificateManager(); var now = DateTimeOffset.Now; manager.EnsureAspNetCoreHttpsDevelopmentCertificate(now, now.AddYears(1)); } } } ================================================ FILE: src/Microsoft.AspNetCore.DeveloperCertificates.XPlat/Microsoft.AspNetCore.DeveloperCertificates.XPlat.csproj ================================================ netcoreapp3.0 Package for the CLI first run experience. $(DefineConstants);XPLAT aspnet;cli ================================================ FILE: src/Microsoft.HttpRepl/AggregateDirectoryStructure.cs ================================================ // 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; 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; } } ================================================ FILE: src/Microsoft.HttpRepl/Commands/BaseHttpCommand.cs ================================================ // 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; using System.Net.Http; using System.Net.Http.Headers; 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 NoFormattingOption = nameof(NoFormattingOption); private const string StreamingOption = nameof(StreamingOption); 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; } public 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", })) .WithOption(new CommandOptionSpecification(NoFormattingOption, maximumOccurrences: 1, forms: new[] { "--no-formatting", "-F" })) .WithOption(new CommandOptionSpecification(StreamingOption, maximumOccurrences: 1, forms: new[] { "--streaming", "-s" })); 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 && (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".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]) { int equalsIndex = header.Text.IndexOfAny(HeaderSeparatorChars); if (equalsIndex < 0) { shellState.ConsoleManager.Error.WriteLine("Headers must be formatted as {header}={value} or {header}:{value}".SetColor(programState.ErrorColor)); 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 (!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; if (!File.Exists(filePath)) { shellState.ConsoleManager.Error.WriteLine($"Content file {filePath} does not exist".SetColor(programState.ErrorColor)); 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}}\"`".SetColor(programState.ErrorColor)); return; } deleteFile = true; filePath = Path.GetTempFileName(); string exampleBody = programState.GetExampleBody(commandInput.Arguments.Count > 0 ? commandInput.Arguments[0].Text : string.Empty, ref 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(); } } if (string.IsNullOrEmpty(contentType)) { contentType = "application/json"; } byte[] data = noBody ? new byte[0] : string.IsNullOrEmpty(bodyContent) ? File.ReadAllBytes(filePath) : Encoding.UTF8.GetBytes(bodyContent); HttpContent content = new ByteArrayContent(data); content.Headers.ContentType = new MediaTypeHeaderValue(contentType); 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, commandInput, shellState.ConsoleManager, response, programState.EchoRequest, headersTarget, bodyTarget, cancellationToken).ConfigureAwait(false); } 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); 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.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(commandInput, 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(commandInput, programState, consoleManager, response.Content, bodyFileWriter, cancellationToken).ConfigureAwait(false); } bodyFileWriter.Flush(); bodyFileWriter.Close(); bodyFileWriter.Dispose(); consoleManager.WriteLine(); } 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)) { contentType = contentTypeValues.FirstOrDefault()?.Split(';').FirstOrDefault(); } contentType = contentType?.ToUpperInvariant() ?? "text/plain"; if (commandInput.Options[NoFormattingOption].Count == 0) { 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; } } } 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) { 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) { 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; } } } ================================================ FILE: src/Microsoft.HttpRepl/Commands/ChangeDirectoryCommand.cs ================================================ // 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.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 { 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; } } 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; } public override CommandInputSpecification InputSpec { get; } = CommandInputSpecification.Create("cd") .MaximumArgCount(1) .Finish(); protected override string GetHelpDetails(IShellState shellState, HttpState programState, DefaultCommandInput commandInput, ICoreParseResult parseResult) { 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 help.ToString(); } 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); } } } ================================================ FILE: src/Microsoft.HttpRepl/Commands/ClearCommand.cs ================================================ // 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; 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; } } } ================================================ FILE: src/Microsoft.HttpRepl/Commands/ConfigCommand.cs ================================================ // 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; 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".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)".SetColor(programState.ErrorColor)); 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".SetColor(programState.ErrorColor)); 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".SetColor(programState.ErrorColor)); return; } List configItems = await response.Content.ReadAsAsync>(cancellationToken).ConfigureAwait(false); foreach (ConfigItem item in configItems) { shellState.ConsoleManager.WriteLine($"{item.Key.Cyan()}: {item.Value}"); } } public 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"; } } } ================================================ FILE: src/Microsoft.HttpRepl/Commands/DeleteCommand.cs ================================================ // 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 { protected override string Verb => "delete"; protected override bool RequiresBody => false; } } ================================================ FILE: src/Microsoft.HttpRepl/Commands/EchoCommand.cs ================================================ // 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.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 { 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'".SetColor(programState.ErrorColor)); 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; } public override CommandInputSpecification InputSpec { get; } = CommandInputSpecification.Create("echo").ExactArgCount(1).Finish(); protected override string GetHelpDetails(IShellState shellState, HttpState programState, DefaultCommandInput commandInput, ICoreParseResult parseResult) { 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) { 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; } } } ================================================ FILE: src/Microsoft.HttpRepl/Commands/ExitCommand.cs ================================================ // 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 { public class ExitCommand : CommandWithStructuredInputBase { protected override Task ExecuteAsync(IShellState shellState, object programState, DefaultCommandInput commandInput, ICoreParseResult parseResult, CancellationToken cancellationToken) { shellState.IsExiting = true; return Task.CompletedTask; } public override CommandInputSpecification InputSpec { get; } = CommandInputSpecification.Create("exit").ExactArgCount(0).Finish(); protected override string GetHelpDetails(IShellState shellState, object programState, DefaultCommandInput commandInput, ICoreParseResult parseResult) { 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) { return "exit - Exits the shell"; } } } ================================================ FILE: src/Microsoft.HttpRepl/Commands/Formatter.cs ================================================ // 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 int _prefix; private int _maxDepth; public void RegisterEntry(int prefixLength, int depth) { 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; } } } ================================================ FILE: src/Microsoft.HttpRepl/Commands/GetCommand.cs ================================================ // 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 { protected override string Verb => "get"; protected override bool RequiresBody => false; } } ================================================ FILE: src/Microsoft.HttpRepl/Commands/HeadCommand.cs ================================================ // 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 { protected override string Verb => "head"; protected override bool RequiresBody => false; } } ================================================ FILE: src/Microsoft.HttpRepl/Commands/HelpCommand.cs ================================================ // 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; using System.Threading.Tasks; 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 { 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 async Task ExecuteAsync(IShellState shellState, HttpState programState, ICoreParseResult parseResult, CancellationToken cancellationToken) { if (shellState.CommandDispatcher is ICommandDispatcher dispatcher) { if (parseResult.Sections.Count == 1) { CoreGetHelp(shellState, dispatcher, programState); } 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(); 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; } } } if (!anyHelp) { //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); } } //Structure is null because, for example, SwaggerEndpoint exists but is not reachable. if (programState.Structure != null) { 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"); } } } } } 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; } public void CoreGetHelp(IShellState shellState, ICommandDispatcher dispatcher, HttpState 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(); 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(); } } } ================================================ FILE: src/Microsoft.HttpRepl/Commands/ListCommand.cs ================================================ // 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; 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 { public class ListCommand : CommandWithStructuredInputBase { private const string RecursiveOption = nameof(RecursiveOption); 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; } 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; } 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, ".", thisDirMethod)); if (s.Parent != null) { 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; 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()); } } 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); } } 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) { 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 - List known routes for the current location"; } 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; } } } ================================================ FILE: src/Microsoft.HttpRepl/Commands/OptionsCommand.cs ================================================ // 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 { protected override string Verb => "options"; protected override bool RequiresBody => false; } } ================================================ FILE: src/Microsoft.HttpRepl/Commands/PatchCommand.cs ================================================ // 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 { protected override string Verb => "patch"; protected override bool RequiresBody => true; } } ================================================ FILE: src/Microsoft.HttpRepl/Commands/PostCommand.cs ================================================ // 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 { protected override string Verb => "post"; protected override bool RequiresBody => true; } } ================================================ FILE: src/Microsoft.HttpRepl/Commands/PrefCommand.cs ================================================ // 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.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 { 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) { var helpText = new StringBuilder(); helpText.Append("Usage: ".Bold()); if (commandInput.Arguments.Count == 0 || !_allowedSubcommands.Contains(commandInput.Arguments[0]?.Text)) { helpText.AppendLine("pref [get/set] {setting} [{value}] - Get or sets a preference to a particular value"); } else if (string.Equals(commandInput.Arguments[0].Text, "get", StringComparison.OrdinalIgnoreCase)) { helpText.AppendLine("pref get [{setting}] - Gets the value of the specified preference or lists all preferences if no preference is specified"); } else { 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) { 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".SetColor(programState.ErrorColor)); } 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").SetColor(programState.ErrorColor)); } } else { foreach (KeyValuePair entry in programState.Preferences.OrderBy(x => x.Key)) { shellState.ConsoleManager.WriteLine($"{entry.Key}={entry.Value}"); } } return Task.CompletedTask; } public 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; } } } ================================================ FILE: src/Microsoft.HttpRepl/Commands/PutCommand.cs ================================================ // 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 { protected override string Verb => "put"; protected override bool RequiresBody => true; } } ================================================ FILE: src/Microsoft.HttpRepl/Commands/RunCommand.cs ================================================ // 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.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; 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)) { 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; } 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; } } } ================================================ FILE: src/Microsoft.HttpRepl/Commands/SetBaseCommand.cs ================================================ // 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.Net.Http; using System.Net.Sockets; 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 { 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".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.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); } 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); } } } } } 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)) { var helpText = new StringBuilder(); helpText.Append("Usage: ".Bold()); helpText.AppendLine($"set base [uri]"); helpText.AppendLine(); helpText.AppendLine(Description); return helpText.ToString(); } 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; } } } ================================================ FILE: src/Microsoft.HttpRepl/Commands/SetDiagCommand.cs ================================================ // 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; 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".SetColor(programState.ErrorColor)); } 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".SetColor(programState.ErrorColor)); 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".SetColor(programState.ErrorColor)); 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; } } } ================================================ FILE: src/Microsoft.HttpRepl/Commands/SetHeaderCommand.cs ================================================ // 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.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 { 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) { 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) { 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; } } } ================================================ FILE: src/Microsoft.HttpRepl/Commands/SetSwaggerCommand.cs ================================================ // 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; 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, parameterSetsByContentType.Key, 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".SetColor(programState.ErrorColor)); } else { await CreateDirectoryStructureForSwaggerEndpointAsync(shellState, programState, serverUri, cancellationToken).ConfigureAwait(false); } } 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); DirectoryStructure d = new DirectoryStructure(null); foreach (EndpointMetadata entry in doc) { FillDirectoryInfo(d, entry); } programState.SwaggerStructure = !cancellationToken.IsCancellationRequested ? d : null; } catch { programState.SwaggerStructure = null; } } } } ================================================ FILE: src/Microsoft.HttpRepl/Commands/TreeNode.cs ================================================ // 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 { 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); } } } ================================================ FILE: src/Microsoft.HttpRepl/Commands/UICommand.cs ================================================ // 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; 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".SetColor(programState.ErrorColor)); return Task.CompletedTask; } Uri uri = new Uri(programState.BaseAddress, "swagger"); string agent = "cmd"; string agentParam = $"/c start {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; } } } ================================================ FILE: src/Microsoft.HttpRepl/Diagnostics/ConfigItem.cs ================================================ // 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 { public string Key { get; set; } public string Value { get; set; } } } ================================================ FILE: src/Microsoft.HttpRepl/Diagnostics/DiagEndpoint.cs ================================================ // 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 { public string DisplayName { get; set; } public string Url { get; set; } public DiagEndpointMetadata[] Metadata { get; set; } } } ================================================ FILE: src/Microsoft.HttpRepl/Diagnostics/DiagEndpointMetadata.cs ================================================ // 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 { public object Item { get; set; } public string Type { get; set; } public string[] Interfaces { get; set; } } } ================================================ FILE: src/Microsoft.HttpRepl/Diagnostics/DiagItem.cs ================================================ // 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 { public string DisplayName { get; set; } public string Description { get; set; } public string Url { get; set; } } } ================================================ FILE: src/Microsoft.HttpRepl/Diagnostics/DiagnosticsState.cs ================================================ // 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 { public class DiagnosticsState { public string DiagnosticsEndpoint { get; set; } public IReadOnlyList DiagnosticItems { get; internal set; } public IDirectoryStructure DiagEndpointsStructure { get; set; } } } ================================================ FILE: src/Microsoft.HttpRepl/DirectoryStructure.cs ================================================ // 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; 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 _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(ref 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)) { if (_fallbackContentTypeStringsByMethod.TryGetValue(method, out string newContentType)) { contentType = newContentType; } 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 contentType, string fallbackBodyString) { _fallbackBodyStringsByMethod[method] = fallbackBodyString; _fallbackContentTypeStringsByMethod[method] = contentType; } } } ================================================ FILE: src/Microsoft.HttpRepl/DirectoryStructureExtensions.cs ================================================ // 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 { 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; } } } ================================================ FILE: src/Microsoft.HttpRepl/Formatting/JsonVisitor.cs ================================================ // 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; 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(); } } } ================================================ FILE: src/Microsoft.HttpRepl/HttpState.cs ================================================ // 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; using System.Net.Http; using System.Runtime.InteropServices; using Microsoft.HttpRepl.Diagnostics; using Microsoft.HttpRepl.Preferences; using Microsoft.Repl.ConsoleHandling; namespace Microsoft.HttpRepl { public class HttpState { private string _userProfileDir; private string _prefsFilePath; 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; } 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 Uri SwaggerEndpoint { get; set; } 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, 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(ref 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; } } } ================================================ FILE: src/Microsoft.HttpRepl/IDirectoryStructure.cs ================================================ // 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 IDirectoryStructure { IEnumerable DirectoryNames { get; } IDirectoryStructure Parent { get; } IDirectoryStructure GetChildDirectory(string name); IRequestInfo RequestInfo { get; } } } ================================================ FILE: src/Microsoft.HttpRepl/IRequestInfo.cs ================================================ // 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(ref string contentType, string method); } } ================================================ FILE: src/Microsoft.HttpRepl/Microsoft.HttpRepl.csproj ================================================  Exe netcoreapp3.0 true dotnet-httprepl latest Command line tool to for making HTTP calls and viewing their results. dotnet;http;httprepl win-x64;win-x86 false ================================================ FILE: src/Microsoft.HttpRepl/OpenApi/Either.cs ================================================ // 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 { 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); } } } ================================================ FILE: src/Microsoft.HttpRepl/OpenApi/EitherConverter.cs ================================================ // 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; 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(); } } } ================================================ FILE: src/Microsoft.HttpRepl/OpenApi/EndpointMetadata.cs ================================================ // 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 { public class EndpointMetadata { public EndpointMetadata(string path, IReadOnlyDictionary>> requestsByMethodAndContentType) { Path = path; AvailableRequests = requestsByMethodAndContentType ?? new Dictionary>>(); } public string Path { get; } public IReadOnlyDictionary>> AvailableRequests { get; } } } ================================================ FILE: src/Microsoft.HttpRepl/OpenApi/EndpointMetadataReader.cs ================================================ // 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; 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; } } } ================================================ FILE: src/Microsoft.HttpRepl/OpenApi/IEndpointMetadataReader.cs ================================================ // 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; namespace Microsoft.HttpRepl.OpenApi { public interface IEndpointMetadataReader { bool CanHandle(JObject document); IEnumerable ReadMetadata(JObject document); } } ================================================ FILE: src/Microsoft.HttpRepl/OpenApi/OpenApiV3EndpointMetadataReader.cs ================================================ // 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 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; } } } ================================================ FILE: src/Microsoft.HttpRepl/OpenApi/Parameter.cs ================================================ // 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 { public string Name { get; set; } public string Location { get; set; } public bool IsRequired { get; set; } public Schema Schema { get; set; } } } ================================================ FILE: src/Microsoft.HttpRepl/OpenApi/PointerUtil.cs ================================================ // 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; 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) { JToken cursor = root; 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); } 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 string fragment = loadTarget.Fragment; if (fragment.StartsWith('#')) { fragment = fragment.Substring(1); } string[] parts = fragment.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); 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; } } } ================================================ FILE: src/Microsoft.HttpRepl/OpenApi/Schema.cs ================================================ // 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; 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; } } } ================================================ FILE: src/Microsoft.HttpRepl/OpenApi/SwaggerV1EndpointMetadataReader.cs ================================================ // 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 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; } } } ================================================ FILE: src/Microsoft.HttpRepl/OpenApi/SwaggerV2EndpointMetadataReader.cs ================================================ // 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 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; } } } ================================================ FILE: src/Microsoft.HttpRepl/Preferences/IJsonConfig.cs ================================================ // 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 { 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; } } } ================================================ FILE: src/Microsoft.HttpRepl/Preferences/JsonConfig.cs ================================================ // 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 { 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; } } } ================================================ FILE: src/Microsoft.HttpRepl/Preferences/RequestConfig.cs ================================================ // 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 { 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); } } ================================================ FILE: src/Microsoft.HttpRepl/Preferences/RequestOrResponseConfig.cs ================================================ // 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 { 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); } } ================================================ FILE: src/Microsoft.HttpRepl/Preferences/ResponseConfig.cs ================================================ // 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 { 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); } } ================================================ FILE: src/Microsoft.HttpRepl/Preferences/WellKnownPreference.cs ================================================ // 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; 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; } } } 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"; 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 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"; 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 string SwaggerRequeryBehavior { get; } = "swagger.requery"; 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; } } } ================================================ FILE: src/Microsoft.HttpRepl/Program.cs ================================================ // 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; namespace Microsoft.HttpRepl { class Program { static async Task Main(string[] args) { var state = new HttpState(); if (Console.IsOutputRedirected) { Reporter.Error.WriteLine("Cannot start the REPL when output is being redirected".SetColor(state.ErrorColor)); return; } 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) { 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(); 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); } Task result = shell.RunAsync(source.Token); await result.ConfigureAwait(false); } } } ================================================ FILE: src/Microsoft.HttpRepl/Properties/launchSettings.json ================================================ { "profiles": { "Microsoft.HttpRepl": { "commandName": "Project", "commandLineArgs": "http://localhost" } } } ================================================ FILE: src/Microsoft.HttpRepl/Suggestions/HeaderCompletion.cs ================================================ // 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; 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; } } } } ================================================ FILE: src/Microsoft.HttpRepl/Suggestions/ServerPathCompletion.cs ================================================ // 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; 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; } } } ================================================ FILE: src/Microsoft.Repl/Commanding/CommandHistory.cs ================================================ // 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 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); } } } ================================================ FILE: src/Microsoft.Repl/Commanding/CommandInputLocation.cs ================================================ // 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 { CommandName, Argument, OptionName, OptionValue } } ================================================ FILE: src/Microsoft.Repl/Commanding/CommandInputProcessingIssue.cs ================================================ // 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 { public CommandInputProcessingIssueKind Kind { get; } public string Text { get; } public CommandInputProcessingIssue(CommandInputProcessingIssueKind kind, string text) { Kind = kind; Text = text; } } } ================================================ FILE: src/Microsoft.Repl/Commanding/CommandInputProcessingIssueKind.cs ================================================ // 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 { CommandMismatch, ArgumentCountOutOfRange, UnknownOption, OptionUseCountOutOfRange, MissingRequiredOptionInput, } } ================================================ FILE: src/Microsoft.Repl/Commanding/CommandInputSpecification.cs ================================================ // 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 { 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); } } } ================================================ FILE: src/Microsoft.Repl/Commanding/CommandInputSpecificationBuilder.cs ================================================ // 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 List> _name; private char _optionPreamble; private int _minimumArgs; private int _maximumArgs; private readonly List _options = new List(); public CommandInputSpecificationBuilder(IReadOnlyList name) { _name = new List> { 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); } public CommandInputSpecificationBuilder AlternateName(string baseName, params string[] additionalNameParts) { List nameParts = new List { baseName }; nameParts.AddRange(additionalNameParts); _name.Add(nameParts); return this; } } } ================================================ FILE: src/Microsoft.Repl/Commanding/CommandOptionSpecification.cs ================================================ // 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 { 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; } } } ================================================ FILE: src/Microsoft.Repl/Commanding/CommandWithStructuredInputBase.cs ================================================ // 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; 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) { IReadOnlyList commandName = null; for (int j = 0; j < InputSpec.CommandName.Count; ++j) { bool success = true; for (int i = 0; i < parseResult.SelectedSection; ++i) { if (!string.Equals(InputSpec.CommandName[j][i], parseResult.Sections[i], StringComparison.OrdinalIgnoreCase)) { success = false; break; } } if (success) { commandName = InputSpec.CommandName[j]; break; } } if (commandName is null) { return null; } if (commandName[parseResult.SelectedSection].StartsWith(normalCompletionString, StringComparison.OrdinalIgnoreCase)) { return new[] {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); public abstract CommandInputSpecification InputSpec { get; } } } ================================================ FILE: src/Microsoft.Repl/Commanding/DefaultCommandDispatcher.cs ================================================ // 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; 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 CollectSuggestions(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()); shellState.ConsoleManager.Error.WriteLine("Execute 'help' to see available commands".Red().Bold()); } } public void OnReady(IShellState shellState) { if (!_isReady) { _onReady(shellState); shellState.ConsoleManager.ResetCommandStart(); _isReady = true; } } } } ================================================ FILE: src/Microsoft.Repl/Commanding/DefaultCommandInput.cs ================================================ // 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; 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 = null; List commandNameElements = null; foreach (IReadOnlyList commandName in spec.CommandName) { if (TryProcessCommandName(commandName, parseResult, out List nameElements, out issues)) { commandNameElements = nameElements; break; } } if (commandNameElements is null) { 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; } 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; } public IReadOnlyList Arguments { get; } public IReadOnlyDictionary> Options { get; } } } ================================================ FILE: src/Microsoft.Repl/Commanding/ICommand.cs ================================================ // 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; 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); } } ================================================ FILE: src/Microsoft.Repl/Commanding/ICommandDispatcher.cs ================================================ // 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; namespace Microsoft.Repl.Commanding { public interface ICommandDispatcher { IParser Parser { get; } IReadOnlyList CollectSuggestions(IShellState shellState); void OnReady(IShellState shellState); Task ExecuteCommandAsync(IShellState shellState, CancellationToken cancellationToken); } public interface ICommandDispatcher : ICommandDispatcher where TParseResult : ICoreParseResult { IEnumerable> Commands { get; } } } ================================================ FILE: src/Microsoft.Repl/Commanding/ICommandHistory.cs ================================================ // 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 { public interface ICommandHistory { string GetPreviousCommand(); string GetNextCommand(); void AddCommand(string command); IDisposable SuspendHistory(); } } ================================================ FILE: src/Microsoft.Repl/Commanding/InputElement.cs ================================================ // 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 { 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; } } } ================================================ FILE: src/Microsoft.Repl/ConsoleHandling/AllowedColors.cs ================================================ // 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 { [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 } } ================================================ FILE: src/Microsoft.Repl/ConsoleHandling/AnsiColorExtensions.cs ================================================ // 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 { 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; } } } } ================================================ FILE: src/Microsoft.Repl/ConsoleHandling/AnsiConsole.cs ================================================ // 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 { 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; } } } } } ================================================ FILE: src/Microsoft.Repl/ConsoleHandling/ConsoleManager.cs ================================================ // 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; 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; } int bufferWidth = Console.BufferWidth; int cursorTop = Console.CursorTop; int cursorLeft = Console.CursorLeft; while (positions < 0 && CaretPosition > 0) { if (-positions > bufferWidth) { if (cursorTop == 0) { cursorLeft = 0; positions = 0; } else { positions += bufferWidth; --cursorTop; } } else { int remaining = cursorLeft + positions; if (remaining >= 0) { cursorLeft = remaining; } else if (cursorTop == 0) { cursorLeft = 0; } else { --cursorTop; cursorLeft = bufferWidth + remaining; } positions = 0; } } while (positions > 0) { if (positions > bufferWidth) { positions -= bufferWidth; ++cursorTop; } else { int spaceLeftOnLine = bufferWidth - cursorLeft - 1; if (positions > spaceLeftOnLine) { ++cursorTop; cursorLeft = positions - spaceLeftOnLine - 1; } else { cursorLeft += positions; } positions = 0; } } Console.SetCursorPosition(cursorLeft, cursorTop); } } 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; } } } ================================================ FILE: src/Microsoft.Repl/ConsoleHandling/IConsoleManager.cs ================================================ // 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 { 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); } } ================================================ FILE: src/Microsoft.Repl/ConsoleHandling/IWritable.cs ================================================ // 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 { void Write(char c); void Write(string s); void WriteLine(); void WriteLine(string s); bool IsCaretVisible { get; set; } } } ================================================ FILE: src/Microsoft.Repl/ConsoleHandling/Point.cs ================================================ // 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 { 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})"; } } } ================================================ FILE: src/Microsoft.Repl/ConsoleHandling/Reporter.cs ================================================ // 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 { 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; } } } } ================================================ FILE: src/Microsoft.Repl/ConsoleHandling/Writable.cs ================================================ // 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 { 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); } } } } ================================================ FILE: src/Microsoft.Repl/Disposable.cs ================================================ // 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 { 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(); } } } ================================================ FILE: src/Microsoft.Repl/IShellState.cs ================================================ // 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; 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; } } } ================================================ FILE: src/Microsoft.Repl/Input/AsyncKeyPressHandler.cs ================================================ // 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; namespace Microsoft.Repl.Input { public delegate Task AsyncKeyPressHandler(ConsoleKeyInfo keyInfo, IShellState state, CancellationToken cancellationToken); } ================================================ FILE: src/Microsoft.Repl/Input/IInputManager.cs ================================================ // 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; namespace Microsoft.Repl.Input { public interface IInputManager { bool IsOverwriteMode { get; set; } IInputManager RegisterKeyHandler(ConsoleKey key, AsyncKeyPressHandler handler); IInputManager RegisterKeyHandler(ConsoleKey key, ConsoleModifiers modifiers, 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); } } ================================================ FILE: src/Microsoft.Repl/Input/InputManager.cs ================================================ // 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; 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 (!_handlers.TryGetValue(key, out Dictionary handlers)) { _handlers[key] = handlers = new Dictionary(); } if (handler == null) { handlers.Remove(default(ConsoleModifiers)); } else { 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; } 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() { string sttyFlags = null; if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.OSX)) { _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(); } } private static string GetTtyState() { ProcessStartInfo psi = new ProcessStartInfo("stty", "-g") { RedirectStandardOutput = true }; Process p = Process.Start(psi); p?.WaitForExit(); string result = p?.StandardOutput.ReadToEnd().Trim(); 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 Dictionary handlerLookup) && handlerLookup.TryGetValue(keyPress.Modifiers, 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); } //TODO: Verify on a mac whether these are still needed 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); } } //TODO: Register these like regular commands 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; } } } ================================================ FILE: src/Microsoft.Repl/Input/KeyHandlers.cs ================================================ // 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; 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.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); inputManager.RegisterKeyHandler(ConsoleKey.DownArrow, DownArrow); //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); //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; } } } ================================================ FILE: src/Microsoft.Repl/Microsoft.Repl.csproj ================================================  netcoreapp3.0 A framework for creating REPLs in .NET Core. dotnet;repl false ================================================ FILE: src/Microsoft.Repl/Parsing/CoreParseResult.cs ================================================ // 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; 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); } } } ================================================ FILE: src/Microsoft.Repl/Parsing/CoreParser.cs ================================================ // 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 { 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); } } } ================================================ FILE: src/Microsoft.Repl/Parsing/ICoreParseResult.cs ================================================ // 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 { 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); } } ================================================ FILE: src/Microsoft.Repl/Parsing/IParser.cs ================================================ // 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 { ICoreParseResult Parse(string commandText, int caretPosition); } public interface IParser : IParser { new TParseResult Parse(string commandText, int caretPosition); } } ================================================ FILE: src/Microsoft.Repl/Scripting/IScriptExecutor.cs ================================================ // 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; namespace Microsoft.Repl.Scripting { public interface IScriptExecutor { Task ExecuteScriptAsync(IShellState shellState, IEnumerable commandTexts, CancellationToken cancellationToken); } } ================================================ FILE: src/Microsoft.Repl/Scripting/ScriptExecutor.cs ================================================ // 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; 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); } } } } } } ================================================ FILE: src/Microsoft.Repl/Shell.cs ================================================ // 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; 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); } } } ================================================ FILE: src/Microsoft.Repl/ShellState.cs ================================================ // 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; 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; } } } ================================================ FILE: src/Microsoft.Repl/Suggestions/FileSystemCompletion.cs ================================================ // 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; 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; } } } ================================================ FILE: src/Microsoft.Repl/Suggestions/ISuggestionManager.cs ================================================ // 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 { void NextSuggestion(IShellState shellState); void PreviousSuggestion(IShellState shellState); } } ================================================ FILE: src/Microsoft.Repl/Suggestions/SuggestionManager.cs ================================================ // 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; 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.CollectSuggestions(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.CollectSuggestions(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); } } } ================================================ FILE: src/Microsoft.Repl/Utils.cs ================================================ // 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 { public static class Utils { public static string Stringify(this IReadOnlyList keys) { return string.Join("", keys); } } } ================================================ FILE: src/dotnet-dev-certs/Program.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Security.Cryptography.X509Certificates; using Microsoft.AspNetCore.Certificates.Generation; using Microsoft.Extensions.CommandLineUtils; using Microsoft.Extensions.Tools.Internal; namespace Microsoft.AspNetCore.DeveloperCertificates.Tools { internal class Program { private const int CriticalError = -1; private const int Success = 0; private const int ErrorCreatingTheCertificate = 1; private const int ErrorSavingTheCertificate = 2; private const int ErrorExportingTheCertificate = 3; private const int ErrorTrustingTheCertificate = 4; private const int ErrorUserCancelledTrustPrompt = 5; private const int ErrorNoValidCertificateFound = 6; private const int ErrorCertificateNotTrusted = 7; private const int ErrorCleaningUpCertificates = 8; public static readonly TimeSpan HttpsCertificateValidity = TimeSpan.FromDays(365); public static int Main(string[] args) { try { var app = new CommandLineApplication { Name = "dotnet dev-certs" }; app.Command("https", c => { var exportPath = c.Option("-ep|--export-path", "Full path to the exported certificate", CommandOptionType.SingleValue); var password = c.Option("-p|--password", "Password to use when exporting the certificate with the private key into a pfx file", CommandOptionType.SingleValue); var check = c.Option( "-c|--check", "Check for the existence of the certificate but do not perform any action", CommandOptionType.NoValue); var clean = c.Option( "--clean", "Cleans all HTTPS development certificates from the machine.", CommandOptionType.NoValue); CommandOption trust = null; if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { trust = c.Option("-t|--trust", "Trust the certificate on the current platform", CommandOptionType.NoValue); } var verbose = c.Option("-v|--verbose", "Display more debug information.", CommandOptionType.NoValue); var quiet = c.Option("-q|--quiet", "Display warnings and errors only.", CommandOptionType.NoValue); c.HelpOption("-h|--help"); c.OnExecute(() => { var reporter = new ConsoleReporter(PhysicalConsole.Singleton, verbose.HasValue(), quiet.HasValue()); if ((clean.HasValue() && (exportPath.HasValue() || password.HasValue() || trust?.HasValue() == true)) || (check.HasValue() && (exportPath.HasValue() || password.HasValue() || clean.HasValue()))) { reporter.Error(@"Incompatible set of flags. Sample usages 'dotnet dev-certs https --clean' 'dotnet dev-certs https --check --trust' 'dotnet dev-certs https -ep ./certificate.pfx -p password --trust'"); } if (check.HasValue()) { return CheckHttpsCertificate(trust, reporter); } if (clean.HasValue()) { return CleanHttpsCertificates(reporter); } return EnsureHttpsCertificate(exportPath, password, trust, reporter); }); }); app.HelpOption("-h|--help"); app.OnExecute(() => { app.ShowHelp(); return Success; }); return app.Execute(args); } catch { return CriticalError; } } private static int CleanHttpsCertificates(IReporter reporter) { var manager = new CertificateManager(); try { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { reporter.Output("Cleaning HTTPS development certificates from the machine. A prompt might get " + "displayed to confirm the removal of some of the certificates."); } if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { reporter.Output("Cleaning HTTPS development certificates from the machine. This operation might " + "require elevated privileges. If that is the case, a prompt for credentials will be displayed."); } manager.CleanupHttpsCertificates(); reporter.Verbose("HTTPS development certificates successfully removed from the machine."); return Success; } catch(Exception e) { reporter.Error("There was an error trying to clean HTTPS development certificates on this machine."); reporter.Error(e.Message); return ErrorCleaningUpCertificates; } } private static int CheckHttpsCertificate(CommandOption trust, IReporter reporter) { var now = DateTimeOffset.Now; var certificateManager = new CertificateManager(); var certificates = certificateManager.ListCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, isValid: true); if (certificates.Count == 0) { reporter.Verbose("No valid certificate found."); return ErrorNoValidCertificateFound; } else { reporter.Verbose("A valid certificate was found."); } if (trust != null && trust.HasValue()) { var store = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? StoreName.My : StoreName.Root; var trustedCertificates = certificateManager.ListCertificates(CertificatePurpose.HTTPS, store, StoreLocation.CurrentUser, isValid: true); if (!certificates.Any(c => certificateManager.IsTrusted(c))) { reporter.Verbose($@"The following certificates were found, but none of them is trusted: {string.Join(Environment.NewLine, certificates.Select(c => $"{c.Subject} - {c.Thumbprint}"))}"); return ErrorCertificateNotTrusted; } else { reporter.Verbose("A trusted certificate was found."); } } return Success; } private static int EnsureHttpsCertificate(CommandOption exportPath, CommandOption password, CommandOption trust, IReporter reporter) { var now = DateTimeOffset.Now; var manager = new CertificateManager(); if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) && trust?.HasValue() == true) { reporter.Warn("Trusting the HTTPS development certificate was requested. If the certificate is not " + "already trusted we will run the following command:" + Environment.NewLine + "'sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain <>'" + Environment.NewLine + "This command might prompt you for your password to install the certificate " + "on the system keychain."); } if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && trust?.HasValue() == true) { reporter.Warn("Trusting the HTTPS development certificate was requested. A confirmation prompt will be displayed " + "if the certificate was not previously trusted. Click yes on the prompt to trust the certificate."); } var result = manager.EnsureAspNetCoreHttpsDevelopmentCertificate( now, now.Add(HttpsCertificateValidity), exportPath.Value(), trust == null ? false : trust.HasValue(), password.HasValue(), password.Value()); switch (result) { case EnsureCertificateResult.Succeeded: reporter.Output("The HTTPS developer certificate was generated successfully."); if (exportPath.Value() != null) { reporter.Verbose($"The certificate was exported to {Path.GetFullPath(exportPath.Value())}"); } return Success; case EnsureCertificateResult.ValidCertificatePresent: reporter.Output("A valid HTTPS certificate is already present."); if (exportPath.Value() != null) { reporter.Verbose($"The certificate was exported to {Path.GetFullPath(exportPath.Value())}"); } return Success; case EnsureCertificateResult.ErrorCreatingTheCertificate: reporter.Error("There was an error creating the HTTPS developer certificate."); return ErrorCreatingTheCertificate; case EnsureCertificateResult.ErrorSavingTheCertificateIntoTheCurrentUserPersonalStore: reporter.Error("There was an error saving the HTTPS developer certificate to the current user personal certificate store."); return ErrorSavingTheCertificate; case EnsureCertificateResult.ErrorExportingTheCertificate: reporter.Warn("There was an error exporting HTTPS developer certificate to a file."); return ErrorExportingTheCertificate; case EnsureCertificateResult.FailedToTrustTheCertificate: reporter.Warn("There was an error trusting HTTPS developer certificate."); return ErrorTrustingTheCertificate; case EnsureCertificateResult.UserCancelledTrustStep: reporter.Warn("The user cancelled the trust step."); return ErrorUserCancelledTrustPrompt; default: reporter.Error("Something went wrong. The HTTPS developer certificate could not be created."); return CriticalError; } } } } ================================================ FILE: src/dotnet-dev-certs/README.md ================================================ dotnet-dev-certs ================ `dotnet-dev-certs` is a command line tool to generate certificates used in ASP.NET Core during development. ### How To Use Run `dotnet dev-certs --help` for more information about usage. ================================================ FILE: src/dotnet-dev-certs/dotnet-dev-certs.csproj ================================================ netcoreapp3.0 exe Command line tool to generate certificates used in ASP.NET Core during development. Microsoft.AspNetCore.DeveloperCertificates.Tools dotnet;developercertificates true win-x64;win-x86 ================================================ FILE: src/dotnet-sql-cache/Program.cs ================================================ // 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.Data; using System.Data.SqlClient; using System.Reflection; using Microsoft.Extensions.CommandLineUtils; using Microsoft.Extensions.Tools.Internal; namespace Microsoft.Extensions.Caching.SqlConfig.Tools { public class Program { private string _connectionString = null; private string _schemaName = null; private string _tableName = null; private readonly IConsole _console; public Program(IConsole console) { Ensure.NotNull(console, nameof(console)); _console = console; } public static int Main(string[] args) { return new Program(PhysicalConsole.Singleton).Run(args); } public int Run(string[] args) { DebugHelper.HandleDebugSwitch(ref args); try { var app = new CommandLineApplication { Name = "dotnet sql-cache", FullName = "SQL Server Cache Command Line Tool", Description = "Creates table and indexes in Microsoft SQL Server database to be used for distributed caching", }; app.HelpOption(); app.VersionOptionFromAssemblyAttributes(typeof(Program).GetTypeInfo().Assembly); var verbose = app.VerboseOption(); app.Command("create", command => { command.Description = app.Description; var connectionStringArg = command.Argument( "[connectionString]", "The connection string to connect to the database."); var schemaNameArg = command.Argument( "[schemaName]", "Name of the table schema."); var tableNameArg = command.Argument( "[tableName]", "Name of the table to be created."); command.HelpOption(); command.OnExecute(() => { var reporter = CreateReporter(verbose.HasValue()); if (string.IsNullOrEmpty(connectionStringArg.Value) || string.IsNullOrEmpty(schemaNameArg.Value) || string.IsNullOrEmpty(tableNameArg.Value)) { reporter.Error("Invalid input"); app.ShowHelp(); return 2; } _connectionString = connectionStringArg.Value; _schemaName = schemaNameArg.Value; _tableName = tableNameArg.Value; return CreateTableAndIndexes(reporter); }); }); // Show help information if no subcommand/option was specified. app.OnExecute(() => { app.ShowHelp(); return 2; }); return app.Execute(args); } catch (Exception exception) { CreateReporter(verbose: false).Error($"An error occurred. {exception.Message}"); return 1; } } private IReporter CreateReporter(bool verbose) => new ConsoleReporter(_console, verbose, quiet: false); private int CreateTableAndIndexes(IReporter reporter) { ValidateConnectionString(); using (var connection = new SqlConnection(_connectionString)) { connection.Open(); var sqlQueries = new SqlQueries(_schemaName, _tableName); var command = new SqlCommand(sqlQueries.TableInfo, connection); using (var reader = command.ExecuteReader(CommandBehavior.SingleRow)) { if (reader.Read()) { reporter.Warn( $"Table with schema '{_schemaName}' and name '{_tableName}' already exists. " + "Provide a different table name and try again."); return 1; } } using (var transaction = connection.BeginTransaction()) { try { command = new SqlCommand(sqlQueries.CreateTable, connection, transaction); reporter.Verbose($"Executing {command.CommandText}"); command.ExecuteNonQuery(); command = new SqlCommand( sqlQueries.CreateNonClusteredIndexOnExpirationTime, connection, transaction); reporter.Verbose($"Executing {command.CommandText}"); command.ExecuteNonQuery(); transaction.Commit(); reporter.Output("Table and index were created successfully."); } catch (Exception ex) { reporter.Error( $"An error occurred while trying to create the table and index. {ex.Message}"); transaction.Rollback(); return 1; } } } return 0; } private void ValidateConnectionString() { try { new SqlConnectionStringBuilder(_connectionString); } catch (Exception ex) { throw new ArgumentException( $"Invalid SQL Server connection string '{_connectionString}'. {ex.Message}", ex); } } } } ================================================ FILE: src/dotnet-sql-cache/README.md ================================================ dotnet-sql-cache ================ `dotnet-sql-cache` is a command line tool that creates table and indexes in Microsoft SQL Server database to be used for distributed caching ### How To Use Run `dotnet sql-cache --help` for more information about usage. ================================================ FILE: src/dotnet-sql-cache/SqlQueries.cs ================================================ // 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.Extensions.Caching.SqlConfig.Tools { internal class SqlQueries { private const string CreateTableFormat = "CREATE TABLE {0}(" + // Maximum size of primary key column is 900 bytes (898 bytes from key + 2 additional bytes used by the // Sql Server). In the case where the key is greater than 898 bytes, then it gets truncated. // - Add collation to the key column to make it case-sensitive "Id nvarchar(449) COLLATE SQL_Latin1_General_CP1_CS_AS NOT NULL, " + "Value varbinary(MAX) NOT NULL, " + "ExpiresAtTime datetimeoffset NOT NULL, " + "SlidingExpirationInSeconds bigint NULL," + "AbsoluteExpiration datetimeoffset NULL, " + "PRIMARY KEY (Id))"; private const string CreateNonClusteredIndexOnExpirationTimeFormat = "CREATE NONCLUSTERED INDEX Index_ExpiresAtTime ON {0}(ExpiresAtTime)"; private const string TableInfoFormat = "SELECT TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME, TABLE_TYPE " + "FROM INFORMATION_SCHEMA.TABLES " + "WHERE TABLE_SCHEMA = '{0}' " + "AND TABLE_NAME = '{1}'"; public SqlQueries(string schemaName, string tableName) { if (string.IsNullOrEmpty(schemaName)) { throw new ArgumentException("Schema name cannot be empty or null"); } if (string.IsNullOrEmpty(tableName)) { throw new ArgumentException("Table name cannot be empty or null"); } var tableNameWithSchema = string.Format( "{0}.{1}", DelimitIdentifier(schemaName), DelimitIdentifier(tableName)); CreateTable = string.Format(CreateTableFormat, tableNameWithSchema); CreateNonClusteredIndexOnExpirationTime = string.Format( CreateNonClusteredIndexOnExpirationTimeFormat, tableNameWithSchema); TableInfo = string.Format(TableInfoFormat, EscapeLiteral(schemaName), EscapeLiteral(tableName)); } public string CreateTable { get; } public string CreateNonClusteredIndexOnExpirationTime { get; } public string TableInfo { get; } // From EF's SqlServerQuerySqlGenerator private string DelimitIdentifier(string identifier) { return "[" + identifier.Replace("]", "]]") + "]"; } private string EscapeLiteral(string literal) { return literal.Replace("'", "''"); } } } ================================================ FILE: src/dotnet-sql-cache/dotnet-sql-cache.csproj ================================================ netcoreapp3.0 exe Command line tool to create tables and indexes in a Microsoft SQL Server database for distributed caching. cache;distributedcache;sqlserver true win-x64;win-x86 ================================================ FILE: src/dotnet-user-secrets/CommandLineOptions.cs ================================================ // 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.Reflection; using Microsoft.Extensions.CommandLineUtils; using Microsoft.Extensions.SecretManager.Tools.Internal; using Microsoft.Extensions.Tools.Internal; namespace Microsoft.Extensions.SecretManager.Tools { public class CommandLineOptions { public ICommand Command { get; set; } public string Configuration { get; private set; } public string Id { get; private set; } public bool IsHelp { get; private set; } public bool IsVerbose { get; private set; } public string Project { get; private set; } public static CommandLineOptions Parse(string[] args, IConsole console) { var app = new CommandLineApplication() { Out = console.Out, Error = console.Error, Name = "dotnet user-secrets", FullName = "User Secrets Manager", Description = "Manages user secrets" }; app.HelpOption(); app.VersionOptionFromAssemblyAttributes(typeof(Program).GetTypeInfo().Assembly); var optionVerbose = app.VerboseOption(); var optionProject = app.Option("-p|--project ", "Path to project. Defaults to searching the current directory.", CommandOptionType.SingleValue, inherited: true); var optionConfig = app.Option("-c|--configuration ", $"The project configuration to use. Defaults to 'Debug'.", CommandOptionType.SingleValue, inherited: true); // the escape hatch if project evaluation fails, or if users want to alter a secret store other than the one // in the current project var optionId = app.Option("--id", "The user secret ID to use.", CommandOptionType.SingleValue, inherited: true); var options = new CommandLineOptions(); app.Command("set", c => SetCommand.Configure(c, options, console)); app.Command("remove", c => RemoveCommand.Configure(c, options)); app.Command("list", c => ListCommand.Configure(c, options)); app.Command("clear", c => ClearCommand.Configure(c, options)); app.Command("init", c => InitCommandFactory.Configure(c, options)); // Show help information if no subcommand/option was specified. app.OnExecute(() => app.ShowHelp()); if (app.Execute(args) != 0) { // when command line parsing error in subcommand return null; } options.Configuration = optionConfig.Value(); options.Id = optionId.Value(); options.IsHelp = app.IsShowingInformation; options.IsVerbose = optionVerbose.HasValue(); options.Project = optionProject.Value(); return options; } } } ================================================ FILE: src/dotnet-user-secrets/Internal/ClearCommand.cs ================================================ // 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.Extensions.CommandLineUtils; namespace Microsoft.Extensions.SecretManager.Tools.Internal { internal class ClearCommand : ICommand { public static void Configure(CommandLineApplication command, CommandLineOptions options) { command.Description = "Deletes all the application secrets"; command.HelpOption(); command.OnExecute(() => { options.Command = new ClearCommand(); }); } public void Execute(CommandContext context) { context.SecretStore.Clear(); context.SecretStore.Save(); } } } ================================================ FILE: src/dotnet-user-secrets/Internal/CommandContext.cs ================================================ // 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.Extensions.Tools.Internal; namespace Microsoft.Extensions.SecretManager.Tools.Internal { public class CommandContext { public CommandContext( SecretsStore store, IReporter reporter, IConsole console) { SecretStore = store; Reporter = reporter; Console = console; } public IConsole Console { get; } public IReporter Reporter { get; } public SecretsStore SecretStore { get; } } } ================================================ FILE: src/dotnet-user-secrets/Internal/ICommand.cs ================================================ // 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.Extensions.SecretManager.Tools.Internal { public interface ICommand { void Execute(CommandContext context); } } ================================================ FILE: src/dotnet-user-secrets/Internal/InitCommand.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.IO; using System.Linq; using System.Xml.Linq; using System.Xml.XPath; using Microsoft.Extensions.CommandLineUtils; namespace Microsoft.Extensions.SecretManager.Tools.Internal { // Workaround used to handle the fact that the options have not been parsed at configuration time public class InitCommandFactory : ICommand { public CommandLineOptions Options { get; } internal static void Configure(CommandLineApplication command, CommandLineOptions options) { command.Description = "Set a user secrets ID to enable secret storage"; command.HelpOption(); command.OnExecute(() => { options.Command = new InitCommandFactory(options); }); } public InitCommandFactory(CommandLineOptions options) { Options = options; } public void Execute(CommandContext context) { new InitCommand(Options.Id, Options.Project).Execute(context); } public void Execute(CommandContext context, string workingDirectory) { new InitCommand(Options.Id, Options.Project).Execute(context, workingDirectory); } } public class InitCommand : ICommand { public string OverrideId { get; } public string ProjectPath { get; } public string WorkingDirectory { get; private set; } = Directory.GetCurrentDirectory(); public InitCommand(string id, string project) { OverrideId = id; ProjectPath = project; } public void Execute(CommandContext context, string workingDirectory) { WorkingDirectory = workingDirectory; Execute(context); } public void Execute(CommandContext context) { var projectPath = ResolveProjectPath(ProjectPath, WorkingDirectory); // Load the project file as XML var projectDocument = XDocument.Load(projectPath); // Accept the `--id` CLI option to the main app string newSecretsId = string.IsNullOrWhiteSpace(OverrideId) ? Guid.NewGuid().ToString() : OverrideId; // Confirm secret ID does not contain invalid characters if (Path.GetInvalidPathChars().Any(invalidChar => newSecretsId.Contains(invalidChar))) { throw new ArgumentException(Resources.FormatError_InvalidSecretsId(newSecretsId)); } var existingUserSecretsId = projectDocument.XPathSelectElements("//UserSecretsId").FirstOrDefault(); // Check if a UserSecretsId is already set if (existingUserSecretsId != default) { // Only set the UserSecretsId if the user specified an explicit value if (string.IsNullOrWhiteSpace(OverrideId)) { context.Reporter.Output(Resources.FormatMessage_ProjectAlreadyInitialized(projectPath)); return; } existingUserSecretsId.SetValue(newSecretsId); } else { // Find the first non-conditional PropertyGroup var propertyGroup = projectDocument.Root.DescendantNodes() .FirstOrDefault(node => node is XElement el && el.Name == "PropertyGroup" && el.Attributes().All(attr => attr.Name != "Condition")) as XElement; // No valid property group, create a new one if (propertyGroup == null) { propertyGroup = new XElement("PropertyGroup"); projectDocument.Root.AddFirst(propertyGroup); } // Add UserSecretsId element propertyGroup.Add(new XElement("UserSecretsId", newSecretsId)); } projectDocument.Save(projectPath); context.Reporter.Output(Resources.FormatMessage_SetUserSecretsIdForProject(newSecretsId, projectPath)); } private static string ResolveProjectPath(string name, string path) { var finder = new MsBuildProjectFinder(path); return finder.FindMsBuildProject(name); } } } ================================================ FILE: src/dotnet-user-secrets/Internal/ListCommand.cs ================================================ // 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.Extensions.CommandLineUtils; using Newtonsoft.Json; using Newtonsoft.Json.Linq; namespace Microsoft.Extensions.SecretManager.Tools.Internal { internal class ListCommand : ICommand { private readonly bool _jsonOutput; public static void Configure(CommandLineApplication command, CommandLineOptions options) { command.Description = "Lists all the application secrets"; command.HelpOption(); var optJson = command.Option("--json", "Use json output. JSON is wrapped by '//BEGIN' and '//END'", CommandOptionType.NoValue); command.OnExecute(() => { options.Command = new ListCommand(optJson.HasValue()); }); } public ListCommand(bool jsonOutput) { _jsonOutput = jsonOutput; } public void Execute(CommandContext context) { if (_jsonOutput) { ReportJson(context); return; } if (context.SecretStore.Count == 0) { context.Reporter.Output(Resources.Error_No_Secrets_Found); } else { foreach (var secret in context.SecretStore.AsEnumerable()) { context.Reporter.Output(Resources.FormatMessage_Secret_Value_Format(secret.Key, secret.Value)); } } } private void ReportJson(CommandContext context) { var jObject = new JObject(); foreach(var item in context.SecretStore.AsEnumerable()) { jObject[item.Key] = item.Value; } context.Reporter.Output("//BEGIN"); context.Reporter.Output(jObject.ToString(Formatting.Indented)); context.Reporter.Output("//END"); } } } ================================================ FILE: src/dotnet-user-secrets/Internal/MsBuildProjectFinder.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.IO; using System.Linq; using Microsoft.Extensions.Tools.Internal; namespace Microsoft.Extensions.SecretManager.Tools.Internal { internal class MsBuildProjectFinder { private readonly string _directory; public MsBuildProjectFinder(string directory) { Ensure.NotNullOrEmpty(directory, nameof(directory)); _directory = directory; } public string FindMsBuildProject(string project) { var projectPath = project ?? _directory; if (!Path.IsPathRooted(projectPath)) { projectPath = Path.Combine(_directory, projectPath); } if (Directory.Exists(projectPath)) { var projects = Directory.EnumerateFileSystemEntries(projectPath, "*.*proj", SearchOption.TopDirectoryOnly) .Where(f => !".xproj".Equals(Path.GetExtension(f), StringComparison.OrdinalIgnoreCase)) .ToList(); if (projects.Count > 1) { throw new FileNotFoundException(Resources.FormatError_MultipleProjectsFound(projectPath)); } if (projects.Count == 0) { throw new FileNotFoundException(Resources.FormatError_NoProjectsFound(projectPath)); } return projects[0]; } if (!File.Exists(projectPath)) { throw new FileNotFoundException(Resources.FormatError_ProjectPath_NotFound(projectPath)); } return projectPath; } } } ================================================ FILE: src/dotnet-user-secrets/Internal/ProjectIdResolver.cs ================================================ // 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.Linq; using System.Reflection; using Microsoft.Extensions.CommandLineUtils; using Microsoft.Extensions.Tools.Internal; namespace Microsoft.Extensions.SecretManager.Tools.Internal { public class ProjectIdResolver { private const string DefaultConfig = "Debug"; private readonly IReporter _reporter; private readonly string _targetsFile; private readonly string _workingDirectory; public ProjectIdResolver(IReporter reporter, string workingDirectory) { _workingDirectory = workingDirectory; _reporter = reporter; _targetsFile = FindTargetsFile(); } public string Resolve(string project, string configuration) { var finder = new MsBuildProjectFinder(_workingDirectory); var projectFile = finder.FindMsBuildProject(project); _reporter.Verbose(Resources.FormatMessage_Project_File_Path(projectFile)); configuration = !string.IsNullOrEmpty(configuration) ? configuration : DefaultConfig; var outputFile = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); try { var args = new[] { "msbuild", projectFile, "/nologo", "/t:_ExtractUserSecretsMetadata", // defined in SecretManager.targets "/p:_UserSecretsMetadataFile=" + outputFile, "/p:Configuration=" + configuration, "/p:CustomAfterMicrosoftCommonTargets=" + _targetsFile, "/p:CustomAfterMicrosoftCommonCrossTargetingTargets=" + _targetsFile, }; var psi = new ProcessStartInfo { FileName = DotNetMuxer.MuxerPathOrDefault(), Arguments = ArgumentEscaper.EscapeAndConcatenate(args), RedirectStandardOutput = true, RedirectStandardError = true }; #if DEBUG _reporter.Verbose($"Invoking '{psi.FileName} {psi.Arguments}'"); #endif var process = Process.Start(psi); process.WaitForExit(); if (process.ExitCode != 0) { _reporter.Verbose(process.StandardOutput.ReadToEnd()); _reporter.Verbose(process.StandardError.ReadToEnd()); throw new InvalidOperationException(Resources.FormatError_ProjectFailedToLoad(projectFile)); } var id = File.ReadAllText(outputFile)?.Trim(); if (string.IsNullOrEmpty(id)) { throw new InvalidOperationException(Resources.FormatError_ProjectMissingId(projectFile)); } return id; } finally { TryDelete(outputFile); } } private string FindTargetsFile() { var assemblyDir = Path.GetDirectoryName(typeof(ProjectIdResolver).Assembly.Location); var searchPaths = new[] { Path.Combine(AppContext.BaseDirectory, "assets"), Path.Combine(assemblyDir, "assets"), AppContext.BaseDirectory, assemblyDir, }; var targetPath = searchPaths.Select(p => Path.Combine(p, "SecretManager.targets")).FirstOrDefault(File.Exists); if (targetPath == null) { _reporter.Error("Fatal error: could not find SecretManager.targets"); return null; } return targetPath; } private static void TryDelete(string file) { try { if (File.Exists(file)) { File.Delete(file); } } catch { // whatever } } } } ================================================ FILE: src/dotnet-user-secrets/Internal/ReadableJsonConfigurationSource.cs ================================================ // 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 Microsoft.Extensions.Configuration.Json; namespace Microsoft.Extensions.SecretManager.Tools.Internal { public class ReadableJsonConfigurationProvider : JsonConfigurationProvider { public ReadableJsonConfigurationProvider() : base(new JsonConfigurationSource()) { } public IDictionary CurrentData => Data; } } ================================================ FILE: src/dotnet-user-secrets/Internal/RemoveCommand.cs ================================================ // 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.Extensions.CommandLineUtils; namespace Microsoft.Extensions.SecretManager.Tools.Internal { internal class RemoveCommand : ICommand { private readonly string _keyName; public static void Configure(CommandLineApplication command, CommandLineOptions options) { command.Description = "Removes the specified user secret"; command.HelpOption(); var keyArg = command.Argument("[name]", "Name of the secret"); command.OnExecute(() => { if (keyArg.Value == null) { throw new CommandParsingException(command, Resources.FormatError_MissingArgument("name")); } options.Command = new RemoveCommand(keyArg.Value); }); } public RemoveCommand(string keyName) { _keyName = keyName; } public void Execute(CommandContext context) { if (!context.SecretStore.ContainsKey(_keyName)) { context.Reporter.Warn(Resources.FormatError_Missing_Secret(_keyName)); } else { context.SecretStore.Remove(_keyName); context.SecretStore.Save(); } } } } ================================================ FILE: src/dotnet-user-secrets/Internal/SecretsStore.cs ================================================ // 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; using System.Text; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration.UserSecrets; using Microsoft.Extensions.Tools.Internal; using Newtonsoft.Json.Linq; namespace Microsoft.Extensions.SecretManager.Tools.Internal { public class SecretsStore { private readonly string _secretsFilePath; private IDictionary _secrets; public SecretsStore(string userSecretsId, IReporter reporter) { Ensure.NotNull(userSecretsId, nameof(userSecretsId)); _secretsFilePath = PathHelper.GetSecretsPathFromSecretsId(userSecretsId); // workaround bug in configuration var secretDir = Path.GetDirectoryName(_secretsFilePath); Directory.CreateDirectory(secretDir); reporter.Verbose(Resources.FormatMessage_Secret_File_Path(_secretsFilePath)); _secrets = Load(userSecretsId); } public string this[string key] { get { return _secrets[key]; } } public int Count => _secrets.Count; public bool ContainsKey(string key) => _secrets.ContainsKey(key); public IEnumerable> AsEnumerable() => _secrets; public void Clear() => _secrets.Clear(); public void Set(string key, string value) => _secrets[key] = value; public void Remove(string key) { if (_secrets.ContainsKey(key)) { _secrets.Remove(key); } } public virtual void Save() { Directory.CreateDirectory(Path.GetDirectoryName(_secretsFilePath)); var contents = new JObject(); if (_secrets != null) { foreach (var secret in _secrets.AsEnumerable()) { contents[secret.Key] = secret.Value; } } File.WriteAllText(_secretsFilePath, contents.ToString(), Encoding.UTF8); } protected virtual IDictionary Load(string userSecretsId) { return new ConfigurationBuilder() .AddJsonFile(_secretsFilePath, optional: true) .Build() .AsEnumerable() .Where(i => i.Value != null) .ToDictionary(i => i.Key, i => i.Value, StringComparer.OrdinalIgnoreCase); } } } ================================================ FILE: src/dotnet-user-secrets/Internal/SetCommand.cs ================================================ // 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.Runtime.InteropServices; using System.Text; using Microsoft.Extensions.CommandLineUtils; using Microsoft.Extensions.Tools.Internal; namespace Microsoft.Extensions.SecretManager.Tools.Internal { internal class SetCommand { public static void Configure(CommandLineApplication command, CommandLineOptions options, IConsole console) { command.Description = "Sets the user secret to the specified value"; command.ExtendedHelpText = @" Additional Info: This command will also handle piped input. Piped input is expected to be a valid JSON format. Examples: dotnet user-secrets set ConnStr ""User ID=bob;Password=***"" "; var catCmd = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? @"type .\secrets.json" : "cat ./secrets.json"; command.ExtendedHelpText += $@" {catCmd} | dotnet user-secrets set"; command.HelpOption(); var nameArg = command.Argument("[name]", "Name of the secret"); var valueArg = command.Argument("[value]", "Value of the secret"); command.OnExecute(() => { if (console.IsInputRedirected && nameArg.Value == null) { options.Command = new FromStdInStrategy(); } else { if (string.IsNullOrEmpty(nameArg.Value)) { throw new CommandParsingException(command, Resources.FormatError_MissingArgument("name")); } if (valueArg.Value == null) { throw new CommandParsingException(command, Resources.FormatError_MissingArgument("value")); } options.Command = new ForOneValueStrategy(nameArg.Value, valueArg.Value); } }); } public class FromStdInStrategy : ICommand { public void Execute(CommandContext context) { // parses stdin with the same parser that Microsoft.Extensions.Configuration.Json would use var provider = new ReadableJsonConfigurationProvider(); using (var stream = new MemoryStream()) { using (var writer = new StreamWriter(stream, Encoding.Unicode, 1024, true)) { writer.Write(context.Console.In.ReadToEnd()); // TODO buffer? } stream.Seek(0, SeekOrigin.Begin); provider.Load(stream); } foreach (var k in provider.CurrentData) { context.SecretStore.Set(k.Key, k.Value); } context.Reporter.Output(Resources.FormatMessage_Saved_Secrets(provider.CurrentData.Count)); context.SecretStore.Save(); } } public class ForOneValueStrategy : ICommand { private readonly string _keyName; private readonly string _keyValue; public ForOneValueStrategy(string keyName, string keyValue) { _keyName = keyName; _keyValue = keyValue; } public void Execute(CommandContext context) { context.SecretStore.Set(_keyName, _keyValue); context.SecretStore.Save(); context.Reporter.Output(Resources.FormatMessage_Saved_Secret(_keyName, _keyValue)); } } } } ================================================ FILE: src/dotnet-user-secrets/Program.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.IO; using Microsoft.Extensions.CommandLineUtils; using Microsoft.Extensions.SecretManager.Tools.Internal; using Microsoft.Extensions.Tools.Internal; namespace Microsoft.Extensions.SecretManager.Tools { public class Program { private readonly IConsole _console; private readonly string _workingDirectory; public static int Main(string[] args) { DebugHelper.HandleDebugSwitch(ref args); int rc; new Program(PhysicalConsole.Singleton, Directory.GetCurrentDirectory()).TryRun(args, out rc); return rc; } public Program(IConsole console, string workingDirectory) { _console = console; _workingDirectory = workingDirectory; } public bool TryRun(string[] args, out int returnCode) { try { returnCode = RunInternal(args); return true; } catch (Exception exception) { var reporter = CreateReporter(verbose: true); reporter.Verbose(exception.ToString()); reporter.Error(Resources.FormatError_Command_Failed(exception.Message)); returnCode = 1; return false; } } internal int RunInternal(params string[] args) { CommandLineOptions options; try { options = CommandLineOptions.Parse(args, _console); } catch (CommandParsingException ex) { CreateReporter(verbose: false).Error(ex.Message); return 1; } if (options == null) { return 1; } if (options.IsHelp) { return 2; } var reporter = CreateReporter(options.IsVerbose); if (options.Command is InitCommandFactory initCmd) { initCmd.Execute(new CommandContext(null, reporter, _console), _workingDirectory); return 0; } string userSecretsId; try { userSecretsId = ResolveId(options, reporter); } catch (Exception ex) when (ex is InvalidOperationException || ex is FileNotFoundException) { reporter.Error(ex.Message); return 1; } var store = new SecretsStore(userSecretsId, reporter); var context = new Internal.CommandContext(store, reporter, _console); options.Command.Execute(context); return 0; } private IReporter CreateReporter(bool verbose) => new ConsoleReporter(_console, verbose, quiet: false); internal string ResolveId(CommandLineOptions options, IReporter reporter) { if (!string.IsNullOrEmpty(options.Id)) { return options.Id; } var resolver = new ProjectIdResolver(reporter, _workingDirectory); return resolver.Resolve(options.Project, options.Configuration); } } } ================================================ FILE: src/dotnet-user-secrets/Properties/AssemblyInfo.cs ================================================ // 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.Runtime.CompilerServices; [assembly: InternalsVisibleTo("Microsoft.Extensions.SecretManager.Tools.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] ================================================ FILE: src/dotnet-user-secrets/Properties/Resources.Designer.cs ================================================ // namespace Microsoft.Extensions.SecretManager.Tools { using System.Globalization; using System.Reflection; using System.Resources; internal static class Resources { private static readonly ResourceManager _resourceManager = new ResourceManager("Microsoft.Extensions.SecretManager.Tools.Resources", typeof(Resources).GetTypeInfo().Assembly); /// /// Command failed : {message} /// internal static string Error_Command_Failed { get => GetString("Error_Command_Failed"); } /// /// Command failed : {message} /// internal static string FormatError_Command_Failed(object message) => string.Format(CultureInfo.CurrentCulture, GetString("Error_Command_Failed", "message"), message); /// /// Missing parameter value for '{name}'. /// Use the '--help' flag to see info. /// internal static string Error_MissingArgument { get => GetString("Error_MissingArgument"); } /// /// Missing parameter value for '{name}'. /// Use the '--help' flag to see info. /// internal static string FormatError_MissingArgument(object name) => string.Format(CultureInfo.CurrentCulture, GetString("Error_MissingArgument", "name"), name); /// /// Cannot find '{key}' in the secret store. /// internal static string Error_Missing_Secret { get => GetString("Error_Missing_Secret"); } /// /// Cannot find '{key}' in the secret store. /// internal static string FormatError_Missing_Secret(object key) => string.Format(CultureInfo.CurrentCulture, GetString("Error_Missing_Secret", "key"), key); /// /// Multiple MSBuild project files found in '{projectPath}'. Specify which to use with the --project option. /// internal static string Error_MultipleProjectsFound { get => GetString("Error_MultipleProjectsFound"); } /// /// Multiple MSBuild project files found in '{projectPath}'. Specify which to use with the --project option. /// internal static string FormatError_MultipleProjectsFound(object projectPath) => string.Format(CultureInfo.CurrentCulture, GetString("Error_MultipleProjectsFound", "projectPath"), projectPath); /// /// No secrets configured for this application. /// internal static string Error_No_Secrets_Found { get => GetString("Error_No_Secrets_Found"); } /// /// No secrets configured for this application. /// internal static string FormatError_No_Secrets_Found() => GetString("Error_No_Secrets_Found"); /// /// Could not find a MSBuild project file in '{projectPath}'. Specify which project to use with the --project option. /// internal static string Error_NoProjectsFound { get => GetString("Error_NoProjectsFound"); } /// /// Could not find a MSBuild project file in '{projectPath}'. Specify which project to use with the --project option. /// internal static string FormatError_NoProjectsFound(object projectPath) => string.Format(CultureInfo.CurrentCulture, GetString("Error_NoProjectsFound", "projectPath"), projectPath); /// /// Could not find the global property 'UserSecretsId' in MSBuild project '{project}'. Ensure this property is set in the project or use the '--id' command line option. /// internal static string Error_ProjectMissingId { get => GetString("Error_ProjectMissingId"); } /// /// Could not find the global property 'UserSecretsId' in MSBuild project '{project}'. Ensure this property is set in the project or use the '--id' command line option. /// internal static string FormatError_ProjectMissingId(object project) => string.Format(CultureInfo.CurrentCulture, GetString("Error_ProjectMissingId", "project"), project); /// /// The project file '{path}' does not exist. /// internal static string Error_ProjectPath_NotFound { get => GetString("Error_ProjectPath_NotFound"); } /// /// The project file '{path}' does not exist. /// internal static string FormatError_ProjectPath_NotFound(object path) => string.Format(CultureInfo.CurrentCulture, GetString("Error_ProjectPath_NotFound", "path"), path); /// /// Could not load the MSBuild project '{project}'. /// internal static string Error_ProjectFailedToLoad { get => GetString("Error_ProjectFailedToLoad"); } /// /// Could not load the MSBuild project '{project}'. /// internal static string FormatError_ProjectFailedToLoad(object project) => string.Format(CultureInfo.CurrentCulture, GetString("Error_ProjectFailedToLoad", "project"), project); /// /// Project file path {project}. /// internal static string Message_Project_File_Path { get => GetString("Message_Project_File_Path"); } /// /// Project file path {project}. /// internal static string FormatMessage_Project_File_Path(object project) => string.Format(CultureInfo.CurrentCulture, GetString("Message_Project_File_Path", "project"), project); /// /// Successfully saved {key} = {value} to the secret store. /// internal static string Message_Saved_Secret { get => GetString("Message_Saved_Secret"); } /// /// Successfully saved {key} = {value} to the secret store. /// internal static string FormatMessage_Saved_Secret(object key, object value) => string.Format(CultureInfo.CurrentCulture, GetString("Message_Saved_Secret", "key", "value"), key, value); /// /// Successfully saved {number} secrets to the secret store. /// internal static string Message_Saved_Secrets { get => GetString("Message_Saved_Secrets"); } /// /// Successfully saved {number} secrets to the secret store. /// internal static string FormatMessage_Saved_Secrets(object number) => string.Format(CultureInfo.CurrentCulture, GetString("Message_Saved_Secrets", "number"), number); /// /// Secrets file path {secretsFilePath}. /// internal static string Message_Secret_File_Path { get => GetString("Message_Secret_File_Path"); } /// /// Secrets file path {secretsFilePath}. /// internal static string FormatMessage_Secret_File_Path(object secretsFilePath) => string.Format(CultureInfo.CurrentCulture, GetString("Message_Secret_File_Path", "secretsFilePath"), secretsFilePath); /// /// {key} = {value} /// internal static string Message_Secret_Value_Format { get => GetString("Message_Secret_Value_Format"); } /// /// {key} = {value} /// internal static string FormatMessage_Secret_Value_Format(object key, object value) => string.Format(CultureInfo.CurrentCulture, GetString("Message_Secret_Value_Format", "key", "value"), key, value); /// /// The UserSecretsId '{userSecretsId}' cannot contain any characters that cannot be used in a file path. /// internal static string Error_InvalidSecretsId { get => GetString("Error_InvalidSecretsId"); } /// /// The UserSecretsId '{userSecretsId}' cannot contain any characters that cannot be used in a file path. /// internal static string FormatError_InvalidSecretsId(object userSecretsId) => string.Format(CultureInfo.CurrentCulture, GetString("Error_InvalidSecretsId", "userSecretsId"), userSecretsId); /// /// The MSBuild project '{project}' has already been initialized with a UserSecretsId. /// internal static string Message_ProjectAlreadyInitialized { get => GetString("Message_ProjectAlreadyInitialized"); } /// /// The MSBuild project '{project}' has already been initialized with a UserSecretsId. /// internal static string FormatMessage_ProjectAlreadyInitialized(object project) => string.Format(CultureInfo.CurrentCulture, GetString("Message_ProjectAlreadyInitialized", "project"), project); /// /// Set UserSecretsId to '{userSecretsId}' for MSBuild project '{project}'. /// internal static string Message_SetUserSecretsIdForProject { get => GetString("Message_SetUserSecretsIdForProject"); } /// /// Set UserSecretsId to '{userSecretsId}' for MSBuild project '{project}'. /// internal static string FormatMessage_SetUserSecretsIdForProject(object userSecretsId, object project) => string.Format(CultureInfo.CurrentCulture, GetString("Message_SetUserSecretsIdForProject", "userSecretsId", "project"), userSecretsId, project); private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name); System.Diagnostics.Debug.Assert(value != null); if (formatterNames != null) { for (var i = 0; i < formatterNames.Length; i++) { value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}"); } } return value; } } } ================================================ FILE: src/dotnet-user-secrets/README.md ================================================ dotnet-user-secrets =================== `dotnet-user-secrets` is a command line tool for managing the secrets in a user secret store. ### How To Use Run `dotnet user-secrets --help` for more information about usage. ================================================ FILE: src/dotnet-user-secrets/Resources.resx ================================================  text/microsoft-resx 2.0 System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 Command failed : {message} Missing parameter value for '{name}'. Use the '--help' flag to see info. Cannot find '{key}' in the secret store. Multiple MSBuild project files found in '{projectPath}'. Specify which to use with the --project option. No secrets configured for this application. Could not find a MSBuild project file in '{projectPath}'. Specify which project to use with the --project option. Could not find the global property 'UserSecretsId' in MSBuild project '{project}'. Ensure this property is set in the project or use the '--id' command line option. The project file '{path}' does not exist. Could not load the MSBuild project '{project}'. Project file path {project}. Successfully saved {key} = {value} to the secret store. Successfully saved {number} secrets to the secret store. Secrets file path {secretsFilePath}. {key} = {value} The UserSecretsId '{userSecretsId}' cannot contain any characters that cannot be used in a file path. The MSBuild project '{project}' has already been initialized with a UserSecretsId. Set UserSecretsId to '{userSecretsId}' for MSBuild project '{project}'. ================================================ FILE: src/dotnet-user-secrets/assets/SecretManager.targets ================================================ ================================================ FILE: src/dotnet-user-secrets/dotnet-user-secrets.csproj ================================================  netcoreapp3.0 exe Command line tool to manage user secrets for Microsoft.Extensions.Configuration. false Microsoft.Extensions.SecretManager.Tools configuration;secrets;usersecrets true win-x64;win-x86 ================================================ FILE: src/dotnet-watch/CommandLineOptions.cs ================================================ // 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.DotNet.Watcher.Tools; using Microsoft.Extensions.CommandLineUtils; using Microsoft.Extensions.Tools.Internal; namespace Microsoft.DotNet.Watcher { internal class CommandLineOptions { public string Project { get; private set; } public bool IsHelp { get; private set; } public bool IsQuiet { get; private set; } public bool IsVerbose { get; private set; } public IList RemainingArguments { get; private set; } public bool ListFiles { get; private set; } public static bool IsPollingEnabled { get { var envVar = Environment.GetEnvironmentVariable("DOTNET_USE_POLLING_FILE_WATCHER"); return envVar != null && (envVar.Equals("1", StringComparison.OrdinalIgnoreCase) || envVar.Equals("true", StringComparison.OrdinalIgnoreCase)); } } public static CommandLineOptions Parse(string[] args, IConsole console) { Ensure.NotNull(args, nameof(args)); Ensure.NotNull(console, nameof(console)); var app = new CommandLineApplication(throwOnUnexpectedArg: false) { Name = "dotnet watch", FullName = "Microsoft DotNet File Watcher", Out = console.Out, Error = console.Error, AllowArgumentSeparator = true, ExtendedHelpText = @" Environment variables: DOTNET_USE_POLLING_FILE_WATCHER When set to '1' or 'true', dotnet-watch will poll the file system for changes. This is required for some file systems, such as network shares, Docker mounted volumes, and other virtual file systems. 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. Its use is optional. When the special option '--' is not used, dotnet-watch will use the first unrecognized argument as the beginning of all arguments passed into the child dotnet process. For example: dotnet watch -- --verbose run Even though '--verbose' is an option dotnet-watch supports, the use of '--' indicates that '--verbose' should be treated instead as an argument for dotnet-run. Examples: dotnet watch run dotnet watch test " }; app.HelpOption("-?|-h|--help"); // TODO multiple shouldn't be too hard to support var optProjects = app.Option("-p|--project ", "The project to watch", CommandOptionType.SingleValue); var optQuiet = app.Option("-q|--quiet", "Suppresses all output except warnings and errors", CommandOptionType.NoValue); var optVerbose = app.VerboseOption(); var optList = app.Option("--list", "Lists all discovered files without starting the watcher", CommandOptionType.NoValue); app.VersionOptionFromAssemblyAttributes(typeof(Program).GetTypeInfo().Assembly); if (app.Execute(args) != 0) { return null; } if (optQuiet.HasValue() && optVerbose.HasValue()) { throw new CommandParsingException(app, Resources.Error_QuietAndVerboseSpecified); } if (app.RemainingArguments.Count == 0 && !app.IsShowingInformation && !optList.HasValue()) { app.ShowHelp(); } return new CommandLineOptions { Project = optProjects.Value(), IsQuiet = optQuiet.HasValue(), IsVerbose = optVerbose.HasValue(), RemainingArguments = app.RemainingArguments, IsHelp = app.IsShowingInformation, ListFiles = optList.HasValue(), }; } } } ================================================ FILE: src/dotnet-watch/DotNetWatcher.cs ================================================ // 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.Threading; using System.Threading.Tasks; using Microsoft.DotNet.Watcher.Internal; using Microsoft.Extensions.CommandLineUtils; using Microsoft.Extensions.Tools.Internal; namespace Microsoft.DotNet.Watcher { public class DotNetWatcher { private readonly IReporter _reporter; private readonly ProcessRunner _processRunner; public DotNetWatcher(IReporter reporter) { Ensure.NotNull(reporter, nameof(reporter)); _reporter = reporter; _processRunner = new ProcessRunner(reporter); } public async Task WatchAsync(ProcessSpec processSpec, IFileSetFactory fileSetFactory, CancellationToken cancellationToken) { Ensure.NotNull(processSpec, nameof(processSpec)); var cancelledTaskSource = new TaskCompletionSource(); 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) { _reporter.Error("Failed to find a list of files to watch"); return; } if (cancellationToken.IsCancellationRequested) { return; } using (var currentRunCancellationSource = new CancellationTokenSource()) using (var combinedCancellationSource = CancellationTokenSource.CreateLinkedTokenSource( cancellationToken, currentRunCancellationSource.Token)) using (var fileSetWatcher = new FileSetWatcher(fileSet, _reporter)) { var fileSetTask = fileSetWatcher.GetChangedFileAsync(combinedCancellationSource.Token); var processTask = _processRunner.RunAsync(processSpec, combinedCancellationSource.Token); var args = ArgumentEscaper.EscapeAndConcatenate(processSpec.Arguments); _reporter.Verbose($"Running {processSpec.ShortDisplayName()} with the following arguments: {args}"); _reporter.Output("Started"); var finishedTask = await Task.WhenAny(processTask, fileSetTask, cancelledTaskSource.Task); // Regardless of the which task finished first, make sure everything is cancelled // and wait for dotnet to exit. We don't want orphan processes currentRunCancellationSource.Cancel(); await Task.WhenAll(processTask, fileSetTask); 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 { _reporter.Output("Exited"); } if (finishedTask == cancelledTaskSource.Task || cancellationToken.IsCancellationRequested) { return; } if (finishedTask == processTask) { _reporter.Warn("Waiting for a file to change before restarting dotnet..."); // Now wait for a file to change before restarting process await fileSetWatcher.GetChangedFileAsync(cancellationToken); } if (!string.IsNullOrEmpty(fileSetTask.Result)) { _reporter.Output($"File changed: {fileSetTask.Result}"); } } } } } } ================================================ FILE: src/dotnet-watch/IFileSet.cs ================================================ // 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.DotNet.Watcher { public interface IFileSet : IEnumerable { bool Contains(string filePath); } } ================================================ FILE: src/dotnet-watch/IFileSetFactory.cs ================================================ // 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; namespace Microsoft.DotNet.Watcher { public interface IFileSetFactory { Task CreateAsync(CancellationToken cancellationToken); } } ================================================ FILE: src/dotnet-watch/Internal/FileSet.cs ================================================ // 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; using System.Collections.Generic; namespace Microsoft.DotNet.Watcher.Internal { public class FileSet : IFileSet { private readonly HashSet _files; public FileSet(IEnumerable files) { _files = new HashSet(files, StringComparer.OrdinalIgnoreCase); } public bool Contains(string filePath) => _files.Contains(filePath); public int Count => _files.Count; public IEnumerator GetEnumerator() => _files.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => _files.GetEnumerator(); } } ================================================ FILE: src/dotnet-watch/Internal/FileSetWatcher.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.IO; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Tools.Internal; namespace Microsoft.DotNet.Watcher.Internal { public class FileSetWatcher : IDisposable { private readonly FileWatcher _fileWatcher; private readonly IFileSet _fileSet; public FileSetWatcher(IFileSet fileSet, IReporter reporter) { Ensure.NotNull(fileSet, nameof(fileSet)); _fileSet = fileSet; _fileWatcher = new FileWatcher(reporter); } public async Task GetChangedFileAsync(CancellationToken cancellationToken) { foreach (var file in _fileSet) { _fileWatcher.WatchDirectory(Path.GetDirectoryName(file)); } var tcs = new TaskCompletionSource(); cancellationToken.Register(() => tcs.TrySetResult(null)); Action callback = path => { if (_fileSet.Contains(path)) { tcs.TrySetResult(path); } }; _fileWatcher.OnFileChange += callback; var changedFile = await tcs.Task; _fileWatcher.OnFileChange -= callback; return changedFile; } public void Dispose() { _fileWatcher.Dispose(); } } } ================================================ FILE: src/dotnet-watch/Internal/FileWatcher/DotnetFileWatcher.cs ================================================ // 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.ComponentModel; using System.IO; using Microsoft.Extensions.Tools.Internal; namespace Microsoft.DotNet.Watcher.Internal { internal class DotnetFileWatcher : IFileSystemWatcher { private volatile bool _disposed; private readonly Func _watcherFactory; private FileSystemWatcher _fileSystemWatcher; private readonly object _createLock = new object(); public DotnetFileWatcher(string watchedDirectory) : this(watchedDirectory, DefaultWatcherFactory) { } internal DotnetFileWatcher(string watchedDirectory, Func fileSystemWatcherFactory) { Ensure.NotNull(fileSystemWatcherFactory, nameof(fileSystemWatcherFactory)); Ensure.NotNullOrEmpty(watchedDirectory, nameof(watchedDirectory)); BasePath = watchedDirectory; _watcherFactory = fileSystemWatcherFactory; CreateFileSystemWatcher(); } public event EventHandler OnFileChange; public event EventHandler OnError; public string BasePath { get; } private static FileSystemWatcher DefaultWatcherFactory(string watchedDirectory) { Ensure.NotNullOrEmpty(watchedDirectory, nameof(watchedDirectory)); return new FileSystemWatcher(watchedDirectory); } 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 // that is not supported, such as a network share. Don't attempt to recreate the watcher // in this case as it will cause a StackOverflowException if (!(exception is Win32Exception)) { // Recreate the watcher if it is a recoverable error. CreateFileSystemWatcher(); } OnError?.Invoke(this, exception); } private void WatcherRenameHandler(object sender, RenamedEventArgs e) { if (_disposed) { return; } NotifyChange(e.OldFullPath); NotifyChange(e.FullPath); if (Directory.Exists(e.FullPath)) { foreach (var newLocation in Directory.EnumerateFileSystemEntries(e.FullPath, "*", SearchOption.AllDirectories)) { // Calculated previous path of this moved item. var oldLocation = Path.Combine(e.OldFullPath, newLocation.Substring(e.FullPath.Length + 1)); NotifyChange(oldLocation); NotifyChange(newLocation); } } } private void WatcherChangeHandler(object sender, FileSystemEventArgs e) { if (_disposed) { return; } NotifyChange(e.FullPath); } private void NotifyChange(string fullPath) { // Only report file changes OnFileChange?.Invoke(this, fullPath); } private void CreateFileSystemWatcher() { lock (_createLock) { bool enableEvents = false; if (_fileSystemWatcher != null) { enableEvents = _fileSystemWatcher.EnableRaisingEvents; DisposeInnerWatcher(); } _fileSystemWatcher = _watcherFactory(BasePath); _fileSystemWatcher.IncludeSubdirectories = true; _fileSystemWatcher.Created += WatcherChangeHandler; _fileSystemWatcher.Deleted += WatcherChangeHandler; _fileSystemWatcher.Changed += WatcherChangeHandler; _fileSystemWatcher.Renamed += WatcherRenameHandler; _fileSystemWatcher.Error += WatcherErrorHandler; _fileSystemWatcher.EnableRaisingEvents = enableEvents; } } 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; set => _fileSystemWatcher.EnableRaisingEvents = value; } public void Dispose() { _disposed = true; DisposeInnerWatcher(); } } } ================================================ FILE: src/dotnet-watch/Internal/FileWatcher/FileWatcherFactory.cs ================================================ // 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.DotNet.Watcher.Internal { public static class FileWatcherFactory { public static IFileSystemWatcher CreateWatcher(string watchedDirectory) => CreateWatcher(watchedDirectory, CommandLineOptions.IsPollingEnabled); public static IFileSystemWatcher CreateWatcher(string watchedDirectory, bool usePollingWatcher) { return usePollingWatcher ? new PollingFileWatcher(watchedDirectory) : new DotnetFileWatcher(watchedDirectory) as IFileSystemWatcher; } } } ================================================ FILE: src/dotnet-watch/Internal/FileWatcher/IFileSystemWatcher.cs ================================================ // 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.DotNet.Watcher.Internal { public interface IFileSystemWatcher : IDisposable { event EventHandler OnFileChange; event EventHandler OnError; string BasePath { get; } bool EnableRaisingEvents { get; set; } } } ================================================ FILE: src/dotnet-watch/Internal/FileWatcher/PollingFileWatcher.cs ================================================ // 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.Threading; using Microsoft.Extensions.Tools.Internal; namespace Microsoft.DotNet.Watcher.Internal { internal class PollingFileWatcher : IFileSystemWatcher { // The minimum interval to rerun the scan private static readonly TimeSpan _minRunInternal = TimeSpan.FromSeconds(.5); private readonly DirectoryInfo _watchedDirectory; private Dictionary _knownEntities = new Dictionary(); private Dictionary _tempDictionary = new Dictionary(); private HashSet _changes = new HashSet(); private Thread _pollingThread; private bool _raiseEvents; private bool _disposed; public PollingFileWatcher(string watchedDirectory) { Ensure.NotNullOrEmpty(watchedDirectory, nameof(watchedDirectory)); _watchedDirectory = new DirectoryInfo(watchedDirectory); BasePath = _watchedDirectory.FullName; _pollingThread = new Thread(new ThreadStart(PollingLoop)); _pollingThread.IsBackground = true; _pollingThread.Name = nameof(PollingFileWatcher); CreateKnownFilesSnapshot(); _pollingThread.Start(); } public event EventHandler OnFileChange; #pragma warning disable CS0067 // not used public event EventHandler OnError; #pragma warning restore public string BasePath { get; } public bool EnableRaisingEvents { get => _raiseEvents; set { EnsureNotDisposed(); _raiseEvents = value; } } private void PollingLoop() { var stopwatch = Stopwatch.StartNew(); stopwatch.Start(); while (!_disposed) { if (stopwatch.Elapsed < _minRunInternal) { // Don't run too often // The min wait time here can be double // the value of the variable (FYI) Thread.Sleep(_minRunInternal); } stopwatch.Reset(); if (!_raiseEvents) { continue; } CheckForChangedFiles(); } stopwatch.Stop(); } private void CreateKnownFilesSnapshot() { _knownEntities.Clear(); ForeachEntityInDirectory(_watchedDirectory, f => { _knownEntities.Add(f.FullName, new FileMeta(f)); }); } private void CheckForChangedFiles() { _changes.Clear(); ForeachEntityInDirectory(_watchedDirectory, f => { var fullFilePath = f.FullName; if (!_knownEntities.ContainsKey(fullFilePath)) { // New file RecordChange(f); } else { var fileMeta = _knownEntities[fullFilePath]; try { if (fileMeta.FileInfo.LastWriteTime != f.LastWriteTime) { // File changed RecordChange(f); } _knownEntities[fullFilePath] = new FileMeta(fileMeta.FileInfo, true); } catch (FileNotFoundException) { _knownEntities[fullFilePath] = new FileMeta(fileMeta.FileInfo, false); } } _tempDictionary.Add(f.FullName, new FileMeta(f)); }); foreach (var file in _knownEntities) { if (!file.Value.FoundAgain) { // File deleted RecordChange(file.Value.FileInfo); } } NotifyChanges(); // Swap the two dictionaries var swap = _knownEntities; _knownEntities = _tempDictionary; _tempDictionary = swap; _tempDictionary.Clear(); } private void RecordChange(FileSystemInfo fileInfo) { if (fileInfo == null || _changes.Contains(fileInfo.FullName) || fileInfo.FullName.Equals(_watchedDirectory.FullName, StringComparison.Ordinal)) { return; } _changes.Add(fileInfo.FullName); if (fileInfo.FullName != _watchedDirectory.FullName) { var file = fileInfo as FileInfo; if (file != null) { RecordChange(file.Directory); } else { var dir = fileInfo as DirectoryInfo; if (dir != null) { RecordChange(dir.Parent); } } } } private void ForeachEntityInDirectory(DirectoryInfo dirInfo, Action fileAction) { if (!dirInfo.Exists) { return; } var entities = dirInfo.EnumerateFileSystemInfos("*.*"); foreach (var entity in entities) { fileAction(entity); var subdirInfo = entity as DirectoryInfo; if (subdirInfo != null) { ForeachEntityInDirectory(subdirInfo, fileAction); } } } private void NotifyChanges() { foreach (var path in _changes) { if (_disposed || !_raiseEvents) { break; } if (OnFileChange != null) { OnFileChange(this, path); } } } private void EnsureNotDisposed() { if (_disposed) { throw new ObjectDisposedException(nameof(PollingFileWatcher)); } } public void Dispose() { EnableRaisingEvents = false; _disposed = true; } private struct FileMeta { public FileMeta(FileSystemInfo fileInfo, bool foundAgain = false) { FileInfo = fileInfo; FoundAgain = foundAgain; } public FileSystemInfo FileInfo; public bool FoundAgain; } } } ================================================ FILE: src/dotnet-watch/Internal/FileWatcher.cs ================================================ // 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; using Microsoft.Extensions.Tools.Internal; namespace Microsoft.DotNet.Watcher.Internal { public class FileWatcher { private bool _disposed; private readonly IDictionary _watchers; private readonly IReporter _reporter; public FileWatcher() : this(NullReporter.Singleton) { } public FileWatcher(IReporter reporter) { _reporter = reporter ?? throw new ArgumentNullException(nameof(reporter)); _watchers = new Dictionary(); } public event Action OnFileChange; public void WatchDirectory(string directory) { EnsureNotDisposed(); AddDirectoryWatcher(directory); } public void Dispose() { if (_disposed) { return; } _disposed = true; foreach (var watcher in _watchers) { watcher.Value.OnFileChange -= WatcherChangedHandler; watcher.Value.OnError -= WatcherErrorHandler; watcher.Value.Dispose(); } _watchers.Clear(); } private void AddDirectoryWatcher(string directory) { directory = EnsureTrailingSlash(directory); var alreadyWatched = _watchers .Where(d => directory.StartsWith(d.Key)) .Any(); if (alreadyWatched) { return; } var redundantWatchers = _watchers .Where(d => d.Key.StartsWith(directory)) .Select(d => d.Key) .ToList(); if (redundantWatchers.Any()) { foreach (var watcher in redundantWatchers) { DisposeWatcher(watcher); } } var newWatcher = FileWatcherFactory.CreateWatcher(directory); newWatcher.OnFileChange += WatcherChangedHandler; newWatcher.OnError += WatcherErrorHandler; newWatcher.EnableRaisingEvents = true; _watchers.Add(directory, newWatcher); } private void WatcherErrorHandler(object sender, Exception error) { if (sender is IFileSystemWatcher watcher) { _reporter.Warn($"The file watcher observing '{watcher.BasePath}' encountered an error: {error.Message}"); } } private void WatcherChangedHandler(object sender, string changedPath) { NotifyChange(changedPath); } private void NotifyChange(string path) { if (OnFileChange != null) { OnFileChange(path); } } private void DisposeWatcher(string directory) { var watcher = _watchers[directory]; _watchers.Remove(directory); watcher.EnableRaisingEvents = false; watcher.OnFileChange -= WatcherChangedHandler; watcher.OnError -= WatcherErrorHandler; watcher.Dispose(); } private void EnsureNotDisposed() { if (_disposed) { throw new ObjectDisposedException(nameof(FileWatcher)); } } private static string EnsureTrailingSlash(string path) { if (!string.IsNullOrEmpty(path) && path[path.Length - 1] != Path.DirectorySeparatorChar) { return path + Path.DirectorySeparatorChar; } return path; } } } ================================================ FILE: src/dotnet-watch/Internal/MsBuildFileSetFactory.cs ================================================ // 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; using System.Reflection; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.CommandLineUtils; using Microsoft.Extensions.Tools.Internal; namespace Microsoft.DotNet.Watcher.Internal { public class MsBuildFileSetFactory : IFileSetFactory { private const string TargetName = "GenerateWatchList"; private const string WatchTargetsFileName = "DotNetWatch.targets"; private readonly IReporter _reporter; private readonly string _projectFile; private readonly OutputSink _outputSink; private readonly ProcessRunner _processRunner; private readonly bool _waitOnError; private readonly IReadOnlyList _buildFlags; public MsBuildFileSetFactory(IReporter reporter, string projectFile, bool waitOnError, bool trace) : this(reporter, projectFile, new OutputSink(), trace) { _waitOnError = waitOnError; } // output sink is for testing internal MsBuildFileSetFactory(IReporter reporter, string projectFile, OutputSink outputSink, bool trace) { Ensure.NotNull(reporter, nameof(reporter)); Ensure.NotNullOrEmpty(projectFile, nameof(projectFile)); Ensure.NotNull(outputSink, nameof(outputSink)); _reporter = reporter; _projectFile = projectFile; _outputSink = outputSink; _processRunner = new ProcessRunner(reporter); _buildFlags = InitializeArgs(FindTargetsFile(), trace); } public async Task CreateAsync(CancellationToken cancellationToken) { var watchList = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); try { var projectDir = Path.GetDirectoryName(_projectFile); while (true) { cancellationToken.ThrowIfCancellationRequested(); var capture = _outputSink.StartCapture(); // TODO adding files doesn't currently work. Need to provide a way to detect new files // find files var processSpec = new ProcessSpec { Executable = DotNetMuxer.MuxerPathOrDefault(), WorkingDirectory = projectDir, Arguments = new[] { "msbuild", _projectFile, $"/p:_DotNetWatchListFile={watchList}" }.Concat(_buildFlags), OutputCapture = capture }; _reporter.Verbose($"Running MSBuild target '{TargetName}' on '{_projectFile}'"); var exitCode = await _processRunner.RunAsync(processSpec, cancellationToken); if (exitCode == 0 && File.Exists(watchList)) { var fileset = new FileSet( File.ReadAllLines(watchList) .Select(l => l?.Trim()) .Where(l => !string.IsNullOrEmpty(l))); _reporter.Verbose($"Watching {fileset.Count} file(s) for changes"); #if DEBUG foreach (var file in fileset) { _reporter.Verbose($" -> {file}"); } Debug.Assert(fileset.All(Path.IsPathRooted), "All files should be rooted paths"); #endif return fileset; } _reporter.Error($"Error(s) finding watch items project file '{Path.GetFileName(_projectFile)}'"); _reporter.Output($"MSBuild output from target '{TargetName}':"); _reporter.Output(string.Empty); foreach (var line in capture.Lines) { _reporter.Output($" {line}"); } _reporter.Output(string.Empty); if (!_waitOnError) { return null; } else { _reporter.Warn("Fix the error to continue or press Ctrl+C to exit."); var fileSet = new FileSet(new[] { _projectFile }); using (var watcher = new FileSetWatcher(fileSet, _reporter)) { await watcher.GetChangedFileAsync(cancellationToken); _reporter.Output($"File changed: {_projectFile}"); } } } } finally { if (File.Exists(watchList)) { File.Delete(watchList); } } } private IReadOnlyList InitializeArgs(string watchTargetsFile, bool trace) { var args = new List { "/nologo", "/v:n", "/t:" + TargetName, "/p:DotNetWatchBuild=true", // extensibility point for users "/p:DesignTimeBuild=true", // don't do expensive things "/p:CustomAfterMicrosoftCommonTargets=" + watchTargetsFile, "/p:CustomAfterMicrosoftCommonCrossTargetingTargets=" + watchTargetsFile, }; if (trace) { // enables capturing markers to know which projects have been visited args.Add("/p:_DotNetWatchTraceOutput=true"); } return args; } private string FindTargetsFile() { var assemblyDir = Path.GetDirectoryName(typeof(MsBuildFileSetFactory).Assembly.Location); var searchPaths = new[] { Path.Combine(AppContext.BaseDirectory, "assets"), Path.Combine(assemblyDir, "assets"), AppContext.BaseDirectory, assemblyDir, }; var targetPath = searchPaths.Select(p => Path.Combine(p, WatchTargetsFileName)).FirstOrDefault(File.Exists); if (targetPath == null) { _reporter.Error("Fatal error: could not find DotNetWatch.targets"); return null; } return targetPath; } } } ================================================ FILE: src/dotnet-watch/Internal/MsBuildProjectFinder.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.IO; using System.Linq; using Microsoft.DotNet.Watcher.Tools; using Microsoft.Extensions.Tools.Internal; namespace Microsoft.DotNet.Watcher.Internal { internal class MsBuildProjectFinder { /// /// Finds a compatible MSBuild project. /// The base directory to search /// The filename of the project. Can be null. /// public static string FindMsBuildProject(string searchBase, string project) { Ensure.NotNullOrEmpty(searchBase, nameof(searchBase)); var projectPath = project ?? searchBase; if (!Path.IsPathRooted(projectPath)) { projectPath = Path.Combine(searchBase, projectPath); } if (Directory.Exists(projectPath)) { var projects = Directory.EnumerateFileSystemEntries(projectPath, "*.*proj", SearchOption.TopDirectoryOnly) .Where(f => !".xproj".Equals(Path.GetExtension(f), StringComparison.OrdinalIgnoreCase)) .ToList(); if (projects.Count > 1) { throw new FileNotFoundException(Resources.FormatError_MultipleProjectsFound(projectPath)); } if (projects.Count == 0) { throw new FileNotFoundException(Resources.FormatError_NoProjectsFound(projectPath)); } return projects[0]; } if (!File.Exists(projectPath)) { throw new FileNotFoundException(Resources.FormatError_ProjectPath_NotFound(projectPath)); } return projectPath; } } } ================================================ FILE: src/dotnet-watch/Internal/OutputCapture.cs ================================================ // 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.DotNet.Watcher.Internal { public class OutputCapture { private readonly List _lines = new List(); public IEnumerable Lines => _lines; public void AddLine(string line) => _lines.Add(line); } } ================================================ FILE: src/dotnet-watch/Internal/OutputSink.cs ================================================ // 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.DotNet.Watcher.Internal { public class OutputSink { public OutputCapture Current { get; private set; } public OutputCapture StartCapture() { return (Current = new OutputCapture()); } } } ================================================ FILE: src/dotnet-watch/Internal/ProcessRunner.cs ================================================ // 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; using System.Threading.Tasks; using Microsoft.Extensions.CommandLineUtils; using Microsoft.Extensions.Internal; using Microsoft.Extensions.Tools.Internal; namespace Microsoft.DotNet.Watcher.Internal { public class ProcessRunner { private readonly IReporter _reporter; public ProcessRunner(IReporter reporter) { Ensure.NotNull(reporter, nameof(reporter)); _reporter = reporter; } // May not be necessary in the future. See https://github.com/dotnet/corefx/issues/12039 public async Task RunAsync(ProcessSpec processSpec, CancellationToken cancellationToken) { Ensure.NotNull(processSpec, nameof(processSpec)); int exitCode; var stopwatch = new Stopwatch(); using (var process = CreateProcess(processSpec)) using (var processState = new ProcessState(process, _reporter)) { cancellationToken.Register(() => processState.TryKill()); stopwatch.Start(); process.Start(); _reporter.Verbose($"Started '{processSpec.Executable}' with process id {process.Id}"); if (processSpec.IsOutputCaptured) { await Task.WhenAll( processState.Task, ConsumeStreamAsync(process.StandardOutput, processSpec.OutputCapture.AddLine), ConsumeStreamAsync(process.StandardError, processSpec.OutputCapture.AddLine) ); } else { await processState.Task; } exitCode = process.ExitCode; stopwatch.Stop(); _reporter.Verbose($"Process id {process.Id} ran for {stopwatch.ElapsedMilliseconds}ms"); } return exitCode; } private Process CreateProcess(ProcessSpec processSpec) { var process = new Process { EnableRaisingEvents = true, StartInfo = { FileName = processSpec.Executable, Arguments = ArgumentEscaper.EscapeAndConcatenate(processSpec.Arguments), UseShellExecute = false, WorkingDirectory = processSpec.WorkingDirectory, RedirectStandardOutput = processSpec.IsOutputCaptured, RedirectStandardError = processSpec.IsOutputCaptured, } }; foreach (var env in processSpec.EnvironmentVariables) { process.StartInfo.Environment.Add(env.Key, env.Value); } return process; } private static async Task ConsumeStreamAsync(StreamReader reader, Action consume) { string line; while ((line = await reader.ReadLineAsync().ConfigureAwait(false)) != null) { consume?.Invoke(line); } } 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, IReporter reporter) { _reporter = reporter; _process = process; _process.Exited += OnExited; Task = _tcs.Task.ContinueWith(_ => { try { // 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(); } catch (InvalidOperationException) { // suppress if this throws if no process is associated with this object anymore. } }); } public Task Task { get; } public void TryKill() { if (_disposed) { return; } try { if (!_process.HasExited) { _reporter.Verbose($"Killing process {_process.Id}"); _process.KillTree(); } } 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) => _tcs.TrySetResult(null); public void Dispose() { if (!_disposed) { TryKill(); _disposed = true; _process.Exited -= OnExited; _process.Dispose(); } } } } } ================================================ FILE: src/dotnet-watch/PrefixConsoleReporter.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.IO; using Microsoft.Extensions.Tools.Internal; namespace Microsoft.DotNet.Watcher { public class PrefixConsoleReporter : ConsoleReporter { private object _lock = new object(); public PrefixConsoleReporter(IConsole console, bool verbose, bool quiet) : base(console, verbose, quiet) { } protected override void WriteLine(TextWriter writer, string message, ConsoleColor? color) { const string prefix = "watch : "; lock (_lock) { Console.ForegroundColor = ConsoleColor.DarkGray; writer.Write(prefix); Console.ResetColor(); base.WriteLine(writer, message, color); } } } } ================================================ FILE: src/dotnet-watch/ProcessSpec.cs ================================================ // 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.IO; using Microsoft.DotNet.Watcher.Internal; namespace Microsoft.DotNet.Watcher { public class ProcessSpec { public string Executable { get; set; } public string WorkingDirectory { get; set; } public IDictionary EnvironmentVariables { get; } = new Dictionary(); public IEnumerable Arguments { get; set; } public OutputCapture OutputCapture { get; set; } public string ShortDisplayName() => Path.GetFileNameWithoutExtension(Executable); public bool IsOutputCaptured => OutputCapture != null; } } ================================================ FILE: src/dotnet-watch/Program.cs ================================================ // 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; using System.Threading.Tasks; using Microsoft.DotNet.Watcher.Internal; using Microsoft.Extensions.CommandLineUtils; using Microsoft.Extensions.Tools.Internal; namespace Microsoft.DotNet.Watcher { public class Program : IDisposable { private readonly IConsole _console; private readonly string _workingDir; private readonly CancellationTokenSource _cts; private IReporter _reporter; public Program(IConsole console, string workingDir) { Ensure.NotNull(console, nameof(console)); Ensure.NotNullOrEmpty(workingDir, nameof(workingDir)); _console = console; _workingDir = workingDir; _cts = new CancellationTokenSource(); _console.CancelKeyPress += OnCancelKeyPress; _reporter = CreateReporter(verbose: true, quiet: false, console: _console); } public static async Task Main(string[] args) { try { DebugHelper.HandleDebugSwitch(ref args); using (var program = new Program(PhysicalConsole.Singleton, Directory.GetCurrentDirectory())) { return await program.RunAsync(args); } } catch (Exception ex) { Console.Error.WriteLine("Unexpected error:"); Console.Error.WriteLine(ex.ToString()); return 1; } } public async Task RunAsync(string[] args) { CommandLineOptions options; try { options = CommandLineOptions.Parse(args, _console); } catch (CommandParsingException ex) { _reporter.Error(ex.Message); return 1; } if (options == null) { // invalid args syntax return 1; } if (options.IsHelp) { return 2; } // update reporter as configured by options _reporter = CreateReporter(options.IsVerbose, options.IsQuiet, _console); try { if (_cts.IsCancellationRequested) { return 1; } if (options.ListFiles) { return await ListFilesAsync(_reporter, options.Project, _cts.Token); } else { return await MainInternalAsync(_reporter, options.Project, options.RemainingArguments, _cts.Token); } } catch (Exception ex) { if (ex is TaskCanceledException || ex is OperationCanceledException) { // swallow when only exception is the CTRL+C forced an exit return 0; } _reporter.Error(ex.ToString()); _reporter.Error("An unexpected error occurred"); return 1; } } private void OnCancelKeyPress(object sender, ConsoleCancelEventArgs args) { // suppress CTRL+C on the first press args.Cancel = !_cts.IsCancellationRequested; if (args.Cancel) { _reporter.Output("Shutdown requested. Press Ctrl+C again to force exit."); } _cts.Cancel(); } private async Task MainInternalAsync( IReporter reporter, string project, ICollection args, CancellationToken cancellationToken) { // TODO multiple projects should be easy enough to add here string projectFile; try { projectFile = MsBuildProjectFinder.FindMsBuildProject(_workingDir, project); } catch (FileNotFoundException ex) { reporter.Error(ex.Message); return 1; } var fileSetFactory = new MsBuildFileSetFactory(reporter, projectFile, waitOnError: true, trace: false); var processInfo = new ProcessSpec { Executable = DotNetMuxer.MuxerPathOrDefault(), WorkingDirectory = Path.GetDirectoryName(projectFile), Arguments = args, EnvironmentVariables = { ["DOTNET_WATCH"] = "1" }, }; if (CommandLineOptions.IsPollingEnabled) { _reporter.Output("Polling file watcher is enabled"); } await new DotNetWatcher(reporter) .WatchAsync(processInfo, fileSetFactory, cancellationToken); return 0; } private async Task ListFilesAsync( IReporter reporter, string project, CancellationToken cancellationToken) { // TODO multiple projects should be easy enough to add here string projectFile; try { projectFile = MsBuildProjectFinder.FindMsBuildProject(_workingDir, project); } catch (FileNotFoundException ex) { reporter.Error(ex.Message); return 1; } var fileSetFactory = new MsBuildFileSetFactory(reporter, projectFile, waitOnError: false, trace: false); var files = await fileSetFactory.CreateAsync(cancellationToken); if (files == null) { return 1; } foreach (var file in files) { _console.Out.WriteLine(file); } return 0; } private static IReporter CreateReporter(bool verbose, bool quiet, IConsole console) => new PrefixConsoleReporter(console, verbose || CliContext.IsGlobalVerbose(), quiet); public void Dispose() { _console.CancelKeyPress -= OnCancelKeyPress; _cts.Dispose(); } } } ================================================ FILE: src/dotnet-watch/Properties/AssemblyInfo.cs ================================================ // 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.Runtime.CompilerServices; [assembly: InternalsVisibleTo("Microsoft.DotNet.Watcher.Tools.Tests, PublicKey = 0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] ================================================ FILE: src/dotnet-watch/Properties/Resources.Designer.cs ================================================ // namespace Microsoft.DotNet.Watcher.Tools { using System.Globalization; using System.Reflection; using System.Resources; internal static class Resources { private static readonly ResourceManager _resourceManager = new ResourceManager("Microsoft.DotNet.Watcher.Tools.Resources", typeof(Resources).GetTypeInfo().Assembly); /// /// The project file '{path}' does not exist. /// internal static string Error_ProjectPath_NotFound { get { return GetString("Error_ProjectPath_NotFound"); } } /// /// The project file '{path}' does not exist. /// internal static string FormatError_ProjectPath_NotFound(object path) { return string.Format(CultureInfo.CurrentCulture, GetString("Error_ProjectPath_NotFound", "path"), path); } /// /// Multiple MSBuild project files found in '{projectPath}'. Specify which to use with the --project option. /// internal static string Error_MultipleProjectsFound { get { return GetString("Error_MultipleProjectsFound"); } } /// /// Multiple MSBuild project files found in '{projectPath}'. Specify which to use with the --project option. /// internal static string FormatError_MultipleProjectsFound(object projectPath) { return string.Format(CultureInfo.CurrentCulture, GetString("Error_MultipleProjectsFound", "projectPath"), projectPath); } /// /// Could not find a MSBuild project file in '{projectPath}'. Specify which project to use with the --project option. /// internal static string Error_NoProjectsFound { get { return GetString("Error_NoProjectsFound"); } } /// /// Could not find a MSBuild project file in '{projectPath}'. Specify which project to use with the --project option. /// internal static string FormatError_NoProjectsFound(object projectPath) { return string.Format(CultureInfo.CurrentCulture, GetString("Error_NoProjectsFound", "projectPath"), projectPath); } /// /// Cannot specify both '--quiet' and '--verbose' options. /// internal static string Error_QuietAndVerboseSpecified { get { return GetString("Error_QuietAndVerboseSpecified"); } } /// /// Cannot specify both '--quiet' and '--verbose' options. /// internal static string FormatError_QuietAndVerboseSpecified() { return GetString("Error_QuietAndVerboseSpecified"); } private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name); System.Diagnostics.Debug.Assert(value != null); if (formatterNames != null) { for (var i = 0; i < formatterNames.Length; i++) { value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}"); } } return value; } } } ================================================ FILE: src/dotnet-watch/README.md ================================================ dotnet-watch ============ `dotnet-watch` is a file watcher for `dotnet` that restarts the specified application when changes in the source code are detected. ### How To Use The command must be executed in the directory that contains the project to be watched. Usage: dotnet watch [options] [[--] ...] Options: -?|-h|--help Show help information -q|--quiet Suppresses all output except warnings and errors -v|--verbose Show verbose output Add `watch` after `dotnet` and before the command arguments that you want to run: | What you want to run | Dotnet watch command | | ---------------------------------------------- | -------------------------------------------------------- | | dotnet run | dotnet **watch** run | | dotnet run --arg1 value1 | dotnet **watch** run --arg1 value | | dotnet run --framework net451 -- --arg1 value1 | dotnet **watch** run --framework net451 -- --arg1 value1 | | dotnet test | dotnet **watch** test | ### Environment variables Some configuration options can be passed to `dotnet watch` through environment variables. The available variables are: | Variable | Effect | | ---------------------------------------------- | -------------------------------------------------------- | | DOTNET_USE_POLLING_FILE_WATCHER | If set to "1" or "true", `dotnet watch` will use a polling file watcher instead of CoreFx's `FileSystemWatcher`. Used when watching files on network shares or Docker mounted volumes. | ### MSBuild dotnet-watch can be configured from the MSBuild project file being watched. **Watch items** dotnet-watch will watch all items in the **Watch** item group. By default, this group inclues all items in **Compile** and **EmbeddedResource**. More items can be added to watch in a project file by adding items to 'Watch'. ```xml ``` dotnet-watch will ignore Compile and EmbeddedResource items with the `Watch="false"` attribute. Example: ```xml ``` **Project References** By default, dotnet-watch will scan the entire graph of project references and watch all files within those projects. dotnet-watch will ignore project references with the `Watch="false"` attribute. ```xml ``` **Advanced configuration** dotnet-watch performs a design-time build to find items to watch. When this build is run, dotnet-watch will set the property `DotNetWatchBuild=true`. Example: ```xml ``` ================================================ FILE: src/dotnet-watch/Resources.resx ================================================  text/microsoft-resx 2.0 System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 The project file '{path}' does not exist. Multiple MSBuild project files found in '{projectPath}'. Specify which to use with the --project option. Could not find a MSBuild project file in '{projectPath}'. Specify which project to use with the --project option. Cannot specify both '--quiet' and '--verbose' options. ================================================ FILE: src/dotnet-watch/assets/DotNetWatch.targets ================================================ <_CollectWatchItemsDependsOn Condition=" '$(TargetFrameworks)' != '' AND '$(TargetFramework)' == '' "> _CollectWatchItemsPerFramework; <_CollectWatchItemsDependsOn Condition=" '$(TargetFramework)' != '' "> _CoreCollectWatchItems; <_TargetFramework Include="$(TargetFrameworks)" /> <_WatchProjects Include="%(ProjectReference.Identity)" Condition="'%(ProjectReference.Watch)' != 'false'" /> ================================================ FILE: src/dotnet-watch/dotnet-watch.csproj ================================================  netcoreapp3.0 exe Command line tool to watch for source file changes during development and restart the dotnet command. Microsoft.DotNet.Watcher.Tools dotnet;watch true win-x64;win-x86 ================================================ FILE: test/Directory.Build.props ================================================ ================================================ FILE: test/Microsoft.HttpRepl.Tests/JsonVisitorTests.cs ================================================ 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; } } } ================================================ FILE: test/Microsoft.HttpRepl.Tests/Microsoft.HttpRepl.Tests.csproj ================================================ netcoreapp3.0 false ================================================ FILE: test/Microsoft.Repl.Tests/Microsoft.Repl.Tests.csproj ================================================  netcoreapp3.0 ================================================ FILE: test/Microsoft.Repl.Tests/ParserTests.cs ================================================ 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); } } } ================================================ FILE: test/Shared/TestConsole.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.IO; using System.Linq; using System.Reflection; using System.Threading.Tasks; using System.Text; using Xunit.Abstractions; namespace Microsoft.Extensions.Tools.Internal { public class TestConsole : IConsole { private event ConsoleCancelEventHandler _cancelKeyPress; private readonly TaskCompletionSource _cancelKeySubscribed = new TaskCompletionSource(); public TestConsole(ITestOutputHelper output) { var writer = new TestOutputWriter(output); Error = writer; Out = writer; } public event ConsoleCancelEventHandler CancelKeyPress { add { _cancelKeyPress += value; _cancelKeySubscribed.TrySetResult(true); } remove => _cancelKeyPress -= value; } public Task CancelKeyPressSubscribed => _cancelKeySubscribed.Task; public TextWriter Error { get; set; } public TextWriter Out { get; set; } public TextReader In { get; set; } = new StringReader(string.Empty); public bool IsInputRedirected { get; set; } = false; public bool IsOutputRedirected { get; } = false; public bool IsErrorRedirected { get; } = false; public ConsoleColor ForegroundColor { get; set; } public ConsoleCancelEventArgs ConsoleCancelKey() { var ctor = typeof(ConsoleCancelEventArgs) .GetTypeInfo() .DeclaredConstructors .Single(c => c.GetParameters().First().ParameterType == typeof(ConsoleSpecialKey)); var args = (ConsoleCancelEventArgs)ctor.Invoke(new object[] { ConsoleSpecialKey.ControlC }); _cancelKeyPress.Invoke(this, args); return args; } public void ResetColor() { } private class TestOutputWriter : TextWriter { private readonly ITestOutputHelper _output; private readonly StringBuilder _sb = new StringBuilder(); public TestOutputWriter(ITestOutputHelper output) { _output = output; } public override Encoding Encoding => Encoding.Unicode; public override void Write(char value) { if (value == '\r' || value == '\n') { if (_sb.Length > 0) { _output.WriteLine(_sb.ToString()); _sb.Clear(); } } else { _sb.Append(value); } } } } } ================================================ FILE: test/Shared/TestReporter.cs ================================================ // 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 Xunit.Abstractions; namespace Microsoft.Extensions.Tools.Internal { public class TestReporter : IReporter { private readonly ITestOutputHelper _output; public TestReporter(ITestOutputHelper output) { _output = output; } public void Verbose(string message) { _output.WriteLine("verbose: " + message); } public void Output(string message) { _output.WriteLine("output: " + message); } public void Warn(string message) { _output.WriteLine("warn: " + message); } public void Error(string message) { _output.WriteLine("error: " + message); } } } ================================================ FILE: test/dotnet-user-secrets.Tests/InitCommandTest.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.IO; using System.Text; using Microsoft.Extensions.Configuration.UserSecrets.Tests; using Microsoft.Extensions.SecretManager.Tools.Internal; using Microsoft.Extensions.Tools.Internal; using Xunit; using Xunit.Abstractions; namespace Microsoft.Extensions.SecretManager.Tools.Tests { public class InitCommandTests : IClassFixture { private UserSecretsTestFixture _fixture; private ITestOutputHelper _output; private TestConsole _console; private StringBuilder _textOutput; public InitCommandTests(UserSecretsTestFixture fixture, ITestOutputHelper output) { _fixture = fixture; _output = output; _textOutput = new StringBuilder(); _console = new TestConsole(output) { Error = new StringWriter(_textOutput), Out = new StringWriter(_textOutput), }; } private CommandContext MakeCommandContext() => new CommandContext(null, new TestReporter(_output), _console); [Fact] public void AddsSecretIdToProject() { var projectDir = _fixture.CreateProject(null); new InitCommand(null, null).Execute(MakeCommandContext(), projectDir); var idResolver = new ProjectIdResolver(MakeCommandContext().Reporter, projectDir); Assert.False(string.IsNullOrWhiteSpace(idResolver.Resolve(null, null))); } [Fact] public void AddsSpecificSecretIdToProject() { const string SecretId = "TestSecretId"; var projectDir = _fixture.CreateProject(null); new InitCommand(SecretId, null).Execute(MakeCommandContext(), projectDir); var idResolver = new ProjectIdResolver(MakeCommandContext().Reporter, projectDir); Assert.Equal(SecretId, idResolver.Resolve(null, null)); } [Fact] public void AddsEscapedSpecificSecretIdToProject() { const string SecretId = @"&"; var projectDir = _fixture.CreateProject(null); new InitCommand(SecretId, null).Execute(MakeCommandContext(), projectDir); var idResolver = new ProjectIdResolver(MakeCommandContext().Reporter, projectDir); Assert.Equal(SecretId, idResolver.Resolve(null, null)); } [Fact] public void DoesNotGenerateIdForProjectWithSecretId() { const string SecretId = "AlreadyExists"; var projectDir = _fixture.CreateProject(SecretId); new InitCommand(null, null).Execute(MakeCommandContext(), projectDir); var idResolver = new ProjectIdResolver(MakeCommandContext().Reporter, projectDir); Assert.Equal(SecretId, idResolver.Resolve(null, null)); } [Fact] public void OverridesIdForProjectWithSecretId() { const string SecretId = "AlreadyExists"; const string NewId = "TestValue"; var projectDir = _fixture.CreateProject(SecretId); new InitCommand(NewId, null).Execute(MakeCommandContext(), projectDir); var idResolver = new ProjectIdResolver(MakeCommandContext().Reporter, projectDir); Assert.Equal(NewId, idResolver.Resolve(null, null)); } [Fact] public void FailsForInvalidId() { string secretId = $"invalid{Path.GetInvalidPathChars()[0]}secret-id"; var projectDir = _fixture.CreateProject(null); Assert.Throws(() => { new InitCommand(secretId, null).Execute(MakeCommandContext(), projectDir); }); } } } ================================================ FILE: test/dotnet-user-secrets.Tests/MsBuildProjectFinderTest.cs ================================================ // 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 Microsoft.Extensions.SecretManager.Tools.Internal; using Xunit; namespace Microsoft.Extensions.SecretManager.Tools.Tests { public class MsBuildProjectFinderTest { [Theory] [InlineData(".csproj")] [InlineData(".vbproj")] [InlineData(".fsproj")] public void FindsSingleProject(string extension) { using (var files = new TemporaryFileProvider()) { var filename = "TestProject" + extension; files.Add(filename, ""); var finder = new MsBuildProjectFinder(files.Root); Assert.Equal(Path.Combine(files.Root, filename), finder.FindMsBuildProject(null)); } } [Fact] public void ThrowsWhenNoFile() { using (var files = new TemporaryFileProvider()) { var finder = new MsBuildProjectFinder(files.Root); Assert.Throws(() => finder.FindMsBuildProject(null)); } } [Fact] public void DoesNotMatchXproj() { using (var files = new TemporaryFileProvider()) { var finder = new MsBuildProjectFinder(files.Root); files.Add("test.xproj", ""); Assert.Throws(() => finder.FindMsBuildProject(null)); } } [Fact] public void ThrowsWhenMultipleFile() { using (var files = new TemporaryFileProvider()) { files.Add("Test1.csproj", ""); files.Add("Test2.csproj", ""); var finder = new MsBuildProjectFinder(files.Root); Assert.Throws(() => finder.FindMsBuildProject(null)); } } [Fact] public void ThrowsWhenFileDoesNotExist() { using (var files = new TemporaryFileProvider()) { var finder = new MsBuildProjectFinder(files.Root); Assert.Throws(() => finder.FindMsBuildProject("test.csproj")); } } [Fact] public void ThrowsWhenRootDoesNotExist() { var files = new TemporaryFileProvider(); var finder = new MsBuildProjectFinder(files.Root); files.Dispose(); Assert.Throws(() => finder.FindMsBuildProject(null)); } } } ================================================ FILE: test/dotnet-user-secrets.Tests/SecretManagerTests.cs ================================================ // 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.Text; using Microsoft.Extensions.Configuration.UserSecrets; using Microsoft.Extensions.Configuration.UserSecrets.Tests; using Microsoft.Extensions.Tools.Internal; using Xunit; using Xunit.Abstractions; namespace Microsoft.Extensions.SecretManager.Tools.Tests { public class SecretManagerTests : IClassFixture { private readonly TestConsole _console; private readonly UserSecretsTestFixture _fixture; private readonly StringBuilder _output = new StringBuilder(); public SecretManagerTests(UserSecretsTestFixture fixture, ITestOutputHelper output) { _fixture = fixture; _console = new TestConsole(output) { Error = new StringWriter(_output), Out = new StringWriter(_output), }; } private Program CreateProgram() { return new Program(_console, Directory.GetCurrentDirectory()); } [Theory] [InlineData(null)] [InlineData("")] public void Error_MissingId(string id) { var project = Path.Combine(_fixture.CreateProject(id), "TestProject.csproj"); var secretManager = CreateProgram(); secretManager.RunInternal("list", "-p", project); Assert.Contains(Resources.FormatError_ProjectMissingId(project), _output.ToString()); } [Fact] public void Error_InvalidProjectFormat() { var project = Path.Combine(_fixture.CreateProject("<"), "TestProject.csproj"); var secretManager = CreateProgram(); secretManager.RunInternal("list", "-p", project); Assert.Contains(Resources.FormatError_ProjectFailedToLoad(project), _output.ToString()); } [Fact] public void Error_Project_DoesNotExist() { var projectPath = Path.Combine(_fixture.GetTempSecretProject(), "does_not_exist", "TestProject.csproj"); var secretManager = CreateProgram(); secretManager.RunInternal("list", "--project", projectPath); Assert.Contains(Resources.FormatError_ProjectPath_NotFound(projectPath), _output.ToString()); } [Fact] public void SupportsRelativePaths() { var projectPath = _fixture.GetTempSecretProject(); var cwd = Path.Combine(projectPath, "nested1"); Directory.CreateDirectory(cwd); var secretManager = new Program(_console, cwd); secretManager.RunInternal("list", "-p", ".." + Path.DirectorySeparatorChar, "--verbose"); Assert.Contains(Resources.FormatMessage_Project_File_Path(Path.Combine(cwd, "..", "TestProject.csproj")), _output.ToString()); } [Theory] [InlineData(true)] [InlineData(false)] public void SetSecrets(bool fromCurrentDirectory) { var secrets = new KeyValuePair[] { new KeyValuePair("key1", Guid.NewGuid().ToString()), new KeyValuePair("Facebook:AppId", Guid.NewGuid().ToString()), new KeyValuePair(@"key-@\/.~123!#$%^&*())-+==", @"key-@\/.~123!#$%^&*())-+=="), new KeyValuePair("key2", string.Empty) }; var projectPath = _fixture.GetTempSecretProject(); var dir = fromCurrentDirectory ? projectPath : Path.GetTempPath(); var secretManager = new Program(_console, dir); foreach (var secret in secrets) { var parameters = fromCurrentDirectory ? new string[] { "set", secret.Key, secret.Value } : new string[] { "set", secret.Key, secret.Value, "-p", projectPath }; secretManager.RunInternal(parameters); } foreach (var keyValue in secrets) { Assert.Contains( string.Format("Successfully saved {0} = {1} to the secret store.", keyValue.Key, keyValue.Value), _output.ToString()); } _output.Clear(); var args = fromCurrentDirectory ? new string[] { "list" } : new string[] { "list", "-p", projectPath }; secretManager.RunInternal(args); foreach (var keyValue in secrets) { Assert.Contains( string.Format("{0} = {1}", keyValue.Key, keyValue.Value), _output.ToString()); } // Remove secrets. _output.Clear(); foreach (var secret in secrets) { var parameters = fromCurrentDirectory ? new string[] { "remove", secret.Key } : new string[] { "remove", secret.Key, "-p", projectPath }; secretManager.RunInternal(parameters); } // Verify secrets are removed. _output.Clear(); args = fromCurrentDirectory ? new string[] { "list" } : new string[] { "list", "-p", projectPath }; secretManager.RunInternal(args); Assert.Contains(Resources.Error_No_Secrets_Found, _output.ToString()); } [Fact] public void SetSecret_Update_Existing_Secret() { var projectPath = _fixture.GetTempSecretProject(); var secretManager = CreateProgram(); secretManager.RunInternal("set", "secret1", "value1", "-p", projectPath); Assert.Contains("Successfully saved secret1 = value1 to the secret store.", _output.ToString()); secretManager.RunInternal("set", "secret1", "value2", "-p", projectPath); Assert.Contains("Successfully saved secret1 = value2 to the secret store.", _output.ToString()); _output.Clear(); secretManager.RunInternal("list", "-p", projectPath); Assert.Contains("secret1 = value2", _output.ToString()); } [Fact] public void SetSecret_With_Verbose_Flag() { string secretId; var projectPath = _fixture.GetTempSecretProject(out secretId); var secretManager = CreateProgram(); secretManager.RunInternal("-v", "set", "secret1", "value1", "-p", projectPath); Assert.Contains(string.Format("Project file path {0}.", Path.Combine(projectPath, "TestProject.csproj")), _output.ToString()); Assert.Contains(string.Format("Secrets file path {0}.", PathHelper.GetSecretsPathFromSecretsId(secretId)), _output.ToString()); Assert.Contains("Successfully saved secret1 = value1 to the secret store.", _output.ToString()); _output.Clear(); secretManager.RunInternal("-v", "list", "-p", projectPath); Assert.Contains(string.Format("Project file path {0}.", Path.Combine(projectPath, "TestProject.csproj")), _output.ToString()); Assert.Contains(string.Format("Secrets file path {0}.", PathHelper.GetSecretsPathFromSecretsId(secretId)), _output.ToString()); Assert.Contains("secret1 = value1", _output.ToString()); } [Fact] public void Remove_Non_Existing_Secret() { var projectPath = _fixture.GetTempSecretProject(); var secretManager = CreateProgram(); secretManager.RunInternal("remove", "secret1", "-p", projectPath); Assert.Contains("Cannot find 'secret1' in the secret store.", _output.ToString()); } [Fact] public void Remove_Is_Case_Insensitive() { var projectPath = _fixture.GetTempSecretProject(); var secretManager = CreateProgram(); secretManager.RunInternal("set", "SeCreT1", "value", "-p", projectPath); secretManager.RunInternal("list", "-p", projectPath); Assert.Contains("SeCreT1 = value", _output.ToString()); secretManager.RunInternal("remove", "secret1", "-p", projectPath); _output.Clear(); secretManager.RunInternal("list", "-p", projectPath); Assert.Contains(Resources.Error_No_Secrets_Found, _output.ToString()); } [Fact] public void List_Flattens_Nested_Objects() { string secretId; var projectPath = _fixture.GetTempSecretProject(out secretId); var secretsFile = PathHelper.GetSecretsPathFromSecretsId(secretId); Directory.CreateDirectory(Path.GetDirectoryName(secretsFile)); File.WriteAllText(secretsFile, @"{ ""AzureAd"": { ""ClientSecret"": ""abcd郩˙î""} }", Encoding.UTF8); var secretManager = CreateProgram(); secretManager.RunInternal("list", "-p", projectPath); Assert.Contains("AzureAd:ClientSecret = abcd郩˙î", _output.ToString()); } [Fact] public void List_Json() { string id; var projectPath = _fixture.GetTempSecretProject(out id); var secretsFile = PathHelper.GetSecretsPathFromSecretsId(id); Directory.CreateDirectory(Path.GetDirectoryName(secretsFile)); File.WriteAllText(secretsFile, @"{ ""AzureAd"": { ""ClientSecret"": ""abcd郩˙î""} }", Encoding.UTF8); var secretManager = new Program(_console, Path.GetDirectoryName(projectPath)); secretManager.RunInternal("list", "--id", id, "--json"); var stdout = _output.ToString(); Assert.Contains("//BEGIN", stdout); Assert.Contains(@"""AzureAd:ClientSecret"": ""abcd郩˙î""", stdout); Assert.Contains("//END", stdout); } [Fact] public void Set_Flattens_Nested_Objects() { string secretId; var projectPath = _fixture.GetTempSecretProject(out secretId); var secretsFile = PathHelper.GetSecretsPathFromSecretsId(secretId); Directory.CreateDirectory(Path.GetDirectoryName(secretsFile)); File.WriteAllText(secretsFile, @"{ ""AzureAd"": { ""ClientSecret"": ""abcd郩˙î""} }", Encoding.UTF8); var secretManager = CreateProgram(); secretManager.RunInternal("set", "AzureAd:ClientSecret", "¡™£¢∞", "-p", projectPath); secretManager.RunInternal("list", "-p", projectPath); Assert.Contains("AzureAd:ClientSecret = ¡™£¢∞", _output.ToString()); var fileContents = File.ReadAllText(secretsFile, Encoding.UTF8); Assert.Equal(@"{ ""AzureAd:ClientSecret"": ""¡™£¢∞"" }", fileContents, ignoreLineEndingDifferences: true, ignoreWhiteSpaceDifferences: true); } [Fact] public void List_Empty_Secrets_File() { var projectPath = _fixture.GetTempSecretProject(); var secretManager = CreateProgram(); secretManager.RunInternal("list", "-p", projectPath); Assert.Contains(Resources.Error_No_Secrets_Found, _output.ToString()); } [Theory] [InlineData(true)] [InlineData(false)] public void Clear_Secrets(bool fromCurrentDirectory) { var projectPath = _fixture.GetTempSecretProject(); var dir = fromCurrentDirectory ? projectPath : Path.GetTempPath(); var secretManager = new Program(_console, dir); var secrets = new KeyValuePair[] { new KeyValuePair("key1", Guid.NewGuid().ToString()), new KeyValuePair("Facebook:AppId", Guid.NewGuid().ToString()), new KeyValuePair(@"key-@\/.~123!#$%^&*())-+==", @"key-@\/.~123!#$%^&*())-+=="), new KeyValuePair("key2", string.Empty) }; foreach (var secret in secrets) { var parameters = fromCurrentDirectory ? new string[] { "set", secret.Key, secret.Value } : new string[] { "set", secret.Key, secret.Value, "-p", projectPath }; secretManager.RunInternal(parameters); } foreach (var keyValue in secrets) { Assert.Contains( string.Format("Successfully saved {0} = {1} to the secret store.", keyValue.Key, keyValue.Value), _output.ToString()); } // Verify secrets are persisted. _output.Clear(); var args = fromCurrentDirectory ? new string[] { "list" } : new string[] { "list", "-p", projectPath }; secretManager.RunInternal(args); foreach (var keyValue in secrets) { Assert.Contains( string.Format("{0} = {1}", keyValue.Key, keyValue.Value), _output.ToString()); } // Clear secrets. _output.Clear(); args = fromCurrentDirectory ? new string[] { "clear" } : new string[] { "clear", "-p", projectPath }; secretManager.RunInternal(args); args = fromCurrentDirectory ? new string[] { "list" } : new string[] { "list", "-p", projectPath }; secretManager.RunInternal(args); Assert.Contains(Resources.Error_No_Secrets_Found, _output.ToString()); } [Fact] public void Init_When_Project_Has_No_Secrets_Id() { var projectPath = _fixture.CreateProject(null); var project = Path.Combine(projectPath, "TestProject.csproj"); var secretManager = new Program(_console, projectPath); secretManager.RunInternal("init", "-p", project); Assert.DoesNotContain(Resources.FormatError_ProjectMissingId(project), _output.ToString()); Assert.DoesNotContain("--help", _output.ToString()); } } } ================================================ FILE: test/dotnet-user-secrets.Tests/SetCommandTest.cs ================================================ // 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.Collections.Generic; using Microsoft.Extensions.SecretManager.Tools.Internal; using Microsoft.Extensions.Tools.Internal; using Xunit; using Xunit.Abstractions; namespace Microsoft.Extensions.SecretManager.Tools.Tests { public class SetCommandTest { private readonly ITestOutputHelper _output; public SetCommandTest(ITestOutputHelper output) { _output = output; } [Fact] public void SetsFromPipedInput() { var input = @" { ""Key1"": ""str value"", ""Key2"": 1234, ""Key3"": false }"; var testConsole = new TestConsole(_output) { IsInputRedirected = true, In = new StringReader(input) }; var secretStore = new TestSecretsStore(_output); var command = new SetCommand.FromStdInStrategy(); command.Execute(new CommandContext(secretStore, new TestReporter(_output), testConsole)); Assert.Equal(3, secretStore.Count); Assert.Equal("str value", secretStore["Key1"]); Assert.Equal("1234", secretStore["Key2"]); Assert.Equal("False", secretStore["Key3"]); } [Fact] public void ParsesNestedObjects() { var input = @" { ""Key1"": { ""nested"" : ""value"" }, ""array"": [ 1, 2 ] }"; var testConsole = new TestConsole(_output) { IsInputRedirected = true, In = new StringReader(input) }; var secretStore = new TestSecretsStore(_output); var command = new SetCommand.FromStdInStrategy(); command.Execute(new CommandContext(secretStore, new TestReporter(_output), testConsole)); Assert.Equal(3, secretStore.Count); Assert.True(secretStore.ContainsKey("Key1:nested")); Assert.Equal("value", secretStore["Key1:nested"]); Assert.Equal("1", secretStore["array:0"]); Assert.Equal("2", secretStore["array:1"]); } [Fact] public void OnlyPipesInIfNoArgs() { var testConsole = new TestConsole(_output) { IsInputRedirected = true, In = new StringReader("") }; var options = CommandLineOptions.Parse(new [] { "set", "key", "value" }, testConsole); Assert.IsType(options.Command); } private class TestSecretsStore : SecretsStore { public TestSecretsStore(ITestOutputHelper output) : base("xyz", new TestReporter(output)) { } protected override IDictionary Load(string userSecretsId) { return new Dictionary(); } public override void Save() { // noop } } } } ================================================ FILE: test/dotnet-user-secrets.Tests/TemporaryFileProvider.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.IO; using System.Text; namespace Microsoft.Extensions.SecretManager.Tools.Tests { internal class TemporaryFileProvider : IDisposable { public TemporaryFileProvider() { Root = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), "tmpfiles", Guid.NewGuid().ToString())).FullName; } public string Root { get; } public void Add(string filename, string contents) { File.WriteAllText(Path.Combine(Root, filename), contents, Encoding.UTF8); } public void Dispose() { Directory.Delete(Root, recursive: true); } } } ================================================ FILE: test/dotnet-user-secrets.Tests/UserSecretsTestFixture.cs ================================================ // 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; namespace Microsoft.Extensions.Configuration.UserSecrets.Tests { public class UserSecretsTestFixture : IDisposable { private Stack _disposables = new Stack(); public const string TestSecretsId = "b918174fa80346bbb7f4a386729c0eff"; public UserSecretsTestFixture() { _disposables.Push(() => TryDelete(Path.GetDirectoryName(PathHelper.GetSecretsPathFromSecretsId(TestSecretsId)))); } public void Dispose() { while (_disposables.Count > 0) { _disposables.Pop()?.Invoke(); } } public string GetTempSecretProject() { string userSecretsId; return GetTempSecretProject(out userSecretsId); } private const string ProjectTemplate = @" Exe netcoreapp3.0 {0} false "; public string GetTempSecretProject(out string userSecretsId) { userSecretsId = Guid.NewGuid().ToString(); return CreateProject(userSecretsId); } public string CreateProject(string userSecretsId) { var projectPath = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), "usersecretstest", Guid.NewGuid().ToString())); var prop = string.IsNullOrEmpty(userSecretsId) ? string.Empty : $"{userSecretsId}"; File.WriteAllText( Path.Combine(projectPath.FullName, "TestProject.csproj"), string.Format(ProjectTemplate, prop)); var id = userSecretsId; _disposables.Push(() => { try { // may throw if id is bad var secretsDir = Path.GetDirectoryName(PathHelper.GetSecretsPathFromSecretsId(id)); TryDelete(secretsDir); } catch { } }); _disposables.Push(() => TryDelete(projectPath.FullName)); return projectPath.FullName; } private static void TryDelete(string directory) { try { if (Directory.Exists(directory)) { Directory.Delete(directory, true); } } catch (Exception) { // Ignore failures. Console.WriteLine("Failed to delete " + directory); } } } } ================================================ FILE: test/dotnet-user-secrets.Tests/dotnet-user-secrets.Tests.csproj ================================================ netcoreapp3.0 Microsoft.Extensions.SecretManager.Tools.Tests ================================================ FILE: test/dotnet-watch.FunctionalTests/AppWithDepsTests.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.IO; using System.Threading.Tasks; using Microsoft.AspNetCore.Testing; using Xunit; using Xunit.Abstractions; namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests { public class AppWithDepsTests : IDisposable { private readonly AppWithDeps _app; public AppWithDepsTests(ITestOutputHelper logger) { _app = new AppWithDeps(logger); } [Fact] public async Task ChangeFileInDependency() { await _app.StartWatcherAsync(); var fileToChange = Path.Combine(_app.DependencyFolder, "Foo.cs"); var programCs = File.ReadAllText(fileToChange); File.WriteAllText(fileToChange, programCs); await _app.HasRestarted(); } public void Dispose() { _app.Dispose(); } private class AppWithDeps : WatchableApp { private const string Dependency = "Dependency"; public AppWithDeps(ITestOutputHelper logger) : base("AppWithDeps", logger) { Scenario.AddTestProjectFolder(Dependency); DependencyFolder = Path.Combine(Scenario.WorkFolder, Dependency); } public string DependencyFolder { get; private set; } } } } ================================================ FILE: test/dotnet-watch.FunctionalTests/AwaitableProcess.cs ================================================ // 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.Threading; using System.Threading.Tasks; using System.Threading.Tasks.Dataflow; using Microsoft.Extensions.Internal; using Microsoft.Extensions.CommandLineUtils; using Xunit.Abstractions; namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests { public class AwaitableProcess : IDisposable { 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) { throw new InvalidOperationException("Already started"); } _process = new Process { EnableRaisingEvents = true, StartInfo = new ProcessStartInfo { UseShellExecute = false, FileName = _spec.Executable, WorkingDirectory = _spec.WorkingDirectory, Arguments = ArgumentEscaper.EscapeAndConcatenate(_spec.Arguments), RedirectStandardOutput = true, RedirectStandardError = true, Environment = { ["DOTNET_SKIP_FIRST_TIME_EXPERIENCE"] = "true" } } }; foreach (var env in _spec.EnvironmentVariables) { _process.StartInfo.EnvironmentVariables[env.Key] = env.Value; } _process.OutputDataReceived += OnData; _process.ErrorDataReceived += OnData; _process.Exited += OnExit; _process.Start(); _process.BeginErrorReadLine(); _process.BeginOutputReadLine(); _logger.WriteLine($"{DateTime.Now}: process start: '{_process.StartInfo.FileName} {_process.StartInfo.Arguments}'"); } public async Task GetOutputLineAsync(string message, TimeSpan timeout) { _logger.WriteLine($"Waiting for output line [msg == '{message}']. Will wait for {timeout.TotalSeconds} sec."); 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."); 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(string predicateName, Predicate predicate, CancellationToken cancellationToken) { while (!_source.Completion.IsCompleted) { while (await _source.OutputAvailableAsync(cancellationToken)) { var next = await _source.ReceiveAsync(cancellationToken); _lines.Add(next); var match = predicate(next); _logger.WriteLine($"{DateTime.Now}: recv: '{next}'. {(match ? "Matches" : "Does not match")} condition '{predicateName}'."); if (match) { return next; } } } return null; } public async Task> GetAllOutputLinesAsync(CancellationToken cancellationToken) { var lines = new List(); while (!_source.Completion.IsCompleted) { while (await _source.OutputAvailableAsync(cancellationToken)) { var next = await _source.ReceiveAsync(cancellationToken); _logger.WriteLine($"{DateTime.Now}: recv: '{next}'"); lines.Add(next); } } return lines; } private void OnData(object sender, DataReceivedEventArgs args) { var line = args.Data ?? string.Empty; _logger.WriteLine($"{DateTime.Now}: post: '{line}'"); _source.Post(line); } private void OnExit(object sender, EventArgs args) { // 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() { _source.Complete(); if (_process != null) { if (!_process.HasExited) { _process.KillTree(); } _process.ErrorDataReceived -= OnData; _process.OutputDataReceived -= OnData; _process.Exited -= OnExit; _process.Dispose(); } } } } ================================================ FILE: test/dotnet-watch.FunctionalTests/DotNetWatcherTests.cs ================================================ // 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.Globalization; using System.Threading.Tasks; using Xunit; using Xunit.Abstractions; 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); } [Fact] public async Task RunsWithDotnetWatchEnvVariable() { Assert.True(string.IsNullOrEmpty(Environment.GetEnvironmentVariable("DOTNET_WATCH")), "DOTNET_WATCH cannot be set already when this test is running"); await _app.StartWatcherAsync(); const string messagePrefix = "DOTNET_WATCH = "; var message = await _app.Process.GetOutputLineStartsWithAsync(messagePrefix, TimeSpan.FromMinutes(2)); var envValue = message.Substring(messagePrefix.Length); Assert.Equal("1", envValue); } [Fact] public async Task RunsWithIterationEnvVariable() { 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 <= 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); await _app.IsWaitingForFileChange(); try { File.SetLastWriteTime(source, DateTime.Now); await _app.HasRestarted(); } catch (Exception ex) { _logger.WriteLine("Retrying. First attempt to restart app failed: " + ex.Message); // retry File.SetLastWriteTime(source, DateTime.Now); await _app.HasRestarted(); } } } public void Dispose() { _app.Dispose(); } private class KitchenSinkApp : WatchableApp { public KitchenSinkApp(ITestOutputHelper logger) : base("KitchenSink", logger) { } } } } ================================================ FILE: test/dotnet-watch.FunctionalTests/FileWatcherTests.cs ================================================ // 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; 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)] [InlineData(false)] public void NewFile(bool usePolling) { UsingTempDirectory(dir => { using (var changedEv = new ManualResetEvent(false)) using (var watcher = FileWatcherFactory.CreateWatcher(dir, usePolling)) { var filesChanged = new HashSet(); watcher.OnFileChange += (_, f) => { filesChanged.Add(f); changedEv.Set(); }; watcher.EnableRaisingEvents = true; var testFileFullPath = Path.Combine(dir, "foo"); File.WriteAllText(testFileFullPath, string.Empty); Assert.True(changedEv.WaitOne(DefaultTimeout)); Assert.Equal(testFileFullPath, filesChanged.Single()); } }); } [Theory] [InlineData(true)] [InlineData(false)] public void ChangeFile(bool usePolling) { UsingTempDirectory(dir => { var testFileFullPath = Path.Combine(dir, "foo"); File.WriteAllText(testFileFullPath, string.Empty); using (var changedEv = new ManualResetEvent(false)) using (var watcher = FileWatcherFactory.CreateWatcher(dir, usePolling)) { var filesChanged = new HashSet(); EventHandler handler = null; handler = (_, f) => { watcher.EnableRaisingEvents = false; watcher.OnFileChange -= handler; filesChanged.Add(f); changedEv.Set(); }; watcher.OnFileChange += handler; watcher.EnableRaisingEvents = true; // 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.WriteAllText(testFileFullPath, string.Empty); Assert.True(changedEv.WaitOne(DefaultTimeout)); Assert.Equal(testFileFullPath, filesChanged.Single()); } }); } [Theory] [InlineData(true)] [InlineData(false)] public void MoveFile(bool usePolling) { UsingTempDirectory(dir => { var srcFile = Path.Combine(dir, "foo"); var dstFile = Path.Combine(dir, "foo2"); File.WriteAllText(srcFile, string.Empty); using (var changedEv = new ManualResetEvent(false)) using (var watcher = FileWatcherFactory.CreateWatcher(dir, usePolling)) { var filesChanged = new HashSet(); EventHandler handler = null; handler = (_, f) => { filesChanged.Add(f); if (filesChanged.Count >= 2) { watcher.EnableRaisingEvents = false; watcher.OnFileChange -= handler; changedEv.Set(); } }; watcher.OnFileChange += handler; watcher.EnableRaisingEvents = true; File.Move(srcFile, dstFile); Assert.True(changedEv.WaitOne(DefaultTimeout)); Assert.Contains(srcFile, filesChanged); Assert.Contains(dstFile, filesChanged); } }); } [Fact] public void FileInSubdirectory() { UsingTempDirectory(dir => { var subdir = Path.Combine(dir, "subdir"); Directory.CreateDirectory(subdir); var testFileFullPath = Path.Combine(subdir, "foo"); File.WriteAllText(testFileFullPath, string.Empty); using (var changedEv = new ManualResetEvent(false)) using (var watcher = FileWatcherFactory.CreateWatcher(dir, true)) { var filesChanged = new HashSet(); EventHandler handler = null; handler = (_, f) => { filesChanged.Add(f); if (filesChanged.Count >= 2) { watcher.EnableRaisingEvents = false; watcher.OnFileChange -= handler; changedEv.Set(); } }; watcher.OnFileChange += handler; watcher.EnableRaisingEvents = true; // 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.WriteAllText(testFileFullPath, string.Empty); Assert.True(changedEv.WaitOne(DefaultTimeout)); Assert.Contains(subdir, filesChanged); Assert.Contains(testFileFullPath, filesChanged); } }); } [Theory] [InlineData(true)] [InlineData(false)] public void NoNotificationIfDisabled(bool usePolling) { UsingTempDirectory(dir => { using (var watcher = FileWatcherFactory.CreateWatcher(dir, usePolling)) using (var changedEv = new ManualResetEvent(false)) { watcher.OnFileChange += (_, f) => changedEv.Set(); // Disable watcher.EnableRaisingEvents = false; var testFileFullPath = Path.Combine(dir, "foo"); // 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.WriteAllText(testFileFullPath, string.Empty); Assert.False(changedEv.WaitOne(DefaultTimeout / 2)); } }); } [Theory] [InlineData(true)] [InlineData(false)] public void DisposedNoEvents(bool usePolling) { UsingTempDirectory(dir => { using (var changedEv = new ManualResetEvent(false)) { using (var watcher = FileWatcherFactory.CreateWatcher(dir, usePolling)) { watcher.OnFileChange += (_, f) => changedEv.Set(); watcher.EnableRaisingEvents = true; } var testFileFullPath = Path.Combine(dir, "foo"); // 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.WriteAllText(testFileFullPath, string.Empty); Assert.False(changedEv.WaitOne(DefaultTimeout / 2)); } }); } [Theory] [InlineData(true)] [InlineData(false)] public void MultipleFiles(bool usePolling) { UsingTempDirectory(dir => { File.WriteAllText(Path.Combine(dir, "foo1"), string.Empty); File.WriteAllText(Path.Combine(dir, "foo2"), string.Empty); File.WriteAllText(Path.Combine(dir, "foo3"), string.Empty); File.WriteAllText(Path.Combine(dir, "foo4"), string.Empty); File.WriteAllText(Path.Combine(dir, "foo4"), string.Empty); var testFileFullPath = Path.Combine(dir, "foo3"); using (var changedEv = new ManualResetEvent(false)) using (var watcher = FileWatcherFactory.CreateWatcher(dir, usePolling)) { var filesChanged = new HashSet(); EventHandler handler = null; handler = (_, f) => { watcher.EnableRaisingEvents = false; watcher.OnFileChange -= handler; filesChanged.Add(f); changedEv.Set(); }; watcher.OnFileChange += handler; watcher.EnableRaisingEvents = true; // 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.WriteAllText(testFileFullPath, string.Empty); Assert.True(changedEv.WaitOne(DefaultTimeout)); Assert.Equal(testFileFullPath, filesChanged.Single()); } }); } [Theory] [InlineData(true)] [InlineData(false)] public void MultipleTriggers(bool usePolling) { var filesChanged = new HashSet(); UsingTempDirectory(dir => { using (var watcher = FileWatcherFactory.CreateWatcher(dir, usePolling)) { watcher.EnableRaisingEvents = true; for (var i = 0; i < 5; i++) { AssertFileChangeRaisesEvent(dir, watcher); } watcher.EnableRaisingEvents = false; } }); } private void AssertFileChangeRaisesEvent(string directory, IFileSystemWatcher watcher) { using (var semaphoreSlim = new SemaphoreSlim(0)) { 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 { // 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; } } } [Theory] [InlineData(true)] [InlineData(false)] public void DeleteSubfolder(bool usePolling) { UsingTempDirectory(dir => { var subdir = Path.Combine(dir, "subdir"); Directory.CreateDirectory(subdir); var f1 = Path.Combine(subdir, "foo1"); var f2 = Path.Combine(subdir, "foo2"); var f3 = Path.Combine(subdir, "foo3"); File.WriteAllText(f1, string.Empty); File.WriteAllText(f2, string.Empty); File.WriteAllText(f3, string.Empty); using (var changedEv = new AutoResetEvent(false)) using (var watcher = FileWatcherFactory.CreateWatcher(dir, usePolling)) { var filesChanged = new HashSet(); EventHandler handler = null; handler = (_, f) => { filesChanged.Add(f); if (filesChanged.Count >= 4) { watcher.EnableRaisingEvents = false; watcher.OnFileChange -= handler; changedEv.Set(); } }; watcher.OnFileChange += handler; watcher.EnableRaisingEvents = true; Directory.Delete(subdir, recursive: true); Assert.True(changedEv.WaitOne(DefaultTimeout)); Assert.Contains(f1, filesChanged); Assert.Contains(f2, filesChanged); Assert.Contains(f3, filesChanged); Assert.Contains(subdir, filesChanged); } }); } private static void UsingTempDirectory(Action action) { var tempFolder = Path.Combine(Path.GetTempPath(), $"{nameof(FileWatcherTests)}-{Guid.NewGuid().ToString("N")}"); if (Directory.Exists(tempFolder)) { Directory.Delete(tempFolder, recursive: true); } Directory.CreateDirectory(tempFolder); try { action(tempFolder); } finally { Directory.Delete(tempFolder, recursive: true); } } } } ================================================ FILE: test/dotnet-watch.FunctionalTests/GlobbingAppTests.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.Watcher.Tools.Tests; using Xunit; using Xunit.Abstractions; namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests { public class GlobbingAppTests : IDisposable { private GlobbingApp _app; public GlobbingAppTests(ITestOutputHelper logger) { _app = new GlobbingApp(logger); } [Theory] [InlineData(true)] [InlineData(false)] public async Task ChangeCompiledFile(bool usePollingWatcher) { _app.UsePollingWatcher = usePollingWatcher; await _app.StartWatcherAsync(); var types = await _app.GetCompiledAppDefinedTypes(); Assert.Equal(2, types); var fileToChange = Path.Combine(_app.SourceDirectory, "include", "Foo.cs"); var programCs = File.ReadAllText(fileToChange); File.WriteAllText(fileToChange, programCs); await _app.HasRestarted(); types = await _app.GetCompiledAppDefinedTypes(); Assert.Equal(2, types); } [Fact] public async Task DeleteCompiledFile() { await _app.StartWatcherAsync(); var types = await _app.GetCompiledAppDefinedTypes(); Assert.Equal(2, types); var fileToChange = Path.Combine(_app.SourceDirectory, "include", "Foo.cs"); File.Delete(fileToChange); await _app.HasRestarted(); types = await _app.GetCompiledAppDefinedTypes(); Assert.Equal(1, types); } [Fact] public async Task DeleteSourceFolder() { await _app.StartWatcherAsync(); var types = await _app.GetCompiledAppDefinedTypes(); Assert.Equal(2, types); var folderToDelete = Path.Combine(_app.SourceDirectory, "include"); Directory.Delete(folderToDelete, recursive: true); await _app.HasRestarted(); types = await _app.GetCompiledAppDefinedTypes(); Assert.Equal(1, types); } [Fact] public async Task RenameCompiledFile() { await _app.StartWatcherAsync(); var oldFile = Path.Combine(_app.SourceDirectory, "include", "Foo.cs"); var newFile = Path.Combine(_app.SourceDirectory, "include", "Foo_new.cs"); File.Move(oldFile, newFile); await _app.HasRestarted(); } [Fact] public async Task ChangeExcludedFile() { await _app.StartWatcherAsync(); var changedFile = Path.Combine(_app.SourceDirectory, "exclude", "Baz.cs"); File.WriteAllText(changedFile, ""); var restart = _app.HasRestarted(); var finished = await Task.WhenAny(Task.Delay(TimeSpan.FromSeconds(10)), restart); Assert.NotSame(restart, finished); } [Fact] public async Task ListsFiles() { await _app.PrepareAsync(); _app.Start(new [] { "--list" }); 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( _app.Scenario.WorkFolder, new[] { "GlobbingApp/Program.cs", "GlobbingApp/include/Foo.cs", "GlobbingApp/GlobbingApp.csproj", }, files); } public void Dispose() { _app.Dispose(); } private class GlobbingApp : WatchableApp { public GlobbingApp(ITestOutputHelper logger) : base("GlobbingApp", logger) { } public async Task GetCompiledAppDefinedTypes() { var definedTypesMessage = await Process.GetOutputLineStartsWithAsync("Defined types = ", TimeSpan.FromSeconds(30)); return int.Parse(definedTypesMessage.Split('=').Last()); } } } } ================================================ FILE: test/dotnet-watch.FunctionalTests/NoDepsAppTests.cs ================================================ // 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 Xunit; using Xunit.Abstractions; namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests { public class NoDepsAppTests : IDisposable { 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; } [Fact] public async Task RestartProcessOnFileChange() { await _app.StartWatcherAsync(new[] { "--no-exit" }); var processIdentifier = await _app.GetProcessIdentifier(); // Then wait for it to restart when we change a file var fileToChange = Path.Combine(_app.SourceDirectory, "Program.cs"); var programCs = File.ReadAllText(fileToChange); File.WriteAllText(fileToChange, programCs); await _app.HasRestarted(); Assert.DoesNotContain(_app.Process.Output, l => l.StartsWith("Exited with error code")); var processIdentifier2 = await _app.GetProcessIdentifier(); Assert.NotEqual(processIdentifier, processIdentifier2); } [Fact] public async Task RestartProcessThatTerminatesAfterFileChange() { await _app.StartWatcherAsync(); var processIdentifier = await _app.GetProcessIdentifier(); await _app.HasExited(); // process should exit after run await _app.IsWaitingForFileChange(); var fileToChange = Path.Combine(_app.SourceDirectory, "Program.cs"); try { File.SetLastWriteTime(fileToChange, DateTime.Now); await _app.HasRestarted(); } catch { // retry File.SetLastWriteTime(fileToChange, DateTime.Now); await _app.HasRestarted(); } var processIdentifier2 = await _app.GetProcessIdentifier(); Assert.NotEqual(processIdentifier, processIdentifier2); await _app.HasExited(); // process should exit after run } public void Dispose() { _app.Dispose(); } } } ================================================ FILE: test/dotnet-watch.FunctionalTests/Scenario/ProjectToolScenario.cs ================================================ // 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.Linq; using System.Reflection; using System.Threading; using System.Threading.Tasks; using System.Xml.Linq; using Microsoft.Extensions.CommandLineUtils; using Microsoft.Extensions.Internal; using Xunit.Abstractions; namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests { public class ProjectToolScenario : IDisposable { private static readonly string TestProjectSourceRoot = Path.Combine(AppContext.BaseDirectory, "TestProjects"); private readonly ITestOutputHelper _logger; public ProjectToolScenario() : this(null) { } public ProjectToolScenario(ITestOutputHelper logger) { WorkFolder = Path.Combine(AppContext.BaseDirectory, "tmp", Path.GetRandomFileName()); DotNetWatchPath = Path.Combine(AppContext.BaseDirectory, "tool", "dotnet-watch.dll"); _logger = logger; _logger?.WriteLine($"The temporary test folder is {WorkFolder}"); CreateTestDirectory(); } public string WorkFolder { get; } public string DotNetWatchPath { get; } public void AddTestProjectFolder(string projectName) { var srcFolder = Path.Combine(TestProjectSourceRoot, projectName); var destinationFolder = Path.Combine(WorkFolder, Path.GetFileName(projectName)); _logger?.WriteLine($"Copying project {srcFolder} to {destinationFolder}"); Directory.CreateDirectory(destinationFolder); foreach (var directory in Directory.GetDirectories(srcFolder, "*", SearchOption.AllDirectories)) { Directory.CreateDirectory(directory.Replace(srcFolder, destinationFolder)); } foreach (var file in Directory.GetFiles(srcFolder, "*", SearchOption.AllDirectories)) { File.Copy(file, file.Replace(srcFolder, destinationFolder), true); } } public Task RestoreAsync(string project) { _logger?.WriteLine($"Restoring msbuild project in {project}"); return ExecuteCommandAsync(project, TimeSpan.FromSeconds(120), "restore"); } public Task BuildAsync(string project) { _logger?.WriteLine($"Building {project}"); return ExecuteCommandAsync(project, TimeSpan.FromSeconds(60), "build"); } private async Task ExecuteCommandAsync(string project, TimeSpan timeout, params string[] arguments) { var tcs = new TaskCompletionSource(); project = Path.Combine(WorkFolder, project); _logger?.WriteLine($"Project directory: '{project}'"); var process = new Process { EnableRaisingEvents = true, StartInfo = new ProcessStartInfo { FileName = DotNetMuxer.MuxerPathOrDefault(), Arguments = ArgumentEscaper.EscapeAndConcatenate(arguments), WorkingDirectory = project, RedirectStandardOutput = true, RedirectStandardError = true, Environment = { ["DOTNET_SKIP_FIRST_TIME_EXPERIENCE"] = "true" } }, }; void OnData(object sender, DataReceivedEventArgs args) => _logger?.WriteLine(args.Data ?? string.Empty); void OnExit(object sender, EventArgs args) { _logger?.WriteLine($"Process exited {process.Id}"); tcs.TrySetResult(null); } process.ErrorDataReceived += OnData; process.OutputDataReceived += OnData; process.Exited += OnExit; process.Start(); process.BeginErrorReadLine(); process.BeginOutputReadLine(); _logger?.WriteLine($"Started process {process.Id}: {process.StartInfo.FileName} {process.StartInfo.Arguments}"); var done = await Task.WhenAny(tcs.Task, Task.Delay(timeout)); process.CancelErrorRead(); process.CancelOutputRead(); process.ErrorDataReceived -= OnData; process.OutputDataReceived -= OnData; process.Exited -= OnExit; if (!ReferenceEquals(done, tcs.Task)) { if (!process.HasExited) { _logger?.WriteLine($"Killing process {process.Id}"); process.KillTree(); } throw new TimeoutException($"Process timed out after {timeout.TotalSeconds} seconds"); } _logger?.WriteLine($"Process exited {process.Id} with code {process.ExitCode}"); if (process.ExitCode != 0) { throw new InvalidOperationException($"Exit code {process.ExitCode}"); } } private void CreateTestDirectory() { Directory.CreateDirectory(WorkFolder); File.WriteAllText(Path.Combine(WorkFolder, "Directory.Build.props"), ""); var restoreSources = GetMetadata("TestSettings:RestoreSources"); var frameworkVersion = GetMetadata("TestSettings:RuntimeFrameworkVersion"); var dbTargets = new XDocument( new XElement("Project", new XElement("PropertyGroup", new XElement("RuntimeFrameworkVersion", frameworkVersion), new XElement("RestoreSources", restoreSources)))); dbTargets.Save(Path.Combine(WorkFolder, "Directory.Build.targets")); } private string GetMetadata(string key) { return typeof(ProjectToolScenario) .Assembly .GetCustomAttributes() .First(a => string.Equals(a.Key, key, StringComparison.Ordinal)) .Value; } public void Dispose() { try { Directory.Delete(WorkFolder, recursive: true); } catch { Console.WriteLine($"Failed to delete {WorkFolder}. Retrying..."); Thread.Sleep(TimeSpan.FromSeconds(5)); Directory.Delete(WorkFolder, recursive: true); } } } } ================================================ FILE: test/dotnet-watch.FunctionalTests/Scenario/WatchableApp.cs ================================================ // 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; using System.Runtime.CompilerServices; using System.Threading.Tasks; using Microsoft.Extensions.CommandLineUtils; using Xunit.Abstractions; namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests { public class WatchableApp : IDisposable { private static readonly TimeSpan DefaultMessageTimeOut = TimeSpan.FromSeconds(30); 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; private bool _prepared; public WatchableApp(string appName, ITestOutputHelper logger) { _logger = logger; _appName = appName; Scenario = new ProjectToolScenario(logger); Scenario.AddTestProjectFolder(appName); SourceDirectory = Path.Combine(Scenario.WorkFolder, appName); } public ProjectToolScenario Scenario { get; } public AwaitableProcess Process { get; protected set; } public string SourceDirectory { get; } public Task HasRestarted() => Process.GetOutputLineAsync(StartedMessage, DefaultMessageTimeOut); public async Task HasExited() { await Process.GetOutputLineAsync(ExitingMessage, DefaultMessageTimeOut); await Process.GetOutputLineStartsWithAsync(WatchExitedMessage, DefaultMessageTimeOut); } public async Task IsWaitingForFileChange() { await Process.GetOutputLineStartsWithAsync(WaitingForFileChangeMessage, DefaultMessageTimeOut); } public bool UsePollingWatcher { get; set; } public async Task GetProcessIdentifier() { // Process ID is insufficient because PID's may be reused. Process identifier also includes other info to distinguish // between different process instances. var line = await Process.GetOutputLineStartsWithAsync("Process identifier =", DefaultMessageTimeOut); return line.Split('=').Last(); } public async Task PrepareAsync() { await Scenario.RestoreAsync(_appName); await Scenario.BuildAsync(_appName); _prepared = true; } public void Start(IEnumerable arguments, [CallerMemberName] string name = null) { if (!_prepared) { throw new InvalidOperationException($"Call {nameof(PrepareAsync)} first"); } var args = new List { Scenario.DotNetWatchPath, }; args.AddRange(arguments); var spec = new ProcessSpec { Executable = DotNetMuxer.MuxerPathOrDefault(), Arguments = args, WorkingDirectory = SourceDirectory, EnvironmentVariables = { ["DOTNET_CLI_CONTEXT_VERBOSE"] = bool.TrueString, ["DOTNET_USE_POLLING_FILE_WATCHER"] = UsePollingWatcher.ToString(), }, }; Process = new AwaitableProcess(spec, _logger); Process.Start(); } public Task StartWatcherAsync([CallerMemberName] string name = null) => StartWatcherAsync(Array.Empty(), name); public async Task StartWatcherAsync(string[] arguments, [CallerMemberName] string name = null) { if (!_prepared) { await PrepareAsync(); } var args = new[] { "run", "--" }.Concat(arguments); Start(args, name); // Make this timeout long because it depends much on the MSBuild compilation speed. // Slow machines may take a bit to compile and boot test apps await Process.GetOutputLineAsync(StartedMessage, TimeSpan.FromMinutes(2)); } public virtual void Dispose() { _logger?.WriteLine("Disposing WatchableApp"); Process?.Dispose(); Scenario?.Dispose(); } } } ================================================ FILE: test/dotnet-watch.FunctionalTests/TestProjects/AppWithDeps/AppWithDeps.csproj ================================================ netcoreapp3.0 exe true 99.9 ================================================ FILE: test/dotnet-watch.FunctionalTests/TestProjects/AppWithDeps/Program.cs ================================================ // 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.Threading; namespace ConsoleApplication { public class Program { private static readonly int processId = Process.GetCurrentProcess().Id; public static void Main(string[] args) { Console.WriteLine("Started"); // Process ID is insufficient because PID's may be reused. Console.WriteLine($"Process identifier = {Process.GetCurrentProcess().Id}, {Process.GetCurrentProcess().StartTime:hh:mm:ss.FF}"); Thread.Sleep(Timeout.Infinite); } } } ================================================ FILE: test/dotnet-watch.FunctionalTests/TestProjects/Dependency/Dependency.csproj ================================================ netstandard2.0 true ================================================ FILE: test/dotnet-watch.FunctionalTests/TestProjects/Dependency/Foo.cs ================================================ // 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 Dependency { public class Foo { } } ================================================ FILE: test/dotnet-watch.FunctionalTests/TestProjects/GlobbingApp/GlobbingApp.csproj ================================================ netcoreapp3.0 exe false true 99.9 ================================================ FILE: test/dotnet-watch.FunctionalTests/TestProjects/GlobbingApp/Program.cs ================================================ // 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.Linq; using System.Reflection; using System.Threading; namespace ConsoleApplication { public class Program { public static void Main(string[] args) { Console.WriteLine("Started"); // Process ID is insufficient because PID's may be reused. Console.WriteLine($"Process identifier = {Process.GetCurrentProcess().Id}, {Process.GetCurrentProcess().StartTime:hh:mm:ss.FF}"); Console.WriteLine("Defined types = " + typeof(Program).GetTypeInfo().Assembly.DefinedTypes.Count()); Thread.Sleep(Timeout.Infinite); } } } ================================================ FILE: test/dotnet-watch.FunctionalTests/TestProjects/GlobbingApp/exclude/Baz.cs ================================================ // 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 GlobbingApp.exclude { public class Baz { "THIS FILE SHOULD NOT BE INCLUDED IN COMPILATION" } } ================================================ FILE: test/dotnet-watch.FunctionalTests/TestProjects/GlobbingApp/include/Foo.cs ================================================ // 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 GlobbingApp.include { public class Foo { } } ================================================ FILE: test/dotnet-watch.FunctionalTests/TestProjects/KitchenSink/KitchenSink.csproj ================================================ .net/obj .net/bin Exe netcoreapp3.0 true 99.9 ================================================ FILE: test/dotnet-watch.FunctionalTests/TestProjects/KitchenSink/Program.cs ================================================ // 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; namespace KitchenSink { class Program { static void Main(string[] args) { Console.WriteLine("Started"); // Process ID is insufficient because PID's may be reused. Console.WriteLine($"Process identifier = {Process.GetCurrentProcess().Id}, {Process.GetCurrentProcess().StartTime:hh:mm:ss.FF}"); Console.WriteLine("DOTNET_WATCH = " + Environment.GetEnvironmentVariable("DOTNET_WATCH")); Console.WriteLine("DOTNET_WATCH_ITERATION = " + Environment.GetEnvironmentVariable("DOTNET_WATCH_ITERATION")); } } } ================================================ FILE: test/dotnet-watch.FunctionalTests/TestProjects/NoDepsApp/NoDepsApp.csproj ================================================ netcoreapp3.0 exe true 99.9 ================================================ FILE: test/dotnet-watch.FunctionalTests/TestProjects/NoDepsApp/Program.cs ================================================ // 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.Threading; namespace ConsoleApplication { public class Program { public static void Main(string[] args) { Console.WriteLine("Started"); // Process ID is insufficient because PID's may be reused. Console.WriteLine($"Process identifier = {Process.GetCurrentProcess().Id}, {Process.GetCurrentProcess().StartTime:hh:mm:ss.FF}"); if (args.Length > 0 && args[0] == "--no-exit") { Thread.Sleep(Timeout.Infinite); } Console.WriteLine("Exiting"); } } } ================================================ FILE: test/dotnet-watch.FunctionalTests/dotnet-watch.FunctionalTests.csproj ================================================ netcoreapp3.0 $(DefaultItemExcludes);TestProjects\**\* Microsoft.DotNet.Watcher.Tools.FunctionalTests <_Parameter1>TestSettings:RestoreSources <_Parameter2>$(RestoreSources) <_Parameter1>TestSettings:RuntimeFrameworkVersion <_Parameter2>$(RuntimeFrameworkVersion) ================================================ FILE: test/dotnet-watch.Tests/AssertEx.cs ================================================ // 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; using Xunit; using Xunit.Sdk; namespace Microsoft.DotNet.Watcher.Tools.Tests { public static class AssertEx { public static void EqualFileList(string root, IEnumerable expectedFiles, IEnumerable actualFiles) { var expected = expectedFiles.Select(p => Path.Combine(root, p)); EqualFileList(expected, actualFiles); } public static void EqualFileList(IEnumerable expectedFiles, IEnumerable actualFiles) { string normalize(string p) => p.Replace('\\', '/'); var expected = new HashSet(expectedFiles.Select(normalize)); var actual = new HashSet(actualFiles.Where(p => !string.IsNullOrEmpty(p)).Select(normalize)); if (!expected.SetEquals(actual)) { throw new AssertActualExpectedException( expected: "\n" + string.Join("\n", expected), actual: "\n" + string.Join("\n", actual), userMessage: "File sets should be equal"); } } } } ================================================ FILE: test/dotnet-watch.Tests/CommandLineOptionsTests.cs ================================================ // 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.Linq; using System.Text; using Microsoft.Extensions.CommandLineUtils; using Microsoft.Extensions.Tools.Internal; using Xunit; using Xunit.Abstractions; namespace Microsoft.DotNet.Watcher.Tools.Tests { public class CommandLineOptionsTests { private readonly IConsole _console; private readonly StringBuilder _stdout = new StringBuilder(); public CommandLineOptionsTests(ITestOutputHelper output) { _console = new TestConsole(output) { Out = new StringWriter(_stdout), }; } [Theory] [InlineData(new object[] { new[] { "-h" } })] [InlineData(new object[] { new[] { "-?" } })] [InlineData(new object[] { new[] { "--help" } })] [InlineData(new object[] { new[] { "--help", "--bogus" } })] [InlineData(new object[] { new[] { "--" } })] [InlineData(new object[] { new string[0] })] public void HelpArgs(string[] args) { var options = CommandLineOptions.Parse(args, _console); Assert.True(options.IsHelp); Assert.Contains("Usage: dotnet watch ", _stdout.ToString()); } [Theory] [InlineData(new[] { "run" }, new[] { "run" })] [InlineData(new[] { "run", "--", "subarg" }, new[] { "run", "--", "subarg" })] [InlineData(new[] { "--", "run", "--", "subarg" }, new[] { "run", "--", "subarg" })] [InlineData(new[] { "--unrecognized-arg" }, new[] { "--unrecognized-arg" })] public void ParsesRemainingArgs(string[] args, string[] expected) { var options = CommandLineOptions.Parse(args, _console); Assert.Equal(expected, options.RemainingArguments.ToArray()); Assert.False(options.IsHelp); Assert.Empty(_stdout.ToString()); } [Fact] public void CannotHaveQuietAndVerbose() { var ex = Assert.Throws(() => CommandLineOptions.Parse(new[] { "--quiet", "--verbose" }, _console)); Assert.Equal(Resources.Error_QuietAndVerboseSpecified, ex.Message); } } } ================================================ FILE: test/dotnet-watch.Tests/ConsoleReporterTests.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.IO; using System.Text; using Microsoft.Extensions.Tools.Internal; using Xunit; namespace Microsoft.Extensions.Tools.Tests { public class ReporterTests { private static readonly string EOL = Environment.NewLine; [Fact] public void WritesToStandardStreams() { var testConsole = new TestConsole(); var reporter = new ConsoleReporter(testConsole, verbose: true, quiet: false); // stdout reporter.Verbose("verbose"); Assert.Equal("verbose" + EOL, testConsole.GetOutput()); testConsole.Clear(); reporter.Output("out"); Assert.Equal("out" + EOL, testConsole.GetOutput()); testConsole.Clear(); reporter.Warn("warn"); Assert.Equal("warn" + EOL, testConsole.GetOutput()); testConsole.Clear(); // stderr reporter.Error("error"); Assert.Equal("error" + EOL, testConsole.GetError()); testConsole.Clear(); } private class TestConsole : IConsole { private readonly StringBuilder _out; private readonly StringBuilder _error; public TestConsole() { _out = new StringBuilder(); _error = new StringBuilder(); Out = new StringWriter(_out); Error = new StringWriter(_error); } event ConsoleCancelEventHandler IConsole.CancelKeyPress { add { } remove { } } public string GetOutput() => _out.ToString(); public string GetError() => _error.ToString(); public void Clear() { _out.Clear(); _error.Clear(); } public void ResetColor() { ForegroundColor = default(ConsoleColor); } public TextWriter Out { get; } public TextWriter Error { get; } public TextReader In { get; } public bool IsInputRedirected { get; } public bool IsOutputRedirected { get; } public bool IsErrorRedirected { get; } public ConsoleColor ForegroundColor { get; set; } } } } ================================================ FILE: test/dotnet-watch.Tests/MsBuildFileSetFactoryTest.cs ================================================ // 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.Linq; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Testing; using Microsoft.DotNet.Watcher.Internal; using Microsoft.Extensions.Tools.Internal; using Xunit; using Xunit.Abstractions; namespace Microsoft.DotNet.Watcher.Tools.Tests { using ItemSpec = TemporaryCSharpProject.ItemSpec; public class MsBuildFileSetFactoryTest : IDisposable { private readonly IReporter _reporter; private readonly TemporaryDirectory _tempDir; public MsBuildFileSetFactoryTest(ITestOutputHelper output) { _reporter = new TestReporter(output); _tempDir = new TemporaryDirectory(); } [Fact] public async Task FindsCustomWatchItems() { _tempDir .WithCSharpProject("Project1", out var target) .WithTargetFrameworks("netcoreapp1.0") .WithItem(new ItemSpec { Name = "Watch", Include = "*.js", Exclude = "gulpfile.js" }) .Dir() .WithFile("Program.cs") .WithFile("app.js") .WithFile("gulpfile.js"); var fileset = await GetFileSet(target); AssertEx.EqualFileList( _tempDir.Root, new[] { "Project1.csproj", "Program.cs", "app.js" }, fileset ); } [Fact] public async Task ExcludesDefaultItemsWithWatchFalseMetadata() { _tempDir .WithCSharpProject("Project1", out var target) .WithTargetFrameworks("net40") .WithItem(new ItemSpec { Name = "EmbeddedResource", Update = "*.resx", Watch = false }) .Dir() .WithFile("Program.cs") .WithFile("Strings.resx"); var fileset = await GetFileSet(target); AssertEx.EqualFileList( _tempDir.Root, new[] { "Project1.csproj", "Program.cs", }, fileset ); } [Fact] public async Task SingleTfm() { _tempDir .SubDir("src") .SubDir("Project1") .WithCSharpProject("Project1", out var target) .WithProperty("BaseIntermediateOutputPath", "obj") .WithTargetFrameworks("netcoreapp1.0") .Dir() .WithFile("Program.cs") .WithFile("Class1.cs") .SubDir("obj").WithFile("ignored.cs").Up() .SubDir("Properties").WithFile("Strings.resx").Up() .Up() .Up() .Create(); var fileset = await GetFileSet(target); AssertEx.EqualFileList( _tempDir.Root, new[] { "src/Project1/Project1.csproj", "src/Project1/Program.cs", "src/Project1/Class1.cs", "src/Project1/Properties/Strings.resx", }, fileset ); } [Fact] public async Task MultiTfm() { _tempDir .SubDir("src") .SubDir("Project1") .WithCSharpProject("Project1", out var target) .WithTargetFrameworks("netcoreapp1.0", "net451") .WithProperty("EnableDefaultCompileItems", "false") .WithItem("Compile", "Class1.netcore.cs", "'$(TargetFramework)'=='netcoreapp1.0'") .WithItem("Compile", "Class1.desktop.cs", "'$(TargetFramework)'=='net451'") .Dir() .WithFile("Class1.netcore.cs") .WithFile("Class1.desktop.cs") .WithFile("Class1.notincluded.cs"); var fileset = await GetFileSet(target); AssertEx.EqualFileList( _tempDir.Root, new[] { "src/Project1/Project1.csproj", "src/Project1/Class1.netcore.cs", "src/Project1/Class1.desktop.cs", }, fileset ); } [Fact] public async Task ProjectReferences_OneLevel() { _tempDir .SubDir("src") .SubDir("Project2") .WithCSharpProject("Project2", out var proj2) .WithTargetFrameworks("netstandard1.1") .Dir() .WithFile("Class2.cs") .Up() .SubDir("Project1") .WithCSharpProject("Project1", out var target) .WithTargetFrameworks("netcoreapp1.0", "net451") .WithProjectReference(proj2) .Dir() .WithFile("Class1.cs"); var fileset = await GetFileSet(target); AssertEx.EqualFileList( _tempDir.Root, new[] { "src/Project2/Project2.csproj", "src/Project2/Class2.cs", "src/Project1/Project1.csproj", "src/Project1/Class1.cs", }, fileset ); } [Fact] public async Task TransitiveProjectReferences_TwoLevels() { _tempDir .SubDir("src") .SubDir("Project3") .WithCSharpProject("Project3", out var proj3) .WithTargetFrameworks("netstandard1.0") .Dir() .WithFile("Class3.cs") .Up() .SubDir("Project2") .WithCSharpProject("Project2", out TemporaryCSharpProject proj2) .WithTargetFrameworks("netstandard1.1") .WithProjectReference(proj3) .Dir() .WithFile("Class2.cs") .Up() .SubDir("Project1") .WithCSharpProject("Project1", out TemporaryCSharpProject target) .WithTargetFrameworks("netcoreapp1.0", "net451") .WithProjectReference(proj2) .Dir() .WithFile("Class1.cs"); var fileset = await GetFileSet(target); AssertEx.EqualFileList( _tempDir.Root, new[] { "src/Project3/Project3.csproj", "src/Project3/Class3.cs", "src/Project2/Project2.csproj", "src/Project2/Class2.cs", "src/Project1/Project1.csproj", "src/Project1/Class1.cs", }, fileset ); } [Fact] public async Task ProjectReferences_Graph() { var graph = new TestProjectGraph(_tempDir); graph.OnCreate(p => p.WithTargetFrameworks("net45")); var matches = Regex.Matches(@" A->B B->C C->D D->E B->E A->F F->G G->E F->E W->U Y->Z Y->B Y->F", @"(\w)->(\w)"); Assert.Equal(13, matches.Count); foreach (Match m in matches) { var target = graph.GetOrCreate(m.Groups[2].Value); graph.GetOrCreate(m.Groups[1].Value).WithProjectReference(target); } graph.Find("A").WithProjectReference(graph.Find("W"), watch: false); var output = new OutputSink(); var filesetFactory = new MsBuildFileSetFactory(_reporter, graph.GetOrCreate("A").Path, output, trace: true); var fileset = await GetFileSet(filesetFactory); _reporter.Output(string.Join( Environment.NewLine, output.Current.Lines.Select(l => "Sink output: " + l))); var includedProjects = new[] { "A", "B", "C", "D", "E", "F", "G" }; AssertEx.EqualFileList( _tempDir.Root, includedProjects .Select(p => $"{p}/{p}.csproj"), fileset ); // ensure unreachable projects exist but where not included Assert.NotNull(graph.Find("W")); Assert.NotNull(graph.Find("U")); Assert.NotNull(graph.Find("Y")); Assert.NotNull(graph.Find("Z")); // ensure each project is only visited once for collecting watch items Assert.All(includedProjects, projectName => Assert.Single(output.Current.Lines, line => line.Contains($"Collecting watch items from '{projectName}'")) ); } private Task GetFileSet(TemporaryCSharpProject target) => GetFileSet(new MsBuildFileSetFactory(_reporter, target.Path, waitOnError: false, trace: false)); private async Task GetFileSet(MsBuildFileSetFactory filesetFactory) { _tempDir.Create(); return await filesetFactory .CreateAsync(CancellationToken.None) .TimeoutAfter(TimeSpan.FromSeconds(30)); } public void Dispose() { _tempDir.Dispose(); } } } ================================================ FILE: test/dotnet-watch.Tests/ProgramTests.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.IO; using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Tools.Internal; using Xunit; using Xunit.Abstractions; namespace Microsoft.DotNet.Watcher.Tools.Tests { public class ProgramTests : IDisposable { private readonly TemporaryDirectory _tempDir; private readonly TestConsole _console; public ProgramTests(ITestOutputHelper output) { _tempDir = new TemporaryDirectory(); _console = new TestConsole(output); } [Fact] public async Task ConsoleCancelKey() { _tempDir .WithCSharpProject("testproj") .WithTargetFrameworks("netcoreapp3.0") .Dir() .WithFile("Program.cs") .Create(); 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(); var exitCode = await run.TimeoutAfter(TimeSpan.FromSeconds(30)); Assert.Contains("Shutdown requested. Press Ctrl+C again to force exit.", output.ToString()); Assert.Equal(0, exitCode); } } public void Dispose() { _tempDir.Dispose(); } } } ================================================ FILE: test/dotnet-watch.Tests/Utilities/TemporaryCSharpProject.cs ================================================ // 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.Text; namespace Microsoft.DotNet.Watcher.Tools.Tests { public class TemporaryCSharpProject { private const string Template = @" {0} Exe {1} "; private readonly string _filename; private readonly TemporaryDirectory _directory; private List _items = new List(); private List _properties = new List(); public TemporaryCSharpProject(string name, TemporaryDirectory directory) { Name = name; _filename = name + ".csproj"; _directory = directory; } public string Name { get; } public string Path => System.IO.Path.Combine(_directory.Root, _filename); public TemporaryCSharpProject WithTargetFrameworks(params string[] tfms) { Debug.Assert(tfms.Length > 0); var propertySpec = new PropertySpec { Value = string.Join(";", tfms) }; propertySpec.Name = tfms.Length == 1 ? "TargetFramework" : "TargetFrameworks"; return WithProperty(propertySpec); } public TemporaryCSharpProject WithProperty(string name, string value) => WithProperty(new PropertySpec { Name = name, Value = value }); public TemporaryCSharpProject WithProperty(PropertySpec property) { var sb = new StringBuilder(); sb.Append('<').Append(property.Name).Append('>') .Append(property.Value) .Append("'); _properties.Add(sb.ToString()); return this; } public TemporaryCSharpProject WithItem(string itemName, string include, string condition = null) => WithItem(new ItemSpec { Name = itemName, Include = include, Condition = condition }); public TemporaryCSharpProject WithItem(ItemSpec item) { var sb = new StringBuilder("<"); sb.Append(item.Name).Append(" "); if (item.Include != null) sb.Append(" Include=\"").Append(item.Include).Append('"'); if (item.Remove != null) sb.Append(" Remove=\"").Append(item.Remove).Append('"'); if (item.Update != null) sb.Append(" Update=\"").Append(item.Update).Append('"'); if (item.Exclude != null) sb.Append(" Exclude=\"").Append(item.Exclude).Append('"'); if (item.Condition != null) sb.Append(" Exclude=\"").Append(item.Condition).Append('"'); if (!item.Watch) sb.Append(" Watch=\"false\" "); sb.Append(" />"); _items.Add(sb.ToString()); return this; } public TemporaryCSharpProject WithProjectReference(TemporaryCSharpProject reference, bool watch = true) { if (ReferenceEquals(this, reference)) { throw new InvalidOperationException("Can add project reference to self"); } return WithItem(new ItemSpec { Name = "ProjectReference", Include = reference.Path, Watch = watch }); } public TemporaryDirectory Dir() => _directory; public void Create() { _directory.CreateFile(_filename, string.Format(Template, string.Join("\r\n", _properties), string.Join("\r\n", _items))); } public class ItemSpec { public string Name { get; set; } public string Include { get; set; } public string Exclude { get; set; } public string Update { get; set; } public string Remove { get; set; } public bool Watch { get; set; } = true; public string Condition { get; set; } } public class PropertySpec { public string Name { get; set; } public string Value { get; set; } } } } ================================================ FILE: test/dotnet-watch.Tests/Utilities/TemporaryDirectory.cs ================================================ // 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; namespace Microsoft.DotNet.Watcher.Tools.Tests { public class TemporaryDirectory : IDisposable { private List _projects = new List(); private List _subdirs = new List(); private Dictionary _files = new Dictionary(); private TemporaryDirectory _parent; public TemporaryDirectory() { Root = Path.Combine(Path.GetTempPath(), "dotnet-watch-tests", Guid.NewGuid().ToString("N")); } private TemporaryDirectory(string path, TemporaryDirectory parent) { _parent = parent; Root = path; } public TemporaryDirectory SubDir(string name) { var subdir = new TemporaryDirectory(Path.Combine(Root, name), this); _subdirs.Add(subdir); return subdir; } public string Root { get; } public TemporaryCSharpProject WithCSharpProject(string name) { var project = new TemporaryCSharpProject(name, this); _projects.Add(project); return project; } public TemporaryCSharpProject WithCSharpProject(string name, out TemporaryCSharpProject project) { project = WithCSharpProject(name); return project; } public TemporaryDirectory WithFile(string name, string contents = "") { _files[name] = contents; return this; } public TemporaryDirectory Up() { if (_parent == null) { throw new InvalidOperationException("This is the root directory"); } return _parent; } public void Create() { Directory.CreateDirectory(Root); foreach (var dir in _subdirs) { dir.Create(); } foreach (var project in _projects) { project.Create(); } foreach (var file in _files) { CreateFile(file.Key, file.Value); } } public void CreateFile(string filename, string contents) { File.WriteAllText(Path.Combine(Root, filename), contents); } public void Dispose() { if (Root == null || !Directory.Exists(Root) || _parent != null) { return; } try { Directory.Delete(Root, recursive: true); } catch { Console.Error.WriteLine($"Test cleanup failed to delete '{Root}'"); } } } } ================================================ FILE: test/dotnet-watch.Tests/Utilities/TestProjectGraph.cs ================================================ // 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.DotNet.Watcher.Tools.Tests { public class TestProjectGraph { private readonly TemporaryDirectory _directory; private Action _onCreate; private Dictionary _projects = new Dictionary(); public TestProjectGraph(TemporaryDirectory directory) { _directory = directory; } public void OnCreate(Action onCreate) { _onCreate = onCreate; } public TemporaryCSharpProject Find(string projectName) => _projects.ContainsKey(projectName) ? _projects[projectName] : null; public TemporaryCSharpProject GetOrCreate(string projectName) { TemporaryCSharpProject sourceProj; if (!_projects.TryGetValue(projectName, out sourceProj)) { sourceProj = _directory.SubDir(projectName).WithCSharpProject(projectName); _onCreate?.Invoke(sourceProj); _projects.Add(projectName, sourceProj); } return sourceProj; } } } ================================================ FILE: test/dotnet-watch.Tests/dotnet-watch.Tests.csproj ================================================ netcoreapp3.0 Microsoft.DotNet.Watcher.Tools.Tests ================================================ FILE: tooling/Microsoft.VisualStudio.SecretManager/Microsoft.VisualStudio.SecretManager.csproj ================================================ 15.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) CommonExtensions Microsoft\ASP.NET Core\UserSecrets true False False False True True ..\..\build\Key.snk True Debug AnyCPU 2.0 {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} {5E117F2E-7152-447F-BF47-59F759EEF3A7} Library Properties Microsoft.VisualStudio.SecretManager Microsoft.VisualStudio.SecretManager v4.6.1 true true false true true true Program $(DevEnvDir)devenv.exe /rootsuffix Exp true full false bin\Debug\ TRACE;DEBUG;EXTENSION_DEVELOPER_MODE prompt 4 latest true full true bin\Release\ TRACE prompt 4 latest Designer Designer ================================================ FILE: tooling/Microsoft.VisualStudio.SecretManager/ProjectLocalSecretsManager.cs ================================================ // 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; using Microsoft.VisualStudio.ProjectSystem.Properties; using Microsoft.VisualStudio.Threading; using Task = System.Threading.Tasks.Task; namespace Microsoft.VisualStudio.SecretManager { /// /// Provides an thread-safe access the secrets.json file based on the UserSecretsId property in a configured project. /// internal class ProjectLocalSecretsManager : Shell.IVsProjectSecrets, Shell.SVsProjectLocalSecrets { private const string UserSecretsPropertyName = "UserSecretsId"; private readonly AsyncSemaphore _semaphore; private readonly IProjectPropertiesProvider _propertiesProvider; private readonly Lazy _services; public ProjectLocalSecretsManager(IProjectPropertiesProvider propertiesProvider, Lazy serviceProvider) { _propertiesProvider = propertiesProvider ?? throw new ArgumentNullException(nameof(propertiesProvider)); _services = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); _semaphore = new AsyncSemaphore(1); } public string SanitizeName(string name) => name; public IReadOnlyCollection GetInvalidCharactersFrom(string name) => Array.Empty(); public async Task AddSecretAsync(string name, string value, CancellationToken cancellationToken = default) { EnsureKeyNameIsValid(name); await TaskScheduler.Default; using (await _semaphore.EnterAsync(cancellationToken)) using (var store = await GetOrCreateStoreAsync(cancellationToken)) { if (store.ContainsKey(name)) { throw new ArgumentException(Resources.Error_SecretAlreadyExists, nameof(name)); } store.Set(name, value); await store.SaveAsync(cancellationToken); } } public async Task SetSecretAsync(string name, string value, CancellationToken cancellationToken = default) { EnsureKeyNameIsValid(name); await TaskScheduler.Default; using (await _semaphore.EnterAsync(cancellationToken)) using (var store = await GetOrCreateStoreAsync(cancellationToken)) { store.Set(name, value); await store.SaveAsync(cancellationToken); } } public async Task GetSecretAsync(string name, CancellationToken cancellationToken = default) { EnsureKeyNameIsValid(name); await TaskScheduler.Default; using (await _semaphore.EnterAsync(cancellationToken)) using (var store = await GetOrCreateStoreAsync(cancellationToken)) { return store.Get(name); } } public async Task> GetSecretNamesAsync(CancellationToken cancellationToken = default) { await TaskScheduler.Default; using (await _semaphore.EnterAsync(cancellationToken)) using (var store = await GetOrCreateStoreAsync(cancellationToken)) { return store.ReadOnlyKeys; } } public async Task> GetSecretsAsync(CancellationToken cancellationToken = default) { await TaskScheduler.Default; using (await _semaphore.EnterAsync(cancellationToken)) using (var store = await GetOrCreateStoreAsync(cancellationToken)) { return store.Values; } } public async Task RemoveSecretAsync(string name, CancellationToken cancellationToken = default) { EnsureKeyNameIsValid(name); await TaskScheduler.Default; using (await _semaphore.EnterAsync(cancellationToken)) using (var store = await GetOrCreateStoreAsync(cancellationToken)) { if (store.Remove(name)) { await store.SaveAsync(cancellationToken); return true; } return false; } } private void EnsureKeyNameIsValid(string name) { if (name == null) { throw new ArgumentNullException(nameof(name)); } if (name.Length == 0) { throw new ArgumentException(nameof(name)); } } private async Task GetOrCreateStoreAsync(CancellationToken cancellationToken) { var userSecretsId = await _propertiesProvider.GetCommonProperties().GetEvaluatedPropertyValueAsync(UserSecretsPropertyName); if (string.IsNullOrEmpty(userSecretsId)) { userSecretsId = Guid.NewGuid().ToString(); await _propertiesProvider.GetCommonProperties().SetPropertyValueAsync(UserSecretsPropertyName, userSecretsId); } var store = new SecretStore(userSecretsId); await store.LoadAsync(cancellationToken); return store; } } } ================================================ FILE: tooling/Microsoft.VisualStudio.SecretManager/Properties/AssemblyInfo.cs ================================================ // 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.Reflection; using Microsoft.VisualStudio.Shell; // required for VS to generate the pkgdef [assembly: AssemblyVersion("1.0.0.0")] [assembly: ProvideCodeBase(CodeBase = @"$PackageFolder$\Microsoft.VisualStudio.SecretManager.dll")] [assembly: ProvideBindingRedirection( AssemblyName = "Microsoft.VisualStudio.SecretManager", GenerateCodeBase = true, PublicKeyToken = "adb9793829ddae60", OldVersionLowerBound = "0.0.0.0", OldVersionUpperBound = "1.0.0.0", NewVersion = "1.0.0.0")] ================================================ FILE: tooling/Microsoft.VisualStudio.SecretManager/Resources.Designer.cs ================================================ // namespace Microsoft.VisualStudio.SecretManager { using System.Globalization; using System.Reflection; using System.Resources; internal static class Resources { private static readonly ResourceManager _resourceManager = new ResourceManager("Microsoft.VisualStudio.SecretManager.Resources", typeof(Resources).GetTypeInfo().Assembly); /// /// A secret with this name already exists. /// internal static string Error_SecretAlreadyExists { get => GetString("Error_SecretAlreadyExists"); } /// /// Value cannot be null or an empty string. /// internal static string Common_StringNullOrEmpty { get => GetString("Common_StringNullOrEmpty"); } /// /// Value cannot be null or an empty string. /// internal static string FormatCommon_StringNullOrEmpty() => GetString("Common_StringNullOrEmpty"); /// /// Invalid character '{0}' found in the user secrets ID at index '{1}'. /// internal static string Error_Invalid_Character_In_UserSecrets_Id { get => GetString("Error_Invalid_Character_In_UserSecrets_Id"); } /// /// Invalid character '{0}' found in the user secrets ID at index '{1}'. /// internal static string FormatError_Invalid_Character_In_UserSecrets_Id(object p0, object p1) => string.Format(CultureInfo.CurrentCulture, GetString("Error_Invalid_Character_In_UserSecrets_Id"), p0, p1); /// /// Could not find 'UserSecretsIdAttribute' on assembly '{0}'. /// Check that the project for '{0}' has set the 'UserSecretsId' build property. /// If the 'UserSecretsId' property is already set then add a reference to the Microsoft.Extensions.Configuration.UserSecrets package. /// internal static string Error_Missing_UserSecretsIdAttribute { get => GetString("Error_Missing_UserSecretsIdAttribute"); } /// /// Could not find 'UserSecretsIdAttribute' on assembly '{0}'. /// Check that the project for '{0}' has set the 'UserSecretsId' build property. /// If the 'UserSecretsId' property is already set then add a reference to the Microsoft.Extensions.Configuration.UserSecrets package. /// internal static string FormatError_Missing_UserSecretsIdAttribute(object p0) => string.Format(CultureInfo.CurrentCulture, GetString("Error_Missing_UserSecretsIdAttribute"), p0); /// /// File path must be a non-empty string. /// internal static string Error_InvalidFilePath { get => GetString("Error_InvalidFilePath"); } /// /// File path must be a non-empty string. /// internal static string FormatError_InvalidFilePath() => GetString("Error_InvalidFilePath"); /// /// Could not parse the JSON file. Error on line number '{0}': '{1}'. /// internal static string Error_JSONParseError { get => GetString("Error_JSONParseError"); } /// /// Could not parse the JSON file. Error on line number '{0}': '{1}'. /// internal static string FormatError_JSONParseError(object p0, object p1) => string.Format(CultureInfo.CurrentCulture, GetString("Error_JSONParseError"), p0, p1); /// /// A duplicate key '{0}' was found. /// internal static string Error_KeyIsDuplicated { get => GetString("Error_KeyIsDuplicated"); } /// /// A duplicate key '{0}' was found. /// internal static string FormatError_KeyIsDuplicated(object p0) => string.Format(CultureInfo.CurrentCulture, GetString("Error_KeyIsDuplicated"), p0); /// /// Unsupported JSON token '{0}' was found. Path '{1}', line {2} position {3}. /// internal static string Error_UnsupportedJSONToken { get => GetString("Error_UnsupportedJSONToken"); } /// /// Unsupported JSON token '{0}' was found. Path '{1}', line {2} position {3}. /// internal static string FormatError_UnsupportedJSONToken(object p0, object p1, object p2, object p3) => string.Format(CultureInfo.CurrentCulture, GetString("Error_UnsupportedJSONToken"), p0, p1, p2, p3); private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name); System.Diagnostics.Debug.Assert(value != null); if (formatterNames != null) { for (var i = 0; i < formatterNames.Length; i++) { value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}"); } } return value; } } } ================================================ FILE: tooling/Microsoft.VisualStudio.SecretManager/Resources.resx ================================================ text/microsoft-resx 2.0 System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 File path must be a non-empty string. Could not parse the JSON file. Error on line number '{0}': '{1}'. A duplicate key '{0}' was found. Unsupported JSON token '{0}' was found. Path '{1}', line {2} position {3}. Value cannot be null or an empty string. Invalid character '{0}' found in the user secrets ID at index '{1}'. Could not find 'UserSecretsIdAttribute' on assembly '{0}'. Check that the project for '{0}' has set the 'UserSecretsId' build property. If the 'UserSecretsId' property is already set then add a reference to the Microsoft.Extensions.Configuration.UserSecrets package. A secret with this name already exists. ================================================ FILE: tooling/Microsoft.VisualStudio.SecretManager/SecretManagerFactory.cs ================================================ // 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.ComponentModel.Composition; using Microsoft.VisualStudio.ProjectSystem; using Microsoft.VisualStudio.Shell; namespace Microsoft.VisualStudio.SecretManager { internal class SecretManagerFactory { // This is capability is set in Microsoft.Extensions.Configuration.UserSecrets private const string CapabilityName = "LocalUserSecrets"; private readonly Lazy _secretManager; private readonly UnconfiguredProject _project; [ImportingConstructor] public SecretManagerFactory(UnconfiguredProject project, SVsServiceProvider vsServiceProvider) { _project = project; var serviceProvider = new Lazy(() => vsServiceProvider); _secretManager = new Lazy(() => { var propertiesProvider = _project.Services.ActiveConfiguredProjectProvider.ActiveConfiguredProject.Services.ProjectPropertiesProvider; return new ProjectLocalSecretsManager(propertiesProvider, serviceProvider); }); } [ExportVsProfferedProjectService(typeof(SVsProjectLocalSecrets))] [AppliesTo(CapabilityName)] public SVsProjectLocalSecrets ProjectLocalSecretsManager => _secretManager.Value; } } ================================================ FILE: tooling/Microsoft.VisualStudio.SecretManager/SecretStore.cs ================================================ // 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.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Configuration.Json; using Microsoft.Extensions.Configuration.UserSecrets; using Microsoft.VisualStudio.Threading; using Newtonsoft.Json.Linq; using Task = System.Threading.Tasks.Task; namespace Microsoft.VisualStudio.SecretManager { /// /// Provides read and write access to the secrets.json file for local user secrets. /// This is not thread-safe. /// This object is meant to have a short lifetime. /// When calling , this will overwrite the secrets.json file. It does not check for concurrency issues if another process has edited this file. /// internal class SecretStore : IDisposable { private Dictionary _secrets; private string _fileDir; private string _filePath; private bool _isDirty; private volatile bool _disposed; public SecretStore(string userSecretsId) { _filePath = PathHelper.GetSecretsPathFromSecretsId(userSecretsId); _fileDir = Path.GetDirectoryName(_filePath); } public IReadOnlyCollection ReadOnlyKeys { get { EnsureNotDisposed(); return _secrets.Keys; } } public IReadOnlyDictionary Values { get { EnsureNotDisposed(); return _secrets; } } public bool ContainsKey(string key) { EnsureNotDisposed(); return _secrets.ContainsKey(key); } public string Get(string name) { EnsureNotDisposed(); return _secrets[name]; } public void Set(string key, string value) { EnsureNotDisposed(); _isDirty = true; _secrets[key] = value; } public bool Remove(string key) { EnsureNotDisposed(); _isDirty = true; return _secrets.Remove(key); } public async Task LoadAsync(CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); await TaskScheduler.Default; EnsureNotDisposed(); string text = null; if (File.Exists(_filePath)) { text = File.ReadAllText(_filePath); } _secrets = DeserializeJson(text); } public async Task SaveAsync(CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); await TaskScheduler.Default; EnsureNotDisposed(); if (!_isDirty) { return; } Directory.CreateDirectory(_fileDir); File.WriteAllText(_filePath, Stringify(_secrets), Encoding.UTF8); _isDirty = false; } private void EnsureNotDisposed() { if (_disposed) { throw new ObjectDisposedException(nameof(SecretStore)); } } private static string Stringify(Dictionary secrets) { var contents = new JObject(); if (secrets != null) { foreach (var secret in secrets) { contents[secret.Key] = secret.Value; } } return contents.ToString(); } private static Dictionary DeserializeJson(string text) { if (string.IsNullOrEmpty(text)) { return new Dictionary(StringComparer.OrdinalIgnoreCase); } using (var stream = new MemoryStream()) { var bytes = Encoding.UTF8.GetBytes(text); stream.Write(bytes, 0, bytes.Length); stream.Position = 0; // might throw FormatException if JSON is malformed. var data = JsonConfigurationFileParser.Parse(stream); return new Dictionary(data, StringComparer.OrdinalIgnoreCase); } } public void Dispose() { if (_disposed) return; _disposed = true; } } } ================================================ FILE: tooling/Microsoft.VisualStudio.SecretManager/Sources/ConfigurationPath.cs ================================================ // 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.Extensions.Configuration { /// /// Utility methods and constants for manipulating Configuration paths /// internal static class ConfigurationPath { /// /// The delimiter ":" used to separate individual keys in a path. /// public static readonly string KeyDelimiter = ":"; /// /// Combines path segments into one path. /// /// The path segments to combine. /// The combined path. public static string Combine(params string[] pathSegments) { if (pathSegments == null) { throw new ArgumentNullException(nameof(pathSegments)); } return string.Join(KeyDelimiter, pathSegments); } /// /// Combines path segments into one path. /// /// The path segments to combine. /// The combined path. public static string Combine(IEnumerable pathSegments) { if (pathSegments == null) { throw new ArgumentNullException(nameof(pathSegments)); } return string.Join(KeyDelimiter, pathSegments); } /// /// Extracts the last path segment from the path. /// /// The path. /// The last path segment of the path. public static string GetSectionKey(string path) { if (string.IsNullOrEmpty(path)) { return path; } var lastDelimiterIndex = path.LastIndexOf(KeyDelimiter, StringComparison.OrdinalIgnoreCase); return lastDelimiterIndex == -1 ? path : path.Substring(lastDelimiterIndex + 1); } /// /// Extracts the path corresponding to the parent node for a given path. /// /// The path. /// The original path minus the last individual segment found in it. Null if the original path corresponds to a top level node. public static string GetParentPath(string path) { if (string.IsNullOrEmpty(path)) { return null; } var lastDelimiterIndex = path.LastIndexOf(KeyDelimiter, StringComparison.OrdinalIgnoreCase); return lastDelimiterIndex == -1 ? null : path.Substring(0, lastDelimiterIndex); } } } ================================================ FILE: tooling/Microsoft.VisualStudio.SecretManager/Sources/JsonConfigurationFileParser.cs ================================================ // 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.IO; using System.Linq; using Microsoft.VisualStudio.SecretManager; using Newtonsoft.Json; using Newtonsoft.Json.Linq; namespace Microsoft.Extensions.Configuration.Json { internal class JsonConfigurationFileParser { private JsonConfigurationFileParser() { } private readonly IDictionary _data = new SortedDictionary(StringComparer.OrdinalIgnoreCase); private readonly Stack _context = new Stack(); private string _currentPath; private JsonTextReader _reader; public static IDictionary Parse(Stream input) => new JsonConfigurationFileParser().ParseStream(input); private IDictionary ParseStream(Stream input) { _data.Clear(); _reader = new JsonTextReader(new StreamReader(input)); _reader.DateParseHandling = DateParseHandling.None; var jsonConfig = JObject.Load(_reader); VisitJObject(jsonConfig); return _data; } private void VisitJObject(JObject jObject) { foreach (var property in jObject.Properties()) { EnterContext(property.Name); VisitProperty(property); ExitContext(); } } private void VisitProperty(JProperty property) { VisitToken(property.Value); } private void VisitToken(JToken token) { switch (token.Type) { case JTokenType.Object: VisitJObject(token.Value()); break; case JTokenType.Array: VisitArray(token.Value()); break; case JTokenType.Integer: case JTokenType.Float: case JTokenType.String: case JTokenType.Boolean: case JTokenType.Bytes: case JTokenType.Raw: case JTokenType.Null: VisitPrimitive(token.Value()); break; default: throw new FormatException(Resources.FormatError_UnsupportedJSONToken( _reader.TokenType, _reader.Path, _reader.LineNumber, _reader.LinePosition)); } } private void VisitArray(JArray array) { for (int index = 0; index < array.Count; index++) { EnterContext(index.ToString()); VisitToken(array[index]); ExitContext(); } } private void VisitPrimitive(JValue data) { var key = _currentPath; if (_data.ContainsKey(key)) { throw new FormatException(Resources.FormatError_KeyIsDuplicated(key)); } _data[key] = data.ToString(CultureInfo.InvariantCulture); } private void EnterContext(string context) { _context.Push(context); _currentPath = ConfigurationPath.Combine(_context.Reverse()); } private void ExitContext() { _context.Pop(); _currentPath = ConfigurationPath.Combine(_context.Reverse()); } } } ================================================ FILE: tooling/Microsoft.VisualStudio.SecretManager/Sources/PathHelper.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.IO; using Microsoft.VisualStudio.SecretManager; namespace Microsoft.Extensions.Configuration.UserSecrets { /// /// Provides paths for user secrets configuration files. /// internal class PathHelper { internal const string SecretsFileName = "secrets.json"; /// /// /// Returns the path to the JSON file that stores user secrets. /// /// /// This uses the current user profile to locate the secrets file on disk in a location outside of source control. /// /// /// The user secret ID. /// The full path to the secret file. public static string GetSecretsPathFromSecretsId(string userSecretsId) { if (string.IsNullOrEmpty(userSecretsId)) { throw new ArgumentException(Resources.Common_StringNullOrEmpty, nameof(userSecretsId)); } var badCharIndex = userSecretsId.IndexOfAny(Path.GetInvalidFileNameChars()); if (badCharIndex != -1) { throw new InvalidOperationException( string.Format( Resources.Error_Invalid_Character_In_UserSecrets_Id, userSecretsId[badCharIndex], badCharIndex)); } var root = Environment.GetEnvironmentVariable("APPDATA") ?? // On Windows it goes to %APPDATA%\Microsoft\UserSecrets\ Environment.GetEnvironmentVariable("HOME"); // On Mac/Linux it goes to ~/.microsoft/usersecrets/ if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("APPDATA"))) { return Path.Combine(root, "Microsoft", "UserSecrets", userSecretsId, SecretsFileName); } else { return Path.Combine(root, ".microsoft", "usersecrets", userSecretsId, SecretsFileName); } } } } ================================================ FILE: tooling/Microsoft.VisualStudio.SecretManager/source.extension.vsixmanifest ================================================ ASP.NET Core User Secrets Extension Microsoft.VisualStudio.SecretManager Enables IVsProjectSecrets for ASP.NET Core projects that use Microsoft.Extensions.Configuration.UserSecrets. ================================================ FILE: tooling/Microsoft.VisualStudio.SecretManager.TestExtension/Microsoft.VisualStudio.SecretManager.TestExtension.csproj ================================================ 15.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) False False False True True ..\..\build\Key.snk True Debug AnyCPU 2.0 {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} {965F8820-F809-4081-9090-1AEC903F291B} Library Properties Microsoft.VisualStudio.SecretManager.TestExtension Microsoft.VisualStudio.SecretManager.TestExtension v4.6.1 true true true true true false Program $(DevEnvDir)devenv.exe /rootsuffix Exp true full false bin\Debug\ DEBUG;TRACE prompt 4 pdbonly true bin\Release\ TRACE prompt 4 SecretManagerTestControl.xaml Designer Designer MSBuild:Compile False 7.10.6071 11.0.61030 12.0.30110 15.0.26606 8.0.50727 9.0.30729 4.4.0 Menus.ctmenu true VSPackage ================================================ FILE: tooling/Microsoft.VisualStudio.SecretManager.TestExtension/NotifyPropertyChanged.cs ================================================ // 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.ComponentModel; using System.Runtime.CompilerServices; namespace Microsoft.VisualStudio.SecretManager.TestExtension { public abstract class NotifyPropertyChanged : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } } ================================================ FILE: tooling/Microsoft.VisualStudio.SecretManager.TestExtension/ProjectViewModel.cs ================================================ // 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 Microsoft.VisualStudio.ProjectSystem; namespace Microsoft.VisualStudio.SecretManager.TestExtension { public class ProjectViewModel : NotifyPropertyChanged { public ProjectViewModel(UnconfiguredProject project) { Project = project; } internal UnconfiguredProject Project { get; } public string ProjectName => Path.GetFileNameWithoutExtension(Project.FullPath); } } ================================================ FILE: tooling/Microsoft.VisualStudio.SecretManager.TestExtension/Properties/AssemblyInfo.cs ================================================ using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: AssemblyTitle("Microsoft.VisualStudio.SecretManager.TestExtension")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("Microsoft.VisualStudio.SecretManager.TestExtension")] [assembly: AssemblyCopyright("")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. [assembly: ComVisible(false)] // Version information for an assembly consists of the following four values: // // Major Version // Minor Version // Build Number // Revision // // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")] ================================================ FILE: tooling/Microsoft.VisualStudio.SecretManager.TestExtension/RelayCommand.cs ================================================ // 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.Windows.Input; namespace Microsoft.VisualStudio.SecretManager.TestExtension { public class RelayCommand : ICommand { readonly Action _execute = null; readonly Predicate _canExecute = null; public RelayCommand(Action execute) : this(execute, null) { } public RelayCommand(Action execute, Predicate canExecute) { if (execute == null) throw new ArgumentNullException("execute"); _execute = execute; _canExecute = canExecute; } public bool CanExecute(object parameter) { return _canExecute == null ? true : _canExecute((T)parameter); } public event EventHandler CanExecuteChanged { add { CommandManager.RequerySuggested += value; } remove { CommandManager.RequerySuggested -= value; } } public void Execute(object parameter) { _execute((T)parameter); } } } ================================================ FILE: tooling/Microsoft.VisualStudio.SecretManager.TestExtension/SecretManagerTestCommand.cs ================================================ // 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.ComponentModel.Design; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; namespace Microsoft.VisualStudio.SecretManager.TestExtension { internal sealed class SecretManagerTestCommand { /// /// Command ID. /// public const int CommandId = 0x0100; /// /// Command menu group (command set GUID). /// public static readonly Guid CommandSet = new Guid("e415a3f4-f2a8-4834-b7f7-f89844b2505c"); /// /// VS Package that provides this command, not null. /// private readonly Package package; /// /// Initializes a new instance of the class. /// Adds our command handlers for menu (commands must exist in the command table file) /// /// Owner package, not null. private SecretManagerTestCommand(Package package) { if (package == null) { throw new ArgumentNullException("package"); } this.package = package; OleMenuCommandService commandService = this.ServiceProvider.GetService(typeof(IMenuCommandService)) as OleMenuCommandService; if (commandService != null) { var menuCommandID = new CommandID(CommandSet, CommandId); var menuItem = new MenuCommand(this.ShowToolWindow, menuCommandID); commandService.AddCommand(menuItem); } } /// /// Gets the instance of the command. /// public static SecretManagerTestCommand Instance { get; private set; } /// /// Gets the service provider from the owner package. /// private IServiceProvider ServiceProvider { get { return this.package; } } /// /// Initializes the singleton instance of the command. /// /// Owner package, not null. public static void Initialize(Package package) { Instance = new SecretManagerTestCommand(package); } /// /// Shows the tool window when the menu item is clicked. /// /// The event sender. /// The event args. private void ShowToolWindow(object sender, EventArgs e) { // Get the instance number 0 of this tool window. This window is single instance so this instance // is actually the only one. // The last flag is set to true so that if the tool window does not exists it will be created. ToolWindowPane window = this.package.FindToolWindow(typeof(SecretManagerTestWindow), 0, true); if ((null == window) || (null == window.Frame)) { throw new NotSupportedException("Cannot create tool window"); } IVsWindowFrame windowFrame = (IVsWindowFrame)window.Frame; Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure(windowFrame.Show()); } } } ================================================ FILE: tooling/Microsoft.VisualStudio.SecretManager.TestExtension/SecretManagerTestControl.xaml ================================================ ================================================ FILE: tooling/Microsoft.VisualStudio.SecretManager.TestExtension/SecretManagerTestControl.xaml.cs ================================================ // 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.VisualStudio.SecretManager.TestExtension { using System.Diagnostics.CodeAnalysis; using System.Windows; using System.Windows.Controls; /// /// Interaction logic for SecretManagerTestControl. /// public partial class SecretManagerTestControl : UserControl { /// /// Initializes a new instance of the class. /// public SecretManagerTestControl() { this.InitializeComponent(); } /// /// Handles click on the button by displaying a message box. /// /// The event sender. /// The event args. [SuppressMessage("Microsoft.Globalization", "CA1300:SpecifyMessageBoxOptions", Justification = "Sample code")] [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:ElementMustBeginWithUpperCaseLetter", Justification = "Default event handler naming pattern")] private void button1_Click(object sender, RoutedEventArgs e) { MessageBox.Show( string.Format(System.Globalization.CultureInfo.CurrentUICulture, "Invoked '{0}'", this.ToString()), "SecretManagerTest"); } } } ================================================ FILE: tooling/Microsoft.VisualStudio.SecretManager.TestExtension/SecretManagerTestPackage.cs ================================================ // 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.CodeAnalysis; using System.Runtime.InteropServices; using Microsoft.VisualStudio.Shell; namespace Microsoft.VisualStudio.SecretManager.TestExtension { [PackageRegistration(UseManagedResourcesOnly = true)] [InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)] // Info on this package for Help/About [ProvideMenuResource("Menus.ctmenu", 1)] [ProvideToolWindow(typeof(SecretManagerTestWindow))] [Guid(SecretManagerTestPackage.PackageGuidString)] [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "pkgdef, VS and vsixmanifest are valid VS terms")] public sealed class SecretManagerTestPackage : Package { public const string PackageGuidString = "7b771e3e-f599-4fde-95a9-e35019e705f7"; protected override void Initialize() { SecretManagerTestCommand.Initialize(this); base.Initialize(); } } } ================================================ FILE: tooling/Microsoft.VisualStudio.SecretManager.TestExtension/SecretManagerTestPackage.vsct ================================================ ================================================ FILE: tooling/Microsoft.VisualStudio.SecretManager.TestExtension/SecretManagerTestWindow.cs ================================================ // 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.Runtime.InteropServices; using Microsoft.VisualStudio.ComponentModelHost; using Microsoft.VisualStudio.ProjectSystem; using Microsoft.VisualStudio.Shell; namespace Microsoft.VisualStudio.SecretManager.TestExtension { [Guid("6afffd63-17b6-4ef2-b515-fee22d767631")] public class SecretManagerTestWindow : ToolWindowPane { public SecretManagerTestWindow() : base(null) { this.Caption = "SecretManager Test Window"; this.Content = new SecretManagerTestControl(); } protected override void Initialize() { base.Initialize(); var component = (IComponentModel)GetService(typeof(SComponentModel)); var projectService = component.GetService().GetProjectService(); ((SecretManagerTestControl)Content).DataContext = new SecretManagerViewModel(projectService); } } } ================================================ FILE: tooling/Microsoft.VisualStudio.SecretManager.TestExtension/SecretManagerViewModel.cs ================================================ // 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.Collections.ObjectModel; using System.Linq; using System.Runtime.InteropServices; using System.Threading.Tasks; using System.Windows; using Microsoft.VisualStudio.ProjectSystem; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio.Threading; using IOleServiceProvider = Microsoft.VisualStudio.OLE.Interop.IServiceProvider; using Task = System.Threading.Tasks.Task; namespace Microsoft.VisualStudio.SecretManager.TestExtension { public class SecretManagerViewModel : NotifyPropertyChanged { private readonly IProjectService _projectService; private readonly Random _rand; private string _error; private bool _isLoaded; private ProjectViewModel _selectedProject; public SecretManagerViewModel(IProjectService projectService) { _projectService = projectService; RefreshCommand = new RelayCommand(Refresh, RefreshIsEnabled); AddCommand = new RelayCommand(Add, IsProjectLoaded); SaveCommand = new RelayCommand(Save, IsProjectLoaded); Refresh(null); _rand = new Random(); } public RelayCommand RefreshCommand { get; } public RelayCommand AddCommand { get; } public RelayCommand SaveCommand { get; } public ObservableCollection Projects { get; } = new ObservableCollection(); public ProjectViewModel SelectedProject { get => _selectedProject; set { if (value == _selectedProject) { return; } _selectedProject = value; OnSelectedProjectChanged(); OnPropertyChanged(); } } public bool IsLoaded { get => _isLoaded; set { if (value == _isLoaded) { return; } _isLoaded = value; OnPropertyChanged(); } } public string Error { get => _error; set { if (value == _error) { return; } _error = value; OnPropertyChanged(); OnPropertyChanged(nameof(ErrorVisibility)); } } public Visibility ErrorVisibility => Error == null ? Visibility.Collapsed : Visibility.Visible; public ObservableCollection> Secrets { get; } = new ObservableCollection>(); private bool RefreshIsEnabled(object obj) => IsLoaded || SelectedProject == null; private void Refresh(object obj) { Projects.Clear(); foreach (var project in _projectService.LoadedUnconfiguredProjects) { Projects.Add(new ProjectViewModel(project)); } } private bool IsProjectLoaded(object obj) => IsLoaded && SelectedProject != null; private void Add(object obj) { Secrets.Add(new KeyValuePair("NewKey" + _rand.Next(10_000), "My new totally random and secret test value")); } private async void Save(object obj) { Exception exception; try { IOleServiceProvider oleServices; var project = (IVsProject)_selectedProject.Project.Services.HostObject; Marshal.ThrowExceptionForHR(project.GetItemContext((uint)VSConstants.VSITEMID.Root, out oleServices)); var services = new ServiceProvider(oleServices); var projectSecrets = (IVsProjectSecrets)services.GetService(typeof(SVsProjectLocalSecrets)); await TaskScheduler.Default; if (projectSecrets == null) { exception = null; } else { foreach (var secret in Secrets) { await projectSecrets.SetSecretAsync(secret.Key, secret.Value).ConfigureAwait(false); } exception = null; } } catch (Exception ex) { exception = ex; } if (exception != null) { Error = exception.ToString(); } } private async void OnSelectedProjectChanged() { Secrets.Clear(); IsLoaded = false; if (_selectedProject == null) { return; } KeyValuePair[] results; Exception exception; try { IOleServiceProvider oleServices; var project = (IVsProject)_selectedProject.Project.Services.HostObject; Marshal.ThrowExceptionForHR(project.GetItemContext((uint)VSConstants.VSITEMID.Root, out oleServices)); var services = new ServiceProvider(oleServices); var projectSecrets = (IVsProjectSecrets)services.GetService(typeof(SVsProjectLocalSecrets)); await TaskScheduler.Default; if (projectSecrets == null) { results = null; exception = null; } else { var secrets = await projectSecrets.GetSecretsAsync().ConfigureAwait(false); results = secrets.ToArray(); exception = null; } } catch (Exception ex) { results = null; exception = ex; } await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); if (exception != null) { Error = exception.ToString(); } else if (results != null) { for (var i = 0; i < results.Length; i++) { Secrets.Add(results[i]); } } IsLoaded = true; } } } ================================================ FILE: tooling/Microsoft.VisualStudio.SecretManager.TestExtension/VSPackage.resx ================================================  text/microsoft-resx 2.0 System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 SecretManagerTest Extension SecretManagerTest Visual Studio Extension Detailed Info Resources\SecretManagerTestPackage.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a ================================================ FILE: tooling/Microsoft.VisualStudio.SecretManager.TestExtension/app.config ================================================  ================================================ FILE: tooling/Microsoft.VisualStudio.SecretManager.TestExtension/source.extension.vsixmanifest ================================================  Microsoft.VisualStudio.SecretManager.TestExtension A test extension for Microsoft.VisualStudio.TestExtension ================================================ FILE: version.props ================================================  3.0.0 16.0 alpha1 $(VersionPrefix) $(VersionPrefix)-$(VersionSuffix)-final $(VsixVersion).$(BuildNumber) $(VsixVersion).999999 t000 a- $(FeatureBranchVersionPrefix)$(VersionSuffix)-$([System.Text.RegularExpressions.Regex]::Replace('$(FeatureBranchVersionSuffix)', '[^\w-]', '-')) $(VersionSuffix)-$(BuildNumber)