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
This commit is contained in:
Claude
2026-01-11 10:06:44 +00:00
parent 8789011e45
commit 533b93f17e
2 changed files with 93 additions and 7 deletions

View File

@@ -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<NipNode>(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<NipNode>(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<NipNode>(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<NipNode>(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<NipNode>(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<NipNode>(tree, "nip");
expect(nips).toHaveLength(4);
expect(nips.every((n) => n.number === "C7")).toBe(true);
});
});
});

View File

@@ -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",