git.delta.rocks / jrsonnet / refs/commits / afbb8dc16504

difftreelog

Merge pull request #64 from CertainLach/feat/manifest-yaml-doc-builtin

Yaroslav Bolyukin2021-10-29parents: #27b30fb #c0cb444.patch.diff
in: master
Make manifestYamlDoc builtin

6 files changed

modifiedcrates/jrsonnet-cli/src/manifest.rsdiffbeforeafterboth
--- a/crates/jrsonnet-cli/src/manifest.rs
+++ b/crates/jrsonnet-cli/src/manifest.rs
@@ -38,9 +38,9 @@
 	#[clap(long, short = 'y')]
 	yaml_stream: bool,
 	/// Number of spaces to pad output manifest with.
-	/// `0` for hard tabs, `-1` for single line output
-	#[clap(long, default_value = "3")]
-	line_padding: usize,
+	/// `0` for hard tabs, `-1` for single line output [default: 3 for json, 2 for yaml]
+	#[clap(long)]
+	line_padding: Option<usize>,
 }
 impl ConfigureState for ManifestOpts {
 	fn configure(&self, state: &EvaluationState) -> Result<()> {
@@ -50,10 +50,10 @@
 			match self.format {
 				ManifestFormatName::String => state.set_manifest_format(ManifestFormat::String),
 				ManifestFormatName::Json => {
-					state.set_manifest_format(ManifestFormat::Json(self.line_padding))
+					state.set_manifest_format(ManifestFormat::Json(self.line_padding.unwrap_or(3)))
 				}
 				ManifestFormatName::Yaml => {
-					state.set_manifest_format(ManifestFormat::Yaml(self.line_padding))
+					state.set_manifest_format(ManifestFormat::Yaml(self.line_padding.unwrap_or(2)))
 				}
 			}
 		}
modifiedcrates/jrsonnet-evaluator/src/builtin/manifest.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/builtin/manifest.rs
+++ b/crates/jrsonnet-evaluator/src/builtin/manifest.rs
@@ -156,3 +156,127 @@
 	}
 	buf.push('"');
 }
