Skip to content

Instantly share code, notes, and snippets.

@gordonbrander
Created March 16, 2026 11:08
Show Gist options
  • Select an option

  • Save gordonbrander/0e683bbfda6a939ee298e9b653819d97 to your computer and use it in GitHub Desktop.

Select an option

Save gordonbrander/0e683bbfda6a939ee298e9b653819d97 to your computer and use it in GitHub Desktop.
Human-friendly attribute string parser
import { assertEquals } from "@std/assert";
import { array, record } from "./attribute-parser.ts";
// --- Record converter ---
Deno.test("record.fromAttribute parses spaced input", () => {
assertEquals(record.fromAttribute("sm: 1; md: 2; lg: 2;"), {
sm: "1",
md: "2",
lg: "2",
});
});
Deno.test("record.fromAttribute parses compact input", () => {
assertEquals(record.fromAttribute("sm:1;md:2;lg:2;"), {
sm: "1",
md: "2",
lg: "2",
});
});
Deno.test("record.fromAttribute returns null for null", () => {
assertEquals(record.fromAttribute(null), null);
});
Deno.test("record.fromAttribute returns empty object for empty string", () => {
assertEquals(record.fromAttribute(""), {});
});
Deno.test("record.fromAttribute handles value with colons", () => {
assertEquals(record.fromAttribute("url: http://example.com"), {
url: "http://example.com",
});
});
Deno.test("record.toAttribute serializes entries", () => {
assertEquals(record.toAttribute({ sm: "1", md: "2" }), "sm: 1; md: 2");
});
Deno.test("record.toAttribute returns null for null", () => {
assertEquals(record.toAttribute(null), null);
});
// --- Array converter ---
Deno.test("array.fromAttribute parses comma-separated values", () => {
assertEquals(array.fromAttribute("one, two, three"), [
"one",
"two",
"three",
]);
});
Deno.test("array.fromAttribute parses compact values", () => {
assertEquals(array.fromAttribute("a,b,c"), ["a", "b", "c"]);
});
Deno.test("array.fromAttribute preserves trailing empty string", () => {
assertEquals(array.fromAttribute("a,b,"), ["a", "b", ""]);
});
Deno.test("array.fromAttribute parses single value", () => {
assertEquals(array.fromAttribute("one"), ["one"]);
});
Deno.test("array.fromAttribute returns empty array for empty string", () => {
assertEquals(array.fromAttribute(""), []);
});
Deno.test("array.fromAttribute returns null for null", () => {
assertEquals(array.fromAttribute(null), null);
});
Deno.test("array.toAttribute joins with comma-space", () => {
assertEquals(array.toAttribute(["a", "b", "c"]), "a, b, c");
});
Deno.test("array.toAttribute returns null for null", () => {
assertEquals(array.toAttribute(null), null);
});
export type Converter<T> = {
fromAttribute: (value: string | null) => T | null;
toAttribute: (value: T | null) => string | null;
};
export const parseRecordString = (value: string): Record<string, string> => {
const result: Record<string, string> = {};
for (const segment of value.split(";")) {
const colonIndex = segment.indexOf(":");
if (colonIndex === -1) continue;
const key = segment.slice(0, colonIndex).trim();
const val = segment.slice(colonIndex + 1).trim();
if (key) result[key] = val;
}
return result;
};
export const serializeRecordString = (
value: Record<string, string>,
): string => {
return Object.entries(value)
.map(([k, v]) => `${k}: ${v}`)
.join("; ");
};
export const record: Converter<Record<string, string>> = {
fromAttribute: (value) => {
if (value === null) return null;
return parseRecordString(value);
},
toAttribute: (value) => {
if (value === null) return null;
return serializeRecordString(value);
},
};
export const parseArrayString = (value: string): string[] => {
if (value.trim() === "") return [];
return value.split(",").map((s) => s.trim());
};
export const serializeArrayString = (value: string[]): string => {
return value.join(", ");
};
export const array: Converter<string[]> = {
fromAttribute: (value) => {
if (value === null) return null;
return parseArrayString(value);
},
toAttribute: (value) => {
if (value === null) return null;
return serializeArrayString(value);
},
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment