diff --git a/Mvc.NoFun.sln b/Mvc.NoFun.sln
index 3995a5463a..0167992a7f 100644
--- a/Mvc.NoFun.sln
+++ b/Mvc.NoFun.sln
@@ -117,6 +117,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Ap
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mvc.Api.Analyzers.Test", "test\Mvc.Api.Analyzers.Test\Mvc.Api.Analyzers.Test.csproj", "{71C626FC-6408-494B-A127-38CB64F71324}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-getdocument", "src\dotnet-getdocument\dotnet-getdocument.csproj", "{4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GetDocumentInsider", "src\GetDocumentInsider\GetDocumentInsider.csproj", "{2F683CF8-B055-46AE-BF83-9D1307F8D45F}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.ApiDescription.Client", "src\Microsoft.Extensions.ApiDescription.Client\Microsoft.Extensions.ApiDescription.Client.csproj", "{34E3C302-B767-40C8-B538-3EE2BD4000C4}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -575,6 +581,42 @@ Global
{71C626FC-6408-494B-A127-38CB64F71324}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{71C626FC-6408-494B-A127-38CB64F71324}.Release|x86.ActiveCfg = Release|Any CPU
{71C626FC-6408-494B-A127-38CB64F71324}.Release|x86.Build.0 = Release|Any CPU
+ {4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Debug|x86.Build.0 = Debug|Any CPU
+ {4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+ {4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Release|x86.ActiveCfg = Release|Any CPU
+ {4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Release|x86.Build.0 = Release|Any CPU
+ {2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Debug|x86.Build.0 = Debug|Any CPU
+ {2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+ {2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Release|x86.ActiveCfg = Release|Any CPU
+ {2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Release|x86.Build.0 = Release|Any CPU
+ {34E3C302-B767-40C8-B538-3EE2BD4000C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {34E3C302-B767-40C8-B538-3EE2BD4000C4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {34E3C302-B767-40C8-B538-3EE2BD4000C4}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {34E3C302-B767-40C8-B538-3EE2BD4000C4}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {34E3C302-B767-40C8-B538-3EE2BD4000C4}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {34E3C302-B767-40C8-B538-3EE2BD4000C4}.Debug|x86.Build.0 = Debug|Any CPU
+ {34E3C302-B767-40C8-B538-3EE2BD4000C4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {34E3C302-B767-40C8-B538-3EE2BD4000C4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {34E3C302-B767-40C8-B538-3EE2BD4000C4}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {34E3C302-B767-40C8-B538-3EE2BD4000C4}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+ {34E3C302-B767-40C8-B538-3EE2BD4000C4}.Release|x86.ActiveCfg = Release|Any CPU
+ {34E3C302-B767-40C8-B538-3EE2BD4000C4}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -619,6 +661,9 @@ Global
{92D959F2-66B8-490A-BA33-DA4421EBC948} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E}
{1B398182-9EAE-400B-A2BD-EFFAC0168A36} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E}
{71C626FC-6408-494B-A127-38CB64F71324} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}
+ {4EDC489F-3EC5-4AE3-9841-A285F40F5FF6} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E}
+ {2F683CF8-B055-46AE-BF83-9D1307F8D45F} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E}
+ {34E3C302-B767-40C8-B538-3EE2BD4000C4} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {D003597F-372F-4068-A2F0-353BE3C3B39A}
diff --git a/Mvc.sln b/Mvc.sln
index 775683a5fa..ce10a95245 100644
--- a/Mvc.sln
+++ b/Mvc.sln
@@ -178,6 +178,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Ap
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RazorRendering", "benchmarkapps\RazorRendering\RazorRendering.csproj", "{D7C6A696-F232-4288-BCCD-367407E4A934}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-getdocument", "src\dotnet-getdocument\dotnet-getdocument.csproj", "{4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GetDocumentInsider", "src\GetDocumentInsider\GetDocumentInsider.csproj", "{2F683CF8-B055-46AE-BF83-9D1307F8D45F}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.ApiDescription.Client", "src\Microsoft.Extensions.ApiDescription.Client\Microsoft.Extensions.ApiDescription.Client.csproj", "{34E3C302-B767-40C8-B538-3EE2BD4000C4}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -938,6 +944,42 @@ Global
{D7C6A696-F232-4288-BCCD-367407E4A934}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{D7C6A696-F232-4288-BCCD-367407E4A934}.Release|x86.ActiveCfg = Release|Any CPU
{D7C6A696-F232-4288-BCCD-367407E4A934}.Release|x86.Build.0 = Release|Any CPU
+ {4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Debug|x86.Build.0 = Debug|Any CPU
+ {4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+ {4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Release|x86.ActiveCfg = Release|Any CPU
+ {4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Release|x86.Build.0 = Release|Any CPU
+ {2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Debug|x86.Build.0 = Debug|Any CPU
+ {2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+ {2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Release|x86.ActiveCfg = Release|Any CPU
+ {2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Release|x86.Build.0 = Release|Any CPU
+ {34E3C302-B767-40C8-B538-3EE2BD4000C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {34E3C302-B767-40C8-B538-3EE2BD4000C4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {34E3C302-B767-40C8-B538-3EE2BD4000C4}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {34E3C302-B767-40C8-B538-3EE2BD4000C4}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {34E3C302-B767-40C8-B538-3EE2BD4000C4}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {34E3C302-B767-40C8-B538-3EE2BD4000C4}.Debug|x86.Build.0 = Debug|Any CPU
+ {34E3C302-B767-40C8-B538-3EE2BD4000C4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {34E3C302-B767-40C8-B538-3EE2BD4000C4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {34E3C302-B767-40C8-B538-3EE2BD4000C4}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {34E3C302-B767-40C8-B538-3EE2BD4000C4}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+ {34E3C302-B767-40C8-B538-3EE2BD4000C4}.Release|x86.ActiveCfg = Release|Any CPU
+ {34E3C302-B767-40C8-B538-3EE2BD4000C4}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -1010,6 +1052,9 @@ Global
{DD7B9F20-354C-4D9E-8C8A-8AE6E7595A87} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}
{3B550487-10E4-4E6D-9CEF-B1B4CA1253DA} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E}
{D7C6A696-F232-4288-BCCD-367407E4A934} = {2859F266-673A-45A2-9E3C-7B39C6DDD38E}
+ {4EDC489F-3EC5-4AE3-9841-A285F40F5FF6} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E}
+ {2F683CF8-B055-46AE-BF83-9D1307F8D45F} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E}
+ {34E3C302-B767-40C8-B538-3EE2BD4000C4} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {63D344F6-F86D-40E6-85B9-0AABBE338C4A}
diff --git a/build/dependencies.props b/build/dependencies.props
index 4e7f5dcd1f..304c16413f 100644
--- a/build/dependencies.props
+++ b/build/dependencies.props
@@ -54,9 +54,11 @@
2.2.0-preview3-35359
2.2.0-preview3-35359
2.2.0-preview3-35359
+ 2.0.0
2.2.0-preview3-35359
2.2.0-preview3-35359
5.2.6
+ 15.6.82
2.8.0
2.8.0
2.2.0-preview3-35359
@@ -100,8 +102,10 @@
4.7.49
2.0.3
1.0.1
+ 11.0.2
4.5.0
4.5.0
+ 4.3.2
4.5.1
0.10.0
2.3.1
diff --git a/src/GetDocumentInsider/AnsiConsole.cs b/src/GetDocumentInsider/AnsiConsole.cs
new file mode 100644
index 0000000000..30397229aa
--- /dev/null
+++ b/src/GetDocumentInsider/AnsiConsole.cs
@@ -0,0 +1,15 @@
+// Copyright (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 GetDocument
+{
+ internal class AnsiConsole
+ {
+ public static readonly AnsiTextWriter _out = new AnsiTextWriter(Console.Out);
+
+ public static void WriteLine(string text)
+ => _out.WriteLine(text);
+ }
+}
diff --git a/src/GetDocumentInsider/AnsiConstants.cs b/src/GetDocumentInsider/AnsiConstants.cs
new file mode 100644
index 0000000000..e529180983
--- /dev/null
+++ b/src/GetDocumentInsider/AnsiConstants.cs
@@ -0,0 +1,20 @@
+// Copyright (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 GetDocument
+{
+ internal static class AnsiConstants
+ {
+ public const string Reset = "\x1b[22m\x1b[39m";
+ public const string Bold = "\x1b[1m";
+ public const string Dark = "\x1b[22m";
+ public const string Black = "\x1b[30m";
+ public const string Red = "\x1b[31m";
+ public const string Green = "\x1b[32m";
+ public const string Yellow = "\x1b[33m";
+ public const string Blue = "\x1b[34m";
+ public const string Magenta = "\x1b[35m";
+ public const string Cyan = "\x1b[36m";
+ public const string Gray = "\x1b[37m";
+ }
+}
diff --git a/src/GetDocumentInsider/AnsiTextWriter.cs b/src/GetDocumentInsider/AnsiTextWriter.cs
new file mode 100644
index 0000000000..c8393d4810
--- /dev/null
+++ b/src/GetDocumentInsider/AnsiTextWriter.cs
@@ -0,0 +1,131 @@
+// Copyright (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.Text.RegularExpressions;
+
+namespace GetDocument
+{
+ internal class AnsiTextWriter
+ {
+ private readonly TextWriter _writer;
+
+ public AnsiTextWriter(TextWriter writer) => _writer = writer;
+
+ public void WriteLine(string text)
+ {
+ Interpret(text);
+ _writer.Write(Environment.NewLine);
+ }
+
+ private void Interpret(string value)
+ {
+ var matches = Regex.Matches(value, "\x1b\\[([0-9]+)?m");
+
+ var start = 0;
+ foreach (Match match in matches)
+ {
+ var length = match.Index - start;
+ if (length != 0)
+ {
+ _writer.Write(value.Substring(start, length));
+ }
+
+ Apply(match.Groups[1].Value);
+
+ start = match.Index + match.Length;
+ }
+
+ if (start != value.Length)
+ {
+ _writer.Write(value.Substring(start));
+ }
+ }
+
+ private static void Apply(string parameter)
+ {
+ switch (parameter)
+ {
+ case "1":
+ ApplyBold();
+ break;
+
+ case "22":
+ ResetBold();
+ break;
+
+ case "30":
+ ApplyColor(ConsoleColor.Black);
+ break;
+
+ case "31":
+ ApplyColor(ConsoleColor.DarkRed);
+ break;
+
+ case "32":
+ ApplyColor(ConsoleColor.DarkGreen);
+ break;
+
+ case "33":
+ ApplyColor(ConsoleColor.DarkYellow);
+ break;
+
+ case "34":
+ ApplyColor(ConsoleColor.DarkBlue);
+ break;
+
+ case "35":
+ ApplyColor(ConsoleColor.DarkMagenta);
+ break;
+
+ case "36":
+ ApplyColor(ConsoleColor.DarkCyan);
+ break;
+
+ case "37":
+ ApplyColor(ConsoleColor.Gray);
+ break;
+
+ case "39":
+ ResetColor();
+ break;
+
+ default:
+ Debug.Fail("Unsupported parameter: " + parameter);
+ break;
+ }
+ }
+
+ private static void ApplyBold()
+ => Console.ForegroundColor = (ConsoleColor)((int)Console.ForegroundColor | 8);
+
+ private static void ResetBold()
+ => Console.ForegroundColor = (ConsoleColor)((int)Console.ForegroundColor & 7);
+
+ private static void ApplyColor(ConsoleColor color)
+ {
+ var wasBold = ((int)Console.ForegroundColor & 8) != 0;
+
+ Console.ForegroundColor = color;
+
+ if (wasBold)
+ {
+ ApplyBold();
+ }
+ }
+
+ private static void ResetColor()
+ {
+ var wasBold = ((int)Console.ForegroundColor & 8) != 0;
+
+ Console.ResetColor();
+
+ if (wasBold)
+ {
+ ApplyBold();
+ }
+ }
+ }
+}
diff --git a/src/GetDocumentInsider/CodeAnnotations.cs b/src/GetDocumentInsider/CodeAnnotations.cs
new file mode 100644
index 0000000000..7a179f24d3
--- /dev/null
+++ b/src/GetDocumentInsider/CodeAnnotations.cs
@@ -0,0 +1,23 @@
+// Copyright (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 JetBrains.Annotations
+{
+ [AttributeUsage(
+ AttributeTargets.Method | AttributeTargets.Parameter |
+ AttributeTargets.Property | AttributeTargets.Delegate |
+ AttributeTargets.Field)]
+ internal sealed class NotNullAttribute : Attribute
+ {
+ }
+
+ [AttributeUsage(
+ AttributeTargets.Method | AttributeTargets.Parameter |
+ AttributeTargets.Property | AttributeTargets.Delegate |
+ AttributeTargets.Field)]
+ internal sealed class CanBeNullAttribute : Attribute
+ {
+ }
+}
diff --git a/src/GetDocumentInsider/CommandException.cs b/src/GetDocumentInsider/CommandException.cs
new file mode 100644
index 0000000000..5d9778e61f
--- /dev/null
+++ b/src/GetDocumentInsider/CommandException.cs
@@ -0,0 +1,20 @@
+// Copyright (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 GetDocument
+{
+ internal class CommandException : Exception
+ {
+ public CommandException(string message)
+ : base(message)
+ {
+ }
+
+ public CommandException(string message, Exception innerException)
+ : base(message, innerException)
+ {
+ }
+ }
+}
diff --git a/src/GetDocumentInsider/CommandLineUtils/CommandArgument.cs b/src/GetDocumentInsider/CommandLineUtils/CommandArgument.cs
new file mode 100644
index 0000000000..3346ea0ecb
--- /dev/null
+++ b/src/GetDocumentInsider/CommandLineUtils/CommandArgument.cs
@@ -0,0 +1,19 @@
+// Copyright (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.DotNet.Cli.CommandLine
+{
+ internal class CommandArgument
+ {
+ public CommandArgument() => Values = new List();
+
+ public string Name { get; set; }
+ public string Description { get; set; }
+ public List Values { get; private set; }
+ public bool MultipleValues { get; set; }
+ public string Value => Values.FirstOrDefault();
+ }
+}
diff --git a/src/GetDocumentInsider/CommandLineUtils/CommandLineApplication.cs b/src/GetDocumentInsider/CommandLineUtils/CommandLineApplication.cs
new file mode 100644
index 0000000000..facbb68ad0
--- /dev/null
+++ b/src/GetDocumentInsider/CommandLineUtils/CommandLineApplication.cs
@@ -0,0 +1,604 @@
+// Copyright (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;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Microsoft.DotNet.Cli.CommandLine
+{
+ internal class CommandLineApplication
+ {
+ private enum ParseOptionResult
+ {
+ Succeeded,
+ ShowHelp,
+ ShowVersion,
+ UnexpectedArgs,
+ }
+
+ // Indicates whether the parser should throw an exception when it runs into an unexpected argument.
+ // If this field is set to false, the parser will stop parsing when it sees an unexpected argument, and all
+ // remaining arguments, including the first unexpected argument, will be stored in RemainingArguments property.
+ private readonly bool _throwOnUnexpectedArg;
+
+ public CommandLineApplication(bool throwOnUnexpectedArg = true)
+ {
+ _throwOnUnexpectedArg = throwOnUnexpectedArg;
+ Options = new List();
+ Arguments = new List();
+ Commands = new List();
+ RemainingArguments = new List();
+ Invoke = () => 0;
+ }
+
+ public CommandLineApplication Parent { get; set; }
+ public string Name { get; set; }
+ public string FullName { get; set; }
+ public string Syntax { get; set; }
+ public string Description { get; set; }
+ public List Options { get; private set; }
+ public CommandOption OptionHelp { get; private set; }
+ public CommandOption OptionVersion { get; private set; }
+ public List Arguments { get; private set; }
+ public List RemainingArguments { get; private set; }
+ public bool IsShowingInformation { get; protected set; } // Is showing help or version?
+ public Func Invoke { get; set; }
+ public Func LongVersionGetter { get; set; }
+ public Func ShortVersionGetter { get; set; }
+ public List Commands { get; private set; }
+ public bool HandleResponseFiles { get; set; }
+ public bool AllowArgumentSeparator { get; set; }
+ public bool HandleRemainingArguments { get; set; }
+ public string ArgumentSeparatorHelpText { get; set; }
+
+ public CommandLineApplication Command(string name, bool throwOnUnexpectedArg = true)
+ => Command(name, _ => { }, throwOnUnexpectedArg);
+
+ public CommandLineApplication Command(string name, Action configuration,
+ bool throwOnUnexpectedArg = true)
+ {
+ var command = new CommandLineApplication(throwOnUnexpectedArg) { Name = name, Parent = this };
+ Commands.Add(command);
+ configuration(command);
+ return command;
+ }
+
+ public CommandOption Option(string template, string description, CommandOptionType optionType)
+ => Option(template, description, optionType, _ => { });
+
+ public CommandOption Option(string template, string description, CommandOptionType optionType, Action configuration)
+ {
+ var option = new CommandOption(template, optionType) { Description = description };
+ Options.Add(option);
+ configuration(option);
+ return option;
+ }
+
+ public CommandArgument Argument(string name, string description, bool multipleValues = false)
+ => Argument(name, description, _ => { }, multipleValues);
+
+ public CommandArgument Argument(string name, string description, Action configuration, bool multipleValues = false)
+ {
+ var lastArg = Arguments.LastOrDefault();
+ if (lastArg != null && lastArg.MultipleValues)
+ {
+ var message = string.Format("The last argument '{0}' accepts multiple values. No more argument can be added.",
+ lastArg.Name);
+ throw new InvalidOperationException(message);
+ }
+
+ var argument = new CommandArgument { Name = name, Description = description, MultipleValues = multipleValues };
+ Arguments.Add(argument);
+ configuration(argument);
+ return argument;
+ }
+
+ public void OnExecute(Func invoke) => Invoke = invoke;
+
+ public void OnExecute(Func> invoke) => Invoke = () => invoke().Result;
+
+ public int Execute(params string[] args)
+ {
+ var command = this;
+ IEnumerator arguments = null;
+
+ if (HandleResponseFiles)
+ {
+ args = ExpandResponseFiles(args).ToArray();
+ }
+
+ for (var index = 0; index < args.Length; index++)
+ {
+ var arg = args[index];
+
+ var isLongOption = arg.StartsWith("--");
+ if (isLongOption || arg.StartsWith("-"))
+ {
+ var result = ParseOption(isLongOption, command, args, ref index, out var option);
+ if (result == ParseOptionResult.ShowHelp)
+ {
+ command.ShowHelp();
+ return 0;
+ }
+ else if (result == ParseOptionResult.ShowVersion)
+ {
+ command.ShowVersion();
+ return 0;
+ }
+ }
+ else
+ {
+ var subcommand = ParseSubCommand(arg, command);
+ if (subcommand != null)
+ {
+ command = subcommand;
+ }
+ else
+ {
+ if (arguments == null)
+ {
+ arguments = new CommandArgumentEnumerator(command.Arguments.GetEnumerator());
+ }
+
+ if (arguments.MoveNext())
+ {
+ arguments.Current.Values.Add(arg);
+ }
+ else
+ {
+ HandleUnexpectedArg(command, args, index, argTypeName: "command or argument");
+ }
+ }
+ }
+ }
+
+ return command.Invoke();
+ }
+
+ private ParseOptionResult ParseOption(
+ bool isLongOption,
+ CommandLineApplication command,
+ string[] args,
+ ref int index,
+ out CommandOption option)
+ {
+ option = null;
+ var result = ParseOptionResult.Succeeded;
+ var arg = args[index];
+
+ var optionPrefixLength = isLongOption ? 2 : 1;
+ var optionComponents = arg.Substring(optionPrefixLength).Split(new[] { ':', '=' }, 2);
+ var optionName = optionComponents[0];
+
+ if (isLongOption)
+ {
+ option = command.Options.SingleOrDefault(
+ opt => string.Equals(opt.LongName, optionName, StringComparison.Ordinal));
+ }
+ else
+ {
+ option = command.Options.SingleOrDefault(
+ opt => string.Equals(opt.ShortName, optionName, StringComparison.Ordinal));
+
+ if (option == null)
+ {
+ option = command.Options.SingleOrDefault(
+ opt => string.Equals(opt.SymbolName, optionName, StringComparison.Ordinal));
+ }
+ }
+
+ if (option == null)
+ {
+ if (isLongOption && string.IsNullOrEmpty(optionName) &&
+ !command._throwOnUnexpectedArg && AllowArgumentSeparator)
+ {
+ // a stand-alone "--" is the argument separator, so skip it and
+ // handle the rest of the args as unexpected args
+ index++;
+ }
+
+ HandleUnexpectedArg(command, args, index, argTypeName: "option");
+ result = ParseOptionResult.UnexpectedArgs;
+ }
+ else if (command.OptionHelp == option)
+ {
+ result = ParseOptionResult.ShowHelp;
+ }
+ else if (command.OptionVersion == option)
+ {
+ result = ParseOptionResult.ShowVersion;
+ }
+ else
+ {
+ if (optionComponents.Length == 2)
+ {
+ if (!option.TryParse(optionComponents[1]))
+ {
+ command.ShowHint();
+ throw new CommandParsingException(command,
+ $"Unexpected value '{optionComponents[1]}' for option '{optionName}'");
+ }
+ }
+ else
+ {
+ if (option.OptionType == CommandOptionType.NoValue ||
+ option.OptionType == CommandOptionType.BoolValue)
+ {
+ // No value is needed for this option
+ option.TryParse(null);
+ }
+ else
+ {
+ index++;
+ arg = args[index];
+ if (!option.TryParse(arg))
+ {
+ command.ShowHint();
+ throw new CommandParsingException(command, $"Unexpected value '{arg}' for option '{optionName}'");
+
+ }
+ }
+ }
+ }
+
+ return result;
+ }
+
+ private static CommandLineApplication ParseSubCommand(string arg, CommandLineApplication command)
+ {
+ foreach (var subcommand in command.Commands)
+ {
+ if (string.Equals(subcommand.Name, arg, StringComparison.OrdinalIgnoreCase))
+ {
+ return subcommand;
+ }
+ }
+
+ return null;
+ }
+
+ // Helper method that adds a help option
+ public CommandOption HelpOption(string template)
+ {
+ // Help option is special because we stop parsing once we see it
+ // So we store it separately for further use
+ OptionHelp = Option(template, "Show help information", CommandOptionType.NoValue);
+
+ return OptionHelp;
+ }
+
+ public CommandOption VersionOption(string template,
+ string shortFormVersion,
+ string longFormVersion = null)
+ {
+ if (longFormVersion == null)
+ {
+ return VersionOption(template, () => shortFormVersion);
+ }
+ else
+ {
+ return VersionOption(template, () => shortFormVersion, () => longFormVersion);
+ }
+ }
+
+ // Helper method that adds a version option
+ public CommandOption VersionOption(string template,
+ Func shortFormVersionGetter,
+ Func longFormVersionGetter = null)
+ {
+ // Version option is special because we stop parsing once we see it
+ // So we store it separately for further use
+ OptionVersion = Option(template, "Show version information", CommandOptionType.NoValue);
+ ShortVersionGetter = shortFormVersionGetter;
+ LongVersionGetter = longFormVersionGetter ?? shortFormVersionGetter;
+
+ return OptionVersion;
+ }
+
+ // Show short hint that reminds users to use help option
+ public void ShowHint()
+ {
+ if (OptionHelp != null)
+ {
+ Console.WriteLine(string.Format("Specify --{0} for a list of available options and commands.", OptionHelp.LongName));
+ }
+ }
+
+ // Show full help
+ public void ShowHelp(string commandName = null)
+ {
+ var headerBuilder = new StringBuilder("Usage:");
+ var usagePrefixLength = headerBuilder.Length;
+ for (var cmd = this; cmd != null; cmd = cmd.Parent)
+ {
+ cmd.IsShowingInformation = true;
+ if (cmd != this && cmd.Arguments.Any())
+ {
+ var args = string.Join(" ", cmd.Arguments.Select(arg => arg.Name));
+ headerBuilder.Insert(usagePrefixLength, string.Format(" {0} {1}", cmd.Name, args));
+ }
+ else
+ {
+ headerBuilder.Insert(usagePrefixLength, string.Format(" {0}", cmd.Name));
+ }
+ }
+
+ CommandLineApplication target;
+
+ if (commandName == null || string.Equals(Name, commandName, StringComparison.OrdinalIgnoreCase))
+ {
+ target = this;
+ }
+ else
+ {
+ target = Commands.SingleOrDefault(cmd => string.Equals(cmd.Name, commandName, StringComparison.OrdinalIgnoreCase));
+
+ if (target != null)
+ {
+ headerBuilder.AppendFormat(" {0}", commandName);
+ }
+ else
+ {
+ // The command name is invalid so don't try to show help for something that doesn't exist
+ target = this;
+ }
+
+ }
+
+ var optionsBuilder = new StringBuilder();
+ var commandsBuilder = new StringBuilder();
+ var argumentsBuilder = new StringBuilder();
+ var argumentSeparatorBuilder = new StringBuilder();
+
+ var maxArgLen = 0;
+ for (var cmd = target; cmd != null; cmd = cmd.Parent)
+ {
+ if (cmd.Arguments.Any())
+ {
+ if (cmd == target)
+ {
+ headerBuilder.Append(" [arguments]");
+ }
+
+ if (argumentsBuilder.Length == 0)
+ {
+ argumentsBuilder.AppendLine();
+ argumentsBuilder.AppendLine("Arguments:");
+ }
+
+ maxArgLen = Math.Max(maxArgLen, MaxArgumentLength(cmd.Arguments));
+ }
+ }
+
+ for (var cmd = target; cmd != null; cmd = cmd.Parent)
+ {
+ if (cmd.Arguments.Any())
+ {
+ var outputFormat = " {0}{1}";
+ foreach (var arg in cmd.Arguments)
+ {
+ argumentsBuilder.AppendFormat(
+ outputFormat,
+ arg.Name.PadRight(maxArgLen + 2),
+ arg.Description);
+ argumentsBuilder.AppendLine();
+ }
+ }
+ }
+
+ if (target.Options.Any())
+ {
+ headerBuilder.Append(" [options]");
+
+ optionsBuilder.AppendLine();
+ optionsBuilder.AppendLine("Options:");
+ var maxOptLen = MaxOptionTemplateLength(target.Options);
+ var outputFormat = string.Format(" {{0, -{0}}}{{1}}", maxOptLen + 2);
+ foreach (var opt in target.Options)
+ {
+ optionsBuilder.AppendFormat(outputFormat, opt.Template, opt.Description);
+ optionsBuilder.AppendLine();
+ }
+ }
+
+ if (target.Commands.Any())
+ {
+ headerBuilder.Append(" [command]");
+
+ commandsBuilder.AppendLine();
+ commandsBuilder.AppendLine("Commands:");
+ var maxCmdLen = MaxCommandLength(target.Commands);
+ var outputFormat = string.Format(" {{0, -{0}}}{{1}}", maxCmdLen + 2);
+ foreach (var cmd in target.Commands.OrderBy(c => c.Name))
+ {
+ commandsBuilder.AppendFormat(outputFormat, cmd.Name, cmd.Description);
+ commandsBuilder.AppendLine();
+ }
+
+ if (OptionHelp != null)
+ {
+ commandsBuilder.AppendLine();
+ commandsBuilder.AppendFormat("Use \"{0} [command] --help\" for more information about a command.", Name);
+ commandsBuilder.AppendLine();
+ }
+ }
+
+ if (target.AllowArgumentSeparator || target.HandleRemainingArguments)
+ {
+ if (target.AllowArgumentSeparator)
+ {
+ headerBuilder.Append(" [[--] ...]]");
+ }
+ else
+ {
+ headerBuilder.Append(" [args]");
+ }
+
+ if (!string.IsNullOrEmpty(target.ArgumentSeparatorHelpText))
+ {
+ argumentSeparatorBuilder.AppendLine();
+ argumentSeparatorBuilder.AppendLine("Args:");
+ argumentSeparatorBuilder.AppendLine($" {target.ArgumentSeparatorHelpText}");
+ argumentSeparatorBuilder.AppendLine();
+ }
+ }
+
+ headerBuilder.AppendLine();
+
+ var nameAndVersion = new StringBuilder();
+ nameAndVersion.AppendLine(GetFullNameAndVersion());
+ nameAndVersion.AppendLine();
+
+ Console.Write("{0}{1}{2}{3}{4}{5}", nameAndVersion, headerBuilder, argumentsBuilder, optionsBuilder, commandsBuilder, argumentSeparatorBuilder);
+ }
+
+ public void ShowVersion()
+ {
+ for (var cmd = this; cmd != null; cmd = cmd.Parent)
+ {
+ cmd.IsShowingInformation = true;
+ }
+
+ Console.WriteLine(FullName);
+ Console.WriteLine(LongVersionGetter());
+ }
+
+ public string GetFullNameAndVersion()
+ => ShortVersionGetter == null ? FullName : string.Format("{0} {1}", FullName, ShortVersionGetter());
+
+ public void ShowRootCommandFullNameAndVersion()
+ {
+ var rootCmd = this;
+ while (rootCmd.Parent != null)
+ {
+ rootCmd = rootCmd.Parent;
+ }
+
+ Console.WriteLine(rootCmd.GetFullNameAndVersion());
+ Console.WriteLine();
+ }
+
+ private static int MaxOptionTemplateLength(IEnumerable options)
+ {
+ var maxLen = 0;
+ foreach (var opt in options)
+ {
+ maxLen = opt.Template.Length > maxLen ? opt.Template.Length : maxLen;
+ }
+ return maxLen;
+ }
+
+ private static int MaxCommandLength(IEnumerable commands)
+ {
+ var maxLen = 0;
+ foreach (var cmd in commands)
+ {
+ maxLen = cmd.Name.Length > maxLen ? cmd.Name.Length : maxLen;
+ }
+ return maxLen;
+ }
+
+ private static int MaxArgumentLength(IEnumerable arguments)
+ {
+ var maxLen = 0;
+ foreach (var arg in arguments)
+ {
+ maxLen = arg.Name.Length > maxLen ? arg.Name.Length : maxLen;
+ }
+ return maxLen;
+ }
+
+ private static void HandleUnexpectedArg(CommandLineApplication command, string[] args, int index, string argTypeName)
+ {
+ if (command._throwOnUnexpectedArg)
+ {
+ command.ShowHint();
+ throw new CommandParsingException(command, $"Unrecognized {argTypeName} '{args[index]}'");
+ }
+ else
+ {
+ command.RemainingArguments.Add(args[index]);
+ }
+ }
+
+ private IEnumerable ExpandResponseFiles(IEnumerable args)
+ {
+ foreach (var arg in args)
+ {
+ if (!arg.StartsWith("@", StringComparison.Ordinal))
+ {
+ yield return arg;
+ }
+ else
+ {
+ var fileName = arg.Substring(1);
+
+ var responseFileArguments = ParseResponseFile(fileName);
+
+ // ParseResponseFile can suppress expanding this response file by
+ // returning null. In that case, we'll treat the response
+ // file token as a regular argument.
+
+ if (responseFileArguments == null)
+ {
+ yield return arg;
+ }
+ else
+ {
+ foreach (var responseFileArgument in responseFileArguments)
+ {
+ yield return responseFileArgument.Trim();
+ }
+ }
+ }
+ }
+ }
+
+ private IEnumerable ParseResponseFile(string fileName)
+ {
+ if (!HandleResponseFiles)
+ {
+ return null;
+ }
+
+ if (!File.Exists(fileName))
+ {
+ throw new InvalidOperationException($"Response file '{fileName}' doesn't exist.");
+ }
+
+ return File.ReadLines(fileName);
+ }
+
+ private class CommandArgumentEnumerator : IEnumerator
+ {
+ private readonly IEnumerator _enumerator;
+
+ public CommandArgumentEnumerator(IEnumerator enumerator) => _enumerator = enumerator;
+
+ public CommandArgument Current => _enumerator.Current;
+
+ object IEnumerator.Current => Current;
+
+ public void Dispose() => _enumerator.Dispose();
+
+ public bool MoveNext()
+ {
+ if (Current == null || !Current.MultipleValues)
+ {
+ return _enumerator.MoveNext();
+ }
+
+ // If current argument allows multiple values, we don't move forward and
+ // all later values will be added to current CommandArgument.Values
+ return true;
+ }
+
+ public void Reset() => _enumerator.Reset();
+ }
+ }
+}
diff --git a/src/GetDocumentInsider/CommandLineUtils/CommandLineApplicationExtensions.cs b/src/GetDocumentInsider/CommandLineUtils/CommandLineApplicationExtensions.cs
new file mode 100644
index 0000000000..1c43455ee1
--- /dev/null
+++ b/src/GetDocumentInsider/CommandLineUtils/CommandLineApplicationExtensions.cs
@@ -0,0 +1,18 @@
+// Copyright (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.Cli.CommandLine
+{
+ internal static class CommandLineApplicationExtensions
+ {
+ public static CommandOption Option(this CommandLineApplication command, string template, string description)
+ => command.Option(
+ template,
+ description,
+ template.IndexOf('<') != -1
+ ? template.EndsWith(">...")
+ ? CommandOptionType.MultipleValue
+ : CommandOptionType.SingleValue
+ : CommandOptionType.NoValue);
+ }
+}
diff --git a/src/GetDocumentInsider/CommandLineUtils/CommandOption.cs b/src/GetDocumentInsider/CommandLineUtils/CommandOption.cs
new file mode 100644
index 0000000000..5ba4b78ae3
--- /dev/null
+++ b/src/GetDocumentInsider/CommandLineUtils/CommandOption.cs
@@ -0,0 +1,125 @@
+// Copyright (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.DotNet.Cli.CommandLine
+{
+ internal class CommandOption
+ {
+ public CommandOption(string template, CommandOptionType optionType)
+ {
+ Template = template;
+ OptionType = optionType;
+ Values = new List();
+
+ foreach (var part in Template.Split(new[] { ' ', '|' }, StringSplitOptions.RemoveEmptyEntries))
+ {
+ if (part.StartsWith("--"))
+ {
+ LongName = part.Substring(2);
+ }
+ else if (part.StartsWith("-"))
+ {
+ var optName = part.Substring(1);
+
+ // If there is only one char and it is not an English letter, it is a symbol option (e.g. "-?")
+ if (optName.Length == 1 && !IsEnglishLetter(optName[0]))
+ {
+ SymbolName = optName;
+ }
+ else
+ {
+ ShortName = optName;
+ }
+ }
+ else if (part.StartsWith("<") && part.EndsWith(">"))
+ {
+ ValueName = part.Substring(1, part.Length - 2);
+ }
+ else if (optionType == CommandOptionType.MultipleValue && part.StartsWith("<") && part.EndsWith(">..."))
+ {
+ ValueName = part.Substring(1, part.Length - 5);
+ }
+ else
+ {
+ throw new ArgumentException($"Invalid template pattern '{template}'", nameof(template));
+ }
+ }
+
+ if (string.IsNullOrEmpty(LongName) && string.IsNullOrEmpty(ShortName) && string.IsNullOrEmpty(SymbolName))
+ {
+ throw new ArgumentException($"Invalid template pattern '{template}'", nameof(template));
+ }
+ }
+
+ public string Template { get; set; }
+ public string ShortName { get; set; }
+ public string LongName { get; set; }
+ public string SymbolName { get; set; }
+ public string ValueName { get; set; }
+ public string Description { get; set; }
+ public List Values { get; private set; }
+ public bool? BoolValue { get; private set; }
+ public CommandOptionType OptionType { get; private set; }
+
+ public bool TryParse(string value)
+ {
+ switch (OptionType)
+ {
+ case CommandOptionType.MultipleValue:
+ Values.Add(value);
+ break;
+ case CommandOptionType.SingleValue:
+ if (Values.Any())
+ {
+ return false;
+ }
+ Values.Add(value);
+ break;
+ case CommandOptionType.BoolValue:
+ if (Values.Any())
+ {
+ return false;
+ }
+
+ if (value == null)
+ {
+ // add null to indicate that the option was present, but had no value
+ Values.Add(null);
+ BoolValue = true;
+ }
+ else
+ {
+ if (!bool.TryParse(value, out var boolValue))
+ {
+ return false;
+ }
+
+ Values.Add(value);
+ BoolValue = boolValue;
+ }
+ break;
+ case CommandOptionType.NoValue:
+ if (value != null)
+ {
+ return false;
+ }
+ // Add a value to indicate that this option was specified
+ Values.Add("on");
+ break;
+ default:
+ break;
+ }
+ return true;
+ }
+
+ public bool HasValue() => Values.Any();
+
+ public string Value() => HasValue() ? Values[0] : null;
+
+ private static bool IsEnglishLetter(char c) => (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
+ }
+}
diff --git a/src/GetDocumentInsider/CommandLineUtils/CommandOptionType.cs b/src/GetDocumentInsider/CommandLineUtils/CommandOptionType.cs
new file mode 100644
index 0000000000..5f7d37f029
--- /dev/null
+++ b/src/GetDocumentInsider/CommandLineUtils/CommandOptionType.cs
@@ -0,0 +1,13 @@
+// Copyright (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.Cli.CommandLine
+{
+ internal enum CommandOptionType
+ {
+ MultipleValue,
+ SingleValue,
+ BoolValue,
+ NoValue
+ }
+}
diff --git a/src/GetDocumentInsider/CommandLineUtils/CommandParsingException.cs b/src/GetDocumentInsider/CommandLineUtils/CommandParsingException.cs
new file mode 100644
index 0000000000..c735ecbf12
--- /dev/null
+++ b/src/GetDocumentInsider/CommandLineUtils/CommandParsingException.cs
@@ -0,0 +1,15 @@
+// Copyright (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.Cli.CommandLine
+{
+ internal class CommandParsingException : Exception
+ {
+ public CommandParsingException(CommandLineApplication command, string message)
+ : base(message) => Command = command;
+
+ public CommandLineApplication Command { get; }
+ }
+}
diff --git a/src/GetDocumentInsider/Commands/CommandBase.cs b/src/GetDocumentInsider/Commands/CommandBase.cs
new file mode 100644
index 0000000000..4f66e51d5e
--- /dev/null
+++ b/src/GetDocumentInsider/Commands/CommandBase.cs
@@ -0,0 +1,39 @@
+// Copyright (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 GetDocument.Properties;
+using Microsoft.DotNet.Cli.CommandLine;
+
+namespace GetDocument.Commands
+{
+ internal abstract class CommandBase
+ {
+ public virtual void Configure(CommandLineApplication command)
+ {
+ var verbose = command.Option("-v|--verbose", Resources.VerboseDescription);
+ var noColor = command.Option("--no-color", Resources.NoColorDescription);
+ var prefixOutput = command.Option("--prefix-output", Resources.PrefixDescription);
+
+ command.HandleResponseFiles = true;
+
+ command.OnExecute(
+ () =>
+ {
+ Reporter.IsVerbose = verbose.HasValue();
+ Reporter.NoColor = noColor.HasValue();
+ Reporter.PrefixOutput = prefixOutput.HasValue();
+
+ Validate();
+
+ return Execute();
+ });
+ }
+
+ protected virtual void Validate()
+ {
+ }
+
+ protected virtual int Execute()
+ => 0;
+ }
+}
diff --git a/src/GetDocumentInsider/Commands/GetDocumentCommand.cs b/src/GetDocumentInsider/Commands/GetDocumentCommand.cs
new file mode 100644
index 0000000000..f615e4399b
--- /dev/null
+++ b/src/GetDocumentInsider/Commands/GetDocumentCommand.cs
@@ -0,0 +1,175 @@
+// Copyright (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;
+#if NETCOREAPP2_0
+using System.Runtime.Loader;
+#endif
+using GetDocument.Properties;
+using Microsoft.DotNet.Cli.CommandLine;
+
+namespace GetDocument.Commands
+{
+ internal class GetDocumentCommand : ProjectCommandBase
+ {
+ internal const string FallbackDocumentName = "v1";
+ internal const string FallbackMethod = "Generate";
+ internal const string FallbackService = "Microsoft.Extensions.ApiDescription.IDocumentProvider";
+ private const string WorkerType = "GetDocument.Commands.GetDocumentCommandWorker";
+
+ private CommandOption _documentName;
+ private CommandOption _method;
+ private CommandOption _output;
+ private CommandOption _service;
+ private CommandOption _uri;
+
+ public override void Configure(CommandLineApplication command)
+ {
+ base.Configure(command);
+
+ _documentName = command.Option(
+ "--documentName ",
+ Resources.DocumentDescription(FallbackDocumentName));
+ _method = command.Option("--method ", Resources.MethodDescription(FallbackMethod));
+ _output = command.Option("--output ", Resources.OutputDescription);
+ _service = command.Option("--service ", Resources.ServiceDescription(FallbackService));
+ _uri = command.Option("--uri ", Resources.UriDescription);
+ }
+
+ protected override void Validate()
+ {
+ base.Validate();
+
+ if (!_output.HasValue())
+ {
+ throw new CommandException(Resources.MissingOption(_output.LongName));
+ }
+
+ if (_method.HasValue() && !_service.HasValue())
+ {
+ throw new CommandException(Resources.MissingOption(_service.LongName));
+ }
+
+ if (_service.HasValue() && !_method.HasValue())
+ {
+ throw new CommandException(Resources.MissingOption(_method.LongName));
+ }
+ }
+
+ protected override int Execute()
+ {
+ var thisAssembly = typeof(GetDocumentCommand).Assembly;
+
+ var toolsDirectory = ToolsDirectory.Value();
+ var packagedAssemblies = Directory
+ .EnumerateFiles(toolsDirectory, "*.dll")
+ .Except(new[] { Path.GetFullPath(thisAssembly.Location) })
+ .ToDictionary(path => Path.GetFileNameWithoutExtension(path), path => new AssemblyInfo(path));
+
+ // Explicitly load all assemblies we need first to preserve target project as much as possible. This
+ // executable is always run in the target project's context (either through location or .deps.json file).
+ foreach (var keyValuePair in packagedAssemblies)
+ {
+ try
+ {
+ keyValuePair.Value.Assembly = Assembly.Load(new AssemblyName(keyValuePair.Key));
+ }
+ catch
+ {
+ // Ignore all failures because missing assemblies should be loadable from tools directory.
+ }
+ }
+
+#if NETCOREAPP2_0
+ AssemblyLoadContext.Default.Resolving += (loadContext, assemblyName) =>
+ {
+ var name = assemblyName.Name;
+ if (!packagedAssemblies.TryGetValue(name, out var info))
+ {
+ return null;
+ }
+
+ var assemblyPath = info.Path;
+ if (!File.Exists(assemblyPath))
+ {
+ throw new InvalidOperationException(
+ $"Referenced assembly '{name}' was not found in '{toolsDirectory}'.");
+ }
+
+ return loadContext.LoadFromAssemblyPath(assemblyPath);
+ };
+
+#elif NET461
+ AppDomain.CurrentDomain.AssemblyResolve += (source, eventArgs) =>
+ {
+ var assemblyName = new AssemblyName(eventArgs.Name);
+ var name = assemblyName.Name;
+ if (!packagedAssemblies.TryGetValue(name, out var info))
+ {
+ return null;
+ }
+
+ var assembly = info.Assembly;
+ if (assembly != null)
+ {
+ // Loaded already
+ return assembly;
+ }
+
+ var assemblyPath = info.Path;
+ if (!File.Exists(assemblyPath))
+ {
+ throw new InvalidOperationException(
+ $"Referenced assembly '{name}' was not found in '{toolsDirectory}'.");
+ }
+
+ return Assembly.LoadFile(assemblyPath);
+ };
+#else
+#error target frameworks need to be updated.
+#endif
+
+ // Now safe to reference TestHost type.
+ try
+ {
+ var workerType = thisAssembly.GetType(WorkerType, throwOnError: true);
+ var methodInfo = workerType.GetMethod("Process", BindingFlags.Public | BindingFlags.Static);
+
+ var assemblyPath = AssemblyPath.Value();
+ var context = new GetDocumentCommandContext
+ {
+ AssemblyPath = assemblyPath,
+ AssemblyDirectory = Path.GetDirectoryName(assemblyPath),
+ AssemblyName = Path.GetFileNameWithoutExtension(assemblyPath),
+ DocumentName = _documentName.Value(),
+ Method = _method.Value(),
+ Output = _output.Value(),
+ Service = _service.Value(),
+ Uri = _uri.Value(),
+ };
+
+ return (int)methodInfo.Invoke(obj: null, parameters: new[] { context });
+ }
+ catch (Exception ex)
+ {
+ Console.Error.WriteLine(ex.ToString());
+ return 1;
+ }
+ }
+
+ private class AssemblyInfo
+ {
+ public AssemblyInfo(string path)
+ {
+ Path = path;
+ }
+
+ public string Path { get; }
+
+ public Assembly Assembly { get; set; }
+ }
+ }
+}
diff --git a/src/GetDocumentInsider/Commands/GetDocumentCommandContext.cs b/src/GetDocumentInsider/Commands/GetDocumentCommandContext.cs
new file mode 100644
index 0000000000..996e9e9701
--- /dev/null
+++ b/src/GetDocumentInsider/Commands/GetDocumentCommandContext.cs
@@ -0,0 +1,24 @@
+using System;
+
+namespace GetDocument.Commands
+{
+ [Serializable]
+ public class GetDocumentCommandContext
+ {
+ public string AssemblyDirectory { get; set; }
+
+ public string AssemblyName { get; set; }
+
+ public string AssemblyPath { get; set; }
+
+ public string DocumentName { get; set; }
+
+ public string Method { get; set; }
+
+ public string Output { get; set; }
+
+ public string Service { get; set; }
+
+ public string Uri { get; set; }
+ }
+}
diff --git a/src/GetDocumentInsider/Commands/GetDocumentCommandWorker.cs b/src/GetDocumentInsider/Commands/GetDocumentCommandWorker.cs
new file mode 100644
index 0000000000..c010165e61
--- /dev/null
+++ b/src/GetDocumentInsider/Commands/GetDocumentCommandWorker.cs
@@ -0,0 +1,260 @@
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Reflection;
+using System.Threading;
+using System.Threading.Tasks;
+using GenerationTasks;
+using GetDocument.Properties;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.TestHost;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace GetDocument.Commands
+{
+ internal class GetDocumentCommandWorker
+ {
+ public static int Process(GetDocumentCommandContext context)
+ {
+ var assemblyName = new AssemblyName(context.AssemblyName);
+ var assembly = Assembly.Load(assemblyName);
+ var entryPointType = assembly.EntryPoint?.DeclaringType;
+ if (entryPointType == null)
+ {
+ Reporter.WriteError(Resources.MissingEntryPoint(context.AssemblyPath));
+ return 2;
+ }
+
+ var services = GetServices(entryPointType, context.AssemblyPath, context.AssemblyName);
+ if (services == null)
+ {
+ return 3;
+ }
+
+ var success = TryProcess(context, services);
+ if (!success && string.IsNullOrEmpty(context.Uri))
+ {
+ return 4;
+ }
+
+ var builder = GetBuilder(entryPointType, context.AssemblyPath, context.AssemblyName);
+ if (builder == null)
+ {
+ return 5;
+ }
+
+ // Mute the HttpsRedirectionMiddleware warning about HTTPS configuration.
+ builder.ConfigureLogging(loggingBuilder => loggingBuilder.AddFilter(
+ "Microsoft.AspNetCore.HttpsPolicy.HttpsRedirectionMiddleware",
+ LogLevel.Error));
+
+ using (var server = new TestServer(builder))
+ {
+ ProcessAsync(context, server).Wait();
+ }
+
+ return 0;
+ }
+
+ public static bool TryProcess(GetDocumentCommandContext context, IServiceProvider services)
+ {
+ var documentName = string.IsNullOrEmpty(context.DocumentName) ?
+ GetDocumentCommand.FallbackDocumentName :
+ context.DocumentName;
+ var methodName = string.IsNullOrEmpty(context.Method) ?
+ GetDocumentCommand.FallbackMethod :
+ context.Method;
+ var serviceName = string.IsNullOrEmpty(context.Service) ?
+ GetDocumentCommand.FallbackService :
+ context.Service;
+
+ Reporter.WriteInformation(Resources.UsingDocument(documentName));
+ Reporter.WriteInformation(Resources.UsingMethod(methodName));
+ Reporter.WriteInformation(Resources.UsingService(serviceName));
+
+ try
+ {
+ var serviceType = Type.GetType(serviceName, throwOnError: true);
+ var method = serviceType.GetMethod(methodName, new[] { typeof(TextWriter), typeof(string) });
+ var service = services.GetRequiredService(serviceType);
+
+ var success = true;
+ using (var writer = File.CreateText(context.Output))
+ {
+ if (method.ReturnType == typeof(bool))
+ {
+ success = (bool)method.Invoke(service, new object[] { writer, documentName });
+ }
+ else
+ {
+ method.Invoke(service, new object[] { writer, documentName });
+ }
+ }
+
+ if (!success)
+ {
+ var message = Resources.MethodInvocationFailed(methodName, serviceName, documentName);
+ if (string.IsNullOrEmpty(context.Uri) && !File.Exists(context.Output))
+ {
+ Reporter.WriteError(message);
+ }
+ else
+ {
+ Reporter.WriteWarning(message);
+ }
+ }
+
+ return success;
+ }
+ catch (Exception ex)
+ {
+ var message = FormatException(ex);
+ if (string.IsNullOrEmpty(context.Uri) && !File.Exists(context.Output))
+ {
+ Reporter.WriteError(message);
+ }
+ else
+ {
+ Reporter.WriteWarning(message);
+ }
+
+ return false;
+ }
+ }
+
+ public static async Task ProcessAsync(GetDocumentCommandContext context, TestServer server)
+ {
+
+ Debug.Assert(!string.IsNullOrEmpty(context.Uri));
+ Reporter.WriteInformation(Resources.UsingUri(context.Uri));
+
+ var httpClient = server.CreateClient();
+ await DownloadFileCore.DownloadAsync(
+ context.Uri,
+ context.Output,
+ httpClient,
+ new LogWrapper(),
+ CancellationToken.None,
+ timeoutSeconds: 60);
+ }
+
+ // TODO: Use Microsoft.AspNetCore.Hosting.WebHostBuilderFactory.Sources once we have dev feed available.
+ private static IServiceProvider GetServices(Type entryPointType, string assemblyPath, string assemblyName)
+ {
+ var args = new[] { Array.Empty() };
+ var methodInfo = entryPointType.GetMethod("BuildWebHost");
+ if (methodInfo != null)
+ {
+ // BuildWebHost (old style has highest priority)
+ var parameters = methodInfo.GetParameters();
+ if (!methodInfo.IsStatic ||
+ parameters.Length != 1 ||
+ typeof(string[]) != parameters[0].ParameterType ||
+ typeof(IWebHost) != methodInfo.ReturnType)
+ {
+ Reporter.WriteError(
+ "BuildWebHost method found in {assemblyPath} does not have expected signature.");
+
+ return null;
+ }
+
+ try
+ {
+ var webHost = (IWebHost)methodInfo.Invoke(obj: null, parameters: args);
+
+ return webHost.Services;
+ }
+ catch (Exception ex)
+ {
+ Reporter.WriteError($"BuildWebHost method threw: {FormatException(ex)}");
+
+ return null;
+ }
+ }
+
+ if ((methodInfo = entryPointType.GetMethod("CreateWebHostBuilder")) != null)
+ {
+ // CreateWebHostBuilder
+ var parameters = methodInfo.GetParameters();
+ if (!methodInfo.IsStatic ||
+ parameters.Length != 1 ||
+ typeof(string[]) != parameters[0].ParameterType ||
+ typeof(IWebHostBuilder) != methodInfo.ReturnType)
+ {
+ Reporter.WriteError(
+ "CreateWebHostBuilder method found in {assemblyPath} does not have expected signature.");
+
+ return null;
+ }
+
+ try
+ {
+ var builder = (IWebHostBuilder)methodInfo.Invoke(obj: null, parameters: args);
+
+ return builder.Build().Services;
+ }
+ catch (Exception ex)
+ {
+ Reporter.WriteError($"CreateWebHostBuilder method threw: {FormatException(ex)}");
+
+ return null;
+ }
+ }
+
+ // Startup
+ return new WebHostBuilder().UseStartup(assemblyName).Build().Services;
+ }
+
+ // TODO: Use Microsoft.AspNetCore.Hosting.WebHostBuilderFactory.Sources once we have dev feed available.
+ private static IWebHostBuilder GetBuilder(Type entryPointType, string assemblyPath, string assemblyName)
+ {
+ var methodInfo = entryPointType.GetMethod("BuildWebHost");
+ if (methodInfo != null)
+ {
+ // BuildWebHost cannot be used. Fall through, most likely to Startup fallback.
+ Reporter.WriteWarning(
+ "BuildWebHost method cannot be used. Falling back to minimal Startup configuration.");
+ }
+
+ methodInfo = entryPointType.GetMethod("CreateWebHostBuilder");
+ if (methodInfo != null)
+ {
+ // CreateWebHostBuilder
+ var parameters = methodInfo.GetParameters();
+ if (!methodInfo.IsStatic ||
+ parameters.Length != 1 ||
+ typeof(string[]) != parameters[0].ParameterType ||
+ typeof(IWebHostBuilder) != methodInfo.ReturnType)
+ {
+ Reporter.WriteError(
+ "CreateWebHostBuilder method found in {assemblyPath} does not have expected signature.");
+
+ return null;
+ }
+
+ try
+ {
+ var args = new[] { Array.Empty() };
+ var builder = (IWebHostBuilder)methodInfo.Invoke(obj: null, parameters: args);
+
+ return builder;
+ }
+ catch (Exception ex)
+ {
+ Reporter.WriteError($"CreateWebHostBuilder method threw: {FormatException(ex)}");
+
+ return null;
+ }
+ }
+
+ // Startup
+ return new WebHostBuilder().UseStartup(assemblyName);
+ }
+
+ private static string FormatException(Exception exception)
+ {
+ return $"{exception.GetType().FullName}: {exception.Message}";
+ }
+ }
+}
diff --git a/src/GetDocumentInsider/Commands/HelpCommandBase.cs b/src/GetDocumentInsider/Commands/HelpCommandBase.cs
new file mode 100644
index 0000000000..7e2c89cb5a
--- /dev/null
+++ b/src/GetDocumentInsider/Commands/HelpCommandBase.cs
@@ -0,0 +1,17 @@
+// Copyright (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.DotNet.Cli.CommandLine;
+
+namespace GetDocument.Commands
+{
+ internal class HelpCommandBase : CommandBase
+ {
+ public override void Configure(CommandLineApplication command)
+ {
+ base.Configure(command);
+
+ command.HelpOption("-h|--help");
+ }
+ }
+}
diff --git a/src/GetDocumentInsider/Commands/ProjectCommandBase.cs b/src/GetDocumentInsider/Commands/ProjectCommandBase.cs
new file mode 100644
index 0000000000..a1a762f96c
--- /dev/null
+++ b/src/GetDocumentInsider/Commands/ProjectCommandBase.cs
@@ -0,0 +1,38 @@
+// Copyright (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 GetDocument.Properties;
+using Microsoft.DotNet.Cli.CommandLine;
+
+namespace GetDocument.Commands
+{
+ internal abstract class ProjectCommandBase : HelpCommandBase
+ {
+ public CommandOption AssemblyPath { get; private set; }
+
+ public CommandOption ToolsDirectory { get; private set; }
+
+ public override void Configure(CommandLineApplication command)
+ {
+ base.Configure(command);
+
+ AssemblyPath = command.Option("-a|--assembly ", Resources.AssemblyDescription);
+ ToolsDirectory = command.Option("--tools-directory ", Resources.ToolsDirectoryDescription);
+ }
+
+ protected override void Validate()
+ {
+ base.Validate();
+
+ if (!AssemblyPath.HasValue())
+ {
+ throw new CommandException(Resources.MissingOption(AssemblyPath.LongName));
+ }
+
+ if (!ToolsDirectory.HasValue())
+ {
+ throw new CommandException(Resources.MissingOption(ToolsDirectory.LongName));
+ }
+ }
+ }
+}
diff --git a/src/GetDocumentInsider/GetDocumentInsider.csproj b/src/GetDocumentInsider/GetDocumentInsider.csproj
new file mode 100644
index 0000000000..8fe28cd313
--- /dev/null
+++ b/src/GetDocumentInsider/GetDocumentInsider.csproj
@@ -0,0 +1,39 @@
+
+
+ GetDocument.Insider
+ GetDocument Command-line Tool inside man
+ false
+ Exe
+ GetDocument
+ netcoreapp2.0;net461
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ TextTemplatingFileGenerator
+ Resources.Designer.cs
+
+
+
+
+
+
+
+
+
+ True
+ True
+ Resources.Designer.tt
+
+
+
diff --git a/src/GetDocumentInsider/Json.cs b/src/GetDocumentInsider/Json.cs
new file mode 100644
index 0000000000..acb62e449f
--- /dev/null
+++ b/src/GetDocumentInsider/Json.cs
@@ -0,0 +1,19 @@
+// Copyright (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 GetDocument.Properties;
+using Microsoft.DotNet.Cli.CommandLine;
+
+namespace GetDocument
+{
+ internal static class Json
+ {
+ public static CommandOption ConfigureOption(CommandLineApplication command)
+ => command.Option("--json", Resources.JsonDescription);
+
+ public static string Literal(string text)
+ => text != null
+ ? "\"" + text.Replace("\\", "\\\\").Replace("\"", "\\\"") + "\""
+ : "null";
+ }
+}
diff --git a/src/GetDocumentInsider/LogWrapper.cs b/src/GetDocumentInsider/LogWrapper.cs
new file mode 100644
index 0000000000..0ab10cb744
--- /dev/null
+++ b/src/GetDocumentInsider/LogWrapper.cs
@@ -0,0 +1,29 @@
+using System;
+using GenerationTasks;
+
+namespace GetDocument
+{
+ public class LogWrapper : ILogWrapper
+ {
+ public void LogError(string message, params object[] messageArgs)
+ {
+ Reporter.WriteError(string.Format(message, messageArgs));
+ }
+
+ public void LogError(Exception exception, bool showStackTrace)
+ {
+ var message = showStackTrace ? exception.ToString() : exception.Message;
+ Reporter.WriteError(message);
+ }
+
+ public void LogInformational(string message, params object[] messageArgs)
+ {
+ Reporter.WriteInformation(string.Format(message, messageArgs));
+ }
+
+ public void LogWarning(string message, params object[] messageArgs)
+ {
+ Reporter.WriteWarning(string.Format(message, messageArgs));
+ }
+ }
+}
diff --git a/src/GetDocumentInsider/ProductInfo.cs b/src/GetDocumentInsider/ProductInfo.cs
new file mode 100644
index 0000000000..22045ee0df
--- /dev/null
+++ b/src/GetDocumentInsider/ProductInfo.cs
@@ -0,0 +1,24 @@
+// Copyright (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;
+
+namespace GetDocument
+{
+ ///
+ /// This API supports the GetDocument infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public static class ProductInfo
+ {
+ ///
+ /// This API supports the GetDocument infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public static string GetVersion()
+ => typeof(ProductInfo)
+ .Assembly
+ .GetCustomAttribute()
+ .InformationalVersion;
+ }
+}
diff --git a/src/GetDocumentInsider/Program.cs b/src/GetDocumentInsider/Program.cs
new file mode 100644
index 0000000000..935fca4de0
--- /dev/null
+++ b/src/GetDocumentInsider/Program.cs
@@ -0,0 +1,51 @@
+// Copyright (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.Text;
+using GetDocument.Commands;
+using Microsoft.DotNet.Cli.CommandLine;
+
+namespace GetDocument
+{
+ internal static class Program
+ {
+ private static int Main(string[] args)
+ {
+ if (Console.IsOutputRedirected)
+ {
+ Console.OutputEncoding = Encoding.UTF8;
+ }
+
+ var app = new CommandLineApplication(throwOnUnexpectedArg: false)
+ {
+ Name = "GetDocument.Insider"
+ };
+
+ new GetDocumentCommand().Configure(app);
+
+ try
+ {
+ return app.Execute(args);
+ }
+ catch (Exception ex)
+ {
+ if (ex is CommandException
+ || ex is CommandParsingException
+ || (ex is WrappedException wrappedException
+ && wrappedException.Type == "GetDocument.Design.OperationException"))
+ {
+ Reporter.WriteVerbose(ex.ToString());
+ }
+ else
+ {
+ Reporter.WriteInformation(ex.ToString());
+ }
+
+ Reporter.WriteError(ex.Message);
+
+ return 1;
+ }
+ }
+ }
+}
diff --git a/src/GetDocumentInsider/Properties/Resources.Designer.cs b/src/GetDocumentInsider/Properties/Resources.Designer.cs
new file mode 100644
index 0000000000..90463782ab
--- /dev/null
+++ b/src/GetDocumentInsider/Properties/Resources.Designer.cs
@@ -0,0 +1,207 @@
+//
+
+using System;
+using System.Reflection;
+using System.Resources;
+using JetBrains.Annotations;
+
+namespace GetDocument.Properties
+{
+ ///
+ /// This API supports the GetDocument infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ internal static class Resources
+ {
+ private static readonly ResourceManager _resourceManager
+ = new ResourceManager("GetDocument.Properties.Resources", typeof(Resources).GetTypeInfo().Assembly);
+
+ ///
+ /// The assembly to use.
+ ///
+ public static string AssemblyDescription
+ => GetString("AssemblyDescription");
+
+ ///
+ /// Show JSON output.
+ ///
+ public static string JsonDescription
+ => GetString("JsonDescription");
+
+ ///
+ /// Missing required option '--{option}'.
+ ///
+ public static string MissingOption([CanBeNull] object option)
+ => string.Format(
+ GetString("MissingOption", nameof(option)),
+ option);
+
+ ///
+ /// Do not colorize output.
+ ///
+ public static string NoColorDescription
+ => GetString("NoColorDescription");
+
+ ///
+ /// The file to write the result to.
+ ///
+ public static string OutputDescription
+ => GetString("OutputDescription");
+
+ ///
+ /// Prefix console output with logging level.
+ ///
+ public static string PrefixDescription
+ => GetString("PrefixDescription");
+
+ ///
+ /// Using application base '{appBase}'.
+ ///
+ public static string UsingApplicationBase([CanBeNull] object appBase)
+ => string.Format(
+ GetString("UsingApplicationBase", nameof(appBase)),
+ appBase);
+
+ ///
+ /// Using assembly '{assembly}'.
+ ///
+ public static string UsingAssembly([CanBeNull] object assembly)
+ => string.Format(
+ GetString("UsingAssembly", nameof(assembly)),
+ assembly);
+
+ ///
+ /// Using configuration file '{config}'.
+ ///
+ public static string UsingConfigurationFile([CanBeNull] object config)
+ => string.Format(
+ GetString("UsingConfigurationFile", nameof(config)),
+ config);
+
+ ///
+ /// Show verbose output.
+ ///
+ public static string VerboseDescription
+ => GetString("VerboseDescription");
+
+ ///
+ /// Writing '{file}'...
+ ///
+ public static string WritingFile([CanBeNull] object file)
+ => string.Format(
+ GetString("WritingFile", nameof(file)),
+ file);
+
+ ///
+ /// Using working directory '{workingDirectory}'.
+ ///
+ public static string UsingWorkingDirectory([CanBeNull] object workingDirectory)
+ => string.Format(
+ GetString("UsingWorkingDirectory", nameof(workingDirectory)),
+ workingDirectory);
+
+ ///
+ /// Location from which inside man was copied (in the .NET Framework case) or loaded.
+ ///
+ public static string ToolsDirectoryDescription
+ => GetString("ToolsDirectoryDescription");
+
+ ///
+ /// The URI to download the document from.
+ ///
+ public static string UriDescription
+ => GetString("UriDescription");
+
+ ///
+ /// The name of the method to invoke on the '--service' instance. Default value '{defaultMethod}'.
+ ///
+ public static string MethodDescription([CanBeNull] object defaultMethod)
+ => string.Format(
+ GetString("MethodDescription", nameof(defaultMethod)),
+ defaultMethod);
+
+ ///
+ /// The qualified name of the service type to retrieve from dependency injection. Default value '{defaultService}'.
+ ///
+ public static string ServiceDescription([CanBeNull] object defaultService)
+ => string.Format(
+ GetString("ServiceDescription", nameof(defaultService)),
+ defaultService);
+
+ ///
+ /// Missing required option '--{option1}' or '--{option2}'.
+ ///
+ public static string MissingOptions([CanBeNull] object option1, [CanBeNull] object option2)
+ => string.Format(
+ GetString("MissingOptions", nameof(option1), nameof(option2)),
+ option1, option2);
+
+ ///
+ /// The name of the document to pass to the '--method' method. Default value '{defaultDocumentName}'.
+ ///
+ public static string DocumentDescription([CanBeNull] object defaultDocumentName)
+ => string.Format(
+ GetString("DocumentDescription", nameof(defaultDocumentName)),
+ defaultDocumentName);
+
+ ///
+ /// Using document name '{documentName}'.
+ ///
+ public static string UsingDocument([CanBeNull] object documentName)
+ => string.Format(
+ GetString("UsingDocument", nameof(documentName)),
+ documentName);
+
+ ///
+ /// Using method '{method}'.
+ ///
+ public static string UsingMethod([CanBeNull] object method)
+ => string.Format(
+ GetString("UsingMethod", nameof(method)),
+ method);
+
+ ///
+ /// Using service '{service}'.
+ ///
+ public static string UsingService([CanBeNull] object service)
+ => string.Format(
+ GetString("UsingService", nameof(service)),
+ service);
+
+ ///
+ /// Using URI '{uri}'.
+ ///
+ public static string UsingUri([CanBeNull] object uri)
+ => string.Format(
+ GetString("UsingUri", nameof(uri)),
+ uri);
+
+ ///
+ /// Method '{method}' of service '{service}' failed to generate document '{documentName}'.
+ ///
+ public static string MethodInvocationFailed([CanBeNull] object method, [CanBeNull] object service, [CanBeNull] object documentName)
+ => string.Format(
+ GetString("MethodInvocationFailed", nameof(method), nameof(service), nameof(documentName)),
+ method, service, documentName);
+
+ ///
+ /// Assembly '{assemblyPath}' does not contain an entry point.
+ ///
+ public static string MissingEntryPoint([CanBeNull] object assemblyPath)
+ => string.Format(
+ GetString("MissingEntryPoint", nameof(assemblyPath)),
+ assemblyPath);
+
+ private static string GetString(string name, params string[] formatterNames)
+ {
+ var value = _resourceManager.GetString(name);
+ for (var i = 0; i < formatterNames.Length; i++)
+ {
+ value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}");
+ }
+
+ return value;
+ }
+ }
+}
+
diff --git a/src/GetDocumentInsider/Properties/Resources.Designer.tt b/src/GetDocumentInsider/Properties/Resources.Designer.tt
new file mode 100644
index 0000000000..3f636a4db5
--- /dev/null
+++ b/src/GetDocumentInsider/Properties/Resources.Designer.tt
@@ -0,0 +1,6 @@
+<#
+ Session["ResourceFile"] = "Resources.resx";
+ Session["AccessModifier"] = "internal";
+ Session["NoDiagnostics"] = true;
+#>
+<#@ include file="..\..\tools\Resources.tt" #>
diff --git a/src/GetDocumentInsider/Properties/Resources.resx b/src/GetDocumentInsider/Properties/Resources.resx
new file mode 100644
index 0000000000..f5c08e3a40
--- /dev/null
+++ b/src/GetDocumentInsider/Properties/Resources.resx
@@ -0,0 +1,192 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 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 assembly to use.
+
+
+ Show JSON output.
+
+
+ Missing required option '--{option}'.
+
+
+ Do not colorize output.
+
+
+ The file to write the result to.
+
+
+ Prefix console output with logging level.
+
+
+ Using application base '{appBase}'.
+
+
+ Using assembly '{assembly}'.
+
+
+ Using configuration file '{config}'.
+
+
+ Show verbose output.
+
+
+ Writing '{file}'...
+
+
+ Using working directory '{workingDirectory}'.
+
+
+ Location from which inside man was copied (in the .NET Framework case) or loaded.
+
+
+ The URI to download the document from.
+
+
+ The name of the method to invoke on the '--service' instance. Default value '{defaultMethod}'.
+
+
+ The qualified name of the service type to retrieve from dependency injection. Default value '{defaultService}'.
+
+
+ Missing required option '--{option1}' or '--{option2}'.
+
+
+ The name of the document to pass to the '--method' method. Default value '{defaultDocumentName}'.
+
+
+ Using document name '{documentName}'.
+
+
+ Using method '{method}'.
+
+
+ Using service '{service}'.
+
+
+ Using URI '{uri}'.
+
+
+ Method '{method}' of service '{service}' failed to generate document '{documentName}'.
+
+
+ Assembly '{assemblyPath}' does not contain an entry point.
+
+
\ No newline at end of file
diff --git a/src/GetDocumentInsider/Reporter.cs b/src/GetDocumentInsider/Reporter.cs
new file mode 100644
index 0000000000..b7c0c264a5
--- /dev/null
+++ b/src/GetDocumentInsider/Reporter.cs
@@ -0,0 +1,58 @@
+// Copyright (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 static GetDocument.AnsiConstants;
+
+namespace GetDocument
+{
+ internal static class Reporter
+ {
+ public static bool IsVerbose { get; set; }
+ public static bool NoColor { get; set; }
+ public static bool PrefixOutput { get; set; }
+
+ public static string Colorize(string value, Func colorizeFunc)
+ => NoColor ? value : colorizeFunc(value);
+
+ public static void WriteError(string message)
+ => WriteLine(Prefix("error: ", Colorize(message, x => Bold + Red + x + Reset)));
+
+ public static void WriteWarning(string message)
+ => WriteLine(Prefix("warn: ", Colorize(message, x => Bold + Yellow + x + Reset)));
+
+ public static void WriteInformation(string message)
+ => WriteLine(Prefix("info: ", message));
+
+ public static void WriteData(string message)
+ => WriteLine(Prefix("data: ", Colorize(message, x => Bold + Gray + x + Reset)));
+
+ public static void WriteVerbose(string message)
+ {
+ if (IsVerbose)
+ {
+ WriteLine(Prefix("verbose: ", Colorize(message, x => Bold + Black + x + Reset)));
+ }
+ }
+
+ private static string Prefix(string prefix, string value)
+ => PrefixOutput
+ ? string.Join(
+ Environment.NewLine,
+ value.Split(new[] { Environment.NewLine }, StringSplitOptions.None).Select(l => prefix + l))
+ : value;
+
+ private static void WriteLine(string value)
+ {
+ if (NoColor)
+ {
+ Console.WriteLine(value);
+ }
+ else
+ {
+ AnsiConsole.WriteLine(value);
+ }
+ }
+ }
+}
diff --git a/src/GetDocumentInsider/WrappedException.cs b/src/GetDocumentInsider/WrappedException.cs
new file mode 100644
index 0000000000..7cd7bfc0d3
--- /dev/null
+++ b/src/GetDocumentInsider/WrappedException.cs
@@ -0,0 +1,24 @@
+// Copyright (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 GetDocument
+{
+ internal class WrappedException : Exception
+ {
+ private readonly string _stackTrace;
+
+ public WrappedException(string type, string message, string stackTrace)
+ : base(message)
+ {
+ Type = type;
+ _stackTrace = stackTrace;
+ }
+
+ public string Type { get; }
+
+ public override string ToString()
+ => _stackTrace;
+ }
+}
diff --git a/src/Microsoft.Extensions.ApiDescription.Client/DownloadFile.cs b/src/Microsoft.Extensions.ApiDescription.Client/DownloadFile.cs
new file mode 100644
index 0000000000..a1341e0581
--- /dev/null
+++ b/src/Microsoft.Extensions.ApiDescription.Client/DownloadFile.cs
@@ -0,0 +1,113 @@
+// Copyright (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.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+using Task = System.Threading.Tasks.Task;
+using Utilities = Microsoft.Build.Utilities;
+
+namespace GenerationTasks
+{
+ ///
+ /// Downloads a file.
+ ///
+ public class DownloadFile : Utilities.Task, ICancelableTask
+ {
+ private readonly CancellationTokenSource _cts = new CancellationTokenSource();
+
+ ///
+ /// The URI to download.
+ ///
+ [Required]
+ public string Uri { get; set; }
+
+ ///
+ /// Destination for the downloaded file. If the file already exists, it is not re-downloaded unless
+ /// is true.
+ ///
+ [Required]
+ public string DestinationPath { get; set; }
+
+ ///
+ /// Should be overwritten. When true, the file is downloaded and its hash
+ /// compared to the existing file. If those hashes do not match (or does not
+ /// exist), is overwritten.
+ ///
+ public bool Overwrite { get; set; }
+
+ ///
+ /// The maximum amount of time in seconds to allow for downloading the file. Defaults to 2 minutes.
+ ///
+ public int TimeoutSeconds { get; set; } = 60 * 2;
+
+ ///
+ public void Cancel() => _cts.Cancel();
+
+ ///
+ public override bool Execute() => ExecuteAsync().Result;
+
+ public async Task ExecuteAsync()
+ {
+ if (string.IsNullOrEmpty(Uri))
+ {
+ Log.LogError("Uri parameter must not be null or empty.");
+ return false;
+ }
+
+ if (string.IsNullOrEmpty(Uri))
+ {
+ Log.LogError("DestinationPath parameter must not be null or empty.");
+ return false;
+ }
+
+ var builder = new UriBuilder(Uri);
+ if (!string.Equals(System.Uri.UriSchemeHttp, builder.Scheme, StringComparison.OrdinalIgnoreCase) &&
+ !string.Equals(System.Uri.UriSchemeHttps, builder.Scheme, StringComparison.OrdinalIgnoreCase))
+ {
+ Log.LogError($"{nameof(Uri)} parameter does not have scheme {System.Uri.UriSchemeHttp} or " +
+ $"{System.Uri.UriSchemeHttps}.");
+ return false;
+ }
+
+ await DownloadFileAsync(Uri, DestinationPath, Overwrite, _cts.Token, TimeoutSeconds, Log);
+
+ return !Log.HasLoggedErrors;
+ }
+
+ private static async Task DownloadFileAsync(
+ string uri,
+ string destinationPath,
+ bool overwrite,
+ CancellationToken cancellationToken,
+ int timeoutSeconds,
+ TaskLoggingHelper log)
+ {
+ var destinationExists = File.Exists(destinationPath);
+ if (destinationExists && !overwrite)
+ {
+ log.LogMessage($"Not downloading '{uri}' to overwrite existing file '{destinationPath}'.");
+ return;
+ }
+
+ log.LogMessage($"Downloading '{uri}' to '{destinationPath}'.");
+
+ using (var httpClient = new HttpClient
+ {
+ })
+ {
+ await DownloadFileCore.DownloadAsync(
+ uri,
+ destinationPath,
+ httpClient,
+ new LogWrapper(log),
+ cancellationToken,
+ timeoutSeconds);
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.Extensions.ApiDescription.Client/DownloadFileCore.cs b/src/Microsoft.Extensions.ApiDescription.Client/DownloadFileCore.cs
new file mode 100644
index 0000000000..bf8156e32f
--- /dev/null
+++ b/src/Microsoft.Extensions.ApiDescription.Client/DownloadFileCore.cs
@@ -0,0 +1,118 @@
+using System;
+using System.IO;
+using System.Net.Http;
+using System.Net.Sockets;
+using System.Security.Cryptography;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace GenerationTasks
+{
+ internal static class DownloadFileCore
+ {
+ public static async Task DownloadAsync(
+ string uri,
+ string destinationPath,
+ HttpClient httpClient,
+ ILogWrapper log,
+ CancellationToken cancellationToken,
+ int timeoutSeconds)
+ {
+ // Timeout if the response has not begun within 1 minute
+ httpClient.Timeout = TimeSpan.FromMinutes(1);
+
+ var destinationExists = File.Exists(destinationPath);
+ var reachedCopy = false;
+ try
+ {
+ using (var response = await httpClient.GetAsync(uri, cancellationToken))
+ {
+ response.EnsureSuccessStatusCode();
+ cancellationToken.ThrowIfCancellationRequested();
+
+ using (var responseStreamTask = response.Content.ReadAsStreamAsync())
+ {
+ var finished = await Task.WhenAny(
+ responseStreamTask,
+ Task.Delay(TimeSpan.FromSeconds(timeoutSeconds)));
+
+ if (!ReferenceEquals(responseStreamTask, finished))
+ {
+ throw new TimeoutException($"Download failed to complete in {timeoutSeconds} seconds.");
+ }
+
+ var responseStream = await responseStreamTask;
+ if (destinationExists)
+ {
+ // Check hashes before using the downloaded information.
+ var downloadHash = GetHash(responseStream);
+ responseStream.Position = 0L;
+
+ byte[] destinationHash;
+ using (var destinationStream = File.OpenRead(destinationPath))
+ {
+ destinationHash = GetHash(destinationStream);
+ }
+
+ var sameHashes = downloadHash.LongLength == destinationHash.LongLength;
+ for (var i = 0L; sameHashes && i < downloadHash.LongLength; i++)
+ {
+ sameHashes = downloadHash[i] == destinationHash[i];
+ }
+
+ if (sameHashes)
+ {
+ log.LogInformational($"Not overwriting existing and matching file '{destinationPath}'.");
+ return;
+ }
+ }
+ else
+ {
+ // May need to create directory to hold the file.
+ var destinationDirectory = Path.GetDirectoryName(destinationPath);
+ if (!(string.IsNullOrEmpty(destinationDirectory) || Directory.Exists(destinationDirectory)))
+ {
+ Directory.CreateDirectory(destinationDirectory);
+ }
+ }
+
+ // Create or overwrite the destination file.
+ reachedCopy = true;
+ using (var outStream = File.Create(destinationPath))
+ {
+ responseStream.CopyTo(outStream);
+ }
+ }
+ }
+ }
+ catch (HttpRequestException ex) when (destinationExists)
+ {
+ if (ex.InnerException is SocketException socketException)
+ {
+ log.LogWarning($"Unable to download {uri}, socket error code '{socketException.SocketErrorCode}'.");
+ }
+ else
+ {
+ log.LogWarning($"Unable to download {uri}: {ex.Message}");
+ }
+ }
+ catch (Exception ex)
+ {
+ log.LogError($"Downloading '{uri}' failed.");
+ log.LogError(ex, showStackTrace: true);
+ if (reachedCopy)
+ {
+ File.Delete(destinationPath);
+ }
+ }
+ }
+
+ private static byte[] GetHash(Stream stream)
+ {
+ using (var algorithm = SHA256.Create())
+ {
+ return algorithm.ComputeHash(stream);
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.Extensions.ApiDescription.Client/GetFileReferenceMetadata.cs b/src/Microsoft.Extensions.ApiDescription.Client/GetFileReferenceMetadata.cs
new file mode 100644
index 0000000000..6ae50f8b21
--- /dev/null
+++ b/src/Microsoft.Extensions.ApiDescription.Client/GetFileReferenceMetadata.cs
@@ -0,0 +1,95 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+
+namespace GenerationTasks
+{
+ ///
+ /// Adds or corrects Namespace and OutputPath metadata in ServiceFileReference items.
+ ///
+ public class GetFileReferenceMetadata : Task
+ {
+ ///
+ /// Default Namespace metadata value for C# output.
+ ///
+ [Required]
+ public string CSharpNamespace { get; set; }
+
+ ///
+ /// Default directory for OutputPath values.
+ ///
+ [Required]
+ public string OutputDirectory { get; set; }
+
+ ///
+ /// Default Namespace metadata value for TypeScript output.
+ ///
+ [Required]
+ public string TypeScriptNamespace { get; set; }
+
+ ///
+ /// The ServiceFileReference items to update.
+ ///
+ [Required]
+ public ITaskItem[] Inputs { get; set; }
+
+ ///
+ /// The updated ServiceFileReference items. Will include Namespace and OutputPath metadata. OutputPath metadata
+ /// will contain full paths.
+ ///
+ [Output]
+ public ITaskItem[] Outputs{ get; set; }
+
+ ///
+ public override bool Execute()
+ {
+ var outputs = new List(Inputs.Length);
+ foreach (var item in Inputs)
+ {
+ var newItem = new TaskItem(item);
+ outputs.Add(newItem);
+
+ var codeGenerator = item.GetMetadata("CodeGenerator");
+ var isTypeScript = codeGenerator.EndsWith("TypeScript", StringComparison.OrdinalIgnoreCase);
+
+ var @namespace = item.GetMetadata("Namespace");
+ if (string.IsNullOrEmpty(@namespace))
+ {
+ @namespace = isTypeScript ? CSharpNamespace : TypeScriptNamespace;
+ newItem.SetMetadata("Namespace", @namespace);
+ }
+
+ var outputPath = item.GetMetadata("OutputPath");
+ if (string.IsNullOrEmpty(outputPath))
+ {
+ var className = item.GetMetadata("ClassName");
+ outputPath = className + (isTypeScript ? ".ts" : ".cs");
+ }
+
+ outputPath = GetFullPath(outputPath);
+ newItem.SetMetadata("OutputPath", outputPath);
+ }
+
+ Outputs = outputs.ToArray();
+
+ return true;
+ }
+
+ private string GetFullPath(string path)
+ {
+ if (!Path.IsPathRooted(path))
+ {
+ if (!string.IsNullOrEmpty(OutputDirectory))
+ {
+ path = Path.Combine(OutputDirectory, path);
+ }
+
+ path = Path.GetFullPath(path);
+ }
+
+ return path;
+ }
+ }
+}
diff --git a/src/Microsoft.Extensions.ApiDescription.Client/GetProjectReferenceMetadata.cs b/src/Microsoft.Extensions.ApiDescription.Client/GetProjectReferenceMetadata.cs
new file mode 100644
index 0000000000..e8ac95a819
--- /dev/null
+++ b/src/Microsoft.Extensions.ApiDescription.Client/GetProjectReferenceMetadata.cs
@@ -0,0 +1,76 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+
+namespace GenerationTasks
+{
+ ///
+ /// Adds or corrects DocumentPath and project-related metadata in ServiceProjectReference items.
+ ///
+ public class GetProjectReferenceMetadata : Task
+ {
+ ///
+ /// Default directory for DocumentPath values.
+ ///
+ [Required]
+ public string DocumentDirectory { get; set; }
+
+ ///
+ /// The ServiceFileReference items to update.
+ ///
+ [Required]
+ public ITaskItem[] Inputs { get; set; }
+
+ ///
+ /// The updated ServiceFileReference items. Will include Namespace and OutputPath metadata. OutputPath metadata
+ /// will contain full paths.
+ ///
+ [Output]
+ public ITaskItem[] Outputs{ get; set; }
+
+ ///
+ public override bool Execute()
+ {
+ var outputs = new List(Inputs.Length);
+ foreach (var item in Inputs)
+ {
+ var newItem = new TaskItem(item);
+ outputs.Add(newItem);
+
+ var codeGenerator = item.GetMetadata("CodeGenerator");
+ var isTypeScript = codeGenerator.EndsWith("TypeScript", StringComparison.OrdinalIgnoreCase);
+
+ var outputPath = item.GetMetadata("OutputPath");
+ if (string.IsNullOrEmpty(outputPath))
+ {
+ var className = item.GetMetadata("ClassName");
+ outputPath = className + (isTypeScript ? ".ts" : ".cs");
+ }
+
+ outputPath = GetFullPath(outputPath);
+ newItem.SetMetadata("OutputPath", outputPath);
+ }
+
+ Outputs = outputs.ToArray();
+
+ return true;
+ }
+
+ private string GetFullPath(string path)
+ {
+ if (!Path.IsPathRooted(path))
+ {
+ if (!string.IsNullOrEmpty(DocumentDirectory))
+ {
+ path = Path.Combine(DocumentDirectory, path);
+ }
+
+ path = Path.GetFullPath(path);
+ }
+
+ return path;
+ }
+ }
+}
diff --git a/src/Microsoft.Extensions.ApiDescription.Client/GetUriReferenceMetadata.cs b/src/Microsoft.Extensions.ApiDescription.Client/GetUriReferenceMetadata.cs
new file mode 100644
index 0000000000..7d922e19dc
--- /dev/null
+++ b/src/Microsoft.Extensions.ApiDescription.Client/GetUriReferenceMetadata.cs
@@ -0,0 +1,118 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+
+namespace GenerationTasks
+{
+ ///
+ /// Adds or corrects DocumentPath metadata in ServiceUriReference items.
+ ///
+ public class GetUriReferenceMetadata : Task
+ {
+ ///
+ /// Default directory for DocumentPath metadata values.
+ ///
+ [Required]
+ public string DocumentDirectory { get; set; }
+
+ ///
+ /// The ServiceUriReference items to update.
+ ///
+ [Required]
+ public ITaskItem[] Inputs { get; set; }
+
+ ///
+ /// The updated ServiceUriReference items. Will include DocumentPath metadata with full paths.
+ ///
+ [Output]
+ public ITaskItem[] Outputs{ get; set; }
+
+ ///
+ public override bool Execute()
+ {
+ var outputs = new List(Inputs.Length);
+ foreach (var item in Inputs)
+ {
+ var newItem = new TaskItem(item);
+ outputs.Add(newItem);
+
+ var documentPath = item.GetMetadata("DocumentPath");
+ if (string.IsNullOrEmpty(documentPath))
+ {
+ var uri = item.ItemSpec;
+ var builder = new UriBuilder(uri);
+ if (!builder.Uri.IsAbsoluteUri)
+ {
+ Log.LogError($"{nameof(Inputs)} item '{uri}' is not an absolute URI.");
+ return false;
+ }
+
+ if (!string.Equals(Uri.UriSchemeHttp, builder.Scheme, StringComparison.OrdinalIgnoreCase) &&
+ !string.Equals(Uri.UriSchemeHttps, builder.Scheme, StringComparison.OrdinalIgnoreCase))
+ {
+ Log.LogError($"{nameof(Inputs)} item '{uri}' does not have scheme {Uri.UriSchemeHttp} or " +
+ $"{Uri.UriSchemeHttps}.");
+ return false;
+ }
+
+ var host = builder.Host
+ .Replace("/", string.Empty)
+ .Replace("[", string.Empty)
+ .Replace("]", string.Empty)
+ .Replace(':', '_');
+ var path = builder.Path
+ .Replace("!", string.Empty)
+ .Replace("'", string.Empty)
+ .Replace("$", string.Empty)
+ .Replace("%", string.Empty)
+ .Replace("&", string.Empty)
+ .Replace("(", string.Empty)
+ .Replace(")", string.Empty)
+ .Replace("*", string.Empty)
+ .Replace("@", string.Empty)
+ .Replace("~", string.Empty)
+ .Replace('/', '_')
+ .Replace(':', '_')
+ .Replace(';', '_')
+ .Replace('+', '_')
+ .Replace('=', '_');
+
+ documentPath = host + path;
+ if (char.IsLower(documentPath[0]))
+ {
+ documentPath = char.ToUpper(documentPath[0]) + documentPath.Substring(startIndex: 1);
+ }
+
+ if (!documentPath.EndsWith(".json", StringComparison.OrdinalIgnoreCase))
+ {
+ documentPath = $"{documentPath}.json";
+ }
+ }
+
+ documentPath = GetFullPath(documentPath);
+ newItem.SetMetadata("DocumentPath", documentPath);
+ }
+
+ Outputs = outputs.ToArray();
+
+ return true;
+ }
+
+ private string GetFullPath(string path)
+ {
+ if (!Path.IsPathRooted(path))
+ {
+ if (!string.IsNullOrEmpty(DocumentDirectory))
+ {
+ path = Path.Combine(DocumentDirectory, path);
+ }
+
+ path = Path.GetFullPath(path);
+ }
+
+ return path;
+ }
+ }
+}
diff --git a/src/Microsoft.Extensions.ApiDescription.Client/ILogWrapper.cs b/src/Microsoft.Extensions.ApiDescription.Client/ILogWrapper.cs
new file mode 100644
index 0000000000..7c773ab359
--- /dev/null
+++ b/src/Microsoft.Extensions.ApiDescription.Client/ILogWrapper.cs
@@ -0,0 +1,50 @@
+using System;
+
+namespace GenerationTasks
+{
+ internal interface ILogWrapper
+ {
+ ///
+ /// Logs specified informational . Implementations should be thread safe.
+ ///
+ /// The message to log.
+ /// Optional arguments for formatting the string.
+ ///
+ /// Thrown when is .
+ ///
+ void LogInformational(string message, params object[] messageArgs);
+
+ ///
+ /// Logs a warning with the specified . Implementations should be thread safe.
+ ///
+ /// The message to log.
+ /// Optional arguments for formatting the string.
+ ///
+ /// Thrown when is .
+ ///
+ void LogWarning(string message, params object[] messageArgs);
+
+ ///
+ /// Logs an error with the specified . Implementations should be thread safe.
+ ///
+ /// The message to log.
+ /// Optional arguments for formatting the string.
+ ///
+ /// Thrown when is .
+ ///
+ void LogError(string message, params object[] messageArgs);
+
+ ///
+ /// Logs an error with the message and (optionally) the stack trace of the given .
+ /// Implementations should be thread safe.
+ ///
+ /// The to log.
+ ///
+ /// If , append stack trace to 's message.
+ ///
+ ///
+ /// Thrown when is .
+ ///
+ void LogError(Exception exception, bool showStackTrace);
+ }
+}
diff --git a/src/Microsoft.Extensions.ApiDescription.Client/LogWrapper.cs b/src/Microsoft.Extensions.ApiDescription.Client/LogWrapper.cs
new file mode 100644
index 0000000000..75ae1407a8
--- /dev/null
+++ b/src/Microsoft.Extensions.ApiDescription.Client/LogWrapper.cs
@@ -0,0 +1,35 @@
+using System;
+using Microsoft.Build.Utilities;
+
+namespace GenerationTasks
+{
+ internal class LogWrapper : ILogWrapper
+ {
+ private readonly TaskLoggingHelper _log;
+
+ public LogWrapper(TaskLoggingHelper log)
+ {
+ _log = log;
+ }
+
+ public void LogError(string message, params object[] messageArgs)
+ {
+ _log.LogError(message, messageArgs);
+ }
+
+ public void LogError(Exception exception, bool showStackTrace)
+ {
+ _log.LogErrorFromException(exception, showStackTrace);
+ }
+
+ public void LogInformational(string message, params object[] messageArgs)
+ {
+ _log.LogMessage(message, messageArgs);
+ }
+
+ public void LogWarning(string message, params object[] messageArgs)
+ {
+ _log.LogWarning(message, messageArgs);
+ }
+ }
+}
diff --git a/src/Microsoft.Extensions.ApiDescription.Client/Microsoft.Extensions.ApiDescription.Client.csproj b/src/Microsoft.Extensions.ApiDescription.Client/Microsoft.Extensions.ApiDescription.Client.csproj
new file mode 100644
index 0000000000..5dab6ea1aa
--- /dev/null
+++ b/src/Microsoft.Extensions.ApiDescription.Client/Microsoft.Extensions.ApiDescription.Client.csproj
@@ -0,0 +1,46 @@
+
+
+
+ $(GenerateNuspecDependsOn);PopulateNuspec
+
+
+ true
+
+ MSBuild tasks and targets for code generation
+ false
+ false
+ $(MSBuildProjectName).nuspec
+ Build Tasks;msbuild;DownloadFile;GetFilenameFromUri;code generation
+ GenerationTasks
+ netstandard2.0;net461
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ id=$(PackageId);
+ authors=$(Authors);
+ configuration=$(Configuration);
+ copyright=$(Copyright);
+ description=$(PackageDescription);
+ iconUrl=$(PackageIconUrl);
+ licenseUrl=$(PackageLicenseUrl);
+ owners=$(Company);
+ projectUrl=$(PackageProjectUrl);
+ repositoryCommit=$(RepositoryCommit);
+ repositoryUrl=$(RepositoryUrl);
+ tags=$(PackageTags.Replace(';', ' '));
+ version=$(PackageVersion);
+
+
+
+
diff --git a/src/Microsoft.Extensions.ApiDescription.Client/Microsoft.Extensions.ApiDescription.Client.nuspec b/src/Microsoft.Extensions.ApiDescription.Client/Microsoft.Extensions.ApiDescription.Client.nuspec
new file mode 100644
index 0000000000..5b840bd379
--- /dev/null
+++ b/src/Microsoft.Extensions.ApiDescription.Client/Microsoft.Extensions.ApiDescription.Client.nuspec
@@ -0,0 +1,28 @@
+
+
+
+ $id$
+ $authors$
+ $copyright$
+ $description$
+ true
+ $iconUrl$
+ $licenseUrl$
+ 2.8
+ $owners$
+ $projectUrl$
+
+ false
+ $tags$
+ $version$
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Microsoft.Extensions.ApiDescription.Client/ServiceProjectReferenceMetadata.targets b/src/Microsoft.Extensions.ApiDescription.Client/ServiceProjectReferenceMetadata.targets
new file mode 100644
index 0000000000..5ed88a3f6c
--- /dev/null
+++ b/src/Microsoft.Extensions.ApiDescription.Client/ServiceProjectReferenceMetadata.targets
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Microsoft.Extensions.ApiDescription.Client/build/GenerationTasks.props b/src/Microsoft.Extensions.ApiDescription.Client/build/GenerationTasks.props
new file mode 100644
index 0000000000..dc67dca96a
--- /dev/null
+++ b/src/Microsoft.Extensions.ApiDescription.Client/build/GenerationTasks.props
@@ -0,0 +1,133 @@
+
+
+
+
+ <_GenerationTasksAssemblyTarget Condition="'$(MSBuildRuntimeType)' == 'Core'">netstandard2.0
+ <_GenerationTasksAssemblyTarget Condition="'$(MSBuildRuntimeType)' != 'Core'">net461
+ <_GenerationTasksAssemblyPath>$(MSBuildThisFileDirectory)/../tasks/$(_GenerationTasksAssemblyTarget)/GenerationTasks.dll
+ <_GenerationTasksAssemblyTarget />
+
+
+
+
+
+
+
+ true
+ $([MSBuild]::EnsureTrailingSlash('$(ServiceProjectReferenceDirectory)'))
+
+ true
+ $([MSBuild]::EnsureTrailingSlash('$(ServiceUriReferenceDirectory)'))
+
+ true
+ $([MSBuild]::EnsureTrailingSlash('$(ServiceFileReferenceDirectory)'))
+ $(RootNamespace)
+ $(RootNamespace)
+
+
+ _DefaultDocumentGenerator_GetMetadata;
+ _DefaultDocumentGenerator_Core;
+ _DefaultDocumentGenerator_SetMetadata
+
+
+ _ServiceProjectReferenceGenerator_GetTargetFramework;
+ _ServiceProjectReferenceGenerator_GetProjectTargetPath;
+ _ServiceProjectReferenceGenerator_Restore;
+ _ServiceProjectReferenceGenerator_Build;
+ _ServiceProjectReferenceGenerator_Core
+
+
+ _ServiceUriReferenceGenerator_GetMetadata;
+ _ServiceUriReferenceGenerator_Core
+
+
+ _CheckServiceReferences;
+ ServiceProjectReferenceGenerator;
+ ServiceUriReferenceGenerator;
+ _ServiceFileReferenceGenerator_GetMetadata;
+ _ServiceFileReferenceGenerator_Core
+
+
+
+
+
+
+
+ Default
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ %(Filename)Client
+
+
+
+
+
+
+
+
+
diff --git a/src/Microsoft.Extensions.ApiDescription.Client/build/GenerationTasks.targets b/src/Microsoft.Extensions.ApiDescription.Client/build/GenerationTasks.targets
new file mode 100644
index 0000000000..7c70d7d279
--- /dev/null
+++ b/src/Microsoft.Extensions.ApiDescription.Client/build/GenerationTasks.targets
@@ -0,0 +1,234 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <_FullPath>%(ServiceProjectReference.FullPath)
+
+
+ <_Temporary Remove="@(_Temporary)" />
+
+
+
+
+
+
+
+
+ <_TargetFrameworks>%(_Temporary.TargetFrameworks)
+ <_TargetFramework>$(_TargetFrameworks.Split(';')[0])
+
+
+
+ $(_TargetFramework)
+
+ <_Temporary Remove="@(_Temporary)" />
+
+
+
+ <_FullPath />
+ <_TargetFramework />
+ <_TargetFrameworks />
+
+
+
+
+
+
+
+ <_FullPath>%(ServiceProjectReference.FullPath)
+ <_TargetFramework>%(ServiceProjectReference.TargetFramework)
+
+
+ <_Temporary Remove="@(_Temporary)" />
+
+
+
+
+
+
+
+
+ <_ProjectTargetPath>%(_Temporary.FullPath)
+
+
+
+ $(_ProjectTargetPath)
+
+ <_Temporary Remove="@(_Temporary)" />
+
+
+
+ <_FullPath />
+ <_ProjectTargetPath />
+ <_TargetFramework />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <_Temporary Remove="@(_Temporary)" />
+ <_Temporary Include="@(ServiceProjectReference -> WithMetadataValue('DocumentGenerator', 'Default'))" />
+
+
+
+
+
+
+
+
+
+ <_Command>dotnet getdocument --configuration $(Configuration) --no-build
+
+
+ <_Temporary Update="@(_Temporary)">
+ $(DefaultDocumentGeneratorDefaultOptions)
+ $(_Command) --project %(FullPath) --output %(DocumentPath) --framework %(TargetFramework)
+
+ <_Temporary Update="@(_Temporary)">
+ %(Command) --uri %(_Temporary.Uri)
+
+ <_Temporary Update="@(_Temporary)">
+ %(Command) --service %(_Temporary.Service) --method %(_Temporary.Method)
+
+ <_Temporary Update="@(_Temporary)">
+ %(Command) %(_Temporary.Options)
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <_Temporary Remove="@(_Temporary)" />
+
+
+
+
+
+
+
+
+
+ <_Temporary Remove="@(_Temporary)" />
+
+
+
+
+
+
+
+
+
+ <_Temporary Remove="@(_Temporary)" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <_Temporary Remove="@(_Temporary)" />
+
+
+
+
+
+
+
+
+
+ <_Temporary Remove="@(_Temporary)" />
+
+
+
+
+
+
+
diff --git a/src/Microsoft.Extensions.ApiDescription.Client/build/NSwagServiceReference.props b/src/Microsoft.Extensions.ApiDescription.Client/build/NSwagServiceReference.props
new file mode 100644
index 0000000000..4269fdd11c
--- /dev/null
+++ b/src/Microsoft.Extensions.ApiDescription.Client/build/NSwagServiceReference.props
@@ -0,0 +1,20 @@
+
+
+
+
+ _NSwagDocumentGenerator_GetMetadata;
+ _NSwagDocumentGenerator_Core;
+ _NSwagDocumentGenerator_SetMetadata
+
+
+ _NSwagCSharpCodeGenerator_GetMetadata;
+ _NSwagCSharpCodeGenerator_Core;
+ _NSwagCSharpCodeGenerator_SetMetadata
+
+
+ _NSwagTypeScriptCodeGenerator_GetMetadata;
+ _NSwagTypeScriptCodeGenerator_Core;
+ _NSwagTypeScriptCodeGenerator_SetMetadata
+
+
+
diff --git a/src/Microsoft.Extensions.ApiDescription.Client/build/NSwagServiceReference.targets b/src/Microsoft.Extensions.ApiDescription.Client/build/NSwagServiceReference.targets
new file mode 100644
index 0000000000..e73cd8d641
--- /dev/null
+++ b/src/Microsoft.Extensions.ApiDescription.Client/build/NSwagServiceReference.targets
@@ -0,0 +1,131 @@
+
+
+
+
+
+
+ <_NSwagTemporary Remove="@(_NSwagTemporary)" />
+ <_NSwagTemporary Include="@(ServiceProjectReference -> WithMetadataValue('DocumentGenerator', 'NSwag'))">
+ $(NSwagDocumentGeneratorDefaultOptions)
+
+
+
+
+
+
+
+ <_Command>$(NSwagExe_Core21) aspnetcore2swagger /Configuration:$(Configuration) /NoBuild:true
+
+
+
+
+
+
+ <_Command />
+
+
+
+
+
+
+
+
+ <_NSwagTemporary Remove="@(_NSwagTemporary)" />
+
+
+
+
+
+
+
+
+
+ <_NSwagTemporary Remove="@(_NSwagTemporary)" />
+ <_NSwagTemporary Include="@(ServiceFileReference -> WithMetadataValue('CodeGenerator', 'NSwagCSharp'))">
+ $(NSwagCSharpCodeGeneratorDefaultOptions)
+
+ <_NSwagTemporary Update="@(_NSwagTemporary)">
+ $(ServiceFileReferenceDirectory)%(ClassName).cs
+
+
+
+
+
+
+ <_Command>$(NSwagExe_Core21) swagger2csclient /namespace:$(NSwagCSharpCodeGeneratorNamespace)
+
+
+
+
+
+
+ <_Command />
+
+
+
+
+
+
+
+
+
+
+ <_NSwagTemporary Remove="@(_NSwagTemporary)" />
+
+
+
+
+
+
+
+
+
+ <_NSwagTemporary Remove="@(_NSwagTemporary)" />
+ <_NSwagTemporary Include="@(ServiceFileReference -> WithMetadataValue('CodeGenerator', 'NSwagTypeScript'))">
+ $(NSwagTypeScriptCodeGeneratorDefaultOptions)
+
+ <_NSwagTemporary Update="@(_NSwagTemporary)">
+ $(ServiceFileReferenceDirectory)%(ClassName).ts
+
+
+
+
+
+
+ <_Command>$(NSwagExe_Core21) swagger2tsclient /namespace:$(NSwagTypeScriptCodeGeneratorNamespace)
+
+
+
+
+
+
+ <_Command />
+
+
+
+
+
+
+
+
+ <_NSwagTemporary Remove="@(_NSwagTemporary)" />
+
+
+
+
+
diff --git a/src/Microsoft.Extensions.ApiDescription.Client/buildMultiTargeting/GenerationTasks.targets b/src/Microsoft.Extensions.ApiDescription.Client/buildMultiTargeting/GenerationTasks.targets
new file mode 100644
index 0000000000..5e73fd66e1
--- /dev/null
+++ b/src/Microsoft.Extensions.ApiDescription.Client/buildMultiTargeting/GenerationTasks.targets
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/dotnet-getdocument/Commands/InvokeCommand.cs b/src/dotnet-getdocument/Commands/InvokeCommand.cs
new file mode 100644
index 0000000000..cf3308ec46
--- /dev/null
+++ b/src/dotnet-getdocument/Commands/InvokeCommand.cs
@@ -0,0 +1,241 @@
+// Copyright (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.Versioning;
+using GetDocument.Properties;
+using Microsoft.DotNet.Cli.CommandLine;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace GetDocument.Commands
+{
+ internal class InvokeCommand : HelpCommandBase
+ {
+ private const string InsideManName = "GetDocument.Insider";
+
+ private CommandOption _configuration;
+ private CommandOption _framework;
+ private CommandOption _msbuildprojectextensionspath;
+ private CommandOption _output;
+ private CommandOption _project;
+ private CommandOption _runtime;
+ private IList _args;
+
+ public override void Configure(CommandLineApplication command)
+ {
+ var options = new ProjectOptions();
+ options.Configure(command);
+
+ _project = options.Project;
+ _framework = options.Framework;
+ _configuration = options.Configuration;
+ _runtime = options.Runtime;
+ _msbuildprojectextensionspath = options.MSBuildProjectExtensionsPath;
+
+ _output = command.Option("--output ", Resources.OutputDescription);
+ command.VersionOption("--version", ProductInfo.GetVersion);
+ _args = command.RemainingArguments;
+
+ base.Configure(command);
+ }
+
+ protected override int Execute()
+ {
+ var projectFile = FindProjects(
+ _project.Value(),
+ Resources.NoProject,
+ Resources.MultipleProjects);
+ Reporter.WriteVerbose(Resources.UsingProject(projectFile));
+
+ var project = Project.FromFile(
+ projectFile,
+ _msbuildprojectextensionspath.Value(),
+ _framework.Value(),
+ _configuration.Value(),
+ _runtime.Value());
+ if (!File.Exists(project.AssemblyPath))
+ {
+ throw new CommandException(Resources.MustBuild);
+ }
+
+ var thisPath = Path.GetFullPath(Path.GetDirectoryName(typeof(InvokeCommand).Assembly.Location));
+
+ string executable = null;
+ var cleanupExecutable = false;
+ try
+ {
+ string toolsDirectory;
+ var args = new List();
+ var targetFramework = new FrameworkName(project.TargetFrameworkMoniker);
+ switch (targetFramework.Identifier)
+ {
+ case ".NETFramework":
+ cleanupExecutable = true;
+ executable = Path.Combine(project.OutputPath, InsideManName + ".exe");
+ toolsDirectory = Path.Combine(
+ thisPath,
+ project.PlatformTarget == "x86" ? "net461-x86" : "net461");
+
+ var executableSource = Path.Combine(toolsDirectory, InsideManName + ".exe");
+ File.Copy(executableSource, executable, overwrite: true);
+
+ if (!string.IsNullOrEmpty(project.ConfigPath))
+ {
+ File.Copy(project.ConfigPath, executable + ".config", overwrite: true);
+ }
+ break;
+
+ case ".NETCoreApp":
+ executable = "dotnet";
+ toolsDirectory = Path.Combine(thisPath, "netcoreapp2.0");
+
+ if (targetFramework.Version < new Version(2, 0))
+ {
+ throw new CommandException(
+ Resources.NETCoreApp1Project(project.Name, targetFramework.Version));
+ }
+
+ args.Add("exec");
+ args.Add("--depsFile");
+ args.Add(project.DepsPath);
+
+ if (!string.IsNullOrEmpty(project.AssetsPath))
+ {
+ using (var reader = new JsonTextReader(File.OpenText(project.AssetsPath)))
+ {
+ var projectAssets = JToken.ReadFrom(reader);
+ var packageFolders = projectAssets["packageFolders"]
+ .Children()
+ .Select(p => p.Name);
+
+ foreach (var packageFolder in packageFolders)
+ {
+ args.Add("--additionalProbingPath");
+ args.Add(packageFolder.TrimEnd(Path.DirectorySeparatorChar));
+ }
+ }
+ }
+
+ if (File.Exists(project.RuntimeConfigPath))
+ {
+ args.Add("--runtimeConfig");
+ args.Add(project.RuntimeConfigPath);
+ }
+ else if (!string.IsNullOrEmpty(project.RuntimeFrameworkVersion))
+ {
+ args.Add("--fx-version");
+ args.Add(project.RuntimeFrameworkVersion);
+ }
+
+ args.Add(Path.Combine(toolsDirectory, InsideManName + ".dll"));
+ break;
+
+ case ".NETStandard":
+ throw new CommandException(Resources.NETStandardProject(project.Name));
+
+ default:
+ throw new CommandException(
+ Resources.UnsupportedFramework(project.Name, targetFramework.Identifier));
+ }
+
+ args.AddRange(_args);
+ args.Add("--assembly");
+ args.Add(project.AssemblyPath);
+ args.Add("--tools-directory");
+ args.Add(toolsDirectory);
+
+ if (!(args.Contains("--method") || string.IsNullOrEmpty(project.DefaultMethod)))
+ {
+ args.Add("--method");
+ args.Add(project.DefaultMethod);
+ }
+
+ if (!(args.Contains("--service") || string.IsNullOrEmpty(project.DefaultService)))
+ {
+ args.Add("--service");
+ args.Add(project.DefaultService);
+ }
+
+ if (!(args.Contains("--uri") || string.IsNullOrEmpty(project.DefaultUri)))
+ {
+ args.Add("--uri");
+ args.Add(project.DefaultUri);
+ }
+
+ if (_output.HasValue())
+ {
+ args.Add("--output");
+ args.Add(Path.GetFullPath(_output.Value()));
+ }
+
+ if (Reporter.IsVerbose)
+ {
+ args.Add("--verbose");
+ }
+
+ if (Reporter.NoColor)
+ {
+ args.Add("--no-color");
+ }
+
+ if (Reporter.PrefixOutput)
+ {
+ args.Add("--prefix-output");
+ }
+
+ return Exe.Run(executable, args, project.Directory);
+ }
+ finally
+ {
+ if (cleanupExecutable && !string.IsNullOrEmpty(executable))
+ {
+ File.Delete(executable);
+ File.Delete(executable + ".config");
+ }
+ }
+ }
+
+ private static string FindProjects(
+ string path,
+ string errorWhenNoProject,
+ string errorWhenMultipleProjects)
+ {
+ var specified = true;
+ if (path == null)
+ {
+ specified = false;
+ path = Directory.GetCurrentDirectory();
+ }
+ else if (!Directory.Exists(path)) // It's not a directory
+ {
+ return path;
+ }
+
+ var projectFiles = Directory
+ .EnumerateFiles(path, "*.*proj", SearchOption.TopDirectoryOnly)
+ .Where(f => !string.Equals(Path.GetExtension(f), ".xproj", StringComparison.OrdinalIgnoreCase))
+ .Take(2)
+ .ToList();
+ if (projectFiles.Count == 0)
+ {
+ throw new CommandException(
+ specified
+ ? Resources.NoProjectInDirectory(path)
+ : errorWhenNoProject);
+ }
+ if (projectFiles.Count != 1)
+ {
+ throw new CommandException(
+ specified
+ ? Resources.MultipleProjectsInDirectory(path)
+ : errorWhenMultipleProjects);
+ }
+
+ return projectFiles[0];
+ }
+ }
+}
diff --git a/src/dotnet-getdocument/Exe.cs b/src/dotnet-getdocument/Exe.cs
new file mode 100644
index 0000000000..0c8f7d89ae
--- /dev/null
+++ b/src/dotnet-getdocument/Exe.cs
@@ -0,0 +1,118 @@
+// Copyright (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.Diagnostics;
+using System.Text;
+
+namespace GetDocument
+{
+ internal static class Exe
+ {
+ public static int Run(
+ string executable,
+ IReadOnlyList args,
+ string workingDirectory = null,
+ bool interceptOutput = false)
+ {
+ var arguments = ToArguments(args);
+
+ Reporter.WriteVerbose(executable + " " + arguments);
+
+ var startInfo = new ProcessStartInfo
+ {
+ FileName = executable,
+ Arguments = arguments,
+ UseShellExecute = false,
+ RedirectStandardOutput = interceptOutput
+ };
+ if (workingDirectory != null)
+ {
+ startInfo.WorkingDirectory = workingDirectory;
+ }
+
+ var process = Process.Start(startInfo);
+
+ if (interceptOutput)
+ {
+ string line;
+ while ((line = process.StandardOutput.ReadLine()) != null)
+ {
+ Reporter.WriteVerbose(line);
+ }
+ }
+
+ process.WaitForExit();
+
+ return process.ExitCode;
+ }
+
+ private static string ToArguments(IReadOnlyList args)
+ {
+ var builder = new StringBuilder();
+ for (var i = 0; i < args.Count; i++)
+ {
+ if (i != 0)
+ {
+ builder.Append(" ");
+ }
+
+ if (args[i].IndexOf(' ') == -1)
+ {
+ builder.Append(args[i]);
+
+ continue;
+ }
+
+ builder.Append("\"");
+
+ var pendingBackslashs = 0;
+ for (var j = 0; j < args[i].Length; j++)
+ {
+ switch (args[i][j])
+ {
+ case '\"':
+ if (pendingBackslashs != 0)
+ {
+ builder.Append('\\', pendingBackslashs * 2);
+ pendingBackslashs = 0;
+ }
+ builder.Append("\\\"");
+ break;
+
+ case '\\':
+ pendingBackslashs++;
+ break;
+
+ default:
+ if (pendingBackslashs != 0)
+ {
+ if (pendingBackslashs == 1)
+ {
+ builder.Append("\\");
+ }
+ else
+ {
+ builder.Append('\\', pendingBackslashs * 2);
+ }
+
+ pendingBackslashs = 0;
+ }
+
+ builder.Append(args[i][j]);
+ break;
+ }
+ }
+
+ if (pendingBackslashs != 0)
+ {
+ builder.Append('\\', pendingBackslashs * 2);
+ }
+
+ builder.Append("\"");
+ }
+
+ return builder.ToString();
+ }
+ }
+}
diff --git a/src/dotnet-getdocument/Program.cs b/src/dotnet-getdocument/Program.cs
new file mode 100644
index 0000000000..495573a776
--- /dev/null
+++ b/src/dotnet-getdocument/Program.cs
@@ -0,0 +1,43 @@
+// Copyright (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 GetDocument.Commands;
+using GetDocument.Properties;
+using Microsoft.DotNet.Cli.CommandLine;
+
+namespace GetDocument
+{
+ internal static class Program
+ {
+ private static int Main(string[] args)
+ {
+ var app = new CommandLineApplication(throwOnUnexpectedArg: false)
+ {
+ FullName = Resources.CommandFullName,
+ };
+
+ new InvokeCommand().Configure(app);
+
+ try
+ {
+ return app.Execute(args);
+ }
+ catch (Exception ex)
+ {
+ if (ex is CommandException || ex is CommandParsingException)
+ {
+ Reporter.WriteVerbose(ex.ToString());
+ }
+ else
+ {
+ Reporter.WriteInformation(ex.ToString());
+ }
+
+ Reporter.WriteError(ex.Message);
+
+ return 1;
+ }
+ }
+ }
+}
diff --git a/src/dotnet-getdocument/Project.cs b/src/dotnet-getdocument/Project.cs
new file mode 100644
index 0000000000..274960a08b
--- /dev/null
+++ b/src/dotnet-getdocument/Project.cs
@@ -0,0 +1,228 @@
+// Copyright (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.Diagnostics;
+using System.IO;
+using System.Linq;
+using GetDocument.Properties;
+using IODirectory = System.IO.Directory;
+
+namespace GetDocument
+{
+ internal class Project
+ {
+ private const string MSBuildResourceName = "GetDocument.ServiceProjectReferenceMetadata";
+
+ private Project()
+ {
+ }
+
+ public string AssemblyName { get; private set; }
+
+ public string AssemblyPath { get; private set; }
+
+ public string AssetsPath { get; private set; }
+
+ public string Configuration { get; private set; }
+
+ public string ConfigPath { get; private set; }
+
+ public string DefaultDocumentName { get; private set; }
+
+ public string DefaultMethod { get; private set; }
+
+ public string DefaultService { get; private set; }
+
+ public string DefaultUri { get; private set; }
+
+ public string DepsPath { get; private set; }
+
+ public string Directory { get; private set; }
+
+ public string ExtensionsPath { get; private set; }
+
+ public string Name { get; private set; }
+
+ public string OutputPath { get; private set; }
+
+ public string Platform { get; private set; }
+
+ public string PlatformTarget { get; private set; }
+
+ public string RuntimeConfigPath { get; private set; }
+
+ public string RuntimeFrameworkVersion { get; private set; }
+
+ public string TargetFramework { get; private set; }
+
+ public string TargetFrameworkMoniker { get; private set; }
+
+ public static Project FromFile(
+ string file,
+ string buildExtensionsDirectory,
+ string framework = null,
+ string configuration = null,
+ string runtime = null)
+ {
+ Debug.Assert(!string.IsNullOrEmpty(file), "file is null or empty.");
+
+ if (string.IsNullOrEmpty(buildExtensionsDirectory))
+ {
+ buildExtensionsDirectory = Path.Combine(Path.GetDirectoryName(file), "obj");
+ }
+
+ IODirectory.CreateDirectory(buildExtensionsDirectory);
+
+ var assembly = typeof(Project).Assembly;
+ var propsPath = Path.Combine(
+ buildExtensionsDirectory,
+ Path.GetFileName(file) + ".ServiceProjectReferenceMetadata.props");
+ using (var input = assembly.GetManifestResourceStream($"{MSBuildResourceName}.props"))
+ {
+ using (var output = File.OpenWrite(propsPath))
+ {
+ Reporter.WriteVerbose(Resources.WritingFile(propsPath));
+ input.CopyTo(output);
+ }
+ }
+
+ var targetsPath = Path.ChangeExtension(propsPath, ".targets");
+ using (var input = assembly.GetManifestResourceStream($"{MSBuildResourceName}.targets"))
+ {
+ using (var output = File.OpenWrite(targetsPath))
+ {
+ // NB: Copy always in case it changes
+ Reporter.WriteVerbose(Resources.WritingFile(targetsPath));
+ input.CopyTo(output);
+ }
+ }
+
+ IDictionary metadata;
+ var metadataPath = Path.GetTempFileName();
+ try
+ {
+ var propertyArg = "/property:ServiceProjectReferenceMetadataPath=" + metadataPath;
+ if (!string.IsNullOrEmpty(framework))
+ {
+ propertyArg += ";TargetFramework=" + framework;
+ }
+ if (!string.IsNullOrEmpty(configuration))
+ {
+ propertyArg += ";Configuration=" + configuration;
+ }
+ if (!string.IsNullOrEmpty(runtime))
+ {
+ propertyArg += ";RuntimeIdentifier=" + runtime;
+ }
+
+ var args = new List
+ {
+ "msbuild",
+ "/target:WriteServiceProjectReferenceMetadata",
+ propertyArg,
+ "/verbosity:quiet",
+ "/nologo"
+ };
+
+ if (!string.IsNullOrEmpty(file))
+ {
+ args.Add(file);
+ }
+
+ var exitCode = Exe.Run("dotnet", args);
+ if (exitCode != 0)
+ {
+ throw new CommandException(Resources.GetMetadataFailed);
+ }
+
+ metadata = File.ReadLines(metadataPath).Select(l => l.Split(new[] { ':' }, 2))
+ .ToDictionary(s => s[0], s => s[1].TrimStart());
+ }
+ finally
+ {
+ File.Delete(propsPath);
+ File.Delete(metadataPath);
+ File.Delete(targetsPath);
+ }
+
+ var project = new Project
+ {
+ AssemblyName = metadata[nameof(AssemblyName)],
+ AssemblyPath = metadata[nameof(AssemblyPath)],
+ AssetsPath = metadata[nameof(AssetsPath)],
+ Configuration = metadata[nameof(Configuration)],
+ DefaultDocumentName = metadata[nameof(DefaultDocumentName)],
+ DefaultMethod = metadata[nameof(DefaultMethod)],
+ DefaultService = metadata[nameof(DefaultService)],
+ DefaultUri = metadata[nameof(DefaultUri)],
+ DepsPath = metadata[nameof(DepsPath)],
+ Directory = metadata[nameof(Directory)],
+ ExtensionsPath = metadata[nameof(ExtensionsPath)],
+ Name = metadata[nameof(Name)],
+ OutputPath = metadata[nameof(OutputPath)],
+ Platform = metadata[nameof(Platform)],
+ PlatformTarget = metadata[nameof(PlatformTarget)] ?? metadata[nameof(Platform)],
+ RuntimeConfigPath = metadata[nameof(RuntimeConfigPath)],
+ RuntimeFrameworkVersion = metadata[nameof(RuntimeFrameworkVersion)],
+ TargetFramework = metadata[nameof(TargetFramework)],
+ TargetFrameworkMoniker = metadata[nameof(TargetFrameworkMoniker)],
+ };
+
+ if (string.IsNullOrEmpty(project.AssemblyPath))
+ {
+ throw new CommandException(Resources.GetMetadataValueFailed(nameof(AssemblyPath), "TargetPath"));
+ }
+
+ if (string.IsNullOrEmpty(project.Directory))
+ {
+ throw new CommandException(Resources.GetMetadataValueFailed(nameof(Directory), "ProjectDir"));
+ }
+
+ if (string.IsNullOrEmpty(project.OutputPath))
+ {
+ throw new CommandException(Resources.GetMetadataValueFailed(nameof(OutputPath), "OutDir"));
+ }
+
+ if (!Path.IsPathRooted(project.Directory))
+ {
+ project.Directory = Path.GetFullPath(Path.Combine(IODirectory.GetCurrentDirectory(), project.Directory));
+ }
+
+ if (!Path.IsPathRooted(project.AssemblyPath))
+ {
+ project.AssemblyPath = Path.GetFullPath(Path.Combine(project.Directory, project.AssemblyPath));
+ }
+
+ if (!Path.IsPathRooted(project.OutputPath))
+ {
+ project.OutputPath = Path.GetFullPath(Path.Combine(project.Directory, project.OutputPath));
+ }
+
+ // Some document generation tools support non-ASP.NET Core projects.
+ // Thus any of the remaining properties may be empty.
+ if (!(string.IsNullOrEmpty(project.AssetsPath) || Path.IsPathRooted(project.AssetsPath)))
+ {
+ project.AssetsPath = Path.GetFullPath(Path.Combine(project.Directory, project.AssetsPath));
+ }
+
+ var configPath = $"{project.AssemblyPath}.config";
+ if (File.Exists(configPath))
+ {
+ project.ConfigPath = configPath;
+ }
+
+ if (!(string.IsNullOrEmpty(project.DepsPath) || Path.IsPathRooted(project.DepsPath)))
+ {
+ project.DepsPath = Path.GetFullPath(Path.Combine(project.Directory, project.DepsPath));
+ }
+
+ if (!(string.IsNullOrEmpty(project.RuntimeConfigPath) || Path.IsPathRooted(project.RuntimeConfigPath)))
+ {
+ project.RuntimeConfigPath = Path.GetFullPath(Path.Combine(project.Directory, project.RuntimeConfigPath));
+ }
+
+ return project;
+ }
+ }
+}
diff --git a/src/dotnet-getdocument/ProjectOptions.cs b/src/dotnet-getdocument/ProjectOptions.cs
new file mode 100644
index 0000000000..bca160eeaa
--- /dev/null
+++ b/src/dotnet-getdocument/ProjectOptions.cs
@@ -0,0 +1,30 @@
+// Copyright (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 GetDocument.Properties;
+using Microsoft.DotNet.Cli.CommandLine;
+
+namespace GetDocument
+{
+ internal class ProjectOptions
+ {
+ public CommandOption Project { get; private set; }
+
+ public CommandOption Framework { get; private set; }
+
+ public CommandOption Configuration { get; private set; }
+
+ public CommandOption Runtime { get; private set; }
+
+ public CommandOption MSBuildProjectExtensionsPath { get; private set; }
+
+ public void Configure(CommandLineApplication command)
+ {
+ Project = command.Option("-p|--project ", Resources.ProjectDescription);
+ Framework = command.Option("--framework ", Resources.FrameworkDescription);
+ Configuration = command.Option("--configuration ", Resources.ConfigurationDescription);
+ Runtime = command.Option("--runtime ", Resources.RuntimeDescription);
+ MSBuildProjectExtensionsPath = command.Option("--msbuildprojectextensionspath ", Resources.ProjectExtensionsDescription);
+ }
+ }
+}
diff --git a/src/dotnet-getdocument/Properties/Resources.Designer.cs b/src/dotnet-getdocument/Properties/Resources.Designer.cs
new file mode 100644
index 0000000000..3ee7f0ba79
--- /dev/null
+++ b/src/dotnet-getdocument/Properties/Resources.Designer.cs
@@ -0,0 +1,179 @@
+//
+
+using System;
+using System.Reflection;
+using System.Resources;
+using JetBrains.Annotations;
+
+namespace GetDocument.Properties
+{
+ ///
+ /// This API supports the GetDocument infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ internal static class Resources
+ {
+ private static readonly ResourceManager _resourceManager
+ = new ResourceManager("GetDocument.Properties.Resources", typeof(Resources).GetTypeInfo().Assembly);
+
+ ///
+ /// The configuration to use.
+ ///
+ public static string ConfigurationDescription
+ => GetString("ConfigurationDescription");
+
+ ///
+ /// dotnet getdocument
+ ///
+ public static string CommandFullName
+ => GetString("CommandFullName");
+
+ ///
+ /// The target framework.
+ ///
+ public static string FrameworkDescription
+ => GetString("FrameworkDescription");
+
+ ///
+ /// Unable to retrieve project metadata. If you are using custom BaseIntermediateOutputPath or MSBuildProjectExtensionsPath values, use the --msbuildprojectextensionspath option.
+ ///
+ public static string GetMetadataFailed
+ => GetString("GetMetadataFailed");
+
+ ///
+ /// More than one project was found in the current working directory. Use the --project option.
+ ///
+ public static string MultipleProjects
+ => GetString("MultipleProjects");
+
+ ///
+ /// More than one project was found in directory '{projectDirectory}'. Specify one using its file name.
+ ///
+ public static string MultipleProjectsInDirectory([CanBeNull] object projectDirectory)
+ => string.Format(
+ GetString("MultipleProjectsInDirectory", nameof(projectDirectory)),
+ projectDirectory);
+
+ ///
+ /// Project '{Project}' targets framework '.NETCoreApp' version '{targetFrameworkVersion}'. This version of the GetDocument Command-line Tool only supports version 2.0 or higher.
+ ///
+ public static string NETCoreApp1Project([CanBeNull] object Project, [CanBeNull] object targetFrameworkVersion)
+ => string.Format(
+ GetString("NETCoreApp1Project", nameof(Project), nameof(targetFrameworkVersion)),
+ Project, targetFrameworkVersion);
+
+ ///
+ /// Project '{Project}' targets framework '.NETStandard'. There is no runtime associated with this framework, and projects targeting it cannot be executed directly. To use the GetDocument Command-line Tool with this project, add an executable project targeting .NET Core or .NET Framework that references this project and specify it using the --project option; or, update this project to target .NET Core and / or .NET Framework.
+ ///
+ public static string NETStandardProject([CanBeNull] object Project)
+ => string.Format(
+ GetString("NETStandardProject", nameof(Project)),
+ Project);
+
+ ///
+ /// Do not colorize output.
+ ///
+ public static string NoColorDescription
+ => GetString("NoColorDescription");
+
+ ///
+ /// No project was found. Change the current working directory or use the --project option.
+ ///
+ public static string NoProject
+ => GetString("NoProject");
+
+ ///
+ /// No project was found in directory '{projectDirectory}'.
+ ///
+ public static string NoProjectInDirectory([CanBeNull] object projectDirectory)
+ => string.Format(
+ GetString("NoProjectInDirectory", nameof(projectDirectory)),
+ projectDirectory);
+
+ ///
+ /// Prefix output with level.
+ ///
+ public static string PrefixDescription
+ => GetString("PrefixDescription");
+
+ ///
+ /// The project to use.
+ ///
+ public static string ProjectDescription
+ => GetString("ProjectDescription");
+
+ ///
+ /// The MSBuild project extensions path. Defaults to "obj".
+ ///
+ public static string ProjectExtensionsDescription
+ => GetString("ProjectExtensionsDescription");
+
+ ///
+ /// The runtime identifier to use.
+ ///
+ public static string RuntimeDescription
+ => GetString("RuntimeDescription");
+
+ ///
+ /// Project '{Project}' targets framework '{targetFramework}'. The GetDocument Command-line Tool does not support this framework.
+ ///
+ public static string UnsupportedFramework([CanBeNull] object Project, [CanBeNull] object targetFramework)
+ => string.Format(
+ GetString("UnsupportedFramework", nameof(Project), nameof(targetFramework)),
+ Project, targetFramework);
+
+ ///
+ /// Using project '{project}'.
+ ///
+ public static string UsingProject([CanBeNull] object project)
+ => string.Format(
+ GetString("UsingProject", nameof(project)),
+ project);
+
+ ///
+ /// Show verbose output.
+ ///
+ public static string VerboseDescription
+ => GetString("VerboseDescription");
+
+ ///
+ /// Writing '{file}'...
+ ///
+ public static string WritingFile([CanBeNull] object file)
+ => string.Format(
+ GetString("WritingFile", nameof(file)),
+ file);
+
+ ///
+ /// Project output not found and --no-build was specified. Project must be up-to-date when using the --no-build option.
+ ///
+ public static string MustBuild
+ => GetString("MustBuild");
+
+ ///
+ /// The file to write the result to.
+ ///
+ public static string OutputDescription
+ => GetString("OutputDescription");
+
+ ///
+ /// Unable to retrieve '{properrty}' project metadata. Ensure '{msbuildProperty}' is set.
+ ///
+ public static string GetMetadataValueFailed([CanBeNull] object properrty, [CanBeNull] object msbuildProperty)
+ => string.Format(
+ GetString("GetMetadataValueFailed", nameof(properrty), nameof(msbuildProperty)),
+ properrty, msbuildProperty);
+
+ private static string GetString(string name, params string[] formatterNames)
+ {
+ var value = _resourceManager.GetString(name);
+ for (var i = 0; i < formatterNames.Length; i++)
+ {
+ value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}");
+ }
+
+ return value;
+ }
+ }
+}
+
diff --git a/src/dotnet-getdocument/Properties/Resources.Designer.tt b/src/dotnet-getdocument/Properties/Resources.Designer.tt
new file mode 100644
index 0000000000..3f636a4db5
--- /dev/null
+++ b/src/dotnet-getdocument/Properties/Resources.Designer.tt
@@ -0,0 +1,6 @@
+<#
+ Session["ResourceFile"] = "Resources.resx";
+ Session["AccessModifier"] = "internal";
+ Session["NoDiagnostics"] = true;
+#>
+<#@ include file="..\..\tools\Resources.tt" #>
diff --git a/src/dotnet-getdocument/Properties/Resources.resx b/src/dotnet-getdocument/Properties/Resources.resx
new file mode 100644
index 0000000000..16e16b8086
--- /dev/null
+++ b/src/dotnet-getdocument/Properties/Resources.resx
@@ -0,0 +1,186 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 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 configuration to use.
+
+
+ dotnet getdocument
+
+
+ The target framework.
+
+
+ Unable to retrieve project metadata. If you are using custom BaseIntermediateOutputPath or MSBuildProjectExtensionsPath values, use the --msbuildprojectextensionspath option.
+
+
+ More than one project was found in the current working directory. Use the --project option.
+
+
+ More than one project was found in directory '{projectDirectory}'. Specify one using its file name.
+
+
+ Project '{Project}' targets framework '.NETCoreApp' version '{targetFrameworkVersion}'. This version of the GetDocument Command-line Tool only supports version 2.0 or higher.
+
+
+ Project '{Project}' targets framework '.NETStandard'. There is no runtime associated with this framework, and projects targeting it cannot be executed directly. To use the GetDocument Command-line Tool with this project, add an executable project targeting .NET Core or .NET Framework that references this project and specify it using the --project option; or, update this project to target .NET Core and / or .NET Framework.
+
+
+ Do not colorize output.
+
+
+ No project was found. Change the current working directory or use the --project option.
+
+
+ No project was found in directory '{projectDirectory}'.
+
+
+ Prefix output with level.
+
+
+ The project to use.
+
+
+ The MSBuild project extensions path. Defaults to "obj".
+
+
+ The runtime identifier to use.
+
+
+ Project '{Project}' targets framework '{targetFramework}'. The GetDocument Command-line Tool does not support this framework.
+
+
+ Using project '{project}'.
+
+
+ Show verbose output.
+
+
+ Writing '{file}'...
+
+
+ Project output not found and --no-build was specified. Project must be up-to-date when using the --no-build option.
+
+
+ The file to write the result to.
+
+
+ Unable to retrieve '{properrty}' project metadata. Ensure '{msbuildProperty}' is set.
+
+
\ No newline at end of file
diff --git a/src/dotnet-getdocument/ServiceProjectReferenceMetadata.props b/src/dotnet-getdocument/ServiceProjectReferenceMetadata.props
new file mode 100644
index 0000000000..30f045f3c0
--- /dev/null
+++ b/src/dotnet-getdocument/ServiceProjectReferenceMetadata.props
@@ -0,0 +1,17 @@
+
+
+
+
+
+ $(WriteServiceProjectReferenceMetadataDependsOn)
+
+
+
+
+ $(WriteServiceProjectReferenceMetadataDependsOn)
+
+
+ $(WriteServiceProjectReferenceMetadataDependsOn)
+
+
+
diff --git a/src/dotnet-getdocument/ServiceProjectReferenceMetadata.targets b/src/dotnet-getdocument/ServiceProjectReferenceMetadata.targets
new file mode 100644
index 0000000000..e8840ea37b
--- /dev/null
+++ b/src/dotnet-getdocument/ServiceProjectReferenceMetadata.targets
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/dotnet-getdocument/dotnet-getdocument.csproj b/src/dotnet-getdocument/dotnet-getdocument.csproj
new file mode 100644
index 0000000000..b88db8367a
--- /dev/null
+++ b/src/dotnet-getdocument/dotnet-getdocument.csproj
@@ -0,0 +1,112 @@
+
+
+
+
+
+
+ $(GenerateNuspecDependsOn);PopulateNuspec
+
+ dotnet-getdocument
+ GetDocument Command-line Tool outside man
+ false
+ true
+ false
+ false
+ $(MSBuildProjectName).nuspec
+ Exe
+ true
+ GetDocument;command line;command-line;tool
+ GetDocument
+ netcoreapp2.1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ TextTemplatingFileGenerator
+ Resources.Designer.cs
+
+
+
+
+
+
+
+
+
+ True
+ True
+ Resources.Designer.tt
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <_Temporary Remove="@(Temporary)" />
+ <_Temporary Include="../GetDocumentInsider/GetDocumentInsider.csproj" Properties="TargetFramework=net461" />
+ <_Temporary Include="../GetDocumentInsider/GetDocumentInsider.csproj" Properties="TargetFramework=net461;Platform=x86" />
+ <_Temporary Include="../GetDocumentInsider/GetDocumentInsider.csproj" Properties="TargetFramework=netcoreapp2.0" />
+
+
+
+
+
+
+ <_Temporary Remove="@(_Temporary)" />
+
+
+
+
+ authors=$(Authors);
+ copyright=$(Copyright);
+ description=$(Description);
+ iconUrl=$(PackageIconUrl);
+ id=$(PackageId);
+ InsiderNet461Output=..\GetDocumentInsider\bin\$(Configuration)\net461\publish\*;
+ InsiderNet461X86Output=..\GetDocumentInsider\bin\x86\$(Configuration)\net461\publish\*;
+ InsiderNetCoreOutput=..\GetDocumentInsider\bin\$(Configuration)\netcoreapp2.0\publish\*;
+ licenseUrl=$(PackageLicenseUrl);
+ Output=$(PublishDir)**\*;
+ OutputShims=$(IntermediateOutputPath)shims\**\*;
+ packageType=$(PackageType);
+ projectUrl=$(PackageProjectUrl);
+ repositoryCommit=$(RepositoryCommit);
+ repositoryUrl=$(RepositoryUrl);
+ SettingsFile=$(_ToolsSettingsFilePath);
+ tags=$(PackageTags.Replace(';', ' '));
+ targetFramework=$(TargetFramework);
+ version=$(PackageVersion);
+
+
+
+
diff --git a/src/dotnet-getdocument/dotnet-getdocument.nuspec b/src/dotnet-getdocument/dotnet-getdocument.nuspec
new file mode 100644
index 0000000000..60c51f7869
--- /dev/null
+++ b/src/dotnet-getdocument/dotnet-getdocument.nuspec
@@ -0,0 +1,28 @@
+
+
+
+ $id$
+ $version$
+ $authors$
+ true
+ $licenseUrl$
+ $projectUrl$
+ $iconUrl$
+ $description$
+ $copyright$
+ $tags$
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+