+
+pub struct ManifestYamlOptions<'s> {
+	/// Padding before fields, i.e
+	/// ```yaml
+	/// a:
+	///   b:
+	/// ## <- this
+	/// ```
+	pub padding: &'s str,
+	/// Padding before array elements in objects
+	/// ```yaml
+	/// a:
+	///   - 1
+	/// ## <- this
+	/// ```
+	pub arr_element_padding: &'s str,
+}
+
+pub fn manifest_yaml_ex(val: &Val, options: &ManifestYamlOptions<'_>) -> Result<String> {
+	let mut out = String::new();
+	manifest_yaml_ex_buf(val, &mut out, &mut String::new(), options)?;
+	Ok(out)
+}
+fn manifest_yaml_ex_buf(
+	val: &Val,
+	buf: &mut String,
+	cur_padding: &mut String,
+	options: &ManifestYamlOptions<'_>,
+) -> Result<()> {
+	use std::fmt::Write;
+	match val {
+		Val::Bool(v) => {
+			if *v {
+				buf.push_str("true")
+			} else {
+				buf.push_str("false")
+			}
+		}
+		Val::Null => buf.push_str("null"),
+		Val::Str(s) => {
+			if s.is_empty() {
+				buf.push_str("\"\"");
+			} else if let Some(s) = s.strip_suffix('\n') {
+				buf.push('|');
+				for line in s.split('\n') {
+					buf.push('\n');
+					buf.push_str(options.padding);
+					buf.push_str(line);
+				}
+			} else {
+				escape_string_json_buf(s, buf)
+			}
+		}
+		Val::Num(n) => write!(buf, "{}", *n).unwrap(),
+		Val::Arr(a) => {
+			if a.is_empty() {
+				buf.push_str("[]");
+			} else {
+				for (i, item) in a.iter().enumerate() {
+					if i != 0 {
+						buf.push('\n');
+						buf.push_str(cur_padding);
+					}
+					let item = item?;
+					buf.push('-');
+					match &item {
+						Val::Arr(a) if !a.is_empty() => {
+							buf.push('\n');
+							buf.push_str(cur_padding);
+							buf.push_str(options.padding);
+						}
+						_ => buf.push(' '),
+					}
+					let extra_padding = match &item {
+						Val::Arr(a) => !a.is_empty(),
+						Val::Obj(o) => !o.is_empty(),
+						_ => false,
+					};
+					let prev_len = cur_padding.len();
+					if extra_padding {
+						cur_padding.push_str(options.padding);
+					}
+					manifest_yaml_ex_buf(&item, buf, cur_padding, options)?;
+					cur_padding.truncate(prev_len);
+				}
+			}
+		}
+		Val::Obj(o) => {
+			if o.is_empty() {
+				buf.push_str("{}");
+			} else {
+				for (i, key) in o.fields().iter().enumerate() {
+					if i != 0 {
+						buf.push('\n');
+						buf.push_str(cur_padding);
+					}
+					escape_string_json_buf(key, buf);
+					buf.push(':');
+					let prev_len = cur_padding.len();
+					let item = o.get(key.clone())?.expect("field exists");
+					match &item {
+						Val::Arr(a) if !a.is_empty() => {
+							buf.push('\n');
+							buf.push_str(cur_padding);
+							buf.push_str(options.arr_element_padding);
+							cur_padding.push_str(options.arr_element_padding);
+						}
+						Val::Obj(o) if !o.is_empty() => {
+							buf.push('\n');
+							buf.push_str(cur_padding);
+							buf.push_str(options.padding);
+							cur_padding.push_str(options.padding);
+						}
+						_ => buf.push(' '),
+					}
+					manifest_yaml_ex_buf(&item, buf, cur_padding, options)?;
+					cur_padding.truncate(prev_len);
+				}
+			}
+		}
+		Val::Func(_) => throw!(RuntimeError("tried to manifest function".into())),
+	}
+	Ok(())
+}
modifiedcrates/jrsonnet-evaluator/src/builtin/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/builtin/mod.rs
+++ b/crates/jrsonnet-evaluator/src/builtin/mod.rs
@@ -1,4 +1,5 @@
 use crate::{
+	builtin::manifest::{manifest_yaml_ex, ManifestYamlOptions},
 	equals,
 	error::{Error::*, Result},
 	operator::evaluate_mod_op,
@@ -121,6 +122,7 @@
 			("join".into(), builtin_join),
 			("escapeStringJson".into(), builtin_escape_string_json),
 			("manifestJsonEx".into(), builtin_manifest_json_ex),
+			("manifestYamlDocImpl".into(), builtin_manifest_yaml_doc),
 			("reverse".into(), builtin_reverse),
 			("id".into(), builtin_id),
 			("strReplace".into(), builtin_str_replace),
@@ -768,6 +770,22 @@
 	})
 }
 
