1use regex::Regex;
2use std::{fmt, str::FromStr};
3
4use crate::{Error, Progression, Result};
5
6#[derive(Debug, PartialEq, Clone)]
11pub struct Song {
12 pub title: String,
15 pub composer: String,
18 pub style: String,
21 pub key_signature: String,
24 pub progression: Progression,
26}
27
28impl fmt::Display for Song {
29 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30 write!(
31 f,
32 "irealbook://{}={}={}={}=n={}",
33 self.title, self.composer, self.style, self.key_signature, self.progression
34 )
35 }
36}
37
38impl FromStr for Song {
39 type Err = Error;
40
41 fn from_str(s: &str) -> Result<Self> {
42 parse_irealbook_url(s)
43 }
44}
45
46pub fn parse_irealbook_url(url: &str) -> Result<Song> {
56 let re = Regex::new(r"irealbook://(.*?)=(.*?)=(.*?)=(.*?)=(.*?)=(.*)").unwrap();
57 let Some(captures) = re.captures(url) else {
58 return Err(Error::InvalidUrl);
59 };
60
61 let title = captures
62 .get(1)
63 .ok_or(Error::MissingField("Title"))?
64 .as_str()
65 .to_owned();
66 let composer = captures
67 .get(2)
68 .ok_or(Error::MissingField("Composer"))?
69 .as_str()
70 .to_owned();
71 let style = captures
72 .get(3)
73 .ok_or(Error::MissingField("Style"))?
74 .as_str()
75 .to_owned();
76 let key_signature = captures
77 .get(4)
78 .ok_or(Error::MissingField("Key signature"))?
79 .as_str()
80 .to_owned();
81 let progression = captures
82 .get(6)
83 .ok_or(Error::MissingField("Chord progression"))?
84 .as_str()
85 .parse()?;
86
87 Ok(Song {
88 title,
89 composer,
90 style,
91 key_signature,
92 progression,
93 })
94}