use anyhow::Result;
use clap::{Args, Parser};
use crate::cli::drive::{format::OutputFormat, helpers};
use crate::drive::client::DriveClient;
use crate::drive::docs::{
client::DocsClient,
named_range::{Mutation, Scope},
write::{WriteOptions, WritePayload},
};
#[derive(Args)]
pub struct ScopedArgs {
pub document_id: String,
#[arg(long)]
pub tab: String,
#[arg(long)]
pub segment: String,
#[arg(long)]
pub dry_run: bool,
#[command(flatten)]
pub lease: helpers::LeaseTokenArg,
#[arg(short = 'o', long, value_enum, default_value_t = OutputFormat::Table)]
pub output: OutputFormat,
}
impl ScopedArgs {
fn scope(&self) -> Scope {
Scope {
tab_id: (self.tab != "legacy").then(|| self.tab.clone()),
segment_id: (self.segment != "body").then(|| self.segment.clone()),
}
}
async fn execute(self, client: &DriveClient, mutation: Mutation) -> Result<()> {
let opts = WriteOptions {
payload: WritePayload::NamedRange {
scope: self.scope(),
mutation,
},
document_id: self.document_id,
dry_run: self.dry_run,
lease_token: self.lease.lease,
ledger_path: helpers::resolve_ledger_path(self.dry_run)?,
};
super::write::run_write(
client,
&DocsClient::from_drive_client(client)?,
&opts,
&helpers::active_account_rules()?,
&self.output,
)
.await
}
}
#[derive(Parser)]
pub struct CreateNamedRangeCommand {
#[command(flatten)]
pub scope: ScopedArgs,
#[arg(long)]
pub name: String,
#[arg(long)]
pub start_index: i64,
#[arg(long)]
pub end_index: i64,
}
impl CreateNamedRangeCommand {
pub async fn execute(self, client: &DriveClient) -> Result<()> {
self.scope
.execute(
client,
Mutation::Create {
name: self.name,
start_index: self.start_index,
end_index: self.end_index,
},
)
.await
}
}
#[derive(Parser)]
pub struct DeleteNamedRangeCommand {
#[command(flatten)]
pub scope: ScopedArgs,
#[arg(long)]
pub id: String,
}
impl DeleteNamedRangeCommand {
pub async fn execute(self, client: &DriveClient) -> Result<()> {
self.scope
.execute(client, Mutation::Delete { id: self.id })
.await
}
}
#[derive(Parser)]
pub struct ReplaceNamedRangeContentCommand {
#[command(flatten)]
pub scope: ScopedArgs,
#[arg(long)]
pub id: String,
#[arg(
long,
conflicts_with = "text_file",
required_unless_present = "text_file"
)]
pub text: Option<String>,
#[arg(long, value_name = "PATH")]
pub text_file: Option<String>,
}
impl ReplaceNamedRangeContentCommand {
pub async fn execute(self, client: &DriveClient) -> Result<()> {
let text = match (self.text, self.text_file.as_deref()) {
(Some(text), None) => text,
(None, Some(source)) => super::write::read_text(source)?,
_ => anyhow::bail!("exactly one of --text or --text-file is required"),
};
self.scope
.execute(client, Mutation::Replace { id: self.id, text })
.await
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
fn scoped(tab: &str, segment: &str) -> ScopedArgs {
ScopedArgs {
document_id: "d".into(),
tab: tab.into(),
segment: segment.into(),
dry_run: false,
lease: helpers::LeaseTokenArg { lease: None },
output: OutputFormat::Table,
}
}
#[test]
fn legacy_and_body_name_the_absent_tab_and_segment() {
for (tab, segment, expected) in [
("legacy", "body", (None, None)),
("t.1", "body", (Some("t.1"), None)),
("legacy", "kix.h1", (None, Some("kix.h1"))),
("t.1", "kix.h1", (Some("t.1"), Some("kix.h1"))),
("Legacy", "Body", (Some("Legacy"), Some("Body"))),
] {
let scope = scoped(tab, segment).scope();
assert_eq!(
(scope.tab_id.as_deref(), scope.segment_id.as_deref()),
expected,
"{tab}/{segment}"
);
}
}
#[test]
fn scope_and_stable_id_are_required_and_text_sources_are_exclusive() {
let base = [
"replace-named-range-content",
"d",
"--tab",
"t",
"--segment",
"body",
"--id",
"nr",
];
assert!(ReplaceNamedRangeContentCommand::try_parse_from(base).is_err());
assert!(ReplaceNamedRangeContentCommand::try_parse_from(
base.into_iter().chain(["--text", ""])
)
.is_ok());
assert!(ReplaceNamedRangeContentCommand::try_parse_from(
base.into_iter().chain(["--text-file", "f"])
)
.is_ok());
assert!(
ReplaceNamedRangeContentCommand::try_parse_from(base.into_iter().chain([
"--text",
"x",
"--text-file",
"f"
]))
.is_err()
);
assert!(
DeleteNamedRangeCommand::try_parse_from(["delete-named-range", "d", "--id", "nr"])
.is_err()
);
assert!(DeleteNamedRangeCommand::try_parse_from([
"delete-named-range",
"d",
"--tab",
"t",
"--segment",
"body",
"--name",
"label"
])
.is_err());
assert!(CreateNamedRangeCommand::try_parse_from([
"create-named-range",
"d",
"--tab",
"t",
"--segment",
"body",
"--name",
"label",
"--start-index",
"1",
"--end-index",
"3"
])
.is_ok());
}
}