Merge pull request #8585 from aspnet/feature/client.code.generation

Merge feature/client.code.generation branch into release/2.2
This commit is contained in:
Doug Bunting 2018-10-11 16:36:36 -07:00 committed by GitHub
commit 18acae77c8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
54 changed files with 4961 additions and 12 deletions

View File

@ -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.Design", "src\Microsoft.Extensions.ApiDescription.Design\Microsoft.Extensions.ApiDescription.Design.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}

45
Mvc.sln
View File

@ -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.Design", "src\Microsoft.Extensions.ApiDescription.Design\Microsoft.Extensions.ApiDescription.Design.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}

View File

@ -1,7 +1,31 @@
{
"Default": {
"rules": [
"DefaultCompositeRule"
]
"Default": {
"rules": [
"DefaultCompositeRule"
],
"packages": {
"Microsoft.Extensions.ApiDescription.Design": {
"Exclusions": {
"BUILD_ITEMS_FRAMEWORK": {
"*": "Package includes tool with different target frameworks."
},
"SERVICING_ATTRIBUTE": {
"tools/Newtonsoft.Json.dll": "External assembly, not built as part of this process"
},
"WRONG_PUBLICKEYTOKEN": {
"tools/Newtonsoft.Json.dll": "External assembly, not built as part of this process"
},
"ASSEMBLY_INFORMATIONAL_VERSION_MISMATCH": {
"tools/Newtonsoft.Json.dll": "External assembly, not built as part of this process"
},
"ASSEMBLY_FILE_VERSION_MISMATCH": {
"tools/Newtonsoft.Json.dll": "External assembly, not built as part of this process"
},
"ASSEMBLY_VERSION_MISMATCH": {
"tools/Newtonsoft.Json.dll": "External assembly, not built as part of this process"
}
}
}
}
}
}
}

View File

@ -7,7 +7,7 @@
is not otherwise referenced. They avoid unnecessary changes to the Universe build graph or to product
dependencies. Do not use these properties elsewhere.
-->
<AngleSharpPackageVersion>0.9.9</AngleSharpPackageVersion>
<BenchmarkDotNetPackageVersion>0.10.13</BenchmarkDotNetPackageVersion>
<BenchmarksOnlyMicrosoftEntityFrameworkCoreDesignPackageVersion>2.1.1</BenchmarksOnlyMicrosoftEntityFrameworkCoreDesignPackageVersion>
@ -32,6 +32,7 @@
<MicrosoftAspNetCoreDiagnosticsAbstractionsPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreDiagnosticsAbstractionsPackageVersion>
<MicrosoftAspNetCoreDiagnosticsPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreDiagnosticsPackageVersion>
<MicrosoftAspNetCoreHostingAbstractionsPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreHostingAbstractionsPackageVersion>
<MicrosoftAspNetCoreHostingAbstractions20PackageVersion>2.0.0</MicrosoftAspNetCoreHostingAbstractions20PackageVersion>
<MicrosoftAspNetCoreHostingPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreHostingPackageVersion>
<MicrosoftAspNetCoreHtmlAbstractionsPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreHtmlAbstractionsPackageVersion>
<MicrosoftAspNetCoreHttpExtensionsPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreHttpExtensionsPackageVersion>
@ -57,6 +58,7 @@
<MicrosoftAspNetCoreTestingPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreTestingPackageVersion>
<MicrosoftAspNetCoreWebUtilitiesPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreWebUtilitiesPackageVersion>
<MicrosoftAspNetWebApiClientPackageVersion>5.2.6</MicrosoftAspNetWebApiClientPackageVersion>
<MicrosoftBuildUtilitiesCorePackageVersion>15.6.82</MicrosoftBuildUtilitiesCorePackageVersion>
<MicrosoftCodeAnalysisCSharpPackageVersion>2.8.0</MicrosoftCodeAnalysisCSharpPackageVersion>
<MicrosoftCodeAnalysisCSharpWorkspacesPackageVersion>2.8.0</MicrosoftCodeAnalysisCSharpWorkspacesPackageVersion>
<MicrosoftCodeAnalysisRazorPackageVersion>2.2.0-preview3-35359</MicrosoftCodeAnalysisRazorPackageVersion>
@ -100,8 +102,10 @@
<MoqPackageVersion>4.7.49</MoqPackageVersion>
<NETStandardLibrary20PackageVersion>2.0.3</NETStandardLibrary20PackageVersion>
<NewtonsoftJsonBsonPackageVersion>1.0.1</NewtonsoftJsonBsonPackageVersion>
<NewtonsoftJsonPackageVersion>11.0.2</NewtonsoftJsonPackageVersion>
<SystemComponentModelAnnotationsPackageVersion>4.5.0</SystemComponentModelAnnotationsPackageVersion>
<SystemDiagnosticsDiagnosticSourcePackageVersion>4.5.0</SystemDiagnosticsDiagnosticSourcePackageVersion>
<SystemNetHttpPackageVersion>4.3.2</SystemNetHttpPackageVersion>
<SystemThreadingTasksExtensionsPackageVersion>4.5.1</SystemThreadingTasksExtensionsPackageVersion>
<XunitAnalyzersPackageVersion>0.10.0</XunitAnalyzersPackageVersion>
<XunitPackageVersion>2.3.1</XunitPackageVersion>

View File

@ -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.Extensions.ApiDescription.Tool
{
internal class AnsiConsole
{
public static readonly AnsiTextWriter _out = new AnsiTextWriter(Console.Out);
public static void WriteLine(string text)
=> _out.WriteLine(text);
}
}

View File

@ -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 Microsoft.Extensions.ApiDescription.Tool
{
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";
}
}

View File

@ -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 Microsoft.Extensions.ApiDescription.Tool
{
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();
}
}
}
}

View File

@ -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 Microsoft.Extensions.ApiDescription.Tool
{
internal class CommandException : Exception
{
public CommandException(string message)
: base(message)
{
}
public CommandException(string message, Exception innerException)
: base(message, innerException)
{
}
}
}

View File

@ -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<string>();
public string Name { get; set; }
public string Description { get; set; }
public List<string> Values { get; private set; }
public bool MultipleValues { get; set; }
public string Value => Values.FirstOrDefault();
}
}

View File

@ -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<CommandOption>();
Arguments = new List<CommandArgument>();
Commands = new List<CommandLineApplication>();
RemainingArguments = new List<string>();
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<CommandOption> Options { get; private set; }
public CommandOption OptionHelp { get; private set; }
public CommandOption OptionVersion { get; private set; }
public List<CommandArgument> Arguments { get; private set; }
public List<string> RemainingArguments { get; private set; }
public bool IsShowingInformation { get; protected set; } // Is showing help or version?
public Func<int> Invoke { get; set; }
public Func<string> LongVersionGetter { get; set; }
public Func<string> ShortVersionGetter { get; set; }
public List<CommandLineApplication> 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<CommandLineApplication> 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<CommandOption> 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<CommandArgument> 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<int> invoke) => Invoke = invoke;
public void OnExecute(Func<Task<int>> invoke) => Invoke = () => invoke().Result;
public int Execute(params string[] args)
{
var command = this;
IEnumerator<CommandArgument> 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<string> shortFormVersionGetter,
Func<string> 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(" [[--] <arg>...]]");
}
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<CommandOption> 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<CommandLineApplication> 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<CommandArgument> 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<string> ExpandResponseFiles(IEnumerable<string> 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<string> 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<CommandArgument>
{
private readonly IEnumerator<CommandArgument> _enumerator;
public CommandArgumentEnumerator(IEnumerator<CommandArgument> 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();
}
}
}

View File

@ -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);
}
}

View File

@ -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<string>();
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<string> 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');
}
}

View File

@ -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
}
}

View File

@ -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; }
}
}

View File

@ -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 Microsoft.DotNet.Cli.CommandLine;
namespace Microsoft.Extensions.ApiDescription.Tool.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;
}
}

View File

@ -0,0 +1,167 @@
// Copyright (c) .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 Microsoft.DotNet.Cli.CommandLine;
namespace Microsoft.Extensions.ApiDescription.Tool.Commands
{
internal class GetDocumentCommand : ProjectCommandBase
{
internal const string FallbackDocumentName = "v1";
internal const string FallbackMethod = "Generate";
internal const string FallbackService = "Microsoft.Extensions.ApiDescription.IDocumentProvider";
private CommandOption _documentName;
private CommandOption _method;
private CommandOption _output;
private CommandOption _service;
public override void Configure(CommandLineApplication command)
{
base.Configure(command);
_documentName = command.Option(
"--documentName <Name>",
Resources.FormatDocumentDescription(FallbackDocumentName));
_method = command.Option("--method <Name>", Resources.FormatMethodDescription(FallbackMethod));
_output = command.Option("--output <Path>", Resources.OutputDescription);
_service = command.Option("--service <QualifiedName>", Resources.FormatServiceDescription(FallbackService));
}
protected override void Validate()
{
base.Validate();
if (!_output.HasValue())
{
throw new CommandException(Resources.FormatMissingOption(_output.LongName));
}
if (_method.HasValue() && !_service.HasValue())
{
throw new CommandException(Resources.FormatMissingOption(_service.LongName));
}
if (_service.HasValue() && !_method.HasValue())
{
throw new CommandException(Resources.FormatMissingOption(_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 the application's code.
try
{
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(),
};
return GetDocumentCommandWorker.Process(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; }
}
}
}

View File

@ -0,0 +1,25 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.Extensions.ApiDescription.Tool.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; }
}
}

View File

@ -0,0 +1,167 @@
// Copyright (c) .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.Reflection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.Extensions.ApiDescription.Tool.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.FormatMissingEntryPoint(context.AssemblyPath));
return 2;
}
var services = GetServices(entryPointType, context.AssemblyPath, context.AssemblyName);
if (services == null)
{
return 3;
}
var success = TryProcess(context, services);
if (!success)
{
// As part of the aspnet/Mvc#8425 fix, return 4 here.
return 0;
}
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.FormatUsingDocument(documentName));
Reporter.WriteInformation(Resources.FormatUsingMethod(methodName));
Reporter.WriteInformation(Resources.FormatUsingService(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)
{
// As part of the aspnet/Mvc#8425 fix, make this an error unless the file already exists.
var message = Resources.FormatMethodInvocationFailed(methodName, serviceName, documentName);
Reporter.WriteWarning(message);
}
return success;
}
catch (Exception ex)
{
var message = FormatException(ex);
// As part of the aspnet/Mvc#8425 fix, make this an error unless the file already exists.
Reporter.WriteWarning(message);
return false;
}
}
// 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<string>() };
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;
}
}
return null;
}
private static string FormatException(Exception exception)
{
return $"{exception.GetType().FullName}: {exception.Message}";
}
}
}

View File

@ -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 Microsoft.Extensions.ApiDescription.Tool.Commands
{
internal class HelpCommandBase : CommandBase
{
public override void Configure(CommandLineApplication command)
{
base.Configure(command);
command.HelpOption("-h|--help");
}
}
}

View File

@ -0,0 +1,37 @@
// Copyright (c) .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 Microsoft.Extensions.ApiDescription.Tool.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 <PATH>", Resources.AssemblyDescription);
ToolsDirectory = command.Option("--tools-directory <PATH>", Resources.ToolsDirectoryDescription);
}
protected override void Validate()
{
base.Validate();
if (!AssemblyPath.HasValue())
{
throw new CommandException(Resources.FormatMissingOption(AssemblyPath.LongName));
}
if (!ToolsDirectory.HasValue())
{
throw new CommandException(Resources.FormatMissingOption(ToolsDirectory.LongName));
}
}
}
}

View File

@ -0,0 +1,22 @@
<Project Sdk="Internal.AspNetCore.Sdk">
<PropertyGroup>
<AssemblyName>GetDocument.Insider</AssemblyName>
<Description>GetDocument Command-line Tool inside man</Description>
<IsPackable>false</IsPackable>
<OutputType>Exe</OutputType>
<RootNamespace>Microsoft.Extensions.ApiDescription.Tool</RootNamespace>
<TargetFrameworks>netcoreapp2.0;net461</TargetFrameworks>
</PropertyGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net461'">
<Reference Include="Microsoft.CSharp" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="$(MicrosoftAspNetCoreHostingAbstractions20PackageVersion)" />
</ItemGroup>
<Target Name="BuildX86" AfterTargets="Build" Condition=" '$(TargetFramework)' == 'net461' And '$(Platform)' != 'x86' ">
<MSBuild Projects="$(MSBuildProjectFullPath)" Properties="TargetFramework=$(TargetFramework);Platform=x86" Targets="Build" />
</Target>
</Project>

