Skip to content

Commit e7d32b6

Browse files
committed
Add list-options and explain commands
1 parent 99bbbc5 commit e7d32b6

File tree

5 files changed

+146
-33
lines changed

5 files changed

+146
-33
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1212
### Added
1313

1414
- VSCode debugger launch configuration now sets `verifyBeforeFlashing` to `true` (#290)
15+
- esp-generate adds the selected template parameters to the generated code (#293)
16+
- Added `explain` and `list-options` subcommands (#293)
1517

1618
### Changed
1719

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ update-informer = { version = "1.3.0", optional = true }
3737
serde = { version = "1.0.228", features = ["derive"] }
3838
serde_yaml = "0.9.33"
3939
toml_edit = "0.23.9"
40+
indexmap = "2.13.0"
4041

4142
[build-dependencies]
4243
quote = "1.0.43"

README.md

Lines changed: 2 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -36,40 +36,12 @@ You can also directly download pre-compiled [release binaries] or use [`cargo-bi
3636
```
3737
Use the `--headless` flag to avoid using the TUI.
3838
39-
Replace the chip and project name accordingly, and select the desired options using the `-o/--option` flag. For a full list of available options, see [Available Options](#available-options) section of this README.
39+
Replace the chip and project name accordingly, and select the desired options using the `-o/--option` flag.
40+
Use the `esp-generate list-options` command to see a list of available options. Use `esp-generate explain <option>` to get a detailed explanation of an option.
4041
4142
[release binaries]: https://github.com/esp-rs/esp-generate/releases
4243
[`cargo-binstall`]: https://github.com/cargo-bins/cargo-binstall
4344
44-
## Available Options
45-
46-
- `unstable-hal`: Enables esp-hal features that may not be ready for general use yet.
47-
- `alloc`: Enables allocations via the `esp-alloc` crate.
48-
- `wifi`: Enables Wi-Fi via the `esp-radio` crate; requires `alloc`.
49-
- `ble-bleps`: Enables BLE via the `esp-radio` crate using `bleps`; requires `alloc`, mutually exclusive with `ble-trouble`.
50-
- `ble-trouble`: Enables BLE via the `esp-radio`crate using `embassy-trouble`; requires `alloc`, mutually exclusive with `ble-bleps`.
51-
- `embassy`: Adds `embassy` framework support.
52-
- `stack-smashing-protection`: Enables [stack smashing protection](https://doc.rust-lang.org/rustc/exploit-mitigations.html#stack-smashing-protection). Requires nightly Rust.
53-
- `probe-rs`: Replaces `espflash` with `probe-rs` and enables RTT-based options.
54-
- `flashing-probe-rs`: Contains options that require `probe-rs`:
55-
- `defmt`: Adds support for `defmt` printing. Uses `rtt-target` as the RTT implementation.
56-
- `panic-rtt-target`: Uses `panic-rtt-target` as the panic handler.
57-
- `embedded-test`: Enables `embedded-test` support and generates a simple demo test case.
58-
- `flashing-espflash`: Contains options that require `espflash`:
59-
- `log`: Uses the `log` library to print messages.
60-
- `defmt`: Adds support for `defmt` printing. Uses `esp-println` and configures `espflash` to decode `defmt` logs.
61-
- `esp-backtrace`: Uses `esp-backtrace` as the panic handler.
62-
- `optional`: Enables the following set of options:
63-
- `wokwi`: Adds support for Wokwi simulation using [VS Code Wokwi extension].
64-
- `ci` Adds GitHub Actions support with some basics checks.
65-
- `editors`: Select the editor integrations:
66-
- `helix`: The Helix editor
67-
- `neovim`: Neovim
68-
- `vscode`: Visual Studio Code
69-
- `zed`: The Zed editor
70-
71-
[VS Code Wokwi extension]: https://marketplace.visualstudio.com/items?itemName=wokwi.wokwi-vscode
72-
7345
## License
7446
7547
Licensed under either of:

src/main.rs

Lines changed: 140 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
use anyhow::{Result, bail};
2-
use clap::Parser;
2+
use clap::{Parser, Subcommand};
33
use env_logger::{Builder, Env};
4-
use esp_generate::template::{GeneratorOptionItem, Template};
4+
use esp_generate::template::{GeneratorOption, GeneratorOptionItem, Template};
55
use esp_generate::{
66
append_list_as_sentence,
77
config::{ActiveConfiguration, Relationships},
88
};
99
use esp_generate::{cargo, config::find_option};
1010
use esp_metadata::Chip;
11+
use indexmap::IndexMap;
1112
use inquire::{Select, Text};
1213
use ratatui::crossterm::event;
14+
use std::collections::HashSet;
1315
use std::{
1416
collections::HashMap,
1517
env, fs,
@@ -40,7 +42,7 @@ static TEMPLATE: LazyLock<Template> = LazyLock::new(|| {
4042
});
4143

4244
#[derive(Parser, Debug)]
43-
#[command(author, version, about = about(), long_about = None)]
45+
#[command(author, version, about = about(), long_about = None, subcommand_negates_reqs = true)]
4446
struct Args {
4547
/// Name of the project to generate
4648
name: Option<String>,
@@ -83,6 +85,136 @@ struct Args {
8385
/// Note that in headless mode this is not checked.
8486
#[arg(long)]
8587
toolchain: Option<String>,
88+
89+
#[clap(subcommand)]
90+
subcommands: Option<SubCommands>,
91+
}
92+
93+
#[derive(Subcommand, Debug)]
94+
enum SubCommands {
95+
/// List available template options
96+
ListOptions,
97+
98+
/// Print information about a template option
99+
Explain { option: String },
100+
}
101+
102+
impl SubCommands {
103+
fn handle(&self) -> Result<()> {
104+
fn chip_info_text(options: &[&GeneratorOption], opt: &GeneratorOption) -> String {
105+
let mut chips = Vec::new();
106+
for option in options.iter().filter(|o| o.name == opt.name) {
107+
chips.extend_from_slice(&option.chips);
108+
}
109+
110+
let chip_count = Chip::iter().count();
111+
112+
if chips.is_empty() || chips.len() == chip_count {
113+
String::new()
114+
} else if chips.len() < chip_count / 2 {
115+
format!(
116+
"Only available on {}.",
117+
chips
118+
.iter()
119+
.map(ToString::to_string)
120+
.collect::<Vec<_>>()
121+
.join(", ")
122+
)
123+
} else {
124+
format!(
125+
"Not available on {}.",
126+
Chip::iter()
127+
.filter(|c| !chips.contains(c))
128+
.map(|c| c.to_string())
129+
.collect::<Vec<_>>()
130+
.join(", ")
131+
)
132+
}
133+
}
134+
135+
match self {
136+
SubCommands::ListOptions => {
137+
println!(
138+
"The following template options are available. The group names are not part of the option name. Only one option in a group can be selected."
139+
);
140+
let mut groups = IndexMap::new();
141+
let mut seen = HashSet::new();
142+
let all_options = TEMPLATE.all_options();
143+
for (index, option) in all_options
144+
.iter()
145+
.enumerate()
146+
.filter(|(_, o)| !["toolchain", "module"].contains(&o.selection_group.as_str()))
147+
{
148+
let group = groups.entry(&option.selection_group).or_insert(Vec::new());
149+
150+
if seen.insert(&option.name) {
151+
group.push(index);
152+
}
153+
}
154+
for (group, options) in groups {
155+
println!("Group: {}", group);
156+
for option in options {
157+
let option = &all_options[option];
158+
let mut help_text = option.display_name.clone();
159+
160+
if !option.requires.is_empty() {
161+
help_text.push_str(" Requires: ");
162+
help_text.push_str(&option.requires.join(", "));
163+
help_text.push('.');
164+
}
165+
let chip_info = chip_info_text(&all_options, option);
166+
if !chip_info.is_empty() {
167+
help_text.push(' ');
168+
help_text.push_str(&chip_info);
169+
}
170+
println!(" {}: {help_text}", option.name);
171+
}
172+
}
173+
Ok(())
174+
}
175+
SubCommands::Explain { option } => {
176+
let all_options = TEMPLATE.all_options();
177+
if let Some(option) = all_options.iter().find(|o| &o.name == option) {
178+
println!(
179+
"Option: {}\n\n{}{}",
180+
option.name,
181+
option.display_name,
182+
if option.help.is_empty() {
183+
String::new()
184+
} else {
185+
format!("\n{}\n", option.help)
186+
}
187+
);
188+
if !option.requires.is_empty() {
189+
println!();
190+
let positive_req = option.requires.iter().filter(|r| !r.starts_with("!"));
191+
let negative_req = option.requires.iter().filter(|r| r.starts_with("!"));
192+
if positive_req.clone().next().is_some() {
193+
println!("Requires the following options to be set:");
194+
for require in positive_req {
195+
println!(" {}", require);
196+
}
197+
}
198+
if negative_req.clone().next().is_some() {
199+
println!("Requires the following options to NOT be set:");
200+
for require in negative_req {
201+
if let Some(stripped) = require.strip_prefix('!') {
202+
println!(" {}", stripped);
203+
}
204+
}
205+
}
206+
}
207+
let chip_info = chip_info_text(&all_options, option);
208+
if !chip_info.is_empty() {
209+
println!("{}", chip_info);
210+
}
211+
} else {
212+
println!("Unknown option: {}", option);
213+
}
214+
Ok(())
215+
}
216+
}
217+
}
86218
}
87219

88220
/// Check crates.io for a new version of the application
@@ -164,12 +296,17 @@ fn setup_args_interactive(args: &mut Args) -> Result<()> {
164296
}
165297

166298
fn main() -> Result<()> {
299+
// Set up logging.
167300
Builder::from_env(Env::default().default_filter_or(log::LevelFilter::Info.as_str()))
168301
.format_target(false)
169302
.init();
170303

171304
let mut args = Args::parse();
172305

306+
if let Some(subcommand) = args.subcommands {
307+
return subcommand.handle();
308+
}
309+
173310
// Only check for updates once the command-line arguments have been processed,
174311
// to avoid printing any update notifications when the help message is
175312
// displayed.

0 commit comments

Comments
 (0)