Skip to main content

chordsketch_convert/
lib.rs

1//! ChordPro ↔ iReal Pro format-conversion bridge.
2//!
3//! Bidirectional converter tracked under
4//! [#2050](https://github.com/koedame/chordsketch/issues/2050).
5//! Both directions are now implemented:
6//!
7//! - **iReal → ChordPro** ([#2053](https://github.com/koedame/chordsketch/issues/2053))
8//!   — near-lossless; lives in [`crate::from_ireal`].
9//! - **ChordPro → iReal** ([#2061](https://github.com/koedame/chordsketch/issues/2061))
10//!   — lossy (lyrics drop, fonts / colours / capo dropped); lives
11//!   in [`crate::to_ireal`]. Every drop surfaces as a
12//!   [`ConversionWarning`] so callers never silently lose data.
13//!
14//! # Layout
15//!
16//! - [`Converter`] is the generic trait every direction implements.
17//!   Free functions [`chordpro_to_ireal`] and [`ireal_to_chordpro`]
18//!   wrap the trait implementations for ergonomics. New formats
19//!   (MusicXML, Guitar Pro, etc.) plug in as additional `Converter`
20//!   implementations or as their own crates that depend on this one
21//!   for the warning / error vocabulary.
22//! - [`ConversionOutput`] pairs the converted value with a
23//!   [`ConversionWarning`] list so callers can surface lossy
24//!   transformations without parsing the error type.
25//! - [`ConversionError`] enumerates the reasons a conversion can
26//!   fail. The `NotImplemented` variant is the placeholder every
27//!   pre-implementation function currently returns.
28//!
29//! # Why a separate crate from `chordsketch-convert-musicxml`
30//!
31//! `chordsketch-convert-musicxml` predates this crate and binds the
32//! ChordPro AST to MusicXML directly via free functions. The new
33//! `chordsketch-convert` crate is the conversion home for formats
34//! that share a small intermediate concept set (warnings, lossy
35//! drops, approximations) — namely the ChordPro ↔ iReal bridge and
36//! its expected MusicXML / Guitar Pro siblings. Consolidating the
37//! musicxml converter into this crate is tracked in a future
38//! cleanup; it would be a breaking change that does not block
39//! v0.3.0.
40//!
41//! # Stability
42//!
43//! Pre-1.0 — the trait surface is intentionally narrow so the
44//! follow-up issues can fill in implementations without breaking
45//! the bindings or CLI. [`ConversionError`] and [`WarningKind`]
46//! are both `#[non_exhaustive]`, so adding a new variant is
47//! non-breaking for downstream `match` expressions; renaming an
48//! existing variant remains breaking.
49//!
50//! # Example
51//!
52//! ```
53//! use chordsketch_chordpro::ast::Song;
54//! use chordsketch_convert::chordpro_to_ireal;
55//!
56//! let song = Song::new();
57//! let result = chordpro_to_ireal(&song).expect("conversion succeeds");
58//! // An empty source produces an empty `IrealSong` with no
59//! // warnings — there is no metadata, no lyrics, and no
60//! // sections to drop.
61//! assert!(result.warnings.is_empty());
62//! ```
63
64#![forbid(unsafe_code)]
65
66pub mod error;
67pub mod from_ireal;
68pub mod ireal;
69pub mod to_ireal;
70
71pub use error::{ConversionError, ConversionWarning, WarningKind};
72pub use ireal::{ChordProToIreal, IrealToChordPro, chordpro_to_ireal, ireal_to_chordpro};
73
74/// Result of a successful conversion.
75///
76/// `output` is the converted value; `warnings` is the list of
77/// information lost or approximated during the transformation. An
78/// empty `warnings` vector indicates a clean (lossless) conversion;
79/// callers that prefer fail-fast behaviour can promote any non-empty
80/// warning list to an error themselves.
81#[derive(Debug, Clone, PartialEq, Eq)]
82pub struct ConversionOutput<T> {
83    /// The converted value.
84    pub output: T,
85    /// Warnings emitted during conversion (lossy drops,
86    /// approximations, unsupported features).
87    pub warnings: Vec<ConversionWarning>,
88}
89
90impl<T> ConversionOutput<T> {
91    /// Wraps `output` with an empty warning list.
92    #[must_use]
93    pub fn lossless(output: T) -> Self {
94        Self {
95            output,
96            warnings: Vec::new(),
97        }
98    }
99
100    /// Wraps `output` with the provided warning list.
101    #[must_use]
102    pub fn with_warnings(output: T, warnings: Vec<ConversionWarning>) -> Self {
103        Self { output, warnings }
104    }
105}
106
107/// A converter that transforms `Source` into `Target`.
108///
109/// Implementations live alongside the source / target types — the
110/// ChordPro ↔ iReal pair is in [`crate::ireal`]. Adding a new
111/// direction means implementing `Converter<S, T>` for a new unit
112/// struct (or extending an existing one) and re-exporting an
113/// ergonomic free function from this crate's root.
114///
115/// # Why a trait in addition to free functions
116///
117/// The free functions in [`crate::ireal`] are the ergonomic entry
118/// point for a known direction. The trait exists for two callers
119/// the free functions cannot serve:
120///
121/// - **Generic dispatch.** A future pipeline that runs the same
122///   ChordSketch `Song` through several converters
123///   (`Vec<Box<dyn Converter<Song, MusicXml>>>` etc.) needs a
124///   uniform type to hold them. The CLI auto-detect path (#2066)
125///   is the first concrete consumer.
126/// - **Configurable converters.** Once #2053 / #2061 land, an
127///   implementation may need configuration (strictness level,
128///   warning thresholds). A marker struct with fields holds that
129///   state; the free function then becomes a thin wrapper around
130///   the default-configured marker. Keeping the trait in place
131///   from day one avoids a breaking re-shape later.
132///
133/// # Errors
134///
135/// Implementations return [`ConversionError`]; see that type for the
136/// failure-mode taxonomy. Lossy but successful conversions return
137/// `Ok(ConversionOutput { warnings: [...], .. })`, not `Err` — the
138/// caller decides whether warnings should be promoted to errors.
139pub trait Converter<Source, Target> {
140    /// Converts `source` into `Target`, accumulating any
141    /// information lost in `ConversionOutput::warnings`.
142    ///
143    /// # Errors
144    ///
145    /// See [`ConversionError`] for the documented failure modes.
146    /// While the scaffold is in place every implementation returns
147    /// [`ConversionError::NotImplemented`].
148    #[must_use = "ignoring a conversion result drops both warnings and errors"]
149    fn convert(&self, source: &Source) -> Result<ConversionOutput<Target>, ConversionError>;
150}
151
152/// Returns the library version (the workspace `Cargo.toml`
153/// `version` field, baked in at compile time).
154#[must_use]
155pub fn version() -> &'static str {
156    env!("CARGO_PKG_VERSION")
157}