UNPKG

@zenbase/llml

Version:

Lightweight Language Markup Language - Convert nested data structures to human-readable, XML-like markup

123 lines (105 loc) 3.73 kB
import dedent from "dedent" import type { Formatters } from "../types" export const isObject = (v: unknown): boolean => typeof v === "object" && v !== null && !Array.isArray(v) && !(v instanceof Date) export const formatObject = ( value: unknown, llml: (data: unknown, formatters: Formatters) => string, formatters: Formatters, ): string => { // Check if object has a custom toString method (not the default Object.prototype.toString) if (value && typeof value.toString === "function" && value.toString !== Object.prototype.toString) { return value.toString() } const entries = Object.entries(value as Record<string, unknown>) if (entries.length === 0) { // Plain empty objects should return empty string // Other objects with no enumerable properties should use String(value) if (Object.getPrototypeOf(value) === Object.prototype) { return "" } return String(value) } const results: string[] = [] for (let i = 0; i < entries.length; i++) { const [key, val] = entries[i] if (i > 0) results.push("\n") // Format key-value pair with proper XML structure const formattedContent = formatKeyValue(key, val, llml, formatters) results.push(formattedContent) } return results.join("") } /** * Helper function to format key-value pairs in VibeXML style */ function formatKeyValue( key: string, value: unknown, llml: (data: unknown, formatters: Formatters) => string, formatters: Formatters, ): string { if (Array.isArray(value)) { if (value.length === 0) return "" const wrapperTag = key let result = `<${wrapperTag}>\n` for (let i = 0; i < value.length; i++) { const item = value[i] const itemTag = `${key}-${i + 1}` if (typeof item === "object" && item !== null && !Array.isArray(item)) { const itemFormatted = llml(item, formatters) if (itemFormatted.includes("\n")) { result += ` <${itemTag}>\n` const indentedContent = itemFormatted .split("\n") .map(line => (line ? ` ${line}` : "")) .join("\n") result += indentedContent result += `\n </${itemTag}>\n` } else { result += ` <${itemTag}>${itemFormatted}</${itemTag}>\n` } } else { if (typeof item === "string" && item.includes("\n")) { const cleanText = dedent(item).trim() const lines = cleanText.split("\n") result += ` <${itemTag}>` for (let j = 0; j < lines.length; j++) { if (j > 0) { result += "\n " } else { result += " " } result += lines[j] } result += `</${itemTag}>\n` } else { const formatted = llml(item, formatters) result += ` <${itemTag}>${formatted}</${itemTag}>\n` } } } result += `</${wrapperTag}>` return result } if (typeof value === "object" && value !== null) { const formatted = llml(value, formatters) if (formatted.includes("\n")) { const indentedContent = formatted .split("\n") .map(line => (line ? ` ${line}` : "")) .join("\n") return `<${key}>\n${indentedContent}\n</${key}>` } return `<${key}>${formatted}</${key}>` } if (typeof value === "string" && value.includes("\n")) { const cleanText = dedent(value).trim() const lines = cleanText.split("\n") const formattedLines = lines.map(line => ` ${line}`) const formattedContent = formattedLines.join("\n") return `<${key}>\n${formattedContent}\n</${key}>` } const formatted = llml(value, formatters) return `<${key}>${formatted}</${key}>` }