[opentitantool] Support parsing an opentitantoolrc
Allow parsing an `opentitantoolrc` file which can hold the user's desired
default configuration options.
Signed-off-by: Chris Frantz <cfrantz@google.com>
diff --git a/sw/host/opentitantool/Cargo.toml b/sw/host/opentitantool/Cargo.toml
index 93bedde..043c83c 100644
--- a/sw/host/opentitantool/Cargo.toml
+++ b/sw/host/opentitantool/Cargo.toml
@@ -20,6 +20,8 @@
nix = "0.17.0"
indicatif = "0.16.2"
humantime = "2.1.0"
+directories = "4.0.1"
+shellwords = "1.1.0"
serde = {version="1", features=["serde_derive"]}
serde_json = "1"
diff --git a/sw/host/opentitantool/src/main.rs b/sw/host/opentitantool/src/main.rs
index cad72bd..1398c1b 100644
--- a/sw/host/opentitantool/src/main.rs
+++ b/sw/host/opentitantool/src/main.rs
@@ -3,7 +3,12 @@
// SPDX-License-Identifier: Apache-2.0
use anyhow::Result;
+use directories::ProjectDirs;
use log::LevelFilter;
+use std::env::{args_os, ArgsOs};
+use std::ffi::OsString;
+use std::io::ErrorKind;
+use std::path::PathBuf;
use structopt::StructOpt;
mod backend;
@@ -29,6 +34,13 @@
#[derive(Debug, StructOpt)]
struct Opts {
+ #[structopt(
+ long,
+ default_value = "config",
+ help = "Filename of a default flagsfile. Relative to $XDG_CONFIG_HOME/opentitantool."
+ )]
+ rcfile: PathBuf,
+
#[structopt(long, default_value = "off")]
logging: LevelFilter,
@@ -39,11 +51,63 @@
command: RootCommandHierarchy,
}
+// Given some existing option configuration, maybe re-evaluate command
+// line options by reading an `rcfile`.
+fn parse_command_line(opts: Opts, mut args: ArgsOs) -> Result<Opts> {
+ // Initialize the logger if the user requested the non-defualt option.
+ let logging = opts.logging;
+ if logging != LevelFilter::Off {
+ env_logger::Builder::from_default_env()
+ .filter(None, opts.logging)
+ .init();
+ }
+ if opts.rcfile.as_os_str().is_empty() {
+ // No rcfile to parse.
+ return Ok(opts);
+ }
+
+ // Construct the rcfile path based on the user's config directory
+ // (ie: $HOME/.config/opentitantool/<filename>).
+ let rcfile = if let Some(base) = ProjectDirs::from("org", "opentitan", "opentitantool") {
+ base.config_dir().join(&opts.rcfile)
+ } else {
+ opts.rcfile.clone()
+ };
+
+ // argument[0] is the executable name.
+ let mut arguments = vec![args.next().unwrap()];
+
+ // Read in the rcfile and extend the argument list.
+ match std::fs::read_to_string(&rcfile) {
+ Ok(content) => {
+ for line in content.split('\n') {
+ // Strip basic comments as shellwords won't handle comments.
+ let (line, _) = line.split_once('#').unwrap_or((line, ""));
+ arguments.extend(shellwords::split(line)?.iter().map(OsString::from));
+ }
+ Ok(())
+ }
+ Err(e) if e.kind() == ErrorKind::NotFound => {
+ log::warn!("Could not read {:?}. Ignoring.", rcfile);
+ Ok(())
+ }
+ Err(e) => Err(anyhow::Error::new(e).context(format!("Reading file {:?}", rcfile))),
+ }?;
+
+ // Extend the argument list with all remaining command line arguments.
+ arguments.extend(args.into_iter());
+ let opts = Opts::from_iter(&arguments);
+ if opts.logging != logging {
+ // Try re-initializing the logger. Ignore errors.
+ let _ = env_logger::Builder::from_default_env()
+ .filter(None, opts.logging)
+ .try_init();
+ }
+ Ok(opts)
+}
+
fn main() -> Result<()> {
- let opts = Opts::from_args();
- env_logger::Builder::from_default_env()
- .filter(None, opts.logging)
- .init();
+ let opts = parse_command_line(Opts::from_args(), args_os())?;
let transport = backend::create(&opts.backend_opts)?;