mirror of
https://github.com/voson-wang/toon.git
synced 2026-01-29 23:34:10 +08:00
refactor: move encode functions
This commit is contained in:
339
src/encode/encoders.ts
Normal file
339
src/encode/encoders.ts
Normal file
@@ -0,0 +1,339 @@
|
||||
import type {
|
||||
Depth,
|
||||
JsonArray,
|
||||
JsonObject,
|
||||
JsonPrimitive,
|
||||
JsonValue,
|
||||
ResolvedEncodeOptions,
|
||||
} from '../types'
|
||||
import { LIST_ITEM_MARKER } from '../constants'
|
||||
import {
|
||||
isArrayOfArrays,
|
||||
isArrayOfObjects,
|
||||
isArrayOfPrimitives,
|
||||
isJsonArray,
|
||||
isJsonObject,
|
||||
isJsonPrimitive,
|
||||
} from './normalize'
|
||||
import {
|
||||
encodeAndJoinPrimitives,
|
||||
encodeKey,
|
||||
encodePrimitive,
|
||||
formatHeader,
|
||||
} from './primitives'
|
||||
import { LineWriter } from './writer'
|
||||
|
||||
// #region Encode normalized JsonValue
|
||||
|
||||
export function encodeValue(value: JsonValue, options: ResolvedEncodeOptions): string {
|
||||
if (isJsonPrimitive(value)) {
|
||||
return encodePrimitive(value, options.delimiter)
|
||||
}
|
||||
|
||||
const writer = new LineWriter(options.indent)
|
||||
|
||||
if (isJsonArray(value)) {
|
||||
encodeArray(undefined, value, writer, 0, options)
|
||||
}
|
||||
else if (isJsonObject(value)) {
|
||||
encodeObject(value, writer, 0, options)
|
||||
}
|
||||
|
||||
return writer.toString()
|
||||
}
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region Object encoding
|
||||
|
||||
export function encodeObject(value: JsonObject, writer: LineWriter, depth: Depth, options: ResolvedEncodeOptions): void {
|
||||
const keys = Object.keys(value)
|
||||
|
||||
for (const key of keys) {
|
||||
encodeKeyValuePair(key, value[key]!, writer, depth, options)
|
||||
}
|
||||
}
|
||||
|
||||
export function encodeKeyValuePair(key: string, value: JsonValue, writer: LineWriter, depth: Depth, options: ResolvedEncodeOptions): void {
|
||||
const encodedKey = encodeKey(key)
|
||||
|
||||
if (isJsonPrimitive(value)) {
|
||||
writer.push(depth, `${encodedKey}: ${encodePrimitive(value, options.delimiter)}`)
|
||||
}
|
||||
else if (isJsonArray(value)) {
|
||||
encodeArray(key, value, writer, depth, options)
|
||||
}
|
||||
else if (isJsonObject(value)) {
|
||||
const nestedKeys = Object.keys(value)
|
||||
if (nestedKeys.length === 0) {
|
||||
// Empty object
|
||||
writer.push(depth, `${encodedKey}:`)
|
||||
}
|
||||
else {
|
||||
writer.push(depth, `${encodedKey}:`)
|
||||
encodeObject(value, writer, depth + 1, options)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region Array encoding
|
||||
|
||||
export function encodeArray(
|
||||
key: string | undefined,
|
||||
value: JsonArray,
|
||||
writer: LineWriter,
|
||||
depth: Depth,
|
||||
options: ResolvedEncodeOptions,
|
||||
): void {
|
||||
if (value.length === 0) {
|
||||
const header = formatHeader(0, { key, delimiter: options.delimiter, lengthMarker: options.lengthMarker })
|
||||
writer.push(depth, header)
|
||||
return
|
||||
}
|
||||
|
||||
// Primitive array
|
||||
if (isArrayOfPrimitives(value)) {
|
||||
const formatted = encodeInlineArrayLine(value, options.delimiter, key, options.lengthMarker)
|
||||
writer.push(depth, formatted)
|
||||
return
|
||||
}
|
||||
|
||||
// Array of arrays (all primitives)
|
||||
if (isArrayOfArrays(value)) {
|
||||
const allPrimitiveArrays = value.every(arr => isArrayOfPrimitives(arr))
|
||||
if (allPrimitiveArrays) {
|
||||
encodeArrayOfArraysAsListItems(key, value, writer, depth, options)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Array of objects
|
||||
if (isArrayOfObjects(value)) {
|
||||
const header = extractTabularHeader(value)
|
||||
if (header) {
|
||||
encodeArrayOfObjectsAsTabular(key, value, header, writer, depth, options)
|
||||
}
|
||||
else {
|
||||
encodeMixedArrayAsListItems(key, value, writer, depth, options)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Mixed array: fallback to expanded format
|
||||
encodeMixedArrayAsListItems(key, value, writer, depth, options)
|
||||
}
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region Array of arrays (expanded format)
|
||||
|
||||
export function encodeArrayOfArraysAsListItems(
|
||||
prefix: string | undefined,
|
||||
values: readonly JsonArray[],
|
||||
writer: LineWriter,
|
||||
depth: Depth,
|
||||
options: ResolvedEncodeOptions,
|
||||
): void {
|
||||
const header = formatHeader(values.length, { key: prefix, delimiter: options.delimiter, lengthMarker: options.lengthMarker })
|
||||
writer.push(depth, header)
|
||||
|
||||
for (const arr of values) {
|
||||
if (isArrayOfPrimitives(arr)) {
|
||||
const inline = encodeInlineArrayLine(arr, options.delimiter, undefined, options.lengthMarker)
|
||||
writer.pushListItem(depth + 1, inline)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function encodeInlineArrayLine(values: readonly JsonPrimitive[], delimiter: string, prefix?: string, lengthMarker?: '#' | false): string {
|
||||
const header = formatHeader(values.length, { key: prefix, delimiter, lengthMarker })
|
||||
const joinedValue = encodeAndJoinPrimitives(values, delimiter)
|
||||
// Only add space if there are values
|
||||
if (values.length === 0) {
|
||||
return header
|
||||
}
|
||||
return `${header} ${joinedValue}`
|
||||
}
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region Array of objects (tabular format)
|
||||
|
||||
export function encodeArrayOfObjectsAsTabular(
|
||||
prefix: string | undefined,
|
||||
rows: readonly JsonObject[],
|
||||
header: readonly string[],
|
||||
writer: LineWriter,
|
||||
depth: Depth,
|
||||
options: ResolvedEncodeOptions,
|
||||
): void {
|
||||
const headerStr = formatHeader(rows.length, { key: prefix, fields: header, delimiter: options.delimiter, lengthMarker: options.lengthMarker })
|
||||
writer.push(depth, `${headerStr}`)
|
||||
|
||||
writeTabularRows(rows, header, writer, depth + 1, options)
|
||||
}
|
||||
|
||||
export function extractTabularHeader(rows: readonly JsonObject[]): string[] | undefined {
|
||||
if (rows.length === 0)
|
||||
return
|
||||
|
||||
const firstRow = rows[0]!
|
||||
const firstKeys = Object.keys(firstRow)
|
||||
if (firstKeys.length === 0)
|
||||
return
|
||||
|
||||
if (isTabularArray(rows, firstKeys)) {
|
||||
return firstKeys
|
||||
}
|
||||
}
|
||||
|
||||
export function isTabularArray(
|
||||
rows: readonly JsonObject[],
|
||||
header: readonly string[],
|
||||
): boolean {
|
||||
for (const row of rows) {
|
||||
const keys = Object.keys(row)
|
||||
|
||||
// All objects must have the same keys (but order can differ)
|
||||
if (keys.length !== header.length) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check that all header keys exist in the row and all values are primitives
|
||||
for (const key of header) {
|
||||
if (!(key in row)) {
|
||||
return false
|
||||
}
|
||||
if (!isJsonPrimitive(row[key])) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
function writeTabularRows(
|
||||
rows: readonly JsonObject[],
|
||||
header: readonly string[],
|
||||
writer: LineWriter,
|
||||
depth: Depth,
|
||||
options: ResolvedEncodeOptions,
|
||||
): void {
|
||||
for (const row of rows) {
|
||||
const values = header.map(key => row[key])
|
||||
const joinedValue = encodeAndJoinPrimitives(values as JsonPrimitive[], options.delimiter)
|
||||
writer.push(depth, joinedValue)
|
||||
}
|
||||
}
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region Array of objects (expanded format)
|
||||
|
||||
export function encodeMixedArrayAsListItems(
|
||||
prefix: string | undefined,
|
||||
items: readonly JsonValue[],
|
||||
writer: LineWriter,
|
||||
depth: Depth,
|
||||
options: ResolvedEncodeOptions,
|
||||
): void {
|
||||
const header = formatHeader(items.length, { key: prefix, delimiter: options.delimiter, lengthMarker: options.lengthMarker })
|
||||
writer.push(depth, header)
|
||||
|
||||
for (const item of items) {
|
||||
encodeListItemValue(item, writer, depth + 1, options)
|
||||
}
|
||||
}
|
||||
|
||||
export function encodeObjectAsListItem(obj: JsonObject, writer: LineWriter, depth: Depth, options: ResolvedEncodeOptions): void {
|
||||
const keys = Object.keys(obj)
|
||||
if (keys.length === 0) {
|
||||
writer.push(depth, LIST_ITEM_MARKER)
|
||||
return
|
||||
}
|
||||
|
||||
// First key-value on the same line as "- "
|
||||
const firstKey = keys[0]!
|
||||
const encodedKey = encodeKey(firstKey)
|
||||
const firstValue = obj[firstKey]!
|
||||
|
||||
if (isJsonPrimitive(firstValue)) {
|
||||
writer.pushListItem(depth, `${encodedKey}: ${encodePrimitive(firstValue, options.delimiter)}`)
|
||||
}
|
||||
else if (isJsonArray(firstValue)) {
|
||||
if (isArrayOfPrimitives(firstValue)) {
|
||||
// Inline format for primitive arrays
|
||||
const formatted = encodeInlineArrayLine(firstValue, options.delimiter, firstKey, options.lengthMarker)
|
||||
writer.pushListItem(depth, formatted)
|
||||
}
|
||||
else if (isArrayOfObjects(firstValue)) {
|
||||
// Check if array of objects can use tabular format
|
||||
const header = extractTabularHeader(firstValue)
|
||||
if (header) {
|
||||
// Tabular format for uniform arrays of objects
|
||||
const headerStr = formatHeader(firstValue.length, { key: firstKey, fields: header, delimiter: options.delimiter, lengthMarker: options.lengthMarker })
|
||||
writer.pushListItem(depth, headerStr)
|
||||
writeTabularRows(firstValue, header, writer, depth + 1, options)
|
||||
}
|
||||
else {
|
||||
// Fall back to list format for non-uniform arrays of objects
|
||||
writer.pushListItem(depth, `${encodedKey}[${firstValue.length}]:`)
|
||||
for (const item of firstValue) {
|
||||
encodeObjectAsListItem(item, writer, depth + 1, options)
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Complex arrays on separate lines (array of arrays, etc.)
|
||||
writer.pushListItem(depth, `${encodedKey}[${firstValue.length}]:`)
|
||||
|
||||
// Encode array contents at depth + 1
|
||||
for (const item of firstValue) {
|
||||
encodeListItemValue(item, writer, depth + 1, options)
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (isJsonObject(firstValue)) {
|
||||
const nestedKeys = Object.keys(firstValue)
|
||||
if (nestedKeys.length === 0) {
|
||||
writer.pushListItem(depth, `${encodedKey}:`)
|
||||
}
|
||||
else {
|
||||
writer.pushListItem(depth, `${encodedKey}:`)
|
||||
encodeObject(firstValue, writer, depth + 2, options)
|
||||
}
|
||||
}
|
||||
|
||||
// Remaining keys on indented lines
|
||||
for (let i = 1; i < keys.length; i++) {
|
||||
const key = keys[i]!
|
||||
encodeKeyValuePair(key, obj[key]!, writer, depth + 1, options)
|
||||
}
|
||||
}
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region List item encoding helpers
|
||||
|
||||
function encodeListItemValue(
|
||||
value: JsonValue,
|
||||
writer: LineWriter,
|
||||
depth: Depth,
|
||||
options: ResolvedEncodeOptions,
|
||||
): void {
|
||||
if (isJsonPrimitive(value)) {
|
||||
writer.pushListItem(depth, encodePrimitive(value, options.delimiter))
|
||||
}
|
||||
else if (isJsonArray(value) && isArrayOfPrimitives(value)) {
|
||||
const inline = encodeInlineArrayLine(value, options.delimiter, undefined, options.lengthMarker)
|
||||
writer.pushListItem(depth, inline)
|
||||
}
|
||||
else if (isJsonObject(value)) {
|
||||
encodeObjectAsListItem(value, writer, depth, options)
|
||||
}
|
||||
}
|
||||
|
||||
// #endregion
|
||||
127
src/encode/normalize.ts
Normal file
127
src/encode/normalize.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
import type {
|
||||
JsonArray,
|
||||
JsonObject,
|
||||
JsonPrimitive,
|
||||
JsonValue,
|
||||
} from '../types'
|
||||
|
||||
// #region Normalization (unknown → JsonValue)
|
||||
|
||||
export function normalizeValue(value: unknown): JsonValue {
|
||||
// null
|
||||
if (value === null) {
|
||||
return null
|
||||
}
|
||||
|
||||
// Primitives
|
||||
if (typeof value === 'string' || typeof value === 'boolean') {
|
||||
return value
|
||||
}
|
||||
|
||||
// Numbers: canonicalize -0 to 0, handle NaN and Infinity
|
||||
if (typeof value === 'number') {
|
||||
if (Object.is(value, -0)) {
|
||||
return 0
|
||||
}
|
||||
if (!Number.isFinite(value)) {
|
||||
return null
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// BigInt → number (if safe) or string
|
||||
if (typeof value === 'bigint') {
|
||||
// Try to convert to number if within safe integer range
|
||||
if (value >= Number.MIN_SAFE_INTEGER && value <= Number.MAX_SAFE_INTEGER) {
|
||||
return Number(value)
|
||||
}
|
||||
// Otherwise convert to string (will be unquoted as it looks numeric)
|
||||
return value.toString()
|
||||
}
|
||||
|
||||
// Date → ISO string
|
||||
if (value instanceof Date) {
|
||||
return value.toISOString()
|
||||
}
|
||||
|
||||
// Array
|
||||
if (Array.isArray(value)) {
|
||||
return value.map(normalizeValue)
|
||||
}
|
||||
|
||||
// Set → array
|
||||
if (value instanceof Set) {
|
||||
return Array.from(value).map(normalizeValue)
|
||||
}
|
||||
|
||||
// Map → object
|
||||
if (value instanceof Map) {
|
||||
return Object.fromEntries(
|
||||
Array.from(value, ([k, v]) => [String(k), normalizeValue(v)]),
|
||||
)
|
||||
}
|
||||
|
||||
// Plain object
|
||||
if (isPlainObject(value)) {
|
||||
const result: Record<string, JsonValue> = {}
|
||||
|
||||
for (const key in value) {
|
||||
if (Object.prototype.hasOwnProperty.call(value, key)) {
|
||||
result[key] = normalizeValue(value[key])
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Fallback: function, symbol, undefined, or other → null
|
||||
return null
|
||||
}
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region Type guards
|
||||
|
||||
export function isJsonPrimitive(value: unknown): value is JsonPrimitive {
|
||||
return (
|
||||
value === null
|
||||
|| typeof value === 'string'
|
||||
|| typeof value === 'number'
|
||||
|| typeof value === 'boolean'
|
||||
)
|
||||
}
|
||||
|
||||
export function isJsonArray(value: unknown): value is JsonArray {
|
||||
return Array.isArray(value)
|
||||
}
|
||||
|
||||
export function isJsonObject(value: unknown): value is JsonObject {
|
||||
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
||||
}
|
||||
|
||||
export function isPlainObject(value: unknown): value is Record<string, unknown> {
|
||||
if (value === null || typeof value !== 'object') {
|
||||
return false
|
||||
}
|
||||
|
||||
const prototype = Object.getPrototypeOf(value)
|
||||
return prototype === null || prototype === Object.prototype
|
||||
}
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region Array type detection
|
||||
|
||||
export function isArrayOfPrimitives(value: JsonArray): value is readonly JsonPrimitive[] {
|
||||
return value.every(item => isJsonPrimitive(item))
|
||||
}
|
||||
|
||||
export function isArrayOfArrays(value: JsonArray): value is readonly JsonArray[] {
|
||||
return value.every(item => isJsonArray(item))
|
||||
}
|
||||
|
||||
export function isArrayOfObjects(value: JsonArray): value is readonly JsonObject[] {
|
||||
return value.every(item => isJsonObject(item))
|
||||
}
|
||||
|
||||
// #endregion
|
||||
171
src/encode/primitives.ts
Normal file
171
src/encode/primitives.ts
Normal file
@@ -0,0 +1,171 @@
|
||||
import type { JsonPrimitive } from '../types'
|
||||
import {
|
||||
BACKSLASH,
|
||||
COMMA,
|
||||
DEFAULT_DELIMITER,
|
||||
DOUBLE_QUOTE,
|
||||
FALSE_LITERAL,
|
||||
LIST_ITEM_MARKER,
|
||||
NULL_LITERAL,
|
||||
TRUE_LITERAL,
|
||||
} from '../constants'
|
||||
|
||||
// #region Primitive encoding
|
||||
|
||||
export function encodePrimitive(value: JsonPrimitive, delimiter?: string): string {
|
||||
if (value === null) {
|
||||
return NULL_LITERAL
|
||||
}
|
||||
|
||||
if (typeof value === 'boolean') {
|
||||
return String(value)
|
||||
}
|
||||
|
||||
if (typeof value === 'number') {
|
||||
return String(value)
|
||||
}
|
||||
|
||||
return encodeStringLiteral(value, delimiter)
|
||||
}
|
||||
|
||||
export function encodeStringLiteral(value: string, delimiter: string = COMMA): string {
|
||||
if (isSafeUnquoted(value, delimiter)) {
|
||||
return value
|
||||
}
|
||||
|
||||
return `${DOUBLE_QUOTE}${escapeString(value)}${DOUBLE_QUOTE}`
|
||||
}
|
||||
|
||||
export function escapeString(value: string): string {
|
||||
return value
|
||||
.replace(/\\/g, `${BACKSLASH}${BACKSLASH}`)
|
||||
.replace(/"/g, `${BACKSLASH}${DOUBLE_QUOTE}`)
|
||||
.replace(/\n/g, `${BACKSLASH}n`)
|
||||
.replace(/\r/g, `${BACKSLASH}r`)
|
||||
.replace(/\t/g, `${BACKSLASH}t`)
|
||||
}
|
||||
|
||||
export function isSafeUnquoted(value: string, delimiter: string = COMMA): boolean {
|
||||
if (!value) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (isPaddedWithWhitespace(value)) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (value === TRUE_LITERAL || value === FALSE_LITERAL || value === NULL_LITERAL) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (isNumericLike(value)) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check for colon (always structural)
|
||||
if (value.includes(':')) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check for quotes and backslash (always need escaping)
|
||||
if (value.includes('"') || value.includes('\\')) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check for brackets and braces (always structural)
|
||||
if (/[[\]{}]/.test(value)) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check for control characters (newline, carriage return, tab - always need quoting/escaping)
|
||||
if (/[\n\r\t]/.test(value)) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check for the active delimiter
|
||||
if (value.includes(delimiter)) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check for hyphen at start (list marker)
|
||||
if (value.startsWith(LIST_ITEM_MARKER)) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
function isNumericLike(value: string): boolean {
|
||||
// Match numbers like: 42, -3.14, 1e-6, 05, etc.
|
||||
return /^-?\d+(?:\.\d+)?(?:e[+-]?\d+)?$/i.test(value) || /^0\d+$/.test(value)
|
||||
}
|
||||
|
||||
function isPaddedWithWhitespace(value: string): boolean {
|
||||
return value !== value.trim()
|
||||
}
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region Key encoding
|
||||
|
||||
export function encodeKey(key: string): string {
|
||||
if (isValidUnquotedKey(key)) {
|
||||
return key
|
||||
}
|
||||
|
||||
return `${DOUBLE_QUOTE}${escapeString(key)}${DOUBLE_QUOTE}`
|
||||
}
|
||||
|
||||
function isValidUnquotedKey(key: string): boolean {
|
||||
return /^[A-Z_][\w.]*$/i.test(key)
|
||||
}
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region Value joining
|
||||
|
||||
export function encodeAndJoinPrimitives(values: readonly JsonPrimitive[], delimiter: string = COMMA): string {
|
||||
return values.map(v => encodePrimitive(v, delimiter)).join(delimiter)
|
||||
}
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region Header formatters
|
||||
|
||||
/**
|
||||
* Header formatter for arrays and tables with optional key prefix and field names
|
||||
*/
|
||||
export function formatHeader(
|
||||
length: number,
|
||||
options?: {
|
||||
key?: string
|
||||
fields?: readonly string[]
|
||||
delimiter?: string
|
||||
lengthMarker?: '#' | false
|
||||
},
|
||||
): string {
|
||||
const key = options?.key
|
||||
const fields = options?.fields
|
||||
const delimiter = options?.delimiter ?? COMMA
|
||||
const lengthMarker = options?.lengthMarker ?? false
|
||||
|
||||
let header = ''
|
||||
|
||||
if (key) {
|
||||
header += encodeKey(key)
|
||||
}
|
||||
|
||||
// Only include delimiter if it's not the default (comma)
|
||||
header += `[${lengthMarker || ''}${length}${delimiter !== DEFAULT_DELIMITER ? delimiter : ''}]`
|
||||
|
||||
if (fields) {
|
||||
const quotedFields = fields.map(f => encodeKey(f))
|
||||
header += `{${quotedFields.join(delimiter)}}`
|
||||
}
|
||||
|
||||
header += ':'
|
||||
|
||||
return header
|
||||
}
|
||||
|
||||
// #endregion
|
||||
24
src/encode/writer.ts
Normal file
24
src/encode/writer.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import type { Depth } from '../types'
|
||||
import { LIST_ITEM_PREFIX } from '../constants'
|
||||
|
||||
export class LineWriter {
|
||||
private readonly lines: string[] = []
|
||||
private readonly indentationString: string
|
||||
|
||||
constructor(indentSize: number) {
|
||||
this.indentationString = ' '.repeat(indentSize)
|
||||
}
|
||||
|
||||
push(depth: Depth, content: string): void {
|
||||
const indent = this.indentationString.repeat(depth)
|
||||
this.lines.push(indent + content)
|
||||
}
|
||||
|
||||
pushListItem(depth: Depth, content: string): void {
|
||||
this.push(depth, `${LIST_ITEM_PREFIX}${content}`)
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return this.lines.join('\n')
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user