difftreelog
feat(cli) --no-trailing-newline
in: master
4 files changed
bindings/jsonnet/src/lib.rsdiffbeforeafterboth--- a/bindings/jsonnet/src/lib.rs
+++ b/bindings/jsonnet/src/lib.rs
@@ -44,8 +44,8 @@
/// If this does not match `LIB_JSONNET_VERSION`
/// then there is a mismatch between header and compiled library.
#[unsafe(no_mangle)]
-pub extern "C" fn jsonnet_version() -> &'static [u8; 12] {
- b"v0.22.0-rc1\0"
+pub extern "C" fn jsonnet_version() -> &'static [u8; 8] {
+ b"v0.22.0\0"
}
unsafe fn parse_path(input: &CStr) -> Cow<'_, Path> {
@@ -105,6 +105,7 @@
pub struct VM {
state: State,
manifest_format: Box<dyn ManifestFormat>,
+ trailing_newline: bool,
trace_format: Box<dyn TraceFormat>,
tla_args: FxHashMap<IStr, TlaArg>,
}
@@ -142,6 +143,7 @@
state,
manifest_format: Box::new(JsonFormat::default()),
trace_format: Box::new(CompactFormat::default()),
+ trailing_newline: true,
tla_args: FxHashMap::new(),
}))
}
@@ -181,6 +183,12 @@
};
}
+/// Enable/disable trailing newline in manifested/string output.
+#[unsafe(no_mangle)]
+pub extern "C" fn jsonnet_set_trailing_newline(vm: &mut VM, enable: c_int) {
+ vm.trailing_newline = enable != 0;
+}
+
/// Allocate, resize, or free a buffer. This will abort if the memory cannot be allocated. It will
/// only return NULL if sz was zero.
///
@@ -285,7 +293,10 @@
.and_then(|val| apply_tla(&vm.tla_args, val))
.and_then(|val| val.manifest(&vm.manifest_format))
{
- Ok(v) => {
+ Ok(mut v) => {
+ if vm.trailing_newline {
+ v.push('\n');
+ }
*error = 0;
CString::new(&*v as &str).unwrap().into_raw()
}
@@ -312,7 +323,7 @@
Ok(out)
}
-fn multi_to_raw(multi: Vec<(IStr, IStr)>) -> *const c_char {
+fn multi_to_raw(multi: Vec<(IStr, IStr)>, trailing_newline: bool) -> *const c_char {
let mut out = Vec::new();
for (i, (k, v)) in multi.iter().enumerate() {
if i != 0 {
@@ -321,6 +332,9 @@
out.extend_from_slice(k.as_bytes());
out.push(0);
out.extend_from_slice(v.as_bytes());
+ if trailing_newline {
+ out.push(b'\n');
+ }
}
out.push(0);
out.push(0);
@@ -345,7 +359,7 @@
{
Ok(v) => {
*error = 0;
- multi_to_raw(v)
+ multi_to_raw(v, vm.trailing_newline)
}
Err(e) => {
*error = 1;
@@ -374,7 +388,7 @@
{
Ok(v) => {
*error = 0;
- multi_to_raw(v)
+ multi_to_raw(v, vm.trailing_newline)
}
Err(e) => {
*error = 1;
@@ -396,13 +410,16 @@
Ok(out)
}
-fn stream_to_raw(multi: Vec<IStr>) -> *const c_char {
+fn stream_to_raw(multi: Vec<IStr>, trailing_newline: bool) -> *const c_char {
let mut out = Vec::new();
for (i, v) in multi.iter().enumerate() {
if i != 0 {
out.push(0);
}
out.extend_from_slice(v.as_bytes());
+ if trailing_newline {
+ out.push(b'\n');
+ }
}
out.push(0);
out.push(0);
@@ -427,7 +444,7 @@
{
Ok(v) => {
*error = 0;
- stream_to_raw(v)
+ stream_to_raw(v, vm.trailing_newline)
}
Err(e) => {
*error = 1;
@@ -456,7 +473,7 @@
{
Ok(v) => {
*error = 0;
- stream_to_raw(v)
+ stream_to_raw(v, vm.trailing_newline)
}
Err(e) => {
*error = 1;
cmds/jrsonnet/src/main.rsdiffbeforeafterboth--- a/cmds/jrsonnet/src/main.rs
+++ b/cmds/jrsonnet/src/main.rs
@@ -238,7 +238,7 @@
data.manifest(&manifest_format)
.with_description(|| format!("manifesting {field}"))?,
)?;
- if manifest_format.file_trailing_newline() {
+ if !opts.manifest.no_trailing_newline {
writeln!(file)?;
}
file.flush()?;
crates/jrsonnet-cli/src/manifest.rsdiffbeforeafterboth--- a/crates/jrsonnet-cli/src/manifest.rs
+++ b/crates/jrsonnet-cli/src/manifest.rs
@@ -38,6 +38,9 @@
/// [default: 3 for json, 2 for yaml/toml]
#[clap(long)]
line_padding: Option<usize>,
+ /// No not add a trailing newline to the output
+ #[clap(long)]
+ pub no_trailing_newline: bool,
/// Preserve order in object manifestification
#[cfg(feature = "exp-preserve-order")]
#[clap(long)]
crates/jrsonnet-evaluator/src/manifest.rsdiffbeforeafterboth1use std::{borrow::Cow, fmt::Write, hint::black_box, ptr};23use crate::{4 Error, Result, ResultExt, Val, bail, evaluate::ensure_sufficient_stack, in_description_frame,5};67pub trait ManifestFormat {8 fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()>;9 fn manifest(&self, val: Val) -> Result<String> {10 let mut out = String::new();11 self.manifest_buf(val, &mut out)?;12 Ok(out)13 }14 /// When outputing to file, is it safe to append a trailing newline (I.e newline won't change15 /// the meaning).16 ///17 /// Default implementation returns `true`18 fn file_trailing_newline(&self) -> bool {19 true20 }21}22impl<T> ManifestFormat for Box<T>23where24 T: ManifestFormat + ?Sized,25{26 fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {27 let inner = &**self;28 inner.manifest_buf(val, buf)29 }30 fn file_trailing_newline(&self) -> bool {31 let inner = &**self;32 inner.file_trailing_newline()33 }34}35impl<T> ManifestFormat for &'_ T36where37 T: ManifestFormat + ?Sized,38{39 fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {40 let inner = &**self;41 inner.manifest_buf(val, buf)42 }43 fn file_trailing_newline(&self) -> bool {44 let inner = &**self;45 inner.file_trailing_newline()46 }47}4849pub struct BlackBoxFormat;50impl ManifestFormat for BlackBoxFormat {51 #[allow(clippy::only_used_in_recursion)]52 fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {53 match val {54 Val::Bool(v) => {55 black_box(v);56 }57 val @ Val::Null => {58 black_box(val);59 }60 Val::Str(str_value) => {61 black_box(format!("{str_value}"));62 }63 Val::Num(num_value) => {64 black_box(num_value);65 }66 Val::Arr(arr_value) => {67 for ele in arr_value.iter() {68 let ele = ele?;69 self.manifest_buf(ele, buf)?;70 }71 }72 Val::Obj(obj_value) => {73 for (name, value) in obj_value.iter(74 #[cfg(feature = "exp-preserve-order")]75 true,76 ) {77 black_box(name);78 let value = value?;79 self.manifest_buf(value, buf)?;80 }81 }82 Val::Func(func_val) => {83 black_box(func_val);84 bail!("tried to manifest function")85 }86 #[cfg(feature = "exp-bigint")]87 Val::BigInt(n) => {88 black_box(n);89 }90 }91 Ok(())92 }93}9495#[derive(PartialEq, Eq, Clone, Copy)]96enum JsonFormatting {97 // Applied in manifestification98 Manifest,99 /// Used for std.manifestJson100 /// Empty array/objects extends to "[\n\n]" instead of "[ ]" as in manifest101 Std,102 /// No line breaks, used in `obj+''`103 ToString,104 /// Minified json105 Minify,106}107108pub struct JsonFormat<'s> {109 padding: Cow<'s, str>,110 mtype: JsonFormatting,111 newline: &'s str,112 key_val_sep: &'s str,113 #[cfg(feature = "exp-preserve-order")]114 preserve_order: bool,115 #[cfg(feature = "exp-bigint")]116 preserve_bigints: bool,117}118119impl<'s> JsonFormat<'s> {120 // Minifying format121 pub fn minify(#[cfg(feature = "exp-preserve-order")] preserve_order: bool) -> Self {122 Self {123 padding: Cow::Borrowed(""),124 mtype: JsonFormatting::Minify,125 newline: "\n",126 key_val_sep: ":",127 #[cfg(feature = "exp-preserve-order")]128 preserve_order,129 #[cfg(feature = "exp-bigint")]130 preserve_bigints: false,131 }132 }133 /// Same format as std.toString, except does not keeps top-level string as-is134 /// To avoid confusion, the format is private in jrsonnet, use [`ToStringFormat`] instead135 const fn std_to_string_helper() -> Self {136 Self {137 padding: Cow::Borrowed(""),138 mtype: JsonFormatting::ToString,139 newline: "\n",140 key_val_sep: ": ",141 #[cfg(feature = "exp-preserve-order")]142 preserve_order: false,143 #[cfg(feature = "exp-bigint")]144 preserve_bigints: false,145 }146 }147 pub fn std_to_json(148 padding: String,149 newline: &'s str,150 key_val_sep: &'s str,151 #[cfg(feature = "exp-preserve-order")] preserve_order: bool,152 ) -> Self {153 Self {154 padding: Cow::Owned(padding),155 mtype: JsonFormatting::Std,156 newline,157 key_val_sep,158 #[cfg(feature = "exp-preserve-order")]159 preserve_order,160 #[cfg(feature = "exp-bigint")]161 preserve_bigints: false,162 }163 }164 // Same format as CLI manifestification165 pub fn cli(166 padding: usize,167 #[cfg(feature = "exp-preserve-order")] preserve_order: bool,168 ) -> Self {169 if padding == 0 {170 return Self::minify(171 #[cfg(feature = "exp-preserve-order")]172 preserve_order,173 );174 }175 Self {176 padding: Cow::Owned(" ".repeat(padding)),177 mtype: JsonFormatting::Manifest,178 newline: "\n",179 key_val_sep: ": ",180 #[cfg(feature = "exp-preserve-order")]181 preserve_order,182 #[cfg(feature = "exp-bigint")]183 preserve_bigints: false,184 }185 }186 // Same format as CLI manifestification187 pub fn debug() -> Self {188 Self {189 padding: Cow::Borrowed(" "),190 mtype: JsonFormatting::Manifest,191 newline: "\n",192 key_val_sep: ": ",193 #[cfg(feature = "exp-preserve-order")]194 preserve_order: true,195 #[cfg(feature = "exp-bigint")]196 preserve_bigints: true,197 }198 }199}200impl Default for JsonFormat<'static> {201 fn default() -> Self {202 Self {203 padding: Cow::Borrowed(" "),204 mtype: JsonFormatting::Manifest,205 newline: "\n",206 key_val_sep: ": ",207 #[cfg(feature = "exp-preserve-order")]208 preserve_order: false,209 #[cfg(feature = "exp-bigint")]210 preserve_bigints: false,211 }212 }213}214215pub fn manifest_json_ex(val: &Val, options: &JsonFormat<'_>) -> Result<String> {216 let mut out = String::new();217 manifest_json_ex_buf(val, &mut out, &mut String::new(), options)?;218 Ok(out)219}220221#[allow(clippy::too_many_lines)]222fn manifest_json_ex_buf(223 val: &Val,224 buf: &mut String,225 cur_padding: &mut String,226 options: &JsonFormat<'_>,227) -> Result<()> {228 use JsonFormatting::*;229230 let mtype = options.mtype;231 match val {232 Val::Bool(v) => {233 if *v {234 buf.push_str("true");235 } else {236 buf.push_str("false");237 }238 }239 Val::Null => buf.push_str("null"),240 Val::Str(s) => {241 buf.reserve(2 + s.len());242 buf.push('"');243 s.chunks(&mut |c| {244 escape_string_json_buf_raw(c, buf);245 });246 buf.push('"');247 }248 Val::Num(n) => write!(buf, "{n}").unwrap(),249 #[cfg(feature = "exp-bigint")]250 Val::BigInt(n) => {251 if options.preserve_bigints {252 write!(buf, "{n}").unwrap();253 } else {254 write!(buf, "{:?}", n.to_string()).unwrap();255 }256 }257 Val::Arr(items) => ensure_sufficient_stack(|| {258 buf.push('[');259260 let old_len = cur_padding.len();261 cur_padding.push_str(&options.padding);262263 let mut had_items = false;264 for (i, item) in items.iter().enumerate() {265 had_items = true;266 let item = item.with_description(|| format!("elem <{i}> evaluation"))?;267268 if i != 0 {269 buf.push(',');270 }271 match mtype {272 Manifest | Std => {273 buf.push_str(options.newline);274 buf.push_str(cur_padding);275 }276 ToString if i != 0 => buf.push(' '),277 Minify | ToString => {}278 }279280 in_description_frame(281 || format!("elem <{i}> manifestification"),282 || manifest_json_ex_buf(&item, buf, cur_padding, options),283 )?;284 }285286 cur_padding.truncate(old_len);287288 match mtype {289 Manifest | ToString if !had_items => {290 // Empty array as "[ ]"291 buf.push(' ');292 }293 Manifest => {294 buf.push_str(options.newline);295 buf.push_str(cur_padding);296 }297 Std => {298 if !had_items {299 // Stdlib formats empty array as "[\n\n]"300 buf.push_str(options.newline);301 }302 buf.push_str(options.newline);303 buf.push_str(cur_padding);304 }305 Minify | ToString => {}306 }307308 buf.push(']');309 Ok::<_, Error>(())310 })?,311 Val::Obj(obj) => ensure_sufficient_stack(|| {312 obj.run_assertions()?;313 buf.push('{');314315 let old_len = cur_padding.len();316 cur_padding.push_str(&options.padding);317318 let mut had_fields = false;319 for (i, (key, value)) in obj320 .iter(321 #[cfg(feature = "exp-preserve-order")]322 options.preserve_order,323 )324 .enumerate()325 {326 had_fields = true;327 let value = value.with_description(|| format!("field <{key}> evaluation"))?;328329 if i != 0 {330 buf.push(',');331 }332 match mtype {333 Manifest | Std => {334 buf.push_str(options.newline);335 buf.push_str(cur_padding);336 }337 ToString if i != 0 => buf.push(' '),338 Minify | ToString => {}339 }340341 escape_string_json_buf(&key, buf);342 buf.push_str(options.key_val_sep);343 in_description_frame(344 || format!("field <{key}> manifestification"),345 || manifest_json_ex_buf(&value, buf, cur_padding, options),346 )?;347 }348349 cur_padding.truncate(old_len);350351 match mtype {352 Manifest | ToString if !had_fields => {353 // Empty object as "{ }"354 buf.push(' ');355 }356 Manifest => {357 buf.push_str(options.newline);358 buf.push_str(cur_padding);359 }360 Std => {361 if !had_fields {362 // Stdlib formats empty object as "{\n\n}"363 buf.push_str(options.newline);364 }365 buf.push_str(options.newline);366 buf.push_str(cur_padding);367 }368 Minify | ToString => {}369 }370371 buf.push('}');372 Ok::<_, Error>(())373 })?,374 Val::Func(_) => bail!("tried to manifest function"),375 }376 Ok(())377}378379impl ManifestFormat for JsonFormat<'_> {380 fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {381 manifest_json_ex_buf(&val, buf, &mut String::new(), self)382 }383}384385/// Same as [`JsonFormat`] with pre-set options, but top-level string is serialized as-is,386/// without quoting.387pub struct ToStringFormat;388impl ManifestFormat for ToStringFormat {389 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {390 const JSON_TO_STRING: JsonFormat = JsonFormat::std_to_string_helper();391 if let Some(str) = val.as_str() {392 out.push_str(&str);393 return Ok(());394 }395 #[cfg(feature = "exp-bigint")]396 if let Some(int) = val.as_bigint() {397 out.push_str(&int.to_str_radix(10));398 return Ok(());399 }400 JSON_TO_STRING.manifest_buf(val, out)401 }402 fn file_trailing_newline(&self) -> bool {403 false404 }405}406pub struct StringFormat;407impl ManifestFormat for StringFormat {408 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {409 let Val::Str(s) = val else {410 bail!(411 "output should be string for string manifest format, got {}",412 val.value_type()413 )414 };415 write!(out, "{s}").unwrap();416 Ok(())417 }418 fn file_trailing_newline(&self) -> bool {419 false420 }421}422423pub struct YamlStreamFormat<I> {424 inner: I,425 c_document_end: bool,426 end_newline: bool,427}428impl<I> YamlStreamFormat<I> {429 pub fn std_yaml_stream(inner: I, c_document_end: bool) -> Self {430 Self {431 inner,432 c_document_end,433 // Stdlib format always inserts useless newline at the end434 end_newline: true,435 }436 }437 pub fn cli(inner: I) -> Self {438 Self {439 inner,440 c_document_end: true,441 end_newline: false,442 }443 }444}445impl<I: ManifestFormat> ManifestFormat for YamlStreamFormat<I> {446 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {447 let Val::Arr(arr) = val else {448 bail!(449 "output should be array for yaml stream format, got {}",450 val.value_type()451 )452 };453 for (i, v) in arr.iter().enumerate() {454 if i != 0 {455 out.push('\n');456 }457 let v = v.with_description(|| format!("elem <{i}> evaluation"))?;458 out.push_str("---\n");459 in_description_frame(460 || format!("elem <{i}> manifestification"),461 || self.inner.manifest_buf(v, out),462 )?;463 }464 if self.c_document_end {465 out.push('\n');466 out.push_str("...");467 }468 if self.end_newline {469 out.push('\n');470 }471 Ok(())472 }473}474475pub fn escape_string_json(s: &str) -> String {476 let mut buf = String::new();477 escape_string_json_buf(s, &mut buf);478 buf479}480481// Json string encoding was borrowed from https://github.com/serde-rs/json482483const BB: u8 = b'b'; // \x08484const TT: u8 = b't'; // \x09485const NN: u8 = b'n'; // \x0A486const FF: u8 = b'f'; // \x0C487const RR: u8 = b'r'; // \x0D488const QU: u8 = b'"'; // \x22489const BS: u8 = b'\\'; // \x5C490const UU: u8 = b'u'; // \x00...\x1F except the ones above491const __: u8 = 0;492493// Lookup table of escape sequences. A value of b'x' at index i means that byte494// i is escaped as "\x" in JSON. A value of 0 means that byte i is not escaped.495static ESCAPE: [u8; 256] = [496 // 1 2 3 4 5 6 7 8 9 A B C D E F497 UU, UU, UU, UU, UU, UU, UU, UU, BB, TT, NN, UU, FF, RR, UU, UU, // 0498 UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, // 1499 __, __, QU, __, __, __, __, __, __, __, __, __, __, __, __, __, // 2500 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 3501 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 4502 __, __, __, __, __, __, __, __, __, __, __, __, BS, __, __, __, // 5503 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 6504 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 7505 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 8506 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 9507 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // A508 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // B509 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // C510 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // D511 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // E512 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // F513];514515pub fn escape_string_json_buf(value: &str, buf: &mut String) {516 buf.reserve_exact(value.len() + 2);517 buf.push('"');518 escape_string_json_buf_raw(value, buf);519 buf.push('"');520}521522fn escape_string_json_buf_raw(value: &str, buf: &mut String) {523 // Safety: we only write correct utf-8 in this function524 let buf: &mut Vec<u8> = unsafe { &mut *ptr::from_mut(buf).cast::<Vec<u8>>() };525 let bytes = value.as_bytes();526527 let mut start = 0;528529 for (i, &byte) in bytes.iter().enumerate() {530 let escape = ESCAPE[byte as usize];531 if escape == __ {532 continue;533 }534535 if start < i {536 buf.extend_from_slice(&bytes[start..i]);537 }538 start = i + 1;539540 match escape {541 self::BB | self::TT | self::NN | self::FF | self::RR | self::QU | self::BS => {542 buf.extend_from_slice(&[b'\\', escape]);543 }544 self::UU => {545 static HEX_DIGITS: [u8; 16] = *b"0123456789abcdef";546 let bytes = &[547 b'\\',548 b'u',549 b'0',550 b'0',551 HEX_DIGITS[(byte >> 4) as usize],552 HEX_DIGITS[(byte & 0xF) as usize],553 ];554 buf.extend_from_slice(bytes);555 }556 _ => unreachable!(),557 }558 }559560 if start == bytes.len() {561 return;562 }563564 buf.extend_from_slice(&bytes[start..]);565}1use std::{borrow::Cow, fmt::Write, hint::black_box, ptr};23use crate::{4 Error, Result, ResultExt, Val, bail, evaluate::ensure_sufficient_stack, in_description_frame,5};67pub trait ManifestFormat {8 fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()>;9 fn manifest(&self, val: Val) -> Result<String> {10 let mut out = String::new();11 self.manifest_buf(val, &mut out)?;12 Ok(out)13 }14}15impl<T> ManifestFormat for Box<T>16where17 T: ManifestFormat + ?Sized,18{19 fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {20 let inner = &**self;21 inner.manifest_buf(val, buf)22 }23}24impl<T> ManifestFormat for &'_ T25where26 T: ManifestFormat + ?Sized,27{28 fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {29 let inner = &**self;30 inner.manifest_buf(val, buf)31 }32}3334pub struct BlackBoxFormat;35impl ManifestFormat for BlackBoxFormat {36 #[allow(clippy::only_used_in_recursion)]37 fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {38 match val {39 Val::Bool(v) => {40 black_box(v);41 }42 val @ Val::Null => {43 black_box(val);44 }45 Val::Str(str_value) => {46 black_box(format!("{str_value}"));47 }48 Val::Num(num_value) => {49 black_box(num_value);50 }51 Val::Arr(arr_value) => {52 for ele in arr_value.iter() {53 let ele = ele?;54 self.manifest_buf(ele, buf)?;55 }56 }57 Val::Obj(obj_value) => {58 for (name, value) in obj_value.iter(59 #[cfg(feature = "exp-preserve-order")]60 true,61 ) {62 black_box(name);63 let value = value?;64 self.manifest_buf(value, buf)?;65 }66 }67 Val::Func(func_val) => {68 black_box(func_val);69 bail!("tried to manifest function")70 }71 #[cfg(feature = "exp-bigint")]72 Val::BigInt(n) => {73 black_box(n);74 }75 }76 Ok(())77 }78}7980#[derive(PartialEq, Eq, Clone, Copy)]81enum JsonFormatting {82 // Applied in manifestification83 Manifest,84 /// Used for std.manifestJson85 /// Empty array/objects extends to "[\n\n]" instead of "[ ]" as in manifest86 Std,87 /// No line breaks, used in `obj+''`88 ToString,89 /// Minified json90 Minify,91}9293pub struct JsonFormat<'s> {94 padding: Cow<'s, str>,95 mtype: JsonFormatting,96 newline: &'s str,97 key_val_sep: &'s str,98 #[cfg(feature = "exp-preserve-order")]99 preserve_order: bool,100 #[cfg(feature = "exp-bigint")]101 preserve_bigints: bool,102}103104impl<'s> JsonFormat<'s> {105 // Minifying format106 pub fn minify(#[cfg(feature = "exp-preserve-order")] preserve_order: bool) -> Self {107 Self {108 padding: Cow::Borrowed(""),109 mtype: JsonFormatting::Minify,110 newline: "\n",111 key_val_sep: ":",112 #[cfg(feature = "exp-preserve-order")]113 preserve_order,114 #[cfg(feature = "exp-bigint")]115 preserve_bigints: false,116 }117 }118 /// Same format as std.toString, except does not keeps top-level string as-is119 /// To avoid confusion, the format is private in jrsonnet, use [`ToStringFormat`] instead120 const fn std_to_string_helper() -> Self {121 Self {122 padding: Cow::Borrowed(""),123 mtype: JsonFormatting::ToString,124 newline: "\n",125 key_val_sep: ": ",126 #[cfg(feature = "exp-preserve-order")]127 preserve_order: false,128 #[cfg(feature = "exp-bigint")]129 preserve_bigints: false,130 }131 }132 pub fn std_to_json(133 padding: String,134 newline: &'s str,135 key_val_sep: &'s str,136 #[cfg(feature = "exp-preserve-order")] preserve_order: bool,137 ) -> Self {138 Self {139 padding: Cow::Owned(padding),140 mtype: JsonFormatting::Std,141 newline,142 key_val_sep,143 #[cfg(feature = "exp-preserve-order")]144 preserve_order,145 #[cfg(feature = "exp-bigint")]146 preserve_bigints: false,147 }148 }149 // Same format as CLI manifestification150 pub fn cli(151 padding: usize,152 #[cfg(feature = "exp-preserve-order")] preserve_order: bool,153 ) -> Self {154 if padding == 0 {155 return Self::minify(156 #[cfg(feature = "exp-preserve-order")]157 preserve_order,158 );159 }160 Self {161 padding: Cow::Owned(" ".repeat(padding)),162 mtype: JsonFormatting::Manifest,163 newline: "\n",164 key_val_sep: ": ",165 #[cfg(feature = "exp-preserve-order")]166 preserve_order,167 #[cfg(feature = "exp-bigint")]168 preserve_bigints: false,169 }170 }171 // Same format as CLI manifestification172 pub fn debug() -> Self {173 Self {174 padding: Cow::Borrowed(" "),175 mtype: JsonFormatting::Manifest,176 newline: "\n",177 key_val_sep: ": ",178 #[cfg(feature = "exp-preserve-order")]179 preserve_order: true,180 #[cfg(feature = "exp-bigint")]181 preserve_bigints: true,182 }183 }184}185impl Default for JsonFormat<'static> {186 fn default() -> Self {187 Self {188 padding: Cow::Borrowed(" "),189 mtype: JsonFormatting::Manifest,190 newline: "\n",191 key_val_sep: ": ",192 #[cfg(feature = "exp-preserve-order")]193 preserve_order: false,194 #[cfg(feature = "exp-bigint")]195 preserve_bigints: false,196 }197 }198}199200pub fn manifest_json_ex(val: &Val, options: &JsonFormat<'_>) -> Result<String> {201 let mut out = String::new();202 manifest_json_ex_buf(val, &mut out, &mut String::new(), options)?;203 Ok(out)204}205206#[allow(clippy::too_many_lines)]207fn manifest_json_ex_buf(208 val: &Val,209 buf: &mut String,210 cur_padding: &mut String,211 options: &JsonFormat<'_>,212) -> Result<()> {213 use JsonFormatting::*;214215 let mtype = options.mtype;216 match val {217 Val::Bool(v) => {218 if *v {219 buf.push_str("true");220 } else {221 buf.push_str("false");222 }223 }224 Val::Null => buf.push_str("null"),225 Val::Str(s) => {226 buf.reserve(2 + s.len());227 buf.push('"');228 s.chunks(&mut |c| {229 escape_string_json_buf_raw(c, buf);230 });231 buf.push('"');232 }233 Val::Num(n) => write!(buf, "{n}").unwrap(),234 #[cfg(feature = "exp-bigint")]235 Val::BigInt(n) => {236 if options.preserve_bigints {237 write!(buf, "{n}").unwrap();238 } else {239 write!(buf, "{:?}", n.to_string()).unwrap();240 }241 }242 Val::Arr(items) => ensure_sufficient_stack(|| {243 buf.push('[');244245 let old_len = cur_padding.len();246 cur_padding.push_str(&options.padding);247248 let mut had_items = false;249 for (i, item) in items.iter().enumerate() {250 had_items = true;251 let item = item.with_description(|| format!("elem <{i}> evaluation"))?;252253 if i != 0 {254 buf.push(',');255 }256 match mtype {257 Manifest | Std => {258 buf.push_str(options.newline);259 buf.push_str(cur_padding);260 }261 ToString if i != 0 => buf.push(' '),262 Minify | ToString => {}263 }264265 in_description_frame(266 || format!("elem <{i}> manifestification"),267 || manifest_json_ex_buf(&item, buf, cur_padding, options),268 )?;269 }270271 cur_padding.truncate(old_len);272273 match mtype {274 Manifest | ToString if !had_items => {275 // Empty array as "[ ]"276 buf.push(' ');277 }278 Manifest => {279 buf.push_str(options.newline);280 buf.push_str(cur_padding);281 }282 Std => {283 if !had_items {284 // Stdlib formats empty array as "[\n\n]"285 buf.push_str(options.newline);286 }287 buf.push_str(options.newline);288 buf.push_str(cur_padding);289 }290 Minify | ToString => {}291 }292293 buf.push(']');294 Ok::<_, Error>(())295 })?,296 Val::Obj(obj) => ensure_sufficient_stack(|| {297 obj.run_assertions()?;298 buf.push('{');299300 let old_len = cur_padding.len();301 cur_padding.push_str(&options.padding);302303 let mut had_fields = false;304 for (i, (key, value)) in obj305 .iter(306 #[cfg(feature = "exp-preserve-order")]307 options.preserve_order,308 )309 .enumerate()310 {311 had_fields = true;312 let value = value.with_description(|| format!("field <{key}> evaluation"))?;313314 if i != 0 {315 buf.push(',');316 }317 match mtype {318 Manifest | Std => {319 buf.push_str(options.newline);320 buf.push_str(cur_padding);321 }322 ToString if i != 0 => buf.push(' '),323 Minify | ToString => {}324 }325326 escape_string_json_buf(&key, buf);327 buf.push_str(options.key_val_sep);328 in_description_frame(329 || format!("field <{key}> manifestification"),330 || manifest_json_ex_buf(&value, buf, cur_padding, options),331 )?;332 }333334 cur_padding.truncate(old_len);335336 match mtype {337 Manifest | ToString if !had_fields => {338 // Empty object as "{ }"339 buf.push(' ');340 }341 Manifest => {342 buf.push_str(options.newline);343 buf.push_str(cur_padding);344 }345 Std => {346 if !had_fields {347 // Stdlib formats empty object as "{\n\n}"348 buf.push_str(options.newline);349 }350 buf.push_str(options.newline);351 buf.push_str(cur_padding);352 }353 Minify | ToString => {}354 }355356 buf.push('}');357 Ok::<_, Error>(())358 })?,359 Val::Func(_) => bail!("tried to manifest function"),360 }361 Ok(())362}363364impl ManifestFormat for JsonFormat<'_> {365 fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {366 manifest_json_ex_buf(&val, buf, &mut String::new(), self)367 }368}369370/// Same as [`JsonFormat`] with pre-set options, but top-level string is serialized as-is,371/// without quoting.372pub struct ToStringFormat;373impl ManifestFormat for ToStringFormat {374 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {375 const JSON_TO_STRING: JsonFormat = JsonFormat::std_to_string_helper();376 if let Some(str) = val.as_str() {377 out.push_str(&str);378 return Ok(());379 }380 #[cfg(feature = "exp-bigint")]381 if let Some(int) = val.as_bigint() {382 out.push_str(&int.to_str_radix(10));383 return Ok(());384 }385 JSON_TO_STRING.manifest_buf(val, out)386 }387}388pub struct StringFormat;389impl ManifestFormat for StringFormat {390 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {391 let Val::Str(s) = val else {392 bail!(393 "output should be string for string manifest format, got {}",394 val.value_type()395 )396 };397 write!(out, "{s}").unwrap();398 Ok(())399 }400}401402pub struct YamlStreamFormat<I> {403 inner: I,404 c_document_end: bool,405 end_newline: bool,406}407impl<I> YamlStreamFormat<I> {408 pub fn std_yaml_stream(inner: I, c_document_end: bool) -> Self {409 Self {410 inner,411 c_document_end,412 // Stdlib format always inserts useless newline at the end413 end_newline: true,414 }415 }416 pub fn cli(inner: I) -> Self {417 Self {418 inner,419 c_document_end: true,420 end_newline: false,421 }422 }423}424impl<I: ManifestFormat> ManifestFormat for YamlStreamFormat<I> {425 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {426 let Val::Arr(arr) = val else {427 bail!(428 "output should be array for yaml stream format, got {}",429 val.value_type()430 )431 };432 for (i, v) in arr.iter().enumerate() {433 if i != 0 {434 out.push('\n');435 }436 let v = v.with_description(|| format!("elem <{i}> evaluation"))?;437 out.push_str("---\n");438 in_description_frame(439 || format!("elem <{i}> manifestification"),440 || self.inner.manifest_buf(v, out),441 )?;442 }443 if self.c_document_end {444 out.push('\n');445 out.push_str("...");446 }447 if self.end_newline {448 out.push('\n');449 }450 Ok(())451 }452}453454pub fn escape_string_json(s: &str) -> String {455 let mut buf = String::new();456 escape_string_json_buf(s, &mut buf);457 buf458}459460// Json string encoding was borrowed from https://github.com/serde-rs/json461462const BB: u8 = b'b'; // \x08463const TT: u8 = b't'; // \x09464const NN: u8 = b'n'; // \x0A465const FF: u8 = b'f'; // \x0C466const RR: u8 = b'r'; // \x0D467const QU: u8 = b'"'; // \x22468const BS: u8 = b'\\'; // \x5C469const UU: u8 = b'u'; // \x00...\x1F except the ones above470const __: u8 = 0;471472// Lookup table of escape sequences. A value of b'x' at index i means that byte473// i is escaped as "\x" in JSON. A value of 0 means that byte i is not escaped.474static ESCAPE: [u8; 256] = [475 // 1 2 3 4 5 6 7 8 9 A B C D E F476 UU, UU, UU, UU, UU, UU, UU, UU, BB, TT, NN, UU, FF, RR, UU, UU, // 0477 UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, // 1478 __, __, QU, __, __, __, __, __, __, __, __, __, __, __, __, __, // 2479 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 3480 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 4481 __, __, __, __, __, __, __, __, __, __, __, __, BS, __, __, __, // 5482 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 6483 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 7484 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 8485 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 9486 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // A487 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // B488 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // C489 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // D490 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // E491 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // F492];493494pub fn escape_string_json_buf(value: &str, buf: &mut String) {495 buf.reserve_exact(value.len() + 2);496 buf.push('"');497 escape_string_json_buf_raw(value, buf);498 buf.push('"');499}500501fn escape_string_json_buf_raw(value: &str, buf: &mut String) {502 // Safety: we only write correct utf-8 in this function503 let buf: &mut Vec<u8> = unsafe { &mut *ptr::from_mut(buf).cast::<Vec<u8>>() };504 let bytes = value.as_bytes();505506 let mut start = 0;507508 for (i, &byte) in bytes.iter().enumerate() {509 let escape = ESCAPE[byte as usize];510 if escape == __ {511 continue;512 }513514 if start < i {515 buf.extend_from_slice(&bytes[start..i]);516 }517 start = i + 1;518519 match escape {520 self::BB | self::TT | self::NN | self::FF | self::RR | self::QU | self::BS => {521 buf.extend_from_slice(&[b'\\', escape]);522 }523 self::UU => {524 static HEX_DIGITS: [u8; 16] = *b"0123456789abcdef";525 let bytes = &[526 b'\\',527 b'u',528 b'0',529 b'0',530 HEX_DIGITS[(byte >> 4) as usize],531 HEX_DIGITS[(byte & 0xF) as usize],532 ];533 buf.extend_from_slice(bytes);534 }535 _ => unreachable!(),536 }537 }538539 if start == bytes.len() {540 return;541 }542543 buf.extend_from_slice(&bytes[start..]);544}