serde_nt 0.1.12

NestedText (de)serialization for serde, powered by nt_syntax
Documentation
//! 值树写回 NestedText 文本及底层块排版原语。

use std::borrow::Cow;

use memchr::{memchr2, memchr3};
use nt_syntax::{STEP, Value, push_indent, split_nt_line_li};

/// 把值树写成 NestedText 文本(末尾换行)
pub fn dumps(value: &Value<'_>) -> String {
  let mut out = String::with_capacity(256);
  write(value, 0, &mut out);
  out
}

fn write(value: &Value<'_>, depth: usize, out: &mut String) {
  match value {
    Value::Str(s) => write_str_block(out, depth, s),
    Value::List(item_li) => write_list(item_li, depth, out),
    Value::Dict(pair_li) => write_dict(pair_li, depth, out),
  }
}

/// 写出 `>` 多行块(末尾含换行,流式归一 `\r\n` / `\r` 零中间分配)
pub(crate) fn write_str_block(out: &mut String, depth: usize, s: &str) {
  for (i, line) in split_nt_line_li(s).enumerate() {
    if i > 0 {
      out.push('\n');
    }
    push_indent(out, depth);
    out.push('>');
    if !line.is_empty() {
      out.push(' ');
      out.push_str(line);
    }
  }
  out.push('\n');
}

/// 写出 `: ...` 多行字典键(末尾含换行,流式归一 `\r\n` / `\r` 零中间分配)
pub(crate) fn write_multiline_key(out: &mut String, depth: usize, key: &str) {
  for (i, line) in split_nt_line_li(key).enumerate() {
    if i > 0 {
      out.push('\n');
    }
    push_indent(out, depth);
    out.push(':');
    if !line.is_empty() {
      out.push(' ');
      out.push_str(line);
    }
  }
  out.push('\n');
}

fn write_list(item_li: &[Value<'_>], depth: usize, out: &mut String) {
  if item_li.is_empty() {
    push_indent(out, depth);
    out.push_str("[]\n");
    return;
  }
  for item in item_li {
    push_indent(out, depth);
    match item {
      Value::Str(s) if s.is_empty() => out.push_str("-\n"),
      Value::Str(s) if !multiline(s) => {
        out.push_str("- ");
        out.push_str(s);
        out.push('\n');
      }
      _ => {
        out.push_str("-\n");
        write(item, depth + STEP, out);
      }
    }
  }
}

fn write_dict(pair_li: &[(Cow<'_, str>, Value<'_>)], depth: usize, out: &mut String) {
  if pair_li.is_empty() {
    push_indent(out, depth);
    out.push_str("{}\n");
    return;
  }
  for (key, value) in pair_li {
    if !is_inline_key(key) {
      write_multiline_key(out, depth, key);
      write(value, depth + STEP, out);
      continue;
    }
    push_indent(out, depth);
    out.push_str(key);
    out.push(':');
    match value {
      Value::Str(s) if s.is_empty() => out.push('\n'),
      Value::Str(s) if !multiline(s) => {
        out.push(' ');
        out.push_str(s);
        out.push('\n');
      }
      _ => {
        out.push('\n');
        write(value, depth + STEP, out);
      }
    }
  }
}

/// 值是否需要写成多行块(含换行或首尾空白):先 O(1) 判首尾空白,再单遍 SIMD 扫 `\n`/`\r`
#[inline]
pub(crate) fn multiline(s: &str) -> bool {
  let b = s.as_bytes();
  if b.is_empty() {
    return false;
  }
  let first = b[0];
  let last = b[b.len() - 1];
  if first.is_ascii_whitespace()
    || last.is_ascii_whitespace()
    || ((first >= 0x80 || last >= 0x80) && s != s.trim())
  {
    return true;
  }
  memchr2(b'\n', b'\r', b).is_some()
}

/// 键能否作为单行 `key:` 原样写出:单次 `memchr3(b'\n', b'\r', b':')` 合一检测,零分配
#[inline]
pub(crate) fn is_inline_key(key: &str) -> bool {
  let b = key.as_bytes();
  let Some(&first) = b.first() else {
    return false;
  };
  let last = b[b.len() - 1];
  if matches!(first, b'#' | b'[' | b'{')
    || matches!((first, b.get(1)), (b'-' | b'>' | b':', None | Some(b' ')))
    || last == b':'
    || first.is_ascii_whitespace()
    || last.is_ascii_whitespace()
    || ((first >= 0x80 || last >= 0x80) && key != key.trim())
  {
    return false;
  }
  let mut from = 0;
  while let Some(rel) = memchr3(b'\n', b'\r', b':', unsafe { b.get_unchecked(from..) }) {
    let pos = from + rel;
    if unsafe { *b.get_unchecked(pos) } != b':' || b.get(pos + 1) == Some(&b' ') {
      return false;
    }
    from = pos + 1;
  }
  true
}