Compare commits
7 Commits
1823c5751c
...
43b4ef364e
Author | SHA1 | Date |
---|---|---|
|
43b4ef364e | 2 years ago |
|
b988194972 | 2 years ago |
|
86f3c3a5ec | 2 years ago |
|
ef330a3672 | 2 years ago |
|
c74958f1b8 | 2 years ago |
|
16bcbb313d | 2 years ago |
|
afdac21450 | 2 years ago |
@ -0,0 +1,45 @@
|
|||||||
|
namespace DocFx.Plugin.LastModified.Files;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A managed reference YAML document.
|
||||||
|
/// </summary>
|
||||||
|
public class ManagedReferenceDocument
|
||||||
|
{
|
||||||
|
public required ManagedReferenceItem[]? Items { get; set; }
|
||||||
|
|
||||||
|
public class ManagedReferenceItem
|
||||||
|
{
|
||||||
|
public required string Uid { get; set; }
|
||||||
|
|
||||||
|
public required string CommentId { get; set; }
|
||||||
|
|
||||||
|
public required string Id { get; set; }
|
||||||
|
|
||||||
|
public required string Name { get; set; }
|
||||||
|
|
||||||
|
public required string FullName { get; set; }
|
||||||
|
|
||||||
|
public required string NameWithType { get; set; }
|
||||||
|
|
||||||
|
public string? Type { get; set; }
|
||||||
|
|
||||||
|
public string? Parent { get; set; }
|
||||||
|
|
||||||
|
public string[]? Children { get; set; }
|
||||||
|
|
||||||
|
public string[]? Langs { get; set; }
|
||||||
|
|
||||||
|
public ManagedReferenceItemSource? Source { get; set; }
|
||||||
|
|
||||||
|
public string[]? Assemblies { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ManagedReferenceItemSource
|
||||||
|
{
|
||||||
|
public required string Id { get; set; }
|
||||||
|
|
||||||
|
public required string Path { get; set; }
|
||||||
|
|
||||||
|
public int? StartLine { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
namespace DocFx.Plugin.LastModified;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
public record LastModifiedInfo
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the last modified date of the file.
|
||||||
|
/// </summary>
|
||||||
|
public DateTimeOffset LastModified { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the commit header, if any.
|
||||||
|
/// </summary>
|
||||||
|
public string CommitHeader { get; init; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the commit body, if any.
|
||||||
|
/// </summary>
|
||||||
|
public string CommitBody { get; init; } = string.Empty;
|
||||||
|
}
|
@ -1,171 +1,56 @@
|
|||||||
using System;
|
namespace DocFx.Plugin.LastModified;
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Composition;
|
using System.Composition;
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text;
|
using Docfx.Common;
|
||||||
using DocFx.Plugin.LastModified.Helpers;
|
using Docfx.Plugins;
|
||||||
using HtmlAgilityPack;
|
using Processors;
|
||||||
using LibGit2Sharp;
|
|
||||||
using Microsoft.DocAsCode.Common;
|
/// <summary>
|
||||||
using Microsoft.DocAsCode.Plugins;
|
/// Post-processor responsible for injecting last modified date according to commit or file modified date.
|
||||||
|
/// </summary>
|
||||||
namespace DocFx.Plugin.LastModified
|
[Export(nameof(LastModifiedPostProcessor), typeof(IPostProcessor))]
|
||||||
|
public class LastModifiedPostProcessor : IPostProcessor
|
||||||
{
|
{
|
||||||
/// <summary>
|
private int _addedFiles;
|
||||||
/// Post-processor responsible for injecting last modified date according to commit or file modified date.
|
|
||||||
/// </summary>
|
|
||||||
[Export(nameof(LastModifiedPostProcessor), typeof(IPostProcessor))]
|
|
||||||
public class LastModifiedPostProcessor : IPostProcessor
|
|
||||||
{
|
|
||||||
private int _addedFiles;
|
|
||||||
private Repository _repo;
|
|
||||||
|
|
||||||
public ImmutableDictionary<string, object> PrepareMetadata(ImmutableDictionary<string, object> metadata)
|
|
||||||
=> metadata;
|
|
||||||
|
|
||||||
public Manifest Process(Manifest manifest, string outputFolder)
|
|
||||||
{
|
|
||||||
var versionInfo = Assembly.GetExecutingAssembly()
|
|
||||||
.GetCustomAttribute<AssemblyInformationalVersionAttribute>()
|
|
||||||
?.InformationalVersion ??
|
|
||||||
Assembly.GetExecutingAssembly().GetName().Version?.ToString();
|
|
||||||
Logger.LogInfo($"Version: {versionInfo}");
|
|
||||||
Logger.LogInfo("Begin adding last modified date to items...");
|
|
||||||
|
|
||||||
// attempt to fetch git repo from the current project
|
|
||||||
var gitDirectory = Repository.Discover(manifest.SourceBasePath);
|
|
||||||
if (gitDirectory != null) _repo = new Repository(gitDirectory);
|
|
||||||
|
|
||||||
foreach (var manifestItem in manifest.Files.Where(x => x.DocumentType == "Conceptual"))
|
private IEnumerable<IProcessor> Processors { get; } = new List<IProcessor>
|
||||||
foreach (var manifestItemOutputFile in manifestItem.OutputFiles)
|
{
|
||||||
{
|
new ConceptualProcessor(),
|
||||||
var sourcePath = Path.Combine(manifest.SourceBasePath, manifestItem.SourceRelativePath);
|
new ManagedReferenceProcessor(),
|
||||||
var outputPath = Path.Combine(outputFolder, manifestItemOutputFile.Value.RelativePath);
|
};
|
||||||
if (_repo != null)
|
|
||||||
{
|
|
||||||
var commitInfo = _repo.GetCommitInfo(sourcePath);
|
|
||||||
if (commitInfo != null)
|
|
||||||
{
|
|
||||||
Logger.LogVerbose("Assigning commit date...");
|
|
||||||
var lastModified = commitInfo.Author.When;
|
|
||||||
|
|
||||||
var commitHeaderBuilder = new StringBuilder();
|
|
||||||
Logger.LogVerbose("Appending commit author and email...");
|
|
||||||
commitHeaderBuilder.AppendLine($"Author: {commitInfo.Author.Name}");
|
|
||||||
Logger.LogVerbose("Appending commit SHA...");
|
|
||||||
commitHeaderBuilder.AppendLine($"Commit: {commitInfo.Sha}");
|
|
||||||
|
|
||||||
var commitHeader = commitHeaderBuilder.ToString();
|
|
||||||
// truncate to 200 in case of huge commit body
|
|
||||||
var commitBody = commitInfo.Message.Truncate(300);
|
|
||||||
Logger.LogVerbose($"Writing {lastModified} with reason for {outputPath}...");
|
|
||||||
WriteModifiedDate(outputPath, lastModified, commitHeader, commitBody);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var fileLastModified = File.GetLastWriteTimeUtc(sourcePath);
|
/// <inheritdoc />
|
||||||
Logger.LogVerbose($"Writing {fileLastModified} for {outputPath}...");
|
public ImmutableDictionary<string, object> PrepareMetadata(ImmutableDictionary<string, object> metadata)
|
||||||
WriteModifiedDate(outputPath, fileLastModified);
|
=> metadata;
|
||||||
}
|
|
||||||
|
|
||||||
// dispose repo after usage
|
/// <inheritdoc />
|
||||||
_repo?.Dispose();
|
public Manifest Process(Manifest manifest, string outputFolder)
|
||||||
|
{
|
||||||
|
var versionInfo = Assembly.GetExecutingAssembly().GetName().Version;
|
||||||
|
|
||||||
Logger.LogInfo($"Added modification date to {_addedFiles} conceptual articles.");
|
Logger.LogInfo($"Version: {versionInfo}");
|
||||||
return manifest;
|
Logger.LogInfo("Begin adding last modified date to items...");
|
||||||
}
|
|
||||||
|
|
||||||
private void WriteModifiedDate(string outputPath, DateTimeOffset modifiedDate, string commitHeader = null,
|
foreach (var manifestItem in manifest.Files)
|
||||||
string commitBody = null)
|
|
||||||
{
|
{
|
||||||
if (outputPath == null) throw new ArgumentNullException(nameof(outputPath));
|
var processor = Processors.FirstOrDefault(p => p.Supports(manifestItem.Type));
|
||||||
|
if (processor == null)
|
||||||
// load the document
|
|
||||||
var htmlDoc = new HtmlDocument();
|
|
||||||
htmlDoc.Load(outputPath);
|
|
||||||
|
|
||||||
// check for article container
|
|
||||||
var articleNode = htmlDoc.DocumentNode.SelectSingleNode("//article[contains(@class, 'content wrap')]");
|
|
||||||
if (articleNode == null)
|
|
||||||
{
|
{
|
||||||
Logger.LogDiagnostic("ArticleNode not found, returning.");
|
Logger.LogDiagnostic($"No processor found for {manifestItem.Type}, skipping.");
|
||||||
return;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var paragraphNode = htmlDoc.CreateElement("p");
|
if (processor.TryProcess(manifest, manifestItem, outputFolder))
|
||||||
paragraphNode.InnerHtml = $"This page was last modified at {modifiedDate} (UTC).";
|
|
||||||
var separatorNode = htmlDoc.CreateElement("hr");
|
|
||||||
articleNode.AppendChild(separatorNode);
|
|
||||||
articleNode.AppendChild(paragraphNode);
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(commitHeader))
|
|
||||||
{
|
{
|
||||||
// inject collapsible container script
|
_addedFiles++;
|
||||||
InjectCollapseScript(htmlDoc);
|
|
||||||
|
|
||||||
// create collapse container
|
|
||||||
var collapsibleNode = htmlDoc.CreateElement("div");
|
|
||||||
collapsibleNode.SetAttributeValue("class", "collapse-container last-modified");
|
|
||||||
collapsibleNode.SetAttributeValue("id", "accordion");
|
|
||||||
var reasonHeaderNode = htmlDoc.CreateElement("span");
|
|
||||||
reasonHeaderNode.InnerHtml = "<span class=\"arrow-r\"></span>Commit Message";
|
|
||||||
var reasonContainerNode = htmlDoc.CreateElement("div");
|
|
||||||
|
|
||||||
// inject header
|
|
||||||
var preCodeBlockNode = htmlDoc.CreateElement("pre");
|
|
||||||
var codeBlockNode = htmlDoc.CreateElement("code");
|
|
||||||
codeBlockNode.InnerHtml = commitHeader;
|
|
||||||
preCodeBlockNode.AppendChild(codeBlockNode);
|
|
||||||
reasonContainerNode.AppendChild(preCodeBlockNode);
|
|
||||||
|
|
||||||
// inject body
|
|
||||||
preCodeBlockNode = htmlDoc.CreateElement("pre");
|
|
||||||
codeBlockNode = htmlDoc.CreateElement("code");
|
|
||||||
codeBlockNode.SetAttributeValue("class", "xml");
|
|
||||||
codeBlockNode.InnerHtml = commitBody;
|
|
||||||
preCodeBlockNode.AppendChild(codeBlockNode);
|
|
||||||
reasonContainerNode.AppendChild(preCodeBlockNode);
|
|
||||||
|
|
||||||
// inject the entire block
|
|
||||||
collapsibleNode.AppendChild(reasonHeaderNode);
|
|
||||||
collapsibleNode.AppendChild(reasonContainerNode);
|
|
||||||
articleNode.AppendChild(collapsibleNode);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
htmlDoc.Save(outputPath);
|
|
||||||
_addedFiles++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
Logger.LogInfo($"Added modification date to {_addedFiles} articles.");
|
||||||
/// Injects script required for collapsible dropdown menu.
|
return manifest;
|
||||||
/// </summary>
|
|
||||||
/// <seealso cref="!:https://github.com/jordnkr/collapsible" />
|
|
||||||
private static void InjectCollapseScript(HtmlDocument htmlDoc)
|
|
||||||
{
|
|
||||||
var bodyNode = htmlDoc.DocumentNode.SelectSingleNode("//body");
|
|
||||||
|
|
||||||
var accordionNode = htmlDoc.CreateElement("script");
|
|
||||||
accordionNode.InnerHtml = @"
|
|
||||||
$( function() {
|
|
||||||
$( ""#accordion"" ).collapsible();
|
|
||||||
} );";
|
|
||||||
bodyNode.AppendChild(accordionNode);
|
|
||||||
|
|
||||||
var collapsibleScriptNode = htmlDoc.CreateElement("script");
|
|
||||||
collapsibleScriptNode.SetAttributeValue("type", "text/javascript");
|
|
||||||
collapsibleScriptNode.SetAttributeValue("src",
|
|
||||||
"https://cdn.rawgit.com/jordnkr/collapsible/master/jquery.collapsible.min.js");
|
|
||||||
bodyNode.AppendChild(collapsibleScriptNode);
|
|
||||||
|
|
||||||
var headNode = htmlDoc.DocumentNode.SelectSingleNode("//head");
|
|
||||||
var collapsibleCssNode = htmlDoc.CreateElement("link");
|
|
||||||
collapsibleCssNode.SetAttributeValue("rel", "stylesheet");
|
|
||||||
collapsibleCssNode.SetAttributeValue("href",
|
|
||||||
"https://cdn.rawgit.com/jordnkr/collapsible/master/collapsible.css");
|
|
||||||
headNode.AppendChild(collapsibleCssNode);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,116 @@
|
|||||||
|
namespace DocFx.Plugin.LastModified.Processors;
|
||||||
|
|
||||||
|
using Docfx.Common;
|
||||||
|
using Docfx.Plugins;
|
||||||
|
using HtmlAgilityPack;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An abstract processor for adding last modified date to articles, contains common methods.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class AbstractProcessor : IProcessor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether this processor supports the given type.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">The type of manifest item.</param>
|
||||||
|
/// <returns>True if this processor supports the given type, false otherwise.</returns>
|
||||||
|
public abstract bool Supports(string type);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Processes the given manifest item.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="manifest">The <see cref="Manifest"/> that contains the manifest item.</param>
|
||||||
|
/// <param name="manifestItem">The <see cref="ManifestItem"/> to process.</param>
|
||||||
|
/// <param name="outputFolder">The output folder.</param>
|
||||||
|
public abstract void Process(Manifest manifest, ManifestItem manifestItem, string outputFolder);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attempts to process the given manifest item.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="manifest">The <see cref="Manifest"/> that contains the manifest item.</param>
|
||||||
|
/// <param name="manifestItem">The <see cref="ManifestItem"/> to process.</param>
|
||||||
|
/// <param name="outputFolder">The output folder.</param>
|
||||||
|
/// <returns>True if the manifest item was processed, false otherwise.</returns>
|
||||||
|
public bool TryProcess(Manifest manifest, ManifestItem manifestItem, string outputFolder)
|
||||||
|
{
|
||||||
|
if (!Supports(manifestItem.Type))
|
||||||
|
{
|
||||||
|
Logger.LogVerbose($"Skipping {manifestItem.Type}...");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.LogDiagnostic($"Processing {manifestItem.Type}...");
|
||||||
|
Process(manifest, manifestItem, outputFolder);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Builds the last modified info for the given file path.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="filePath">The file path.</param>
|
||||||
|
/// <returns>The <see cref="LastModifiedInfo"/> for the given file path.</returns>
|
||||||
|
protected abstract LastModifiedInfo GetLastModifiedInfo(string filePath);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Modifies the given document with the given last modified info.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="filePath">The file path to the document.</param>
|
||||||
|
/// <param name="lastModifiedInfo">The <see cref="LastModifiedInfo"/> to use.</param>
|
||||||
|
protected virtual void ModifyDocument(string filePath, LastModifiedInfo lastModifiedInfo)
|
||||||
|
{
|
||||||
|
var htmlDocument = new HtmlDocument();
|
||||||
|
htmlDocument.Load(filePath);
|
||||||
|
|
||||||
|
var articleNode = htmlDocument.DocumentNode.SelectSingleNode("//article");
|
||||||
|
if (articleNode == null)
|
||||||
|
{
|
||||||
|
Logger.LogWarning($"No article node found in {filePath}.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var separatorNode = htmlDocument.CreateElement("hr");
|
||||||
|
articleNode.AppendChild(separatorNode);
|
||||||
|
|
||||||
|
var lastModifiedNode = htmlDocument.CreateElement("div");
|
||||||
|
lastModifiedNode.SetAttributeValue("class", "last-modified");
|
||||||
|
articleNode.AppendChild(lastModifiedNode);
|
||||||
|
|
||||||
|
var paragraphNode = htmlDocument.CreateElement("p");
|
||||||
|
paragraphNode.InnerHtml = $"This page was last modified at {lastModifiedInfo.LastModified} (UTC).";
|
||||||
|
lastModifiedNode.AppendChild(paragraphNode);
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(lastModifiedInfo.CommitHeader))
|
||||||
|
{
|
||||||
|
htmlDocument.Save(filePath);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var collapsibleNode = htmlDocument.CreateElement("details");
|
||||||
|
lastModifiedNode.AppendChild(collapsibleNode);
|
||||||
|
|
||||||
|
var reasonHeaderNode = htmlDocument.CreateElement("summary");
|
||||||
|
reasonHeaderNode.SetAttributeValue("style", "display: list-item;");
|
||||||
|
reasonHeaderNode.InnerHtml = "Commit Message";
|
||||||
|
collapsibleNode.AppendChild(reasonHeaderNode);
|
||||||
|
|
||||||
|
var reasonContainerNode = htmlDocument.CreateElement("div");
|
||||||
|
collapsibleNode.AppendChild(reasonContainerNode);
|
||||||
|
|
||||||
|
var preCodeBlockNode = htmlDocument.CreateElement("pre");
|
||||||
|
var codeBlockNode = htmlDocument.CreateElement("code");
|
||||||
|
codeBlockNode.InnerHtml = lastModifiedInfo.CommitHeader;
|
||||||
|
preCodeBlockNode.AppendChild(codeBlockNode);
|
||||||
|
reasonContainerNode.AppendChild(preCodeBlockNode);
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(lastModifiedInfo.CommitBody))
|
||||||
|
{
|
||||||
|
preCodeBlockNode = htmlDocument.CreateElement("pre");
|
||||||
|
codeBlockNode = htmlDocument.CreateElement("code");
|
||||||
|
codeBlockNode.InnerHtml = lastModifiedInfo.CommitBody;
|
||||||
|
preCodeBlockNode.AppendChild(codeBlockNode);
|
||||||
|
reasonContainerNode.AppendChild(preCodeBlockNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
htmlDocument.Save(filePath);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,74 @@
|
|||||||
|
namespace DocFx.Plugin.LastModified.Processors;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
using Docfx.Common;
|
||||||
|
using Docfx.Plugins;
|
||||||
|
using Helpers;
|
||||||
|
using LibGit2Sharp;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Processor for adding last modified date to conceptual articles.
|
||||||
|
/// </summary>
|
||||||
|
public class ConceptualProcessor : AbstractProcessor
|
||||||
|
{
|
||||||
|
private Repository? _repo;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override bool Supports(string type)
|
||||||
|
{
|
||||||
|
return type == "Conceptual";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void Process(Manifest manifest, ManifestItem manifestItem, string outputFolder)
|
||||||
|
{
|
||||||
|
var repository = Repository.Discover(manifest.SourceBasePath);
|
||||||
|
if (repository != null)
|
||||||
|
{
|
||||||
|
_repo = new Repository(repository);
|
||||||
|
}
|
||||||
|
|
||||||
|
var sourcePath = Path.Combine(manifest.SourceBasePath, manifestItem.SourceRelativePath);
|
||||||
|
var outputPath = Path.Combine(outputFolder, manifestItem.Output[".html"].RelativePath);
|
||||||
|
|
||||||
|
var lastModifiedInfo = GetLastModifiedInfo(sourcePath);
|
||||||
|
|
||||||
|
ModifyDocument(outputPath, lastModifiedInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override LastModifiedInfo GetLastModifiedInfo(string filePath)
|
||||||
|
{
|
||||||
|
var lastModified = DateTimeOffset.MinValue;
|
||||||
|
var commitHeader = string.Empty;
|
||||||
|
var commitBody = string.Empty;
|
||||||
|
|
||||||
|
if (_repo?.GetCommitInfo(filePath) is { } commitInfo)
|
||||||
|
{
|
||||||
|
lastModified = commitInfo.Author.When;
|
||||||
|
Logger.LogDiagnostic($"Last modified date: {lastModified} (UTC)");
|
||||||
|
|
||||||
|
var commitHeaderBuilder = new StringBuilder();
|
||||||
|
commitHeaderBuilder.AppendLine($"Author: {commitInfo.Author.Name}");
|
||||||
|
commitHeaderBuilder.AppendLine($"Commit: {commitInfo.Sha}");
|
||||||
|
|
||||||
|
commitHeader = commitHeaderBuilder.ToString();
|
||||||
|
commitBody = commitInfo.Message.Truncate(300);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastModified == DateTimeOffset.MinValue)
|
||||||
|
{
|
||||||
|
lastModified = File.GetLastWriteTimeUtc(filePath);
|
||||||
|
Logger.LogVerbose($"Last modified date: {lastModified} (UTC)");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new LastModifiedInfo
|
||||||
|
{
|
||||||
|
LastModified = lastModified,
|
||||||
|
CommitHeader = commitHeader,
|
||||||
|
CommitBody = commitBody,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
namespace DocFx.Plugin.LastModified.Processors;
|
||||||
|
|
||||||
|
using Docfx.Plugins;
|
||||||
|
|
||||||
|
public interface IProcessor
|
||||||
|
{
|
||||||
|
bool Supports(string type);
|
||||||
|
|
||||||
|
void Process(Manifest manifest, ManifestItem manifestItem, string outputFolder);
|
||||||
|
|
||||||
|
bool TryProcess(Manifest manifest, ManifestItem manifestItem, string outputFolder);
|
||||||
|
}
|
@ -0,0 +1,93 @@
|
|||||||
|
namespace DocFx.Plugin.LastModified.Processors;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using Docfx.Common;
|
||||||
|
using Docfx.Plugins;
|
||||||
|
using Files;
|
||||||
|
using Helpers;
|
||||||
|
using LibGit2Sharp;
|
||||||
|
using YamlDotNet.Serialization;
|
||||||
|
using YamlDotNet.Serialization.NamingConventions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Processor for adding last modified date to managed reference articles.
|
||||||
|
/// </summary>
|
||||||
|
public class ManagedReferenceProcessor : AbstractProcessor
|
||||||
|
{
|
||||||
|
private readonly Deserializer _deserializer = Deserializer.FromValueDeserializer(
|
||||||
|
new DeserializerBuilder()
|
||||||
|
.WithNamingConvention(CamelCaseNamingConvention.Instance)
|
||||||
|
.IgnoreUnmatchedProperties()
|
||||||
|
.BuildValueDeserializer());
|
||||||
|
|
||||||
|
private Repository? _repo;
|
||||||
|
private Manifest? _manifest;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override bool Supports(string type)
|
||||||
|
{
|
||||||
|
return type == "ManagedReference";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void Process(Manifest manifest, ManifestItem manifestItem, string outputFolder)
|
||||||
|
{
|
||||||
|
var repository = Repository.Discover(manifest.SourceBasePath);
|
||||||
|
if (repository != null)
|
||||||
|
{
|
||||||
|
_repo = new Repository(repository);
|
||||||
|
}
|
||||||
|
|
||||||
|
_manifest = manifest;
|
||||||
|
|
||||||
|
var sourcePath = Path.Combine(manifest.SourceBasePath, manifestItem.SourceRelativePath);
|
||||||
|
var outputPath = Path.Combine(outputFolder, manifestItem.Output[".html"].RelativePath);
|
||||||
|
|
||||||
|
var lastModifiedInfo = GetLastModifiedInfo(sourcePath);
|
||||||
|
|
||||||
|
ModifyDocument(outputPath, lastModifiedInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override LastModifiedInfo GetLastModifiedInfo(string filePath)
|
||||||
|
{
|
||||||
|
var yml = File.ReadAllText(filePath);
|
||||||
|
var managedReferenceDocument = _deserializer.Deserialize<ManagedReferenceDocument?>(yml);
|
||||||
|
var itemSource = managedReferenceDocument?.Items?.FirstOrDefault(i => !string.IsNullOrEmpty(i.Source?.Path));
|
||||||
|
var itemSourcePath = itemSource?.Source?.Path ?? string.Empty;
|
||||||
|
|
||||||
|
var lastModified = DateTimeOffset.MinValue;
|
||||||
|
var commitHeader = string.Empty;
|
||||||
|
var commitBody = string.Empty;
|
||||||
|
|
||||||
|
var sourcePath = Path.Combine(_manifest?.SourceBasePath ?? string.Empty, itemSourcePath);
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(itemSourcePath) && _repo?.GetCommitInfo(sourcePath) is { } commitInfo)
|
||||||
|
{
|
||||||
|
lastModified = commitInfo.Author.When;
|
||||||
|
Logger.LogDiagnostic($"Last modified date: {lastModified} (UTC)");
|
||||||
|
|
||||||
|
var commitHeaderBuilder = new System.Text.StringBuilder();
|
||||||
|
commitHeaderBuilder.AppendLine($"Author: {commitInfo.Author.Name}");
|
||||||
|
commitHeaderBuilder.AppendLine($"Commit: {commitInfo.Sha}");
|
||||||
|
|
||||||
|
commitHeader = commitHeaderBuilder.ToString();
|
||||||
|
commitBody = commitInfo.Message.Truncate(300);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastModified == DateTimeOffset.MinValue)
|
||||||
|
{
|
||||||
|
lastModified = File.GetLastWriteTimeUtc(sourcePath);
|
||||||
|
Logger.LogVerbose($"Last modified date: {lastModified} (UTC)");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new LastModifiedInfo
|
||||||
|
{
|
||||||
|
LastModified = lastModified,
|
||||||
|
CommitHeader = commitHeader,
|
||||||
|
CommitBody = commitBody,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
Based on https://github.com/dhkatz/DocFx.Plugin.LastModified
|
Loading…
Reference in New Issue