Update to Rust 1.88

We'll need to handle https://github.com/ankitects/anki/issues/4134 before
we get access to let chains.
This commit is contained in:
Damien Elmes 2025-06-29 11:50:25 +07:00
parent b872852afe
commit f89ab00236
67 changed files with 195 additions and 237 deletions

View file

@ -51,7 +51,7 @@ fn normalize_version(version: &str) -> String {
part.to_string() part.to_string()
} else { } else {
let normalized_prefix = numeric_prefix.parse::<u32>().unwrap_or(0).to_string(); let normalized_prefix = numeric_prefix.parse::<u32>().unwrap_or(0).to_string();
format!("{}{}", normalized_prefix, rest) format!("{normalized_prefix}{rest}")
} }
} }
}) })

View file

@ -72,12 +72,11 @@ fn fetch_protoc_release_info() -> Result<String, Box<dyn Error>> {
"MacArm" => continue, // Skip MacArm since it's handled with MacX64 "MacArm" => continue, // Skip MacArm since it's handled with MacX64
"WindowsX64" => "Platform::WindowsX64 | Platform::WindowsArm", "WindowsX64" => "Platform::WindowsX64 | Platform::WindowsArm",
"WindowsArm" => continue, // Skip WindowsArm since it's handled with WindowsX64 "WindowsArm" => continue, // Skip WindowsArm since it's handled with WindowsX64
_ => &format!("Platform::{}", platform), _ => &format!("Platform::{platform}"),
}; };
match_blocks.push(format!( match_blocks.push(format!(
" {} => {{\n OnlineArchive {{\n url: \"{}\",\n sha256: \"{}\",\n }}\n }}", " {match_pattern} => {{\n OnlineArchive {{\n url: \"{download_url}\",\n sha256: \"{sha256}\",\n }}\n }}"
match_pattern, download_url, sha256
)); ));
} }

View file

@ -53,7 +53,7 @@ fn fetch_uv_release_info() -> Result<String, Box<dyn Error>> {
// Find the corresponding .sha256 or .sha256sum asset // Find the corresponding .sha256 or .sha256sum asset
let sha_asset = assets.iter().find(|a| { let sha_asset = assets.iter().find(|a| {
let name = a["name"].as_str().unwrap_or(""); let name = a["name"].as_str().unwrap_or("");
name == format!("{}.sha256", asset_name) || name == format!("{}.sha256sum", asset_name) name == format!("{asset_name}.sha256") || name == format!("{asset_name}.sha256sum")
}); });
if sha_asset.is_none() { if sha_asset.is_none() {
eprintln!("No sha256 asset found for {asset_name}"); eprintln!("No sha256 asset found for {asset_name}");
@ -71,8 +71,7 @@ fn fetch_uv_release_info() -> Result<String, Box<dyn Error>> {
let sha256 = sha_text.split_whitespace().next().unwrap_or(""); let sha256 = sha_text.split_whitespace().next().unwrap_or("");
match_blocks.push(format!( match_blocks.push(format!(
" Platform::{} => {{\n OnlineArchive {{\n url: \"{}\",\n sha256: \"{}\",\n }}\n }}", " Platform::{platform} => {{\n OnlineArchive {{\n url: \"{download_url}\",\n sha256: \"{sha256}\",\n }}\n }}"
platform, download_url, sha256
)); ));
} }
@ -135,10 +134,7 @@ mod tests {
assert_eq!( assert_eq!(
updated_lines, updated_lines,
original_lines - EXPECTED_LINES_REMOVED, original_lines - EXPECTED_LINES_REMOVED,
"Expected line count to decrease by exactly {} lines (original: {}, updated: {})", "Expected line count to decrease by exactly {EXPECTED_LINES_REMOVED} lines (original: {original_lines}, updated: {updated_lines})"
EXPECTED_LINES_REMOVED,
original_lines,
updated_lines
); );
} }
} }

View file

@ -300,7 +300,7 @@ impl BuildStatement<'_> {
writeln!(buf, "build {outputs_str}: {action_name} {inputs_str}").unwrap(); writeln!(buf, "build {outputs_str}: {action_name} {inputs_str}").unwrap();
for (key, value) in self.variables.iter().sorted() { for (key, value) in self.variables.iter().sorted() {
writeln!(buf, " {key} = {}", value).unwrap(); writeln!(buf, " {key} = {value}").unwrap();
} }
writeln!(buf).unwrap(); writeln!(buf).unwrap();
@ -476,7 +476,7 @@ impl FilesHandle for BuildStatement<'_> {
let outputs = outputs.into_iter().map(|v| { let outputs = outputs.into_iter().map(|v| {
let v = v.as_ref(); let v = v.as_ref();
let v = if !v.starts_with("$builddir/") && !v.starts_with("$builddir\\") { let v = if !v.starts_with("$builddir/") && !v.starts_with("$builddir\\") {
format!("$builddir/{}", v) format!("$builddir/{v}")
} else { } else {
v.to_owned() v.to_owned()
}; };

View file

@ -148,7 +148,7 @@ impl BuildAction for PythonEnvironment {
// Add --python flag to extra_args if PYTHON_BINARY is set // Add --python flag to extra_args if PYTHON_BINARY is set
let mut args = self.extra_args.to_string(); let mut args = self.extra_args.to_string();
if let Ok(python_binary) = env::var("PYTHON_BINARY") { if let Ok(python_binary) = env::var("PYTHON_BINARY") {
args = format!("--python {} {}", python_binary, args); args = format!("--python {python_binary} {args}");
} }
build.add_variable("extra_args", args); build.add_variable("extra_args", args);
} }

View file

@ -30,12 +30,12 @@ impl Build {
) )
.unwrap(); .unwrap();
for (key, value) in &self.variables { for (key, value) in &self.variables {
writeln!(&mut buf, "{} = {}", key, value).unwrap(); writeln!(&mut buf, "{key} = {value}").unwrap();
} }
buf.push('\n'); buf.push('\n');
for (key, value) in &self.pools { for (key, value) in &self.pools {
writeln!(&mut buf, "pool {}\n depth = {}", key, value).unwrap(); writeln!(&mut buf, "pool {key}\n depth = {value}").unwrap();
} }
buf.push('\n'); buf.push('\n');

View file

@ -65,7 +65,7 @@ fn sha2_data(data: &[u8]) -> String {
let mut digest = sha2::Sha256::new(); let mut digest = sha2::Sha256::new();
digest.update(data); digest.update(data);
let result = digest.finalize(); let result = digest.finalize();
format!("{:x}", result) format!("{result:x}")
} }
enum CompressionKind { enum CompressionKind {

View file

@ -138,7 +138,7 @@ fn setup_build_root() -> Utf8PathBuf {
true true
}; };
if create { if create {
println!("Switching build root to {}", new_target); println!("Switching build root to {new_target}");
std::os::unix::fs::symlink(new_target, build_root).unwrap(); std::os::unix::fs::symlink(new_target, build_root).unwrap();
} }
} }

View file

@ -83,7 +83,7 @@ fn split_args(args: Vec<String>) -> Vec<Vec<String>> {
pub fn run_command(command: &mut Command) { pub fn run_command(command: &mut Command) {
if let Err(err) = command.ensure_success() { if let Err(err) = command.ensure_success() {
println!("{}", err); println!("{err}");
std::process::exit(1); std::process::exit(1);
} }
} }

View file

@ -435,7 +435,7 @@ impl TextWriter {
item = item.trim_start_matches(' '); item = item.trim_start_matches(' ');
} }
write!(self.buffer, "{}", item) write!(self.buffer, "{item}")
} }
fn write_char_into_indent(&mut self, ch: char) { fn write_char_into_indent(&mut self, ch: char) {

View file

@ -67,7 +67,7 @@ fn additional_template_folder(dst_folder: &Utf8Path) -> Option<Utf8PathBuf> {
fn all_langs(lang_folder: &Utf8Path) -> Result<Vec<Utf8PathBuf>> { fn all_langs(lang_folder: &Utf8Path) -> Result<Vec<Utf8PathBuf>> {
std::fs::read_dir(lang_folder) std::fs::read_dir(lang_folder)
.with_context(|| format!("reading {:?}", lang_folder))? .with_context(|| format!("reading {lang_folder:?}"))?
.filter_map(Result::ok) .filter_map(Result::ok)
.map(|e| Ok(e.path().utf8()?)) .map(|e| Ok(e.path().utf8()?))
.collect() .collect()

View file

@ -28,6 +28,6 @@ fn main() {
.to_string(); .to_string();
let libs_path = stdlib_path + "s"; let libs_path = stdlib_path + "s";
println!("cargo:rustc-link-search={}", libs_path); println!("cargo:rustc-link-search={libs_path}");
} }
} }

View file

@ -12,7 +12,7 @@ use anyhow::Result;
fn main() { fn main() {
if let Err(e) = run() { if let Err(e) = run() {
eprintln!("Error: {:#}", e); eprintln!("Error: {e:#}");
std::process::exit(1); std::process::exit(1);
} }
} }

View file

@ -221,7 +221,7 @@ fn generate_install_manifest(output_dir: &Path) -> Result<()> {
// Convert to Windows-style backslashes for NSIS // Convert to Windows-style backslashes for NSIS
let windows_path = relative_path.display().to_string().replace('/', "\\"); let windows_path = relative_path.display().to_string().replace('/', "\\");
// Use Windows line endings (\r\n) as expected by NSIS // Use Windows line endings (\r\n) as expected by NSIS
manifest_content.push_str(&format!("{}\r\n", windows_path)); manifest_content.push_str(&format!("{windows_path}\r\n"));
} }
} }
} }

