654 lines
21 KiB
Rust
654 lines
21 KiB
Rust
use serde::{Deserialize, Serialize};
|
|
use std::collections::HashMap;
|
|
use std::path::{Path, PathBuf};
|
|
use std::fs;
|
|
use anyhow::{Result, Context, bail};
|
|
|
|
use crate::version::ANVIL_VERSION;
|
|
|
|
pub const CONFIG_FILENAME: &str = ".anvil.toml";
|
|
|
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
pub struct ProjectConfig {
|
|
pub project: ProjectMeta,
|
|
pub build: BuildConfig,
|
|
pub monitor: MonitorConfig,
|
|
#[serde(default)]
|
|
pub boards: HashMap<String, BoardProfile>,
|
|
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
|
|
pub pins: HashMap<String, BoardPinConfig>,
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
pub struct ProjectMeta {
|
|
pub name: String,
|
|
pub anvil_version: String,
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
pub struct BuildConfig {
|
|
/// Name of the default board from [boards.*]
|
|
#[serde(default)]
|
|
pub default: String,
|
|
pub warnings: String,
|
|
pub include_dirs: Vec<String>,
|
|
pub extra_flags: Vec<String>,
|
|
/// Legacy: FQBN used to live here directly. Now lives in [boards.*].
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub fqbn: Option<String>,
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
pub struct MonitorConfig {
|
|
pub baud: u32,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub port: Option<String>,
|
|
}
|
|
|
|
/// A named board with its FQBN and optional baud override.
|
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
pub struct BoardProfile {
|
|
pub fqbn: String,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub baud: Option<u32>,
|
|
}
|
|
|
|
/// Pin configuration for a specific board.
|
|
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
|
pub struct BoardPinConfig {
|
|
/// Individual pin assignments: name -> { pin, mode }
|
|
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
|
|
pub assignments: HashMap<String, PinAssignment>,
|
|
/// Bus group reservations: "spi" -> { cs = 10 }, "i2c" -> {}
|
|
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
|
|
pub buses: HashMap<String, BusConfig>,
|
|
}
|
|
|
|
/// A single pin assignment with a friendly name.
|
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
pub struct PinAssignment {
|
|
pub pin: u8,
|
|
pub mode: String,
|
|
}
|
|
|
|
/// Configuration for a claimed bus group.
|
|
/// User-selectable pins (like CS for SPI) are stored as flattened keys.
|
|
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
|
pub struct BusConfig {
|
|
#[serde(flatten)]
|
|
pub user_pins: HashMap<String, u8>,
|
|
}
|
|
|
|
impl ProjectConfig {
|
|
/// Create a new project config with sensible defaults.
|
|
pub fn new(name: &str) -> Self {
|
|
let mut boards = HashMap::new();
|
|
boards.insert("uno".to_string(), BoardProfile {
|
|
fqbn: "arduino:avr:uno".to_string(),
|
|
baud: None,
|
|
});
|
|
Self {
|
|
project: ProjectMeta {
|
|
name: name.to_string(),
|
|
anvil_version: ANVIL_VERSION.to_string(),
|
|
},
|
|
build: BuildConfig {
|
|
default: "uno".to_string(),
|
|
fqbn: None,
|
|
warnings: "more".to_string(),
|
|
include_dirs: vec!["lib/hal".to_string(), "lib/app".to_string()],
|
|
extra_flags: vec!["-Werror".to_string()],
|
|
},
|
|
monitor: MonitorConfig {
|
|
baud: 115200,
|
|
port: None,
|
|
},
|
|
boards,
|
|
pins: HashMap::new(),
|
|
}
|
|
}
|
|
|
|
/// Create a new project config with a specific board preset.
|
|
pub fn new_with_board(name: &str, board_name: &str, fqbn: &str, baud: u32) -> Self {
|
|
let mut config = Self::new(name);
|
|
config.build.default = board_name.to_string();
|
|
config.boards.clear();
|
|
config.boards.insert(board_name.to_string(), BoardProfile {
|
|
fqbn: fqbn.to_string(),
|
|
baud: None,
|
|
});
|
|
config.monitor.baud = baud;
|
|
config
|
|
}
|
|
|
|
/// Get the default board's FQBN and baud.
|
|
pub fn default_board(&self) -> Result<(String, u32)> {
|
|
self.resolve_board(&self.build.default)
|
|
}
|
|
|
|
/// Get the default board's FQBN.
|
|
pub fn default_fqbn(&self) -> Result<String> {
|
|
let (fqbn, _) = self.default_board()?;
|
|
Ok(fqbn)
|
|
}
|
|
|
|
/// Resolve the FQBN and baud for a named board.
|
|
/// Returns (fqbn, baud).
|
|
pub fn resolve_board(&self, board_name: &str) -> Result<(String, u32)> {
|
|
match self.boards.get(board_name) {
|
|
Some(profile) => {
|
|
let baud = profile.baud.unwrap_or(self.monitor.baud);
|
|
Ok((profile.fqbn.clone(), baud))
|
|
}
|
|
None => {
|
|
let available: Vec<&String> = self.boards.keys().collect();
|
|
if available.is_empty() {
|
|
bail!(
|
|
"No board '{}' found in .anvil.toml.\n \
|
|
No boards are defined. Add one: anvil board --add {}",
|
|
board_name, board_name
|
|
);
|
|
} else {
|
|
bail!(
|
|
"No board '{}' found in .anvil.toml.\n \
|
|
Available: {}\n \
|
|
Add it: anvil board --add {}",
|
|
board_name,
|
|
available.iter().map(|s| s.as_str()).collect::<Vec<_>>().join(", "),
|
|
board_name
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Load config from a project directory.
|
|
pub fn load(project_root: &Path) -> Result<Self> {
|
|
let config_path = project_root.join(CONFIG_FILENAME);
|
|
if !config_path.exists() {
|
|
bail!(
|
|
"Not an Anvil project (missing {}).\n\
|
|
Create one with: anvil new <name>",
|
|
CONFIG_FILENAME
|
|
);
|
|
}
|
|
let contents = fs::read_to_string(&config_path)
|
|
.context(format!("Failed to read {}", config_path.display()))?;
|
|
let mut config: ProjectConfig = toml::from_str(&contents)
|
|
.context(format!("Failed to parse {}", config_path.display()))?;
|
|
|
|
// Migrate old format: fqbn in [build] -> [boards.X] + default
|
|
if config.build.default.is_empty() {
|
|
if let Some(ref legacy_fqbn) = config.build.fqbn {
|
|
let board_name = crate::board::presets::PRESETS.iter()
|
|
.find(|p| p.fqbn == *legacy_fqbn)
|
|
.map(|p| p.name.to_string())
|
|
.unwrap_or_else(|| "default".to_string());
|
|
|
|
// Text-based migration of the actual file
|
|
migrate_config_file(&config_path, &board_name, legacy_fqbn)?;
|
|
|
|
// Update in-memory config to match
|
|
let fqbn_clone = legacy_fqbn.clone();
|
|
config.build.fqbn = None;
|
|
config.build.default = board_name.clone();
|
|
config.boards.entry(board_name).or_insert(BoardProfile {
|
|
fqbn: fqbn_clone,
|
|
baud: None,
|
|
});
|
|
}
|
|
}
|
|
|
|
Ok(config)
|
|
}
|
|
|
|
/// Save config to a project directory.
|
|
pub fn save(&self, project_root: &Path) -> Result<()> {
|
|
let config_path = project_root.join(CONFIG_FILENAME);
|
|
let contents = toml::to_string_pretty(self)
|
|
.context("Failed to serialize config")?;
|
|
fs::write(&config_path, contents)
|
|
.context(format!("Failed to write {}", config_path.display()))?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Walk up from a directory to find the project root containing .anvil.toml.
|
|
pub fn find_project_root(start: &Path) -> Result<PathBuf> {
|
|
let mut dir = if start.is_absolute() {
|
|
start.to_path_buf()
|
|
} else {
|
|
std::env::current_dir()?.join(start)
|
|
};
|
|
|
|
for _ in 0..10 {
|
|
if dir.join(CONFIG_FILENAME).exists() {
|
|
return Ok(dir);
|
|
}
|
|
match dir.parent() {
|
|
Some(parent) => dir = parent.to_path_buf(),
|
|
None => break,
|
|
}
|
|
}
|
|
|
|
bail!(
|
|
"No {} found in {} or any parent directory.\n\
|
|
Create a project with: anvil new <name>",
|
|
CONFIG_FILENAME,
|
|
start.display()
|
|
);
|
|
}
|
|
|
|
/// Resolve include directories to absolute paths relative to project root.
|
|
pub fn resolve_include_flags(&self, project_root: &Path) -> Vec<String> {
|
|
let mut flags = Vec::new();
|
|
for dir in &self.build.include_dirs {
|
|
let abs = project_root.join(dir);
|
|
if abs.is_dir() {
|
|
flags.push(format!("-I{}", abs.display()));
|
|
}
|
|
}
|
|
flags
|
|
}
|
|
|
|
/// Build the full extra_flags string for arduino-cli.
|
|
pub fn extra_flags_string(&self, project_root: &Path) -> String {
|
|
let mut parts = self.resolve_include_flags(project_root);
|
|
for flag in &self.build.extra_flags {
|
|
parts.push(flag.clone());
|
|
}
|
|
parts.join(" ")
|
|
}
|
|
}
|
|
|
|
impl Default for ProjectConfig {
|
|
fn default() -> Self {
|
|
Self::new("untitled")
|
|
}
|
|
}
|
|
|
|
/// Return the Anvil home directory (~/.anvil).
|
|
pub fn anvil_home() -> Result<PathBuf> {
|
|
let home = dirs::home_dir()
|
|
.context("Could not determine home directory")?;
|
|
let anvil_dir = home.join(".anvil");
|
|
fs::create_dir_all(&anvil_dir)?;
|
|
Ok(anvil_dir)
|
|
}
|
|
|
|
// -- Text-based .anvil.toml editing -----------------------------------------
|
|
// These operate on the raw file text to preserve comments and formatting.
|
|
|
|
/// Migrate an old-format config: add default = "X" to [build] and add
|
|
/// [boards.X] section if needed. Keeps old fqbn line for backward
|
|
/// compatibility with scripts that haven't been refreshed yet.
|
|
fn migrate_config_file(config_path: &Path, board_name: &str, fqbn: &str) -> Result<()> {
|
|
let content = fs::read_to_string(config_path)
|
|
.context("Failed to read .anvil.toml for migration")?;
|
|
|
|
let mut output = String::new();
|
|
let mut in_build = false;
|
|
let mut added_default = false;
|
|
let mut has_board_section = false;
|
|
|
|
// Check if [boards.NAME] already exists
|
|
let section_header = format!("[boards.{}]", board_name);
|
|
for line in content.lines() {
|
|
if line.trim() == section_header {
|
|
has_board_section = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
for line in content.lines() {
|
|
let trimmed = line.trim();
|
|
|
|
// Track which section we're in
|
|
if trimmed == "[build]" {
|
|
in_build = true;
|
|
output.push_str(line);
|
|
output.push('\n');
|
|
continue;
|
|
}
|
|
if trimmed.starts_with('[') && trimmed != "[build]" {
|
|
// Leaving [build] -- add default before leaving if we haven't yet
|
|
if in_build && !added_default {
|
|
output.push_str(&format!("default = \"{}\"\n", board_name));
|
|
added_default = true;
|
|
}
|
|
in_build = false;
|
|
}
|
|
|
|
// In [build]: add default right before fqbn (natural reading order)
|
|
if in_build && !added_default && trimmed.starts_with("fqbn") && trimmed.contains('=') {
|
|
output.push_str(&format!("default = \"{}\"\n", board_name));
|
|
added_default = true;
|
|
}
|
|
|
|
output.push_str(line);
|
|
output.push('\n');
|
|
}
|
|
|
|
// Add [boards.NAME] section if it doesn't already exist
|
|
if !has_board_section {
|
|
while output.ends_with("\n\n") {
|
|
output.pop();
|
|
}
|
|
output.push('\n');
|
|
output.push_str(&format!("\n[boards.{}]\n", board_name));
|
|
output.push_str(&format!("fqbn = \"{}\"\n", fqbn));
|
|
}
|
|
|
|
fs::write(config_path, &output)
|
|
.context("Failed to write migrated .anvil.toml")?;
|
|
|
|
eprintln!(
|
|
"\x1b[33minfo\x1b[0m Migrated .anvil.toml: default = \"{}\"{}",
|
|
board_name,
|
|
if has_board_section { "" } else { ", added [boards] section" }
|
|
);
|
|
eprintln!(
|
|
"\x1b[33minfo\x1b[0m Run \x1b[36manvil refresh --force\x1b[0m to update scripts."
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Set or change the default board in .anvil.toml (text-based edit).
|
|
/// Returns the old default name (empty string if there wasn't one).
|
|
pub fn set_default_in_file(config_path: &Path, board_name: &str) -> Result<String> {
|
|
let content = fs::read_to_string(config_path)
|
|
.context("Failed to read .anvil.toml")?;
|
|
|
|
let mut output = String::new();
|
|
let mut in_build = false;
|
|
let mut replaced = false;
|
|
let mut old_default = String::new();
|
|
|
|
for line in content.lines() {
|
|
let trimmed = line.trim();
|
|
|
|
if trimmed == "[build]" {
|
|
in_build = true;
|
|
output.push_str(line);
|
|
output.push('\n');
|
|
continue;
|
|
}
|
|
if trimmed.starts_with('[') && trimmed != "[build]" {
|
|
// Leaving [build] without having found default -- add it
|
|
if in_build && !replaced {
|
|
output.push_str(&format!("default = \"{}\"\n", board_name));
|
|
replaced = true;
|
|
}
|
|
in_build = false;
|
|
}
|
|
|
|
if in_build && trimmed.starts_with("default") && trimmed.contains('=') {
|
|
// Extract old value
|
|
if let Some(val) = trimmed.split('=').nth(1) {
|
|
old_default = val.trim().trim_matches('"').to_string();
|
|
}
|
|
output.push_str(&format!("default = \"{}\"\n", board_name));
|
|
replaced = true;
|
|
continue;
|
|
}
|
|
|
|
output.push_str(line);
|
|
output.push('\n');
|
|
}
|
|
|
|
// Edge case: [build] was the last section and had no default
|
|
if in_build && !replaced {
|
|
output.push_str(&format!("default = \"{}\"\n", board_name));
|
|
}
|
|
|
|
fs::write(config_path, &output)
|
|
.context("Failed to write .anvil.toml")?;
|
|
|
|
Ok(old_default)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use tempfile::TempDir;
|
|
|
|
#[test]
|
|
fn test_new_config_defaults() {
|
|
let config = ProjectConfig::new("test_project");
|
|
assert_eq!(config.project.name, "test_project");
|
|
assert_eq!(config.build.default, "uno");
|
|
assert_eq!(config.monitor.baud, 115200);
|
|
assert!(config.build.include_dirs.contains(&"lib/hal".to_string()));
|
|
assert!(config.boards.contains_key("uno"));
|
|
assert_eq!(config.boards["uno"].fqbn, "arduino:avr:uno");
|
|
}
|
|
|
|
#[test]
|
|
fn test_save_and_load() {
|
|
let tmp = TempDir::new().unwrap();
|
|
let config = ProjectConfig::new("roundtrip");
|
|
config.save(tmp.path()).unwrap();
|
|
|
|
let loaded = ProjectConfig::load(tmp.path()).unwrap();
|
|
assert_eq!(loaded.project.name, "roundtrip");
|
|
assert_eq!(loaded.build.default, "uno");
|
|
assert_eq!(loaded.monitor.baud, config.monitor.baud);
|
|
assert!(loaded.boards.contains_key("uno"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_new_with_board() {
|
|
let config = ProjectConfig::new_with_board(
|
|
"test", "mega", "arduino:avr:mega:cpu=atmega2560", 115200
|
|
);
|
|
assert_eq!(config.build.default, "mega");
|
|
assert!(config.boards.contains_key("mega"));
|
|
assert_eq!(config.boards["mega"].fqbn, "arduino:avr:mega:cpu=atmega2560");
|
|
assert!(!config.boards.contains_key("uno"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_find_project_root() {
|
|
let tmp = TempDir::new().unwrap();
|
|
let config = ProjectConfig::new("finder");
|
|
config.save(tmp.path()).unwrap();
|
|
|
|
let sub = tmp.path().join("sketch").join("deep");
|
|
fs::create_dir_all(&sub).unwrap();
|
|
|
|
let found = ProjectConfig::find_project_root(&sub).unwrap();
|
|
assert_eq!(found, tmp.path());
|
|
}
|
|
|
|
#[test]
|
|
fn test_find_project_root_not_found() {
|
|
let tmp = TempDir::new().unwrap();
|
|
let result = ProjectConfig::find_project_root(tmp.path());
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_resolve_include_flags() {
|
|
let tmp = TempDir::new().unwrap();
|
|
fs::create_dir_all(tmp.path().join("lib/hal")).unwrap();
|
|
fs::create_dir_all(tmp.path().join("lib/app")).unwrap();
|
|
|
|
let config = ProjectConfig::new("includes");
|
|
let flags = config.resolve_include_flags(tmp.path());
|
|
|
|
assert_eq!(flags.len(), 2);
|
|
assert!(flags[0].starts_with("-I"));
|
|
assert!(flags[0].contains("lib"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_extra_flags_string() {
|
|
let tmp = TempDir::new().unwrap();
|
|
fs::create_dir_all(tmp.path().join("lib/hal")).unwrap();
|
|
fs::create_dir_all(tmp.path().join("lib/app")).unwrap();
|
|
|
|
let config = ProjectConfig::new("flags");
|
|
let flags = config.extra_flags_string(tmp.path());
|
|
|
|
assert!(flags.contains("-Werror"));
|
|
assert!(flags.contains("-I"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_default_board() {
|
|
let config = ProjectConfig::new("test");
|
|
let (fqbn, baud) = config.default_board().unwrap();
|
|
assert_eq!(fqbn, "arduino:avr:uno");
|
|
assert_eq!(baud, 115200);
|
|
}
|
|
|
|
#[test]
|
|
fn test_resolve_board_named() {
|
|
let mut config = ProjectConfig::new("test");
|
|
config.boards.insert("mega".to_string(), BoardProfile {
|
|
fqbn: "arduino:avr:mega:cpu=atmega2560".to_string(),
|
|
baud: Some(9600),
|
|
});
|
|
|
|
let (fqbn, baud) = config.resolve_board("mega").unwrap();
|
|
assert_eq!(fqbn, "arduino:avr:mega:cpu=atmega2560");
|
|
assert_eq!(baud, 9600);
|
|
}
|
|
|
|
#[test]
|
|
fn test_resolve_board_inherits_baud() {
|
|
let mut config = ProjectConfig::new("test");
|
|
config.boards.insert("nano".to_string(), BoardProfile {
|
|
fqbn: "arduino:avr:nano:cpu=atmega328".to_string(),
|
|
baud: None,
|
|
});
|
|
|
|
let (fqbn, baud) = config.resolve_board("nano").unwrap();
|
|
assert_eq!(fqbn, "arduino:avr:nano:cpu=atmega328");
|
|
assert_eq!(baud, 115200);
|
|
}
|
|
|
|
#[test]
|
|
fn test_resolve_board_unknown() {
|
|
let config = ProjectConfig::new("test");
|
|
assert!(config.resolve_board("esp32").is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_board_roundtrip() {
|
|
let tmp = TempDir::new().unwrap();
|
|
let mut config = ProjectConfig::new("multi");
|
|
config.boards.insert("mega".to_string(), BoardProfile {
|
|
fqbn: "arduino:avr:mega:cpu=atmega2560".to_string(),
|
|
baud: Some(57600),
|
|
});
|
|
config.save(tmp.path()).unwrap();
|
|
|
|
let loaded = ProjectConfig::load(tmp.path()).unwrap();
|
|
assert!(loaded.boards.contains_key("mega"));
|
|
let mega = &loaded.boards["mega"];
|
|
assert_eq!(mega.fqbn, "arduino:avr:mega:cpu=atmega2560");
|
|
assert_eq!(mega.baud, Some(57600));
|
|
}
|
|
|
|
#[test]
|
|
fn test_migrate_old_format() {
|
|
let tmp = TempDir::new().unwrap();
|
|
let old_config = r#"[project]
|
|
name = "hand"
|
|
anvil_version = "1.0.0"
|
|
|
|
[build]
|
|
fqbn = "arduino:avr:uno"
|
|
warnings = "more"
|
|
include_dirs = ["lib/hal", "lib/app"]
|
|
extra_flags = ["-Werror"]
|
|
|
|
[monitor]
|
|
baud = 115200
|
|
|
|
[boards.micro]
|
|
fqbn = "arduino:avr:micro"
|
|
"#;
|
|
fs::write(tmp.path().join(CONFIG_FILENAME), old_config).unwrap();
|
|
|
|
let config = ProjectConfig::load(tmp.path()).unwrap();
|
|
assert_eq!(config.build.default, "uno");
|
|
assert!(config.boards.contains_key("uno"));
|
|
assert_eq!(config.boards["uno"].fqbn, "arduino:avr:uno");
|
|
assert!(config.boards.contains_key("micro"));
|
|
|
|
// Verify the file was rewritten
|
|
let content = fs::read_to_string(tmp.path().join(CONFIG_FILENAME)).unwrap();
|
|
assert!(content.contains("default = \"uno\""));
|
|
assert!(content.contains("[boards.uno]"));
|
|
// Old fqbn stays in [build] for backward compat with old scripts
|
|
assert!(content.contains("fqbn = \"arduino:avr:uno\""));
|
|
}
|
|
|
|
#[test]
|
|
fn test_migrate_preserves_existing_boards() {
|
|
let tmp = TempDir::new().unwrap();
|
|
let old_config = r#"[project]
|
|
name = "test"
|
|
anvil_version = "1.0.0"
|
|
|
|
[build]
|
|
fqbn = "arduino:avr:mega:cpu=atmega2560"
|
|
warnings = "more"
|
|
include_dirs = ["lib/hal", "lib/app"]
|
|
extra_flags = ["-Werror"]
|
|
|
|
[monitor]
|
|
baud = 115200
|
|
"#;
|
|
fs::write(tmp.path().join(CONFIG_FILENAME), old_config).unwrap();
|
|
|
|
let config = ProjectConfig::load(tmp.path()).unwrap();
|
|
assert_eq!(config.build.default, "mega");
|
|
assert!(config.boards.contains_key("mega"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_set_default_in_file() {
|
|
let tmp = TempDir::new().unwrap();
|
|
let config = ProjectConfig::new("test");
|
|
config.save(tmp.path()).unwrap();
|
|
|
|
let config_path = tmp.path().join(CONFIG_FILENAME);
|
|
let old = set_default_in_file(&config_path, "mega").unwrap();
|
|
assert_eq!(old, "uno");
|
|
|
|
let content = fs::read_to_string(&config_path).unwrap();
|
|
assert!(content.contains("default = \"mega\""));
|
|
assert!(!content.contains("default = \"uno\""));
|
|
}
|
|
|
|
#[test]
|
|
fn test_set_default_adds_when_missing() {
|
|
let tmp = TempDir::new().unwrap();
|
|
let content = r#"[project]
|
|
name = "test"
|
|
anvil_version = "1.0.0"
|
|
|
|
[build]
|
|
warnings = "more"
|
|
include_dirs = ["lib/hal", "lib/app"]
|
|
extra_flags = ["-Werror"]
|
|
|
|
[monitor]
|
|
baud = 115200
|
|
|
|
[boards.uno]
|
|
fqbn = "arduino:avr:uno"
|
|
"#;
|
|
let config_path = tmp.path().join(CONFIG_FILENAME);
|
|
fs::write(&config_path, content).unwrap();
|
|
|
|
let old = set_default_in_file(&config_path, "uno").unwrap();
|
|
assert_eq!(old, "");
|
|
|
|
let result = fs::read_to_string(&config_path).unwrap();
|
|
assert!(result.contains("default = \"uno\""));
|
|
}
|
|
} |