Compare commits

..

No commits in common. 'master' and 'v1.1' have entirely different histories.
master ... v1.1

357
.gitignore vendored

@ -1,357 +0,0 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
/src/docker-compose/cache_postgres/*
/src/docker-compose/cache_redis/*
/src/docker-compose/scraper_postgres/*
/src/docker-compose/subscriptions_postgres/*

@ -8,28 +8,39 @@ inputs:
docs-directory:
description: ''
default: '$PWD/docs'
docfx-version:
description: ''
default: '2.74.1'
newtonsoftjson-version:
description: ''
default: '13.0.1'
default: '11.0.2'
sandcastlexrefgenerator-directory:
description: ''
default: './build/SandcastleXrefGenerator'
runs:
using: "composite"
steps:
- name: Checkout Repository
uses: actions/checkout@v3
uses: actions/checkout@v2
with:
submodules: recursive
fetch-depth: 0
- name: Setup .NET 8
- name: Setup .NET Core 3.1
uses: actions/setup-dotnet@master
with:
dotnet-version: 3.1.x
- name: Setup .NET 5
uses: actions/setup-dotnet@master
with:
dotnet-version: 8.x.x
dotnet-version: 5.0.x
- name: Ensure NuGet Source
uses: fabriciomurta/ensure-nuget-source@v1
with:
name: 'nuget.org'
url: 'https://api.nuget.org/v3/index.json'
- name: Setup BUTR GPR
uses: actions/setup-dotnet@master
@ -37,38 +48,22 @@ runs:
source-url: https://nuget.pkg.github.com/BUTR/index.json
env:
NUGET_AUTH_TOKEN: ${{inputs.github-token}}
- name: Setup NuGet
uses: nuget/setup-nuget@v1
with:
nuget-version: 'latest'
- name: Generating Newtonsoft.Json xref maps
run: >-
dotnet run --project ${{ github.action_path }}/build/SandcastleXrefGenerator -- `
if (-Not (Test-Path -Path '${{inputs.sandcastlexrefgenerator-directory}}')) { Return };
dotnet run -p ${{inputs.sandcastlexrefgenerator-directory}} -- `
Newtonsoft.Json ${{inputs.newtonsoftjson-version}} netstandard2.0 `
https://www.newtonsoft.com/json/help/html/ `
${{inputs.docs-directory}}/xrefs/Newtonsoft.Json-xrefmap.yml
shell: pwsh
- name: Build DocFx.Plugin.LastModified
run: dotnet publish ${{ github.action_path }}/build/DocFx.Plugin.LastModified --configuration Release --output ${{inputs.docs-directory}}/_template/last-modified/plugins;
shell: pwsh
# When a custom fork is needed
# - uses: actions/checkout@v3
# with:
# repository: BUTR/docfx
# path: _actions_docfx
# - uses: actions/setup-node@v4
# with:
# node-version: 16
# - run: npm install
# working-directory: _actions_docfx/templates
# shell: pwsh
# - run: npm run build
# working-directory: _actions_docfx/templates
# shell: pwsh
# - run: dotnet pack _actions_docfx/src/docfx -c Release -o _actions_docfx/drop/nuget
# shell: pwsh
# - run: dotnet tool update --global docfx
# working-directory: _actions_docfx
# shell: pwsh
- name: Install DocFX
run: dotnet tool update -g docfx
shell: pwsh
#- name: DocFx.Plugin.LastModified
# run: >-
# dotnet build build/DocFx.Plugin.LastModified/DocFx.Plugin.LastModified --configuration Release --output docs/_template/last-modified/plugins;
# shell: pwsh

@ -1,30 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<AssemblyName>LastModifiedPostProcessor</AssemblyName>
<RootNamespace>DocFx.Plugin.LastModified</RootNamespace>
<Version>1.2.5</Version>
<RepositoryUrl>https://github.com/Still34/DocFx.Plugin.LastModified</RepositoryUrl>
<PackageLicenseUrl>https://github.com/Still34/DocFx.Plugin.LastModified/blob/master/LICENSE</PackageLicenseUrl>
<RepositoryType>GitHub</RepositoryType>
<PackageTags>DocFX</PackageTags>
<Description>A post-processor plugin for DocFX v2 that adds the last commit date of the conceptual document to the end of the page.</Description>
<Copyright>Still Hsu © 2021</Copyright>
<Company />
<Authors>Still Hsu</Authors>
<Product />
<PackageVersion>1.2.5</PackageVersion>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Docfx.Common" Version="2.74.1" />
<PackageReference Include="Docfx.Plugins" Version="2.74.1" />
<PackageReference Include="htmlagilitypack" Version="1.11.54" />
<PackageReference Include="libgit2sharp" Version="0.29.0" />
<PackageReference Include="Microsoft.Composition" Version="1.0.31" />
<PackageReference Include="YamlDotNet" Version="13.7.1" />
</ItemGroup>
</Project>

@ -1,45 +0,0 @@
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; }
}
}

@ -1,42 +0,0 @@
namespace DocFx.Plugin.LastModified.Helpers;
using System.IO;
using System.Linq;
using LibGit2Sharp;
/// <summary>
/// Provides methods for repository-related operations.
/// </summary>
public static class RepositoryHelper
{
/// <summary>
/// Returns the commit information for the specified file.
/// </summary>
/// <param name="repo">The repository to query against.</param>
/// <param name="srcPath">The path of the file.</param>
/// <returns>
/// A <see cref="Commit"/> object containing the information of the commit.
/// </returns>
public static Commit? GetCommitInfo(this Repository repo, string srcPath)
{
var gitDir = repo.Info.Path
.Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
var repoRoot = Path.Join(gitDir, "..");
if (string.IsNullOrEmpty(repoRoot))
{
throw new DirectoryNotFoundException("Cannot obtain the root directory of the repository.");
}
var relativePath = Path
.GetRelativePath(repoRoot, Path.GetFullPath(srcPath))
.Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
// See libgit2sharp#1520 for sort issue
var logEntry = repo.Commits
.QueryBy(relativePath, new CommitFilter { SortBy = CommitSortStrategies.Topological })
.FirstOrDefault();
return logEntry?.Commit;
}
}

@ -1,54 +0,0 @@
namespace DocFx.Plugin.LastModified.Helpers;
using System.Linq;
/// <summary>
/// Extensions for <see cref="string"/>.
/// </summary>
public static class StringHelper
{
/// <summary>
/// Truncates the string to match the specified <paramref name="length"/>.
/// </summary>
/// <remarks>
/// <para>This method is retrieved and modified from Humanizr/Humanizer.</para>
/// <para>MIT License (c)</para>
/// <para>Copyright (c) .NET Foundation and Contributors</para>
/// </remarks>
/// <param name="value">The string to truncate.</param>
/// <param name="length">The target maximum length of the string.</param>
/// <returns>
/// A truncated string based on the <paramref name="length" /> specified.
/// </returns>
public static string Truncate(this string value, int length)
{
const string truncationString = "...";
if (value.Length == 0)
{
return value;
}
var alphaNumericalCharactersProcessed = 0;
if (value.ToCharArray().Count(char.IsLetterOrDigit) <= length)
{
return value;
}
for (var i = 0; i < value.Length - truncationString.Length; i++)
{
if (char.IsLetterOrDigit(value[i]))
{
alphaNumericalCharactersProcessed++;
}
if (alphaNumericalCharactersProcessed + truncationString.Length == length)
{
return value[.. (i + 1)] + truncationString;
}
}
return value;
}
}

@ -1,21 +0,0 @@
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,56 +0,0 @@
namespace DocFx.Plugin.LastModified;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Composition;
using System.Linq;
using System.Reflection;
using Docfx.Common;
using Docfx.Plugins;
using Processors;
/// <summary>
/// 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 IEnumerable<IProcessor> Processors { get; } = new List<IProcessor>
{
new ConceptualProcessor(),
new ManagedReferenceProcessor(),
};
/// <inheritdoc />
public ImmutableDictionary<string, object> PrepareMetadata(ImmutableDictionary<string, object> metadata)
=> metadata;
/// <inheritdoc />
public Manifest Process(Manifest manifest, string outputFolder)
{
var versionInfo = Assembly.GetExecutingAssembly().GetName().Version;
Logger.LogInfo($"Version: {versionInfo}");
Logger.LogInfo("Begin adding last modified date to items...");
foreach (var manifestItem in manifest.Files)
{
var processor = Processors.FirstOrDefault(p => p.Supports(manifestItem.Type));
if (processor == null)
{
Logger.LogDiagnostic($"No processor found for {manifestItem.Type}, skipping.");
continue;
}
if (processor.TryProcess(manifest, manifestItem, outputFolder))
{
_addedFiles++;
}
}
Logger.LogInfo($"Added modification date to {_addedFiles} articles.");
return manifest;
}
}

@ -1,116 +0,0 @@
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);
}
}

@ -1,74 +0,0 @@
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,
};
}
}

@ -1,12 +0,0 @@
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);
}

@ -1,93 +0,0 @@
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,
};
}
}

@ -1 +0,0 @@
Based on https://github.com/dhkatz/DocFx.Plugin.LastModified

@ -1,92 +0,0 @@
// Copyright 2019 The Noda Time Authors. All rights reserved.
// Use of this source code is governed by the Apache License 2.0,
// as found in the LICENSE.txt file.
using Microsoft.DocAsCode.Build.Engine;
using Microsoft.DocAsCode.Plugins;
using Mono.Cecil;
using SharpCompress.Archives.Zip;
using System;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using YamlDotNet.Serialization;
namespace SandcastleXrefGenerator
{
public static class Program
{
public static async Task<int> Main(string[] args)
{
if (args.Length != 5)
{
Console.WriteLine("Usage: <nuget package> <version> <tfm> <baseUrl> <outputFile>");
return 1;
}
var packageStream = await DownloadPackage(args[0], args[1]);
var module = LoadModule(packageStream, args[2]);
var map = BuildXRefMap(module, args[3]);
var serializer = new SerializerBuilder().Build();
var file = new FileInfo(args[4]);
file.Directory!.Create();
await using var stream = file.CreateText();
await stream.WriteLineAsync("### YamlMime:ManagedReference");
serializer.Serialize(stream, map);
return 0;
}
private static async Task<Stream> DownloadPackage(string package, string version)
{
var client = new HttpClient();
var bytes = await client.GetByteArrayAsync($"https://www.nuget.org/api/v2/package/{package}/{version}");
return new MemoryStream(bytes);
}
private static ModuleDefinition LoadModule(Stream package, string tfm)
{
var prefix = $"lib/{tfm}";
using var zip = ZipArchive.Open(package);
foreach (var entry in zip.Entries)
{
// We assume there's just one assembly, for simplicity.
if (!entry.Key.StartsWith(prefix) || !entry.Key.EndsWith(".dll")) continue;
using var stream = entry.OpenEntryStream();
// Mono.Cecil requires the stream to be seekable. It's simplest
// just to copy the whole DLL to a MemoryStream and pass that to Cecil.
var ms = new MemoryStream();
stream.CopyTo(ms);
ms.Position = 0;
return ModuleDefinition.ReadModule(ms);
}
throw new Exception($"No file found in package starting with '{prefix}'");
}
private static XRefMap BuildXRefMap(ModuleDefinition module, string baseUrl) => new()
{
BaseUrl = baseUrl,
References = module.Types
.Select(CreateXRefSpec)
.Where(spec => spec != null)
.ToList()
};
private static XRefSpec CreateXRefSpec(TypeDefinition type)
{
if (!type.IsPublic)
return null;
return new XRefSpec
{
Name = type.Name, // TODO: Get the name with <T> etc in
Uid = type.FullName, // Is this really enough?
Href = $"T_{type.FullName.Replace('.', '_').Replace('`', '_')}.htm",
CommentId = $"T:{type.FullName}"
};
}
}
}

@ -1,12 +0,0 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"profiles": {
"SandcastleXrefGenerator": {
"commandName": "Project",
"commandLineArgs": "Newtonsoft.Json 13.0.1 netstandard2.0 https://www.newtonsoft.com/json/help/html/ xrefs/Newtonsoft.Json-xrefmap.yml",
"environmentVariables": {
}
}
}
}

@ -1,22 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<LangVersion>11.0</LangVersion>
</PropertyGroup>
<ItemGroup>
<!-- It's this dependency that forces us to use the full framework. Replace when we can... -->
<PackageReference Include="Microsoft.DocAsCode.Build.Engine" Version="2.66.1" />
<PackageReference Include="Mono.Cecil" Version="0.11.5" />
<PackageReference Include="MoreLinq" Version="3.4.2" />
<PackageReference Include="SharpCompress" Version="0.33.0" />
<PackageReference Include="YamlDotNet" Version="13.1.0" />
<Reference Include="System.Net.Http" />
<!-- Make sure we can build on non-Windows, even though we can't run. -->
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" PrivateAssets="All" />
</ItemGroup>
</Project>
Loading…
Cancel
Save