switch from ancient clap/structopt release to bpaf

Improves #70: this reduces binary size from 12.3 MiB to 11.9 MiB (3%) on
macOS/arm64.

The user experience is almost the same. (The help output's `Usage:`
lines lack the e.g. `moonfire-nvr run` prefix of argv[0] and subcommand,
which isn't ideal, but I guess it's pretty minor in the grand scheme of
things.)
This commit is contained in:
Scott Lamb 2023-02-11 11:38:12 -08:00
parent 23c1b9404b
commit e21f795e93
No known key found for this signature in database
12 changed files with 229 additions and 217 deletions

139
server/Cargo.lock generated
View File

@ -47,15 +47,6 @@ dependencies = [
"libc",
]
[[package]]
name = "ansi_term"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
dependencies = [
"winapi",
]
[[package]]
name = "anyhow"
version = "1.0.68"
@ -153,6 +144,27 @@ dependencies = [
"generic-array",
]
[[package]]
name = "bpaf"
version = "0.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "863c0b21775e45ebf9bbb3a6d0cd9b3421c88a036e825359e3d4015561f3e23c"
dependencies = [
"bpaf_derive",
"owo-colors",
]
[[package]]
name = "bpaf_derive"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ed63113bfb3a9bb25dd131acdf0044c7404000ea57dd9eec1f221e3547b4748"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "bstr"
version = "1.1.0"
@ -220,20 +232,6 @@ dependencies = [
"inout",
]
[[package]]
name = "clap"
version = "2.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
dependencies = [
"ansi_term",
"atty",
"bitflags",
"term_size",
"textwrap",
"unicode-width",
]
[[package]]
name = "codespan-reporting"
version = "0.11.1"
@ -756,15 +754,6 @@ dependencies = [
"hashbrown 0.13.1",
]
[[package]]
name = "heck"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
dependencies = [
"unicode-segmentation",
]
[[package]]
name = "hermit-abi"
version = "0.1.19"
@ -974,6 +963,12 @@ version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11b0d96e660696543b251e58030cf9787df56da39dab19ad60eae7353040917e"
[[package]]
name = "is_ci"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "616cde7c720bb2bb5824a224687d8f77bfd38922027f01d825cd7453be5099fb"
[[package]]
name = "itertools"
version = "0.10.5"
@ -1163,9 +1158,9 @@ version = "0.7.5"
dependencies = [
"base64",
"blake3",
"bpaf",
"byteorder",
"bytes",
"clap",
"cursive",
"failure",
"fnv",
@ -1196,7 +1191,6 @@ dependencies = [
"serde",
"serde_json",
"smallvec",
"structopt",
"sync_wrapper",
"tempfile",
"time 0.1.45",
@ -1412,6 +1406,15 @@ dependencies = [
"stable_deref_trait",
]
[[package]]
name = "owo-colors"
version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f"
dependencies = [
"supports-color",
]
[[package]]
name = "password-hash"
version = "0.4.2"
@ -1488,30 +1491,6 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6fa0831dd7cc608c38a5e323422a0077678fa5744aa2be4ad91c4ece8eec8d5"
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote",
"version_check",
]
[[package]]
name = "proc-macro2"
version = "1.0.49"
@ -1967,36 +1946,22 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "structopt"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10"
dependencies = [
"clap",
"lazy_static",
"structopt-derive",
]
[[package]]
name = "structopt-derive"
version = "0.4.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0"
dependencies = [
"heck",
"proc-macro-error",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "subtle"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
[[package]]
name = "supports-color"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ba6faf2ca7ee42fdd458f4347ae0a9bd6bcc445ad7cb57ad82b383f18870d6f"
dependencies = [
"atty",
"is_ci",
]
[[package]]
name = "syn"
version = "1.0.107"
@ -2059,16 +2024,6 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "textwrap"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
dependencies = [
"term_size",
"unicode-width",
]
[[package]]
name = "thiserror"
version = "1.0.38"

View File

@ -2,7 +2,7 @@
name = "moonfire-nvr"
version = "0.7.5"
authors = ["Scott Lamb <slamb@slamb.org>"]
edition = "2018"
edition = "2021"
resolver = "2"
license-file = "../LICENSE.txt"
rust-version = "1.64"
@ -24,9 +24,9 @@ members = ["base", "db"]
base = { package = "moonfire-base", path = "base" }
base64 = "0.13.0"
blake3 = "1.0.0"
bpaf = { version = "0.7.8", features = ["autocomplete", "bright-color", "derive"] }
bytes = "1"
byteorder = "1.0"
clap = { version = "2.33.3", default-features = false, features = ["color", "wrap_help"] }
cursive = "0.20.0"
db = { package = "moonfire-db", path = "db" }
failure = "0.1.1"
@ -53,7 +53,6 @@ rusqlite = "0.28.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
smallvec = { version = "1.7", features = ["union"] }
structopt = { version = "0.3.13", default-features = false }
sync_wrapper = "0.1.0"
time = "0.1"
tokio = { version = "1.24", features = ["macros", "rt-multi-thread", "signal", "sync", "time"] }

View File

@ -4,45 +4,39 @@
//! Subcommand to check the database and sample file dir for errors.
use bpaf::Bpaf;
use db::check;
use failure::Error;
use std::path::PathBuf;
use structopt::StructOpt;
#[derive(StructOpt)]
#[derive(Bpaf, Debug)]
pub struct Args {
/// Directory holding the SQLite3 index database.
#[structopt(
long,
default_value = "/var/lib/moonfire-nvr/db",
value_name = "path",
parse(from_os_str)
)]
///
/// default: `/var/lib/moonfire-nvr/db`.
#[bpaf(argument("PATH"), fallback_with(crate::default_db_dir))]
db_dir: PathBuf,
/// Compare sample file lengths on disk to the database.
#[structopt(long)]
/// Compares sample file lengths on disk to the database.
compare_lens: bool,
/// Trash sample files without matching recording rows in the database.
/// This addresses "Missing ... row" errors.
/// Trashes sample files without matching recording rows in the database.
/// This addresses `Missing ... row` errors.
///
/// The ids are added to the "garbage" table to indicate the files need to
/// The ids are added to the `garbage` table to indicate the files need to
/// be deleted. Garbage is collected on normal startup.
#[structopt(long)]
trash_orphan_sample_files: bool,
/// Delete recording rows in the database without matching sample files.
/// This addresses "Recording ... missing file" errors.
#[structopt(long)]
/// Deletes recording rows in the database without matching sample files.
///
/// This addresses `Recording ... missing file` errors.
delete_orphan_rows: bool,
/// Trash recordings when their database rows appear corrupt.
/// Trashes recordings when their database rows appear corrupt.
/// This addresses "bad video_index" errors.
///
/// The ids are added to the "garbage" table to indicate their files need to
/// The ids are added to the `garbage` table to indicate their files need to
/// be deleted. Garbage is collected on normal startup.
#[structopt(long)]
trash_corrupt_rows: bool,
}