+fn builtin_manifest_yaml_doc(
+	context: Context,
+	_loc: Option<&ExprLocation>,
+	args: &ArgsDesc,
+) -> Result<Val> {
+	parse_args!(context, "manifestYamlDoc", args, 2, [
+		0, value: ty!(any);
+		1, indent_array_in_object: ty!(boolean) => Val::Bool;
+	], {
+		Ok(Val::Str(manifest_yaml_ex(&value, &ManifestYamlOptions {
+			padding: "  ",
+			arr_element_padding: if indent_array_in_object { "  " } else { "" },
+		})?.into()))
+	})
+}
+
 fn builtin_reverse(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result<Val> {
 	parse_args!(context, "reverse", args, 1, [
 		0, value: ty!(array) => Val::Arr;
@@ -794,18 +812,7 @@
 		1, from: ty!(string) => Val::Str;
 		2, to: ty!(string) => Val::Str;
 	], {
-		let mut out = String::new();
-		let mut last_idx = 0;
-		while let Some(idx) = (&str[last_idx..]).find(&from as &str) {
-			out.push_str(&str[last_idx..last_idx+idx]);
-			out.push_str(&to);
-			last_idx += idx + from.len();
-		}
-		if last_idx == 0 {
-			return Ok(Val::Str(str))
-		}
-		out.push_str(&str[last_idx..]);
-		Ok(Val::Str(out.into()))
+		Ok(Val::Str(str.replace(&from as &str, &to as &str).into()))
 	})
 }
 
modifiedcrates/jrsonnet-evaluator/src/obj.rsdiffbeforeafterboth
before · crates/jrsonnet-evaluator/src/obj.rs
1use crate::operator::evaluate_add_op;2use crate::{Bindable, LazyBinding, LazyVal, Result, Val};3use jrsonnet_gc::{Gc, GcCell, Trace};4use jrsonnet_interner::IStr;5use jrsonnet_parser::{ExprLocation, Visibility};6use rustc_hash::{FxHashMap, FxHashSet, FxHasher};7use std::collections::HashMap;8use std::hash::{Hash, Hasher};9use std::{fmt::Debug, hash::BuildHasherDefault};1011#[derive(Debug, Trace)]12#[trivially_drop]13pub struct ObjMember {14	pub add: bool,15	pub visibility: Visibility,16	pub invoke: LazyBinding,17	pub location: Option<ExprLocation>,18}1920pub trait ObjectAssertion: Trace {21	fn run(&self, this: Option<ObjValue>, super_obj: Option<ObjValue>) -> Result<()>;22}2324// Field => This25type CacheKey = (IStr, ObjValue);26#[derive(Trace)]27#[trivially_drop]28pub struct ObjValueInternals {29	super_obj: Option<ObjValue>,30	assertions: Gc<Vec<Box<dyn ObjectAssertion>>>,31	assertions_ran: GcCell<FxHashSet<ObjValue>>,32	this_obj: Option<ObjValue>,33	this_entries: Gc<FxHashMap<IStr, ObjMember>>,34	value_cache: GcCell<FxHashMap<CacheKey, Option<Val>>>,35}3637#[derive(Clone, Trace)]38#[trivially_drop]39pub struct ObjValue(pub(crate) Gc<ObjValueInternals>);40impl Debug for ObjValue {41	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {42		if let Some(super_obj) = self.0.super_obj.as_ref() {43			if f.alternate() {44				write!(f, "{:#?}", super_obj)?;45			} else {46				write!(f, "{:?}", super_obj)?;47			}48			write!(f, " + ")?;49		}50		let mut debug = f.debug_struct("ObjValue");51		for (name, member) in self.0.this_entries.iter() {52			debug.field(name, member);53		}54		#[cfg(feature = "unstable")]55		{56			debug.finish_non_exhaustive()57		}58		#[cfg(not(feature = "unstable"))]59		{60			debug.finish()61		}62	}63}6465impl ObjValue {66	pub fn new(67		super_obj: Option<Self>,68		this_entries: Gc<FxHashMap<IStr, ObjMember>>,69		assertions: Gc<Vec<Box<dyn ObjectAssertion>>>,70	) -> Self {71		Self(Gc::new(ObjValueInternals {72			super_obj,73			assertions,74			assertions_ran: GcCell::new(FxHashSet::default()),75			this_obj: None,76			this_entries,77			value_cache: GcCell::new(FxHashMap::default()),78		}))79	}80	pub fn new_empty() -> Self {81		Self::new(None, Gc::new(FxHashMap::default()), Gc::new(Vec::new()))82	}83	pub fn extend_from(&self, super_obj: Self) -> Self {84		match &self.0.super_obj {85			None => Self::new(86				Some(super_obj),87				self.0.this_entries.clone(),88				self.0.assertions.clone(),89			),90			Some(v) => Self::new(91				Some(v.extend_from(super_obj)),92				self.0.this_entries.clone(),93				self.0.assertions.clone(),94			),95		}96	}97	pub fn with_this(&self, this_obj: Self) -> Self {98		Self(Gc::new(ObjValueInternals {99			super_obj: self.0.super_obj.clone(),100			assertions: self.0.assertions.clone(),101			assertions_ran: GcCell::new(FxHashSet::default()),102			this_obj: Some(this_obj),103			this_entries: self.0.this_entries.clone(),104			value_cache: GcCell::new(FxHashMap::default()),105		}))106	}107108	/// Run callback for every field found in object109	pub(crate) fn enum_fields(&self, handler: &mut impl FnMut(&IStr, &Visibility) -> bool) -> bool {110		if let Some(s) = &self.0.super_obj {111			if s.enum_fields(handler) {112				return true;113			}114		}115		for (name, member) in self.0.this_entries.iter() {116			if handler(name, &member.visibility) {117				return true;118			}119		}120		false121	}122123	pub fn fields_visibility(&self) -> FxHashMap<IStr, bool> {124		let mut out = FxHashMap::default();125		self.enum_fields(&mut |name, visibility| {126			match visibility {127				Visibility::Normal => {128					let entry = out.entry(name.to_owned());129					entry.or_insert(true);130				}131				Visibility::Hidden => {132					out.insert(name.to_owned(), false);133				}134				Visibility::Unhide => {135					out.insert(name.to_owned(), true);136				}137			};138			false139		});140		out141	}142	pub fn fields_ex(&self, include_hidden: bool) -> Vec<IStr> {143		let mut fields: Vec<_> = self144			.fields_visibility()145			.into_iter()146			.filter(|(_k, v)| include_hidden || *v)147			.map(|(k, _)| k)148			.collect();149		fields.sort_unstable();150		fields151	}152	pub fn fields(&self) -> Vec<IStr> {153		self.fields_ex(false)154	}155156	pub fn field_visibility(&self, name: IStr) -> Option<Visibility> {157		if let Some(m) = self.0.this_entries.get(&name) {158			Some(match &m.visibility {159				Visibility::Normal => self160					.0161					.super_obj162					.as_ref()163					.and_then(|super_obj| super_obj.field_visibility(name))164					.unwrap_or(Visibility::Normal),165				v => *v,166			})167		} else if let Some(super_obj) = &self.0.super_obj {168			super_obj.field_visibility(name)169		} else {170			None171		}172	}173174	fn has_field_include_hidden(&self, name: IStr) -> bool {175		if self.0.this_entries.contains_key(&name) {176			true177		} else if let Some(super_obj) = &self.0.super_obj {178			super_obj.has_field_include_hidden(name)179		} else {180			false181		}182	}183184	pub fn has_field_ex(&self, name: IStr, include_hidden: bool) -> bool {185		if include_hidden {186			self.has_field_include_hidden(name)187		} else {188			self.has_field(name)189		}190	}191	pub fn has_field(&self, name: IStr) -> bool {192		self.field_visibility(name)193			.map(|v| v.is_visible())194			.unwrap_or(false)195	}196197	pub fn get(&self, key: IStr) -> Result<Option<Val>> {198		self.run_assertions()?;199		self.get_raw(key, self.0.this_obj.as_ref())200	}201202	pub fn extend_with_field(self, key: IStr, value: ObjMember) -> Self {203		let mut new = FxHashMap::with_capacity_and_hasher(1, BuildHasherDefault::default());204		new.insert(key, value);205		Self::new(Some(self), Gc::new(new), Gc::new(Vec::new()))206	}207208	fn get_raw(&self, key: IStr, real_this: Option<&Self>) -> Result<Option<Val>> {209		let real_this = real_this.unwrap_or(self);210		let cache_key = (key.clone(), real_this.clone());211212		if let Some(v) = self.0.value_cache.borrow().get(&cache_key) {213			return Ok(v.clone());214		}215		let value = match (self.0.this_entries.get(&key), &self.0.super_obj) {216			(Some(k), None) => Ok(Some(self.evaluate_this(k, real_this)?)),217			(Some(k), Some(s)) => {218				let our = self.evaluate_this(k, real_this)?;219				if k.add {220					s.get_raw(key, Some(real_this))?221						.map_or(Ok(Some(our.clone())), |v| {222							Ok(Some(evaluate_add_op(&v, &our)?))223						})224				} else {225					Ok(Some(our))226				}227			}228			(None, Some(s)) => s.get_raw(key, Some(real_this)),229			(None, None) => Ok(None),230		}?;231		self.0232			.value_cache233			.borrow_mut()234			.insert(cache_key, value.clone());235		Ok(value)236	}237	fn evaluate_this(&self, v: &ObjMember, real_this: &Self) -> Result<Val> {238		v.invoke239			.evaluate(Some(real_this.clone()), self.0.super_obj.clone())?240			.evaluate()241	}242243	fn run_assertions_raw(&self, real_this: &Self) -> Result<()> {244		if self.0.assertions_ran.borrow_mut().insert(real_this.clone()) {245			for assertion in self.0.assertions.iter() {246				if let Err(e) = assertion.run(Some(real_this.clone()), self.0.super_obj.clone()) {247					self.0.assertions_ran.borrow_mut().remove(real_this);248					return Err(e);249				}250			}251			if let Some(super_obj) = &self.0.super_obj {252				super_obj.run_assertions_raw(real_this)?;253			}254		}255		Ok(())256	}257	pub fn run_assertions(&self) -> Result<()> {258		self.run_assertions_raw(self)259	}260261	pub fn ptr_eq(a: &Self, b: &Self) -> bool {262		Gc::ptr_eq(&a.0, &b.0)263	}264}265266impl PartialEq for ObjValue {267	fn eq(&self, other: &Self) -> bool {268		Gc::ptr_eq(&self.0, &other.0)269	}270}271272impl Eq for ObjValue {}273impl Hash for ObjValue {274	fn hash<H: Hasher>(&self, hasher: &mut H) {275		hasher.write_usize(&*self.0 as *const _ as usize)276	}277}278279pub struct ObjValueBuilder {280	super_obj: Option<ObjValue>,281	map: FxHashMap<IStr, ObjMember>,282	assertions: Vec<Box<dyn ObjectAssertion>>,283}284impl ObjValueBuilder {285	pub fn new() -> Self {286		Self::with_capacity(0)287	}288	pub fn with_capacity(capacity: usize) -> Self {289		Self {290			super_obj: None,291			map: HashMap::with_capacity_and_hasher(292				capacity,293				BuildHasherDefault::<FxHasher>::default(),294			),295			assertions: Vec::new(),296		}297	}298	pub fn reserve_asserts(&mut self, capacity: usize) -> &mut Self {299		self.assertions.reserve_exact(capacity);300		self301	}302	pub fn with_super(&mut self, super_obj: ObjValue) -> &mut Self {303		self.super_obj = Some(super_obj);304		self305	}306307	pub fn assert(&mut self, assertion: Box<dyn ObjectAssertion>) -> &mut Self {308		self.assertions.push(assertion);309		self310	}311	pub fn member(&mut self, name: IStr) -> ObjMemberBuilder {312		ObjMemberBuilder {313			value: self,314			name,315			add: false,316			visibility: Visibility::Normal,317			location: None,318		}319	}320321	pub fn build(self) -> ObjValue {322		ObjValue::new(self.super_obj, Gc::new(self.map), Gc::new(self.assertions))323	}324}325impl Default for ObjValueBuilder {326	fn default() -> Self {327		Self::with_capacity(0)328	}329}330331#[must_use = "value not added unless binding() was called"]332pub struct ObjMemberBuilder<'v> {333	value: &'v mut ObjValueBuilder,334	name: IStr,335	add: bool,336	visibility: Visibility,337	location: Option<ExprLocation>,338}339340#[allow(clippy::missing_const_for_fn)]341impl<'v> ObjMemberBuilder<'v> {342	pub const fn with_add(mut self, add: bool) -> Self {343		self.add = add;344		self345	}346	pub fn add(self) -> Self {347		self.with_add(true)348	}349	pub fn with_visibility(mut self, visibility: Visibility) -> Self {350		self.visibility = visibility;351		self352	}353	pub fn hide(self) -> Self {354		self.with_visibility(Visibility::Hidden)355	}356	pub fn with_location(mut self, location: Option<ExprLocation>) -> Self {357		self.location = location;358		self359	}360	pub fn value(self, value: Val) -> &'v mut ObjValueBuilder {361		self.binding(LazyBinding::Bound(LazyVal::new_resolved(value)))362	}363	pub fn bindable(self, bindable: Box<dyn Bindable>) -> &'v mut ObjValueBuilder {364		self.binding(LazyBinding::Bindable(Gc::new(bindable)))365	}366	pub fn binding(self, binding: LazyBinding) -> &'v mut ObjValueBuilder {367		self.value.map.insert(368			self.name,369			ObjMember {370				add: self.add,371				visibility: self.visibility,372				invoke: binding,373				location: self.location,374			},375		);376		self.value377	}378}
modifiedcrates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/val.rs
+++ b/crates/jrsonnet-evaluator/src/val.rs
@@ -1,17 +1,20 @@
 use crate::{
 	builtin::{
 		call_builtin,
-		manifest::{manifest_json_ex, ManifestJsonOptions, ManifestType},
+		manifest::{
+			manifest_json_ex, manifest_yaml_ex, ManifestJsonOptions, ManifestType,
+			ManifestYamlOptions,
+		},
 	},
 	error::{Error::*, LocError},
 	evaluate,
 	function::{parse_function_call, parse_function_call_map, place_args},
 	native::NativeCallback,
-	throw, with_state, Context, ObjValue, Result,
+	throw, Context, ObjValue, Result,
 };
 use jrsonnet_gc::{Gc, GcCell, Trace};
 use jrsonnet_interner::IStr;
-use jrsonnet_parser::{el, ArgsDesc, Expr, ExprLocation, LiteralType, LocExpr, ParamsDesc};
+use jrsonnet_parser::{ArgsDesc, ExprLocation, LocExpr, ParamsDesc};
 use jrsonnet_types::ValType;
 use std::{collections::HashMap, fmt::Debug, rc::Rc};
 
@@ -393,6 +396,12 @@
 	pub fn unwrap_num(self) -> Result<f64> {
 		Ok(matches_unwrap!(self, Self::Num(v), v))
 	}
+	pub fn unwrap_str(self) -> Result<IStr> {
+		Ok(matches_unwrap!(self, Self::Str(v), v))
+	}
+	pub fn unwrap_arr(self) -> Result<ArrValue> {
+		Ok(matches_unwrap!(self, Self::Arr(v), v))
+	}
 	pub fn unwrap_func(self) -> Result<Gc<FuncVal>> {
 		Ok(matches_unwrap!(self, Self::Func(v), v))
 	}
@@ -544,33 +553,15 @@
 	}
 
 	pub fn to_yaml(&self, padding: usize) -> Result<IStr> {
-		with_state(|s| {
-			let ctx = s
-				.create_default_context()
-				.with_var("__tmp__to_json__".into(), self.clone());
-			evaluate(
-				ctx,
-				&el!(Expr::Apply(
-					el!(Expr::Index(
-						el!(Expr::Var("std".into())),
-						el!(Expr::Str("manifestYamlDoc".into()))
-					)),
-					ArgsDesc::new(
-						vec![
-							el!(Expr::Var("__tmp__to_json__".into())),
-							el!(Expr::Literal(if padding != 0 {
-								LiteralType::True
-							} else {
-								LiteralType::False
-							})),
-						],
-						vec![]
-					),
-					false
-				)),
-			)?
-			.try_cast_str("to json")
-		})
+		let padding = &" ".repeat(padding);
+		manifest_yaml_ex(
+			self,
+			&ManifestYamlOptions {
+				padding,
+				arr_element_padding: padding,
+			},
+		)
+		.map(|s| s.into())
 	}
 	pub fn into_indexable(self) -> Result<IndexableVal> {
 		Ok(match self {
modifiedcrates/jrsonnet-stdlib/src/std.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/std.jsonnet
+++ b/crates/jrsonnet-stdlib/src/std.jsonnet
@@ -374,88 +374,9 @@
 
   manifestJsonEx:: $intrinsic(manifestJsonEx),
 
-  manifestYamlDoc(value, indent_array_in_object=false)::
-    local aux(v, path, cindent) =
-      if v == true then
-        'true'
-      else if v == false then
-        'false'
-      else if v == null then
-        'null'
-      else if std.isNumber(v) then
-        '' + v
-      else if std.isString(v) then
-        local len = std.length(v);
-        if len == 0 then
-          '""'
-        else if v[len - 1] == '\n' then
-          local split = std.split(v, '\n');
-          std.join('\n' + cindent + '  ', ['|'] + split[0:std.length(split) - 1])
-        else
-          std.escapeStringJson(v)
-      else if std.isFunction(v) then
-        error 'Tried to manifest function at ' + path
-      else if std.isArray(v) then
-        if std.length(v) == 0 then
-          '[]'
-        else
-          local params(value) =
-            if std.isArray(value) && std.length(value) > 0 then {
-              // While we could avoid the new line, it yields YAML that is
-              // hard to read, e.g.:
-              // - - - 1
-              //     - 2
-              //   - - 3
-              //     - 4
-              new_indent: cindent + '  ',
-              space: '\n' + self.new_indent,
-            } else if std.isObject(value) && std.length(value) > 0 then {
-              new_indent: cindent + '  ',
-              // In this case we can start on the same line as the - because the indentation
-              // matches up then.  The converse is not true, because fields are not always
-              // 1 character long.
-              space: ' ',
-            } else {
-              // In this case, new_indent is only used in the case of multi-line strings.
-              new_indent: cindent,
-              space: ' ',
-            };
-          local range = std.range(0, std.length(v) - 1);
-          local parts = [
-            '-' + param.space + aux(v[i], path + [i], param.new_indent)
-            for i in range
-            for param in [params(v[i])]
-          ];
-          std.join('\n' + cindent, parts)
-      else if std.isObject(v) then
-        if std.length(v) == 0 then
-          '{}'
-        else
-          local params(value) =
-            if std.isArray(value) && std.length(value) > 0 then {
-              // Not indenting allows e.g.
-              // ports:
-              // - 80
-              // instead of
-              // ports:
-              //   - 80
-              new_indent: if indent_array_in_object then cindent + '  ' else cindent,
-              space: '\n' + self.new_indent,
-            } else if std.isObject(value) && std.length(value) > 0 then {
-              new_indent: cindent + '  ',
-              space: '\n' + self.new_indent,
-            } else {
-              // In this case, new_indent is only used in the case of multi-line strings.
-              new_indent: cindent,
-              space: ' ',
-            };
-          local lines = [
-            std.escapeStringJson(k) + ':' + param.space + aux(v[k], path + [k], param.new_indent)
-            for k in std.objectFields(v)
-            for param in [params(v[k])]
-          ];
-          std.join('\n' + cindent, lines);
-    aux(value, [], ''),
+  manifestYamlDocImpl:: $intrinsic(manifestYamlDocImpl),
+
+  manifestYamlDoc(value, indent_array_in_object=false):: std.manifestYamlDocImpl(value, indent_array_in_object),
 
   manifestYamlStream(value, indent_array_in_object=false, c_document_end=true)::
     if !std.isArray(value) then