form 0.13.0

A small script to move inline modules into the proper directory structure
Documentation
use anyhow::{anyhow, Result};
use getopts::Options;
use std::{
    env,
    fs::File,
    io::{self, Read},
    path::Path,
};

pub struct FormOpts {
    pub input: String,
    pub output_dir: String,
    pub format_output: bool,
}

impl FormOpts {
    pub fn from_args() -> Result<Option<Self>> {
        const SHORT_INPUT: &str = "i";
        const SHORT_OUTDIR: &str = "o";
        const SHORT_FMT: &str = "f";
        const SHORT_HELP: &str = "h";
        const SHORT_VERSION: &str = "v";

        let args: Vec<String> = env::args().collect();
        let program = args[0].clone();
        let mut opts = Options::new();
        opts.optopt(
            SHORT_INPUT,
            "input",
            "input file to read instead of stdin",
            "FILE",
        );
        opts.optopt(SHORT_OUTDIR, "outdir", "set output directory", "DIR");
        opts.optflag(
            "f",
            "format-output",
            "format result with prettyplease formatter",
        );
        opts.optflag(SHORT_HELP, "help", "print this help menu");
        opts.optflag(SHORT_VERSION, "version", "print the version");

        let matches = opts.parse(&args[1..]).unwrap();
        let format_output = matches.opt_present(SHORT_FMT);
        if matches.opt_present(SHORT_HELP) {
            print_usage(&program, opts);
            return Ok(None);
        }
        if matches.opt_present(SHORT_VERSION) {
            print_version();
            return Ok(None);
        }
        let output_dir = matches
            .opt_str(SHORT_OUTDIR)
            .ok_or_else(|| anyhow!("Output directory missing"))?;
        let input = read_input(matches.opt_str(SHORT_INPUT))?;

        Ok(Some(FormOpts {
            output_dir,
            input,
            format_output,
        }))
    }
}

fn print_usage(program: &str, opts: Options) {
    let brief = format!("Usage: {} [options]", program);
    println!("{}", opts.usage(&brief));
}

fn print_version() {
    println!("{} {}", crate::NAME, crate::VERSION);
}

fn read_input<P: AsRef<Path>>(input_file: Option<P>) -> Result<String> {
    let mut input = String::new();
    match input_file {
        Some(file_name) => {
            let mut file = File::open(file_name)?;
            file.read_to_string(&mut input)?;
        }
        None => {
            io::stdin().read_to_string(&mut input)?;
        }
    }
    Ok(input)
}