View File

@ -0,0 +1,16 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Reflection;
namespace Microsoft.Extensions.ApiDescription.Tool
{
internal static class ProductInfo
{
public static string GetVersion()
=> typeof(ProductInfo)
.Assembly
.GetCustomAttribute<AssemblyInformationalVersionAttribute>()
.InformationalVersion;
}
}

View File

@ -0,0 +1,48 @@
// Copyright (c) .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 Microsoft.DotNet.Cli.CommandLine;
using Microsoft.Extensions.ApiDescription.Tool.Commands;
namespace Microsoft.Extensions.ApiDescription.Tool
{
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)
{
Reporter.WriteVerbose(ex.ToString());
}
else
{
Reporter.WriteInformation(ex.ToString());
}
Reporter.WriteError(ex.Message);
return 1;
}
}
}
}

View File

@ -0,0 +1,240 @@
// <auto-generated />
namespace Microsoft.Extensions.ApiDescription.Tool
{
using System.Globalization;
using System.Reflection;
using System.Resources;
internal static class Resources
{
private static readonly ResourceManager _resourceManager
= new ResourceManager("Microsoft.Extensions.ApiDescription.Tool.Resources", typeof(Resources).GetTypeInfo().Assembly);
/// <summary>
/// The assembly to use.
/// </summary>
internal static string AssemblyDescription
{
get => GetString("AssemblyDescription");
}
/// <summary>
/// The assembly to use.
/// </summary>
internal static string FormatAssemblyDescription()
=> GetString("AssemblyDescription");
/// <summary>
/// Missing required option '--{0}'.
/// </summary>
internal static string MissingOption
{
get => GetString("MissingOption");
}
/// <summary>
/// Missing required option '--{0}'.
/// </summary>
internal static string FormatMissingOption(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("MissingOption"), p0);
/// <summary>
/// Do not colorize output.
/// </summary>
internal static string NoColorDescription
{
get => GetString("NoColorDescription");
}
/// <summary>
/// Do not colorize output.
/// </summary>
internal static string FormatNoColorDescription()
=> GetString("NoColorDescription");
/// <summary>
/// The file to write the result to.
/// </summary>
internal static string OutputDescription
{
get => GetString("OutputDescription");
}
/// <summary>
/// The file to write the result to.
/// </summary>
internal static string FormatOutputDescription()
=> GetString("OutputDescription");
/// <summary>
/// Prefix console output with logging level.
/// </summary>
internal static string PrefixDescription
{
get => GetString("PrefixDescription");
}
/// <summary>
/// Prefix console output with logging level.
/// </summary>
internal static string FormatPrefixDescription()
=> GetString("PrefixDescription");
/// <summary>
/// Show verbose output.
/// </summary>
internal static string VerboseDescription
{
get => GetString("VerboseDescription");
}
/// <summary>
/// Show verbose output.
/// </summary>
internal static string FormatVerboseDescription()
=> GetString("VerboseDescription");
/// <summary>
/// Location from which inside man was copied (in the .NET Framework case) or loaded.
/// </summary>
internal static string ToolsDirectoryDescription
{
get => GetString("ToolsDirectoryDescription");
}
/// <summary>
/// Location from which inside man was copied (in the .NET Framework case) or loaded.
/// </summary>
internal static string FormatToolsDirectoryDescription()
=> GetString("ToolsDirectoryDescription");
/// <summary>
/// The name of the method to invoke on the '--service' instance. Default value '{0}'.
/// </summary>
internal static string MethodDescription
{
get => GetString("MethodDescription");
}
/// <summary>
/// The name of the method to invoke on the '--service' instance. Default value '{0}'.
/// </summary>
internal static string FormatMethodDescription(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("MethodDescription"), p0);
/// <summary>
/// The qualified name of the service type to retrieve from dependency injection. Default value '{0}'.
/// </summary>
internal static string ServiceDescription
{
get => GetString("ServiceDescription");
}
/// <summary>
/// The qualified name of the service type to retrieve from dependency injection. Default value '{0}'.
/// </summary>
internal static string FormatServiceDescription(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("ServiceDescription"), p0);
/// <summary>
/// The name of the document to pass to the '--method' method. Default value '{0}'.
/// </summary>
internal static string DocumentDescription
{
get => GetString("DocumentDescription");
}
/// <summary>
/// The name of the document to pass to the '--method' method. Default value '{0}'.
/// </summary>
internal static string FormatDocumentDescription(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("DocumentDescription"), p0);
/// <summary>
/// Using document name '{0}'.
/// </summary>
internal static string UsingDocument
{
get => GetString("UsingDocument");
}
/// <summary>
/// Using document name '{0}'.
/// </summary>
internal static string FormatUsingDocument(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("UsingDocument"), p0);
/// <summary>
/// Using method '{0}'.
/// </summary>
internal static string UsingMethod
{
get => GetString("UsingMethod");
}
/// <summary>
/// Using method '{0}'.
/// </summary>
internal static string FormatUsingMethod(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("UsingMethod"), p0);
/// <summary>
/// Using service '{0}'.
/// </summary>
internal static string UsingService
{
get => GetString("UsingService");
}
/// <summary>
/// Using service '{0}'.
/// </summary>
internal static string FormatUsingService(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("UsingService"), p0);
/// <summary>
/// Method '{0}' of service '{1}' failed to generate document '{2}'.
/// </summary>
internal static string MethodInvocationFailed
{
get => GetString("MethodInvocationFailed");
}
/// <summary>
/// Method '{0}' of service '{1}' failed to generate document '{2}'.
/// </summary>
internal static string FormatMethodInvocationFailed(object p0, object p1, object p2)
=> string.Format(CultureInfo.CurrentCulture, GetString("MethodInvocationFailed"), p0, p1, p2);
/// <summary>
/// Assembly '{0}' does not contain an entry point.
/// </summary>
internal static string MissingEntryPoint
{
get => GetString("MissingEntryPoint");
}
/// <summary>
/// Assembly '{0}' does not contain an entry point.
/// </summary>
internal static string FormatMissingEntryPoint(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("MissingEntryPoint"), p0);
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);
System.Diagnostics.Debug.Assert(value != null);
if (formatterNames != null)
{
for (var i = 0; i < formatterNames.Length; i++)
{
value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}");
}
}
return value;
}
}
}

View File

@ -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 Microsoft.Extensions.ApiDescription.Tool.AnsiConstants;
namespace Microsoft.Extensions.ApiDescription.Tool
{
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<string, string> 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);
}
}
}
}

View File

@ -0,0 +1,165 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="AssemblyDescription" xml:space="preserve">
<value>The assembly to use.</value>
</data>
<data name="MissingOption" xml:space="preserve">
<value>Missing required option '--{0}'.</value>
</data>
<data name="NoColorDescription" xml:space="preserve">
<value>Do not colorize output.</value>
</data>
<data name="OutputDescription" xml:space="preserve">
<value>The file to write the result to.</value>
</data>
<data name="PrefixDescription" xml:space="preserve">
<value>Prefix console output with logging level.</value>
</data>
<data name="VerboseDescription" xml:space="preserve">
<value>Show verbose output.</value>
</data>
<data name="ToolsDirectoryDescription" xml:space="preserve">
<value>Location from which inside man was copied (in the .NET Framework case) or loaded.</value>
</data>
<data name="MethodDescription" xml:space="preserve">
<value>The name of the method to invoke on the '--service' instance. Default value '{0}'.</value>
</data>
<data name="ServiceDescription" xml:space="preserve">
<value>The qualified name of the service type to retrieve from dependency injection. Default value '{0}'.</value>
</data>
<data name="DocumentDescription" xml:space="preserve">
<value>The name of the document to pass to the '--method' method. Default value '{0}'.</value>
</data>
<data name="UsingDocument" xml:space="preserve">
<value>Using document name '{0}'.</value>
</data>
<data name="UsingMethod" xml:space="preserve">
<value>Using method '{0}'.</value>
</data>
<data name="UsingService" xml:space="preserve">
<value>Using service '{0}'.</value>
</data>
<data name="MethodInvocationFailed" xml:space="preserve">
<value>Method '{0}' of service '{1}' failed to generate document '{2}'.</value>
</data>
<data name="MissingEntryPoint" xml:space="preserve">
<value>Assembly '{0}' does not contain an entry point.</value>
</data>
</root>

View File

