From 15bb1d5b8aa2d13bde5ac5f8d4cc7cf2828957f9 Mon Sep 17 00:00:00 2001 From: Yufei Huang Date: Tue, 30 May 2023 17:17:49 +0800 Subject: [PATCH] fix: support load plugins from template (#8812) fix: load plugins from template --- ...pe_of_documentation_with_custom_plug-in.md | 23 ++-- .../Helpers/DocumentBuilderWrapper.cs | 33 +---- .../TemplateProcessors/TemplateManager.cs | 22 ++-- .../template/plugins/CustomPostProcessor.cs | 15 +++ .../template/plugins/Directory.Build.props | 1 + .../Assets/template/plugins/plugins.csproj | 19 +++ .../Assets/template/plugins/plugins.dll | Bin 0 -> 5120 bytes test/docfx.Tests/CompositeCommandTest.cs | 115 ++++++++++-------- test/docfx.Tests/DocsetTest.cs | 22 ++++ test/docfx.Tests/PdfTest.cs | 2 - 10 files changed, 144 insertions(+), 108 deletions(-) create mode 100644 test/docfx.Tests/Assets/template/plugins/CustomPostProcessor.cs create mode 100644 test/docfx.Tests/Assets/template/plugins/Directory.Build.props create mode 100644 test/docfx.Tests/Assets/template/plugins/plugins.csproj create mode 100644 test/docfx.Tests/Assets/template/plugins/plugins.dll diff --git a/docs/tutorial/howto_build_your_own_type_of_documentation_with_custom_plug-in.md b/docs/tutorial/howto_build_your_own_type_of_documentation_with_custom_plug-in.md index 08ac2b0aad4..f9778815996 100644 --- a/docs/tutorial/howto_build_your_own_type_of_documentation_with_custom_plug-in.md +++ b/docs/tutorial/howto_build_your_own_type_of_documentation_with_custom_plug-in.md @@ -15,23 +15,14 @@ Goal and limitation Preparation ----------- -1. Create a new C# class library project in `Visual Studio`, targets .NET Framework 4.7.2 or later. +1. Create a new C# class library targeting `net6.0` or later. -2. Add nuget packages: - * `System.Collections.Immutable` with version 1.3.1 or later (if not already included in your .NET Framework target version) - * `Microsoft.Composition` with version 1.0.31 +2. Add NuGet package reference to `System.Composition`, `Microsoft.DocAsCode.Plugins` and `Microsoft.DocAsCode.Common`. -3. Add `Microsoft.DocAsCode.Plugins` and `Microsoft.DocAsCode.Common` - If building DocFX from source code then add a reference to the project, - otherwise add the nuget packages with the same version as DocFX. - -4. Add framework assembly references: - `PresentationCore`, `PresentationFramework`, `WindowsBase`. (This step is optional in Visual Studio 2017 or above) - -5. Add a project for converting rtf to html: +4. Add a project for converting rtf to html: Clone project [MarkupConverter](https://github.com/mmanela/MarkupConverter), and reference it. -6. Copy the code file `CSharp/parallel/ParallelExtensionsExtras/TaskSchedulers/StaTaskScheduler.cs` from [DotNet Samples](https://github.com/dotnet/samples) +5. Copy the code file `CSharp/parallel/ParallelExtensionsExtras/TaskSchedulers/StaTaskScheduler.cs` from [DotNet Samples](https://github.com/dotnet/samples) Create a document processor --------------------------- @@ -137,10 +128,10 @@ Enable plug-in -------------- 1. Build our project. 2. Copy the output dll files to: - * Global: a folder you create, named `Plugins` under the folder where the Docfx executable resides. - * Non-global: a folder you create with the name `Plugins` under a template folder. Then run `DocFX build` command with parameter `-t {template}`. + * Global: the Docfx executable directory. + * Non-global: a folder you create with the name `plugins` under a template folder. Then run `DocFX build` command with parameter `-t {template}`. - *Hint*: DocFX can merge templates so create a template that only contains the `Plugins` folder, then run the command `DocFX build` with parameter `-t {templateForRender},{templateForPlugins}`. + *Hint*: DocFX can merge templates so create a template that only contains the `plugins` folder, then run the command `DocFX build` with parameter `-t {templateForRender},{templateForPlugins}`. Build document -------------- diff --git a/src/Microsoft.DocAsCode.App/Helpers/DocumentBuilderWrapper.cs b/src/Microsoft.DocAsCode.App/Helpers/DocumentBuilderWrapper.cs index 249f9b152b4..bf1cc56fde0 100644 --- a/src/Microsoft.DocAsCode.App/Helpers/DocumentBuilderWrapper.cs +++ b/src/Microsoft.DocAsCode.App/Helpers/DocumentBuilderWrapper.cs @@ -6,14 +6,7 @@ using System.Reflection; using System.Reflection.Metadata; using System.Reflection.PortableExecutable; -using Microsoft.DocAsCode.Build.ConceptualDocuments; using Microsoft.DocAsCode.Build.Engine; -using Microsoft.DocAsCode.Build.ManagedReference; -using Microsoft.DocAsCode.Build.ResourceFiles; -using Microsoft.DocAsCode.Build.RestApi; -using Microsoft.DocAsCode.Build.SchemaDriven; -using Microsoft.DocAsCode.Build.TableOfContents; -using Microsoft.DocAsCode.Build.UniversalReference; using Microsoft.DocAsCode.Common; using Microsoft.DocAsCode.Plugins; @@ -42,7 +35,9 @@ public static void BuildDocument(BuildJsonConfig config, BuildOptions options, T postProcessorNames = postProcessorNames.Add("SitemapGenerator"); } - using var builder = new DocumentBuilder(s_pluginAssemblies, postProcessorNames); + var pluginAssemblies = templateManager.GetTemplateDirectories().Select(d => Path.Combine(d, "plugins")).SelectMany(LoadPluginAssemblies); + + using var builder = new DocumentBuilder(s_pluginAssemblies.Concat(pluginAssemblies), postProcessorNames); using (new PerformanceScope("building documents", LogLevel.Info)) { var parameters = ConfigToParameter(config, options, templateManager, baseDirectory, outputDirectory, templateDirectory); @@ -52,20 +47,8 @@ public static void BuildDocument(BuildJsonConfig config, BuildOptions options, T private static IEnumerable LoadPluginAssemblies(string pluginDirectory) { - var defaultPluggedAssemblies = new List - { - typeof(ConceptualDocumentProcessor).Assembly, - typeof(ManagedReferenceDocumentProcessor).Assembly, - typeof(ResourceDocumentProcessor).Assembly, - typeof(RestApiDocumentProcessor).Assembly, - typeof(TocDocumentProcessor).Assembly, - typeof(SchemaDrivenDocumentProcessor).Assembly, - typeof(UniversalReferenceDocumentProcessor).Assembly, - }; - foreach (var assem in defaultPluggedAssemblies) - { - yield return assem; - } + if (!Directory.Exists(pluginDirectory)) + yield break; Logger.LogInfo($"Searching custom plugins in directory {pluginDirectory}..."); @@ -88,12 +71,6 @@ private static IEnumerable LoadPluginAssemblies(string pluginDirectory continue; } - if (defaultPluggedAssemblies.Select(n => n.GetName().Name).Contains(assemblyName)) - { - Logger.LogVerbose($"Skipping default plugged assembly: {assemblyName}."); - continue; - } - if (!IsDocfxPluginAssembly(assemblyFile)) { Logger.LogVerbose($"Skipping non-plugin assembly: {assemblyName}."); diff --git a/src/Microsoft.DocAsCode.Build.Engine/TemplateProcessors/TemplateManager.cs b/src/Microsoft.DocAsCode.Build.Engine/TemplateProcessors/TemplateManager.cs index 601d828b518..0d6f6030935 100644 --- a/src/Microsoft.DocAsCode.Build.Engine/TemplateProcessors/TemplateManager.cs +++ b/src/Microsoft.DocAsCode.Build.Engine/TemplateProcessors/TemplateManager.cs @@ -34,23 +34,29 @@ public TemplateProcessor GetTemplateProcessor(DocumentBuildContext context, int private CompositeResourceReader CreateTemplateResource(IEnumerable resources) { - return new(resources.Select(FindResource).Where(s => s != null)); + return new(GetTemplateDirectories(resources).Select(path => new LocalFileResourceReader(path))); + } + + public IEnumerable GetTemplateDirectories() + { + return GetTemplateDirectories(_templates); + } - ResourceFileReader? FindResource(string name) + private IEnumerable GetTemplateDirectories(IEnumerable names) + { + foreach (var name in names) { - var directory = Path.Combine(AppContext.BaseDirectory, "templates", name); + var directory = Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "templates", name)); if (Directory.Exists(directory)) { - return new LocalFileResourceReader(directory); + yield return directory; } - directory = Path.Combine(_baseDirectory, name); + directory = Path.GetFullPath(Path.Combine(_baseDirectory, name)); if (Directory.Exists(directory)) { - return new LocalFileResourceReader(directory); + yield return directory; } - - return null; } } diff --git a/test/docfx.Tests/Assets/template/plugins/CustomPostProcessor.cs b/test/docfx.Tests/Assets/template/plugins/CustomPostProcessor.cs new file mode 100644 index 00000000000..38c4d49a809 --- /dev/null +++ b/test/docfx.Tests/Assets/template/plugins/CustomPostProcessor.cs @@ -0,0 +1,15 @@ +using System.Collections.Immutable; +using System.Composition; +using Microsoft.DocAsCode.Plugins; + +[Export(nameof(CustomPostProcessor), typeof(IPostProcessor))] +public class CustomPostProcessor : IPostProcessor +{ + public ImmutableDictionary PrepareMetadata(ImmutableDictionary metadata) => metadata; + + public Manifest Process(Manifest manifest, string outputFolder) + { + File.WriteAllText(Path.Combine(outputFolder, "customPostProcessor.txt"), "customPostProcessor"); + return manifest; + } +} diff --git a/test/docfx.Tests/Assets/template/plugins/Directory.Build.props b/test/docfx.Tests/Assets/template/plugins/Directory.Build.props new file mode 100644 index 00000000000..03cc13c81c8 --- /dev/null +++ b/test/docfx.Tests/Assets/template/plugins/Directory.Build.props @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/docfx.Tests/Assets/template/plugins/plugins.csproj b/test/docfx.Tests/Assets/template/plugins/plugins.csproj new file mode 100644 index 00000000000..a15a04d7c50 --- /dev/null +++ b/test/docfx.Tests/Assets/template/plugins/plugins.csproj @@ -0,0 +1,19 @@ + + + + net6.0 + enable + enable + false + false + + + + + + + + + + + diff --git a/test/docfx.Tests/Assets/template/plugins/plugins.dll b/test/docfx.Tests/Assets/template/plugins/plugins.dll new file mode 100644 index 0000000000000000000000000000000000000000..aa4447dd68b99166a1364b22a418e437cbcf382e GIT binary patch literal 5120 zcmeHLU2Ggz6+ScTb(}SEY=^pWBPWx%DUM@}oS#-nR3~2VI`tO+;oT$+van}&Z`RY! z&MbFkQX7e4l2VC>pcaXTssRE;NT5`0QIR5kBtS@jAiM;4LZVOf1&O{@szk$g?#%u; zjuM1asSi2pd*=MzbI&>VXYP3B^Dk3^h}zI@-zK_&C&ReH_eSebckTIE7rnFf=H46H z^v%76727vz(yK_TYLqP3^#Y?P3>mtH?Hbv+yixVa!rZnk^=RyRev)WfOVH?6IBgp~Syv=B@rb!?_z?-S+Hk6G7+z9|;ipONg+R^#4Iwf} zG00X$Aff!z6IvWC=YH|1IcgpmJ#y$c2cIMd_+9uNd;#kW3;pj*=L2cG6`w7Ju)cU6 z2G8W_DrjCRgHN5wW%0ZY{yKsgoN&A%cm{OW&OLlh!}wS{+7arDYQ?2aMtVdTEn9RY zw%d-zH2SK>bhKW7tuf8k1KMG|oh~T+0qxPZ(*n%f>9@r4FKIWmc6wW9{10OM25^c# z(5~oRG^YIplHWj^qPHPQ(FMAKUK$<764*{Y{Jf|CWo-ElJpo^`UvpXwNaWk*xgGz`_-7|8{Bd9kqz!7xJwO= zb>CFnKev91vfy5gxs*Ny?rOwoGc*nEyD^u?9!${pV{VZIBKfJ}qS3MRVEuDMsnZ`g z0eT7ePC7{kFfaYOMm@mo)DN73jOjFRH=PIWL#8Zwh5oD$kfrGTsOJ$fsgu@#chDu^ zUGzQ5&>;O7I0al%bSb6}(wlTyNiHkNWjaWIqt|I4{Rf!6y^fa9l8BY)Rr(@ummVVK zBr?@8b4hAjhj0V@NUe>gXpp|681~VGE3hKsn$%&CzM)LwLsf|fu|8tRO}b%KNUP|G zOelY1@m4o^JLKE z>mD+m%SU}v5huX@;G<|ecCgmePOKq`(~ebXmZnY1XB8p$D4%M8n zVq*@W2VcOlGDdM6lZcdhkB(oHFvhFHtTNM{mXqWuD zC{D?&7eAg|KQ1lKT8`4EdQCV#pJwcm^nC9^fPQ6>b}eVkw|!Env}zGv+Gi7UKFY72 zCpGF(up~nEX|DJz)*-DRJUS=sK%^a~5WQ&kWGKW+56Gh`UV|aocsG2#u;|#O z&x*Bz_ndIYTv}j;++SlZhA> z^4a{2FZA!e_|6;Y*HY)Ny}!2nRW5Y)#A4YiU07WVgdaqtSpexTrhOmF8uDt*!C|%- zZ}3I0`0S!OwiaCxJYkO36=tnmr2JI+@Z(QVeWYFSNXX`efBmlehaa9FcK`T`-@mdi zzWH%*Pcm&VwHfL+#7MK0@nqI_q@1yDJBY0ZMq0iTY(y6Bf}|Ne;Y zX?Mb@Sitw>7Pm^ucXJJ1Et2rV=eU4R7q?zd=^w>ymA}>`EL@!44xRz*Ja7@b)rNBj zKKS&P(74#&n?0>7n^EK){f^?TfEv&UBGMZ6S&Z)E*2jKo8^$qUJBpLHX?qs764uRF zM)6i@Hlkzrg40ws{9clX%tHLLz*X@01OgW#T8`OA-!gbUv4&Jk)3B+i9vNg?!!4Dg zs?ZAJB5X~fapiw5ma`Z3U(cPndyjKexxlcYvALm{l_$&W&^88l(}g56dd5 g=pZuVzWvbk0esvQFrxn%%%AwU2YTTDlURZO0{wl%h5!Hn literal 0 HcmV?d00001 diff --git a/test/docfx.Tests/CompositeCommandTest.cs b/test/docfx.Tests/CompositeCommandTest.cs index 14d17d758e5..99a867628d4 100644 --- a/test/docfx.Tests/CompositeCommandTest.cs +++ b/test/docfx.Tests/CompositeCommandTest.cs @@ -31,61 +31,66 @@ public CompositeCommandTest() public void TestCommandFromCSCodeToHtml() { // Create source file - var sourceCode = @" -namespace Hello{ -/// -/// The class < > > description goes here... -/// -/// -/// Here is some < encoded > example... -/// > [!NOTE] -/// > This is *note* -/// -/// -/// var handler = DateTimeHandler(); -/// for (var i = 0; i < 10; i++){ -/// date = date.AddMonths(1); -/// } -/// -/// -public class HelloWorld {}} -"; + var sourceCode = """ + + namespace Hello{ + /// + /// The class < > > description goes here... + /// + /// + /// Here is some < encoded > example... + /// > [!NOTE] + /// > This is *note* + /// + /// + /// var handler = DateTimeHandler(); + /// for (var i = 0; i < 10; i++){ + /// date = date.AddMonths(1); + /// } + /// + /// + public class HelloWorld {}} + + """; var sourceFile = Path.Combine(_projectFolder, "src", "test.cs"); CreateFile(sourceFile, sourceCode, "src"); - var docfxJson = $@"{{ -""metadata"": [ - {{ - ""src"": ""src/test.cs"", - ""dest"": ""api"" - }} -], -""build"": {{ - ""content"": {{ - ""files"": ""api/*.yml"" - }}, - ""dest"": ""{_outputFolder.ToNormalizedPath()}/site"", - ""sitemap"":{{ - ""baseUrl"": ""https://dotnet.github.io/docfx"", - ""priority"": 0.1, - ""changefreq"": ""monthly"", - ""fileOptions"":{{ - ""**.yml"": {{ - ""priority"": 0.3, - ""lastmod"": ""1999-01-01"" - }}, - ""**/Hello.yml"": {{ - ""baseUrl"": ""https://dotnet.github.io/docfx/1"", - ""priority"": 0.8, - ""changefreq"": ""Daily"" - }} - }} - }} -}} -}}"; + var docfxJson = $$""" + { + "metadata": [ + { + "src": "src/test.cs", + "dest": "api" + } + ], + "build": { + "content": { + "files": "api/*.yml" + }, + "dest": "{{_outputFolder.ToNormalizedPath()}}/site", + "sitemap":{ + "baseUrl": "https://dotnet.github.io/docfx", + "priority": 0.1, + "changefreq": "monthly", + "fileOptions":{ + "**.yml": { + "priority": 0.3, + "lastmod": "1999-01-01" + }, + "**/Hello.yml": { + "baseUrl": "https://dotnet.github.io/docfx/1", + "priority": 0.8, + "changefreq": "Daily" + } + } + } + } + } + """; + var docfxJsonFile = Path.Combine(_projectFolder, "docfx.json"); File.WriteAllText(docfxJsonFile, docfxJson); - Program.Main(new string[] { docfxJsonFile }); + Assert.Equal(0, Program.Main(new string[] { docfxJsonFile })); var filePath = Path.Combine(_outputFolder, "site", "api", "Hello.HelloWorld.html"); Assert.True(File.Exists(filePath)); var html = new HtmlDocument(); @@ -95,10 +100,12 @@ public class HelloWorld {}} var note = html.DocumentNode.SelectSingleNode("//div[@class='NOTE']").InnerHtml; Assert.Equal("
Note
\n

This is note

", note.Trim()); var code = html.DocumentNode.SelectNodes("//pre/code")[1].InnerHtml; - Assert.Equal(@"var handler = DateTimeHandler(); -for (var i = 0; i < 10; i++){ - date = date.AddMonths(1); -}".Replace("\r\n", "\n"), code); + Assert.Equal(""" + var handler = DateTimeHandler(); + for (var i = 0; i < 10; i++){ + date = date.AddMonths(1); + } + """.Replace("\r\n", "\n"), code); var sitemap = Path.Combine(_outputFolder, "site", "sitemap.xml"); Assert.True(File.Exists(sitemap)); diff --git a/test/docfx.Tests/DocsetTest.cs b/test/docfx.Tests/DocsetTest.cs index 6dd1d9bc692..4e1ca526c6c 100644 --- a/test/docfx.Tests/DocsetTest.cs +++ b/test/docfx.Tests/DocsetTest.cs @@ -54,4 +54,26 @@ public static async Task CustomLogo_Override_LogoFromTemplate() Assert.Equal("my svg", outputs["logo.svg"]()); } + + [Fact] + public static async Task Load_Custom_Plugin_From_Template() + { + var outputs = await Build(new() + { + ["docfx.json"] = + """ + { + "build": { + "content": [{ "files": [ "*.md" ] }], + "template": ["default", "../../Assets/template"], + "dest": "_site", + "postProcessors": ["CustomPostProcessor"] + } + } + """, + ["index.md"] = "" + }); + + Assert.Equal("customPostProcessor", outputs["customPostProcessor.txt"]()); + } } diff --git a/test/docfx.Tests/PdfTest.cs b/test/docfx.Tests/PdfTest.cs index 2645c2bbee2..8a2d0a96fdc 100644 --- a/test/docfx.Tests/PdfTest.cs +++ b/test/docfx.Tests/PdfTest.cs @@ -2,9 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.Runtime.CompilerServices; - using Microsoft.DocAsCode.Tests.Common; - using Xunit; namespace Microsoft.DocAsCode.Tests;