From 96fcd38e88ea3a558d9d0a10271339bf21225d56 Mon Sep 17 00:00:00 2001 From: Michael Becker Date: Fri, 13 Dec 2024 00:08:06 -0500 Subject: [PATCH] add Commands and Union --- .../src/lib/MBS.Core/Application.cs | 32 ++- framework-dotnet/src/lib/MBS.Core/Command.cs | 31 +++ .../src/lib/MBS.Core/CommandEvent.cs | 31 +++ .../src/lib/MBS.Core/CommandItem.cs | 25 +++ .../MBS.Core/CommandReferenceCommandItem.cs | 27 +++ .../src/lib/MBS.Core/SeparatorCommandItem.cs | 26 +++ framework-dotnet/src/lib/MBS.Core/Union.cs | 189 ++++++++++++++++++ .../src/tests/MBS.Core.Tests/GlobalUsings.cs | 1 + .../MBS.Core.Tests/MBS.Core.Tests.csproj | 24 +++ .../src/tests/MBS.Core.Tests/UnionTests.cs | 72 +++++++ 10 files changed, 457 insertions(+), 1 deletion(-) create mode 100644 framework-dotnet/src/lib/MBS.Core/Command.cs create mode 100644 framework-dotnet/src/lib/MBS.Core/CommandEvent.cs create mode 100644 framework-dotnet/src/lib/MBS.Core/CommandItem.cs create mode 100644 framework-dotnet/src/lib/MBS.Core/CommandReferenceCommandItem.cs create mode 100644 framework-dotnet/src/lib/MBS.Core/SeparatorCommandItem.cs create mode 100644 framework-dotnet/src/lib/MBS.Core/Union.cs create mode 100644 framework-dotnet/src/tests/MBS.Core.Tests/GlobalUsings.cs create mode 100644 framework-dotnet/src/tests/MBS.Core.Tests/MBS.Core.Tests.csproj create mode 100644 framework-dotnet/src/tests/MBS.Core.Tests/UnionTests.cs diff --git a/framework-dotnet/src/lib/MBS.Core/Application.cs b/framework-dotnet/src/lib/MBS.Core/Application.cs index 024c65c..d00de12 100644 --- a/framework-dotnet/src/lib/MBS.Core/Application.cs +++ b/framework-dotnet/src/lib/MBS.Core/Application.cs @@ -1,7 +1,10 @@ -namespace MBS.Core; +using System.ComponentModel.Design; + +namespace MBS.Core; public class Application { + public IList Commands { get; } = new List(); public static Application? Instance { get; private set; } public CommandLine CommandLine { get; } = new CommandLine(); @@ -43,4 +46,31 @@ public class Application StopInternal(exitCode); } + private Dictionary, List> _commandEventHandlers = new Dictionary, List>(); + public void AttachCommandEventHandler(Union commandId, CommandEventHandler handler) + { + if (!_commandEventHandlers.ContainsKey(commandId)) + { + _commandEventHandlers[commandId] = new List(); + } + if (!_commandEventHandlers[commandId].Contains(handler)) + { + _commandEventHandlers[commandId].Add(handler); + } + } + public void ExecuteCommand(Union commandId) + { + if (_commandEventHandlers.ContainsKey(commandId)) + { + foreach (CommandEventHandler handler in _commandEventHandlers[commandId]) + { + handler(this, new CommandEventArgs(commandId)); + } + } + else + { + Console.WriteLine("command event handler not registered for command '{0}'", commandId); + } + } + } diff --git a/framework-dotnet/src/lib/MBS.Core/Command.cs b/framework-dotnet/src/lib/MBS.Core/Command.cs new file mode 100644 index 0000000..8cf1c8e --- /dev/null +++ b/framework-dotnet/src/lib/MBS.Core/Command.cs @@ -0,0 +1,31 @@ +// Copyright (C) 2024 Michael Becker +// +// This file is part of editor-dotnet. +// +// editor-dotnet 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. +// +// editor-dotnet 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 editor-dotnet. If not, see . + +namespace MBS.Core; + +public class Command +{ + public Command(Union id, string title) + { + ID = id; + Title = title; + } + + public Union ID { get; } + public string Title { get; set; } + public IEnumerable Items { get; set; } +} \ No newline at end of file diff --git a/framework-dotnet/src/lib/MBS.Core/CommandEvent.cs b/framework-dotnet/src/lib/MBS.Core/CommandEvent.cs new file mode 100644 index 0000000..dee385c --- /dev/null +++ b/framework-dotnet/src/lib/MBS.Core/CommandEvent.cs @@ -0,0 +1,31 @@ +/** + * Copyright (C) 2024 Michael Becker + * + * This file is part of editor-dotnet. + * + * editor-dotnet 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. + * + * editor-dotnet 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 editor-dotnet. If not, see . + */ + +namespace MBS.Core; + +public delegate void CommandEventHandler(object sender, CommandEventArgs e); +public class CommandEventArgs : EventArgs +{ + public Union CommandID { get; } + + public CommandEventArgs(Union commandId) + { + CommandID = commandId; + } +} \ No newline at end of file diff --git a/framework-dotnet/src/lib/MBS.Core/CommandItem.cs b/framework-dotnet/src/lib/MBS.Core/CommandItem.cs new file mode 100644 index 0000000..763050a --- /dev/null +++ b/framework-dotnet/src/lib/MBS.Core/CommandItem.cs @@ -0,0 +1,25 @@ +// Copyright (C) 2024 Michael Becker +// +// This file is part of editor-dotnet. +// +// editor-dotnet 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. +// +// editor-dotnet 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 editor-dotnet. If not, see . + +namespace MBS.Core; + +/// +/// The base class from which all command items derive. +/// +public abstract class CommandItem +{ +} \ No newline at end of file diff --git a/framework-dotnet/src/lib/MBS.Core/CommandReferenceCommandItem.cs b/framework-dotnet/src/lib/MBS.Core/CommandReferenceCommandItem.cs new file mode 100644 index 0000000..382feef --- /dev/null +++ b/framework-dotnet/src/lib/MBS.Core/CommandReferenceCommandItem.cs @@ -0,0 +1,27 @@ +// Copyright (C) 2024 Michael Becker +// +// This file is part of editor-dotnet. +// +// editor-dotnet 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. +// +// editor-dotnet 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 editor-dotnet. If not, see . + +namespace MBS.Core; + +public class CommandReferenceCommandItem : CommandItem +{ + public Union CommandID { get; } + public CommandReferenceCommandItem(Union commandId) + { + CommandID = commandId; + } +} \ No newline at end of file diff --git a/framework-dotnet/src/lib/MBS.Core/SeparatorCommandItem.cs b/framework-dotnet/src/lib/MBS.Core/SeparatorCommandItem.cs new file mode 100644 index 0000000..f14eabb --- /dev/null +++ b/framework-dotnet/src/lib/MBS.Core/SeparatorCommandItem.cs @@ -0,0 +1,26 @@ +// Copyright (C) 2024 Michael Becker +// +// This file is part of editor-dotnet. +// +// editor-dotnet 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. +// +// editor-dotnet 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 editor-dotnet. If not, see . + +namespace MBS.Core; + +/// +/// Defines a command item which separates groups of related command items. +/// +public class SeparatorCommandItem : CommandItem +{ + +} \ No newline at end of file diff --git a/framework-dotnet/src/lib/MBS.Core/Union.cs b/framework-dotnet/src/lib/MBS.Core/Union.cs new file mode 100644 index 0000000..e2e2178 --- /dev/null +++ b/framework-dotnet/src/lib/MBS.Core/Union.cs @@ -0,0 +1,189 @@ +// Copyright (C) 2024 Michael Becker +// +// This file is part of editor-dotnet. +// +// editor-dotnet 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. +// +// editor-dotnet 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 editor-dotnet. If not, see . + +using System.Diagnostics.CodeAnalysis; + +public struct Union +{ + private T1 _value1; + private T2 _value2; + private int _which; + private bool _notEmpty; + + public bool IsEmpty { get { return !_notEmpty; } } + + public override string ToString() + { + if (_which == 1) + { + return _value1?.ToString() ?? ""; + } + else if (_which == 2) + { + return _value2?.ToString() ?? ""; + } + throw new InvalidOperationException("internal error: _which state inconsistent!"); + } + + private Union(T1 value1, T2 value2, int which) + { + _value1 = value1; + _value2 = value2; + _which = which; + _notEmpty = true; + } + + public static implicit operator Union(T1 value1) + { + return new Union(value1, default(T2), 1); + } + public static implicit operator Union(T2 value2) + { + return new Union(default(T1), value2, 2); + } + + public static implicit operator T1(Union union) + { + if (union._which == 1) + { + return union._value1; + } + throw new InvalidOperationException(); + } + public static implicit operator T2(Union union) + { + if (union._which == 2) + { + return union._value2; + } + throw new InvalidOperationException(); + } + + public static implicit operator Union(Union other) + { + return new Union(other._value2, other._value1, other._which == 2 ? 1 : 2); + } + + public override bool Equals([NotNullWhen(true)] object? obj) + { + if (obj is Union) + { + return (this.GetValue()?.Equals(((Union)obj).GetValue())).GetValueOrDefault(false); + } + else if (obj is T1) + { + return (_value1?.Equals((T1)obj)).GetValueOrDefault(false); + } + else if (obj is T2) + { + return (_value2?.Equals((T2)obj)).GetValueOrDefault(false); + } + return false; + } + + private object? GetValue() + { + if (_which == 1) + return _value1; + else if (_which == 2) + return _value2; + throw new InvalidOperationException(); + } +} + +public struct Union +{ + private object _value; + private bool _notEmpty; + + public bool IsEmpty { get { return !_notEmpty; } } + + public override string ToString() + { + return _value?.ToString(); + } + + private Union(object value) + { + _value = value; + _notEmpty = true; + } + + public static implicit operator Union(T1 value) + { + return new Union(value); + } + public static implicit operator Union(T2 value) + { + return new Union(value); + } + public static implicit operator Union(T3 value) + { + return new Union(value); + } + + public static implicit operator T1(Union union) + { + if (union._value is T1) + { + return (T1)union._value; + } + throw new InvalidOperationException(); + } + public static implicit operator T2(Union union) + { + if (union._value is T2) + { + return (T2)union._value; + } + throw new InvalidOperationException(); + } + public static implicit operator T3(Union union) + { + if (union._value is T3) + { + return (T3)union._value; + } + throw new InvalidOperationException(); + } + + public override bool Equals([NotNullWhen(true)] object? obj) + { + if (obj is Union) + { + return (this.GetValue()?.Equals(((Union)obj).GetValue())).GetValueOrDefault(false); + } + else if (obj is T1) + { + return (_value?.Equals((T1)obj)).GetValueOrDefault(false); + } + else if (obj is T2) + { + return (_value?.Equals((T2)obj)).GetValueOrDefault(false); + } + else if (obj is T3) + { + return (_value?.Equals((T3)obj)).GetValueOrDefault(false); + } + return false; + } + + private object? GetValue() + { + return _value; + } +} \ No newline at end of file diff --git a/framework-dotnet/src/tests/MBS.Core.Tests/GlobalUsings.cs b/framework-dotnet/src/tests/MBS.Core.Tests/GlobalUsings.cs new file mode 100644 index 0000000..cefced4 --- /dev/null +++ b/framework-dotnet/src/tests/MBS.Core.Tests/GlobalUsings.cs @@ -0,0 +1 @@ +global using NUnit.Framework; \ No newline at end of file diff --git a/framework-dotnet/src/tests/MBS.Core.Tests/MBS.Core.Tests.csproj b/framework-dotnet/src/tests/MBS.Core.Tests/MBS.Core.Tests.csproj new file mode 100644 index 0000000..7e2f50e --- /dev/null +++ b/framework-dotnet/src/tests/MBS.Core.Tests/MBS.Core.Tests.csproj @@ -0,0 +1,24 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + diff --git a/framework-dotnet/src/tests/MBS.Core.Tests/UnionTests.cs b/framework-dotnet/src/tests/MBS.Core.Tests/UnionTests.cs new file mode 100644 index 0000000..87bb8df --- /dev/null +++ b/framework-dotnet/src/tests/MBS.Core.Tests/UnionTests.cs @@ -0,0 +1,72 @@ +using System.Diagnostics.CodeAnalysis; + +namespace MBS.Core.Tests; + +public class UnionTests +{ + public class Union2 + { + [SetUp] + public void Setup() + { + } + + public const string TEST_VALUE_STR = "Test value"; + public const int TEST_VALUE_INT = 314159; + + [Test] + public void OperatorEqualsToStringLiteral() + { + Union value = TEST_VALUE_STR; + Assert.That(value == TEST_VALUE_STR); + } + [Test] + public void MethodEqualsToStringLiteral() + { + Union value = TEST_VALUE_STR; + Assert.That(value.Equals(TEST_VALUE_STR)); + } + } + public class Union3 + { + [SetUp] + public void Setup() + { + } + + public const string TEST_VALUE_STR = "Test value"; + public const int TEST_VALUE_INT = 314159; + + [Test] + public void OperatorEqualsToInt32Literal() + { + Union value = TEST_VALUE_INT; + Assert.That(value == TEST_VALUE_INT); + } + [Test] + public void MethodEqualsToStringLiteral() + { + Union value = TEST_VALUE_STR; + Assert.That(value.Equals(TEST_VALUE_STR)); + } + + [Test] + public void OperatorEqualsToDoubleLiteral() + { + Union value = 0.53; + Assert.That(value == 0.53); + } + + [Test] + public void MethodCallWithUnion() + { + Union value = 25.75; + PrintDoubleValue(value); + } + + private void PrintDoubleValue(double value) + { + Assert.That(value, Is.EqualTo(25.75)); + } + } +} \ No newline at end of file