Every program I write is in some sense a command line program, even if it transmogrifies itself into a server process or an interactive terminal user interface. It starts by doing the kinds of things a Unix command line program does: it parses the command line, maybe loads some configuration files, maybe runs some other programs.
I've made a crate, clingwrap
, which makes
a couple of the common things easier. I've done my best to implement the
well and put them in a library. This means I don't keep copying the code from
project to project, inevitably resulting in differences, and bugs fixed in one
place, but not the others.
It's a small library, and may never grow big. There's a module for handling
configuration files, and one to run other programs. Note that it's a library,
not a framework. You call clingwrap
, it doesn't call you.
I use clap
for command line parsing, and
don't feel to wrap or reinvent that.
Example
The code below parses configuration files for a "hello, world" program. It also validates the result of merging many configuration files. The result of the validation is meant to not require checking at run time: if the configuration files can be loaded, the configuration is valid.
use clingwrap::config::*;
use serde::{Deserialize, Serialize};
#[derive(Debug)]
struct Simple {
greeting: String,
whom: String,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, Eq, PartialEq)]
struct SimpleFile {
greeting: Option<String>,
whom: Option<String>,
}
impl<'a> ConfigFile<'a> for SimpleFile {
type Error = SimpleError;
fn merge(&mut self, config_file: SimpleFile) -> Result<(), Self::Error> {
if let Some(value) = &config_file.greeting {
self.greeting = Some(value.to_string());
}
if let Some(value) = &config_file.whom {
self.whom = Some(value.to_string());
}
Ok(())
}
}
#[derive(Default)]
struct SimpleValidator {}
impl ConfigValidator for SimpleValidator {
type File = SimpleFile;
type Valid = Simple;
type Error = SimpleError;
fn validate(&self, runtime: &Self::File) -> Result<Self::Valid, Self::Error> {
Ok(Simple {
greeting: runtime.greeting.clone().ok_or(SimpleError::Missing)?,
whom: runtime.whom.clone().ok_or(SimpleError::Missing)?,
})
}
}
#[derive(Debug, thiserror::Error)]
enum SimpleError {
#[error("required field has not been set")]
Missing,
}