1use std::{
4 fs::File,
5 io::{BufReader, BufWriter, Read, Write},
6 path::PathBuf,
7};
8
9use clap::{Args, Parser};
10use error_stack::{report, Report, ResultExt};
11
12use crate::{conversion::ConversionDirection, Converter, Error};
13
14type DynamicConverter = Converter<Box<dyn Read>, Box<dyn Write>>;
16
17#[derive(Debug, Parser)]
21#[command(author, version, about, long_about = None)]
22pub struct Cli {
23 #[command(flatten)]
25 direction: ConversionDirectionArgs,
26 #[arg(short, long)]
28 input: Option<PathBuf>,
29 #[arg(short, long)]
31 output: Option<PathBuf>,
32}
33
34#[derive(Debug, Args)]
36#[group(required = false, multiple = false)]
37struct ConversionDirectionArgs {
38 #[arg(long = "m2j")]
40 msgpack2json: bool,
41 #[arg(long = "j2m")]
43 json2msgpack: bool,
44}
45
46impl TryFrom<ConversionDirectionArgs> for ConversionDirection {
49 type Error = Report<Error>;
50
51 fn try_from(direction: ConversionDirectionArgs) -> Result<Self, Self::Error> {
52 if direction.msgpack2json && direction.json2msgpack {
53 return Err(report!(Error::MultipleConversionDirections));
54 }
55
56 if direction.msgpack2json {
57 Ok(Self::MsgPack2Json)
58 } else if direction.json2msgpack {
59 Ok(Self::Json2MsgPack)
60 } else {
61 Ok(Self::Auto)
62 }
63 }
64}
65
66impl Cli {
67 pub fn into_converter(self) -> Result<DynamicConverter, Report<Error>> {
69 let mut direction = self.direction.try_into()?;
70
71 let input: Box<dyn Read> = if let Some(input_file) = self.input {
72 if direction == ConversionDirection::Auto && input_file.ends_with(".json") {
74 direction = ConversionDirection::Json2MsgPack;
75 }
76
77 let file = File::open(input_file).change_context(Error::FileRead)?;
78 Box::new(BufReader::new(file))
79 } else {
80 Box::new(std::io::stdin().lock())
81 };
82
83 let output: Box<dyn Write> = if let Some(output_file) = self.output {
84 let file = File::create(output_file).change_context(Error::FileWrite)?;
85 Box::new(BufWriter::new(file))
86 } else {
87 Box::new(std::io::stdout().lock())
88 };
89
90 Ok(Converter::new(input, output, direction))
91 }
92}
93
94#[cfg(test)]
95mod tests {
96 use clap::CommandFactory;
97
98 use super::Cli;
99
100 #[test]
101 fn cli() {
102 Cli::command().debug_assert();
103 }
104}