View file

@ -64,7 +64,7 @@ pub enum MainMenuChoice {
fn main() { fn main() {
if let Err(e) = run() { if let Err(e) = run() {
eprintln!("Error: {:#}", e); eprintln!("Error: {e:#}");
eprintln!("Press enter to close..."); eprintln!("Press enter to close...");
let mut input = String::new(); let mut input = String::new();
let _ = stdin().read_line(&mut input); let _ = stdin().read_line(&mut input);
@ -252,7 +252,7 @@ fn main_menu_loop(state: &State) -> Result<()> {
// If sync fails due to things like a missing wheel on pypi, // If sync fails due to things like a missing wheel on pypi,
// we need to remove the lockfile or uv will cache the bad result. // we need to remove the lockfile or uv will cache the bad result.
let _ = remove_file(&state.uv_lock_path); let _ = remove_file(&state.uv_lock_path);
println!("Install failed: {:#}", e); println!("Install failed: {e:#}");
println!(); println!();
continue; continue;
} }
@ -402,7 +402,7 @@ fn update_pyproject_for_version(
) )
} }
VersionKind::Uv(version) => { VersionKind::Uv(version) => {
content_str.replace("anki-release", &format!("anki-release=={}", version)) content_str.replace("anki-release", &format!("anki-release=={version}"))
} }
}; };
write_file(&user_pyproject_path, &updated_content)?; write_file(&user_pyproject_path, &updated_content)?;

View file

@ -21,14 +21,11 @@ pub fn check(lang_map: &TranslationsByLang) {
fn check_content(lang: &str, fname: &str, content: &str) { fn check_content(lang: &str, fname: &str, content: &str) {
let lang_id: LanguageIdentifier = "en-US".parse().unwrap(); let lang_id: LanguageIdentifier = "en-US".parse().unwrap();
let resource = FluentResource::try_new(content.into()).unwrap_or_else(|e| { let resource = FluentResource::try_new(content.into()).unwrap_or_else(|e| {
panic!("{}\nUnable to parse {}/{}: {:?}", content, lang, fname, e); panic!("{content}\nUnable to parse {lang}/{fname}: {e:?}");
}); });
let mut bundle: FluentBundle<FluentResource> = FluentBundle::new(vec![lang_id]); let mut bundle: FluentBundle<FluentResource> = FluentBundle::new(vec![lang_id]);
bundle.add_resource(resource).unwrap_or_else(|e| { bundle.add_resource(resource).unwrap_or_else(|e| {
panic!( panic!("{content}\nUnable to bundle - duplicate key? {lang}/{fname}: {e:?}");
"{}\nUnable to bundle - duplicate key? {}/{}: {:?}",
content, lang, fname, e
);
}); });
} }

View file

@ -48,8 +48,7 @@ fn add_folder(map: &mut TranslationsByLang, folder: &Path, lang: &str) {
let text = fs::read_to_string(entry.path()).unwrap(); let text = fs::read_to_string(entry.path()).unwrap();
assert!( assert!(
text.ends_with('\n'), text.ends_with('\n'),
"file was missing final newline: {:?}", "file was missing final newline: {entry:?}"
entry
); );
map_entry.entry(module).or_default().push_str(&text); map_entry.entry(module).or_default().push_str(&text);
println!("cargo:rerun-if-changed={}", entry.path().to_str().unwrap()); println!("cargo:rerun-if-changed={}", entry.path().to_str().unwrap());

View file

@ -130,7 +130,7 @@ fn get_bundle(
) -> Option<FluentBundle<FluentResource>> { ) -> Option<FluentBundle<FluentResource>> {
let res = FluentResource::try_new(text.into()) let res = FluentResource::try_new(text.into())
.map_err(|e| { .map_err(|e| {
println!("Unable to parse translations file: {:?}", e); println!("Unable to parse translations file: {e:?}");
}) })
.ok()?; .ok()?;
@ -138,14 +138,14 @@ fn get_bundle(
bundle bundle
.add_resource(res) .add_resource(res)
.map_err(|e| { .map_err(|e| {
println!("Duplicate key detected in translation file: {:?}", e); println!("Duplicate key detected in translation file: {e:?}");
}) })
.ok()?; .ok()?;
if !extra_text.is_empty() { if !extra_text.is_empty() {
match FluentResource::try_new(extra_text) { match FluentResource::try_new(extra_text) {
Ok(res) => bundle.add_resource_overriding(res), Ok(res) => bundle.add_resource_overriding(res),
Err((_res, e)) => println!("Unable to parse translations file: {:?}", e), Err((_res, e)) => println!("Unable to parse translations file: {e:?}"),
} }
} }
@ -291,7 +291,7 @@ impl I18n {
let mut errs = vec![]; let mut errs = vec![];
let out = bundle.format_pattern(pat, args.as_ref(), &mut errs); let out = bundle.format_pattern(pat, args.as_ref(), &mut errs);
if !errs.is_empty() { if !errs.is_empty() {
println!("Error(s) in translation '{}': {:?}", key, errs); println!("Error(s) in translation '{key}': {errs:?}");
} }
// clone so we can discard args // clone so we can discard args
return out.to_string().into(); return out.to_string().into();

View file

@ -81,7 +81,7 @@ fn get_args(variables: &[Variable]) -> String {
.iter() .iter()
.map(|v| format!("\"{}\": args.{}", v.name, typescript_arg_name(&v.name))) .map(|v| format!("\"{}\": args.{}", v.name, typescript_arg_name(&v.name)))
.join(", "); .join(", ");
format!("{{{}}}", out) format!("{{{out}}}")
} }
} }

View file

@ -69,12 +69,6 @@ impl I18n {
{var_build} {var_build}
self.translate("{key}"{out_args}) self.translate("{key}"{out_args})
}}"#, }}"#,
func = func,
key = key,
doc = doc,
in_args = in_args,
out_args = out_args,
var_build = var_build,
) )
.unwrap(); .unwrap();
} }
@ -103,9 +97,6 @@ fn build_vars(translation: &Translation) -> String {
writeln!( writeln!(
buf, buf,
r#" args.set("{fluent_name}", {rust_name}{trailer});"#, r#" args.set("{fluent_name}", {rust_name}{trailer});"#,
fluent_name = fluent_name,
rust_name = rust_name,
trailer = trailer,
) )
.unwrap(); .unwrap();
} }
@ -204,13 +195,7 @@ pub(crate) const {lang_name}: phf::Map<&str, &str> = phf::phf_map! {{",
.unwrap(); .unwrap();
for (module, contents) in modules { for (module, contents) in modules {
writeln!( writeln!(buf, r###" "{module}" => r##"{contents}"##,"###).unwrap();
buf,
r###" "{module}" => r##"{contents}"##,"###,
module = module,
contents = contents
)
.unwrap();
} }
buf.push_str("};\n"); buf.push_str("};\n");

View file

@ -183,9 +183,9 @@ fn python_type(field: &FieldDescriptor, output: bool) -> String {
}; };
if field.is_list() { if field.is_list() {
if output { if output {
format!("Sequence[{}]", kind) format!("Sequence[{kind}]")
} else { } else {
format!("Iterable[{}]", kind) format!("Iterable[{kind}]")
} }
} else if field.is_map() { } else if field.is_map() {
let map_kind = field.kind(); let map_kind = field.kind();

View file

@ -263,7 +263,7 @@ impl MethodHelpers for Method {
fn get_input_arg_with_label(&self) -> String { fn get_input_arg_with_label(&self) -> String {
self.input_type() self.input_type()
.as_ref() .as_ref()
.map(|t| format!("input: {}", t)) .map(|t| format!("input: {t}"))
.unwrap_or_default() .unwrap_or_default()
} }

View file

@ -515,7 +515,7 @@ impl RowContext {
return "".into(); return "".into();
}; };
if self.cards[0].is_undue_queue() { if self.cards[0].is_undue_queue() {
format!("({})", due) format!("({due})")
} else { } else {
due.into() due.into()
} }
@ -623,7 +623,7 @@ impl RowContext {
if self.notes_mode { if self.notes_mode {
let decks = self.cards.iter().map(|c| c.deck_id).unique().count(); let decks = self.cards.iter().map(|c| c.deck_id).unique().count();
if decks > 1 { if decks > 1 {
return format!("({})", decks); return format!("({decks})");
} }
} }
let deck_name = self.deck.human_name(); let deck_name = self.deck.human_name();

View file

@ -52,7 +52,7 @@ trait Write {
} }
fn write_sound(&mut self, buf: &mut String, resource: &str) { fn write_sound(&mut self, buf: &mut String, resource: &str) {
write!(buf, "[sound:{}]", resource).unwrap(); write!(buf, "[sound:{resource}]").unwrap();
} }
fn write_directive(&mut self, buf: &mut String, directive: &Directive) { fn write_directive(&mut self, buf: &mut String, directive: &Directive) {
@ -94,9 +94,9 @@ trait Write {
fn write_directive_option(&mut self, buf: &mut String, key: &str, val: &str) { fn write_directive_option(&mut self, buf: &mut String, key: &str, val: &str) {
if val.contains([']', ' ', '\t', '\r', '\n']) { if val.contains([']', ' ', '\t', '\r', '\n']) {
write!(buf, " {}=\"{}\"", key, val).unwrap(); write!(buf, " {key}=\"{val}\"").unwrap();
} else { } else {
write!(buf, " {}={}", key, val).unwrap(); write!(buf, " {key}={val}").unwrap();
} }
} }
@ -158,7 +158,7 @@ impl Write for AvExtractor<'_> {
fn write_tts_directive(&mut self, buf: &mut String, directive: &TtsDirective) { fn write_tts_directive(&mut self, buf: &mut String, directive: &TtsDirective) {
if let Some(error) = directive.error(self.tr) { if let Some(error) = directive.error(self.tr) {
write!(buf, "[{}]", error).unwrap(); write!(buf, "[{error}]").unwrap();
return; return;
} }
@ -173,7 +173,7 @@ impl Write for AvExtractor<'_> {
other_args: directive other_args: directive
.options .options
.iter() .iter()
.map(|(key, val)| format!("{}={}", key, val)) .map(|(key, val)| format!("{key}={val}"))
.collect(), .collect(),
}, },
)), )),
@ -204,7 +204,7 @@ impl AvPrettifier {
impl Write for AvPrettifier { impl Write for AvPrettifier {
fn write_sound(&mut self, buf: &mut String, resource: &str) { fn write_sound(&mut self, buf: &mut String, resource: &str) {
write!(buf, "🔉{}🔉", resource).unwrap(); write!(buf, "🔉{resource}🔉").unwrap();
} }
fn write_tts_directive(&mut self, buf: &mut String, directive: &TtsDirective) { fn write_tts_directive(&mut self, buf: &mut String, directive: &TtsDirective) {

View file

@ -41,5 +41,5 @@ impl Collection {
} }
fn build_aux_deck_key(deck: DeckId, key: &str) -> String { fn build_aux_deck_key(deck: DeckId, key: &str) -> String {
format!("_deck_{deck}_{key}", deck = deck, key = key) format!("_deck_{deck}_{key}")
} }

View file

@ -32,7 +32,7 @@ impl Collection {
}; };
Ok(get_aux_notetype_config_key( Ok(get_aux_notetype_config_key(
ntid, ntid,
&format!("{}_{}", key, ordinal), &format!("{key}_{ordinal}"),
)) ))
} }
} }
@ -70,5 +70,5 @@ impl Collection {
} }
pub fn get_aux_notetype_config_key(ntid: NotetypeId, key: &str) -> String { pub fn get_aux_notetype_config_key(ntid: NotetypeId, key: &str) -> String {
format!("_nt_{ntid}_{key}", ntid = ntid, key = key) format!("_nt_{ntid}_{key}")
} }

View file

@ -387,10 +387,10 @@ impl Collection {
let mut basic = all_stock_notetypes(&self.tr).remove(0); let mut basic = all_stock_notetypes(&self.tr).remove(0);
let mut field = 3; let mut field = 3;
while basic.fields.len() < field_count { while basic.fields.len() < field_count {
basic.add_field(format!("{}", field)); basic.add_field(format!("{field}"));
field += 1; field += 1;
} }
basic.name = format!("db-check-{}-{}", stamp, field_count); basic.name = format!("db-check-{stamp}-{field_count}");
let qfmt = basic.templates[0].config.q_format.clone(); let qfmt = basic.templates[0].config.q_format.clone();
let afmt = basic.templates[0].config.a_format.clone(); let afmt = basic.templates[0].config.a_format.clone();
for n in 0..extra_cards_required { for n in 0..extra_cards_required {

View file

@ -93,7 +93,7 @@ impl Collection {
pub(crate) fn recover_missing_deck(&mut self, did: DeckId, usn: Usn) -> Result<()> { pub(crate) fn recover_missing_deck(&mut self, did: DeckId, usn: Usn) -> Result<()> {
let mut deck = Deck::new_normal(); let mut deck = Deck::new_normal();
deck.id = did; deck.id = did;
deck.name = NativeDeckName::from_native_str(format!("recovered{}", did)); deck.name = NativeDeckName::from_native_str(format!("recovered{did}"));
deck.set_modified(usn); deck.set_modified(usn);
self.add_or_update_single_deck_with_existing_id(&mut deck, usn) self.add_or_update_single_deck_with_existing_id(&mut deck, usn)
} }

View file

@ -67,7 +67,7 @@ impl From<Error> for AnkiError {
} }
AnkiError::DbError { AnkiError::DbError {
source: DbError { source: DbError {
info: format!("{:?}", err), info: format!("{err:?}"),
kind: DbErrorKind::Other, kind: DbErrorKind::Other,
}, },
} }
@ -88,7 +88,7 @@ impl From<FromSqlError> for AnkiError {
} }
AnkiError::DbError { AnkiError::DbError {
source: DbError { source: DbError {
info: format!("{:?}", err), info: format!("{err:?}"),
kind: DbErrorKind::Other, kind: DbErrorKind::Other,
}, },
} }
@ -101,7 +101,7 @@ impl DbError {
DbErrorKind::Corrupt => self.info.clone(), DbErrorKind::Corrupt => self.info.clone(),
// fixme: i18n // fixme: i18n
DbErrorKind::Locked => "Anki already open, or media currently syncing.".into(), DbErrorKind::Locked => "Anki already open, or media currently syncing.".into(),
_ => format!("{:?}", self), _ => format!("{self:?}"),
} }
} }
} }

