305 lines
5.9 KiB
Go
305 lines
5.9 KiB
Go
package fin
|
|
|
|
/*
|
|
writer.go
|
|
Copyright 2025 Sharkk, sharkk.net
|
|
Authors: Sky Johnson
|
|
*/
|
|
|
|
import (
|
|
"io"
|
|
"strconv"
|
|
)
|
|
|
|
// Write serializes the data to the provided writer
|
|
func (d *Data) Write(w io.Writer) error {
|
|
return writeMap(w, d.data, 0)
|
|
}
|
|
|
|
// Save writes data to a writer (standalone function)
|
|
func Save(w io.Writer, d *Data) error {
|
|
return d.Write(w)
|
|
}
|
|
|
|
// writeMap writes a map at the given indent level
|
|
func writeMap(w io.Writer, data map[string]any, level int) error {
|
|
for key, value := range data {
|
|
// Write indentation
|
|
for i := 0; i < level; i++ {
|
|
if _, err := w.Write([]byte{'\t'}); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Write key
|
|
if _, err := io.WriteString(w, key); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Special case: combined value+object
|
|
if m, ok := value.(map[string]any); ok && len(m) > 1 {
|
|
if simpleValue, hasValue := m["value"]; hasValue {
|
|
// Write simple value first
|
|
if err := writeSimpleValue(w, simpleValue); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Write object portion
|
|
if _, err := io.WriteString(w, " {\n"); err != nil {
|
|
return err
|
|
}
|
|
|
|
for k, v := range m {
|
|
if k != "value" {
|
|
for i := 0; i < level+1; i++ {
|
|
if _, err := w.Write([]byte{'\t'}); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if _, err := io.WriteString(w, k); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := writeValueWithNewline(w, v, level+1); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
for i := 0; i < level; i++ {
|
|
if _, err := w.Write([]byte{'\t'}); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if _, err := io.WriteString(w, "}\n"); err != nil {
|
|
return err
|
|
}
|
|
|
|
continue
|
|
}
|
|
}
|
|
|
|
// Regular value handling
|
|
if err := writeValueWithNewline(w, value, level); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// writeValueWithNewline writes a value and adds a newline
|
|
func writeValueWithNewline(w io.Writer, value any, level int) error {
|
|
if err := writeValue(w, value, level); err != nil {
|
|
return err
|
|
}
|
|
|
|
if _, err := io.WriteString(w, "\n"); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// writeValue writes a value with appropriate formatting
|
|
func writeValue(w io.Writer, value any, level int) error {
|
|
// Space after key
|
|
if _, err := w.Write([]byte{' '}); err != nil {
|
|
return err
|
|
}
|
|
|
|
switch v := value.(type) {
|
|
case nil:
|
|
return nil
|
|
|
|
case string:
|
|
// Quote strings
|
|
if _, err := io.WriteString(w, "\""); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := writeEscapedString(w, v); err != nil {
|
|
return err
|
|
}
|
|
|
|
if _, err := io.WriteString(w, "\""); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
|
|
case int:
|
|
// Use byte slice pool for better performance
|
|
buffer := GetByteSlice()
|
|
*buffer = strconv.AppendInt((*buffer)[:0], int64(v), 10)
|
|
_, err := w.Write(*buffer)
|
|
PutByteSlice(buffer)
|
|
return err
|
|
|
|
case float64:
|
|
buffer := GetByteSlice()
|
|
*buffer = strconv.AppendFloat((*buffer)[:0], v, 'g', -1, 64)
|
|
_, err := w.Write(*buffer)
|
|
PutByteSlice(buffer)
|
|
return err
|
|
|
|
case bool:
|
|
if v {
|
|
_, err := io.WriteString(w, "true")
|
|
return err
|
|
}
|
|
_, err := io.WriteString(w, "false")
|
|
return err
|
|
|
|
case map[string]any:
|
|
if _, err := io.WriteString(w, "{\n"); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := writeMap(w, v, level+1); err != nil {
|
|
return err
|
|
}
|
|
|
|
for i := 0; i < level; i++ {
|
|
if _, err := w.Write([]byte{'\t'}); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if _, err := io.WriteString(w, "}"); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
|
|
case []any:
|
|
if _, err := io.WriteString(w, "{\n"); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := writeArray(w, v, level+1); err != nil {
|
|
return err
|
|
}
|
|
|
|
for i := 0; i < level; i++ {
|
|
if _, err := w.Write([]byte{'\t'}); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if _, err := io.WriteString(w, "}"); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
|
|
default:
|
|
// Fall back for any other types
|
|
buffer := GetByteSlice()
|
|
*buffer = append((*buffer)[:0], []byte(strconv.FormatInt(int64(v.(int)), 10))...)
|
|
_, err := w.Write(*buffer)
|
|
PutByteSlice(buffer)
|
|
return err
|
|
}
|
|
}
|
|
|
|
// writeArray writes array elements
|
|
func writeArray(w io.Writer, array []any, level int) error {
|
|
for _, item := range array {
|
|
for i := 0; i < level; i++ {
|
|
if _, err := w.Write([]byte{'\t'}); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if err := writeValue(w, item, level); err != nil {
|
|
return err
|
|
}
|
|
|
|
if _, err := io.WriteString(w, "\n"); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// writeEscapedString writes a string with escape sequences
|
|
func writeEscapedString(w io.Writer, s string) error {
|
|
for i := 0; i < len(s); i++ {
|
|
c := s[i]
|
|
switch c {
|
|
case '"':
|
|
if _, err := io.WriteString(w, "\\\""); err != nil {
|
|
return err
|
|
}
|
|
case '\\':
|
|
if _, err := io.WriteString(w, "\\\\"); err != nil {
|
|
return err
|
|
}
|
|
case '\n':
|
|
if _, err := io.WriteString(w, "\\n"); err != nil {
|
|
return err
|
|
}
|
|
case '\t':
|
|
if _, err := io.WriteString(w, "\\t"); err != nil {
|
|
return err
|
|
}
|
|
default:
|
|
if _, err := w.Write([]byte{c}); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// writeSimpleValue writes a simple value without newline
|
|
func writeSimpleValue(w io.Writer, value any) error {
|
|
if _, err := w.Write([]byte{' '}); err != nil {
|
|
return err
|
|
}
|
|
|
|
switch v := value.(type) {
|
|
case string:
|
|
if _, err := io.WriteString(w, "\""); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := writeEscapedString(w, v); err != nil {
|
|
return err
|
|
}
|
|
|
|
if _, err := io.WriteString(w, "\""); err != nil {
|
|
return err
|
|
}
|
|
|
|
case int:
|
|
buffer := GetByteSlice()
|
|
*buffer = strconv.AppendInt((*buffer)[:0], int64(v), 10)
|
|
_, err := w.Write(*buffer)
|
|
PutByteSlice(buffer)
|
|
return err
|
|
|
|
case float64:
|
|
buffer := GetByteSlice()
|
|
*buffer = strconv.AppendFloat((*buffer)[:0], v, 'g', -1, 64)
|
|
_, err := w.Write(*buffer)
|
|
PutByteSlice(buffer)
|
|
return err
|
|
|
|
case bool:
|
|
if v {
|
|
_, err := io.WriteString(w, "true")
|
|
return err
|
|
}
|
|
_, err := io.WriteString(w, "false")
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|