use crate::generator::{MAX_WIDTH, use_list};
pub fn push_signature(
output: &mut String,
indent: usize,
head: &str,
params: &[String],
returns: &str,
) {
push_head(output, indent, head, params, returns, " {");
output.push('\n');
}
pub fn push_signature_where(
output: &mut String,
indent: usize,
head: &str,
params: &[String],
returns: &str,
bounds: &[&str],
) {
let pad = " ".repeat(indent);
push_head(output, indent, head, params, returns, "");
output.push_str(&format!("\n{pad}where\n"));
for bound in bounds {
output.push_str(&format!("{pad} {bound},\n"));
}
output.push_str(&format!("{pad}{{\n"));
}
fn push_head(
output: &mut String,
indent: usize,
head: &str,
params: &[String],
returns: &str,
end: &str,
) {
let pad = " ".repeat(indent);
let arrow = if returns.is_empty() {
String::new()
} else {
format!(" -> {returns}")
};
let one_line = format!("{pad}{head}({}){arrow}{end}", params.join(", "));
if one_line.len() <= MAX_WIDTH {
output.push_str(&one_line);
return;
}
output.push_str(&format!("{pad}{head}(\n"));
for param in params {
output.push_str(&format!("{pad} {param},\n"));
}
output.push_str(&format!("{pad}){arrow}{end}"));
}
const STRUCT_LIT_WIDTH: usize = 18;
const ATTR_WIDTH: usize = 70;
pub fn push_struct_lit(output: &mut String, indent: usize, name: &str, fields: &[String]) {
let pad = " ".repeat(indent);
let joined = fields.join(", ");
let one_line = format!("{pad}{name} {{ {joined} }}");
if joined.len() <= STRUCT_LIT_WIDTH && one_line.len() <= MAX_WIDTH {
output.push_str(&one_line);
output.push('\n');
return;
}
output.push_str(&format!("{pad}{name} {{\n"));
for field in fields {
let line = format!("{pad} {field},");
let split = field.split_once(": ").and_then(|(name, value)| {
value
.split_once('.')
.map(|(binding, rest)| (name, binding, rest))
});
match split {
Some((name, binding, rest)) if line.len() > MAX_WIDTH => {
output.push_str(&format!(
"{pad} {name}: {binding}\n{pad} .{rest},\n"
));
}
_ => {
output.push_str(&line);
output.push('\n');
}
}
}
output.push_str(&format!("{pad}}}\n"));
}
pub fn push_field_attr(output: &mut String, name: &str, args: &[String]) {
let joined = args.join(", ");
let one_line = format!(" #[{name}({joined})]");
if joined.len() <= ATTR_WIDTH && one_line.len() <= MAX_WIDTH {
output.push_str(&one_line);
output.push('\n');
return;
}
output.push_str(&format!(
" #[{name}(\n {}\n )]\n",
args.join(",\n ")
));
}
#[derive(Debug, Default)]
pub struct Uses {
modules: std::collections::BTreeMap<String, std::collections::BTreeSet<String>>,
}
impl Uses {
pub fn add(&mut self, path: &str) {
if let Some((module, item)) = path.rsplit_once("::") {
self.modules
.entry(module.to_owned())
.or_default()
.insert(item.to_owned());
}
}
pub fn render(&self) -> String {
let local = |module: &str| {
let first = module.split("::").next().unwrap_or_default();
matches!(first, "super" | "crate" | "self")
};
let mut output = String::new();
for group in [false, true] {
let mut lines: Vec<String> = self
.modules
.iter()
.filter(|(module, _)| local(module) == group)
.map(|(module, items)| {
let items: Vec<String> = items.iter().cloned().collect();
use_list(module, &items)
})
.collect();
lines.sort_by_key(|line| {
line.trim_start_matches("use ")
.trim_end_matches(";\n")
.split("::")
.map(|segment| (segment != "super", segment.to_owned()))
.collect::<Vec<_>>()
});
if !lines.is_empty() {
output.push_str(&lines.concat());
output.push('\n');
}
}
output
}
}
pub fn push_call(
output: &mut String,
indent: usize,
lead: &str,
callee: &str,
args: &[String],
tail: &str,
) {
if let Some((receiver, method)) = callee.split_once('.') {
push_method_call(output, indent, lead, receiver, method, args, tail);
return;
}
let pad = " ".repeat(indent);
let joined = args.join(", ");
let call = format!("{callee}({joined}){tail}");
let fits_args = joined.len() <= crate::generator::FN_CALL_WIDTH;
let width = if tail.starts_with('.') {
MAX_WIDTH - 1
} else {
MAX_WIDTH
};
if fits_args && pad.len() + lead.len() + call.len() <= width {
output.push_str(&format!("{pad}{lead}{call}\n"));
return;
}
let inner = " ".repeat(indent + 4);
if fits_args && lead.starts_with("let ") {
if inner.len() + call.len() <= width {
output.push_str(&format!("{pad}{}\n{inner}{call}\n", lead.trim_end()));
return;
}
let bare = format!("{callee}({joined})");
if tail.starts_with('.') && inner.len() + bare.len() <= MAX_WIDTH {
output.push_str(&format!(
"{pad}{}\n{inner}{bare}\n{inner} {tail}\n",
lead.trim_end()
));
return;
}
}
output.push_str(&format!("{pad}{lead}{callee}(\n"));
let short = args.iter().all(|arg| {
arg.len() <= 10
&& arg
.chars()
.all(|c| c.is_alphanumeric() || matches!(c, '_' | '.' | '&' | '*' | ' '))
});
if fits_args && short {
output.push_str(&format!("{inner}{joined},\n"));
} else {
for arg in args {
output.push_str(&format!("{inner}{arg},\n"));
}
}
if tail.starts_with('.') {
output.push_str(&format!("{pad})\n{pad}{tail}\n"));
} else {
output.push_str(&format!("{pad}){tail}\n"));
}
}
const CHAIN_WIDTH: usize = 60;
fn push_method_call(
output: &mut String,
indent: usize,
lead: &str,
receiver: &str,
method: &str,
args: &[String],
tail: &str,
) {
let pad = " ".repeat(indent);
let joined = args.join(", ");
let fits_args = joined.len() <= crate::generator::FN_CALL_WIDTH;
let end = tail.trim_end_matches(';');
let chain = format!("{receiver}.{method}({joined}){end}");
let one_line = format!("{pad}{lead}{chain}{}", &tail[end.len()..]);
if fits_args && chain.len() < CHAIN_WIDTH && one_line.len() <= MAX_WIDTH {
output.push_str(&one_line);
output.push('\n');
return;
}
let call = format!("{method}({joined})");
if lead.is_empty() && receiver.len() <= 4 {
let first = format!("{pad}{receiver}.{call}");
if fits_args && first.len() <= MAX_WIDTH {
output.push_str(&format!("{first}\n{pad} {tail}\n"));
} else {
output.push_str(&format!("{pad}{receiver}.{method}(\n"));
for arg in args {
output.push_str(&format!("{pad} {arg},\n"));
}
output.push_str(&format!("{pad})\n{pad}{tail}\n"));
}
return;
}
output.push_str(&format!("{pad}{lead}{receiver}\n"));
let inner = " ".repeat(indent + 4);
if fits_args && inner.len() + 1 + call.len() <= MAX_WIDTH {
output.push_str(&format!("{inner}.{call}\n"));
} else {
output.push_str(&format!("{inner}.{method}(\n"));
for arg in args {
output.push_str(&format!("{inner} {arg},\n"));
}
output.push_str(&format!("{inner})\n"));
}
output.push_str(&format!("{inner}{tail}\n"));
}
pub fn impl_head(item: &str, ty: &str) -> String {
let one_line = format!("impl {item} for {ty} {{\n");
if one_line.len() - 1 <= MAX_WIDTH {
one_line
} else {
format!("impl {item}\n for {ty}\n{{\n")
}
}