bliplab 0.0.3

Tool to combine several BLIP channels in a song
Documentation
//! See [BLIPlab](https://docs.rs/bliplab)

#[cfg(not(any(
    feature = "json",
    feature = "json5",
    feature = "yaml",
    feature = "ron",
    feature = "lexpr"
)))]
compile_error!("You must select one serializer / deserializer feature");

cfg_exclusive::cfg_exclusive! {
    serializer_deserializer,
    ["json", "json5", "yaml", "ron", "lexpr"],
    "You can select only one serializer / deserializer feature (json, json5, yaml, toml, ron or lexpr)"
}

#[cfg(any(feature = "json5", feature = "ron"))]
use std::io::read_to_string;
use std::{env::args, fs::File, io::Read};

use anyhow::Context;
use bliplab::{Channel, PathOrString, Song, VariableChange};
use cfg_if::cfg_if;
use serde::{Serialize, de::DeserializeOwned};

const TARGET: &str = "BLIP lab file";

fn example_song() -> Song<'static> {
    Song::new(
        Default::default(),
        [(
            "Piano".into(),
            Channel::new(
                PathOrString::String("hi".into()),
                vec!["do", "ré", "mi"].into_iter().map(Into::into),
                "sin(2*pi()*(442*2^((n+1)/N))*t)".parse().unwrap(),
                [
                    ('l', 4f64),
                    ('L', 0.0),
                    ('t', 0.0),
                    ('T', 60.0),
                    ('N', 12.0),
                ],
                [],
                [(
                    "L".into(),
                    VariableChange::new('L', "2^(2-log(2, l))*(60/T)".parse().unwrap()),
                )],
            ),
        )],
    )
}

fn prettify<V: Serialize>(value: &V) -> anyhow::Result<String> {
    cfg_if! {
        if #[cfg(feature = "json")] {
            serde_json::to_string_pretty(value).map_err(Into::into)
        } else if #[cfg(feature = "json5")] {
            json5::to_string(value).map_err(Into::into)
        } else if #[cfg(feature = "yaml")] {
            serde_yaml::to_string(value).map_err(Into::into)
        } else if #[cfg(feature = "ron")] {
            ron::to_string(value).map_err(Into::into)
        } else if #[cfg(feature = "lexpr")] {
            serde_lexpr::to_string(value).map_err(Into::into)
        }
    }
}

fn from_reader<R: Read, T: DeserializeOwned>(reader: R) -> anyhow::Result<T> {
    cfg_if! {
        if #[cfg(feature = "json")] {
            serde_json::from_reader(reader).map_err(Into::into)
        } else if #[cfg(feature = "json5")] {
            json5::from_str(&read_to_string(reader)?).map_err(Into::into)
        } else if #[cfg(feature = "yaml")] {
            serde_yaml::from_reader(reader).map_err(Into::into)
        } else if #[cfg(feature = "ron")] {
            ron::from_str(&read_to_string(reader)?).map_err(Into::into)
        } else if #[cfg(feature = "lexpr")] {
            serde_lexpr::from_reader(reader).map_err(Into::into)
        }
    }
}

fn main() -> anyhow::Result<()> {
    let string = args().skip(1).next().context(format!(
        "You need to give a single argument with the filename of your {TARGET}."
    ))?;
    if let Some(argument) = string.strip_prefix("--") {
        if argument == "example" {
            println!(
                "{}",
                prettify(&example_song())
                    .context("Can't serialize the example song, this is a bug.")?
            );
        } else if argument == "help" {
            println!(
                "BLIP lab file player. Pass it a path to a {TARGET} or use with \"--example\" for an example {TARGET}."
            );
        } else {
            eprintln!("Unknown argument: \"{argument}\"");
        }
    } else {
        let song: Song =
            from_reader(File::open(string).context(format!("Failed to open the {TARGET}."))?)
                .context(format!("Failed to read the {TARGET}."))?;

        println!(
            "{}",
            prettify(&song).context("Failed to pretty print the song! What?!")?
        );
    }

    Ok(())
}