Skip to content

Commit 99166de

Browse files
authored
Add docs-generator tool to generate random docsets (#96)
1 parent fdfcc59 commit 99166de

File tree

13 files changed

+385
-7
lines changed

13 files changed

+385
-7
lines changed

.github/check-license-headers.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,4 @@ if [[ $nErrors -eq 0 ]]; then
4242
exit 0
4343
else
4444
exit 1
45-
fi
45+
fi

build/Program.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@
2525
dotnet publish {source} -c Release -o .artifacts/publish \
2626
--self-contained true /p:PublishTrimmed=true /p:PublishSingleFile=false /p:PublishAot=true
2727
""";
28+
29+
var generatorSource = "src/docs-generator/docs-generator.csproj";
30+
await $"""
31+
dotnet publish {generatorSource} -c Release -o .artifacts/publish \
32+
--self-contained true /p:PublishTrimmed=true /p:PublishSingleFile=false /p:PublishAot=true
33+
""";
2834
});
2935

3036
// this is manual for now and quite hacky.

docs-builder.sln

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{67B576EE
3030
EndProject
3131
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elastic.Markdown.Tests", "tests\Elastic.Markdown.Tests\Elastic.Markdown.Tests.csproj", "{B27C5107-128B-465A-B8F8-8985399E4CFB}"
3232
EndProject
33+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "docs-generator", "src\docs-generator\docs-generator.csproj", "{61904527-9753-4379-B546-56B6A29073AC}"
34+
EndProject
3335
Global
3436
GlobalSection(SolutionConfigurationPlatforms) = preSolution
3537
Debug|Any CPU = Debug|Any CPU
@@ -60,10 +62,15 @@ Global
6062
{B27C5107-128B-465A-B8F8-8985399E4CFB}.Debug|Any CPU.Build.0 = Debug|Any CPU
6163
{B27C5107-128B-465A-B8F8-8985399E4CFB}.Release|Any CPU.ActiveCfg = Release|Any CPU
6264
{B27C5107-128B-465A-B8F8-8985399E4CFB}.Release|Any CPU.Build.0 = Release|Any CPU
65+
{61904527-9753-4379-B546-56B6A29073AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
66+
{61904527-9753-4379-B546-56B6A29073AC}.Debug|Any CPU.Build.0 = Debug|Any CPU
67+
{61904527-9753-4379-B546-56B6A29073AC}.Release|Any CPU.ActiveCfg = Release|Any CPU
68+
{61904527-9753-4379-B546-56B6A29073AC}.Release|Any CPU.Build.0 = Release|Any CPU
6369
EndGlobalSection
6470
GlobalSection(NestedProjects) = preSolution
6571
{4D198E25-C211-41DC-9E84-B15E89BD7048} = {BE6011CC-1200-4957-B01F-FCCA10C5CF5A}
6672
{01F05AD0-E0E0-401F-A7EC-905928E1E9F0} = {BE6011CC-1200-4957-B01F-FCCA10C5CF5A}
6773
{B27C5107-128B-465A-B8F8-8985399E4CFB} = {67B576EE-02FA-4F9B-94BC-3630BC09ECE5}
74+
{61904527-9753-4379-B546-56B6A29073AC} = {BE6011CC-1200-4957-B01F-FCCA10C5CF5A}
6875
EndGlobalSection
6976
EndGlobal

src/Elastic.Markdown/IO/MarkdownFile.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ private void ReadDocumentInstructions(MarkdownDocument document)
8585
}
8686

8787
var contents = document
88-
.Where(block => block is HeadingBlock { Level: 2 })
88+
.Where(block => block is HeadingBlock { Level: >= 2 })
8989
.Cast<HeadingBlock>()
9090
.Select(h => h.Inline?.FirstChild?.ToString())
9191
.Where(title => !string.IsNullOrWhiteSpace(title))

src/Elastic.Markdown/Myst/InlineParsers/DiagnosticLinkInlineParser.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ public override bool Match(InlineProcessor processor, ref StringSlice slice)
6868
includeFrom = context.Parser.SourcePath.FullName;
6969

7070
var anchors = url.Split('#');
71-
var anchor = anchors.Length > 1 ? anchors[1] : null;
71+
var anchor = anchors.Length > 1 ? anchors[1].Trim() : null;
7272
url = anchors[0];
7373

7474
if (!string.IsNullOrWhiteSpace(url))
@@ -87,7 +87,7 @@ public override bool Match(InlineProcessor processor, ref StringSlice slice)
8787
if (link.FirstChild == null || !string.IsNullOrEmpty(anchor))
8888
{
8989
var file = string.IsNullOrWhiteSpace(url) ? context.Path
90-
: context.Build.ReadFileSystem.FileInfo.New(Path.Combine(context.Build.SourcePath.FullName, url));
90+
: context.Build.ReadFileSystem.FileInfo.New(Path.Combine(context.Build.SourcePath.FullName, url.TrimStart('/')));
9191
var markdown = context.GetMarkdownFile?.Invoke(file);
9292
var title = markdown?.Title;
9393

src/docs-builder/Http/DocumentationWebHost.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,7 @@ public class DocumentationWebHost
2626
public DocumentationWebHost(string? path, ILoggerFactory logger, IFileSystem fileSystem)
2727
{
2828
var builder = WebApplication.CreateSlimBuilder();
29-
var sourcePath = path != null ? fileSystem.DirectoryInfo.New(path) : null;
30-
var context = new BuildContext(fileSystem)
29+
var context = new BuildContext(fileSystem, fileSystem, path, null)
3130
{
3231
Collector = new ConsoleDiagnosticsCollector(logger)
3332
};
@@ -36,7 +35,7 @@ public DocumentationWebHost(string? path, ILoggerFactory logger, IFileSystem fil
3635
s.FolderToMonitor = context.SourcePath.FullName;
3736
s.ClientFileExtensions = ".md,.yml";
3837
});
39-
builder.Services.AddSingleton<ReloadableGeneratorState>(_ => new ReloadableGeneratorState(sourcePath, null, context, logger));
38+
builder.Services.AddSingleton<ReloadableGeneratorState>(_ => new ReloadableGeneratorState(context.SourcePath, null, context, logger));
4039
builder.Services.AddHostedService<ReloadGeneratorService>();
4140
builder.Services.AddSingleton(logger);
4241
builder.Logging.SetMinimumLevel(LogLevel.Warning);
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Licensed to Elasticsearch B.V under one or more agreements.
2+
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3+
// See the LICENSE file in the project root for more information
4+
5+
using Bogus;
6+
7+
namespace Documentation.Generator.Domain;
8+
9+
public record Determinism
10+
{
11+
public Determinism(int? seedFileSystem, int? seedContent)
12+
{
13+
var randomizer = new Randomizer();
14+
SeedFileSystem = seedFileSystem ?? randomizer.Int(1, int.MaxValue);
15+
SeedContent = seedContent ?? randomizer.Int(1, int.MaxValue);
16+
FileSystem = new Randomizer(SeedFileSystem);
17+
Contents = new Randomizer(SeedContent);
18+
19+
SectionProbability = Contents.Float(0.001f, Contents.Float(0.1f));
20+
FileProbability = FileSystem.Float(0.001f, Contents.Float(0.1f));
21+
}
22+
23+
public int SeedFileSystem { get; }
24+
public int SeedContent { get; }
25+
26+
27+
public Randomizer FileSystem { get; }
28+
public Randomizer Contents { get; }
29+
30+
public float SectionProbability { get; }
31+
public float FileProbability { get; }
32+
33+
public static Determinism Random { get; set; } = new(null, null);
34+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Licensed to Elasticsearch B.V under one or more agreements.
2+
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3+
// See the LICENSE file in the project root for more information
4+
5+
using Slugify;
6+
using Soenneker.Utils.AutoBogus;
7+
8+
namespace Documentation.Generator.Domain;
9+
10+
public static class Generators
11+
{
12+
public static AutoFaker<FolderName> FolderName { get; } = new();
13+
public static AutoFaker<Section> Section { get; } = new();
14+
public static AutoFaker<MarkdownFile> File { get; } = new();
15+
public static SlugHelper Slug { get; } = new();
16+
17+
static Generators()
18+
{
19+
FolderName
20+
.RuleFor(p => p.Folder, f => f.Lorem.Slug(1));
21+
22+
Section
23+
.RuleFor(p => p.Paragraphs, f => f.Lorem.Paragraphs(f.Random.Number(1, 10)))
24+
.RuleFor(p => p.Level, f => f.Random.Number(2, 4));
25+
26+
File
27+
.Ignore(p => p.Links)
28+
.Ignore(p => p.Directory)
29+
.RuleFor(p => p.FileName, f => f.System.FileName("md"))
30+
.RuleFor(p => p.IncludeInUpdate, f => f.Random.Float() <= Determinism.Random.FileProbability)
31+
.RuleFor(p => p.Sections, f => Section.Generate(Determinism.Random.Contents.Number(1, 12)).ToArray());
32+
}
33+
34+
public static IEnumerable<string> CreateSubPaths(string parent, int maxDepth, int currentDepth)
35+
{
36+
yield return parent;
37+
if (currentDepth == maxDepth) yield break;
38+
var subFolders = FolderName.Generate(Determinism.Random.FileSystem.Number(0, 4));
39+
foreach (var subFolder in subFolders)
40+
{
41+
var path = $"{parent}/{subFolder.Folder}";
42+
yield return path;
43+
var subPaths = CreateSubPaths(path, maxDepth, currentDepth + 1);
44+
foreach (var p in subPaths)
45+
yield return p;
46+
}
47+
}
48+
49+
public static string[] FolderNames { get; set; } = [];
50+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Licensed to Elasticsearch B.V under one or more agreements.
2+
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3+
// See the LICENSE file in the project root for more information
4+
5+
using Slugify;
6+
using Soenneker.Utils.AutoBogus;
7+
8+
namespace Documentation.Generator.Domain;
9+
10+
public record MarkdownFile
11+
{
12+
public required string FileName { get; init; }
13+
public required Section[] Sections { get; init; }
14+
public required string Title { get; init; }
15+
16+
public string RelativePath => $"{Directory}/{FileName}";
17+
18+
public required bool IncludeInUpdate { get; init; } = true;
19+
20+
public string Directory { get; set; } = string.Empty;
21+
public List<string> Links { get; set; } = [];
22+
23+
public void RewriteLinksIntoSections()
24+
{
25+
var linksLength = Links.Count;
26+
var sectionsLength = Sections.Length;
27+
for (var i = 0; i < linksLength; i++)
28+
{
29+
var link = Links[i];
30+
var section = Sections[Determinism.Random.Contents.Number(0, sectionsLength - 1)];
31+
var words = section.Paragraphs.Split(" ");
32+
var w = Determinism.Random.Contents.Number(0, words.Length - 1);
33+
var word = words[w];
34+
words[w] = $"[{word}](/{link})";
35+
section.Paragraphs = string.Join(" ", words);
36+
}
37+
}
38+
39+
40+
public string GetRandomLink()
41+
{
42+
var sectionLink = Determinism.Random.Contents.Bool(0.8f);
43+
if (!sectionLink) return RelativePath;
44+
var section = Sections[Determinism.Random.Contents.Number(0, Sections.Length - 1)];
45+
return $"{RelativePath}#{Generators.Slug.GenerateSlug(section.Header)}";
46+
47+
}
48+
}
49+

src/docs-generator/Domain/Path.cs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Licensed to Elasticsearch B.V under one or more agreements.
2+
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3+
// See the LICENSE file in the project root for more information
4+
5+
namespace Documentation.Generator.Domain;
6+
7+
public static class Paths
8+
{
9+
private static DirectoryInfo RootDirectoryInfo()
10+
{
11+
var directory = new DirectoryInfo(Directory.GetCurrentDirectory());
12+
while (directory != null &&
13+
(directory.GetFiles("*.sln").Length == 0 || directory.GetDirectories(".git").Length == 0))
14+
directory = directory.Parent;
15+
return directory ?? new DirectoryInfo(Directory.GetCurrentDirectory());
16+
}
17+
18+
public static readonly DirectoryInfo Root = RootDirectoryInfo();
19+
}
20+
21+
public record FolderName
22+
{
23+
public required string Folder { get; init; }
24+
}
25+
26+
public record Folder
27+
{
28+
public required string Path { get; init; }
29+
public required MarkdownFile[] Files { get; init; }
30+
}

src/docs-generator/Domain/Section.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Licensed to Elasticsearch B.V under one or more agreements.
2+
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3+
// See the LICENSE file in the project root for more information
4+
5+
namespace Documentation.Generator.Domain;
6+
7+
public record Section
8+
{
9+
public required string Header { get; init; }
10+
11+
public required int Level { get; init; }
12+
13+
public required string Paragraphs { get; set; }
14+
15+
}

0 commit comments

Comments
 (0)