From 131d3b30eed1e51998b55f36b282e339503a9d42 Mon Sep 17 00:00:00 2001 From: ef3d0c3e Date: Wed, 31 Jul 2024 10:54:19 +0200 Subject: [PATCH] Layouts --- src/elements/graphviz.rs | 5 +- src/elements/layout.rs | 416 ++++++++++++++++++++++++++++++++++++++ src/elements/mod.rs | 27 +-- src/elements/raw.rs | 10 - src/elements/registrar.rs | 2 + src/elements/style.rs | 219 +++++++++++--------- src/elements/tex.rs | 4 +- src/main.rs | 8 +- 8 files changed, 567 insertions(+), 124 deletions(-) create mode 100644 src/elements/layout.rs diff --git a/src/elements/graphviz.rs b/src/elements/graphviz.rs index 29d7f58..f2cbdc9 100644 --- a/src/elements/graphviz.rs +++ b/src/elements/graphviz.rs @@ -39,7 +39,6 @@ struct Graphviz { pub dot: String, pub layout: Layout, pub width: String, - pub caption: Option, } fn layout_from_str(value: &str) -> Result { @@ -100,6 +99,7 @@ impl Cached for Graphviz { fn key(&self) -> ::Key { let mut hasher = Sha512::new(); hasher.input((self.layout as usize).to_be_bytes().as_slice()); + hasher.input(self.width.as_bytes()); hasher.input(self.dot.as_bytes()); hasher.result_str() @@ -354,8 +354,6 @@ impl RegexRule for GraphRule { }, }; - // TODO: Caption - parser.push( document, Box::new(Graphviz { @@ -363,7 +361,6 @@ impl RegexRule for GraphRule { dot: graph_content, layout: graph_layout, width: graph_width, - caption: None, }), ); diff --git a/src/elements/layout.rs b/src/elements/layout.rs new file mode 100644 index 0000000..e86ff73 --- /dev/null +++ b/src/elements/layout.rs @@ -0,0 +1,416 @@ +use crate::compiler::compiler::Compiler; +use crate::compiler::compiler::Target; +use crate::document::document::Document; +use crate::document::element::ElemKind; +use crate::document::element::Element; +use crate::parser::parser::Parser; +use crate::parser::rule::RegexRule; +use crate::parser::source::Source; +use crate::parser::source::Token; +use crate::parser::state::Scope; +use crate::parser::state::State; +use ariadne::Fmt; +use ariadne::Label; +use ariadne::Report; +use ariadne::ReportKind; +use lazy_static::lazy_static; +use mlua::Function; +use mlua::Lua; +use regex::Captures; +use regex::Regex; +use std::cell::RefCell; +use std::collections::HashMap; +use std::ops::Range; +use std::rc::Rc; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum LayoutToken { + BEGIN, + NEXT, + END, +} + +/// Represents the type of a layout +pub trait LayoutType: core::fmt::Debug { + /// Name of the layout + fn name(&self) -> &'static str; + + /// Expected number of blocks + fn expects(&self) -> Range; + + /// Compile layout + fn compile( + &self, + token: LayoutToken, + id: usize, + compiler: &Compiler, + document: &dyn Document, + ) -> Result; +} + +mod default_layouts { + use super::*; + + #[derive(Debug)] + pub struct Centered; + + impl LayoutType for Centered { + fn name(&self) -> &'static str { "Centered" } + + fn expects(&self) -> Range { 1..1 } + + fn compile( + &self, + token: LayoutToken, + _id: usize, + compiler: &Compiler, + _document: &dyn Document, + ) -> Result { + match compiler.target() { + Target::HTML => match token { + LayoutToken::BEGIN => Ok(r#"
"#.to_string()), + LayoutToken::NEXT => panic!(), + LayoutToken::END => Ok(r#"
"#.to_string()), + }, + _ => todo!(""), + } + } + } +} + +#[derive(Debug)] +struct Layout { + pub(self) location: Token, + pub(self) layout: Rc, + pub(self) id: usize, + pub(self) token: LayoutToken, +} + +impl Element for Layout { + fn location(&self) -> &Token { &self.location } + fn kind(&self) -> ElemKind { ElemKind::Block } + fn element_name(&self) -> &'static str { "Layout" } + fn to_string(&self) -> String { format!("{self:#?}") } + fn compile(&self, compiler: &Compiler, document: &dyn Document) -> Result { + self.layout.compile(self.token, self.id, compiler, document) + } +} + +struct LayoutState { + /// The layout stack + pub(self) stack: Vec<(Vec, Rc)>, +} + +impl State for LayoutState { + fn scope(&self) -> Scope { Scope::DOCUMENT } + + fn on_remove<'a>( + &self, + parser: &dyn Parser, + document: &dyn Document, + ) -> Vec, Range)>> { + let mut reports = vec![]; + + let doc_borrow = document.content().borrow(); + let at = doc_borrow.last().unwrap().location(); + + for (tokens, layout_type) in &self.stack { + let start = tokens.first().unwrap(); + reports.push( + Report::build(ReportKind::Error, start.source(), start.start()) + .with_message("Unterminated Layout") + //.with_label( + // Label::new((document.source(), active_range.clone())) + // .with_order(0) + // .with_message(format!("Style {} is not terminated before the end of paragraph", + // name.fg(parser.colors().info))) + // .with_color(parser.colors().error)) + .with_label( + Label::new((start.source(), start.range.start + 1..start.range.end)) + .with_order(1) + .with_message(format!( + "Layout {} stars here", + layout_type.name().fg(parser.colors().info) + )) + .with_color(parser.colors().error), + ) + .with_label( + Label::new((at.source(), at.range.clone())) + .with_order(2) + .with_message("Document ends here".to_string()) + .with_color(parser.colors().error), + ) + .finish(), + ); + } + + return reports; + } +} + +pub struct LayoutRule { + re: [Regex; 3], + layouts: HashMap>, +} + +impl LayoutRule { + pub fn new() -> Self { + let mut layouts: HashMap> = HashMap::new(); + let layout_centered = default_layouts::Centered {}; + layouts.insert(layout_centered.name().to_string(), Rc::new(layout_centered)); + + Self { + re: [ + Regex::new(r"(?:^|\n)#\+LAYOUT_BEGIN(.*)").unwrap(), + Regex::new(r"(?:^|\n)#\+LAYOUT_NEXT(?:$|\n)").unwrap(), + Regex::new(r"(?:^|\n)#\+LAYOUT_END(?:$|\n)").unwrap(), + ], + layouts, + } + } +} + +lazy_static! { + static ref STATE_NAME: String = "elements.layout".to_string(); +} + +impl RegexRule for LayoutRule { + fn name(&self) -> &'static str { "Layout" } + + fn regexes(&self) -> &[regex::Regex] { &self.re } + + fn on_regex_match( + &self, + index: usize, + parser: &dyn Parser, + document: &dyn Document, + token: Token, + matches: Captures, + ) -> Vec, Range)>> { + let mut reports = vec![]; + + let query = parser.state().query(&STATE_NAME); + let state = match query { + Some(state) => state, + None => { + // Insert as a new state + match parser.state_mut().insert( + STATE_NAME.clone(), + Rc::new(RefCell::new(LayoutState { stack: vec![] })), + ) { + Err(_) => panic!("Unknown error"), + Ok(state) => state, + } + } + }; + + if index == 0 + // BEGIN_LAYOUT + { + match matches.get(1) { + None => { + reports.push( + Report::build(ReportKind::Error, token.source(), token.start()) + .with_message("Missing Layout Name") + .with_label( + Label::new((token.source(), token.range.clone())) + .with_message(format!( + "Missing layout name after `{}`", + "#+BEGIN_LAYOUT".fg(parser.colors().highlight) + )) + .with_color(parser.colors().error), + ) + .finish(), + ); + return reports; + } + Some(name) => { + let trimmed = name.as_str().trim_start().trim_end(); + if name.as_str().is_empty() || trimmed.is_empty() + // Empty name + { + reports.push( + Report::build(ReportKind::Error, token.source(), name.start()) + .with_message("Empty Layout Name") + .with_label( + Label::new((token.source(), token.range.clone())) + .with_message(format!( + "Empty layout name after `{}`", + "#+BEGIN_LAYOUT".fg(parser.colors().highlight) + )) + .with_color(parser.colors().error), + ) + .finish(), + ); + return reports; + } else if !name.as_str().chars().next().unwrap().is_whitespace() + // Missing space + { + reports.push( + Report::build(ReportKind::Error, token.source(), name.start()) + .with_message("Empty Layout Name") + .with_label( + Label::new((token.source(), name.range())) + .with_message(format!( + "Missing a space before layout `{}`", + name.as_str().fg(parser.colors().highlight) + )) + .with_color(parser.colors().error), + ) + .finish(), + ); + return reports; + } + + // Get layout + let layout_type = match self.layouts.get(trimmed) { + None => { + reports.push( + Report::build(ReportKind::Error, token.source(), name.start()) + .with_message("Unknown Layout") + .with_label( + Label::new((token.source(), name.range())) + .with_message(format!( + "Cannot find layout `{}`", + trimmed.fg(parser.colors().highlight) + )) + .with_color(parser.colors().error), + ) + .finish(), + ); + return reports; + } + Some(layout_type) => layout_type, + }; + + parser.push( + document, + Box::new(Layout { + location: token.clone(), + layout: layout_type.clone(), + id: 0, + token: LayoutToken::BEGIN, + }), + ); + + state + .borrow_mut() + .downcast_mut::() + .map_or_else( + || panic!("Invalid state at: `{}`", STATE_NAME.as_str()), + |s| s.stack.push((vec![token.clone()], layout_type.clone())), + ); + } + }; + return reports; + } + + let (id, token_type, layout_type) = if index == 1 + // LAYOUT_NEXT + { + let mut state_borrow = state.borrow_mut(); + let state = state_borrow.downcast_mut::().unwrap(); + + let (tokens, layout_type) = match state.stack.last_mut() { + None => { + reports.push( + Report::build(ReportKind::Error, token.source(), token.start()) + .with_message("Invalid #+LAYOUT_NEXT") + .with_label( + Label::new((token.source(), token.range.clone())) + .with_message("No active layout found".to_string()) + .with_color(parser.colors().error), + ) + .finish(), + ); + return reports; + } + Some(last) => last, + }; + + if layout_type.expects().end >= tokens.len() + // Too many blocks + { + reports.push( + Report::build(ReportKind::Error, token.source(), token.start()) + .with_message("Unexpected #+LAYOUT_NEXT") + .with_label( + Label::new((token.source(), token.range.clone())) + .with_message(format!( + "Layout expects a maximum of {} blocks, currently at {}", + layout_type.expects().end.fg(parser.colors().info), + tokens.len().fg(parser.colors().info), + )) + .with_color(parser.colors().error), + ) + .finish(), + ); + return reports; + } + + tokens.push(token.clone()); + (tokens.len() - 1, LayoutToken::NEXT, layout_type.clone()) + } else { + // LAYOUT_END + let mut state_borrow = state.borrow_mut(); + let state = state_borrow.downcast_mut::().unwrap(); + + let (tokens, layout_type) = match state.stack.last_mut() { + None => { + reports.push( + Report::build(ReportKind::Error, token.source(), token.start()) + .with_message("Invalid #+LAYOUT_END") + .with_label( + Label::new((token.source(), token.range.clone())) + .with_message("No active layout found".to_string()) + .with_color(parser.colors().error), + ) + .finish(), + ); + return reports; + } + Some(last) => last, + }; + + if layout_type.expects().start < tokens.len() + // Not enough blocks + { + reports.push( + Report::build(ReportKind::Error, token.source(), token.start()) + .with_message("Unexpected #+LAYOUT_END") + .with_label( + Label::new((token.source(), token.range.clone())) + .with_message(format!( + "Layout expects a minimum of {} blocks, currently at {}", + layout_type.expects().start.fg(parser.colors().info), + tokens.len().fg(parser.colors().info), + )) + .with_color(parser.colors().error), + ) + .finish(), + ); + return reports; + } + + let layout_type = layout_type.clone(); + let id = tokens.len(); + state.stack.pop(); + (id, LayoutToken::END, layout_type) + }; + + parser.push( + document, + Box::new(Layout { + location: token, + layout: layout_type, + id, + token: token_type, + }), + ); + + return reports; + } + + // TODO + fn lua_bindings<'lua>(&self, _lua: &'lua Lua) -> Option)>> { None } +} diff --git a/src/elements/mod.rs b/src/elements/mod.rs index 75a200d..9128fc6 100644 --- a/src/elements/mod.rs +++ b/src/elements/mod.rs @@ -1,17 +1,18 @@ -pub mod registrar; -pub mod text; -pub mod comment; -pub mod paragraph; -pub mod variable; -pub mod import; -pub mod script; -pub mod list; -pub mod style; -pub mod section; -pub mod link; pub mod code; -pub mod tex; +pub mod comment; pub mod graphviz; -pub mod raw; +pub mod import; +pub mod layout; +pub mod link; +pub mod list; pub mod media; +pub mod paragraph; +pub mod raw; pub mod reference; +pub mod registrar; +pub mod script; +pub mod section; +pub mod style; +pub mod tex; +pub mod text; +pub mod variable; diff --git a/src/elements/raw.rs b/src/elements/raw.rs index 2af1c11..32ba94f 100644 --- a/src/elements/raw.rs +++ b/src/elements/raw.rs @@ -33,16 +33,6 @@ struct Raw { pub(self) content: String, } -impl Raw { - fn new(location: Token, kind: ElemKind, content: String) -> Self { - Self { - location, - kind, - content, - } - } -} - impl Element for Raw { fn location(&self) -> &Token { &self.location } fn kind(&self) -> ElemKind { self.kind.clone() } diff --git a/src/elements/registrar.rs b/src/elements/registrar.rs index 7e74512..5b08177 100644 --- a/src/elements/registrar.rs +++ b/src/elements/registrar.rs @@ -4,6 +4,7 @@ use super::code::CodeRule; use super::comment::CommentRule; use super::graphviz::GraphRule; use super::import::ImportRule; +use super::layout::LayoutRule; use super::link::LinkRule; use super::list::ListRule; use super::media::MediaRule; @@ -31,6 +32,7 @@ pub fn register(parser: &mut P) { parser.add_rule(Box::new(TexRule::new()), None).unwrap(); parser.add_rule(Box::new(GraphRule::new()), None).unwrap(); parser.add_rule(Box::new(MediaRule::new()), None).unwrap(); + parser.add_rule(Box::new(LayoutRule::new()), None).unwrap(); parser.add_rule(Box::new(StyleRule::new()), None).unwrap(); parser.add_rule(Box::new(SectionRule::new()), None).unwrap(); diff --git a/src/elements/style.rs b/src/elements/style.rs index 2657403..47508ab 100644 --- a/src/elements/style.rs +++ b/src/elements/style.rs @@ -1,10 +1,27 @@ -use mlua::{Function, Lua}; -use regex::{Captures, Regex}; -use crate::{compiler::compiler::{Compiler, Target}, document::{document::{DocumentAccessors, Document}, element::{ElemKind, Element}}, parser::{parser::Parser, rule::RegexRule, source::{Source, Token}, state::State}}; -use ariadne::{Fmt, Label, Report, ReportKind}; +use crate::compiler::compiler::Compiler; +use crate::compiler::compiler::Target; +use crate::document::document::Document; +use crate::document::document::DocumentAccessors; +use crate::document::element::ElemKind; +use crate::document::element::Element; +use crate::parser::parser::Parser; +use crate::parser::rule::RegexRule; +use crate::parser::source::Source; +use crate::parser::source::Token; use crate::parser::state::Scope; -use std::{cell::RefCell, ops::Range, rc::Rc}; +use crate::parser::state::State; +use ariadne::Fmt; +use ariadne::Label; +use ariadne::Report; +use ariadne::ReportKind; use lazy_static::lazy_static; +use mlua::Function; +use mlua::Lua; +use regex::Captures; +use regex::Regex; +use std::cell::RefCell; +use std::ops::Range; +use std::rc::Rc; use super::paragraph::Paragraph; @@ -15,101 +32,118 @@ pub struct Style { close: bool, } -impl Style -{ +impl Style { pub fn new(location: Token, kind: usize, close: bool) -> Self { - Self { location, kind, close } + Self { + location, + kind, + close, + } } } -impl Element for Style -{ +impl Element for Style { fn location(&self) -> &Token { &self.location } fn kind(&self) -> ElemKind { ElemKind::Inline } fn element_name(&self) -> &'static str { "Section" } fn to_string(&self) -> String { format!("{self:#?}") } fn compile(&self, compiler: &Compiler, _document: &dyn Document) -> Result { - match compiler.target() - { + match compiler.target() { Target::HTML => { Ok([ // Bold - "", "", - // Italic - "", "", - // Underline - "", "", - // Code + "", "", // Italic + "", "", // Underline + "", "", // Code "", "", - ][self.kind*2 + self.close as usize].to_string()) + ][self.kind * 2 + self.close as usize] + .to_string()) } - Target::LATEX => Err("Unimplemented compiler".to_string()) + Target::LATEX => Err("Unimplemented compiler".to_string()), } } } -struct StyleState -{ - toggled: [Option; 4] +struct StyleState { + toggled: [Option; 4], } impl StyleState { - const NAMES : [&'static str; 4] = ["Bold", "Italic", "Underline", "Code"]; + const NAMES: [&'static str; 4] = ["Bold", "Italic", "Underline", "Code"]; fn new() -> Self { - Self { toggled: [None, None, None, None] } + Self { + toggled: [None, None, None, None], + } } } -impl State for StyleState -{ +impl State for StyleState { fn scope(&self) -> Scope { Scope::PARAGRAPH } - fn on_remove<'a>(&self, parser: &dyn Parser, document: &dyn Document) -> Vec, Range)>> { - let mut result = Vec::new(); + fn on_remove<'a>( + &self, + parser: &dyn Parser, + document: &dyn Document, + ) -> Vec, Range)>> { + let mut reports = vec![]; + self.toggled .iter() .zip(StyleState::NAMES) - .for_each(|(token, name)| - { - if token.is_none() { return } // Style not enabled - let token = token.as_ref().unwrap(); + .for_each(|(token, name)| { + if token.is_none() { + return; + } // Style not enabled + let token = token.as_ref().unwrap(); - //let range = range.as_ref().unwrap(); + //let range = range.as_ref().unwrap(); - //let active_range = range.start .. paragraph.location().end()-1; + //let active_range = range.start .. paragraph.location().end()-1; - let paragraph = document.last_element::().unwrap(); - let paragraph_end = paragraph.content.last() - .and_then(|last| Some((last.location().source(), last.location().end()-1 .. last.location().end()))) - .unwrap(); + let paragraph = document.last_element::().unwrap(); + let paragraph_end = paragraph + .content + .last() + .and_then(|last| { + Some(( + last.location().source(), + last.location().end() - 1..last.location().end(), + )) + }) + .unwrap(); - // TODO: Allow style to span multiple documents if they don't break paragraph. - result.push( - Report::build(ReportKind::Error, token.source(), token.start()) - .with_message("Unterminated style") - //.with_label( - // Label::new((document.source(), active_range.clone())) - // .with_order(0) - // .with_message(format!("Style {} is not terminated before the end of paragraph", - // name.fg(parser.colors().info))) - // .with_color(parser.colors().error)) - .with_label( - Label::new((token.source(), token.range.clone())) - .with_order(1) - .with_message(format!("Style {} starts here", - name.fg(parser.colors().info))) - .with_color(parser.colors().info)) - .with_label( - Label::new(paragraph_end) - .with_order(1) - .with_message(format!("Paragraph ends here")) - .with_color(parser.colors().info)) - .with_note("Styles cannot span multiple documents (i.e @import)") - .finish()); - }); + // TODO: Allow style to span multiple documents if they don't break paragraph. + reports.push( + Report::build(ReportKind::Error, token.source(), token.start()) + .with_message("Unterminated style") + //.with_label( + // Label::new((document.source(), active_range.clone())) + // .with_order(0) + // .with_message(format!("Style {} is not terminated before the end of paragraph", + // name.fg(parser.colors().info))) + // .with_color(parser.colors().error)) + .with_label( + Label::new((token.source(), token.range.clone())) + .with_order(1) + .with_message(format!( + "Style {} starts here", + name.fg(parser.colors().info) + )) + .with_color(parser.colors().info), + ) + .with_label( + Label::new(paragraph_end) + .with_order(1) + .with_message(format!("Paragraph ends here")) + .with_color(parser.colors().info), + ) + .with_note("Styles cannot span multiple documents (i.e @import)") + .finish(), + ); + }); - return result; + return reports; } } @@ -128,31 +162,37 @@ impl StyleRule { // Underline Regex::new(r"__").unwrap(), // Code - Regex::new(r"`").unwrap() - ] + Regex::new(r"`").unwrap(), + ], } } } lazy_static! { - static ref STATE_NAME : String = "elements.style".to_string(); + static ref STATE_NAME: String = "elements.style".to_string(); } -impl RegexRule for StyleRule -{ +impl RegexRule for StyleRule { fn name(&self) -> &'static str { "Style" } fn regexes(&self) -> &[regex::Regex] { &self.re } - fn on_regex_match(&self, index: usize, parser: &dyn Parser, document: &dyn Document, token: Token, _matches: Captures) -> Vec, Range)>> { - let result = vec![]; - + fn on_regex_match( + &self, + index: usize, + parser: &dyn Parser, + document: &dyn Document, + token: Token, + _matches: Captures, + ) -> Vec, Range)>> { let query = parser.state().query(&STATE_NAME); - let state = match query - { + let state = match query { Some(state) => state, - None => { // Insert as a new state - match parser.state_mut().insert(STATE_NAME.clone(), Rc::new(RefCell::new(StyleState::new()))) + None => { + // Insert as a new state + match parser + .state_mut() + .insert(STATE_NAME.clone(), Rc::new(RefCell::new(StyleState::new()))) { Err(_) => panic!("Unknown error"), Ok(state) => state, @@ -160,26 +200,23 @@ impl RegexRule for StyleRule } }; - if let Some(style_state) = state - .borrow_mut() - .as_any_mut() - .downcast_mut::() - { - style_state.toggled[index] = style_state.toggled[index].clone().map_or(Some(token.clone()), |_| None); - parser.push(document, Box::new( - Style::new( + if let Some(style_state) = state.borrow_mut().as_any_mut().downcast_mut::() { + style_state.toggled[index] = style_state.toggled[index] + .clone() + .map_or(Some(token.clone()), |_| None); + parser.push( + document, + Box::new(Style::new( token.clone(), index, - !style_state.toggled[index].is_some() - ) - )); - } - else - { + !style_state.toggled[index].is_some(), + )), + ); + } else { panic!("Invalid state at `{}`", STATE_NAME.as_str()); } - return result; + return vec![]; } // TODO diff --git a/src/elements/tex.rs b/src/elements/tex.rs index b1e80e5..e2a7708 100644 --- a/src/elements/tex.rs +++ b/src/elements/tex.rs @@ -189,7 +189,7 @@ impl Element for Tex { Tex::format_latex(&fontsize, &preamble, &format!("{prepend}{}", self.tex)) }; - let mut result = if let Some(mut con) = compiler.cache() { + let result = if let Some(mut con) = compiler.cache() { match latex.cached(&mut con, |s| s.latex_to_svg(&exec, &fontsize)) { Ok(s) => Ok(s), Err(e) => match e { @@ -387,7 +387,7 @@ impl RegexRule for TexRule { ); return reports; } - PropertyMapError::NotFoundError(err) => { + PropertyMapError::NotFoundError(_) => { if index == 1 { TexKind::Inline } else { diff --git a/src/main.rs b/src/main.rs index f02bf01..ff5fb64 100644 --- a/src/main.rs +++ b/src/main.rs @@ -199,7 +199,7 @@ fn main() -> ExitCode { let input_meta = match std::fs::metadata(&input) { Ok(meta) => meta, Err(e) => { - eprintln!("Unable to get metadata for input: `{input}`"); + eprintln!("Unable to get metadata for input `{input}`: {e}"); return ExitCode::FAILURE; } }; @@ -218,7 +218,7 @@ fn main() -> ExitCode { match std::fs::metadata(&output) { Ok(_) => {} Err(e) => { - eprintln!("Unable to get metadata for output: `{output}`"); + eprintln!("Unable to get metadata for output `{output}`: {e}"); return ExitCode::FAILURE; } } @@ -226,7 +226,7 @@ fn main() -> ExitCode { let output_meta = match std::fs::metadata(&output) { Ok(meta) => meta, Err(e) => { - eprintln!("Unable to get metadata for output: `{output}`"); + eprintln!("Unable to get metadata for output `{output}`: {e}"); return ExitCode::FAILURE; } }; @@ -302,7 +302,7 @@ fn main() -> ExitCode { } } Err(e) => { - eprintln!("Faield to get metadata for `{entry:#?}`"); + eprintln!("Faield to get metadata for `{entry:#?}`: {e}"); return ExitCode::FAILURE; } }