@ -1,4 +1,7 @@
using System;
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.AspNetCore.Mvc.Razor
{

View File

@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Net;

View File

@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;

View File

@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;

View File

@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;

View File

@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;

View File

@ -0,0 +1,227 @@
// Copyright (c) .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.Net.Sockets;
using System.Reflection;
using System.Security.Cryptography;
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 Microsoft.Extensions.ApiDescription.Tasks
{
/// <summary>
/// Downloads a file.
/// </summary>
public class DownloadFile : Utilities.Task, ICancelableTask
{
private readonly CancellationTokenSource _cts = new CancellationTokenSource();
/// <summary>
/// The URI to download.
/// </summary>
[Required]
public string Uri { get; set; }
/// <summary>
/// Destination for the downloaded file. If the file already exists, it is not re-downloaded unless
/// <see cref="Overwrite"/> is true.
/// </summary>
[Required]
public string DestinationPath { get; set; }
/// <summary>
/// Should <see cref="DestinationPath"/> be overwritten. When <c>true</c>, the file is downloaded and its hash
/// compared to the existing file. If those hashes do not match (or <see cref="DestinationPath"/> does not
/// exist), <see cref="DestinationPath"/> is overwritten.
/// </summary>
public bool Overwrite { get; set; }
/// <summary>
/// The maximum amount of time in seconds to allow for downloading the file. Defaults to 2 minutes.
/// </summary>
public int TimeoutSeconds { get; set; } = 60 * 2;
/// <inheritdoc/>
public void Cancel() => _cts.Cancel();
/// <inheritdoc/>
public override bool Execute() => ExecuteAsync().Result;
public async Task<bool> 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(MessageImportance.High, $"Downloading '{uri}' to '{destinationPath}'.");
using (var httpClient = new HttpClient())
{
await DownloadAsync(uri, destinationPath, httpClient, cancellationToken, log, timeoutSeconds);
}
}
public static async Task DownloadAsync(
string uri,
string destinationPath,
HttpClient httpClient,
CancellationToken cancellationToken,
TaskLoggingHelper log,
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.");
}
using (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.Length == destinationHash.Length;
for (var i = 0; sameHashes && i < downloadHash.Length; i++)
{
sameHashes = downloadHash[i] == destinationHash[i];
}
if (sameHashes)
{
log.LogMessage($"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.CreateDirectory(destinationDirectory);
}
}
// Create or overwrite the destination file.
reachedCopy = true;
using (var outStream = File.Create(destinationPath))
{
await responseStream.CopyToAsync(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.LogErrorFromException(ex, showStackTrace: true);
if (reachedCopy)
{
File.Delete(destinationPath);
}
}
}
private static byte[] GetHash(Stream stream)
{
SHA256 algorithm;
try
{
algorithm = SHA256.Create();
}
catch (TargetInvocationException)
{
// SHA256.Create is documented to throw this exception on FIPS-compliant machines. See
// https://msdn.microsoft.com/en-us/library/z08hz7ad Fall back to a FIPS-compliant SHA256 algorithm.
algorithm = new SHA256CryptoServiceProvider();
}
using (algorithm)
{
return algorithm.ComputeHash(stream);
}
}
}
}

View File

@ -0,0 +1,34 @@
// Copyright (c) .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.Build.Framework;
using Microsoft.Build.Utilities;
namespace Microsoft.Extensions.ApiDescription.Tasks
{
/// <summary>
/// Restore <see cref="ITaskItem"/>s from given property value.
/// </summary>
public class GetCurrentItems : Task
{
/// <summary>
/// The property value to deserialize.
/// </summary>
[Required]
public string Input { get; set; }
/// <summary>
/// The restored <see cref="ITaskItem"/>s. Will never contain more than one item.
/// </summary>
[Output]
public ITaskItem[] Outputs { get; set; }
/// <inheritdoc />
public override bool Execute()
{
Outputs = new[] { MetadataSerializer.DeserializeMetadata(Input) };
return true;
}
}
}

View File

@ -0,0 +1,132 @@
// Copyright (c) .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 Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
namespace Microsoft.Extensions.ApiDescription.Tasks
{
/// <summary>
/// Adds or corrects ClassName, Namespace and OutputPath metadata in ServiceFileReference items. Also stores final
/// metadata as SerializedMetadata.
/// </summary>
public class GetFileReferenceMetadata : Task
{
private const string TypeScriptLanguageName = "TypeScript";
/// <summary>
/// Extension to use in default OutputPath metadata value. Ignored when generating TypeScript.
/// </summary>
[Required]
public string Extension { get; set; }
/// <summary>
/// Default Namespace metadata value.
/// </summary>
[Required]
public string Namespace { get; set; }
/// <summary>
/// Default directory for OutputPath values.
/// </summary>
public string OutputDirectory { get; set; }
/// <summary>
/// The ServiceFileReference items to update.
/// </summary>
[Required]
public ITaskItem[] Inputs { get; set; }
/// <summary>
/// The updated ServiceFileReference items. Will include ClassName, Namespace and OutputPath metadata.
/// </summary>
[Output]
public ITaskItem[] Outputs{ get; set; }
/// <inheritdoc />
public override bool Execute()
{
var outputs = new List<ITaskItem>(Inputs.Length);
var destinations = new HashSet<string>();
foreach (var item in Inputs)
{
var newItem = new TaskItem(item);
outputs.Add(newItem);
var codeGenerator = item.GetMetadata("CodeGenerator");
if (string.IsNullOrEmpty("CodeGenerator"))
{
// This case occurs when user forgets to specify the required metadata. We have no default here.
string type;
if (!string.IsNullOrEmpty(item.GetMetadata("SourceProject")))
{
type = "ServiceProjectReference";
}
else if (!string.IsNullOrEmpty(item.GetMetadata("SourceUri")))
{
type = "ServiceUriReference";
}
else
{
type = "ServiceFileReference";
}
Log.LogError(Resources.FormatInvalidEmptyMetadataValue("CodeGenerator", type, item.ItemSpec));
}
var className = item.GetMetadata("ClassName");
if (string.IsNullOrEmpty(className))
{
var filename = item.GetMetadata("Filename");
className = $"{filename}Client";
if (char.IsLower(className[0]))
{
className = char.ToUpper(className[0]) + className.Substring(startIndex: 1);
}
MetadataSerializer.SetMetadata(newItem, "ClassName", className);
}
var @namespace = item.GetMetadata("Namespace");
if (string.IsNullOrEmpty(@namespace))
{
MetadataSerializer.SetMetadata(newItem, "Namespace", Namespace);
}
var outputPath = item.GetMetadata("OutputPath");
if (string.IsNullOrEmpty(outputPath))
{
var isTypeScript = codeGenerator.EndsWith(TypeScriptLanguageName, StringComparison.OrdinalIgnoreCase);
outputPath = $"{className}{(isTypeScript ? ".ts" : Extension)}";
}
// Place output file in correct directory (relative to project directory).
if (!Path.IsPathRooted(outputPath) && !string.IsNullOrEmpty(OutputDirectory))
{
outputPath = Path.Combine(OutputDirectory, outputPath);
}
if (!destinations.Add(outputPath))
{
// This case may occur when user is experimenting e.g. with multiple code generators or options.
// May also occur when user accidentally duplicates OutputPath metadata.
Log.LogError(Resources.FormatDuplicateFileOutputPaths(outputPath));
}
MetadataSerializer.SetMetadata(newItem, "OutputPath", outputPath);
// Add metadata which may be used as a property and passed to an inner build.
newItem.RemoveMetadata("SerializedMetadata");
newItem.SetMetadata("SerializedMetadata", MetadataSerializer.SerializeMetadata(newItem));
}
Outputs = outputs.ToArray();
return !Log.HasLoggedErrors;
}
}
}

View File

@ -0,0 +1,103 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.IO;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
namespace Microsoft.Extensions.ApiDescription.Tasks
{
/// <summary>
/// Adds or corrects DocumentPath and project-related metadata in ServiceProjectReference items. Also stores final
/// metadata as SerializedMetadata.
/// </summary>
public class GetProjectReferenceMetadata : Task
{
/// <summary>
/// Default directory for DocumentPath values.
/// </summary>
public string DocumentDirectory { get; set; }
/// <summary>
/// The ServiceFileReference items to update.
/// </summary>
[Required]
public ITaskItem[] Inputs { get; set; }
/// <summary>
/// The updated ServiceFileReference items. Will include Namespace and OutputPath metadata. OutputPath metadata
/// will contain full paths.
/// </summary>
[Output]
public ITaskItem[] Outputs{ get; set; }
/// <inheritdoc />
public override bool Execute()
{
var outputs = new List<ITaskItem>(Inputs.Length);
var destinations = new HashSet<string>();
foreach (var item in Inputs)
{
var newItem = new TaskItem(item);
outputs.Add(newItem);
var documentGenerator = item.GetMetadata("DocumentGenerator");
if (string.IsNullOrEmpty(documentGenerator))
{
// This case occurs when user overrides the default metadata.
Log.LogError(Resources.FormatInvalidEmptyMetadataValue(
"DocumentGenerator",
"ServiceProjectReference",
item.ItemSpec));
}
var documentPath = item.GetMetadata("DocumentPath");
if (string.IsNullOrEmpty(documentPath))
{
var filename = item.GetMetadata("Filename");
var documentName = item.GetMetadata("DocumentName");
if (string.IsNullOrEmpty(documentName))
{
documentName = "v1";
}
documentPath = $"{filename}.{documentName}.json";
}
documentPath = GetFullPath(documentPath);
MetadataSerializer.SetMetadata(newItem, "DocumentPath", documentPath);
if (!destinations.Add(documentPath))
{
// This case may occur when user is experimenting e.g. with multiple generators or options.
// May also occur when user accidentally duplicates DocumentPath metadata.
Log.LogError(Resources.FormatDuplicateProjectDocumentPaths(documentPath));
}
// Add metadata which may be used as a property and passed to an inner build.
newItem.SetMetadata("SerializedMetadata", MetadataSerializer.SerializeMetadata(newItem));
}
Outputs = outputs.ToArray();
return !Log.HasLoggedErrors;
}
private string GetFullPath(string path)
{
if (!Path.IsPathRooted(path))
{
if (!string.IsNullOrEmpty(DocumentDirectory))
{
path = Path.Combine(DocumentDirectory, path);
}
path = Path.GetFullPath(path);
}
return path;
}
}
}

View File

@ -0,0 +1,128 @@
// Copyright (c) .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 Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
namespace Microsoft.Extensions.ApiDescription.Tasks
{
/// <summary>
/// Adds or corrects DocumentPath metadata in ServiceUriReference items.
/// </summary>
public class GetUriReferenceMetadata : Task
{
/// <summary>
/// Default directory for DocumentPath metadata values.
/// </summary>
public string DocumentDirectory { get; set; }
/// <summary>
/// The ServiceUriReference items to update.
/// </summary>
[Required]
public ITaskItem[] Inputs { get; set; }
/// <summary>
/// The updated ServiceUriReference items. Will include DocumentPath metadata with full paths.
/// </summary>
[Output]
public ITaskItem[] Outputs{ get; set; }
/// <inheritdoc />
public override bool Execute()
{
var outputs = new List<ITaskItem>(Inputs.Length);
var destinations = new HashSet<string>();
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);
MetadataSerializer.SetMetadata(newItem, "DocumentPath", documentPath);
if (!destinations.Add(documentPath))
{
// This case may occur when user is experimenting e.g. with multiple code generators or options.
// May also occur when user accidentally duplicates DocumentPath metadata.
Log.LogError(Resources.FormatDuplicateUriDocumentPaths(documentPath));
}
}
Outputs = outputs.ToArray();
return !Log.HasLoggedErrors;
}
private string GetFullPath(string path)
{
if (!Path.IsPathRooted(path))
{
if (!string.IsNullOrEmpty(DocumentDirectory))
{
path = Path.Combine(DocumentDirectory, path);
}
path = Path.GetFullPath(path);
}
return path;
}
}
}

View File

@ -0,0 +1,147 @@
// Copyright (c) .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.Text;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
namespace Microsoft.Extensions.ApiDescription.Tasks
{
/// <summary>
/// Utility methods to serialize and deserialize <see cref="ITaskItem"/> metadata.
/// </summary>
/// <remarks>
/// Based on and uses the same escaping as
/// https://github.com/Microsoft/msbuild/blob/e70a3159d64f9ed6ec3b60253ef863fa883a99b1/src/Shared/EscapingUtilities.cs
/// </remarks>
public static class MetadataSerializer
{
private static readonly char[] CharsToEscape = { '%', '*', '?', '@', '$', '(', ')', ';', '\'' };
private static readonly HashSet<char> CharsToEscapeHash = new HashSet<char>(CharsToEscape);
/// <summary>
/// Add the given <paramref name="key"/> and <paramref name="value"/> to the <paramref name="item"/>. Or,
/// modify existing value to be <paramref name="value"/>.
/// </summary>
/// <param name="item">The <see cref="ITaskItem"/> to update.</param>
/// <param name="key">The name of the new metadata.</param>
/// <param name="value">The value of the new metadata. Assumed to be unescaped.</param>
/// <remarks>Uses same hex-encoded format as MSBuild's EscapeUtilities.</remarks>
public static void SetMetadata(ITaskItem item, string key, string value)
{
if (item is ITaskItem2 item2)
{
item2.SetMetadataValueLiteral(key, value);
return;
}
if (value.IndexOfAny(CharsToEscape) == -1)
{
item.SetMetadata(key, value);
return;
}
var builder = new StringBuilder();
EscapeValue(value, builder);
item.SetMetadata(key, builder.ToString());
}
/// <summary>
/// Serialize metadata for use as a property value passed into an inner build.
/// </summary>
/// <param name="item">The item to serialize.</param>
/// <returns>A <see cref="string"/> containing the serialized metadata.</returns>
/// <remarks>Uses same hex-encoded format as MSBuild's EscapeUtilities.</remarks>
public static string SerializeMetadata(ITaskItem item)
{
var builder = new StringBuilder();
if (item is ITaskItem2 item2)
{
builder.Append($"Identity={item2.EvaluatedIncludeEscaped}");
var metadata = item2.CloneCustomMetadataEscaped();
foreach (var key in metadata.Keys)
{
var value = metadata[key];
builder.Append($"|{key.ToString()}={value.ToString()}");
}
}
else
{
builder.Append($"Identity=");
EscapeValue(item.ItemSpec, builder);
var metadata = item.CloneCustomMetadata();
foreach (var key in metadata.Keys)
{
builder.Append($"|{key.ToString()}=");
var value = metadata[key];
EscapeValue(value.ToString(), builder);
}
}
return builder.ToString();
}
/// <summary>
/// Recreate an <see cref="ITaskItem"/> with metadata encoded in given <paramref name="value"/>.
/// </summary>
/// <param name="value">The serialized metadata.</param>
/// <returns>The deserialized <see cref="ITaskItem"/>.</returns>
public static ITaskItem DeserializeMetadata(string value)
{
var metadata = value.Split('|');
var item = new TaskItem();
// TaskItem implements ITaskITem2 explicitly and ITaskItem implicitly.
var item2 = (ITaskItem2)item;
foreach (var segment in metadata)
{
var keyAndValue = segment.Split(new[] { '=' }, count: 2);
if (string.Equals("Identity", keyAndValue[0]))
{
item2.EvaluatedIncludeEscaped = keyAndValue[1];
continue;
}
item2.SetMetadata(keyAndValue[0], keyAndValue[1]);
}
return item;
}
private static void EscapeValue(string value, StringBuilder builder)
{
if (string.IsNullOrEmpty(value))
{
builder.Append(value);
return;
}
if (value.IndexOfAny(CharsToEscape) == -1)
{
builder.Append(value);
return;
}
foreach (var @char in value)
{
if (CharsToEscapeHash.Contains(@char))
{
builder.Append('%');
builder.Append(HexDigitChar(@char / 0x10));
builder.Append(HexDigitChar(@char & 0x0F));
continue;
}
builder.Append(@char);
}
}
private static char HexDigitChar(int x)
{
return (char)(x + (x < 10 ? '0' : ('a' - 10)));
}
}
}

