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]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
|
cli = ["dep:clap", "dep:figment", "dep:tokio", "dep:color-eyre", "dep:num_cpus"]
|
||||||
# Very long running.
|
# Very long running.
|
||||||
get_time_test = []
|
get_time_test = []
|
||||||
|
|
||||||
|
@ -43,4 +44,10 @@ bytes = { version = "1.10.1", features = ["serde"] }
|
||||||
url = { version = "2.5.4", features = ["serde"] }
|
url = { version = "2.5.4", features = ["serde"] }
|
||||||
blake3 = "1.8.2"
|
blake3 = "1.8.2"
|
||||||
slotmap = { version = "1.0.7", features = ["serde"] }
|
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 slotmap;
|
||||||
pub use trait_set::trait_set;
|
pub use trait_set::trait_set;
|
||||||
|
|
||||||
|
#[cfg(feature = "cli")]
|
||||||
|
pub mod cli;
|
||||||
|
|
||||||
pub mod array;
|
pub mod array;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
|
|
|
@ -1,5 +1 @@
|
||||||
pub use eva_utils::sync::{
|
pub use eva_utils::sync::{chan, notifies, spin};
|
||||||
chan,
|
|
||||||
notifies,
|
|
||||||
spin,
|
|
||||||
};
|
|
||||||
|
|
27
src/utils.rs
27
src/utils.rs
|
@ -1,5 +1,32 @@
|
||||||
//! # Uncategorized useful functionality
|
//! # 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.
|
/// Hint that this branch is cold.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub const fn cold<T>(v: T) -> T {
|
pub const fn cold<T>(v: T) -> T {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue