difftreelog
refactor remove p!(new:) constructor from macros
in: master
2 files changed
cmds/jrsonnet-fmt/src/comments.rsdiffbeforeafterboth--- a/cmds/jrsonnet-fmt/src/comments.rs
+++ b/cmds/jrsonnet-fmt/src/comments.rs
@@ -12,22 +12,19 @@
EndOfItems,
}
-#[must_use]
-pub fn format_comments(comments: &ChildTrivia, loc: CommentLocation) -> PrintItems {
- let mut pi = p!(new:);
-
+pub fn format_comments(comments: &ChildTrivia, loc: CommentLocation, out: &mut PrintItems) {
for c in comments {
let Ok(c) = c else {
let mut text = c.as_ref().unwrap_err() as &str;
while !text.is_empty() {
let pos = text.find(|c| c == '\n' || c == '\t').unwrap_or(text.len());
let sliced = &text[..pos];
- p!(pi: string(sliced.to_string()));
+ p!(out, string(sliced.to_string()));
text = &text[pos..];
if !text.is_empty() {
match text.as_bytes()[0] {
- b'\n' => p!(pi: nl),
- b'\t' => p!(pi: tab),
+ b'\n' => p!(out, nl),
+ b'\t' => p!(out, tab),
_ => unreachable!(),
}
text = &text[1..];
@@ -70,9 +67,9 @@
}
if lines.len() == 1 && !doc {
if matches!(loc, CommentLocation::ItemInline) {
- p!(pi: str(" "));
+ p!(out, str(" "));
}
- p!(pi: str("/* ") string(lines[0].trim().to_string()) str(" */") nl)
+ p!(out, str("/* ") string(lines[0].trim().to_string()) str(" */") nl)
} else if !lines.is_empty() {
fn common_ws_prefix<'a>(a: &'a str, b: &str) -> &'a str {
let offset = a
@@ -107,36 +104,36 @@
.to_string();
}
- p!(pi: str("/*"));
+ p!(out, str("/*"));
if doc {
- p!(pi: str("*"));
+ p!(out, str("*"));
}
- p!(pi: nl);
+ p!(out, nl);
for mut line in lines {
if doc {
- p!(pi: str(" *"));
+ p!(out, str(" *"));
}
if line.is_empty() {
- p!(pi: nl);
+ p!(out, nl);
} else {
if doc {
- p!(pi: str(" "));
+ p!(out, str(" "));
}
while let Some(new_line) = line.strip_prefix('\t') {
if doc {
- p!(pi: str(" "));
+ p!(out, str(" "));
} else {
- p!(pi: tab);
+ p!(out, tab);
}
line = new_line.to_string();
}
- p!(pi: string(line.to_string()) nl)
+ p!(out, string(line.to_string()) nl)
}
}
if doc {
- p!(pi: str(" "));
+ p!(out, str(" "));
}
- p!(pi: str("*/") nl)
+ p!(out, str("*/") nl)
}
}
// TODO: Keep common padding for multiple continous lines of single-line comments
@@ -157,27 +154,25 @@
// ```
TriviaKind::SingleLineHashComment => {
if matches!(loc, CommentLocation::ItemInline) {
- p!(pi: str(" "))
+ p!(out, str(" "))
}
- p!(pi: str("# ") string(c.text().strip_prefix('#').expect("hash comment starts with #").trim().to_string()));
+ p!(out, str("# ") string(c.text().strip_prefix('#').expect("hash comment starts with #").trim().to_string()));
if !matches!(loc, CommentLocation::ItemInline) {
- p!(pi: nl)
+ p!(out, nl)
}
}
TriviaKind::SingleLineSlashComment => {
if matches!(loc, CommentLocation::ItemInline) {
- p!(pi: str(" "))
+ p!(out, str(" "))
}
- p!(pi: str("// ") string(c.text().strip_prefix("//").expect("comment starts with //").trim().to_string()));
+ p!(out, str("// ") string(c.text().strip_prefix("//").expect("comment starts with //").trim().to_string()));
if !matches!(loc, CommentLocation::ItemInline) {
- p!(pi: nl)
+ p!(out, nl)
}
}
// Garbage in - garbage out
- TriviaKind::ErrorCommentTooShort => p!(pi: str("/*/")),
- TriviaKind::ErrorCommentUnterminated => p!(pi: string(c.text().to_string())),
+ TriviaKind::ErrorCommentTooShort => p!(out, str("/*/")),
+ TriviaKind::ErrorCommentUnterminated => p!(out, string(c.text().to_string())),
}
}
-
- pi
}
cmds/jrsonnet-fmt/src/main.rsdiffbeforeafterboth1use std::{2 any::type_name,3 fs,4 io::{self, Write},5 path::PathBuf,6 process,7};89use children::{children_between, trivia_before};10use clap::Parser;11use dprint_core::formatting::{PrintItems, PrintOptions};12use jrsonnet_rowan_parser::{13 nodes::{14 ArgsDesc, Assertion, BinaryOperator, Bind, CompSpec, Destruct, DestructArrayPart,15 DestructRest, Expr, ExprBase, FieldName, ForSpec, IfSpec, ImportKind, Literal, Member,16 Name, Number, ObjBody, ObjLocal, ParamsDesc, SliceDesc, SourceFile, Stmt, Suffix, Text,17 UnaryOperator, Visibility,18 },19 AstNode, AstToken, SyntaxToken,20};2122use crate::{23 children::trivia_after,24 comments::{format_comments, CommentLocation},25};2627mod children;28mod comments;29#[cfg(test)]30mod tests;3132pub trait Printable {33 fn print(&self) -> PrintItems;34}3536macro_rules! pi {37 (@i; $($t:tt)*) => {{38 #[allow(unused_mut)]39 let mut o = dprint_core::formatting::PrintItems::new();40 pi!(@s; o: $($t)*);41 o42 }};43 (@s; $o:ident: str($e:expr $(,)?) $($t:tt)*) => {{44 $o.push_str($e);45 pi!(@s; $o: $($t)*);46 }};47 (@s; $o:ident: string($e:expr $(,)?) $($t:tt)*) => {{48 $o.push_string($e);49 pi!(@s; $o: $($t)*);50 }};51 (@s; $o:ident: nl $($t:tt)*) => {{52 $o.push_signal(dprint_core::formatting::Signal::NewLine);53 pi!(@s; $o: $($t)*);54 }};55 (@s; $o:ident: tab $($t:tt)*) => {{56 $o.push_signal(dprint_core::formatting::Signal::Tab);57 pi!(@s; $o: $($t)*);58 }};59 (@s; $o:ident: >i $($t:tt)*) => {{60 $o.push_signal(dprint_core::formatting::Signal::StartIndent);61 pi!(@s; $o: $($t)*);62 }};63 (@s; $o:ident: <i $($t:tt)*) => {{64 $o.push_signal(dprint_core::formatting::Signal::FinishIndent);65 pi!(@s; $o: $($t)*);66 }};67 (@s; $o:ident: {$expr:expr} $($t:tt)*) => {{68 $o.extend($expr.print());69 pi!(@s; $o: $($t)*);70 }};71 (@s; $o:ident: items($expr:expr) $($t:tt)*) => {{72 $o.extend($expr);73 pi!(@s; $o: $($t)*);74 }};75 (@s; $o:ident: if ($e:expr)($($then:tt)*) $($t:tt)*) => {{76 if $e {77 pi!(@s; $o: $($then)*);78 }79 pi!(@s; $o: $($t)*);80 }};81 (@s; $o:ident: ifelse ($e:expr)($($then:tt)*)($($else:tt)*) $($t:tt)*) => {{82 if $e {83 pi!(@s; $o: $($then)*);84 } else {85 pi!(@s; $o: $($else)*);86 }87 pi!(@s; $o: $($t)*);88 }};89 (@s; $i:ident:) => {}90}91macro_rules! p {92 (new: $($t:tt)*) => {93 pi!(@i; $($t)*)94 };95 ($o:ident: $($t:tt)*) => {96 pi!(@s; $o: $($t)*)97 };98}99pub(crate) use p;100pub(crate) use pi;101102impl<P> Printable for Option<P>103where104 P: Printable,105{106 fn print(&self) -> PrintItems {107 if let Some(v) = self {108 v.print()109 } else {110 p!(new: string(111 format!(112 "/*missing {}*/",113 type_name::<P>().replace("jrsonnet_rowan_parser::generated::nodes::", "")114 ),115 ))116 }117 }118}119120impl Printable for SyntaxToken {121 fn print(&self) -> PrintItems {122 p!(new: string(self.to_string()))123 }124}125126impl Printable for Text {127 fn print(&self) -> PrintItems {128 p!(new: string(format!("{}", self)))129 }130}131impl Printable for Number {132 fn print(&self) -> PrintItems {133 p!(new: string(format!("{}", self)))134 }135}136137impl Printable for Name {138 fn print(&self) -> PrintItems {139 p!(new: {self.ident_lit()})140 }141}142143impl Printable for DestructRest {144 fn print(&self) -> PrintItems {145 let mut pi = p!(new: str("..."));146 if let Some(name) = self.into() {147 p!(pi: {name});148 }149 pi150 }151}152153impl Printable for Destruct {154 fn print(&self) -> PrintItems {155 let mut pi = p!(new:);156 match self {157 Destruct::DestructFull(f) => {158 p!(pi: {f.name()})159 }160 Destruct::DestructSkip(_) => p!(pi: str("?")),161 Destruct::DestructArray(a) => {162 p!(pi: str("[") >i nl);163 for el in a.destruct_array_parts() {164 match el {165 DestructArrayPart::DestructArrayElement(e) => {166 p!(pi: {e.destruct()} str(",") nl)167 }168 DestructArrayPart::DestructRest(d) => {169 p!(pi: {d} str(",") nl)170 }171 }172 }173 p!(pi: <i str("]"));174 }175 Destruct::DestructObject(o) => {176 p!(pi: str("{") >i nl);177 for item in o.destruct_object_fields() {178 p!(pi: {item.field()});179 if let Some(des) = item.destruct() {180 p!(pi: str(": ") {des})181 }182 if let Some(def) = item.expr() {183 p!(pi: str(" = ") {def});184 }185 p!(pi: str(",") nl);186 }187 if let Some(rest) = o.destruct_rest() {188 p!(pi: {rest} nl)189 }190 p!(pi: <i str("}"));191 }192 }193 pi194 }195}196197impl Printable for FieldName {198 fn print(&self) -> PrintItems {199 match self {200 FieldName::FieldNameFixed(f) => {201 if let Some(id) = f.id() {202 p!(new: {id})203 } else if let Some(str) = f.text() {204 p!(new: {str})205 } else {206 p!(new: str("/*missing FieldName*/"))207 }208 }209 FieldName::FieldNameDynamic(d) => {210 p!(new: str("[") {d.expr()} str("]"))211 }212 }213 }214}215216impl Printable for Visibility {217 fn print(&self) -> PrintItems {218 p!(new: string(self.to_string()))219 }220}221222impl Printable for ObjLocal {223 fn print(&self) -> PrintItems {224 p!(new: str("local ") {self.bind()})225 }226}227228impl Printable for Assertion {229 fn print(&self) -> PrintItems {230 let mut pi = p!(new: str("assert ") {self.condition()});231 if self.colon_token().is_some() || self.message().is_some() {232 p!(pi: str(": ") {self.message()})233 }234 pi235 }236}237238impl Printable for ParamsDesc {239 fn print(&self) -> PrintItems {240 let mut pi = p!(new: str("(") >i nl);241 for param in self.params() {242 p!(pi: {param.destruct()});243 if param.assign_token().is_some() || param.expr().is_some() {244 p!(pi: str(" = ") {param.expr()})245 }246 p!(pi: str(",") nl)247 }248 p!(pi: <i str(")"));249 pi250 }251}252impl Printable for ArgsDesc {253 fn print(&self) -> PrintItems {254 let mut pi = p!(new: str("(") >i nl);255 for arg in self.args() {256 if arg.name().is_some() || arg.assign_token().is_some() {257 p!(pi: {arg.name()} str(" = "));258 }259 p!(pi: {arg.expr()} str(",") nl)260 }261 p!(pi: <i str(")"));262 pi263 }264}265impl Printable for SliceDesc {266 fn print(&self) -> PrintItems {267 let mut pi = p!(new: str("["));268 if self.from().is_some() {269 p!(pi: {self.from()});270 }271 p!(pi: str(":"));272 if self.end().is_some() {273 p!(pi: {self.end().map(|e|e.expr())})274 }275 // Keep only one : in case if we don't need step276 if self.step().is_some() {277 p!(pi: str(":") {self.step().map(|e|e.expr())});278 }279 p!(pi: str("]"));280 pi281 }282}283284impl Printable for Member {285 fn print(&self) -> PrintItems {286 match self {287 Member::MemberBindStmt(b) => {288 p!(new: {b.obj_local()})289 }290 Member::MemberAssertStmt(ass) => {291 p!(new: {ass.assertion()})292 }293 Member::MemberFieldNormal(n) => {294 p!(new: {n.field_name()} if(n.plus_token().is_some())({n.plus_token()}) {n.visibility()} str(" ") {n.expr()})295 }296 Member::MemberFieldMethod(m) => {297 p!(new: {m.field_name()} {m.params_desc()} {m.visibility()} str(" ") {m.expr()})298 }299 }300 }301}302303impl Printable for ObjBody {304 fn print(&self) -> PrintItems {305 match self {306 ObjBody::ObjBodyComp(l) => {307 let (children, mut end_comments) = children_between::<Member>(308 l.syntax().clone(),309 l.l_brace_token().map(Into::into).as_ref(),310 Some(311 &(l.comp_specs()312 .next()313 .expect("at least one spec is defined")314 .syntax()315 .clone())316 .into(),317 ),318 None,319 );320 let trailing_for_comp = end_comments.extract_trailing();321 let mut pi = p!(new: str("{") >i nl);322 for mem in children.into_iter() {323 if mem.should_start_with_newline {324 p!(pi: nl);325 }326 p!(pi: items(format_comments(&mem.before_trivia, CommentLocation::AboveItem)));327 p!(pi: {mem.value} str(","));328 p!(pi: items(format_comments(&mem.inline_trivia, CommentLocation::ItemInline)));329 p!(pi: nl)330 }331332 if end_comments.should_start_with_newline {333 p!(pi: nl);334 }335 p!(pi: items(format_comments(&end_comments.trivia, CommentLocation::EndOfItems)));336337 let (compspecs, end_comments) = children_between::<CompSpec>(338 l.syntax().clone(),339 l.member_comps()340 .last()341 .map(|m| m.syntax().clone())342 .map(Into::into)343 .or_else(|| l.l_brace_token().map(Into::into))344 .as_ref(),345 l.r_brace_token().map(Into::into).as_ref(),346 Some(trailing_for_comp),347 );348 for mem in compspecs.into_iter() {349 if mem.should_start_with_newline {350 p!(pi: nl);351 }352 p!(pi: items(format_comments(&mem.before_trivia, CommentLocation::AboveItem)));353 p!(pi: {mem.value});354 p!(pi: items(format_comments(&mem.inline_trivia, CommentLocation::ItemInline)));355 }356 if end_comments.should_start_with_newline {357 p!(pi: nl);358 }359 p!(pi: items(format_comments(&end_comments.trivia, CommentLocation::EndOfItems)));360361 p!(pi: nl <i str("}"));362 pi363 }364 ObjBody::ObjBodyMemberList(l) => {365 let (children, end_comments) = children_between::<Member>(366 l.syntax().clone(),367 l.l_brace_token().map(Into::into).as_ref(),368 l.r_brace_token().map(Into::into).as_ref(),369 None,370 );371 if children.is_empty() && end_comments.is_empty() {372 return p!(new: str("{ }"));373 }374 let mut pi = p!(new: str("{") >i nl);375 for (i, mem) in children.into_iter().enumerate() {376 if mem.should_start_with_newline && i != 0 {377 p!(pi: nl);378 }379 p!(pi: items(format_comments(&mem.before_trivia, CommentLocation::AboveItem)));380 p!(pi: {mem.value} str(","));381 p!(pi: items(format_comments(&mem.inline_trivia, CommentLocation::ItemInline)));382 p!(pi: nl)383 }384385 if end_comments.should_start_with_newline {386 p!(pi: nl);387 }388 p!(pi: items(format_comments(&end_comments.trivia, CommentLocation::EndOfItems)));389 p!(pi: <i str("}"));390 pi391 }392 }393 }394}395impl Printable for UnaryOperator {396 fn print(&self) -> PrintItems {397 p!(new: string(self.text().to_string()))398 }399}400impl Printable for BinaryOperator {401 fn print(&self) -> PrintItems {402 p!(new: string(self.text().to_string()))403 }404}405impl Printable for Bind {406 fn print(&self) -> PrintItems {407 match self {408 Bind::BindDestruct(d) => {409 p!(new: {d.into()} str(" = ") {d.value()})410 }411 Bind::BindFunction(f) => {412 p!(new: {f.name()} {f.params()} str(" = ") {f.value()})413 }414 }415 }416}417impl Printable for Literal {418 fn print(&self) -> PrintItems {419 p!(new: string(self.syntax().to_string()))420 }421}422impl Printable for ImportKind {423 fn print(&self) -> PrintItems {424 p!(new: string(self.syntax().to_string()))425 }426}427impl Printable for ForSpec {428 fn print(&self) -> PrintItems {429 p!(new: str("for ") {self.bind()} str(" in ") {self.expr()})430 }431}432impl Printable for IfSpec {433 fn print(&self) -> PrintItems {434 p!(new: str("if ") {self.expr()})435 }436}437impl Printable for CompSpec {438 fn print(&self) -> PrintItems {439 match self {440 CompSpec::ForSpec(f) => f.print(),441 CompSpec::IfSpec(i) => i.print(),442 }443 }444}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 o478 }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 o504 }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 pi540 }541 Stmt::StmtAssert(a) => {542 p!(new: {a.assertion()} str(";") nl)543 }544 }545 }546}547impl Printable for ExprBase {548 fn print(&self) -> PrintItems {549 match self {550 Self::ExprBinary(b) => {551 p!(new: {b.lhs_work()} str(" ") {b.binary_operator()} str(" ") {b.rhs_work()})552 }553 Self::ExprUnary(u) => p!(new: {u.unary_operator()} {u.rhs()}),554 // Self::ExprSlice(s) => {555 // p!(new: {s.expr()} {s.slice_desc()})556 // }557 // Self::ExprIndex(i) => {558 // p!(new: {i.expr()} str(".") {i.index()})559 // }560 // Self::ExprIndexExpr(i) => p!(new: {i.base()} str("[") {i.index()} str("]")),561 // Self::ExprApply(a) => {562 // let mut pi = p!(new: {a.expr()} {a.args_desc()});563 // if a.tailstrict_kw_token().is_some() {564 // p!(pi: str(" tailstrict"));565 // }566 // pi567 // }568 Self::ExprObjExtend(ex) => {569 p!(new: {ex.lhs_work()} str(" ") {ex.rhs_work()})570 }571 Self::ExprParened(p) => {572 p!(new: str("(") {p.expr()} str(")"))573 }574 Self::ExprString(s) => p!(new: {s.text()}),575 Self::ExprNumber(n) => p!(new: {n.number()}),576 Self::ExprArray(a) => {577 let mut pi = p!(new: str("[") >i nl);578 for el in a.exprs() {579 p!(pi: {el} str(",") nl);580 }581 p!(pi: <i str("]"));582 pi583 }584 Self::ExprObject(obj) => {585 p!(new: {obj.obj_body()})586 }587 Self::ExprArrayComp(arr) => {588 let mut pi = p!(new: str("[") {arr.expr()});589 for spec in arr.comp_specs() {590 p!(pi: str(" ") {spec});591 }592 p!(pi: str("]"));593 pi594 }595 Self::ExprImport(v) => {596 p!(new: {v.import_kind()} str(" ") {v.text()})597 }598 Self::ExprVar(n) => p!(new: {n.name()}),599 // Self::ExprLocal(l) => {600 // }601 Self::ExprIfThenElse(ite) => {602 let mut pi =603 p!(new: str("if ") {ite.cond()} str(" then ") {ite.then().map(|t| t.expr())});604 if ite.else_kw_token().is_some() || ite.else_().is_some() {605 p!(pi: str(" else ") {ite.else_().map(|t| t.expr())})606 }607 pi608 }609 Self::ExprFunction(f) => p!(new: str("function") {f.params_desc()} nl {f.expr()}),610 // Self::ExprAssert(a) => p!(new: {a.assertion()} str("; ") {a.expr()}),611 Self::ExprError(e) => p!(new: str("error ") {e.expr()}),612 Self::ExprLiteral(l) => {613 p!(new: {l.literal()})614 }615 }616 }617}618619impl Printable for SourceFile {620 fn print(&self) -> PrintItems {621 let mut pi = p!(new:);622 let before = trivia_before(623 self.syntax().clone(),624 self.expr()625 .map(|e| e.syntax().clone())626 .map(Into::into)627 .as_ref(),628 );629 let after = trivia_after(630 self.syntax().clone(),631 self.expr()632 .map(|e| e.syntax().clone())633 .map(Into::into)634 .as_ref(),635 );636 p!(pi: items(format_comments(&before, CommentLocation::AboveItem)));637 p!(pi: {self.expr()} nl);638 p!(pi: items(format_comments(&after, CommentLocation::EndOfItems)));639 pi640 }641}642643struct FormatOptions {644 // 0 for hard tabs645 indent: u8,646}647fn format(input: &str, opts: &FormatOptions) -> Option<String> {648 let (parsed, errors) = jrsonnet_rowan_parser::parse(input);649 if !errors.is_empty() {650 let mut builder = hi_doc::SnippetBuilder::new(input);651 for error in errors {652 builder653 .error(hi_doc::Text::single(654 format!("{:?}", error.error).chars(),655 Default::default(),656 ))657 .range(658 error.range.start().into()659 ..=(usize::from(error.range.end()) - 1).max(error.range.start().into()),660 )661 .build();662 }663 let snippet = builder.build();664 let ansi = hi_doc::source_to_ansi(&snippet);665 eprintln!("{ansi}");666 // It is possible to recover from this failure, but the output may be broken, as formatter is free to skip667 // 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;672 }673 Some(dprint_core::formatting::format(674 || parsed.print(),675 PrintOptions {676 indent_width: if opts.indent == 0 {677 // Reasonable max length for both 2 and 4 space sized tabs.678 3679 } else {680 opts.indent681 },682 max_width: 100,683 use_tabs: opts.indent == 0,684 new_line_text: "\n",685 },686 ))687}688689#[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,700701 /// Exit with error if formatted does not match input702 #[arg(long)]703 test: bool,704 /// Number of spaces to indent with705 ///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 indentation710 #[arg(long)]711 hard_tabs: bool,712713 /// 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}719720#[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}731732fn 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();736 let input = if opts.exec {737 if opts.in_place {738 return Err(Error::InPlaceExec);739 }740 opts.input.clone()741 } else {742 fs::read_to_string(&opts.input)?743 };744745 if opts.indent == 0 {746 // Sane default.747 // TODO: Implement actual guessing.748 opts.hard_tabs = true;749 }750751 let mut iteration = 0;752 let mut formatted = input.clone();753 let mut tmp;754 // https://github.com/dprint/dprint/pull/423755 loop {756 let Some(reformatted) = format(757 &formatted,758 &FormatOptions {759 indent: if opts.indent == 0 || opts.hard_tabs {760 0761 } else {762 opts.indent763 },764 },765 ) else {766 return Err(Error::ParseError);767 };768 tmp = reformatted.trim().to_owned();769 if formatted == tmp {770 break;771 }772 formatted = tmp;773 if opts.conv_limit == 0 {774 break;775 }776 iteration += 1;777 if iteration > opts.conv_limit {778 panic!("formatting not converged");779 }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 {794 print!("{formatted}")795 }796 Ok(())797}798799fn main() {800 if let Err(e) = main_result() {801 eprintln!("{e}");802 process::exit(1);803 }804}1use std::{2 any::type_name,3 fs,4 io::{self, Write},5 path::PathBuf,6 process,7};89use children::{children_between, trivia_before};10use clap::Parser;11use dprint_core::formatting::{PrintItems, PrintOptions};12use jrsonnet_rowan_parser::{13 nodes::{14 ArgsDesc, Assertion, BinaryOperator, Bind, CompSpec, Destruct, DestructArrayPart,15 DestructRest, Expr, ExprBase, FieldName, ForSpec, IfSpec, ImportKind, Literal, Member,16 Name, Number, ObjBody, ObjLocal, ParamsDesc, SliceDesc, SourceFile, Stmt, Suffix, Text,17 UnaryOperator, Visibility,18 },19 AstNode, AstToken as _, SyntaxToken,20};2122use crate::{23 children::trivia_after,24 comments::{format_comments, CommentLocation},25};2627mod children;28mod comments;29#[cfg(test)]30mod tests;3132pub trait Printable {33 fn print(&self, out: &mut PrintItems);34}3536macro_rules! pi {37 (@i; $($t:tt)*) => {{38 #[allow(unused_mut)]39 let mut o = dprint_core::formatting::PrintItems::new();40 pi!(@s; o: $($t)*);41 o42 }};43 (@s; $o:ident: str($e:expr $(,)?) $($t:tt)*) => {{44 $o.push_str($e);45 pi!(@s; $o: $($t)*);46 }};47 (@s; $o:ident: string($e:expr $(,)?) $($t:tt)*) => {{48 $o.push_string($e);49 pi!(@s; $o: $($t)*);50 }};51 (@s; $o:ident: nl $($t:tt)*) => {{52 $o.push_signal(dprint_core::formatting::Signal::NewLine);53 pi!(@s; $o: $($t)*);54 }};55 (@s; $o:ident: tab $($t:tt)*) => {{56 $o.push_signal(dprint_core::formatting::Signal::Tab);57 pi!(@s; $o: $($t)*);58 }};59 (@s; $o:ident: >i $($t:tt)*) => {{60 $o.push_signal(dprint_core::formatting::Signal::StartIndent);61 pi!(@s; $o: $($t)*);62 }};63 (@s; $o:ident: <i $($t:tt)*) => {{64 $o.push_signal(dprint_core::formatting::Signal::FinishIndent);65 pi!(@s; $o: $($t)*);66 }};67 (@s; $o:ident: {$expr:expr} $($t:tt)*) => {{68 $expr.print($o);69 pi!(@s; $o: $($t)*);70 }};71 (@s; $o:ident: items($expr:expr) $($t:tt)*) => {{72 $o.extend($expr);73 pi!(@s; $o: $($t)*);74 }};75 (@s; $o:ident: if ($e:expr)($($then:tt)*) $($t:tt)*) => {{76 if $e {77 pi!(@s; $o: $($then)*);78 }79 pi!(@s; $o: $($t)*);80 }};81 (@s; $o:ident: ifelse ($e:expr)($($then:tt)*)($($else:tt)*) $($t:tt)*) => {{82 if $e {83 pi!(@s; $o: $($then)*);84 } else {85 pi!(@s; $o: $($else)*);86 }87 pi!(@s; $o: $($t)*);88 }};89 (@s; $i:ident:) => {}90}91macro_rules! p {92 ($o:ident, $($t:tt)*) => {93 pi!(@s; $o: $($t)*)94 };95}96pub(crate) use p;97pub(crate) use pi;9899impl<P> Printable for Option<P>100where101 P: Printable,102{103 fn print(&self, out: &mut PrintItems) {104 if let Some(v) = self {105 v.print(out)106 } else {107 p!(108 out,109 string(format!(110 "/*missing {}*/",111 type_name::<P>().replace("jrsonnet_rowan_parser::generated::nodes::", "")112 ),)113 )114 }115 }116}117118impl Printable for SyntaxToken {119 fn print(&self, out: &mut PrintItems) {120 p!(out, string(self.to_string()))121 }122}123124impl Printable for Text {125 fn print(&self, out: &mut PrintItems) {126 p!(out, string(format!("{}", self)))127 }128}129impl Printable for Number {130 fn print(&self, out: &mut PrintItems) {131 p!(out, string(format!("{}", self)))132 }133}134135impl Printable for Name {136 fn print(&self, out: &mut PrintItems) {137 p!(out, { self.ident_lit() })138 }139}140141impl Printable for DestructRest {142 fn print(&self, out: &mut PrintItems) {143 p!(out, str("..."));144 if let Some(name) = self.into() {145 p!(out, { name });146 }147 }148}149150impl Printable for Destruct {151 fn print(&self, out: &mut PrintItems) {152 match self {153 Destruct::DestructFull(f) => {154 p!(out, { f.name() })155 }156 Destruct::DestructSkip(_) => p!(out, str("?")),157 Destruct::DestructArray(a) => {158 p!(out, str("[") >i nl);159 for el in a.destruct_array_parts() {160 match el {161 DestructArrayPart::DestructArrayElement(e) => {162 p!(out, {e.destruct()} str(",") nl)163 }164 DestructArrayPart::DestructRest(d) => {165 p!(out, {d} str(",") nl)166 }167 }168 }169 p!(out, <i str("]"));170 }171 Destruct::DestructObject(o) => {172 p!(out, str("{") >i nl);173 for item in o.destruct_object_fields() {174 p!(out, { item.field() });175 if let Some(des) = item.destruct() {176 p!(out, str(": ") {des})177 }178 if let Some(def) = item.expr() {179 p!(out, str(" = ") {def});180 }181 p!(out, str(",") nl);182 }183 if let Some(rest) = o.destruct_rest() {184 p!(out, {rest} nl)185 }186 p!(out, <i str("}"));187 }188 }189 }190}191192impl Printable for FieldName {193 fn print(&self, out: &mut PrintItems) {194 match self {195 FieldName::FieldNameFixed(f) => {196 if let Some(id) = f.id() {197 p!(out, { id })198 } else if let Some(str) = f.text() {199 p!(out, { str })200 } else {201 p!(out, str("/*missing FieldName*/"))202 }203 }204 FieldName::FieldNameDynamic(d) => {205 p!(out, str("[") {d.expr()} str("]"))206 }207 }208 }209}210211impl Printable for Visibility {212 fn print(&self, out: &mut PrintItems) {213 p!(out, string(self.to_string()))214 }215}216217impl Printable for ObjLocal {218 fn print(&self, out: &mut PrintItems) {219 p!(out, str("local ") {self.bind()})220 }221}222223impl Printable for Assertion {224 fn print(&self, out: &mut PrintItems) {225 p!(out, str("assert ") {self.condition()});226 if self.colon_token().is_some() || self.message().is_some() {227 p!(out, str(": ") {self.message()})228 }229 }230}231232impl Printable for ParamsDesc {233 fn print(&self, out: &mut PrintItems) {234 p!(out, str("(") >i nl);235 for param in self.params() {236 p!(out, { param.destruct() });237 if param.assign_token().is_some() || param.expr().is_some() {238 p!(out, str(" = ") {param.expr()})239 }240 p!(out, str(",") nl)241 }242 p!(out, <i str(")"));243 }244}245impl Printable for ArgsDesc {246 fn print(&self, out: &mut PrintItems) {247 p!(out, str("(") >i nl);248 for arg in self.args() {249 if arg.name().is_some() || arg.assign_token().is_some() {250 p!(out, {arg.name()} str(" = "));251 }252 p!(out, {arg.expr()} str(",") nl)253 }254 p!(out, <i str(")"));255 }256}257impl Printable for SliceDesc {258 fn print(&self, out: &mut PrintItems) {259 p!(out, str("["));260 if self.from().is_some() {261 p!(out, { self.from() });262 }263 p!(out, str(":"));264 if self.end().is_some() {265 p!(out, { self.end().map(|e| e.expr()) })266 }267 // Keep only one : in case if we don't need step268 if self.step().is_some() {269 p!(out, str(":") {self.step().map(|e|e.expr())});270 }271 p!(out, str("]"));272 }273}274275impl Printable for Member {276 fn print(&self, out: &mut PrintItems) {277 match self {278 Member::MemberBindStmt(b) => {279 p!(out, { b.obj_local() })280 }281 Member::MemberAssertStmt(ass) => {282 p!(out, { ass.assertion() })283 }284 Member::MemberFieldNormal(n) => {285 p!(out, {n.field_name()} if(n.plus_token().is_some())({n.plus_token()}) {n.visibility()} str(" ") {n.expr()})286 }287 Member::MemberFieldMethod(m) => {288 p!(out, {m.field_name()} {m.params_desc()} {m.visibility()} str(" ") {m.expr()})289 }290 }291 }292}293294impl Printable for ObjBody {295 fn print(&self, out: &mut PrintItems) {296 match self {297 ObjBody::ObjBodyComp(l) => {298 let (children, mut end_comments) = children_between::<Member>(299 l.syntax().clone(),300 l.l_brace_token().map(Into::into).as_ref(),301 Some(302 &(l.comp_specs()303 .next()304 .expect("at least one spec is defined")305 .syntax()306 .clone())307 .into(),308 ),309 None,310 );311 let trailing_for_comp = end_comments.extract_trailing();312 p!(out, str("{") >i nl);313 for mem in children.into_iter() {314 if mem.should_start_with_newline {315 p!(out, nl);316 }317 format_comments(&mem.before_trivia, CommentLocation::AboveItem, out);318 p!(out, {mem.value} str(","));319 format_comments(&mem.inline_trivia, CommentLocation::ItemInline, out);320 p!(out, nl)321 }322323 if end_comments.should_start_with_newline {324 p!(out, nl);325 }326 format_comments(&end_comments.trivia, CommentLocation::EndOfItems, out);327328 let (compspecs, end_comments) = children_between::<CompSpec>(329 l.syntax().clone(),330 l.member_comps()331 .last()332 .map(|m| m.syntax().clone())333 .map(Into::into)334 .or_else(|| l.l_brace_token().map(Into::into))335 .as_ref(),336 l.r_brace_token().map(Into::into).as_ref(),337 Some(trailing_for_comp),338 );339 for mem in compspecs.into_iter() {340 if mem.should_start_with_newline {341 p!(out, nl);342 }343 format_comments(&mem.before_trivia, CommentLocation::AboveItem, out);344 p!(out, { mem.value });345 format_comments(&mem.inline_trivia, CommentLocation::ItemInline, out);346 }347 if end_comments.should_start_with_newline {348 p!(out, nl);349 }350 format_comments(&end_comments.trivia, CommentLocation::EndOfItems, out);351352 p!(out, nl <i str("}"));353 }354 ObjBody::ObjBodyMemberList(l) => {355 let (children, end_comments) = children_between::<Member>(356 l.syntax().clone(),357 l.l_brace_token().map(Into::into).as_ref(),358 l.r_brace_token().map(Into::into).as_ref(),359 None,360 );361 if children.is_empty() && end_comments.is_empty() {362 p!(out, str("{ }"));363 return;364 }365 p!(out, str("{") >i nl);366 for (i, mem) in children.into_iter().enumerate() {367 if mem.should_start_with_newline && i != 0 {368 p!(out, nl);369 }370 format_comments(&mem.before_trivia, CommentLocation::AboveItem, out);371 p!(out, {mem.value} str(","));372 format_comments(&mem.inline_trivia, CommentLocation::ItemInline, out);373 p!(out, nl)374 }375376 if end_comments.should_start_with_newline {377 p!(out, nl);378 }379 format_comments(&end_comments.trivia, CommentLocation::EndOfItems, out);380 p!(out, <i str("}"));381 }382 }383 }384}385impl Printable for UnaryOperator {386 fn print(&self, out: &mut PrintItems) {387 p!(out, string(self.text().to_string()))388 }389}390impl Printable for BinaryOperator {391 fn print(&self, out: &mut PrintItems) {392 p!(out, string(self.text().to_string()))393 }394}395impl Printable for Bind {396 fn print(&self, out: &mut PrintItems) {397 match self {398 Bind::BindDestruct(d) => {399 p!(out, {d.into()} str(" = ") {d.value()})400 }401 Bind::BindFunction(f) => {402 p!(out, {f.name()} {f.params()} str(" = ") {f.value()})403 }404 }405 }406}407impl Printable for Literal {408 fn print(&self, out: &mut PrintItems) {409 p!(out, string(self.syntax().to_string()))410 }411}412impl Printable for ImportKind {413 fn print(&self, out: &mut PrintItems) {414 p!(out, string(self.syntax().to_string()))415 }416}417impl Printable for ForSpec {418 fn print(&self, out: &mut PrintItems) {419 p!(out, str("for ") {self.bind()} str(" in ") {self.expr()})420 }421}422impl Printable for IfSpec {423 fn print(&self, out: &mut PrintItems) {424 p!(out, str("if ") {self.expr()})425 }426}427impl Printable for CompSpec {428 fn print(&self, out: &mut PrintItems) {429 match self {430 CompSpec::ForSpec(f) => f.print(out),431 CompSpec::IfSpec(i) => i.print(out),432 }433 }434}435impl Printable for Expr {436 fn print(&self, out: &mut PrintItems) {437 let (stmts, _ending) = children_between::<Stmt>(438 self.syntax().clone(),439 None,440 self.expr_base()441 .as_ref()442 .map(ExprBase::syntax)443 .cloned()444 .map(Into::into)445 .as_ref(),446 None,447 );448 for stmt in stmts {449 p!(out, { stmt.value });450 }451 p!(out, { self.expr_base() });452 let (suffixes, _ending) = children_between::<Suffix>(453 self.syntax().clone(),454 self.expr_base()455 .as_ref()456 .map(ExprBase::syntax)457 .cloned()458 .map(Into::into)459 .as_ref(),460 None,461 None,462 );463 for suffix in suffixes {464 p!(out, { suffix.value });465 }466 }467}468impl Printable for Suffix {469 fn print(&self, out: &mut PrintItems) {470 match self {471 Suffix::SuffixIndex(i) => {472 if i.question_mark_token().is_some() {473 p!(out, str("?"));474 }475 p!(out, str(".") {i.index()});476 }477 Suffix::SuffixIndexExpr(e) => {478 if e.question_mark_token().is_some() {479 p!(out, str(".?"));480 }481 p!(out, str("[") {e.index()} str("]"))482 }483 Suffix::SuffixSlice(d) => {484 p!(out, { d.slice_desc() })485 }486 Suffix::SuffixApply(a) => {487 p!(out, { a.args_desc() })488 }489 }490 }491}492impl Printable for Stmt {493 fn print(&self, out: &mut PrintItems) {494 match self {495 Stmt::StmtLocal(l) => {496 let (binds, end_comments) = children_between::<Bind>(497 l.syntax().clone(),498 l.local_kw_token().map(Into::into).as_ref(),499 l.semi_token().map(Into::into).as_ref(),500 None,501 );502 if binds.len() == 1 {503 let bind = &binds[0];504 format_comments(&bind.before_trivia, CommentLocation::AboveItem, out);505 p!(out, str("local ") {bind.value});506 // TODO: keep end_comments, child.inline_trivia somehow, force multiple locals formatting in case of presence?507 } else {508 p!(out,str("local") >i nl);509 for bind in binds {510 if bind.should_start_with_newline {511 p!(out, nl);512 }513 format_comments(&bind.before_trivia, CommentLocation::AboveItem, out);514 p!(out, {bind.value} str(","));515 format_comments(&bind.inline_trivia, CommentLocation::ItemInline, out);516 }517 if end_comments.should_start_with_newline {518 p!(out, nl)519 }520 format_comments(&end_comments.trivia, CommentLocation::EndOfItems, out);521 p!(out,<i);522 }523 p!(out,str(";") nl);524 }525 Stmt::StmtAssert(a) => {526 p!(out, {a.assertion()} str(";") nl)527 }528 }529 }530}531impl Printable for ExprBase {532 fn print(&self, out: &mut PrintItems) {533 match self {534 Self::ExprBinary(b) => {535 p!(out, {b.lhs_work()} str(" ") {b.binary_operator()} str(" ") {b.rhs_work()})536 }537 Self::ExprUnary(u) => p!(out, {u.unary_operator()} {u.rhs()}),538 // Self::ExprSlice(s) => {539 // p!(new: {s.expr()} {s.slice_desc()})540 // }541 // Self::ExprIndex(i) => {542 // p!(new: {i.expr()} str(".") {i.index()})543 // }544 // Self::ExprIndexExpr(i) => p!(new: {i.base()} str("[") {i.index()} str("]")),545 // Self::ExprApply(a) => {546 // let mut pi = p!(new: {a.expr()} {a.args_desc()});547 // if a.tailstrict_kw_token().is_some() {548 // p!(out,str(" tailstrict"));549 // }550 // pi551 // }552 Self::ExprObjExtend(ex) => {553 p!(out, {ex.lhs_work()} str(" ") {ex.rhs_work()})554 }555 Self::ExprParened(p) => {556 p!(out, str("(") {p.expr()} str(")"))557 }558 Self::ExprString(s) => p!(out, { s.text() }),559 Self::ExprNumber(n) => p!(out, { n.number() }),560 Self::ExprArray(a) => {561 p!(out, str("[") >i nl);562 for el in a.exprs() {563 p!(out, {el} str(",") nl);564 }565 p!(out, <i str("]"));566 }567 Self::ExprObject(obj) => {568 p!(out, { obj.obj_body() })569 }570 Self::ExprArrayComp(arr) => {571 p!(out, str("[") {arr.expr()});572 for spec in arr.comp_specs() {573 p!(out, str(" ") {spec});574 }575 p!(out, str("]"));576 }577 Self::ExprImport(v) => {578 p!(out, {v.import_kind()} str(" ") {v.text()})579 }580 Self::ExprVar(n) => p!(out, { n.name() }),581 // Self::ExprLocal(l) => {582 // }583 Self::ExprIfThenElse(ite) => {584 p!(out, str("if ") {ite.cond()} str(" then ") {ite.then().map(|t| t.expr())});585 if ite.else_kw_token().is_some() || ite.else_().is_some() {586 p!(out, str(" else ") {ite.else_().map(|t| t.expr())})587 }588 }589 Self::ExprFunction(f) => p!(out, str("function") {f.params_desc()} nl {f.expr()}),590 // Self::ExprAssert(a) => p!(new: {a.assertion()} str("; ") {a.expr()}),591 Self::ExprError(e) => p!(out, str("error ") {e.expr()}),592 Self::ExprLiteral(l) => {593 p!(out, { l.literal() })594 }595 }596 }597}598599impl Printable for SourceFile {600 fn print(&self, out: &mut PrintItems) {601 let before = trivia_before(602 self.syntax().clone(),603 self.expr()604 .map(|e| e.syntax().clone())605 .map(Into::into)606 .as_ref(),607 );608 let after = trivia_after(609 self.syntax().clone(),610 self.expr()611 .map(|e| e.syntax().clone())612 .map(Into::into)613 .as_ref(),614 );615 format_comments(&before, CommentLocation::AboveItem, out);616 p!(out, {self.expr()} nl);617 format_comments(&after, CommentLocation::EndOfItems, out)618 }619}620621struct FormatOptions {622 // 0 for hard tabs623 indent: u8,624}625fn format(input: &str, opts: &FormatOptions) -> Option<String> {626 let (parsed, errors) = jrsonnet_rowan_parser::parse(input);627 if !errors.is_empty() {628 let mut builder = hi_doc::SnippetBuilder::new(input);629 for error in errors {630 builder631 .error(hi_doc::Text::single(632 format!("{:?}", error.error).chars(),633 Default::default(),634 ))635 .range(636 error.range.start().into()637 ..=(usize::from(error.range.end()) - 1).max(error.range.start().into()),638 )639 .build();640 }641 let snippet = builder.build();642 let ansi = hi_doc::source_to_ansi(&snippet);643 eprintln!("{ansi}");644 // It is possible to recover from this failure, but the output may be broken, as formatter is free to skip645 // ERROR rowan nodes.646 // Recovery needs to be enabled for LSP, though.647 //648 // TODO: Verify how formatter interacts in cases of missing positional values, i.e `if cond then /*missing Expr*/ else residual`.649 return None;650 }651 Some(dprint_core::formatting::format(652 || {653 let mut out = PrintItems::new();654 parsed.print(&mut out);655 out656 },657 PrintOptions {658 indent_width: if opts.indent == 0 {659 // Reasonable max length for both 2 and 4 space sized tabs.660 3661 } else {662 opts.indent663 },664 max_width: 100,665 use_tabs: opts.indent == 0,666 new_line_text: "\n",667 },668 ))669}670671#[derive(Parser)]672struct Opts {673 /// Treat input as code, reformat it instead of reading file.674 #[clap(long, short = 'e')]675 exec: bool,676 /// Path to be reformatted if `--exec` if unset, otherwise code itself.677 input: String,678 /// Replace code with formatted in-place, instead of printing it to stdout.679 /// Only applicable if `--exec` is unset.680 #[clap(long, short = 'i')]681 in_place: bool,682683 /// Exit with error if formatted does not match input684 #[arg(long)]685 test: bool,686 /// Number of spaces to indent with687 ///688 /// 0 for guess from input (default), and use hard tabs if unable to guess.689 #[arg(long, default_value = "0")]690 indent: u8,691 /// Force hard tab for indentation692 #[arg(long)]693 hard_tabs: bool,694695 /// Debug option: how many times to call reformatting in case of unstable dprint output resolution.696 ///697 /// 0 for not retrying to reformat.698 #[arg(long, default_value = "0")]699 conv_limit: usize,700}701702#[derive(thiserror::Error, Debug)]703enum Error {704 #[error("--in-place is incompatible with --exec")]705 InPlaceExec,706 #[error("io: {0}")]707 Io(#[from] io::Error),708 #[error("persist: {0}")]709 Persist(#[from] tempfile::PersistError),710 #[error("parsing failed, refusing to reformat corrupted input")]711 Parse,712}713714fn main_result() -> Result<(), Error> {715 eprintln!("jrsonnet-fmt is a prototype of a jsonnet code formatter, do not expect it to produce meaningful results right now.");716 eprintln!("It is not expected for its output to match other implementations, it will be completly separate implementation with maybe different name.");717 let mut opts = Opts::parse();718 let input = if opts.exec {719 if opts.in_place {720 return Err(Error::InPlaceExec);721 }722 opts.input.clone()723 } else {724 fs::read_to_string(&opts.input)?725 };726727 if opts.indent == 0 {728 // Sane default.729 // TODO: Implement actual guessing.730 opts.hard_tabs = true;731 }732733 let mut iteration = 0;734 let mut formatted = input.clone();735 let mut tmp;736 // https://github.com/dprint/dprint/pull/423737 loop {738 let Some(reformatted) = format(739 &formatted,740 &FormatOptions {741 indent: if opts.indent == 0 || opts.hard_tabs {742 0743 } else {744 opts.indent745 },746 },747 ) else {748 return Err(Error::Parse);749 };750 tmp = reformatted.trim().to_owned();751 if formatted == tmp {752 break;753 }754 formatted = tmp;755 if opts.conv_limit == 0 {756 break;757 }758 iteration += 1;759 if iteration > opts.conv_limit {760 panic!("formatting not converged");761 }762 }763 formatted.push('\n');764 if opts.test && formatted != input {765 process::exit(1);766 }767 if opts.in_place {768 let path = PathBuf::from(opts.input);769 let mut temp = tempfile::NamedTempFile::new_in(path.parent().expect(770 "not failed during read, this path is not a directory, and there is a parent",771 ))?;772 temp.write_all(formatted.as_bytes())?;773 temp.flush()?;774 temp.persist(&path)?;775 } else {776 print!("{formatted}")777 }778 Ok(())779}780781fn main() {782 if let Err(e) = main_result() {783 eprintln!("{e}");784 process::exit(1);785 }786}