Fin/writer.go
2025-04-19 12:21:52 -05:00

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
}