View File

@ -8,26 +8,23 @@
//! configuration will likely be almost entirely done through a web-based UI.
use base::clock;
use bpaf::Bpaf;
use cursive::views;
use cursive::Cursive;
use failure::Error;
use std::path::PathBuf;
use std::sync::Arc;
use structopt::StructOpt;
mod cameras;
mod dirs;
mod users;
#[derive(StructOpt)]
#[derive(Bpaf, Debug)]
pub struct Args {
/// Directory holding the SQLite3 index database.
#[structopt(
long,
default_value = "/var/lib/moonfire-nvr/db",
value_name = "path",
parse(from_os_str)
)]
///
/// default: `/var/lib/moonfire-nvr/db`.
#[bpaf(argument("PATH"), fallback_with(crate::default_db_dir))]
db_dir: PathBuf,
}

View File

@ -2,20 +2,17 @@
// Copyright (C) 2020 The Moonfire NVR Authors; see AUTHORS and LICENSE.txt.
// SPDX-License-Identifier: GPL-v3.0-or-later WITH GPL-3.0-linking-exception.
use bpaf::Bpaf;
use failure::Error;
use log::info;
use std::path::PathBuf;
use structopt::StructOpt;
#[derive(StructOpt)]
#[derive(Bpaf, Debug)]
pub struct Args {
/// Directory holding the SQLite3 index database.
#[structopt(
long,
default_value = "/var/lib/moonfire-nvr/db",
value_name = "path",
parse(from_os_str)
)]
///
/// default: `/var/lib/moonfire-nvr/db`.
#[bpaf(argument("PATH"), fallback_with(crate::default_db_dir))]
db_dir: PathBuf,
}

