mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-06 02:21:45 +00:00
85 lines
3.9 KiB
Rust
85 lines
3.9 KiB
Rust
/// What can go wrong when printing.
|
||
///
|
||
/// The three things a *person* can be wrong about — there is nothing to print, a page number past
|
||
/// the end, a range that is not a page — each have their own variant with the numbers apart from
|
||
/// the words, so an interface can say them in its own language with [`PrintError::said`]. The rest
|
||
/// are faults rather than mistakes and carry their text as it is.
|
||
#[derive(Debug, thiserror::Error, PartialEq)]
|
||
pub enum PrintError {
|
||
#[error("there are no pages to print")]
|
||
NoPages,
|
||
/// A page number past the end of the document: the number asked for, and the page count.
|
||
#[error("page {0} is out of range (1–{1})")]
|
||
OutOfRange(usize, usize),
|
||
/// A range token that is neither a page number nor a page label.
|
||
#[error("{0:?} is not a page number or label")]
|
||
NotAPage(String),
|
||
#[error("{0}")]
|
||
Invalid(String),
|
||
#[error("{0}")]
|
||
Spool(String),
|
||
#[error(transparent)]
|
||
Cos(#[from] pdfcraft_cos::CosError),
|
||
}
|
||
|
||
/// How an interface says one of our wordings in its own language, holes and all: given the English
|
||
/// in [`WORDINGS`], the same wording in that language. [`in_english`] keeps the English.
|
||
pub type Words<'a> = &'a dyn Fn(&str) -> String;
|
||
|
||
/// The [`Words`] that translate nothing: the wording as it is written here.
|
||
pub fn in_english(wording: &str) -> String {
|
||
wording.to_owned()
|
||
}
|
||
|
||
/// What a person can get wrong about what to print, in English, with their holes still in them.
|
||
///
|
||
/// An interface that reports these in another language translates these three, which is the whole
|
||
/// reason they are gathered here; `wordings_are_said` keeps them in step with the errors.
|
||
pub const WORDINGS: &[&str] = &["there are no pages to print", "page {n} is out of range (1–{count})", "{page} is not a page number or label"];
|
||
|
||
impl PrintError {
|
||
/// This error as one sentence, said in the language `words` translates into.
|
||
///
|
||
/// The wording is translated and the numbers go in afterwards, in a single pass, so what the
|
||
/// person typed cannot be read as a hole. With [`in_english`] this is the error's own
|
||
/// `Display`, which is what the command line and scripts report.
|
||
pub fn said(&self, words: Words) -> String {
|
||
match self {
|
||
PrintError::NoPages => words("there are no pages to print"),
|
||
PrintError::OutOfRange(n, count) => {
|
||
say(&words("page {n} is out of range (1–{count})"), &[("{n}", n.to_string()), ("{count}", count.to_string())])
|
||
}
|
||
// The token goes in quoted, the way it is typed, as the error itself prints it.
|
||
PrintError::NotAPage(token) => say(&words("{page} is not a page number or label"), &[("{page}", format!("{token:?}"))]),
|
||
fault => fault.to_string(),
|
||
}
|
||
}
|
||
}
|
||
|
||
/// A wording with its holes filled.
|
||
///
|
||
/// A hole is a name in braces, such as `{n}`; what is not in `values` is left as it is written.
|
||
/// Values go in during a single pass, so a value with braces in it is never read as a hole.
|
||
fn say(wording: &str, values: &[(&str, String)]) -> String {
|
||
let mut out = String::with_capacity(wording.len() + 8);
|
||
let mut rest = wording;
|
||
while let Some(open) = rest.find('{') {
|
||
let (before, from_brace) = rest.split_at(open);
|
||
out.push_str(before);
|
||
// `find` counts from the brace and `}` is one byte, so this is a char boundary.
|
||
let Some(close) = from_brace.find('}') else {
|
||
// A brace with nothing closing it is just text.
|
||
out.push_str(from_brace);
|
||
return out;
|
||
};
|
||
let Some(hole) = from_brace.get(..=close) else { return out + from_brace };
|
||
match values.iter().find(|(name, _)| *name == hole) {
|
||
Some((_, value)) => out.push_str(value),
|
||
None => out.push_str(hole),
|
||
}
|
||
rest = from_brace.get(close + 1..).unwrap_or_default();
|
||
}
|
||
out.push_str(rest);
|
||
out
|
||
}
|