aboutsummaryrefslogtreecommitdiff
path: root/src/main.rs
diff options
context:
space:
mode:
authorDaniel Mueller <deso@posteo.net>2020-08-25 19:04:50 -0700
committerDaniel Mueller <deso@posteo.net>2020-10-11 17:07:07 -0700
commit8cf63c6790192c30c81294e7a940d470bf061cbf (patch)
tree169ca792304f74a7b1548b5bad798b39649cd4c5 /src/main.rs
parent330142e68cac0116babbf2fd64fc9ff0fde132c0 (diff)
downloadnitrocli-8cf63c6790192c30c81294e7a940d470bf061cbf.tar.gz
nitrocli-8cf63c6790192c30c81294e7a940d470bf061cbf.tar.bz2
Introduce support for user-provided extensions
This change introduces support for discovering and executing user-provided extensions to the program. Extensions are useful for allowing users to provide additional functionality on top of the nitrocli proper. Implementation wise we stick to an approach similar to git or cargo subcommands in nature: we search the directories listed in the PATH environment variable for a file that starts with "nitrocli-", followed by the extension name. This file is then executed. It is assumed that the extension recognizes (or at least not prohibits) the following arguments: --nitrocli (providing the path to the nitrocli binary), --model (with the model passed to the main program), and --verbosity (the verbosity level).
Diffstat (limited to 'src/main.rs')
-rw-r--r--src/main.rs62
1 files changed, 51 insertions, 11 deletions
diff --git a/src/main.rs b/src/main.rs
index c0c7da5..e7a7d2f 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -62,9 +62,19 @@ mod pinentry;
mod tests;
use std::env;
+use std::error;
use std::ffi;
+use std::fmt;
use std::io;
use std::process;
+use std::str;
+
+const NITROCLI_BINARY: &str = "NITROCLI_BINARY";
+const NITROCLI_MODEL: &str = "NITROCLI_MODEL";
+const NITROCLI_USB_PATH: &str = "NITROCLI_USB_PATH";
+const NITROCLI_VERBOSITY: &str = "NITROCLI_VERBOSITY";
+const NITROCLI_NO_CACHE: &str = "NITROCLI_NO_CACHE";
+const NITROCLI_SERIAL_NUMBERS: &str = "NITROCLI_SERIAL_NUMBERS";
const NITROCLI_ADMIN_PIN: &str = "NITROCLI_ADMIN_PIN";
const NITROCLI_USER_PIN: &str = "NITROCLI_USER_PIN";
@@ -72,6 +82,28 @@ const NITROCLI_NEW_ADMIN_PIN: &str = "NITROCLI_NEW_ADMIN_PIN";
const NITROCLI_NEW_USER_PIN: &str = "NITROCLI_NEW_USER_PIN";
const NITROCLI_PASSWORD: &str = "NITROCLI_PASSWORD";
+/// A special error type that indicates the desire to exit directly,
+/// without additional error reporting.
+///
+/// This error is mostly used by the extension support code so that we
+/// are able to mirror the extension's exit code while preserving our
+/// context logic and the fairly isolated testing it enables.
+struct DirectExitError(i32);
+
+impl fmt::Debug for DirectExitError {
+ fn fmt(&self, _: &mut fmt::Formatter<'_>) -> fmt::Result {
+ unreachable!()
+ }
+}
+
+impl fmt::Display for DirectExitError {
+ fn fmt(&self, _: &mut fmt::Formatter<'_>) -> fmt::Result {
+ unreachable!()
+ }
+}
+
+impl error::Error for DirectExitError {}
+
/// Parse the command-line arguments and execute the selected command.
fn handle_arguments(ctx: &mut Context<'_>, args: Vec<String>) -> anyhow::Result<()> {
use structopt::StructOpt;
@@ -101,6 +133,8 @@ pub struct Context<'io> {
pub stderr: &'io mut dyn io::Write,
/// Whether `stdout` is a TTY.
pub is_tty: bool,
+ /// The content of the `PATH` environment variable.
+ pub path: Option<ffi::OsString>,
/// The admin PIN, if provided through an environment variable.
pub admin_pin: Option<ffi::OsString>,
/// The user PIN, if provided through an environment variable.
@@ -135,6 +169,10 @@ impl<'io> Context<'io> {
stdout,
stderr,
is_tty,
+ // The std::env module has several references to the PATH
+ // environment variable, indicating that this name is considered
+ // platform independent from their perspective. We do the same.
+ path: env::var_os("PATH"),
admin_pin: env::var_os(NITROCLI_ADMIN_PIN),
user_pin: env::var_os(NITROCLI_USER_PIN),
new_admin_pin: env::var_os(NITROCLI_NEW_ADMIN_PIN),
@@ -145,16 +183,21 @@ impl<'io> Context<'io> {
}
}
-fn run<'ctx, 'io: 'ctx>(ctx: &'ctx mut Context<'io>, args: Vec<String>) -> i32 {
- match handle_arguments(ctx, args) {
- Ok(()) => 0,
- Err(err) => {
- let _ = eprintln!(ctx, "{:?}", err);
- 1
- }
+fn evaluate_err(err: anyhow::Error, stderr: &mut dyn io::Write) -> i32 {
+ if let Some(err) = err.root_cause().downcast_ref::<DirectExitError>() {
+ err.0
+ } else {
+ let _ = writeln!(stderr, "{:?}", err);
+ 1
}
}
+fn run<'ctx, 'io: 'ctx>(ctx: &'ctx mut Context<'io>, args: Vec<String>) -> i32 {
+ handle_arguments(ctx, args)
+ .map(|()| 0)
+ .unwrap_or_else(|err| evaluate_err(err, ctx.stderr))
+}
+
fn main() {
use std::io::Write;
@@ -169,10 +212,7 @@ fn main() {
run(ctx, args)
}
- Err(err) => {
- let _ = writeln!(stderr, "{:?}", err);
- 1
- }
+ Err(err) => evaluate_err(err, &mut stderr),
};
// We exit the process the hard way below. The problem is that because