config_struct 0.5.0

Create struct definitions from config files at build time.
Documentation
use crate::{error::OptionsError, format::Format, validation};

/// Options for serde support.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum SerdeSupport {
    /// Do not derive any serde traits for the struct.
    No,

    /// Derive `Serialize` and `Deserialize` for the struct.
    Yes,

    /// Derive any combination of `Serialize` and `Deserialize`
    /// for the struct.
    Mixed { serialize: bool, deserialize: bool },
}

impl SerdeSupport {
    pub(crate) fn should_derive_ser_de(self) -> Option<(bool, bool)> {
        match self {
            Self::No => None,
            Self::Yes => Some((true, true)),
            Self::Mixed {
                serialize,
                deserialize,
            } => {
                if !(serialize || deserialize) {
                    None
                } else {
                    Some((serialize, deserialize))
                }
            }
        }
    }
}

impl Default for SerdeSupport {
    fn default() -> Self {
        Self::No
    }
}

/// When to perform dynamic loading from the config file itself.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DynamicLoading {
    /// Always load the config from file.
    Always,

    /// Load from file in debug mode, but use the statically-included
    /// const in release mode.
    DebugOnly,

    /// Never load dynamically. Always use the statically-included
    /// const.
    Never,
}

impl Default for DynamicLoading {
    fn default() -> Self {
        Self::DebugOnly
    }
}

/// Represents a floating-point type.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FloatSize {
    F32,
    F64,
}

/// Represents an integer type.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IntSize {
    I8,
    I16,
    I32,
    I64,
    ISize,
}

/// Options for configuring the generation of a struct.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StructOptions {
    /// The format of the source data.
    ///
    /// Defaults to `None` which will cause it to be inferred from the
    /// file type.
    pub format: Option<Format>,

    /// The name of the resulting struct.
    ///
    /// Defaults to `"Config"`.
    pub struct_name: String,

    /// The name of the resulting const, if generated.
    ///
    /// Defaults to the value of `struct_name` in uppercase.
    pub const_name: Option<String>,

    /// Whether or not to generate a `const` instance of the struct.
    ///
    /// Defaults to `true`.
    pub generate_const: bool,

    /// A list of traits for the struct to derive.
    ///
    /// Defaults to `["Debug", "Clone"]`
    ///
    /// (Note that the `serde_support` option below may add to this
    /// list.)
    pub derived_traits: Vec<String>,

    /// Shorthand for generating the Serialize and Deserialize traits.
    ///
    /// Defaults to `No`.
    pub serde_support: SerdeSupport,

    /// The recommended way to derive Serialize and Deserialize
    /// is via the `serde` crate's
    /// [`derive` feature](https://serde.rs/derive.html).
    ///
    /// If you instead need to use the old method of including the
    /// `serde_derive` crate, set this flag to `true`.
    pub use_serde_derive_crate: bool,

    /// Whether or not to generate helper functions to load the
    /// struct at runtime.
    ///
    /// Defaults to `true`.
    ///
    /// **Note:** These load functions depend on the `Deserialize`
    /// trait, as well as the relevant serde library for the config
    /// format.
    ///
    /// So for example, if you generate a struct from `config.json`
    /// then you will have to enable `serde_support` for the
    /// `Deserialize` trait, and you will also have to include the
    /// `serde_json` library in your crate.
    pub generate_load_fns: bool,

    /// Whether the load functions, if generated, are dynamic,
    /// and when.
    ///
    /// Defaults to `DebugOnly`.
    pub dynamic_loading: DynamicLoading,

    /// Whether or not to create the parent directories of the
    /// output file, if they don't exist.
    ///
    /// Defaults to `true`.
    pub create_dirs: bool,

    /// Whether to check if the destination file would be changed
    /// before writing output.
    ///
    /// This is to avoid unnecessary writes from marking the
    /// destination file as changed (which could, for example,
    /// trigger a process which is watching for changes). This
    /// option only works with the `create_*` functions.
    ///
    /// Defaults to `true`.
    pub write_only_if_changed: bool,

    /// The type of floating point values in the config, where the
    /// format does not make it explicit.
    ///
    /// Defaults to `F64`.
    pub default_float_size: FloatSize,

    /// The type of integer values in the config, where the
    /// format does not make it explicit.
    ///
    /// Defaults to `I64`.
    pub default_int_size: IntSize,

    /// The maximum array size, over which array values in the
    /// config will be represented as slices instead.
    ///
    /// If set to `0`, slices will always be used.
    ///
    /// Defaults to `0`.
    pub max_array_size: usize,
}