View file

@ -26,7 +26,7 @@ impl InvalidInputError {
pub fn context(&self) -> String { pub fn context(&self) -> String {
if let Some(source) = &self.source { if let Some(source) = &self.source {
format!("{}", source) format!("{source}")
} else { } else {
String::new() String::new()
} }

View file

@ -149,13 +149,13 @@ impl AnkiError {
} }
CardTypeErrorDetails::MissingCloze => tr.card_templates_missing_cloze(), CardTypeErrorDetails::MissingCloze => tr.card_templates_missing_cloze(),
}; };
format!("{}<br>{}", header, details) format!("{header}<br>{details}")
} }
AnkiError::DbError { source } => source.message(tr), AnkiError::DbError { source } => source.message(tr),
AnkiError::SearchError { source } => source.message(tr), AnkiError::SearchError { source } => source.message(tr),
AnkiError::ParseNumError => tr.errors_parse_number_fail().into(), AnkiError::ParseNumError => tr.errors_parse_number_fail().into(),
AnkiError::FilteredDeckError { source } => source.message(tr), AnkiError::FilteredDeckError { source } => source.message(tr),
AnkiError::InvalidRegex { info: source } => format!("<pre>{}</pre>", source), AnkiError::InvalidRegex { info: source } => format!("<pre>{source}</pre>"),
AnkiError::MultipleNotetypesSelected => tr.errors_multiple_notetypes_selected().into(), AnkiError::MultipleNotetypesSelected => tr.errors_multiple_notetypes_selected().into(),
AnkiError::DatabaseCheckRequired => tr.errors_please_check_database().into(), AnkiError::DatabaseCheckRequired => tr.errors_please_check_database().into(),
AnkiError::MediaCheckRequired => tr.errors_please_check_media().into(), AnkiError::MediaCheckRequired => tr.errors_please_check_media().into(),
@ -172,7 +172,7 @@ impl AnkiError {
| AnkiError::InvalidServiceIndex | AnkiError::InvalidServiceIndex
| AnkiError::InvalidMethodIndex | AnkiError::InvalidMethodIndex
| AnkiError::UndoEmpty | AnkiError::UndoEmpty
| AnkiError::InvalidCertificateFormat => format!("{:?}", self), | AnkiError::InvalidCertificateFormat => format!("{self:?}"),
AnkiError::FileIoError { source } => source.message(), AnkiError::FileIoError { source } => source.message(),
AnkiError::InvalidInput { source } => source.message(), AnkiError::InvalidInput { source } => source.message(),
AnkiError::NotFound { source } => source.message(tr), AnkiError::NotFound { source } => source.message(tr),

View file

@ -68,7 +68,7 @@ impl AnkiError {
impl From<&reqwest::Error> for AnkiError { impl From<&reqwest::Error> for AnkiError {
fn from(err: &reqwest::Error) -> Self { fn from(err: &reqwest::Error) -> Self {
let url = err.url().map(|url| url.as_str()).unwrap_or(""); let url = err.url().map(|url| url.as_str()).unwrap_or("");
let str_err = format!("{}", err); let str_err = format!("{err}");
// strip url from error to avoid exposing keys // strip url from error to avoid exposing keys
let info = str_err.replace(url, ""); let info = str_err.replace(url, "");
@ -205,7 +205,7 @@ impl NetworkError {
NetworkErrorKind::Other => tr.network_other(), NetworkErrorKind::Other => tr.network_other(),
}; };
let details = tr.network_details(self.info.as_str()); let details = tr.network_details(self.info.as_str());
format!("{}\n\n{}", summary, details) format!("{summary}\n\n{details}")
} }
} }
@ -226,7 +226,7 @@ impl From<HttpError> for AnkiError {
} }
.into() .into()
} else { } else {
AnkiError::sync_error(format!("{:?}", err), SyncErrorKind::Other) AnkiError::sync_error(format!("{err:?}"), SyncErrorKind::Other)
} }
} }
} }

View file

@ -77,7 +77,7 @@ impl Collection {
) -> Result<GetImageOcclusionNoteResponse> { ) -> Result<GetImageOcclusionNoteResponse> {
let value = match self.get_image_occlusion_note_inner(note_id) { let value = match self.get_image_occlusion_note_inner(note_id) {
Ok(note) => Value::Note(note), Ok(note) => Value::Note(note),
Err(err) => Value::Error(format!("{:?}", err)), Err(err) => Value::Error(format!("{err:?}")),
}; };
Ok(GetImageOcclusionNoteResponse { value: Some(value) }) Ok(GetImageOcclusionNoteResponse { value: Some(value) })
} }

