use crate::cli::drive::format::OutputFormat;
use crate::cli::drive::helpers;
use crate::drive::client::DriveClient;
use crate::drive::docs::client::DocsClient;
use crate::drive::docs::style::{Alignment, NamedStyle, ParagraphStyle, StylePatch, TextStyle};
use crate::drive::docs::write::{WriteOptions, WritePayload};
use anyhow::Result;
use clap::{ArgGroup, Args, Parser};
#[derive(Args)]
#[command(group(ArgGroup::new("selection").required(true).args(["match_text", "from"])))]
pub struct Selection {
pub document_id: String,
#[arg(long = "match", conflicts_with = "to")]
pub match_text: Option<String>,
#[arg(long, requires = "to")]
pub from: Option<String>,
#[arg(long, requires = "from")]
pub to: Option<String>,
#[arg(long)]
pub ignore_case: bool,
#[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,
}
#[derive(Parser)]
#[command(group(ArgGroup::new("style").required(true).multiple(true).args(["bold", "italic", "underline", "strikethrough"])))]
pub struct TextStyleCommand {
#[command(flatten)]
pub selection: Selection,
#[arg(long, action = clap::ArgAction::Set)]
pub bold: Option<bool>,
#[arg(long, action = clap::ArgAction::Set)]
pub italic: Option<bool>,
#[arg(long, action = clap::ArgAction::Set)]
pub underline: Option<bool>,
#[arg(long, action = clap::ArgAction::Set)]
pub strikethrough: Option<bool>,
}
#[derive(Parser)]
#[command(group(ArgGroup::new("style").required(true).multiple(true).args(["alignment", "named_style"])))]
pub struct ParagraphStyleCommand {
#[command(flatten)]
pub selection: Selection,
#[arg(long, value_enum)]
pub alignment: Option<Alignment>,
#[arg(long, value_enum)]
pub named_style: Option<NamedStyle>,
}
impl TextStyleCommand {
pub async fn execute(self, client: &DriveClient) -> Result<()> {
self.selection
.execute(
client,
StylePatch::Text(TextStyle {
bold: self.bold,
italic: self.italic,
underline: self.underline,
strikethrough: self.strikethrough,
}),
)
.await
}
}
impl ParagraphStyleCommand {
pub async fn execute(self, client: &DriveClient) -> Result<()> {
self.selection
.execute(
client,
StylePatch::Paragraph(ParagraphStyle {
alignment: self.alignment,
named_style_type: self.named_style,
}),
)
.await
}
}
impl Selection {
async fn execute(self, client: &DriveClient, style: StylePatch) -> Result<()> {
let docs = DocsClient::from_drive_client(client)?;
let (from, to) = match (self.match_text, self.from, self.to) {
(Some(text), None, None) => (text, None),
(None, Some(from), Some(to)) => (from, Some(to)),
_ => anyhow::bail!("use --match or both --from and --to"),
};
let opts = WriteOptions {
document_id: self.document_id,
payload: WritePayload::Format {
from,
to,
match_case: !self.ignore_case,
style,
},
dry_run: self.dry_run,
lease_token: self.lease.lease,
ledger_path: helpers::resolve_ledger_path(self.dry_run)?,
};
super::write::run_write(
client,
&docs,
&opts,
&helpers::active_account_rules()?,
&self.output,
)
.await
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
#[test]
fn formatting_requires_one_selection_and_explicit_style_values() {
for args in [
vec!["text-style", "d", "--match", "x"],
vec!["text-style", "d", "--bold", "true"],
vec!["text-style", "d", "--from", "x", "--bold", "true"],
vec![
"text-style",
"d",
"--match",
"x",
"--from",
"y",
"--to",
"z",
"--bold",
"true",
],
vec!["text-style", "d", "--match", "x", "--bold"],
] {
assert!(TextStyleCommand::try_parse_from(args).is_err());
}
let cmd = TextStyleCommand::try_parse_from([
"text-style",
"d",
"--match",
"x",
"--bold",
"false",
"--italic",
"true",
])
.unwrap();
assert_eq!(cmd.bold, Some(false));
assert_eq!(cmd.italic, Some(true));
assert!(!cmd.selection.ignore_case);
assert!(ParagraphStyleCommand::try_parse_from([
"paragraph-style",
"d",
"--match",
"x",
"--alignment",
"bogus"
])
.is_err());
assert!(ParagraphStyleCommand::try_parse_from([
"paragraph-style",
"d",
"--from",
"x",
"--to",
"y",
"--named-style",
"heading1",
"--alignment",
"center"
])
.is_ok());
}
}