use std::process::ExitCode;
use clap::Parser;
use sharepoint_cli::cli::{self, Cli};
use sharepoint_cli::error::{CliError, exit_codes};
use sharepoint_cli::output::{OutputConfig, OutputFormat};
fn rewrite_argv(args: impl Iterator<Item = std::ffi::OsString>) -> Vec<std::ffi::OsString> {
let args: Vec<std::ffi::OsString> = args.collect();
let files_pos = args.iter().position(|a| a == "files");
let download_pos = files_pos.and_then(|fp| {
args[fp + 1..]
.iter()
.position(|a| a == "download")
.map(|rel| fp + 1 + rel)
});
let Some(dl_pos) = download_pos else {
return args;
};
let mut result: Vec<std::ffi::OsString> = Vec::with_capacity(args.len());
result.extend_from_slice(&args[..=dl_pos]);
let mut past_separator = false;
let tail = &args[dl_pos + 1..];
let mut i = 0;
while i < tail.len() {
let arg = &tail[i];
if past_separator {
result.push(arg.clone());
i += 1;
continue;
}
if arg == "--" {
past_separator = true;
result.push(arg.clone());
i += 1;
continue;
}
if arg == "--output" {
result.push("--path".into());
i += 1;
continue;
}
if let Some(val) = arg.to_str().and_then(|s| s.strip_prefix("--output=")) {
result.push(format!("--path={val}").into());
i += 1;
continue;
}
if arg == "-o" {
result.push("--path".into());
i += 1;
continue;
}
if let Some(val) = arg
.to_str()
.and_then(|s| s.strip_prefix("-o"))
.filter(|val| !val.is_empty())
{
result.push(format!("--path={val}").into());
i += 1;
continue;
}
result.push(arg.clone());
i += 1;
}
result
}
#[tokio::main]
async fn main() -> ExitCode {
let cli = match Cli::try_parse_from(rewrite_argv(std::env::args_os())) {
Ok(c) => c,
Err(e) => {
if matches!(
e.kind(),
clap::error::ErrorKind::DisplayHelp
| clap::error::ErrorKind::DisplayVersion
| clap::error::ErrorKind::DisplayHelpOnMissingArgumentOrSubcommand
) {
e.print().ok();
let code: u8 = if e.use_stderr() { 2 } else { 0 };
return ExitCode::from(code);
}
let out = OutputConfig::new(OutputFormat::Auto, false);
let msg = e.to_string();
let first_line = msg
.lines()
.next()
.unwrap_or("invalid arguments")
.trim_start_matches("error: ")
.to_string();
let exit = out.render_error(&CliError::Input(first_line));
return ExitCode::from(exit as u8);
}
};
let out = OutputConfig::new(cli.output, cli.quiet);
match cli::run(cli).await {
Ok(()) => ExitCode::from(exit_codes::SUCCESS as u8),
Err(err) => ExitCode::from(out.render_error(&err) as u8),
}
}
#[cfg(test)]
mod tests {
use super::rewrite_argv;
fn argv(args: &[&str]) -> Vec<std::ffi::OsString> {
args.iter().map(|s| s.into()).collect()
}
fn rewritten(args: &[&str]) -> Vec<String> {
rewrite_argv(argv(args).into_iter())
.into_iter()
.map(|a| a.into_string().unwrap())
.collect()
}
#[test]
fn download_long_flag_separate_value() {
let got = rewritten(&["sp", "files", "download", "REF", "--output", "-"]);
assert_eq!(got, ["sp", "files", "download", "REF", "--path", "-"]);
}
#[test]
fn download_long_flag_equals_value() {
let got = rewritten(&["sp", "files", "download", "REF", "--output=-"]);
assert_eq!(got, ["sp", "files", "download", "REF", "--path=-"]);
}
#[test]
fn download_short_flag_separate_value() {
let got = rewritten(&["sp", "files", "download", "REF", "-o", "-"]);
assert_eq!(got, ["sp", "files", "download", "REF", "--path", "-"]);
}
#[test]
fn download_short_flag_attached_value() {
let got = rewritten(&["sp", "files", "download", "REF", "-o-"]);
assert_eq!(got, ["sp", "files", "download", "REF", "--path=-"]);
}
#[test]
fn download_short_flag_attached_path() {
let got = rewritten(&["sp", "files", "download", "REF", "-o/tmp/out.docx"]);
assert_eq!(
got,
["sp", "files", "download", "REF", "--path=/tmp/out.docx"]
);
}
#[test]
fn download_long_flag_equals_real_path() {
let got = rewritten(&["sp", "files", "download", "REF", "--output=/tmp/file.xlsx"]);
assert_eq!(
got,
["sp", "files", "download", "REF", "--path=/tmp/file.xlsx"]
);
}
#[test]
fn download_after_separator_not_rewritten() {
let got = rewritten(&[
"sp",
"files",
"download",
"REF",
"--",
"--output",
"something",
]);
assert_eq!(
got,
[
"sp",
"files",
"download",
"REF",
"--",
"--output",
"something"
]
);
}
#[test]
fn download_before_separator_rewritten_after_not() {
let got = rewritten(&[
"sp", "files", "download", "REF", "--output", "/a", "--", "--output", "/b",
]);
assert_eq!(
got,
[
"sp", "files", "download", "REF", "--path", "/a", "--", "--output", "/b"
]
);
}
#[test]
fn files_stat_short_flag_not_rewritten() {
let got = rewritten(&["sp", "files", "stat", "REF", "-o", "json"]);
assert_eq!(got, ["sp", "files", "stat", "REF", "-o", "json"]);
}
#[test]
fn files_ls_output_not_rewritten() {
let got = rewritten(&["sp", "files", "ls", "REF", "--output", "text"]);
assert_eq!(got, ["sp", "files", "ls", "REF", "--output", "text"]);
}
#[test]
fn global_output_before_subcommand_not_rewritten() {
let got = rewritten(&["sp", "--output", "json", "files", "stat", "REF"]);
assert_eq!(got, ["sp", "--output", "json", "files", "stat", "REF"]);
}
#[test]
fn global_output_before_files_download_not_rewritten() {
let got = rewritten(&[
"sp", "--output", "json", "files", "download", "REF", "--output", "-",
]);
assert_eq!(
got,
[
"sp", "--output", "json", "files", "download", "REF", "--path", "-"
]
);
}
#[test]
fn auth_status_not_rewritten() {
let got = rewritten(&["sp", "auth", "status", "-o", "json"]);
assert_eq!(got, ["sp", "auth", "status", "-o", "json"]);
}
#[test]
fn no_files_subcommand_not_rewritten() {
let got = rewritten(&["sp", "sites", "list", "--output", "json"]);
assert_eq!(got, ["sp", "sites", "list", "--output", "json"]);
}
#[test]
fn overwrite_flag_after_download_not_rewritten() {
let got = rewritten(&[
"sp",
"files",
"download",
"REF",
"--output",
"/tmp/f",
"--overwrite",
]);
assert_eq!(
got,
[
"sp",
"files",
"download",
"REF",
"--path",
"/tmp/f",
"--overwrite"
]
);
}
}