View File

@ -5,51 +5,58 @@
//! Subcommand to login a user (without requiring a password).
use base::clock::{self, Clocks};
use bpaf::Bpaf;
use db::auth::SessionFlag;
use failure::{format_err, Error};
use std::io::Write as _;
use std::os::unix::fs::OpenOptionsExt as _;
use std::path::PathBuf;
use structopt::StructOpt;
use std::str::FromStr;
#[derive(Debug, Default, StructOpt)]
fn parse_perms(perms: String) -> Result<db::Permissions, protobuf::text_format::ParseError> {
protobuf::text_format::parse_from_str(&perms)
}
fn parse_flags(flags: String) -> Result<Vec<SessionFlag>, Error> {
flags.split(',').map(SessionFlag::from_str).collect()
}
#[derive(Bpaf, Debug)]
pub struct Args {
/// Directory holding the SQLite3 index database.
#[structopt(
long,
default_value = "/var/lib/moonfire-nvr/db",
value_name = "path",
parse(from_os_str)
)]
///
/// default: `/var/lib/moonfire-nvr/db`.
#[bpaf(argument("PATH"), fallback_with(crate::default_db_dir))]
db_dir: PathBuf,
/// Create a session with the given permissions.
/// Creates a session with the given permissions.
///
/// If unspecified, uses user's default permissions.
#[structopt(long, value_name="perms",
parse(try_from_str = protobuf::text_format::parse_from_str))]
#[bpaf(argument::<String>("PERMS"), parse(parse_perms), optional)]
permissions: Option<db::Permissions>,
/// Restrict this cookie to the given domain.
#[structopt(long)]
/// Restricts this cookie to the given domain.
#[bpaf(argument("DOMAIN"))]
domain: Option<String>,
/// Write the cookie to a new curl-compatible cookie-jar file.
/// Writes the cookie to a new curl-compatible cookie-jar file.
///
/// ---domain must be specified. This file can be used later with curl's --cookie flag.
#[structopt(long, requires("domain"), value_name = "path")]
/// `--domain` must be specified. This file can be used later with curl's `--cookie` flag.
#[bpaf(argument("PATH"))]
curl_cookie_jar: Option<PathBuf>,
/// Set the given db::auth::SessionFlags.
#[structopt(
long,
default_value = "http-only,secure,same-site,same-site-strict",
value_name = "flags",
use_delimiter = true
/// Sets the given db::auth::SessionFlags.
///
/// default: `http-only,secure,same-site,same-site-strict`.
#[bpaf(
argument::<String>("FLAGS"),
fallback_with(|| Ok::<_, std::convert::Infallible>("http-only,secure,same-site,same-site-strict".to_owned())),
parse(parse_flags),
)]
session_flags: Vec<SessionFlag>,
/// Create the session for this username.
#[bpaf(argument("USERNAME"))]
username: String,
}
@ -87,7 +94,7 @@ pub fn run(args: Args) -> Result<i32, Error> {
let d = args
.domain
.as_ref()
.ok_or_else(|| format_err!("--cookiejar requires --domain"))?;
.ok_or_else(|| format_err!("--curl-cookie-jar requires --domain"))?;
let mut f = std::fs::OpenOptions::new()
.write(true)
.create_new(true)

View File

@ -27,6 +27,9 @@ pub struct ConfigFile {
pub binds: Vec<BindConfig>,
/// Directory holding the SQLite3 index database.
///
///
/// default: `/var/lib/moonfire-nvr/db`.
#[serde(default = "default_db_dir")]
pub db_dir: PathBuf,

View File

@ -6,6 +6,7 @@ use crate::streamer;
use crate::web;
use crate::web::accept::Listener;
use base::clock;
use bpaf::Bpaf;
use db::{dir, writer};
use failure::{bail, Error, ResultExt};
use fnv::FnvHashMap;
@ -18,23 +19,24 @@ use std::path::Path;
use std::path::PathBuf;
use std::sync::Arc;
use std::thread;
use structopt::StructOpt;
use tokio::signal::unix::{signal, SignalKind};
use self::config::ConfigFile;
mod config;
#[derive(StructOpt)]
#[derive(Bpaf, Debug)]
pub struct Args {
#[structopt(short, long, default_value = "/etc/moonfire-nvr.toml")]
/// Path to configuration file.
///
/// default: `/etc/moonfire-nvr.toml`. See `ref/config.md` for config file documentation.
#[bpaf(short, long, argument("PATH"), fallback_with(|| Ok::<_, Error>("/etc/moonfire-nvr.toml".into())))]
config: PathBuf,
/// Open the database in read-only mode and disables recording.
/// Opens the database in read-only mode and disables recording.
///
/// Note this is incompatible with session authentication; consider adding
/// a bind with `allow_unauthenticated_permissions` to your config.
#[structopt(long)]
/// a bind with `allowUnauthenticatedPermissions` to your config.
read_only: bool,
}

View File

@ -5,35 +5,31 @@
//! Subcommand to run a SQLite shell.
use super::OpenMode;
use bpaf::Bpaf;
use failure::Error;
use std::ffi::OsString;
use std::os::unix::process::CommandExt;
use std::path::PathBuf;
use std::process::Command;
use structopt::StructOpt;
#[derive(StructOpt)]
#[derive(Bpaf, Debug, PartialEq, Eq)]
pub struct Args {
/// Directory holding the SQLite3 index database.
#[structopt(
long,
default_value = "/var/lib/moonfire-nvr/db",
value_name = "path",
parse(from_os_str)
)]
///
/// default: `/var/lib/moonfire-nvr/db`.
#[bpaf(fallback_with(crate::default_db_dir))]
db_dir: PathBuf,
/// Opens the database in read-only mode and locks it only for shared access.
///
/// This can be run simultaneously with "moonfire-nvr run --read-only".
#[structopt(long)]
/// This can be run simultaneously with `moonfire-nvr run --read-only`.
read_only: bool,
/// Arguments to pass to sqlite3.
///
/// Use the -- separator to pass sqlite3 options, as in
/// "moonfire-nvr sql -- -line 'select username from user'".
#[structopt(parse(from_os_str))]
/// Use the `--` separator to pass sqlite3 options, as in
/// `moonfire-nvr sql -- -line 'select username from user'`.
#[bpaf(positional)]
arg: Vec<OsString>,
}
@ -58,3 +54,32 @@ pub fn run(args: Args) -> Result<i32, Error> {
.exec()
.into())
}
#[cfg(test)]
mod tests {
use super::*;
use bpaf::Parser;
#[test]
fn parse_args() {
let args = args()
.to_options()
.run_inner(bpaf::Args::from(&[
"--db-dir",
"/foo/bar",
"--",
"-line",
"select username from user",
]))
.unwrap();
assert_eq!(
args,
Args {
db_dir: "/foo/bar".into(),
read_only: false, // default
arg: vec!["-line".into(), "select username from user".into()],
}
);
}
}

