{"id":21515717,"url":"https://github.com/jamsoft/jamsoft.helpers","last_synced_at":"2025-04-09T20:12:46.300Z","repository":{"id":98092543,"uuid":"264467553","full_name":"jamsoft/JamSoft.Helpers","owner":"jamsoft","description":"A collection of general helpers for applications and libraries. The goal is to provide convenience methods and core building blocks. All in a unit tested, cross-platform .NET Standard 2.0 library with minimal dependencies.","archived":false,"fork":false,"pushed_at":"2024-06-23T14:47:39.000Z","size":409,"stargazers_count":5,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-09T20:12:37.635Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jamsoft.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-05-16T15:31:18.000Z","updated_at":"2025-02-24T14:55:06.000Z","dependencies_parsed_at":"2024-06-23T14:42:59.037Z","dependency_job_id":"9f3fe452-6cf4-43b0-9820-c92738d20b84","html_url":"https://github.com/jamsoft/JamSoft.Helpers","commit_stats":null,"previous_names":[],"tags_count":16,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamsoft%2FJamSoft.Helpers","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamsoft%2FJamSoft.Helpers/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamsoft%2FJamSoft.Helpers/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamsoft%2FJamSoft.Helpers/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jamsoft","download_url":"https://codeload.github.com/jamsoft/JamSoft.Helpers/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248103872,"owners_count":21048245,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-23T23:56:29.255Z","updated_at":"2025-04-09T20:12:46.264Z","avatar_url":"https://github.com/jamsoft.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"![jamsoft-logo](https://github.com/jamsoft/jamsoft.helpers/blob/master/img/logo.png?raw=true)\n\n# JamSoft.Helpers\n\nA collection of general helpers for applications and libraries. The goal is to provide convenience methods and core building blocks. All in a unit tested, cross-platform .NET Standard 2.0 library with minimal dependencies.\n\n![.NET Core](https://github.com/jamsoft/JamSoft.Helpers/workflows/.NET%20Core/badge.svg?branch=master)\n[![Coverage Status](https://coveralls.io/repos/github/jamsoft/JamSoft.Helpers/badge.svg?branch=master)](https://coveralls.io/github/jamsoft/JamSoft.Helpers?branch=master)\n![Nuget](https://img.shields.io/nuget/v/JamSoft.Helpers)\n![GitHub](https://img.shields.io/github/license/jamsoft/JamSoft.Helpers)\n[![CodeFactor](https://www.codefactor.io/repository/github/jamsoft/jamsoft.helpers/badge)](https://www.codefactor.io/repository/github/jamsoft/jamsoft.helpers)\n\n## Table of Contents\n- [Docs](#Docs)\n- [Installation](#Installation)\n- [Tests](#Tests)\n- [Configuration](#Configuration)\n- [Dirty Object Tracking](#Dirty-Object-Tracking)\n- [Collections](#Collections)\n- [Environment Variables](#Environment)\n- [Cryptography](#Cryptography)\n- [Graphics](#Graphics)\n- [Human Readable UI Values](#UI-Values)\n- [Math](#Math)\n- [Strings](#Strings)\n- [Serialization](#Serialization)\n- [Mvvm Pattern](#Mvvm)\n- [Observer Pattern](#Observer)\n- [Memento Pattern](#Memento)\n\n# Docs\n\nhttps://jamsoft.github.io/JamSoft.Helpers/\n\n# Installation\n### Nuget\n```shell\nInstall-Package JamSoft.Helpers -Version 1.2.4\n```\n### CLI\n```shell\ndotnet add package JamSoft.Helpers --version 1.2.4\n```\n### Package Reference\n```xml\n\u003cPackageReference Include=\"JamSoft.Helpers\" Version=\"1.2.4\" /\u003e\n```\n### Package Reference\n```shell\npaket add JamSoft.Helpers --version 1.2.4\n```\n# Tests\n\nThere is a high level of test coverage as shown in the badge, however, at the moment the pipeline executes only on Windows which means some tests cannot be run in this environment.\nThe library has been fully tested on Windows 10, 11, OSX Catalina, MacOS, and Fedora 31.\n\nThe following test classes also show basic example implementations and uses of the provided pattern classes.\n\n- ObserverTests\n- MementoTests\n- MyTestViewModel\n- ATestSettingsClass\n- BTestSettingsClass\n- PersonViewModel\n\n# Sample Application\n\nA sample AvaloniaUI application is now also included in the Sample directory.\n\n# Configuration\n\nRather than getting embroiled in the convoluted and sometimes awkward user settings infrastructure provided by .NET (*.settings), sometimes you just want to store some values in a file, yes?\n\nThe issues and additional complications around user.config and strong names can sometimes get in the way. Using the `SettingsBase\u003cT\u003e` class can bypass this.\n\n## Set Up\n\nCreate a POCO class containing your settings properties, default values and inherit `SettingsBase\u003cT\u003e`, such as:\n\n```csharp\npublic sealed class MySettings : SettingsBase\u003cMySettings\u003e\n{\n    public string ASetting { get; set; } = \"A Default Value\";\n}\n```\n### Loading \u0026 Access\n\nNow you can load, save and manage your settings at runtime like:\n\n```csharp\nstring myDefaultSettingsPath = \"C:\\Some\\location\\on\\disk\";\nMySettings.Load(myDefaultSettingsPath);\n```\n\nThis call will either load the settings from a file called `mysettings.json`, the name is automatically taken from the type name, or if no file exists, the defaults are loaded.\n\nYou can also load and save from a provided file name instead of deriving from the type name, such as:\n\n```csharp\nstring myDefaultSettingsPath = \"C:\\Some\\location\\on\\disk\";\nMySettings.Load(myDefaultSettingsPath, \"custom-name.json\");\n```\n\nYou can access the values using the instance:\n\n```csharp\nvar theValue = MySettings.Instance.ASetting;\n```\nOr\n```csharp\nMySettings.Instance.ASetting = theValue;\n```\n### Saving\n\nSaving the settings is a call to the `Save()` method, like:\n\n```csharp\nMySettings.Save();\n```\n\nThis will always save back to the same file the settings were originally loaded from, or if there was no file, the file will be created and the settings saved.\n\n### Reset\nYou can easily return back to the defaults by calling the `ResetToDefaults()` method, like:\n```csharp\nMySettings.ResetToDefaults();\n```\nThis will reset all settings to their default values and immediately write them to disk. If you do not want to write them to disk, simply pass a `false` to the method.\n```csharp\nMySettings.ResetToDefaults(saveToDisk:false);\n```\n# Dirty Object Tracking\nUsing the attributes and validators you can track classes with changes, such as view models, in order save new data or update UI state accordingly.\n\n## The Interface\nThere are a number of ways of implementing something like such as decorators and so forth, but to keep this as pluggable as possible this feature makes use of an interface to implement on your validatable classes.\n\n```csharp\npublic interface IDirtyMonitoring\n{\n    /// \u003csummary\u003e\n    /// A flag denoting if the object is dirty\n    /// \u003c/summary\u003e\n    bool IsDirty { get; set; }\n    \n    /// \u003csummary\u003e\n    /// The object hash value\n    /// \u003c/summary\u003e\n    string? Hash { get; set; }\n}\n```\nTo create instances of validators in 1.3.0 things have changed a bit. It's no longer a state class and is now instantiated from a factory, such as:\n```csharp\nservices.RegisterLazySingleton(() =\u003e DirtyValidatorFactory.Create());\n```\nThen once you have everything registered at runtime this can be resolved for constructor injection.\n```csharp\npublic class MyClass\n{\n    private IDirtyValidator _dirtyValidator;\n    \n    public MyClass(IDirtyValidator dirtyValidator) // easier to mock in tests etc\n    {\n        _dirtyValidator = dirtyValidator;\n        ...\n    }\n    ...\n}\n```\n\nOr you can simply call the factory directly.\n```csharp\npublic class MyClass\n{\n    private IDirtyValidator _dirtyValidator;\n    \n    public MyClass()\n    {\n        _isDirtyValidator = DirtyValidatorFactory.Create();\n        ...\n    }\n    ...\n}\n```\nObviously you could also wire this up in your favourite DI container.\n## Usage\nFirst implement the interface on your own classes, such as:\n```csharp\npublic class PersonViewModel : IDirtyMonitoring\n{\n    public string Name { get; set; }\n    [IsDirtyMonitoring]\n    public string DisplayName { get; set; }\n    public bool IsDirty { get; set; }    \n    public string? Hash { get; set; }\n}\n```\nIn this example only the `DisplayName` property is monitored for changes. After an object has completed being initialised and is in it's \"clean\" state, validate it.\n```csharp\n_isDirtyValidator.Validate(p);\n```\nNow, at any point in time you can validate it again to detect if the class is dirty.\n```csharp\np.DisplayName = \"Original\";\n_isDirtyValidator.Validate(p).IsDirty; // false\np.DisplayName = \"SomedifferentValue\";\n_isDirtyValidator.Validate(p).IsDirty; // true\np.DisplayName = \"Original\";\n_isDirtyValidator.Validate(p).IsDirty; // false\n```\n## Property \u0026 Field Tracking\n\nAs of v1.2.0 it is also possible to track which properties in a given object instance have changed. In order to track properties, call the `Validate` method and pass `True` as the `trackProperties` parameter.\n```csharp\n_isDirtyValidator.Validate(p, trackProperties:true);\n```\nNow that the object, its properties and fields have been validated changes can be reported in more detail. Calls to the `ValidatePropertiesAndFields()` method will return collections of `PropertyInfo` and `FieldInfo` objects, such as:\n```csharp\nvar (props, fields) = _isDirtyValidator.ValidatePropertiesAndFields(p);\n```\nIn the example above the `props` and `fields` collections contain details of the properties and fields that have changed since the previous validation process.\n\nTo restart this validation process on the same instance, simply re-validate it and pass `True` again. \n```csharp\n_isDirtyValidator.Validate(p, true);\n```\n## Dirty Monitoring Example Usage\n\n```csharp\npublic void LoadPeopleFromDataSource()\n{\n    var vms = Mapper.Map(_dataService.GetPeople());\n    foreach(var vm in vms)\n    {\n        _isDirtyValidator.Validate(p, trackProperties:true);\n        ...\n    }\n}\n\npublic void SaveUiState()\n{\n    foreach(var vm in _people)\n    {\n        if(_isDirtyValidator.Validate(vm).IsDirty)\n        {\n            // save logic\n            \n            var (props, fields) = _isDirtyValidator.ValidatePropertiesAndFields(vm);\n            // more granular save logic\n        }\n    }\n}\n```\n## Managing Hashes\n\nThe property containing the hash store is now exposed (since v1.2.5) so you can better manage the resources in use. You can simple set the property to a new empty collection or clear the existing one.\n\n```csharp\n_isDirtyValidator.ObjectValueHashStore = new();\n```\n\nOr you can perform a complete reset using the provided method with the option of clearing down the cached type data:\n```csharp\n_isDirtyValidator.Reset(); // clearTypeInfo:false\n```\n```csharp\n// will clear any cached type info\n_isDirtyValidator.Reset(clearTypeInfo:true);\n```\n# Collections\n## Shuffle Collections\n```csharp\nIEnumerable\u003cint\u003e ints = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };\nIEnumerable\u003cint\u003e shuffledInts = ints.Shuffle();\n```\nOr you can provide your own instance of `Random`.\n```csharp\nRandom randomiser = new Random();\nIEnumerable\u003cint\u003e ints = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };\nIEnumerable\u003cint\u003e shuffledInts = ints.Shuffle(randomiser);\n```\n\n# Environment\nThere are a handful of helper methods and classes for access environment variables on various platforms.\n## Common Variables\n```csharp\nvar path = EnvEx.GetVariable(EnvExVariableNames.Path);\n```\n## Windows Variables\n```csharp\nvar appData = EnvEx.GetVariable(EnvExWinVariableNames.AppData); // C:\\Users\\username\\AppData\\Roaming\n```\n## OSX Variables\n```csharp\nvar shell = EnvEx.GetVariable(EnvExOsxVariableNames.Shell); // \"/bin/bash\"\n```\n## Linux Variables\n```csharp\nvar manPath = EnvEx.GetVariable(EnvExLinuxVariableNames.ManPath); // \":\"\n```\nMore variables names are included in the library than are shown above. You can make use of these via helper constants in the following classes:\n\n- EnvExVariableNames (Common)\n- EnvExWinVariableNames\n- EnvExOsxVariableNames\n- EnvExLinuxVariableNames\n\nSince the `EnvEx.GetVariable` method just takes a string, any value can be passed, such as:\n```csharp\nvar envValue = EnvEx.GetVariable(\"MYVARIABLENAME\");\n```\nOn Window you can also pass a target parameter of type `EnvironmentVariableTarget`. The default for this is `Process` as Linux and OSX do not support this parameter. If anything\nother than `Process` is passed on a non-Windows platform it will be defaulted to `Process` to prevent exceptions being raised.\n\n# Cryptography\nThere is a new little class to help digitally sign data with RSA Cryptography. The main class is created via a factory which can be registered in your DI container of choice.\n```csharp\npublic interface IRsaCryptoFactory\n{\n}\n\ncontainer.Register\u003cIRsaCryptoFactory, RsaCryptoFactory\u003e();\n```\nYou can then use this factory to obtain instances of the service.\n```csharp\nvar crypto = cryptoFactory.Create();\n```\nThere are many overloads of the create method to control how the service is built and how you want it configured.\n\nYou should also wrap each use of this within a using statement such as:\n```csharp\nusing(var crypto = cryptoFactory.Create(_privateKey, _publicKey, HashAlgorithmName.SHA512, RSASignaturePadding.Pkcs1))\n{\n   // do your stuff.\n}\n```\nOnce you have created an instance if you do not provide either of the keys, you can obtain the used keys for storage via the two properties, such as:\n```csharp\nvar crypto = cryptoFactory.Create();\nvar publicKey = sut.PublicKey;\nvar privateKey = sut.PrivateKey;\n```\n# Graphics\n## Convert to HEX\n```csharp\nint red = 121;\nint green = 155;\nint blue = 56;\n\nvar hex = Graphics.Colors.ToHex(red, green, blue);\n```\nOr you can also use an alpha value.\n```csharp\nvar hex = Graphics.Colors.ToHex(alpha, red, green, blue);\n```\nYou can also pass values as an array\n```csharp\nvar hex = Graphics.Colors.ToHex(new[] { alpha, red, green, blue });\n```\n## Convert to RGB\n```csharp\nint red = 255;\nint green = 169;\nint blue = 104;\n\nvar color = Graphics.Colors.ToRgb(\"#FFA968\");\n```\n## Convert to ARGB\nThis can be useful for WPF and XAML which supports an alpha value in the HEX.\n```csharp\nint alpha = 255;\nint red = 146;\nint green = 145;\nint blue = 145;\n\nvar c = Graphics.Colors.ToArgb(\"#FF929191\");\n```\n# UI Values\n### Data Sizes\nConverts `integer` and `long` values representing data sizes to human readable form\n```csharp\nint input = 10000000;\ninput.ToHumanReadable(); returns \"9.54 Mb\"\n\nlong input = 2000000000000000000;\ninput.ToHumanReadable(); returns \"1.73 Eb\"\n```\n### Time\n```csharp\ndouble input = 3657;\ninput.ToTimeDisplayFromSeconds() returns \"01:00:57\"\n\ndouble input = 3657.12;\ninput.ToTimeDisplayFromSeconds(withMs: true) returns \"01:00:57:120\"\n\nTimeSpan input = new TimeSpan(16, 45, 0);\ninput.GetTime() returns \"16:45\"\n```\n\n# Math\n\n## Even or Odd\nEven number detection\n```csharp\nint value = 2;\nvar isMyNumberEven = value.IsEvenNumber();\n```\n## Percentages\nBasic percentage calculations\n```csharp\nint value = 500;\nint total = 2000;\n\nvar percent = value.IsWhatPercentageOf(total) // 25\n```\n# Strings\n\n## Hamming Distance\nCalculates the number of edits required to go from one string to another must be equal lengths to start\n```csharp\nvar inputOne = \"InputString1\";\nvar inputTwo = \"InputString2\";\n\nvar distance = Distance.GetHammingDistance(inputOne, inputTwo);\ninputOne.HammingDistanceTo(inputTwo)\n```\n## Levenshtein Distance\nCalculates the number of edits required to go from one string to another\n```csharp\nvar inputOne = \"InputString1\";\nvar inputTwo = \"InputString2\";\n\nvar distance = Distance.GetLevenshteinDistance(inputOne, inputTwo);\ninputOne.LevenshteinDistanceTo(inputTwo)\n```\n## Shorten Strings\nThis method allows you to shorten strings to a predetermined length and pad with `...` or any pattern you provide.\n```csharp\nstring input = \"Thisismylongstringthatneedsshortening\";\nvar result = input.DotShortenString(10, 20); // \"Thisism...shortening\"\n\nstring input = \"Thisismylongstringthatneedsshortening\";\nvar result = input.DotShortenString(10, 20, \";;;\"); // \"Thisism;;;shortening\"\n```\n## Remove All Multi-spaces\n```csharp\nvar input = \"  This  has    too  many  spaces   \";\ninput.RemoveAllMultiSpace(); // \" This has too many spaces \"\n```\n```csharp\nvar input = \"  This  has    too  many  spaces   \";\ninput.RemoveAllMultiSpace(trim:true); // \"This has too many spaces\"\n```\n```csharp\nvar input = \"  This  has    too  many  spaces   \";\ninput.RemoveAllMultiSpace(\"--\"); // \"--This--has--too--many--spaces--\"\n```\n```csharp\nvar input = \"  This  has    too  many  spaces   \";\ninput.RemoveAllMultiSpace(pattern:\"--\", trim:true) // \"This--has--too--many--spaces\"\n```\n## String Compare\n```csharp\nstring input = \"string1\";\nstring pattern = \"strinG1\";\n\ninput.IsExactlySameAs(pattern); // false\n```\n# Serialization\n\n## XML Encoding Formatting\nAdds a strict uppercase UTF-8 to XML declarations\n```csharp\nusing (var sw = new UppercaseUtf8StringWriter())\n{\n    xsSubmit.Serialize(sw, new TestObject { SomeProperty = \"SomeValue\" });\n    xml = sw.ToString();\n}\n```\n# Patterns\n\n## Mvvm\nA very bare bones view model with property changed updates\n```csharp\npublic abstract class ViewModelBase : INotifyPropertyChanged\n{\n    ...\n    public bool IsEditable ...\n    ...\n    public bool IsBusy ...\n    ...\n    protected virtual bool SetProperty\u003cT\u003e(ref T storage, T value, [CallerMemberName] string propertyName = \"\")\n    {\n        ...\n    }\n}\n```\n## Mvvm - SuperObservableCollection\u003cT\u003e\nAn observable collection that mutes change notifications when adding a range of objects and allows sorting\n```csharp\npublic class SuperObservableCollection\u003cT\u003e : ObservableCollection\u003cT\u003e\n{\n    public SuperObservableCollection(IEnumerable\u003cT\u003e coll)\n    {\n    }\n\n    public void AddRange(IEnumerable\u003cT\u003e list, bool suppressNotifications = true, bool notifiyOnceAllAdded = true)\n    {\n        ...\n    }\n\n    public void Sort(bool suppressNotifications = false)\n    {\n        ...\n    }\n\t\n    public void Sort(IComparer\u003cT\u003e comparer, bool suppressNotifications = false)\n    {\n        ...\n    }\n}\n```\n## Observer\nA very basic implementation of the core bits of the observer pattern\n```csharp\npublic interface IObservable\n{\n    void Attach(IObserver observer);\n\n    void Detach(IObserver observer);\n\n    void Notify();\n}\n```\n```csharp\npublic interface IObserver\n{\n    void Update(IObservable observable);\n}\n```\n```csharp\npublic abstract class ObservableBase : IObservable\n{\n}\n```\n## Memento\n```csharp\npublic interface IMemento\n{\n    object GetState();\n}\n```\n```csharp\npublic interface IMementoOwner\n{\n    IMemento Save();\n\n    void Restore(IMemento memento);\n}\n```\n```csharp\npublic class MementoManager\n{\n    ...\n}\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjamsoft%2Fjamsoft.helpers","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjamsoft%2Fjamsoft.helpers","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjamsoft%2Fjamsoft.helpers/lists"}