View File

@ -0,0 +1,91 @@
<Project Sdk="Internal.AspNetCore.Sdk">
<PropertyGroup>
<!-- Execute PopulateNuspec fairly late. -->
<GenerateNuspecDependsOn>$(GenerateNuspecDependsOn);PopulateNuspec</GenerateNuspecDependsOn>
<!-- Do not complain about lack of lib folder. -->
<NoPackageAnalysis>true</NoPackageAnalysis>
<AssemblyName>Microsoft.Extensions.ApiDescription.Tasks</AssemblyName>
<Description>MSBuild tasks and targets for code generation</Description>
<EnableApiCheck>false</EnableApiCheck>
<IncludeBuildOutput>false</IncludeBuildOutput>
<IncludeSource>false</IncludeSource>
<IncludeSymbols>false</IncludeSymbols>
<NuspecFile>$(MSBuildProjectName).nuspec</NuspecFile>
<PackageId>$(MSBuildProjectName)</PackageId>
<PackageTags>Build Tasks;MSBuild;Swagger;Open API;code generation; Web API client</PackageTags>
<RootNamespace>$(AssemblyName)</RootNamespace>
<TargetFrameworks>netstandard2.0;net461</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Build.Utilities.Core"
Version="$(MicrosoftBuildUtilitiesCorePackageVersion)" />
<PackageReference Include="System.Net.Http"
Condition="'$(TargetFramework)' == 'net461'"
Version="$(SystemNetHttpPackageVersion)" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' != ''">
<SignedPackageFile Include="$(TargetPath)">
<Certificate>$(AssemblySigningCertName)</Certificate>
<PackagePath>tasks/$(TargetFramework)/$(TargetFileName)</PackagePath>
<StrongName>$(AssemblySigningStrongName)</StrongName>
</SignedPackageFile>
</ItemGroup>
<!-- Add other signed files in a single inner build, avoiding duplications in this multi-TFM project. -->
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
<SignedPackageFile Include="../dotnet-getdocument/bin/$(Configuration)/netcoreapp2.1/publish/dotnet-getdocument.dll">
<Certificate>$(AssemblySigningCertName)</Certificate>
<PackagePath>tools/dotnet-getdocument.dll</PackagePath>
<StrongName>$(AssemblySigningStrongName)</StrongName>
</SignedPackageFile>
<SignedPackageFile Include="../GetDocumentInsider/bin/$(Configuration)/net461/GetDocument.Insider.exe">
<Certificate>$(AssemblySigningCertName)</Certificate>
<PackagePath>tools/net461/GetDocument.Insider.exe</PackagePath>
<StrongName>$(AssemblySigningStrongName)</StrongName>
</SignedPackageFile>
<SignedPackageFile Include="../GetDocumentInsider/bin/x86/$(Configuration)/net461/GetDocument.Insider.exe">
<Certificate>$(AssemblySigningCertName)</Certificate>
<PackagePath>tools/net461-x86/GetDocument.Insider.exe</PackagePath>
<StrongName>$(AssemblySigningStrongName)</StrongName>
</SignedPackageFile>
<SignedPackageFile Include="../GetDocumentInsider/bin/$(Configuration)/netcoreapp2.0/GetDocument.Insider.dll">
<Certificate>$(AssemblySigningCertName)</Certificate>
<PackagePath>tools/netcoreapp2.0/GetDocument.Insider.exe</PackagePath>
<StrongName>$(AssemblySigningStrongName)</StrongName>
</SignedPackageFile>
<SignedPackageFile Include="../dotnet-getdocument/bin/$(Configuration)/netcoreapp2.1/publish/Newtonsoft.Json.dll">
<PackagePath>tools/Newtonsoft.Json.dll"</PackagePath>
<Certificate>$(AssemblySigning3rdPartyCertName)"</Certificate>
</SignedPackageFile>
</ItemGroup>
<Target Name="PopulateNuspec">
<MSBuild Projects="../dotnet-getdocument/dotnet-getdocument.csproj"
BuildInParallel="$(BuildInParallel)"
RemoveProperties="RuntimeIdentifier;TargetFrameworks;TargetFramework"
Targets="Publish" />
<PropertyGroup>
<NuspecProperties>
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);
</NuspecProperties>
</PropertyGroup>
</Target>
</Project>

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
<metadata>
<id>$id$</id>
<authors>$authors$</authors>
<copyright>$copyright$</copyright>
<description>$description$</description>
<developmentDependency>true</developmentDependency>
<iconUrl>$iconUrl$</iconUrl>
<licenseUrl>$licenseUrl$</licenseUrl>
<minClientVersion>2.8</minClientVersion>
<owners>$owners$</owners>
<projectUrl>$projectUrl$</projectUrl>
<repository type="git" url="$repositoryUrl$" commit="$repositoryCommit$" />
<requireLicenseAcceptance>true</requireLicenseAcceptance>
<tags>$tags$</tags>
<version>$version$</version>
</metadata>
<files>
<file src="build\*" target="build" />
<file src="buildMultiTargeting\*" target="buildMultiTargeting" />
<file src="bin\$configuration$\net461\Microsoft.Extensions.ApiDescription.Tasks.*" target="tasks\net461" />
<file src="bin\$configuration$\netstandard2.0\Microsoft.Extensions.ApiDescription.Tasks.*" target="tasks\netstandard2.0" />
<file src="..\dotnet-getdocument\bin\$configuration$\netcoreapp2.1\publish\*.*" target="tools" />
<file src="..\GetDocumentInsider\bin\$configuration$\net461\GetDocument.Insider.*" target="tools\net461" />
<file src="..\GetDocumentInsider\bin\x86\$configuration$\net461\GetDocument.Insider.*" target="tools\net461-x86" />
<file src="..\GetDocumentInsider\bin\$configuration$\netcoreapp2.0\GetDocument.Insider.*" target="tools\netcoreapp2.0" />
</files>
</package>

View File

@ -0,0 +1,86 @@
// <auto-generated />
namespace Microsoft.Extensions.ApiDescription.Tasks
{
using System.Globalization;
using System.Reflection;
using System.Resources;
internal static class Resources
{
private static readonly ResourceManager _resourceManager
= new ResourceManager("Microsoft.Extensions.ApiDescription.Tasks.Resources", typeof(Resources).GetTypeInfo().Assembly);
/// <summary>
/// Multiple items have OutputPath='{0}'. All ServiceFileReference, ServiceProjectReference and ServiceUriReference items must have unique OutputPath metadata.
/// </summary>
internal static string DuplicateFileOutputPaths
{
get => GetString("DuplicateFileOutputPaths");
}
/// <summary>
/// Multiple items have OutputPath='{0}'. All ServiceFileReference, ServiceProjectReference and ServiceUriReference items must have unique OutputPath metadata.
/// </summary>
internal static string FormatDuplicateFileOutputPaths(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("DuplicateFileOutputPaths"), p0);
/// <summary>
/// Mutliple ServiceProjectReference items have DocumentPath='{0}'. ServiceProjectReference items must have unique DocumentPath metadata.
/// </summary>
internal static string DuplicateProjectDocumentPaths
{
get => GetString("DuplicateProjectDocumentPaths");
}
/// <summary>
/// Mutliple ServiceProjectReference items have DocumentPath='{0}'. ServiceProjectReference items must have unique DocumentPath metadata.
/// </summary>
internal static string FormatDuplicateProjectDocumentPaths(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("DuplicateProjectDocumentPaths"), p0);
/// <summary>
/// Mutliple ServiceUriReference items have DocumentPath='{0}'. ServiceUriReference items must have unique DocumentPath metadata.
/// </summary>
internal static string DuplicateUriDocumentPaths
{
get => GetString("DuplicateUriDocumentPaths");
}
/// <summary>
/// Mutliple ServiceUriReference items have DocumentPath='{0}'. ServiceUriReference items must have unique DocumentPath metadata.
/// </summary>
internal static string FormatDuplicateUriDocumentPaths(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("DuplicateUriDocumentPaths"), p0);
/// <summary>
/// Invalid {0} metadata value for {1} item '{2}'. {0} metadata must not be set to the empty string.
/// </summary>
internal static string InvalidEmptyMetadataValue
{
get => GetString("InvalidEmptyMetadataValue");
}
/// <summary>
/// Invalid {0} metadata value for {1} item '{2}'. {0} metadata must not be set to the empty string.
/// </summary>
internal static string FormatInvalidEmptyMetadataValue(object p0, object p1, object p2)
=> string.Format(CultureInfo.CurrentCulture, GetString("InvalidEmptyMetadataValue"), p0, p1, p2);
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);
System.Diagnostics.Debug.Assert(value != null);
if (formatterNames != null)
{
for (var i = 0; i < formatterNames.Length; i++)
{
value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}");
}
}
return value;
}
}
}

View File

@ -0,0 +1,134 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="DuplicateFileOutputPaths" xml:space="preserve">
<value>Multiple items have OutputPath='{0}'. All ServiceFileReference, ServiceProjectReference and ServiceUriReference items must have unique OutputPath metadata.</value>
<comment>ServiceProjectReference and ServiceUriReference items become ServiceFileReference items and all ServiceFileReference items must have unique OutputPath metadata.</comment>
</data>
<data name="DuplicateProjectDocumentPaths" xml:space="preserve">
<value>Mutliple ServiceProjectReference items have DocumentPath='{0}'. ServiceProjectReference items must have unique DocumentPath metadata.</value>
</data>
<data name="DuplicateUriDocumentPaths" xml:space="preserve">
<value>Mutliple ServiceUriReference items have DocumentPath='{0}'. ServiceUriReference items must have unique DocumentPath metadata.</value>
<comment>Ignore corner case of ServiceProjectReference and ServiceUriReference items having the same DocumentPath.</comment>
</data>
<data name="InvalidEmptyMetadataValue" xml:space="preserve">
<value>Invalid {0} metadata value for {1} item '{2}'. {0} metadata must not be set to the empty string.</value>
</data>
</root>

View File