impl StructOptions {
    pub(crate) fn validate(&self) -> Result<(), OptionsError> {
        if !validation::valid_identifier(&self.struct_name) {
            return Err(OptionsError::InvalidStructName(self.struct_name.clone()));
        }

        Ok(())
    }

    pub(crate) fn real_const_name(&self) -> String {
        self.const_name
            .clone()
            .unwrap_or_else(|| self.struct_name.to_uppercase())
    }

    /// The default options plus serde support. This includes
    /// `Serialize`/`Deserialize` traits, plus helpers functions
    /// to load the config.
    ///
    /// ```rust
    /// use config_struct::{StructOptions, SerdeSupport};
    ///
    /// let options = StructOptions::serde_default();
    ///
    /// assert_eq!(options, StructOptions {
    ///     serde_support: SerdeSupport::Yes,
    ///     generate_load_fns: true,
    ///     .. StructOptions::default()
    /// });
    /// ```
    pub fn serde_default() -> Self {
        StructOptions {
            serde_support: SerdeSupport::Yes,
            generate_load_fns: true,
            ..Self::default()
        }
    }
}

impl Default for StructOptions {
    /// ```rust
    /// use config_struct::*;
    ///
    /// let default_options = StructOptions {
    ///     format: None,
    ///     struct_name: "Config".to_owned(),
    ///     const_name: None,
    ///     generate_const: true,
    ///     derived_traits: vec![
    ///         "Debug".to_owned(),
    ///         "Clone".to_owned(),
    ///     ],
    ///     serde_support: SerdeSupport::No,
    ///     use_serde_derive_crate: false,
    ///     generate_load_fns: false,
    ///     dynamic_loading: DynamicLoading::DebugOnly,
    ///     create_dirs: true,
    ///     write_only_if_changed: true,
    ///     default_float_size: FloatSize::F64,
    ///     default_int_size: IntSize::I64,
    ///     max_array_size: 0,
    /// };
    /// assert_eq!(default_options, StructOptions::default());
    /// ```
    fn default() -> Self {
        StructOptions {
            format: None,
            struct_name: "Config".to_owned(),
            const_name: None,
            generate_const: true,
            derived_traits: vec!["Debug".to_owned(), "Clone".to_owned()],
            serde_support: SerdeSupport::default(),
            use_serde_derive_crate: false,
            generate_load_fns: false,
            dynamic_loading: DynamicLoading::DebugOnly,
            create_dirs: true,
            write_only_if_changed: true,
            default_float_size: FloatSize::F64,
            default_int_size: IntSize::I64,
            max_array_size: 0,
        }
    }
}

