From 604901d6735f1a3d694d8b874389ea696dc0233b Mon Sep 17 00:00:00 2001 From: Michael Becker Date: Sat, 20 Jul 2024 23:43:02 -0400 Subject: [PATCH] port MBS.Framework .NET legacy to .NET Core --- .../src/lib/MBS.Core/Application.cs | 448 +++++++++++++++++- .../Collections/Generic/ExtensionMethods.cs | 2 +- .../src/lib/MBS.Core/FindFileOptions.cs | 45 ++ framework-dotnet/src/lib/MBS.Core/NanoId.cs | 2 + .../src/lib/MBS.Core/Reflection/TypeLoader.cs | 19 +- .../src/lib/MBS.Core/StreamExtensions.cs | 25 + 6 files changed, 536 insertions(+), 5 deletions(-) create mode 100644 framework-dotnet/src/lib/MBS.Core/FindFileOptions.cs diff --git a/framework-dotnet/src/lib/MBS.Core/Application.cs b/framework-dotnet/src/lib/MBS.Core/Application.cs index 024c65c..9c1bcb3 100644 --- a/framework-dotnet/src/lib/MBS.Core/Application.cs +++ b/framework-dotnet/src/lib/MBS.Core/Application.cs @@ -1,4 +1,6 @@ -namespace MBS.Core; +using System.ComponentModel; + +namespace MBS.Core; public class Application { @@ -8,12 +10,224 @@ public class Application protected virtual void OnStartup(EventArgs e) { } + public event ApplicationActivatedEventHandler BeforeActivated; + protected virtual void OnBeforeActivated(ApplicationActivatedEventArgs e) + { + BeforeActivated?.Invoke(this, e); + } + + public event ApplicationActivatedEventHandler Activated; protected virtual void OnActivated(ApplicationActivatedEventArgs e) + { + Activated?.Invoke(this, e); + } + + + public event ApplicationActivatedEventHandler AfterActivated; + protected virtual void OnAfterActivated(ApplicationActivatedEventArgs e) + { + AfterActivated?.Invoke(this, e); + } + + /// + /// Parses the option. + /// + /// true, if option was parsed, false otherwise. + /// Arguments. + /// Index. + /// The list into which to add the option if it has been specified. + /// The set of available options. + private bool ParseOption(string[] args, ref int index, IList list, CommandLineOption.CommandLineOptionCollection optionSet) + { + string longOptionPrefix = "--", shortOptionPrefix = "-"; + + bool breakout = false; + if (args[index].StartsWith(shortOptionPrefix) && args[index].Length == (shortOptionPrefix.Length + 1)) + { + char shortOptionChar = args[index][args[index].Length - 1]; + CommandLineOption option = optionSet[shortOptionChar]; + if (option != null) + { + if (option.Abbreviation == shortOptionChar) + { + if (option.Type != CommandLineOptionValueType.None) + { + index++; + option.Value = args[index]; + } + else + { + option.Value = true; + } + + if (list != null) + list.Add(option); + } + } + else + { + list.Add(new CommandLineOption() { Abbreviation = shortOptionChar }); + } + } + else if (args[index].StartsWith(longOptionPrefix)) + { + // long option format is --name[=value] + string name = args[index].Substring(longOptionPrefix.Length); + string value = null; + if (name.Contains("=")) + { + int idx = name.IndexOf('='); + value = name.Substring(idx + 1); + name = name.Substring(0, idx); + } + CommandLineOption option = optionSet[name]; + if (option != null) + { + if (option.Type != CommandLineOptionValueType.None) + { + // index++; + // option.Value = args[index]; + if (!name.Contains("=")) + { + // already taken care of the true case above + if (index + 1 < args.Length) + { + index++; + value = args[index]; + } + } + option.Value = value; + } + else + { + option.Value = true; + } + list.Add(option); + } + else + { + list.Add(new CommandLineOption() { Name = name }); + } + } + else + { + // we have reached a non-option + return true; + } + return false; + } + + protected virtual void OnBeforeStartInternal(CancelEventArgs e) { } protected virtual int StartInternal() { + CancelEventArgs ce = new CancelEventArgs(); + OnBeforeStartInternal(ce); + if (ce.Cancel) + return 2; + + CommandLine cline = new CommandLine(); + string[] args = CommandLine.Arguments; + + if (args.Length > 0) + { + int i = 0; + for (i = 0; i < args.Length; i++) + { + if (ParseOption(args, ref i, cline.Options, CommandLine.Options)) + break; + } + + // we have finished parsing the first set of options ("global" options) + // now we see if we have commands + if (CommandLine.Commands.Count > 0 && i < args.Length) + { + // we support commands like git and apt, "appname --app-global-options --command-options" + CommandLineCommand cmd = CommandLine.Commands[args[i]]; + if (cmd != null) + { + for (i++; i < args.Length; i++) + { + if (ParseOption(args, ref i, cline.Options, cmd.Options)) + break; + } + + cline.Command = cmd; + } + else + { + // assume filename + } + } + + for (/* intentionally left blank */; i < args.Length; i++) + { + cline.FileNames.Add(args[i]); + } + } + + ApplicationActivatedEventArgs e = new ApplicationActivatedEventArgs(true, ApplicationActivationType.CommandLineLaunch, cline); + + if (CommandLine.Options["help"]?.Value is bool && ((bool)CommandLine.Options["help"]?.Value) == true) + { + if (ShowCommandLineHelp(out int resultCode)) + { + return resultCode; + } + } + + OnStartup(EventArgs.Empty); + if (cline.Command != null && cline.Command.ActivationDelegate != null) + { + OnBeforeActivated(e); + if (!e.Success) + { + Console.WriteLine(String.Format("Try '{0} --help' for more information.", ShortName)); + return e.ExitCode; + } + + // use the activation delegate instead of calling OnActivated + cline.Command.ActivationDelegate(e); + if (!e.Success) + { + Console.WriteLine(String.Format("Try '{0} --help' for more information.", ShortName)); + return e.ExitCode; + } + + OnAfterActivated(e); + if (!e.Success) + { + Console.WriteLine(String.Format("Try '{0} --help' for more information.", ShortName)); + return e.ExitCode; + } + } + else + { + OnBeforeActivated(e); + if (!e.Success) + { + Console.WriteLine(String.Format("Try '{0} --help' for more information.", ShortName)); + return e.ExitCode; + } + + OnActivated(e); + if (!e.Success) + { + Console.WriteLine(String.Format("Try '{0} --help' for more information.", ShortName)); + return e.ExitCode; + } + + OnAfterActivated(e); + if (!e.Success) + { + Console.WriteLine(String.Format("Try '{0} --help' for more information.", ShortName)); + return e.ExitCode; + } + } + return e.ExitCode; + /* OnStartup(EventArgs.Empty); CommandLine cline = new CommandLine(); @@ -23,6 +237,7 @@ public class Application OnActivated(e); return e.ExitCode; + */ } public int Start() @@ -43,4 +258,235 @@ public class Application StopInternal(exitCode); } + private string _UniqueName = null; + public string UniqueName + { + get + { + if (_UniqueName == null) + { + return ShortName; + } + return _UniqueName; + } + set + { + _UniqueName = value; + } + } + + private string mvarBasePath = null; + public string BasePath + { + get + { + if (mvarBasePath == null) + { + // Set up the base path for the current application. Should this be able to be + // overridden with a switch (/basepath:...) ? + mvarBasePath = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location); + } + return mvarBasePath; + } + } + + protected virtual string DataDirectoryName => ShortName; + public string[] EnumerateDataPaths() => EnumerateDataPaths(FindFileOptions.All); + public string[] EnumerateDataPaths(FindFileOptions options) + { + List list = new List(); + + string[] dataDirectoryNames = null; + if (AdditionalShortNames != null) + { + dataDirectoryNames = new string[AdditionalShortNames.Length + 1]; + dataDirectoryNames[0] = DataDirectoryName; + for (int i = 0; i < AdditionalShortNames.Length; i++) + { + dataDirectoryNames[i + 1] = AdditionalShortNames[i]; + } + } + else + { + dataDirectoryNames = new string[] { DataDirectoryName }; + } + + if ((options & FindFileOptions.All) == FindFileOptions.All) + { + // first look in the application root directory since this will override everything else + list.Add(BasePath); + + foreach (string dataDirName in dataDirectoryNames) + { + if (Environment.OSVersion.Platform == PlatformID.Unix) + { + // if we are on Unix or Mac OS X, look in /etc/... + list.Add(String.Join(System.IO.Path.DirectorySeparatorChar.ToString(), new string[] + { + String.Empty, // *nix root directory + "etc", + dataDirName + })); + } + + // then look in /usr/share/universal-editor or C:\ProgramData\Mike Becker's Software\Universal Editor + list.Add(String.Join(System.IO.Path.DirectorySeparatorChar.ToString(), new string[] + { + System.Environment.GetFolderPath(System.Environment.SpecialFolder.CommonApplicationData), + dataDirName + })); + + // then look in ~/.local/share/universal-editor or C:\Users\USERNAME\AppData\Local\Mike Becker's Software\Universal Editor + list.Add(String.Join(System.IO.Path.DirectorySeparatorChar.ToString(), new string[] + { + System.Environment.GetFolderPath(System.Environment.SpecialFolder.LocalApplicationData), + dataDirName + })); + } + } + + + + //fixme: addd finddfileoption.userconfig, localdata, etc. + // now for the user-writable locations... + + foreach (string dataDirName in dataDirectoryNames) + { + // then look in ~/.universal-editor or C:\Users\USERNAME\AppData\Roaming\Mike Becker's Software\Universal Editor + list.Add(String.Join(System.IO.Path.DirectorySeparatorChar.ToString(), new string[] + { + System.Environment.GetFolderPath(System.Environment.SpecialFolder.ApplicationData), + dataDirName + })); + } + return list.ToArray(); + } + + public string ShortName { get; set; } + public virtual string[] AdditionalShortNames => null; + + protected void PrintUsageStatement(CommandLineCommand command = null) + { + string shortOptionPrefix = CommandLine.ShortOptionPrefix ?? "-"; + string longOptionPrefix = CommandLine.LongOptionPrefix ?? "--"; + + Console.Write("usage: {0} ", ShortName); + foreach (CommandLineOption option in CommandLine.Options) + { + PrintUsageStatementOption(option); + } + + if (CommandLine.HelpTextPrefix != null) + { + Console.WriteLine(); + Console.WriteLine(); + Console.WriteLine(CommandLine.HelpTextPrefix); + } + + Console.WriteLine(); + + bool printDescriptions = true; + if (printDescriptions) + { + foreach (CommandLineOption option in CommandLine.Options) + { + Console.WriteLine(" {0}{1}", option.Abbreviation != '\0' ? String.Format("{0}{1}, {2}{3}", shortOptionPrefix, option.Abbreviation, longOptionPrefix, option.Name) : String.Format("{0}{1}", longOptionPrefix, option.Name), option.Description == null ? null : String.Format(" - {0}", option.Description)); + } + } + // Console.Write("[]"); + + if (command != null) + { + Console.WriteLine(" {0}{1}", command.Name, command.Description == null ? null : String.Format(" - {0}", command.Description)); + foreach (CommandLineOption option in command.Options) + { + PrintUsageStatementOption(option); + } + } + else + { + if (CommandLine.Commands.Count > 0) + { + Console.WriteLine(" []"); + Console.WriteLine(); + + List commands = new List(CommandLine.Commands); + commands.Sort((x, y) => + { + return x.Name.CompareTo(y.Name); + }); + foreach (CommandLineCommand command1 in commands) + { + Console.WriteLine(" {0}{1}", command1.Name, command1.Description == null ? null : String.Format(" - {0}", command1.Description)); + } + } + } + + if (CommandLine.HelpTextSuffix != null) + { + Console.WriteLine(); + Console.WriteLine(CommandLine.HelpTextSuffix); + } + } + + private void PrintUsageStatementOption(CommandLineOption option) + { + Console.Write(" "); + if (option.Optional) + { + Console.Write('['); + } + + string shortOptionPrefix = CommandLine.ShortOptionPrefix ?? "-"; + string longOptionPrefix = CommandLine.LongOptionPrefix ?? "--"; + + if (option.Abbreviation != '\0') + { + Console.Write("{0}{1}", shortOptionPrefix, option.Abbreviation); + if (option.Type == CommandLineOptionValueType.Single) + { + Console.Write(" "); + } + else if (option.Type == CommandLineOptionValueType.Multiple) + { + Console.Write(" [,,...]"); + } + + Console.Write(" | {0}{1}", longOptionPrefix, option.Name); + if (option.Type == CommandLineOptionValueType.Single) + { + Console.Write("="); + } + else if (option.Type == CommandLineOptionValueType.Multiple) + { + Console.Write("=[,,...]"); + } + } + else + { + Console.Write(longOptionPrefix ?? "--"); + Console.Write("{0}", option.Name); + if (option.Type == CommandLineOptionValueType.Single) + { + Console.Write("="); + } + else if (option.Type == CommandLineOptionValueType.Multiple) + { + Console.Write("=[,,...]"); + } + } + if (option.Optional) + { + Console.Write(']'); + } + // Console.WriteLine(); + } + protected virtual bool ShowCommandLineHelp(out int resultCode) + { + // bash: cd returns 2 if --help is specified OR invalid option selected, 1 if file not found + PrintUsageStatement(); + resultCode = 2; + return true; + } + } diff --git a/framework-dotnet/src/lib/MBS.Core/Collections/Generic/ExtensionMethods.cs b/framework-dotnet/src/lib/MBS.Core/Collections/Generic/ExtensionMethods.cs index db03cbf..0b58403 100755 --- a/framework-dotnet/src/lib/MBS.Core/Collections/Generic/ExtensionMethods.cs +++ b/framework-dotnet/src/lib/MBS.Core/Collections/Generic/ExtensionMethods.cs @@ -55,7 +55,7 @@ namespace MBS.Core.Collections.Generic return ((List)(cacheOfT[obj][typeof(T)])).ToArray(); } - public static void AddRange(this IList list, IEnumerable items) + public static void AddRange(this ICollection list, IEnumerable items) { foreach (T item in items) { diff --git a/framework-dotnet/src/lib/MBS.Core/FindFileOptions.cs b/framework-dotnet/src/lib/MBS.Core/FindFileOptions.cs new file mode 100644 index 0000000..473678d --- /dev/null +++ b/framework-dotnet/src/lib/MBS.Core/FindFileOptions.cs @@ -0,0 +1,45 @@ +// +// FindFileOptions.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; +namespace MBS.Core; + +/// +/// Controls the behavior of relative file resolution. +/// +[Flags()] +public enum FindFileOptions +{ + /// + /// Returns all matching fully-qualified file paths across all global, + /// application, and user directories. + /// + All = 1, + /// + /// Returns only file paths that are writable by the user (i.e., in the + /// user's local or roaming data directory). + /// + UserWritable = 2, + /// + /// Allows the user to create a file if it does not exist (i.e., returns + /// a file name even if it does not exist) + /// + Create = 4 +} diff --git a/framework-dotnet/src/lib/MBS.Core/NanoId.cs b/framework-dotnet/src/lib/MBS.Core/NanoId.cs index 6259def..8349238 100755 --- a/framework-dotnet/src/lib/MBS.Core/NanoId.cs +++ b/framework-dotnet/src/lib/MBS.Core/NanoId.cs @@ -163,6 +163,8 @@ namespace MBS.Core public const string DefaultAlphabet = "_-0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; public const string DefaultAlphabetNoSpecialChars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + public const string CapitalAlphabetNoSpecialChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + public const string CapitalAlphanumericNoSpecialChars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; private static readonly CryptoRandom Random = new CryptoRandom(); /// diff --git a/framework-dotnet/src/lib/MBS.Core/Reflection/TypeLoader.cs b/framework-dotnet/src/lib/MBS.Core/Reflection/TypeLoader.cs index 29e999e..eeab03f 100644 --- a/framework-dotnet/src/lib/MBS.Core/Reflection/TypeLoader.cs +++ b/framework-dotnet/src/lib/MBS.Core/Reflection/TypeLoader.cs @@ -197,16 +197,29 @@ public class TypeLoader } } mvarAvailableTypes = types.ToArray(); + /* + foreach (Type t in mvarAvailableTypes) + { + Console.WriteLine("Type load: {0}", t.FullName); + } + */ } if (inheritsFrom != null) { List retval = new List(); - for (int iTyp = 0; iTyp < mvarAvailableTypes.Length; iTyp++) + foreach (Type t in mvarAvailableTypes) { - for (int jInh = 0; jInh < inheritsFrom.Length; jInh++) + foreach (Type inheritsFromType in inheritsFrom) { - if (mvarAvailableTypes[iTyp].IsSubclassOf(inheritsFrom[jInh])) retval.Add(mvarAvailableTypes[iTyp]); + if (t.FullName.Contains("Mini.")) + { + Console.WriteLine(inheritsFromType.FullName + " ? inherits ? " + t.FullName); + } + if (t.IsSubclassOf(inheritsFromType)) + { + retval.Add(t); + } } } return retval.ToArray(); diff --git a/framework-dotnet/src/lib/MBS.Core/StreamExtensions.cs b/framework-dotnet/src/lib/MBS.Core/StreamExtensions.cs index b1a5d14..9568243 100644 --- a/framework-dotnet/src/lib/MBS.Core/StreamExtensions.cs +++ b/framework-dotnet/src/lib/MBS.Core/StreamExtensions.cs @@ -19,6 +19,8 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +using System.Net.Http.Headers; + namespace MBS.Core; public static class StreamExtensions @@ -96,4 +98,27 @@ public static class StreamExtensions } _streamPositions[st].Push(st.Position); } + + public static byte[] ReadToEnd(this Stream st) + { + byte[] retval = new byte[0]; + byte[] buffer = new byte[4096]; + + long j = 0; + bool remaining = true; + while (remaining) + { + int count = st.Read(buffer, 0, buffer.Length); + if (count < buffer.Length) + { + Array.Resize(ref buffer, count); + remaining = false; + } + + Array.Resize(ref retval, retval.Length + buffer.Length); + Array.Copy(buffer, 0, retval, j, buffer.Length); + j += buffer.Length; + } + return retval; + } } \ No newline at end of file