Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Build docs with Statiq #579

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
output/
.vs/
cache/
15 changes: 15 additions & 0 deletions docs/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System.Threading.Tasks;
using Statiq.App;
using Statiq.Docs;

namespace docs
{
public class Program
{
public static async Task<int> Main(string[] args) =>
await Bootstrapper
.Factory
.CreateDocs(args)
.RunAsync();
}
}
5 changes: 5 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Preview docs

```
dotnet run -- preview
```
24 changes: 24 additions & 0 deletions docs/docs.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>

<ItemGroup>
<Compile Remove="output\**" />
<EmbeddedResource Remove="output\**" />
<None Remove="output\**" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Statiq.CodeAnalysis" Version="1.0.0-beta.42" />
<PackageReference Include="Statiq.Docs" Version="1.0.0-alpha.2" />
<PackageReference Include="Statiq.Web" Version="1.0.0-beta.28" />
</ItemGroup>

<!-- <ItemGroup>
<ProjectReference Include="../../Statiq.Docs\src\Statiq.Docs\Statiq.Docs.csproj" />
</ItemGroup> -->

</Project>
34 changes: 34 additions & 0 deletions docs/docs.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.6.30114.105
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "docs", "docs.csproj", "{C7FC1074-EE53-4787-8E49-F3805698606D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{C7FC1074-EE53-4787-8E49-F3805698606D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C7FC1074-EE53-4787-8E49-F3805698606D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C7FC1074-EE53-4787-8E49-F3805698606D}.Debug|x64.ActiveCfg = Debug|Any CPU
{C7FC1074-EE53-4787-8E49-F3805698606D}.Debug|x64.Build.0 = Debug|Any CPU
{C7FC1074-EE53-4787-8E49-F3805698606D}.Debug|x86.ActiveCfg = Debug|Any CPU
{C7FC1074-EE53-4787-8E49-F3805698606D}.Debug|x86.Build.0 = Debug|Any CPU
{C7FC1074-EE53-4787-8E49-F3805698606D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C7FC1074-EE53-4787-8E49-F3805698606D}.Release|Any CPU.Build.0 = Release|Any CPU
{C7FC1074-EE53-4787-8E49-F3805698606D}.Release|x64.ActiveCfg = Release|Any CPU
{C7FC1074-EE53-4787-8E49-F3805698606D}.Release|x64.Build.0 = Release|Any CPU
{C7FC1074-EE53-4787-8E49-F3805698606D}.Release|x86.ActiveCfg = Release|Any CPU
{C7FC1074-EE53-4787-8E49-F3805698606D}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
4 changes: 4 additions & 0 deletions docs/input/dap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Title: Debug Adapter Protocol
ShowInSidebar: true
Order: 3
---
9 changes: 9 additions & 0 deletions docs/input/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Title: C# Language Server Protocol
NoSidebar: false
ShowInSidebar: false
NoTitle: true
---

# Welcome!

You can explore the documentation by selecting sections from the navigation bar above.
5 changes: 5 additions & 0 deletions docs/jsonrpc.md → docs/input/jsonrpc.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
Title: JSON RPC
ShowInSidebar: true
Order: 2
---

