sound_to_patch/preset/
template.rs1use std::error::Error;
8use std::fmt;
9use std::io;
10use std::path::{Path, PathBuf};
11
12use crate::preset::format::PresetModel;
13use crate::preset::read_prefix;
14
15pub trait TemplateSource: Send + Sync {
17 fn help(&self) -> TemplateHelp;
19
20 fn candidates(&self) -> Vec<PathBuf>;
22
23 fn builtin(&self) -> Option<Vec<u8>>;
26
27 fn check(&self, model: &dyn PresetModel) -> Vec<TemplateCheck>;
29}
30
31#[derive(Clone, Debug, PartialEq, Eq)]
33pub struct TemplateHelp(pub String);
34
35impl fmt::Display for TemplateHelp {
36 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37 f.write_str(&self.0)
38 }
39}
40
41#[derive(Clone, Copy, Debug, PartialEq, Eq)]
43pub enum Severity {
44 Error,
46 Warning,
48}
49
50#[derive(Clone, Debug, PartialEq, Eq)]
52pub struct TemplateCheck {
53 pub severity: Severity,
55 pub text: String,
57 pub fix: String,
59}
60
61impl fmt::Display for TemplateCheck {
62 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63 let level = match self.severity {
64 Severity::Error => "error",
65 Severity::Warning => "warning",
66 };
67 write!(f, "{level}: {}. {}", self.text, self.fix)
68 }
69}
70
71#[derive(Clone, Debug, PartialEq, Eq)]
73pub enum Origin {
74 CommandLine(PathBuf),
76 Candidate(PathBuf),
78 Builtin,
80}
81
82impl fmt::Display for Origin {
83 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84 match self {
85 Self::CommandLine(path) => write!(f, "{}", path.display()),
86 Self::Candidate(path) => write!(f, "{} (found)", path.display()),
87 Self::Builtin => write!(f, "built-in default"),
88 }
89 }
90}
91
92#[derive(Clone, Debug, PartialEq, Eq)]
94pub struct Template {
95 pub bytes: Vec<u8>,
97 pub origin: Origin,
99}
100
101#[derive(Debug)]
103pub enum TemplateError {
104 Unreadable {
106 path: PathBuf,
108 cause: io::Error,
110 },
111 Missing {
113 tried: Vec<PathBuf>,
115 help: TemplateHelp,
117 },
118}
119
120impl fmt::Display for TemplateError {
121 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
122 match self {
123 Self::Unreadable { path, cause } => write!(
124 f,
125 "cannot read the template {}: {cause}. Check the path given with --template",
126 path.display()
127 ),
128 Self::Missing { tried, help } => {
129 if tried.is_empty() {
130 return write!(f, "no template found and no path was searched. {help}");
131 }
132 write!(f, "no template found; tried:")?;
133 for path in tried {
134 write!(f, "\n {}", path.display())?;
135 }
136 write!(f, "\n{help}")
137 }
138 }
139 }
140}
141
142impl Error for TemplateError {
143 fn source(&self) -> Option<&(dyn Error + 'static)> {
144 match self {
145 Self::Unreadable { cause, .. } => Some(cause),
146 Self::Missing { .. } => None,
147 }
148 }
149}
150
151pub fn locate_template(
161 source: &dyn TemplateSource,
162 cli_path: Option<&Path>,
163 max: usize,
164) -> Result<Template, TemplateError> {
165 let read = |path: &Path| {
166 let bytes = read_prefix(path, max)?;
167 if bytes.len() > max {
168 return Err(io::Error::new(
169 io::ErrorKind::InvalidData,
170 format!("the file is larger than the limit of {max} bytes"),
171 ));
172 }
173 Ok(bytes)
174 };
175 if let Some(path) = cli_path {
176 return read(path)
177 .map(|bytes| Template {
178 bytes,
179 origin: Origin::CommandLine(path.to_owned()),
180 })
181 .map_err(|cause| TemplateError::Unreadable {
182 path: path.to_owned(),
183 cause,
184 });
185 }
186 let candidates = source.candidates();
187 for path in &candidates {
188 if path.is_file()
189 && let Ok(bytes) = read(path)
190 {
191 return Ok(Template {
192 bytes,
193 origin: Origin::Candidate(path.clone()),
194 });
195 }
196 }
197 source
198 .builtin()
199 .map(|bytes| Template {
200 bytes,
201 origin: Origin::Builtin,
202 })
203 .ok_or_else(|| TemplateError::Missing {
204 tried: candidates,
205 help: source.help(),
206 })
207}