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}