git.delta.rocks / jrsonnet / refs/commits / 9ebfdebec2e2

difftreelog

refactor use new rowan-parser in jrsonnet-fmt

Yaroslav Bolyukin2023-09-11parent: #1b82950.patch.diff
in: master

6 files changed

addedCargo.lockdiffbeforeafterboth

no changes

modifiedcmds/jrsonnet-fmt/Cargo.tomldiffbeforeafterboth
4edition = "2021"4edition = "2021"
55
6[dependencies]6[dependencies]
7dprint-core = "0.60.0"7dprint-core = "0.63.2"
8jrsonnet-rowan-parser.workspace = true8jrsonnet-rowan-parser.workspace = true
9insta = "1.15"9insta = "1.15"
10indoc = "1.0"10indoc = "1.0"
11ass-stroke = { path = "/home/lach/build/ass-stroke/crates/ass-stroke", version = "0.1.0" }11ass-stroke = { path = "/home/lach/build/ass-stroke/crates/ass-stroke", version = "0.1.0" }
12clap = { version = "4.4.2", features = ["derive"] }
13tempfile = "3.8.0"
14thiserror = "1.0.48"
1215
modifiedcmds/jrsonnet-fmt/src/children.rsdiffbeforeafterboth
99 node: SyntaxNode,99 node: SyntaxNode,
100 start: Option<&SyntaxElement>,100 start: Option<&SyntaxElement>,
101 end: Option<&SyntaxElement>,101 end: Option<&SyntaxElement>,
102 trailing: Option<ChildTrivia>,
102) -> (Vec<Child<T>>, EndingComments) {103) -> (Vec<Child<T>>, EndingComments) {
103 let mut iter = node.children_with_tokens().peekable();104 let mut iter = node.children_with_tokens().peekable();
105 if start.is_some() {
104 while iter.peek() != start {106 while iter.peek() != start {
105 iter.next();107 iter.next();
106 }108 }
107 iter.next();109 iter.next();
110 }
108 children(111 children(
109 iter.take_while(|i| Some(i) != end),112 iter.take_while(|i| Some(i) != end),
110 start.is_none() || end.is_none(),113 start.is_none() && end.is_none(),
114 trailing,
111 )115 )
112}116}
113117
165pub fn children<T: AstNode + Debug>(169pub fn children<T: AstNode + Debug>(
166 items: impl Iterator<Item = SyntaxElement>,170 items: impl Iterator<Item = SyntaxElement>,
167 loose: bool,171 loose: bool,
172 mut trailing: Option<ChildTrivia>,
168) -> (Vec<Child<T>>, EndingComments) {173) -> (Vec<Child<T>>, EndingComments) {
169 let mut out = Vec::new();174 let mut out = Vec::new();
170 let mut current_child = None::<Child<T>>;175 let mut current_child = None::<Child<T>>;
175180
176 for item in items {181 for item in items {
177 if let Some(value) = item.as_node().cloned().and_then(T::cast) {182 if let Some(value) = item.as_node().cloned().and_then(T::cast) {
178 let before_trivia = mem::take(&mut next);183 let before_trivia = if let Some(trailing) = trailing.take() {
184 assert!(next.is_empty());
185 trailing
186 } else {
187 mem::take(&mut next)
188 };
179 let last_child = current_child.replace(Child {189 let last_child = current_child.replace(Child {
180 // First item should not start with newline190 // First item should not start with newline
181 should_start_with_newline: had_some191 should_start_with_newline: had_some
195 } else if let Some(trivia) = item.as_token().cloned().and_then(Trivia::cast) {205 } else if let Some(trivia) = item.as_token().cloned().and_then(Trivia::cast) {
196 let is_single_line_comment = trivia.kind() == TriviaKind::SingleLineHashComment206 let is_single_line_comment = trivia.kind() == TriviaKind::SingleLineHashComment
197 || trivia.kind() == TriviaKind::SingleLineSlashComment;207 || trivia.kind() == TriviaKind::SingleLineSlashComment;
208 if trailing.is_some() {
209 // Someone have already parsed trivia for us
210 continue;
198 if started_next211 } else if started_next
199 || current_child.is_none()212 || current_child.is_none()
200 || trivia.text().contains('\n') && !is_single_line_comment213 || trivia.text().contains('\n') && !is_single_line_comment
201 {214 {
271 pub fn is_empty(&self) -> bool {284 pub fn is_empty(&self) -> bool {
272 !self.should_start_with_newline && self.trivia.is_empty()285 !self.should_start_with_newline && self.trivia.is_empty()
273 }286 }
287 pub fn extract_trailing(&mut self) -> ChildTrivia {
288 mem::take(&mut self.trivia)
289 }
274}290}
275291
modifiedcmds/jrsonnet-fmt/src/comments.rsdiffbeforeafterboth
28 match text.as_bytes()[0] {28 match text.as_bytes()[0] {
29 b'\n' => p!(pi: nl),29 b'\n' => p!(pi: nl),
30 b'\t' => p!(pi: tab),30 b'\t' => p!(pi: tab),
31 _ => unreachable!()31 _ => unreachable!(),
32 }32 }
33 text = &text[1..];33 text = &text[1..];
34 }34 }
69 lines.pop();69 lines.pop();
70 }70 }
71 if lines.len() == 1 && !doc {71 if lines.len() == 1 && !doc {
72 if matches!(loc, CommentLocation::ItemInline) {
73 p!(pi: str(" "));
74 }
72 p!(pi: str("/* ") string(lines[0].trim().to_string()) str(" */") nl)75 p!(pi: str("/* ") string(lines[0].trim().to_string()) str(" */"))
73 } else if !lines.is_empty() {76 } else if !lines.is_empty() {
74 fn common_ws_prefix<'a>(a: &'a str, b: &str) -> &'a str {77 fn common_ws_prefix<'a>(a: &'a str, b: &str) -> &'a str {
75 let offset = a78 let offset = a
modifiedcmds/jrsonnet-fmt/src/main.rsdiffbeforeafterboth
1use std::any::type_name;1use std::{
2 any::type_name,
3 fs,
4 io::{self, Write},
5 path::PathBuf,
6 process,
7};
28
3use children::{children_between, trivia_before};9use children::{children_between, trivia_before};
10use clap::Parser;
4use dprint_core::formatting::{PrintItems, PrintOptions};11use dprint_core::formatting::{PrintItems, PrintOptions};
5use jrsonnet_rowan_parser::{12use jrsonnet_rowan_parser::{
6 nodes::{13 nodes::{
7 ArgsDesc, Assertion, BinaryOperator, Bind, CompSpec, Destruct, DestructArrayPart,14 ArgsDesc, Assertion, BinaryOperator, Bind, CompSpec, Destruct, DestructArrayPart,
8 DestructRest, Expr, FieldName, ForSpec, IfSpec, ImportKind, LhsExpr, Literal, Member, Name,15 DestructRest, Expr, ExprBase, FieldName, ForSpec, IfSpec, ImportKind, Literal, Member,
9 Number, ObjBody, ObjLocal, ParamsDesc, SliceDesc, SourceFile, Text, UnaryOperator,16 Name, Number, ObjBody, ObjLocal, ParamsDesc, SliceDesc, SourceFile, Stmt, Suffix, Text,
10 Visibility, VisibilityKind,17 UnaryOperator, Visibility,
11 },18 },
12 rowan::NodeOrToken,
13 AstNode, AstToken, SyntaxToken,19 AstNode, AstToken, SyntaxToken,
14};20};
1521
287 Member::MemberFieldNormal(n) => {293 Member::MemberFieldNormal(n) => {
288 p!(new: {n.field_name()} if(n.plus_token().is_some())({n.plus_token()}) {n.visibility()} str(" ") {n.expr()})294 p!(new: {n.field_name()} if(n.plus_token().is_some())({n.plus_token()}) {n.visibility()} str(" ") {n.expr()})
289 }295 }
290 Member::MemberFieldMethod(_) => todo!(),296 Member::MemberFieldMethod(m) => {
297 p!(new: {m.field_name()} {m.params_desc()} {m.visibility()} str(" ") {m.expr()})
298 }
291 }299 }
292 }300 }
293}301}
296 fn print(&self) -> PrintItems {304 fn print(&self) -> PrintItems {
297 match self {305 match self {
298 ObjBody::ObjBodyComp(l) => {306 ObjBody::ObjBodyComp(l) => {
299 let (children, end_comments) = children_between::<Member>(307 let (children, mut end_comments) = children_between::<Member>(
300 l.syntax().clone(),308 l.syntax().clone(),
301 l.l_brace_token().map(Into::into).as_ref(),309 l.l_brace_token().map(Into::into).as_ref(),
302 Some(310 Some(
307 .clone())315 .clone())
308 .into(),316 .into(),
309 ),317 ),
318 None,
310 );319 );
320 let trailing_for_comp = end_comments.extract_trailing();
311 let mut pi = p!(new: str("{") >i nl);321 let mut pi = p!(new: str("{") >i nl);
312 for mem in children.into_iter() {322 for mem in children.into_iter() {
313 if mem.should_start_with_newline {323 if mem.should_start_with_newline {
333 .or_else(|| l.l_brace_token().map(Into::into))343 .or_else(|| l.l_brace_token().map(Into::into))
334 .as_ref(),344 .as_ref(),
335 l.r_brace_token().map(Into::into).as_ref(),345 l.r_brace_token().map(Into::into).as_ref(),
346 Some(trailing_for_comp),
336 );347 );
337 for mem in compspecs.into_iter() {348 for mem in compspecs.into_iter() {
338 if mem.should_start_with_newline {349 if mem.should_start_with_newline {
347 }358 }
348 p!(pi: items(format_comments(&end_comments.trivia, CommentLocation::EndOfItems)));359 p!(pi: items(format_comments(&end_comments.trivia, CommentLocation::EndOfItems)));
349360
350 p!(pi: <i str("}"));361 p!(pi: nl <i str("}"));
351 pi362 pi
352 }363 }
353 ObjBody::ObjBodyMemberList(l) => {364 ObjBody::ObjBodyMemberList(l) => {
354 let (children, end_comments) = children_between::<Member>(365 let (children, end_comments) = children_between::<Member>(
355 l.syntax().clone(),366 l.syntax().clone(),
356 l.l_brace_token().map(Into::into).as_ref(),367 l.l_brace_token().map(Into::into).as_ref(),
357 l.r_brace_token().map(Into::into).as_ref(),368 l.r_brace_token().map(Into::into).as_ref(),
369 None,
358 );370 );
359 if children.is_empty() && end_comments.is_empty() {371 if children.is_empty() && end_comments.is_empty() {
360 return p!(new: str("{ }"));372 return p!(new: str("{ }"));
412 p!(new: string(self.syntax().to_string()))424 p!(new: string(self.syntax().to_string()))
413 }425 }
414}426}
415impl Printable for LhsExpr {
416 fn print(&self) -> PrintItems {
417 p!(new: {self.expr()})
418 }
419}
420impl Printable for ForSpec {427impl Printable for ForSpec {
421 fn print(&self) -> PrintItems {428 fn print(&self) -> PrintItems {
422 p!(new: str("for ") {self.bind()} str(" in ") {self.expr()})429 p!(new: str("for ") {self.bind()} str(" in ") {self.expr()})
435 }442 }
436 }443 }
437}444}
438impl Printable for Expr {445impl Printable for Expr {
446 fn print(&self) -> PrintItems {
447 let mut o = p!(new:);
448 let (stmts, ending) = children_between::<Stmt>(
449 self.syntax().clone(),
450 None,
451 self.expr_base()
452 .as_ref()
453 .map(ExprBase::syntax)
454 .cloned()
455 .map(Into::into)
456 .as_ref(),
457 None,
458 );
459 for stmt in stmts {
460 p!(o: {stmt.value});
461 }
462 p!(o: {self.expr_base()});
463 let (suffixes, ending) = children_between::<Suffix>(
464 self.syntax().clone(),
465 self.expr_base()
466 .as_ref()
467 .map(ExprBase::syntax)
468 .cloned()
469 .map(Into::into)
470 .as_ref(),
471 None,
472 None,
473 );
474 for suffix in suffixes {
475 p!(o: {suffix.value});
476 }
477 o
478 }
479}
480impl Printable for Suffix {
481 fn print(&self) -> PrintItems {
482 let mut o = p!(new:);
483 match self {
484 Suffix::SuffixIndex(i) => {
485 if i.question_mark_token().is_some() {
486 p!(o: str("?"));
487 }
488 p!(o: str(".") {i.index()});
489 }
490 Suffix::SuffixIndexExpr(e) => {
491 if e.question_mark_token().is_some() {
492 p!(o: str(".?"));
493 }
494 p!(o: str("[") {e.index()} str("]"))
495 }
496 Suffix::SuffixSlice(d) => {
497 p!(o: {d.slice_desc()})
498 }
499 Suffix::SuffixApply(a) => {
500 p!(o: {a.args_desc()})
501 }
502 }
503 o
504 }
505}
506impl Printable for Stmt {
507 fn print(&self) -> PrintItems {
508 match self {
509 Stmt::StmtLocal(l) => {
510 let mut pi = p!(new:);
511 let (binds, end_comments) = children_between::<Bind>(
512 l.syntax().clone(),
513 l.local_kw_token().map(Into::into).as_ref(),
514 l.semi_token().map(Into::into).as_ref(),
515 None,
516 );
517 if binds.len() == 1 {
518 let bind = &binds[0];
519 p!(pi: items(format_comments(&bind.before_trivia, CommentLocation::AboveItem)));
520 p!(pi: str("local ") {bind.value});
521 // TODO: keep end_comments, child.inline_trivia somehow, force multiple locals formatting in case of presence?
522 } else {
523 p!(pi: str("local") >i nl);
524 for bind in binds {
525 if bind.should_start_with_newline {
526 p!(pi: nl);
527 }
528 p!(pi: items(format_comments(&bind.before_trivia, CommentLocation::AboveItem)));
529 p!(pi: {bind.value} str(","));
530 p!(pi: items(format_comments(&bind.inline_trivia, CommentLocation::ItemInline)) nl);
531 }
532 if end_comments.should_start_with_newline {
533 p!(pi: nl)
534 }
535 p!(pi: items(format_comments(&end_comments.trivia, CommentLocation::EndOfItems)));
536 p!(pi: <i);
537 }
538 p!(pi: str(";") nl);
539 pi
540 }
541 Stmt::StmtAssert(a) => {
542 p!(new: {a.assertion()} str(";") nl)
543 }
544 }
545 }
546}
547impl Printable for ExprBase {
439 fn print(&self) -> PrintItems {548 fn print(&self) -> PrintItems {
440 match self {549 match self {
441 Expr::ExprBinary(b) => {550 Self::ExprBinary(b) => {
442 p!(new: {b.lhs()} str(" ") {b.binary_operator()} str(" ") {b.rhs()})551 p!(new: {b.lhs_work()} str(" ") {b.binary_operator()} str(" ") {b.rhs_work()})
443 }552 }
444 Expr::ExprUnary(u) => p!(new: {u.unary_operator()} {u.rhs()}),553 Self::ExprUnary(u) => p!(new: {u.unary_operator()} {u.rhs()}),
445 Expr::ExprSlice(s) => {554 // Self::ExprSlice(s) => {
446 p!(new: {s.expr()} {s.slice_desc()})555 // p!(new: {s.expr()} {s.slice_desc()})
447 }556 // }
448 Expr::ExprIndex(i) => {557 // Self::ExprIndex(i) => {
449 p!(new: {i.expr()} str(".") {i.index()})558 // p!(new: {i.expr()} str(".") {i.index()})
450 }559 // }
451 Expr::ExprIndexExpr(i) => p!(new: {i.base()} str("[") {i.index()} str("]")),560 // Self::ExprIndexExpr(i) => p!(new: {i.base()} str("[") {i.index()} str("]")),
452 Expr::ExprApply(a) => {561 // Self::ExprApply(a) => {
453 let mut pi = p!(new: {a.expr()} {a.args_desc()});562 // let mut pi = p!(new: {a.expr()} {a.args_desc()});
454 if a.tailstrict_kw_token().is_some() {563 // if a.tailstrict_kw_token().is_some() {
455 p!(pi: str(" tailstrict"));564 // p!(pi: str(" tailstrict"));
456 }565 // }
457 pi566 // pi
458 }567 // }
459 Expr::ExprObjExtend(ex) => {568 Self::ExprObjExtend(ex) => {
460 p!(new: {ex.lhs_expr()} str(" ") {ex.expr()})569 p!(new: {ex.lhs_work()} str(" ") {ex.rhs_work()})
461 }570 }
462 Expr::ExprParened(p) => {571 Self::ExprParened(p) => {
463 p!(new: str("(") {p.expr()} str(")"))572 p!(new: str("(") {p.expr()} str(")"))
464 }573 }
465 Expr::ExprString(s) => p!(new: {s.text()}),574 Self::ExprString(s) => p!(new: {s.text()}),
466 Expr::ExprNumber(n) => p!(new: {n.number()}),575 Self::ExprNumber(n) => p!(new: {n.number()}),
467 Expr::ExprArray(a) => {576 Self::ExprArray(a) => {
468 let mut pi = p!(new: str("[") >i nl);577 let mut pi = p!(new: str("[") >i nl);
469 for el in a.exprs() {578 for el in a.exprs() {
470 p!(pi: {el} str(",") nl);579 p!(pi: {el} str(",") nl);
471 }580 }
472 p!(pi: <i str("]"));581 p!(pi: <i str("]"));
473 pi582 pi
474 }583 }
475 Expr::ExprObject(o) => {584 Self::ExprObject(obj) => {
476 p!(new: {o.obj_body()})585 p!(new: {obj.obj_body()})
477 }586 }
478 Expr::ExprArrayComp(arr) => {587 Self::ExprArrayComp(arr) => {
479 let mut pi = p!(new: str("[") {arr.expr()});588 let mut pi = p!(new: str("[") {arr.expr()});
480 for spec in arr.comp_specs() {589 for spec in arr.comp_specs() {
481 p!(pi: str(" ") {spec});590 p!(pi: str(" ") {spec});
482 }591 }
483 p!(pi: str("]"));592 p!(pi: str("]"));
484 pi593 pi
485 }594 }
486 Expr::ExprImport(v) => {595 Self::ExprImport(v) => {
487 p!(new: {v.import_kind()} str(" ") {v.text()})596 p!(new: {v.import_kind()} str(" ") {v.text()})
488 }597 }
489 Expr::ExprVar(n) => p!(new: {n.name()}),598 Self::ExprVar(n) => p!(new: {n.name()}),
490 Expr::ExprLocal(l) => {599 // Self::ExprLocal(l) => {
491 let mut pi = p!(new:);600 // }
492 let (binds, end_comments) = children_between::<Bind>(
493 l.syntax().clone(),
494 l.local_kw_token().map(Into::into).as_ref(),
495 l.semi_token().map(Into::into).as_ref(),
496 );
497 if binds.len() == 1 {
498 let bind = &binds[0];
499 p!(pi: items(format_comments(&bind.before_trivia, CommentLocation::AboveItem)));
500 p!(pi: str("local ") {bind.value});
501 // TODO: keep end_comments, child.inline_trivia somehow, force multiple locals formatting in case of presence?
502 } else {
503 p!(pi: str("local") >i nl);
504 for bind in binds {
505 if bind.should_start_with_newline {
506 p!(pi: nl);
507 }
508 p!(pi: items(format_comments(&bind.before_trivia, CommentLocation::AboveItem)));
509 p!(pi: {bind.value} str(","));
510 p!(pi: items(format_comments(&bind.inline_trivia, CommentLocation::ItemInline)) nl);
511 }
512 if end_comments.should_start_with_newline {
513 p!(pi: nl)
514 }
515 p!(pi: items(format_comments(&end_comments.trivia, CommentLocation::EndOfItems)));
516 p!(pi: <i);
517 }
518 p!(pi: str(";") nl);
519
520 let expr_comments = trivia_between(
521 l.syntax().clone(),
522 l.semi_token().map(Into::into).as_ref(),
523 l.expr()
524 .map(|e| e.syntax().clone())
525 .map(Into::into)
526 .as_ref(),
527 );
528
529 if expr_comments.should_start_with_newline {
530 p!(pi: nl);
531 }
532 p!(pi: items(format_comments(&expr_comments.trivia, CommentLocation::AboveItem)));
533 p!(pi: {l.expr()});
534 pi
535 }
536 Expr::ExprIfThenElse(ite) => {601 Self::ExprIfThenElse(ite) => {
537 let mut pi =602 let mut pi =
538 p!(new: str("if ") {ite.cond()} str(" then ") {ite.then().map(|t| t.expr())});603 p!(new: str("if ") {ite.cond()} str(" then ") {ite.then().map(|t| t.expr())});
539 if ite.else_kw_token().is_some() || ite.else_().is_some() {604 if ite.else_kw_token().is_some() || ite.else_().is_some() {
540 p!(pi: str(" else ") {ite.else_().map(|t| t.expr())})605 p!(pi: str(" else ") {ite.else_().map(|t| t.expr())})
541 }606 }
542 pi607 pi
543 }608 }
544 Expr::ExprFunction(f) => p!(new: str("function") {f.params_desc()} str(" ") {f.expr()}),609 Self::ExprFunction(f) => p!(new: str("function") {f.params_desc()} nl {f.expr()}),
545 Expr::ExprAssert(a) => p!(new: {a.assertion()} str("; ") {a.expr()}),610 // Self::ExprAssert(a) => p!(new: {a.assertion()} str("; ") {a.expr()}),
546 Expr::ExprError(e) => p!(new: str("error ") {e.expr()}),611 Self::ExprError(e) => p!(new: str("error ") {e.expr()}),
547 Expr::ExprLiteral(l) => {612 Self::ExprLiteral(l) => {
548 p!(new: {l.literal()})613 p!(new: {l.literal()})
549 }614 }
550 }615 }
575 }640 }
576}641}
577642
643struct FormatOptions {
644 // 0 for hard tabs
645 indent: u8,
646}
578fn format(input: &str) -> String {647fn format(input: &str, opts: &FormatOptions) -> Option<String> {
579 let (parsed, errors) = jrsonnet_rowan_parser::parse(input);648 let (parsed, errors) = jrsonnet_rowan_parser::parse(input);
580 if !errors.is_empty() {649 if !errors.is_empty() {
581 let mut builder = ass_stroke::SnippetBuilder::new(input);650 let mut builder = ass_stroke::SnippetBuilder::new(input);
593 }662 }
594 let snippet = builder.build();663 let snippet = builder.build();
595 let ansi = ass_stroke::source_to_ansi(&snippet);664 let ansi = ass_stroke::source_to_ansi(&snippet);
596 println!("{ansi}");665 eprintln!("{ansi}");
666 // It is possible to recover from this failure, but the output may be broken, as formatter is free to skip
667 // ERROR rowan nodes.
668 // Recovery needs to be enabled for LSP, though.
669 //
670 // TODO: Verify how formatter interacts in cases of missing positional values, i.e `if cond then /*missing Expr*/ else residual`.
671 return None;
597 }672 }
598 dprint_core::formatting::format(673 Some(dprint_core::formatting::format(
599 || parsed.print(),674 || parsed.print(),
600 PrintOptions {675 PrintOptions {
601 indent_width: 2,676 indent_width: if opts.indent == 0 {
677 // Reasonable max length for both 2 and 4 space sized tabs.
678 3
679 } else {
680 opts.indent
681 },
602 max_width: 100,682 max_width: 100,
603 use_tabs: false,683 use_tabs: opts.indent == 0,
604 new_line_text: "\n",684 new_line_text: "\n",
605 },685 },
606 )686 ))
607}687}
688
689#[derive(Parser)]
690struct Opts {
691 /// Treat input as code, reformat it instead of reading file.
692 #[clap(long, short = 'e')]
693 exec: bool,
694 /// Path to be reformatted if `--exec` if unset, otherwise code itself.
695 input: String,
696 /// Replace code with formatted in-place, instead of printing it to stdout.
697 /// Only applicable if `--exec` is unset.
698 #[clap(long, short = 'i')]
699 in_place: bool,
700
701 /// Exit with error if formatted does not match input
702 #[arg(long)]
703 test: bool,
704 /// Number of spaces to indent with
705 ///
706 /// 0 for guess from input (default), and use hard tabs if unable to guess.
707 #[arg(long, default_value = "0")]
708 indent: u8,
709 /// Force hard tab for indentation
710 #[arg(long)]
711 hard_tabs: bool,
712
713 /// Debug option: how many times to call reformatting in case of unstable dprint output resolution.
714 ///
715 /// 0 for not retrying to reformat.
716 #[arg(long, default_value = "0")]
717 conv_limit: usize,
718}
719
720#[derive(thiserror::Error, Debug)]
721enum Error {
722 #[error("--in-place is incompatible with --exec")]
723 InPlaceExec,
724 #[error("io: {0}")]
725 Io(#[from] io::Error),
726 #[error("persist: {0}")]
727 Persist(#[from] tempfile::PersistError),
728 #[error("parsing failed, refusing to reformat corrupted input")]
729 ParseError,
730}
731
608fn main() {732fn main_result() -> Result<(), Error> {
733 eprintln!("jrsonnet-fmt is a prototype of a jsonnet code formatter, do not expect it to produce meaningful results right now.");
734 eprintln!("It is not expected for its output to match other implementations, it will be completly separate implementation with maybe different name.");
735 let mut opts = Opts::parse();
609 let input = r#"736 let input = if opts.exec {
610737 if opts.in_place {
611738 return Err(Error::InPlaceExec);
612 # Edit me!739 }
613 local b = import "b.libsonnet"; # comment740 opts.input.clone()
614 local a = import "a.libsonnet";741 } else {
615742 fs::read_to_string(&opts.input)?
616 local f(x,y)=x+y;
617
618 local {a: [b, ..., c], d, ...e} = null;
619
620 local ass = assert false : false; false;
621
622 local fn = function(a, b, c = 3) 4;
623
624 local comp = [a for b in c if d == e];
625 local ocomp = {[k]: 1 for k in v};
626
627 local ? = skip;
628
629 local ie = a[expr];
630
631 local unary = !a;
632
633 local
634 // I am comment
635 singleLocalWithItemComment = 1,
636 ;
637
638 // Comment between local and expression
639
640 local
641 a = 1, // Inline
642 // Comment above b
643 b = 4,
644
645 // c needs some space
646 c = 5,
647
648 // Comment after everything
649 ;
650
651
652 local Template = {z: "foo"};
653
654 {
655 local
656
657 h = 3,
658 assert self.a == 1
659
660 : "error",
661 "f": ((((((3)))))) ,
662 "g g":
663 f(4,2),
664 arr: [[
665 1, 2,
666 ],
667 3,
668 {
669 b: {
670 c: {
671 k: [16]
672 }
673 }
674 }
675 ],
676 m: a[1::],
677 m: b[::],
678
679 comments: {
680 _: '',
681 // Plain comment
682 a: '',
683
684 # Plain comment with empty line before
685 b: '',
686 /*Single-line multiline comment
687
688 */
689 c: '',
690
691 /**Single-line multiline doc comment
692
693 */
694 c: '',
695
696 /**multiline doc comment
697 s
698 */
699 c: '',
700
701 /*
702
703 Multi-line
704
705 comment
706 */
707 d: '',
708
709 e: '', // Inline comment
710
711 k: '',
712
713 // Text after everything
714 },
715 comments2: {
716 k: '',
717 // Text after everything, but no newline above
718 },
719 k: if a == b then
720
721
722 2
723
724 else Template {},
725
726 compspecs: {
727 obj_with_no_item: {a:1, for i in [1, 2, 3]},
728 obj_with_2_items: {a:1, /*b:2,*/ for i in [1,2,3]},
729 }
730
731 } + Template
732"#;743 };
744
745 if opts.indent == 0 {
746 // Sane default.
747 // TODO: Implement actual guessing.
748 opts.hard_tabs = true;
749 }
733750
734 let mut iteration = 0;751 let mut iteration = 0;
735 let mut a = input.to_string();752 let mut formatted = input.clone();
736 let mut b;753 let mut tmp;
737 // https://github.com/dprint/dprint/pull/423754 // https://github.com/dprint/dprint/pull/423
738 loop {755 loop {
756 let Some(reformatted) = format(
757 &formatted,
758 &FormatOptions {
759 indent: if opts.indent == 0 || opts.hard_tabs {
760 0
761 } else {
762 opts.indent
763 },
764 },
765 ) else {
766 return Err(Error::ParseError);
767 };
739 b = format(&a).trim().to_owned();768 tmp = reformatted.trim().to_owned();
740 if a == b {769 if formatted == tmp {
741 break;770 break;
742 }771 }
743 println!("{b}");772 formatted = tmp;
773 if opts.conv_limit == 0 {
744 a = b;774 break;
775 }
745 iteration += 1;776 iteration += 1;
746 if iteration > 5 {777 if iteration > opts.conv_limit {
747 panic!("formatting not converged");778 panic!("formatting not converged");
748 break;
749 }779 }
750 }780 }
781 formatted.push('\n');
782 if opts.test && formatted != input {
783 process::exit(1);
784 }
785 if opts.in_place {
786 let path = PathBuf::from(opts.input);
787 let mut temp = tempfile::NamedTempFile::new_in(path.parent().expect(
788 "not failed during read, this path is not a directory, and there is a parent",
789 ))?;
790 temp.write_all(formatted.as_bytes())?;
791 temp.flush()?;
792 temp.persist(&path)?;
793 } else {
751 println!("{a}");794 print!("{formatted}")
795 }
796 Ok(())
752}797}
798
799fn main() {
800 if let Err(e) = main_result() {
801 eprintln!("{e}");
802 process::exit(1);
803 }
804}
753805
modifiedcmds/jrsonnet/src/main.rsdiffbeforeafterboth
37#[derive(Parser)]37#[derive(Parser)]
38#[clap(next_help_heading = "INPUT")]38#[clap(next_help_heading = "INPUT")]
39struct InputOpts {39struct InputOpts {
40 /// Treat input as code, evaluate them instead of reading file40 /// Treat input as code, evaluate it instead of reading file.
41 #[clap(long, short = 'e')]41 #[clap(long, short = 'e')]
42 pub exec: bool,42 pub exec: bool,
4343
44 /// Path to the file to be compiled if `--evaluate` is unset, otherwise code itself44 /// Path to the file to be compiled if `--exec` is unset, otherwise code itself.
45 pub input: Option<String>,45 pub input: Option<String>,
4646
47 /// After executing input, apply specified code.47 /// After executing input, apply specified code.
48 /// Output of the initial input will be accessible using `$`48 /// Output of the initial input will be accessible using `_`.
49 #[cfg(feature = "exp-apply")]49 #[cfg(feature = "exp-apply")]
50 #[clap(long)]50 #[clap(long)]
51 pub exp_apply: Vec<String>,51 pub exp_apply: Vec<String>,