@ -0,0 +1,140 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<Project>
<PropertyGroup>
<_ApiDescriptionTasksAssemblyTarget
Condition="'$(MSBuildRuntimeType)' == 'Core'">netstandard2.0</_ApiDescriptionTasksAssemblyTarget>
<_ApiDescriptionTasksAssemblyTarget
Condition="'$(MSBuildRuntimeType)' != 'Core'">net461</_ApiDescriptionTasksAssemblyTarget>
<_ApiDescriptionTasksAssemblyPath>$(MSBuildThisFileDirectory)/../tasks/$(_ApiDescriptionTasksAssemblyTarget)/Microsoft.Extensions.ApiDescription.Tasks.dll</_ApiDescriptionTasksAssemblyPath>
<_ApiDescriptionTasksAssemblyTarget />
</PropertyGroup>
<UsingTask TaskName="GetCurrentItems" AssemblyFile="$(_ApiDescriptionTasksAssemblyPath)" />
<UsingTask TaskName="GetFileReferenceMetadata" AssemblyFile="$(_ApiDescriptionTasksAssemblyPath)" />
<UsingTask TaskName="GetProjectReferenceMetadata" AssemblyFile="$(_ApiDescriptionTasksAssemblyPath)" />
<UsingTask TaskName="GetUriReferenceMetadata" AssemblyFile="$(_ApiDescriptionTasksAssemblyPath)" />
<UsingTask TaskName="Microsoft.Extensions.ApiDescription.Tasks.DownloadFile"
AssemblyFile="$(_ApiDescriptionTasksAssemblyPath)" />
<!--
Settings users may update as they see fit. All $(...Directory) values are interpreted relative to the client
project folder, unless already an absolute path.
-->
<PropertyGroup>
<ServiceProjectReferenceCheckIfNewer
Condition="'$(ServiceProjectReferenceCheckIfNewer)' == ''">true</ServiceProjectReferenceCheckIfNewer>
<ServiceProjectReferenceDirectory
Condition="'$(ServiceProjectReferenceDirectory)' != ''">$([MSBuild]::EnsureTrailingSlash('$(ServiceProjectReferenceDirectory)'))</ServiceProjectReferenceDirectory>
<ServiceUriReferenceCheckIfNewer
Condition="'$(ServiceUriReferenceCheckIfNewer)' == ''">true</ServiceUriReferenceCheckIfNewer>
<ServiceUriReferenceDirectory
Condition="'$(ServiceUriReferenceDirectory)' != ''">$([MSBuild]::EnsureTrailingSlash('$(ServiceUriReferenceDirectory)'))</ServiceUriReferenceDirectory>
<ServiceFileReferenceCheckIfNewer
Condition="'$(ServiceFileReferenceCheckIfNewer)' == ''">true</ServiceFileReferenceCheckIfNewer>
<ServiceFileReferenceDirectory
Condition="'$(ServiceFileReferenceDirectory)' != ''">$([MSBuild]::EnsureTrailingSlash('$(ServiceFileReferenceDirectory)'))</ServiceFileReferenceDirectory>
<GenerateDefaultDocumentDefaultOptions Condition="'$(GenerateDefaultDocumentDefaultOptions)' == ''" />
</PropertyGroup>
<!--
Well-known metadata of the code and document generator item groups. ServiceProjectReference and ServiceUriReference
items may also include ServiceFileReference metadata.
-->
<ItemDefinitionGroup>
<ServiceProjectReference>
<!--
Name of the API description document generator. Builds will invoke a target named
"Generate%(DocumentGenerator)Document" to do actual document retrieval / generation.
-->
<DocumentGenerator>Default</DocumentGenerator>
<!-- Server project metadata which is likely applicable to all document generators. -->
<!--
Server project's chosen configuration. Corresponds to $(Configuration) which likely matches client project's
$(Configuration).
-->
<Configuration />
<!--
Server project's extensions path. Corresponds to $(MSBuildProjectExtensionsPath). User must set this if
server project's value is not 'obj/'.
-->
<ProjectExtensionsPath />
<!-- Server project's target framework. Defaults to $(TargetFramework) or first of $(TargetFrameworks). -->
<TargetFramework />
<!--
Full path of the server project's generated assembly. Corresponds to $(TargetPath). Because common code builds
server projects, file exists prior to document generator invocation.
-->
<TargetPath />
<!--
Semicolon-separated list of targets in the server project that should be built. Default is empty, indicating
the default targets of the server project. Does not honor $(ProjectReferenceBuildTargets) because that property
is too general for these references and it's normally empty too.
-->
<Targets />
<!--
Metadata specific to the Default document generator (though other document generators are free to use it).
-->
<!--
Options added to Default document generator tool's command line. Defaults to
$(GenerateDefaultDocumentDefaultOptions) if that is set in the client project.
-->
<GenerateDefaultDocumentOptions />
<!--
Name of the document to generate. Passed to the %(Method) when using Default document generator. Default is set
in server project, falling back to "v1".
-->
<DocumentName />
<!--
Full path where the API description document is placed. Default filename is %(Filename).%(DocumentName).json.
Filenames and relative paths (if explicitly set) are combined with $(ServiceProjectReferenceDirectory).
-->
<DocumentPath />
<!--
Method Default document generator should invoke on the %(Service) to generate document.
Default is set in server project, falling back to "Generate".
-->
<Method />
<!--
Service Default document generator should retrieve from DI to generate document.
Default is set in server project, falling back to "Microsoft.Extensions.ApiDescription.IDocumentProvider".
-->
<Service />
</ServiceProjectReference>
<ServiceUriReference>
<!--
Full path where the API description document is placed. Default filename is based on %(Identity).
Filenames and relative paths (if explicitly set) are combined with $(ServiceUriReferenceDirectory).
-->
<DocumentPath />
</ServiceUriReference>
<ServiceFileReference>
<!-- Name of the class to generate. Defaults to %(Filename)Client but with an uppercase first letter. -->
<ClassName />
<!--
Code generator to use. Required and must end with "CSharp" or "TypeScript" (the currently-supported target
languages) unless %(OutputPath) is set. Builds will invoke a target named "Generate%(CodeGenerator)" to do
actual code generation.
-->
<CodeGenerator />
<!--
Namespace to contain generated class. Default is $(RootNamespace).
-->
<Namespace />
<!--
Path to place generated code. Code generator may interpret path as a filename or directory. Default filename or
folder name is %(ClassName).[cs|ts]. Filenames and relative paths (if explicitly set) are combined with
$(ServiceFileReferenceDirectory). Final value (depending on $(ServiceFileReferenceDirectory)) is likely to be
a path relative to the client project.
-->
<OutputPath />
</ServiceFileReference>
</ItemDefinitionGroup>
</Project>

View File

