git.delta.rocks / jrsonnet / refs/commits / 6e67554e1625

difftreelog

source

cmds/jrsonnet-fmt/src/main.rs21.8 KiBsourcehistory
1use 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 hi_doc::Formatting;17use jrsonnet_rowan_parser::{18	nodes::{19		Arg, ArgsDesc, Assertion, BinaryOperator, Bind, CompSpec, Destruct, DestructArrayPart,20		DestructRest, Expr, ExprBase, FieldName, ForSpec, IfSpec, ImportKind, Literal, Member,21		Name, Number, ObjBody, ObjLocal, ParamsDesc, SliceDesc, SourceFile, Stmt, Suffix, Text,22		UnaryOperator, Visibility,23	},24	AstNode, AstToken as _, SyntaxToken,25};2627use crate::{28	children::trivia_after,29	comments::{format_comments, CommentLocation},30};3132mod children;33mod comments;34#[cfg(test)]35mod tests;3637pub trait Printable {38	fn print(&self, out: &mut PrintItems);39}4041macro_rules! pi {42	(@i; $($t:tt)*) => {{43		#[allow(unused_mut)]44		let mut o = dprint_core::formatting::PrintItems::new();45		pi!(@s; o: $($t)*);46		o47	}};48	(@s; $o:ident: str($e:expr $(,)?) $($t:tt)*) => {{49		$o.push_string($e.to_owned());50		pi!(@s; $o: $($t)*);51	}};52	(@s; $o:ident: string($e:expr $(,)?) $($t:tt)*) => {{53		$o.push_string($e);54		pi!(@s; $o: $($t)*);55	}};56	(@s; $o:ident: nl $($t:tt)*) => {{57		$o.push_signal(dprint_core::formatting::Signal::NewLine);58		pi!(@s; $o: $($t)*);59	}};60	(@s; $o:ident: tab $($t:tt)*) => {{61		$o.push_signal(dprint_core::formatting::Signal::Tab);62		pi!(@s; $o: $($t)*);63	}};64	(@s; $o:ident: >i $($t:tt)*) => {{65		$o.push_signal(dprint_core::formatting::Signal::StartIndent);66		pi!(@s; $o: $($t)*);67	}};68	(@s; $o:ident: <i $($t:tt)*) => {{69		$o.push_signal(dprint_core::formatting::Signal::FinishIndent);70		pi!(@s; $o: $($t)*);71	}};72	(@s; $o:ident: info($v:expr) $($t:tt)*) => {{73		$o.push_info($v);74		pi!(@s; $o: $($t)*);75	}};76	(@s; $o:ident: if($s:literal, $cond:expr, $($i:tt)*) $($t:tt)*) => {{77		$o.push_condition(dprint_core::formatting::conditions::if_true(78			$s,79			$cond.clone(),80			{81				let mut o = PrintItems::new();82				p!(o, $($i)*);83				o84			},85		));86		pi!(@s; $o: $($t)*);87	}};88	(@s; $o:ident: if_else($s:literal, $cond:expr, $($i:tt)*)($($e:tt)+) $($t:tt)*) => {{89		$o.push_condition(dprint_core::formatting::conditions::if_true_or(90			$s,91			$cond.clone(),92			{93				let mut o = PrintItems::new();94				p!(o, $($i)*);95				o96			},97			{98				let mut o = PrintItems::new();99				p!(o, $($e)*);100				o101			},102		));103		pi!(@s; $o: $($t)*);104	}};105	(@s; $o:ident: if_not($s:literal, $cond:expr, $($e:tt)*) $($t:tt)*) => {{106		$o.push_condition(dprint_core::formatting::conditions::if_true_or(107			$s,108			$cond.clone(),109			{110				let o = PrintItems::new();111				o112			},113			{114				let mut o = PrintItems::new();115				p!(o, $($e)*);116				o117			},118		));119		pi!(@s; $o: $($t)*);120	}};121	(@s; $o:ident: {$expr:expr} $($t:tt)*) => {{122		$expr.print($o);123		pi!(@s; $o: $($t)*);124	}};125	(@s; $o:ident: items($expr:expr) $($t:tt)*) => {{126		$o.extend($expr);127		pi!(@s; $o: $($t)*);128	}};129	(@s; $o:ident: if ($e:expr)($($then:tt)*) $($t:tt)*) => {{130		if $e {131			pi!(@s; $o: $($then)*);132		}133		pi!(@s; $o: $($t)*);134	}};135	(@s; $o:ident: ifelse ($e:expr)($($then:tt)*)($($else:tt)*) $($t:tt)*) => {{136		if $e {137			pi!(@s; $o: $($then)*);138		} else {139			pi!(@s; $o: $($else)*);140		}141		pi!(@s; $o: $($t)*);142	}};143	(@s; $i:ident:) => {}144}145macro_rules! p {146	($o:ident, $($t:tt)*) => {147		pi!(@s; $o: $($t)*)148	};149}150pub(crate) use p;151pub(crate) use pi;152153impl<P> Printable for Option<P>154where155	P: Printable,156{157	fn print(&self, out: &mut PrintItems) {158		if let Some(v) = self {159			v.print(out);160		} else {161			p!(162				out,163				string(format!(164					"/*missing {}*/",165					type_name::<P>().replace("jrsonnet_rowan_parser::generated::nodes::", "")166				),)167			);168		}169	}170}171172impl Printable for SyntaxToken {173	fn print(&self, out: &mut PrintItems) {174		p!(out, string(self.to_string()));175	}176}177178impl Printable for Text {179	fn print(&self, out: &mut PrintItems) {180		p!(out, string(format!("{}", self)));181	}182}183impl Printable for Number {184	fn print(&self, out: &mut PrintItems) {185		p!(out, string(format!("{}", self)));186	}187}188189impl Printable for Name {190	fn print(&self, out: &mut PrintItems) {191		p!(out, { self.ident_lit() });192	}193}194195impl Printable for DestructRest {196	fn print(&self, out: &mut PrintItems) {197		p!(out, str("..."));198		if let Some(name) = self.into() {199			p!(out, { name });200		}201	}202}203204impl Printable for Destruct {205	fn print(&self, out: &mut PrintItems) {206		match self {207			Self::DestructFull(f) => {208				p!(out, { f.name() });209			}210			Self::DestructSkip(_) => p!(out, str("?")),211			Self::DestructArray(a) => {212				p!(out, str("[") >i nl);213				for el in a.destruct_array_parts() {214					match el {215						DestructArrayPart::DestructArrayElement(e) => {216							p!(out, {e.destruct()} str(",") nl);217						}218						DestructArrayPart::DestructRest(d) => {219							p!(out, {d} str(",") nl);220						}221					}222				}223				p!(out, <i str("]"));224			}225			Self::DestructObject(o) => {226				p!(out, str("{") >i nl);227				for item in o.destruct_object_fields() {228					p!(out, { item.field() });229					if let Some(des) = item.destruct() {230						p!(out, str(": ") {des});231					}232					if let Some(def) = item.expr() {233						p!(out, str(" = ") {def});234					}235					p!(out, str(",") nl);236				}237				if let Some(rest) = o.destruct_rest() {238					p!(out, {rest} nl);239				}240				p!(out, <i str("}"));241			}242		}243	}244}245246impl Printable for FieldName {247	fn print(&self, out: &mut PrintItems) {248		match self {249			Self::FieldNameFixed(f) => {250				if let Some(id) = f.id() {251					p!(out, { id });252				} else if let Some(str) = f.text() {253					p!(out, { str });254				} else {255					p!(out, str("/*missing FieldName*/"));256				}257			}258			Self::FieldNameDynamic(d) => {259				p!(out, str("[") {d.expr()} str("]"));260			}261		}262	}263}264265impl Printable for Visibility {266	fn print(&self, out: &mut PrintItems) {267		p!(out, string(self.to_string()));268	}269}270271impl Printable for ObjLocal {272	fn print(&self, out: &mut PrintItems) {273		p!(out, str("local ") {self.bind()});274	}275}276277impl Printable for Assertion {278	fn print(&self, out: &mut PrintItems) {279		p!(out, str("assert ") {self.condition()});280		if self.colon_token().is_some() || self.message().is_some() {281			p!(out, str(": ") {self.message()});282		}283	}284}285286impl Printable for ParamsDesc {287	fn print(&self, out: &mut PrintItems) {288		p!(out, str("(") >i nl);289		for param in self.params() {290			p!(out, { param.destruct() });291			if param.assign_token().is_some() || param.expr().is_some() {292				p!(out, str(" = ") {param.expr()});293			}294			p!(out, str(",") nl);295		}296		p!(out, <i str(")"));297	}298}299impl Printable for ArgsDesc {300	fn print(&self, out: &mut PrintItems) {301		let start = LineNumber::new("start");302		let end = LineNumber::new("end");303		let multi_line = Rc::new(move |condition_context: &mut ConditionResolverContext| {304			is_multiple_lines(condition_context, start, end).map(|v| !v)305		});306		p!(out, str("(") info(start) if("start args", multi_line, >i nl));307		let (children, end_comments) = children_between::<Arg>(308			self.syntax().clone(),309			self.l_paren_token().map(Into::into).as_ref(),310			self.r_paren_token().map(Into::into).as_ref(),311			None,312		);313		let mut args = children.into_iter().peekable();314		while let Some(ele) = args.next() {315			if ele.should_start_with_newline {316				p!(out, nl);317			}318			format_comments(&ele.before_trivia, CommentLocation::AboveItem, out);319			let arg = ele.value;320			if arg.name().is_some() || arg.assign_token().is_some() {321				p!(out, {arg.name()} str(" = "));322			}323			let comma_between = if args.peek().is_some() {324				true_resolver()325			} else {326				multi_line.clone()327			};328			p!(out, {arg.expr()} if("arg comma", comma_between, str(",") if_not("between args", multi_line, str(" "))));329			format_comments(&ele.inline_trivia, CommentLocation::ItemInline, out);330			p!(out, if("between args", multi_line, nl));331		}332		if end_comments.should_start_with_newline {333			p!(out, nl);334		}335		format_comments(&end_comments.trivia, CommentLocation::EndOfItems, out);336		p!(out, if("end args", multi_line, <i info(end)) str(")"));337	}338}339impl Printable for SliceDesc {340	fn print(&self, out: &mut PrintItems) {341		p!(out, str("["));342		if self.from().is_some() {343			p!(out, { self.from() });344		}345		p!(out, str(":"));346		if self.end().is_some() {347			p!(out, { self.end().map(|e| e.expr()) });348		}349		// Keep only one : in case if we don't need step350		if self.step().is_some() {351			p!(out, str(":") {self.step().map(|e|e.expr())});352		}353		p!(out, str("]"));354	}355}356357impl Printable for Member {358	fn print(&self, out: &mut PrintItems) {359		match self {360			Self::MemberBindStmt(b) => {361				p!(out, { b.obj_local() });362			}363			Self::MemberAssertStmt(ass) => {364				p!(out, { ass.assertion() });365			}366			Self::MemberFieldNormal(n) => {367				p!(out, {n.field_name()} if(n.plus_token().is_some())({n.plus_token()}) {n.visibility()} str(" ") {n.expr()});368			}369			Self::MemberFieldMethod(m) => {370				p!(out, {m.field_name()} {m.params_desc()} {m.visibility()} str(" ") {m.expr()});371			}372		}373	}374}375376impl Printable for ObjBody {377	fn print(&self, out: &mut PrintItems) {378		match self {379			Self::ObjBodyComp(l) => {380				let (children, mut end_comments) = children_between::<Member>(381					l.syntax().clone(),382					l.l_brace_token().map(Into::into).as_ref(),383					Some(384						&(l.comp_specs()385							.next()386							.expect("at least one spec is defined")387							.syntax()388							.clone())389						.into(),390					),391					None,392				);393				let trailing_for_comp = end_comments.extract_trailing();394				p!(out, str("{") >i nl);395				for mem in children {396					if mem.should_start_with_newline {397						p!(out, nl);398					}399					format_comments(&mem.before_trivia, CommentLocation::AboveItem, out);400					p!(out, {mem.value} str(","));401					format_comments(&mem.inline_trivia, CommentLocation::ItemInline, out);402					p!(out, nl);403				}404405				if end_comments.should_start_with_newline {406					p!(out, nl);407				}408				format_comments(&end_comments.trivia, CommentLocation::EndOfItems, out);409410				let (compspecs, end_comments) = children_between::<CompSpec>(411					l.syntax().clone(),412					l.member_comps()413						.last()414						.map(|m| m.syntax().clone())415						.map(Into::into)416						.or_else(|| l.l_brace_token().map(Into::into))417						.as_ref(),418					l.r_brace_token().map(Into::into).as_ref(),419					Some(trailing_for_comp),420				);421				for mem in compspecs {422					if mem.should_start_with_newline {423						p!(out, nl);424					}425					format_comments(&mem.before_trivia, CommentLocation::AboveItem, out);426					p!(out, { mem.value });427					format_comments(&mem.inline_trivia, CommentLocation::ItemInline, out);428				}429				if end_comments.should_start_with_newline {430					p!(out, nl);431				}432				format_comments(&end_comments.trivia, CommentLocation::EndOfItems, out);433434				p!(out, nl <i str("}"));435			}436			Self::ObjBodyMemberList(l) => {437				let (children, end_comments) = children_between::<Member>(438					l.syntax().clone(),439					l.l_brace_token().map(Into::into).as_ref(),440					l.r_brace_token().map(Into::into).as_ref(),441					None,442				);443				if children.is_empty() && end_comments.is_empty() {444					p!(out, str("{ }"));445					return;446				}447				p!(out, str("{") >i nl);448				for (i, mem) in children.into_iter().enumerate() {449					if mem.should_start_with_newline && i != 0 {450						p!(out, nl);451					}452					format_comments(&mem.before_trivia, CommentLocation::AboveItem, out);453					p!(out, {mem.value} str(","));454					format_comments(&mem.inline_trivia, CommentLocation::ItemInline, out);455					p!(out, nl);456				}457458				if end_comments.should_start_with_newline {459					p!(out, nl);460				}461				format_comments(&end_comments.trivia, CommentLocation::EndOfItems, out);462				p!(out, <i str("}"));463			}464		}465	}466}467impl Printable for UnaryOperator {468	fn print(&self, out: &mut PrintItems) {469		p!(out, string(self.text().to_string()));470	}471}472impl Printable for BinaryOperator {473	fn print(&self, out: &mut PrintItems) {474		p!(out, string(self.text().to_string()));475	}476}477impl Printable for Bind {478	fn print(&self, out: &mut PrintItems) {479		match self {480			Self::BindDestruct(d) => {481				p!(out, {d.into()} str(" = ") {d.value()});482			}483			Self::BindFunction(f) => {484				p!(out, {f.name()} {f.params()} str(" = ") {f.value()});485			}486		}487	}488}489impl Printable for Literal {490	fn print(&self, out: &mut PrintItems) {491		p!(out, string(self.syntax().to_string()));492	}493}494impl Printable for ImportKind {495	fn print(&self, out: &mut PrintItems) {496		p!(out, string(self.syntax().to_string()));497	}498}499impl Printable for ForSpec {500	fn print(&self, out: &mut PrintItems) {501		p!(out, str("for ") {self.bind()} str(" in ") {self.expr()});502	}503}504impl Printable for IfSpec {505	fn print(&self, out: &mut PrintItems) {506		p!(out, str("if ") {self.expr()});507	}508}509impl Printable for CompSpec {510	fn print(&self, out: &mut PrintItems) {511		match self {512			Self::ForSpec(f) => f.print(out),513			Self::IfSpec(i) => i.print(out),514		}515	}516}517impl Printable for Expr {518	fn print(&self, out: &mut PrintItems) {519		let (stmts, _ending) = children_between::<Stmt>(520			self.syntax().clone(),521			None,522			self.expr_base()523				.as_ref()524				.map(ExprBase::syntax)525				.cloned()526				.map(Into::into)527				.as_ref(),528			None,529		);530		for stmt in stmts {531			p!(out, { stmt.value });532		}533		p!(out, { self.expr_base() });534		let (suffixes, _ending) = children_between::<Suffix>(535			self.syntax().clone(),536			self.expr_base()537				.as_ref()538				.map(ExprBase::syntax)539				.cloned()540				.map(Into::into)541				.as_ref(),542			None,543			None,544		);545		for suffix in suffixes {546			p!(out, { suffix.value });547		}548	}549}550impl Printable for Suffix {551	fn print(&self, out: &mut PrintItems) {552		match self {553			Self::SuffixIndex(i) => {554				if i.question_mark_token().is_some() {555					p!(out, str("?"));556				}557				p!(out, str(".") {i.index()});558			}559			Self::SuffixIndexExpr(e) => {560				if e.question_mark_token().is_some() {561					p!(out, str(".?"));562				}563				p!(out, str("[") {e.index()} str("]"));564			}565			Self::SuffixSlice(d) => {566				p!(out, { d.slice_desc() });567			}568			Self::SuffixApply(a) => {569				p!(out, { a.args_desc() });570			}571		}572	}573}574impl Printable for Stmt {575	fn print(&self, out: &mut PrintItems) {576		match self {577			Self::StmtLocal(l) => {578				let (binds, end_comments) = children_between::<Bind>(579					l.syntax().clone(),580					l.local_kw_token().map(Into::into).as_ref(),581					l.semi_token().map(Into::into).as_ref(),582					None,583				);584				if binds.len() == 1 {585					let bind = &binds[0];586					format_comments(&bind.before_trivia, CommentLocation::AboveItem, out);587					p!(out, str("local ") {bind.value});588				// TODO: keep end_comments, child.inline_trivia somehow, force multiple locals formatting in case of presence?589				} else {590					p!(out,str("local") >i nl);591					for bind in binds {592						if bind.should_start_with_newline {593							p!(out, nl);594						}595						format_comments(&bind.before_trivia, CommentLocation::AboveItem, out);596						p!(out, {bind.value} str(","));597						format_comments(&bind.inline_trivia, CommentLocation::ItemInline, out);598						p!(out, nl);599					}600					if end_comments.should_start_with_newline {601						p!(out, nl);602					}603					format_comments(&end_comments.trivia, CommentLocation::EndOfItems, out);604					p!(out,<i);605				}606				p!(out,str(";") nl);607			}608			Self::StmtAssert(a) => {609				p!(out, {a.assertion()} str(";") nl);610			}611		}612	}613}614impl Printable for ExprBase {615	fn print(&self, out: &mut PrintItems) {616		match self {617			Self::ExprBinary(b) => {618				p!(out, {b.lhs_work()} str(" ") {b.binary_operator()} str(" ") {b.rhs_work()});619			}620			Self::ExprUnary(u) => p!(out, {u.unary_operator()} {u.rhs()}),621			// Self::ExprSlice(s) => {622			// 	p!(new: {s.expr()} {s.slice_desc()})623			// }624			// Self::ExprIndex(i) => {625			// 	p!(new: {i.expr()} str(".") {i.index()})626			// }627			// Self::ExprIndexExpr(i) => p!(new: {i.base()} str("[") {i.index()} str("]")),628			// Self::ExprApply(a) => {629			// 	let mut pi = p!(new: {a.expr()} {a.args_desc()});630			// 	if a.tailstrict_kw_token().is_some() {631			// 		p!(out,str(" tailstrict"));632			// 	}633			// 	pi634			// }635			Self::ExprObjExtend(ex) => {636				p!(out, {ex.lhs_work()} str(" ") {ex.rhs_work()});637			}638			Self::ExprParened(p) => {639				p!(out, str("(") {p.expr()} str(")"));640			}641			Self::ExprString(s) => p!(out, { s.text() }),642			Self::ExprNumber(n) => p!(out, { n.number() }),643			Self::ExprArray(a) => {644				p!(out, str("[") >i nl);645				for el in a.exprs() {646					p!(out, {el} str(",") nl);647				}648				p!(out, <i str("]"));649			}650			Self::ExprObject(obj) => {651				p!(out, { obj.obj_body() });652			}653			Self::ExprArrayComp(arr) => {654				p!(out, str("[") {arr.expr()});655				for spec in arr.comp_specs() {656					p!(out, str(" ") {spec});657				}658				p!(out, str("]"));659			}660			Self::ExprImport(v) => {661				p!(out, {v.import_kind()} str(" ") {v.text()});662			}663			Self::ExprVar(n) => p!(out, { n.name() }),664			// Self::ExprLocal(l) => {665			// }666			Self::ExprIfThenElse(ite) => {667				p!(out, str("if ") {ite.cond()} str(" then ") {ite.then().map(|t| t.expr())});668				if ite.else_kw_token().is_some() || ite.else_().is_some() {669					p!(out, str(" else ") {ite.else_().map(|t| t.expr())});670				}671			}672			Self::ExprFunction(f) => p!(out, str("function") {f.params_desc()} nl {f.expr()}),673			// Self::ExprAssert(a) => p!(new: {a.assertion()} str("; ") {a.expr()}),674			Self::ExprError(e) => p!(out, str("error ") {e.expr()}),675			Self::ExprLiteral(l) => {676				p!(out, { l.literal() });677			}678		}679	}680}681682impl Printable for SourceFile {683	fn print(&self, out: &mut PrintItems) {684		let before = trivia_before(685			self.syntax().clone(),686			self.expr()687				.map(|e| e.syntax().clone())688				.map(Into::into)689				.as_ref(),690		);691		let after = trivia_after(692			self.syntax().clone(),693			self.expr()694				.map(|e| e.syntax().clone())695				.map(Into::into)696				.as_ref(),697		);698		format_comments(&before, CommentLocation::AboveItem, out);699		p!(out, {self.expr()} nl);700		format_comments(&after, CommentLocation::EndOfItems, out);701	}702}703704struct FormatOptions {705	// 0 for hard tabs706	indent: u8,707}708fn format(input: &str, opts: &FormatOptions) -> Option<String> {709	let (parsed, errors) = jrsonnet_rowan_parser::parse(input);710	if !errors.is_empty() {711		let mut builder = hi_doc::SnippetBuilder::new(input);712		for error in errors {713			builder714				.error(hi_doc::Text::fragment(715					format!("{:?}", error.error),716					Formatting::default(),717				))718				.range(719					error.range.start().into()720						..=(usize::from(error.range.end()) - 1).max(error.range.start().into()),721				)722				.build();723		}724		let snippet = builder.build();725		let ansi = hi_doc::source_to_ansi(&snippet);726		eprintln!("{ansi}");727		// It is possible to recover from this failure, but the output may be broken, as formatter is free to skip728		// ERROR rowan nodes.729		// Recovery needs to be enabled for LSP, though.730		//731		// TODO: Verify how formatter interacts in cases of missing positional values, i.e `if cond then /*missing Expr*/ else residual`.732		return None;733	}734	Some(dprint_core::formatting::format(735		|| {736			let mut out = PrintItems::new();737			parsed.print(&mut out);738			out739		},740		PrintOptions {741			indent_width: if opts.indent == 0 {742				// Reasonable max length for both 2 and 4 space sized tabs.743				3744			} else {745				opts.indent746			},747			max_width: 100,748			use_tabs: opts.indent == 0,749			new_line_text: "\n",750		},751	))752}753754#[derive(Parser)]755#[allow(clippy::struct_excessive_bools)]756struct Opts {757	/// Treat input as code, reformat it instead of reading file.758	#[clap(long, short = 'e')]759	exec: bool,760	/// Path to be reformatted if `--exec` if unset, otherwise code itself.761	input: String,762	/// Replace code with formatted in-place, instead of printing it to stdout.763	/// Only applicable if `--exec` is unset.764	#[clap(long, short = 'i')]765	in_place: bool,766767	/// Exit with error if formatted does not match input768	#[arg(long)]769	test: bool,770	/// Number of spaces to indent with771	///772	/// 0 for guess from input (default), and use hard tabs if unable to guess.773	#[arg(long, default_value = "0")]774	indent: u8,775	/// Force hard tab for indentation776	#[arg(long)]777	hard_tabs: bool,778779	/// Debug option: how many times to call reformatting in case of unstable dprint output resolution.780	///781	/// 0 for not retrying to reformat.782	#[arg(long, default_value = "0")]783	conv_limit: usize,784}785786#[derive(thiserror::Error, Debug)]787enum Error {788	#[error("--in-place is incompatible with --exec")]789	InPlaceExec,790	#[error("io: {0}")]791	Io(#[from] io::Error),792	#[error("persist: {0}")]793	Persist(#[from] tempfile::PersistError),794	#[error("parsing failed, refusing to reformat corrupted input")]795	Parse,796}797798fn main_result() -> Result<(), Error> {799	eprintln!("jrsonnet-fmt is a prototype of a jsonnet code formatter, do not expect it to produce meaningful results right now.");800	eprintln!("It is not expected for its output to match other implementations, it will be completly separate implementation with maybe different name.");801	let mut opts = Opts::parse();802	let input = if opts.exec {803		if opts.in_place {804			return Err(Error::InPlaceExec);805		}806		opts.input.clone()807	} else {808		fs::read_to_string(&opts.input)?809	};810811	if opts.indent == 0 {812		// Sane default.813		// TODO: Implement actual guessing.814		opts.hard_tabs = true;815	}816817	let mut iteration = 0;818	let mut formatted = input.clone();819	let mut convergence_tmp;820	// https://github.com/dprint/dprint/pull/423821	loop {822		let Some(reformatted) = format(823			&formatted,824			&FormatOptions {825				indent: if opts.indent == 0 || opts.hard_tabs {826					0827				} else {828					opts.indent829				},830			},831		) else {832			return Err(Error::Parse);833		};834		convergence_tmp = reformatted.trim().to_owned();835		if formatted == convergence_tmp {836			break;837		}838		formatted = convergence_tmp;839		if opts.conv_limit == 0 {840			break;841		}842		iteration += 1;843		assert!(iteration <= opts.conv_limit, "formatting not converged");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}