This is a library for converting config files into matching source files at build time.
This library is intended to be used in a build.rs
file, so it needs to be added to [build-dependencies]
.
[build-dependencies.config_struct]
version = "~0.4.0"
features = ["toml-parsing"]
By default, config_struct
is markup-language-agnostic, so include the relevant feature for whatever language your config file is written in. Choices are:
json-parsing
ron-parsing
toml-parsing
yaml-parsing
Now in your build.rs
file, add code like the following:
use config_struct::{Error, StructOptions};
fn main() -> Result<(), Error> {
config_struct::create_config(
"config.toml",
"src/config.rs",
&StructOptions::default())
}
This will take the following config.toml
file:
name = "Config name"
... and generate a config.rs
file like the following:
// ...
use std::borrow::Cow;
#[derive(Debug, Clone)]
#[allow(non_camel_case_types)]
pub struct Config {
pub name: Cow<'static, str>,
}
pub const CONFIG: Config = Config {
name: Cow::Borrowed("Config name"),
};
Strings and arrays are represented by Cow
types, which allows the entire Config struct to be either heap allocated at runtime, or a compile time constant, as shown above.
serde
Unless you are specifically avoiding the serde
family of crates at runtime, it's recommended to use the following options:
StructOptions {
serde_support: SerdeSupport::Yes,
generate_load_fns: true,
.. my_other_options
}
This will derive the Serialize
and Deserialize
traits for your struct, as well as providing a handy load()
method to read and parse the file at runtime.
If these are the only options you want to set beyond the defaults, you can use StructOptions::serde_default()
as a shorthand.
There are a few different ways to access the config at runtime.
let config = Config::load();
generate_load_fns
option described above.CONFIG
const directly, e.g. let x = CONFIG.name;
let config: Config = toml::from_str(file_contents)?
serde_support
option above, or requires you to manually add serde::Serialize
and serde::Deserialize
to the derived_traits
option.The first method is recommended, as it will return the const value in release mode, but load from the filesystem in debug mode. This gives you flexibility during development and immutability in release.
Enum generation works in a similar way to structs, but for the keys of a map.
// build.rs
use config_struct::{Error, EnumOptions};
fn main() -> Result<(), Error> {
config_struct::create_enum(
"items.yaml",
"src/items.rs",
&EnumOptions::default())
}
The above build script will take the following items.yaml
file and generate
a (not-formatted) items.rs
like the following:
# items.yaml
ItemOne:
- data
ItemTwo:
- more
- data
// items.rs
// ...
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Key {
ItemOne,
ItemTwo,
}
impl Key {
pub const ALL: &'static [Key] = &[Key::ItemOne, Key::ItemTwo];
}
impl Default for Key {
fn default() -> Self {
Self::ItemOne
}
}
impl std::fmt::Display for Key {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
impl std::str::FromStr for Key {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
const STRINGS: &'static [&'static str] = &["ItemOne", "ItemTwo"];
for (index, &key) in STRINGS.iter().enumerate() {
if key == s {
return Ok(Key::ALL[index]);
}
}
Err(())
}
}
As you can see, this provides more functionality out-of-the-box - most of
which could be disabled in the EnumOptions
. The intended purpose of
this is to have a small efficient type to use as a key into the data stored
in the initial config file.