use importer::classic_song::slides_from_classic_song;
use importer::errors::*;
use slides::{LanguageConfiguration, ShowMetaInformation, Slide, SlideSettings};
use std::error::Error;
use std::ffi::{c_char, c_int, CStr, CString};
use std::path::{Path, PathBuf};
pub mod song;
#[cfg(doctest)]
#[doc = include_str!("../docs/data-model.md")]
#[doc = include_str!("../docs/abc-export.md")]
#[doc = include_str!("../docs/ccli-import.md")]
#[doc = include_str!("../docs/meta-information.md")]
#[doc = include_str!("../docs/complex-slides.md")]
#[doc = include_str!("../docs/text-export.md")]
pub struct DocumentationExamples;
pub mod importer;
pub mod filetypes;
pub mod slides;
pub mod templating;
pub mod exporter;
pub extern "C" fn create_presentation_from_file_c(
c_file_path: *const c_char,
c_title_slide: c_int,
c_show_spoiler: c_int,
c_show_meta_information: c_int,
c_meta_syntax: *const c_char,
c_empty_last_side: c_int,
c_max_lines: c_int
) -> *const c_char {
let file_path: PathBuf = PathBuf::from(c_string_to_rust(c_file_path).unwrap());
let title_slide: bool = c_title_slide == 1;
let show_spoiler: bool = c_show_spoiler == 1;
let show_meta_information = ShowMetaInformation::from_bits(c_show_meta_information as u8);
let meta_syntax = c_string_to_rust(c_meta_syntax).unwrap();
let empty_last_slide: bool = c_empty_last_side == 1;
let max_lines: Option<usize> = match c_max_lines as usize {
0 => None,
_ => Some(c_max_lines as usize)
};
let slide_settings: SlideSettings = SlideSettings {
title_slide,
show_spoiler,
show_meta_information,
meta_syntax,
empty_last_slide,
max_lines,
language: LanguageConfiguration::default(),
};
match create_presentation_from_file(file_path, slide_settings) {
Ok(v) => rust_string_to_c_char(serde_json::to_string(&v).unwrap()).unwrap(),
Err(err) => rust_string_to_c_char(err.to_string()).unwrap(),
}
}
pub fn slides_from_file(
file_path: impl AsRef<Path>,
slide_settings: &SlideSettings,
) -> Result<Vec<Slide>, Box<dyn Error>> {
let file_path = file_path.as_ref();
if !file_path.exists() {
return Err(Box::new(CantaraFileDoesNotExistError));
}
let file_type = filetypes::FileType::from_path(file_path).ok_or_else(|| {
Box::new(CantaraImportUnknownFileExtensionError {
file_extension: file_path
.extension()
.and_then(|extension| extension.to_str())
.unwrap_or("")
.to_string(),
})
})?;
if file_type == filetypes::FileType::ClassicSongFile {
let content = std::fs::read_to_string(file_path)?;
let backup_title = file_path
.file_stem()
.and_then(|stem| stem.to_str())
.unwrap_or_default()
.to_string();
return Ok(slides_from_classic_song(&content, slide_settings, backup_title));
}
let song = importer::import_song_from_file(file_path)?;
Ok(exporter::slides::slides_from_song(&song, slide_settings))
}
pub fn create_presentation_from_file(
file_path: PathBuf,
slide_settings: SlideSettings,
) -> Result<Vec<Slide>, Box<dyn Error>> {
slides_from_file(file_path, &slide_settings)
}
fn c_string_to_rust(c_str: *const c_char) -> Option<String> {
if c_str.is_null() {
return None; }
unsafe {
let cstr = CStr::from_ptr(c_str);
cstr.to_str()
.map(|s| s.to_string()) .ok() }
}
fn rust_string_to_c_char(rust_str: String) -> Option<*const c_char> {
match CString::new(rust_str) {
Ok(c_string) => {
let c_ptr = c_string.as_ptr();
std::mem::forget(c_string); Some(c_ptr)
}
Err(_) => {
None
}
}
}
#[cfg(test)]
mod tests {
use std::path::PathBuf;
use crate::{create_presentation_from_file, slides::SlideSettings};
use super::song::Song;
#[test]
fn create_example_song() {
let song: Song = Song::new("Test Song");
assert_eq!(song.title, "Test Song");
assert_eq!(song.part_count(), 0);
assert_eq!(song.parts().len(), 0)
}
#[test]
fn test_file_does_not_exist_error() {
let file_path: PathBuf = "Ich existiere nicht.song".into();
let slide_settings: SlideSettings = SlideSettings::default();
assert!(
create_presentation_from_file(file_path, slide_settings)
.is_err()
)
}
}