feat: use clap subcommand

This commit is contained in:
2021-06-02 00:08:28 +08:00
parent 2d6c0a6291
commit 86eefdb1f9
5 changed files with 144 additions and 90 deletions

View File

@@ -1,42 +0,0 @@
use clap::{ArgMatches, App, Arg};
pub enum PlistFormat {
Xml,
Binary,
}
pub struct ParsedArgs {
pub in_file: String,
pub format: PlistFormat,
pub out_file: Option<String>,
pub search: Option<String>,
}
pub fn parse_args(matches: ArgMatches<'static>) -> ParsedArgs {
ParsedArgs {
in_file: matches.value_of("FILE").unwrap().to_string(),
format: match matches.value_of("format") {
Some("xml") => PlistFormat::Xml,
Some("bin") | Some("binary") => PlistFormat::Binary,
_ => failure_and_exit!("Plist format error."),
},
out_file: matches.value_of("out").map(ToString::to_string),
search: matches.value_of("search").map(ToString::to_string),
}
}
pub fn get_args_matches() -> ArgMatches<'static> {
App::new(env!("CARGO_PKG_NAME"))
.version(env!("CARGO_PKG_VERSION"))
.author(env!("CARGO_PKG_AUTHORS"))
.about(env!("CARGO_PKG_DESCRIPTION"))
.arg(Arg::with_name("format").short("f").long("format").takes_value(true)
.default_value("xml")
.possible_values(&["xml", "binary", "bin"])
.help("Output plist format")
)
.arg(Arg::with_name("search").long("search").takes_value(true).help("Search plist value"))
.arg(Arg::with_name("out").short("o").long("out").takes_value(true).help("Output plist file"))
.arg(Arg::with_name("FILE").required(true).index(1).help("Input plist file name"))
.get_matches()
}

10
src/cmd.rs Normal file
View File

@@ -0,0 +1,10 @@
use clap::{ArgMatches, App};
use rust_util::XResult;
pub type CommandError = XResult<()>;
pub trait Command {
fn name(&self) -> &str;
fn subcommand<'a>(&self) -> App<'a, 'a>;
fn run(&self, arg_matches: &ArgMatches, _: &ArgMatches) -> CommandError;
}

32
src/cmd_find.rs Normal file
View File

@@ -0,0 +1,32 @@
use clap::{ArgMatches, SubCommand, App, Arg};
use crate::cmd::{Command, CommandError};
use crate::find;
use plist::Value;
pub struct CommandImpl;
impl Command for CommandImpl {
fn name(&self) -> &str { "find" }
fn subcommand<'a>(&self) -> App<'a, 'a> {
SubCommand::with_name(self.name()).about("Find subcommand")
.arg(Arg::with_name("search").long("search").takes_value(true).required(true).help("Search plist value"))
.arg(Arg::with_name("FILE").required(true).index(1).help("Input plist file name"))
}
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
let search = sub_arg_matches.value_of("search").unwrap();
let in_file = sub_arg_matches.value_of("FILE").unwrap();
// read plist file
let plist_value = Value::from_file(in_file).unwrap_or_else(|e| {
failure_and_exit!("Read plist file: {}, failed: {}", in_file, e);
});
let matched = find::find(&plist_value, &search);
for (k, v) in &matched {
information!("Matched: {} -> {}", k, v);
}
success!("Found {} values(s)", matched.len());
Ok(())
}
}

82
src/cmd_plist_default.rs Normal file
View File

