Niddy.Avalonia.Generators
1.1.1
dotnet add package Niddy.Avalonia.Generators --version 1.1.1
NuGet\Install-Package Niddy.Avalonia.Generators -Version 1.1.1
<PackageReference Include="Niddy.Avalonia.Generators" Version="1.1.1" />
<PackageVersion Include="Niddy.Avalonia.Generators" Version="1.1.1" />
<PackageReference Include="Niddy.Avalonia.Generators" />
paket add Niddy.Avalonia.Generators --version 1.1.1
#r "nuget: Niddy.Avalonia.Generators, 1.1.1"
#:package Niddy.Avalonia.Generators@1.1.1
#addin nuget:?package=Niddy.Avalonia.Generators&version=1.1.1
#tool nuget:?package=Niddy.Avalonia.Generators&version=1.1.1
<p align="center"> <img src="https://raw.githubusercontent.com/vonderborch/Niddy/main/assets/branding/niddy-package-icon.png" alt="Niddy logo" width="160" height="160" /> </p>
Niddy
This is my personal utility library. It's public in case it's useful to someone, but it's built for my own projects first: APIs may change between versions, and issues or PRs may not get a response. Use it, fork it, or borrow from it freely (MIT).
A collection of general-purpose .NET utilities and Avalonia UI helpers, split into focused NuGet packages so you only pull in what you need.
Packages
dotnet add package Niddy.Core
dotnet add package Niddy.Avalonia
dotnet add package Niddy.Avalonia.Generators # optional: automatic page registration
Alternatively, clone this repo and reference the relevant project(s) directly.
Features
Niddy.Core
- File system (
Niddy.Core.IO): C# 14 extensions onFile,DirectoryandPath(Directory.DeleteWithRetryfor locked or read-only files,Directory.CreateEmpty,Directory.Copywith exclusions,Path.Comparison,Path.IsInList,Path.SanitizeFileName), andstream.ReadToStringAsync() - Backups (
Niddy.Core.IO.Backups):File.Backup/Directory.Backupmake uniquely named copies or archives and prune old ones - Archives (
Niddy.Core.IO.Archives): zip, tar and tar.gz withArchive(create, safely extract, read or stream single files) - JSON (
Niddy.Core.Serialization):JsonFilereads and atomically writes JSON files;JsonSerializerOptions.NiddyandJsonSerializer.DeserializeOrDefault - Retry (
Niddy.Core.Resilience): retry with exponential backoff and optional jitter (sync and async), throwingMaxRetriesExceptionorRetryBlockedExceptionwhen it stops - Small extensions:
string.IsNullOrWhiteSpace()andJoin(Niddy.Core.Text),List<T>extensions (Niddy.Core.Collections),value.DisposeIfPossible()andUrlLauncher.Open(Niddy.Core) - Threading (
Niddy.Core.Threading): lock-freeGuard(atomic boolean flag) andAtomicOperations, plusDebouncerandThrottlerfor bursty events (sync or async actions,TimeProvider-driven, posting back to the captured synchronization context) - Results (
Niddy.Core.Results):Result,Result<T>andOption<T>value types withMatch/Map/Bind,Result.Try/TryAsyncand anErrorrecord - Humanize (
Niddy.Core.Text): file sizes (1.5 MB), counts (12.3K), durations (5 min 3 s), relative times (3 minutes ago), plurals and ordinals - Files (
Niddy.Core.IO):AtomicFilewrites (temp file + replace, so a crash never leaves half a file),DebouncedFileWatcher(one batchedChangedevent per burst), andAppPathsfor per-platform data, config, cache and log folders - Logging (
Niddy.Core.Logging): a rolling file logger forMicrosoft.Extensions.Logging(AddFile,NiddyLogging.CreateFactory; the provider is in.Files), plusBatchingLoggerProvider(.Batching) for writing your own non-blocking providers (a database, a log server) andAddOwnedProviderto register them - Settings (
Niddy.Core.Settings):SettingsStore<T>, a typed JSON settings file with atomic saves,Update/Reset, and optional reload on external changes - Secure storage (
Niddy.Core.Security): platform-native secure storage viaISecureStorage— Keychain on every Apple platform, Windows Credential Manager, Linux Secret Service, with an encrypted-file fallback (and an error in the browser, which can't keep secrets);SecureStorageFactoryauto-selects the right implementation, and apps or other packages can register their own providers to replace, wrap or extend it - Platform info (
Niddy.Core.SystemInfo):Platform.Infowith the OS and its version, architecture, Rust/LLVM-style target triple (aarch64-apple-darwin,x86_64-unknown-linux-musl), runtime, memory and estimated GPUs (Mac Catalyst, iPadOS, browser and WASI included, on every architecture .NET supports), plusDeclareSupportedOperatingSystemsto fail fast on unsupported OSes
Niddy.Avalonia
- App setup (
Niddy.Avalonia.Hosting): derive yourAppfromNiddyAppand configure pages, dialogs, toasts, theme and the main window in oneConfiguremethod. It decides between desktop and mobile mode itself (or you setMode) and builds the right shell, so you only write pages, never windows - Dialog system (
Niddy.Avalonia.Dialogs):Dialogstatic facade for Notification, Confirmation, Warning, Input, MultiInput, Progress, Selection, Exception, Color, Markdown, Table and Web dialogs. Each dialog is one control, shown either as an overlay card or in a modal window (DialogDisplayMode.Autopicks); derive fromDialogBase<TResult>and callDialog.Showfor your own dialogs. Every dialog inherits the same title, description and button-row layout fromDialogLayout. Window dialogs take an optional fixedwindowWidthandwindowHeight. Escape, the window close button, and the platform back request act as the cancel button - Page system (
Niddy.Avalonia.PageSystem): derive pages fromPageorPage<TAppDataContext, TPageDataContext>, register them explicitly or automatically with the generator (including pages in referenced projects and plugin assemblies loaded at runtime), and show them in aPageViewwith a back stack, parameters, lifecycle hooks, navigation guards, kept-alive pages, nested child views and page transitions. Pages can declare keyboard shortcuts (Shortcut = "Primary+1"). Menus list pages alphabetically, or in any order you define with a sort key per page or a sort function - Data contexts (
Niddy.Avalonia.DataContexts):DataContextBase, a ReactiveUI base class for a view's data context withIsBusy/ErrorMessageand aRunAsynchelper - Toasts (
Niddy.Avalonia.Toasts):Toast.Showover aToastHost, shown at any of eight screen positions (ScreenPlacement), with a separate placement for mobile and narrow windows. Toasts can carry an action button (Undo, Retry…), repeated toasts merge into one with a count, andToast.ShowProgressshows a progress toast you update and complete - File pickers (
Niddy.Avalonia):FilePickerreturnsIStorageFile/IStorageFolderhandles, with desktop-only*Pathvariants - Window and layout memory (
Niddy.Avalonia.State): windows reopen at their last size, position and state (kept on screen if a monitor went away), andGrid/ProportionalStackPanelsplitters keep their sizes, viaWindowMemory.Key/LayoutMemory.Keyattached properties - Global exception handler (
Niddy.Avalonia.Hosting): logs UI-thread, unobserved-task and background-thread exceptions and showsDialog.Exceptioninstead of crashing - Converters and markup (
Niddy.Avalonia.Converters): enum-to-bool (radio buttons), bool-to-value, humanize and collection-emptiness converters,{niddy:EnumValues}, and oneniddyXAML namespace for everything - Responsive layout (
Niddy.Avalonia.Layout):Responsive.NarrowBelow/WideAboveaddnarrow/medium/wideclasses by width for styles to target
Desktop, mobile, and browser
Everything works on desktop. For mobile and browser apps (a single view, no windows):
- With
NiddyAppthis is already set up. Otherwise, use anAppShellas your main view (new AppShell(options)), or wrap your content in aDialogOverlayHost(and aToastHostfor toasts). Dialogs then open as overlays, includingDialog.Exception; without a host, dialogs throw a clearInvalidOperationExceptionbecause there's no window to open. - Use the
FilePickermethods that return storage handles; the*Pathvariants return null there. - The platform back request (Android back button, browser back) dismisses an open overlay dialog first, then goes back a page in the most recently attached
PageView. SetHandleBackRequests="False"on aPageViewto opt out. - Overlay dialogs and toasts stay clear of safe-area insets (notch, status and navigation bars) and the on-screen keyboard.
Documentation
Full docs live in docs/: a page per class, with examples, organized by package and folder.
- Docs index: every class in Niddy.Core, Niddy.Avalonia and the generator.
- Walkthroughs that span several classes:
- AI-GUIDE.md: one file that explains how to use the whole library, written for AI coding assistants (and handy for people too). Point your assistant at it.
Quick start
An Avalonia app is an App class deriving from NiddyApp, plus pages:
using Niddy.Avalonia.Hosting;
using Niddy.Avalonia.PageSystem;
public class App : NiddyApp
{
protected override void Configure(NiddyAppOptions options)
{
options.Title = "My App";
options.StartingPage = HomePage.PageId;
options.AppDataContext = new MainDataContext();
options.Layout = pages => new PageMenu { Content = pages }; // optional navigation menu
}
}
// Registered at compile time by Niddy.Avalonia.Generators
[PageRegistration(DisplayName = "Home", Shortcut = "Primary+1")]
public partial class HomePage : Page<MainDataContext, HomePageDataContext>
{
public HomePage() => InitializeComponent();
}
// Program.cs (desktop)
AppBuilder.Configure<App>().UsePlatformDetect().StartWithClassicDesktopLifetime(args);
Then, from any page:
using Niddy.Avalonia.Dialogs;
using Niddy.Avalonia.Toasts;
if (await Dialog.Warning.Open(this, "Delete project?", "This can't be undone.", yesButtonText: "Delete"))
{
DeleteProject();
Toast.Show(this, "Project deleted", actionText: "Undo", action: RestoreProject);
}
NavigateTo<SettingsPage>();
And Niddy.Core, in any app:
using Niddy.Core.IO;
using Niddy.Core.Logging;
using Niddy.Core.Security;
AppPaths paths = AppPaths.For("MyApp").EnsureCreated(); // per-platform data, config, cache, log folders
using ILoggerFactory loggerFactory = NiddyLogging.CreateFactory(paths); // rolling log files in paths.Logs
ISecureStorage secrets = SecureStorageFactory.Create(paths.Data, "MyApp"); // keychain / credential manager
secrets.SetToken("api-key", key);
See the docs for everything else.
Development
Requires the .NET 10 SDK (pinned in global.json). C# 14, Avalonia 12, xUnit v3.
Layout
Niddy.slnx solution: /src/ and /tests/ solution folders
src/Niddy.Core/<Folder>/*.cs namespace Niddy.Core.<Folder> (e.g. Niddy.Core.Settings, Niddy.Core.IO.Archives)
src/Niddy.Avalonia/<Folder>/*.cs namespace Niddy.Avalonia.<Folder> (e.g. Niddy.Avalonia.PageSystem)
src/Niddy.Avalonia.Generators/ netstandard2.0, packed with its .nuspec
tests/<Project>.Tests/ one test project per package
tests/Fixtures/ small page libraries the tests reference or load at runtime
docs/ documentation
A new Avalonia folder whose types are used from XAML also needs an XmlnsDefinition line in src/Niddy.Avalonia/XmlnsDefinitions.cs (every public namespace is mapped to https://github.com/vonderborch/Niddy; Dialogs.BuiltIn holds only internal dialogs, so it isn't).
Build and test
dotnet build
dotnet test
- Builds must be warning-free:
TreatWarningsAsErrorsis on inDirectory.Build.props, and that includes missing or broken XML docs. The only suppression is AVLN3001 onToastItem, inNiddy.Avalonia.csproj. - Avalonia tests run headless (
[AvaloniaFact]); callDispatcher.UIThread.RunJobs()to flush posted work. - Generator tests run the generator on in-memory compilations.
- Add tests with every behavior change.
Code conventions
- File-scoped namespaces; XML doc comments on every public member (they feed IntelliSense and the docs).
- Folders, namespaces and docs folders match:
src/Niddy.Core/IO/Archives/Archive.csisNiddy.Core.IO.Archives, documented indocs/Niddy.Core/IO/Archives/.- A feature made of several public types gets its own folder (or subfolder).
- Don't split a feature into sub-namespaces that its users would import together. Everything for pages is in
PageSystem, so oneusingcovers it. - A single type that doesn't belong to a domain goes in the project root namespace (
Niddy.Core.UrlLauncher). - Never name a namespace after a type in it (
ToastsholdsToast), or after a BCL or Avalonia type its users will also import (Logging.Files, notFile). - No catch-all
HelpersorUtilitiesfolders: put a type in the domain it serves.
- Extensions use C# 14
extensionblocks in a static class named<Target>Extensions(FileSystemExtensions,StreamExtensions). Add static members to BCL types where that reads naturally (Directory.DeleteWithRetry,Path.Comparison). Don't reuse a name the target already has: the BCL's own member always wins. - Explicit types, no
varunless it's required (anonymous types). When the type is on the left, use target-typednew:List<string> names = new();,Button button = new() { Content = "OK" };. Always use braces on control blocks (if,else, etc.). Code examples indocs/follow the same style. These rules are in.editorconfigand enforced by the build (EnforceCodeStyleInBuild). System.Threading.Lock(private readonly Lock _gate = new();) for locking.- Inside
Niddy.Avalonia, refer to Avalonia types asglobal::Avalonia.*where theNiddy.Avalonianamespace would shadow them. - Match the surrounding code's style and comment density.
- Things that must work off desktop (dialogs, toasts, file pickers, navigation) need a mobile/browser path; don't assume windows exist.
- Don't remove package references from the
.csprojfiles; some are there for upcoming work.
Documentation
Any change to the public API or to documented behavior updates the docs in the same change.
Every new public type is documented when it's added: classes, records, structs, interfaces, enums, delegates, attributes, everything. There are no exceptions for "small", "internal-feeling" or "helper" types. A new top-level type gets its own page (or a section on its owning class's page, if it's a small supporting type per the rules below), an entry in docs/README.md, and, if it changes how the library is used, an update to docs/AI-GUIDE.md. New members on existing types are added to that type's page. A change that adds an undocumented public type is not finished.
docs/<Project>/<Folder>/<Class>.md: one page per public class, mirroring the source layout undersrc/. Small supporting types (enums, event args, options records, nested types) go on the page of the class that uses them. Internal types are not documented.- Page shape:
# Name, a line with`Namespace` · Package · [source](../../../src/<Project>/<Folder>/<Class>.cs), a short summary,## APItables,## Example/## Examples,## See also. docs/examples/*.md: walkthroughs that span several classes. Add one when a feature only makes sense combined with others.docs/README.md: the index. Add, rename or remove pages there too.docs/AI-GUIDE.md: a single-file guide to the whole library for AI assistants. Update it when a change affects how the library should be used: new features, changed signatures, new gotchas.- This
README.md: packages, features, a short quick start and links intodocs/. It is packed into every NuGet package, so its links into the repo are absolute GitHub URLs. Keep usage detail indocs/.
Write docs from the source, not from memory: check each member's signature and XML docs, make sure examples compile against the real API, and make sure relative links resolve.
Contributing
- Clone or fork the repo
- Create a new branch
- Code!
- Push your changes and open a PR
- Once approved, they'll be merged in
- Profit!
Releasing
All packages share one SemVer 2 version, taken from the GitHub release tag.
- Create a GitHub release with a tag like
v1.2.0(orv1.3.0-beta.1for a pre-release; the leadingvis optional) - Publishing the release runs the publish workflow, which runs the tests, then packs every project at that version and pushes it to NuGet
Publishing uses nuget.org trusted publishing, so no API key is stored in the repo. It needs, once:
- a trusted publishing policy on nuget.org (your username → Trusted Publishing) for owner
vonderborch, repositoryNiddyand workflow filenuget-publish.yml - a
NUGET_USERrepository secret holding the nuget.org profile name that owns the packages
Local builds are versioned 0.0.0-dev.
Future Plans
See list of issues under the Milestones: https://github.com/vonderborch/Niddy/milestones
Learn more about Target Frameworks and .NET Standard.
-
net10.0
- Niddy.Avalonia (>= 1.1.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.