Skip to main content

music21/common/
misc.rs

1//! music21's `common.misc`: what fits nowhere else in `common`.
2
3/// The message music21 gives when optional packages are missing, naming
4/// them and where to read how to install them: music21's
5/// `getMissingImportStr`. Nothing is missing, nothing is said.
6///
7/// The lines after the first keep the indent music21's source gives them.
8///
9/// ```
10/// use music21::common::misc::missing_import_message;
11///
12/// let message = missing_import_message(&["matplotlib"]).unwrap();
13/// assert!(message.starts_with("Certain music21 functions might need the optional package matplotlib;"));
14/// assert_eq!(missing_import_message(&[]), None);
15/// ```
16pub fn missing_import_message(names: &[&str]) -> Option<String> {
17    const WHERE: &str = "https://www.music21.org/music21docs/installing/installAdditional.html";
18    match names {
19        [] => None,
20        [name] => Some(format!(
21            "Certain music21 functions might need the optional package {name};\n{}if you run \
22             into errors, install it by following the instructions at\n{}{WHERE}",
23            " ".repeat(18),
24            " ".repeat(18)
25        )),
26        _ => Some(format!(
27            "Certain music21 functions might need these optional packages: {};\n{}if you run \
28             into errors, install them by following the instructions at\n{}{WHERE}",
29            names.join(", "),
30            " ".repeat(19),
31            " ".repeat(19)
32        )),
33    }
34}
35
36/// The platform music21 says it runs on: `win`, `darwin` or `nix`, which
37/// is every other Unix: music21's `getPlatform`.
38///
39/// ```
40/// use music21::common::misc::platform_name;
41///
42/// assert!(["win", "darwin", "nix", "other"].contains(&platform_name()));
43/// ```
44pub fn platform_name() -> &'static str {
45    if cfg!(windows) {
46        return "win";
47    }
48    if cfg!(target_os = "macos") {
49        return "darwin";
50    }
51    if cfg!(unix) {
52        return "nix";
53    }
54    "other"
55}
56
57/// A note name with each flat written `b` written `-` instead, as music21
58/// spells it: music21's `cleanedFlatNotation`. Only a `b` after a note
59/// letter is a flat.
60///
61/// ```
62/// use music21::common::misc::cleaned_flat_notation;
63///
64/// assert_eq!(cleaned_flat_notation("Cb"), "C-");
65/// assert_eq!(cleaned_flat_notation("Ebb E bb"), "E-b E b-");
66/// ```
67pub fn cleaned_flat_notation(text: &str) -> String {
68    let characters: Vec<char> = text.chars().collect();
69    let mut cleaned = String::with_capacity(text.len());
70    let mut index = 0;
71    while index < characters.len() {
72        let letter = characters[index];
73        cleaned.push(letter);
74        let flat =
75            matches!(letter, 'A'..='G' | 'a'..='g') && characters.get(index + 1) == Some(&'b');
76        if flat {
77            cleaned.push('-');
78            index += 2;
79        } else {
80            index += 1;
81        }
82    }
83    cleaned
84}
85
86#[cfg(test)]
87mod tests {
88    use super::*;
89
90    #[test]
91    fn several_missing_packages_are_listed_together() {
92        let message = missing_import_message(&["matplotlib", "numpy"]).unwrap();
93        let lines: Vec<&str> = message.lines().collect();
94        assert_eq!(
95            lines[0],
96            "Certain music21 functions might need these optional packages: matplotlib, numpy;"
97        );
98        assert_eq!(
99            lines[1],
100            "                   if you run into errors, install them by following the \
101             instructions at"
102        );
103        assert_eq!(lines.len(), 3);
104    }
105
106    #[test]
107    fn a_flat_is_only_a_b_after_a_note_letter() {
108        assert_eq!(cleaned_flat_notation("Bbb"), "B-b");
109        assert_eq!(cleaned_flat_notation("Hb abc"), "Hb a-c");
110    }
111}