Skip to main content

carmen_lang/exporter/
mod.rs

1//! Score export functionality for converting Carmen files to various formats.
2//!
3//! This module provides a pluggable export system that can convert musical scores
4//! into different formats like LilyPond notation files and plain text representations.
5//!
6//! # Architecture
7//!
8//! The export system is built around the [`Exporter`] trait, which defines a common
9//! interface for all export formats. Specific exporters implement this trait to
10//! handle format-specific conversion logic.
11//!
12//! # Available Formats
13//!
14//! - **LilyPond**: Exports scores as `.ly` files for professional music typesetting
15//! - **Text**: Exports scores as human-readable text files for debugging and inspection
16
17pub mod lilypond;
18pub mod text;
19
20use crate::core::Score;
21use crate::errors::{AddSpan, ErrorSource, Position, Result, Span};
22use std::io::Write;
23
24/// Core trait for score exporters that convert musical scores to specific output formats.
25///
26/// All export formats must implement this trait to provide a consistent interface
27/// for score conversion. The trait handles both the conversion logic and metadata
28/// about the export format.
29pub trait Exporter {
30    /// Export a score to the given writer.
31    ///
32    /// This is the main conversion method that takes a parsed musical score and
33    /// writes it in the target format to the provided writer.
34    ///
35    /// # Arguments
36    ///
37    /// * `score` - The musical score to export
38    /// * `writer` - The destination for the exported data
39    ///
40    /// # Errors
41    ///
42    /// Returns an error if the score cannot be converted to the target format
43    /// or if writing to the output fails.
44    fn export(&self, score: &Score, writer: &mut dyn Write) -> Result<()>;
45
46    /// Get the file extension for this export format.
47    ///
48    /// Returns the standard file extension (without the dot) that should be
49    /// used for files in this format.
50    fn file_extension(&self) -> &'static str;
51
52    /// Get a human-readable name for this export format.
53    ///
54    /// Returns a descriptive name that can be displayed to users when
55    /// selecting export formats.
56    fn format_name(&self) -> &'static str;
57}
58
59/// Configuration for score export operations.
60///
61/// Specifies both the target format and the output destination for the exported score.
62pub struct ExportConfig {
63    pub format: ExportFormat,
64    pub output: ExportOutput,
65}
66
67/// Available export formats for musical scores.
68#[derive(Debug, Clone)]
69pub enum ExportFormat {
70    /// Plain text format for human-readable output and debugging
71    Text,
72    /// LilyPond format for professional music typesetting
73    LilyPond,
74}
75
76/// Output destinations for exported scores.
77#[derive(Debug, Clone)]
78pub enum ExportOutput {
79    /// Write to standard output
80    Stdout,
81    /// Write to a file at the specified path
82    File(String),
83}
84
85/// Manager for coordinating score export operations.
86///
87/// The export manager provides a high-level interface for exporting scores
88/// to different formats and destinations. It handles the creation of appropriate
89/// exporters and manages the export process.
90pub struct ExportManager;
91
92impl ExportManager {
93    /// Create a new export manager.
94    pub fn new() -> Self {
95        Self
96    }
97
98    /// Export a score according to the provided configuration.
99    ///
100    /// This is the main entry point for score export. It creates the appropriate
101    /// exporter for the target format and handles writing to the specified output.
102    ///
103    /// # Arguments
104    ///
105    /// * `score` - The musical score to export
106    /// * `config` - Export configuration specifying format and output destination
107    pub fn export_score(&self, score: &Score, config: &ExportConfig) -> Result<()> {
108        let exporter = self.get_exporter(&config.format);
109
110        match &config.output {
111            ExportOutput::Stdout => {
112                let mut stdout = std::io::stdout();
113                exporter.export(score, &mut stdout)
114            }
115            ExportOutput::File(path) => {
116                let mut file = std::fs::File::create(path).map_err(|e| {
117                    ErrorSource::Runtime(format!("Failed to create output file '{path}': {e}"))
118                        .with_span(Span::single(Position::start()))
119                })?;
120                exporter.export(score, &mut file)
121            }
122        }
123    }
124
125    /// Get an exporter instance for the specified format.
126    ///
127    /// Creates and returns the appropriate exporter implementation based on
128    /// the requested format.
129    fn get_exporter(&self, format: &ExportFormat) -> Box<dyn Exporter> {
130        match format {
131            ExportFormat::Text => Box::new(text::TextExporter::new()),
132            ExportFormat::LilyPond => Box::new(lilypond::LilyPondExporter {}),
133        }
134    }
135
136    /// Generate a default filename for the given export format.
137    ///
138    /// Creates a filename by combining the base name with the appropriate
139    /// file extension for the export format.
140    ///
141    /// # Arguments
142    ///
143    /// * `format` - The export format to generate a filename for
144    /// * `base_name` - Optional base name for the file (defaults to "score")
145    ///
146    /// # Returns
147    ///
148    /// A filename string with the appropriate extension for the format
149    pub fn get_default_filename(&self, format: &ExportFormat, base_name: Option<&str>) -> String {
150        let exporter = self.get_exporter(format);
151        let base = base_name.unwrap_or("score");
152        format!("{}.{}", base, exporter.file_extension())
153    }
154}
155
156impl Default for ExportManager {
157    fn default() -> Self {
158        Self::new()
159    }
160}