View File

@ -2,18 +2,18 @@
// Copyright (C) 2020 The Moonfire NVR Authors; see AUTHORS and LICENSE.txt.
// SPDX-License-Identifier: GPL-v3.0-or-later WITH GPL-3.0-linking-exception.
use bpaf::Bpaf;
use failure::Error;
use structopt::StructOpt;
#[derive(StructOpt)]
#[derive(Bpaf, Debug)]
pub struct Args {
/// Timestamp(s) to translate.
///
/// May be either an integer or an RFC-3339-like string:
/// `YYYY-mm-dd[THH:MM[:SS[:FFFFF]]][{Z,{+,-,}HH:MM}]`.
///
/// Eg: `142913484000000`, `2020-04-26`, `2020-04-26T12:00:00:00000-07:00`.
#[structopt(required = true)]
/// E.g.: `142913484000000`, `2020-04-26`, `2020-04-26T12:00:00:00000-07:00`.
#[bpaf(positional("TS"), some("must specify at least one timestamp"))]
timestamps: Vec<String>,
}

View File

@ -2,41 +2,36 @@
// Copyright (C) 2020 The Moonfire NVR Authors; see AUTHORS and LICENSE.txt.
// SPDX-License-Identifier: GPL-v3.0-or-later WITH GPL-3.0-linking-exception.
use bpaf::Bpaf;
/// Upgrades the database schema.
///
/// See `guide/schema.md` for more information.
use failure::Error;
use structopt::StructOpt;
#[derive(StructOpt)]
#[derive(Bpaf, Debug)]
pub struct Args {
#[structopt(
long,
help = "Directory holding the SQLite3 index database.",
default_value = "/var/lib/moonfire-nvr/db",
parse(from_os_str)
)]
/// Directory holding the SQLite3 index database.
///
///
/// default: `/var/lib/moonfire-nvr/db`.
#[bpaf(argument("PATH"), fallback_with(crate::default_db_dir))]
db_dir: std::path::PathBuf,
#[structopt(
help = "When upgrading from schema version 1 to 2, the sample file directory.",
long,
parse(from_os_str)
)]
/// When upgrading from schema version 1 to 2, the sample file directory.
#[bpaf(argument("PATH"))]
sample_file_dir: Option<std::path::PathBuf>,
#[structopt(
help = "Resets the SQLite journal_mode to the specified mode prior to \
the upgrade. The default, delete, is recommended. off is very \
dangerous but may be desirable in some circumstances. See \
guide/schema.md for more information. The journal mode will be \
reset to wal after the upgrade.",
long,
default_value = "delete"
)]
/// Resets the SQLite journal_mode to the specified mode prior to
/// the upgrade.
///
///
/// default: `delete` (recommended). `off` is very dangerous but may be
/// desirable in some circumstances. See `guide/schema.md` for more
/// information. The journal mode will be reset to `wal` after the upgrade.
#[bpaf(argument("MODE"), fallback_with(|| Ok::<_, std::convert::Infallible>("delete".into())))]
preset_journal: String,
#[structopt(help = "Skips the normal post-upgrade vacuum operation.", long)]
/// Skips the normal post-upgrade vacuum operation.
no_vacuum: bool,
}

