From 533b93f17ecc7cbf3240c6689daa57f547d95120 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 11 Jan 2026 10:06:44 +0000 Subject: [PATCH] feat(nip-transformer): add support for hex NIPs like NIP-C7 - Extend pattern to match hex NIP identifiers (NIP-C7, NIP-C0, NIP-A0) - Normalize hex NIPs to uppercase (nip-c7 -> C7) - Add tests for hex NIP parsing and normalization --- src/lib/nip-transformer.test.ts | 66 +++++++++++++++++++++++++++++++++ src/lib/nip-transformer.ts | 34 +++++++++++++---- 2 files changed, 93 insertions(+), 7 deletions(-) diff --git a/src/lib/nip-transformer.test.ts b/src/lib/nip-transformer.test.ts index e538f01..0f5bae1 100644 --- a/src/lib/nip-transformer.test.ts +++ b/src/lib/nip-transformer.test.ts @@ -174,4 +174,70 @@ describe("nipReferences transformer", () => { expect(nips[1].number).toBe("999"); }); }); + + describe("hex NIP support", () => { + it("should parse hex NIP-C7", () => { + const tree = createTree("Code snippets are defined in NIP-C7"); + const transformer = nipReferences(); + transformer(tree); + + const nips = getNodesOfType(tree, "nip"); + expect(nips).toHaveLength(1); + expect(nips[0].number).toBe("C7"); + expect(nips[0].raw).toBe("NIP-C7"); + }); + + it("should parse lowercase hex nip-c7", () => { + const tree = createTree("See nip-c7 for code snippets"); + const transformer = nipReferences(); + transformer(tree); + + const nips = getNodesOfType(tree, "nip"); + expect(nips).toHaveLength(1); + expect(nips[0].number).toBe("C7"); // Normalized to uppercase + expect(nips[0].raw).toBe("nip-c7"); + }); + + it("should parse NIP-C0", () => { + const tree = createTree("NIP-C0 defines something"); + const transformer = nipReferences(); + transformer(tree); + + const nips = getNodesOfType(tree, "nip"); + expect(nips).toHaveLength(1); + expect(nips[0].number).toBe("C0"); + }); + + it("should parse NIP-A0", () => { + const tree = createTree("Check NIP-A0"); + const transformer = nipReferences(); + transformer(tree); + + const nips = getNodesOfType(tree, "nip"); + expect(nips).toHaveLength(1); + expect(nips[0].number).toBe("A0"); + }); + + it("should handle mixed decimal and hex NIPs", () => { + const tree = createTree("NIP-01, NIP-C7, and NIP-19 together"); + const transformer = nipReferences(); + transformer(tree); + + const nips = getNodesOfType(tree, "nip"); + expect(nips).toHaveLength(3); + expect(nips[0].number).toBe("01"); + expect(nips[1].number).toBe("C7"); + expect(nips[2].number).toBe("19"); + }); + + it("should normalize mixed case hex to uppercase", () => { + const tree = createTree("nip-c7 NIP-C7 nip-C7 NIP-c7"); + const transformer = nipReferences(); + transformer(tree); + + const nips = getNodesOfType(tree, "nip"); + expect(nips).toHaveLength(4); + expect(nips.every((n) => n.number === "C7")).toBe(true); + }); + }); }); diff --git a/src/lib/nip-transformer.ts b/src/lib/nip-transformer.ts index 7a4c40c..32cbe0c 100644 --- a/src/lib/nip-transformer.ts +++ b/src/lib/nip-transformer.ts @@ -6,15 +6,36 @@ import type { Root, Content } from "applesauce-content/nast"; */ export interface NipNode { type: "nip"; - /** The NIP number (e.g., "01", "19", "30") */ + /** The NIP number/identifier (e.g., "01", "19", "C7") */ number: string; - /** The raw matched text (e.g., "NIP-01", "nip-19") */ + /** The raw matched text (e.g., "NIP-01", "nip-19", "NIP-C7") */ raw: string; } -// Match NIP-xx patterns (case insensitive, 1-3 digits) -// Supports: NIP-01, NIP-1, nip-19, NIP-100, etc. -const NIP_PATTERN = /\bNIP-(\d{1,3})\b/gi; +// Match NIP-xx patterns (case insensitive) +// Supports both decimal (NIP-01, NIP-19, NIP-100) and hex (NIP-C7, NIP-C0, NIP-A0) +// Pattern: 1-3 hex characters (which includes pure decimal) +const NIP_PATTERN = /\bNIP-([0-9A-Fa-f]{1,3})\b/gi; + +/** + * Check if a NIP identifier is purely decimal + */ +function isDecimalNip(nip: string): boolean { + return /^\d+$/.test(nip); +} + +/** + * Normalize a NIP identifier: + * - Decimal NIPs: pad to 2 digits (1 -> 01, 19 -> 19) + * - Hex NIPs: uppercase (c7 -> C7) + */ +function normalizeNip(nip: string): string { + if (isDecimalNip(nip)) { + return nip.padStart(2, "0"); + } + // Hex NIP - uppercase it + return nip.toUpperCase(); +} /** * Transformer that finds NIP-xx references and converts them to nip nodes. @@ -26,8 +47,7 @@ export function nipReferences() { [ NIP_PATTERN, (full, number) => { - // Normalize to 2 digits with leading zero for consistency - const normalized = number.padStart(2, "0"); + const normalized = normalizeNip(number); // Cast to Content since we're extending with a custom node type return { type: "nip",