Skip to content

Commit

Permalink
#86 AI Context friendly documentation - API
Browse files Browse the repository at this point in the history
  • Loading branch information
NikolayPianikov committed Feb 27, 2025
1 parent c50d6b8 commit 826b278
Show file tree
Hide file tree
Showing 28 changed files with 10,460 additions and 56 deletions.
5,500 changes: 5,491 additions & 9 deletions AI_CONTEXT.md

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions build/Core/Doc/DocumentElement.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
namespace Build.Core.Doc;

using System.Xml.Linq;

record DocumentElement(XElement Source);
3 changes: 3 additions & 0 deletions build/Core/Doc/DocumentMember.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace Build.Core.Doc;

record DocumentMember(DocumentTypeName TypeName, DocumentMemberKind Kind);
10 changes: 10 additions & 0 deletions build/Core/Doc/DocumentMemberKind.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace Build.Core.Doc;

enum DocumentMemberKind
{
Field,
Property,
Method,
Constructor,
ImplicitOperator
}
43 changes: 43 additions & 0 deletions build/Core/Doc/DocumentParts.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
namespace Build.Core.Doc;

using System.Text;

class DocumentParts : IMarkdownParts
{
public Memory<string> GetParts(string? text)
{
var result = new List<string>();
if (text is null)
{
return Memory<string>.Empty;
}

var sb = new StringBuilder();
var isArgs = false;
foreach (var symbol in text)
{
switch (symbol)
{
case '.' when !isArgs:
result.Add(sb.ToString());
sb.Clear();
continue;

case '(':
isArgs = true;
break;
}

sb.Append(symbol);
}

if (sb.Length > 0)
{
result.Add(sb.ToString());
}

return result.ToArray();
}

public string Join(ReadOnlyMemory<string> parts) => string.Join('.', parts.ToArray());
}
5 changes: 5 additions & 0 deletions build/Core/Doc/DocumentText.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
namespace Build.Core.Doc;

using System.Xml.Linq;

public record DocumentText(XText Text);
3 changes: 3 additions & 0 deletions build/Core/Doc/DocumentType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace Build.Core.Doc;

record DocumentType(DocumentTypeName TypeName);
6 changes: 6 additions & 0 deletions build/Core/Doc/DocumentTypeName.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Build.Core.Doc;

public record DocumentTypeName(
string NamespaceName,
string TypeName,
string MemberName = "");
131 changes: 131 additions & 0 deletions build/Core/Doc/DotNetXmlDocumentWalker.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
namespace Build.Core.Doc;

using System.Xml.Linq;
using System.Xml.XPath;

class DotNetXmlDocumentWalker<T>(IMarkdownParts markdownParts) : IDocumentWalker<T>
{
public async Task WalkAsync(
T ctx,
XDocument document,
IDocumentVisitor<T> visitor,
CancellationToken cancellationToken)
{
var items = (
from element in document.XPathSelectElements("/doc/members/member")
let name = element.Attribute("name")?.Value
where !string.IsNullOrWhiteSpace(name)
where name.Length >= 3
let kind = char.ToUpper(name[0])
let parts = markdownParts.GetParts(name[2..])
where parts.Length > 1
let typeFullNameParts = kind == 'T' ? parts : parts[..^1]
let namespaceName = markdownParts.Join(typeFullNameParts[..^1])
let typeName = markdownParts.Join(typeFullNameParts[^1..]) ?? ""
let memberName = markdownParts.Join(parts[^1..]) ?? ""
select (name: new DocumentTypeName(namespaceName, typeName, memberName), kind, element))
.OrderBy(i => i.name.NamespaceName).ThenBy(i => i.name.TypeName);

DocumentType? type = null;
foreach (var (name, kind, element) in items)
{
if (cancellationToken.IsCancellationRequested)
{
break;
}

var memberName = name.MemberName;
DocumentMember? member = null;
switch (kind)
{
case 'T':
if (type is {} curType)
{
ctx = await visitor.FinishTypeVisitAsync(ctx, curType, cancellationToken);
}

type = new DocumentType(name);
ctx = await visitor.StartTypeVisitAsync(ctx, type, cancellationToken);
ctx = await ExplorerContainerAsync(ctx, element, visitor, cancellationToken);
break;

case 'F':
member = new DocumentMember(name, DocumentMemberKind.Field);
break;

case 'P':
member = new DocumentMember(name, DocumentMemberKind.Property);
break;

case 'M':
if (memberName.StartsWith("#ctor"))
{
member =
new DocumentMember(
name with { MemberName = name.MemberName.Replace("#ctor", name.TypeName) },
DocumentMemberKind.Constructor);
}
else
{
if (memberName.StartsWith("op_Implicit"))
{
member =
new DocumentMember(
name with { MemberName = name.MemberName.Replace("op_Implicit", "") },
DocumentMemberKind.ImplicitOperator);
}
else
{
member = new DocumentMember(name, DocumentMemberKind.Method);
}
}

break;
}

if (member is not {} curMember)
{
continue;
}

ctx = await visitor.StartMemberVisitAsync(ctx, curMember, cancellationToken);
ctx = await ExplorerContainerAsync(ctx, element, visitor, cancellationToken);
ctx = await visitor.FinishMemberVisitAsync(ctx, curMember, cancellationToken);
}

if (type is {} lastType)
{
ctx = await visitor.FinishTypeVisitAsync(ctx, lastType, cancellationToken);
}

await visitor.CompletedAsync(ctx, cancellationToken);
}

private static async Task<T> ExplorerContainerAsync(T ctx, XContainer container, IDocumentVisitor<T> visitor, CancellationToken cancellationToken)
{
foreach (var node in container.Nodes())
{
if (cancellationToken.IsCancellationRequested)
{
break;
}

switch (node)
{
case XElement element:
var markdownElement = new DocumentElement(element);
ctx = await visitor.StartElementVisitAsync(ctx, markdownElement, cancellationToken);
ctx = await ExplorerContainerAsync(ctx, element, visitor, cancellationToken);
ctx = await visitor.FinishElementVisitAsync(ctx, markdownElement, cancellationToken);

break;

case XText text:
ctx = await visitor.TextVisitAsync(ctx, new DocumentText(text), cancellationToken);
break;
}
}

return ctx;
}
}
22 changes: 22 additions & 0 deletions build/Core/Doc/IDocumentVisitor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// ReSharper disable UnusedParameter.Global

