implement basic cli app template
This commit is contained in:
parent
5ace79d1e6
commit
752c859fd7
5 changed files with 133 additions and 6 deletions
|
@ -8,6 +8,7 @@ members = ["macros", "utils"]
|
|||
|
||||
[features]
|
||||
default = []
|
||||
cli = ["dep:clap", "dep:figment", "dep:tokio", "dep:color-eyre", "dep:num_cpus"]
|
||||
# Very long running.
|
||||
get_time_test = []
|
||||
|
||||
|
@ -43,4 +44,10 @@ bytes = { version = "1.10.1", features = ["serde"] }
|
|||
url = { version = "2.5.4", features = ["serde"] }
|
||||
blake3 = "1.8.2"
|
||||
slotmap = { version = "1.0.7", features = ["serde"] }
|
||||
instant-acme = "0.7.2"
|
||||
|
||||
clap = { version = "4.5.47", features = ["derive"], optional = true }
|
||||
figment = { version = "0.10.19", features = ["json", "yaml", "env", "toml"], optional = true }
|
||||
tokio = { version = "1.47.1", features = ["rt", "rt-multi-thread"], optional = true }
|
||||
color-eyre = { version = "0.6.5", optional = true }
|
||||
eyre = { version = "0.6.12" }
|
||||
num_cpus = { version = "1.17.0", optional = true }
|
||||
|
|
94
src/cli/mod.rs
Normal file
94
src/cli/mod.rs
Normal file
|
@ -0,0 +1,94 @@
|
|||
use std::{num::NonZeroUsize, path::PathBuf};
|
||||
|
||||
use clap::{Args, Parser};
|
||||
|
||||
use eyre::Context;
|
||||
use figment::{
|
||||
Figment,
|
||||
providers::{Env, Format, Json, Toml, YamlExtended},
|
||||
};
|
||||
|
||||
use tokio::runtime as rt;
|
||||
|
||||
#[derive(Parser)]
|
||||
struct AppArgs<A: Args> {
|
||||
#[clap(long, short)]
|
||||
/// Config file(s) for the application.
|
||||
config: Vec<PathBuf>,
|
||||
#[clap(long, short)]
|
||||
/// Number of worker threads app will consume.
|
||||
worker_threads: Option<NonZeroUsize>,
|
||||
|
||||
#[clap(flatten)]
|
||||
args: A,
|
||||
}
|
||||
|
||||
pub struct App {
|
||||
figment: Figment,
|
||||
}
|
||||
|
||||
impl App {
|
||||
pub fn env_prefix(mut self, pfx: &str) -> Self {
|
||||
self.figment = self.figment.merge(Env::prefixed(pfx));
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn run<A, C>(
|
||||
self,
|
||||
f: impl Send + 'static + AsyncFnOnce(A, C) -> eyre::Result<()>,
|
||||
) -> eyre::Result<()>
|
||||
where
|
||||
A: Args,
|
||||
C: serde::de::DeserializeOwned,
|
||||
{
|
||||
let Self { mut figment } = self;
|
||||
color_eyre::install().wrap_err("failed to install color-eyre")?;
|
||||
let AppArgs {
|
||||
config: configs,
|
||||
worker_threads,
|
||||
args,
|
||||
} = AppArgs::<A>::parse();
|
||||
|
||||
for config in configs {
|
||||
// TODO: support glob? Expand home?
|
||||
let component = config.components().next_back().ok_or_else(|| {
|
||||
eyre::eyre!("{} must contain at least one component", config.display())
|
||||
})?;
|
||||
let last = component.as_os_str().as_encoded_bytes();
|
||||
|
||||
if config.is_dir() {
|
||||
todo!("populate everything from the directory")
|
||||
} else if last.ends_with(b".yaml") || last.ends_with(b".yml") {
|
||||
figment = figment.merge(YamlExtended::file(config.as_path()));
|
||||
} else if last.ends_with(b".json") {
|
||||
figment = figment.merge(Json::file(config.as_path()));
|
||||
} else if last.ends_with(b".toml") {
|
||||
figment = figment.merge(Toml::file(config.as_path()));
|
||||
}
|
||||
}
|
||||
|
||||
let config: C = figment.extract_lossy().wrap_err("failed to load config")?;
|
||||
let rt = match worker_threads.map_or(num_cpus::get(), NonZeroUsize::get) {
|
||||
0 | 1 => rt::Builder::new_current_thread(),
|
||||
n => {
|
||||
let mut b = rt::Builder::new_multi_thread();
|
||||
b.worker_threads(n);
|
||||
b
|
||||
}
|
||||
}
|
||||
.enable_all()
|
||||
.build()
|
||||
.wrap_err("failed to create tokio runtime")?;
|
||||
|
||||
rt.block_on(f(args, config))
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for App {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
figment: Figment::new(),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -25,6 +25,9 @@ pub use perfect_derive::perfect_derive;
|
|||
pub use slotmap;
|
||||
pub use trait_set::trait_set;
|
||||
|
||||
#[cfg(feature = "cli")]
|
||||
pub mod cli;
|
||||
|
||||
pub mod array;
|
||||
pub mod error;
|
||||
pub mod utils;
|
||||
|
|
|
@ -1,5 +1 @@
|
|||
pub use eva_utils::sync::{
|
||||
chan,
|
||||
notifies,
|
||||
spin,
|
||||
};
|
||||
pub use eva_utils::sync::{chan, notifies, spin};
|
||||
|
|
27
src/utils.rs
27
src/utils.rs
|
@ -1,5 +1,32 @@
|
|||
//! # Uncategorized useful functionality
|
||||
|
||||
use eyre::Context as _;
|
||||
|
||||
use std::{fmt, path::PathBuf};
|
||||
|
||||
use crate::data;
|
||||
|
||||
#[data(crate = crate, not(Debug), display("<secret>"))]
|
||||
pub enum SecretString {
|
||||
Plaintext(String),
|
||||
File(PathBuf),
|
||||
}
|
||||
|
||||
impl SecretString {
|
||||
pub fn read(&self) -> eyre::Result<String> {
|
||||
match self {
|
||||
Self::Plaintext(t) => Ok(t.clone()),
|
||||
Self::File(f) => std::fs::read_to_string(f).wrap_err("failed to read secret string"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for SecretString {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{self}")
|
||||
}
|
||||
}
|
||||
|
||||
/// Hint that this branch is cold.
|
||||
#[inline(always)]
|
||||
pub const fn cold<T>(v: T) -> T {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue