use std::path::{Path, PathBuf};
use miette::Diagnostic;
use thiserror::Error;
use crate::config::{BoardConfig, FusibleConfig};
mod kind;
use kind::entry_from_conventions;
pub use kind::{is_kicad_target, is_tsx_target};
#[derive(Debug, Error, Diagnostic)]
pub enum TargetError {
#[error("no circuit file specified and no fusible.yml found")]
#[diagnostic(
code(fusible::target::missing),
help("specify a file via arguments or run `fusible init` to create a manifest")
)]
Missing,
#[error("this project has several boards: {}", names.join(", "))]
#[diagnostic(
code(fusible::target::ambiguous),
help("name the board to use, e.g. `fusible <command> {}`", names[0])
)]
Ambiguous { names: Vec<String> },
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct BoardTarget {
pub name: Option<String>,
pub entry: PathBuf,
pub title: String,
pub mcu: Option<String>,
pub firmware: Vec<String>,
pub pins_rs: Vec<PathBuf>,
pub port: Option<u16>,
pub out_dir: PathBuf,
}
pub fn resolve_entry(explicit: Option<PathBuf>) -> Result<PathBuf, TargetError> {
resolve_board(explicit).map(|board| board.entry)
}
pub fn resolve_board(explicit: Option<PathBuf>) -> Result<BoardTarget, TargetError> {
let mut boards = resolve_boards(explicit)?;
if boards.len() > 1 {
let names = boards.into_iter().filter_map(|b| b.name).collect();
return Err(TargetError::Ambiguous { names });
}
boards.pop().ok_or(TargetError::Missing)
}
pub fn resolve_boards(explicit: Option<PathBuf>) -> Result<Vec<BoardTarget>, TargetError> {
let cfg = FusibleConfig::nearest().map(|(_, c)| c).unwrap_or_default();
if let Some(arg) = explicit {
return Ok(vec![board_for(&cfg, arg)]);
}
if cfg.boards.is_empty() {
let entry = entry_from_conventions().ok_or(TargetError::Missing)?;
return Ok(vec![bare_board(&cfg, entry)]);
}
cfg.boards
.iter()
.map(|(name, board)| named_board(&cfg, name, board, None))
.collect()
}
pub fn board_for_entry(entry: &Path) -> BoardTarget {
let cfg = FusibleConfig::nearest().map(|(_, c)| c).unwrap_or_default();
board_for(&cfg, entry.to_path_buf())
}
fn board_for(cfg: &FusibleConfig, arg: PathBuf) -> BoardTarget {
let by_name = arg.to_str().and_then(|n| cfg.boards.get_key_value(n));
if let Some((name, board)) = by_name {
if let Ok(target) = named_board(cfg, name, board, None) {
return target;
}
}
let absolute = std::path::absolute(&arg).ok();
let owner = cfg.boards.iter().find(|(_, b)| {
let entry = b.entry.as_ref().or(b.circuit_json.as_ref());
entry.is_some() && entry.and_then(|e| std::path::absolute(e).ok()) == absolute
});
match owner.or_else(|| cfg.boards.iter().next().filter(|_| cfg.boards.len() == 1)) {
Some((name, board)) => named_board(cfg, name, board, Some(arg.clone()))
.unwrap_or_else(|_| bare_board(cfg, arg)),
None => bare_board(cfg, arg),
}
}
fn named_board(
cfg: &FusibleConfig,
name: &str,
board: &BoardConfig,
entry: Option<PathBuf>,
) -> Result<BoardTarget, TargetError> {
let entry = entry
.or_else(|| board.entry.clone())
.or_else(|| board.circuit_json.clone())
.or_else(entry_from_conventions)
.ok_or(TargetError::Missing)?;
let out_dir = board
.out_dir
.clone()
.unwrap_or_else(|| PathBuf::from("dist"));
Ok(BoardTarget {
name: Some(name.to_string()),
entry,
title: cfg.board_title(name),
mcu: board.mcu.clone(),
firmware: firmware_for(cfg, Some(name)),
pins_rs: pins_for(cfg, Some(name)),
port: board.port,
out_dir: match cfg.boards.len() {
0 | 1 => out_dir,
_ => out_dir.join(name),
},
})
}
fn bare_board(cfg: &FusibleConfig, entry: PathBuf) -> BoardTarget {
let (firmware, pins_rs) = match cfg.boards.is_empty() {
true => (firmware_for(cfg, None), pins_for(cfg, None)),
false => (Vec::new(), Vec::new()),
};
BoardTarget {
name: None,
entry,
title: cfg.name.clone().unwrap_or_else(|| "Hardware Board".into()),
firmware,
pins_rs,
out_dir: PathBuf::from("dist"),
..Default::default()
}
}
fn firmware_for(cfg: &FusibleConfig, board: Option<&str>) -> Vec<String> {
cfg.firmware_for(board)
.map(|(name, _)| name.clone())
.collect()
}
fn pins_for(cfg: &FusibleConfig, board: Option<&str>) -> Vec<PathBuf> {
cfg.firmware_for(board)
.filter_map(|(_, fw)| fw.pins_rs.clone())
.collect()
}