difftreelog
perf implement std.prune in native
in: master
6 files changed
cmds/jrsonnet-fmt/src/main.rsdiffbeforeafterboth1use std::{2 any::type_name,3 fs,4 io::{self, Write},5 path::PathBuf,6 process,7 rc::Rc,8};910use children::{children_between, trivia_before};11use clap::Parser;12use dprint_core::formatting::{13 condition_helpers::is_multiple_lines, condition_resolvers::true_resolver,14 ConditionResolverContext, LineNumber, PrintItems, PrintOptions,15};16use jrsonnet_rowan_parser::{17 nodes::{18 Arg, ArgsDesc, Assertion, BinaryOperator, Bind, CompSpec, Destruct, DestructArrayPart,19 DestructRest, Expr, ExprBase, FieldName, ForSpec, IfSpec, ImportKind, Literal, Member,20 Name, Number, ObjBody, ObjLocal, ParamsDesc, SliceDesc, SourceFile, Stmt, Suffix, Text,21 UnaryOperator, Visibility,22 },23 AstNode, AstToken as _, SyntaxToken,24};2526use crate::{27 children::trivia_after,28 comments::{format_comments, CommentLocation},29};3031mod children;32mod comments;33#[cfg(test)]34mod tests;3536pub trait Printable {37 fn print(&self, out: &mut PrintItems);38}3940macro_rules! pi {41 (@i; $($t:tt)*) => {{42 #[allow(unused_mut)]43 let mut o = dprint_core::formatting::PrintItems::new();44 pi!(@s; o: $($t)*);45 o46 }};47 (@s; $o:ident: str($e:expr $(,)?) $($t:tt)*) => {{48 $o.push_str($e);49 pi!(@s; $o: $($t)*);50 }};51 (@s; $o:ident: string($e:expr $(,)?) $($t:tt)*) => {{52 $o.push_string($e);53 pi!(@s; $o: $($t)*);54 }};55 (@s; $o:ident: nl $($t:tt)*) => {{56 $o.push_signal(dprint_core::formatting::Signal::NewLine);57 pi!(@s; $o: $($t)*);58 }};59 (@s; $o:ident: tab $($t:tt)*) => {{60 $o.push_signal(dprint_core::formatting::Signal::Tab);61 pi!(@s; $o: $($t)*);62 }};63 (@s; $o:ident: >i $($t:tt)*) => {{64 $o.push_signal(dprint_core::formatting::Signal::StartIndent);65 pi!(@s; $o: $($t)*);66 }};67 (@s; $o:ident: <i $($t:tt)*) => {{68 $o.push_signal(dprint_core::formatting::Signal::FinishIndent);69 pi!(@s; $o: $($t)*);70 }};71 (@s; $o:ident: info($v:expr) $($t:tt)*) => {{72 $o.push_info($v);73 pi!(@s; $o: $($t)*);74 }};75 (@s; $o:ident: if($s:literal, $cond:expr, $($i:tt)*) $($t:tt)*) => {{76 $o.push_condition(dprint_core::formatting::conditions::if_true(77 $s,78 $cond.clone(),79 {80 let mut o = PrintItems::new();81 p!(o, $($i)*);82 o83 },84 ));85 pi!(@s; $o: $($t)*);86 }};87 (@s; $o:ident: if_else($s:literal, $cond:expr, $($i:tt)*)($($e:tt)+) $($t:tt)*) => {{88 $o.push_condition(dprint_core::formatting::conditions::if_true_or(89 $s,90 $cond.clone(),91 {92 let mut o = PrintItems::new();93 p!(o, $($i)*);94 o95 },96 {97 let mut o = PrintItems::new();98 p!(o, $($e)*);99 o100 },101 ));102 pi!(@s; $o: $($t)*);103 }};104 (@s; $o:ident: if_not($s:literal, $cond:expr, $($e:tt)*) $($t:tt)*) => {{105 $o.push_condition(dprint_core::formatting::conditions::if_true_or(106 $s,107 $cond.clone(),108 {109 let o = PrintItems::new();110 o111 },112 {113 let mut o = PrintItems::new();114 p!(o, $($e)*);115 o116 },117 ));118 pi!(@s; $o: $($t)*);119 }};120 (@s; $o:ident: {$expr:expr} $($t:tt)*) => {{121 $expr.print($o);122 pi!(@s; $o: $($t)*);123 }};124 (@s; $o:ident: items($expr:expr) $($t:tt)*) => {{125 $o.extend($expr);126 pi!(@s; $o: $($t)*);127 }};128 (@s; $o:ident: if ($e:expr)($($then:tt)*) $($t:tt)*) => {{129 if $e {130 pi!(@s; $o: $($then)*);131 }132 pi!(@s; $o: $($t)*);133 }};134 (@s; $o:ident: ifelse ($e:expr)($($then:tt)*)($($else:tt)*) $($t:tt)*) => {{135 if $e {136 pi!(@s; $o: $($then)*);137 } else {138 pi!(@s; $o: $($else)*);139 }140 pi!(@s; $o: $($t)*);141 }};142 (@s; $i:ident:) => {}143}144macro_rules! p {145 ($o:ident, $($t:tt)*) => {146 pi!(@s; $o: $($t)*)147 };148}149pub(crate) use p;150pub(crate) use pi;151152impl<P> Printable for Option<P>153where154 P: Printable,155{156 fn print(&self, out: &mut PrintItems) {157 if let Some(v) = self {158 v.print(out)159 } else {160 p!(161 out,162 string(format!(163 "/*missing {}*/",164 type_name::<P>().replace("jrsonnet_rowan_parser::generated::nodes::", "")165 ),)166 )167 }168 }169}170171impl Printable for SyntaxToken {172 fn print(&self, out: &mut PrintItems) {173 p!(out, string(self.to_string()))174 }175}176177impl Printable for Text {178 fn print(&self, out: &mut PrintItems) {179 p!(out, string(format!("{}", self)))180 }181}182impl Printable for Number {183 fn print(&self, out: &mut PrintItems) {184 p!(out, string(format!("{}", self)))185 }186}187188impl Printable for Name {189 fn print(&self, out: &mut PrintItems) {190 p!(out, { self.ident_lit() })191 }192}193194impl Printable for DestructRest {195 fn print(&self, out: &mut PrintItems) {196 p!(out, str("..."));197 if let Some(name) = self.into() {198 p!(out, { name });199 }200 }201}202203impl Printable for Destruct {204 fn print(&self, out: &mut PrintItems) {205 match self {206 Destruct::DestructFull(f) => {207 p!(out, { f.name() })208 }209 Destruct::DestructSkip(_) => p!(out, str("?")),210 Destruct::DestructArray(a) => {211 p!(out, str("[") >i nl);212 for el in a.destruct_array_parts() {213 match el {214 DestructArrayPart::DestructArrayElement(e) => {215 p!(out, {e.destruct()} str(",") nl)216 }217 DestructArrayPart::DestructRest(d) => {218 p!(out, {d} str(",") nl)219 }220 }221 }222 p!(out, <i str("]"));223 }224 Destruct::DestructObject(o) => {225 p!(out, str("{") >i nl);226 for item in o.destruct_object_fields() {227 p!(out, { item.field() });228 if let Some(des) = item.destruct() {229 p!(out, str(": ") {des})230 }231 if let Some(def) = item.expr() {232 p!(out, str(" = ") {def});233 }234 p!(out, str(",") nl);235 }236 if let Some(rest) = o.destruct_rest() {237 p!(out, {rest} nl)238 }239 p!(out, <i str("}"));240 }241 }242 }243}244245impl Printable for FieldName {246 fn print(&self, out: &mut PrintItems) {247 match self {248 FieldName::FieldNameFixed(f) => {249 if let Some(id) = f.id() {250 p!(out, { id })251 } else if let Some(str) = f.text() {252 p!(out, { str })253 } else {254 p!(out, str("/*missing FieldName*/"))255 }256 }257 FieldName::FieldNameDynamic(d) => {258 p!(out, str("[") {d.expr()} str("]"))259 }260 }261 }262}263264impl Printable for Visibility {265 fn print(&self, out: &mut PrintItems) {266 p!(out, string(self.to_string()))267 }268}269270impl Printable for ObjLocal {271 fn print(&self, out: &mut PrintItems) {272 p!(out, str("local ") {self.bind()})273 }274}275276impl Printable for Assertion {277 fn print(&self, out: &mut PrintItems) {278 p!(out, str("assert ") {self.condition()});279 if self.colon_token().is_some() || self.message().is_some() {280 p!(out, str(": ") {self.message()})281 }282 }283}284285impl Printable for ParamsDesc {286 fn print(&self, out: &mut PrintItems) {287 p!(out, str("(") >i nl);288 for param in self.params() {289 p!(out, { param.destruct() });290 if param.assign_token().is_some() || param.expr().is_some() {291 p!(out, str(" = ") {param.expr()})292 }293 p!(out, str(",") nl)294 }295 p!(out, <i str(")"));296 }297}298impl Printable for ArgsDesc {299 fn print(&self, out: &mut PrintItems) {300 let start = LineNumber::new("start");301 let end = LineNumber::new("end");302 let multi_line = Rc::new(move |condition_context: &mut ConditionResolverContext| {303 is_multiple_lines(condition_context, start, end).map(|v| !v)304 });305 p!(out, str("(") info(start) if("start args", multi_line, >i nl));306 let (children, end_comments) = children_between::<Arg>(307 self.syntax().clone(),308 self.l_paren_token().map(Into::into).as_ref(),309 self.r_paren_token().map(Into::into).as_ref(),310 None,311 );312 let mut args = children.into_iter().peekable();313 while let Some(ele) = args.next() {314 if ele.should_start_with_newline {315 p!(out, nl);316 }317 format_comments(&ele.before_trivia, CommentLocation::AboveItem, out);318 let arg = ele.value;319 if arg.name().is_some() || arg.assign_token().is_some() {320 p!(out, {arg.name()} str(" = "));321 }322 let comma_between = if args.peek().is_some() {323 true_resolver()324 } else {325 multi_line.clone()326 };327 p!(out, {arg.expr()} if("arg comma", comma_between, str(",") if_not("between args", multi_line, str(" "))));328 format_comments(&ele.inline_trivia, CommentLocation::ItemInline, out);329 p!(out, if("between args", multi_line, nl));330 }331 if end_comments.should_start_with_newline {332 p!(out, nl);333 }334 format_comments(&end_comments.trivia, CommentLocation::EndOfItems, out);335 p!(out, if("end args", multi_line, <i info(end)) str(")"));336 }337}338impl Printable for SliceDesc {339 fn print(&self, out: &mut PrintItems) {340 p!(out, str("["));341 if self.from().is_some() {342 p!(out, { self.from() });343 }344 p!(out, str(":"));345 if self.end().is_some() {346 p!(out, { self.end().map(|e| e.expr()) })347 }348 // Keep only one : in case if we don't need step349 if self.step().is_some() {350 p!(out, str(":") {self.step().map(|e|e.expr())});351 }352 p!(out, str("]"));353 }354}355356impl Printable for Member {357 fn print(&self, out: &mut PrintItems) {358 match self {359 Member::MemberBindStmt(b) => {360 p!(out, { b.obj_local() })361 }362 Member::MemberAssertStmt(ass) => {363 p!(out, { ass.assertion() })364 }365 Member::MemberFieldNormal(n) => {366 p!(out, {n.field_name()} if(n.plus_token().is_some())({n.plus_token()}) {n.visibility()} str(" ") {n.expr()})367 }368 Member::MemberFieldMethod(m) => {369 p!(out, {m.field_name()} {m.params_desc()} {m.visibility()} str(" ") {m.expr()})370 }371 }372 }373}374375impl Printable for ObjBody {376 fn print(&self, out: &mut PrintItems) {377 match self {378 ObjBody::ObjBodyComp(l) => {379 let (children, mut end_comments) = children_between::<Member>(380 l.syntax().clone(),381 l.l_brace_token().map(Into::into).as_ref(),382 Some(383 &(l.comp_specs()384 .next()385 .expect("at least one spec is defined")386 .syntax()387 .clone())388 .into(),389 ),390 None,391 );392 let trailing_for_comp = end_comments.extract_trailing();393 p!(out, str("{") >i nl);394 for mem in children.into_iter() {395 if mem.should_start_with_newline {396 p!(out, nl);397 }398 format_comments(&mem.before_trivia, CommentLocation::AboveItem, out);399 p!(out, {mem.value} str(","));400 format_comments(&mem.inline_trivia, CommentLocation::ItemInline, out);401 p!(out, nl)402 }403404 if end_comments.should_start_with_newline {405 p!(out, nl);406 }407 format_comments(&end_comments.trivia, CommentLocation::EndOfItems, out);408409 let (compspecs, end_comments) = children_between::<CompSpec>(410 l.syntax().clone(),411 l.member_comps()412 .last()413 .map(|m| m.syntax().clone())414 .map(Into::into)415 .or_else(|| l.l_brace_token().map(Into::into))416 .as_ref(),417 l.r_brace_token().map(Into::into).as_ref(),418 Some(trailing_for_comp),419 );420 for mem in compspecs.into_iter() {421 if mem.should_start_with_newline {422 p!(out, nl);423 }424 format_comments(&mem.before_trivia, CommentLocation::AboveItem, out);425 p!(out, { mem.value });426 format_comments(&mem.inline_trivia, CommentLocation::ItemInline, out);427 }428 if end_comments.should_start_with_newline {429 p!(out, nl);430 }431 format_comments(&end_comments.trivia, CommentLocation::EndOfItems, out);432433 p!(out, nl <i str("}"));434 }435 ObjBody::ObjBodyMemberList(l) => {436 let (children, end_comments) = children_between::<Member>(437 l.syntax().clone(),438 l.l_brace_token().map(Into::into).as_ref(),439 l.r_brace_token().map(Into::into).as_ref(),440 None,441 );442 if children.is_empty() && end_comments.is_empty() {443 p!(out, str("{ }"));444 return;445 }446 p!(out, str("{") >i nl);447 for (i, mem) in children.into_iter().enumerate() {448 if mem.should_start_with_newline && i != 0 {449 p!(out, nl);450 }451 format_comments(&mem.before_trivia, CommentLocation::AboveItem, out);452 p!(out, {mem.value} str(","));453 format_comments(&mem.inline_trivia, CommentLocation::ItemInline, out);454 p!(out, nl)455 }456457 if end_comments.should_start_with_newline {458 p!(out, nl);459 }460 format_comments(&end_comments.trivia, CommentLocation::EndOfItems, out);461 p!(out, <i str("}"));462 }463 }464 }465}466impl Printable for UnaryOperator {467 fn print(&self, out: &mut PrintItems) {468 p!(out, string(self.text().to_string()))469 }470}471impl Printable for BinaryOperator {472 fn print(&self, out: &mut PrintItems) {473 p!(out, string(self.text().to_string()))474 }475}476impl Printable for Bind {477 fn print(&self, out: &mut PrintItems) {478 match self {479 Bind::BindDestruct(d) => {480 p!(out, {d.into()} str(" = ") {d.value()})481 }482 Bind::BindFunction(f) => {483 p!(out, {f.name()} {f.params()} str(" = ") {f.value()})484 }485 }486 }487}488impl Printable for Literal {489 fn print(&self, out: &mut PrintItems) {490 p!(out, string(self.syntax().to_string()))491 }492}493impl Printable for ImportKind {494 fn print(&self, out: &mut PrintItems) {495 p!(out, string(self.syntax().to_string()))496 }497}498impl Printable for ForSpec {499 fn print(&self, out: &mut PrintItems) {500 p!(out, str("for ") {self.bind()} str(" in ") {self.expr()})501 }502}503impl Printable for IfSpec {504 fn print(&self, out: &mut PrintItems) {505 p!(out, str("if ") {self.expr()})506 }507}508impl Printable for CompSpec {509 fn print(&self, out: &mut PrintItems) {510 match self {511 CompSpec::ForSpec(f) => f.print(out),512 CompSpec::IfSpec(i) => i.print(out),513 }514 }515}516impl Printable for Expr {517 fn print(&self, out: &mut PrintItems) {518 let (stmts, _ending) = children_between::<Stmt>(519 self.syntax().clone(),520 None,521 self.expr_base()522 .as_ref()523 .map(ExprBase::syntax)524 .cloned()525 .map(Into::into)526 .as_ref(),527 None,528 );529 for stmt in stmts {530 p!(out, { stmt.value });531 }532 p!(out, { self.expr_base() });533 let (suffixes, _ending) = children_between::<Suffix>(534 self.syntax().clone(),535 self.expr_base()536 .as_ref()537 .map(ExprBase::syntax)538 .cloned()539 .map(Into::into)540 .as_ref(),541 None,542 None,543 );544 for suffix in suffixes {545 p!(out, { suffix.value });546 }547 }548}549impl Printable for Suffix {550 fn print(&self, out: &mut PrintItems) {551 match self {552 Suffix::SuffixIndex(i) => {553 if i.question_mark_token().is_some() {554 p!(out, str("?"));555 }556 p!(out, str(".") {i.index()});557 }558 Suffix::SuffixIndexExpr(e) => {559 if e.question_mark_token().is_some() {560 p!(out, str(".?"));561 }562 p!(out, str("[") {e.index()} str("]"))563 }564 Suffix::SuffixSlice(d) => {565 p!(out, { d.slice_desc() })566 }567 Suffix::SuffixApply(a) => {568 p!(out, { a.args_desc() })569 }570 }571 }572}573impl Printable for Stmt {574 fn print(&self, out: &mut PrintItems) {575 match self {576 Stmt::StmtLocal(l) => {577 let (binds, end_comments) = children_between::<Bind>(578 l.syntax().clone(),579 l.local_kw_token().map(Into::into).as_ref(),580 l.semi_token().map(Into::into).as_ref(),581 None,582 );583 if binds.len() == 1 {584 let bind = &binds[0];585 format_comments(&bind.before_trivia, CommentLocation::AboveItem, out);586 p!(out, str("local ") {bind.value});587 // TODO: keep end_comments, child.inline_trivia somehow, force multiple locals formatting in case of presence?588 } else {589 p!(out,str("local") >i nl);590 for bind in binds {591 if bind.should_start_with_newline {592 p!(out, nl);593 }594 format_comments(&bind.before_trivia, CommentLocation::AboveItem, out);595 p!(out, {bind.value} str(","));596 format_comments(&bind.inline_trivia, CommentLocation::ItemInline, out);597 p!(out, nl)598 }599 if end_comments.should_start_with_newline {600 p!(out, nl)601 }602 format_comments(&end_comments.trivia, CommentLocation::EndOfItems, out);603 p!(out,<i);604 }605 p!(out,str(";") nl);606 }607 Stmt::StmtAssert(a) => {608 p!(out, {a.assertion()} str(";") nl)609 }610 }611 }612}613impl Printable for ExprBase {614 fn print(&self, out: &mut PrintItems) {615 match self {616 Self::ExprBinary(b) => {617 p!(out, {b.lhs_work()} str(" ") {b.binary_operator()} str(" ") {b.rhs_work()})618 }619 Self::ExprUnary(u) => p!(out, {u.unary_operator()} {u.rhs()}),620 // Self::ExprSlice(s) => {621 // p!(new: {s.expr()} {s.slice_desc()})622 // }623 // Self::ExprIndex(i) => {624 // p!(new: {i.expr()} str(".") {i.index()})625 // }626 // Self::ExprIndexExpr(i) => p!(new: {i.base()} str("[") {i.index()} str("]")),627 // Self::ExprApply(a) => {628 // let mut pi = p!(new: {a.expr()} {a.args_desc()});629 // if a.tailstrict_kw_token().is_some() {630 // p!(out,str(" tailstrict"));631 // }632 // pi633 // }634 Self::ExprObjExtend(ex) => {635 p!(out, {ex.lhs_work()} str(" ") {ex.rhs_work()})636 }637 Self::ExprParened(p) => {638 p!(out, str("(") {p.expr()} str(")"))639 }640 Self::ExprString(s) => p!(out, { s.text() }),641 Self::ExprNumber(n) => p!(out, { n.number() }),642 Self::ExprArray(a) => {643 p!(out, str("[") >i nl);644 for el in a.exprs() {645 p!(out, {el} str(",") nl);646 }647 p!(out, <i str("]"));648 }649 Self::ExprObject(obj) => {650 p!(out, { obj.obj_body() })651 }652 Self::ExprArrayComp(arr) => {653 p!(out, str("[") {arr.expr()});654 for spec in arr.comp_specs() {655 p!(out, str(" ") {spec});656 }657 p!(out, str("]"));658 }659 Self::ExprImport(v) => {660 p!(out, {v.import_kind()} str(" ") {v.text()})661 }662 Self::ExprVar(n) => p!(out, { n.name() }),663 // Self::ExprLocal(l) => {664 // }665 Self::ExprIfThenElse(ite) => {666 p!(out, str("if ") {ite.cond()} str(" then ") {ite.then().map(|t| t.expr())});667 if ite.else_kw_token().is_some() || ite.else_().is_some() {668 p!(out, str(" else ") {ite.else_().map(|t| t.expr())})669 }670 }671 Self::ExprFunction(f) => p!(out, str("function") {f.params_desc()} nl {f.expr()}),672 // Self::ExprAssert(a) => p!(new: {a.assertion()} str("; ") {a.expr()}),673 Self::ExprError(e) => p!(out, str("error ") {e.expr()}),674 Self::ExprLiteral(l) => {675 p!(out, { l.literal() })676 }677 }678 }679}680681impl Printable for SourceFile {682 fn print(&self, out: &mut PrintItems) {683 let before = trivia_before(684 self.syntax().clone(),685 self.expr()686 .map(|e| e.syntax().clone())687 .map(Into::into)688 .as_ref(),689 );690 let after = trivia_after(691 self.syntax().clone(),692 self.expr()693 .map(|e| e.syntax().clone())694 .map(Into::into)695 .as_ref(),696 );697 format_comments(&before, CommentLocation::AboveItem, out);698 p!(out, {self.expr()} nl);699 format_comments(&after, CommentLocation::EndOfItems, out)700 }701}702703struct FormatOptions {704 // 0 for hard tabs705 indent: u8,706}707fn format(input: &str, opts: &FormatOptions) -> Option<String> {708 let (parsed, errors) = jrsonnet_rowan_parser::parse(input);709 if !errors.is_empty() {710 let mut builder = hi_doc::SnippetBuilder::new(input);711 for error in errors {712 builder713 .error(hi_doc::Text::single(714 format!("{:?}", error.error).chars(),715 Default::default(),716 ))717 .range(718 error.range.start().into()719 ..=(usize::from(error.range.end()) - 1).max(error.range.start().into()),720 )721 .build();722 }723 let snippet = builder.build();724 let ansi = hi_doc::source_to_ansi(&snippet);725 eprintln!("{ansi}");726 // It is possible to recover from this failure, but the output may be broken, as formatter is free to skip727 // ERROR rowan nodes.728 // Recovery needs to be enabled for LSP, though.729 //730 // TODO: Verify how formatter interacts in cases of missing positional values, i.e `if cond then /*missing Expr*/ else residual`.731 return None;732 }733 Some(dprint_core::formatting::format(734 || {735 let mut out = PrintItems::new();736 parsed.print(&mut out);737 out738 },739 PrintOptions {740 indent_width: if opts.indent == 0 {741 // Reasonable max length for both 2 and 4 space sized tabs.742 3743 } else {744 opts.indent745 },746 max_width: 100,747 use_tabs: opts.indent == 0,748 new_line_text: "\n",749 },750 ))751}752753#[derive(Parser)]754struct Opts {755 /// Treat input as code, reformat it instead of reading file.756 #[clap(long, short = 'e')]757 exec: bool,758 /// Path to be reformatted if `--exec` if unset, otherwise code itself.759 input: String,760 /// Replace code with formatted in-place, instead of printing it to stdout.761 /// Only applicable if `--exec` is unset.762 #[clap(long, short = 'i')]763 in_place: bool,764765 /// Exit with error if formatted does not match input766 #[arg(long)]767 test: bool,768 /// Number of spaces to indent with769 ///770 /// 0 for guess from input (default), and use hard tabs if unable to guess.771 #[arg(long, default_value = "0")]772 indent: u8,773 /// Force hard tab for indentation774 #[arg(long)]775 hard_tabs: bool,776777 /// Debug option: how many times to call reformatting in case of unstable dprint output resolution.778 ///779 /// 0 for not retrying to reformat.780 #[arg(long, default_value = "0")]781 conv_limit: usize,782}783784#[derive(thiserror::Error, Debug)]785enum Error {786 #[error("--in-place is incompatible with --exec")]787 InPlaceExec,788 #[error("io: {0}")]789 Io(#[from] io::Error),790 #[error("persist: {0}")]791 Persist(#[from] tempfile::PersistError),792 #[error("parsing failed, refusing to reformat corrupted input")]793 Parse,794}795796fn main_result() -> Result<(), Error> {797 eprintln!("jrsonnet-fmt is a prototype of a jsonnet code formatter, do not expect it to produce meaningful results right now.");798 eprintln!("It is not expected for its output to match other implementations, it will be completly separate implementation with maybe different name.");799 let mut opts = Opts::parse();800 let input = if opts.exec {801 if opts.in_place {802 return Err(Error::InPlaceExec);803 }804 opts.input.clone()805 } else {806 fs::read_to_string(&opts.input)?807 };808809 if opts.indent == 0 {810 // Sane default.811 // TODO: Implement actual guessing.812 opts.hard_tabs = true;813 }814815 let mut iteration = 0;816 let mut formatted = input.clone();817 let mut tmp;818 // https://github.com/dprint/dprint/pull/423819 loop {820 let Some(reformatted) = format(821 &formatted,822 &FormatOptions {823 indent: if opts.indent == 0 || opts.hard_tabs {824 0825 } else {826 opts.indent827 },828 },829 ) else {830 return Err(Error::Parse);831 };832 tmp = reformatted.trim().to_owned();833 if formatted == tmp {834 break;835 }836 formatted = tmp;837 if opts.conv_limit == 0 {838 break;839 }840 iteration += 1;841 if iteration > opts.conv_limit {842 panic!("formatting not converged");843 }844 }845 formatted.push('\n');846 if opts.test && formatted != input {847 process::exit(1);848 }849 if opts.in_place {850 let path = PathBuf::from(opts.input);851 let mut temp = tempfile::NamedTempFile::new_in(path.parent().expect(852 "not failed during read, this path is not a directory, and there is a parent",853 ))?;854 temp.write_all(formatted.as_bytes())?;855 temp.flush()?;856 temp.persist(&path)?;857 } else {858 print!("{formatted}")859 }860 Ok(())861}862863fn main() {864 if let Err(e) = main_result() {865 eprintln!("{e}");866 process::exit(1);867 }868}crates/jrsonnet-evaluator/src/manifest.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/manifest.rs
+++ b/crates/jrsonnet-evaluator/src/manifest.rs
@@ -1,6 +1,6 @@
use std::{borrow::Cow, fmt::Write};
-use crate::{bail, Result, State, Val};
+use crate::{bail, Result, ResultExt, State, Val};
pub trait ManifestFormat {
fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()>;
@@ -235,7 +235,8 @@
}
}
buf.push_str(cur_padding);
- manifest_json_ex_buf(&item?, buf, cur_padding, options)?;
+ manifest_json_ex_buf(&item?, buf, cur_padding, options)
+ .with_description(|| format!("elem <{i}> manifestification"))?;
}
cur_padding.truncate(old_len);
crates/jrsonnet-evaluator/src/obj.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/obj.rs
+++ b/crates/jrsonnet-evaluator/src/obj.rs
@@ -278,7 +278,7 @@
}
impl ObjectLike for ThisOverride {
fn with_this(&self, _me: ObjValue, this: ObjValue) -> ObjValue {
- ObjValue::new(ThisOverride {
+ ObjValue::new(Self {
inner: self.inner.clone(),
this,
})
@@ -398,7 +398,7 @@
self.get_for(key, self.0.this().unwrap_or_else(|| self.clone()))
}
- pub fn get_for(&self, key: IStr, this: ObjValue) -> Result<Option<Val>> {
+ pub fn get_for(&self, key: IStr, this: Self) -> Result<Option<Val>> {
self.0.get_for(key, this)
}
@@ -410,7 +410,7 @@
Ok(value)
}
- fn get_raw(&self, key: IStr, this: ObjValue) -> Result<Option<Val>> {
+ fn get_raw(&self, key: IStr, this: Self) -> Result<Option<Val>> {
self.0.get_for_uncached(key, this)
}
@@ -422,7 +422,7 @@
// FIXME: Should it use `self.0.this()` in case of standalone super?
self.run_assertions_raw(self.clone())
}
- fn run_assertions_raw(&self, this: ObjValue) -> Result<()> {
+ fn run_assertions_raw(&self, this: Self) -> Result<()> {
self.0.run_assertions_raw(this)
}
crates/jrsonnet-stdlib/src/arrays.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/arrays.rs
+++ b/crates/jrsonnet-stdlib/src/arrays.rs
@@ -6,7 +6,7 @@
runtime_error,
typed::{BoundedI32, BoundedUsize, Either2, NativeFn, Typed},
val::{equals, ArrValue, IndexableVal},
- Either, IStr, Result, Thunk, Val,
+ Either, IStr, ObjValueBuilder, Result, ResultExt, Thunk, Val,
};
pub(crate) fn eval_on_empty(on_empty: Option<Thunk<Val>>) -> Result<Val> {
@@ -21,16 +21,17 @@
pub fn builtin_make_array(sz: BoundedI32<0, { i32::MAX }>, func: FuncVal) -> Result<ArrValue> {
if *sz == 0 {
return Ok(ArrValue::empty());
- }
- if let Some(trivial) = func.evaluate_trivial() {
- let mut out = Vec::with_capacity(*sz as usize);
- for _ in 0..*sz {
- out.push(trivial.clone())
- }
- Ok(ArrValue::eager(out))
- } else {
- Ok(ArrValue::range_exclusive(0, *sz).map(func))
}
+ func.evaluate_trivial().map_or_else(
+ || Ok(ArrValue::range_exclusive(0, *sz).map(func)),
+ |trivial| {
+ let mut out = Vec::with_capacity(*sz as usize);
+ for _ in 0..*sz {
+ out.push(trivial.clone());
+ }
+ Ok(ArrValue::eager(out))
+ },
+ )
}
#[builtin]
@@ -180,7 +181,7 @@
out += &sep;
}
first = false;
- write!(out, "{item}").unwrap()
+ write!(out, "{item}").unwrap();
} else if matches!(item, Val::Null) {
continue;
} else {
@@ -320,3 +321,61 @@
process(value, &mut out)?;
Ok(out)
}
+
+#[builtin]
+pub fn builtin_prune(
+ a: Val,
+ #[cfg(feature = "exp-preserve-order")] preserve_order: bool,
+) -> Result<Val> {
+ fn is_content(val: &Val) -> bool {
+ match val {
+ Val::Null => false,
+ Val::Arr(a) => !a.is_empty(),
+ Val::Obj(o) => !o.is_empty(),
+ _ => true,
+ }
+ }
+ Ok(match a {
+ Val::Arr(a) => {
+ let mut out = Vec::new();
+ for (i, ele) in a.iter().enumerate() {
+ let ele = ele
+ .and_then(|v| {
+ builtin_prune(
+ v,
+ #[cfg(feature = "exp-preserve-order")]
+ preserve_order,
+ )
+ })
+ .with_description(|| format!("elem <{i}> pruning"))?;
+ if is_content(&ele) {
+ out.push(ele);
+ }
+ }
+ Val::Arr(ArrValue::eager(out))
+ }
+ Val::Obj(o) => {
+ let mut out = ObjValueBuilder::new();
+ for (name, value) in o.iter(
+ #[cfg(feature = "exp-preserve-order")]
+ preserve_order,
+ ) {
+ let value = value
+ .and_then(|v| {
+ builtin_prune(
+ v,
+ #[cfg(feature = "exp-preserve-order")]
+ preserve_order,
+ )
+ })
+ .with_description(|| format!("field <{name}> pruning"))?;
+ if !is_content(&value) {
+ continue;
+ }
+ out.field(name).value(value);
+ }
+ Val::Obj(out.build())
+ }
+ _ => a,
+ })
+}
crates/jrsonnet-stdlib/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/lib.rs
+++ b/crates/jrsonnet-stdlib/src/lib.rs
@@ -92,6 +92,7 @@
("remove", builtin_remove::INST),
("flattenArrays", builtin_flatten_arrays::INST),
("flattenDeepArray", builtin_flatten_deep_array::INST),
+ ("prune", builtin_prune::INST),
("filterMap", builtin_filter_map::INST),
// Math
("abs", builtin_abs::INST),
crates/jrsonnet-stdlib/src/std.jsonnetdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/std.jsonnet
+++ b/crates/jrsonnet-stdlib/src/std.jsonnet
@@ -209,25 +209,6 @@
local arr = std.split(f, '/');
std.join('/', std.makeArray(std.length(arr) - 1, function(i) arr[i]) + [r]),
- prune(a)::
- local isContent(b) =
- if b == null then
- false
- else if std.isArray(b) then
- std.length(b) > 0
- else if std.isObject(b) then
- std.length(b) > 0
- else
- true;
- if std.isArray(a) then
- [std.prune(x) for x in a if isContent($.prune(x))]
- else if std.isObject(a) then {
- [x]: $.prune(a[x])
- for x in std.objectFields(a)
- if isContent(std.prune(a[x]))
- } else
- a,
-
find(value, arr)::
if !std.isArray(arr) then
error 'find second parameter should be an array, got ' + std.type(arr)