    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147

    // If no features are used, there is an "unused mut" warning in `ALL_EXTENSIONS`
    // BUG: ? For some reason this doesn't do anything if I try and function scope this
    #![allow(unused_mut)]

    use lazy_static::lazy_static;
    use std::collections::HashMap;
    use std::error::Error;

    use crate::map::Map;
    use crate::{file::FileStoredFormat, value::Value, Format};

    #[cfg(feature = "toml")]
    mod toml;

    #[cfg(feature = "json")]
    mod json;

    #[cfg(feature = "yaml")]
    mod yaml;

    #[cfg(feature = "ini")]
    mod ini;

    #[cfg(feature = "ron")]
    mod ron;

    #[cfg(feature = "json5")]
    mod json5;

    /// File formats provided by the library.
    ///
    /// Although it is possible to define custom formats using [`Format`] trait it is recommended to use FileFormat if possible.
    #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
    pub enum FileFormat {
        /// TOML (parsed with toml)
        #[cfg(feature = "toml")]
        Toml,

        /// JSON (parsed with serde_json)
        #[cfg(feature = "json")]
        Json,

        /// YAML (parsed with yaml_rust)
        #[cfg(feature = "yaml")]
        Yaml,

        /// INI (parsed with rust_ini)
        #[cfg(feature = "ini")]
        Ini,

        /// RON (parsed with ron)
        #[cfg(feature = "ron")]
        Ron,

        /// JSON5 (parsed with json5)
        #[cfg(feature = "json5")]
        Json5,
    }

    lazy_static! {
        #[doc(hidden)]
        // #[allow(unused_mut)] ?
        pub static ref ALL_EXTENSIONS: HashMap<FileFormat, Vec<&'static str>> = {
            let mut formats: HashMap<FileFormat, Vec<_>> = HashMap::new();

            #[cfg(feature = "toml")]
            formats.insert(FileFormat::Toml, vec!["toml"]);

            #[cfg(feature = "json")]
            formats.insert(FileFormat::Json, vec!["json"]);

            #[cfg(feature = "yaml")]
            formats.insert(FileFormat::Yaml, vec!["yaml", "yml"]);

            #[cfg(feature = "ini")]
            formats.insert(FileFormat::Ini, vec!["ini"]);

            #[cfg(feature = "ron")]
            formats.insert(FileFormat::Ron, vec!["ron"]);

            #[cfg(feature = "json5")]
            formats.insert(FileFormat::Json5, vec!["json5"]);

            formats
        };
    }

    impl FileFormat {
        pub(crate) fn extensions(&self) -> &'static [&'static str] {
            // It should not be possible for this to fail
            // A FileFormat would need to be declared without being added to the
            // ALL_EXTENSIONS map.
            ALL_EXTENSIONS.get(self).unwrap()
        }

        pub(crate) fn parse(
            &self,
            uri: Option<&String>,
            text: &str,
        ) -> Result<Map<String, Value>, Box<dyn Error + Send + Sync>> {
            match self {
                #[cfg(feature = "toml")]
                FileFormat::Toml => toml::parse(uri, text),

                #[cfg(feature = "json")]
                FileFormat::Json => json::parse(uri, text),

                #[cfg(feature = "yaml")]
                FileFormat::Yaml => yaml::parse(uri, text),

                #[cfg(feature = "ini")]
                FileFormat::Ini => ini::parse(uri, text),

                #[cfg(feature = "ron")]
                FileFormat::Ron => ron::parse(uri, text),

                #[cfg(feature = "json5")]
                FileFormat::Json5 => json5::parse(uri, text),

                #[cfg(all(
                    not(feature = "toml"),
                    not(feature = "json"),
                    not(feature = "yaml"),
                    not(feature = "ini"),
                    not(feature = "ron"),
                    not(feature = "json5"),
                ))]
                _ => unreachable!("No features are enabled, this library won't work without features"),
            }
        }
    }

    impl Format for FileFormat {
        fn parse(
            &self,
            uri: Option<&String>,
            text: &str,
        ) -> Result<Map<String, Value>, Box<dyn Error + Send + Sync>> {
            self.parse(uri, text)
        }
    }

    impl FileStoredFormat for FileFormat {
        fn file_extensions(&self) -> &'static [&'static str] {
            self.extensions()
        }
    }