/// Options for configuring the generation of a struct.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EnumOptions {
    /// The format of the source data.
    ///
    /// Defaults to `None` which will cause it to be inferred from the
    /// file type.
    pub format: Option<Format>,

    /// The name of the resulting enum.
    ///
    /// Defaults to `"Key"`.
    pub enum_name: String,

    /// The name of the const slice containing all variants.
    /// For example, if you specify `Some("ALL")`, then
    /// `MyEnum::ALL` will contain all variants of the enum.
    ///
    /// If you specify `None` then no constant will be
    /// generated.
    ///
    /// Defaults to `Some("ALL")`.
    pub all_variants_const: Option<String>,

    /// A list of traits for the struct to derive.
    ///
    /// Defaults to `["Debug", "Clone", "Copy", "PartialEq",
    /// "Eq", "PartialOrd", "Ord", "Hash"]`
    ///
    /// (Note that the `serde_support` option below may add
    /// to this list.)
    pub derived_traits: Vec<String>,

    /// Whether to implement the `Default` trait for this enum.
    /// If `true` then the default value will be the first
    /// variant specified.
    ///
    /// Defaults to `true`.
    pub first_variant_is_default: bool,

    /// Whether to implement the `Display` trait for this enum.
    /// This requires the `Debug` trait to be implemented.
    ///
    /// Defaults to `true`.
    pub impl_display: bool,

    /// Whether to implement the `FromStr` trait for this enum.
    /// This requires the `all_variants_const` to be set to
    /// something other than `None`.
    ///
    /// Defaults to `true`.
    pub impl_from_str: bool,

    /// Shorthand for generating the Serialize and Deserialize
    /// traits.
    ///
    /// Defaults to `No`.
    pub serde_support: SerdeSupport,

    /// The recommended way to derive Serialize and Deserialize
    /// is via the `serde` crate's
    /// [`derive` feature](https://serde.rs/derive.html).
    ///
    /// If you instead need to use the old method of including
    /// the `serde_derive` crate, set this flag to `true`.
    pub use_serde_derive_crate: bool,

    /// Whether or not to create the parent directories of the
    /// output file, if they don't exist.
    ///
    /// Defaults to `true`.
    pub create_dirs: bool,

    /// Whether to check if the destination file would be changed
    /// before writing output.
    ///
    /// This is to avoid unnecessary writes from marking the
    /// destination file as changed (which could, for example,
    /// trigger a process which is watching for changes). This
    /// option only works with the `create_*` functions.
    ///
    /// Defaults to `true`.
    pub write_only_if_changed: bool,
}

impl EnumOptions {
    pub(crate) fn validate(&self) -> Result<(), OptionsError> {
        eprintln!("TODO: EnumOptions::validate");
        Ok(())
    }

    /// The default options plus serde support. This includes
    /// `Serialize`/`Deserialize` traits, plus helpers functions
    /// to load the config.
    ///
    /// ```rust
    /// use config_struct::{EnumOptions, SerdeSupport};
    ///
    /// let options = EnumOptions::serde_default();
    ///
    /// assert_eq!(options, EnumOptions {
    ///     serde_support: SerdeSupport::Yes,
    ///     .. EnumOptions::default()
    /// });
    /// ```
    pub fn serde_default() -> Self {
        EnumOptions {
            serde_support: SerdeSupport::Yes,
            ..Self::default()
        }
    }
}

    /// Defaults to `["Debug", "Clone", "Copy", "PartialEq",
    /// "Eq", "PartialOrd", "Ord", "Hash"]`
impl Default for EnumOptions {
    /// ```rust
    /// use config_struct::*;
    ///
    /// let default_options = EnumOptions {
    ///     format: None,
    ///     enum_name: "Key".to_owned(),
    ///     all_variants_const: Some("ALL".to_owned()),
    ///     derived_traits: vec![
    ///         "Debug".to_owned(),
    ///         "Clone".to_owned(),
    ///         "Copy".to_owned(),
    ///         "PartialEq".to_owned(),
    ///         "Eq".to_owned(),
    ///         "PartialOrd".to_owned(),
    ///         "Ord".to_owned(),
    ///         "Hash".to_owned(),
    ///     ],
    ///     first_variant_is_default: true,
    ///     impl_display: true,
    ///     impl_from_str: true,
    ///     serde_support: SerdeSupport::No,
    ///     use_serde_derive_crate: false,
    ///     create_dirs: true,
    ///     write_only_if_changed: true,
    /// };
    /// assert_eq!(default_options, EnumOptions::default());
    /// ```
    fn default() -> Self {
        EnumOptions {
            format: None,
            enum_name: "Key".to_owned(),
            all_variants_const: Some("ALL".to_owned()),
            derived_traits: vec![
                "Debug".to_owned(),
                "Clone".to_owned(),
                "Copy".to_owned(),
                "PartialEq".to_owned(),
                "Eq".to_owned(),
                "PartialOrd".to_owned(),
                "Ord".to_owned(),
                "Hash".to_owned(),
            ],
            first_variant_is_default: true,
            impl_display: true,
            impl_from_str: true,
            serde_support: SerdeSupport::default(),
            use_serde_derive_crate: false,
            create_dirs: true,
            write_only_if_changed: true,
        }
    }
}