"))
+ ],
+ timeOut: TimeSpan.FromSeconds(15),
+ downloadLogger: logger
+ );
+ }
+
+ }
+ }
+}
diff --git a/Beam.Temporary.Cli/NovelStatics.cs b/Beam.Temporary.Cli/NovelStatics.cs
new file mode 100644
index 0000000..7183ee7
--- /dev/null
+++ b/Beam.Temporary.Cli/NovelStatics.cs
@@ -0,0 +1,144 @@
+
+
+using aeqw89.DataKeys;
+using Beam.Dynamic;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Beam.Temporary.Cli {
+
+ internal static class NovelStatics {
+ public static void Define_LightNovelWorld_Novel_TheLegendaryMechanic(SharedDataDictionary sdd) {
+ var lnwAggregator = new DataKey
("aeqw89:document:aggregators:light_novel_world");
+ var lnwAuxiliary = new DataKey("aeqw89:document:auxillaries:light_novel_world");
+ var novel = new TextResource() {
+ Key = new DataKey("novels:the_legendary_mechanic"),
+ AssociatedSource = lnwAggregator,
+ AssociatedMetaSource = lnwAuxiliary,
+ TemplateInitialData = ["the-legendary-mechanic-245", "1"],
+ MetaTemplateInitialData = ["the-legendary-mechanic"]
+ };
+ sdd.Novels.TryAdd(novel.Key, novel);
+
+ sdd.AggregatorNovels.TryAdd(lnwAggregator, [novel.Key]);
+ }
+
+ public static void Define_LightNovelWorl_Novel_IAloneLevelUp(SharedDataDictionary sdd) {
+ var lnwAggregator = new DataKey("light_novel_world").ToAggregator().As();
+ var lnwAuxiliary = new DataKey("light_novel_world").ToAuxiliary().As();
+ var novel = new TextResource() {
+ Key = new DataKey("novels:i_alone_level_up"),
+ AssociatedSource = lnwAggregator,
+ AssociatedMetaSource = lnwAuxiliary,
+ TemplateInitialData = ["i-alone-level-up-236", "1"],
+ MetaTemplateInitialData = ["i-alone-level-up-solo-leveling-05122225"]
+ };
+
+ sdd.Novels.TryAdd(novel.Key, novel);
+
+ sdd.AggregatorNovels.TryAdd(lnwAggregator, [novel.Key]);
+ }
+
+ public static void Define_NovelFull(SharedDataDictionary sdd) {
+ var docNamespace = "aeqw89:document";
+ var nfAgg = new DataKey("aggregators:novel_full").WithNamespace(docNamespace);
+ var nfAux = new DataKey("auxillaries:novel_full").WithNamespace(docNamespace);
+ var nfBindings = new DataKey("aeqw89:bindings:light_novel_world");
+ var aggregator = new WebResource(nfAgg) {
+ Name = "Novel Full",
+ Description = "A novel aggregator site",
+ Domain = "https://novelfull.net",
+ Bindings = nfBindings
+ };
+ var auxiliary = new WebResource(nfAux) {
+ Name = "Novel Full",
+ Description = "A novel aggregator site",
+ Domain = "https://novelfull.net",
+ Bindings = nfBindings.WithSuffix("_aux")
+ };
+
+ sdd.Templates.TryAdd(nfAgg, new() {
+ Template = ""
+ });
+ }
+
+ public static void Define_LightNovelWorld(SharedDataDictionary sdd) {
+ var lnwAggregator = new DataKey("aeqw89:document:aggregators:light_novel_world");
+ var lnwAuxiliary = new DataKey("aeqw89:document:auxillaries:light_novel_world");
+ const string lnwBindingsA = "aeqw89:bindings:light_novel_world";
+ var aggregator = new WebResource(lnwAggregator) {
+ Name = "Light Novel World",
+ Description = "A novel aggregator site maintained by NetherClaw",
+ Domain = "https://www.lightnovelworld.co",
+ Bindings = new DataKey(lnwBindingsA)
+ };
+ const string lnwBindingsB = "aeqw89:bindings:light_novel_world_aux";
+ var auxiliary = new WebResource(lnwAuxiliary) {
+ Name = "Light Novel World",
+ Description = "A novel aggregator site maintained by NetherClaw",
+ Domain = "https://www.lightnovelworld.co",
+ Bindings = new DataKey(lnwBindingsB)
+ };
+
+ sdd.Templates.TryAdd(lnwAuxiliary, new() {
+ Template = "https://www.lightnovelworld.co/novel/{0}",
+ IndexOfChapterIndex = -1
+ });
+ sdd.Templates.TryAdd(lnwAggregator, new() {
+ Template = "https://www.lightnovelworld.co/novel/{0}/chapter-{1}",
+ IndexOfChapterIndex = 1
+ });
+
+ sdd.Aggregators.TryAdd(aggregator.Key, aggregator);
+ sdd.Auxillaries.TryAdd(auxiliary.Key, auxiliary);
+
+ var lnwBindings = new DataKey(lnwBindingsA);
+ var lnwBindingsAux = new DataKey(lnwBindingsB);
+ sdd.Bindings.TryAdd(lnwBindings, new DataBindings() {
+ Title = new Binding("aeqw89:binding:light_novel_world:title") {
+ XPath = "/html/body/main/article/section/div[1]/h1/span[2]",
+ Type = BindingType.Single
+ },
+ Content = new("aeqw89:binding:light_novel_world:content") {
+ Provider = new ParagraphedContentDataProvider() {
+ Content = new Binding() {
+ XPath = "//*[@id=\"chapter-container\"]"
+ }
+ },
+ Type = BindingType.UseProvider
+ },
+ });
+ sdd.Bindings.TryAdd(lnwBindingsAux, new DataBindings() {
+ Title = new("aeqw89:binding:light_novel_world_aux:title") {
+ XPath = "/html/body/main/article/header/div[2]/div[2]/div[1]/h1",
+ Type = BindingType.Single
+ },
+ Authors = new("aeqw89:binding:light_novel_world_aux:authors") {
+ XPath = "/html/body/main/article/header/div[2]/div[2]/div[1]/div[1]/a",
+ Type = BindingType.Single
+ },
+ Description = new("aeqw89:binding:light_novel_world_aux:description") {
+ Provider = new ParagraphedContentDataProvider() {
+ Content = new() {
+ XPath = "/html/body/main/article/div/section/div[1]/div"
+ }
+ },
+ Type = BindingType.UseProvider
+ },
+ Tags = new("aeqw89:binding:light_novel_world_aux:tags") {
+ Provider = new ListContentDataProvider() {
+ Content = new() {
+ XPath = "/html/body/main/article/header/div[2]/div[2]/div[3]/ul"
+ }
+ },
+ Type = BindingType.UseProvider
+ }
+ });
+ }
+
+
+ }
+}
diff --git a/Beam.Temporary.Cli/Program.cs b/Beam.Temporary.Cli/Program.cs
new file mode 100644
index 0000000..5958b63
--- /dev/null
+++ b/Beam.Temporary.Cli/Program.cs
@@ -0,0 +1,135 @@
+using aeqw89.PersistentData;
+using aeqw89.DataKeys;
+using Beam.Dynamic;
+using HtmlAgilityPack;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using System.Text.Json.Serialization.Metadata;
+using Beam.Temporary.Cli.Templates.Classic;
+using Beam.Exports;
+
+namespace Beam.Temporary.Cli {
+ internal class Program {
+
+ public static JsonSerializerOptions ConversionOptions { get; internal set; } = new();
+
+ public static SharedDataDictionary Shared { get; set; } = [];
+
+ public static IArchitecture Architecture = IArchitecture.Default;
+
+ const string SharedDataPath = "data/.dat";
+
+ static async Task Main(string[] args) {
+ ConversionOptions.Converters.AddPersistentDataRequiredConverters();
+ ConversionOptions.WriteIndented = true;
+
+ var web = new HtmlWeb();
+
+ var lf = LoggerFactory.Create((x) => {
+ x.AddConsole();
+ });
+
+ ILogger logger = lf
+ .CreateLogger("Program");
+
+ await using var sharedContext = await DataDictionaryContext.Create(
+ SharedDataPath,
+ DataKind.Shared,
+ logger,
+ ConversionOptions
+ );
+
+ Shared = sharedContext.Data;
+
+ Shared.Clear();
+ NovelStatics.Define_LightNovelWorld(Shared);
+ NovelStatics.Define_LightNovelWorld_Novel_TheLegendaryMechanic(Shared);
+ NovelStatics.Define_LightNovelWorl_Novel_IAloneLevelUp(Shared);
+ ClassicTemplates.Register(Shared);
+
+ var novel = new DataKey("novels:i_alone_level_up");
+ var context_aux = Architecture.GetMeta(web, novel, Shared);
+ var metaDownloader = new DownloadEnumerable(
+ new SequentialFragmentDownloader(
+ context_aux,
+ (c) => new UnitFragmentDownloader(c.Web, c.AsyncTranformer, c.AsyncFailurePredicates, 4, logger),
+ logger)
+ .UnwrapFragmented());
+ var metadata = (await metaDownloader.FirstAsync());
+
+ var context = Architecture.GetTextRecord(web, novel, Shared, metadata.Data);
+ context.DownloadReporter = new Progress((x) => Console.WriteLine(x.Filename));
+ var downloader = new DownloadEnumerable(
+ new SequentialFragmentDownloader(
+ context,
+ (c) => new UnitFragmentDownloader(c.Web, c.AsyncTranformer, c.AsyncFailurePredicates, 4, logger),
+ logger)
+ .UnwrapFragmented());
+
+ List> documents = [];
+
+ await foreach (var download in downloader.Take(20)) {
+ if (!download.Data.MetaData.TryGetValue(Architecture.ChapterKey, out var meta))
+ continue;
+ if (meta is not ArticleData articleMetaData)
+ continue;
+ //Console.WriteLine($"Title: {data.Name}");
+ //Console.WriteLine($"Description: {data.Description}");
+ //Console.WriteLine($"Categories: {data.Categories.Aggregate((x, y) => $"{x}; {y}")}");
+ //Console.WriteLine($"Authors: {data.Authors.Aggregate((x,y) => $"{x}; {y}")}");
+ Console.WriteLine($"Chapter title: {articleMetaData.Name}");
+ //Console.WriteLine($"Content: {download}");
+
+ documents.Add(download);
+ }
+
+ string testDir = Path.Combine("txt", Path.GetRandomFileName());
+ Directory.CreateDirectory(testDir);
+
+ int len = documents.MaxBy((x) => x.Order)?.Order ?? -1;
+ foreach (var document in documents.OrderBy((x) => x.Order)) {
+ document.Data.MetaData.TryGetValue(Architecture.ChapterKey, out var chapterMetaData);
+ Dictionary linkButtons = new();
+ if (document.Order != 0)
+ linkButtons.Add("Previous", $"{document.Order - 1}.html");
+ if (document.Order != len)
+ linkButtons.Add("Next", $"{document.Order + 1}.html");
+ new HtmlExporter(document.Data, chapterMetaData as ArticleData, linkButtons).Write(Path.Combine(testDir, $"{document.Order}.html"));
+ }
+
+ Console.ReadKey();
+
+ //foreach (var download in documents.OrderBy((x) => x.Order)) {
+ // if (download.Data.TryGetTaggedMetaData(Architecture.ChapterKey, out var meta))
+ // Console.WriteLine($"{download.Order}:{meta.Name}");
+ //}
+
+ //string[] templates = new DataKey[] {
+ // HtmlBook.Keys.ContentPage,
+ // HtmlBook.Keys.NoContentPage,
+ // HtmlBook.Keys.TitlePage,
+ // HtmlBook.Keys.StylesPage,
+ //}.Select(
+ // (x) => Shared.Files.ReadToString(x.WithNamespace("aeqw89:files:templates:classic"))
+ //).ToArray();
+
+ //HtmlBook book = new(
+ // bookname: Path.Combine(Path.GetRandomFileName(), "I Alone Level Up"),
+ // new CssData(),
+ // new ArticleData(),
+ // new HtmlBookTemplates() {
+ // ContentPageTemplate = templates[0],
+ // NoContentTemplate = templates[1],
+ // TitlePageTemplate = templates[2],
+ // CssTemplate = templates[3],
+ // },
+ // documents: documents.Select((x) => x.Data).ToList()
+ //);
+
+ //book.Update();
+ //Console.WriteLine("One variable!");
+ }
+ }
+}
diff --git a/Beam.Temporary.Cli/SharedDataDictionary.cs b/Beam.Temporary.Cli/SharedDataDictionary.cs
new file mode 100644
index 0000000..c39edda
--- /dev/null
+++ b/Beam.Temporary.Cli/SharedDataDictionary.cs
@@ -0,0 +1,48 @@
+using aeqw89.PersistentData;
+using aeqw89.DataKeys;
+using Beam.Dynamic;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Text.Json;
+using System.Threading.Tasks;
+
+namespace Beam.Temporary.Cli {
+ public class SharedDataDictionary : BaseDataDictionary {
+ public Dictionary, PackagedSourceLinkGenerationData> Templates {
+ get => GetOrCreateDictionary, PackagedSourceLinkGenerationData>(nameof(Templates));
+ set => Data[nameof(Templates)] = value;
+ }
+
+ public Dictionary, WebResource> Aggregators {
+ get => GetOrCreateDictionary, WebResource>(nameof(Aggregators));
+ set => Data[nameof(Aggregators)] = value;
+ }
+
+ public Dictionary, WebResource> Auxillaries {
+ get => GetOrCreateDictionary, WebResource>(nameof(Auxillaries));
+ set => Data[nameof(Auxillaries)] = value;
+ }
+
+ public Dictionary, DataBindings> Bindings {
+ get => GetOrCreateDictionary, DataBindings>(nameof(Bindings));
+ set => Data[nameof(Bindings)] = value;
+ }
+
+ public Dictionary, HashSet>> AggregatorNovels {
+ get => GetOrCreateDictionary, HashSet>>(nameof(AggregatorNovels));
+ set => Data[nameof(AggregatorNovels)] = value;
+ }
+
+ public Dictionary, TextResource> Novels {
+ get => GetOrCreateDictionary, TextResource>(nameof(Novels));
+ set => Data[nameof(Novels)] = value;
+ }
+
+ internal Dictionary, File> Files {
+ get => GetOrCreateDictionary, File>(nameof(Files));
+ set => Data[nameof(Files)] = value;
+ }
+ }
+}
diff --git a/Beam.Temporary.Cli/StringExtensions.cs b/Beam.Temporary.Cli/StringExtensions.cs
new file mode 100644
index 0000000..8e95d2b
--- /dev/null
+++ b/Beam.Temporary.Cli/StringExtensions.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Beam.Temporary.Cli {
+ public static class StringExtensions {
+ public static string Aggregate(this IEnumerable str, string separator) {
+ if (!str.Any())
+ return string.Empty;
+ return str.Aggregate((x, y) => $"{x}{separator}{y}");
+ }
+ }
+}
diff --git a/Beam.Temporary.Cli/Templates/Classic/ClassicTemplates.cs b/Beam.Temporary.Cli/Templates/Classic/ClassicTemplates.cs
new file mode 100644
index 0000000..6b2ac8a
--- /dev/null
+++ b/Beam.Temporary.Cli/Templates/Classic/ClassicTemplates.cs
@@ -0,0 +1,30 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Beam.Temporary.Cli.Templates.Classic {
+ internal class ClassicTemplates {
+ public static void Register(SharedDataDictionary sdd) {
+ sdd.Files.TryAdd(
+ new("aeqw89:files:templates:classic:content_page"),
+ new("C:\\Users\\qwsdc\\source\\repos\\Beam\\Beam.Temporary.Cli\\Templates\\Classic\\Content.template.html", "htmlpage", "templates"));
+ sdd.Files.TryAdd(
+ new("aeqw89:files:templates:classic:title_page"),
+ new("C:\\Users\\qwsdc\\source\\repos\\Beam\\Beam.Temporary.Cli\\Templates\\Classic\\Title.template.html", "htmlpage", "templates"));
+ sdd.Files.TryAdd(
+ new("aeqw89:files:templates:classic:styles_page"),
+ new("C:\\Users\\qwsdc\\source\\repos\\Beam\\Beam.Temporary.Cli\\Templates\\Classic\\Styles.template.css", "styles", "templates"));
+ sdd.Files.TryAdd(
+ new("aeqw89:files:templates:classic:no_content_page"),
+ new("C:\\Users\\qwsdc\\source\\repos\\Beam\\Beam.Temporary.Cli\\Templates\\Classic\\NoContent.template.html", "htmlpage", "templates"));
+ }
+ }
+
+ internal static class DictionaryOfFileExtensions {
+ public static string ReadToString(this Dictionary dict, T key) where T: notnull {
+ return System.IO.File.ReadAllText(dict[key].Path);
+ }
+ }
+}
diff --git a/Beam.Temporary.Cli/Templates/Classic/Content.template.html b/Beam.Temporary.Cli/Templates/Classic/Content.template.html
new file mode 100644
index 0000000..6ee9342
--- /dev/null
+++ b/Beam.Temporary.Cli/Templates/Classic/Content.template.html
@@ -0,0 +1,27 @@
+
+
+
+
+ {Name}
+
+
+
+
+
+ {Content}
+
+
+
+
+
+
+
diff --git a/Beam.Temporary.Cli/Templates/Classic/NoContent.template.html b/Beam.Temporary.Cli/Templates/Classic/NoContent.template.html
new file mode 100644
index 0000000..f41ef6a
--- /dev/null
+++ b/Beam.Temporary.Cli/Templates/Classic/NoContent.template.html
@@ -0,0 +1,15 @@
+
+
+
+
+ 404 - Not Found
+
+
+
+
+
404 - Content Not Found
+
The file {Filename} was not found.
+
{Content}
+
+
+
diff --git a/Beam.Temporary.Cli/Templates/Classic/Styles.template.css b/Beam.Temporary.Cli/Templates/Classic/Styles.template.css
new file mode 100644
index 0000000..b9154d9
--- /dev/null
+++ b/Beam.Temporary.Cli/Templates/Classic/Styles.template.css
@@ -0,0 +1,60 @@
+/* styles.css */
+/* Placeholders:
+ {PrimaryColor}, {SecondaryColor}, {TertiaryColor}, {ButtonColor},
+ {ForegroundColor}, {ContentFont}, {ContentFontSize}, {TitleFont}, {TitleFontSize}
+*/
+body {
+ font-family: {ContentFont};
+ font-size: {ContentFontSize};
+ background-color: {PrimaryColor};
+ color: {ForegroundColor};
+ margin: 0;
+ padding: 20px;
+}
+
+header {
+ background-color: {SecondaryColor};
+ padding: 20px;
+ text-align: center;
+}
+
+header h1 {
+ font-family: {TitleFont};
+ font-size: {TitleFontSize};
+ margin: 0;
+}
+
+header p {
+ font-style: italic;
+ margin: 5px 0;
+}
+
+section, article, nav {
+ background: {TertiaryColor};
+ padding: 15px;
+ border-radius: 8px;
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
+ margin: 20px auto;
+ max-width: 800px;
+}
+
+.navigation {
+ display: flex;
+ justify-content: space-between;
+ max-width: 800px;
+ margin: 20px auto;
+}
+
+button {
+ background-color: {ButtonColor};
+ color: {ForegroundColor};
+ border: none;
+ padding: 10px 20px;
+ cursor: pointer;
+ font-size: {ContentFontSize};
+ border-radius: 4px;
+}
+
+nav h2 {
+ margin-top: 0;
+}
diff --git a/Beam.Temporary.Cli/Templates/Classic/Title.template.html b/Beam.Temporary.Cli/Templates/Classic/Title.template.html
new file mode 100644
index 0000000..19153dd
--- /dev/null
+++ b/Beam.Temporary.Cli/Templates/Classic/Title.template.html
@@ -0,0 +1,26 @@
+
+
+
+
+ {Name}
+
+
+
+
+ {Name}
+ {Description}
+
+
+ Authors: {Authors}
+ Language: {Language}
+ Categories: {Categories}
+ Version: {Version}
+
+
+
+
diff --git a/Beam.Temporary.Cli/TextResource.cs b/Beam.Temporary.Cli/TextResource.cs
new file mode 100644
index 0000000..ff27c57
--- /dev/null
+++ b/Beam.Temporary.Cli/TextResource.cs
@@ -0,0 +1,26 @@
+
+
+using aeqw89.DataKeys;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Beam.Temporary.Cli {
+ public class TextResource : IKeyed {
+ public required DataKey Key { get; set; }
+ public DataKey? AssociatedSource { get; set; }
+ public DataKey? AssociatedMetaSource { get; set; }
+ public required string[] TemplateInitialData { get; set; }
+ public string?[]? MetaTemplateInitialData { get; set; }
+
+ public TextResourceRecord ToRecord(SharedDataDictionary sdd) {
+ return new(this,
+ AssociatedSource is null ? null : sdd.Aggregators[AssociatedSource],
+ AssociatedMetaSource is null ? null : sdd.Auxillaries[AssociatedMetaSource]);
+ }
+ }
+
+ public record TextResourceRecord(TextResource Resource, WebResource? AssociatedSource, WebResource? AssociatedMetaSource);
+}
diff --git a/Beam.Temporary.Cli/Tracked.cs b/Beam.Temporary.Cli/Tracked.cs
new file mode 100644
index 0000000..61321ff
--- /dev/null
+++ b/Beam.Temporary.Cli/Tracked.cs
@@ -0,0 +1,11 @@
+namespace Beam.Temporary.Cli {
+ internal class Tracked(T obj) {
+ public T TrackedObject { get; set; } = obj;
+ public bool IsDirty { get; set; } = true;
+
+ public Tracked SetDirty() {
+ IsDirty = true;
+ return this;
+ }
+ }
+}
diff --git a/Beam.Temporary.Cli/WebResource.cs b/Beam.Temporary.Cli/WebResource.cs
new file mode 100644
index 0000000..f7fcc71
--- /dev/null
+++ b/Beam.Temporary.Cli/WebResource.cs
@@ -0,0 +1,28 @@
+using aeqw89.PersistentData;
+using aeqw89.DataKeys;
+using Beam.Dynamic;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Beam.Temporary.Cli {
+ public class WebResource(DataKey key) : IKeyed {
+ public DataKey Key { get; set; } = key;
+
+ public required DataKey Bindings { get; set; }
+ public string? Name { get; set; }
+ public string? Domain { get; set; }
+ public string? Description { get; set; }
+
+
+ public WebResource() : this(new(string.Empty)) { }
+
+ public WebResourceRecord ToRecord(SharedDataDictionary sdd) {
+ return new WebResourceRecord(this, sdd.Bindings[Bindings]);
+ }
+ }
+
+ public record WebResourceRecord(WebResource Resource, DataBindings Bindings);
+}
diff --git a/Beam.sln b/Beam.sln
new file mode 100644
index 0000000..f0c9e82
--- /dev/null
+++ b/Beam.sln
@@ -0,0 +1,40 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.12.35506.116
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Beam", "Beam\Beam.csproj", "{3BC9A070-85B0-405D-A6F8-D0AEEE625B81}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Beam.Temporary.Cli", "Beam.Temporary.Cli\Beam.Temporary.Cli.csproj", "{8F650BBA-3800-4B5E-A6FF-9057633601EE}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Beam.Dynamic", "Beam.Dynamic\Beam.Dynamic.csproj", "{DDEABE82-096C-4799-87F1-56F494D35FAA}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Beam.Exports", "Beam.Exports\Beam.Exports.csproj", "{7C0ADBC0-44D4-48F8-901B-9C93F1B1FFDC}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {3BC9A070-85B0-405D-A6F8-D0AEEE625B81}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3BC9A070-85B0-405D-A6F8-D0AEEE625B81}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3BC9A070-85B0-405D-A6F8-D0AEEE625B81}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3BC9A070-85B0-405D-A6F8-D0AEEE625B81}.Release|Any CPU.Build.0 = Release|Any CPU
+ {8F650BBA-3800-4B5E-A6FF-9057633601EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8F650BBA-3800-4B5E-A6FF-9057633601EE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8F650BBA-3800-4B5E-A6FF-9057633601EE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8F650BBA-3800-4B5E-A6FF-9057633601EE}.Release|Any CPU.Build.0 = Release|Any CPU
+ {DDEABE82-096C-4799-87F1-56F494D35FAA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {DDEABE82-096C-4799-87F1-56F494D35FAA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {DDEABE82-096C-4799-87F1-56F494D35FAA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {DDEABE82-096C-4799-87F1-56F494D35FAA}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7C0ADBC0-44D4-48F8-901B-9C93F1B1FFDC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7C0ADBC0-44D4-48F8-901B-9C93F1B1FFDC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7C0ADBC0-44D4-48F8-901B-9C93F1B1FFDC}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7C0ADBC0-44D4-48F8-901B-9C93F1B1FFDC}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/Beam/ArticleData.cs b/Beam/ArticleData.cs
new file mode 100644
index 0000000..f016b26
--- /dev/null
+++ b/Beam/ArticleData.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Text.Json;
+using System.Threading.Tasks;
+
+namespace Beam {
+ public class ArticleData : IDocumentMetaData {
+ public string? Name { get; set; }
+ public string[] Authors { get; set; } = [];
+ public string? Language { get; set; }
+ public string[] Categories { get; set; } = [];
+ public string? Version { get; set; }
+ public string? Description { get; set; }
+
+ public string AsJson(JsonSerializerOptions? options = null) {
+ return JsonSerializer.Serialize(this, options);
+ }
+ }
+}
diff --git a/Beam/Beam.csproj b/Beam/Beam.csproj
new file mode 100644
index 0000000..8d25a7b
--- /dev/null
+++ b/Beam/Beam.csproj
@@ -0,0 +1,25 @@
+
+
+
+ net9.0
+ enable
+ enable
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+ ..\..\aeqw89.DataKeys\aeqw89.DataKeys\bin\Debug\net9.0\aeqw89.DataKeys.dll
+
+
+
+
diff --git a/Beam/ByteDocument.cs b/Beam/ByteDocument.cs
new file mode 100644
index 0000000..7a6cdfe
--- /dev/null
+++ b/Beam/ByteDocument.cs
@@ -0,0 +1,15 @@
+using System.Text;
+
+namespace Beam {
+ internal class ByteDocument(string filename, byte[] content, Encoding? encoding = null) : Document(filename, encoding) {
+ public byte[] Content { get; set; } = content;
+
+ public override byte[] ToBytes() {
+ return Content;
+ }
+
+ public override string ToString() {
+ return Encoding.GetString(Content);
+ }
+ }
+}
diff --git a/Beam/DataBackedSourceLinkGenerator.cs b/Beam/DataBackedSourceLinkGenerator.cs
new file mode 100644
index 0000000..bf178ce
--- /dev/null
+++ b/Beam/DataBackedSourceLinkGenerator.cs
@@ -0,0 +1,9 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Beam {
+ public class DataBackedSourceLinkGenerator(PackagedSourceLinkGenerationData data, params object[] initialState) : DelegateBackedSourceLinkGenerator(data.GenerateLink, data.GetBehaviour(), initialState) {}
+}
diff --git a/Beam/DelegateBackedSourceLinkGenerator.cs b/Beam/DelegateBackedSourceLinkGenerator.cs
new file mode 100644
index 0000000..78023fc
--- /dev/null
+++ b/Beam/DelegateBackedSourceLinkGenerator.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Beam {
+ public delegate DocumentSourceLink LinkGenerator(params object[] ps);
+ public delegate object Incrementor(object obj, int amount);
+
+ public class DelegateBackedSourceLinkGenerator : IEnumerator {
+ public LinkGenerator Generator { get; set; }
+ public IncrementationBehaviour Behaviour { get; }
+ private object[] InitialState;
+
+ public DelegateBackedSourceLinkGenerator(LinkGenerator generator, IncrementationBehaviour behaviour, params object[] initialState) {
+ Generator = generator;
+ Behaviour = behaviour;
+ InitialState = (object[])initialState.Clone();
+ State = (object[])initialState.Clone();
+
+ Reset();
+ }
+
+ public object[] State { get; set; }
+ public DocumentSourceLink Current { get; private set; }
+
+ object IEnumerator.Current => Current;
+
+ public void Dispose() {
+ return;
+ }
+
+ public bool MoveNext() {
+ Behaviour.Apply(State, 1);
+ Current = Generator(State);
+ return Current.HasValue;
+ }
+
+ public void Reset() {
+ State = (object[])InitialState.Clone();
+ Behaviour.Apply(State, -1);
+ Current = Generator(State);
+ }
+ }
+}
diff --git a/Beam/Document.cs b/Beam/Document.cs
new file mode 100644
index 0000000..cacb842
--- /dev/null
+++ b/Beam/Document.cs
@@ -0,0 +1,13 @@
+using aeqw89.DataKeys;
+using System.Text;
+
+namespace Beam {
+ public abstract class Document(string filename, Encoding? encoding = null) : IDocument {
+ public string Filename { get; set; } = filename;
+ public Encoding Encoding { get; set; } = encoding ?? Encoding.UTF8;
+ public Dictionary, IDocumentMetaData> MetaData { get; set; } = [];
+
+ public abstract byte[] ToBytes();
+ public override abstract string ToString();
+ }
+}
diff --git a/Beam/DocumentCache.cs b/Beam/DocumentCache.cs
new file mode 100644
index 0000000..a0b1b91
--- /dev/null
+++ b/Beam/DocumentCache.cs
@@ -0,0 +1,57 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Beam {
+ ///
+ /// Holds a collection of objects in memory to facilitate lazy loading
+ ///
+ public class DocumentCache : Dictionary