pub(crate) mod scalar;
use oneyaml_core::{Diagnostic, Label, Limits, Schema, Settings, Span, code};
use crate::error::Error;
use crate::resolve::conflict::Conflict;
use crate::value::{MappingIter, TagStr, Value};
use scalar::{BlockIndent, Rules};
pub(crate) fn output_rules(settings: &Settings) -> Result<Rules, Error> {
crate::construct::settings::diagnostics(settings, |c| matches!(c, Conflict::ZeroIndent))?;
let schema = settings.get_schema();
if schema != Schema::Core {
let name = if schema == Schema::Json {
"JSON"
} else {
"Failsafe"
};
let d = Diagnostic::error(
code::INCORRECT_SETTINGS,
format!("output is written for the Core schema; the {name} schema would not read it back as the same values"),
Label::new(Span::empty(0), ""),
)
.with_help("serialize with Schema::Core (the default); every reader-safe output reads back under Core with any YAML 1.1 setting");
return Err(Error::from_diagnostic(d));
}
if let Some(width) = settings.get_emit().get_line_width() {
let d = Diagnostic::error(
code::UNSUPPORTED_SETTING,
format!(
"line wrapping (Emit::line_width(Some({width}))) is not supported by this version"
),
Label::new(Span::empty(0), ""),
)
.with_help("use Emit::line_width(None) (the default): output is never wrapped");
return Err(Error::from_diagnostic(d));
}
Rules::new(settings.get_emit(), settings)
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct Slot {
compact: bool,
pub(crate) map_col: usize,
pub(crate) seq_col: usize,
block: BlockIndent,
}
impl Slot {
pub(crate) fn root(rules: &Rules) -> Self {
Self {
compact: true,
map_col: 0,
seq_col: 0,
block: root_block(rules),
}
}
pub(crate) fn inline(parent: usize, rules: &Rules) -> Self {
let col = parent.saturating_add(2);
Self {
compact: true,
map_col: col,
seq_col: col,
block: BlockIndent {
content: parent.saturating_add(rules.indent()),
indicator: Some(rules.indent()),
},
}
}
pub(crate) fn value(key_col: usize, rules: &Rules) -> Self {
let nested = key_col.saturating_add(rules.indent());
Self {
compact: false,
map_col: nested,
seq_col: if rules.indent_sequences() {
nested
} else {
key_col
},
block: BlockIndent {
content: nested,
indicator: Some(rules.indent()),
},
}
}
fn after_marker(rules: &Rules) -> Self {
Self {
compact: false,
map_col: 0,
seq_col: 0,
block: root_block(rules),
}
}
}
fn root_block(rules: &Rules) -> BlockIndent {
BlockIndent {
content: rules.indent(),
indicator: None,
}
}
pub(crate) enum Task<'a> {
Node(&'a Value, Slot),
Seq(std::slice::Iter<'a, Value>, usize),
Map(MappingIter<'a>, usize),
ExplicitValue(&'a Value, usize),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Start {
First,
Next,
}
#[derive(Default)]
pub(crate) struct Stacks {
tasks: Vec<Task<'static>>,
walk: Vec<(&'static Value, usize)>,
}
impl Stacks {
pub(crate) fn take_tasks<'a>(&mut self) -> Vec<Task<'a>> {
std::mem::take(&mut self.tasks)
}
pub(crate) fn put_tasks(&mut self, tasks: Vec<Task<'_>>) {
self.tasks = recycle(tasks);
}
pub(crate) fn depth(&mut self, v: &Value, cap: usize) -> usize {
let mut walk = std::mem::take(&mut self.walk);
let d = crate::value::walk::depth_with(v, cap, &mut walk);
self.walk = recycle(walk);
d
}
}
fn recycle<T, U>(mut v: Vec<T>) -> Vec<U> {
v.clear();
v.into_iter().filter_map(|_| None).collect()
}
pub(crate) fn emit_document(
out: &mut String,
value: &Value,
rules: &Rules,
limits: &Limits,
start: Start,
) -> Result<(), Error> {
value
.check_depth(limits)
.map_err(crate::value::DepthExceeded::into_error)?;
let slot = document_start(out, rules, start);
Emitter { out, rules }.run(value, slot, false)?;
out.push('\n');
Ok(())
}
pub(crate) fn document_start(out: &mut String, rules: &Rules, start: Start) -> Slot {
match start {
Start::First => Slot::root(rules),
Start::Next if rules.serde_yaml_layout() => {
out.push_str("---");
Slot::after_marker(rules)
}
Start::Next => {
out.push_str("---\n");
Slot::root(rules)
}
}
}
fn unrepresentable(what: String) -> Error {
Error::new(code::UNREPRESENTABLE_VALUE, what, None)
}
pub(crate) fn write_plain_scalar(out: &mut String, rules: &Rules, v: &Value) -> Result<(), Error> {
match v {
Value::Null => scalar::write_null(out),
Value::Bool(b) => scalar::write_bool(out, *b),
Value::Number(n) => rules.write_number(out, n)?,
_ => {
debug_assert!(false, "write_plain_scalar given a non-scalar");
return Err(Error::internal(
"write_plain_scalar given a non-scalar",
None,
));
}
}
Ok(())
}
pub(crate) struct Emitter<'o, 'r> {
out: &'o mut String,
rules: &'r Rules,
}
impl<'o, 'r> Emitter<'o, 'r> {
pub(crate) fn new(out: &'o mut String, rules: &'r Rules) -> Self {
Self { out, rules }
}
}
impl<'a> Emitter<'_, '_> {
pub(crate) fn run(&mut self, root: &'a Value, slot: Slot, tagged: bool) -> Result<(), Error> {
self.run_with(root, slot, tagged, &mut Vec::new())
}
pub(crate) fn run_with(
&mut self,
root: &'a Value,
slot: Slot,
tagged: bool,
stack: &mut Vec<Task<'a>>,
) -> Result<(), Error> {
stack.clear();
if tagged {
self.content(root, slot, true, stack)?;
} else {
stack.push(Task::Node(root, slot));
}
while let Some(task) = stack.pop() {
match task {
Task::Node(v, slot) => self.node(v, slot, stack)?,
Task::Seq(mut it, col) => {
if let Some(item) = it.next() {
self.newline(col);
self.out.push_str("- ");
stack.push(Task::Seq(it, col));
stack.push(Task::Node(item, Slot::inline(col, self.rules)));
}
}
Task::Map(mut it, col) => {
if let Some((k, v)) = it.next() {
self.newline(col);
stack.push(Task::Map(it, col));
self.entry(k, v, col, stack)?;
}
}
Task::ExplicitValue(v, col) => {
self.newline(col);
self.out.push_str(": ");
stack.push(Task::Node(v, Slot::inline(col, self.rules)));
}
}
}
Ok(())
}
pub(crate) fn newline(&mut self, col: usize) {
self.out.push('\n');
scalar::push_spaces(self.out, col);
}
fn tag(&mut self, slot: Slot, tag: TagStr<'_>) -> Result<(), Error> {
if !self.rules.tag_fits(tag.len()) {
return Err(self.rules.tag_too_long(tag.len()));
}
if !slot.compact {
self.out.push(' ');
}
scalar::write_tag(self.out, tag).map_err(|()| {
unrepresentable(format!(
"the tag `{tag}` has no spec-valid form that reads back as the same tag"
))
})
}
pub(crate) fn local_tag(&mut self, slot: Slot, name: &str) -> Result<(), Error> {
if name.is_empty() {
debug_assert!(false, "an empty local tag name");
return Err(Error::internal("an empty local tag name", None));
}
let len = name.len().saturating_add(1);
if !self.rules.tag_fits(len) {
return Err(self.rules.tag_too_long(len));
}
if !slot.compact {
self.out.push(' ');
}
scalar::write_local_tag(self.out, name)
.map_err(|()| Error::internal("an empty local tag name", None))
}
pub(crate) fn string(&mut self, slot: Slot, tagged: bool, s: &str) {
self.separate(slot, tagged);
let style = self.rules.string_style(s, slot.block);
self.rules.write_string(self.out, s, style, slot.block);
}
pub(crate) fn str_key(&mut self, name: &str, col: usize) -> Slot {
if self.rules.write_implicit_key(self.out, name, None) {
self.out.push(':');
return Slot::value(col, self.rules);
}
self.out.push_str("? ");
self.string(Slot::inline(col, self.rules), false, name);
self.explicit_value(col)
}
pub(crate) fn key(
&mut self,
k: &'a Value,
col: usize,
stack: &mut Vec<Task<'a>>,
) -> Result<Slot, Error> {
if self.write_implicit_key(k)? {
self.out.push(':');
return Ok(Slot::value(col, self.rules));
}
self.out.push_str("? ");
self.run_with(k, Slot::inline(col, self.rules), false, stack)?;
Ok(self.explicit_value(col))
}
fn explicit_value(&mut self, col: usize) -> Slot {
self.newline(col);
self.out.push_str(": ");
Slot::inline(col, self.rules)
}
fn node(&mut self, v: &'a Value, slot: Slot, stack: &mut Vec<Task<'a>>) -> Result<(), Error> {
let (tag, content) = match v {
Value::Tagged(t) => (Some(t.tag.parts()), &t.value),
other => (None, other),
};
if let Some(tag) = tag {
self.tag(slot, tag)?;
match content {
Value::Tagged(_) => {
return Err(unrepresentable(
"a tagged value inside a tagged value: a YAML node has one tag".to_owned(),
));
}
Value::String(_) | Value::Sequence(_) | Value::Mapping(_) => {}
_ => {
return Err(unrepresentable(format!(
"a `{tag}`-tagged non-string scalar: a custom-tagged scalar always reads back as a string"
)));
}
}
}
self.content(content, slot, tag.is_some(), stack)
}
fn content(
&mut self,
content: &'a Value,
slot: Slot,
tagged: bool,
stack: &mut Vec<Task<'a>>,
) -> Result<(), Error> {
match content {
Value::Sequence(s) => match s.as_slice().split_first() {
None => self.inline_text(slot, tagged, "[]"),
Some((first, rest)) => {
let col = slot.seq_col;
self.open_block(slot, tagged, col);
self.out.push_str("- ");
stack.push(Task::Seq(rest.iter(), col));
stack.push(Task::Node(first, Slot::inline(col, self.rules)));
}
},
Value::Mapping(m) => {
let mut it = m.iter();
match it.next() {
None => self.inline_text(slot, tagged, "{}"),
Some((k, val)) => {
let col = slot.map_col;
self.open_block(slot, tagged, col);
stack.push(Task::Map(it, col));
self.entry(k, val, col, stack)?;
}
}
}
Value::String(s) => self.string(slot, tagged, s),
other => {
self.separate(slot, tagged);
write_plain_scalar(self.out, self.rules, other)?;
}
}
Ok(())
}
pub(crate) fn separate(&mut self, slot: Slot, tagged: bool) {
if tagged || !slot.compact {
self.out.push(' ');
}
}
pub(crate) fn inline_text(&mut self, slot: Slot, tagged: bool, text: &str) {
self.separate(slot, tagged);
self.out.push_str(text);
}
pub(crate) fn open_block(&mut self, slot: Slot, tagged: bool, col: usize) {
if tagged || !slot.compact {
self.newline(col);
}
}
fn entry(
&mut self,
k: &'a Value,
v: &'a Value,
col: usize,
stack: &mut Vec<Task<'a>>,
) -> Result<(), Error> {
if self.write_implicit_key(k)? {
self.out.push(':');
stack.push(Task::Node(v, Slot::value(col, self.rules)));
} else {
self.out.push_str("? ");
stack.push(Task::ExplicitValue(v, col));
stack.push(Task::Node(k, Slot::inline(col, self.rules)));
}
Ok(())
}
pub(crate) fn push_str(&mut self, s: &str) {
self.out.push_str(s);
}
pub(crate) fn plain(&mut self, slot: Slot, tagged: bool, v: &Value) -> Result<(), Error> {
self.separate(slot, tagged);
write_plain_scalar(self.out, self.rules, v)
}
fn write_implicit_key(&mut self, k: &Value) -> Result<bool, Error> {
let (tag, content) = match k {
Value::Tagged(t) => (Some(t.tag.parts()), &t.value),
other => (None, other),
};
let empty = match content {
Value::Tagged(_) => return Ok(false),
Value::String(s) => return Ok(self.rules.write_implicit_key(self.out, s, tag)),
Value::Sequence(s) if s.is_empty() && self.rules.serde_yaml_layout() => Some("[]"),
Value::Mapping(m) if m.is_empty() && self.rules.serde_yaml_layout() => Some("{}"),
Value::Sequence(_) | Value::Mapping(_) => return Ok(false),
_ if tag.is_some() => return Ok(false),
_ => None,
};
let start = self.out.len();
if let Some(tag) = tag {
if !self.rules.tag_fits(tag.len()) || scalar::write_tag(self.out, tag).is_err() {
self.out.truncate(start);
return Ok(false);
}
self.out.push(' ');
}
match empty {
Some(text) => self.out.push_str(text),
None => write_plain_scalar(self.out, self.rules, content)?,
}
let fits = self.rules.short_key(self.out.len().saturating_sub(start));
if !fits {
self.out.truncate(start);
}
Ok(fits)
}
}
#[cfg(test)]
pub(crate) fn root_style(rules: &Rules, s: &str) -> scalar::Style {
rules.string_style(s, root_block(rules))
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::indexing_slicing)]
use super::*;
use crate::value::{Mapping, Tag, TaggedValue};
use oneyaml_core::{Emit, Layout};
fn emit(v: &Value, settings: &Settings) -> String {
let rules = output_rules(settings).unwrap();
let mut out = String::new();
emit_document(&mut out, v, &rules, settings.get_limits(), Start::First).unwrap();
out
}
#[test]
fn no_indentation_indicator_at_the_root() {
let rules = output_rules(&Settings::new()).unwrap();
assert_eq!(root_style(&rules, " a\nb"), scalar::Style::Double);
assert_eq!(root_style(&rules, "a\n b"), scalar::Style::Literal);
assert_eq!(
emit(&Value::from(" a\nb"), &Settings::new()),
"\" a\\nb\"\n"
);
let nested = Value::from(vec![Value::from(" a\nb")]);
assert_eq!(emit(&nested, &Settings::new()), "- |2-\n a\n b\n");
}
#[test]
fn tagged_non_string_scalars_are_refused() {
let v = Value::from(TaggedValue {
tag: Tag::new("!n"),
value: Value::from(1),
});
let rules = output_rules(&Settings::new()).unwrap();
let e = emit_document(&mut String::new(), &v, &rules, &Limits::new(), Start::First)
.unwrap_err();
assert_eq!(e.code(), code::UNREPRESENTABLE_VALUE);
let mut m = Mapping::new();
m.insert(v, Value::Null);
let e = emit_document(
&mut String::new(),
&Value::from(m),
&rules,
&Limits::new(),
Start::First,
)
.unwrap_err();
assert_eq!(e.code(), code::UNREPRESENTABLE_VALUE);
}
#[test]
fn serde_yaml_layout_marks() {
let s = Settings::new().emit(Emit::new().layout(Layout::SerdeYaml));
let rules = output_rules(&s).unwrap();
let mut out = String::new();
emit_document(
&mut out,
&Value::from(1),
&rules,
&Limits::new(),
Start::First,
)
.unwrap();
emit_document(
&mut out,
&Value::from("x"),
&rules,
&Limits::new(),
Start::Next,
)
.unwrap();
emit_document(
&mut out,
&Value::from(vec![Value::from(1)]),
&rules,
&Limits::new(),
Start::Next,
)
.unwrap();
assert_eq!(out, "1\n--- x\n---\n- 1\n");
}
}
#[cfg(test)]
mod recycle_tests {
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::indexing_slicing)]
use super::*;
#[test]
fn recycling_keeps_the_allocation() {
let v = Value::from(vec![Value::from(1), Value::from(vec![Value::Null])]);
let mut st = Stacks::default();
assert_eq!(st.depth(&v, 10), 2);
let cap = st.walk.capacity();
let ptr = st.walk.as_ptr() as usize;
assert!(cap > 0);
assert_eq!(st.depth(&v, 10), 2);
assert_eq!(st.walk.capacity(), cap);
assert_eq!(st.walk.as_ptr() as usize, ptr);
let mut t = st.take_tasks();
let rules = output_rules(&Settings::new()).unwrap();
let mut out = String::new();
Emitter::new(&mut out, &rules)
.run_with(&v, Slot::root(&rules), false, &mut t)
.unwrap();
let cap = t.capacity();
st.put_tasks(t);
assert!(cap > 0);
assert_eq!(st.tasks.capacity(), cap);
}
}