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,
}