# Using Json Rpc Standalone
One of the benefits of the [JSON-RPC protocol](https://www.jsonrpc.org/specification) is that communication can be fully bi-directional. Through some sort of interface agreement, like LSP / DAP, both sides can act as both client (sender) and server (reciever).

Expand Down
5 changes: 5 additions & 0 deletions docs/lsp.md → docs/input/lsp.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
Title: Language Server Protocol
ShowInSidebar: true
Order: 1
---

# Implementing the Language Server Protocol

The goal of this library is to implement [Language Server Protocol](https://microsoft.github.io/language-server-protocol/) as closely as possible
Expand Down
5 changes: 5 additions & 0 deletions docs/source-generation.md → docs/input/source-generation.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
Title: Source Generators
ShowInSidebar: true
Order: 4
---

# Source Generators
We have built a few source generators to help with aid with implementing the plumbing of all the things. The goals of using source generation this way is to ensure that errors and mistakes are avoided and don't cause issues.

Expand Down
4 changes: 4 additions & 0 deletions docs/settings.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# solutionFiles:
# - ../../LSP.sln
sourceFiles:
- ../../src/**/{!.git,!bin,!obj,!packages,!*.Tests,}/**/*.cs
17 changes: 17 additions & 0 deletions docs/theme/DocsTable.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using Statiq.Common;
using System.Collections.Generic;

namespace Generator
{
/// <summary>
/// This model is used for the DocsTable partial that renders documents, titles, headers, and summaries as a table.
/// </summary>
public class DocsTable
{
public IList<IDocument> Docs { get; set; } = new List<IDocument>();
public string Title { get; set; } = string.Empty;
public string Header { get; set; } = string.Empty;
public bool HasSummary { get; set; }
public bool LinkTypeArguments { get; set; } = true;
}
}
175 changes: 175 additions & 0 deletions docs/theme/Extensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Html;
using Statiq.CodeAnalysis;
using Statiq.Common;
using Statiq.Html;

namespace Generator
{
/// <summary>
/// Extensions used by the views.
/// </summary>
public static class Extensions
{
public static HtmlString Name(this IDocument document) => new HtmlString(FormatName(document.GetString(CodeAnalysisKeys.DisplayName)));

public static HtmlString GetTypeLink(this IExecutionContext context, IDocument document) => context.GetTypeLink(document, null, true);

public static HtmlString GetTypeLink(this IExecutionContext context, IDocument document, bool linkTypeArguments) => context.GetTypeLink(document, null, linkTypeArguments);

public static HtmlString GetTypeLink(this IExecutionContext context, IDocument document, string name) => context.GetTypeLink(document, name, true);

public static HtmlString GetTypeLink(this IExecutionContext context, IDocument document, string name, bool linkTypeArguments)
{
name = name ?? document.GetString(CodeAnalysisKeys.DisplayName);

// Link nullable types to their type argument
if (document.GetString("Name") == "Nullable")
{
IDocument nullableType = document.GetDocumentList(CodeAnalysisKeys.TypeArguments)?.FirstOrDefault();
if (nullableType != null)
{
return context.GetTypeLink(nullableType, name);
}
}

// If it wasn't nullable, format the name
name = FormatName(name);

// Link the type and type parameters seperatly for generic types
IReadOnlyList<IDocument> typeArguments = document.GetDocumentList(CodeAnalysisKeys.TypeArguments);
if (typeArguments?.Count > 0)
{
// Link to the original definition of the generic type
document = document.GetDocument(CodeAnalysisKeys.OriginalDefinition) ?? document;

if (linkTypeArguments)
{
// Get the type argument positions
int begin = name.IndexOf("<wbr>&lt;") + 9;
int openParen = name.IndexOf("&gt;<wbr>(");
int end = name.LastIndexOf("&gt;<wbr>", openParen == -1 ? name.Length : openParen); // Don't look past the opening paren if there is one

// Remove existing type arguments and insert linked type arguments (do this first to preserve original indexes)
if (end - begin > 0)
{
name = name
.Remove(begin, end - begin)
.Insert(begin, string.Join(", <wbr>", typeArguments.Select(x => context.GetTypeLink(x, true).Value)));

// Insert the link for the type
if (!document.Destination.IsNull)
{
name = name.Insert(begin - 9, "</a>").Insert(0, $"<a href=\"{context.GetLink(document.Destination)}\">");
}
}

return new HtmlString(name);
}
}

// If it's a type parameter, create an anchor link to the declaring type's original definition
if (document.GetString("Kind") == "TypeParameter")
{
IDocument declaringType = document.GetDocument(CodeAnalysisKeys.DeclaringType)?.GetDocument(CodeAnalysisKeys.OriginalDefinition);
if (declaringType != null)
{
return new HtmlString(declaringType.Destination.IsNull
? name
: $"<a href=\"{context.GetLink(declaringType.Destination)}#typeparam-{document["Name"]}\">{name}</a>");
}
}

return new HtmlString(document.Destination.IsNull
? name
: $"<a href=\"{context.GetLink(document.Destination)}\">{name}</a>");
}

// https://stackoverflow.com/a/3143036/807064
private static IEnumerable<string> SplitAndKeep(this string s, params char[] delims)
{
int start = 0, index;

while ((index = s.IndexOfAny(delims, start)) != -1)
{
if (index - start > 0)
{
yield return s.Substring(start, index - start);
}

yield return s.Substring(index, 1);
start = index + 1;
}

if (start < s.Length)
{
yield return s.Substring(start);
}
}

private static string FormatName(string name)
{
if (name == null)
{
return string.Empty;
}

// Encode and replace .()<> with word break opportunities
name = WebUtility.HtmlEncode(name)
.Replace(".", "<wbr>.")
.Replace("(", "<wbr>(")
.Replace(")", ")<wbr>")
.Replace(", ", ", <wbr>")
.Replace("&lt;", "<wbr>&lt;")
.Replace("&gt;", "&gt;<wbr>");

// Add additional break opportunities in long un-broken segments
List<string> segments = name.Split(new[] { "<wbr>" }, StringSplitOptions.None).ToList();
bool replaced = false;
for (int c = 0; c < segments.Count; c++)
{
if (segments[c].Length > 20)
{
segments[c] = new string(segments[c]
.SelectMany((x, i) => char.IsUpper(x) && i != 0 ? new[] { '<', 'w', 'b', 'r', '>', x } : new[] { x })
.ToArray());
replaced = true;
}
}

return replaced ? string.Join("<wbr>", segments) : name;
}

/// <summary>
/// Generates links to each heading on a page and returns a string containing all of the links.
/// </summary>
public static async Task<string> GenerateInfobarHeadingsAsync(this IExecutionContext context, IDocument document)
{
StringBuilder content = new StringBuilder();
IReadOnlyList<IDocument> headings = document.GetDocumentList(HtmlKeys.Headings);
if (headings != null)
{
foreach (IDocument heading in headings)
{
string id = heading.GetString(HtmlKeys.HeadingId);
if (id != null)
{
content.AppendLine($"<p><a href=\"#{id}\">{await heading.GetContentStringAsync()}</a></p>");
}
}
}
if (content.Length > 0)
{
content.Insert(0, "<h6>On This Page</h6>");
content.AppendLine("<hr class=\"infobar-hidden\" />");
return content.ToString();
}
return null;
}
}
}
Loading