1use std::fmt::Display;
4use std::str::FromStr;
5
6use serde::{
7 Serialize,
8 ser::{SerializeMap, Serializer},
9};
10
11use crate::{Error, Positioning, SourceLocation};
12
13use super::location::{Location, Position};
14use super::metadata::BlockMetadata;
15use super::title::Title;
16
17#[derive(Clone, Debug, PartialEq)]
50pub enum Source {
51 Path(std::path::PathBuf),
53 Url(url::Url),
55 Name(String),
57}
58
59impl Source {
60 #[must_use]
65 pub fn get_filename(&self) -> Option<&str> {
66 match self {
67 Source::Path(path) => path.file_name().and_then(|os_str| os_str.to_str()),
68 Source::Url(url) => url
69 .path_segments()
70 .and_then(std::iter::Iterator::last)
71 .filter(|s| !s.is_empty()),
72 Source::Name(name) => Some(name.as_str()),
73 }
74 }
75}
76
77impl FromStr for Source {
78 type Err = Error;
79
80 fn from_str(value: &str) -> Result<Self, Self::Err> {
81 if value.starts_with("http://")
83 || value.starts_with("https://")
84 || value.starts_with("ftp://")
85 || value.starts_with("irc://")
86 || value.starts_with("mailto:")
87 {
88 url::Url::parse(value).map(Source::Url).map_err(|e| {
89 Error::Parse(
90 Box::new(SourceLocation {
91 file: None,
92 positioning: Positioning::Position(Position::default()),
93 }),
94 format!("invalid URL: {e}"),
95 )
96 })
97 } else if value.contains('/') || value.contains('\\') || value.contains('.') {
98 Ok(Source::Path(std::path::PathBuf::from(value)))
101 } else {
102 Ok(Source::Name(value.to_string()))
104 }
105 }
106}
107
108impl Display for Source {
109 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
110 match self {
111 Source::Path(path) => write!(f, "{}", path.display()),
112 Source::Url(url) => {
113 let url_str = url.as_str();
117 if url.path() == "/" && !url_str.ends_with("://") {
118 write!(f, "{}", url_str.trim_end_matches('/'))
119 } else {
120 write!(f, "{url}")
121 }
122 }
123 Source::Name(name) => write!(f, "{name}"),
124 }
125 }
126}
127
128impl Serialize for Source {
129 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
130 where
131 S: Serializer,
132 {
133 let mut state = serializer.serialize_map(None)?;
134 match self {
135 Source::Path(path) => {
136 state.serialize_entry("type", "path")?;
137 state.serialize_entry("value", &path.display().to_string())?;
138 }
139 Source::Url(url) => {
140 state.serialize_entry("type", "url")?;
141 state.serialize_entry("value", url.as_str())?;
142 }
143 Source::Name(name) => {
144 state.serialize_entry("type", "name")?;
145 state.serialize_entry("value", name)?;
146 }
147 }
148 state.end()
149 }
150}
151
152#[derive(Clone, Debug, PartialEq)]
154#[non_exhaustive]
155pub struct Audio {
156 pub title: Title,
157 pub source: Source,
158 pub metadata: BlockMetadata,
159 pub location: Location,
160}
161
162impl Audio {
163 #[must_use]
165 pub fn new(source: Source, location: Location) -> Self {
166 Self {
167 title: Title::default(),
168 source,
169 metadata: BlockMetadata::default(),
170 location,
171 }
172 }
173
174 #[must_use]
176 pub fn with_title(mut self, title: Title) -> Self {
177 self.title = title;
178 self
179 }
180
181 #[must_use]
183 pub fn with_metadata(mut self, metadata: BlockMetadata) -> Self {
184 self.metadata = metadata;
185 self
186 }
187}
188
189#[derive(Clone, Debug, PartialEq)]
191#[non_exhaustive]
192pub struct Video {
193 pub title: Title,
194 pub sources: Vec<Source>,
195 pub metadata: BlockMetadata,
196 pub location: Location,
197}
198
199impl Video {
200 #[must_use]
202 pub fn new(sources: Vec<Source>, location: Location) -> Self {
203 Self {
204 title: Title::default(),
205 sources,
206 metadata: BlockMetadata::default(),
207 location,
208 }
209 }
210
211 #[must_use]
213 pub fn with_title(mut self, title: Title) -> Self {
214 self.title = title;
215 self
216 }
217
218 #[must_use]
220 pub fn with_metadata(mut self, metadata: BlockMetadata) -> Self {
221 self.metadata = metadata;
222 self
223 }
224}
225
226#[derive(Clone, Debug, PartialEq)]
228#[non_exhaustive]
229pub struct Image {
230 pub title: Title,
231 pub source: Source,
232 pub metadata: BlockMetadata,
233 pub location: Location,
234}
235
236impl Image {
237 #[must_use]
239 pub fn new(source: Source, location: Location) -> Self {
240 Self {
241 title: Title::default(),
242 source,
243 metadata: BlockMetadata::default(),
244 location,
245 }
246 }
247
248 #[must_use]
250 pub fn with_title(mut self, title: Title) -> Self {
251 self.title = title;
252 self
253 }
254
255 #[must_use]
257 pub fn with_metadata(mut self, metadata: BlockMetadata) -> Self {
258 self.metadata = metadata;
259 self
260 }
261}
262
263impl Serialize for Audio {
264 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
265 where
266 S: Serializer,
267 {
268 let mut state = serializer.serialize_map(None)?;
269 state.serialize_entry("name", "audio")?;
270 state.serialize_entry("type", "block")?;
271 state.serialize_entry("form", "macro")?;
272 if !self.metadata.is_default() {
273 state.serialize_entry("metadata", &self.metadata)?;
274 }
275 if !self.title.is_empty() {
276 state.serialize_entry("title", &self.title)?;
277 }
278 state.serialize_entry("source", &self.source)?;
279 state.serialize_entry("location", &self.location)?;
280 state.end()
281 }
282}
283
284impl Serialize for Image {
285 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
286 where
287 S: Serializer,
288 {
289 let mut state = serializer.serialize_map(None)?;
290 state.serialize_entry("name", "image")?;
291 state.serialize_entry("type", "block")?;
292 state.serialize_entry("form", "macro")?;
293 if !self.metadata.is_default() {
294 state.serialize_entry("metadata", &self.metadata)?;
295 }
296 if !self.title.is_empty() {
297 state.serialize_entry("title", &self.title)?;
298 }
299 state.serialize_entry("source", &self.source)?;
300 state.serialize_entry("location", &self.location)?;
301 state.end()
302 }
303}
304
305impl Serialize for Video {
306 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
307 where
308 S: Serializer,
309 {
310 let mut state = serializer.serialize_map(None)?;
311 state.serialize_entry("name", "video")?;
312 state.serialize_entry("type", "block")?;
313 state.serialize_entry("form", "macro")?;
314 if !self.metadata.is_default() {
315 state.serialize_entry("metadata", &self.metadata)?;
316 }
317 if !self.title.is_empty() {
318 state.serialize_entry("title", &self.title)?;
319 }
320 if !self.sources.is_empty() {
321 state.serialize_entry("sources", &self.sources)?;
322 }
323 state.serialize_entry("location", &self.location)?;
324 state.end()
325 }
326}