@ -0,0 +1,335 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<Project>
<!-- Internal settings. Not intended for customization. -->
<PropertyGroup>
<GenerateServiceProjectReferenceDocumentsDependsOn>
_GetTargetFrameworkForServiceProjectReferences;
_GetTargetPathForServiceProjectReferences;
_GetMetadataForServiceProjectReferences;
_BuildServiceProjectReferences;
_GenerateServiceProjectReferenceDocuments;
_CreateFileItemsForServiceProjectReferences
</GenerateServiceProjectReferenceDocumentsDependsOn>
<GenerateServiceUriReferenceDocumentsDependsOn>
_GetMetadataForServiceUriReferences;
_GenerateServiceUriReferenceDocuments
</GenerateServiceUriReferenceDocumentsDependsOn>
<GenerateServiceFileReferenceCodesDependsOn>
GenerateServiceProjectReferenceDocuments;
GenerateServiceUriReferenceDocuments;
_GetMetadataForServiceFileReferences;
_GenerateServiceFileReferenceCodes;
_CreateCompileItemsForServiceFileReferences
</GenerateServiceFileReferenceCodesDependsOn>
</PropertyGroup>
<!-- ServiceProjectReference support -->
<!--
Metadata setup phase 1: Ensure items have TargetFramework metadata. Calls GetTargetFrameworks in the target
project. Inputs and outputs cause MSBuild to run target unconditionally and to batch it (run once per project).
-->
<Target Name="_GetTargetFrameworkForServiceProjectReferences"
Inputs="%(ServiceProjectReference.FullPath)"
Outputs="&lt;not-a-file !&gt;">
<PropertyGroup>
<_FullPath>%(ServiceProjectReference.FullPath)</_FullPath>
</PropertyGroup>
<ItemGroup>
<_Temporary Remove="@(_Temporary)" />
</ItemGroup>
<MSBuild Projects="$(_FullPath)"
RebaseOutputs="true"
RemoveProperties="TargetFramework;TargetFrameworks;RuntimeIdentifier"
Targets="GetTargetFrameworks"
UseResultsCache="true">
<Output TaskParameter="TargetOutputs" ItemName="_Temporary" />
</MSBuild>
<!--
Please excuse the mess necessary to extract information from _Temporary and use it in ServiceProjectReference.
-->
<PropertyGroup>
<_TargetFrameworks>%(_Temporary.TargetFrameworks)</_TargetFrameworks>
<_TargetFramework>$(_TargetFrameworks.Split(';')[0])</_TargetFramework>
</PropertyGroup>
<ItemGroup>
<ServiceProjectReference Update="@(ServiceProjectReference)" Condition="'%(FullPath)' == '$(_FullPath)'">
<TargetFramework Condition="'%(TargetFramework)' == ''">$(_TargetFramework)</TargetFramework>
</ServiceProjectReference>
<_Temporary Remove="@(_Temporary)" />
</ItemGroup>
<PropertyGroup>
<_FullPath />
<_TargetFramework />
<_TargetFrameworks />
</PropertyGroup>
</Target>
<!--
Metadata setup phase 2: Ensure items have TargetPath metadata. Calls GetTargetPath in the target project.
Inputs and outputs cause MSBuild to run target unconditionally and batch it (run once per TargetFramework x
project combination).
-->
<Target Name="_GetTargetPathForServiceProjectReferences"
Inputs="%(ServiceProjectReference.TargetFramework)%(FullPath)')"
Outputs="&lt;not-a-file !&gt;">
<PropertyGroup>
<_FullPath>%(ServiceProjectReference.FullPath)</_FullPath>
<_TargetFramework>%(ServiceProjectReference.TargetFramework)</_TargetFramework>
</PropertyGroup>
<ItemGroup>
<_Temporary Remove="@(_Temporary)" />
</ItemGroup>
<MSBuild Projects="$(_FullPath)"
Properties="TargetFramework=$(_TargetFramework)"
RebaseOutputs="true"
RemoveProperties="TargetFrameworks;RuntimeIdentifier"
Targets="GetTargetPath"
UseResultsCache="true">
<Output TaskParameter="TargetOutputs" ItemName="_Temporary" />
</MSBuild>
<PropertyGroup>
<_TargetPath>%(_Temporary.FullPath)</_TargetPath>
</PropertyGroup>
<ItemGroup>
<ServiceProjectReference Update="@(ServiceProjectReference)"
Condition="'%(FullPath)' == '$(_FullPath)' AND '%(TargetFramework)' == '$(_TargetFramework)'">
<TargetPath>$(_TargetPath)</TargetPath>
</ServiceProjectReference>
<_Temporary Remove="@(_Temporary)" />
</ItemGroup>
<PropertyGroup>
<_FullPath />
<_TargetPath />
<_TargetFramework />
</PropertyGroup>
</Target>
<!-- Metadata setup phase 3: Ensure items have DocumentPath metadata. -->
<Target Name="_GetMetadataForServiceProjectReferences" Condition="'@(ServiceProjectReference)' != ''">
<ItemGroup>
<_Temporary Remove="@(_Temporary)" />
</ItemGroup>
<GetProjectReferenceMetadata Inputs="@(ServiceProjectReference)"
DocumentDirectory="$(ServiceProjectReferenceDirectory)">
<Output TaskParameter="Outputs" ItemName="_Temporary" />
</GetProjectReferenceMetadata>
<ItemGroup>
<ServiceProjectReference Remove="@(ServiceProjectReference)" />
<ServiceProjectReference Include="@(_Temporary)" />
</ItemGroup>
<ItemGroup>
<_Temporary Remove="@(_Temporary)" />
</ItemGroup>
</Target>
<Target Name="_BuildServiceProjectReferences"
Condition="'$(BuildProjectReferences)' == 'true'"
Inputs="@(ServiceProjectReference)"
Outputs="%(TargetPath)">
<MSBuild Projects="@(ServiceProjectReference -> Distinct())"
BuildInParallel="$(BuildInParallel)"
RemoveProperties="TargetFramework;TargetFrameworks;RuntimeIdentifier"
Targets="%(Targets)" />
</Target>
<Target Name="_GetCurrentServiceProjectReference">
<GetCurrentItems Input="$(GeneratorMetadata)">
<Output TaskParameter="Outputs" ItemName="CurrentServiceProjectReference" />
</GetCurrentItems>
</Target>
<Target Name="_GenerateServiceProjectReferenceDocument"
DependsOnTargets="_GetCurrentServiceProjectReference;$(GeneratorTarget)" />
<Target Name="_GenerateServiceProjectReferenceDocuments"
Inputs="@(ServiceProjectReference)"
Outputs="%(DocumentPath)">
<MSBuild BuildInParallel="$(BuildInParallel)"
Projects="$(MSBuildProjectFullPath)"
Properties="GeneratorTargetPath=%(ServiceProjectReference.DocumentPath);GeneratorTarget=Generate%(DocumentGenerator)Document;GeneratorMetadata=%(SerializedMetadata)"
RemoveProperties="TargetFrameworks"
Targets="_GenerateServiceProjectReferenceDocument" />
</Target>
<Target Name="_CreateFileItemsForServiceProjectReferences" Condition="'@(ServiceProjectReference)' != ''">
<!-- GetProjectReferenceMetadata task guarantees %(DocumentPath) values are unique. -->
<ItemGroup>
<ServiceFileReference Remove="@(ServiceProjectReference -> '%(DocumentPath)')" />
<!-- Condition here is temporary. Useful while GenerateDefaultDocument fails. -->
<ServiceFileReference Include="@(ServiceProjectReference -> '%(DocumentPath)')"
Condition="Exists('%(ServiceProjectReference.DocumentPath)')">
<SourceProject>%(ServiceProjectReference.FullPath)</SourceProject>
</ServiceFileReference>
</ItemGroup>
</Target>
<Target Name="GenerateServiceProjectReferenceDocuments"
DependsOnTargets="$(GenerateServiceProjectReferenceDocumentsDependsOn)" />
<!-- GenerateDefaultDocument -->
<Target Name="GenerateDefaultDocument">
<ItemGroup>
<!-- @(CurrentServiceProjectReference) item group will never contain more than one item. -->
<CurrentServiceProjectReference Update="@(CurrentServiceProjectReference)">
<Command>dotnet $(MSBuildThisFileDirectory)/../tools/dotnet-getdocument.dll --project %(FullPath)</Command>
<Configuration Condition="'%(Configuration)' == ''">$(Configuration)</Configuration>
<GenerateDefaultDocumentOptions
Condition="'%(GenerateDefaultDocumentOptions)' == ''">$(GenerateDefaultDocumentDefaultOptions)</GenerateDefaultDocumentOptions>
</CurrentServiceProjectReference>
<CurrentServiceProjectReference Update="@(CurrentServiceProjectReference)">
<Command>%(Command) --framework %(TargetFramework) --output %(DocumentPath)</Command>
</CurrentServiceProjectReference>
<CurrentServiceProjectReference Update="@(CurrentServiceProjectReference)">
<Command Condition="'%(Method)' != ''">%(Command) --method %(Method)</Command>
</CurrentServiceProjectReference>
<CurrentServiceProjectReference Update="@(CurrentServiceProjectReference)">
<Command Condition="'%(Service)' != ''">%(Command) --service %(Service)</Command>
</CurrentServiceProjectReference>
<CurrentServiceProjectReference Update="@(CurrentServiceProjectReference)">
<Command
Condition="'%(ProjectExtensionsPath)' != ''">%(Command) --projectExtensionsPath %(ProjectExtensionsPath)</Command>
</CurrentServiceProjectReference>
<CurrentServiceProjectReference Update="@(CurrentServiceProjectReference)">
<Command>%(Command) --configuration %(Configuration) %(GenerateDefaultDocumentOptions)</Command>
</CurrentServiceProjectReference>
</ItemGroup>
<Message Importance="high" Text="%0AGenerateDefaultDocument:" />
<Message Importance="high" Text=" %(CurrentServiceProjectReference.Command)" />
<Exec Command="%(CurrentServiceProjectReference.Command)"
IgnoreExitCode="$([System.IO.File]::Exists('%(DocumentPath)'))" />
</Target>
<!-- ServiceUriReference support -->
<Target Name="_GetMetadataForServiceUriReferences" Condition="'@(ServiceUriReference)' != ''">
<ItemGroup>
<_Temporary Remove="@(_Temporary)" />
</ItemGroup>
<GetUriReferenceMetadata Inputs="@(ServiceUriReference)" DocumentDirectory="$(ServiceUriReferenceDirectory)">
<Output TaskParameter="Outputs" ItemName="_Temporary" />
</GetUriReferenceMetadata>
<ItemGroup>
<ServiceUriReference Remove="@(ServiceUriReference)" />
<ServiceUriReference Include="@(_Temporary)" />
<_Temporary Remove="@(_Temporary)" />
</ItemGroup>
</Target>
<Target Name="_GenerateServiceUriReferenceDocuments" Condition="'@(ServiceUriReference)' != ''">
<Microsoft.Extensions.ApiDescription.Tasks.DownloadFile Uri="%(ServiceUriReference.Identity)"
DestinationPath="%(DocumentPath)"
Overwrite="$(ServiceUriReferenceCheckIfNewer)" />
<!-- GetUriReferenceMetadata task guarantees %(DocumentPath) values are unique. -->
<ItemGroup>
<ServiceFileReference Remove="@(ServiceUriReference -> '%(DocumentPath)')" />
<ServiceFileReference Include="@(ServiceUriReference -> '%(DocumentPath)')">
<SourceUri>%(ServiceUriReference.Identity)</SourceUri>
</ServiceFileReference>
</ItemGroup>
</Target>
<Target Name="GenerateServiceUriReferenceDocuments"
DependsOnTargets="$(GenerateServiceUriReferenceDocumentsDependsOn)" />
<!-- ServiceFileReference support -->
<Target Name="_GetMetadataForServiceFileReferences" Condition="'@(ServiceFileReference)' != ''">
<ItemGroup>
<_Temporary Remove="@(_Temporary)" />
</ItemGroup>
<GetFileReferenceMetadata Inputs="@(ServiceFileReference)"
Extension="$(DefaultLanguageSourceExtension)"
Namespace="$(RootNamespace)"
OutputDirectory="$(ServiceFileReferenceDirectory)">
<Output TaskParameter="Outputs" ItemName="_Temporary" />
</GetFileReferenceMetadata>
<ItemGroup>
<ServiceFileReference Remove="@(ServiceFileReference)" />
<ServiceFileReference Include="@(_Temporary)" />
<_Temporary Remove="@(_Temporary)" />
</ItemGroup>
</Target>
<Target Name="_GetCurrentServiceFileReference">
<GetCurrentItems Input="$(GeneratorMetadata)">
<Output TaskParameter="Outputs" ItemName="CurrentServiceFileReference" />
</GetCurrentItems>
</Target>
<Target Name="_GenerateServiceFileReferenceCode"
DependsOnTargets="_GetCurrentServiceFileReference;$(GeneratorTarget)" />
<Target Name="_GenerateServiceFileReferenceCodes" Inputs="@(ServiceFileReference)" Outputs="%(OutputPath)">
<MSBuild BuildInParallel="$(BuildInParallel)"
Projects="$(MSBuildProjectFullPath)"
Properties="GeneratorTargetPath=%(ServiceFileReference.OutputPath);GeneratorTarget=Generate%(CodeGenerator);GeneratorMetadata=%(SerializedMetadata)"
RemoveProperties="TargetFrameworks"
Targets="_GenerateServiceFileReferenceCode" />
</Target>
<Target Name="_CreateCompileItemsForServiceFileReferences" Condition="'@(ServiceFileReference)' != ''">
<!--
While %(DocumentPath) metadata may include duplicates (due to overlaps between ServiceUriReference and
ServiceProjectReference items), GetFileReferenceMetadata task guarantees %(OutputPath) values are unique.
-->
<ItemGroup>
<_Files Remove="@(_Files)" />
<_Files Include="@(ServiceFileReference -> '%(OutputPath)')"
Condition="$([System.IO.File]::Exists('%(ServiceFileReference.OutputPath)'))">
<OutputPathExtension>$([System.IO.Path]::GetExtension('%(ServiceFileReference.OutputPath)'))</OutputPathExtension>
</_Files>
<_Directories Remove="@(_Directories)" />
<_Directories Include="@(ServiceFileReference -> '%(OutputPath)')"
Condition="Exists('%(ServiceFileReference.OutputPath)') AND ! $([System.IO.File]::Exists('%(ServiceFileReference.OutputPath)'))" />
<!-- If OutputPath is a file, add it directly to relevant items. -->
<TypeScriptCompile Remove="@(_Files)" Condition="'%(_Files.OutputPathExtension)' == '.ts'" />
<TypeScriptCompile Include="@(_Files)" Condition="'%(_Files.OutputPathExtension)' == '.ts'">
<SourceDocument>%(_Files.FullPath)</SourceDocument>
</TypeScriptCompile>
<Compile Remove="@(_Files)"
Condition="'$(DefaultLanguageSourceExtension)' != '.ts' AND '%(_Files.OutputPathExtension)' == '$(DefaultLanguageSourceExtension)'" />
<Compile Include="@(_Files)"
Condition="'$(DefaultLanguageSourceExtension)' != '.ts' AND '%(_Files.OutputPathExtension)' == '$(DefaultLanguageSourceExtension)'">
<SourceDocument>%(ServiceFileReference.FullPath)</SourceDocument>
</Compile>
<!-- Otherwise, add all descendent files with the expected extension. -->
<TypeScriptCompile Remove="@(_Directories -> '%(Identity)/**/*.ts')" />
<TypeScriptCompile Include="@(_Directories -> '%(Identity)/**/*.ts')">
<SourceDocument>%(_Directories.FullPath)</SourceDocument>
</TypeScriptCompile>
<Compile Remove="@(_Directories -> '%(Identity)/**/*.$(DefaultLanguageSourceExtension)')"
Condition="'$(DefaultLanguageSourceExtension)' != '.ts'" />
<Compile Include="@(_Directories -> '%(Identity)/**/*.$(DefaultLanguageSourceExtension)')"
Condition="'$(DefaultLanguageSourceExtension)' != '.ts'">
<SourceDocument>%(_Directories.FullPath)</SourceDocument>
</Compile>
<_Files Remove="@(_Files)" />
<_Directories Remove="@(_Directories)" />
</ItemGroup>
</Target>
<Target Name="GenerateServiceFileReferenceCodes"
BeforeTargets="BeforeCompile"
DependsOnTargets="$(GenerateServiceFileReferenceCodesDependsOn)" />
</Project>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<Project>
<Target Name="GenerateServiceFileReferenceCodes" BeforeTargets="BeforeCompile">
<MsBuild Projects="$(MSBuildProjectFile)"
Targets="GenerateServiceFileReferenceCodes"
Properties="TargetFramework=$(TargetFrameworks.Split(';')[0])"
RemoveProperties="TargetFrameworks;RuntimeIdentifier" />
</Target>
</Project>

View File

@ -0,0 +1,234 @@
// Copyright (c) .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 Microsoft.DotNet.Cli.CommandLine;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace Microsoft.Extensions.ApiDescription.Tool.Commands
{
internal class InvokeCommand : HelpCommandBase
{
private const string InsideManName = "GetDocument.Insider";
private IList<string> _args;
private CommandOption _configuration;
private CommandOption _output;
private CommandOption _project;
private CommandOption _projectExtensionsPath;
private CommandOption _runtime;
private CommandOption _targetFramework;
public override void Configure(CommandLineApplication command)
{
var options = new ProjectOptions();
options.Configure(command);
_configuration = options.Configuration;
_project = options.Project;
_projectExtensionsPath = options.ProjectExtensionsPath;
_runtime = options.Runtime;
_targetFramework = options.TargetFramework;
_output = command.Option("--output <Path>", 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.FormatUsingProject(projectFile));
var project = Project.FromFile(
projectFile,
_projectExtensionsPath.Value(),
_targetFramework.Value(),
_configuration.Value(),
_runtime.Value());
if (!File.Exists(project.TargetPath))
{
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<string>();
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.FormatNETCoreApp1Project(project.ProjectName, targetFramework.Version));
}
args.Add("exec");
args.Add("--depsFile");
args.Add(project.ProjectDepsFilePath);
if (!string.IsNullOrEmpty(project.ProjectAssetsFile))
{
using (var reader = new JsonTextReader(File.OpenText(project.ProjectAssetsFile)))
{
var projectAssets = JToken.ReadFrom(reader);
var packageFolders = projectAssets["packageFolders"]
.Children<JProperty>()
.Select(p => p.Name);
foreach (var packageFolder in packageFolders)
{
args.Add("--additionalProbingPath");
args.Add(packageFolder.TrimEnd(Path.DirectorySeparatorChar));
}
}
}
if (File.Exists(project.ProjectRuntimeConfigFilePath))
{
args.Add("--runtimeConfig");
args.Add(project.ProjectRuntimeConfigFilePath);
}
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.FormatNETStandardProject(project.ProjectName));
default:
throw new CommandException(
Resources.FormatUnsupportedFramework(project.ProjectName, targetFramework.Identifier));
}
args.AddRange(_args);
args.Add("--assembly");
args.Add(project.TargetPath);
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 (_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.ProjectDirectory);
}
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.FormatNoProjectInDirectory(path)
: errorWhenNoProject);
}
if (projectFiles.Count != 1)
{
throw new CommandException(
specified
? Resources.FormatMultipleProjectsInDirectory(path)
: errorWhenMultipleProjects);
}
return projectFiles[0];
}
}
}

View File

