mirror of
https://github.com/edera-dev/sprout.git
synced 2025-12-19 15:40:16 +00:00
implement new argument parser with --help support
This commit is contained in:
@@ -2,6 +2,8 @@
|
|||||||
#![feature(uefi_std)]
|
#![feature(uefi_std)]
|
||||||
|
|
||||||
use crate::context::{RootContext, SproutContext};
|
use crate::context::{RootContext, SproutContext};
|
||||||
|
use crate::options::SproutOptions;
|
||||||
|
use crate::options::parser::OptionsRepresentable;
|
||||||
use crate::phases::phase;
|
use crate::phases::phase;
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use log::info;
|
use log::info;
|
||||||
@@ -51,7 +53,7 @@ fn main() -> Result<()> {
|
|||||||
setup::init()?;
|
setup::init()?;
|
||||||
|
|
||||||
// Parse the options to the sprout executable.
|
// Parse the options to the sprout executable.
|
||||||
let options = options::parser::parse().context("unable to parse options")?;
|
let options = SproutOptions::parse().context("unable to parse options")?;
|
||||||
|
|
||||||
// Load the configuration of sprout.
|
// Load the configuration of sprout.
|
||||||
// At this point, the configuration has been validated and the specified
|
// At this point, the configuration has been validated and the specified
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
use crate::options::parser::{OptionDescription, OptionForm, OptionsRepresentable};
|
||||||
|
use anyhow::{Context, Result, bail};
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
/// The Sprout options parser.
|
/// The Sprout options parser.
|
||||||
pub mod parser;
|
pub mod parser;
|
||||||
|
|
||||||
@@ -22,3 +26,59 @@ impl Default for SproutOptions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The options parser mechanism for Sprout.
|
||||||
|
impl OptionsRepresentable for SproutOptions {
|
||||||
|
/// Produce the [SproutOptions] structure.
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
/// All the Sprout options that are defined.
|
||||||
|
fn options() -> &'static [(&'static str, OptionDescription<'static>)] {
|
||||||
|
&[
|
||||||
|
(
|
||||||
|
"config",
|
||||||
|
OptionDescription {
|
||||||
|
description: "Path to Sprout configuration file",
|
||||||
|
form: OptionForm::Value,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"boot",
|
||||||
|
OptionDescription {
|
||||||
|
description: "Entry to boot, bypassing the menu",
|
||||||
|
form: OptionForm::Value,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"help",
|
||||||
|
OptionDescription {
|
||||||
|
description: "Display Sprout Help",
|
||||||
|
form: OptionForm::Help,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Produces [SproutOptions] from the parsed raw `options` map.
|
||||||
|
fn produce(options: BTreeMap<String, Option<String>>) -> Result<Self> {
|
||||||
|
// Use the default value of sprout options and have the raw options be parsed into it.
|
||||||
|
let mut result = Self::default();
|
||||||
|
|
||||||
|
for (key, value) in options {
|
||||||
|
match key.as_str() {
|
||||||
|
"config" => {
|
||||||
|
// The configuration file to load.
|
||||||
|
result.config = value.context("--config option requires a value")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
"boot" => {
|
||||||
|
// The entry to boot.
|
||||||
|
result.boot = Some(value.context("--boot option requires a value")?);
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => bail!("unknown option: --{key}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,11 +1,49 @@
|
|||||||
use crate::options::SproutOptions;
|
|
||||||
use anyhow::{Context, Result, bail};
|
use anyhow::{Context, Result, bail};
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
/// For minimalism, we don't want a full argument parser. Instead, we use
|
/// The type of option. This disambiguates different behavior
|
||||||
/// a simple --xyz = xyz: None and --abc 123 = abc: Some("123") format.
|
/// of how options are handled.
|
||||||
/// We also support --abc=123 = abc: Some("123") format.
|
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)]
|
||||||
fn parse_raw() -> Result<BTreeMap<String, Option<String>>> {
|
pub enum OptionForm {
|
||||||
|
/// A flag, like --verbose.
|
||||||
|
Flag,
|
||||||
|
/// A value, in the form --abc 123 or --abc=123.
|
||||||
|
Value,
|
||||||
|
/// Help flag, like --help.
|
||||||
|
Help,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The description of an option, used in the options parser
|
||||||
|
/// to make decisions about how to progress.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct OptionDescription<'a> {
|
||||||
|
/// The description of the option.
|
||||||
|
pub description: &'a str,
|
||||||
|
/// The type of option to parse as.
|
||||||
|
pub form: OptionForm,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents a type that can be parsed from command line arguments.
|
||||||
|
/// This is a super minimal options parser mechanism just for Sprout.
|
||||||
|
pub trait OptionsRepresentable {
|
||||||
|
/// The output type that parsing will produce.
|
||||||
|
type Output;
|
||||||
|
|
||||||
|
/// The configured options for this type. This should describe all the options
|
||||||
|
/// that are valid to produce the type. The left hand side is the name of the option,
|
||||||
|
/// and the right hand side is the description.
|
||||||
|
fn options() -> &'static [(&'static str, OptionDescription<'static>)];
|
||||||
|
|
||||||
|
/// Produces the type by taking the `options` and processing it into the output.
|
||||||
|
fn produce(options: BTreeMap<String, Option<String>>) -> Result<Self::Output>;
|
||||||
|
|
||||||
|
/// For minimalism, we don't want a full argument parser. Instead, we use
|
||||||
|
/// a simple --xyz = xyz: None and --abc 123 = abc: Some("123") format.
|
||||||
|
/// We also support --abc=123 = abc: Some("123") format.
|
||||||
|
fn parse_raw() -> Result<BTreeMap<String, Option<String>>> {
|
||||||
|
// Access the configured options for this type.
|
||||||
|
let configured: BTreeMap<_, _> = BTreeMap::from_iter(Self::options().to_vec());
|
||||||
|
|
||||||
// Collect all the arguments to Sprout.
|
// Collect all the arguments to Sprout.
|
||||||
// Skip the first argument which is the path to our executable.
|
// Skip the first argument which is the path to our executable.
|
||||||
let args = std::env::args().skip(1).collect::<Vec<_>>();
|
let args = std::env::args().skip(1).collect::<Vec<_>>();
|
||||||
@@ -45,7 +83,18 @@ fn parse_raw() -> Result<BTreeMap<String, Option<String>>> {
|
|||||||
value = Some(part_value);
|
value = Some(part_value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if value.is_none() {
|
// Error on empty option names.
|
||||||
|
if option.is_empty() {
|
||||||
|
bail!("invalid empty option");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the description of the configured option, if any.
|
||||||
|
let Some(description) = configured.get(option.as_str()) else {
|
||||||
|
bail!("invalid option: --{option}");
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check if the option requires a value and error if none was provided.
|
||||||
|
if description.form == OptionForm::Value && value.is_none() {
|
||||||
// Check for the next value.
|
// Check for the next value.
|
||||||
let maybe_next = iterator.peek();
|
let maybe_next = iterator.peek();
|
||||||
|
|
||||||
@@ -60,37 +109,42 @@ fn parse_raw() -> Result<BTreeMap<String, Option<String>>> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error on empty option names.
|
// If the option form does not support a value and there is a value, error.
|
||||||
if option.is_empty() {
|
if description.form != OptionForm::Value && value.is_some() {
|
||||||
bail!("invalid empty option: {option}");
|
bail!("option --{} does not take a value", option);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle the --help flag case.
|
||||||
|
if description.form == OptionForm::Help {
|
||||||
|
// Generic configured options output.
|
||||||
|
println!("Configured Options:");
|
||||||
|
for (name, description) in &configured {
|
||||||
|
println!(
|
||||||
|
" --{}{}: {}",
|
||||||
|
name,
|
||||||
|
if description.form == OptionForm::Value {
|
||||||
|
" <value>"
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
},
|
||||||
|
description.description
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Exit because the help has been displayed.
|
||||||
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert the option and the value into the map.
|
// Insert the option and the value into the map.
|
||||||
options.insert(option, value);
|
options.insert(option, value);
|
||||||
}
|
}
|
||||||
Ok(options)
|
Ok(options)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse the arguments to Sprout as a [SproutOptions] structure.
|
/// Parses the program arguments as a [Self::Output], calling [Self::parse_raw] and [Self::produce].
|
||||||
pub fn parse() -> anyhow::Result<SproutOptions> {
|
fn parse() -> Result<Self::Output> {
|
||||||
// Use the default value of sprout options and have the raw options be parsed into it.
|
// Parse the program arguments into a raw map.
|
||||||
let mut result = SproutOptions::default();
|
let options = Self::parse_raw().context("unable to parse options")?;
|
||||||
let options = parse_raw().context("unable to parse options")?;
|
// Produce the options from the map.
|
||||||
|
Self::produce(options)
|
||||||
for (key, value) in options {
|
}
|
||||||
match key.as_str() {
|
|
||||||
"config" => {
|
|
||||||
// The configuration file to load.
|
|
||||||
result.config = value.context("--config option requires a value")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
"boot" => {
|
|
||||||
// The entry to boot.
|
|
||||||
result.boot = Some(value.context("--boot option requires a value")?);
|
|
||||||
}
|
|
||||||
|
|
||||||
_ => bail!("unknown option: --{key}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(result)
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user