View file

@ -98,7 +98,7 @@ pub fn get_image_cloze_data(text: &str) -> String {
let Some((x, y)) = point_pair.split_once(',') else { let Some((x, y)) = point_pair.split_once(',') else {
continue; continue;
}; };
write!(&mut point_str, "{},{} ", x, y).unwrap(); write!(&mut point_str, "{x},{y} ").unwrap();
} }
// remove the trailing space // remove the trailing space
point_str.pop(); point_str.pop();

View file

@ -100,7 +100,7 @@ fn fname_for_latex(latex: &str, svg: bool) -> String {
let ext = if svg { "svg" } else { "png" }; let ext = if svg { "svg" } else { "png" };
let csum = hex::encode(sha1_of_data(latex.as_bytes())); let csum = hex::encode(sha1_of_data(latex.as_bytes()));
format!("latex-{}.{}", csum, ext) format!("latex-{csum}.{ext}")
} }
fn image_link_for_fname(src: &str, fname: &str) -> String { fn image_link_for_fname(src: &str, fname: &str) -> String {
@ -122,11 +122,7 @@ mod test {
assert_eq!( assert_eq!(
extract_latex("a[latex]one<br>and<div>two[/latex]b", false), extract_latex("a[latex]one<br>and<div>two[/latex]b", false),
( (
format!( format!("a<img class=latex alt=\"one&#x0A;and&#x0A;two\" src=\"{fname}\">b").into(),
"a<img class=latex alt=\"one&#x0A;and&#x0A;two\" src=\"{}\">b",
fname
)
.into(),
vec![ExtractedLatex { vec![ExtractedLatex {
fname: fname.into(), fname: fname.into(),
latex: "one\nand\ntwo".into() latex: "one\nand\ntwo".into()

View file

@ -69,8 +69,8 @@ fn maybe_rotate_log(path: &str) -> io::Result<()> {
return Ok(()); return Ok(());
} }
let path2 = format!("{}.1", path); let path2 = format!("{path}.1");
let path3 = format!("{}.2", path); let path3 = format!("{path}.2");
// if a rotated file already exists, rename it // if a rotated file already exists, rename it
if let Err(e) = fs::rename(&path2, path3) { if let Err(e) = fs::rename(&path2, path3) {

View file

@ -218,7 +218,7 @@ fn truncate_filename(fname: &str, max_bytes: usize) -> Cow<str> {
let mut new_name = if ext.is_empty() { let mut new_name = if ext.is_empty() {
stem.to_string() stem.to_string()
} else { } else {
format!("{}.{}", stem, ext) format!("{stem}.{ext}")
}; };
// make sure we don't break Windows by ending with a space or dot // make sure we don't break Windows by ending with a space or dot

View file

@ -270,7 +270,7 @@ impl Note {
self.fields self.fields
.last_mut() .last_mut()
.unwrap() .unwrap()
.push_str(&format!("; {}", last)); .push_str(&format!("; {last}"));
} }
} }
} }

View file

@ -126,7 +126,7 @@ fn other_to_bytes(other: &HashMap<String, Value>) -> Vec<u8> {
} else { } else {
serde_json::to_vec(other).unwrap_or_else(|e| { serde_json::to_vec(other).unwrap_or_else(|e| {
// theoretically should never happen // theoretically should never happen
println!("serialization failed for {:?}: {}", other, e); println!("serialization failed for {other:?}: {e}");
vec![] vec![]
}) })
} }
@ -140,7 +140,7 @@ pub(crate) fn parse_other_fields(
Default::default() Default::default()
} else { } else {
let mut map: HashMap<String, Value> = serde_json::from_slice(bytes).unwrap_or_else(|e| { let mut map: HashMap<String, Value> = serde_json::from_slice(bytes).unwrap_or_else(|e| {
println!("deserialization failed for other: {}", e); println!("deserialization failed for other: {e}");
Default::default() Default::default()
}); });
map.retain(|k, _v| !reserved.contains(k)); map.retain(|k, _v| !reserved.contains(k));

View file

@ -179,8 +179,8 @@ pub(crate) fn cloze(tr: &I18n) -> Notetype {
let back_extra = tr.notetypes_back_extra_field(); let back_extra = tr.notetypes_back_extra_field();
config = nt.add_field(back_extra.as_ref()); config = nt.add_field(back_extra.as_ref());
config.tag = Some(ClozeField::BackExtra as u32); config.tag = Some(ClozeField::BackExtra as u32);
let qfmt = format!("{{{{cloze:{}}}}}", text); let qfmt = format!("{{{{cloze:{text}}}}}");
let afmt = format!("{}<br>\n{{{{{}}}}}", qfmt, back_extra); let afmt = format!("{qfmt}<br>\n{{{{{back_extra}}}}}");
nt.add_template(nt.name.clone(), qfmt, afmt); nt.add_template(nt.name.clone(), qfmt, afmt);
nt nt
} }

View file

@ -889,22 +889,20 @@ pub(crate) mod test {
) -> Result<()> { ) -> Result<()> {
// Change due time to fake card answer_time, // Change due time to fake card answer_time,
// works since answer_time is calculated as due - last_ivl // works since answer_time is calculated as due - last_ivl
let update_due_string = format!("update cards set due={}", shift_due_time); let update_due_string = format!("update cards set due={shift_due_time}");
col.storage.db.execute_batch(&update_due_string)?; col.storage.db.execute_batch(&update_due_string)?;
col.clear_study_queues(); col.clear_study_queues();
let current_card_state = current_state(col, post_answer.card_id); let current_card_state = current_state(col, post_answer.card_id);
let state = match current_card_state { let state = match current_card_state {
CardState::Normal(NormalState::Learning(state)) => state, CardState::Normal(NormalState::Learning(state)) => state,
_ => panic!("State is not Normal: {:?}", current_card_state), _ => panic!("State is not Normal: {current_card_state:?}"),
}; };
let elapsed_secs = state.elapsed_secs as i32; let elapsed_secs = state.elapsed_secs as i32;
// Give a 1 second leeway when the test runs on the off chance // Give a 1 second leeway when the test runs on the off chance
// that the test runs as a second rolls over. // that the test runs as a second rolls over.
assert!( assert!(
(elapsed_secs - expected_elapsed_secs).abs() <= 1, (elapsed_secs - expected_elapsed_secs).abs() <= 1,
"elapsed_secs: {} != expected_elapsed_secs: {}", "elapsed_secs: {elapsed_secs} != expected_elapsed_secs: {expected_elapsed_secs}"
elapsed_secs,
expected_elapsed_secs
); );
Ok(()) Ok(())

View file

@ -214,14 +214,14 @@ impl Collection {
.search_terms .search_terms
.get_mut(0) .get_mut(0)
.unwrap(); .unwrap();
term1.search = format!("{} is:due", search); term1.search = format!("{search} is:due");
let term2 = deck let term2 = deck
.filtered_mut() .filtered_mut()
.unwrap() .unwrap()
.search_terms .search_terms
.get_mut(1) .get_mut(1)
.unwrap(); .unwrap();
term2.search = format!("{} is:new", search); term2.search = format!("{search} is:new");
} }
} }

View file

@ -25,7 +25,7 @@ pub fn answer_button_time_collapsible(seconds: u32, collapse_secs: u32, tr: &I18
if seconds == 0 { if seconds == 0 {
tr.scheduling_end().into() tr.scheduling_end().into()
} else if seconds < collapse_secs { } else if seconds < collapse_secs {
format!("<{}", string) format!("<{string}")
} else { } else {
string string
} }

View file

@ -219,7 +219,7 @@ impl From<TemplateKind> for SearchNode {
impl From<NoteId> for SearchNode { impl From<NoteId> for SearchNode {
fn from(n: NoteId) -> Self { fn from(n: NoteId) -> Self {
SearchNode::NoteIds(format!("{}", n)) SearchNode::NoteIds(format!("{n}"))
} }
} }

View file

@ -240,7 +240,7 @@ impl Collection {
} else { } else {
self.storage.setup_searched_cards_table()?; self.storage.setup_searched_cards_table()?;
} }
let sql = format!("insert into search_cids {}", sql); let sql = format!("insert into search_cids {sql}");
let cards = self let cards = self
.storage .storage
@ -307,7 +307,7 @@ impl Collection {
let (sql, args) = writer.build_query(&top_node, mode.required_table())?; let (sql, args) = writer.build_query(&top_node, mode.required_table())?;
self.storage.setup_searched_notes_table()?; self.storage.setup_searched_notes_table()?;
let sql = format!("insert into search_nids {}", sql); let sql = format!("insert into search_nids {sql}");
let notes = self let notes = self
.storage .storage

View file

@ -277,7 +277,7 @@ fn unquoted_term(s: &str) -> IResult<Node> {
Err(parse_failure( Err(parse_failure(
s, s,
FailKind::UnknownEscape { FailKind::UnknownEscape {
provided: format!("\\{}", c), provided: format!("\\{c}"),
}, },
)) ))
} else if "\"() \u{3000}".contains(s.chars().next().unwrap()) { } else if "\"() \u{3000}".contains(s.chars().next().unwrap()) {
@ -637,7 +637,7 @@ fn check_id_list<'a>(s: &'a str, context: &str) -> ParseResult<'a, &'a str> {
s, s,
// id lists are undocumented, so no translation // id lists are undocumented, so no translation
FailKind::Other { FailKind::Other {
info: Some(format!("expected only digits and commas in {}:", context)), info: Some(format!("expected only digits and commas in {context}:")),
}, },
)) ))
} }
@ -1110,19 +1110,19 @@ mod test {
for term in &["added", "edited", "rated", "resched"] { for term in &["added", "edited", "rated", "resched"] {
assert!(matches!( assert!(matches!(
failkind(&format!("{}:1.1", term)), failkind(&format!("{term}:1.1")),
SearchErrorKind::InvalidPositiveWholeNumber { .. } SearchErrorKind::InvalidPositiveWholeNumber { .. }
)); ));
assert!(matches!( assert!(matches!(
failkind(&format!("{}:-1", term)), failkind(&format!("{term}:-1")),
SearchErrorKind::InvalidPositiveWholeNumber { .. } SearchErrorKind::InvalidPositiveWholeNumber { .. }
)); ));
assert!(matches!( assert!(matches!(
failkind(&format!("{}:", term)), failkind(&format!("{term}:")),
SearchErrorKind::InvalidPositiveWholeNumber { .. } SearchErrorKind::InvalidPositiveWholeNumber { .. }
)); ));
assert!(matches!( assert!(matches!(
failkind(&format!("{}:foo", term)), failkind(&format!("{term}:foo")),
SearchErrorKind::InvalidPositiveWholeNumber { .. } SearchErrorKind::InvalidPositiveWholeNumber { .. }
)); ));
} }
@ -1223,19 +1223,19 @@ mod test {
for term in &["ivl", "reps", "lapses", "pos"] { for term in &["ivl", "reps", "lapses", "pos"] {
assert!(matches!( assert!(matches!(
failkind(&format!("prop:{}>", term)), failkind(&format!("prop:{term}>")),
SearchErrorKind::InvalidPositiveWholeNumber { .. } SearchErrorKind::InvalidPositiveWholeNumber { .. }
)); ));
assert!(matches!( assert!(matches!(
failkind(&format!("prop:{}=0.5", term)), failkind(&format!("prop:{term}=0.5")),
SearchErrorKind::InvalidPositiveWholeNumber { .. } SearchErrorKind::InvalidPositiveWholeNumber { .. }
)); ));
assert!(matches!( assert!(matches!(
failkind(&format!("prop:{}!=-1", term)), failkind(&format!("prop:{term}!=-1")),
SearchErrorKind::InvalidPositiveWholeNumber { .. } SearchErrorKind::InvalidPositiveWholeNumber { .. }
)); ));
assert!(matches!( assert!(matches!(
failkind(&format!("prop:{}<foo", term)), failkind(&format!("prop:{term}<foo")),
SearchErrorKind::InvalidPositiveWholeNumber { .. } SearchErrorKind::InvalidPositiveWholeNumber { .. }
)); ));
} }

View file

@ -99,7 +99,7 @@ impl crate::services::SearchService for Collection {
regex::escape(&input.search) regex::escape(&input.search)
}; };
if !input.match_case { if !input.match_case {
search = format!("(?i){}", search); search = format!("(?i){search}");
} }
let mut nids = to_note_ids(input.nids); let mut nids = to_note_ids(input.nids);
let field_name = if input.field_name.is_empty() { let field_name = if input.field_name.is_empty() {

View file

@ -158,13 +158,12 @@ impl SqlWriter<'_> {
}, },
SearchNode::Deck(deck) => self.write_deck(&norm(deck))?, SearchNode::Deck(deck) => self.write_deck(&norm(deck))?,
SearchNode::NotetypeId(ntid) => { SearchNode::NotetypeId(ntid) => {
write!(self.sql, "n.mid = {}", ntid).unwrap(); write!(self.sql, "n.mid = {ntid}").unwrap();
} }
SearchNode::DeckIdsWithoutChildren(dids) => { SearchNode::DeckIdsWithoutChildren(dids) => {
write!( write!(
self.sql, self.sql,
"c.did in ({}) or (c.odid != 0 and c.odid in ({}))", "c.did in ({dids}) or (c.odid != 0 and c.odid in ({dids}))"
dids, dids
) )
.unwrap(); .unwrap();
} }
@ -175,13 +174,13 @@ impl SqlWriter<'_> {
SearchNode::Tag { tag, is_re } => self.write_tag(&norm(tag), *is_re), SearchNode::Tag { tag, is_re } => self.write_tag(&norm(tag), *is_re),
SearchNode::State(state) => self.write_state(state)?, SearchNode::State(state) => self.write_state(state)?,
SearchNode::Flag(flag) => { SearchNode::Flag(flag) => {
write!(self.sql, "(c.flags & 7) == {}", flag).unwrap(); write!(self.sql, "(c.flags & 7) == {flag}").unwrap();
} }
SearchNode::NoteIds(nids) => { SearchNode::NoteIds(nids) => {
write!(self.sql, "{} in ({})", self.note_id_column(), nids).unwrap(); write!(self.sql, "{} in ({})", self.note_id_column(), nids).unwrap();
} }
SearchNode::CardIds(cids) => { SearchNode::CardIds(cids) => {
write!(self.sql, "c.id in ({})", cids).unwrap(); write!(self.sql, "c.id in ({cids})").unwrap();
} }
SearchNode::Property { operator, kind } => self.write_prop(operator, kind)?, SearchNode::Property { operator, kind } => self.write_prop(operator, kind)?,
SearchNode::CustomData(key) => self.write_custom_data(key)?, SearchNode::CustomData(key) => self.write_custom_data(key)?,
@ -199,7 +198,7 @@ impl SqlWriter<'_> {
text text
}; };
// implicitly wrap in % // implicitly wrap in %
let text = format!("%{}%", text); let text = format!("%{text}%");
self.args.push(text); self.args.push(text);
let arg_idx = self.args.len(); let arg_idx = self.args.len();
@ -279,7 +278,7 @@ impl SqlWriter<'_> {
text => { text => {
write!(self.sql, "n.tags regexp ?").unwrap(); write!(self.sql, "n.tags regexp ?").unwrap();
let re = &to_custom_re(text, r"\S"); let re = &to_custom_re(text, r"\S");
self.args.push(format!("(?i).* {}(::| ).*", re)); self.args.push(format!("(?i).* {re}(::| ).*"));
} }
} }
} }
@ -293,10 +292,10 @@ impl SqlWriter<'_> {
write!(self.sql, "c.id in (select cid from revlog where id").unwrap(); write!(self.sql, "c.id in (select cid from revlog where id").unwrap();
match op { match op {
">" => write!(self.sql, " >= {}", target_cutoff_ms), ">" => write!(self.sql, " >= {target_cutoff_ms}"),
">=" => write!(self.sql, " >= {}", day_before_cutoff_ms), ">=" => write!(self.sql, " >= {day_before_cutoff_ms}"),
"<" => write!(self.sql, " < {}", day_before_cutoff_ms), "<" => write!(self.sql, " < {day_before_cutoff_ms}"),
"<=" => write!(self.sql, " < {}", target_cutoff_ms), "<=" => write!(self.sql, " < {target_cutoff_ms}"),
"=" => write!( "=" => write!(
self.sql, self.sql,
" between {} and {}", " between {} and {}",
@ -314,7 +313,7 @@ impl SqlWriter<'_> {
.unwrap(); .unwrap();
match ease { match ease {
RatingKind::AnswerButton(u) => write!(self.sql, " and ease = {})", u), RatingKind::AnswerButton(u) => write!(self.sql, " and ease = {u})"),
RatingKind::AnyAnswerButton => write!(self.sql, " and ease > 0)"), RatingKind::AnyAnswerButton => write!(self.sql, " and ease > 0)"),
RatingKind::ManualReschedule => write!(self.sql, " and ease = 0)"), RatingKind::ManualReschedule => write!(self.sql, " and ease = 0)"),
} }
@ -356,9 +355,9 @@ impl SqlWriter<'_> {
pos = pos pos = pos
) )
.unwrap(), .unwrap(),
PropertyKind::Interval(ivl) => write!(self.sql, "ivl {} {}", op, ivl).unwrap(), PropertyKind::Interval(ivl) => write!(self.sql, "ivl {op} {ivl}").unwrap(),
PropertyKind::Reps(reps) => write!(self.sql, "reps {} {}", op, reps).unwrap(), PropertyKind::Reps(reps) => write!(self.sql, "reps {op} {reps}").unwrap(),
PropertyKind::Lapses(days) => write!(self.sql, "lapses {} {}", op, days).unwrap(), PropertyKind::Lapses(days) => write!(self.sql, "lapses {op} {days}").unwrap(),
PropertyKind::Ease(ease) => { PropertyKind::Ease(ease) => {
write!(self.sql, "factor {} {}", op, (ease * 1000.0) as u32).unwrap() write!(self.sql, "factor {} {}", op, (ease * 1000.0) as u32).unwrap()
} }
@ -474,7 +473,7 @@ impl SqlWriter<'_> {
}; };
// convert to a regex that includes child decks // convert to a regex that includes child decks
self.args.push(format!("(?i)^{}($|\x1f)", native_deck)); self.args.push(format!("(?i)^{native_deck}($|\x1f)"));
let arg_idx = self.args.len(); let arg_idx = self.args.len();
self.sql.push_str(&format!(concat!( self.sql.push_str(&format!(concat!(
"(c.did in (select id from decks where name regexp ?{n})", "(c.did in (select id from decks where name regexp ?{n})",
@ -491,7 +490,7 @@ impl SqlWriter<'_> {
let ids = self.col.storage.deck_id_with_children(&parent)?; let ids = self.col.storage.deck_id_with_children(&parent)?;
let mut buf = String::new(); let mut buf = String::new();
ids_to_string(&mut buf, &ids); ids_to_string(&mut buf, &ids);
write!(self.sql, "c.did in {}", buf,).unwrap(); write!(self.sql, "c.did in {buf}",).unwrap();
} else { } else {
self.sql.push_str("false") self.sql.push_str("false")
} }
@ -502,7 +501,7 @@ impl SqlWriter<'_> {
fn write_template(&mut self, template: &TemplateKind) { fn write_template(&mut self, template: &TemplateKind) {
match template { match template {
TemplateKind::Ordinal(n) => { TemplateKind::Ordinal(n) => {
write!(self.sql, "c.ord = {}", n).unwrap(); write!(self.sql, "c.ord = {n}").unwrap();
} }
TemplateKind::Name(name) => { TemplateKind::Name(name) => {
if is_glob(name) { if is_glob(name) {
@ -550,7 +549,7 @@ impl SqlWriter<'_> {
} }
fn write_all_fields_regexp(&mut self, val: &str) { fn write_all_fields_regexp(&mut self, val: &str) {
self.args.push(format!("(?i){}", val)); self.args.push(format!("(?i){val}"));
write!(self.sql, "regexp_fields(?{}, n.flds)", self.args.len()).unwrap(); write!(self.sql, "regexp_fields(?{}, n.flds)", self.args.len()).unwrap();
} }
@ -566,7 +565,7 @@ impl SqlWriter<'_> {
return Ok(()); return Ok(());
} }
self.args.push(format!("(?i){}", val)); self.args.push(format!("(?i){val}"));
let arg_idx = self.args.len(); let arg_idx = self.args.len();
let all_notetype_clauses = field_indicies_by_notetype let all_notetype_clauses = field_indicies_by_notetype
@ -775,13 +774,13 @@ impl SqlWriter<'_> {
fn write_added(&mut self, days: u32) -> Result<()> { fn write_added(&mut self, days: u32) -> Result<()> {
let cutoff = self.previous_day_cutoff(days)?.as_millis(); let cutoff = self.previous_day_cutoff(days)?.as_millis();
write!(self.sql, "c.id > {}", cutoff).unwrap(); write!(self.sql, "c.id > {cutoff}").unwrap();
Ok(()) Ok(())
} }
fn write_edited(&mut self, days: u32) -> Result<()> { fn write_edited(&mut self, days: u32) -> Result<()> {
let cutoff = self.previous_day_cutoff(days)?; let cutoff = self.previous_day_cutoff(days)?;
write!(self.sql, "n.mod > {}", cutoff).unwrap(); write!(self.sql, "n.mod > {cutoff}").unwrap();
Ok(()) Ok(())
} }
@ -813,7 +812,7 @@ impl SqlWriter<'_> {
} else { } else {
std::borrow::Cow::Borrowed(word) std::borrow::Cow::Borrowed(word)
}; };
self.args.push(format!(r"(?i){}", word)); self.args.push(format!(r"(?i){word}"));
let arg_idx = self.args.len(); let arg_idx = self.args.len();
if let Some(field_indices_by_notetype) = self.included_fields_for_unqualified_regex()? { if let Some(field_indices_by_notetype) = self.included_fields_for_unqualified_regex()? {
let notetype_clause = |ctx: &UnqualifiedRegexSearchContext| -> String { let notetype_clause = |ctx: &UnqualifiedRegexSearchContext| -> String {

View file

@ -70,30 +70,30 @@ fn write_search_node(node: &SearchNode) -> String {
match node { match node {
UnqualifiedText(s) => maybe_quote(&s.replace(':', "\\:")), UnqualifiedText(s) => maybe_quote(&s.replace(':', "\\:")),
SingleField { field, text, is_re } => write_single_field(field, text, *is_re), SingleField { field, text, is_re } => write_single_field(field, text, *is_re),
AddedInDays(u) => format!("added:{}", u), AddedInDays(u) => format!("added:{u}"),
EditedInDays(u) => format!("edited:{}", u), EditedInDays(u) => format!("edited:{u}"),
IntroducedInDays(u) => format!("introduced:{}", u), IntroducedInDays(u) => format!("introduced:{u}"),
CardTemplate(t) => write_template(t), CardTemplate(t) => write_template(t),
Deck(s) => maybe_quote(&format!("deck:{}", s)), Deck(s) => maybe_quote(&format!("deck:{s}")),
DeckIdsWithoutChildren(s) => format!("did:{}", s), DeckIdsWithoutChildren(s) => format!("did:{s}"),
// not exposed on the GUI end // not exposed on the GUI end
DeckIdWithChildren(_) => "".to_string(), DeckIdWithChildren(_) => "".to_string(),
NotetypeId(NotetypeIdType(i)) => format!("mid:{}", i), NotetypeId(NotetypeIdType(i)) => format!("mid:{i}"),
Notetype(s) => maybe_quote(&format!("note:{}", s)), Notetype(s) => maybe_quote(&format!("note:{s}")),
Rated { days, ease } => write_rated(days, ease), Rated { days, ease } => write_rated(days, ease),
Tag { tag, is_re } => write_single_field("tag", tag, *is_re), Tag { tag, is_re } => write_single_field("tag", tag, *is_re),
Duplicates { notetype_id, text } => write_dupe(notetype_id, text), Duplicates { notetype_id, text } => write_dupe(notetype_id, text),
State(k) => write_state(k), State(k) => write_state(k),
Flag(u) => format!("flag:{}", u), Flag(u) => format!("flag:{u}"),
NoteIds(s) => format!("nid:{}", s), NoteIds(s) => format!("nid:{s}"),
CardIds(s) => format!("cid:{}", s), CardIds(s) => format!("cid:{s}"),
Property { operator, kind } => write_property(operator, kind), Property { operator, kind } => write_property(operator, kind),
WholeCollection => "deck:*".to_string(), WholeCollection => "deck:*".to_string(),
Regex(s) => maybe_quote(&format!("re:{}", s)), Regex(s) => maybe_quote(&format!("re:{s}")),
NoCombining(s) => maybe_quote(&format!("nc:{}", s)), NoCombining(s) => maybe_quote(&format!("nc:{s}")),
WordBoundary(s) => maybe_quote(&format!("w:{}", s)), WordBoundary(s) => maybe_quote(&format!("w:{s}")),
CustomData(k) => maybe_quote(&format!("has-cd:{}", k)), CustomData(k) => maybe_quote(&format!("has-cd:{k}")),
Preset(s) => maybe_quote(&format!("preset:{}", s)), Preset(s) => maybe_quote(&format!("preset:{s}")),
} }
} }
@ -128,23 +128,23 @@ fn write_single_field(field: &str, text: &str, is_re: bool) -> String {
fn write_template(template: &TemplateKind) -> String { fn write_template(template: &TemplateKind) -> String {
match template { match template {
TemplateKind::Ordinal(u) => format!("card:{}", u + 1), TemplateKind::Ordinal(u) => format!("card:{}", u + 1),
TemplateKind::Name(s) => maybe_quote(&format!("card:{}", s)), TemplateKind::Name(s) => maybe_quote(&format!("card:{s}")),
} }
} }
fn write_rated(days: &u32, ease: &RatingKind) -> String { fn write_rated(days: &u32, ease: &RatingKind) -> String {
use RatingKind::*; use RatingKind::*;
match ease { match ease {
AnswerButton(n) => format!("rated:{}:{}", days, n), AnswerButton(n) => format!("rated:{days}:{n}"),
AnyAnswerButton => format!("rated:{}", days), AnyAnswerButton => format!("rated:{days}"),
ManualReschedule => format!("resched:{}", days), ManualReschedule => format!("resched:{days}"),
} }
} }
/// Escape double quotes and backslashes: \" /// Escape double quotes and backslashes: \"
fn write_dupe(notetype_id: &NotetypeId, text: &str) -> String { fn write_dupe(notetype_id: &NotetypeId, text: &str) -> String {
let esc = text.replace('\\', r"\\"); let esc = text.replace('\\', r"\\");
maybe_quote(&format!("dupe:{},{}", notetype_id, esc)) maybe_quote(&format!("dupe:{notetype_id},{esc}"))
} }
fn write_state(kind: &StateKind) -> String { fn write_state(kind: &StateKind) -> String {
@ -167,19 +167,19 @@ fn write_state(kind: &StateKind) -> String {
fn write_property(operator: &str, kind: &PropertyKind) -> String { fn write_property(operator: &str, kind: &PropertyKind) -> String {
use PropertyKind::*; use PropertyKind::*;
match kind { match kind {
Due(i) => format!("prop:due{}{}", operator, i), Due(i) => format!("prop:due{operator}{i}"),
Interval(u) => format!("prop:ivl{}{}", operator, u), Interval(u) => format!("prop:ivl{operator}{u}"),
Reps(u) => format!("prop:reps{}{}", operator, u), Reps(u) => format!("prop:reps{operator}{u}"),
Lapses(u) => format!("prop:lapses{}{}", operator, u), Lapses(u) => format!("prop:lapses{operator}{u}"),
Ease(f) => format!("prop:ease{}{}", operator, f), Ease(f) => format!("prop:ease{operator}{f}"),
Position(u) => format!("prop:pos{}{}", operator, u), Position(u) => format!("prop:pos{operator}{u}"),
Stability(u) => format!("prop:s{}{}", operator, u), Stability(u) => format!("prop:s{operator}{u}"),
Difficulty(u) => format!("prop:d{}{}", operator, u), Difficulty(u) => format!("prop:d{operator}{u}"),
Retrievability(u) => format!("prop:r{}{}", operator, u), Retrievability(u) => format!("prop:r{operator}{u}"),
Rated(u, ease) => match ease { Rated(u, ease) => match ease {
RatingKind::AnswerButton(val) => format!("prop:rated{}{}:{}", operator, u, val), RatingKind::AnswerButton(val) => format!("prop:rated{operator}{u}:{val}"),
RatingKind::AnyAnswerButton => format!("prop:rated{}{}", operator, u), RatingKind::AnyAnswerButton => format!("prop:rated{operator}{u}"),
RatingKind::ManualReschedule => format!("prop:resched{}{}", operator, u), RatingKind::ManualReschedule => format!("prop:resched{operator}{u}"),
}, },
CustomDataNumber { key, value } => format!("prop:cdn:{key}{operator}{value}"), CustomDataNumber { key, value } => format!("prop:cdn:{key}{operator}{value}"),
CustomDataString { key, value } => { CustomDataString { key, value } => {

View file

@ -829,8 +829,7 @@ impl fmt::Display for ReviewOrderSubclause {
ReviewOrderSubclause::RetrievabilitySm2 { today, order } => { ReviewOrderSubclause::RetrievabilitySm2 { today, order } => {
temp_string = format!( temp_string = format!(
// - (elapsed days+0.001)/(scheduled interval) // - (elapsed days+0.001)/(scheduled interval)
"-(1 + cast({today}-due+0.001 as real)/ivl) {order}", "-(1 + cast({today}-due+0.001 as real)/ivl) {order}"
today = today
); );
&temp_string &temp_string
} }
@ -844,7 +843,7 @@ impl fmt::Display for ReviewOrderSubclause {
ReviewOrderSubclause::Added => "nid asc, ord asc", ReviewOrderSubclause::Added => "nid asc, ord asc",
ReviewOrderSubclause::ReverseAdded => "nid desc, ord asc", ReviewOrderSubclause::ReverseAdded => "nid desc, ord asc",
}; };
write!(f, "{}", clause) write!(f, "{clause}")
} }
} }

View file

@ -33,7 +33,7 @@ fn row_to_deck(row: &Row) -> Result<Deck> {
common, common,
kind: kind.kind.ok_or_else(|| { kind: kind.kind.ok_or_else(|| {
AnkiError::db_error( AnkiError::db_error(
format!("invalid deck kind: {}", id), format!("invalid deck kind: {id}"),
DbErrorKind::MissingEntity, DbErrorKind::MissingEntity,
) )
})?, })?,
@ -347,8 +347,8 @@ impl SqliteStorage {
))?; ))?;
let top = current.name.as_native_str(); let top = current.name.as_native_str();
let prefix_start = &format!("{}\x1f", top); let prefix_start = &format!("{top}\x1f");
let prefix_end = &format!("{}\x20", top); let prefix_end = &format!("{top}\x20");
self.db self.db
.prepare_cached(include_str!("update_active.sql"))? .prepare_cached(include_str!("update_active.sql"))?
@ -379,7 +379,7 @@ impl SqliteStorage {
let decks = self let decks = self
.get_schema11_decks() .get_schema11_decks()
.map_err(|e| AnkiError::JsonError { .map_err(|e| AnkiError::JsonError {
info: format!("decoding decks: {}", e), info: format!("decoding decks: {e}"),
})?; })?;
let mut names = HashSet::new(); let mut names = HashSet::new();
for (_id, deck) in decks { for (_id, deck) in decks {

View file

@ -197,7 +197,7 @@ impl SqliteStorage {
serde_json::from_value(conf) serde_json::from_value(conf)
}) })
.map_err(|e| AnkiError::JsonError { .map_err(|e| AnkiError::JsonError {
info: format!("decoding deck config: {}", e), info: format!("decoding deck config: {e}"),
}) })
})?; })?;
for (id, mut conf) in conf.into_iter() { for (id, mut conf) in conf.into_iter() {

View file

@ -52,7 +52,7 @@ where
{ {
let mut trailing_sep = false; let mut trailing_sep = false;
for id in ids { for id in ids {
write!(buf, "{},", id).unwrap(); write!(buf, "{id},").unwrap();
trailing_sep = true; trailing_sep = true;
} }
if trailing_sep { if trailing_sep {

View file

@ -345,7 +345,7 @@ impl SqliteStorage {
let nts = self let nts = self
.get_schema11_notetypes() .get_schema11_notetypes()
.map_err(|e| AnkiError::JsonError { .map_err(|e| AnkiError::JsonError {
info: format!("decoding models: {:?}", e), info: format!("decoding models: {e:?}"),
})?; })?;
let mut names = HashSet::new(); let mut names = HashSet::new();
for (mut ntid, nt) in nts { for (mut ntid, nt) in nts {

View file

@ -587,7 +587,7 @@ impl SqliteStorage {
}) { }) {
Ok(corrupt) => corrupt, Ok(corrupt) => corrupt,
Err(e) => { Err(e) => {
println!("error: {:?}", e); println!("error: {e:?}");
true true
} }
} }

View file

@ -54,7 +54,7 @@ impl SqliteStorage {
if let Some(new_usn) = server_usn_if_client { if let Some(new_usn) = server_usn_if_client {
let mut stmt = self let mut stmt = self
.db .db
.prepare_cached(&format!("update {} set usn=? where id=?", table))?; .prepare_cached(&format!("update {table} set usn=? where id=?"))?;
for id in ids { for id in ids {
stmt.execute(params![new_usn, id])?; stmt.execute(params![new_usn, id])?;
} }

View file

@ -11,7 +11,7 @@ impl SqliteStorage {
fn table_has_usn(&self, table: &str) -> Result<bool> { fn table_has_usn(&self, table: &str) -> Result<bool> {
Ok(self Ok(self
.db .db
.prepare(&format!("select null from {} where usn=-1", table))? .prepare(&format!("select null from {table} where usn=-1"))?
.query([])? .query([])?
.next()? .next()?
.is_some()) .is_some())
@ -19,7 +19,7 @@ impl SqliteStorage {
fn table_count(&self, table: &str) -> Result<u32> { fn table_count(&self, table: &str) -> Result<u32> {
self.db self.db
.query_row(&format!("select count() from {}", table), [], |r| r.get(0)) .query_row(&format!("select count() from {table}"), [], |r| r.get(0))
.map_err(Into::into) .map_err(Into::into)
} }
@ -36,7 +36,7 @@ impl SqliteStorage {
] { ] {
if self.table_has_usn(table)? { if self.table_has_usn(table)? {
return Err(AnkiError::sync_error( return Err(AnkiError::sync_error(
format!("table had usn=-1: {}", table), format!("table had usn=-1: {table}"),
SyncErrorKind::Other, SyncErrorKind::Other,
)); ));
} }

View file

@ -100,7 +100,7 @@ where
_lock = LOCK.lock().await; _lock = LOCK.lock().await;
endpoint endpoint
} else { } else {
format!("http://{}/", addr) format!("http://{addr}/")
}; };
let endpoint = Url::try_from(endpoint.as_str()).unwrap(); let endpoint = Url::try_from(endpoint.as_str()).unwrap();
let auth = SyncAuth { let auth = SyncAuth {
@ -734,7 +734,7 @@ async fn regular_sync(ctx: &SyncTestContext) -> Result<()> {
for table in &["cards", "notes", "decks"] { for table in &["cards", "notes", "decks"] {
assert_eq!( assert_eq!(
col1.storage col1.storage
.db_scalar::<u8>(&format!("select count() from {}", table))?, .db_scalar::<u8>(&format!("select count() from {table}"))?,
2 2
); );
} }
@ -754,7 +754,7 @@ async fn regular_sync(ctx: &SyncTestContext) -> Result<()> {
for table in &["cards", "notes", "decks"] { for table in &["cards", "notes", "decks"] {
assert_eq!( assert_eq!(
col2.storage col2.storage
.db_scalar::<u8>(&format!("select count() from {}", table))?, .db_scalar::<u8>(&format!("select count() from {table}"))?,
1 1
); );
} }

View file

@ -285,7 +285,7 @@ fn row_to_name_and_checksum(row: &Row) -> error::Result<(String, Sha1Hash)> {
fn trace(event: rusqlite::trace::TraceEvent) { fn trace(event: rusqlite::trace::TraceEvent) {
if let rusqlite::trace::TraceEvent::Stmt(_, sql) = event { if let rusqlite::trace::TraceEvent::Stmt(_, sql) = event {
println!("sql: {}", sql); println!("sql: {sql}");
} }
} }

View file

@ -35,7 +35,7 @@ impl Collection {
}; };
if !match_case { if !match_case {
search = format!("(?i){}", search).into(); search = format!("(?i){search}").into();
} }
self.transact(Op::UpdateTag, |col| { self.transact(Op::UpdateTag, |col| {

View file

@ -33,7 +33,7 @@ impl TagMatcher {
(?:^|\ ) (?:^|\ )
# 1: the tag prefix # 1: the tag prefix
( (
{} {tags}
) )
(?: (?:
# 2: an optional child separator # 2: an optional child separator
@ -41,8 +41,7 @@ impl TagMatcher {
# or a space/end of string the end of the string # or a space/end of string the end of the string
|\ |$ |\ |$
) )
"#, "#
tags
))?; ))?;
Ok(Self { Ok(Self {
@ -61,7 +60,7 @@ impl TagMatcher {
let out = self.regex.replace(tag, |caps: &Captures| { let out = self.regex.replace(tag, |caps: &Captures| {
// if we captured the child separator, add it to the replacement // if we captured the child separator, add it to the replacement
if caps.get(2).is_some() { if caps.get(2).is_some() {
Cow::Owned(format!("{}::", replacement)) Cow::Owned(format!("{replacement}::"))
} else { } else {
Cow::Borrowed(replacement) Cow::Borrowed(replacement)
} }
@ -92,7 +91,7 @@ impl TagMatcher {
let replacement = replacer(caps.get(1).unwrap().as_str()); let replacement = replacer(caps.get(1).unwrap().as_str());
// if we captured the child separator, add it to the replacement // if we captured the child separator, add it to the replacement
if caps.get(2).is_some() { if caps.get(2).is_some() {
format!("{}::", replacement) format!("{replacement}::")
} else { } else {
replacement replacement
} }

View file

@ -109,7 +109,7 @@ fn reparented_name(existing_name: &str, new_parent: Option<&str>) -> Option<Stri
None None
} else { } else {
// foo::bar onto baz -> baz::bar // foo::bar onto baz -> baz::bar
let new_name = format!("{}::{}", new_parent, existing_base); let new_name = format!("{new_parent}::{existing_base}");
if new_name != existing_name { if new_name != existing_name {
Some(new_name) Some(new_name)
} else { } else {

View file

@ -265,10 +265,8 @@ fn template_error_to_anki_error(
}; };
let details = htmlescape::encode_minimal(&localized_template_error(tr, err)); let details = htmlescape::encode_minimal(&localized_template_error(tr, err));
let more_info = tr.card_template_rendering_more_info(); let more_info = tr.card_template_rendering_more_info();
let source = format!( let source =
"{}<br>{}<br><a href='{}'>{}</a>", format!("{header}<br>{details}<br><a href='{TEMPLATE_ERROR_LINK}'>{more_info}</a>");
header, details, TEMPLATE_ERROR_LINK, more_info
);
AnkiError::TemplateError { info: source } AnkiError::TemplateError { info: source }
} }
@ -279,32 +277,29 @@ fn localized_template_error(tr: &I18n, err: TemplateError) -> String {
.card_template_rendering_no_closing_brackets("}}", tag) .card_template_rendering_no_closing_brackets("}}", tag)
.into(), .into(),
TemplateError::ConditionalNotClosed(tag) => tr TemplateError::ConditionalNotClosed(tag) => tr
.card_template_rendering_conditional_not_closed(format!("{{{{/{}}}}}", tag)) .card_template_rendering_conditional_not_closed(format!("{{{{/{tag}}}}}"))
.into(), .into(),
TemplateError::ConditionalNotOpen { TemplateError::ConditionalNotOpen {
closed, closed,
currently_open, currently_open,
} => if let Some(open) = currently_open { } => if let Some(open) = currently_open {
tr.card_template_rendering_wrong_conditional_closed( tr.card_template_rendering_wrong_conditional_closed(
format!("{{{{/{}}}}}", closed), format!("{{{{/{closed}}}}}"),
format!("{{{{/{}}}}}", open), format!("{{{{/{open}}}}}"),
) )
} else { } else {
tr.card_template_rendering_conditional_not_open( tr.card_template_rendering_conditional_not_open(
format!("{{{{/{}}}}}", closed), format!("{{{{/{closed}}}}}"),
format!("{{{{#{}}}}}", closed), format!("{{{{#{closed}}}}}"),
format!("{{{{^{}}}}}", closed), format!("{{{{^{closed}}}}}"),
) )
} }
.into(), .into(),
TemplateError::FieldNotFound { field, filters } => tr TemplateError::FieldNotFound { field, filters } => tr
.card_template_rendering_no_such_field(format!("{{{{{}{}}}}}", filters, field), field) .card_template_rendering_no_such_field(format!("{{{{{filters}{field}}}}}"), field)
.into(), .into(),
TemplateError::NoSuchConditional(condition) => tr TemplateError::NoSuchConditional(condition) => tr
.card_template_rendering_no_such_field( .card_template_rendering_no_such_field(format!("{{{{{condition}}}}}"), &condition[1..])
format!("{{{{{}}}}}", condition),
&condition[1..],
)
.into(), .into(),
} }
} }
@ -523,10 +518,7 @@ impl RenderContext<'_> {
Ok(false ^ negated) Ok(false ^ negated)
} else { } else {
let prefix = if negated { "^" } else { "#" }; let prefix = if negated { "^" } else { "#" };
Err(TemplateError::NoSuchConditional(format!( Err(TemplateError::NoSuchConditional(format!("{prefix}{key}")))
"{}{}",
prefix, key
)))
} }
} }
} }
@ -858,14 +850,14 @@ fn nodes_to_string(buf: &mut String, nodes: &[ParsedNode]) {
.unwrap(); .unwrap();
} }
ParsedNode::Conditional { key, children } => { ParsedNode::Conditional { key, children } => {
write!(buf, "{{{{#{}}}}}", key).unwrap(); write!(buf, "{{{{#{key}}}}}").unwrap();
nodes_to_string(buf, children); nodes_to_string(buf, children);
write!(buf, "{{{{/{}}}}}", key).unwrap(); write!(buf, "{{{{/{key}}}}}").unwrap();
} }
ParsedNode::NegatedConditional { key, children } => { ParsedNode::NegatedConditional { key, children } => {
write!(buf, "{{{{^{}}}}}", key).unwrap(); write!(buf, "{{{{^{key}}}}}").unwrap();
nodes_to_string(buf, children); nodes_to_string(buf, children);
write!(buf, "{{{{/{}}}}}", key).unwrap(); write!(buf, "{{{{/{key}}}}}").unwrap();
} }
} }
} }

View file

@ -165,15 +165,15 @@ fn furigana_filter(text: &str) -> Cow<str> {
/// convert to [[type:...]] for the gui code to process /// convert to [[type:...]] for the gui code to process
fn type_filter<'a>(field_name: &str) -> Cow<'a, str> { fn type_filter<'a>(field_name: &str) -> Cow<'a, str> {
format!("[[type:{}]]", field_name).into() format!("[[type:{field_name}]]").into()
} }
fn type_cloze_filter<'a>(field_name: &str) -> Cow<'a, str> { fn type_cloze_filter<'a>(field_name: &str) -> Cow<'a, str> {
format!("[[type:cloze:{}]]", field_name).into() format!("[[type:cloze:{field_name}]]").into()
} }
fn type_nc_filter<'a>(field_name: &str) -> Cow<'a, str> { fn type_nc_filter<'a>(field_name: &str) -> Cow<'a, str> {
format!("[[type:nc:{}]]", field_name).into() format!("[[type:nc:{field_name}]]").into()
} }
fn hint_filter<'a>(text: &'a str, field_name: &str) -> Cow<'a, str> { fn hint_filter<'a>(text: &'a str, field_name: &str) -> Cow<'a, str> {
@ -191,18 +191,17 @@ fn hint_filter<'a>(text: &'a str, field_name: &str) -> Cow<'a, str> {
r##" r##"
<a class=hint href="#" <a class=hint href="#"
onclick="this.style.display='none'; onclick="this.style.display='none';
document.getElementById('hint{}').style.display='block'; document.getElementById('hint{id}').style.display='block';
return false;" draggable=false> return false;" draggable=false>
{}</a> {field_name}</a>
<div id="hint{}" class=hint style="display: none">{}</div> <div id="hint{id}" class=hint style="display: none">{text}</div>
"##, "##
id, field_name, id, text
) )
.into() .into()
} }
fn tts_filter(options: &str, text: &str) -> String { fn tts_filter(options: &str, text: &str) -> String {
format!("[anki:tts lang={}]{}[/anki:tts]", options, text) format!("[anki:tts lang={options}]{text}[/anki:tts]")
} }
// Tests // Tests

View file

@ -484,7 +484,7 @@ pub(crate) fn to_custom_re<'a>(txt: &'a str, wildcard: &str) -> Cow<'a, str> {
match s { match s {
r"\\" | r"\*" => s.to_string(), r"\\" | r"\*" => s.to_string(),
r"\_" => "_".to_string(), r"\_" => "_".to_string(),
"*" => format!("{}*", wildcard), "*" => format!("{wildcard}*"),
"_" => wildcard.to_string(), "_" => wildcard.to_string(),
s => regex::escape(s), s => regex::escape(s),
} }

View file

@ -1,3 +1,3 @@
[toolchain] [toolchain]
# older versions may fail to compile; newer versions may fail the clippy tests # older versions may fail to compile; newer versions may fail the clippy tests
channel = "1.87.0" channel = "1.88.0"

View file

@ -108,7 +108,7 @@ impl LintContext {
LazyCell::force(&self.unstaged_changes); LazyCell::force(&self.unstaged_changes);
fix_copyright(path)?; fix_copyright(path)?;
} else { } else {
println!("missing standard copyright header: {:?}", path); println!("missing standard copyright header: {path:?}");
self.found_problems = true; self.found_problems = true;
} }
} }
@ -241,7 +241,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
.write(true) .write(true)
.open(path) .open(path)
.with_context(|| format!("opening {path}"))?; .with_context(|| format!("opening {path}"))?;
write!(file, "{}{}", header, data).with_context(|| format!("writing {path}"))?; write!(file, "{header}{data}").with_context(|| format!("writing {path}"))?;
Ok(()) Ok(())
} }