Skip to content

Commit

Permalink
Replace automatic option enable/disable with a list of requirements (#…
Browse files Browse the repository at this point in the history
…101)

* Refactor a bit

* Replace enables/disables with requires

* Use a single requires list

* Allow negative requirements
  • Loading branch information
bugadani authored Feb 10, 2025
1 parent 7e32ee9 commit f7d1559
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 66 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- Update `probe-rs run` arguments (#90)
- When using `embassy` option, `async_main.rs` file was renamed to `main.rs` (#93)
- The UI no longer allows selecting options with missing requirements, and does not allow deselecting
options that are required by other options. (#101)
- Options can now declare negative requirements (e.g. `!alloc` can not be enabled if `alloc` is used) (#101)

### Fixed

Expand Down
43 changes: 16 additions & 27 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@ pub struct GeneratorOption {
name: &'static str,
display_name: &'static str,
help: &'static str,
enables: &'static [&'static str],
disables: &'static [&'static str],
requires: &'static [&'static str],
chips: &'static [Chip],
}

Expand Down Expand Up @@ -90,10 +89,10 @@ impl GeneratorOptionItem {
}
}

fn enables(&self) -> &[&str] {
fn requires(&self) -> &[&str] {
match self {
GeneratorOptionItem::Category(_) => &[],
GeneratorOptionItem::Option(option) => option.enables,
GeneratorOptionItem::Option(option) => option.requires,
}
}

Expand All @@ -110,16 +109,14 @@ static OPTIONS: &[GeneratorOptionItem] = &[
name: "alloc",
display_name: "Enable allocations via the `esp-alloc` crate.",
help: "",
enables: &[],
disables: &[],
requires: &[],
chips: &[],
}),
GeneratorOptionItem::Option(GeneratorOption {
name: "wifi",
display_name: "Enable Wi-Fi via the `esp-wifi` crate.",
help: "Requires `alloc`. Not available on ESP32-H2.",
enables: &["alloc"],
disables: &[],
requires: &["alloc"],
chips: &[
Chip::Esp32,
Chip::Esp32c2,
Expand All @@ -133,8 +130,7 @@ static OPTIONS: &[GeneratorOptionItem] = &[
name: "ble",
display_name: "Enable BLE via the `esp-wifi` crate.",
help: "Requires `alloc`. Not available on ESP32-S2.",
enables: &["alloc"],
disables: &[],
requires: &["alloc"],
chips: &[
Chip::Esp32,
Chip::Esp32c2,
Expand All @@ -148,16 +144,14 @@ static OPTIONS: &[GeneratorOptionItem] = &[
name: "embassy",
display_name: "Add `embassy` framework support.",
help: "",
enables: &[],
disables: &[],
requires: &[],
chips: &[],
}),
GeneratorOptionItem::Option(GeneratorOption {
name: "probe-rs",
display_name: "Enable `defmt` and flashes using `probe-rs` instead of `espflash`.",
help: "",
enables: &[],
disables: &[],
requires: &[],
chips: &[],
}),
GeneratorOptionItem::Category(GeneratorOptionCategory {
Expand All @@ -169,8 +163,7 @@ static OPTIONS: &[GeneratorOptionItem] = &[
name: "wokwi",
display_name: "Add support for Wokwi simulation using VS Code Wokwi extension.",
help: "",
enables: &[],
disables: &[],
requires: &[],
chips: &[
Chip::Esp32,
Chip::Esp32c3,
Expand All @@ -184,16 +177,14 @@ static OPTIONS: &[GeneratorOptionItem] = &[
name: "dev-container",
display_name: "Add support for VS Code Dev Containers and GitHub Codespaces.",
help: "",
enables: &[],
disables: &[],
requires: &[],
chips: &[],
}),
GeneratorOptionItem::Option(GeneratorOption {
name: "ci",
display_name: "Add GitHub Actions support with some basic checks.",
help: "",
enables: &[],
disables: &[],
requires: &[],
chips: &[],
}),
],
Expand All @@ -207,16 +198,14 @@ static OPTIONS: &[GeneratorOptionItem] = &[
name: "helix",
display_name: "Add rust-analyzer settings for Helix Editor",
help: "",
enables: &[],
disables: &[],
requires: &[],
chips: &[],
}),
GeneratorOptionItem::Option(GeneratorOption {
name: "vscode",
display_name: "Add rust-analyzer settings for Visual Studio Code",
help: "",
enables: &[],
disables: &[],
requires: &[],
chips: &[],
}),
],
Expand Down Expand Up @@ -626,14 +615,14 @@ fn process_options(args: &Args) {
process::exit(-1);
}
if !option_item
.enables()
.requires()
.iter()
.all(|requirement| args.option.contains(&requirement.to_string()))
.all(|requirement| args.option.iter().any(|r| r == requirement))
{
log::error!(
"Option '{}' requires {}",
option_item.name(),
option_item.enables().join(", ")
option_item.requires().join(", ")
);
process::exit(-1);
}
Expand Down
134 changes: 95 additions & 39 deletions src/tui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,51 +49,104 @@ impl Repository {
current
}

fn select(&mut self, index: usize) {
fn enter_group(&mut self, index: usize) {
self.path.push(index);
}

fn toggle_current(&mut self, index: usize) {
let current = self.current_level()[index];
match current {
GeneratorOptionItem::Category(_) => unreachable!(),
GeneratorOptionItem::Option(option) => {
if !option.chips.is_empty() && !option.chips.contains(&self.chip) {
return;
}
fn is_selected(&self, option: &str) -> bool {
self.selected_index(option).is_some()
}

if let Some(i) = self.selected.iter().position(|v| v == option.name) {
self.selected.remove(i);
} else {
self.selected.push(option.name.to_string());
}
fn selected_index(&self, option: &str) -> Option<usize> {
self.selected.iter().position(|s| s == option)
}

let toggled_option = option;
let currently_selected = self.selected.clone();
for option in currently_selected {
let Some(option) = find_option(&option, self.options) else {
ratatui::restore();
panic!("option not found");
};
for enable in option.enables {
if !self.selected.contains(&enable.to_string()) {
self.selected.push(enable.to_string());
}
fn select(&mut self, option: &str) {
self.selected.push(option.to_string());
}

fn is_active(&self, level: &[GeneratorOptionItem], index: usize) -> bool {
match level[index] {
GeneratorOptionItem::Category(options) => {
for i in 0..options.options.len() {
if self.is_active(options.options, i) {
return true;
}
for disable in option.disables {
if disable != &toggled_option.name
&& self.selected.contains(&disable.to_string())
{
let Some(idx) = self.selected.iter().position(|v| v == disable) else {
ratatui::restore();
panic!("disable option not found");
};
self.selected.remove(idx);
}
}
false
}
GeneratorOptionItem::Option(option) => self.requirements_met(option),
}
}

fn toggle_current(&mut self, index: usize) {
if !self.is_active(self.current_level(), index) {
return;
}

let GeneratorOptionItem::Option(option) = self.current_level()[index] else {
ratatui::restore();
unreachable!();
};

if let Some(i) = self.selected_index(option.name) {
if self.can_be_disabled(option.name) {
self.selected.swap_remove(i);
}
} else if self.requirements_met(option) {
self.select(option.name);
}
}

fn requirements_met(&self, option: GeneratorOption) -> bool {
if !option.chips.is_empty() && !option.chips.contains(&self.chip) {
return false;
}

// Are this option's requirements met?
for &requirement in option.requires {
let (key, expected) = if let Some(requirement) = requirement.strip_prefix('!') {
(requirement, false)
} else {
(requirement, true)
};

if self.is_selected(key) != expected {
return false;
}
}

// Does any of the enabled options have a requirement against this one?
for selected in self.selected.iter() {
let Some(selected_option) = find_option(selected, self.options) else {
ratatui::restore();
panic!("selected option not found: {selected}");
};

for requirement in selected_option.requires.iter() {
if let Some(requirement) = requirement.strip_prefix('!') {
if requirement == option.name {
return false;
}
}
}
}

true
}

// An option can only be disabled if it's not required by any other selected option.
fn can_be_disabled(&self, option: &str) -> bool {
for selected in self.selected.iter() {
let Some(selected_option) = find_option(selected, self.options) else {
ratatui::restore();
panic!("selected option not found: {selected}");
};
if selected_option.requires.contains(&option) {
return false;
}
}
true
}

fn is_option(&self, index: usize) -> bool {
Expand All @@ -105,11 +158,14 @@ impl Repository {
}

fn current_level_desc(&self) -> Vec<(bool, String)> {
self.current_level()
let level = self.current_level();

level
.iter()
.map(|v| {
.enumerate()
.map(|(index, v)| {
(
v.chips().is_empty() || v.chips().contains(&self.chip),
self.is_active(level, index),
format!(
" {} {}",
if self.selected.contains(&v.name()) {
Expand Down Expand Up @@ -247,7 +303,7 @@ impl App {
if self.repository.is_option(selected) {
self.repository.toggle_current(selected);
} else {
self.repository.select(self.selected());
self.repository.enter_group(self.selected());
self.enter_menu();
}
}
Expand Down

0 comments on commit f7d1559

Please sign in to comment.