mod discovery;
pub mod error;
mod template;
#[cfg(test)]
mod tests;
use std::path::{Path, PathBuf};
use serde::{Deserialize, Serialize};
use discovery::{check_file_target, find_in_dir};
pub use error::ConfigError;
pub const CONFIG_FILENAMES: &[&str] = &["fusible.yml", "fusible.yaml"];
#[derive(Debug, Clone, Deserialize, Serialize, Default, PartialEq)]
pub struct FusibleConfig {
#[serde(default)]
pub name: Option<String>,
#[serde(default)]
pub hardware: Option<HardwareConfig>,
#[serde(default)]
pub mcu: Option<McuConfig>,
#[serde(default)]
pub firmware: Option<FirmwareConfig>,
#[serde(default)]
pub mechanical: Option<MechanicalConfig>,
}
#[derive(Debug, Clone, Deserialize, Serialize, Default, PartialEq, Eq)]
pub struct HardwareConfig {
#[serde(default)]
pub entry: Option<PathBuf>,
#[serde(default, alias = "out-dir")]
pub out_dir: Option<PathBuf>,
#[serde(default, alias = "circuit-json")]
pub circuit_json: Option<PathBuf>,
#[serde(default)]
pub port: Option<u16>,
}
#[derive(Debug, Clone, Deserialize, Serialize, Default, PartialEq, Eq)]
pub struct McuConfig {
#[serde(default)]
pub chip: Option<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize, Default, PartialEq, Eq)]
pub struct FirmwareConfig {
#[serde(default)]
pub path: Option<PathBuf>,
#[serde(default)]
pub framework: Option<String>,
#[serde(default, alias = "pins-rs")]
pub pins_rs: Option<PathBuf>,
#[serde(default, alias = "build-cmd")]
pub build_cmd: Option<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize, Default, PartialEq)]
pub struct MechanicalConfig {
#[serde(default)]
pub format: Option<String>,
#[serde(default, alias = "out-dir")]
pub out_dir: Option<PathBuf>,
#[serde(default, alias = "wall-thickness")]
pub wall_thickness: Option<f64>,
#[serde(default)]
pub height: Option<f64>,
#[serde(default)]
pub cutouts: Option<bool>,
#[serde(default)]
pub ventilation: Option<String>,
#[serde(default)]
pub mounting: Option<bool>,
}
impl FusibleConfig {
pub fn find_and_load(start_dir: &Path) -> Result<Option<(PathBuf, Self)>, ConfigError> {
if start_dir.is_file() {
if let Some(found) = check_file_target(start_dir)? {
return Ok(Some(found));
}
}
let start_dir =
std::path::absolute(start_dir).map_err(|err| ConfigError::io(start_dir, err))?;
let dir = if start_dir.is_file() {
start_dir.parent().unwrap_or(&start_dir)
} else {
&start_dir
};
for ancestor in dir.ancestors() {
if let Some(found) = find_in_dir(ancestor)? {
return Ok(Some(found));
}
}
Ok(None)
}
fn rebased(mut self, dir: &Path) -> Self {
let rebase = |p: &mut Option<PathBuf>| {
if let Some(path) = p.as_mut().filter(|path| path.is_relative()) {
*path = dir.join(&*path);
}
};
if let Some(hw) = self.hardware.as_mut() {
rebase(&mut hw.entry);
rebase(&mut hw.out_dir);
rebase(&mut hw.circuit_json);
}
if let Some(fw) = self.firmware.as_mut() {
rebase(&mut fw.path);
rebase(&mut fw.pins_rs);
}
if let Some(mech) = self.mechanical.as_mut() {
rebase(&mut mech.out_dir);
}
self
}
pub fn from_file(path: &Path) -> Result<Self, ConfigError> {
let content = std::fs::read_to_string(path).map_err(|err| ConfigError::io(path, err))?;
let config = Self::from_str_named(&content, &path.display().to_string())?;
Ok(config.rebased(path.parent().unwrap_or(Path::new(""))))
}
#[allow(clippy::should_implement_trait)]
pub fn from_str(content: &str) -> Result<Self, ConfigError> {
Self::from_str_named(content, "fusible.yml")
}
pub fn from_str_named(content: &str, file_name: &str) -> Result<Self, ConfigError> {
if content.trim().is_empty() {
return Ok(Self::default());
}
let config: Option<Self> = serde_yaml::from_str(content)
.map_err(|err| ConfigError::from_yaml(err, content, file_name))?;
Ok(config.unwrap_or_default())
}
}
impl std::str::FromStr for FusibleConfig {
type Err = ConfigError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::from_str_named(s, "fusible.yml")
}
}
impl FusibleConfig {
pub fn default_manifest() -> &'static str {
template::DEFAULT_MANIFEST
}
}