@ -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 Microsoft.Extensions.ApiDescription.Tool
{
internal static class Exe
{
public static int Run(
string executable,
IReadOnlyList<string> 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<string> 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();
}
}
}

View File

@ -0,0 +1,42 @@
// Copyright (c) .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 Microsoft.DotNet.Cli.CommandLine;
using Microsoft.Extensions.ApiDescription.Tool.Commands;
namespace Microsoft.Extensions.ApiDescription.Tool
{
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;
}
}
}
}

View File

@ -0,0 +1,235 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using IODirectory = System.IO.Directory;
namespace Microsoft.Extensions.ApiDescription.Tool
{
internal class Project
{
private const string ResourceFilename = "ServiceProjectReferenceMetadata.targets";
private const string MSBuildResourceName = "Microsoft.Extensions.ApiDescription.Tool." + ResourceFilename;
private Project()
{
}
public string AssemblyName { get; private set; }
public string ConfigPath { get; private set; }
public string Configuration { get; private set; }
public string DefaultDocumentName { get; private set; }
public string DefaultMethod { get; private set; }
public string DefaultService { get; private set; }
public string OutputPath { get; private set; }
public string Platform { get; private set; }
public string PlatformTarget { get; private set; }
public string ProjectAssetsFile { get; private set; }
public string ProjectDepsFilePath { get; private set; }
public string ProjectDirectory { get; private set; }
public string ProjectExtensionsPath { get; private set; }
public string ProjectName { get; private set; }
public string ProjectRuntimeConfigFilePath { get; private set; }
public string RuntimeFrameworkVersion { get; private set; }
public string RuntimeIdentifier { get; private set; }
public string TargetFramework { get; private set; }
public string TargetFrameworkMoniker { get; private set; }
public string TargetPath { get; private set; }
public static Project FromFile(
string projectFile,
string buildExtensionsDirectory,
string framework = null,
string configuration = null,
string runtime = null)
{
if (string.IsNullOrEmpty(projectFile))
{
throw new ArgumentNullException(nameof(projectFile));
}
if (string.IsNullOrEmpty(buildExtensionsDirectory))
{
buildExtensionsDirectory = Path.Combine(Path.GetDirectoryName(projectFile), "obj");
}
IODirectory.CreateDirectory(buildExtensionsDirectory);
var assembly = typeof(Project).Assembly;
var targetsPath = Path.Combine(
buildExtensionsDirectory,
$"{Path.GetFileName(projectFile)}.{ResourceFilename}");
using (var input = assembly.GetManifestResourceStream(MSBuildResourceName))
{
using (var output = File.OpenWrite(targetsPath))
{
// NB: Copy always in case it changes
Reporter.WriteVerbose(Resources.FormatWritingFile(targetsPath));
input.CopyTo(output);
}
}
IDictionary<string, string> metadata;
var metadataPath = Path.GetTempFileName();
try
{
var args = new List<string>
{
"msbuild",
"/target:WriteServiceProjectReferenceMetadata",
"/verbosity:quiet",
"/nologo",
$"/property:ServiceProjectReferenceMetadataPath={metadataPath}",
projectFile,
};
if (!string.IsNullOrEmpty(framework))
{
args.Add($"/property:TargetFramework={framework}");
}
if (!string.IsNullOrEmpty(configuration))
{
args.Add($"/property:Configuration={configuration}");
}
if (!string.IsNullOrEmpty(runtime))
{
args.Add($"/property:RuntimeIdentifier={runtime}");
}
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(metadataPath);
File.Delete(targetsPath);
}
var project = new Project
{
DefaultDocumentName = metadata[nameof(DefaultDocumentName)],
DefaultMethod = metadata[nameof(DefaultMethod)],
DefaultService = metadata[nameof(DefaultService)],
AssemblyName = metadata[nameof(AssemblyName)],
Configuration = metadata[nameof(Configuration)],
OutputPath = metadata[nameof(OutputPath)],
Platform = metadata[nameof(Platform)],
PlatformTarget = metadata[nameof(PlatformTarget)] ?? metadata[nameof(Platform)],
ProjectAssetsFile = metadata[nameof(ProjectAssetsFile)],
ProjectDepsFilePath = metadata[nameof(ProjectDepsFilePath)],
ProjectDirectory = metadata[nameof(ProjectDirectory)],
ProjectExtensionsPath = metadata[nameof(ProjectExtensionsPath)],
ProjectName = metadata[nameof(ProjectName)],
ProjectRuntimeConfigFilePath = metadata[nameof(ProjectRuntimeConfigFilePath)],
RuntimeFrameworkVersion = metadata[nameof(RuntimeFrameworkVersion)],
RuntimeIdentifier = metadata[nameof(RuntimeIdentifier)],
TargetFramework = metadata[nameof(TargetFramework)],
TargetFrameworkMoniker = metadata[nameof(TargetFrameworkMoniker)],
TargetPath = metadata[nameof(TargetPath)],
};
if (string.IsNullOrEmpty(project.OutputPath))
{
throw new CommandException(
Resources.FormatGetMetadataValueFailed(nameof(OutputPath), nameof(OutputPath)));
}
if (string.IsNullOrEmpty(project.ProjectDirectory))
{
throw new CommandException(
Resources.FormatGetMetadataValueFailed(nameof(ProjectDirectory), "MSBuildProjectDirectory"));
}
if (string.IsNullOrEmpty(project.TargetPath))
{
throw new CommandException(
Resources.FormatGetMetadataValueFailed(nameof(TargetPath), nameof(TargetPath)));
}
if (!Path.IsPathRooted(project.ProjectDirectory))
{
project.OutputPath = Path.GetFullPath(
Path.Combine(IODirectory.GetCurrentDirectory(), project.ProjectDirectory));
}
if (!Path.IsPathRooted(project.OutputPath))
{
project.OutputPath = Path.GetFullPath(Path.Combine(project.ProjectDirectory, project.OutputPath));
}
if (!Path.IsPathRooted(project.ProjectExtensionsPath))
{
project.ProjectExtensionsPath = Path.GetFullPath(
Path.Combine(project.ProjectDirectory, project.ProjectExtensionsPath));
}
if (!Path.IsPathRooted(project.TargetPath))
{
project.TargetPath = Path.GetFullPath(Path.Combine(project.OutputPath, project.TargetPath));
}
// Some document generation tools support non-ASP.NET Core projects. Any of the remaining properties may
// thus be null empty.
var configPath = $"{project.TargetPath}.config";
if (File.Exists(configPath))
{
project.ConfigPath = configPath;
}
if (!(string.IsNullOrEmpty(project.ProjectAssetsFile) || Path.IsPathRooted(project.ProjectAssetsFile)))
{
project.ProjectAssetsFile = Path.GetFullPath(
Path.Combine(project.ProjectDirectory, project.ProjectAssetsFile));
}
if (!(string.IsNullOrEmpty(project.ProjectDepsFilePath) || Path.IsPathRooted(project.ProjectDepsFilePath)))
{
project.ProjectDepsFilePath = Path.GetFullPath(
Path.Combine(project.ProjectDirectory, project.ProjectDepsFilePath));
}
if (!(string.IsNullOrEmpty(project.ProjectRuntimeConfigFilePath) ||
Path.IsPathRooted(project.ProjectRuntimeConfigFilePath)))
{
project.ProjectRuntimeConfigFilePath = Path.GetFullPath(
Path.Combine(project.OutputPath, project.ProjectRuntimeConfigFilePath));
}
return project;
}
}
}

View File

@ -0,0 +1,31 @@
// Copyright (c) .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 Microsoft.Extensions.ApiDescription.Tool
{
internal class ProjectOptions
{
public CommandOption Configuration { get; private set; }
public CommandOption Project { get; private set; }
public CommandOption ProjectExtensionsPath { get; private set; }
public CommandOption Runtime { get; private set; }
public CommandOption TargetFramework { get; private set; }
public void Configure(CommandLineApplication command)
{
Configuration = command.Option("--configuration <CONFIGURATION>", Resources.ConfigurationDescription);
Project = command.Option("-p|--project <PROJECT>", Resources.ProjectDescription);
ProjectExtensionsPath = command.Option(
"--projectExtensionsPath <PATH>",
Resources.ProjectExtensionsPathDescription);
Runtime = command.Option("--runtime <RUNTIME_IDENTIFIER>", Resources.RuntimeDescription);
TargetFramework = command.Option("--framework <FRAMEWORK>", Resources.TargetFrameworkDescription);
}
}
}

View File

