Skip to main content

music21/metadata/
primitives.rs

1//! The values a score's metadata is made of: music21's
2//! `metadata.primitives`.
3//!
4//! A metadata value is not only a string. A title has a language and may be
5//! a translation; a contributor has a role; and a date may be approximate,
6//! uncertain, a date before or after which something happened, a span
7//! between two dates, or a choice among several. These are the types that
8//! say so.
9//!
10//! **A date is music21's own, not a calendar date.** Any part of it may be
11//! missing -- a year alone, or a year and a month -- and any part may be
12//! marked approximate or uncertain, which is why music21 wrote a type of its
13//! own rather than using Python's `datetime`. A date in the distant past is
14//! as good as a recent one; what is not modelled is a date before the common
15//! era, which music21 does not model either, nor milliseconds, which it
16//! drops on purpose ("this is a tool for musicology and not for file
17//! timestamps").
18
19use std::fmt;
20
21use crate::{
22    defaults::IntegerType,
23    error::{Error, Result},
24};
25
26/// How sure one part of a [`Date`] is: music21's error strings, written as
27/// the symbols it writes them with.
28#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
29#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
30pub enum Certainty {
31    /// Near enough: music21's `approximate`, written `~`.
32    Approximate,
33    /// Not known to be right: music21's `uncertain`, written `?`.
34    Uncertain,
35    /// music21's `priority`, which its own `_stripError` reads off the
36    /// bracket symbols `<`, `{`, `>` and `}`. It has no symbol of its own,
37    /// and nothing of music21's writes one.
38    Priority,
39}
40
41impl Certainty {
42    /// The name music21 gives it.
43    #[must_use]
44    pub fn name(self) -> &'static str {
45        match self {
46            Self::Approximate => "approximate",
47            Self::Uncertain => "uncertain",
48            Self::Priority => "priority",
49        }
50    }
51
52    /// The symbol music21 writes after the part it marks, and nothing for
53    /// `priority`: music21's `errorToSymbol`.
54    #[must_use]
55    pub fn symbol(self) -> Option<char> {
56        match self {
57            Self::Approximate => Some('~'),
58            Self::Uncertain => Some('?'),
59            Self::Priority => None,
60        }
61    }
62
63    /// What a name or a symbol stands for, read as music21's
64    /// `errorToSymbol` and `_stripError` read one.
65    #[must_use]
66    pub fn named(name: &str) -> Option<Self> {
67        match name.to_lowercase().as_str() {
68            "approximate" | "~" | "x" => Some(Self::Approximate),
69            "uncertain" | "?" | "z" => Some(Self::Uncertain),
70            "priority" | "<" | "{" | ">" | "}" => Some(Self::Priority),
71            _ => None,
72        }
73    }
74
75    /// The certainty a symbol inside a written number marks, as music21's
76    /// `_stripError` reads the first one it meets.
77    fn of_symbol(symbol: char) -> Option<Self> {
78        match symbol {
79            '~' | 'x' => Some(Self::Approximate),
80            '?' | 'z' => Some(Self::Uncertain),
81            '<' | '{' | '>' | '}' => Some(Self::Priority),
82            _ => None,
83        }
84    }
85}
86
87/// One part of a [`Date`], in the order music21 writes them.
88#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
89#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
90pub enum DatePart {
91    /// The year.
92    Year,
93    /// The month, one to twelve.
94    Month,
95    /// The day of the month.
96    Day,
97    /// The hour, nought to twenty-three.
98    Hour,
99    /// The minute.
100    Minute,
101    /// The second.
102    Second,
103}
104
105impl DatePart {
106    /// Every part, in the order music21 writes them: music21's `attrNames`.
107    pub const ALL: [DatePart; 6] = [
108        Self::Year,
109        Self::Month,
110        Self::Day,
111        Self::Hour,
112        Self::Minute,
113        Self::Second,
114    ];
115
116    /// Where the part stands among the six.
117    fn at(self) -> usize {
118        match self {
119            Self::Year => 0,
120            Self::Month => 1,
121            Self::Day => 2,
122            Self::Hour => 3,
123            Self::Minute => 4,
124            Self::Second => 5,
125        }
126    }
127
128    /// The name music21 gives it.
129    #[must_use]
130    pub fn name(self) -> &'static str {
131        match self {
132            Self::Year => "year",
133            Self::Month => "month",
134            Self::Day => "day",
135            Self::Hour => "hour",
136            Self::Minute => "minute",
137            Self::Second => "second",
138        }
139    }
140}
141
142/// A date as music21 keeps one: music21's `metadata.Date`. Any part may be
143/// missing, and any part may say how sure it is.
144///
145/// ```
146/// use music21::metadata::primitives::{Certainty, Date, DatePart};
147///
148/// let read = Date::from_written("1030?/12~/?4")?;
149/// assert_eq!(read.value(DatePart::Year), Some(1030));
150/// assert_eq!(read.certainty(DatePart::Year), Some(Certainty::Uncertain));
151/// assert_eq!(read.to_string(), "1030?/12~/04?");
152/// # Ok::<(), music21::Error>(())
153/// ```
154#[derive(Clone, Debug, Default, Eq)]
155#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
156#[must_use]
157pub struct Date {
158    values: [Option<IntegerType>; 6],
159    certainties: [Option<Certainty>; 6],
160}
161
162/// Two dates are equal where they are written alike, which is music21's own
163/// `__eq__` -- it compares the two as strings.
164impl PartialEq for Date {
165    fn eq(&self, other: &Self) -> bool {
166        self.to_string() == other.to_string()
167    }
168}
169
170impl std::hash::Hash for Date {
171    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
172        self.to_string().hash(state);
173    }
174}
175
176impl Date {
177    /// A date saying nothing at all.
178    pub fn new() -> Self {
179        Self::default()
180    }
181
182    /// The value of one part, where it has one.
183    pub fn value(&self, part: DatePart) -> Option<IntegerType> {
184        self.values[part.at()]
185    }
186
187    /// How sure one part is, where it says.
188    pub fn certainty(&self, part: DatePart) -> Option<Certainty> {
189        self.certainties[part.at()]
190    }
191
192    /// The year, where the date gives one.
193    pub fn year(&self) -> Option<IntegerType> {
194        self.value(DatePart::Year)
195    }
196
197    /// The month, where the date gives one.
198    pub fn month(&self) -> Option<IntegerType> {
199        self.value(DatePart::Month)
200    }
201
202    /// The day, where the date gives one.
203    pub fn day(&self) -> Option<IntegerType> {
204        self.value(DatePart::Day)
205    }
206
207    /// Gives one part a value, with how sure it is.
208    ///
209    /// # Errors
210    ///
211    /// A value no date can have -- a thirteenth month, the thirty-first of
212    /// April, the twenty-ninth of February in a year that is not a leap
213    /// year. music21 checks the same, and leaves the value it was given in
214    /// place when it raises, which is why [`Self::set_unchecked`] is here
215    /// beside this.
216    pub fn set(
217        &mut self,
218        part: DatePart,
219        value: Option<IntegerType>,
220        certainty: Option<Certainty>,
221    ) -> Result<()> {
222        self.set_unchecked(part, value, certainty);
223        self.check()
224    }
225
226    /// Gives one part a value without asking whether a calendar has such a
227    /// date: music21 writes its own attributes freely and only checks when
228    /// a date is *loaded*, so a caller standing in for it writes this way
229    /// and checks afterwards.
230    pub fn set_unchecked(
231        &mut self,
232        part: DatePart,
233        value: Option<IntegerType>,
234        certainty: Option<Certainty>,
235    ) {
236        self.values[part.at()] = value;
237        if certainty.is_some() {
238            self.certainties[part.at()] = certainty;
239        }
240    }
241
242    /// The date written as music21 writes one: `year/month/day` with `--`
243    /// for a part it does not give, each part followed by its symbol, and
244    /// `/hour:minute:second` after it where any of those is given -- the
245    /// hour after a slash and the rest after colons, which is how
246    /// music21's own `__str__` joins them.
247    ///
248    /// This is [`fmt::Display`]; the method is here because every reading of
249    /// a date goes through it.
250    fn written(&self) -> String {
251        let has_time = DatePart::ALL[3..]
252            .iter()
253            .any(|part| self.value(*part).is_some());
254        let parts = if has_time { 6 } else { 3 };
255        let mut written: Vec<String> = Vec::with_capacity(parts);
256        for part in &DatePart::ALL[..parts] {
257            match self.value(*part) {
258                None => written.push("--".to_string()),
259                Some(value) => {
260                    let mut text = if *part == DatePart::Year {
261                        format!("{value:04}")
262                    } else {
263                        format!("{value:02}")
264                    };
265                    if let Some(symbol) = self.certainty(*part).and_then(Certainty::symbol) {
266                        text.push(symbol);
267                    }
268                    written.push(text);
269                }
270            }
271        }
272        // music21 joins the first four parts with slashes and the rest with
273        // colons, so the hour goes after a slash: `1030/12/04/04:50:32`.
274        let mut out = written[..written.len().min(4)].join("/");
275        if written.len() > 4 {
276            out.push(':');
277            out.push_str(&written[4..].join(":"));
278        }
279        out
280    }
281
282    /// Whether any part of the time of day is given: music21's `hasTime`.
283    pub fn has_time(&self) -> bool {
284        DatePart::ALL[3..]
285            .iter()
286            .any(|part| self.value(*part).is_some())
287    }
288
289    /// Whether any part says how sure it is: music21's `hasError`.
290    pub fn has_certainty(&self) -> bool {
291        self.certainties.iter().any(Option::is_some)
292    }
293
294    /// A date read from the way music21 writes one: music21's `loadStr`.
295    /// A part written `--` says nothing; a symbol anywhere in a part says
296    /// how sure it is. Colons are read as slashes, so
297    /// `1834/12/4/4:50:32` and `1834/12/4:4:50:32` are one date.
298    ///
299    /// # Errors
300    ///
301    /// A part that is not a number, or a value no date can have.
302    pub fn from_written(written: &str) -> Result<Self> {
303        let date = Self::from_written_unchecked(written)?;
304        date.check()?;
305        Ok(date)
306    }
307
308    /// A date read as [`Self::from_written`] reads one, without asking
309    /// whether a calendar has such a date. music21's own `load` keeps the
310    /// values it read and *then* raises, so a caller standing in for it
311    /// reads this way and calls [`Self::check`] afterwards.
312    ///
313    /// # Errors
314    ///
315    /// A part that is not a number.
316    pub fn from_written_unchecked(written: &str) -> Result<Self> {
317        let mut date = Self::new();
318        let written = written.replace(':', "/").replace(' ', "");
319        for (part, chunk) in DatePart::ALL.iter().zip(written.split('/')) {
320            if chunk == "--" {
321                continue;
322            }
323            let (value, certainty) = strip_certainty(chunk)?;
324            date.values[part.at()] = Some(value);
325            if certainty.is_some() {
326                date.certainties[part.at()] = certainty;
327            }
328        }
329        Ok(date)
330    }
331
332    /// A date of a year, and of a month and a day where they are given.
333    ///
334    /// # Errors
335    ///
336    /// A value no date can have.
337    pub fn from_parts(
338        year: Option<IntegerType>,
339        month: Option<IntegerType>,
340        day: Option<IntegerType>,
341    ) -> Result<Self> {
342        let mut date = Self::new();
343        date.values[0] = year;
344        date.values[1] = month;
345        date.values[2] = day;
346        date.check()?;
347        Ok(date)
348    }
349
350    /// The parts of another date that it gives, written over this one's,
351    /// with their certainties: music21's `loadOther`.
352    ///
353    /// # Errors
354    ///
355    /// A value no date can have.
356    pub fn take_from(&mut self, other: &Self) -> Result<()> {
357        for part in DatePart::ALL {
358            if let Some(value) = other.value(part) {
359                self.values[part.at()] = Some(value);
360                if let Some(certainty) = other.certainty(part) {
361                    self.certainties[part.at()] = Some(certainty);
362                }
363            }
364        }
365        self.check()
366    }
367
368    /// Whether a calendar has such a date: music21's `_sanityCheck`. It
369    /// does not check whether a Gregorian leap year really was one, "as it
370    /// changes historically", and neither does this.
371    ///
372    /// # Errors
373    ///
374    /// A value no date can have.
375    pub fn check(&self) -> Result<()> {
376        let (year, month, day) = (self.year(), self.month(), self.day());
377        if let Some(month) = month
378            && !(1..=12).contains(&month)
379        {
380            return Err(Error::Value(format!(
381                "Month must be between 1 and 12, not {month}."
382            )));
383        }
384        if let Some(day) = day {
385            let impossible = match month {
386                Some(4 | 6 | 9 | 11) => day == 31,
387                Some(2) => day > 29 || (day == 29 && year.is_some_and(|year| year % 4 != 0)),
388                _ => false,
389            };
390            if !(1..=31).contains(&day) || impossible {
391                let month = month.map_or_else(|| "None".to_string(), |month| month.to_string());
392                return Err(Error::Value(format!(
393                    "Day {day} is not possible with month {month}."
394                )));
395            }
396        }
397        if self.value(DatePart::Hour).is_some_and(|hour| hour > 23)
398            || self.value(DatePart::Hour).is_some_and(|hour| hour < 0)
399        {
400            return Err(Error::Value("Hour must be between 0 and 23".to_string()));
401        }
402        if self
403            .value(DatePart::Minute)
404            .is_some_and(|minute| !(0..=59).contains(&minute))
405        {
406            return Err(Error::Value("Minute must be between 0 and 59".to_string()));
407        }
408        if self
409            .value(DatePart::Second)
410            .is_some_and(|second| !(0..=59).contains(&second))
411        {
412            return Err(Error::Value("Second must be between 0 and 59".to_string()));
413        }
414        Ok(())
415    }
416}
417
418impl fmt::Display for Date {
419    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
420        f.write_str(&self.written())
421    }
422}
423
424/// A written number with its certainty taken off it: music21's
425/// `Date._stripError`. Only one symbol is expected, and the first one met is the
426/// one read. A fraction is read as the whole number before the point --
427/// music21 drops milliseconds on purpose.
428///
429/// # Errors
430///
431/// Something that is not a number at all.
432pub fn strip_certainty(written: &str) -> Result<(IntegerType, Option<Certainty>)> {
433    let mut certainty = None;
434    let mut cleaned = written.to_string();
435    for character in written.chars() {
436        if let Some(found) = Certainty::of_symbol(character) {
437            certainty = Some(found);
438            cleaned = written.replace(character, "");
439            break;
440        }
441    }
442    let read: f64 = cleaned
443        .parse()
444        .map_err(|_| Error::Value(format!("{written} is not a date")))?;
445    Ok((read as IntegerType, certainty))
446}
447
448/// What a [`DatePrimitive`] says about the dates it holds: music21's
449/// `relevance`, which is what tells its four date classes apart.
450#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
451#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
452pub enum Relevance {
453    /// The date is the date: music21's `DateSingle('...')`.
454    Certain,
455    /// Near enough.
456    Approximate,
457    /// Not known to be right.
458    Uncertain,
459    /// Something happened before this date: music21's
460    /// `DateRelative(..., 'prior')`, written `prior to ...`.
461    Prior,
462    /// After this date, written `after ...`.
463    After,
464    /// On this date or before it, written `... or earlier`.
465    OnOrBefore,
466    /// On this date or after it, written `... or later`.
467    OnOrAfter,
468    /// Between two dates: music21's `DateBetween`, written `... to ...`.
469    Between,
470    /// One of these dates: music21's `DateSelection`, written
471    /// `... or ...`.
472    Or,
473    /// All of these dates, written `... and ...`.
474    And,
475}
476
477impl Relevance {
478    /// The name music21 gives it.
479    #[must_use]
480    pub fn name(self) -> &'static str {
481        match self {
482            Self::Certain => "certain",
483            Self::Approximate => "approximate",
484            Self::Uncertain => "uncertain",
485            Self::Prior => "prior",
486            Self::After => "after",
487            Self::OnOrBefore => "onorbefore",
488            Self::OnOrAfter => "onorafter",
489            Self::Between => "between",
490            Self::Or => "or",
491            Self::And => "and",
492        }
493    }
494
495    /// What a name stands for, read without regard to case, with
496    /// music21's `DateRelative` reading of `before` as `prior`.
497    #[must_use]
498    pub fn named(name: &str) -> Option<Self> {
499        match name.to_lowercase().as_str() {
500            "certain" => Some(Self::Certain),
501            "approximate" => Some(Self::Approximate),
502            "uncertain" => Some(Self::Uncertain),
503            "prior" | "before" => Some(Self::Prior),
504            "after" => Some(Self::After),
505            "onorbefore" => Some(Self::OnOrBefore),
506            "onorafter" => Some(Self::OnOrAfter),
507            "between" => Some(Self::Between),
508            "or" => Some(Self::Or),
509            "and" => Some(Self::And),
510            _ => None,
511        }
512    }
513
514    /// Which of music21's four date classes keeps a date of this
515    /// relevance.
516    #[must_use]
517    pub fn kind(self) -> DateKind {
518        match self {
519            Self::Certain | Self::Approximate | Self::Uncertain => DateKind::Single,
520            Self::Prior | Self::After | Self::OnOrBefore | Self::OnOrAfter => DateKind::Relative,
521            Self::Between => DateKind::Between,
522            Self::Or | Self::And => DateKind::Selection,
523        }
524    }
525}
526
527/// Which of music21's four date classes a [`DatePrimitive`] is.
528#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
529#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
530pub enum DateKind {
531    /// music21's `DateSingle`: one date, certain, approximate or uncertain.
532    Single,
533    /// music21's `DateRelative`: a date something happened before or after.
534    Relative,
535    /// music21's `DateBetween`: a span between two dates.
536    Between,
537    /// music21's `DateSelection`: a choice among several dates.
538    Selection,
539}
540
541impl DateKind {
542    /// The name music21 gives the class.
543    #[must_use]
544    pub fn name(self) -> &'static str {
545        match self {
546            Self::Single => "DateSingle",
547            Self::Relative => "DateRelative",
548            Self::Between => "DateBetween",
549            Self::Selection => "DateSelection",
550        }
551    }
552
553    /// The relevance music21's class starts out with.
554    #[must_use]
555    pub fn default_relevance(self) -> Relevance {
556        match self {
557            Self::Single => Relevance::Certain,
558            Self::Relative => Relevance::After,
559            Self::Between => Relevance::Between,
560            Self::Selection => Relevance::Or,
561        }
562    }
563}
564
565/// One or more dates and what they say: music21's `DatePrimitive` and the
566/// four classes under it, which differ only in the relevances they take and
567/// in how they are written.
568///
569/// ```
570/// use music21::metadata::primitives::{DateKind, DatePrimitive, Relevance};
571///
572/// let span = DatePrimitive::between(["2009/12/31", "2010/1/28"])?;
573/// assert_eq!(span.to_string(), "2009/12/31 to 2010/01/28");
574/// assert_eq!(span.kind(), DateKind::Between);
575///
576/// let prior = DatePrimitive::relative("2009/12/31", Relevance::Prior)?;
577/// assert_eq!(prior.to_string(), "prior to 2009/12/31");
578/// # Ok::<(), music21::Error>(())
579/// ```
580#[derive(Clone, Debug, PartialEq, Eq, Hash)]
581#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
582#[must_use]
583pub struct DatePrimitive {
584    dates: Vec<Date>,
585    relevance: Relevance,
586}
587
588impl DatePrimitive {
589    /// The dates it holds.
590    pub fn dates(&self) -> &[Date] {
591        &self.dates
592    }
593
594    /// What they say.
595    pub fn relevance(&self) -> Relevance {
596        self.relevance
597    }
598
599    /// Which of music21's four classes it is.
600    pub fn kind(&self) -> DateKind {
601        self.relevance.kind()
602    }
603
604    /// Says something else about the dates it holds.
605    ///
606    /// # Errors
607    ///
608    /// A relevance one of music21's other date classes keeps: its own
609    /// setters refuse any that is not theirs, so `DateSingle` cannot be
610    /// made to say `between`.
611    pub fn set_relevance(&mut self, relevance: Relevance) -> Result<()> {
612        if relevance.kind() != self.kind() {
613            return Err(Error::Metadata(format!(
614                "Relevance value is not supported by this object: '{}'",
615                relevance.name()
616            )));
617        }
618        self.relevance = relevance;
619        Ok(())
620    }
621
622    /// One date: music21's `DateSingle`.
623    ///
624    /// # Errors
625    ///
626    /// A date that cannot be read, or a relevance `DateSingle` does not
627    /// take.
628    pub fn single(written: &str, relevance: Relevance) -> Result<Self> {
629        Self::of(
630            vec![Date::from_written(written)?],
631            relevance,
632            DateKind::Single,
633        )
634    }
635
636    /// A date something happened before or after: music21's
637    /// `DateRelative`.
638    ///
639    /// # Errors
640    ///
641    /// A date that cannot be read, or a relevance `DateRelative` does not
642    /// take.
643    pub fn relative(written: &str, relevance: Relevance) -> Result<Self> {
644        Self::of(
645            vec![Date::from_written(written)?],
646            relevance,
647            DateKind::Relative,
648        )
649    }
650
651    /// A span between dates: music21's `DateBetween`.
652    ///
653    /// # Errors
654    ///
655    /// A date that cannot be read.
656    pub fn between<I, S>(written: I) -> Result<Self>
657    where
658        I: IntoIterator<Item = S>,
659        S: AsRef<str>,
660    {
661        Self::of(read_dates(written)?, Relevance::Between, DateKind::Between)
662    }
663
664    /// A choice among dates: music21's `DateSelection`.
665    ///
666    /// # Errors
667    ///
668    /// A date that cannot be read, or a relevance `DateSelection` does not
669    /// take.
670    pub fn selection<I, S>(written: I, relevance: Relevance) -> Result<Self>
671    where
672        I: IntoIterator<Item = S>,
673        S: AsRef<str>,
674    {
675        Self::of(read_dates(written)?, relevance, DateKind::Selection)
676    }
677
678    /// Dates already read, said to be of a kind.
679    ///
680    /// # Errors
681    ///
682    /// A relevance that kind does not take.
683    pub fn of_dates(dates: Vec<Date>, relevance: Relevance, kind: DateKind) -> Result<Self> {
684        Self::of(dates, relevance, kind)
685    }
686
687    fn of(dates: Vec<Date>, relevance: Relevance, kind: DateKind) -> Result<Self> {
688        if relevance.kind() != kind {
689            return Err(Error::Metadata(format!(
690                "Relevance value is not supported by this object: '{}'",
691                relevance.name()
692            )));
693        }
694        Ok(Self { dates, relevance })
695    }
696}
697
698/// Every date of a list read as music21 reads one.
699fn read_dates<I, S>(written: I) -> Result<Vec<Date>>
700where
701    I: IntoIterator<Item = S>,
702    S: AsRef<str>,
703{
704    written
705        .into_iter()
706        .map(|one| Date::from_written(one.as_ref()))
707        .collect()
708}
709
710impl fmt::Display for DatePrimitive {
711    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
712        let written: Vec<String> = self.dates.iter().map(Date::to_string).collect();
713        match self.relevance {
714            Relevance::Certain | Relevance::Approximate | Relevance::Uncertain => {
715                f.write_str(written.first().map_or("", String::as_str))
716            }
717            Relevance::Prior => write!(f, "prior to {}", written.join(" ")),
718            Relevance::After => write!(f, "after {}", written.join(" ")),
719            Relevance::OnOrBefore => write!(f, "{} or earlier", written.join(" ")),
720            Relevance::OnOrAfter => write!(f, "{} or later", written.join(" ")),
721            Relevance::Between => f.write_str(&written.join(" to ")),
722            Relevance::Or => f.write_str(&written.join(" or ")),
723            Relevance::And => f.write_str(&written.join(" and ")),
724        }
725    }
726}
727
728/// One piece of text a score says about itself: music21's
729/// `metadata.Text`. It keeps the language it is written in, whether it is a
730/// translation, and the scheme it was encoded by.
731///
732/// ```
733/// use music21::metadata::primitives::Text;
734///
735/// let title = Text::new("concerto in d").in_language("en");
736/// assert_eq!(title.to_string(), "concerto in d");
737/// assert_eq!(title.language(), Some("en"));
738/// ```
739#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
740#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
741#[must_use]
742pub struct Text {
743    data: String,
744    language: Option<String>,
745    is_translated: Option<bool>,
746    encoding_scheme: Option<String>,
747    /// What music21's `Copyright` adds to a text: a role.
748    role: Option<String>,
749}
750
751impl Text {
752    /// Some text, saying nothing else about itself.
753    pub fn new(data: impl Into<String>) -> Self {
754        Self {
755            data: data.into(),
756            ..Self::default()
757        }
758    }
759
760    /// The text itself.
761    pub fn data(&self) -> &str {
762        &self.data
763    }
764
765    /// The language it is written in, where it says.
766    pub fn language(&self) -> Option<&str> {
767        self.language.as_deref()
768    }
769
770    /// Whether it is a translation, where it says. Nothing at all means
771    /// nobody has said.
772    pub fn is_translated(&self) -> Option<bool> {
773        self.is_translated
774    }
775
776    /// The scheme it was encoded by, where it says.
777    pub fn encoding_scheme(&self) -> Option<&str> {
778        self.encoding_scheme.as_deref()
779    }
780
781    /// The role it was given, which only music21's `Copyright` has.
782    pub fn role(&self) -> Option<&str> {
783        self.role.as_deref()
784    }
785
786    /// The same text, said to be in a language.
787    pub fn in_language(mut self, language: impl Into<String>) -> Self {
788        self.language = Some(language.into());
789        self
790    }
791
792    /// The same text, said to be a translation or not.
793    pub fn translated(mut self, is_translated: bool) -> Self {
794        self.is_translated = Some(is_translated);
795        self
796    }
797
798    /// The same text, said to be encoded by a scheme.
799    pub fn encoded_by(mut self, scheme: impl Into<String>) -> Self {
800        self.encoding_scheme = Some(scheme.into());
801        self
802    }
803
804    /// The same text, given a role: music21's `Copyright`.
805    pub fn in_role(mut self, role: impl Into<String>) -> Self {
806        self.role = Some(role.into());
807        self
808    }
809
810    /// Says what language it is in.
811    pub fn set_language(&mut self, language: Option<String>) {
812        self.language = language;
813    }
814
815    /// Says whether it is a translation.
816    pub fn set_translated(&mut self, is_translated: Option<bool>) {
817        self.is_translated = is_translated;
818    }
819
820    /// Says what scheme it was encoded by.
821    pub fn set_encoding_scheme(&mut self, scheme: Option<String>) {
822        self.encoding_scheme = scheme;
823    }
824
825    /// Gives it a role.
826    pub fn set_role(&mut self, role: Option<String>) {
827        self.role = role;
828    }
829
830    /// The text with the article put back at the front, where the language
831    /// is one whose articles are moved: music21's `getNormalizedArticle`,
832    /// which is `text::prepend_article`.
833    ///
834    /// # Errors
835    ///
836    /// A language whose articles are not known.
837    pub fn normalized_article(&self) -> Result<String> {
838        crate::text::prepend_article(&self.data, self.language.as_deref())
839    }
840}
841
842impl fmt::Display for Text {
843    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
844        f.write_str(&self.data)
845    }
846}
847
848/// Somebody who had a hand in a work: music21's `metadata.Contributor`. A
849/// composer, a lyricist, an arranger, or anybody else, under one or more
850/// names.
851///
852/// ```
853/// use music21::metadata::primitives::{Contributor, Text};
854///
855/// let composer = Contributor::new("composer", [Text::new("Beethoven, Ludwig van")]);
856/// assert_eq!(composer.role(), Some("composer"));
857/// assert_eq!(composer.to_string(), "composer:Beethoven, Ludwig van");
858/// ```
859#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
860#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
861#[must_use]
862pub struct Contributor {
863    role: Option<String>,
864    names: Vec<Text>,
865    birth: Option<DatePrimitive>,
866    death: Option<DatePrimitive>,
867}
868
869/// The ten roles Humdrum writes an abbreviation for: music21's
870/// `Contributor.roleAbbreviationsDict`.
871pub const ROLE_ABBREVIATIONS: [(&str, &str); 10] = [
872    ("com", "composer"),
873    ("coa", "attributedComposer"),
874    ("cos", "suspectedComposer"),
875    ("col", "composerAlias"),
876    ("coc", "composerCorporate"),
877    ("lyr", "lyricist"),
878    ("lib", "librettist"),
879    ("lar", "arranger"),
880    ("lor", "orchestrator"),
881    ("trn", "translator"),
882];
883
884impl Contributor {
885    /// The role an abbreviation stands for, read without regard to case:
886    /// music21's `abbreviationToRole`.
887    ///
888    /// # Errors
889    ///
890    /// An abbreviation no role is written with.
891    pub fn role_of_abbreviation(abbreviation: &str) -> Result<&'static str> {
892        let abbreviation = abbreviation.to_lowercase();
893        ROLE_ABBREVIATIONS
894            .iter()
895            .find(|(written, _)| *written == abbreviation)
896            .map(|(_, role)| *role)
897            .ok_or_else(|| Error::Metadata(format!("no such role: '{abbreviation}'")))
898    }
899
900    /// The abbreviation a role is written with, read without regard to
901    /// case: music21's `roleToAbbreviation`.
902    ///
903    /// # Errors
904    ///
905    /// A role no abbreviation is written for.
906    pub fn abbreviation_of_role(role: &str) -> Result<&'static str> {
907        ROLE_ABBREVIATIONS
908            .iter()
909            .find(|(_, known)| known.eq_ignore_ascii_case(role))
910            .map(|(written, _)| *written)
911            .ok_or_else(|| Error::Metadata(format!("No such role: {role}")))
912    }
913
914    /// Somebody in a role, under these names. An abbreviation is read as
915    /// the role it stands for.
916    pub fn new<I, T>(role: impl Into<String>, names: I) -> Self
917    where
918        I: IntoIterator<Item = T>,
919        T: Into<Text>,
920    {
921        let mut made = Self {
922            role: None,
923            names: names.into_iter().map(Into::into).collect(),
924            birth: None,
925            death: None,
926        };
927        made.set_role(Some(role.into()));
928        made
929    }
930
931    /// Somebody in no role at all, which is what music21 starts a
932    /// contributor out as.
933    pub fn unnamed() -> Self {
934        Self::default()
935    }
936
937    /// What they did, where it is known.
938    pub fn role(&self) -> Option<&str> {
939        self.role.as_deref()
940    }
941
942    /// Every name they go by, the first being the one music21 answers for
943    /// `name`.
944    pub fn names(&self) -> &[Text] {
945        &self.names
946    }
947
948    /// The name they go by.
949    pub fn name(&self) -> Option<&Text> {
950        self.names.first()
951    }
952
953    /// When they were born, where it is known.
954    pub fn birth(&self) -> Option<&DatePrimitive> {
955        self.birth.as_ref()
956    }
957
958    /// When they died, where it is known.
959    pub fn death(&self) -> Option<&DatePrimitive> {
960        self.death.as_ref()
961    }
962
963    /// Says what they did. An abbreviation is read as the role it stands
964    /// for, as music21's own setter reads one, so a contributor given
965    /// `com` is a composer.
966    pub fn set_role(&mut self, role: Option<String>) {
967        self.role = role
968            .map(|role| Self::role_of_abbreviation(&role).map_or(role, |found| found.to_string()));
969    }
970
971    /// Gives them one name, in place of any they had: music21's `name`
972    /// setter, which replaces the whole list.
973    pub fn set_name(&mut self, name: Text) {
974        self.names = vec![name];
975    }
976
977    /// Gives them every name they go by.
978    pub fn set_names(&mut self, names: Vec<Text>) {
979        self.names = names;
980    }
981
982    /// Says when they were born.
983    pub fn set_birth(&mut self, birth: Option<DatePrimitive>) {
984        self.birth = birth;
985    }
986
987    /// Says when they died.
988    pub fn set_death(&mut self, death: Option<DatePrimitive>) {
989        self.death = death;
990    }
991}
992
993/// Written as music21's `_reprInternal` writes one: the role, a colon, and
994/// every name they go by.
995impl fmt::Display for Contributor {
996    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
997        let names: Vec<String> = self.names.iter().map(Text::to_string).collect();
998        write!(
999            f,
1000            "{}:{}",
1001            self.role.as_deref().unwrap_or(""),
1002            names.join(",")
1003        )
1004    }
1005}
1006
1007impl From<&str> for Text {
1008    fn from(data: &str) -> Self {
1009        Self::new(data)
1010    }
1011}
1012
1013impl From<String> for Text {
1014    fn from(data: String) -> Self {
1015        Self::new(data)
1016    }
1017}
1018
1019#[cfg(test)]
1020mod tests {
1021    use super::*;
1022
1023    /// music21's own printed answers for `Date`.
1024    #[test]
1025    fn a_date_is_written_as_music21_writes_one() {
1026        let mut date = Date::from_written("1030?/12~/?4").expect("a date");
1027        assert_eq!(date.to_string(), "1030?/12~/04?");
1028        assert_eq!(date.value(DatePart::Month), Some(12));
1029        assert_eq!(
1030            date.certainty(DatePart::Month),
1031            Some(Certainty::Approximate)
1032        );
1033        assert_eq!(date.value(DatePart::Day), Some(4));
1034        assert_eq!(date.certainty(DatePart::Day), Some(Certainty::Uncertain));
1035
1036        let timed = Date::from_written("1834/12/4/4:50:32").expect("a date");
1037        assert_eq!(timed.to_string(), "1834/12/04/04:50:32");
1038        assert_eq!(timed.value(DatePart::Minute), Some(50));
1039        assert_eq!(timed.value(DatePart::Second), Some(32));
1040        assert!(timed.has_time());
1041        assert!(!timed.has_certainty());
1042
1043        let mut approximate = Date::from_parts(Some(1805), Some(3), None).expect("a date");
1044        approximate
1045            .set(DatePart::Month, Some(3), Some(Certainty::Approximate))
1046            .expect("a month");
1047        assert_eq!(approximate.to_string(), "1805/03~/--");
1048
1049        assert_eq!(
1050            Date::from_parts(Some(1843), Some(3), Some(3))
1051                .expect("a date")
1052                .to_string(),
1053            "1843/03/03"
1054        );
1055        date.take_from(&Date::from_parts(Some(1843), None, None).expect("a date"))
1056            .expect("taking a year");
1057        assert_eq!(date.year(), Some(1843));
1058    }
1059
1060    /// music21 drops milliseconds, and reads the first symbol it meets.
1061    #[test]
1062    fn a_written_number_gives_up_its_certainty() {
1063        assert_eq!(
1064            strip_certainty("1247~").expect("a number"),
1065            (1247, Some(Certainty::Approximate))
1066        );
1067        assert_eq!(
1068            strip_certainty("63?").expect("a number"),
1069            (63, Some(Certainty::Uncertain))
1070        );
1071        assert_eq!(strip_certainty("4.43").expect("a number"), (4, None));
1072    }
1073
1074    /// music21's `_sanityCheck`, message for message.
1075    #[test]
1076    fn a_date_no_calendar_has_is_refused() {
1077        assert_eq!(
1078            Date::from_written("1999/14/32")
1079                .expect_err("a fourteenth month")
1080                .to_string(),
1081            "Value error: Month must be between 1 and 12, not 14."
1082        );
1083        assert!(Date::from_parts(Some(1999), Some(4), Some(31)).is_err());
1084        assert!(Date::from_parts(Some(1999), Some(2), Some(29)).is_err());
1085        assert!(Date::from_parts(Some(2000), Some(2), Some(29)).is_ok());
1086    }
1087
1088    /// music21's own printed answers for the four date classes.
1089    #[test]
1090    fn the_four_date_classes_are_written_as_music21_writes_them() {
1091        let single = DatePrimitive::single("2009/12/31", Relevance::Approximate).expect("a date");
1092        assert_eq!(single.to_string(), "2009/12/31");
1093        assert_eq!(single.relevance(), Relevance::Approximate);
1094        assert_eq!(single.kind(), DateKind::Single);
1095        assert_eq!(
1096            DatePrimitive::single("1805/3/12", Relevance::Uncertain)
1097                .expect("a date")
1098                .to_string(),
1099            "1805/03/12"
1100        );
1101
1102        let mut relative = DatePrimitive::relative("2009/12/31", Relevance::Prior).expect("a date");
1103        assert_eq!(relative.to_string(), "prior to 2009/12/31");
1104        relative
1105            .set_relevance(Relevance::After)
1106            .expect("after a date");
1107        assert_eq!(relative.to_string(), "after 2009/12/31");
1108        assert!(DatePrimitive::relative("2009/12/31", Relevance::Certain).is_err());
1109
1110        assert_eq!(
1111            DatePrimitive::between(["2009/12/31", "2010/1/28"])
1112                .expect("a span")
1113                .to_string(),
1114            "2009/12/31 to 2010/01/28"
1115        );
1116        assert_eq!(
1117            DatePrimitive::of_dates(
1118                vec![
1119                    Date::from_parts(Some(1605), None, None).expect("a year"),
1120                    Date::from_written("1608/11?").expect("a date"),
1121                ],
1122                Relevance::Between,
1123                DateKind::Between,
1124            )
1125            .expect("a span")
1126            .to_string(),
1127            "1605/--/-- to 1608/11?/--"
1128        );
1129
1130        assert_eq!(
1131            DatePrimitive::selection(["2009/12/31", "2010/1/28", "1894/1/28"], Relevance::Or)
1132                .expect("a choice")
1133                .to_string(),
1134            "2009/12/31 or 2010/01/28 or 1894/01/28"
1135        );
1136        assert_eq!(
1137            DatePrimitive::selection(["1750", "1775/03?"], Relevance::And)
1138                .expect("a choice")
1139                .to_string(),
1140            "1750/--/-- and 1775/03?/--"
1141        );
1142        assert!(DatePrimitive::selection(["2009/12/31"], Relevance::Certain).is_err());
1143    }
1144
1145    /// music21's own printed answers for `Text` and `Contributor`.
1146    #[test]
1147    fn a_text_keeps_what_it_says_of_itself() {
1148        let title = Text::new("concerto in d").in_language("en");
1149        assert_eq!(title.to_string(), "concerto in d");
1150        assert_eq!(title.language(), Some("en"));
1151        assert_ne!(title, Text::new("concerto in d"));
1152        assert_eq!(Text::new("some text"), Text::new("some text"));
1153        assert_ne!(
1154            Text::new("some text"),
1155            Text::new("some text").translated(true)
1156        );
1157
1158        let article = Text::new("Ale is Dear, The").in_language("en");
1159        assert_eq!(
1160            article.normalized_article().expect("an article"),
1161            "The Ale is Dear"
1162        );
1163        let german = Text::new("Ale is Dear, The").in_language("de");
1164        assert_eq!(
1165            german.normalized_article().expect("an article"),
1166            "Ale is Dear, The"
1167        );
1168
1169        let composer = Contributor::new("composer", [Text::new("Beethoven, Ludwig van")]);
1170        assert_eq!(composer.to_string(), "composer:Beethoven, Ludwig van");
1171        assert_eq!(
1172            composer.name().map(Text::data),
1173            Some("Beethoven, Ludwig van")
1174        );
1175        assert_eq!(Contributor::unnamed().role(), None);
1176        // An abbreviation is read as the role it stands for.
1177        assert_eq!(
1178            Contributor::new("com", [Text::new("Chopin")]).role(),
1179            Some("composer")
1180        );
1181        assert_eq!(
1182            Contributor::role_of_abbreviation("lib").expect("a role"),
1183            "librettist"
1184        );
1185        assert_eq!(
1186            Contributor::abbreviation_of_role("composer").expect("an abbreviation"),
1187            "com"
1188        );
1189        assert!(Contributor::role_of_abbreviation("zzz").is_err());
1190        assert!(Contributor::abbreviation_of_role("conductor").is_err());
1191    }
1192}