mylang_cli_ext/
lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
mod command;
mod matches;
mod with_output;

use std::{
    fs::File,
    io::{BufRead, BufReader, BufWriter, Stdin, Stdout, Write},
    str::FromStr,
};

use anyhow::anyhow;
use clap::{builder::PossibleValuesParser, ArgMatches, ValueEnum};
use once_cell::sync::Lazy;

pub use command::{CommandFromParser, CommandFromParserExt};
pub use matches::MatchesFromParser;
pub use with_output::WithOutputExt;

#[derive(Clone, ValueEnum)]
pub enum FileFormat {
    Json,
    Binary,
}

impl FileFormat {
    pub fn value_of(matches: &ArgMatches, name: &str) -> Result<FileFormat, anyhow::Error> {
        let val = matches.get_one::<String>(name).unwrap().as_str();
        <FileFormat as ValueEnum>::from_str(val, true)
            .map_err(|s| anyhow!("Failed to parse {} as file format", s))
    }
}

impl FromStr for FileFormat {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "json" => Ok(FileFormat::Json),
            "binary" => Ok(FileFormat::Binary),
            _ => Err(format!("Invalid input format: {s}")),
        }
    }
}

pub static FILE_FORMAT_PARSER: Lazy<PossibleValuesParser> =
    Lazy::new(|| PossibleValuesParser::new(["json", "binary"]));

pub fn reader_from_stdin_or_file<'a>(
    stdin: &'a Stdin,
    stdin_flag: bool,
    file_path: Option<String>,
) -> anyhow::Result<Box<dyn BufRead + 'a>> {
    match (stdin_flag, file_path) {
        (true, Some(_)) => Err(anyhow!("Cannot specify both --stdin and [input]")),

        (true, None) => Ok(Box::new(stdin.lock())),

        (false, Some(path)) => Ok(Box::new(BufReader::new(File::open(path)?))),

        (false, None) => Err(anyhow!(
            "No input specified. You can specify either --stdin or [input]"
        )),
    }
}

pub fn read<T>(reader: Box<dyn BufRead + '_>, input_format: &FileFormat) -> anyhow::Result<T>
where
    T: serde::de::DeserializeOwned,
{
    let value = match input_format {
        FileFormat::Json => serde_json::from_reader(reader)?,
        FileFormat::Binary => bincode::deserialize_from(reader)?,
    };
    Ok(value)
}

pub fn writer_to_stdout_or_file<'a>(
    stdout: &'a Stdout,
    stdout_flag: bool,
    file_path: Option<String>,
) -> anyhow::Result<Box<dyn Write + 'a>> {
    match (stdout_flag, file_path) {
        (true, Some(_)) => Err(anyhow!("Cannot specify both --stdout and --output")),

        (true, None) => Ok(Box::new(stdout.lock())),

        (false, Some(path)) => Ok(Box::new(BufWriter::new(File::create(path)?))),

        (false, None) => Err(anyhow!(
            "No output specified. You can specify either --stdout or --output"
        )),
    }
}

pub fn write<T>(
    mut writer: Box<dyn Write + '_>,
    output_format: &FileFormat,
    value: &T,
) -> anyhow::Result<()>
where
    T: serde::Serialize,
{
    match output_format {
        FileFormat::Json => serde_json::to_writer_pretty(&mut writer, &value)?,
        FileFormat::Binary => bincode::serialize_into(&mut writer, &value)?,
    }
    Ok(())
}