@@ -0,0 +1,82 @@
use std::fs::File;
use clap::{App, Arg, ArgMatches};
use plist::Value;
use crate::cmd::CommandError;
use crate::utils::print_bytes;
pub enum PlistFormat {
Xml,
Binary,
}
pub struct ParsedArgs {
pub in_file: String,
pub format: PlistFormat,
pub out_file: Option<String>,
}
pub struct CommandImpl;
impl CommandImpl {
pub fn process_command<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
app.arg(Arg::with_name("format").short("f").long("format").takes_value(true)
.default_value("xml")
.possible_values(&["xml", "binary", "bin"])
.help("Output plist format")
)
.arg(Arg::with_name("out").short("o").long("out").takes_value(true).help("Output plist file"))
.arg(Arg::with_name("FILE").required(true).index(1).help("Input plist file name"))
}
pub fn run(matches: &ArgMatches) -> CommandError {
let parsed_args = parse_args(matches);
// read plist file
let plist_value = Value::from_file(&parsed_args.in_file).unwrap_or_else(|e| {
failure_and_exit!("Read plist file: {}, failed: {}", parsed_args.in_file, e);
});
// write plist to file or stdout
if let Some(out_file) = parsed_args.out_file {
if File::open(&out_file).is_ok() {
failure_and_exit!("Output file exits: {}", out_file);
}
match parsed_args.format {
PlistFormat::Xml => if let Err(e) = plist_value.to_file_xml(&out_file) {
failure_and_exit!("Write xml plist file failed: {}", e);
} else {
success!("Write xml plist file: {} success", out_file);
}
PlistFormat::Binary => if let Err(e) = plist_value.to_file_binary(&out_file) {
failure_and_exit!("Write binary plist file failed: {}", e);
} else {
success!("Write binary plist file: {} success", out_file);
}
}
} else {
let mut buf = vec![];
match parsed_args.format {
PlistFormat::Xml => if let Err(e) = plist_value.to_writer_xml(&mut buf) {
failure_and_exit!("Write xml plist failed: {}", e);
}
PlistFormat::Binary => if let Err(e) = plist_value.to_writer_binary(&mut buf) {
failure_and_exit!("Write binary plist failed: {}", e);
}
}
print_bytes(&buf);
}
Ok(())
}
}
fn parse_args(matches: &ArgMatches) -> ParsedArgs {
ParsedArgs {
in_file: matches.value_of("FILE").unwrap().to_string(),
format: match matches.value_of("format") {
Some("xml") => PlistFormat::Xml,
Some("bin") | Some("binary") => PlistFormat::Binary,
_ => failure_and_exit!("Plist format error."),
},
out_file: matches.value_of("out").map(ToString::to_string),
}
}

View File

@@ -1,60 +1,32 @@
#[macro_use]
extern crate rust_util;
mod cmd;
mod utils;
mod args;
mod find;
mod cmd_plist_default;
mod cmd_find;
use std::fs::File;
use plist::Value;
use crate::utils::print_bytes;
use crate::args::PlistFormat;
use clap::App;
use cmd::{Command, CommandError};
fn main() {
let matches = args::get_args_matches();
let parsed_args = args::parse_args(matches);
fn main() -> CommandError {
let commands: Vec<Box<dyn Command>> = vec![
Box::new(cmd_find::CommandImpl)
];
let mut app = App::new(env!("CARGO_PKG_NAME"))
.version(env!("CARGO_PKG_VERSION"))
.about(env!("CARGO_PKG_DESCRIPTION"));
// read plist file
let plist_value = Value::from_file(&parsed_args.in_file).unwrap_or_else(|e| {
failure_and_exit!("Read plist file: {}, failed: {}", parsed_args.in_file, e);
});
if let Some(search) = parsed_args.search {
let matched = find::find(&plist_value, &search);
for (k, v) in &matched {
information!("Matched: {} -> {}", k, v);
}
success!("Found {} values(s)", matched.len());
return;
app = cmd_plist_default::CommandImpl::process_command(app);
for command in &commands {
app = app.subcommand(command.subcommand());
}
// write plist to file or stdout
if let Some(out_file) = parsed_args.out_file {
if File::open(&out_file).is_ok() {
failure_and_exit!("Output file exits: {}", out_file);
let matches = app.get_matches();
for command in &commands {
if let Some(sub_cmd_matches) = matches.subcommand_matches(command.name()) {
return command.run(&matches, sub_cmd_matches);
}
match parsed_args.format {
PlistFormat::Xml => if let Err(e) = plist_value.to_file_xml(&out_file) {
failure_and_exit!("Write xml plist file failed: {}", e);
} else {
success!("Write xml plist file: {} success", out_file);
}
PlistFormat::Binary => if let Err(e) = plist_value.to_file_binary(&out_file) {
failure_and_exit!("Write binary plist file failed: {}", e);
} else {
success!("Write binary plist file: {} success", out_file);
}
}
} else {
let mut buf = vec![];
match parsed_args.format {
PlistFormat::Xml => if let Err(e) = plist_value.to_writer_xml(&mut buf) {
failure_and_exit!("Write xml plist failed: {}", e);
}
PlistFormat::Binary => if let Err(e) = plist_value.to_writer_binary(&mut buf) {
failure_and_exit!("Write binary plist failed: {}", e);
}
}
print_bytes(&buf);
}
cmd_plist_default::CommandImpl::run(&matches)
}