diff --git a/build b/build index d104199..2e5a5d1 100755 --- a/build +++ b/build @@ -16,4 +16,16 @@ THEME_NAME=avondale for each in mocha-dotnet/src/assets/ui-html/css/common/*.less mocha-dotnet/src/assets/ui-html/css/$THEME_NAME/*.less; do cat $each; echo ""; done | lessc --plugin=less-plugin-clean-css=advanced - output/assets/ui-html/$ASSET_UIHTML_VERSION/css/mochaApp.css cp mocha-dotnet/src/app/Mocha.ServerApplication/bin/Debug/net8.0/* output/ +if [ ! -d output/plugins ]; then + mkdir output/plugins +fi + +for each in mocha-dotnet/src/plugins/*; do + pushd $each + dotnet build + popd + + cp $each/bin/Debug/net8.0/*.dll output/plugins +done + exit 0 diff --git a/mocha-dotnet.sln b/mocha-dotnet.sln index 853d6f6..5337d46 100644 --- a/mocha-dotnet.sln +++ b/mocha-dotnet.sln @@ -37,6 +37,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{27C300F5 EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mocha.Core.Tests", "mocha-dotnet\tests\Mocha.Core.Tests\Mocha.Core.Tests.csproj", "{20B7D199-322C-4942-85FE-46B90C75E92A}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "plugins", "plugins", "{B024FD23-7084-4DDF-A185-D58BEE7A006F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mocha.Plugins.Libraries.Yaml", "mocha-dotnet\src\plugins\Mocha.Plugins.Libraries.Yaml\Mocha.Plugins.Libraries.Yaml.csproj", "{96400F5A-98ED-48FF-8000-6135BDF10360}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -63,6 +67,10 @@ Global {20B7D199-322C-4942-85FE-46B90C75E92A}.Debug|Any CPU.Build.0 = Debug|Any CPU {20B7D199-322C-4942-85FE-46B90C75E92A}.Release|Any CPU.ActiveCfg = Release|Any CPU {20B7D199-322C-4942-85FE-46B90C75E92A}.Release|Any CPU.Build.0 = Release|Any CPU + {96400F5A-98ED-48FF-8000-6135BDF10360}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {96400F5A-98ED-48FF-8000-6135BDF10360}.Debug|Any CPU.Build.0 = Debug|Any CPU + {96400F5A-98ED-48FF-8000-6135BDF10360}.Release|Any CPU.ActiveCfg = Release|Any CPU + {96400F5A-98ED-48FF-8000-6135BDF10360}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -82,6 +90,8 @@ Global {1345068A-1AF8-4FAA-A336-457E73ED23A7} = {D67F792A-95F6-44EC-80A8-C17A0D0B0644} {27C300F5-5172-4225-A6F7-3503B9007DD8} = {2A2B84F1-FA6B-4D67-ACB8-E277D50C98F2} {20B7D199-322C-4942-85FE-46B90C75E92A} = {27C300F5-5172-4225-A6F7-3503B9007DD8} + {B024FD23-7084-4DDF-A185-D58BEE7A006F} = {66EB3261-A473-41C7-8D40-E1B4DC8ED4B3} + {96400F5A-98ED-48FF-8000-6135BDF10360} = {B024FD23-7084-4DDF-A185-D58BEE7A006F} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {D28A9CF8-0235-4F8F-865F-C460BDCAE16D} diff --git a/mocha-dotnet/src/lib/Mocha.Core/Library.cs b/mocha-dotnet/src/lib/Mocha.Core/Library.cs new file mode 100644 index 0000000..eb186dd --- /dev/null +++ b/mocha-dotnet/src/lib/Mocha.Core/Library.cs @@ -0,0 +1,13 @@ + +namespace Mocha.Core; + +public class Library +{ + /// + /// Applies all attribute and relationship assignments once all instances have been + /// accounted for. + /// + public void Link() + { + } +} diff --git a/mocha-dotnet/src/lib/Mocha.Core/LibraryHandle.cs b/mocha-dotnet/src/lib/Mocha.Core/LibraryHandle.cs new file mode 100644 index 0000000..627f22f --- /dev/null +++ b/mocha-dotnet/src/lib/Mocha.Core/LibraryHandle.cs @@ -0,0 +1,107 @@ +// Copyright (C) 2024 Michael Becker +// +// This file is part of Mocha.NET. +// +// Mocha.NET is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Mocha.NET is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Mocha.NET. If not, see . + +// +// InstanceReference.cs +// +// Author: +// Michael Becker +// +// Copyright (c) 2022 Mike Becker's Software +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +using System; +using System.Diagnostics.CodeAnalysis; +using System.Reflection.Metadata; +using MBS.Core; + +namespace Mocha.Core +{ + /// + /// Represents an opaque handle to an instance in a Mocha object management system (OMS). + /// + public struct LibraryHandle : IEquatable + { + private NanoId _ID; + private bool isNotEmpty; + + public static LibraryHandle Create() + { + LibraryHandle handle = new LibraryHandle(); + handle._ID = NanoId.Generate(); + handle.isNotEmpty = true; + return handle; + } + + public byte[] ToByteArray() + { + return System.Text.Encoding.ASCII.GetBytes(_ID.ToString()); + } + + public static readonly LibraryHandle Empty = new LibraryHandle(); + + public bool IsEmpty { get { return !isNotEmpty; } } + + public bool Equals(LibraryHandle other) + { + if (IsEmpty && other.IsEmpty) + return true; + + return _ID == other._ID && !IsEmpty; + } + public override bool Equals([NotNullWhen(true)] object? obj) + { + if (obj is LibraryHandle) + { + return this.Equals(((LibraryHandle)obj)); + } + return base.Equals(obj); + } + public override int GetHashCode() + { + return _ID.GetHashCode(); + } + + public static bool operator ==(LibraryHandle left, LibraryHandle right) + { + return left.Equals(right); + } + public static bool operator !=(LibraryHandle left, LibraryHandle right) + { + return !left.Equals(right); + } + + public override string ToString() + { + if (IsEmpty) + return "(empty) "; + + return _ID.ToString(); + } + } +} diff --git a/mocha-dotnet/src/lib/Mocha.Core/LibraryPlugin.cs b/mocha-dotnet/src/lib/Mocha.Core/LibraryPlugin.cs new file mode 100644 index 0000000..50be1c4 --- /dev/null +++ b/mocha-dotnet/src/lib/Mocha.Core/LibraryPlugin.cs @@ -0,0 +1,39 @@ +// Copyright (C) 2024 Michael Becker +// +// This file is part of Mocha.NET. +// +// Mocha.NET is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Mocha.NET is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Mocha.NET. If not, see . + +using System.Reflection; +using MBS.Core.Extensibility; + +namespace Mocha.Core; + +public abstract class LibraryPlugin : Plugin +{ + protected abstract void LoadInternal(string filename, Library library); + public void Load(string filename, Library library) + { + LoadInternal(filename, library); + } + + protected virtual bool SupportsFileNameInternal(string filename) + { + return false; + } + public bool SupportsFileName(string filename) + { + return SupportsFileNameInternal(filename); + } +} \ No newline at end of file diff --git a/mocha-dotnet/src/lib/Mocha.Core/Oms.cs b/mocha-dotnet/src/lib/Mocha.Core/Oms.cs index a71d95f..ce20bfc 100644 --- a/mocha-dotnet/src/lib/Mocha.Core/Oms.cs +++ b/mocha-dotnet/src/lib/Mocha.Core/Oms.cs @@ -27,6 +27,8 @@ using System.Reflection; using System.Runtime.CompilerServices; using System.Text; using MBS.Core; +using MBS.Core.Extensibility; +using MBS.Core.IO; using Mocha.Core.MethodImplementations; using Mocha.Core.Oop; @@ -83,10 +85,17 @@ public abstract class Oms SelectTenant(tenant); InitializeTenant(); - + + OnTenantCreated(new OmsTenantCreatedEventArgs(tenant, tenantName)); return tenant; } + public EventHandler? TenantCreated; + protected virtual void OnTenantCreated(OmsTenantCreatedEventArgs e) + { + TenantCreated?.Invoke(this, e); + } + public TenantHandle GetTenantByName(string tenantName) { if (_tenantsByName.ContainsKey(tenantName)) @@ -1018,4 +1027,83 @@ public abstract class Oms protected virtual void InitializeTenantInternal() { } + + protected abstract void AddLibraryReferenceInternal(LibraryHandle library); + /// + /// Adds the specified library reference to the current tenant. + /// + /// + public void AddLibraryReference(LibraryHandle library) + { + AddLibraryReferenceInternal(library); + } + + private Dictionary _libraries = new Dictionary(); + /// + /// Gets the for the library with the given name. + /// + /// + /// + public LibraryHandle GetLibrary(string name) + { + if (_libraries.ContainsKey(name)) + { + return _libraries[name]; + } + return LibraryHandle.Empty; + } + + /// + /// Loads the specified library file into memory. To add it to the tenant, + /// first the desired tenant, and then call + /// . + /// + /// + /// + public LibraryHandle LoadLibrary(string filename) + { + // some examples: + // .mcl compiled binary library file (fastest?) + // /path/to/directory containing a bunch of XML / JSON / YAML files + // .mcz ZIP archive of XML / JSON / YAML files (slowest?) + LibraryHandle lh = LibraryHandle.Create(); + return lh; + + Library lib = new Library(); + // _libraries[name] = lh; + + if (!File.Exists(filename) && !Directory.Exists(filename)) + { + throw new FileNotFoundException(null, filename); + } + + // get a list of all library loaders we have implemented + LibraryPlugin[] plugins = Plugin.Get(); + foreach (LibraryPlugin plugin in plugins) + { + Console.WriteLine("found plugin: {0}", plugin.GetType().FullName); + if (plugin.SupportsFileName(filename)) + { + // if passed a directory, will enumerate all the + // .xml / .json / .yaml files in the directory and + // subdirectories + plugin.Load(filename, lib); + } + } + + // if the files have not already been compiled, we need to do two passes. + // - the first pass loads all instances into our tenant + // - the second pass applies the attributes and relationships, which depend on the + // presence of the instances + + // compiling to .mcl binary library format does this all ahead of time and should + // be faster. + // let's be lazy and just look at the file extension to determine how to parse + + // once all the instances have been loaded, tell the in-memory library manager + // to do its second pass "linking" the objects + lib.Link(); + + return lh; + } } diff --git a/mocha-dotnet/src/lib/Mocha.Core/OmsTenantCreatedEventArgs.cs b/mocha-dotnet/src/lib/Mocha.Core/OmsTenantCreatedEventArgs.cs new file mode 100644 index 0000000..4f2237b --- /dev/null +++ b/mocha-dotnet/src/lib/Mocha.Core/OmsTenantCreatedEventArgs.cs @@ -0,0 +1,31 @@ +// Copyright (C) 2024 Michael Becker +// +// This file is part of Mocha.NET. +// +// Mocha.NET is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Mocha.NET is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Mocha.NET. If not, see . + +namespace Mocha.Core; + +public class OmsTenantCreatedEventArgs : EventArgs +{ + public TenantHandle Tenant { get; } + public string Name { get; } + + public OmsTenantCreatedEventArgs(TenantHandle tenant, string name) + { + Tenant = tenant; + Name = name; + } + +} \ No newline at end of file diff --git a/mocha-dotnet/src/plugins/Mocha.Plugins.Libraries.Yaml/Mocha.Plugins.Libraries.Yaml.csproj b/mocha-dotnet/src/plugins/Mocha.Plugins.Libraries.Yaml/Mocha.Plugins.Libraries.Yaml.csproj new file mode 100644 index 0000000..7ba799f --- /dev/null +++ b/mocha-dotnet/src/plugins/Mocha.Plugins.Libraries.Yaml/Mocha.Plugins.Libraries.Yaml.csproj @@ -0,0 +1,14 @@ + + + + + + + + + net8.0 + enable + enable + + + diff --git a/mocha-dotnet/src/plugins/Mocha.Plugins.Libraries.Yaml/YamlLibraryPlugin.cs b/mocha-dotnet/src/plugins/Mocha.Plugins.Libraries.Yaml/YamlLibraryPlugin.cs new file mode 100644 index 0000000..31f0ffd --- /dev/null +++ b/mocha-dotnet/src/plugins/Mocha.Plugins.Libraries.Yaml/YamlLibraryPlugin.cs @@ -0,0 +1,28 @@ +using Mocha.Core; + +namespace Mocha.Plugins.Libraries.Yaml; + +public class YamlLibraryPlugin : LibraryPlugin +{ + protected override bool SupportsFileNameInternal(string filename) + { + return Directory.Exists(filename) || Path.GetExtension(filename).Equals("yaml"); + } + + protected override void LoadInternal(string filename, Library library) + { + if (Directory.Exists(filename)) + { + string[] files = Directory.GetFiles(filename, "*.yaml", SearchOption.AllDirectories); + foreach (string file in files) + { + FileStream fs = File.Open(file, FileMode.Open, FileAccess.Read, FileShare.Read); + StreamReader sr = new StreamReader(fs); + + var yaml = new YamlStream(); + yaml.Load(sr); + + } + } + } +} diff --git a/mocha-dotnet/src/plugins/Mocha.Plugins.Libraries.Yaml/YamlParser.cs b/mocha-dotnet/src/plugins/Mocha.Plugins.Libraries.Yaml/YamlParser.cs new file mode 100644 index 0000000..17f14d0 --- /dev/null +++ b/mocha-dotnet/src/plugins/Mocha.Plugins.Libraries.Yaml/YamlParser.cs @@ -0,0 +1,9 @@ +namespace Mocha.Plugins.Libraries.Yaml; + +public class YamlParser +{ + public Dictionary Parse(Stream stream) + { + StreamReader sr = new StreamReader(stream); + } +}