View File

@ -4,10 +4,10 @@
#![cfg_attr(all(feature = "nightly", test), feature(test))]
use bpaf::Bpaf;
use log::{debug, error};
use std::fmt::Write;
use std::str::FromStr;
use structopt::StructOpt;
mod body;
mod cmds;
@ -19,43 +19,50 @@ mod stream;
mod streamer;
mod web;
#[derive(StructOpt)]
#[structopt(
name = "moonfire-nvr",
about = "security camera network video recorder",
global_settings(&[clap::AppSettings::ColoredHelp])
)]
/// Moonfire NVR: security camera network video recorder.
#[derive(Bpaf, Debug)]
#[bpaf(options, version)]
enum Args {
/// Checks database integrity (like fsck).
Check(cmds::check::Args),
#[bpaf(command)]
Check(#[bpaf(external(cmds::check::args))] cmds::check::Args),
/// Interactively edits configuration.
Config(cmds::config::Args),
#[bpaf(command)]
Config(#[bpaf(external(cmds::config::args))] cmds::config::Args),
/// Initializes a database.
Init(cmds::init::Args),
#[bpaf(command)]
Init(#[bpaf(external(cmds::init::args))] cmds::init::Args),
/// Logs in a user, returning the session cookie.
///
///
/// This is a privileged command that directly accesses the database. It doesn't check the
/// user's password and even can be used to create sessions with permissions the user doesn't
/// have.
Login(cmds::login::Args),
#[bpaf(command)]
Login(#[bpaf(external(cmds::login::args))] cmds::login::Args),
/// Runs the server, saving recordings and allowing web access.
Run(cmds::run::Args),
#[bpaf(command)]
Run(#[bpaf(external(cmds::run::args))] cmds::run::Args),
/// Runs a SQLite3 shell on Moonfire NVR's index database.
///
///
/// Note this locks the database to prevent simultaneous access with a running server. The
/// server maintains cached state which could be invalidated otherwise.
Sql(cmds::sql::Args),
#[bpaf(command)]
Sql(#[bpaf(external(cmds::sql::args))] cmds::sql::Args),
/// Translates between integer and human-readable timestamps.
Ts(cmds::ts::Args),
#[bpaf(command)]
Ts(#[bpaf(external(cmds::ts::args))] cmds::ts::Args),
/// Upgrades to the latest database schema.
Upgrade(cmds::upgrade::Args),
#[bpaf(command)]
Upgrade(#[bpaf(external(cmds::upgrade::args))] cmds::upgrade::Args),
}
impl Args {
@ -73,6 +80,11 @@ impl Args {
}
}
/// Returns the default database dir, for use in argument parsing with `bpaf(fallback_with(...))`.
fn default_db_dir() -> Result<std::path::PathBuf, std::convert::Infallible> {
Ok("/var/lib/moonfire-nvr/db".into())
}
/// Custom panic hook that logs instead of directly writing to stderr.
///
/// This means it includes a timestamp and is more recognizable as a serious
@ -107,12 +119,11 @@ fn main() {
if let Err(e) = nix::time::clock_gettime(nix::time::ClockId::CLOCK_MONOTONIC) {
eprintln!(
"clock_gettime failed: {e}\n\n\
This indicates a broken environment. See the troubleshooting guide."
This indicates a broken environment. See the troubleshooting guide."
);
std::process::exit(1);
}
let args = Args::from_args();
let mut h = mylog::Builder::new()
.set_format(
::std::env::var("MOONFIRE_FORMAT")
@ -130,6 +141,22 @@ fn main() {
.build();
h.clone().install().unwrap();
// TODO: remove this when bpaf adds more direct support for defaulting to `--help`.
// See discussion: <https://github.com/pacak/bpaf/discussions/165>.
if std::env::args_os().len() < 2 {
match args().run_inner(bpaf::Args::from(&["--help"])) {
Ok(a) => panic!("bpaf --help should not return Ok: {a:#?}"),
Err(bpaf::ParseFailure::Stdout(msg)) => {
print!("{msg}");
std::process::exit(0);
}
Err(bpaf::ParseFailure::Stderr(msg)) => {
eprint!("{msg}");
std::process::exit(1);
}
}
}
let use_panic_hook = ::std::env::var("MOONFIRE_PANIC_HOOK")
.map(|s| s != "false" && s != "0")
.unwrap_or(true);
@ -137,6 +164,9 @@ fn main() {
std::panic::set_hook(Box::new(&panic_hook));
}
let args = args().run();
log::trace!("Parsed command-line arguments: {args:#?}");
let r = {
let _a = h.async_scope();
args.run()
@ -152,3 +182,11 @@ fn main() {
}
}
}
#[cfg(test)]
mod tests {
#[test]
fn bpaf_invariants() {
super::args().check_invariants(false);
}
}