From 1f2da9c6a339a72e2bcfe78cdf07d0778c79850f Mon Sep 17 00:00:00 2001 From: konard Date: Wed, 10 Sep 2025 16:36:29 +0300 Subject: [PATCH 1/3] Initial commit with task details for issue #23 Adding CLAUDE.md with task information for AI processing. This file will be removed when the task is complete. Issue: https://github.com/link-foundation/link-cli/issues/23 --- CLAUDE.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..243d3ef --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,5 @@ +Issue to solve: https://github.com/link-foundation/link-cli/issues/23 +Your prepared branch: issue-23-e23fa86d +Your prepared working directory: /tmp/gh-issue-solver-1757511387777 + +Proceed. \ No newline at end of file From 801bc43ff0221a1678dd53f5812867b2147d65ca Mon Sep 17 00:00:00 2001 From: konard Date: Wed, 10 Sep 2025 16:36:45 +0300 Subject: [PATCH 2/3] Remove CLAUDE.md - PR created successfully --- CLAUDE.md | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index 243d3ef..0000000 --- a/CLAUDE.md +++ /dev/null @@ -1,5 +0,0 @@ -Issue to solve: https://github.com/link-foundation/link-cli/issues/23 -Your prepared branch: issue-23-e23fa86d -Your prepared working directory: /tmp/gh-issue-solver-1757511387777 - -Proceed. \ No newline at end of file From c88a23129f04dc8b1bfbe68068834396c592f99e Mon Sep 17 00:00:00 2001 From: konard Date: Wed, 10 Sep 2025 16:50:34 +0300 Subject: [PATCH 3/3] Add implementation of universal NamedTypes decorator for ILinks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add INamedTypes interface defining naming operations - Add NamedTypesDecorator implementing both ILinks and INamedTypes - Add comprehensive test suite for NamedTypesDecorator - Supports setting, getting, and removing names for link addresses - Integrates with existing UnicodeStringStorage and NamedLinks infrastructure Fixes #23 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .../NamedTypesDecoratorTests.cs | 193 ++++++++++++++++++ Foundation.Data.Doublets.Cli/INamedTypes.cs | 13 ++ .../NamedTypesDecorator.cs | 136 ++++++++++++ 3 files changed, 342 insertions(+) create mode 100644 Foundation.Data.Doublets.Cli.Tests/NamedTypesDecoratorTests.cs create mode 100644 Foundation.Data.Doublets.Cli/INamedTypes.cs create mode 100644 Foundation.Data.Doublets.Cli/NamedTypesDecorator.cs diff --git a/Foundation.Data.Doublets.Cli.Tests/NamedTypesDecoratorTests.cs b/Foundation.Data.Doublets.Cli.Tests/NamedTypesDecoratorTests.cs new file mode 100644 index 0000000..eb16bc1 --- /dev/null +++ b/Foundation.Data.Doublets.Cli.Tests/NamedTypesDecoratorTests.cs @@ -0,0 +1,193 @@ +using System; +using System.IO; +using System.Linq; +using System.Collections.Generic; +using Xunit; +using Platform.Memory; +using Platform.Data.Doublets; +using Platform.Data.Doublets.Memory.United.Generic; +using Foundation.Data.Doublets.Cli; +using DoubletLink = Platform.Data.Doublets.Link; + +namespace Foundation.Data.Doublets.Cli.Tests +{ + public class NamedTypesDecoratorTests : IDisposable + { + private readonly string _tempDbPath; + private readonly string _tempNamesDbPath; + + public NamedTypesDecoratorTests() + { + _tempDbPath = Path.GetTempFileName(); + _tempNamesDbPath = Path.GetTempFileName(); + } + + public void Dispose() + { + if (File.Exists(_tempDbPath)) File.Delete(_tempDbPath); + if (File.Exists(_tempNamesDbPath)) File.Delete(_tempNamesDbPath); + } + + private static void RunTestWithLinks(Action> testAction) + { + var tempFile = Path.GetTempFileName(); + try + { + using var memory = new FileMappedResizableDirectMemory(tempFile, UnitedMemoryLinks.DefaultLinksSizeStep); + using var unitedMemoryLinks = new UnitedMemoryLinks(memory); + var linksDecoratedWithAutomaticUniquenessResolution = unitedMemoryLinks.DecorateWithAutomaticUniquenessAndUsagesResolution(); + testAction(linksDecoratedWithAutomaticUniquenessResolution); + } + finally + { + if (File.Exists(tempFile)) File.Delete(tempFile); + } + } + + [Fact] + public void NamedTypesDecorator_ImplementsILinks() + { + RunTestWithLinks(links => + { + var decorator = new NamedTypesDecorator(links, _tempNamesDbPath); + + Assert.True(decorator is ILinks); + }); + } + + [Fact] + public void NamedTypesDecorator_ImplementsINamedTypes() + { + RunTestWithLinks(links => + { + var decorator = new NamedTypesDecorator(links, _tempNamesDbPath); + + Assert.True(decorator is INamedTypes); + }); + } + + [Fact] + public void NamedTypesDecorator_CanSetAndGetNames() + { + RunTestWithLinks(links => + { + var decorator = new NamedTypesDecorator(links, _tempNamesDbPath); + + var link1 = decorator.GetOrCreate(10u, 20u); + var link2 = decorator.GetOrCreate(30u, 40u); + + var nameLink1 = decorator.SetName(link1, "TestLink1"); + var nameLink2 = decorator.SetName(link2, "TestLink2"); + + Assert.NotEqual(links.Constants.Null, nameLink1); + Assert.NotEqual(links.Constants.Null, nameLink2); + + var retrievedName1 = decorator.GetName(link1); + var retrievedName2 = decorator.GetName(link2); + + Assert.Equal("TestLink1", retrievedName1); + Assert.Equal("TestLink2", retrievedName2); + }); + } + + [Fact] + public void NamedTypesDecorator_CanGetLinkByName() + { + RunTestWithLinks(links => + { + var decorator = new NamedTypesDecorator(links, _tempNamesDbPath); + + var link = decorator.GetOrCreate(50u, 60u); + decorator.SetName(link, "UniqueTestName"); + + var retrievedLink = decorator.GetByName("UniqueTestName"); + + Assert.Equal(link, retrievedLink); + }); + } + + [Fact] + public void NamedTypesDecorator_CanRemoveNames() + { + RunTestWithLinks(links => + { + var decorator = new NamedTypesDecorator(links, _tempNamesDbPath); + + var link = decorator.GetOrCreate(70u, 80u); + decorator.SetName(link, "TemporaryName"); + + var nameBeforeRemoval = decorator.GetName(link); + Assert.Equal("TemporaryName", nameBeforeRemoval); + + decorator.RemoveName(link); + + var nameAfterRemoval = decorator.GetName(link); + Assert.Null(nameAfterRemoval); + + var linkByName = decorator.GetByName("TemporaryName"); + Assert.Equal(links.Constants.Null, linkByName); + }); + } + + [Fact] + public void NamedTypesDecorator_CanOverwriteNames() + { + RunTestWithLinks(links => + { + var decorator = new NamedTypesDecorator(links, _tempNamesDbPath); + + var link = decorator.GetOrCreate(90u, 100u); + decorator.SetName(link, "FirstName"); + + var firstRetrievedName = decorator.GetName(link); + Assert.Equal("FirstName", firstRetrievedName); + + decorator.SetName(link, "SecondName"); + + var secondRetrievedName = decorator.GetName(link); + Assert.Equal("SecondName", secondRetrievedName); + + var linkByFirstName = decorator.GetByName("FirstName"); + Assert.Equal(links.Constants.Null, linkByFirstName); + + var linkBySecondName = decorator.GetByName("SecondName"); + Assert.Equal(link, linkBySecondName); + }); + } + + [Fact] + public void NamedTypesDecorator_DeleteRemovesAssociatedNames() + { + RunTestWithLinks(links => + { + var decorator = new NamedTypesDecorator(links, _tempNamesDbPath); + + var link = decorator.GetOrCreate(110u, 120u); + decorator.SetName(link, "LinkToDelete"); + + var nameBeforeDeletion = decorator.GetName(link); + Assert.Equal("LinkToDelete", nameBeforeDeletion); + + decorator.Delete(new uint[] { link }, null); + + var linkByName = decorator.GetByName("LinkToDelete"); + Assert.Equal(links.Constants.Null, linkByName); + }); + } + + [Fact] + public void NamedTypesDecorator_HandlesNonexistentNames() + { + RunTestWithLinks(links => + { + var decorator = new NamedTypesDecorator(links, _tempNamesDbPath); + + var linkByNonexistentName = decorator.GetByName("NonexistentName"); + Assert.Equal(links.Constants.Null, linkByNonexistentName); + + var nameOfNonexistentLink = decorator.GetName(999999u); + Assert.Null(nameOfNonexistentLink); + }); + } + } +} \ No newline at end of file diff --git a/Foundation.Data.Doublets.Cli/INamedTypes.cs b/Foundation.Data.Doublets.Cli/INamedTypes.cs new file mode 100644 index 0000000..54b1b62 --- /dev/null +++ b/Foundation.Data.Doublets.Cli/INamedTypes.cs @@ -0,0 +1,13 @@ +using System.Numerics; + +namespace Foundation.Data.Doublets.Cli +{ + public interface INamedTypes + where TLinkAddress : IUnsignedNumber + { + string? GetName(TLinkAddress link); + TLinkAddress SetName(TLinkAddress link, string name); + TLinkAddress GetByName(string name); + void RemoveName(TLinkAddress link); + } +} \ No newline at end of file diff --git a/Foundation.Data.Doublets.Cli/NamedTypesDecorator.cs b/Foundation.Data.Doublets.Cli/NamedTypesDecorator.cs new file mode 100644 index 0000000..be8447e --- /dev/null +++ b/Foundation.Data.Doublets.Cli/NamedTypesDecorator.cs @@ -0,0 +1,136 @@ +using System.Numerics; +using Platform.Delegates; +using Platform.Memory; +using Platform.Data; +using Platform.Data.Doublets; +using Platform.Data.Doublets.Decorators; +using Platform.Data.Doublets.Memory; +using Platform.Data.Doublets.Memory.United.Generic; +using System; +using System.Linq; +using System.Collections.Generic; + +namespace Foundation.Data.Doublets.Cli +{ + public class NamedTypesDecorator : LinksDecoratorBase, INamedTypes + where TLinkAddress : struct, + IUnsignedNumber, + IComparisonOperators, + IShiftOperators, + IBitwiseOperators, + IMinMaxValue + { + private readonly bool _tracingEnabled; + public readonly NamedLinks NamedLinks; + public readonly string NamedLinksDatabaseFileName; + + public static ILinks MakeLinks(string databaseFilename) + { + var links = new UnitedMemoryLinks(databaseFilename); + return links.DecorateWithAutomaticUniquenessAndUsagesResolution(); + } + + public static string MakeNamesDatabaseFilename(string databaseFilename) + { + var filenameWithoutExtension = Path.GetFileNameWithoutExtension(databaseFilename); + var directory = Path.GetDirectoryName(databaseFilename); + var namesDatabaseFilename = Path.Combine(directory ?? string.Empty, $"{filenameWithoutExtension}.names.links"); + return namesDatabaseFilename; + } + + public NamedTypesDecorator(ILinks links, string namesDatabaseFilename, bool tracingEnabled = false) : base(links) + { + _tracingEnabled = tracingEnabled; + if (_tracingEnabled) Console.WriteLine($"[Trace] Constructing NamedTypesDecorator with names DB: {namesDatabaseFilename}"); + var namesConstants = new LinksConstants(enableExternalReferencesSupport: true); + var namesMemory = new FileMappedResizableDirectMemory(namesDatabaseFilename, UnitedMemoryLinks.DefaultLinksSizeStep); + var namesLinks = new UnitedMemoryLinks(namesMemory, UnitedMemoryLinks.DefaultLinksSizeStep, namesConstants, IndexTreeType.Default); + var decoratedNamesLinks = namesLinks.DecorateWithAutomaticUniquenessAndUsagesResolution(); + NamedLinks = new UnicodeStringStorage(decoratedNamesLinks).NamedLinks; + NamedLinksDatabaseFileName = namesDatabaseFilename; + } + + public NamedTypesDecorator(string databaseFilename, bool tracingEnabled = false) + : this(MakeLinks(databaseFilename), MakeNamesDatabaseFilename(databaseFilename), tracingEnabled) + { + } + + public string? GetName(TLinkAddress link) + { + if (_tracingEnabled) Console.WriteLine($"[Trace] GetName called for link: {link}"); + var result = NamedLinks.GetNameByExternalReference(link); + if (_tracingEnabled) Console.WriteLine($"[Trace] GetName result: {result}"); + return result; + } + + public TLinkAddress SetName(TLinkAddress link, string name) + { + if (_tracingEnabled) Console.WriteLine($"[Trace] SetName called for link: {link} with name: '{name}'"); + RemoveName(link); + var result = NamedLinks.SetNameForExternalReference(link, name); + if (_tracingEnabled) Console.WriteLine($"[Trace] SetName result: {result}"); + return result; + } + + public TLinkAddress GetByName(string name) + { + if (_tracingEnabled) Console.WriteLine($"[Trace] GetByName called for name: '{name}'"); + var result = NamedLinks.GetExternalReferenceByName(name); + if (_tracingEnabled) Console.WriteLine($"[Trace] GetByName result: {result}"); + return result; + } + + public void RemoveName(TLinkAddress link) + { + if (_tracingEnabled) Console.WriteLine($"[Trace] RemoveName called for link: {link}"); + NamedLinks.RemoveNameByExternalReference(link); + if (_tracingEnabled) Console.WriteLine($"[Trace] RemoveName completed for link: {link}"); + } + + public override TLinkAddress Update(IList? restriction, IList? substitution, WriteHandler? handler) + { + if (_tracingEnabled) + { + var req = restriction == null ? "null" : string.Join(",", restriction); + Console.WriteLine($"[Trace] Update called with restriction: [{req}]"); + } + var @continue = _links.Constants.Continue; + var result = _links.Update(restriction, substitution, (IList? before, IList? after) => + { + if (_tracingEnabled) Console.WriteLine($"[Trace] Debug: handlerWrapper invoked - before={(before == null ? "null" : string.Join(",", before))}, after={(after == null ? "null" : string.Join(",", after))}"); + if (before != null && after == null) + { + var deletedLinkIndex = _links.GetIndex(link: before); + RemoveName(deletedLinkIndex); + } + return handler == null ? @continue : handler(before, after); + }); + if (_tracingEnabled) Console.WriteLine($"[Trace] Update result: {result}"); + return result; + } + + public override TLinkAddress Delete(IList? restriction, WriteHandler? handler) + { + if (_tracingEnabled) + { + var formattedRestriction = restriction == null ? "null" : string.Join(",", restriction); + Console.WriteLine($"[Trace] Delete called with restriction: [{formattedRestriction}]"); + } + if (_tracingEnabled) Console.WriteLine($"[Trace] Debug: this._links is of type: {_links.GetType()}"); + var @continue = _links.Constants.Continue; + if (_tracingEnabled) Console.WriteLine($"[Trace] Debug: Calling underlying _links.Delete"); + TLinkAddress result = _links.Delete(restriction, (IList? before, IList? after) => + { + if (_tracingEnabled) Console.WriteLine($"[Trace] Debug: handlerWrapper invoked - before={(before == null ? "null" : string.Join(",", before))}, after={(after == null ? "null" : string.Join(",", after))}"); + if (before != null && after == null) + { + var deletedLinkIndex = _links.GetIndex(link: before); + RemoveName(deletedLinkIndex); + } + return handler == null ? @continue : handler(before, after); + }); + if (_tracingEnabled) Console.WriteLine($"[Trace] Debug: Delete result: {result}"); + return result; + } + } +} \ No newline at end of file