@ -0,0 +1,338 @@
// <auto-generated />
namespace Microsoft.Extensions.ApiDescription.Tool
{
using System.Globalization;
using System.Reflection;
using System.Resources;
internal static class Resources
{
private static readonly ResourceManager _resourceManager
= new ResourceManager("Microsoft.Extensions.ApiDescription.Tool.Resources", typeof(Resources).GetTypeInfo().Assembly);
/// <summary>
/// The configuration to use.
/// </summary>
internal static string ConfigurationDescription
{
get => GetString("ConfigurationDescription");
}
/// <summary>
/// The configuration to use.
/// </summary>
internal static string FormatConfigurationDescription()
=> GetString("ConfigurationDescription");
/// <summary>
/// dotnet-getdocument
/// </summary>
internal static string CommandFullName
{
get => GetString("CommandFullName");
}
/// <summary>
/// dotnet-getdocument
/// </summary>
internal static string FormatCommandFullName()
=> GetString("CommandFullName");
/// <summary>
/// The target framework.
/// </summary>
internal static string TargetFrameworkDescription
{
get => GetString("TargetFrameworkDescription");
}
/// <summary>
/// The target framework.
/// </summary>
internal static string FormatTargetFrameworkDescription()
=> GetString("TargetFrameworkDescription");
/// <summary>
/// Unable to retrieve project metadata. If you are using custom BaseIntermediateOutputPath or MSBuildProjectExtensionsPath values, use the --projectExtensionsPath option.
/// </summary>
internal static string GetMetadataFailed
{
get => GetString("GetMetadataFailed");
}
/// <summary>
/// Unable to retrieve project metadata. If you are using custom BaseIntermediateOutputPath or MSBuildProjectExtensionsPath values, use the --projectExtensionsPath option.
/// </summary>
internal static string FormatGetMetadataFailed()
=> GetString("GetMetadataFailed");
/// <summary>
/// More than one project was found in the current working directory. Use the --project option.
/// </summary>
internal static string MultipleProjects
{
get => GetString("MultipleProjects");
}
/// <summary>
/// More than one project was found in the current working directory. Use the --project option.
/// </summary>
internal static string FormatMultipleProjects()
=> GetString("MultipleProjects");
/// <summary>
/// More than one project was found in directory '{0}'. Specify one using its file name.
/// </summary>
internal static string MultipleProjectsInDirectory
{
get => GetString("MultipleProjectsInDirectory");
}
/// <summary>
/// More than one project was found in directory '{0}'. Specify one using its file name.
/// </summary>
internal static string FormatMultipleProjectsInDirectory(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("MultipleProjectsInDirectory"), p0);
/// <summary>
/// Project '{0}' targets framework '.NETCoreApp' version '{1}'. This version of the dotnet-getdocument tool only supports version 2.0 or higher.
/// </summary>
internal static string NETCoreApp1Project
{
get => GetString("NETCoreApp1Project");
}
/// <summary>
/// Project '{0}' targets framework '.NETCoreApp' version '{1}'. This version of the dotnet-getdocument tool only supports version 2.0 or higher.
/// </summary>
internal static string FormatNETCoreApp1Project(object p0, object p1)
=> string.Format(CultureInfo.CurrentCulture, GetString("NETCoreApp1Project"), p0, p1);
/// <summary>
/// Project '{0}' targets framework '.NETStandard'. There is no runtime associated with this framework, and projects targeting it cannot be executed directly. To use the dotnet-getdocument 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.
/// </summary>
internal static string NETStandardProject
{
get => GetString("NETStandardProject");
}
/// <summary>
/// Project '{0}' targets framework '.NETStandard'. There is no runtime associated with this framework, and projects targeting it cannot be executed directly. To use the dotnet-getdocument 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.
/// </summary>
internal static string FormatNETStandardProject(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("NETStandardProject"), p0);
/// <summary>
/// Do not colorize output.
/// </summary>
internal static string NoColorDescription
{
get => GetString("NoColorDescription");
}
/// <summary>
/// Do not colorize output.
/// </summary>
internal static string FormatNoColorDescription()
=> GetString("NoColorDescription");
/// <summary>
/// No project was found. Change the current working directory or use the --project option.
/// </summary>
internal static string NoProject
{
get => GetString("NoProject");
}
/// <summary>
/// No project was found. Change the current working directory or use the --project option.
/// </summary>
internal static string FormatNoProject()
=> GetString("NoProject");
/// <summary>
/// No project was found in directory '{0}'.
/// </summary>
internal static string NoProjectInDirectory
{
get => GetString("NoProjectInDirectory");
}
/// <summary>
/// No project was found in directory '{0}'.
/// </summary>
internal static string FormatNoProjectInDirectory(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("NoProjectInDirectory"), p0);
/// <summary>
/// Prefix output with level.
/// </summary>
internal static string PrefixDescription
{
get => GetString("PrefixDescription");
}
/// <summary>
/// Prefix output with level.
/// </summary>
internal static string FormatPrefixDescription()
=> GetString("PrefixDescription");
/// <summary>
/// The project to use.
/// </summary>
internal static string ProjectDescription
{
get => GetString("ProjectDescription");
}
/// <summary>
/// The project to use.
/// </summary>
internal static string FormatProjectDescription()
=> GetString("ProjectDescription");
/// <summary>
/// The MSBuild project extensions path. Defaults to "obj".
/// </summary>
internal static string ProjectExtensionsPathDescription
{
get => GetString("ProjectExtensionsPathDescription");
}
/// <summary>
/// The MSBuild project extensions path. Defaults to "obj".
/// </summary>
internal static string FormatProjectExtensionsPathDescription()
=> GetString("ProjectExtensionsPathDescription");
/// <summary>
/// The runtime identifier to use.
/// </summary>
internal static string RuntimeDescription
{
get => GetString("RuntimeDescription");
}
/// <summary>
/// The runtime identifier to use.
/// </summary>
internal static string FormatRuntimeDescription()
=> GetString("RuntimeDescription");
/// <summary>
/// Project '{0}' targets framework '{1}'. The dotnet-getdocument tool does not support this framework.
/// </summary>
internal static string UnsupportedFramework
{
get => GetString("UnsupportedFramework");
}
/// <summary>
/// Project '{0}' targets framework '{1}'. The dotnet-getdocument tool does not support this framework.
/// </summary>
internal static string FormatUnsupportedFramework(object p0, object p1)
=> string.Format(CultureInfo.CurrentCulture, GetString("UnsupportedFramework"), p0, p1);
/// <summary>
/// Using project '{0}'.
/// </summary>
internal static string UsingProject
{
get => GetString("UsingProject");
}
/// <summary>
/// Using project '{0}'.
/// </summary>
internal static string FormatUsingProject(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("UsingProject"), p0);
/// <summary>
/// Show verbose output.
/// </summary>
internal static string VerboseDescription
{
get => GetString("VerboseDescription");
}
/// <summary>
/// Show verbose output.
/// </summary>
internal static string FormatVerboseDescription()
=> GetString("VerboseDescription");
/// <summary>
/// Writing '{0}'...
/// </summary>
internal static string WritingFile
{
get => GetString("WritingFile");
}
/// <summary>
/// Writing '{0}'...
/// </summary>
internal static string FormatWritingFile(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("WritingFile"), p0);
/// <summary>
/// Project output not found. Project must be up-to-date when using this tool.
/// </summary>
internal static string MustBuild
{
get => GetString("MustBuild");
}
/// <summary>
/// Project output not found. Project must be up-to-date when using this tool.
/// </summary>
internal static string FormatMustBuild()
=> GetString("MustBuild");
/// <summary>
/// The file to write the result to.
/// </summary>
internal static string OutputDescription
{
get => GetString("OutputDescription");
}
/// <summary>
/// The file to write the result to.
/// </summary>
internal static string FormatOutputDescription()
=> GetString("OutputDescription");
/// <summary>
/// Unable to retrieve '{0}' project metadata. Ensure '{1}' is set.
/// </summary>
internal static string GetMetadataValueFailed
{
get => GetString("GetMetadataValueFailed");
}
/// <summary>
/// Unable to retrieve '{0}' project metadata. Ensure '{1}' is set.
/// </summary>
internal static string FormatGetMetadataValueFailed(object p0, object p1)
=> string.Format(CultureInfo.CurrentCulture, GetString("GetMetadataValueFailed"), p0, p1);
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);
System.Diagnostics.Debug.Assert(value != null);
if (formatterNames != null)
{
for (var i = 0; i < formatterNames.Length; i++)
{
value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}");
}
}
return value;
}
}
}

View File

@ -0,0 +1,186 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="ConfigurationDescription" xml:space="preserve">
<value>The configuration to use.</value>
</data>
<data name="CommandFullName" xml:space="preserve">
<value>dotnet-getdocument</value>
</data>
<data name="TargetFrameworkDescription" xml:space="preserve">
<value>The target framework.</value>
</data>
<data name="GetMetadataFailed" xml:space="preserve">
<value>Unable to retrieve project metadata. If you are using custom BaseIntermediateOutputPath or MSBuildProjectExtensionsPath values, use the --projectExtensionsPath option.</value>
</data>
<data name="MultipleProjects" xml:space="preserve">
<value>More than one project was found in the current working directory. Use the --project option.</value>
</data>
<data name="MultipleProjectsInDirectory" xml:space="preserve">
<value>More than one project was found in directory '{0}'. Specify one using its file name.</value>
</data>
<data name="NETCoreApp1Project" xml:space="preserve">
<value>Project '{0}' targets framework '.NETCoreApp' version '{1}'. This version of the dotnet-getdocument tool only supports version 2.0 or higher.</value>
</data>
<data name="NETStandardProject" xml:space="preserve">
<value>Project '{0}' targets framework '.NETStandard'. There is no runtime associated with this framework, and projects targeting it cannot be executed directly. To use the dotnet-getdocument 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.</value>
</data>
<data name="NoColorDescription" xml:space="preserve">
<value>Do not colorize output.</value>
</data>
<data name="NoProject" xml:space="preserve">
<value>No project was found. Change the current working directory or use the --project option.</value>
</data>
<data name="NoProjectInDirectory" xml:space="preserve">
<value>No project was found in directory '{0}'.</value>
</data>
<data name="PrefixDescription" xml:space="preserve">
<value>Prefix output with level.</value>
</data>
<data name="ProjectDescription" xml:space="preserve">
<value>The project to use.</value>
</data>
<data name="ProjectExtensionsPathDescription" xml:space="preserve">
<value>The MSBuild project extensions path. Defaults to "obj".</value>
</data>
<data name="RuntimeDescription" xml:space="preserve">
<value>The runtime identifier to use.</value>
</data>
<data name="UnsupportedFramework" xml:space="preserve">
<value>Project '{0}' targets framework '{1}'. The dotnet-getdocument tool does not support this framework.</value>
</data>
<data name="UsingProject" xml:space="preserve">
<value>Using project '{0}'.</value>
</data>
<data name="VerboseDescription" xml:space="preserve">
<value>Show verbose output.</value>
</data>
<data name="WritingFile" xml:space="preserve">
<value>Writing '{0}'...</value>
</data>
<data name="MustBuild" xml:space="preserve">
<value>Project output not found. Project must be up-to-date when using this tool.</value>
</data>
<data name="OutputDescription" xml:space="preserve">
<value>The file to write the result to.</value>
</data>
<data name="GetMetadataValueFailed" xml:space="preserve">
<value>Unable to retrieve '{0}' project metadata. Ensure '$({1})' is set.</value>
</data>
</root>

View File

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- Collect properties only in inner build. Execute unconditionally before WriteServiceProjectReferenceMetadata. -->
<Target Name="GetServiceProjectReferenceMetadata"
BeforeTargets="WriteServiceProjectReferenceMetadata"
Condition="'$(TargetFramework)' != ''"
Returns="@(ServiceProjectReferenceMetadata)">
<ItemGroup Condition="'$(TargetFramework)' != ''">
<ServiceProjectReferenceMetadata Include="DefaultDocumentName: $(DefaultServiceProjectDocumentName)" />
<ServiceProjectReferenceMetadata Include="DefaultMethod: $(DefaultServiceProjectMethod)" />
<ServiceProjectReferenceMetadata Include="DefaultService: $(DefaultServiceProjectService)" />
<ServiceProjectReferenceMetadata Include="AssemblyName: $(AssemblyName)" />
<ServiceProjectReferenceMetadata Include="Configuration: $(Configuration)" />
<ServiceProjectReferenceMetadata Include="OutputPath: $(OutputPath)" />
<ServiceProjectReferenceMetadata Include="Platform: $(Platform)" />
<ServiceProjectReferenceMetadata Include="PlatformTarget: $(PlatformTarget)" />
<ServiceProjectReferenceMetadata Include="ProjectAssetsFile: $(ProjectAssetsFile)" />
<ServiceProjectReferenceMetadata Include="ProjectDepsFilePath: $(ProjectDepsFilePath)" />
<ServiceProjectReferenceMetadata Include="ProjectDirectory: $(MSBuildProjectDirectory)" />
<ServiceProjectReferenceMetadata Include="ProjectExtensionsPath: $(MSBuildProjectExtensionsPath)" />
<ServiceProjectReferenceMetadata Include="ProjectName: $(MSBuildProjectName)" />
<ServiceProjectReferenceMetadata Include="ProjectRuntimeConfigFilePath: $(ProjectRuntimeConfigFilePath)" />
<ServiceProjectReferenceMetadata Include="RuntimeFrameworkVersion: $(RuntimeFrameworkVersion)" />
<ServiceProjectReferenceMetadata Include="RuntimeIdentifier: $(RuntimeIdentifier)" />
<ServiceProjectReferenceMetadata Include="TargetFramework: $(TargetFramework)" />
<ServiceProjectReferenceMetadata Include="TargetFrameworkMoniker: $(TargetFrameworkMoniker)" />
<ServiceProjectReferenceMetadata Include="TargetPath: $(TargetPath)" />
</ItemGroup>
</Target>
<!-- Write information only in inner build. -->
<Target Name="WriteServiceProjectReferenceMetadata" Returns="@(ServiceProjectReferenceMetadata)">
<MSBuild Condition="'$(TargetFramework)' == ''"
Projects="$(MSBuildProjectFile)"
Targets="WriteServiceProjectReferenceMetadata"
Properties="TargetFramework=$(TargetFrameworks.Split(';')[0]);ServiceProjectReferenceMetadataFile=$(ServiceProjectReferenceMetadataFile)" />
<WriteLinesToFile Condition="'$(TargetFramework)' != ''"
File="$(ServiceProjectReferenceMetadataPath)"
Lines="@(ServiceProjectReferenceMetadata)" />
</Target>
</Project>

View File

@ -0,0 +1,25 @@
<Project Sdk="Internal.AspNetCore.Sdk">
<PropertyGroup>
<AssemblyName>dotnet-getdocument</AssemblyName>
<Description>GetDocument Command-line Tool outside man</Description>
<EnableApiCheck>false</EnableApiCheck>
<IsPackable>false</IsPackable>
<OutputType>Exe</OutputType>
<RootNamespace>Microsoft.Extensions.ApiDescription.Tool</RootNamespace>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Compile Include="../GetDocumentInsider/Ansi*.cs" />
<Compile Include="../GetDocumentInsider/CommandException.cs" />
<Compile Include="../GetDocumentInsider/CommandLineUtils/*.cs" LinkBase="CommandLineUtils" />
<Compile Include="../GetDocumentInsider/Commands/CommandBase.cs" Link="Commands/CommandBase.cs" />
<Compile Include="../GetDocumentInsider/Commands/HelpCommandBase.cs" Link="Commands/HelpCommandBase.cs" />
<Compile Include="../GetDocumentInsider/ProductInfo.cs" />
<Compile Include="../GetDocumentInsider/Reporter.cs" />
<EmbeddedResource Include="ServiceProjectReferenceMetadata.targets" />
<PackageReference Include="Newtonsoft.Json" Version="$(NewtonsoftJsonPackageVersion)" />
</ItemGroup>
</Project>