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}