mirror of
https://github.com/voson-wang/toon.git
synced 2026-01-29 15:24:10 +08:00
feat!: standardized encoding for list-item objects (spec v3)
This commit is contained in:
@@ -38,6 +38,6 @@
|
||||
"test": "vitest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@toon-format/spec": "^2.1.0"
|
||||
"@toon-format/spec": "^3.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -473,10 +473,42 @@ function* decodeListItemSync(
|
||||
}
|
||||
}
|
||||
|
||||
// Check for tabular-first list-item object: `- key[N]{fields}:`
|
||||
const headerInfo = parseArrayHeaderLine(afterHyphen, DEFAULT_DELIMITER)
|
||||
if (headerInfo && headerInfo.header.key && headerInfo.header.fields) {
|
||||
// Object with tabular array as first field
|
||||
const header = headerInfo.header
|
||||
yield { type: 'startObject' }
|
||||
yield { type: 'key', key: header.key! }
|
||||
|
||||
// Use baseDepth + 1 for the array so rows are at baseDepth + 2
|
||||
yield* decodeArrayFromHeaderSync(header, headerInfo.inlineValues, cursor, baseDepth + 1, options)
|
||||
|
||||
// Read sibling fields at depth = baseDepth + 1
|
||||
const followDepth = baseDepth + 1
|
||||
while (!cursor.atEndSync()) {
|
||||
const nextLine = cursor.peekSync()
|
||||
if (!nextLine || nextLine.depth < followDepth) {
|
||||
break
|
||||
}
|
||||
|
||||
if (nextLine.depth === followDepth && !nextLine.content.startsWith(LIST_ITEM_PREFIX)) {
|
||||
cursor.advanceSync()
|
||||
yield* decodeKeyValueSync(nextLine.content, cursor, followDepth, options)
|
||||
}
|
||||
else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
yield { type: 'endObject' }
|
||||
return
|
||||
}
|
||||
|
||||
// Check for object first field after hyphen
|
||||
if (isKeyValueContent(afterHyphen)) {
|
||||
yield { type: 'startObject' }
|
||||
yield* decodeKeyValueSync(afterHyphen, cursor, baseDepth, options)
|
||||
yield* decodeKeyValueSync(afterHyphen, cursor, baseDepth + 1, options)
|
||||
|
||||
// Read subsequent fields
|
||||
const followDepth = baseDepth + 1
|
||||
@@ -868,10 +900,42 @@ async function* decodeListItemAsync(
|
||||
}
|
||||
}
|
||||
|
||||
// Check for tabular-first list-item object: `- key[N]{fields}:`
|
||||
const headerInfo = parseArrayHeaderLine(afterHyphen, DEFAULT_DELIMITER)
|
||||
if (headerInfo && headerInfo.header.key && headerInfo.header.fields) {
|
||||
// Object with tabular array as first field
|
||||
const header = headerInfo.header
|
||||
yield { type: 'startObject' }
|
||||
yield { type: 'key', key: header.key! }
|
||||
|
||||
// Use baseDepth + 1 for the array so rows are at baseDepth + 2
|
||||
yield* decodeArrayFromHeaderAsync(header, headerInfo.inlineValues, cursor, baseDepth + 1, options)
|
||||
|
||||
// Read sibling fields at depth = baseDepth + 1
|
||||
const followDepth = baseDepth + 1
|
||||
while (!(await cursor.atEnd())) {
|
||||
const nextLine = await cursor.peek()
|
||||
if (!nextLine || nextLine.depth < followDepth) {
|
||||
break
|
||||
}
|
||||
|
||||
if (nextLine.depth === followDepth && !nextLine.content.startsWith(LIST_ITEM_PREFIX)) {
|
||||
await cursor.advance()
|
||||
yield* decodeKeyValueAsync(nextLine.content, cursor, followDepth, options)
|
||||
}
|
||||
else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
yield { type: 'endObject' }
|
||||
return
|
||||
}
|
||||
|
||||
// Check for object first field after hyphen
|
||||
if (isKeyValueContent(afterHyphen)) {
|
||||
yield { type: 'startObject' }
|
||||
yield* decodeKeyValueAsync(afterHyphen, cursor, baseDepth, options)
|
||||
yield* decodeKeyValueAsync(afterHyphen, cursor, baseDepth + 1, options)
|
||||
|
||||
// Read subsequent fields
|
||||
const followDepth = baseDepth + 1
|
||||
|
||||
@@ -295,25 +295,66 @@ export function* encodeObjectAsListItemLines(
|
||||
}
|
||||
|
||||
const entries = Object.entries(obj)
|
||||
const [firstKey, firstValue] = entries[0]!
|
||||
const restEntries = entries.slice(1)
|
||||
|
||||
// Compact form only when the list-item object has a single tabular array field
|
||||
if (entries.length === 1) {
|
||||
const [key, value] = entries[0]!
|
||||
// Check if first field is a tabular array
|
||||
if (isJsonArray(firstValue) && isArrayOfObjects(firstValue)) {
|
||||
const header = extractTabularHeader(firstValue)
|
||||
if (header) {
|
||||
// Tabular array as first field
|
||||
const formattedHeader = formatHeader(firstValue.length, { key: firstKey, fields: header, delimiter: options.delimiter })
|
||||
yield indentedListItem(depth, formattedHeader, options.indent)
|
||||
yield* writeTabularRowsLines(firstValue, header, depth + 2, options)
|
||||
|
||||
if (isJsonArray(value) && isArrayOfObjects(value)) {
|
||||
const header = extractTabularHeader(value)
|
||||
if (header) {
|
||||
const formattedHeader = formatHeader(value.length, { key, fields: header, delimiter: options.delimiter })
|
||||
yield indentedListItem(depth, formattedHeader, options.indent)
|
||||
yield* writeTabularRowsLines(value, header, depth + 1, options)
|
||||
return
|
||||
if (restEntries.length > 0) {
|
||||
const restObj: JsonObject = Object.fromEntries(restEntries)
|
||||
yield* encodeObjectLines(restObj, depth + 1, options)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// All other cases: emit a bare list item marker and all fields at depth + 1
|
||||
yield indentedLine(depth, LIST_ITEM_MARKER, options.indent)
|
||||
yield* encodeObjectLines(obj, depth + 1, options)
|
||||
const encodedKey = encodeKey(firstKey)
|
||||
|
||||
if (isJsonPrimitive(firstValue)) {
|
||||
// Primitive value: `- key: value`
|
||||
const encodedValue = encodePrimitive(firstValue, options.delimiter)
|
||||
yield indentedListItem(depth, `${encodedKey}: ${encodedValue}`, options.indent)
|
||||
}
|
||||
else if (isJsonArray(firstValue)) {
|
||||
if (firstValue.length === 0) {
|
||||
// Empty array: `- key[0]:`
|
||||
const header = formatHeader(0, { delimiter: options.delimiter })
|
||||
yield indentedListItem(depth, `${encodedKey}${header}`, options.indent)
|
||||
}
|
||||
else if (isArrayOfPrimitives(firstValue)) {
|
||||
// Inline primitive array: `- key[N]: values`
|
||||
const arrayLine = encodeInlineArrayLine(firstValue, options.delimiter)
|
||||
yield indentedListItem(depth, `${encodedKey}${arrayLine}`, options.indent)
|
||||
}
|
||||
else {
|
||||
// Non-inline array: `- key[N]:` with items at depth + 2
|
||||
const header = formatHeader(firstValue.length, { delimiter: options.delimiter })
|
||||
yield indentedListItem(depth, `${encodedKey}${header}`, options.indent)
|
||||
|
||||
for (const item of firstValue) {
|
||||
yield* encodeListItemValueLines(item, depth + 2, options)
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (isJsonObject(firstValue)) {
|
||||
// Object value: `- key:` with fields at depth + 2
|
||||
yield indentedListItem(depth, `${encodedKey}:`, options.indent)
|
||||
if (!isEmptyObject(firstValue)) {
|
||||
yield* encodeObjectLines(firstValue, depth + 2, options)
|
||||
}
|
||||
}
|
||||
|
||||
if (restEntries.length > 0) {
|
||||
const restObj: JsonObject = Object.fromEntries(restEntries)
|
||||
yield* encodeObjectLines(restObj, depth + 1, options)
|
||||
}
|
||||
}
|
||||
|
||||
// #endregion
|
||||
|
||||
Reference in New Issue
Block a user