namespace Build.Core.Doc;

interface IDocumentVisitor<T>
{
Task<T> StartTypeVisitAsync(T ctx, DocumentType type, CancellationToken cancellationToken);

Task<T> FinishTypeVisitAsync(T ctx, DocumentType type, CancellationToken cancellationToken);

Task<T> StartMemberVisitAsync(T ctx, DocumentMember member, CancellationToken cancellationToken);

Task<T> FinishMemberVisitAsync(T ctx, DocumentMember member, CancellationToken cancellationToken);

Task<T> StartElementVisitAsync(T ctx, DocumentElement element, CancellationToken cancellationToken);

Task<T> FinishElementVisitAsync(T ctx, DocumentElement element, CancellationToken cancellationToken);

Task<T> TextVisitAsync(T ctx, DocumentText text, CancellationToken cancellationToken);

Task<T> CompletedAsync(T ctx, CancellationToken cancellationToken);
}
12 changes: 12 additions & 0 deletions build/Core/Doc/IDocumentWalker.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace Build.Core.Doc;

using System.Xml.Linq;

interface IDocumentWalker<T>
{
Task WalkAsync(
T ctx,
XDocument document,
IDocumentVisitor<T> visitor,
CancellationToken cancellationToken);
}
12 changes: 12 additions & 0 deletions build/Core/Doc/IMarkdown.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace Build.Core.Doc;

using System.Xml.Linq;

public interface IMarkdown
{
Task ConvertAsync(
XDocument document,
TextWriter markdownWriter,
Predicate<DocumentTypeName> filter,
CancellationToken cancellationToken);
}
8 changes: 8 additions & 0 deletions build/Core/Doc/IMarkdownParts.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Build.Core.Doc;

interface IMarkdownParts
{
Memory<string> GetParts(string? text);

string Join(ReadOnlyMemory<string> parts);
}
8 changes: 8 additions & 0 deletions build/Core/Doc/IXDocumentTools.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Build.Core.Doc;

using System.Xml.Linq;

interface IXDocumentTools
{
Task<XDocument> LoadAsync(TextReader textReader, LoadOptions options, CancellationToken cancellationToken);
}
21 changes: 21 additions & 0 deletions build/Core/Doc/Markdown.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
namespace Build.Core.Doc;

using System.Xml.Linq;

[ExcludeFromCodeCoverage]
class Markdown(
IDocumentWalker<MarkdownWriterContext> dotNetXmlDocumentWalker,
IDocumentVisitor<MarkdownWriterContext> markdownWriterVisitor)
: IMarkdown
{
public Task ConvertAsync(
XDocument document,
TextWriter markdownWriter,
Predicate<DocumentTypeName> filter,
CancellationToken cancellationToken)
=> dotNetXmlDocumentWalker.WalkAsync(
new MarkdownWriterContext(filter, markdownWriter),
document,
markdownWriterVisitor,
cancellationToken);
}
8 changes: 8 additions & 0 deletions build/Core/Doc/MarkdownWriterContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Build.Core.Doc;

record MarkdownWriterContext(
Predicate<DocumentTypeName> Filter,
TextWriter Writer,
string Namespace = "",
bool IsSkipping = false,
bool TrimStart = false);
Loading

0 comments on commit 826b278

Please sign in to comment.